Обходим защиту от дизассемблирования в Golang
tg: AFANX
канал: K0N70R4
GitHub: GitHub
Ку, киберпанки. Сегодня будет разбор на один интересный таск с CTF, который был на этих выходных. В таске встретите защиту от дизассемблирования и необходимость написания кейгена, а также реверс программы, которая написана на Golang. Сам таск отнесу в категорию easy.
Изучаем прогу
Закинем в DiE (Detect It Easy) и посмотрим стороки, энтрипоинт, обфусцирован ли он, на чём написал и чем скомпилен:
Наш энтрипоинт располагается по адресу 0xA9BB40
и этот бинарь написан на Golang, что не есть хорошо. Это плохо, потому что Go компилирует всё статически и оставляет очень много мусора. Теперь попробуем закинуть в IDA Pro и посмотреть, что под капотом. Во время трассировки, я думал, что у меня комп взлетит и понятно почему:
С каждой секундой он находил новую функцию и не мог быстро трассировать. Сразу стало ясно, что это защита от дизассемблирования.
Обходим защиту от дизассемблирования
IDA и GHIDRA не справились с задачей дизассемблирования, поэтому решил сделать это через objdump
:
objdump -A memes.exe -M intel
Как видите много одних и тех же инструкций, причем они все ссылаются друг на друга. Короче говоря, это такая защита от дизассемлирования. Последняя интсрукция указывает на ту, которая в начале, а самая первая на следующую. По DiE знаем, что энтрипоинт по адресу 0xa9bb40
, т.е.
a9bb40: e9 f6 ff ff ff jmp 0xa9bb3b
Заполнить всё это NOPами (0x90
) можно, только получим чисто дизассемблирования, а нам нужна еще отладка. Посмотрим чем всё это заканчивается:
Теперь посмотрим, что находится по адресу 0x46d9e0
:
Это уже похоже на нормальный энтрипоит и на загрузку проги. Таким образом, необходимо запатчить прогу так:
После таких патчей сможем избавиться от защиты от дизассемблирования и нормально отлаживать программу.
Патчим
В HxD нахожу первую инструкцию по байтам E9 F6 FF FF FF
:
После этого заменил всё, до первой такой инструкции (энтипоинт 0xA9BB40
):
Так как, теперь это хорошо открывается в IDA, патчинг закончу там.
Патчим на интересующий нас адрес - 0x46A140
.
Теперь можно поставить точку останова на main
и начать реверсить!
Реверс
В начале есть интересная функция под названием main_keygen
. Она формирует ключ шифрования.
В начале есть такой блок кода:
Это зашифрованный ключ в hex'ах. Расшифровывается так:
Первый шаг - побайтный XOR с 0x66
Второй шаг - побайтный XOR с 0x99
Третий шаг - побайтный XOR с 0x37
Таким образом, получим такой ключ:
В памяти видим, что две одинаковые строки, но это нужно будет для дальнейшего шифорвания.
Дальше обращаем внимание на следующих блок:
Это наш зашифрованный флаг, который будет побайтно сравниваться с нашей строкой.
Само шифрование происходит в функции main_generator
:
В main_generator
строка проходит пять этапов шифрования:
Первый шаг - каждый байт ксорится с ключём encr7pt1on_k3yencr7pt1on_k3y
Второй шаг - каждый байт ксорится по кругу с захардкоженными значениями 0xE5, 0x99, 0xB3
:
Третий шаг - каждый байт складывается с 0x65
:
Четвертый шаг - каждый байт складывается с 0x7D
:
Последний шаг - каждый байт ксорится с другими захардкоженными значениями 0xE8,0x90,0xB6
:
После выхода из функции будет сравнение длины введенной строки с длиной флага. Длина должна быть равна 28 и происходит сравнение строк:
После реверса становится ясно, что без написания кейгена будет сложно расшифровать флаг, да и к тому же все операции обратимы.
Пишем кейген
Для тестирования взял такую строку: ABCDEFGHIJKLMNOPQRSTUVWXYZ{}
.
Первым шагом был ксор строки с ключом encr7pt1on_k3yencr7pt1on_k3y
:
for i in range(len(test)): print(hex(ord(test[i]) ^ ord(key[i])),end=" ") res_1.append(ord(test[i]) ^ ord(key[i]))
Дальше ксор с захардкоженными значениями:
round_key_2 = [0xE5, 0x99, 0xB3] for i in range(len(res_1)): print(hex(res_1[i] ^ round_key_2[i%3]),end=" ") res_2.append(res_1[i] ^ round_key_2[i%3])
for i in range(len(res_2)): print(hex((((res_2[i] + 0x65)%0x100) + 0x7D)%0x100),end=" ") res_3.append((res_2[i] + 0x65)%0x100)
Последний шаг - ксор со вторыми захардкоженными значениями:
round_key_3 = [0xE8, 0x90, 0xB6] for i in range(len(res_4)): print(hex(res_4[i] ^ round_key_3[i%3]),end=" ") res_5.append(res_4[i] ^ round_key_3[i%3])
Таким образом, полный алгоритм шифрования выглядит так:
test = "ABCDEFGHIJKLMNOPQRSTUVWXYZ{}" key = "encr7pt1on_k3yencr7pt1on_k3y" round_key_2 = [0xE5, 0x99, 0xB3] round_key_3 = [0xE8, 0x90, 0xB6] res_1 = [] res_2 = [] res_3 = [] res_4 = [] res_5 = [] print("ROUND 1") # round 1 for i in range(len(test)): print(hex(ord(test[i]) ^ ord(key[i])),end=" ") res_1.append(ord(test[i]) ^ ord(key[i])) #round 2 print("") print("ROUND 2") for i in range(len(res_1)): print(hex(res_1[i] ^ round_key_2[i%3]),end=" ") res_2.append(res_1[i] ^ round_key_2[i%3]) #round 3 print("") print("ROUND 3") for i in range(len(res_2)): print(hex((res_2[i] + 0x65)%0x100),end=" ") res_3.append((res_2[i] + 0x65)%0x100) #round 4 print("") print("ROUND 4") for i in range(len(res_3)): print(hex((res_3[i] + 0x7D)%0x100),end=" ") res_4.append((res_3[i] + 0x7D)%0x100) #round 5 print("") print("ROUND 5") for i in range(len(res_4)): print(hex(res_4[i] ^ round_key_3[i%3]),end=" ") res_5.append(res_4[i] ^ round_key_3[i%3]) print("\n") print("RESULT") for i in res_5: print(hex(i), end=" ")
И проверим, что выведет бинарь:
Получили тоже самое. Теперь осталось это всё обратить и получим флаг.
flag = [0x20,0xed,0x25,0x3a,0x27,0x30,0x6b,0x49,0x79,0x32,0x1a,0x38,0x83,0xe5,0x2b,0x29,0xe4,0x36,0x70,0x2a,0x35,0x7c,0x39,0x16,0x59,0x10,0x2f,0x2b] key = "encr7pt1on_k3yencr7pt1on_k3y" round_key_2 = [0xE5, 0x99, 0xB3] round_key_3 = [0xE8, 0x90, 0xB6] res_1 = [] res_2 = [] res_3 = [] res_4 = [] res_5 = [] for i in range(len(flag)): print(hex(flag[i] ^ round_key_3[i%3]),end=" ") res_1.append(flag[i] ^ round_key_3[i%3]) for i in range(len(res_1)): print(hex((res_1[i] - 0x7D)%0x100),end=" ") res_2.append((res_1[i] - 0x7D)%0x100) for i in range(len(res_2)): print(hex((res_2[i] - 0x65)%0x100),end=" ") res_3.append((res_2[i] - 0x65)%0x100) for i in range(len(res_1)): print(hex(res_3[i] ^ round_key_2[i%3]),end=" ") res_4.append(res_3[i] ^ round_key_2[i%3]) for i in range(len(res_4)): print(hex(res_4[i] ^ ord(key[i])),end=" ") res_5.append(chr(res_4[i] ^ ord(key[i]))) print("\n\n") for i in res_5: print(i,end="")