Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #80, АВГУСТ 2005 г.

Долой импорт!

Николай «GorluM» Андреев

Хакер, номер #080, стр. 080-116-4


...

#define GMessageBeep pushargEx<user32, 0xABBEE6BC>

#define GMessageBoxA pushargEx<user32, 0xABBC680D>

#define GMessageBoxExA pushargEx<user32, 0x1A0256AE>

#define GMessageBoxExW pushargEx<user32, 0x1A0256B8>

...

Как ты уже понял, если добавить подобный хидер в программу вместе с описанными выше функциями, то для вызова API в обход таблицы импорта будет достаточно приписать к имени функции буковку G ;). Ну и, конечно, не забыть подгрузить в самом начале все библиотеки, назвав переменные с их хэндлами по левой части имени файла dll.

[пример программы без таблицы импорта]

#pragma comment(linker,"/ENTRY:WinMain")

#include <windows.h>

#include "kernel32.dll.h"

#include "user32.dll.h"

// хидер с функциями GetProcAddressEx, шаблонами и т.п.

#include "noimport.h"

HMODULE kernel32;

HMODULE user32;

int WINAPI WinMain(HINSTANCE, HINSTANCE, PTSTR, int)

{

kernel32 = GetKernel();

user32 = GLoadLibraryA ("user32.dll");

GMessageBox(0,"Hello world",0,0);

return 0;

}

[тестируем]

Ну, что в итоге мы получили, кроме того, что в PE-заголовке больше нельзя найти ссылки на таблицу импорта. Обычный определяемый любым дизассемблером вызов функции вроде этого:

push 1000

call [Sleep]

Изменился на:

push 3D9972F5h

push [esp+var_4]

call sub_2AA026E4

push 1000

call eax

В коде теперь мало что понятно, правда? И если покруче покопаться, то с дизассембером будет тоже самое. Но можно постепенно понять, что sub_2AA026E4 - это аналог функции GetProcAddress. Однако ищет функцию он не по имени, а по посчитанной хэш-функции от имени. Кошмар! Чтобы разобраться в том, что делает программа, написанная подобным образом, придется не один час просидеть с отладчиком и дизассемблером в руках, пытаясь сопоставить какому из подобных вызовов какой обычный API-вызов соответствует.

Не одному только реверсеру, кстати, кошмар. Всяческие эвристики и подобная фигня на этом деле тоже вымрут - проверено. Взять, к примеру, NOD32. Я для теста написал маленькую программу, копирующую себя в системную директорию, в реестр и слушающую порт. NOD32 на параноидальном уровне безопасности тот час же окрестил ее как «вероятно вирус». Потом я заменил все вызовы на мои «хитрые». Угадай, что на это сказал антивирус? Ничего плохого! Скушал, буркнул «спасибо, вирусов не найдено, приходите еще», и на боковую.

И это ведь только начало такой замечательной штуки как precompiled-метаморфизм (вероятно, термин я придумал дурацкий, зато достиг в этой штуке немалого). Компилятор можно научить таким офигенным штукам, какие и не снились обычным полиморфным или метаморфным движкам. Полная перестройка программы для них - пустяк. Главное - правильно оформить исходный код, то есть научить всему компилятор. Можно, к примеру, заставить его оформлять разные вызовы. Скажем, в зависимости от номера строки исходного кода вставлять либо call, либо push $+5\push addr\ret, либо еще что-нибудь. Хэш-функции использовать все время разные. Можно попробовать научить компилятор (о, недокументированные возможности, как вы прекрасны и ужасны одновременно) перегружать операторы стандартных типов. Ты представь только - подсунуть свой оператор присваивания вместо дефолтного, и впихивать в него все время кучу лишнего кода и каких-нибудь нехороших трюков еще на этапе компиляции. Можно написать отличную систему, которая позволяла бы обычные нормальные программы компилировать так, словно компилятор в задницу оса укусила, так что никакие протекторы и упаковщики не понадобятся.

Назад на стр. 080-116-3  Содержание  Вперед на стр. 080-116-5