Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #89, МАЙ 2006 г.

Дао универсальной распаковки

Крис Касперски

Хакер, номер #089, стр. 089-114-2


Ага, вот он! Рассмотрим его повнимательнее. Я уверяю: тут есть чему поучиться:

[хитро оптимизированный цикл чтения строковых ресурсов]

01004825h mov ebp, ds:LoadStringW ; ebp - указатель на LoadStringW

0100482Bh mov edi, offst off_10080C0 ;указатель на таблицу ресурсов

01004830h

01004830h loc_1004830:

01004830h mov eax, [edi] ; грузим очередной указатель на uID в eax

01004832h push ebx ; nBufferMax (максимальная длина буфера)

01004833h push esi ; lpBuffer (указатель на буфер)

01004834h push dword ptr [eax] ; передаем извлеченный uID функции

01004836h push [esp+0Ch+hInstance] ; hInstance

0100483Ah call ebp ; LoadStringW ; считываем очередную строку из ресурса

0100483Ch mov ecx, [edi] ; грузим тот же самый uID в ecx

0100483Eh inc eax ; увеличиваем длину считанной строки на 1

0100483Fh cmp eax, ebx ; ?строка влезает в буфер?

01004841h mov [ecx], esi ; сохраняем указатель на буфер поверх

01004841h ; старого uID(он больше не понадобится)

01004841h

01004843h lea esi, [esi+eax*2] ; позиция для следующей строки в буфере

01004846h jg short loc_100488B ; если буфер кончился, то это облом

01004848h add edi, 4 ; переходим к следующему uID

0100484Bh sub ebx, eax ; уменьшаем свободное место в буфере

0100484Dh cmp edi, offst off_1008150 ; ?конец таблицы ресурсов?

01004853h jl short loc_1004830 ; мотаем цикл, пока ресурсы не закончились

В переводе на русский, это звучит так: "Блокнот берет очередной идентификатор строки из таблицы ресурсов, загружает строку, размещая ее в локальном буфере, и сохраняет полученный указатель на строку поверх… самого идентификатора, который уже не нужен!"

Классический трюк с повторным использованием освободившихся переменных, известных еще со времен первых PDP, если не раньше. А вы все Microsoft ругаете! Блокнот писал не глупый хакер, бережно относящийся к системным ресурсам и, в частности, к памяти. Для нас же это в первую очередь означает, что снятый с "живой" программы дамп будет неполноценным. Вместо реальных идентификаторов строк в секции ресурсов содержатся указатели на память, указывающие в "космос"! Ведь при повторном запуске Блокнота листинг 1 уже не срабатывает.

Во многих программах встречается конструкции вида:

["защита" от дампинга живых программ]

void *p=0; // глобальная переменная

if (!p) p = malloc(BUFF_SIZE);

Очевидно, если сдампить программу после завершения строки с "if", то глобальная переменная p будет содержать указатель, доставшийся ей в "наследство" от предыдущего запуска, однако соответствующий регион памяти выделен не будет, и программа либо рухнет, либо залезет в чужие данные, устроив там настоящий переполох!

Сформулируем главное правило: дампить программу можно только в точке входа! Остается разобраться, как эту точку входа отловить.

[универсальный пример поиска OEP, основанный на балансе стека]

Вот мы и подобрались к самому интересному и универсальному способу определения OEP, который к тому же легко автоматизировать. Упаковщик (даже если он не совсем корректный) просто обязан после распаковки восстановить стек, в смысле вернуть регистр ESP на место, под которым будет первичный фильтр структурных исключений, установленный системой по умолчанию. Некоторые упаковщики еще восстанавливают и регистры, но делать это в общем-то и не обязательно.

Назад на стр. 089-114-1  Содержание  Вперед на стр. 089-114-3