首页
社区
课程
招聘
[分享]找规律脱themida(2.1.x),2胜1败,大牛帮帮忙!
发表于: 2013-11-11 01:56 14863

[分享]找规律脱themida(2.1.x),2胜1败,大牛帮帮忙!

2013-11-11 01:56
14863
一 问题描述:
一共用了4个程序作实验.
第一个程序是我自己写的,直接用tmd2.1.8默认方式加壳,成功脱掉.

第二个程序是别人的程序好像用vs2005写的,peid显示链接器版本8.0,没加壳,然后我用tmd2.1.8默认加密方式加壳,也成功脱掉.(dtws2.exe)

第三个程序是别人的程序,已加好壳,链接器版本8.0,用peid查壳2.1.*,个人感觉是成功脱掉了,可脱掉后,运行时,运行到vm中,就莫名奇妙的缓冲区溢出了.希望大牛能给点指导意见,谢谢!(GacRunner.exe)
/*78CAE9*/  xor     ecx, eax
/*78CAEB*/  call    dump_2.006BDB43
 
 
/*6BDB43*/  cmp     ecx, dword ptr ds:[0x9D1724]   这两个值不相等,然后程序崩溃
/*6BDB49*/  jnz     short dump_2.006BDB4D
/*6BDB4B*/  prefix rep:
/*6BDB4C*/  ret
/*6BDB4D*/  jmp     dump_2.006CA7EC
 

第四个程序是另一家公司的,peid查壳也是2.1.*,链接器版本8.0,但跟第三个程序入口点不一样,成功脱掉.(xiumoPKGame.exe)
注:虽然全是游戏进程,但本人不是写外挂的,一直都觉得游戏所用的技术才是最先进的,所以喜欢研究一些游戏,这也是本人第一次脱壳.研究了快两个星期了,把这些经验写出来,希望对大家有帮助!一直是坚持 "取之论坛,回馈论坛"的原则,也就是炒现饭了.后面附件会上一些脚本,及研究样本,脚本不具有通用性,但只要明白了themida壳的基本原理,脚本就很好写了.

二.themida基本原理,个人简单理解 ,欢迎拍砖
这个壳只会对导入表中来自kernel32.dll ,user32.dll,advapi32.dll 三个dll中的函数作加密变形.外壳程序会把要执行的代码变在VirtualAlloc 分配的地址空间去执行,执行完了再VirtualFree.
该壳会把 代码段,只读数据段,数据段合并成一段
外壳程序在运行时,会把 代码段 先部分填充,填充成下面这种形式.只填充除导入函数调用相关的代码.还有 mov eax,ds:[??] 这种类型.如果是来自上面3个dll中的函数,就把原函数的代码虚拟化放在
壳申请的内存中比如 call ds:[MessageBoxW] ,变成 call 3d000000 (存虚拟化的MessageBoxW)
    /*401000*/  push    esi
/*401001*/  push    edi
/*401002*/  nop                  call ds:[??]  导入函数 (6字节)
/*401003*/  nop
/*401004*/  nop
/*401005*/  nop
/*401006*/  nop
/*401007*/  nop
/*401008*/  push    0x1
/*40100A*/  push    0x0
/*40100C*/  push    0x0
/*40100E*/  mov     edi, eax
......     
 
  

填充成上面这种形式之后,就开始处理导入函数了.不是上面三个dll中的函数,就不虚拟化,不过把
call ds:[??] 6字节 变成了 call yy 5字节.每处理一个dll时,先VirtualAlloc,再FreeVirtual.(规律的来源)
call填充完之后,就开始执行 原程序代码了,从原程序oep处执行

三.esi表和ebx表,个人定义的(themida是怎样填充call的)
tmd就是根据esi表来填call的,ebx表里全部装的是dll的imagebase.发一段esi表和ebx表的具体数据
esi 表
1.ijl15.dll
0x4b7fdd62, 0xbbbbbbbb, 0x0d84aa6e, 0xc000dd88, 0xaaaaaaaa, 0xffffffff, 0xdddddddd, 
0x00100000, 0xbbbbbbbb, 0x2d84aa6e, 0x0000dd88, 0xaaaaaaaa, 0xffffffff, 0xdddddddd, 
0x4b67dd62, 0xbbbbbbbb,0x4d84aa6e, 0x4000dd87, 0xaaaaaaaa, 0xffffffff, 0xdddddddd, 
0xeeeeeeee, 0xdddddddd, 
 
2.psapi.dll
0x615b7db1, 0xad84aa54, 0xc0065651, 0xaaaaaaaa, 0xffffffff, 0xdddddddd, 
0x913b7694, 0xcd84aa54, 0x8000dd89, 0xaaaaaaaa, 0xffffffff, 0xdddddddd, 
0x4ae33057, 0xed84aa54, 0x00065651, 0xaaaaaaaa, 0xffffffff, 0xdddddddd, 
0xeeeeeeee, 0xdddddddd, 
 
3.kernel32.dll
0xc99a240b, 0x082401bf, 0x55656ea1, 0xd5640053, 0xffffffff,0xdddddddd, 
0xb313e96f, 0xe82401bf, 0xc5656e92, 0xcd653e7b, 0xb5652bf8, 0x7d63fb6d, 0x2d63fb6b, 0xfd63d972, 0xf563d987, 0xdd63d986, 0xffffffff, 0xdddddddd, 
0xe077678d, 0xc82401bf, 0x55653e7b, 0x3d652d7c, 0x8563d99d, 0xffffffff, 0xdddddddd, 0xe1568bb2, 0xa82401bf, 0xfd64fb7a, 0x6d6401f9, 0xffffffff, 0xdddddddd, 
0xc1cae679, 0x882401c0, 0x25656e94, 0x0d653e79, 0x95652d31, 0x3d63fb56, 0xfd63d963, 0x5d63d97f, 0xffffffff, 0xdddddddd, 
0xaad005e7, 0x682401c0, 0x3d6540d4, 0xbd63ffad, 0x2d63fc2e, 0x6563fc32, 0xb563fd2e, 0x6d640729, 0x9d63ead5, 0xffffffff, 0xdddddddd, 
0x2223cd36, 0x482401c0, 0xbd6540d5, 0x7565460d, 0x3d63ffad, 0xed63fc3b, 0xf563fc2f, 0x4d63fc33, 0xbd63fc4c, 0x4563fc55, 0x3563fd2e, 0xed640712, 0xffffffff, 0xdddddddd, 
0x42ee5b73, 0x282401c0, 0x05654e97, 0xaaaaaaaa, 0xffffffff, 0xdddddddd, 
.....
0x000000,0x00000
 
ebx表
0x????(ijl15.dll的imagebase),
0x????(第二个dll的imagebase),
0x7c800000(kernel32.dll的imagebae),.....

esi表,相信大家可以猜出来是什么东西了.论坛上也有介绍.每一小项以0xffffffff,0xdddddddd结束,
每一大项以0xeeeeeeee,0xdddddd结束.
每一小项其实就是代表一个导入api的相关信息
总表以0x00000000,0x00000000结束
每一大项就是指一个dll.
每一小项第一个dword 可以解析出 导入函数的"函数地址"
每一小项第二个dword,如果是0xbbbbbbbb,说明是 mov eax,ds:[xx]这种方式
每一小项如果第二个dword不是0xbbbbbbb,就根据这一项解析出iat的地址
每一小项第二个dword之后的每个dword解析出 call 的地址 如果有0xaaaaaaa 说明是 jmp ds:[xx] 这种调用 方式,否则说明是 call ds:[xx] 这种方式
举例
00401000 call ds:[00402000]
00401100 call ds:[00402000]
00402000地址处存的是 7c809af1 //api VirtualAlloc的地址
那么通过解析每一小项的第一外dword 可以得到 7c809af1,解析第二个(如果不是0xbbbbbbb)可以得到 00402000,如果第二个是0xbbbbbbbb,则通过第三个得到00402000,通过之后的可以得到 00401000(call指令的地址),或其它调用处的地址 比如 00401100 也调用了.
我把上面三个数字分别定义成 api地址(7c809af1),iat地址(00402000),FillCall地址(00401100+1),有些是没有FillCall地址的 比如程序中是这样的调用的 mov eax,ds:[xx] call eax
只要找到 上面3个地址 就可以脱壳了

四.怎样找到关键的3个地址
esi表就派上用场了.对esi表中的第一项下内存访问断点,f7跟一段时间就可以得到api地址和相应的模块地址.再对代码段下内存写断点,就可以找到 iat地址和fillcall地址.这时规律就出来了.我把这个规律分3类
第一类:带0xbbbbbb api的处理
第二类:不带0xbbbbbb api的处理
第三类:加密api 的处理

GetApi1 GetApi2 GetApi2
GetIat1 GetIat1 GetIat2
FillCall 1 FillCall1 FillCall2

从左往右依次为第一类,第二类,第三类
GetAPi1 设个硬件执行断点
GetApi2 设个硬件执行断点
再对代码段设个内存写断点
在这三个地方作点手脚,把加密的call给还原回去,.... 脱壳
还有一个关键性地址,定义为 BaseZero,也就是ebx表中 imagebase清0时的指令地址,
写脚本时有用.
我们还可以再定义一个地址叫GetBase,此处的指令刚好第一次访问 ebx表,这样在写脚本时作下判断,可以加快脚本执行速度

五.soeasy,找oep及还原变形的oep
这时ebx表就派上用场了,在把ebx表的最后一个dword变成0后,对代码段下个内存访问断点,直接就到oep.不过大多数情况这个oep是被tmd变形了.怎样恢复其实有个小技巧.比如你要搞一款游戏,用peid查到这个加壳的程序是链接器版本是8.0,你这时可以找游戏目录下其它的exe或dll看是链接器版本是不是8.0,如果是8.0恭喜你,中奖了,od打开没加壳的,对比下oep.
我脱的这几个程序 oep 全是
oep: call 123456,
oep+5: jmp 654321
123456: ....

你用上面的找oep的方法,其实会停在 123456 这个地址处.对比下oep其实 这个123456地址跟真实的oep没隔多远,找到oep后,把oep变成 call ??,jmp ?? 这样就修复了oep,然后 就是 用uif fix iat,dump,import rec 修复iat.done

六.忘了说怎么去找到esi表和ebx表
这里其实有个小技巧,我是从unpack网站上看到的.od加载程序,在VirtualAlloc的返回处,下断点,
在整个调试过程中,尽量少用 bp断点,这样跟快了,很容易就跟飞了,也不要在api头下bp,所以就下在VirtualAlloc的返回处了,第一次断下后,下硬件执行断点 he eax+8ef1 ,eax就是VirtualAlloc的返回值,这一块virtual memory 其实是为了加载 kernel32.dll (not as image),8ef1 就是 VirtualAlloc
的 FileOffset,not Rva.he eax+8ef1 之后,取消bp的 VirtualAlloc,f9一直跑,每次断下后,注意观察eax的值,当eax的值是 GetProcAddress 后,这时esi 和ebx就快出来了,这时 f9 一直跑,在eax=GetProcAddress 之后,中断两次在 上面的硬件断点时,注意看堆栈中的返回值,跑到堆栈的返回值处,鼠标往下拉下,简单的模拟跟几步,会看到
/*4DF5F5*/  mov     esi, dword ptr ss:[ebp+0x9DA368B]
/*4DF5FB*/  jmp     TestApi2.004DF605
/*4DF605*/  mov     ebx, dword ptr ss:[ebp+0x9DA2395]

testapi.exe

各个版本好像有点不一样,这是我自己的程序用tmd2.1.8加壳后,找到的esi 和 ebx

七. 注意事项
1.在整个下断过程中,尽量用硬件断点,我发现bp断点,跑快了很容易就跑飞了
2.忘了说,不能调试的开od kernle mdoe,如果被检测到调试器,有时是需要重启电脑的

八.找oep的灵感来源
1.在google上搜到youtube上 老外超easy脱壳方式,虽然我看那种脱壳方式失败了,但我有了另外的发现.老外的思想基本上就是说 在ZwFreeVirtualMemory的返回处下断点,不断的跑,直到edi的值
稳定下来(不是一直都是红色),然后在代码段下内存访问段点,这样再次断下直接就到oep了.然后我就试了下,确实是可以的,直不过分界线不是很明显.这让我想到tmd的执行方式可能就 是 VirtualAlloc,do sth 再VirtualFree.后来又看到unpack上 找esi表的方法,我往下看了下,发现了ebx表,发现里面全是dll imagebase,我就把这个表给用上了.试了好几个程序,找oep都很准 .只要ebx表清完0之后,代码段下内存访问断点,直接就到oep了.

九.实例讲解testapi.exe

     1.先用我自己写的一个程序 testapi2.1.8.exe ,这个程序是直接用tmd2.1.8 (csdn上下载的)默认加密方式,没改oep.第一次写脚本,也不会用什么复杂的命令,只好用这个很简单的脚本了,下面这个脚本是通用的找esi,ebx表的,如果找不到说明 可能不适用tmd版本.那就需要根本上面的原理去找了.
     尽量不要有bp 断点,也就是alt+b中的断点 全删了,否则很容易跟飞

     2. 找GetBase,我们的主要目的是找到上面那三类的规律,方便下断点
//清所有硬件断点,并在在VirtualAlloc返回处下断点
BPHWCALL
BP 7c809b0a  
run
 
//此时中断在VirtualAlloc返回处,清VirtualAlloc断点,并在新的VirtualAlloc处下硬件执行断点
BC 7c809b0a
BPHWS eax+8ef1,"x"
run
 
//不断的中断在新的VirtualAlloc处,比较eax是不是GetProcAddress
//不是就断续运行
LOOP:
cmp eax,7C80AE40
JE IsGetProcAddress
run
jmp LOOP
 
//中断在VirtualAlloc,并且eax=GetProcAddress,连续中断两次后,可得到esi和ebx的值
IsGetProcAddress:
log "IsGetProcAddress"
run
//中断一次
run
//中断第二次,此时刚好填充好esi表和ebx表
log "IsGetProcAddress2"

运行上面的脚本停下来后

堆栈是0012FF5C   004E1706  RETURN to TestApi2.004E1706

看下 TestApi2.004DF5ED 这个地址处的代码
/*4E1706*/  jpo     TestApi2.004E1721
/*4E170C*/  jmp     TestApi2.004E1721
/*4E1711*/  dec     dword ptr ds:[ebx-0x3B8B59A9]
/*4E1717*/  lds     esp, edx
/*4E1719*/  mov     al, byte ptr ds:[0x39E57AB3]
/*4E171E*/  xor     byte ptr ds:[edx+0x1C], cl
/*4E1721*/  mov     dword ptr ss:[ebp+0x9E00D14], eax
/*4E1727*/  cmc
/*4E1728*/  mov     esi, dword ptr ss:[ebp+0x9DB3578]  //esi表
/*4E172E*/  cmc
/*4E172F*/  mov     ebx, dword ptr ss:[ebp+0x9DB1C53] //ebx表
/*4E1735*/  cmc
/*4E1736*/  mov     dword ptr ss:[ebp+0x9DF18D9], esi
/*4E173C*/  jge     TestApi2.004E175A
/*4E1742*/  pushad
/*4E1743*/  mov     cl, 0xE5

 


执行完两个mov 之后 要记录下 ebx和 esi的值

ebx={  0x7c800000, 0x77d10000, 0x77da0000, 0x10200000};
依次是是krnl32,user32,advapi32,msvcr71d

esi={  
//kernel32.dll
0x35451750, 0x2ac8cd89, 0xeed97e99, 0xffffffff, 0xdddddddd,
0x27c5585a, 0xcac8cd89, 0xded97e98, 0xffffffff, 0xdddddddd,
0x80d11ceb, 0xeac8cd89, 0x0ed97e83, 0xffffffff, 0xdddddddd,
0xeeeeeeee,  0xdddddddd,

//user32.dll
0x18a32f00, 0x0ac8cd8a, 0xbed97e99, 0xffffffff, 0xdddddddd,
0xeeeeeeee, 0xdddddddd,

//advapi32.dll
0x21d91677, 0x4ac8cd89, 0x56d97e99, 0xffffffff, 0xdddddddd,
0xdb1718fb, 0x6ac8cd89, 0x4ed97e98,  0xffffffff, 0xdddddddd,
0xeeeeeeee, 0xdddddddd,

//msvcr71d.dll
0x770af0e5, 0x4c081957, 0xc0000225, 0xaaaaaaaa, 0xffffffff, 0xdddddddd,
0xc0f9fd27, 0x6c081957, 0x0000029d, 0xaaaaaaaa, 0xffffffff, 0xdddddddd,
  0x573a4c0a, 0x8c081957, 0x00000297, 0xaaaaaaaa, 0xffffffff, 0xdddddddd,
0xfdce5b59, 0xac081957, 0x40000244, 0xffffffff, 0xdddddddd,
0x90d2808b, 0xcc081957, 0xc0000275, 0xaaaaaaaa, 0xffffffff,  0xdddddddd,
0x103e7d4e, 0xec081957, 0xe000022e, 0xffffffff, 0xdddddddd,
0xa71f7fca, 0x0c081958, 0x80000232, 0xffffffff, 0xdddddddd,
0xacd5dd6a, 0x2c081958, 0x40000234, 0xffffffff, 0xdddddddd,
  0x6b2067ac, 0x4c081958, 0xffffffff, 0xdddddddd,
0x031a0a45, 0x6c081958, 0x0000024a, 0xffffffff, 0xdddddddd,
0xce8624bd, 0x8c081958, 0x40000274, 0xaaaaaaaa, 0xffffffff, 0xdddddddd,
0x917be5ba,  0xac081958, 0x20000214, 0xffffffff, 0xdddddddd,
0x82e69949, 0xcc081958, 0xa0000258, 0xffffffff, 0xdddddddd,
0x4e04e8f9, 0xec081958, 0xe0000257, 0xffffffff, 0xdddddddd,
0x35044edf, 0x0c081959,  0x00000275, 0xaaaaaaaa, 0xffffffff, 0xdddddddd,
0xfa72f3b6, 0x2c081959, 0x40000250, 0xffffffff, 0xdddddddd,
0xd3cc3342, 0x4c081959, 0x8000024f, 0xffffffff, 0xdddddddd,
0x8932339b, 0x6c081959,  0xa000023a, 0xffffffff, 0xdddddddd,
0xeeeeeeee, 0xdddddddd,

0x00000000, 0x00000000};

这个exe 只依赖这4个dll.由于ebx表初次出现,我们让时先来找GetBase,这个值其实不是必须的,不过为了让脚本执行得快点,我们还是找一下.对ebx表第一个dword下内存访问断点,正好断在

/*4E179D*/  mov     ecx, dword ptr ds:[edx]  //这句就是取dllbase
/*4E179F*/  pop     edx
/*4E17A0*/  stc


GetBase=4E179D

    3.由于esi表前面是加密的,就先来找GetAPi2
     这个程序很简单,我脱的几个程序,好像加密api都没有0xbbb这样的.对esi表第一个dword下内存访问断点 跑起来,还是那句话,尽量不要有bp断点,否则很容易更飞,断下后来到
/*4E2137*/  lods    dword ptr ds:[esi]  //到esi表中的值
/*4E2138*/  jmp     TestApi2.004E2147
/*4E213D*/  push    ebx
/*4E213E*/  lahf
/*4E213F*/  mov     cl, 0x49


一些关键指令,尽量在大脑里留点印象 ,这时要有耐心的f7,遇到call 也要f7,否则可能更飞,这里代码不是很多.f7 一路上会遇到
cmp eax,0xeeeeeee  
rol eax,0x3 
rol eax,0x10
cmp eax,0x1000
 ...
/*4E255E*/  mov     dl, 0xAC
/*4E2560*/  popad
/*4E2561*/  cmp     eax, dword ptr ds:[edx]   //这句就是在比较函数名的hash值了,edx是函数名hash表,eax是本次函数名hash值
/*4E2563*/  je      TestApi2.004E2612 // 跳转
/*4E2569*/  pushad
/*4E256A*/  jmp     TestApi2.004E2575


接上面那个 cmp,其实我也不知道 hash是什么东东,反正是根据函数得到的一个dword,je 时就说明找到了,这时有个index值,得到一个api的方法,大家都知道的,根据func name index 得到 ord ,再根据ord 得到 func rva,再加上imagebase 得到func addr ,在je 这个地址 下he 004E2612 ,这之后的就是根据上面的 方法得到api地址. 这中间我们要对一些指令有点印象

....
//下面这段代码跟以往一些版本有些不同的,以前好像是sub ??? ,就是判断是不是那3个要加密的imagebase,网上有种说法是直接改成不比较,也就是如果当前处理的dll是加密dll,也按未加密的方式填iat,填call,我试了下改这个地方的代码,已经不行了
/*4E262B*/  mov     byte ptr ss:[ebp+0x9E88B26], 0x0  //是否是加密dll 
/*4E2632*/  mov     dword ptr ss:[ebp+0x9E88B2F], 0x0
/*4E263C*/  mov     ecx, 0x1
/*4E2641*/  test    ecx, ecx
/*4E2643*/  je      short TestApi2.004E2696
/*4E2645*/  mov     ecx, dword ptr ss:[ebp+0x9DB1C53]  
/*4E264B*/  mov     ecx, dword ptr ds:[ecx]
/*4E264D*/  cmp     ecx, dword ptr ss:[ebp+0x9DB1706] //user32.imagebase
/*4E2653*/  je      short TestApi2.004E2665
/*4E2655*/  cmp     ecx, dword ptr ss:[ebp+0x9DB2917] //advapi32.imagebase
/*4E265B*/  je      short TestApi2.004E2665
/*4E265D*/  cmp     ecx, dword ptr ss:[ebp+0x9DB0AB9] //kernel32.imagebase
/*4E2663*/  jnz     short TestApi2.004E2696                   //不是上面3个dll,跳转
/*4E2665*/  mov     byte ptr ss:[ebp+0x9E88B26], 0x1  //是否是加密dll
/*4E266C*/  lea     edx, dword ptr ss:[ebp+0x9E88B33]
/*4E2672*/  lea     ecx, dword ptr ss:[ebp+0x9E88C0B]
/*4E2678*/  cmp     edx, ecx
/*4E267A*/  jnb     short TestApi2.004E2696
/*4E267C*/  cmp     dword ptr ds:[edx], 0x0
/*4E267F*/  je      short TestApi2.004E2696
/*4E2681*/  cmp     dword ptr ds:[edx], eax
/*4E2683*/  jnz     short TestApi2.004E2691
/*4E2685*/  mov     dword ptr ss:[ebp+0x9E88B2F], 0x1
/*4E268F*/  jmp     short TestApi2.004E2696
/*4E2691*/  add     edx, 0x4
/*4E2694*/  jmp     short TestApi2.004E2678
 


有耐心的f7 ,中途关注下寄存器中有没有自己认得的数字,dllbase sth

/*4E27A6*/  lods    word ptr ds:[esi] //像这种会让你联想到???,ord

/*4E27A8*/  jmp     TestApi2.004E27BD
/*4E27AD*/  xlat    byte ptr ds:[ebx+al]

//之后又出现这种,你会想到??
 
....
/*4E27D2*/  lods    dword ptr ds:[esi]  //这个得到func rva,看来离函数地址近了
/*4E27D3*/  jns     TestApi2.004E27EB
/*4E27D9*/  pushad
/*4E27DA*/  jmp     TestApi2.004E27E4
/*4E27DF*/  nop
/*4E27E0*/  pop     edi
/*4E27E1*/  mov     esi, 0xE6813424
/*4E27E6*/  das
/*4E27E7*/  xor     bl, byte ptr ds:[ebx]
/*4E27E9*/  push    esi
/*4E27EA*/  popad
/*4E27EB*/  pushad
/*4E27EC*/  mov     bl, 0xA9
/*4E27EE*/  mov     al, 0x98
/*4E27F0*/  popad
/*4E27F1*/  add     eax, 0x1FDF2115
/*4E27F6*/  sub     eax, 0x1BB749DE
/*4E27FB*/  add     eax, ecx
/*4E27FD*/  add     eax, 0x1BB749DE
/*4E2802*/  sub     eax, 0x1FDF2115 //这个执行完,eax=func addr ecx=imagebase
/*4E2807*/  pushad
/*4E2808*/  push    edx
/*4E2809*/  jmp     TestApi2.004E281E
 


到此 第三类 也就是 GetApi2的值已经搞定
GetApi2=4E2807,
记录下,写脚本用

   4.找GetIat2 和 FillCall2
因为我们现在正在处理加密api 所以... 代码段内存写断点,一次搞定两个地址 F9 断在

/*44148B*/  pop     dword ptr ds:[edx]  // 此时 edx=0040200c [esp]=0276000(你们那儿可能不一样)  [edx]=0
/*44148D*/  jmp     TestApi2.0043B1AE


这时可以用ida 看下 没加壳的 testapi.exe 0040200c处是什么东东 .规律来了
当 eip=44148B and [edx]=0 时 我们可以 认为 edx=iat, 44148B这个地址收下了

GetIat2=44148B,

再f9,停在

/*444194*/  mov     byte ptr ds:[ebx], al    //ebx=0040103c,al=e8,这又是在搞么,e8你认识不?,去ebx那儿看哈,或用ida看下没加壳的程序
 


再f9
/*44148B */  pop     dword ptr ds:[edx]   edx=0040103d,[edx]=90909090,这里又是在搞么??  44148B 这个地址咋又出现了
/*44148D*/  jmp     TestApi2.0043B1AE


44148B 这个地址我又收下了
FillCall2=44148B
当 eip=44148B and [edx]=90909090 时 我们可以定义 认为 call addr=edx

      5.来得到BaseZero
      通过加密dll,我们得到了GetApi2,GetIat2,FillCall2,3 个值,这时对ebx表中相应的dword下内存写断点,f9,断在

/*4E5A84*/  mov     dword ptr ds:[ecx], ebx  //ebx=0,这句就是BaseZero
/*4E5A86*/  pop     ebx
/*4E5A87*/  jmp     TestApi2.004E5A9D


BaseZero=4E5A84  

     6.搞定GetApi
     由于这个程序esi表中,没0xbbbbbbbb这样的项,所以这个值就不要了
  
   7.搞定GetIat1和FillCall1
     在BaseZero这个地址处下硬件执行断点,当清完一个加密的dll,准备处理下一个未加密的dll时,开代码段内存写断点
...
/*4E532F*/  mov     dword ptr ds:[edx], ecx  //edx=0040201c ecx=1020C034 (msvcr71d._except_handler3),edx=iat地址
ds:[0040201C]=00000000

/*4E5331*/  mov     edx, dword ptr ss:[esp]
...



GetIat1=4E532F
   再F9
...
/*4E5825*/  stos    byte ptr es:[edi] //填e8 or e9
/*4E5826*/  pushad
/*4E5827*/  clc
...
再f9
/*4E58D0*/  stos    dword ptr es:[edi]   //edi=0040112f [edi]=90909090,找到了
/*4E58D1*/  jpo     TestApi2.004E58E8
/*4E58D7*/  jmp     TestApi2.004E58E8



FillCall1=4E58D0

    8.到此GetApi1,GetIat1,FillCall1,GetApi2,GetIat2,FillCall2,GetBase,BaseZero 这8个值中,除了GetApi1,由于没有0xbbbbbb项,都获取到了,现在可以写脚本了
GetApi1= none      GetApi2=4e2807 BaseZero=4e5a84 GetBase=4e179d
GetIat1=4E532F  GetIat2=44148b
FillCall1=4E58D0   FillCall2=44148b

下面这个脚本是最后的优化版本,针对这个testapi.exe 有些是没用到的
 
 //清所有硬件断点,并在在VirtualAlloc返回处下断点
BPHWCALL
BP 7c809b0a  
run

//此时中断在VirtualAlloc返回处,清VirtualAlloc断点,并在新的VirtualAlloc处下硬件执行断点
BC 7c809b0a
BPHWS eax+8ef1,"x"
run

//不断的中断在新的VirtualAlloc处,比较eax是不是GetProcAddress
//不是就断续运行
LOOP:
cmp eax,7C80AE40
JE IsGetProcAddress
run
jmp LOOP

//中断在VirtualAlloc,并且eax=GetProcAddress,连续中断两次后,可得到esi和ebx的值
IsGetProcAddress:
log "IsGetProcAddress"
run
//中断一次
run
//中断第二次,此时刚好填充好esi表和ebx表


//上面是通用 找esi和ebx 表 脚本,下面的脚本开始 反加密


var GetApi1
var GetIat1
var FillCall1

var GetApi2
var GetIat2
var FillCall2

var BaseZero
var GetBase

var CodeStart
var CodeSize


var ModBase //模块基址
var FuncAddr //未加密api地址
var FillAddr //fill地址
var NewCallValue //new call ??
var IatMin   
var IatMax
var IatCur  //api所属 iat地址
var OrigFuncAddr //壳填在iat中的地址



mov GetApi1,0
mov GetIat1,4e532f
mov FillCall1,4e58d0

mov GetApi2 ,4e2807
mov GetIat2,44148b
mov FillCall2,44148b

mov BaseZero,4e5a84
mov GetBase,4e179d

mov CodeStart,00401000
mov CodeSize,00003000

mov IatMin,90909090
mov IatMax,0
mov nFuncCount,0
mov nIatCount,0



BPHWCALL
BPHWS GetBase,"x"
BPHWS BaseZero,"x" 


run 
jmp func



//暂停在从ebx表取值
PauseAt_GetBase:
//{
		  log "GetBase"
			cmp [edx],7c800000
			JE PauseAt_GetBase_Next
			
			cmp [edx],77d10000
			JE PauseAt_GetBase_Next
			
			cmp [edx],77da0000
			JE PauseAt_GetBase_Next
			
			//不是上面3个就不要内存写断点
			bpmc
			bphwc GetApi2   //清getapi2
			//bphwc GetApi1  //getapi1
			
			run
			jmp func
			
			
	PauseAt_GetBase_Next:
			BPHWS GetApi2,"x" 
			//BPHWS GetApi1,"x" 
			BPWM  CodeStart,CodeSize //代码断内存写断点

			run 
			jmp func
			
			
//}

//函数8 暂停在ebx表清0处 
PauseAt_BaseZero:
//{
	log "BaseZero"
	cmp [ecx+4],0
	JE FINDOEP
	run
	jmp func
//}


//函数1,获取0xbbbbbb api地址 
PauseAt_GetApi1:
//{
	mov FuncAddr,eax
	mov ModBase,ecx

	log "GetApi1"
	log ModBase
	log FuncAddr
	
	jmp FixRelocApi
	
//}




//函数2,fill 非加密iat 4E3208
PauseAt_GetIat1:
//{
	mov IatCur,edx
	mov OrigFuncAddr,ecx


	log "GetIat1"
	log ModBase
	log FuncAddr
	log IatCur
	log OrigFuncAddr
	
	cmp OrigFuncAddr,FuncAddr
	JE FillIat1_Next
	mov ecx,FuncAddr
	
FillIat1_Next:
	
	jmp GetMinMaxIat
	

//}

//函数3 fill 非加密 call
PauseAt_FillCall1://填充非加密call or jmp 
//{
	mov FillAddr,edi
	
	log "FillCall1"
	log ModBase
	log FuncAddr
	log IatCur
	log OrigFuncAddr
	log FillAddr
	
	cmp OrigFuncAddr,FuncAddr
	
	JE FillCall1_next
	log "not equel"
	mov NewCallValue,FuncAddr
	sub NewCallValue,FillAddr
	sub NewCallValue,4
	mov eax,NewCallValue
FillCall1_next:
	run 
	jmp func
	

//}


//函数4,获取非0xbbbbbb api地址,4E0797
PauseAt_GetApi2:
//{
	mov FuncAddr,eax
	mov ModBase,ecx
	
	log "GetApi2"
	log ModBase
	log FuncAddr
	
	
	jmp FixRelocApi

//}

//函数5,填充加密iat 4427DD
PauseAt_GetIat2:
//{
	 
	mov IatCur,edx
	mov OrigFuncAddr,[esp]
	mov [esp],FuncAddr
	
	
	log "FillIat2"
	log ModBase
	log FuncAddr
	log IatCur
	log OrigFuncAddr
	
	
	jmp GetMinMaxIat
//}


//函数6,填充加密call 4427DD
PauseAt_FillCall2:
//{
		mov FillAddr,edx
		mov NewCallValue,FuncAddr
		sub NewCallValue,FillAddr
		sub NewCallValue,4
		mov [esp],NewCallValue
		
		log "FillCall2"
		log ModBase
		log FuncAddr
		log IatCur
		log OrigFuncAddr
		log FillAddr
		log NewCallValue
		
		run
		jmp func
	

//}





//函数7,获取最大,最小iat
GetMinMaxIat:
//{

	 cmp IatCur,IatMin
	 ja Next
	 mov IatMin,IatCur
	
	
  Next:
	 cmp IatCur,IatMax
	 jb Finish
	 mov IatMax,IatCur
	 
  Finish:
     run
     jmp func
  
//}






//函数9,开始找oep
FINDOEP:
//{
	BPHWCALL
	bpmc
	BPRM  401000,3000 //代码断内存访问断点
	run
	log "FINDOEP"
	log IatMin
	log IatMax
	bpmc
	ret
	
//}



//函数10,eip switch case
func:
//{
	//	cmp eip,GetApi1            //分支1, 停在GetApi1
	//	JE PauseAt_GetApi1 
		
		cmp eip,GetIat1   //分支2 停在GetIat1
		JNE func_next1
		cmp [edx],0
		JE PauseAt_GetIat1
		jmp NotAll
		
	func_next1:
		cmp eip,FillCall1   //分支3 停在FillCall1
		JNE func_next2
		cmp [edi],90909090
		JE PauseAt_FillCall1
		jmp NotAll
		
		
	func_next2:
		
		cmp eip,GetApi2 // 分支4 停在GetApi2
		je PauseAt_GetApi2
		
		
		cmp eip, GetIat2  
		JNE func_next3
		
		cmp [edx],0
		JE PauseAt_GetIat2  //分支5 停在GetIat2

		cmp [edx],90909090
		JE PauseAt_FillCall2  //分支6 停在FillCall2
		jmp NotAll
		
	func_next3:
		cmp eip,BaseZero      //分支7,停在BaseZero
		je PauseAt_BaseZero
		
		cmp eip,GetBase    //分支8,停在GetBase
		je PauseAt_GetBase
		
		
	NotAll:
		run
		jmp func
	
		
//}





//函数11,修正部分函数地址
FixRelocApi:
//{

		cmp ModBase,7c800000
		jne CON
		
	
		cmp FuncAddr,7c8092ef
		JE SetLastError

		cmp FuncAddr,7c809136
		JE HeapSize
		
		cmp FuncAddr,7c80911e
		JE RtlReAllocHeap
		
		cmp FuncAddr,7c8090db
		JE RtlGetLastWind32Error
		
		cmp FuncAddr,7c8090f6
		JE RtlAllocHeap
		
		cmp FuncAddr,7c80910c
		JE RtlFreeHeap
		
		cmp FuncAddr,7c8091c9
		JE RtlLeaveCritical
		
		cmp FuncAddr,7c8090bd
		JE RtlEnterCritical
		
		cmp FuncAddr,7c80906a
		JE RtlDeleteCritical
		
		jmp CON
		
	RtlDeleteCritical:
		mov FuncAddr,7C93137A
		jmp CON
		
	RtlEnterCritical:
		mov FuncAddr,7C921000
		jmp CON
		
	RtlLeaveCritical:
		mov FuncAddr,7C9210E0
		jmp CON
			
	RtlFreeHeap:
		mov FuncAddr,7C92FF2D
		jmp CON
		
	RtlAllocHeap:
		mov FuncAddr,7C9300C4
		jmp CON
		
	RtlGetLastWind32Error:
		mov FuncAddr,7C92FE21
		jmp CON
		
	RtlReAllocHeap:
		mov FuncAddr,7C938477
		jmp CON
		
		
	HeapSize:
		mov FuncAddr,7c9304dd
		jmp CON
	       
	 
	SetLastError:
		mov FuncAddr,7c92fe30
		jmp CON

	CON:
		run
		jmp func

//}

 


脚本运行完正好停在oep(00401140)
这是用UIF(universal import fixer),修复后,用od dump,就脱壳成功了.
再用LordPe 删除不需要的区段,再重建pe 就ok了

1.uif

2.od dump

3.脱壳后,还没减肥


实例二 dtws2.exe
     1.这个exe 是游戏目录下没加壳的,我用tmd2.1.8加了壳,然后再用上面的方法脱.先用通用脚本找到esi表和ebx表,记录下
   2.找GetBase
     对ebx表下内存访问断点,断在
   
/*6200E4*/  mov     ecx, dword ptr ds:[edx]  //ds:[026E0000]=76D30000 (iphlpapi.76D30000)
ecx=00C78F59

/*6200E6*/  pop     edx

  GetBase=6200E4
ebx={
0x76d30000, 0x10000000, 0x76f30000, 0x7c800000, 0x77d10000, 0x77ef0000, 0x76320000, 0x72f70000, 0x77da0000, 0x77f40000, 0x770f0000, 0x71a20000, 0x02800000, 0x3e410000, 0x76b10000};
依次是iphlpapi.dll,libeay32.dll,wldap32.dll,kernel32.dll ....

esi={
0xa135d069, 0xd426dfae, 0x00003e2a, 0xaaaaaaaa, 0xffffffff, 0xdddddddd, 0xeeeeeeee, 0xdddddddd, 0xa0566741, 0xbbbbbbbb, 0x9426df77, 0x80009ba6, 0xaaaaaaaa, 0xffffffff, 0xdddddddd,....};
只抄了最前面一部分
   3.找GetApi2
   由于ebx表第一项是非加密dll 并且esi表 第一项不带0xbbbbbb,esi第一项下内存访问,然后往下跟找到
/*6210C8*/  sub     eax, 0x33917DD2
/*6210CD*/  add     eax, ecx
/*6210CF*/  push    ebx
/*6210D0*/  mov     ebx, 0x2D4A6024
/*6210D5*/  shr     ebx, 0x6
/*6210D8*/  shl     ebx, 0x5
/*6210DB*/  dec     ebx
/*6210DC*/  add     ebx, 0x55925CE5
/*6210E2*/  shr     ebx, 1
/*6210E4*/  sub     ebx, 0x28A48A0
/*6210EA*/  add     eax, ebx    //执行完之后 eax=func addr ,ecx=dllbase
/*6210EC*/  pop     ebx


GetApi2=6210EC

4.找GetIat1和FillCall1
   这时代码段下内存写断点,断在
/*623CCB*/  pop     dword ptr ds:[eax]  //这句
/*623CCD*/  jmp     dtws2.00623CE0

GetIat1=623CCB
  再F9几次,填充call
/*624315*/  stos    dword ptr es:[edi]
/*624316*/  jnz     dtws2.00624326
FillCall1=624315
  
   5.对ebx表 对应项下内存写找 BaseZero
  /*6244F2*/  mov     dword ptr ds:[ecx], ebx
/*6244F4*/  pop     ebx

  BaseZero=6244F2

6.GetApi2,GetIat1,FillCall1 搞定 这时搞 GetIat2
在准备访问ebx表中kernel32.dll时,下内存写断点,he GetApi2 看这个地址对不对,验证发现GetApi2是对 可以根据内存断点找到 GetIat2 和FillCall2
/*57BB64*/  pop     dword ptr ds:[edx]
/*57BB66*/  jmp     dtws2.005715CF

GetIat2 =FillCall2=57BB64

7.搜索发现有0xbb项,对esi表 含0xbb的 下内存访问断点,中途可以删除一些不必要的项
/*620D8D*/  popad
/*620D8E*/  sub     eax, 0x22202137
/*620D93*/  add     eax, 0x736C1130
/*620D98*/  add     eax, ecx
/*620D9A*/  sub     eax, 0x736C1130
/*620D9F*/  add     eax, 0x22202137  //执行完之后 eax=func addr ,ecx=dll base
/*620DA4*/  stc
  GetApi1=620DA4

8 8个值都搞定,开始写脚本
  GetApi1= 620DA4        GetApi2=  6210EC   BaseZero=6244F2    GetBase=6200E4
   GetIat1= 623CCB       GetIat2=57BB64
   FillCall1= 624315      FillCall2=57BB64
  
脚本跟上面的模板基本一样,见附件,运行完脚本,uif fix,这次用od dump,不重建输入表,因为有时数据量太大有过死机经验,所以dump之后用 ImportREC fix.

1.加壳后

2.uif fix图


3.oddump图


4.重建iat


5脱壳后报下图错误,run time error


6.脱壳后(带nop)


text:0047230B                 call    __IsNonwritableInCurrentImage
.text:00472310                 test    eax, eax
.text:00472312                 pop     ecx
.text:00472313                 jz      short loc_472320  // nop
.text:00472315                 push    [esp+arg_0]
.text:00472319                 call    ds:off_4BA658
.text:0047231F                 pop     ecx

把对IsNonwritableInCurrentImage 这个函数的所有调用后的 je/jz nop
具体原因可上网查 什么vs2005新增特性,加壳后把 只读段跟代码段等等全合并了,导致只读段变成了可写段

附件是 加壳前,加壳后,脱壳后 的 测试程序 和 脱壳脚本

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 0
支持
分享
最新回复 (8)
雪    币: 1392
活跃值: (5152)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
2
这么早起来的都是好孩子。感谢楼主分享。现在还在VMP中纠结
2013-11-11 09:12
0
雪    币: 116
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
什么情况下,需要追求程序脱壳后还能正常运行
2013-11-11 09:33
0
雪    币: 253
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
TMD没研究啊。。学习了。。
2013-11-11 12:25
0
雪    币: 1036
活跃值: (1311)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
5
感谢分享,学习到了经验!
2013-11-11 16:54
0
雪    币: 9
活跃值: (369)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
把附件整给我研究下
2013-11-11 18:10
0
雪    币: 59
活跃值: (52)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
int wmain(int argc,WCHAR** argv)
{
       
        HANDLE hProcess=NULL;
        SC_HANDLE hManager=NULL;

        hProcess=GetCurrentProcess();
        hManager=OpenSCManagerW(NULL,NULL,SC_MANAGER_CONNECT);

       
       
        MessageBoxW(NULL,L"ccppyy",L"cap",MB_OK);

       
CLEANUP:
        if(hManager)
                CloseServiceHandle(hManager);
        if(hProcess)
        CloseHandle(hProcess);
        return 0;
}
这是测试程序的源码

void Test333()
{
        UINT ApiAddr=0xD11F32FB;  //esi表第一项 获取api hash
        UINT IatAddr=0x467e88a3;  //esi表 第二项 获取iat地址
        UINT PatchAddr=0x80000273; //esi表第三项及以后,获取填充地址
        UINT Key1=0x302F0BB4;
        UINT Key2=0x38FA8E12;

        //api addr
        _asm
        {
                mov eax,ApiAddr;
                ror eax,3
                        mov ApiAddr,eax
        }
        ApiAddr-=Key2;
        _asm
        {
                mov eax,ApiAddr;
                rol eax,0x10
                        mov ApiAddr,eax
        }
        ApiAddr^=Key1;
        wprintf(L"%0#10x\n",ApiAddr);

        //iad addr
        __asm
        {
                mov  eax, IatAddr
                        rol  eax, 5
                        mov  IatAddr, eax
        }

        IatAddr += Key1;
        IatAddr+=0x00400000;

        wprintf(L"%0#10x\n",IatAddr);

        //patch addr
        __asm
        {
                mov  eax, PatchAddr
                        rol  eax, 3
                        mov  PatchAddr, eax
        }

        PatchAddr+=0x00400000;

        wprintf(L"%0#10x\n",PatchAddr);
}

上面这段代码,论坛上有,是可以直接解析 esi表的,至于key是怎么来的,稍微动下脑筋不难.
这段代码估计以前确实可以解析完整个esi表
但我试了下tmd2.1.8 发现只能用来解析非加密的项 esi表中加密的项已经解不出来了.

peid 没法查壳的,可以在userdb.txt 加点特征码 其实就是oep这个地方的几个字节

[Themida 2.1.x.x -->Oreans Technologies * Sign.By.cxj98]
signature = 83 EC ?? 50 53 E8 ?? ?? ?? ?? CC
ep_only = True

[Themida 2.0.x.x - 2.1.x.x -->Oreans Technologies (Hide From PE Scanners)]
signature = 89 1C 24 E8 01 00 00 00 CC
ep_only = False

[Themida 2.0.x.x -->Oreans Technologies]
signature = B8 00 00 00 00 60 0B C0 74 ??
ep_only = 1

这也是在论坛上找到的
2013-11-11 23:56
0
雪    币: 18
活跃值: (81)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
VM 是一道坎。
2013-11-12 09:57
0
雪    币: 124
活跃值: (469)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
没搞过啊。。。
2013-11-12 12:17
0
游客
登录 | 注册 方可回帖
返回
//