Нужно пройти все проверки в файле, чтобы получить флаг.
Solution
Посмотрим файл.
Info:
File name: /spbctf_rev/task1/task1.out
Size: 7396
File type: ELF32
String: ELF(386)
Extension: elf
Operation system: Linux(ABI: 2.6.32)
Architecture: 386
Mode: 32-bit
Type: EXEC
Endianness: LE
Закину его в тулу. А вот и наш main:
int32_t main(int32_t argc, char** argv, char** envp) {
void* const __return_addr_1 = __return_addr;
int32_t* var_10 = &argc;
if (argc <= 1)
{
puts("What?");
return 1;
}
if ((strlen(argv[1]) & 3) != 0)
{
puts("Ok, you are wrong");
return 2;
}
if (strcmp(argv[1], "FLAG{123REALFLAG!!!}") == 0)
{
puts("Not enough!");
return 3;
}
int32_t eax_14 = hashf(argv[1]);
if (eax_14 == hashf("FLAG{123REALFLAG!!!}"))
{
int32_t str;
__builtin_memcpy(&str, "\xe3\xc7\xed\x8f\xde\xdf\xe4\x81\xf6\xd4\xe5\x9b\xfa\xc6\xf5\x97\xee\xce\xf5\xb5", 0x14);
char var_1d_1 = 0;
decode(&str, eax_14);
puts(&str);
}
return 0;
}
Разберемся:
if (argc <= 1)
{
puts("What?");
return 1;
}
В argc находится количество переданных аргументов командной строки, значит нам нужно передать ключ именно так.
if ((strlen(argv[1]) & 3) != 0)
{
puts("Ok, you are wrong");
return 2;
}
Похоже на то, что длина ключа не должна быть кратна 4.
Но это все потом. Я нашел несколько вариантов решения данной задачи. Итак, есть 3 пути. В каждом есть свои плюсы и минусы. Вперед)
Коллизионная атака
Это самый очевидный способ, нужно немножечко понимать за крипту) Что нам нужно сделать? Разреверсить функцию hashf, чтобы совершить коллизионную атаку. Если же не выпендриваться и говорить проще, то нужно найти ключ, который будет отличаться от FLAG{123REALFLAG!!!}, но давать такой же результат.
Как выглядит функция hashf:
08048490 int32_t hashf(char* arg1)
08048490 {
0804849c int32_t eax = dwords_count(arg1);
080484a7 int32_t result = 0xcafedead;
080484b1 char* var_18 = arg1;
080484b1
080484d9 for (int32_t i = 0; i < eax; i += 1)
080484d9 {
080484c8 result ^= *(uint32_t*)var_18;
080484cb var_18 = &var_18[4];
080484d9 }
080484d9
080484df return result;
08048490 }
Мы можем тут увидеть, что для каждых 4 байтов arg1 вызывается XOR с переменной result. Итак, нам нужно получить такой же хеш, что и при вызове hashf("FLAG{123REALFLAG!!!}"). Как это сделать?
Первый вариант — брутфорс. Это долго и неинтересно.
Второй вариант — свойства XOR. Вы можете почитать о них в прошлом посте.
123 ^ 123 = 0
Это значит, если мы передадим в функцию 2 одинаковые группы по 4 байта, то наш хеш не изменится. Тогда возьмем нашу строку FLAG{123REALFLAG!!!} и добавим к ней 2 одинаковые группы: FLAG{123REALFLAG!!!}aaaaaaaa. Попробую запустить:
root@5626db1d1f64:/rev# ./task1.out 'FLAG{123REALFLAG!!!}aaaaaaaa'
FLAG{THIS_IS_MY_KEY}
Динамическая отладка
Данный способ требует некоторого опыта работы с gdb или любым другим отладчиком. Тут есть 2 варианта: перепрыгнуть все проверки до подсчета хеша (что вполне себе хороший вариант), или вызвать нужные функции отдельно.
Попробуем оба способа.
Перепрыгнем проверки
Что для этого нужно сделать? Первым делом нужно найти адрес программы для прыжка. Для этого идеально подойдет место, где вызывается hashf от переданного аргумента через аргументы командной строки. Вызов происходит по адресу 0x80485d6. Но не все так просто. Глянем на скрин ниже:

По данному адресу происходит именно вызов функции. А до этого нужно передать в нее аргументы) Передача аргументов начинается по адресу 0x80485ca.
Если разобрать asm, то происходит следующее:
- В
eaxпомещается указатель на началоargv, которые находятся на стеке; - В
argvхранятся указатели на строки. Так как у нас 32-х битный бинарный файл, то каждый указатель занимает4байта информации. С помощьюaddмы перемещаем наш указатель на элементargv[1]; - С помощью
sub esp, 0xcвыделяется нужное место на стеке для передачи аргументов; - Через
pushуказатель наargv[1]помещается на стек для передачи в функцию; - Происходит вызов функции
hashf;
Итак, что нам нужно сделать:
- Запустить бинарь через
gdb; - С помощью команды
set args 'FLAG{123REALFLAG!!!}'установить нужный аргумент командной строки; - Установить брекпоинт на функции
mainс помощьюb *main; - С помощью
set $eip = 0x80485caпереместить место выполнения программы.eip— это указатель на текущую инструкцию. Предварительно можно пару раз нажатьni, чтобы выполнить пролог функции:

- Продолжить выполнение программы с помощью команды
continue:
pwndbg> c
Continuing.
FLAG{THIS_IS_MY_KEY}
Флаг получен)
Вызов нужных функций
В отладчике мы можем вызвать функции с нужными аргументами. Давайте так и сделаем)
Вызовем функцию hashf с аргументом FLAG{123REALFLAG!!!}:
pwndbg> p (unsigned int)hashf("FLAG{123REALFLAG!!!}")
$1 = 3366751141
Переведем в hex для удобства:
3366751141 = 0xc8ac8ba5
Зачем это нужно? В функцию decode передается зашифрованный флаг и ключ. Ключом является хеш, который мы только что нашли.
Не особо удобно вызывать функцию, в которую передается указатель на строку, а сама строка меняется на месте. Разберем функцию:
080484e0 int32_t decode(char* arg1, int32_t arg2)
080484e0 {
080484e9 char* var_10 = arg1;
080484f2 int32_t eax_1 = dwords_count(arg1);
08048523 int32_t i;
08048523
08048523 for (i = 0; i < eax_1; i += 1)
08048523 {
08048513 *(uint32_t*)var_10 ^= arg2;
08048515 var_10 = &var_10[4];
08048523 }
08048523
08048527 return i;
080484e0 }
В ней используется XOR, но не самым простым способом, на следующей неделе я расскажу об этом подробнее (читай You either know, XOR you don't). Он совершается блоками по 4 байта. Напишем декодер на Python:
s = b'\xe3\xc7\xed\x8f\xde\xdf\xe4\x81\xf6\xd4\xe5\x9b\xfa\xc6\xf5\x97\xee\xce\xf5\xb5'
key = bytes.fromhex('c8ac8ba5')
key = key[::-1]
def xor(s: bytes, key: list[int]) -> bytes:
return bytes([s[i] ^ key[i % len(key)] for i in range(len(s))])
nums = []
print(xor(s, key).decode())
Важный момент, ключик нужно нам перевернуть)
cu63:task1/ $ python decode.py
FLAG{THIS_IS_MY_KEY}
Патчинг
Это тот вариант, который я выбрал первым) В чем суть: если мы сможем передать строку FLAG{123REALFLAG!!!} в качестве аргумента, то вот эта проверка всегда будет истиной, так как хеш-функции выдают одинаковый результат от одних и тех же данных:
int32_t eax_14 = hashf(argv[1]);
if (eax_14 == hashf("FLAG{123REALFLAG!!!}"))
Мешает же нам вот эта проверка:

Я пропатчил ее с помощью инструментов Binary Ninja: Rigth Click->Patch->Always Branch. Получилось вот так:

Это можно сделать разными путями: затереть проверку инструкцией nop, перепрыгнуть ее через jmp, изменить первый аргумент на рандомную строку и т.д. Сути это не меняет. Сохраню патченный файл и запущу его, передав аргумент FLAG{123REALFLAG!!!}.
root@5626db1d1f64:/rev# ./task1_patched.out 'FLAG{123REALFLAG!!!}'
FLAG{THIS_IS_MY_KEY}