Таблица поиска содержит адреса функций и способ их поиска в библиотеках (по порядковым номерам или именам). В нашем случае будем вести поиск по именам, поэтому таблица поиска, расположенная по адресу 1080, принимает такой вид: 1060,0,0,0,1070,0,0,0. Здесь 1060,0 указывает на функцию MessageBoxA (0 отделяет названия функций между собой), потом идет 0,0 — разграничитель между функциями различных библиотек, а 1070,0 — адрес функции ExitProcess из другой библиотеки. Следует также учитывать, что первые два байта перед названиями функций должны быть равны 0 — они являются индексом, по которому в библиотеке должны находиться функции, но поскольку индекс неизвестен, нужно оставить эти поля пустыми, чтобы загрузчик сам нашел в библиотеках данные функции.
Таблица импортируемых адресов располагается в самом начале секции (в нашем случае по адресу 1000) и содержит адреса импортируемых функций — аналог таблицы поиска.
Таблица импорта связывает библиотеки с таблицами поиска и импортируемых адресов (в коде начинается по адресу 1090). Каждая строка таблицы описывает одну библиотеку (сначала содержится адрес начала описания функций из библиотеки в таблице поиска (1080,0), потом два пустых поля (0,0,0,0), потом адрес, хранящий название библиотеки (1040,0), и адрес начала описания функций данной библиотеки в таблице импортируемых адресов (1000,0)). Аналогично описывается библиотека KERNEL32.DLL. После описания всех библиотек нужно оставить еще одну пустую строку таблицы импорта, то есть следующие 20 байт. Итого размер таблицы импорта равен 3Ч5Ч4 = 60, а в шестнадцатиричном виде — 3c, что мы и вводили в заголовке PE по адресу c0.
И наконец, последняя часть кода — сам код. Он начинается с адреса 10d0, который и является точкой входа в программу — ее мы и указывали в заголовке PE файла по адресу 68.
Код довольно прост, но написан на машинном языке:
db 6a,24
db 68,10,10,40,0
db 68,20,10,40,0
db 6a,0
db ff,15,0,10,40,0
db 6a,0
db ff,5,8,10,40,0
Так вызывается функция MessageBoxA и ей передаются необходимые параметры: сначала помещается значение 24 (указывает, что вызываемое окно имеет две кнопки и значок вопроса), потом адрес заголовка, адрес сообщения и 0 (дескриптор родительского объекта, которого у нас нет). Если описать приведенный код более просто, то получится:
■ Push 24 — поместить в стек значение 24;
■ Push offset «переменная с заголовком окна» — поместить в стек адрес памяти, содержащий заголовок окна;
■ Push offset «переменная с сообщением окна» — поместить в стек адрес памяти, хранящий сообщение окна;
■ Push 0 — поместить в стек 0;
■ Call «адрес памяти, содержащий название функции».
Аналогично вызывается функция ExitProcess.
Вот и все. Теперь только осталось скопировать диапазон адресов от 1000 до 1200, хранящий нашу программу, в память, начиная с адреса 200, а потом сместить всю программу на 100, так как при сохранении файла отладчик обрезает первые 100h байт памяти.
Простой пример
Конечно, приведенный выше пример довольно сложен — ведь он написан на машинном языке и в шестнадцатиричном виде. Но его можно упростить, ведь отладчик в Windows XP поддерживает как ASCII-символы, так и язык «Ассемблера».
Вот упрощением мы сейчас и займемся. Например, напишем программу, которая будет открывать созданный нами ранее файл (если он будет называться hello.exe и находиться на диске D:).
Очистка памяти и описание заголовка файла аналогично приведенному выше примеру, поэтому эту часть кода мы пропустим.
а 1010
db "D:\HELLO.EXE"
а 1040
db "SHELL32.DLL"
а 1050
db "KERNEL32.DLL"
а 1060
db 0,0,"ShellExecuteA"
а 1070
db 0,0,"ExitProcess"
а 1080
dw 1060,0,0,0,1070,0,0,0
а 1000
dw 1060,0,0,0,1070,0,0,0
а 1090
dw 1080,0,0,0,0,0,1040,0,1000,0
dw 1088,0,0,0,0,0,1050,0,1008,0
a 10d0
xor bx,bx
push bx
push bx
push bx
db 68,10,10,40,0
push bx
push bx
db ff,15,0,10,40,0
push bx
db ff,15,8,10,40,0
m 1000 1200 200
m 0 400 100
n d:\rr.bin
r cx
400
w