【吾爱2013CM大赛解答】驱动CM-网际座山雕 完全分析过程&注册机源码&驱动源码
本帖最后由 baby 于 2013-12-15 06:10 编辑下面文章完整DOC文档(本帖可能格式有点乱)&易语言版本注册机源码和驱动源码&C++版本注册机源码和驱动源码附件:
吾爱破解2013CrackMe大赛
作者:Baby题目:驱动CrackMe —— 网际座山雕题目链接:http://www.52pojie.cn/thread-228427-1-1.html前言:感冒发烧,晕乎乎在吾爱溜达溜达,发现有大赛进行,我等菜鸟正是锻炼自己的时候,那么动手来考验考验自己吧!题目选择:浏览了目前的题目,不知道该搞什么,看到有一个驱动的,那么自己驱动刚刚入门,拿驱动来破解也正是加强自己学的时候,那么根据目前已经列出的题目,先选择当前题目进行分析把。准备工作与题目分析:看到驱动,首先想到的就是Ring0层是个关键,他可能带强力的Anti,驱动中可能做一些羞羞的事情,这些都是影响我们逆向分析的绊脚石,搞不定它就没有办法继续下面的逆向分析。CrackMe的破解与追码实际上就是追算法,题目所定是驱动级别,那么驱动在这个情况下能想到的它的用处只有以下情况:1.保护CrackMe防止调试器调试或干扰调试器正常动作;2.与驱动进行通信将部分或全部关键算法驻留在驱动中进行计算并返回相关结果。逆向环境:Windows 7 Professional Service Pack 1逆向工具:OllyDbg吾爱破解专版,IDA 6.4 Pro Plus,DbgView,Miscrosoft OneNote,PCHunter,SPX Instant Screen Capture编程语言:易语言5.11(附带WonderWall扩展编译环境),VC++(Visual Studio 2012 Pro)
完整逆向笔记在所选题目的帖子中下载Bin.rar解压至桌面到文件夹Bin可得到主程序mycrack.exe和驱动文件crackme.sys。运行题目主程序,界面很简单,给出机器码需要填入序列号,我随手填写了一个baby,弹出“胜败乃兵家常事,大侠请重新来过。”的信息(如图1所示),那么此次的目的已经完全明确,找出真正的序列号并还原算法再重写整个CrackMe代码和驱动代码,使自己写的驱动替换原始驱动能正常工作。既然提示让我们重新来过了,那开始切入正题。看了下驱动文件只有4KB大小,我很好奇,4KB他能做很多羞羞的事情么。运行主程序后,打开PCHunter,枚举驱动发现驱动已经加载(如图2所示)。前面已经说道分析时对此驱动的想法,那么首先先看Anti,用PCHunter从Notify、SSDT、Shadow SSDT、内核钩子枚举发现没有任何驱动上的Anti,难道是他用了一些猥琐的手法?后面证明我想多了。。。。。。既然没有发现Anti,题目要求不能使用除了自己写的工具之外的所有加密手段除UPX压缩,那么对于驱动我们直接上IDA。IDA后只有6个Sub,完全是自己多虑了。。。。。。6个Sub都很简单,经过简单浏览只有DriverControl有让我们关心的核心关键代码,将Sub进行重命名后继续分析(如图2和图3所示)。 à 虽然是简单代码,但是还是不禁IDA F5一下走起,得到伪代码,所有流程清晰可见。根据驱动菜鸟编程经验,对各个变量名称进行了自己的命名以方便阅读,整理下代码格式并修正代码得到以下伪代码(测试编译通过):NTSTATUS __stdcallDriverControl(int pDeviceObject, PIRP Irp){ struct _IRP_IO_STACK_LOCATION*pStackLocation; // ST30_4@1 intIoControlCode2; // @1 unsigned intIoControlCode; // @1 intInputBufferLength; // @1 NTSTATUSNTResult; // @1 struct _IRP*pIoBuffer; // @1 unsigned intLoopCount; // @1 ULONGOutputBufferLength; // @1 NTResult = STATUS_INVALID_DEVICE_REQUEST; pStackLocation= (struct _IRP_IO_STACK_LOCATION *)Irp->Tail.Overlay.CurrentStackLocation; LoopCount = 0; IoControlCode =*((DWORD*)pStackLocation + 3); pIoBuffer = Irp->AssociatedIrp.MasterIrp; InputBufferLength= *((DWORD*)pStackLocation + 2); OutputBufferLength= *((DWORD*)pStackLocation + 1); IoControlCode2= *((DWORD*)pStackLocation + 3); // 重复取IoControlCode无实际意义 if (IoControlCode == 0x222000 ){ // IO_DBG_HELLO 简单打印字符串 DbgPrint("Hello World!\r\n"); NTResult = STATUS_SUCCESS; }else{ if (IoControlCode2 == 0x222004 ){ // IO_Calculate DbgPrint("IOCTRL_REC_FROM_APP\r\n"); if (InputBufferLength ){ DbgPrint("Get Data from App: %s\r\n", pIoBuffer); DbgPrint(" GetData from App: %x\r\n", pIoBuffer); } while (LoopCount < 9 ) { // 所有值的操作以字节对齐 原MasterIrp的Flags成员和Type成员各为指针如下 // char*HWID = (char*)(pIoBuffer) // char*SN = (char*)(pIoBuffer+0x8) // char*Result = HWID // 循环9次Result = SN - HWID + 1 // 根据这个计算看出 我们输入的序列号长度应该限定为9跟机器码一样 if ( *((BYTE*)&pIoBuffer->Flags + LoopCount + 1) ) *((BYTE*)&pIoBuffer->Type + LoopCount) = *((BYTE*)&pIoBuffer->Flags + LoopCount + 1) -*((BYTE*)&pIoBuffer->Type + LoopCount) + 1; ++LoopCount;} *((BYTE*)&pIoBuffer->Flags + 1)= 0; // Flags成员置NULL即 Result DbgPrint("Get Data from App: %s\r\n", pIoBuffer); DbgPrint("Get Data from App: %x\r\n", pIoBuffer); Irp->IoStatus.Information= 9; // 返回长度9 DbgPrint("Send Data to App: %s\r\n", pIoBuffer); NTResult= STATUS_SUCCESS; }else{ if (IoControlCode2 == 0x222008 ){ // IO_Send_Result DbgPrint("IOCTRL_SEND_TO_APP\r\n"); if (OutputBufferLength >= 24 ){ // 如果输出缓冲区长度大于等于24则返回24 memcpy(pIoBuffer,"HelloWorld from Driver", 24); Irp->IoStatus.Information= 24; DbgPrint("Send Data to App: %s\r\n", pIoBuffer); NTResult= STATUS_SUCCESS; } }else{ DbgPrint("Unknown IOCTL: 0x%X (%04X,%04X)\r\n", IoControlCode,(IoControlCode& 0xFFFF0000) >> 16,(IoControlCode >> 2) & 0xFFF); NTResult= STATUS_INVALID_PARAMETER; } } } if (NTResult ) // 如果NTResult!=NT_STATUS_SUCCESS则返回长度0否则返回OutputBufferLength Irp->IoStatus.Information= 0; else Irp->IoStatus.Information= OutputBufferLength; Irp->IoStatus.Status= NTResult; IofCompleteRequest(Irp, IO_NO_INCREMENT); returnNTResult;}
由上面代码可直观看到整个驱动IO流程,IO_Calculate通讯码中有详细的计算流程,并在IO_Send_Result通讯码流程中有向主程序返回关键字符串。在详细的计算流程中有一个简单的算法,在上面的代码中已经有了详细的解释,为了安全起见,我们从汇编再验证一遍合个小计算(如图4所示)。
至此,我们已经完全轻触了Ring0下的主要核心流程并完成了可编译的关键代码,为最后完成驱动替换做了完全的保障,下面进行Ring3下的主程序分析。直接运行主程序,驱动都没做Anti,那么主程序应该没明显Anti(PCHunter扫了下应用层钩子证实确实木有),OD直接调试。我们根据前面的驱动调试已经知道是IRP通信方式,所以Ring3下直接下断bp DeviceIoControl,在序列好号中输入baby ,代码断下,得到以下信息:调用代码:00401B06 6A 00 push 0x000401B08 68 60644400 push mycrack.0044646000401B0D 68 03010000 push 0x10300401B12 68 70654400 push mycrack.0044657000401B17 68 03010000 push 0x10300401B1C 68 68644400 push mycrack.00446468; ASCII "134797f97baby"00401B21 68 04202200 push 0x22200400401B26 52 push edx00401B27 FF15 80424300 call dword ptrds:[<&KERNEL32.DeviceIoControl>]堆栈信息:0012F7C0 758DB9B5 /CALL 到 DeviceIoControl 来自 kernel32.758DB9B00012F7C4 00000134 |hDevice = 00000134 (window)0012F7C8 00222004 |IoControlCode = 222004 //IO_Calculate0012F7CC 00446468 |InBuffer = mycrack.004464680012F7D0 00000103 |InBufferSize = 103 (259.)0012F7D4 00446570 |OutBuffer = mycrack.004465700012F7D8 00000103 |OutBufferSize = 103 (259.)0012F7DC 00446460 |pBytesReturned = mycrack.004464600012F7E0 00000000 \pOverlapped = NULL0012F7E4 00371950 ASCII "134797f97baby"0012F7E8/0012F83C0012F7EC|00401B2D 返回到 mycrack.00401B2D 来自 kernel32.DeviceIoControl通过 push mycrack.00446468; ASCII "134797f97baby" 后调用DeviceIoControl发送IRP给驱动,可以看到IoControlCode的通讯码正是我们之前分析的IO_Calculate,算法在上面我们已经分析过,我们现在先手动计算下结果,内存中数据如下(余下缓冲区都为0字节):0044646831 33 34 37 39 37 66 39 37 62 61 62 79 00 0000134797f97baby...
根据前面的算法可知道共循环9次, Result = SN - HWID + 1,可到结果,得到结果后OD断点跑起,观察OutBuffer与计算结果一致,如下所示:0044657032 2F 2F 43 39 37 66 39 37 00 61 62 79 00 00002//C97f97.aby...不过根据之前驱动的分析,SN即我们输入的序列号应该是9位,所以这里测试baby当序列号明显不符。上面的断点返回后紧跟着下面出现了一个算法,算法完成后有比较明显的判断然后跳到失败与成功的地方。在跑了代码流程后发现,失败代码直接执行,真正的成功代码是根据已经存在的一张加密表和处理过的HWID&SN解密出来后再执行的ShellCode。00401B65 .833D 60644400>cmp dword ptr ds:,0x0
//判断DeviceIoControl的BytesReturn是否为空
00401B6C .76 36 jbe Xmycrack.00401BA4
00401B6E .33C9 xor ecx,ecx
00401B70 >8A99 F8384400 mov bl,byte ptr ds:
//0x4438F8即为已经加密的代码码表
00401B76 .80FB E0 cmp bl,0xE0
//循环取出字节判断是否大于E0,如果大于则不进行解密处理,查看码表后发现第一个字节EB,则可以认定解密后的代码第一处是jmp short
00401B79 .73 16 jnb Xmycrack.00401B91
00401B7B B8 398EE338 mov eax,0x38E38E39
00401B80 .F7E1 mul ecx
00401B82 .D1EA shr edx,1
00401B84 .8D04D2 lea eax,dword ptr ds:
00401B87 .8BD1 mov edx,ecx
00401B89 .2BD0 sub edx,eax
00401B8B .2A9A 70654400 sub bl,byte ptr ds:
00401B91 >8899 80674400 mov byte ptr ds:,bl
00401B97 .83C1 01 add ecx,0x1
00401B9A .81F9 A3000000 cmp ecx,0xA3
//0x446570储存着DeviceIoControl处理过的HWID&SN,0x446780极为真正的ShellCode代码地址,循环A3次进行解密,因为本人菜鸟,所以IDA直接F5了这里(不懂C++的乘除优化),得到以下伪代码:
int LoopCount = 0;
do{
byteTemp = CodeEnBuffer_byte_4438F8;
if ( (unsigned __int8)byteTemp < 224 ) byteTemp -= HWID_SN_byte_446570;
*((_BYTE *)CodeRealBuffer_446780 + LoopCount++) = byteTemp;
}
while ( (signed int)LoopCount < 163 );
00401BA0 .^ 7C CE jl Xmycrack.00401B70
00401BA2 .EB 12 jmp Xmycrack.00401BB6
00401BA4 >68 70654400 push mycrack.00446570
00401BA9 .68 DC974300 push mycrack.004397DC ;\tNo data received: %s\n
00401BAE .E8 01C50100 call mycrack.0041E0B4
00401BB3 .83C4 08 add esp,0x8
00401BB6 >68 F4974300 push mycrack.004397F4 ; \tDeviceIoControl IOCTRL_SEND_TO_APP sucessfully sent. \n\n
00401BBB .E8 F4C40100 call mycrack.0041E0B4
00401BC0 .83C4 04 add esp,0x4
00401BC3 .803D 22684400>cmp byte ptr ds:,0x21
//已经知道了ShellCode长度为A3,判断最后一个字节是否为0x21,则LoopCount=0xA2根据上面的HWID_SN_byte_446570算法,可知道驱动中计算后SN所在未知的的第一个字节,CodeEnBuffer_byte_4438F8为0x22, byteTemp -= HWID_SN_byte_446570可得0x22+0x21=0x1, 我的机器码是134797f97,第一位1的ASCII为0x31,再做驱动中的逆运算SN = Result + HWID - 1-> SN = 0x1 + 0x31 – 1 = 0x31,由此可知我们的序列号第一位一定是数字1,根据此算法可以完全确定,序列好长度必须为9,验证了驱动中计算时猜解的序列号长度
00401BCA .75 21 jnz Xmycrack.00401BED
00401BCC .B8 80674400 mov eax,mycrack.00446780
00401BD1 .C605 D8674400>mov byte ptr ds:,0xDB
00401BD8 .FFD0 call eax
//解密后的ShellCode一处机器码置0xDB,调用解密后的ShellCode,如果解密错误执行当然无法成功
00401BDA .8B4C24 14 mov ecx,dword ptr ss:
00401BDE .64:890D 00000>mov dword ptr fs:,ecx
00401BE5 .59 pop ecx
00401BE6 .5F pop edi
00401BE7 .5E pop esi
00401BE8 .5B pop ebx
00401BE9 .83C4 10 add esp,0x10
00401BEC .C3 retn
00401BED >6A 00 push 0x0
00401BEF .6A 00 push 0x0
00401BF1 .68 30984300 push mycrack.00439830 ;胜败乃兵家常事,大侠请重新来过。
00401BF6 .8BCF mov ecx,edi
00401BF8 .E8 47690000 call mycrack.00408544
00401BFD .8B4C24 14 mov ecx,dword ptr ss:
00401C01 .64:890D 00000>mov dword ptr fs:,ecx
00401C08 .59 pop ecx
00401C09 .5F pop edi
00401C0A .5E pop esi
00401C0B .5B pop ebx
00401C0C .83C4 10 add esp,0x10
00401C0F .C3 retn
加密的代码码表(长度为162): EB 44 8E 5D 41 91 63 13 81 04 DB8E 77 25 09 F1 3B FF 50 49 B0 37 ED 15 BD 1C 0A 39 F2 77 0C C6D3 0A 0B EA 41 EB F0 3F 71 2A 0B7D E6 8C 75 27 07 F1 6C 92 44 87 8C 75 1F 07 F1 91 0B C6 0C C25D 62 57 C8 EB 56 3B C9 65 35 4334 90 46 13 93 79 1D AF 8E 4C 0D 5E 3A E3 3C FF 68 C2 37 37 5D6F 7D 7C 66 74 8E FC 58 57 5A 5859 54 59 6B 58 17 87 27 E8 93 FF FF FF FF D5 91 CF 70 2E 5A 3CE4 E8 81 FF FF FF FF D1 5B 6B 9B1E 72 34 E8 78 FF FF FF FF D5 E8 B3 FF FF FF 49 72 73 69 26 51至此,我们已经完全清楚了整个流程,首先获取到HWID,HWID长度恒定9(后面讲到),SN的长度也必须为9且SN第一位一定是1,根据整个流程来看,加密的代码码表决定了必须对应密钥才能和驱动中计算出来的HWID&SN才能解密出真正的成功ShellCode,如果不知道已经解密好的ShellCode或者正确的一组HWID和SN,理论上Key是无法还原的。但是,我们从驱动中简单的加减计算和Ring3下解密ShellCode的简单加减计算,可以产生一个断言:Key与HWID在计算过程中也是简单的加减法,那么,前面已经知道SN第一位必须是1,我们需要知道HWID的计算方法变可以清楚的知道Key的第一个字节。程序一眼可以看出是MFC框架,则直接下断bp SetDlgItemTextA,向上返回后得到HWID的详细获取和计算过程。0040186B > \68 00010000 push 0x100 ; /pFileSystemNameSize = 00000100
00401870 .8D4424 20 lea eax,dword ptr ss: ; |
00401874 .50 push eax ; |pFileSystemNameBuffer
00401875 .8D4C24 1C lea ecx,dword ptr ss: ; |
00401879 .51 push ecx ; |pFileSystemFlags
0040187A .8D5424 24 lea edx,dword ptr ss: ; |
0040187E .52 push edx ; |pMaxFilenameLength
0040187F .8D4424 20 lea eax,dword ptr ss: ; |
00401883 .50 push eax ; |pVolumeSerialNumber
00401884 .68 00010000 push 0x100 ; |MaxVolumeNameSize = 100 (256.)
00401889 .8D8C24 340100>lea ecx,dword ptr ss: ; |
00401890 .51 push ecx ; |VolumeNameBuffer
00401891 .68 B8974300 push mycrack.004397B8 ; |c:\
00401896 .FF15 84424300 call dword ptr ds:[<&KERNEL32.GetVolumeI>; \GetVolumeInformationA
// GetVolumeInformationA获取到VolumeSerialNumber即C盘的卷序列号
0040189C .E8 8AC60000 call mycrack.0040DF2B
004018A1 .33C9 xor ecx,ecx
004018A3 .85C0 test eax,eax
004018A5 .0F95C1 setne cl
004018A8 .85C9 test ecx,ecx
004018AA .75 0A jnz Xmycrack.004018B6
004018AC .68 05400080 push 0x80004005
004018B1 .E8 4A060000 call mycrack.00401F00
004018B6 >8B10 mov edx,dword ptr ds:
004018B8 .8BC8 mov ecx,eax
004018BA .8B42 0C mov eax,dword ptr ds:
004018BD .FFD0 call eax
004018BF .83C0 10 add eax,0x10
004018C2 .894424 0C mov dword ptr ss:,eax
004018C6 .C78424 280200>mov dword ptr ss:,0x0
004018D1 .8B4C24 10 mov ecx,dword ptr ss:
004018D5 .81F1 14587934 xor ecx,0x34795814
//直接与0x34795814异或
004018DB .51 push ecx
004018DC .8D5424 10 lea edx,dword ptr ss:
004018E0 .68 BC974300 push mycrack.004397BC ;1%04x
004018E5 .52 push edx
004018E6 .E8 15090000 call mycrack.00402200
//printf打印异或后的HWID,注意打印的字符串前面有个恒定的1
004018EB .8B7C24 18 mov edi,dword ptr ss:
004018EF .83C4 0C add esp,0xC
004018F2 .57 push edi
004018F3 .68 EA030000 push 0x3EA
004018F8 .8BCE mov ecx,esi
004018FA .E8 78A90000 call mycrack.0040C277 // SetDlgItemTextA
根据上面的算法继续验证,GetVolumeInformationA执行完后VolumeSerialNumber为0x00002783,与0x34795814异或后得0x34797F97,小写打印后得字符串HWID为134797f97。 既然知道了HWID的第一位和SN的第一位恒定为数字1,重复之前ShellCode解密算法可知,Key的第一位一定是字节0,根据HWID和SN已知长度以及ShellCode解密算法可得知Key的长度是9。之前已经提到,再没有一组正确密钥或知道ShellCode部分真正代码,我们无法得知逆算出Key。未知Key的8位若使用穷解的方法为 255^8组,这显然不现实,那么CrackMe是人写的,他的Key排除乱打的情况下,要么为他经常用的一组8位数字,要么就是0 1 2 3 4 5 6 7 8这种最简单的密钥,抱着尝试的心态,我首先尝试了0-8的Key。 侥幸的情况下我用此Key与HWID进行逐位相加计算得到我的正确SN应该为146:=<l@?,于是我输入了SN确定后在0x00401BD8未知断下跟进ShellCode,没错,Key被蒙对了。。。。。。可以看见规整的ShellCode,如下:00446780 /EB 42 jmp Xmycrack.004467C4
//此Sub枚举导出表根据Hash获取函数地址
00446782 |8B59 3C mov ebx,dword ptr ds:
00446785 |8B5C0B 78 mov ebx,dword ptr ds:
00446789 |03D9 add ebx,ecx
0044678B |8B73 20 mov esi,dword ptr ds:
0044678E |03F1 add esi,ecx
00446790 |33FF xor edi,edi
00446792 |4F dec edi
00446793 |47 inc edi
00446794 |AD lods dword ptr ds:
00446795 |33ED xor ebp,ebp
00446797 |0FB61401 movzx edx,byte ptr ds:
0044679B |38F2 cmp dl,dh
0044679D |74 08 je Xmycrack.004467A7
0044679F |C1CD 03 ror ebp,0x3
004467A2 |03EA add ebp,edx
004467A4 |40 inc eax
004467A5^|EB F0 jmp Xmycrack.00446797
004467A7 |3B6C24 04 cmp ebp,dword ptr ss:
004467AB^|75 E6 jnz Xmycrack.00446793
004467AD |8B73 24 mov esi,dword ptr ds:
004467B0 |03F1 add esi,ecx
004467B2 |66:8B3C7E mov di,word ptr ds:
004467B6 |8B73 1C mov esi,dword ptr ds:
004467B9 |03F1 add esi,ecx
004467BB |8B04BE mov eax,dword ptr ds:
004467BE |03C1 add eax,ecx
004467C0 |5B pop ebx
004467C1 |5F pop edi
004467C2 |53 push ebx
004467C3 |C3 retn
004467C4 \EB 4F jmp Xmycrack.00446815
004467C6 33C0 xor eax,eax
004467C8 64:3340 30 xor eax,dword ptr fs:
004467CC 8B40 0C mov eax,dword ptr ds:
004467CF 8B70 1C mov esi,dword ptr ds:
004467D2 AD lods dword ptr ds:
004467D3 8B48 08 mov ecx,dword ptr ds:
//从PEB->_PEB_LDR_DATA->InInitializationOrderModuleList中遍历双向链表
004467D6 58 pop eax
//为“Good Job!”字符串指针
004467D7 33DB xor ebx,ebx
004467D9 33FF xor edi,edi
004467DB 66:BF 3332 mov di,0x3233
004467DF 57 push edi
004467E0 68 75736572 push 0x72657375
004467E5 8BFC mov edi,esp
004467E7 53 push ebx
004467E8 51 push ecx
004467E9 53 push ebx
004467EA 50 push eax
004467EB 50 push eax
004467EC 53 push ebx
004467ED 57 push edi
004467EE 68 54128120 push 0x20811254
004467F3 E8 8AFFFFFF call mycrack.00446782
004467F8 FFD0 call eax
004467FA 8BC8 mov ecx,eax
004467FC 68 25593AE4 push 0xE43A5925
00446801 E8 7CFFFFFF call mycrack.00446782
00446806 FFD0 call eax
00446808 59 pop ecx
00446809 68 97196C2D push 0x2D6C1997
0044680E E8 6FFFFFFF call mycrack.00446782
00446813 FFD0 call eax
00446815 E8 ACFFFFFF call mycrack.004467C6
0044681A//此处储存着“Good Job!”字符串,call后从堆栈取出字符串指针对应机器码表为:EB 42 8B 59 3C 8B 5C 0B 78 03 D98B 73 20 03 F1 33 FF 4F 47 AD 33 ED 0F B6 14 01 38 F2 74 08 C1CD 03 03 EA 40 EB F0 3B 6C 24 0475 E6 8B 73 24 03 F1 66 8B 3C 7E 8B 73 1C 03 F1 8B 04 BE 03 C15B 5F 53 C3 EB 4F 33 C0 64 33 4030 8B 40 0C 8B 70 1C AD 8B 48 08 58 33 DB 33 FF 66 BF 33 32 5768 75 73 65 72 8B FC 53 51 53 5050 53 57 68 54 12 81 20 E8 8A FF FF FF FF D0 8B C8 68 25 59 3AE4 E8 7C FF FF FF FF D0 59 68 9719 6C 2D E8 6F FF FF FF FF D0 E8 AC FF FF FF于是F9快乐跑起,结果程序异常结束。。。难道有Anti?不可能!接着跟进ShellCode进行分析错误原因,很快变跟到了错误。00446797 >0FB61401 movzx edx,byte ptr ds:在重新跑起逐步跟进发现,错误原因是在PEB中遍历DLL的双向链表后,DLL中获取函数失败导致,那么第一反应是系统问题,于是抛弃Windows 7,跑起Win XP SP3虚拟机进行测试,果然不出所料,根据算法得到的SN成功提示了“Good Job!”(如图5所示)!那么根据XP调试进行对比可得到Win7崩溃原因可知,在从PEB遍历DLL后要获取LoadLibraryA和MessageBoxA,XP下获取HASH对比的是kernel32.dll,而Win7遍历直接取出是kernelbase.dll的跳板Dll,如此ShellCode中写的HASH无法匹配,导致ExportTable遍历越界,修正办法改HASH算法或指定kernel32.dll遍历即可,WIN7修正CrackMe ShellCode后成功提示(如图6所示)。至此,完全完成此CrackMe的逆向工作,贴出KeyGen关键代码易语言版(如图7所示)和C版:易语言版:C版:void MakeKey(unsigned char *HWID,unsigned char *SN){
unsigned char Key={0,1,2,3,4,5,6,7,8};
for(int i = 0 ; i < 9 ; i++){
SN = HWID + Key;
}
}备注:完整的CrackMe驱动源代码我分别用C++和易语言写了,两个版本驱动替换原有驱动正常工作,KeyGen源代码也包含这两个语言,可以在目录下找到相关源代码。因为发烧生病进度慢,文章排版较麻烦,此文未发之前已经有人在吾爱发此贴,如有雷同纯属巧合~ 前排支持,下来慢慢学习. 分析的很详细 学习,膜拜中!!! 太精彩了!太精彩了!无发言表!震撼!优雅的解答! 一股膜拜之情油然而生。分析的很到位 Worship da niu!{:301_997:} 真心很帅的一篇文章,写得很用心,膜拜一下 mov ebx,fs: //得到peb结构体的地址
mov ebx, //得到Ldr结构体的地址
mov ebx, //得到ldr.InLoadOrderModuleList.Flink 第一个模块,当前进程
mov ebx, //得到第二个模块地址 ntdll.dll
mov ebx, //得到第三个模块地址 kernel32.dll
mov ebx,//得到第三个模块地址(kernel32模块的dllbase)
这样就可以通用,至少xp和win7可以 {:1_931:}好文章! 膜拜之!~