Способ 3. Бряк на сообщения
Любая завершенная дисциплина имеет свои штампы, свои модели, свое влияние на обучающихся.
Френк Херберт "Дюна"
Если у Вас еще не закружилась голова от количества выпитого во время хака пива, с вашего позволения мы продолжим. Каждый, кто хоть однажды программировал под Windows, наверняка знает, что в Windows все взаимодействие с окнами завязано на сообщениях. Практически все оконные API-функции на самом деле представляют собой высокоуровневые "обертки", посылающие окну сообщения. Не является исключением и GetWindowTextA, – аналог сообщения WM_GETTEXT.
Отсюда следует – чтобы считать текст из окна вовсе не обязательно обращаться к GetWindowTextA, - можно сделать это через SendMessageA(hWnd, WM_GETTEXT, (LPARAM) &buff[0]). Именно так и устроена защита в примере "crack 02". Попробуйте загрузить его и установить бряк на GetWindowTextA (GetDlgItemTextA). Что, не срабатывает? Подобная мера используется разработчиками для запутывания совсем уж желторотых новичков, бегло изучивших пару faq по хаку и тут же бросившихся в бой.
Так может, поставить бряк на SendMessageA? В данном случае в принципе можно, но бряк на сообщение WM_GETTEXT – более универсальное решение, срабатывающее независимо от того, как читают окно.
Для установки бряка на сообщение в Айсе предусмотрена специальная команда – "BMSG", которой мы и пользовались в первом издании этой книги. Но не интереснее ли сделать это своими руками?
Как известно, с каждым окном связана специальная оконная процедура, обслуживающая это окно, т.е. отвечающая за прием и обработку сообщений. Вот если бы узнать ее адрес, да установить на него бряк! И это действительно можно сделать! Специальная команда "HWND" выдает всю информацию об окнах указанного процесса.
<Ctrl-D>
:addr crack02
:hwnd crack02
Handle Class WinProc TID Module
050140 #32770 (Dialog) 6C291B81 2DC crack02
05013E Button 77E18721 2DC crack02
05013C Edit 6C291B81 2DC crack02
05013A Static 77E186D9 2DC crack02
Быстро обнаруживает себя окно редактирования, с адресом оконной процедуры 0x6C291B81. Поставим сюда бряк? Нет, еще не время – ведь оконная процедура вызывается не только при чтении текста, а гораздо чаще. Как бы установить бряк на то, что нам нужно, отсеяв все остальные сообщения? Для начала изучим прототип этой функции:
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
Как нетрудно подсчитать, в момент вызова функции, аргумент uMsg – идентификатор сообщения будет лежать по смещению 8 относительно указателя вершины стека ESP. Если он равен WM_GETTEXT (непосредственное значение 0xD) – недурно бы всплыть!
Вот и настало время познакомиться с условными бряками. Подробнее об их синтаксисе рассказано в прилагаемой к отладчику документации. А, впрочем, программисты, знакомые Си вряд ли к ней обратится, ибо синтаксис лаконичен и интуитивно - понятен.
:bpx 6C291B81 IF (esp->8)==WM_GETTEXT
:x
Выходим их отладчика, вводим какой-нибудь текст в качесвте пароля, скажем "Hello", нажимаем <ENTER>, отладчик тут же "всплывает"
Break due to BPX #0008:6C291B81 IF ((ESP->8)==0xD) (ET=2.52 seconds)
Вот, он хвост Тигры и уши плюшевого медведя! Остается определить адрес буфера, в который возвращается считанная строка. Начинаем соображать: указатель на буфер передается через аргумент lParam (см. в SDK описание WM_GETTEXT), а сам lParam размещается в стеке по смещению 0x10, относительно ESP:
адрес возврата ß ESP
hwnd ß ESP + 0x4
uMsg ß ESP + 0x8
wParam ß ESP + 0xC
lParam ß ESP + 0x10
Даем команду вывода этого буфера в окно данных, выходим из оконной процедуры по P RET и… видим только что введенный нами текст "Hello"
:d *(esp+10)
:p ret
0023: 0012EB28 48 65 6C 6C 6F 00 05 00-0D 00 00 00 FF 03 00 00 Hello...........
0023:0012EB38 1C ED 12 00 01 00 00 00-0D 00 00 00 FD 86 E1 77 ...............w
0023:0012EB48 70 3C 13 00 00 00 00 00-00 00 00 00 00 00 00 00 p<..............
0023:0012EB58 00 00 00 00 00 00 00 00-98 EB 12 00 1E 87 E1 77 ...............w
:bpm 23:12EB28
Установив точку останова, мы ловим одно откровенно "левое" всплытие отладчика (это видно по явно не "юзерскому" значению селектора CS, равного 8) и, уже тянем руку, чтобы нажать 'x' продолжив отслеживание нашего бряка, как вдруг краем глаза замечаем….
0008:A00B017C 8A0A MOV CL,[EDX]
0008:A00B017E 8808 MOV [EAX],CL
0008:A00B0180 40 INC EAX
0008:A00B0181 42 INC EDX
0008:A00B0182 84C9 TEST CL,CL
0008:A00B0184 7406 JZ A00B018C
0008:A00B0186 FF4C2410 DEC DWORD PTR [ESP+10]
0008:A00B018A 75F0 JNZ A00B017C
Эге, буфер-то не "сквозной", - система не отдает его "народу", а копирует в другой буфер. Это видно потому, как из указателя на "наш" буфер EDX символ копируется в CL (то, что EDX – указатель на "наш" буфер следует из того, что он вызвал всплытие отладчика), а из CL он копируется в [EAX], где EAX – какой-то указатель (о котором пока мы еще не можем сказать ничего определенного). Далее – оба указателя увеличиваются на единицу и CL (последний считанный символ) проверяется на равенство нулю – если конец строки не достигнут, то все повторяется. Что ж, суждено нам следить сразу за двумя буферами – ставим еще один бряк.
:bpm EAX
:x
На втором бряке отладчик вскорости всплывает, и мы узнаем нашу родную процедуру сравнения. Ну, а дальнейшее – дело техники.
001B:004013F2 8A1E MOV BL,[ESI]
001B:004013F4 8ACA MOV CL,DL
001B:004013F6 3AD3 CMP DL,BL
001B:004013F8 751E JNZ 00401418
001B:004013FA 84C9 TEST CL,CL
001B:004013FC 7416 JZ 00401414
001B:004013FE 8A5001 MOV DL,[EAX+01]
001B:00401401 8A5E01 MOV BL,[ESI+01]
В Windows 9x обработка сообщений реализована несколько иначе, чем в NT. В частности, оконная процедура окна редактирования находится в 16-разрядном коде. А это – сегментная модель памяти (треска хвостом вперед под хвост Тигре) a la сегмент : смещение. Представляется любопытным механизм передачи адреса – в какой же параметр засунут сегмент? Чтобы ответить на это, взглянем на отчет Айса:
Break due to BMSG 0428 WM_GETTEXT (ET=513.11 milliseconds) hWnd=0428 wParam=0666 lParam=28D70000 msg=000D WM_GETTEXT ^ ^ ^--^^--^ | | сегмент/ \смещение дескриптор окна | | макс. кол-во символов для чтения
Адрес целиком умещается в 32-разрядном аргументе lParam – 16-разрядный сегмент и 16-разрядное смещение. Посему, точка останова должна выглядеть так: "bpm 28D7:0000"