【文章标题】: UPX脱壳详细分析
【文章作者】: index09
【作者主页】: http://hi.baidu.com/index09
【使用工具】: UPX + OD + Stud_PE + Import REC
--------------------------------------------------------------------------------
【详细过程】
  又被R公司鄙视了,每次都被相同的理由鄙视。哭……
  于是决定好好学一下逆向了。
  首先做个幼儿级的脱壳练习,当做开始吧。
  网上有很多类似文章,基本只写了找OEP的过程,这里稍加分析,高手莫笑。
  
  
  用UPX加密记事本,简单用Stud_PE查看一下节表信息。
  No  | Name      | VSize      | VOffset    | RSize      | ROffset    | Charact.   | 
  01  | UPX0      | 0000F000   | 00001000   | 00000000   | 00000400   | E0000080   | 
  02  | UPX1      | 00005000   | 00010000   | 00004600   | 00000400   | E0000040   | 
  03  | .rsrc     | 00008000   | 00015000   | 00007200   | 00004A00   | C0000040   | 
  看样子没有加密资源
  
  OD载入后如下
  01014241   .  BE 00000101   MOV ESI,NOTEPAD.01010000                 ;  esi = sec upx1
  01014246   .  8DBE 0010FFFF LEA EDI,DWORD PTR DS:[ESI+FFFF1000]      ;  edi = sec upx0
  0101424C   .  57            PUSH EDI
  0101424D   .  83CD FF       OR EBP,FFFFFFFF
  01014250   .  EB 10         JMP SHORT NOTEPAD.01014262
  分别把 UPX1和UPX0节的首地址放入了esi和edi
  上面看到UPX0段的RSize为0,猜想是释放解压数据的空间。而UPX1段应该就是加密的程序代码了。
  
  继续向下看
  01014258   > /8A06          MOV AL,BYTE PTR DS:[ESI]                 ;  //////////////////////////////////////////////
  0101425A   . |46            INC ESI
  0101425B   . |8807          MOV BYTE PTR DS:[EDI],AL
  0101425D   . |47            INC EDI
  0101425E   > |01DB          ADD EBX,EBX
  01014260   . |75 07         JNZ SHORT NOTEPAD.01014269               ;  express data in sec upx1 to sec upx0
  01014262   > |8B1E          MOV EBX,DWORD PTR DS:[ESI]
  01014264   . |83EE FC       SUB ESI,-4
  01014267   . |11DB          ADC EBX,EBX
  01014269   >^\72 ED         JB SHORT NOTEPAD.01014258
  0101426B   .  B8 01000000   MOV EAX,1
  01014270   >  01DB          ADD EBX,EBX
  01014272   .  75 07         JNZ SHORT NOTEPAD.0101427B
  01014274   .  8B1E          MOV EBX,DWORD PTR DS:[ESI]
  01014276   .  83EE FC       SUB ESI,-4
  01014279   .  11DB          ADC EBX,EBX
  
  .......
  
  0101431A   > /8A07          MOV AL,BYTE PTR DS:[EDI]                 ;  /////////////////////////
  0101431C   . |47            INC EDI
  0101431D   . |2C E8         SUB AL,0E8                               ;  find [edi] <= 0xE9 && [edi+1] == 1
  0101431F   > |3C 01         CMP AL,1
  01014321   .^ 77 F7         JA SHORT NOTEPAD.0101431A
  01014323   . |803F 01       CMP BYTE PTR DS:[EDI],1
  01014326   .^\75 F2         JNZ SHORT NOTEPAD.0101431A               ;  .........................
  01014328   .  8B07          MOV EAX,DWORD PTR DS:[EDI]
  0101432A   .  8A5F 04       MOV BL,BYTE PTR DS:[EDI+4]
  0101432D   .  66:C1E8 08    SHR AX,8
  01014331   .  C1C0 10       ROL EAX,10                               ;  edi = A B C D
  01014334   .  86C4          XCHG AH,AL                               ;  eax = 0 C B A
  01014336   .  29F8          SUB EAX,EDI
  01014338   .  80EB E8       SUB BL,0E8
  0101433B   .  01F0          ADD EAX,ESI                              ;  eax = edi offset to sec upx0 + eax
  0101433D   .  8907          MOV DWORD PTR DS:[EDI],EAX
  0101433F   .  83C7 05       ADD EDI,5
  01014342   .  88D8          MOV AL,BL
  01014344   .^ E2 D9         LOOPD SHORT NOTEPAD.0101431F             ;  ...........................................
  一大堆都是从UPX1中读取数据,做一些处理,并且放入UPX0中。
  应该是UPX的解压算法。具体算法比较复杂没有详细的分析。
  里面的EBX控制了每一步解压应该做的操作,十分好奇这个数是怎么出来的。改天看看UPX的源代码,看看它神奇的压缩算法。
  看雪上有一篇对算法的分析,有兴趣请自行搜索。
  
  然后来到了这里
  01014346   .  8DBE 00200100 LEA EDI,DWORD PTR DS:[ESI+12000]         ;  //////////////IAT////////////////////
  0101434C   >  8B07          MOV EAX,DWORD PTR DS:[EDI]               ;  edi = upx import table??
  0101434E   .  09C0          OR EAX,EAX
  01014350   .  74 3C         JE SHORT NOTEPAD.0101438E                ;  jmp out
  01014352   .  8B5F 04       MOV EBX,DWORD PTR DS:[EDI+4]
  01014355   .  8D8430 24AE01>LEA EAX,DWORD PTR DS:[EAX+ESI+1AE24]     ;  eax = lib name
  0101435C   .  01F3          ADD EBX,ESI                              ;  ebx = esi + 4-7 (ori IAT??)
  0101435E   .  50            PUSH EAX
  0101435F   .  83C7 08       ADD EDI,8
  01014362   .  FF96 ECAE0100 CALL DWORD PTR DS:[ESI+1AEEC]            ;  loadlibrary
  01014368   .  95            XCHG EAX,EBP                             ;  ebp = lib handle
  01014369   >  8A07          MOV AL,BYTE PTR DS:[EDI]
  0101436B   .  47            INC EDI
  0101436C   .  08C0          OR AL,AL
  0101436E   .^ 74 DC         JE SHORT NOTEPAD.0101434C
  01014370   .  89F9          MOV ECX,EDI
  01014372   .  57            PUSH EDI                                 ;  proc name
  01014373   .  48            DEC EAX
  01014374   .  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
  01014376   .  55            PUSH EBP                                 ;  lib handle
  01014377   .  FF96 F0AE0100 CALL DWORD PTR DS:[ESI+1AEF0]            ;  getprocaddress
  0101437D   .  09C0          OR EAX,EAX
  0101437F   .  74 07         JE SHORT NOTEPAD.01014388
  01014381   .  8903          MOV DWORD PTR DS:[EBX],EAX
  01014383   .  83C3 04       ADD EBX,4
  01014386   .^ EB E1         JMP SHORT NOTEPAD.01014369               ;  ............................................
  这里有两重循环,分别从UPX1中读取dll名称,使用LoadLibrary加载入内存。
  获得句柄后,再从UPX1中读取相应函数名,使用GetProcAddress获得函数地址。
  01014381   .  8903          MOV DWORD PTR DS:[EBX],EAX
  这一句将函数地址填入了源程序的IAT,完成了IAT的填充。
  从这段代码中可以获得IAT的RVA,为0x10000。记下来留着以后修复IAT时使用。
  
  继续往下
  0101438E   > \8BAE F4AE0100 MOV EBP,DWORD PTR DS:[ESI+1AEF4]
  01014394   .  8DBE 00F0FFFF LEA EDI,DWORD PTR DS:[ESI-1000]
  0101439A   .  BB 00100000   MOV EBX,1000
  0101439F   .  50            PUSH EAX
  010143A0   .  54            PUSH ESP
  010143A1   .  6A 04         PUSH 4                                   ;  PAGE_EXECUTE_READWRITE
  010143A3   .  53            PUSH EBX
  010143A4   .  57            PUSH EDI                                 ;  set file header to PAGE_EXECUTE_READWRITE
  010143A5   .  FFD5          CALL EBP                                 ;  virtualprotect
  010143A7   .  8D87 FF010000 LEA EAX,DWORD PTR DS:[EDI+1FF]
  010143AD   .  8020 7F       AND BYTE PTR DS:[EAX],7F                 ;  remove sec UPX0 UNINITIALIZED_DATA character
  010143B0   .  8060 28 7F    AND BYTE PTR DS:[EAX+28],7F              ;  remove sec UPX1 UNINITIALIZED_DATA character
  010143B4   .  58            POP EAX
  010143B5   .  50            PUSH EAX
  010143B6   .  54            PUSH ESP
  010143B7   .  50            PUSH EAX
  010143B8   .  53            PUSH EBX                                 ;  set to old protect
  010143B9   .  57            PUSH EDI
  010143BA   .  FFD5          CALL EBP                                 ;  virtualprotect
  这里首先使用VirtualProtect把文件头设置为PAGE_EXECUTE_READWRITE,获得文件头的写权限。
  然后
  010143AD   .  8020 7F       AND BYTE PTR DS:[EAX],7F                 ;  remove sec UPX0 UNINITIALIZED_DATA character
  010143B0   .  8060 28 7F    AND BYTE PTR DS:[EAX+28],7F              ;  remove sec UPX1 UNINITIALIZED_DATA character
  去除了节表中UPX0和UPX1段的UNINITIALIZED_DATA属性,完成了节表的初始化。有些版本UPX并没有这段代码。
  最后又用VirtualProtect恢复文件头属性。
  
  
  再往下看是一个大大的JMP
  010143CB   .- E9 CD2FFFFF   JMP NOTEPAD.0100739D                     ;  jmp to OEP
  跳转到OEP
  
  记下OEP的RAV为739D。
  使用LoadPE在OEP处Dump出镜像文件。
  
  使用ImportREC修复一下IAT
  IAT的起始地址为刚才记下的0x10000,通过观察那段内存得到IAT大小为0x344
  在ImportREC的IAT Infos Needed中填入我们获得的信息,便可以成功修复。
  
  测试一下脱出来的镜像,可以正确运行。
  
  当然你可以在找到OEP时直接用OllyDump插件直接脱壳和修复IAT。这里只是为了练手小小的绕了个弯。
  有兴趣的同学欢迎交流。

  附件为OD的调试文件,加壳后的记事本和原始的记事本程序。

--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2009年08月30日 15:59:39

上传的附件 notepad.rar