Техника хакерских атак Фундаментальные основы хакерства




НазваниеТехника хакерских атак Фундаментальные основы хакерства
страница8/50
Дата публикации06.09.2013
Размер6.7 Mb.
ТипДокументы
shkolnie.ru > Информатика > Документы
1   ...   4   5   6   7   8   9   10   11   ...   50
^

Шаг четвертый. Знакомство с отладчиком



Оставь свои мозги за дверью и внеси сюда только тело

Фредерик Тейлор
Помимо дизассемблирования существует и другой способ программ – отладка. Изначально под отладкой понималось пошаговое исполнение кода, так же называемое трассировкой. Сегодня же программы распухли настолько, что трассировать их бессмысленно – вы тут же утоните в омуте вложенных процедур, так и не поняв, что они собственно делают. Отладчик – не лучше средство изучения алгоритма программы – с этим лучше справляется интерактивный дизассемблер (например, IDA).
Подробный разговор об устройстве отладчика мы отложим на потом (см. "^ Приемы против отладчиков"), а здесь ограничимся лишь перечнем основных функциональных возможностей типовых отладчиков (без этого невозможно их осмысленное применение):
– отслеживание обращений на запись/чтение/исполнение к заданной ячейке (региону) памяти, далее по тексту именуемое "бряком" ("брейком");
– отслеживание обращений на запись/чтение к портам ввода-вывода (уже не актуально для современных операционных систем, запрещающих пользовательским приложениям проделывать такие трюки – это теперь прерогатива драйверов, а очень на уровне драйверов реализованы очень немногие защиты);
– отслеживание загрузки DLL и вызова из них таких-то функций, включая системные компоненты (как мы увидим далее – это основное оружие современного взломщика);
– отслеживание вызова программных/аппаратных прерываний (большей частью уже не актуально, - не так много защит балуется с прерываниями);
– отслеживание сообщений посылаемых приложением окну;
– и, разумеется, контекстный поиск в памяти.
Как именно делает отладчик – пока знать необязательно, достаточно знать, что он это умеет и все. Куда актуальнее вопрос, – какой отладчик умеет это делать? Широко известный в пользовательских кругах Turbo Debugger на само деле очень примитивный и никчемный отладчик – очень мало хакеров им что-то ломает.

Самое мощное и универсальное средство – Soft-Ice, сейчас доступный для всех Windows-платформ (а когда он поддерживал лишь одну Windows 95, но не Windows NT). Последняя на момент написания книги, четвертая версия, не очень-то стабильно работалает с моим видеоадаптером, поэтому пришлось приходится ограничитваться более ранней, но зато устойчивой версией 3.25.

^

Способ 0. Бряк на оригинальный пароль.



Используя поставляемую вместе с "Айсом" утилиту "wldr" загрузим ломаемый нами файл, указав его имя в командной строке, например, так:
>wldr simple.exe
Да, я знаю, что wldr – 16-разрядный загрузчик, и NuMega рекомендует использовать его 32-разрядную версию loadrer32, специально разработанную для Win 9x\NT. Это так, но loader32 частенько глючит (в частности не всегда останавливается на первой строчке запускаемой программы), а wldr успешно работает и 32-разрядными приложениями, единственный присущий ему недостаток – отсутствие поддержки длинных имен файлов.

Если отладчик настроен корректно, на экране появится черное текстовое окно, обычно вызывающее большое удивление у начинающих – это в нашу-то это эпоху визуальщины серый текст и командный язык a la command.com.! А почему бы и нет? Набрать на клавиатуре нужную команду куда быстрее, чем отыскать ее в длинной веренице вложенных меню, мучительно вспоминая где же вы ее в последний раз видели. К тому же язык – это естественное средство выражения мыслей, а меню – оно годится разве что для выбора блюд в ресторане. Вот хороший пример – попробуйте с помощью проводника Windows вывести на печать список файлов такой-то директории. Не получается? А в MS-DOS это было так просто dir >PRN и никаких лаптей!

Если в окне кода видны одни лишь инструкции "INVALID" (а оно так и будет) не пугайтесь – просто Windows еще не успела спроецировать исполняемый файл в память и выделилавыделить ему страницы. Стоит нажать <F10> (аналог команды "P" – трассировка без заходов в функцию) или <F8> (аналог команды "T" – трассировка с заходами в функции) как все сразу же станет на свои места.
001B:00401277 INVALID

001B:00401279 INVALID

001B:0040127B INVALID

001B:0040127D INVALID

:P
001B:00401285 PUSH EBX

001B:00401286 PUSH ESI

001B:00401287 PUSH EDI

001B:00401288 MOV [EBP-18],ESP

001B:0040128B ^ CALL [KERNEL32!GetVersion]

001B:00401291 XOR EDX,EDX

001B:00401293 MOV DL,AH

001B:00401295 MOV [0040692C],EDX
Обратите внимание: в отличие от дизассемблера DUMPBIN, Айс распознает имена системных функций, чем существенно упрощает анализ. Впрочем, анализировать всю программу целиком, нет никакой нужды. Давайте попробуем наскоро найти защитный механизм, и, не вникая в подробности его функционирования, напрочь отрубить защиту. Легко сказать, но сделать еще проще! Вспомним: по какому адресу расположен в памяти оригинальный пароль. Э… что-то плохо у нас с этим получается – то ли память битая, то ли медведь на лапоть наступил, но точный адрес никак не хочет вспоминаться. Не хочет – не надо. Найдем-ка мы его самостоятельно!

В этом нам поможет команда "map32" выдающая карту памяти выбранного модуля (наш модуль называется "simple" – по имени исполняемого файла за вычетом расширения).
:map32 simple

Owner Obj Name Obj# Address Size Type

simple .text 0001 001B:00401000 00003F66 CODE RO

simple .rdata 0002 0023:00405000 0000081E IDATA RO

simple .data 0003 0023:00406000 00001E44 IDATA RW

^^^^ ^^^^^^^^^^^^^
Вот он, адрес начала секции ".data". То, что пароль находится в секции ".data", надеюсь, читатель все еще помнит. Даем команду "d 23:406000" (возможно предварительно придется создать окно командой "wc" – если окна данных нет) и, нажав, <ALT-D> для перехода в это окно, прокрутим его содержимое <стрелкой вниз> или кирпичом на <Page Down>. Впрочем, кирпич излишен, – долго искать не придется:
0023:00406040 6D 79 47 4F 4F 44 70 61-73 73 77 6F 72 64 0A 00 myGOODpassword..

0023:00406050 57 72 6F 6E 67 20 70 61-73 73 77 6F 72 64 0A 00 Wrong password..

0023:00406060 50 61 73 73 77 6F 72 64-20 4F 4B 0A 00 00 00 00 Password OK.....

0023:00406070 47 6E 40 00 00 00 00 00-40 6E 40 00 01 01 00 00 Gn@.....@n@.....

0023:00406080 00 00 00 00 00 00 00 00-00 10 00 00 00 00 00 00 ................

0023:00406090 00 00 00 00 00 00 00 00-00 00 00 00 02 00 00 00 ................

0023:004060A0 01 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

0023:004060B0 00 00 00 00 00 00 00 00-00 00 00 00 02 00 00 00 ................
Есть контакт! Задумаемся еще раз (второй раз за этот день) чтобы проверить корректность введенного пользователем пароля защита, очевидно должна сравнить его с оригинальным. А раз так – установив точку останова на чтение памяти по адресу 0x406040, мы поймаем "за хвост" сравнивающий механизм. Сказано – сделано.
:bmpm 406040
Теперь нажимаем <CTRL-D> для выхода из отладчика (или отдаем команду "x") и вводим любой пришедший на ум пароль, например, "KPNC++". Отладчик "всплывает" незамедлительно:
001B:004010B0 MOV EAX,[EDX]

001B:004010B2 CMP AL,[ECX]

001B:004010B4 JNZ 004010E4 (JUMP ↑)

001B:004010B6 OR AL,AL

001B:004010B8 JZ 004010E0

001B:004010BA CMP AH,[ECX+01]

001B:004010BD JNZ 004010E4

001B:004010BF OR AH,AH
Break due to BPMB #0023:00406040 RW DR3 (ET=752.27 milliseconds)

MSR LastBranchFromIp=0040104E

MSR LastBranchToIp=004010A0
В силу архитектурных особенностей процессоров Intel, бряк срабатывает после инструкции, выполнившей "поползновение", т.е. CS:EIP указывают на следующую выполняемую команду. В нашем случае – JNZ 004010E4, а к памяти, стало быть, обратилась инструкция CMP AL, [ECX]. А что находится в AL? Поднимаем взгляд еще строкой выше – "MOV EAX,[EDX]". Можно предположить, что EСX содержит указатель на строку оригинального пароля (поскольку он вызвал всплытие отладчика), а EDX в таком случае – указатель на введенный пользователем пароль. Проверим наше предположение.
:d edx

0023:00406040 6D 79 47 4F 4F 44 70 61-73 73 77 6F 72 64 0A 00 myGOODpassword..

:d edx

0023:0012FF18 4B 50 4E 43 2B 2B 0A 00-00 00 00 00 00 00 00 00 KPNC++..........
И правда – догадка оказалась верна. Теперь вопрос – а как это заломить? Вот, скажем, JNZ можно поменять на JZ или, еще оригинальнее, заменить EDX на ECX – тогда оригинальный пароль будет сравниваться сам с собой! Погодите, погодите… не стоит так спешить! А что если мы находится не в теле защиты, а в библиотечной функции (действительно, мы находится в теле strcmp), – ее изменение приведет к тому, что программа любые строки будет воспринимать как идентичные. Любые – а не только строки пароля. Это не повредит нашему примеру, где strcmp вызывалась лишь однажды, но завалит нормальное полнофункциональное приложение. Что же делать?

Выйти из strcmp и подкорректировать тот самый "IF", который анализирует правильный – не правильный пароль. Для этого служит команда "P RET" (трассировать пока не встреться ret – инструкция возврата из функции).
:P RET

001B:0040104E CALL 004010A0

001B:00401053 ADD ESP,08

001B:00401056 TEST EAX,EAX

001B:00401058 JZ 00401069

001B:0040105A PUSH 00406050

001B:0040105F CALL 00401234

001B:00401064 ADD ESP,04

001B:00401067 JMP 0040106B
Знакомые места! Помните, мы их посещали дизассемблером? Алгоритм действий прежний – запоминаем адрес команды "TEST" для последующей замены ее на "XOR" или записываем последовательность байт, идентифицирующую… эй, постойте, а где же наши байты – шестнадцатеричное представление команд? Коварный Айс по умолчанию их не выводит, и заставить его это делать помогает команда "CODE ON"
code on

001B:0040104E E84D000000 CALL 004010A0

001B:00401053 83C408 ADD ESP,08

001B:00401056 85C0 TEST EAX,EAX

001B:00401058 740F JZ 00401069

001B:0040105A 6850604000 PUSH 00406050

001B:0040105F E8D0010000 CALL 00401234

001B:00401064 83C404 ADD ESP,04

001B:00401067 EB02 JMP 0040106B
Вот, теперь совсем другое дело! Но можно ли быть уверенным, что эти байтики по этим самым адресам будут находиться в исполняемом файле? Вопрос не так глуп, каким кажется на первый взгляд. Попробуйте сломать описанным выше методом пример "crackme0x03". На первый взгляд он очень похож на simple.exe, - даже оригинальный пароль располагается по тому же самому адресу. Ставим на него бряк, дожидаемся всплытия отладчика, выходим из сравнивающей процедуры и попадаем на точно такой же код, который уже встречался нам ранее.
001B:0042104E E87D000000 CALL 004210D0

001B:00421053 83C408 ADD ESP,08====

001B:00421056 85C0 TEST EAX,EAX

001B:00421058 740F JZ 00421069
Сейчас мы запустим HIEW, перейдем по адресу 0x421053 и… эй, постой, HIEW ругается и говорит, что в файле нет такого адреса! Последний байт заканчивается на 0x407FFF. Быть может, мы находимся в теле системной функции Windows? Но нет – системные функции Windows расположены значительно выше – начиная с адреса 0x80000000.

Фокус весь в том, что PE-файл может быть загружен по адресу отличному от того, для которого он был создан (это свойство называется перемещаемостью), - при этом система автоматически корректирует все ссылки на абсолютные адреса, заменяя их новыми значениями. В результате – образ файла в памяти не будет соответствовать тому, что записано на диске. Хорошенькое начало! Как же теперь найти место, которое нужно править?

Задачу несколько облегчает тот факт, что системный загрузчик умеет перемещать только DLL, а исполняемые файлы всегда пытается загрузить по "родному" для них адресу. Если же это невозможно – загрузка прерывается с выдачей сообщения об ошибке. Выходит, мы имеем дело с DLL, загруженной исследуемой нами защитой. Хм… вроде бы не должно быть здесь никаких DLL – да и откуда бы им взяться?

Что ж, изучим листинг 2 на предмет выяснения: как же он работает.
#include

#include

__declspec(dllexport) void Demo()

^^^^^^^^^^^^^^^^^^^^^

{

#define PASSWORD_SIZE 100

#define PASSWORD "myGOODpassword\n"
int count=0;

char buff[PASSWORD_SIZE]="";
for(;;)

{

printf("Enter password:");

fgets(&buff[0],PASSWORD_SIZE-1,stdin);
if (strcmp(&buff[0],PASSWORD))

printf("Wrong password\n");

else break;
if (++count>2) return -1;

}

printf("Password OK\n");

}
main()

{

HMODULE hmod;

void (*zzz)();
if ((hmod=LoadLibrary("crack0~1.exe"))

&& (zzz=(void (*)())GetProcAddress(h,"Demo")))

zzz();
}

^ Листинг 2 Исходный текст защиты crackme 0x3

Какой, однако, извращенный способ вызова функции! Защита экспортирует ее непосредственно из самого исполняемого файла и этот же файл загружает как DLL (да, один и тот же файл может быть одновременно и исполняемым приложением и динамической библиотекой!).

"Все равно ничего не сходится", - возразит программист средней квалификации, - "всем же известно, что Windows не настолько глупа, чтобы дважды грузить один и тот же файл, - LoadLibrary всего лишь возвратит базовый адрес модуля crackme0x03, но не станет выделять для него память". А вот как бы не так! Хитрая защита обращается к файлу по его альтернативному короткому имени, вводя системный загрузчик в глубокое заблуждение!

Система выделяет память и возвращает базовый адрес загружаемого модуля в переменной hmod. Очевидно, код и данные этого модуля смещены на расстояние hmod – base, где base – базовый адрес модуля – тот, с которым работают HIEW и дизассемблер. Базовый адрес узнать нетрудно – достаточно вызвать тот же DUMPBIN с ключом "/HEADERS" (его ответ приведен в сокращенном виде)
>dumpbin /HEADERS crack0x03

OPTIONAL HEADER VALUES

...

400000 image base

^^^^^^^^^^^^^^^^^

...
Значит, базовый адрес – 0x400000 (в байтах). А опередить адрес загрузки можно командой "mod -u" отладчика: (ключ u разрешает выводить только прикладные, т.е. не системные модули).
:mod -u

hMod Base PEHeader Module Name File Name

00400000 004000D8 crack0x0 \.PHCK\src\crack0x03.exe

00420000 004200D8 crack0x0 \.PHCK\src\crack0x03.exe

^^^^^^^^

77E80000 77E800D0 kernel32 \WINNT\system32\kernel32.dll

77F80000 77F800C0 ntdll \WINNT\system32\ntdll.dll
Смотрите, загружено сразу две копии crack0x03, причем последняя расположена по адресу 0x420000, как раз что нам надо! Теперь нетрудно посчитать, что адрес 0x421056 (тот, что мы пытались последний раз найти в ломаемом файле) "на диске" будет соответствовать адресу 0x421056 – (0x42000 – 0x400000) == 0x421056 – 0x20000 == 0x401056. Смотрим:
00401056: 85C0 test eax,eax

00401058: 740F je .000401069 -------- (1)
Все верно – посмотрите, как хорошо это совпадает с дампом отладчика:
001B:00421056 85C0 TEST EAX,EAX

001B:00421058 740F JZ 00421069
Разумеется, описанная методика вычислений применима к любым DLL, а не только тем, что представляют собой исполняемый файл.

А вот, если бы мы пошли не путем адресов, а попытались найти в ломаемой программе срисованную с отладчика последовательность байт, включая и ту часть, которая входит в CALL 00422040 – интересно, нашли бы мы ее или нет?
001B:0042104E E87D000000 CALL 004210D0

001B:00421053 83C408 ADD ESP,08

001B:00421056 85C0 TEST EAX,EAX

001B:00421058 740F JZ 00421069

:Образ файла в памяти.
.0040104E: E87D000000 call .0004010D0 -------- (1)

.00401053: 83C408 add esp,008 ;"◘"

.00401056: 85C0 test eax,eax

.00401058: 740F je .000401069 -------- (2)

:Образ файла на диске
Вот это новость – командам CALL 0x4210D0 и CALL 0x4010D0 соответствует один и тот же машинный код – E8 7D 00 00 00! Как же такое может быть?! А вот как – аргумент операнд процессорной инструкции "0xE8" представляет собой не смещение подпрограммы, а разницу смещений подпрограммы и инструкции, следующей за командой call. Т.е. в первом случае: 0x421053 (смещение инструкции, следующей за CALL) + 0x0000007D (не забываем об обратном порядке байтов в двойном слове) == 0x4210D0, - вот он, искомый адрес. Таким образом, при изменении адреса загрузки, коррекция кодов команд CALL не требуется.
"Оценка по аналогии основывается на предположении, что если два или более объекта согласуются друг с другом в некоторых отношениях, то они, вероятно, согласуются и в других отношениях"

Ганс Селье "От мечты к открытию"
Рассуждения по аналогии – опасная штука. Увлеченные стройностью аналогии мы подчас даже не задумываемся о проверке. Между тем, аналогии лгут чаще, чем этого хотелось бы.

В примере crack0x03 среди прочего кода есть и такая строка (найдите ее с помощью hiew):

004012C5: 89154C694000 mov [00040694C],edx
Легко видеть, что команда MOV обращается к ячейке не по относительному, а по абсолютному адресу. Вопрос: а) выясните, что произойдет при изменении адреса загрузки модуля; б) как вы думаете – будет ли теперь совпадать образ файла на диске и в памяти?

Заглянув отладчиком по адресу 0x4212C5 (0x4012C5 + 0x2000) мы увидим, что обращение идет совсем не к ячейке 0x42694C, а – 0x40694C! Наш модуль самым бессовестным образом вторгается в чужие владения, модифицируя их по своему усмотрению. Так и до краха системы докатиться недолго! В данном случае этого не происходит только потому, что искомая строка расположена в Startup-процедуре (стартовом коде) и выполняется лишь однажды – при запуске приложения, а из загруженного модуля не вызывается.

Другое дело, если бы функция Demo() обращалась к какой-нибудь статической переменной – компилятор, подставив ее непосредственное смещение, сделал бы модуль неперемещаемым! После сказанного становится непонятно: как же тогда ухитряются работать динамически подключаемые библиотеки (DLL), адрес загрузки которых заранее неизвестен? Поразмыслив некоторое время, мы найдем, по крайней мере, два решения проблемы:

Первое – вместо непосредственной адресации использовать относительную, например: [reg+offset_val], где reg – регистр, содержащий базовый адрес загрузки, а offset_val – смещение ячейки от начала модуля. Это позволит модулю грузится по любому адресу, но заметно снизит производительность программы уже хотя бы за счет потери одного регистра….

Второе – научить загрузчик корректировать непосредственные смещения в соответствии с выбранным базовым адресом загрузки. Это, конечно, несколько замедлит загрузку, но зато не ухудшит быстродействие самой программы. Не факт, что временем загрузки можно свободно пренебречь, но парни из Microsoft выбрали именно этот способ.

Единственная проблема – как отличить действительные непосредственные смещения от констант, совпадающих с ними по значению? Не дизассемблировать же в самом деле DLL, чтобы разобраться какие именно ячейки в ней необходимо "подкрутить"? Верно, куда проще перечислить их адреса в специальной таблице, расположенной непосредственно в загружаемом файле и носящей гордое имя "Таблицы перемещаемых элементов" или (Relocation [Fix Up] table по-английски). За ее формирование отвечает линкер (он же – компоновщик) и такая таблица присутствует в каждой DLL.

Чтобы познакомиться с ней поближе откомпилируем и изучим следующий пример:
::fixupdemo.c

__declspec(dllexport) void meme(int x)

{

static int a=0x666;

a=x;

}

> cl fixupdemo.c /LD
^ Листинг 3 Исходный текст fixupdemo.c
Откомпилируем и тут же дизассемблируем его: "DUMPBIN /DISASM fixupdemo.dll" и "DUMPBIN /SECTION:.data /RAWDATA".
10001000: 55 push ebp

10001001: 8B EC mov ebp,esp

10001003: 8B 45 08 mov eax,dword ptr [ebp+8]

10001006: A3 30 50 00 10 mov [10005030],eax

^^^^^^^^^^^ ^^^^^^^^

1000100B: 5D pop ebp

1000100C: C3 ret
RAW DATA #3

10005000: 00 00 00 00 00 00 00 00 00 00 00 00 33 24 00 10 ............3$..

10005010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

10005020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

10005030: 66 06 00 00 E3 11 00 10 FF FF FF FF 00 00 00 00 f...у...    ....

^^^^^
Судя по коду, запись содержимого EAX всегда происходит в ячейку 0x10005030, но не торопите с выводами! "DUMPBIN /RELOCATIONS fixupdemo.dll":
BASE RELOCATIONS #4

1000 RVA, 154 SizeOfBlock

7 HIGHLOW

^

1C HIGHLOW

23 HIGHLOW

32 HIGHLOW

3A HIGHLOW
Таблица перемещаемых элементов-то не пуста! И первая же ее запись указывает на ячейку 0x100001007, полученную алгебраическим сложением смещения 0x7 с RVA-адресом 0x1000 и базовым адресом загрузки 0x10000000 (получите его с помощью DUMPBIN самостоятельно). Смотрим – ячейка 0x100001007 принадлежит инструкции "MOV [0x10005030],EAX" и указывает на самый старший байт непосредственного смещения. Вот это самое смещение и корректирует загрузчик в ходе подключения динамической библиотеки (разумеется, если в этом есть необходимость).

Хотите проверить? Пожалуйста, - создадим две копии одной DLL (например, copy fixupdemo.dll fixupdemo2.dll) и загрузим их поочередной следующей программой:
::fixupload.c

#include
main()

{

void (*demo) (int a);

HMODULE h;

if ((h=LoadLibrary("fixupdemo.dll")) &&

(h=LoadLibrary("fixupdemo2.dll")) &&

(demo=(void (*)(int a))GetProcAddress(h,"meme")))

demo(0x777);

}

> cl fixupload

^ Листинг 4 Исходный текст fixupload

Поскольку, по одному и тому же адресу две различные DLL не загрузишь (откуда же системе знать, что это одна и та же DLL!), загрузчику приходится прибегать к ее перемещению. Загрузим откомпилированную программу в отладчик и установим точку останова на функцию LoadLibraryA. Это, – понятное дело, – необходимо чтобы пропустить Startup-код и попасть в тело функции main. (Как легко убедиться исполнение программы начинается отнюдь не с main, а со служебного кода, в котором очень легко утонуть). Но откуда взялась загадочная буква 'A' на конце имени функции? Ее происхождение тесно связано с введением в Windows поддержки уникода – специальной кодировки, каждый символ в которой кодируется двумя байтами, благодаря чему приобретает способность выражать любой из 216 = 65.536 знаков, – количество достаточно для вмещения практически всех алфавитов нашего мира. Применительно к LoadLibrary – теперь имя библиотеки может быть написано на любом языке, а при желании и на любом количестве любых языков одновременно, например, на русско-француско-китайском. Звучит заманчиво, но не ухудшает ли это производительность? Разумеется, ухудшает, еще как – уникод требует жертв! Самое обидное – в подавляющем большинстве случаев вполне достаточно старой доброй кодировки ASCII (во всяком случае нам, – русским, и американцам). Так какой же смысл бросать драгоценные такты процесса на ветер? Ради производительности было решено поступиться размером, создав отдельные варианты функций для работы с уникодом и ASCII-символами. Первые получили суффикс 'W' (от Wideширокий), а вторые – 'A' (от ASCII). Эта тонкость скрыта от прикладных программистов – какую именно функцию вызывать 'W' или 'A' решает компилятор, но при работе с отладчиком необходимо указывать точное имя функции – самостоятельно определить суффикс он не в состоянии. Камень преткновения в том, что некоторые функции, например, ShowWindows вообще не имеют суффиксов – ни 'A', ни 'W' и их библиотечное имя совпадает с каноническим. Как же быть?

Самое простое – заглянуть в таблицу импорта препарируемого файла и отыскать там вашу функцию. Например, применительно к нашему случаю:
> DUMPBIN /IMPORTS fixupload.exe > filename

> type filename

19D HeapDestroy

1C2 LoadLibrary^ A

CA GetCommandLineA

174 GetVersion

7D ExitProcess

29E TerminateProcess

...
Из приведенного выше фрагменты видно, что LoadLibrary все-таки 'A', а вот функции ExitProcess и TerminateProcess не имеют суффиксов, поскольку вообще не работают со строками.

Другой путь – заглянуть в SDK. Конечно, библиотечное имя функций в нем отсутствует, но в "Quick Info" мимоходом приводится информация и поддержке уникода (если таковая присутствует). А раз есть уникод – есть суффиксы 'W' и 'A', соответственно, наоборот – где нет уникода, нет и суффиксов. Проверим?

Вот так выглядит Quick Info от LoadLibrary:
QuickInfo

Windows NT: Requires version 3.1 or later.

Windows: Requires Windows 95 or later.

Windows CE: Requires version 1.0 or later.

Header: Declared in winbase.h.

Import Library: Use kernel32.lib.

Unicode: Implemented as Unicode and ANSI versions on Windows NT.
На чистейшем английском языке здесь сказано – "Реализовано как Unicode и ANSI версии на Windows NT". Стоп! С NT все понятно, а как насчет "народной" девяносто восьмой (пятой)? Беглый взгляд на таблицу экспорта KERNEL32.DLL показывает: такая функция там есть, но, присмотревшись повнимательнее, мы с удивлением обнаружим, что ее точка входа совпадает с точками входа десятка других функций!
ordinal hint RVA name

556 1B3 00039031 LoadLibraryW
Третья колонка в отчете DUMPBIN это RVA-адрес – виртуальный адрес начала функции за вычетом базового адреса загрузки файла. Простой контекстный поиск показывает, что он встречается не единожды. Воспользовавшись программой-фильтром srcln (см. Приложения Исходные тексты) для получения связного протокола, мы увидим следующее:
21: 118 1 00039031 AddAtomW

116: 217 60 00039031 DeleteFileW

119: 220 63 00039031 DisconnectNamedPipe

178: 279 9E 00039031 FindAtomW

204: 305 B8 00039031 FreeEnvironmentStringsW

260: 361 F0 00039031 GetDriveTypeW

297: 398 115 00039031 GetModuleHandleW

341: 442 141 00039031 GetStartupInfoW

377: 478 165 00039031 GetVersionExW

384: 485 16C 00039031 GlobalAddAtomW

389: 490 171 00039031 GlobalFindAtomW

413: 514 189 00039031 HeapLock

417: 518 18D 00039031 HeapUnlock

440: 541 1A4 00039031 IsProcessorFeaturePresent

455: 556 1B3 00039031 LoadLibraryW

508: 611 1E8 00039031 OutputDebugStringW

547: 648 20F 00039031 RemoveDirectoryW

590: 691 23A 00039031 SetComputerNameW

592: 693 23C 00039031 SetConsoleCP

597: 698 241 00039031 SetConsoleOutputCP

601: 702 245 00039031 SetConsoleTitleW

605: 706 249 00039031 SetCurrentDirectoryW

645: 746 271 00039031 SetThreadLocale

678: 779 292 00039031 TryEnterCriticalSection
Вот это сюрприз! Все уникодеовые – функции под одной крышей! Поскольку, трудно поверить в идентичность реализаций LoadLibraryW и, скажем, DeleteFileW, остается предположить, что мы имеем дело с "заглушкой", которая ничего не делает, а только возвращает ошибку. Следовательно, в 9x действительно, функция LoadLibraryW не реализована.

Но, вернемся, к нашим баранам от которых нам пришлось так далеко отойти. Итак, вызываем отладчик, ставим бряк на LoadLibraryA, выходим из отладчика и терпеливо дожидаемся его всплытия. Должно ждать, к счастью, не приходится…
KERNEL32!LoadLibraryA                 

001B:77E98023 PUSH EBP

001B:77E98024 MOV EBP,ESP

001B:77E98026 PUSH EBX

001B:77E98027 PUSH ESI

001B:77E98028 PUSH EDI

001B:77E98029 PUSH 77E98054

001B:77E9802E PUSH DWORD PTR [EBP+08]
Отдаем команду "P RET" для выхода из LoadLibraryA (анализировать ее, в самом деле, ни к чему) и оказываемся в легко узнаваемом теле функции main.
001B:0040100B CALL [KERNEL32!LoadLibraryA]

001B:00401011 MOV [EBP-08],EAX           

001B:00401014 CMP DWORD PTR [EBP-08],00

001B:00401018 JZ 00401051

001B:0040101A PUSH 00405040

001B:0040101F CALL [KERNEL32!LoadLibraryA]

001B:00401025 MOV [EBP-08],EAX

001B:00401028 CMP DWORD PTR [EBP-08],00
Обратите внимание на содержимое регистра EAX – функция возвратила в нем адрес загрузки (на моем компьютере равный 0x10000000). Продолжая трассировку (<F10>), дождитесь выполнения второго вызова LoadLibraryA – не правда ли, на этот раз адрес загрузки изменился? (на моем компьютере он равен 0x0530000).

Приблизившись к вызову функции demo (в отладчике это выглядит как PUSH 00000777\ CALL [EBP-04] – "EBP-04" ни о чем не говорит, но вот аргумент 0x777 определенно что-то нам напоминает, - см. исходный текст fixupload.c), не забудьте переменить руку с <F10> на <F8>, чтобы войти внутрь функции.
001B:00531000 55 PUSH EBP

001B:00531001 8BEC MOV EBP,ESP

001B:00531003 8B4508 MOV EAX,[EBP+08]

001B:00531006 A330505300 MOV [00535030],EAX

001B:0053100B 5D POP EBP

001B:0053100C C3 RET
Вот оно! Системный загрузчик скорректировал адрес ячейки согласно базовому адресу загрузки самой DLL. Это, конечно, хорошо, да вот проблема – в оригинальной DLL нет ни такой ячейки, ни даже последовательности "A3 30 50 53 00", в чем легко убедиться контекстным поиском. Допустим, вознамерились бы мы затереть эту команду NOP-ми. Как это сделать?! Вернее, как найти это место в оригинальной DLL?

Обратим свой взор выше, на команды, заведомо не содержащие перемещаемых элементов – PUSH EBP/MOV EBP, ESP/MOV EAX,[EBP+08]. Отчего бы не поискать последовательность "55 8B EC xxx A3"? В данном случае это сработает, но если бы перемещаемые элементы были густо перемешаны "нормальными" ничего бы не вышло. Опорная последовательность оказалась бы слишком короткой для поиска и выдала бы множество ложных срабатываний.

Более изящно и надежно вычислить истинное содержимое перемещаемых элементов, вычтя их низ разницу между действительным и рекомендуемым адресом загрузки. В данном случае: 0x535030 /модифицированный загрузчиком адрес/ – (0x530000 /базовый адрес загрузки/ - 0x10000000 /рекомендуемый адрес загрузки/) == 0x10005030. Учитывая обратный порядок следования байт, получаем, что инструкция MOV [10005030], EAX в машинном коде должна выглядеть так: "A3 30 50 00 10". Ищем ее HIEW-ом, и чудо – она есть!


1   ...   4   5   6   7   8   9   10   11   ...   50

Похожие:

Техника хакерских атак Фундаментальные основы хакерства iconXss новичкам. Предназначение xss-атак
Приветствую Вас, уважаемые посетители Портала! Зовут меня DrWeb. Я хочу рассказать Вам о предназначении xss-атак, поскольку xss-уязвимости...
Техника хакерских атак Фундаментальные основы хакерства iconЭлективный курс Фундаментальные эксперименты и фундаментальные константы...
В ходе изучения данного элективного курса учащимся создаются условия для решения следующих образовательных задач
Техника хакерских атак Фундаментальные основы хакерства iconУрока информатики и икт в 9 классе по теме «Кодирование графической и мультимедийной информации»
В нем рассматриваются как фундаментальные основы представления информации в электронном виде и общее понятие композиции, так и конкретные...
Техника хакерских атак Фундаментальные основы хакерства iconЭлективный курс Фундаментальные эксперименты и фундаментальные константы...
В ходе изучения данного элективного курса учащимся создаются условия для решения следующих образовательных задач
Техника хакерских атак Фундаментальные основы хакерства iconУчебное пособие Дунаев И. В. г™ Основы лечебного массажа (техника...
Основы лечебного массажа (техника и методики). Учебное пособие.— М.: Ивц «Маркетинг»; Новосибирск: ООО «Издатель­ство юкэа», 2000.—480...
Техника хакерских атак Фундаментальные основы хакерства iconУчебное пособие Дунаев И. В. г™ Основы лечебного массажа (техника...
Основы лечебного массажа (техника и методики). Учебное пособие.— М.: Ивц «Маркетинг»; Новосибирск: ООО «Издатель­ство юкэа», 2000.—480...
Техника хакерских атак Фундаментальные основы хакерства iconАннотационный отчет Номер и наименование конкурсной темы. Блок "Ориентированные...
Блок "Ориентированные фундаментальные исследования". Раздел «Информационные технологии и электроника». Подраздел " Электроника и...
Техника хакерских атак Фундаментальные основы хакерства iconПрограмма фундаментальные основы человеческого общения. Иллюзия понимания....
Все, кто заинтересован в улучшении своих коммуникативных навыков, умения влиять, убеждать
Техника хакерских атак Фундаментальные основы хакерства iconВыставки
Тематика: сельскохозяйственная техника, в частности, тракторы, уборочная техника, системы орошения и другое
Техника хакерских атак Фундаментальные основы хакерства icon«Детско-юношеская спортивная школа» Опыт работы
В основе игры в баскетбол, как в любой другой спортивной игры лежит техника. Чем совершенней техника игры, тем выше её уровень. Техника...
Вы можете разместить ссылку на наш сайт:
Школьные материалы


При копировании материала укажите ссылку © 2014
shkolnie.ru
Главная страница