好友
阅读权限40
听众
最后登录1970-1-1
|
本帖最后由 Blackk 于 2014-11-29 22:06 编辑
小菜也只能拿这样简单的壳来练手啦 ^ ^。
简单说下个人认为的UPX壳解码时的流程,然后我就按流程一点一点写吧。
1.先初始化
2.进行代码还原
3.进行CALL修复
4.进行函数表还原
5.节表初始化
6.解码完毕飞向程序入口
第一步:初始化,这个好像没什么好解释的呃,看代码。
[Asm] 纯文本查看 复制代码 0049B4D0 > $ 60 pushad ; //保存现场 //寄存器入栈 //ESP=12FFC4
0049B4D1 . BE 00204600 mov esi, 00462000 ; //ESI指向UPX1区段 //ESI=462000 //ESP=12FFA4
0049B4D6 . 8DBE 00F0F9FF lea edi, dword ptr [esi+0xFFF9F000] ; //指向要还原的首地址UPX0区段 //EDI=401000
0049B4DC . 57 push edi ; //首地址入栈
0049B4DD . 83CD FF or ebp, 0xFFFFFFFF ; //初始化EBP //EBP用来判断第二次循环还原方式
0049B4E0 . EB 10 jmp short 0049B4F2 ; //初始化完毕
第二步:代码还原。
接下来UPX壳进行代码还原,还原的方式我个人分为两种。
一种是UPX壳无法压缩的代码,UPX老老实实的从UPX1区段将代码还原到UPX0区段;
另一种就是UPX0中的重复指令,UPX壳根据UPX1中的KEY值找到UPX0中重复指令的位置,再进行的代码还原。具体过程如下:
[Asm] 纯文本查看 复制代码 0049B4E8 > /8A06 mov al, byte ptr [esi] ; //依次取UPX1区段的数据进行还原
0049B4EA . |46 inc esi ; //指向下一个要还原的数据 //ESI=ESI+1
0049B4EB . |8807 mov byte ptr [edi], al ; //还原数据
0049B4ED . |47 inc edi ; //EDI=EDI+1
0049B4EE > |01DB add ebx, ebx ; //用EBX做判断,是否继续还原
0049B4F0 . |75 07 jnz short 0049B4F9
0049B4F2 > |8B1E mov ebx, dword ptr [esi] ; //取循环变量KEY //存放在EBX=0xFF36DEEF
0049B4F4 . |83EE FC sub esi, -0x4 ; //ESI=ESI+4
0049B4F7 . |11DB adc ebx, ebx ; //用EBX做判断,是否开始进行还原代码操作
0049B4F9 >^\72 ED jb short 0049B4E8
0049B4FB . B8 01000000 mov eax, 0x1 ; //EAX=1
0049B500 > 01DB add ebx, ebx
0049B502 . 75 07 jnz short 0049B50B
0049B504 . 8B1E mov ebx, dword ptr [esi]
0049B506 . 83EE FC sub esi, -0x4
0049B509 . 11DB adc ebx, ebx
0049B50B > 11C0 adc eax, eax ; //使用EAX判断还原方式
0049B50D . 01DB add ebx, ebx
0049B50F . 73 0B jnb short 0049B51C
0049B511 . 75 28 jnz short 0049B53B
0049B513 . 8B1E mov ebx, dword ptr [esi]
0049B515 . 83EE FC sub esi, -0x4
0049B518 . 11DB adc ebx, ebx
0049B51A . 72 1F jb short 0049B53B
0049B51C > 48 dec eax
0049B51D . 01DB add ebx, ebx
0049B51F . 75 07 jnz short 0049B528
0049B521 . 8B1E mov ebx, dword ptr [esi]
0049B523 . 83EE FC sub esi, -0x4
0049B526 . 11DB adc ebx, ebx
0049B528 > 11C0 adc eax, eax
0049B52A .^ EB D4 jmp short 0049B500
0049B52C > 01DB add ebx, ebx
0049B52E . 75 07 jnz short 0049B537
0049B530 . 8B1E mov ebx, dword ptr [esi]
0049B532 . 83EE FC sub esi, -0x4
0049B535 . 11DB adc ebx, ebx
0049B537 > 11C9 adc ecx, ecx
0049B539 . EB 52 jmp short 0049B58D
0049B53B > 31C9 xor ecx, ecx ; //循环变量清零 //ECX=0
0049B53D . 83E8 03 sub eax, 0x3 ; //EAX=0xFFFFFFFF为单字节代码还原 //EAX!=0xFFFFFFFF为多字节还原代码
0049B540 . 72 11 jb short 0049B553
0049B542 . C1E0 08 shl eax, 0x8 ; //EAX左移8位 //EAX清零
0049B545 . 8A06 mov al, byte ptr [esi] ; //取多字节循环还原地址KEY
0049B547 . 46 inc esi ; //ESI+1
0049B548 . 83F0 FF xor eax, 0xFFFFFFFF ; //EAX和0xFFFFFFFF异或 //循环出口
0049B54B . 74 75 je short 0049B5C2
0049B54D . D1F8 sar eax, 1 ; //EAX右移一位
0049B54F . 89C5 mov ebp, eax ; //EBP=EAX //EBP的值为:~(KEY/2)
0049B551 . EB 0B jmp short 0049B55E
0049B553 > 01DB add ebx, ebx
0049B555 . 75 07 jnz short 0049B55E
0049B557 . 8B1E mov ebx, dword ptr [esi]
0049B559 . 83EE FC sub esi, -0x4
0049B55C . 11DB adc ebx, ebx
0049B55E >^ 72 CC jb short 0049B52C
0049B560 . 41 inc ecx
0049B561 . 01DB add ebx, ebx
0049B563 . 75 07 jnz short 0049B56C
0049B565 . 8B1E mov ebx, dword ptr [esi]
0049B567 . 83EE FC sub esi, -0x4
0049B56A . 11DB adc ebx, ebx
0049B56C >^ 72 BE jb short 0049B52C
0049B56E > 01DB add ebx, ebx
0049B570 . 75 07 jnz short 0049B579
0049B572 . 8B1E mov ebx, dword ptr [esi]
0049B574 . 83EE FC sub esi, -0x4
0049B577 . 11DB adc ebx, ebx
0049B579 > 11C9 adc ecx, ecx
0049B57B . 01DB add ebx, ebx
0049B57D .^ 73 EF jnb short 0049B56E
0049B57F . 75 09 jnz short 0049B58A
0049B581 . 8B1E mov ebx, dword ptr [esi]
0049B583 . 83EE FC sub esi, -0x4
0049B586 . 11DB adc ebx, ebx
0049B588 .^ 73 E4 jnb short 0049B56E
0049B58A > 83C1 02 add ecx, 0x2 ; //循环变量递增
0049B58D > 81FD 00FBFFFF cmp ebp, -0x500
0049B593 . 83D1 02 adc ecx, 0x2 ; //循环变量递增
0049B596 . 8D142F lea edx, dword ptr [edi+ebp] ; //以EDI作为代码还原的基地址,用EBP指向已还原的代码,将相同代码进行还原
0049B599 . 83FD FC cmp ebp, -0x4 ; //再次判断循环还原的方式
0049B59C . 76 0E jbe short 0049B5AC ; //如果还原处和解码处相距距离小于4个字节则进行单字节还原
0049B59E > 8A02 mov al, byte ptr [edx] ; //相同字节单字节还原循环
0049B5A0 . 42 inc edx ; //EDX+1
0049B5A1 . 8807 mov byte ptr [edi], al ; //循环还原
0049B5A3 . 47 inc edi ; //EDI+1
0049B5A4 . 49 dec ecx ; //判断循环是否结束 //ECX
0049B5A5 .^ 75 F7 jnz short 0049B59E
0049B5A7 .^ E9 42FFFFFF jmp 0049B4EE
0049B5AC > 8B02 mov eax, dword ptr [edx] ; //多字节循环还原
0049B5AE . 83C2 04 add edx, 0x4
0049B5B1 . 8907 mov dword ptr [edi], eax
0049B5B3 . 83C7 04 add edi, 0x4
0049B5B6 . 83E9 04 sub ecx, 0x4 ; //判断循环是否结束 //ECX
0049B5B9 .^ 77 F1 ja short 0049B5AC
0049B5BB . 01CF add edi, ecx
0049B5BD .^ E9 2CFFFFFF jmp 0049B4EE
0049B5C2 > 5E pop esi ; //还原完毕 //首地址出栈入ESI
整个过程中,ECX一般作为循环变量,EBX是绝对的王者,它掌控整个还原的节奏。
关于EBX是怎样计算出来的,现在技术不够逆不出来。
所以现在分析的都只不过是皮毛,UPX壳压缩的核心还是没能出来。
第三步:CALL修复
[Asm] 纯文本查看 复制代码 0049B5C3 . 89F7 mov edi, esi
0049B5C5 . B9 73250000 mov ecx, 0x2573 ; //循环变量 //ECX=0x2573
0049B5CA > 8A07 mov al, byte ptr [edi]
0049B5CC . 47 inc edi
0049B5CD . 2C E8 sub al, 0xE8
0049B5CF > 3C 01 cmp al, 0x1 ; //判断是否为0xE8
0049B5D1 .^ 77 F7 ja short 0049B5CA
0049B5D3 . 803F 13 cmp byte ptr [edi], 0x13 ; //判断0xE8后面是否为0x13 //判断是否为CALL
0049B5D6 .^ 75 F2 jnz short 0049B5CA
0049B5D8 . 8B07 mov eax, dword ptr [edi]
0049B5DA . 8A5F 04 mov bl, byte ptr [edi+0x4] ; //使用BL判断下一个指令是否是CALL
0049B5DD . 66:C1E8 08 shr ax, 0x8 ; //AX右移8位 //AL清零,AL=AH
0049B5E1 . C1C0 10 rol eax, 0x10 ; //EAX左移循环
0049B5E4 . 86C4 xchg ah, al ; //AH和AL交换
0049B5E6 . 29F8 sub eax, edi
0049B5E8 . 80EB E8 sub bl, 0xE8
0049B5EB . 01F0 add eax, esi
0049B5ED . 8907 mov dword ptr [edi], eax ; //CALL地址还原
0049B5EF . 83C7 05 add edi, 0x5
0049B5F2 . 88D8 mov al, bl
0049B5F4 .^ E2 D9 loopd short 0049B5CF ; //循环还原CALL地址
关于CALL修复。在第二步代码还原时已经把CALL的特征码还原了,这里只是对CALL的地址做进一步的修正。
它的算法我还是有点不明白。我看到在还原代码时,CALL后面的四个字节标志了CALL地址KEY,通过上面的CALL算法将KEY还原成正确地址。请问这个将KEY还原的代码用高级语言应该怎样表示呢?
第四步:函数表还原,没什么好解释的,看代码。
[Asm] 纯文本查看 复制代码 0049B5F6 8DBE 00800900 lea edi, dword ptr [esi+0x98000] ; //获取函数表还原基地址
0049B5FC 8B07 mov eax, dword ptr [edi] ; //获取函数表还原偏移
0049B5FE 09C0 or eax, eax
0049B600 74 45 je short 0049B647 ; //函数表还原完了进入下一个模块的跳转
0049B602 8B5F 04 mov ebx, dword ptr [edi+0x4] ; //获取函数存放位置偏移
0049B605 8D8430 B0C80900 lea eax, dword ptr [eax+esi+0x9C8B0] ; //获取函数所在DLL名称
0049B60C 01F3 add ebx, esi
0049B60E 50 push eax
0049B60F 83C7 08 add edi, 0x8
0049B612 FF96 B4C90900 call dword ptr [esi+0x9C9B4] ; //载入DLL到内存
0049B618 95 xchg eax, ebp
0049B619 8A07 mov al, byte ptr [edi]
0049B61B 47 inc edi
0049B61C 08C0 or al, al
0049B61E ^ 74 DC je short 0049B5FC ; //还原DLL后进入下一个DLL的还原跳转
0049B620 89F9 mov ecx, edi
0049B622 79 07 jns short 0049B62B
0049B624 0FB707 movzx eax, word ptr [edi]
0049B627 47 inc edi
0049B628 50 push eax
0049B629 47 inc edi
0049B62A B9 5748F2AE mov ecx, 0xAEF24857
0049B62F 55 push ebp
0049B630 FF96 B8C90900 call dword ptr [esi+0x9C9B8] ; //获取函数所在地址
0049B636 09C0 or eax, eax
0049B638 74 07 je short 0049B641
0049B63A 8903 mov dword ptr [ebx], eax ; //进行函数表地址还原
0049B63C 83C3 04 add ebx, 0x4
0049B63F ^ EB D8 jmp short 0049B619 ; //循环还原函数表
第五步:节表初始化,看代码。
[Asm] 纯文本查看 复制代码 0049B641 FF96 C8C90900 call dword ptr [esi+0x9C9C8]
0049B647 8BAE BCC90900 mov ebp, dword ptr [esi+0x9C9BC] ; //指向VirtualProtect函数地址
0049B64D 8DBE 00F0FFFF lea edi, dword ptr [esi-0x1000] ; //指向文件头位置
0049B653 BB 00100000 mov ebx, 0x1000 ; //文件头大小
0049B658 50 push eax
0049B659 54 push esp
0049B65A 6A 04 push 0x4 ; //PAGE_EXECUTE_READWRITE
0049B65C 53 push ebx
0049B65D 57 push edi
0049B65E FFD5 call ebp ; 使用VirtualProtect把文件头设置为PAGE_EXECUTE_READWRITE,获得文件头的写权限
0049B660 8D87 0F020000 lea eax, dword ptr [edi+0x20F]
0049B666 8020 7F and byte ptr [eax], 0x7F
0049B669 8060 28 7F and byte ptr [eax+0x28], 0x7F ; //完成节表的初始化
0049B66D 58 pop eax
0049B66E 50 push eax
0049B66F 54 push esp
0049B670 50 push eax
0049B671 53 push ebx
0049B672 57 push edi
0049B673 FFD5 call ebp ; //用VirtualProtect恢复文件头属性
最后:在0x0049B684飞向程序入口。
[Asm] 纯文本查看 复制代码 0049B675 58 pop eax
0049B676 61 popad
0049B677 8D4424 80 lea eax, dword ptr [esp-0x80]
0049B67B 6A 00 push 0x0
0049B67D 39C4 cmp esp, eax
0049B67F ^ 75 FA jnz short 0049B67B
0049B681 83EC 80 sub esp, -0x80
0049B684 - E9 C89AFAFF jmp 00445151
至此UPX壳解码分析完毕了。第一次逆向壳,写下文章留个记录。
以上文章只是个人见解,难免有误,还希望各路友人帮忙指正。
|
免费评分
-
查看全部评分
本帖被以下淘专辑推荐:
- · 值得收集 |主题: 251, 订阅: 163
- · 优秀逆向文|主题: 238, 订阅: 93
|