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

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

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

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

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

  • Это не конструкция кода while/case/switch

  • Я думаю, что это множество последовательных операторов if/else. Вы можете помочь?

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

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

montonero 19.04.2019 11:37

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

Antti Haapala -- Слава Україні 19.04.2019 11:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
339
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

It's not a for loop. Because i don't see any upward jump and stop-condition.

Правильный.

It's not a while/case/switch code constuct

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

My best guess is that this are a lot of consecutive if/elses. Can you help?

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

void f(const char* arg_0) {
    if (arg_0[4] == 'I' && arg_0[5] == 'o' && 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 password\n");` 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

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

This compare-code-part looks very inefficient

Похоже, он был скомпилирован без оптимизаций. При включенной оптимизации 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 решил спрыгнуть вниз и снова вернуться вместо прямой линии jnes. Кроме того, ret исчез, ваш printf оптимизирован для хвостового вызова, то есть jmp printf вместо call printf, за которым следует ret.

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

bsdanm 19.04.2019 12:59
puts добавляет новую строку, printf нет. (И gcc не ищет оптимизацию использования fputs(str, stdout) для константных строк, которые не заканчиваются символом новой строки.) В любом случае, asm использует puts, было бы яснее просто использовать puts. В любом случае, отсутствие "\n" является причиной того, что ваша версия gcc не оптимизируется для размещения.
Peter Cordes 19.04.2019 18:34

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

tkausl 19.04.2019 18:57

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

Peter Cordes 19.04.2019 19:02

Первый аргумент (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 нетипично.)

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

bsdanm 19.04.2019 13:05

Другие вопросы по теме