首页
社区
课程
招聘
[原创]菜鸟啄硬壳―我的脱壳手记
发表于: 2007-1-17 01:26 19876

[原创]菜鸟啄硬壳―我的脱壳手记

wulje 活跃值
14
2007-1-17 01:26
19876

[摘要]         1.轻松找出脱壳后的OEP。
........2.ESP定律和pushad、popad成对理论都是不对的。
........3.RVA困扰将不复存在,菜鸟也可轻易玩脱壳,只需了解简单地加减400000的原则。
[预备知识] 只须对PE文件头结构有初步的了解。
       
        我贴了几篇烂文就有点沾沾自喜起来,闲着没事又打起了“壳”的歪主意。我想找一个短小的“软壳蛋”来小试一下鸡刀,没想挑选到了一个“铁核桃”,差点没把牙齿啃脱。壮着胆子闯下去,咳~!终于出来了,还悟出不少道理来。
        秀才耍棍棒,还得要点基本功。“壳”这个东西对菜鸟有些神秘化,主要原因是缺乏对PE文件结构的了解,更对RVA的转换头痛。脱壳之前先将段钢的“加密与解密”或罗云彬的“汇编程序设计”中关于PE文件结构部分读三遍就可以应负了!下面用脱FantaMorph.exe 的注册机Keygen.exe(老外写的)的壳为例,谈点脱壳思路和方法。(Keygen.exe见附件,请高手验证一个该壳是软还是硬?)
        一、奇怪的PE头和脱壳的初试牛刀
        1.没见过这样的PE头:
        先用16进制编辑器打开keygen.exe文件,看看它的PE头,如下:

00400000        4D 5A 00 00 00 00 00 00 00 00 00 00 50 45 00 00  MZ..........PE..
00400010        4C 01 02 00 46 53 47 21 00 00 00 00 00 00 00 00  L  .FSG!........
00400020        E0 00 0F 01 0B 01 00 00 00 2C 00 00 00 50 01 00  ?   ...,...P .
00400030        00 00 00 00 54 01 00 00 00 10 00 00 0C 00 00 00  ....T ... ......
00400040        00 00 40 00 00 10 00 00 00 02 00 00 04 00 00 00  ..@.. ... .. ...
00400050        00 00 00 00 04 00 00 00 00 00 00 00 00 30 02 00  .... ........0 .
00400060        00 02 00 00 00 00 00 00 02 00 00 00 00 00 10 00  . ...... ..... .
00400070        00 10 00 00 00 00 10 00 00 10 00 00 00 00 00 00  . .... .. ......
00400080        10 00 00 00 00 00 00 00 00 00 00 00 98 23 02 00   ...........? .
00400090        84 00 00 00 00 C0 01 00 14 0D 00 00 00 00 00 00  ?...?. .......
004000A0        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
004000B0        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
004000C0        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
004000D0        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
004000E0        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
004000F0        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00400100        00 00 00 00 00 00 00 00 00 00 00 00 00 B0 01 00  .............?.
00400110        00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00  . ..............
00400120        00 00 00 00 00 00 00 00 E0 00 00 C0 00 00 00 00  ........?.?...
00400130        00 00 00 00 00 70 00 00 00 C0 01 00 19 64 00 00  .....p...?. d..
00400140        00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00  . ..............
00400150        E0 00 00 C0 87 25 DC 23 42 00 61 94 55 A4 B6 80  ?.?%?B.a?ざ?
00400160        FF 13 73 F9 33 C9 FF 13 73 16 33 C0 FF 13 73 1F  ?s?? s 3? s
00400170        B6 80 41 B0 10 FF 13 12 C0 73 FA 75 3A AA EB E0  ?A?? 荔?:?
00400180        FF 53 08 02 F6 83 D9 01 75 0E FF 53 04 EB 24 AC  ?  ??uS ?
00400190        D1 E8 74 2D 13 C9 EB 18 91 48 C1 E0 08 AC FF 53  谚t- 呻 ?拎 ?S
004001A0        04 3B 43 F8 73 0A 80 FC 05 73 06 83 F8 7F 77 02   ;C?.?s ?w
004001B0        41 41 95 8B C5 B6 00 56 8B F7 2B F0 F3 A4 5E EB  AA?哦.V?+痼ま
004001C0        9F 5E AD 97 AD 50 FF 53 10 95 8B 07 40 78 F3 75  ???? ?@x篚
004001D0        03 FF 63 0C 50 55 FF 53 14 AB EB EE 33 C9 41 FF   ?.PU? ??闪?
004001E0        13 13 C9 FF 13 72 F8 C3 02 D2 75 05 8A 16 46 12    ? r? 阴 ?F
004001F0        D2 C3 4B 45 52 4E 45 4C 33 32 2E 64 6C 6C 00 00  颐KERNEL32.dll..
       
        这是什么PE文件头啊?PE标记怎么跑到第1行上去了?怎么一个区段块都没有?别急,按着标准结构一步步找下去。开始的DOS头标记“MZ”存在,在DOS的IMAGE_DOS_HEADER结构中,成员e_lfanew(名字不重要,只要知道“偏移”=3Ch就足够了)是指向PE头的。该成员在0+3Ch处,值是0C(双字,低位在前),显然PE二字标记该出现在0C位置。果然,但却跑到IMAGE_DOS_HEADER结构中去了。(这是我们以前不敢想的事,也给我们启发:只要MZ字符在,e_ifanew成员在,IMAGE_DOS_HEADER结构中的其它字节都可以任意修改。)
        (1)找到PE头后,往下走(要比照到PE头结构表查找),4C01是CPU类型,0200是块数目,2块?这是欺骗比尔.盖茨让他放行的,脱壳后块数目可以会增加很多。46 53 47 21是日期,E0 00是PE头结构大小,这个大小目前好象没人敢动?我想比尔.盖茨把它放在这里是可以改动的,只要相应地增加PE头的尺寸与这个数据相符是可以的?前面DOS文件头和PE文件头交织在一起不就是很好的例证吗?
        (2)其它不用多说。重要的是34h(对PE头的偏移=28h)位置的54 01 00 00是入口地址。60h位置的00 02 00 00是PE头+节表的尺寸,总长度才200h,程序入口怎么跑到节表里面来了?看来,你只要向比尔.盖茨递交几个确定文件属性和位置的必要数据后,其它懒得管你!
        (3)对PE头就先认识到到这里。以后还要回来和它打交道。其实你先可以连入口地址都不必急于去认识,OD一运行就自动停在入口了。
       
        2.何必这样劳神费力,找几个脱壳工具试试?
        这话有理,忙找来几个壳识别工具上阵,个个都摇头不认识!更不说脱壳了,这道也是,没准那天我也搞出个什么壳来,这些工具认得过来吗?特别是菜鸟们不要迷信那些工具,大多是为一些特例设计的,当人们不再使用众人皆知壳时,它就没用了。自己动手成长可能还会更快一些。Dump,Dump吧,就用OD自带的dump插件吧!运行后一试,OD反到问你:OEP定在那里?自动吧,结果令你哭笑不得,OEP居然跑到kernel32.dll中去了。原来它们判断OEP的方法大概是找jmp最远的地址?记住,凡事都有例外!
       
        3.脱壳的ESP定律、pushad,popad成对的定律有些不灵了:
        有些网友总结出脱壳的一些“定律”,如ESP定律、pushad,popad成对等定律。这些经验不能不说可贵,但一旦上升为“定律”,就不得不论证了。写软件就象写小说,谁知道作者怎样编造故事情节?谁说call后的[ebp+4]就一定是返回地址?程序轻易就可以将该值偷换了。不信就看看这个壳。
        该程序的解壳代码总长不过9Eh字节,从154~1F1。一开始就是popad:

00400154    8725 DC234200.......xchg dword ptr ds:[4223DC],esp
0040015A    61..................popad
0040015B    94..................xchg eax,esp
0040015C    55..................push ebp
0040015D    A4..................movs byte ptr es:[edi],byte ptr ds:[esi]
0040015E    B6 80...............mov dh,80
00400160    FF13................call dword ptr ds:[ebx]
00400162    73 F9...............jnb short 0040015D                  ; 0040015D
00400164    33C9................xor ecx,ecx
00400166    FF13................call dword ptr ds:[ebx]
        154是入口地址,15A就popad,从头到尾找不到pushad。没有pushad那来popad?这不仅仅是事实,而且软件运行得很正常。原来第1句154将esp指向了内存4223DC,第2句popad将内存4223DC前的8个双字弹到了寄存器中去了,第3句将esp还原!好绝啊!这里pushad,popad成对理论是没戏了。再看ESP定律,在我的电脑中,开始esp=13FFC4,若用HW 13FFC0设断,大概只能单步单步地走了,你看看接下来的一连串的call [ebx],个个都要入栈出栈,能连续运行吗?ESP定律也不灵了。
       
        二、壳的基本属性和功能
        我先声明,我只读过文中开始提到的那两本书,不敢说对壳有深入的了解!而且才开始学脱壳。何况壳是人写的,没准明天有人就在里面添加了什么新花样。
        有人说,壳在程序运行前先取得控制权,解壳后才将控制权交给应用程序。我认为过程大概是没错,但说法似乎不太准确。壳其实就是应用程序的一部分,windows将控制权交给应用程序不是在解壳后,而是在解壳前。准确地说,windows将文件读入内存后,首先查找IAT表(导入地址表),若有,就装载相关的API函数地址,再将与程序有关的dll和数据作一些初始化的工作,完了之后就查找入口地址,将eip指向入口后,就将控制权交给了程序。以后控制权的事是属于程序内部的权利之争,windows是不管的。我说这些,不是说我有什么知识,了解这些为后面找到OEP和脱壳后的IAT地址的修改带来极大的方便。
        那么些壳有什么功能呢?我体会有:(1)解压,(2)装载API地址,(3)跳出出口。第(2)(3)条就是我们攻破壳的重要思路。为什么说壳要装载API呢?因为同一个API函数在不同的电脑上的地址是不同的,壳不能先把地址填好再封装,只能现场装载API。请注意,现场!也就是说壳只有先解压,直到所有的API名称归位后(解压到指定位置)才能装载,当我们发现它在装载API地址时,壳离出口就近在咫尺了。另一个更大的好处是我们可以在内存中找到这些归位后的API,IAT表就在它附近。在壳装载IAT表前把它拷贝下来,当dump后,再将它拷贝到文件中去,IAT表就基本修改好了!
        写到这里,我突然想到了什么……。不如在壳开始装载时,就强迫它提前出壳,连IAT表都是现存的!(不管是壳装载还是windows装载,都必需IAT表,故任何壳一定要先解压出一个IAT表来才开始装载,我们抢先抓取这个IAT表会得到极大好处)

        三、寻找OEP的两种思路
        1.最经典的方法就是找jmp转跳的地址:
        前面提到壳必须跳出出口(不能返回),只能用jmp(未必),条件转跳是不行的(跳程太短),call上做点手脚是可行的,不知目前有没有?本程序的解壳代码中共有5个jmp,其中4个都是在本段内跳,只有一个是jmp [ebx+c],它肯定是出口无疑了。在这儿设断逮住OEP是没问题了。万一壳很长,在众多的jmp中找到出口也是有困难的。
        2.用硬件中断,拦截LoadLibrary或GetProcAddress:
        众多的壳将所用的API封装起来,但有两个不能封装,这就是LoadLibrary和GetProcAddress。这两个函数的地址必须在windows交控制权给程序前先由windows装载,如果你把它封装了,windows就不装载它。没有这两个函数,应用程序任何一个API函数的地址都是找不到的(任何壳都不例外),因为这是比尔.盖茨规定的。在壳中可以轻易地拦截到这两个函数,这是壳最大的软肋。(若你自己写一个类似的装载函数又当另议,可能马脚会更大)。马上实验,用OD打开Keygen,在弹出式菜单中找“搜索―全部模块中的命令”,找到GetProcAddress记下地址:4223F4,在OD命令拦中输入:HR 4223F4回车。运行后立即中断在:4001D6。(若在kernel32中,按ctrl+F9返回)

004001C2    AD..........lods dword ptr ds:[esi]
004001C3    97..........xchg eax,edi
004001C4    AD..........lods dword ptr ds:[esi]
004001C5    50..........push eax
004001C6    FF5310......call dword ptr ds:[ebx+10]
004001C9    95..........xchg eax,ebp
004001CA    8B07........mov eax,dword ptr ds:[edi]
004001CC    40..........inc eax
004001CD    78F3........js short 004001C2                   ; 004001C2
004001CF    7503........jnz short 004001D4                  ; 004001D4
004001D1    FF630C......jmp dword ptr ds:[ebx+C]
004001D4    50..........push eax
004001D5    55..........push ebp
004001D6    FF5314......call dword ptr ds:[ebx+14]          ; kernel32.GetProcAddress
004001D9    AB..........stos dword ptr es:[edi]
004001DA    EBEE........jmp short 004001CA                  ; 004001CA
       
        看到了吧,它正是GetProcAddress,这时要取消硬件中断(否则每次都要到kernel32中去转一回),从状态拦中发现dei指向404000,内存换到404000,稍住下走就发现了一连串的API函数名。在程序第一次通过4001D9前,拷贝下404000~4040AF地址中的值,这正是我们需要的IAT表中的值。(若按前面“突然想到”的方法提前出壳的,无须拷贝,但dump后还是要稍加修改。)
        注意到本段程序,单步执行时,程序始终不进入4001D1,原来进入的条件是eax=-1,原来eax是在404000~4040AF取值,当取到404078时,eax=-1,这时API函数也装载完了,程序就进入jmp [ebx+C],要跳出了,这时刻状态拦显示:4038EA。这不正是OEP么?

        四、抓取内存影像,修改IAT表
        由于有了OEP,在OD中可以dump了,将脱壳的文件抓取出来的内存影像取名dump.exe存盘。
        1.轻松解决IAT表中地址转换的RVA问题:
        内存地址和磁盘文件地址的RVA转换问题是新手的一个难于解决的困难,甚至一些老手有时也会犯糊涂,因为在不同的区段中RVA是不同的。但脱壳又回避不了这个问题,故很多新手望而却步。现在却可以轻松解决了,奥妙在那里?往下看:把前面提到的内存中404000~4040AF的那段数据拷贝如下:
        (若按前面“突然想到”的方法提前出壳的,也要了解修改方法。)

00404000  49 41 40 00  59 41 40 00  B7 42 40 00  CB 42 40 00  IA@.YA@.仿@.寺@.
00404010  FF FF FF 7F  35 42 40 00  A9 42 40 00  D7 41 40 00  ??5B@.┞@.琢@.
00404020  DF 41 40 00  E7 41 40 00  FD 41 40 00  05 42 40 00  吡@.缌@.?@. B@.
00404030  95 42 40 00  1B 42 40 00  25 42 40 00  13 42 40 00  ?@. B@.%B@. B@.
00404040  41 42 40 00  55 42 40 00  65 42 40 00  75 42 40 00  AB@.UB@.eB@.uB@.
00404050  83 42 40 00  FF FF FF FF  BF 41 40 00  AD 41 40 00  ?@.??苛@.?@.
00404060  9D 41 40 00  73 41 40 00  91 41 40 00  85 41 40 00  ?@.sA@.?@.?@.
00404070  FF FF FF 7F  00 00 00 00  FF FF FF FF  28 3B 40 00  ??....??(;@.
00404080  3C 3B 40 00  00 40 40 00  64 41 40 00  58 40 40 00  <;@..@@.dA@.X@@.
00404090  CA 41 40 00  14 40 40 00  F0 41 40 00  00 00 00 00  柿@. @@.鹆@.....
004040A0  00 00 00 00  00 00 00 00  00 00 00 00  E8 40 00 00  ............枥..
       
        注意表中的第一个双字:404149,查一查该地址是什么?40414A是字串:GetTickCount,它是kernel32.dll中的函数。为什么404149不直接指到函数名呢?原来调用GetProcAddress前有个inc eax,这就对了。但windows加载和自己调用GetProcAddress加载稍的区别,windows加载可以给被加载函数编号,所以每个函数名前都留有2字节的编号位。若让它为00 00,则编号为0。但这两字节却不能少,若让windows加载,404149指的位置也不对,只能使用404148给字串前留出两个空位!这样修改方就法明确了。
        再用16进制编辑器打开dump.exe,找到和上面位置相同的地方(再稍往下走就是连串的API函数名),把它也拷贝在这儿对比修改:
00004000  35 30 02 00 44 30 02 00   4E 30 02 00 61 30 02 00
00004010  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
00004020  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
……………………………………………………
        现在根本就完全舍去了RVA这个概念。你只须要把前表中的每个值(记住低位减1),去掉那个40,照原样填写在下表相应的位置中,并把7FFFFFFF和FFFFFFFF改为全0,IAT表就改选完成了。即:
00004000  48 41 00 00  58 51 00 00   B6 42 00 00  CA 42 00 00
00004010  00 00 00 00  34 42 00 00   A8 42 00 00  D6 41 00 00
00004020  DE 41 00 00  E6 41 00 00   FC 41 00 00  04 42 00 00
………………………………………………………
        注意,填写的这个值,与dump.exe文件中该表所处的地址一点关系都没有,比方说,如果该表地址不是00004000,而是00003200等什么的,填回的值也是上面这些吗?答案是肯定的!
        奇了!绝了?为什么会是这样呢?原来,你以为比尔.盖茨会为每一个应用程序去计算RVA吗?错了!现在不是比尔.盖茨迁就他的用户,而全世界都在迁就比尔.盖茨!这个RVA就是软件编译器为迁就微软而设计的,害苦了不少人。Windows根本不管你文件IAT表中是什么值,初始化时它只管在每个地址上加上400000(装载地址),其它一概不管!
        若你一定要弄清楚这个RVA是怎么回事,那么可假没dump.exe的IAT表地址在3200,程序要正常运行,则表中的值是不能变的。如下:
00003200  48 41 00 00  58 51 00 00   B6 42 00 00  CA 42 00 00
00003210  00 00 00 00  34 42 00 00   A8 42 00 00  D6 41 00 00
……………………………………………………
        4148指向了一个莫名其妙的地方,而3248才指向了一个API函数名(有两空位)。若将该位置写成3248,程序立即崩溃,必须将3248+RVA填入该地址,程序才能运行(RVA=F00)。你会说RVA不就是4148-3248吗?没错,但当该地址值是空白或别的什么值时,你能说出是多少吗?比尔.盖茨才不管这些,只管给你加400000,软件出品前你得将本应该指向API的3248换成一个莫名其妙的4148。这就是RVA转换,害人。(这个RVA我不想说了,也说不清楚,读者也不感兴趣了)
        由于老比的偷懒,也乐得我也拣个现存。这就是前面说的只要去掉那个40,低位再减1就将IAT表修改好了。

        五、最后的工作
        累了吧?还剩下一点工作,不过不太复杂。坚持下去吧!
        1.创建IID表(导入表):
        脱壳后的程序大多没有IID表,即使有也被写上了垃圾代码,只有重建。所谓IID表,就是指向几个*.dll函数名的表,windows是根据它来找到API的。依然用16进制编辑器打开抓取的dump到4000。我们发现414A开始就是API名,在它们中只须找出带*.dll的函数名,并记下它的地址。它们是:kenerl32.dll,user32.dll,msvcrt.dll。如00004164是kenerl32.dll。

00004140  00 00 00 00 00 00 00 00  00 00 47 65 74 54 69 63  ..........GetTic
00004150  6B 43 6F 75 6E 74 00 00  00 00 6C 73 74 72 6C 65  kCount....lstrle
00004160  6E 41 00 00 4B 45 52 4E  45 4C 33 32 2E 64 6C 6C  nA..KERNEL32.dll
…………………………………………………………
        简单地说,一个IID表(简化可用的)数据结构如下(它可以建在任何位置):
0000xx00        00000000  00000000  00000000  64410000  
0000xx10        00400000
        该数组前面那个4164就指向KERNEL32.dll(windows不给库函数留编号位),后面那个4000就指向前面我们修改过的IAT表中的一个地址。万一这个字串地址不在00004164,而是00003264,如下:
00003260  6E 41 00 00 4B 45 52 4E 45  4C 33 32 2E 64 6C 6C  nA..KERNEL32.dll
        那个IID表还是填64410000吗?只要装载后该字串地址在00404164,则答案是肯定的,而不管磁盘文件中这个字串的地址在那里!去掉老比给加上的那个40,就是文件需要的实际值。如此说来,这是很简单的事吗!世间万物懂了都简单。我代劳写出全部的IID表:在表的最后必须有5*8个0字节。
000040A0        00000000  00000000  00000000  64410000  
000040B0        00400000  00000000  00000000  00000000
000040C0        CA410000  58400000  00000000  00000000
000040D0        00000000  F0410000  14400000  
…………………………………………………………………………       
        只须注意每个IID表的最后那组数(第5组),它指向的是该.dll库中调用的第一个API的(地址),装载前其值指向它自己的函数名,装载后是该函数的地址。用4个0作为一个库结束的标记。至此,程序代码的所有改选都已完成,最后只需在PE头中登记即可。
       
        2.在PE头上登记你的IID表、IAT表位置和表的长度:
        记住了简单地减去400000的原则了吧,而不管它实际地址是什么值。现在回到dump.exe 的PE头中:
00000080  10 00 00 00 00 00 00 00 00 00 00 00 00 30 02 00   ............0 .
00000090  84 00 00 00 00 C0 01 00 14 0D 00 00 00 00 00 00  ?...?. .......
……………………………………………………
000000E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000100  00 00 00 00 00 00 00 00 00 00 00 00 00 B0 01 00  .............?.
00000110  00 10 00 00 00 B0 01 00 00 10 00 00 00 00 00 00  . ...?.. ......
00000120  00 00 00 00 00 00 00 00 E0 00 00 C0 00 00 00 00  ........?.?...
00000130  00 00 00 00 00 70 00 00 00 C0 01 00 00 70 00 00  .....p...?..p..
00000140  00 C0 01 00 00 00 00 00 00 00 00 00 00 00 00 00  .?.............
00000150  E0 00 00 C0 2E 69 64 61 74 61 32 00 00 10 00 00  ?.?idata2.. ..
00000160  00 30 02 00 00 04 00 00 00 30 02 00 FF 13 73 1F  .0 .. ...0 .?s
00000170  B6 80 41 B0 10 FF 13 12 40 00 00 C0 3A AA EB E0  ?A?? @..??
       
        该头变化不大,只是增加了一个.idata2区段,该区段数据结构中居然还有不少垃圾代码。不管这些。该作下列工作:
        (1)登记IID表(导入表):PE头中所有双字都是按地址尾数为0、4、8、C对齐的。PE头中有惟一一个特殊的双字是10000000(它表示16个节表的开始)。也就是说只要在地址尾数为0、4、8、C的某个位置找到10,节表就从这里开始。本程序是在0000080。它后面的第一个8字节是导出表,不管。第二个就是导入表,正是要找的:它已经有0030020084000000了,它没用,是OD搞错了乱写的(笑,的确是OD乱点鸳鸯谱),填写上:40A00000 50000000。前者是改造的IID表的开始位置,后者是它的长度。(再次强调:不管实际IID表在什么地址,只要装载后是004040A0,减去400000就是要登记的地址,老比是不会帮你计算RVA的。)
        (2)登记IAT表(导入函数地址表):前面修改的那个IAT表装载后地址是00404000,长度是80。它是16个节表中的第13个。从双字10000000后开始按8字节一组,数到第13组,就是该表所在位置,即在000000E4开始的位置处填写上:00400000 80000000,存盘。
        OK,全部工作完成!正准备收工时,我又一次“突然想到”……,windows装载函数地址时加上的那个400000会不会用or 400000,如果是这样,前面那些去掉40的作法不是都没必要了吗?我忙重复实验了一次,不全对也不全错……。但前面的修改工作却更加简化了!(你自己实验吧!)
        运行存盘后的dump.exe,成功了!它所调用的所有函数在OD上显现无遗,代码每步可见。不过程序资源部分还不能用eXeScope编辑。这次累了,下次争取把资源部分也解密出来,它肯定已经解压了,只是加了密。
        谢谢你把它读完!最后我想起高中时数学老师对我说过的一句话:“学数学重要的不是学它的公式和算法,而是学它的思想”。现在才似乎若有所悟。


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

上传的附件:
收藏
免费 7
支持
分享
最新回复 (47)
雪    币: 47147
活跃值: (20450)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
文章精彩,看完有收获~
2007-1-17 09:15
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
经典.
2007-1-17 09:16
0
雪    币: 898
活跃值: (4039)
能力值: ( LV9,RANK:3410 )
在线值:
发帖
回帖
粉丝
4
凡事都有相对性
...
算了,不说了
2007-1-17 09:24
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
很受启发
2007-1-17 10:04
0
雪    币: 405
活跃值: (10)
能力值: ( LV9,RANK:1130 )
在线值:
发帖
回帖
粉丝
6
哇楼主辛苦那。打了这么多字我得慢慢欣赏才行。
2007-1-17 10:34
0
雪    币: 208
活跃值: (51)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
这不写着FSG的壳么.......
2007-1-17 10:43
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
8
意思是说“学数学重要的不是学它的公式和算法,而是学它的思想”,不要误会
2007-1-17 10:59
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
还是有收获的
2007-1-17 11:16
0
雪    币: 233
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
需要慢慢体会。
2007-1-17 12:01
0
雪    币: 2870
活跃值: (2335)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
有收获的,说出了原理!!!!
谢谢!!!
2007-1-17 13:21
0
雪    币: 242
活跃值: (163)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
12
还不错的
2007-1-17 15:34
0
雪    币: 154
活跃值: (80)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
好象不太硬的,OD的跟踪到SFX入口就能找到oep,不过我的必须选中字节方式跟踪到sfx入口,然后跟踪过程中再改为停在自解压入口一会就到oep了,手动输入IAT地址和大小,重建后也正常运行 脱壳后多了一个段.mackt
作者的PE功底深厚,文章也很有参考意义,佩服佩服 期待着介绍解密程序资源
2007-1-17 17:04
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
受教了!!!!!!~~~~~~~~~~~
2007-1-17 18:53
0
雪    币: 325
活跃值: (97)
能力值: ( LV13,RANK:530 )
在线值:
发帖
回帖
粉丝
15
LoadLibrary一般来说不好模拟。设计到HMODULE的问题
而GetProcAddress是可以模拟的
2007-1-17 21:24
0
雪    币: 200
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
多好的文章啊!期待着介绍解密程序资源......。
2007-1-17 22:54
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
学习了~~
2007-1-17 23:31
0
雪    币: 226
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
18
Good,很有新意。谢谢分享!
2007-1-18 16:30
0
雪    币: 207
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
楼主这个keygen是变形的fsg2.0,怀疑IAT是改造过的。

我这里有一个标准的helloworld程序,用fsg2.0加壳的:

用OD载入:


00400154 > 8725 48FF4200 xchg [42FF48], esp
0040015A 61 popad
0040015B 94 xchg eax, esp
0040015C 55 push ebp
0040015D A4 movs byte ptr es:[edi], byte ptr [esi>
0040015E B6 80 mov dh, 80
00400160 FF13 call [ebx]
00400162 ^ 73 F9 jnb short 0040015D
00400164 33C9 xor ecx, ecx
00400166 FF13 call [ebx]
00400168 73 16 jnb short 00400180
0040016A 33C0 xor eax, eax
0040016C FF13 call [ebx]
0040016E 73 1F jnb short 0040018F
00400170 B6 80 mov dh, 80
00400172 41 inc ecx
00400173 B0 10 mov al, 10
00400175 FF13 call [ebx]
00400177 12C0 adc al, al
00400179 ^ 73 FA jnb short 00400175
0040017B 75 3A jnz short 004001B7
0040017D AA stos byte ptr es:[edi]
0040017E ^ EB E0 jmp short 00400160
00400180 FF53 08 call [ebx+8]
00400183 02F6 add dh, dh
00400185 83D9 01 sbb ecx, 1
00400188 75 0E jnz short 00400198
0040018A FF53 04 call [ebx+4]
0040018D EB 24 jmp short 004001B3
0040018F AC lods byte ptr [esi]
00400190 D1E8 shr eax, 1
00400192 74 2D je short 004001C1
00400194 13C9 adc ecx, ecx
00400196 EB 18 jmp short 004001B0
00400198 91 xchg eax, ecx
00400199 48 dec eax
0040019A C1E0 08 shl eax, 8
0040019D AC lods byte ptr [esi]
0040019E FF53 04 call [ebx+4]
004001A1 3B43 F8 cmp eax, [ebx-8]
004001A4 73 0A jnb short 004001B0
004001A6 80FC 05 cmp ah, 5
004001A9 73 06 jnb short 004001B1
004001AB 83F8 7F cmp eax, 7F
004001AE 77 02 ja short 004001B2
004001B0 41 inc ecx
004001B1 41 inc ecx
004001B2 95 xchg eax, ebp
004001B3 8BC5 mov eax, ebp
004001B5 B6 00 mov dh, 0
004001B7 56 push esi
004001B8 8BF7 mov esi, edi
004001BA 2BF0 sub esi, eax
004001BC F3:A4 rep movs byte ptr es:[edi], byte ptr>
004001BE 5E pop esi
004001BF ^ EB 9F jmp short 00400160
004001C1 5E pop esi
004001C2 AD lods dword ptr [esi]
004001C3 97 xchg eax, edi
004001C4 AD lods dword ptr [esi]
004001C5 50 push eax
004001C6 FF53 10 call [ebx+10]
004001C9 95 xchg eax, ebp
004001CA 8B07 mov eax, [edi]
004001CC 40 inc eax
004001CD ^ 78 F3 js short 004001C2
004001CF 75 03 jnz short 004001D4
004001D1 FF63 0C jmp [ebx+C]
004001D4 50 push eax
004001D5 55 push ebp
004001D6 FF53 14 call [ebx+14]
004001D9 AB stos dword ptr es:[edi]
004001DA ^ EB EE jmp short 004001CA
004001DC 33C9 xor ecx, ecx
004001DE 41 inc ecx
004001DF FF13 call [ebx]
004001E1 13C9 adc ecx, ecx
004001E3 FF13 call [ebx]
004001E5 ^ 72 F8 jb short 004001DF
004001E7 C3 retn


上面是入口的代码,仔细观察跳转的几个语句,可以跳出这个循环的只有下面2个

004001D1    FF63 0C         jmp     [ebx+C]        ;未知
004001EA   /75 05           jnz     short 004001F1 ;跳至  retn,有可能出去。

在这2语句上面F4实验,只有004001D1  jmp     [ebx+C]是大跳转,然后F7一次到达OEP:


004017B0 55 push ebp ; kernel32.7C800000
004017B1 8B db 8B
004017B2 EC db EC
004017B3 6A db 6A ; CHAR 'j'
004017B4 FF db FF


然后dump,fix,脱壳完毕,运行程序正常。

我把这个程序发出来,你再看一下,和keygen有啥区别,你的方法我不会用。
上传的附件:
2007-1-18 16:50
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
呵呵,对我们这些菜鸟有用极了,感谢楼主~~~
2007-1-18 17:48
0
雪    币: 214
活跃值: (15)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
21
“学数学重要的不是学它的公式和算法,而是学它的思想”

深奥!学习!有收获!
2007-1-19 00:40
0
雪    币: 437
活跃值: (273)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
22
顶 深入浅出
2007-1-20 00:19
0
雪    币: 305
活跃值: (10)
能力值: ( LV9,RANK:570 )
在线值:
发帖
回帖
粉丝
23
这两个壳是完全一样的,只是否154那句中内存地址不同,只说明要弹回寄存器的值存放的地址不同。
1EA的那个jnz 4001F1是跳不出去的,它是160: call [ebx]的呼叫子程序,前面那个1DC―1E7也是call [ebx+?]的呼叫子程序,而且有几个入口。一个子程序有多个入口,好象只有汇编才能实现。
2007-1-21 14:09
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
24
压缩壳。。。
2007-1-21 14:40
0
雪    币: 264
活跃值: (30)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
25
强烈支持
本人认为论坛更需要的此类文章,而不是脱具体壳的脱文,相信把基础搞好,才是真正的收获,今后不论壳如何变化,象我们这样的菜鸟们也不至于跟在高手的屁股后面等他们把技术搞出来后发表个脱文,然后我们按部就班,也只有此类文章才不会导致论坛两极分化!!!
也希望高手们多出此类文章!!!
我相信以高手们的技术写此类文章应该不算难,但是我看到大家的文章都是具体针对某些壳,基础方面讲的相对少!!!!(高手们不要向我扔石头哦!!!)
2007-1-22 17:43
0
游客
登录 | 注册 方可回帖
返回
//