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这个软柿子,实战肯定要麻烦得多了。