Since OP didn't specify the addresses referred to by the hardcoded offsets, I'm gonna assume his intention. I'm assuming the values in
find match the two
printf("gg") statements and the two values in
avoid match the two
First of all, I believe your program has two unintentional considerable bugs in it, preventing the intended solutions from being needed:
- The first and more considerable of the two is the use of
lose in your code. It is sufficient to only correctly provide one of the passwords to permanently set
false. Doing so using the first password means
lose will be
false for both, effectively reaching the two success criteria you specified. Simply put, because
lose is not being reset to the default of
true after the first password validation, is is enough to provide the first password to get two
- This one does not really interfere with solving the challenge but is also probably a bug. We'll go over why it's an issue for angr in a few paragraphs but for now I'll just point out your string comparison is lacking any validation for the correct length, or that the input string is terminated when the hardcoded password is.
I find your example code quite interesting because those are two relatively common security vulnerabilities.
Ignoring the first logical bug above, I believe the issue is your configured success criteria; According to angr documentation (emphasis is mine):
.explore() with a
find argument, execution will run until a state is found that matches the find condition, which can be the address of an instruction to stop at, a list of addresses to stop at, or a function which takes a state and returns whether it meets some criteria.
When any of the states in the active stash match the
find condition, they are placed in the found stash, and execution terminates. You can then explore the found state, or decide to discard it and continue with the other ones. You can also specify an
avoid condition in the same format as
find. When a state matches the
avoid condition, it is put in the avoided stash, and execution continues. Finally, the
num_find argument controls the number of states that should be found before returning, with a default of
1. Of course, if you run out of states in the active stash before finding this many solutions, execution will stop anyway.
Providing a sequence of addresses in
find, like you did, means either one is an acceptable success condition. Because you did not provide any
num_find the default of
1 is assumed and
execute returns with the first success found.
Obviously, this is the success criteria for the first password. That is forced upon angr because the
avoid parameter you provided. If the
avoid condition is reached the current exploration path is terminated, preventing any additional exploration down that path. In your case, providing an
avoid of the failure of the first password means any paths failing the first password will be immediately discarded. Therefore, intentionally or not - thanks to your
avoid, no path is possible that does reach a success in the first password test.
There are several ways to properly solve this challenge and I'll go over them.
num_find to more than one result
One might think a proper solution could be to increase
num_find to more than one, allowing more than one
found states to be reached before
explorer returns. That, however, is a mistake. Because your example code does not makes sure passwords are actually identical strings because there's no length validation and the null terminator is not compared, it appears any string starting with
"first_pass" should be considered a valid solution. Interestingly enough, this accidently allows more than one solution (and in reality, quite a lot), so increasing
num_find to any computationally feasible value will yield additional solutions to the first password, never reaching the second one.
btw, to fix the erroneous password verification condition, you can either verify lengths match, use a string comparison function (such as
strcmp and derivatives), or change the conditional from
<= so it will include the null terminator.
separating the two tests
Since the two scenarios are completely independent of one another, you could run to executions. One for each password, setting a single
avoid value for each, as well as setting the entry point for the second one. Splitting the two executions will effectively create a more simple set of constraints which should be executing faster. Of course, you can even run them in parallel to gain an additional performance advantage.
correctly specifying the success criteria
Although your values of
avoid may initially seem correct, we've discussed several reasons why in reality they fail to accurately describe your intention. A more accurate specification of those parameters will be setting
avoid to both
printf("fail"); statements, as you did. However you'll need to remove the first
printf("gg") and only keep the second, as it won't be reached without passing both passwords (assuming the logical bug mentioned above is fixed).