Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Техника и философия хакерских атак II (фрагмент [2/3]) часть 4

for (int idx=0;idx<String.GetLength()-1;idx++)
	RegCode+= ((WORD) sName[a]*sName[a+1] % 0x20) + 'A'; 

Листинг 39 восстановленный исходный код генератора регистрационных номеров

Остается лишь написать собственный генератор регистрационных номеров. Это можно сделать на любом симпатичном вам языке, например на ассемблере. На диске находится один вариант (file://CD/SRC/crackme.58DD2D69h/HACKGEN/KeyGen.asm). Ключевая процедура может выглядеть так:

; ГЕНЕРАЦИЯ РЕГИСТРАЦИОННОГО НОМЕРА
; ========================================================================
	MOV	ECX, [Nx]		; ECX := strlen(NameString)
	SUB	ECX, 2		; выкусываем перенос строки
	DEC	ECX		; уменьшаем длину строки на единицу
	MOV	EBX, 20h		; магическое число
	LEA	ESI, hello	; указатель на буфер с именем пользователя
	LEA	EDI, buf_in	; ^ указатель на буфер для генерации

; ЯДРО ГЕНЕРАТОРА
; ========================================================================
gen_repeat:		;<<<---------------------------------------------; CORE
	LODSW		; читаем слово				; CORE
	MUL	AH	; AX := NameString[ESI]*NameString[ESI+1] ; CORE
	XOR	EDX, EDX	; EDX := NULL				; CORE
	DIV	EBX	; DX := NameString[ESI]*NameString[ESI+1] % 1Ah ; CORE
	ADD	EDX, 'A'	; переводим в символ			; CORE
			;					; CORE
	XCHG	EAX, EDX	;					; CORE
	STOSB		; записываем результат			; CORE
	DEC	ESI	; на символ назад				; CORE
LOOP	gen_repeat	; ---- цикл --------------------------------->>> ; CORE

Листинг 40 ключевая процедура генератора регистрационных номеров, написанная на ассемблере

Испытаем написанный генератор. Запустив откомпилированный файл KeyGen.exe на выполнение, введем в качестве регистрационного имени какую ни будь текстовую строку (например, свое собственное имя или псевдоним), - не пройдет и секунды как генератор выдаст подходящий regnum в ответ. В частности, имени "Kris Kaspersky" соответствует следующий регистрационный код: "GCLAALTQQ[WRT"


Рисунок 5 - 0х00С демонстрация работы ключеделки

Генератор успешно работает и вычисляет правильные регистрационные номера. Однако, вводить регистрационный номер вручную не только утомительно, но и неэлегантно. Да, можно скопировать его и через буфер обмена, но все равно возня будет еще та. В конечном итоге, компьютер на то и придуман, чтобы служить пользователю, но не наоборот. Идеальный crack - это такой crack который не докучает пользователю теми вопросами, ответ на которые знает сам, равно как и не требует от последнего никаких действий, которые он может выполнить и самостоятельно. Единственное, что требует такой crack - своего запуска. Короче, хорошая программа должна заботиться о себе сама!

Первое, что приходит на ум: просто пропадчить защитный код на диске или в памяти. В предыдущей главе мы как раз разбирали как это сделать. Однако, падчики, во-первых, просто вопиюще незаконны, во-вторых, крайне чувствительны к версии билда. Генераторы регистрационных номеров, напротив, весьма мирно уживаются с уголовным кодексом, поскольку они не подделывают, а именно генерируют регистрационный номер на основе имени, введенного пользователем (см. эпиграф) и их написание столь же "незаконно", сколько открытие мастерской по изготовлению дубликатов ключей например. К тому же алгоритм генерации регистрационного номера если и изменяется, то во всяком случае не в каждой версии программы .

Во времена старушки MS-DOS эта проблема решалась перехватом прерывания int 16h с целью эмуляции ввода с клавиатуры. Ломалка, грубо говоря, прикидывалась пользователем и подсовывала защищенной программе сначала имя, а затем и сгенерированный регистрационный номер. От самого же пользователя не требовалось ничего, кроме запуска такой программы. Ну разве не красота? К сожалению, с переходом на Windows прямой контроль над прерываниями оказался безвозвратно утерян и все трюки старой Лисы перестали работать:

Но, "мало того, что их сосед в жилом доме свинью держит, так он еще и круглосуточно над ней измывается..." . Незадачливого музыканта подвела хорошая межквартирная слышимость (читай: хреновая звукоизоляция). Так вот, Windows с точки зрения безопасности - та же хрущоба и слышимость в ней о-го-го! Архитектура подсистемы пользовательского интерфейса, достающаяся NT/9x в наследство от незаконно рожденной Windows 1.0, неотделима от концепции сообщений (messages) - эдакой собачей будке, перенесенной с заднего двора на самое видное место. Любой процесс в системе может посылать сообщения окнам любого другого процесса, что позволяет ему управлять этими окнами по своему усмотрению. Хотите "подсмотреть" содержимое чужого окна? Пожалуйста! Пошлите ему SendMessage с WM_GETTEXT и все дела! Хотите послать окну свою строку с приветствием? Нет проблем, - SendMessage в купе с WM_SETTEXT спасут отца русской демократии! Аналогичным образам вы можете нажимать на кнопки, двигать мышь, раскрывать пункты меню, словом полностью контролировать работу приложения. Самое интересное, что уровень привилегий при этом никак не проверяется, - процесс с гостевыми правами может свободно манипулировать окнами, принадлежащими процессу-администратору. Знаете, в NT/w2k есть такое забавное окошко "запуск программы от имени другого пользователя", обычно используемое для запуска привилегированных приложений из сеанса непривилегированного пользователя? Ну вот например захотели проверить вы свой жесткий диск на предмет целостности файловой структуры, а перезапускать систему под "Администратором" вам лень (точнее, просто не хочется закрывать все активные приложения). На первый взгляд никакой угрозы для безопасности в этом нет, ведь "запуск программы от имени другого пользователя" требует явного ввода пароля! А вот получи треска гранату, - любое злопакостное приложение сможет перехватить ваш пароль только так! Причем, речь идет не о какой-то непринципиальной недоработке, которая легко устранима простой заплаткой (в просторечии называемой "падчем"). Нет! Все так специально и задумывалось. Не верите? Откроем Рихтера ":система отслеживает сообщения WM_SETTEXT и обрабатывает их не так, как большинство других сообщений. При вызове SendMessage внутренний код функции проверяет, не пытаетесь ли вы послать сообщение WM_SETTEXT. Если это так, функция копирует строку из вашего адресного пространства в блок памяти и делает его доступным другим процессам. Затем сообщение посылается потоку другого процесса. Когда поток-приемник готов к обработке WM_SETTEXT, он определяет адрес общего блока памяти (содержащего новый текст окна) в адресном пространстве своего процесса. Параметру lParam пристраивается значение именного этого адреса, и WM_SETTEXT направляется нужной оконной процедуре. Не слишком ли накручено, а?" Выходит, разработчики оконной подсистемы искусственно и крайне неэлегантно обошли подсистему защиты Windows, разделяющую процессы по их адресным пространствам. Естественно, это делалось отнюдь не с целью диверсии, - просто запрети Microsoft посылку сообщений между процессами куча существующих приложений (написанных большей частью под Windows 3.x) тут же перестала бы работать! А значит, эмуляция ввода с клавиатуры жила, жива и будет жить!

Единственное, что нужно знать - так это дескриптор (handle) окна, которого вы хотите "осчастливить" своим сообщением. Существует множество путей получить эту информацию. Можно например воспользоваться API-функцией FindWindow, которая возвращает дескриптор окна по его названию (текстовой строке, красующейся в заголовке) или тупо переворошить все окна одно за другим, в надежде что рано или поздно среди них встретиться подходящее. Перечисление окон верхнего уровня осуществляется функцией EnumWindows, а дочерних окон (к которым диалоговые элементы управления как раз и принадлежат) - EnumChildWindows.

Собственно, получить дескриптор главного окна ломаемого приложения - не проблема, ведь мы знаем его имя, которое в большинстве случаев однозначно идентифицирует данное окно среди прочих запущенных приложений. С дочерними окнами справиться не в пример сложнее. Ладно, кнопки еще можно распознать по их надписи (получаем дескрипторы всех дочерних окон вызовом EnumChildWindows, а затем посылаем каждому из них сообщение WM_GETTEXT с требованием сказать как кого зовут, после чего нам останется лишь сопоставить дескрипторы кнопок с их названиями). К сожалению с окнами редактирования такой фокус не пройдет, ибо по умолчанию они вообще не содержат в себе никакой информации, - вот и разбирайся это окно для ввода регистрационного имени или номера?

На помощь приходит тот факт, что порядок перечисления окон всегда постоянен и не меняется от одной операционной системе к другой. То есть, определив назначения каждого из дочерних окон экспериментально (или с помощью шпионских средств типа Spyxx из комплекта SDK) мы можем жестко прописать их номера в своей программе. Например, применительно к crackme.58DD2D69h это может выглядеть так: запускаем наш любимый soft-ice и даем команду "HWND" для выдачи списка всех окон, включая дочерние, зарегистрированных в системе.

0B0416    #32770 (Dialog)              6C291B81    43C CRACKME_
  0B0406    Button                      77E18721    43C CRACKME_
  0B040A    Static                      77E186D9    43C CRACKME_
  0D0486    Edit                        6C291B81    43C CRACKME_
  0904C6    Static                      77E186D9    43C CRACKME_
  0D0412    Edit                        6C291B81    43C CRACKME_
  0A047C    Button                      77E18721    43C CRACKME_

Листинг 41 определение порядка перечисления окон с помощью soft-ice

Ага! Вот они окна редактирования (см. текст выделенный жирным шрифтом), - третье и пятое по счету дочернее окно в списке перечисления. Одно из них наверняка принадлежит строке регистрационного имени, а другое - регистрационного номера. Но как узнать какое кому? Воспользовавшись ключом xc, заставим sof-ice выдать более подробную информацию по каждому из окон:

HWND -xc
	Hwnd		: 0D0486    (A0368EF8)
	Class Name	: Edit
	Module		: CRACKME_
	Window Proc	: 6C291B81 (SuperClassed from: 77E19896)
	Win Version	: 0.00
	Parent		: 0B0416    (A0368A88)
	Next		: 0904C6    (A0368FB8)
	Style		:
	Window Rect	: 387, 546, 615, 566 (228 x 20)
	Client Rect	: 2, 2, 226, 18 (224 x 16)
	:
	Hwnd		: 0D0412    (A03690A8)
	Class Name	: Edit
	Module		: CRACKME_
	Window Proc	: 6C291B81 (SuperClassed from: 77E19896)
	Win Version	: 0.00
	Parent		: 0B0416    (A0368A88)
	Next		: 0A047C    (A0369168)
	Style 		:
	Window Rect	: 387, 572, 615, 592 (228 x 20)
	Client Rect	: 2, 2, 226, 18 (224 x 16)

Листинг 42 получение координат окон редактирования (строка с координатами, выделена жирным шрифтом, а координаты верхнего левого угла окна взяты в рамочку)

Как легко установить по координатам вершин окон, первое из них находится на 26 пикселей выше другого (546 против 572), следовательно первое окно - окно регистрационного имени, а второе - окно регистрационного номера.

Теперь, когда порядковые номера окон редактирования известны можно накрапать следующую несложную программку:

// ПЕРЕЧИСЛЕНИЕ ДОЧЕРНИХ ОКОН crackme
// ===========================================================================
// получаем хэндлы всех интересующих нас окон
// (порядок окон определяем либо экспериментально, либо тестовым прогоном
// с отладочным выводом информации по каждому из окон)
BOOL CALLBACK EnumChildWindowsProc(HWND hwnd,LPARAM lParam)
{
	static N = 0;

	switch(++N)
	{
		case 3:	// окно с именем пользователя
				username = hwnd;
				break;

		case 4:	// text со строкой "reg. num."
				hackreg = hwnd;
				break;

		case 5:	// окно для ввода регистрационного номера
				regnum = hwnd;
				break;

		case 6:	// конопка ввода
				input_but = hwnd;
				return 0;
	}
	return 1;
}

Листинг 43 определение дескрипторов элементов управления по их порядковым номерам в списке перечисления

Теперь перейдем непосредственно к технике эмуляции ввода. Ну, ввод/вывод текста в окна редактирования больших проблем не вызывает: WM_SETTEXT/WM_GETTEXT и все пучком, а вот "программно" нажать на кнопку несколько сложнее. Но ведь вам же хочется, чтобы программа не только ввела в соответствующие поля всю необходимую регистрационную информацию, но и самостоятельно долбанула по <Enter>, чтобы закончить ввод?!

Как показывает практика, посылка сообщения BM_SETSTATE элементу управления типа "кнопка" не приводит к ее нажатию. Почему? Наша ошибка заключается в том, что для корректной эмуляции ввода мы во-первых, должны установить фокус (WM_SETFOCUS), а после перевода кнопки в состояние "нажато" этот фокус убить (WM_KILLFOCUS), ведь, как известно даже желторотым пользователям, кнопки срабатывают не в момент их нажатия, но в момент отпускания. Не верите? Поэкспериментируйте с любым приложениям и убедитесь в справедливости сказанного. Кстати, забавный трюк: если под NT/w2k в сообщение WM_KILLFOCUS передать недействительный дескриптор окна, получающего на себя бразды правления, то операционная система по понятным соображениям не передаст фокус несуществующему окну, но у активного окна фокус все-таки отберет. Windows 9x, напротив, оставляет фокус активного окна неизменным! Вот такая разница между двумя операционными системами. Еще одна делать на последок. Если в роли убийцы фокуса выступает функция SendMessage по поток, эмулирующий ввод, блокируется вплоть до того момента, пока обработчик нажатия кнопки не возвратит циклу выборки сообщений своего управления. Чтобы этого не произошло, - используйте функцию PostMessage, которая посылает убийцу фокуса и, не дожидаясь от него ответа, как ни в чем не бывало продолжает выполнение.


Рисунок 6 - 0х00D "автоматическое" считывание имени пользователя, ввод регистрационного номера и эмуляция нажатия на клавишу "ввод"

Испытаем наш автоматический регистратор? (file://CD/SRC/crack-me58DD2D69h/HACKGEN2/autocrack.c). Запустив защищенную программу и при желании заполнив поле имени пользователя (если его оставить пустым, автоматический регистратор использует имя по умолчанию), мы дрожащей от волнения рукой запускаем autocrack.exe: Держите нас! Это сработало! Вот это автоматизация! Вот это хакерство! Вот это мы понимаем!

как сделать исполняемые файлы меньше

Даже будучи написанным на чистом ассемблере, исполняемый файл генератора регистрационных номеров занимает целых 16 килобайт! Хорошенький монстр, нечего сказать! Хакерам, чей первый компьютер был IBM PC с процессором Pentium-4, может показаться, что 16 килобайт это просто фантастически мало, однако еще в восьмидесятых годах существовали компьютеры с объемом памяти равным этому числу! Впрочем, зачем нам так далеко ходить, - откроем первое издание настоящей книги: "Без текстовых строк исполняемый файл [генератора] занимает менее пятидесяти байт и еще оставляет простор для оптимизации". Сравните пятьдесят байт и шестнадцать килобайт, - переход с MS-DOS на Windows увеличил аппетит к памяти без малого в триста раз!

Вообще-то, с чисто потребительской точки зрения никакой проблемы в этом нет. Размеры жестких дисков сегодня измеряются сотнями гигабайт и лишний десяток килобайт особой погоды не делает. К тому же, наш исполняемый файл замечательно ужимается pkzip'ом до семисот с небольшим байт, что существенно для его передачи по медленным коммуникационным сетям, - да только где такие нынче найдешь?!

С чисто же эстетической точки зрения держать у себя такой файл действительно нехорошо. Обиднее всего, что на 99% генератор состоит из воздуха и воды, - нулей, пошедших на вырывание секций по адресам, кратным 4Кб. Три секции (кодовая секция .text, секция данных .data и таблица импорта .itable) плюс PE-заголовок, - вместе они эти самые 16 Кб и создают. Полезного же кода в исполняемом файле просто пшик - немногим менее двухсот байт. Конечно, двести это не пятьдесят и с переходом на Windows мы все равно проигрываем и в компактности, и в скорости, но все-таки кое-какой простор для оптимизации у нас имеется.

Начнем с того, что прикажем линкеру использовать минимальную кратность выравнивания из всех имеющихся, - составляющую всего четыре байта. Указывав в командной строке ключ "/ALIGN:4" мы сократим размер исполняемого файла с 16.384 до 1.032 байт! Согласитесь, что с таким размером уже можно жить!

Причем, это далеко не предел оптимизации! При желании можно: а) выкинуть MS-DOS stub, который все равно бесполезен; б) подчистить IMAGE_DIRECTORY; в) использовать незадействованные поля OLD EXE/PE-заголовков для хранения глобальных переменных; г) объединить секции .text, .data, .rdata в одну общую секцию, сведя тем самым эффективную кратность выравнивая к одному и высвободив еще трохи места за счет ликвидации двух секций. Словом, возможности для самовыражения под Windows все-таки имеются!

Перехват WM_GETTEXT

Использование функций GetWindowText и GetDlgItemText - не единственный путь для извлечения содержимого окна редактирования. Как было показано в предыдущей главе, ту же самую операцию можно осуществить и посылкой сообщения WM_GETTEXT (и некоторые разработчики защитных механизмов именно так и поступают). Достоинство этого метода в том, что он легко и элегантно отсекает большую армию wannabe-хакеров, ничего не смыслящие ни в программировании, ни в операционных системах, но прочитавшие FAQ "ED!SON's Windows 95 Cracking Tutorial v1.oo" и мало помалу пытающие что ни будь взломать.

Чтение регистрационного имени пользователя в обход функций GetWinowsText/ GetDlgItemText ставит таких неопытных хакеров в тупик. Попытка поставить точку останова на SendMessageA так же ничего не дает - уж слишком интенсивно она вызывается и, если не предпринять дополнительных ухищрений, мы просто утонем в море этих вызовов! Как автоматически отсечь все лишние срабатывания? Обратимся к прототипу функции SendMessage. Согласно Platform SDK он выглядит так:

LRESULT SendMessage(
  HWND   hWnd,		// handle of destination window (дескриптор окна-получателя)
  UINT   Msg,		// message to send		  (посылаемое сообщение)
  WPARAM wParam,	// first message parameter	  (первый параметр сообщения)
  LPARAM lParam	// second message parameter	  (второй параметр сообщения)
);

Листинг 44 прототип функции SendMessage

Пара аргументов hWnd + Msg позволяют однозначно идентифицировать любое действие, происходящее в системе. Применительно к данному случаю, чтобы перехватить обращение к строке редактирования мы должны узнать дескриптор соответствующего ей окна. А как его узнать? Даем отладчику команду "HWND" и смотрим:

:hwnd
Handle    Class                         WinProc    TID  Module
240428    #32770 (Dialog)              6C291B81    400 crackme
  110468    Edit                        6C291B81    400 crackme
  0B04A4    Button                      77E18721    400 crackme

Листинг 45 определение дескриптора окна редактирования под soft-ice

Вот он, дескриптор! (см. обведенное рамкой число в самой первой колонке слева). Следовательно, нас будут интересовать все вызовы SendMessage(0x110468, WM_GETTEXT,:), а все остальные мы можем и проигнорировать. Интеллектуальность ранних версий soft-ice была недостаточно велика для автоматизации столь ювелирной работы и "игнорировать" лишние вызовы хакерам приходилось вручную. Хакеры, начинающие свой жизненный путь с soft ice 3.25 или выше, наверное, и не представляют: каким каторжным был этот труд! Сегодня же практически все отладчики оснащены поддержкой условных точек останова и львиную долю рутиной работы берут на себя. Давайте попробуем "объяснить" отладчику нашу ситуацию с WM_GETTEXT и посмотрим справиться ли он с ней или нет. К сожалению, soft-ice не поддерживает "прозрачной" адресации аргументов и потому их смещения относительно вершины стека мы должны вычислять самостоятельно. Впрочем, невелика проблема! Памятуя о том, что все API-функции придерживаются соглашения stdcall, т. е. передают свои аргументы справа налево, можно легко рассчитать, что дескриптор окна лежит на четыре байта ниже ESP, а непосредственно под ним располагается и код посылаемого окну сообщения. Следовательно, команда установки соответствующего точки останова будет выглядеть приблизительно так: "bpx SendMessageA IF (*(esp + 4) == 110468) && ( *(esp+8) == WM_GETTEXT)", однако, это не единственный вариант. Если хотите выражение "*(esp+4)" можете заменить на синтаксически более короткое, но полностью эквивалентное по смыслу: "esp->4". Более подробную информацию о формате условных точек останова вы найдете в прилагаемой к отладчику документации. Здесь же нас в первую очередь интересует то, что установленная нами точка останова действительно срабатывает и срабатывает правильно:

:bpx SendMessageA IF (esp-> == 110468) && (esp->8 == WM_GETTEXT)
x
/* нажимаем на кнопку "ENTER" ломаемого приложения */
Break due to BPX USER32!SendMessageA IF
    ((*((ESP+4))==0x140430)&&((ESP->8)==0xD)) (ET=2.83 seconds)
USER32!SendMessageA
001B:77E1A57C	PUSH	EBP
001B:77E1A57D	MOV	EBP,ESP
001B:77E1A57F	PUSH	ESI
001B:77E1A580	MOV	ESI,[EBP+0C]

Листинг 46 перехват чтения содержимого окна путем посылки ему WM_GETTEXT

Адрес буфера-приемника считываемой строки лежит в стеке на 10h байт ниже его вершины и при желании мы можем его узнать:

:? esp->10
0012FA40  0001243712  " ·@"

Листинг 47 определение адреса буфера-приемника, в который помещается считываемая строка

В ответ на команду "? esp->10" soft-ice сообщает "12FA40". Запомним (записав на бумажке) полученное смещение мы "выпрыгиваем" из функции по команде "P RET" и смотрим содержимое буфера:

:p ret
:d 12FA40
0010:0012FA40 4B 72 69 73 20 4B 61 73-70 65 72 73 6B 79 00 00  Kris Kaspersky..
0010:0012FA50 38 FA 12 00 40 27 2F 00-BC FA 12 00 49 1D E6 77  8...@'/.....I..w
0010:0012FA60 D8 23 29 6C 00 23 40 00-11 01 00 00 9C FA 12 00  .#)l.#@.........
0010:0012FA70 AE 22 29 6C 54 FE 12 00-EA 03 00 00 00 00 00 00  .")lT...........

Листинг 48 просмотр содержимого буфера

Это сработало! Мы рассекретили адрес считываемой строки и теперь нам ничего не стоит поставить на него точку останова для отслеживания всех попыток обращения к последнему (как вариант: можно просто немного потрассировать код в надежде на то, что защитный механизм окажется где-то поблизости).

Вообще-то, для перехвата сообщений существует специальная команда - "BMSG" (Break on MesSaGe), но по малопонятным для меня причинам, в некоторых версиях soft-ice она не работает, выдавая сообщение "Invalid window handle" даже при попытке установить точку останова на заведомо корректный дескриптор окна!


часть 1 | часть 2 | часть 3 | часть 4

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог