【文章标题】: 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
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!