欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

脱壳手记---Themida(2.1.2.0)

程序员文章站 2022-11-03 19:54:57
下载:http://up.2cto.com/2013/0606/20130606103852815.rar “衣带渐宽终不悔,为伊消得人憔悴”   &...
下载:http://up.2cto.com/2013/0606/20130606103852815.rar

“衣带渐宽终不悔,为伊消得人憔悴”

                      ------柳永《蝶恋花》

王国维先生曾以此比喻治学之第二境,我想以此与仍然苦苦挣扎在KSSD大教室的诸君共勉!

然则高版本的脱壳往往是建立在低版本之上,如果你对低版本已经有所了解可以忽略此处,如果需要了解可以看这里http://bbs.pediy.com/showthread.php?t=172921 或移步KSSD

好了,进入正题!

上OD先直接F9跑一次。

程序结束后,将堆栈往上拉发现(图1):

 

有个返回到00401510,去反汇编窗口看看:

代码:

0040147E    8AD4            MOV DL,AH

00401480    8915 F4844000   MOV DWORD PTR DS:[4084F4],EDX

00401486    8BC8            MOV ECX,EAX

00401488    81E1 FF000000   AND ECX,0FF

0040148E    890D F0844000   MOV DWORD PTR DS:[4084F0],ECX

00401494    C1E1 08         SHL ECX,8

00401497    03CA            ADD ECX,EDX

00401499    890D EC844000   MOV DWORD PTR DS:[4084EC],ECX

0040149F    C1E8 10         SHR EAX,10

004014A2    A3 E8844000     MOV DWORD PTR DS:[4084E8],EAX

004014A7    6A 00           PUSH 0

004014A9    E8 A80A0000     CALL sayhello.00401F56

004014AE    59              POP ECX

004014AF    85C0            TEST EAX,EAX

004014B1    75 08           JNZ SHORT sayhello.004014BB

004014B3    6A 1C           PUSH 1C

004014B5    E8 9A000000     CALL sayhello.00401554

004014BA    59              POP ECX

004014BB    8365 FC 00      AND DWORD PTR SS:[EBP-4],0

004014BF    E8 72070000     CALL sayhello.00401C36

004014C4    E8 A0FA2E02     CALL 026F0F69

004014C9    90              NOP

004014CA    A3 F8894000     MOV DWORD PTR DS:[4089F8],EAX

004014CF    E8 30060000     CALL sayhello.00401B04

004014D4    A3 D0844000     MOV DWORD PTR DS:[4084D0],EAX

004014D9    E8 D9030000     CALL sayhello.004018B7

004014DE    E8 1B030000     CALL sayhello.004017FE

004014E3    E8 90000000     CALL sayhello.00401578

004014E8    A1 04854000     MOV EAX,DWORD PTR DS:[408504]

004014ED    A3 08854000     MOV DWORD PTR DS:[408508],EAX

004014F2    50              PUSH EAX

004014F3    FF35 FC844000   PUSH DWORD PTR DS:[4084FC]

004014F9    FF35 F8844000   PUSH DWORD PTR DS:[4084F8]

004014FF    E8 FCFAFFFF     CALL sayhello.00401000

00401504    83C4 0C         ADD ESP,0C

00401507    8945 E4         MOV DWORD PTR SS:[EBP-1C],EAX

0040150A    50              PUSH EAX

0040150B    E8 95000000     CALL sayhello.004015A5

00401510    8B45 EC         MOV EAX,DWORD PTR SS:[EBP-14]

00401513    8B08            MOV ECX,DWORD PTR DS:[EAX]

00401515    8B09            MOV ECX,DWORD PTR DS:[ECX]

00401517    894D E0         MOV DWORD PTR SS:[EBP-20],ECX

0040151A    50              PUSH EAX

0040151B    51              PUSH ECX

0040151C    E8 59010000     CALL sayhello.0040167A

00401521    59              POP ECX

00401522    59              POP ECX

00401523    C3              RETN

可以看出这是一段VC程序的伪OEP,头部被偷取了一些字节

进入CALL sayhello.00401000(图2)

脱壳手记---Themida(2.1.2.0)

 

和低版本一样,call都做了处理。

观察数据窗口,我们可以判断00405000为Iat段起始(图3):

脱壳手记---Themida(2.1.2.0)

好了,重新载入OD,在00405000下内存写入断点:

代码:

004487A0    8F02   POP DWORD PTR DS:[EDX]    ; 

断在004487A0(这里是VM代码)(图4)

脱壳手记---Themida(2.1.2.0)

看堆栈返回(图5)

 

脱壳手记---Themida(2.1.2.0)

发现调用代码为:

代码:

004C26F9    FF95 7A278906   CALL DWORD PTR SS:[EBP+689277A]

记得低版本的时候,它需要一张表,然后依据表中的数据分别取得原API地址,

IAT地址和CALLAPI地址,这里是不是一样呢,我们在此处下个断点一看便知。

F9后我们查看[esi]发现了非常熟悉的一张表(图6):

脱壳手记---Themida(2.1.2.0)

找到了这里,我们知道它要取得各种地址就一定需要这里的数据。可以下内存访问断点后跟踪,也可以直接从004C26F9这个断点跟踪,都一样,因为它的处理过程必是一个循环的过程。

那么我们直接跟踪吧,先F8跳过这个CALL,暂时不去关心VM,先弄清楚流程。

很明显这个CALL之后,IAT填入了处理后的地址(图7)

脱壳手记---Themida(2.1.2.0)

同时,我们的[esi]表也变化了(图8):

脱壳手记---Themida(2.1.2.0)

可以看出至少获取callapi的步骤也是在VM里面完成了。继续单步,就快到下一个API的处理了,是的,就在这个”DDDDDDDD”之后。单步的过程便不详述,跟低版本相差无几,只不过增加了些垃圾代码。不一会便来到:

代码:

004BF68C    C785 2D1D8206 0>MOV DWORD PTR SS:[EBP+6821D2D],0

004BF696    C785 311B8206 0>MOV DWORD PTR SS:[EBP+6821B31],0

004BF6A0    83BD 00B48C06 0>CMP DWORD PTR SS:[EBP+68CB400],0

非常熟悉的循环起始,和低版本一样,下面这段校验也需要修改:

代码:

004BF6E3    83BD 21158206 6>CMP DWORD PTR SS:[EBP+6821521],64

004BF6EA    0F82 09010000   JB sayhello.004BF7F9    ;这里修改成jmp跳过校验

以下一段判断[esi]表处理完毕:

代码:

004BF893    3D EEEEEEEE     CMP EAX,EEEEEEEE

004BF898    0F85 AB000000   JNZ sayhello.004BF949

004BF89E    F5              CMC

004BF89F    813E DDDDDDDD   CMP DWORD PTR DS:[ESI],DDDDDDDD

004BF8A5    0F85 9E000000   JNZ sayhello.004BF949

004BF8AB    F8              CLC

004BF8AC    F8              CLC

004BF8AD    50              PUSH EAX

004BF8AE    B8 00000000     MOV EAX,0

004BF8B3    8906            MOV DWORD PTR DS:[ESI],EAX

004BF8B5    8B0424          MOV EAX,DWORD PTR SS:[ESP]

004BF8B8    83C4 04         ADD ESP,4

004BF8BB    E9 08000000     JMP sayhello.004BF8C8

004BF8C0    53              PUSH EBX

004BF8C1 F4              HLT                 ; 特权命令

004BF8C2    213D 7ADF2136   AND DWORD PTR DS:[3621DF7A],EDI

004BF8C8    52              PUSH EDX

004BF8C9    BA 04000000     MOV EDX,4

004BF8CE    81C6 5C04AE28   ADD ESI,28AE045C

004BF8D4    01D6            ADD ESI,EDX

004BF8D6    81EE 5C04AE28   SUB ESI,28AE045C

004BF8DC    5A              POP EDX

004BF8DD    E9 0F000000     JMP sayhello.004BF8F1

下面取得[esi]表的值:

代码:

004BF85C    AD              LODS DWORD PTR DS:[ESI]

继续跟踪到了一个循环:

代码:

004BFC57    3B02            CMP EAX,DWORD PTR DS:[EDX]

004BFC59    0F84 6F000000   JE sayhello.004BFCCE             ;找到KEY跳出循环

004BFC5F    0F89 1E000000   JNS sayhello.004BFC83

004BFC65    60              PUSHAD

004BFC66    E8 14000000     CALL sayhello.004BFC7F

004BFC6B    AB              STOS DWORD PTR ES:[EDI]

004BFC6C    F2:             PREFIX REPNE:                                         ; 多余前缀

004BFC6D    93              XCHG EAX,EBX

004BFC6E    6B30 A8         IMUL ESI,DWORD PTR DS:[EAX],-58

004BFC71    B6 FD           MOV DH,0FD

004BFC73    D19E 2B586176   RCR DWORD PTR DS:[ESI+7661582B],1

004BFC79    B7 07           MOV BH,7

004BFC7B    05 CCF02A5A     ADD EAX,5A2AF0CC

004BFC80    60              PUSHAD

004BFC81    61              POPAD

004BFC82    61              POPAD

004BFC83    57              PUSH EDI

004BFC84    893424          MOV DWORD PTR SS:[ESP],ESI

004BFC87    BE 04000000     MOV ESI,4

004BFC8C    01F2            ADD EDX,ESI

004BFC8E    5E              POP ESI

004BFC8F    FC              CLD

004BFC90    52              PUSH EDX

004BFC91    BA 01000000     MOV EDX,1

004BFC96    01D1            ADD ECX,EDX

004BFC98    5A              POP EDX

004BFC99    F9              STC

004BFC9A    3B8D B8BF8B06   CMP ECX,DWORD PTR SS:[EBP+68BBFB8]

004BFCA0  ^ 0F85 B1FFFFFF   JNZ sayhello.004BFC57

根据低版本的流程,这里是要获取一个偏移值,每个API对应不同的偏移,

这张偏移表可以看做存的是对应API的解密KEY由TMD维护,跟我们脱壳关系不大,我们在004BFCCE下硬件执行断点跳过循环

代码:

004BFCCE    898D E11C8206   MOV DWORD PTR SS:[EBP+6821CE1],ECX

在这行代码后出现了原始api地址:

代码:

004BFED7    01C8            ADD EAX,ECX

004BFED9    2D 190F5568     SUB EAX,68550F19

有的模块的api在此处还只是一个字符串,继续单步一会:

代码:

004C026C    61              POPAD           

004C026D    FFD3  CALL EBX                ;经过这个CALL后得到API原始地址  

004C026F    60              PUSHAD       ;此时eax为API地址

取得API地址后,它会对API进行处理,处理过程会放在VM中,但是也有一些函数不做处理,那么它将有一个判断,我们继续单步寻找这个判断(单步中会经历一个TMD在自己申请的地址重新构造API的过程,和低版本思路一样,这里就不详述了)。

经历漫长的单步后来到下面(当然,直接在开始的返回后向上找很快就会找到,当然顺序下来有利于我们对流程的了解)

代码:

004C26DF    83BD E9B48C06 0>CMP DWORD PTR SS:[EBP+68CB4E9],0

004C26E6    0F85 91000000   JNZ sayhello.004C277D

这个时候我们还没有发现IAT的地址,可见它也在是VM中处理的。

到VM这里的流程清楚了,我们先直接看看不加密的情况, 

004C26DF处下断点,断不加密条件我们开始单步:

代码:

004C279A    0385 AD108206   ADD EAX,DWORD PTR SS:[EBP+68210AD] ;eax为IAT地址

004C27A0    F5              CMC

然后是对CALLAPI地址的处理,和低版本一样,有对”90”填充的处理,我们都可以不管,因为我们将对CALLAPI重新填充:

004C291C    0385 AD108206   ADD EAX,DWORD PTR SS:[EBP+68210AD] ;这里[EAX]callapi地址

004C2922    F9              STC

004C2923    83BD F9148206 0>CMP DWORD PTR SS:[EBP+68214F9],1

004C292A    0F84 F1040000   JE sayhello.004C2E21

004C293E    61              POPAD                ;这里[EAX]是CALLAPI地址

004C293F    813E AAAAAAAA   CMP DWORD PTR DS:[ESI],AAAAAAAA  ;比较是否填充FF25

---------------------------------------------------------------------------------------------------------------------------------

004C293F 也是我们需要的一个断点

代码:

004C2DF4    AB              STOS DWORD PTR ES:[EDI] ;这里CALLAPI处理完毕,可以重新写回去

004C2DF5    60              PUSHAD

通过以上的步骤,整个流程我们就清楚了,我们下面就来看看VM吧。所谓VM我理解的是它将原本的指令作了新的约定,并按照新的约定执行变形的代码来替换或者说”等价”的完成原先的指令集,它的每一个新约定指令都有一个指令序列来实现。

稍微跟踪一下,可以发现以下两条重要的代码

代码:

00442221    AC              LODS BYTE PTR DS:[ESI]   ;这里取得一个VM字节,将通过对这个字节进行计算,获取指令序列的跳转

00442222    30D8            XOR AL,BL

代码:

00447B00   /FF2487  JMP DWORD PTR DS:[EDI+EAX*4]       ; 跳转到指令序列

00447B03   |61              POPAD

00447B04   |C3              RETN

跟到[edi]发现一个指令序列表(图9):

脱壳手记---Themida(2.1.2.0)

当然要仔细分析的话,可以记录以上两个断点比较。当然我们现在不去分析它,我们主要是为了脱壳。

跟踪发现几个关键数据写入都通过

004487A0    8F02   POP DWORD PTR DS:[EDX]       ; 

通过打印脚本分析004487a0,记录[esp]和edx(因为它们将出现关键数据,这个再跟踪过程中可以发现,TMD差不多都这样,当然不同的VM引擎是可以设置不一样的,具体可以移步KSSD-->保护篇-->虚拟机

跟踪脚本:

代码:

var vedx

var vesp

var x

 

bphwc

bphws 004487a0 ,"x"

bphws 004c0169,"x"

mov x,0

esto

loop:

cmp eip,004487a0

jne exit

mov vedx,edx

mov vesp,[esp]

inc x

log x

log vedx

log vesp

cmp x,15

je exit

esto

jmp loop

exit:

ret

通过观察记录得到以下数据:

第0a次出现混淆后的API地址

第016次计算出iat的RVA

第017次计算出iat的VA

第019次填充iat

第0x1f次计算出callapi地址

第0x25次填充callpi偏移 ,填充e8 e9的代码不在此处 须另外寻找

第0x26次比较esi的下一个元素看是否为ffffffff,如果不是则证明还有需要填充的callapi

如果为ffffffff则将在第0x28次之后进入下一个循环

如果不为ffffffff则将在第0x2b处计算出下一个callapi地址

同样6次断之后在第0x31次填充callapi地址

  

好了 下面分析计算iat的代码 在第0x16次后下断点:

代码:

00444DB0    311C24          XOR DWORD PTR SS:[ESP],EBX

00444DB3    331C24          XOR EBX,DWORD PTR SS:[ESP]

00444DB6    8B2424          MOV ESP,DWORD PTR SS:[ESP]

00444DB9    010424          ADD DWORD PTR SS:[ESP],EAX               ; sayhello.00400000

经过跟踪发现00444DB9处代码计算出iat地址,此时iat地址存放在[esp]处

然后继续分析callapi偏移

在第0x1e次后下断点跟踪:

发现写入callapi的地址也是在00444DB9处

可以在虚拟机段找这段特征码(不过不必过于相信特征码,版本不同会略有差异,但方法都是一样的)

下面继续分析比较ffffffff在第0x26次断点后分析结果跟踪到此处: 

比较ecx 和eax ,相等则元素结束了不相等证明还有callapi

代码:

0044D099    8B2424          MOV ESP,DWORD PTR SS:[ESP]

0044D09C    3BC8            CMP ECX,EAX

下断点00444DB9 加硬件断点004C026F测试一下

发现00444DB9此处需要判断条件eax==00400000时才得到IAT或者CALLAPI地址,且第一次是IAT地址 (其实就这一个断点已经可以处理VM的IAT地址,第一个是IAT,后面都是callapi地址)

 

0044D09C 也要条件 eax==ffffffff eax!=ffffffff则证明还有未处理的CALLAPI ,如果相等则证明当前API元素已处理完。

有了以上的条件,我们就可以写脚本来完成脱壳了。

这样整理一下主要断点:

 

004C026F  ----------取得API原始地址

Case1:

004C26F9  ---------这里进入VM,部分函数的IAT和CALLAPI获取填充都在这里实现

Case2:

004C27A0    --------这里断下来则是不需要加密的API的IAT,值在eax中

            --------用来做判断之后需要增加一个辅助断点取IAT,单步几行代码找到

                 004c27ce

 

Case_VM:

00444DB9  ---------这里断下后通过判断eax==00400000,外加一个数字标识来区分取得的IAT还是callapi

                等待执行完毕直接填充正确IAT和FF15call

Case_NOTVM:

004C293F    ---------------------这里取得callapi值    

004C2DF5       --------------这里填充完毕,重新写回去 

代码:

var api

var iat

var callapi

var n

bc

bphwc

bphws 004c026f,"x"      ;取得原始API地址

esto

bp 004BF8DD           ;处理完毕,可以调到OEP,当然也可以不要这个断点,因为OEP在前面可以通过退出法已经找到了

bp 004C26F9           ;进入VM

bp 00444DB9          ;VM中得到IAT和CALLAPI地址

bp 004C27A0          ;不加密的IAT

bp 004C293F          ;不加密的CALLAPI

bp 004C2DF5         ;CALLAPI填充完毕,重新写回

bp 004c27ce          ;因为004C27A0作为了是否加密的跳转条件,因此在下面增加一个断点来取不加密的IAT

mov [004bf6ea],#E90A01000090#   ;校验处改为JMP略过

loop:

cmp eip,004c026f

jne swtich

mov api,eax

mov n,0            ;判断VM中的IAT和CALLAPI

esto

jmp loop

swtich:               ;分别加密处理的和不加密处理的

cmp eip,004C27A0

je notvm

cmp eip,004C26F9

je vm

jmp exit

notvm:

cmp eip,004C27A0  

jne filliat

mov iat,eax

esto

jmp notvm

filliat:

cmp eip,004C27CE

jne callapi

mov [iat],api          ;得到IAT,有些加密API也在这里处理IAT

esto

jmp notvm

callapi:

cmp eip,004C293F

jne fillcallapi

mov callapi,eax  ;得到CALLAPI地址

esto

jmp notvm

fillcallapi:

cmp eip,004C2DF5

jne loop

find callapi,#E9#,2        ;比较是否填充FF25

cmp $RESULT,0

jne fillFF25A

mov [callapi],#ff15#     

add callapi,2

mov [callapi],iat        

esto

jmp notvm

fillFF25A:

mov [callapi],#ff25#     

add callapi,2

mov [callapi],iat

esto

jmp notvm

vm:

esto

cmp eip,00444DB9 

jne loop

cmp eax,00400000   ;条件eax==00400000时才可能是ITA或者CALLAPI

jne vm

inc n

cmp n,1          ;n来判断当前是处理IAT还是处理CALLAPI

jne callapifun

mov iat,[esp]

add iat,00400000   ;得到IAT或者CALLAPI的VA

bp 004487a0      ;新增断点,此处的代码将填充IAT

esto

esto

esto

esto

mov [iat],api      ;四次F9后IAT填充完毕 

BC 004487a0

jmp vm

callapifun:

mov callapi,[esp]

add callapi,00400000

bp 004487a0    ;新增断点,此处的代码将填充CALLAPI

esto

esto

esto

esto

esto

esto

esto

esto

esto

esto                  ;十次F9后CALLAPI填充完毕

find callapi,#E9#,2      ;比较是否填充FF25

cmp $RESULT,0

bc 004487a0

jne fillFF25b

mov [callapi],#ff15#     

add callapi,2

mov [callapi],iat        

jmp vm

fillFF25b:

mov [callapi],#ff25#     

add callapi,2

mov [callapi],iat

jmp vm

 

exit:

bc

bphwc

bprm 00401000,4000    ;处理完毕后下内存访问断点,将断在OEP

esto

ret

后面按照常规方法恢复OEP即可,和低版本一样。TMD版本会有所不同,加壳方式也会略有差异,

但总体流程都是这样,按照这个思路去分析,大体脱壳是没有问题的!

2013年6月5日