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

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





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.
Вау, это действительно очень близко! Спасибо за вашу поддержку!
puts добавляет новую строку, printf нет. (И gcc не ищет оптимизацию использования fputs(str, stdout) для константных строк, которые не заканчиваются символом новой строки.) В любом случае, asm использует puts, было бы яснее просто использовать puts. В любом случае, отсутствие "\n" является причиной того, что ваша версия gcc не оптимизируется для размещения.
@PeterCordes Спасибо, я отредактировал его, чтобы использовать puts. Я не знал, что puts добавляет новую строку.
Я, вероятно, не вспомнил бы это о puts так легко, если бы не посмотрел на вывод asm компилятора и не узнал об этой оптимизации: P Современный gcc может даже обрабатывать printf("%s\n", "string"), даже для случаев, когда "string" является переменной с константой времени компиляции значение после постоянного распространения. Но на самом деле я иногда набираю делатьputs (особенно в коде игрушек/экспериментов), потому что у него более короткое имя, чем у printf, потому что он добавляет \n, который мне не нужно будет вводить, и потому что он невосприимчив к уязвимостям строки формата для вывод строки переменной времени выполнения.
Первый аргумент (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 нетипично.)
Спасибо! Когда я декомпилирую это, код сборки выглядит почти идентично.
Если это какой-то кряк, то он не должен быть эффективным. Весь смысл кряков в том, чтобы запутать исследователя.