Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Языковые модели способны взять на себя рутинную часть работы реверс‑инженера. Они объяснят, как работает блок кода, и подскажут удачные имена функций и переменных. Рассмотрим широкий спектр облегчающих анализ инструментов — от локальных языковых моделей до агентов, способных к рассуждению и запуску пользовательского кода.
Начнем с простого теста. Возьмем ассемблерный код и попросим GPT декомпилировать в псевдо-C.
Декомпилируй ассемблерный код из IDA Pro в псевдо-C, как Hex-Rays. waveOut_work proc near push ebx mov ebx, offset pwh loc_409F0E: push 20h ; ' ' ; cbwh push ebx ; pwh push ds:hwo ; hwo call __imp_waveOutUnprepareHeader add ebx, 20h ; ' ' cmp ebx, offset byte_412DE4 jb short loc_409F0E push ds:hwo ; hwo call __imp_waveOutClose pop ebx retn waveOut_work endp
Правильный ответ от Hex-Rays:
MMRESULT waveOut_work(){ struct wavehdr_tag *v0; // ebx v0 = &pwh; do waveOutUnprepareHeader(hwo, v0++, 0x20u); while ( v0 < (struct wavehdr_tag *)&byte_412DE4 ); return waveOutClose(hwo);}
Ответ GPT-4o:
void waveOut_work(){ WAVEHDR *pwh = (WAVEHDR *) &pwh; // Начало массива структур WAVEHDR while ((BYTE *)pwh < (BYTE *)0x412DE4) { waveOutUnprepareHeader(hwo, pwh, sizeof(WAVEHDR)); pwh++; } waveOutClose(hwo);}
IDA предполагает, что функция возвращает MMRESULT
, хотя смысла получать ответ от waveOutClose
нет и void
— правильный тип. Имя WAVEHDR
понятней, чем wavehdr_tag
, хотя это одна и та же структура.
Пока ответ GPT лучше, попробуем с участком кода побольше.
Декомпилируй ассемблерный код из IDA Pro в псевдо-C, как Hex-Rays. sub_40C5A4 proc near String = dword ptr 4 push ebx push esi mov ebx, [esp+8+String] mov esi, 105h cmp word ptr [ebx], 21h ; '!' jnz short loc_40C5D0 xor ebx, ebx call __imp_GetVersion test eax, 80000000h mov esi, 3 jnz short loc_40C5FB mov esi, 0Bh jmp short loc_40C5FB loc_40C5D0: cmp word ptr [ebx], 3Fh ; '?' jnz short loc_40C5DF xor ebx, ebx mov esi, 3 jmp short loc_40C5FB loc_40C5DF: movsx ecx, byte ptr [ebx] test ds:(__ctype+1)[ecx], 4 jz short loc_40C5FB push ebx ; String call j__atoi mov ebx, eax mov esi, 1 add esp, 4 loc_40C5FB: push ebx ; dwData push esi ; uCommand push offset FullPath_HLP ; lpszHelp push ds:g_hWnd_0 ; hWndMain call __imp_WinHelpA pop esi pop ebx retn 4 sub_40C5A4 endp
Ответ Hex-Rays:
BOOL __stdcall sub_40C5A4(char *String){ char *v1; // ebx UINT v2; // esi v1 = String; v2 = 261; if ( *(_WORD *)String == 33 ) { v1 = 0; v2 = 3; if ( (GetVersion() & 0x80000000) == 0 ) v2 = 11; } else if ( *(_WORD *)String == 63 ) { v1 = 0; v2 = 3; } else if ( (*((_BYTE *)_ctype + *String + 1) & 4) != 0 ) { v1 = (char *)j__atoi(String); v2 = 1; } return WinHelpA(g_hWnd_0, FullPath_HLP, v2, (ULONG_PTR)v1);}
Ответ GPT-4o:
int __stdcall sub_40C5A4(LPCSTR String){ int dwData = 0; UINT uCommand = 0x105; // Значение по умолчанию if (*(WORD *)String == '!') { // Если строка начинается с '!' GetVersion(); // результат используется только для проверки платформы if (GetVersion() & 0x80000000) uCommand = 3; else uCommand = 0x0B; } else if (*(WORD *)String == '?') { // Если строка начинается с '?' uCommand = 3; } else if (isxdigit((unsigned char)*String)) // или isdigit, если __ctype+1[ecx] == 4 означает цифру { dwData = atoi(String); uCommand = 1; } WinHelpA(g_hWnd_0, FullPath_HLP, uCommand, dwData); return 0;}
И вот у модели начинаются галлюцинации. Зачем‑то поставила два вызова GetVersion
. Откуда‑то взялся возвращаемый ноль, хотя из кода очевидно, что EAX
берется из WinHelpA
, следовательно, тип возврата должен быть BOOL
.
Тем не менее модель удачно распознала имена переменных, преобразовала константы к нужным типам и даже увидела макрос isdigit
. Если бы не галлюцинации, инструменту бы цены не было. Круто, но Hex-Rays пока еще рано списывать со счетов.
Считается, что с анализом кода на си GPT справляется лучше. Проверим это простым скриптом на IDAPython, работающим через API от OpenAI.
import idcimport idaapiimport idautilsimport openaiimport reclient = openai.OpenAI( api_key="sk-proj-*")def ask_gpt_about_function(c_code): prompt_text = f"""Here is a C-like decompiled function from a binary:{c_code}Please suggest a concise and descriptive name for this function (using snake_case), and provide a brief explanation of what it does.Respond strictly in the following format:Function name: <name>Description: <short explanation>""" try: response = client.chat.completions.create( model="gpt-4", messages=[ {"role": "user", "content": prompt_text} ], temperature=0.5 ) return response.choices[0].message.content except Exception as e: print(f"[!] GPT API error: {e}") return Nonedef apply_result_to_function(ea, gpt_response): match = re.search(r"Function name:s*(w+).*?Description:s*(.*)", gpt_response, re.DOTALL) if match: name = "gpt_" + match.group(1) desc = match.group(2).strip() if ida_name.force_name(ea, name, idc.SN_AUTO): print(f"[+] Renamed function at {hex(ea)} to: {name}") else: print(f"[!] Could not rename function to: {name}") idc.set_func_cmt(ea, desc, 0) else: print(f"[!] Could not parse GPT response:n{gpt_response}")def is_user_defined_name(ea): return not idc.get_name(ea).startswith("sub_")def get_decompiled_code(ea): try: cfunc = idaapi.decompile(ea) return str(cfunc) except Exception as e: print(f"[!] Failed to decompile function at {hex(ea)}: {e}") return Nonedef main(): for func_ea in idautils.Functions(): print(f"[*] Processing function at {hex(func_ea)} {is_user_defined_name(func_ea)}") if is_user_defined_name(func_ea): continue code = get_decompiled_code(func_ea) if not code: continue gpt_result = ask_gpt_about_function(code) if gpt_result: apply_result_to_function(func_ea, gpt_result)main()
Код получает адреса распознанных функций и, если функция еще не была переименована, отправляет декомпилированный Hex-Rays код запросом к GPT. В ответе мы ожидаем увидеть новое имя функции и краткое описание, что она делает. Далее скрипт переименовывает функцию и добавляет комментарий с описанием ее работы.
// This function swaps the second and third bytes of the input array and then masks the second byte with a value from a global byte array and a bitwise AND operation. The result is stored back into the second byte of the input array.int __fastcall gpt_swap_and_mask_bytes_0(int a1, int a2, _BYTE *a3){ int result; // eax LOBYTE(a2) = a3[2]; a3[2] = a3[3]; a3[3] = a2; LOBYTE(a2) = a3[1]; result = a2; LOBYTE(result) = byte_4110E4[(int)(unsigned __int8)a2 >> 4] | a2 & 0xF; a3[1] = result; return result;}
Для маленьких функций без контекста придуманные GPT названия практически бесполезны. Если бы мы предварительно указали имена аргументов и правильные типы, анализ был бы точнее. Еще одна проблема скрипта — порядок функций. Сначала надо передавать на анализ вызываемые функции и только затем вызывающие, чтобы расширить контекст последних. Для этого надо создать граф вызовов и постепенно идти от самых дальних узлов наверх.
Попробуем отдать GPT большую изолированную функцию, понятную без внешнего контекста.
// This function copies and inverts the pixels from one area of the image to another. The source and destination areas, as well as the dimensions, are determined by the input parameters. The pixels are processed in reverse order, and zero pixels are skipped.char __stdcall gpt_copy_inverted_pixels(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9){ char *v9; // edi char *v10; // esi int v11; // edx int v12; // ebp int v13; // ecx char result; // al v9 = (char *)lpBits + a4 + dword_412CD0 * (dword_412CD4 - 1 - a3); v10 = (char *)(a9 + a8 + a6 + a6 * -a7 - 1); v11 = a2 - a4 + dword_412CD0; v12 = a1 - a3; dword_412CD8 = a2 - a4; do { v13 = dword_412CD8; while ( 1 ) { result = *v10--; if ( !result ) break;LABEL_4: *v9++ = result; if ( !--v13 ) goto LABEL_8; } while ( 1 ) { ++v9; if ( !--v13 ) break; result = *v10--; if ( result ) goto LABEL_4; }LABEL_8: v9 -= v11; v10 += a2 - a4 + a6; } while ( v12-- > 1 ); return result;}
Работа над байтами без вызова внешнего кода декодируется достаточно бодро. GPT понял, что алгоритм работает с двумя областями изображения. Действительно, по коду v12 = a1 - a3
можно догадаться, что это координаты x1
и x2
.
Разбирать подобную математику вручную было бы слишком утомительно. Обычно я просто смотрю в отладчике, что было с данными до и что стало после. А из этого уже делаю выводы о назначении функции.
Наивность текущего подхода — в зашкаливающем числе запросов. Модель GPT-4 за анализ двухсот небольших функций сожрала аж три бакса! Вероятно, модели попроще стоят дешевле. В любом случае подобный анализ применим только к конкретной функции. То есть загрузить exe-файл целиком и получить его исходный код пока что не удастся.
Источник: xakep.ru