Способ 2. Бряк на функции ввода пароля
Вы боитесь творить, потому что творения ваши отражают вашу истинную суть.
Фрэнк Херберт "Ловец душ"
При всем желании метод прямого поиска пароля в памяти элегантным назвать нельзя, да и практичным тоже. А, собственно, зачем искать сам пароль, спотыкаясь об беспорядочно разбросанные буфера, когда можно поставить бряк непосредственно на функцию, его считывающую? Хм, можно и так… да вот угадать какой именно функцией разработчик вздумал читать пароль, вряд ли будет намного проще.
На самом деле одно и тоже действие может быть выполнено всего лишь несколькими функциями и их перебор не займет много времени. В частности, содержимое окна редактирование обычно добывается либо GetWinodowTextA (что чаще всего и происходит), либо GetDlgItemTextA (а это – значительно реже).
Раз уж речь зашла за окна, запустим наш GUI "крякмис" и установим точку останова на функцию GetWindowTextA ("bpx GetWinodwTextA"). Поскольку, эта функция – системная, точка останова будет глобальной, т.е. затронет все приложения в системе, поэтому, заблаговременно закройте все лишнее от греха подальше. Если установить бряк до запуска "крякмиса", то мы словим несколько ложных всплытий, возникающих вследствие того, что система сама читает содержимое окна в процессе формирования диалога.
Вводим какой-нибудь пароль ("KPNC Kaspersky++" по обыкновению), нажимаем <ENTER> - отладчик незамедливает всплыть:
USER32!GetWindowTextA
001B:77E1A4E2 55 PUSH EBP
001B:77E1A4E3 8BEC MOV EBP,ESP
001B:77E1A4E5 6AFF PUSH FF
001B:77E1A4E7 6870A5E177 PUSH 77E1A570
001B:77E1A4EC 68491DE677 PUSH 77E61D49
001B:77E1A4F1 64A100000000 MOV EAX,FS:[00000000]
001B:77E1A4F7 50 PUSH EAX
Во многих руководствах по взлому советуется тут же выйти из функции по P RET, мол, что ее анализировать-то, но не стоит спешить! Сейчас самое время выяснить: где расположен буфер вводимой строки и установить на него бряк. Вспомним какие аргументы и в какой последовательности принимает функция (а, если не вспомним, то заглянем в SDK):
int GetWindowText(
HWND hWnd, // handle to window or control with text
LPTSTR lpString, // address of buffer for text
int nMaxCount // maximum number of characters to copy
);
Может показаться, раз программа написана на Си, то и аргументы заносятся в стек по Си-соглашению. А вот и нет! Все API функции Windows всегда вызываются по Паскаль- соглашению, на каком бы языке программа ни была написана. Таким образом, аргументы заносятся в стек слева направо, а последним в стек попадает адрес возврата. В 32-разрядной Windows все аргументы и сам адрес возврата занимают двойное слово (4 байта), поэтому, чтобы добраться до указателя на строку, необходимо к регистру указателю вершины стека (ESP) добавить восемь (одно двойное слово на nMaxCount, другое – на сам lpString). Нагляднее это изображено на рис. 3
Рисунок 3 0х02 Состояние стека на момент вызова GetWindowsText
Получить содержимое ячейки по заданному адресу в Айсе можно с помощью оператора "звездочка", вызов которого в нашем случае выглядит так (подробнее – см. документацию, прилагаемую к отладчику):
:d *(esp+8)
0023:0012F9FC 1C FA 12 00 3B 5A E1 77-EC 4D E1 77 06 02 05 00 ....;Z.w.M.w....
0023:0012FA0C 01 01 00 00 10 00 00 00-01 00 2A C0 10 A8 48 00 ..........*...H.
0023:0012FA1C 10 9B 13 00 0A 02 04 00-E8 3E 2F 00 00 00 00 00 .........>/.....
0023:0012FA2C 01 02 04 00 83 63 E1 77-08 DE 48 00 0A 02 04 00 .....c.w..H.....
В буфере мусор – так и следовало ожидать, ведь строка еще не считана. Давайте выйдем из функции по p ret и посмотрим что произойдет (только потом уже нельзя будет пользоваться конструкцией d *esp+8, т.к.
после выхода из функции аргументы будут вытолкнуты из стека):
: p ret
:d 0012F9FC
0023:0012F9FC 4B 50 4E 43 20 4B 61 73-70 65 72 73 6B 79 2B 2B KPNC Kaspersky++
0023:0012FA0C 00 01 00 00 0D 00 00 00-01 00 1C 80 10 A8 48 00 ..............H.
0023:0012FA1C 10 9B 13 00 0A 02 04 00-E8 3E 2F 00 00 00 00 00 .........>/.....
0023:0012FA2C 01 02 04 00 83 63 E1 77-08 DE 48 00 0A 02 04 00 .....c.w..H.....
ОК, это действительно тот буфер, который нам нужен. Ставим бряк на его начало и дожидаемся всплытия. Смотрите, с первого же раза мы очутились именно так, где и надо (узнаете код сравнивающей процедуры?):
001B:004013E3 8A10 MOV DL,[EAX]
001B:004013E5 8A1E MOV BL,[ESI]
001B:004013E7 8ACA MOV CL,DL
001B:004013E9 3AD3 CMP DL,BL
001B:004013EB 751E JNZ 0040140B
001B:004013ED 84C9 TEST CL,CL
001B:004013EF 7416 JZ 00401407
001B:004013F1 8A5001 MOV DL,[EAX+01]
Замечательно! Вот так, безо всяких ложных срабатываний, элегантно, быстро и красиво мы победили защиту!
Этот способ – универсален и впоследствии мы еще не раз им воспользуемся. Вся соль – определить ключевую функцию защиты и поставить на нее бряк. Под Windows все "поползновения" (будь то обращения к ключевому файлу, реестру и т.д.) сводятся к вызову API-функций, перечень которых хотя и велик, но все же конечен и известен заранее.