EncryptPE 20050314 保护Notepad脱壳
这个壳久闻大名,没有碰过。把Win98的Notepad拿来试试。没用SDK,加壳时
只用了缺省选项,省点事。另外,我只用跟了少量代码,所以很多推断可能完全是
错的。不对的地方请告诉我 ;-)
看看加壳后的Notepad,没什么东西。只是用exe中的数据改写V22005314.EPE,
然后LoadLibrary,获取EncryptPE_Init地址后call。对比一下EPE主程序和加壳后
Notepad释放出的V22005314,是一样的,即这个文件中没有特定保护程序的信息,
LoadLibrary后的初始化动作是固定的。区别在调用EncryptPE_Init的时候:
EPE1:0040D2A4 call dword ptr [eax] ; GetProcAddress取
EPE1:0040D2A4 ; EncryptPE_Init
EPE1:0040D2A6 cmp eax, 0
EPE1:0040D2A9 jz short loc_40D2C3
EPE1:0040D2AB mov ebx, eax
EPE1:0040D2AD mov eax, ebp
EPE1:0040D2AF add eax, 171h
EPE1:0040D2B4 mov ecx, [eax]
EPE1:0040D2B6 mov edx, [eax+4]
EPE1:0040D2B9 add eax, 9
EPE1:0040D2BC add eax, edx
EPE1:0040D2BE add eax, ecx
EPE1:0040D2C0 push eax ; 477ecc
EPE1:0040D2C1 call ebx
40D2C0处push的参数指向Notepad的加密数据。
V22005314.EPE是个加壳的dll,用ESP定律即可轻松脱掉。开始想用脱壳的dll代
替原始文件,关闭Notepad内重写dll的代码,这样也许可以调试。后来发现dll内自校
验太多,难以清除干净,只好作罢。不过脱壳的V22005314.EPE可以拿来反汇编,帮助
理解。
程序运行的时候,OD进程被杀掉(开始在re-pair修改过的OD下可以跑,后来不知为
什么又不行了。不过得到个有用的信息,dll没有使用SEH,可以放心设置硬件断点)。
我用OD的LoadDll跟了部分初始化代码。调试用DriverStudio 3.2/Ice Ext 0.67,在
WinXP下脱壳。
1. Inside V22005314.EPE
当Notepad调用LoadLibrary,这个dll就全面插手了 ;-)。如果是初次运行,会安装
一个WH_CALLWNDPROC类型的钩子。
EPE0:711AB9E2 loc_711AB9E2: ; CODE XREF: EPE0:711AB9D5
EPE0:711AB9E2 push offset dwCriticalSection
EPE0:711AB9E7 call LeaveCriticalSection_0
EPE0:711AB9EC cmp ds:bFileMappingObjCreated, 0
EPE0:711AB9F3 jz loc_711ABA87
EPE0:711AB9F9 push 0 ; system-wide hook
EPE0:711AB9F9 ; 装了个全局钩子
EPE0:711AB9FB mov eax, ds:hInstance ; 71120000,v22005314.epe的handle
EPE0:711ABA00 push eax
EPE0:711ABA01 mov eax, offset f_hook_proc
EPE0:711ABA06 push eax
EPE0:711ABA07 push 4 ; WH_CALLWNDPROC
EPE0:711ABA07 ; Installs a hook procedure that monitors
EPE0:711ABA07 ; messages before the system sends them to
EPE0:711ABA07 ; the destination window procedure
EPE0:711ABA09 call SetWindowsHookExA
EPE0:711ABA0E mov ds:hKernelObject, eax
EPE0:711ABA13 cmp ds:hKernelObject, 0
调用SetWindowsHookEx的dwThreadId参数为0,意味着这是个system-wide钩子,所有
的线程都会被拦截。钩子为WH_CALLWNDPROC类型,即当thread调用SendMessage时,安装的
Hook函数将被调用。对于GUI程序,SendMessage调用无疑是很频繁的。所以,在call
SetWindowsHookEx执行后,Hook函数立即就得到机会执行了。
通过设置钩子,V22005314.EPE被注入到几乎所有的进程。可以运行几个程序试试,
用LoadPE的region dump可以看到,dll被映射到了71120000地址,即V22005314.EPE的
预设Imagebase(如果是大程序,可能被重定位到其他地址)。
注意,不是所有的进程。如果程序不调用SendMessage,如果是个console程序,就可
以保持清白 ;-)。进程隐藏也是用Hook实现的(对任务管理器网开一面,允许列出进程)。
写个console程序,直接调用PSAPI中的函数,可以列出隐藏的Notepad进程,OpenProcess,
将其dump出来。
DLL通过查找Desktop窗口来得到Explorer的PID,与GetCurrentProcessId结果比较,如
果相同还要检测文件名是否为"EXPLORER.EXE",以判断是否运行在Explorer进程内。只有
在Explorer内,才负责对Notepad解码,创建子进程。即加壳Notepad的运行牵涉到3个进程,
Notepad父进程,Explorer(真正的父进程,WaitForDebugEvent循环在这里)和Notepad子进
程。由于调试子进程的代码寄生在Explorer内,用OD来调试不方便。
对于一般的gui进程,dll只是简单地Hook一些敏感api,以达到隐藏进程的目的。进程
隐藏是如何实现的? 这个壳并没有进入Ring0。
EPE0:711AE154 mov esi, edi
EPE0:711AE156 push esi ; esi=77F75E55 (ntdll.ZwOpenProcess)
EPE0:711AE157 call GetCurrentProcess
EPE0:711AE15C push eax ; hProcess
EPE0:711AE15D call ReadProcessMemory
EPE0:711AE162 lea eax, [ebp+NumberOfBytesWritten]
EPE0:711AE165 push eax ; lpflOldProtect
EPE0:711AE166 push 40h ; flNewProtect
EPE0:711AE168 push 5 ; dwSize
EPE0:711AE16A push esi ; lpAddress
EPE0:711AE16B call GetCurrentProcess
EPE0:711AE170 push eax ; hProcess
EPE0:711AE171 call VirtualProtectEx ; 0006F794 FFFFFFFF |hProcess = FFFFFFFF
EPE0:711AE171 ; 0006F798 77F75E55 |Address = ntdll.ZwOpenProcess
EPE0:711AE171 ; 0006F79C 00000005 |Size = 5
EPE0:711AE171 ; 0006F7A0 00000040 |NewProtect = PAGE_EXECUTE_READWRITE
EPE0:711AE171 ; 0006F7A4 0006F7D8 \pOldProtect = 0006F7D8
EPE0:711AE171 ;
EPE0:711AE176 lea eax, [ebp+NumberOfBytesWritten]
EPE0:711AE179 push eax ; lpNumberOfBytesWritten
EPE0:711AE17A push 5 ; nSize
EPE0:711AE17C lea eax, [ebp+Buffer]
EPE0:711AE17F push eax ; lpBuffer
EPE0:711AE180 push esi ; lpBaseAddress
EPE0:711AE181 call GetCurrentProcess
EPE0:711AE186 push eax ; hProcess
EPE0:711AE187 call WriteProcessMemory ; 0006F794 FFFFFFFF |hProcess = FFFFFFFF
EPE0:711AE187 ; 0006F798 77F75E55 |Address = 77F75E55
EPE0:711AE187 ; 0006F79C 0006F7DF |Buffer = 0006F7DF
EPE0:711AE187 ; 0006F7A0 00000005 |BytesToWrite = 5
EPE0:711AE187 ; 0006F7A4 0006F7D8 \pBytesWritten = 0006F7D8
EPE0:711AE187 ;
EPE0:711AE187 ; 0006F7DF - E9 6A8323F9 jmp F92A7B4E
EPE0:711AE187 ;
EPE0:711AE18C test bl, bl ; 写入后的代码:
EPE0:711AE18C ;
EPE0:711AE18C ; 77F75E55 >- E9 6A8323F9 jmp V2200531.711AE1C4
EPE0:711AE18C ; 77F75E5A BA 0003FE7F mov edx,7FFE0300
EPE0:711AE18C ; 77F75E5F FFD2 call edx
EPE0:711AE18C ; 77F75E61 C2 1000 retn 10
EPE0:711AE18C ;
EPE0:711AE18C ; 把当前进程中的NtOpenProcess替换了
EPE0:711AE18C ; 以后,OpenProcess不会被拦下
EPE0:711AE18E jz short loc_711AE19A
EPE0:711AE190 push offset dwCriticalSection
EPE0:711AE195 call LeaveCriticalSection_0
这段代码演示了对NtOpenProcess的Hook过程。直接用GetProcAddress取到
api地址,用VirtualProtectEx修改内存属性后写入5 bytes,跳到壳代码。当然,
对于console程序是不会执行的。
2. oep寻找,dump与iat修复
应该拦截的api很明显,CreateProcess,WriteProcessMemory,SetThreadContext。
过多地与dll纠缠会使问题复杂化。设置断点时用硬件断点,bpx通不过自校验。
如果想定位感兴趣的代码,可以在SoftIce这样设断:
:bpmb CreateProcessA x
创建child process后F12到V22005314.EPE空间。
:proc
得到刚创建的Notepad子进程的KPEB,如81594DA8
:addr 81594D18
现在可以对感兴趣的地址(如IAT)下断。
要dump Notepad,设置以下断点:
:bpmb WriteProcessMemory x if *(esp+10)==5 do "eb esp+10 0;x"
拦截WriteProcessMemory,当写5 bytes时为Hook api,改为0 bytes
:bpmb 1B:711E37C5 x do "r edx *(ebp-4);x"
在edx中保留正确api地址,避开IAT加密
:bpmb SetThreadContext x if *(*(esp+8) + B8)<500000
拦截SetThreadContext,当context中eip<0x500000时断下,
因为Notepad的oep为4010CC,开卷考试;-)
当SetThreadContext断下后,F12到V22005314.EPE空间:
EPE0:711B07B1 push eax
EPE0:711B07B2 call SetThreadContext ; 这里
EPE0:711B07B7
EPE0:711B07B7 loc_711B07B7: ; CODE XREF: EPE0:711AFD30
EPE0:711B07B7 ; EPE0:711AFD65
EPE0:711B07B7 ; EPE0:711AFDAD
EPE0:711B07B7 mov eax, [ebp-0E0h]
EPE0:711B07BD xor edx, edx
EPE0:711B07BF push edx
EPE0:711B07C0 push eax
EPE0:711B07C1 lea eax, [ebp-204h]
EPE0:711B07C7 call unknown_libname_49 ; Borland Visual Component Library & Packages
EPE0:711B07CC lea eax, [ebp-204h]
EPE0:711B07D2 push eax
EPE0:711B07D3 lea ecx, [ebp-208h]
EPE0:711B07D9 mov edx, 1
EPE0:711B07DE mov eax, 26h
EPE0:711B07E3 call sub_711AC764
EPE0:711B07E8 mov edx, [ebp-208h]
EPE0:711B07EE pop eax
EPE0:711B07EF call System::__linkproc__ LStrCat(void)
EPE0:711B07F4 mov edx, [ebp-204h]
EPE0:711B07FA xor ecx, ecx
EPE0:711B07FC mov eax, ebx
EPE0:711B07FE call Classes::TStrings::SetValue(System::AnsiString,System::AnsiString)
EPE0:711B0803 jmp short loc_711B0877 ; default
F10走到711B0803处jmp。
EPE0:711B0877 push esi ; default
EPE0:711B0878 mov eax, [ebp-0E0h]
EPE0:711B087E push eax
EPE0:711B087F mov eax, [ebp-0E4h]
EPE0:711B0885 push eax
EPE0:711B0886 call ContinueDebugEvent
EPE0:711B088B
EPE0:711B088B l_wait_for_next_event: ; CODE XREF: EPE0:711AFBBF
EPE0:711B088B push 0FFFFFFFFh ; INFINITE
EPE0:711B088D lea eax, [ebp-0E8h]
EPE0:711B0893 push eax
EPE0:711B0894 call WaitForDebugEvent
EPE0:711B0899 test eax, eax
EPE0:711B089B jnz l_debug_event_handler
在711B0887处改eip为711B088B,跳过对ContinueDebugEvent的调用。
然后F5退出SoftIce。现在Explorer在无限等待child process,child
process被挂在oep处了。
用LoadPE dump(已经避开了api hook:-),ImportRec修复iat。没有
stolen code,前面SetThreadContext断下时context中的eip就是4010CC。
(也许实战会有;-)
3. 修复replaced code
反汇编dumped_.exe,可以看到replaced code有3类:
type 1 对call [iat]的变形
.text:004010D3 FF 15 E4 63 40 00 call ds:GetCommandLineA
被变形为:
EPE0:004010D3 90 nop
EPE0:004010D4 E8 2F 91 5B 00 call near ptr 9BA208h
009BA208 FF 25 E4 63 40 00 jmp [4063E4]
type 2. 对mov指令变形
.text:004017AD 89 35 BC 57 40 00 mov stru_4057B0.hDevNames, esi
变为:
EPE0:004017AD 90 nop
EPE0:004017AE E9 E9 93 5D 00 jmp near ptr 9DAB9Ch
009DAB9C 89 35 BC 57 40 00 mov [4057BC],esi
type 3. 对jmp [iat]变形
00404FAA FF 25 1C 65 40 00 jmp ds:__imp_CommDlgExtendedError
变为:
EPE0:00404FAA E9 25 F9 5A 00 jmp near ptr 9B48D4h
009B48D4 FF 25 1C 65 40 00 jmp [40651C]
这种代码变形有2种,原来的6字节FF25xxxxxx可能变为:
E9 xx xx xx xx 00 或
90 E9 xx xx xx xx
对于Notepad就是这些。我不想费事再去跟代码了,直接写个程序从挂起的子
进程读出代码,修复后写回,再次dump,fix iat。收工。
本文只是抛砖引玉,捏Notepad这个软柿子,实战肯定要麻烦得多了。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课