Какова декомпилированная (C) кодовая конструкция этого кода сборки x86?

#c #assembly #x86 #reverse-engineering #decompiling

# #c #сборка #x86 #обратный инжиниринг #декомпиляция

Вопрос:

Этот код сравнивает каждый символ строки (расположенный по адресу ebp arg_0 ) с различными константами (символами ASCII), такими как ‘I’, ‘o’ и ‘S’. Я предполагаю, основываясь на других частях кода, что этот код изначально написан на C.

сборка

Эта часть сравнения кода выглядит очень неэффективной. Мой вопрос: как, по-вашему, этот код будет выглядеть на языке Си? Какая конструкция кода использовалась изначально? мои мысли до сих пор

  • Это не цикл for. Потому что я не вижу никакого скачка вверх и состояния остановки.
  • Это не конструкция кода while / case / switch
  • Мое лучшее предположение заключается в том, что это множество последовательных операторов if / else. Вы можете мне помочь?

Да, это часть задачи, у меня уже есть флаг / решение, не беспокойтесь об этом. Просто пытаюсь лучше понять код.

Комментарии:

1. Если это какой-то крэк, то он не должен быть эффективным. Весь смысл crackmes в том, чтобы сбить исследователя с толку.

2. Вы можете использовать временную атаку против этого, чтобы найти правильный пароль

Ответ №1:

Это не цикл for . Потому что я не вижу никакого перехода вверх и условия остановки.

Правильно.

Это не while / case / switch code constuct

Этого не может быть, он сравнивает разные показатели массива.

Мое лучшее предположение заключается в том, что это много последовательных if / elses . Вы можете помочь?

Похоже, это может быть этот код:

 void f(const char* arg_0) {
    if(arg_0[4] == 'I' amp;amp; arg_0[5] == 'o' amp;amp; arg_0[6] == 'S') {
        printf("Gratz man :)");
        exit(0); //noreturn, hence your control flow ends here in the assembly
    }
    puts("Wrong password"); // Or `printf("Wrong passwordn");` which gets optimized to `puts`
    // leave, retn
}
 

Вот как gcc компилирует его без оптимизации:

 .LC0:
        .string "Gratz man :)"
.LC1:
        .string "Wrong password"
f(char const*):
        push    ebp
        mov     ebp, esp
        sub     esp, 8
        mov     eax, DWORD PTR [ebp 8]
        add     eax, 4
        movzx   eax, BYTE PTR [eax]
        cmp     al, 73
        jne     .L2
        mov     eax, DWORD PTR [ebp 8]
        add     eax, 5
        movzx   eax, BYTE PTR [eax]
        cmp     al, 111
        jne     .L2
        mov     eax, DWORD PTR [ebp 8]
        add     eax, 6
        movzx   eax, BYTE PTR [eax]
        cmp     al, 83
        jne     .L2
        sub     esp, 12
        push    OFFSET FLAT:.LC0
        call    printf
        add     esp, 16
        sub     esp, 12
        push    0
        call    exit
.L2:
        sub     esp, 12
        push    OFFSET FLAT:.LC1
        call    puts
        add     esp, 16
        nop
        leave
        ret
 

Выглядит очень похоже на ваш дизассемблированный код.

Эта часть compare-code выглядит очень неэффективной

Похоже, он был скомпилирован без оптимизации. С включенной оптимизацией gcc скомпилировал код для:

 .LC0:
        .string "Gratz man :)"
.LC1:
        .string "Wrong password"
f(char const*):
        sub     esp, 12
        mov     eax, DWORD PTR [esp 16]
        cmp     BYTE PTR [eax 4], 73
        jne     .L2
        cmp     BYTE PTR [eax 5], 111
        je      .L5
.L2:
        mov     DWORD PTR [esp 16], OFFSET FLAT:.LC1
        add     esp, 12
        jmp     puts
.L5:
        cmp     BYTE PTR [eax 6], 83
        jne     .L2
        sub     esp, 12
        push    OFFSET FLAT:.LC0
        call    printf
        mov     DWORD PTR [esp], 0
        call    exit
 

Не уверен, почему gcc решил перейти вниз и снова выполнить резервное копирование вместо прямой строки jne s. Кроме того, ret is gone, ваш printf оптимизированный хвостовой вызов, т. Е. a jmp printf является началом a call printf , за которым следует a ret .

Комментарии:

1. Вау, это действительно очень близко! Спасибо за вашу поддержку!

2. puts добавляет новую строку, printf не делает. (И gcc не ищет оптимизацию использования fputs(str, stdout) для постоянных строк, которые не заканчиваются новой строкой.) В любом случае, asm использует puts, было бы проще всего использовать puts. В любом случае, отсутствует "n" причина, по которой ваша версия gcc не оптимизируется для puts.

3. @PeterCordes Спасибо, я отредактировал его, чтобы использовать puts. Я не понял puts , добавляет новую строку.

4. Я, вероятно, не запомнил бы это puts так легко, если бы не посмотрел на вывод asm компилятора и не узнал об этой оптимизации: P Современный gcc может даже обрабатывать printf("%sn", "string") , даже для случаев, когда "string" это переменная с постоянным значением времени компиляции после постоянного распространения. Но я действительно puts иногда печатаю (особенно в коде toy / experiment), потому что у него более короткое имя, чем printf, потому что он добавляет n, который мне не нужно вводить, и потому что он невосприимчив к уязвимостям строки формата для вывода строки переменной среды выполнения.

Ответ №2:

Первый аргумент ( arg_0 ) является указателем на заданную строку пароля, например, const char *arg_0 . Этот указатель (адрес первого символа) загружается в eax регистр ( mov eax, [ebp arg_0] ), а затем добавляется индекс текущего символа, чтобы продвинуть его до этого индекса ( add eax, 4 и т.д.). Затем один байт по этому адресу загружается в eax ( movzx eax, byte ptr [eax] ) .

Затем этот байт / символ сравнивается с правильным ( cmp eax, 'I' и т.д.) . Если результат не равен нулю (т. Е., Если они не равны), программа переходит к ветви «Неправильный пароль» ( jnz — перейти, если не ноль), в противном случае он переходит к следующему сравнению (и, в конечном счете, успех).

Следовательно, ближайший эквивалент прямого C будет чем-то вроде:

 void check(const char *arg_0) {
    // presumably comparisons 0-3 are omitted
    if (arg_0[4] != 'I') goto fail;
    if (arg_0[5] != 'o') goto fail;
    if (arg_0[6] != 'S') goto fail;
    printf("Gratz man :)");
    exit(0);
fail:
    puts("Wrong password");
}
 

(Конечно, фактический код C вряд ли выглядел бы так, поскольку goto fail расположение не является типичным.)

Комментарии:

1. Спасибо! Когда я декомпилирую это, код сборки выглядит почти идентично.