3

I'm writing a script to solve a binary that asks for multiple strings like the following c code,

My problem I can't dump the 'second_pass, I just get "first_pass". probably due of the use of the same variable to read input from. any help please

#include <stdio.h>

int main()
{
    int i, lose = 1;
    char first[20]="first_pass", input[20];
    scanf("%s", input);
    for(i=0; i < strlen(first); i++)
    {
        if(first[i] != input[i])
            lose = 0;
    }

    if(lose)
        printf("gg");
    else
        printf("fail");


    char second[20]="second_pass";
    scanf("%s", input);
    for(i=0; i < strlen(second); i++)
    {
        //printf("%c = %c\n", second[i], input[i]);
        if(second[i] != input[i])
            lose = 0;
    }

    printf("\n");
    if(lose)
        printf("gg");
    else
        printf("fail");
}

my angr script

import angr
import logging



start_at = 0x400550

fail = (0x400681,0x400751)
success = (0x400680, 0x400740)




p = angr.Project("./a.out", load_options={'auto_load_libs': False})


state = p.factory.blank_state(addr=addr_main)

path = p.factory.path(state)
pathgroup = p.factory.path_group(path)
print "Searching..."
found_state = pathgroup.explore(find=success, avoid=fail)

if found_state.found:
    f = found_state.found[0].state

    print "Flag: %s" % (f.posix.dumps(0)[0:20],)

1 Answer 1

1

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 printf("fail"); statements.

First of all, I believe your program has two unintentional considerable bugs in it, preventing the intended solutions from being needed:

  1. 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 lose to 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 ggs.
  2. 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):

When launching .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 explorer's 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.

Increasing 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 < to <= 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 find and 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 find and 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).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.