18c0693f

Способ 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-функций, перечень которых хотя и велик, но все же конечен и известен заранее.