ЗдорОво!
Тебя никогда не посещало желание написать какую-нибудь прогу, которая
не делает ничего полезного, но в то же время в работе такой проги очень много интересного?
Да? Так не одинок, многие программисты любят такие вещи. Уж с давних времен кто демки пишет, кто
вирусы, кто самоудаляющиеся программы... О! Сегодня мы и поговорим о том, как можно реализовать
самоудаление. Внимай!.. Самоудалятся мы будем под Win. И, сначала, для тех, кому важно короткое, простое и быстрое решение,
я приведу пример "легального" самоудаления (поддерживаемого Win):
SHELLEXECUTEINFO sei;
TCHAR szModule [MAX_PATH],szComspec[MAX_PATH],szParams [MAX_PATH];
if((GetModuleFileName(0,szModule,MAX_PATH)!=0) &&
(GetShortPathName(szModule,szModule,MAX_PATH)!=0) &&
(GetEnvironmentVariable("COMSPEC",szComspec,MAX_PATH)!=0))
{
lstrcpy(szParams,"/c del ");
lstrcat(szParams, szModule);
lstrcat(szParams, " > nul");
sei.cbSize = sizeof(sei);
sei.hwnd = 0;
sei.lpVerb = "Open";
sei.lpFile = szComspec;
sei.lpParameters = szParams;
sei.lpDirectory = 0;
sei.nShow = SW_HIDE;
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
if(ShellExecuteEx(&sei))
{
SetPriorityClass(sei.hProcess,IDLE_PRIORITY_CLASS); //Приостанавливаем удаляющий процесс
SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS); //Ускоряем наш процесс
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL); //Ускоряем наш поток
SHChangeNotify(SHCNE_DELETE,SHCNF_PATH,szModule,0);
return TRUE;
}
}
Продолжаем... Ты уже понял, что в этом коде ничего интересного нет, и уже успел разочароваться, Но!
Давай поразмыслим, как можно по-хитрому реализовать самоудаление?
Очевидно, что вызвать DeleteFile для себя самих можно, вот только толку от этого... 🙂
А что если вызвать DeleteFile для нашей самоудалялки из другой проги - це ж друге дило!
А как это сделать? Стоп, стоп, стоп, не надо плодить никаких DLL'ок. Как может
выглядеть код, удаляющий какой-либо файл? Примерно так:
push offset prog_name
call DeleteFileA
call ExitProcess
Именно этот код мы и поместим в адресное пространство другого процесса, откуда без труда
удалим нашу самоудалялку. Теперь - теория (а ты как хотел?). Для вызова функций из других
модулей, модули формата PE-exe содержат т.н. таблицу (секцию) импорта. В ней перечислены
DLL, откуда экспортируются функции и непосредственно имена/ординалы функций. Каждая импортируемая функция
описывается двумя двойными словами (DWORD, dd): одним в таблице Import Lookup Table, а другим -
в таблице Import Address Table. What's this? Итак, в таблице ILT перечислены все
импортируемые из какого-либо модуля функции. Двойное слово, описывающее каждую функцию, может содержать либо ее
ординал (на это указывает установленный старший бит), либо RVA указатель на имя функции. Эта таблица
остается неизменной на всем протяжении выполнения программы. Напротив, таблица IAT, идентичная ДО
загрузки модуля таблице ILT, после загрузки обретает совсем иной облик 🙂 Теперь описывающие
функции двойные слова заменяются непосредственными их адресами в загруженных экспортирующих модулях.
Теперь осталось объяснить, зачем я все это говорю 🙂 Чтобы вызвать
вышеуказанный код в другом модуле мы не можем делать так:
delete_proc:
push offset prog_name
call DeleteFileA
call ExitProcess
push ...
push ...
...
push offset delete_proc
push module_process_handle
call WriteProcessMemory
...
push delete_proc_addr_in_other_module_memory_space
...
call CreateRemoteThread
Почему? Да потому, что вызовы функций из DLL после компиляции будут
выглядеть так:
call dword ptr ds:[004010cf] ;например
004010cf и есть адрес того места в IAT, куда был записан адрес функции (например
DeleteFileA). Теперь, если перенести наш код в другой процесс, то вызовы функций DeleteFileA и ExitProcess
не сработают из-за того, что их положение в IAT отличаются от их же положения в IAT нашего
модуля (если эти функции вообще импортируются)! Ты скажешь: "А почему бы не вычислить адреса функций
заранее и не подставить эти значения в код?". А потому, что адреса функций могут отличатся в разных версиях
WinNT (ой, забыл же сказать, описанный алгоритм работает только под NT из-за присутствия в ней
функции CreateRemoteThread). Что же тогда делать? Есть один способ... 🙂
Мы берем какой-нибудь модуль, куда будем копировать наш удаляющий код, ищем у НЕГО в таблице
импорта функции DeleteFileA и ExitProcess, затем читаем адреса этих функций из IAT (соответственно
модуль надо перед этим запустить), пишем эти адреса в НАШ удаляющий код и только ПОСЛЕ этого
копируем код в этот модуль. Достаточно общее представление о логике работы нашей
самоудалялки получили, теперь смотрим код (попутно почитывая детали реализации). Итак:
;*******************SelfDel.asm*******************
;Для уменьшения размера линкуй так:
;link /subsystem:windows /filealign:512 /merge:.data=.text /section:.text,rwe selfdel.obj
;Т.е. уменьшаем величину выравнивания, объединяем секции .data и .text, ставим для
;секции .text доступ на чтение/запись/выполнение (по дефолту запись не включена),
;иначе мы не сможем изменять данные, которые были в секции данных до объединения
;*******************ИСПОЛЬЗОВАНИЕ*******************
;selfdel путь_к_какой_либо_программе
;*******************ВНИМАНИЕ*******************
;Selfdel работает не со всеми программами, т.к. у некоторых из них директория .idata может быть
;закрыта для записи (например WinRAR)
;И ,конечно, программа должна импортировать функции DeleteFileA и ExitProcess
.386p
.model flat
include structures.inc
includelib kernel32.lib
extrn __imp__lstrcpy@8:dword
extrn __imp__GetCommandLineA@0:dword
extrn __imp__GetModuleFileNameA@12:dword
extrn __imp__ReadProcessMemory@20:dword
extrn __imp__GetLastError@0:dword
extrn __imp__ExitProcess@4:dword
extrn __imp__OpenProcess@12:dword
extrn __imp__Sleep@4:dword
extrn __imp__CreateRemoteThread@28:dword
extrn __imp__WriteProcessMemory@20:dword
extrn __imp__CreateProcessA@40:dword
Sleep equ __imp__Sleep@4
CreateProcess equ __imp__CreateProcessA@40
CreateRemoteThread equ __imp__CreateRemoteThread@28
WriteProcessMemory equ __imp__WriteProcessMemory@20
OpenProcess equ __imp__OpenProcess@12
ExitProcess equ __imp__ExitProcess@4
GetLastError equ __imp__GetLastError@0
ReadProcessMemory equ __imp__ReadProcessMemory@20
GetModuleFileName equ __imp__GetModuleFileNameA@12
GetCommandLine equ __imp__GetCommandLineA@0
lstrcpy equ __imp__lstrcpy@8
.data
thread_proc db 068h ;Ентот код и есть код удаления нашей программы
dd 0 ;Выглядит он так:
db 0ffh ;
db 015h ;push 0h
dd 0 ;call dword ptr ds:[0] /*DeleteFileA*/
db 0ffh ;call dword ptr ds:[0] /*ExitProcess*/
db 015h ;И 20 нулевых байт, куда мы потом запишем имя и путь нашей проги
dd 0 ;
db 20 dup(0) ;Нулевые смещения в процессе выполнения заменятся реальными адресами
delete_file db "DeleteFil"
exit_proc db "ExitProce"
dll_name db "KERN"
idt IMPORT_DIRECTORY_TABLE <0,0,0,0,0>
idd IMAGE_DATA_DIRECTORY <0,0>
snfo STARTUPINFO <0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>
pi PROCESS_INFORMATION <0,0,0,0>
tmp dd 0
dwTmp dd 0
dwP dd 0
dwBase dd 000400000h
count db 0
signal_flag db 02h
exe_name db 15 dup(0)
current_func db 9 dup(0)
.code
_start:
xor ebx, ebx
call GetCommandLine
parse_cmd:
cmp byte ptr [eax], 020h
je get_name
inc eax
jmp parse_cmd
get_name:
inc eax
push eax
push offset exe_name
call lstrcpy
push offset pi
push offset snfo
push ebx
push ebx
push 000000010h
push ebx
push ebx
push ebx
push offset exe_name
push ebx
call CreateProcess
push pi.dwProcessId
push 01h
push 01f0fffh
call OpenProcess
mov dwP, eax
push 01388h ;Примитивно ждем загрузки программы (5 секунд)
call Sleep
mov eax, dwBase
add eax, 03ch
push ebx
push 04h
push offset tmp
push eax
push dwP
call ReadProcessMemory
mov eax, dwBase
add eax, 080h
add tmp, eax
push ebx
push 08h
push offset idd
push tmp
push dwP
call ReadProcessMemory
mov eax, dwBase
add eax, idd.rva
mov tmp, eax
read_dll_name: ;Поиск ILT и IAT, связанных с kernel32.dll
push ebx
push 014h
push offset idt
push tmp
push dwP
call ReadProcessMemory
cmp dword ptr [idt.ilt_rva], 0
je end_s
mov eax, idt.name_rva
add eax, dwBase
push ebx
push 04h
push offset current_func
push eax
push dwP
call ReadProcessMemory
mov esi, offset dll_name
mov edi, offset current_func
cmpsd
je found_dll
add tmp, 014h
jmp read_dll_name
found_dll:
mov eax, dwBase
add eax, idt.ilt_rva
mov tmp, eax
jmp find_func
next_entry:
inc count
find_func: ;Ищем в ILT функции DeleteFileA и ExitProcess
cmp dword ptr [tmp], 0
je end_s
push ebx
push 04h
push offset dwTmp
push tmp
push dwP
call ReadProcessMemory
mov eax, dwTmp
add eax, dwBase
add eax, 02h
push ebx
push 09h
push offset current_func
push eax
push dwP
call ReadProcessMemory
add tmp, 04h
mov ecx, 09h
mov esi, offset delete_file
mov edi, offset current_func
repe cmpsb
jnz next_func
mov ecx, 07h
jmp put_addr
next_func:
mov ecx, 09h
mov esi, offset exit_proc
mov edi, offset current_func
repe cmpsb
jnz next_entry
mov ecx, 0dh
put_addr: ;Берем адреса функций из IAT (модуль загружен - адреса записаны в
IAT)
xor eax, eax
mov al, count
shl ax, 2
add eax, idt.iat_rva
add eax, dwBase
add ecx, offset thread_proc
mov dword ptr [ecx], eax
dec signal_flag
jnz next_entry
mov eax, offset thread_proc
add eax, 012h
push 014h
push eax
push 000400000h
call GetModuleFileName ;Пишем имя и путь нашей программы в thread_proc
mov eax, idd.rva
add eax, dwBase
mov ecx, offset thread_proc
inc ecx
add eax, 012h
mov dword ptr [ecx], eax ;Пишем в thread_proc адрес строки с путем к нашей программе
;Адрес в загруженном модуле, т.к. именно там будет выполняться удаляющий код
sub eax, 012h
push eax
push ebx
push 028h
push offset thread_proc
push eax
push dwP
call WriteProcessMemory ;Пишем thread_proc
test eax, eax
jz end_s
pop eax
push ebx
push ebx
push ebx
push eax
push ebx
push ebx
push dwP
call CreateRemoteThread ;Запускаем thread_proc
end_s:
call ExitProcess
end _start
Итак, по порядку: запускаем прогу, указанную в командной строке, открываем процесс с полным доступом,
ищем, где находится IAT для kernel32.dll, записываем адреса функций DeleteFileA и ExitProcess в наш
удаляющий код, туда же пишем имя нашей программы (служащее параметром для DeleteFileA), записываем
удаляющий код в другой процесс (для скорости, пишем в начало секции .idata, т.к. там находится
IMPORT_DIRECTORY_TABLE, которая уже не используется), запускаем наш код в процессе, наша программа успевает
завершиться и код в другом процессе успешно ее удаляет. Даааа...
Результат... Результат? Результат!
Чем этот пример лучше первого? Не знаю 🙂 Ну, ты запусти первый пример... Что, спрашивает удалить ли файл
такой-то, да? Вот! Второй пример удаляется без лишних вопросов. Да и все равно, такие проги пишутся не для
галочки, а для собственного удовольствия, для собственного интереса. И чем хитрее код, чем больше задействовано
областей - тем интереснее (в разумных пределах, конечно: так можно и операционку для
удаления одного файла написать 🙂 Ну, бывай. Что не оптимизировано - оптимизируй, что не доделано - доделывай, что не нравится - пиши...
Целью не ставилось написание супер-удаления для пупер-скрытных троянов (вообще, не люблю я это дело). Это просто интересная задача и, на мой взгляд, интересное решение, способствующее пониманию многих аспектов функционирования и устройства системы. Чего и вам желаю 🙂