破解豪迪群发器。
打开注册页面,随便输入注册码,会出现以下:
程序无壳,直接破解。右键->中文搜索引擎->智能搜索。找到“已注册版本”,应该是主页页面中央的红字部分。点进去,发现上面有比较和跳转指令。在跳转指令处下断,运行程序,点击注册,发现程序停在断点处,说明找对地方了。这个对比语句比较的是0x579F24
这个所指向的全局变量。
1 2 3
| 00541870 |. 803D 249F5700>cmp byte ptr ds:[0x579F24],0x0 00541877 |. 74 10 je short qqqf.00541889 00541879 |. BA F4185400 mov edx,qqqf.005418F4 ; 已注册版本
|
右键->查找->所有常量,输入0x579F24
。OD跳到所有调用过0x579F24
的指令,右键->在每个命令上设置断点。快速定位第一个赋值点。
1 2 3 4 5 6 7 8 9 10
| 参考位于 qqqf:CODE 到常量 0x579F24 地址 反汇编 注释 0054135F mov byte ptr ds:[0x579F24],0x0 ds:[00579F24]=00 00541366 cmp byte ptr ds:[0x579F24],0x0 ds:[00579F24]=00 0054147C mov byte ptr ds:[0x579F24],0x0 ds:[00579F24]=00 00541650 mov byte ptr ds:[0x579F24],dl 0054169E mov byte ptr ds:[0x579F24],al 005416A3 cmp byte ptr ds:[0x579F24],0x0 ds:[00579F24]=00 00541870 cmp byte ptr ds:[0x579F24],0x0 ds:[00579F24]=00 0056AFB0 push ebp (初始 CPU 选择)
|
重载,去到第一个断点处。
1 2 3 4 5 6 7 8 9 10 11
| 0054163B . E8 842FECFF call qqqf.004045C4 00541640 . 83F8 18 cmp eax,0x18 00541643 . 74 09 je short qqqf.0054164E;eax=0x18则给dl赋值为1 00541645 . 83F8 0C cmp eax,0xC 00541648 . 74 04 je short qqqf.0054164E;eax=0xC也可以给dl赋值为1 0054164A . 33D2 xor edx,edx;eax不等于0x18或0xc则给dl赋值为0 0054164C . EB 02 jmp short qqqf.00541650 0054164E > B2 01 mov dl,0x1 00541650 > 8815 249F5700 mov byte ptr ds:[0x579F24],dl;第一个断点处,将dl的值赋给它 00541656 . 83F8 0C cmp eax,0xC 00541659 . 75 48 jnz short qqqf.005416A3;如果eax不等于0xC则跳转
|
首先假设eax=0x18(在注册页面上输入0x18位注册码)的情况,即最后一个跳转指令要跳转。
1 2
| 005416A3 > \803D 249F5700>cmp byte ptr ds:[0x579F24],0x0 005416AA . 75 05 jnz short qqqf.005416B1;跳转实现
|
Shift+F9运行,去到第二个断点处。
1 2 3 4
| 0054135A |. 83F8 0C cmp eax,0xC 0054135D |. 74 07 je short qqqf.00541366;eax=0xC跳转,否则会执行赋0语句 0054135F |. C605 249F5700>mov byte ptr ds:[0x579F24],0x0 00541366 |> 803D 249F5700>cmp byte ptr ds:[0x579F24],0x0
|
Shift+F9运行,继续去到第三个断点处。
1 2 3 4 5 6
| 00541870 |. 803D 249F5700>cmp byte ptr ds:[0x579F24],0x0 00541877 |. 74 10 je short qqqf.00541889;eax=0x18时会执行赋0语句,所以会跳过“已注册版本” 00541879 |. BA F4185400 mov edx,qqqf.005418F4 ; 已注册版本 0054187E |. 8B83 FC020000 mov eax,dword ptr ds:[ebx+0x2FC] 00541884 |. E8 F77CFBFF call qqqf.004F9580 00541889 |> 33C0 xor eax,eax
|
重载OD,在注册页面上输入0xC位注册码,运行。第一个断点没问题,去到第二个断点处(此时这第二个断点与上次输入0x18的第二个断点已经不一样了)。
1 2 3 4 5 6 7
| 00541696 . /7F 04 jg short qqqf.0054169C 00541698 > |33C0 xor eax,eax 0054169A . |EB 02 jmp short qqqf.0054169E 0054169C > \B0 01 mov al,0x1 0054169E A2 249F5700 mov byte ptr ds:[0x579F24],al;此时al的值为0 005416A3 803D 249F5700 00 cmp byte ptr ds:[0x579F24],0x0 005416AA 75 05 jnz short qqqf.005416B1;这里一定让它跳转,否则注册失败
|
修改第一行的跳转指令,使al为1。
1
| jg short qqqf.0054169C => jmp short qqqf.0054169C
|
保存一下,载入新程序再下断运行,发现运行到54169E
时al还是为0。
1 2 3 4 5 6 7 8 9 10
| 0054168C /75 0A jnz short qqqf1.00541698 0054168E . |8B07 mov eax,dword ptr ds:[edi] 00541690 . |E8 177EFCFF call qqqf1.005094AC 00541695 . |40 inc eax 00541696 . |EB 04 jmp short qqqf1.0054169C;修改过 00541698 > \33C0 xor eax,eax 0054169A . EB 02 jmp short qqqf1.0054169E 0054169C > B0 01 mov al,0x1 0054169E > A2 249F5700 mov byte ptr ds:[0x579F24],al 005416A3 > 803D 249F5700>cmp byte ptr ds:[0x579F24],0x0
|
猜测54168C
的跳转指令绕过了我们刚才修改过的指令,再54168C
处下断,重载,运行到这里果然跳转实现了。那将54168C
改为nop,让它执行541696
使al为1。
保存,运行一下,发现左下角显示“已注册!”,接着显示“正在验证…”,“验证失败!”。
载入OD,查找字符串“验证失败!”处,查看上下代码:
1 2 3 4
| 00540B2A |> \807D EB 00 cmp byte ptr ss:[ebp-0x15],0x0 00540B2E |. 75 68 jnz short qqqf2.00540B98 00540B30 |. B8 E00E5400 mov eax,qqqf2.00540EE0 ; 验证失败! 00540B35 |. E8 3ED00100 call qqqf2.0055DB78
|
尝试将跳转指令修改一下。
1
| jnz short qqqf2.00540B98 => jmp short qqqf2.00540B98
|
F9运行发现经过验证后显示“已注册!”字样,说明验证成功。而且点击注册那里也显示“已注册版本”。
由于每次等待验证的时间都非常长,所以也可以直接在函数段首retn
,绕过验证过程。
当运用“插入文件”、“插入其他”、“插入图片”功能时,都显示乱码。反正就是很多功能都不能用。
这就涉及到程序自校验的问题。这个程序是什么类型的自校验呢?将原程序拉进WinHEX,在程序末尾填充00的任意一个地方修改为01保存,使用以上功能时还是乱码,很有可能是MD5自校验。
从 https://www.52pojie.cn/thread-14986-1-1.html 下载脱壳脚本。将已注册版本的程序载入OD,插件->ODbgScript->打开,选择脱壳脚本里的各语言按钮事件->Delphi & VB事件断点查找脚本。可以在B
模块看到自动下了很多断点。Shift+F9运行起来,在程序界面点击“插入其他”,OD停在某断点处。因为还没有显示下拉菜单,更别提是自校验的过程了,所以这个断点可以去掉。
1
| 00479DF4 |. FF93 20010000 call dword ptr ds:[ebx+0x120] ; qqqf3_1.0056934C
|
F9运行,在程序界面点击插入其他->插入随机字母,OD停在下个断点处。这个函数才是触发自校验过程的函数。
1
| 0048B2D6 . FF93 88000000 call dword ptr ds:[ebx+0x88] ; qqqf3_1.00567ED8
|
F7跟进去。这个函数里也有挺多个call指令,到底哪个是MD5自校验函数呢?我们知道,MD5自校验需要读取文件来计算MD5值,所以如果在call里面看到有关文件的API函数,很大可能这个函数是MD5自校验的关键函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 00567ED8 /. 55 push ebp 00567ED9 |. 8BEC mov ebp,esp 00567EDB |. 6A 00 push 0x0 00567EDD |. 6A 00 push 0x0 00567EDF |. 33C0 xor eax,eax 00567EE1 |. 55 push ebp 00567EE2 |. 68 347F5600 push qqqf3_1.00567F34 00567EE7 |. 64:FF30 push dword ptr fs:[eax] 00567EEA |. 64:8920 mov dword ptr fs:[eax],esp 00567EED |. 8D4D F8 lea ecx,[local.2] 00567EF0 |. 33D2 xor edx,edx 00567EF2 |. B8 1E000000 mov eax,0x1E 00567EF7 |. E8 C4AEF4FF call qqqf3_1.004B2DC0 00567EFC |. 8B55 F8 mov edx,[local.2] 00567EFF |. 8D45 FC lea eax,[local.1] 00567F02 |. E8 D1CCE9FF call qqqf3_1.00404BD8 00567F07 |. 8B45 FC mov eax,[local.1] 00567F0A |. 50 push eax 00567F0B |. E8 5049FFFF call qqqf3_1.0055C860 00567F10 |. 5A pop edx ; qqqf3_1.0048B2DC 00567F11 |. E8 9AF9F8FF call qqqf3_1.004F78B0 00567F16 |. 33C0 xor eax,eax 00567F18 |. 5A pop edx ; qqqf3_1.0048B2DC 00567F19 |. 59 pop ecx ; qqqf3_1.0048B2DC 00567F1A |. 59 pop ecx ; qqqf3_1.0048B2DC 00567F1B |. 64:8910 mov dword ptr fs:[eax],edx 00567F1E |. 68 3B7F5600 push qqqf3_1.00567F3B 00567F23 |> 8D45 F8 lea eax,[local.2] 00567F26 |. E8 ADC3E9FF call qqqf3_1.004042D8 00567F2B |. 8D45 FC lea eax,[local.1] 00567F2E |. E8 DDCAE9FF call qqqf3_1.00404A10 00567F33 \. C3 retn
|
一个一个点击回车跟随call指令进去看看,后面的那几个call要不就是没有call指令要不就是无关文件的API函数,都可以不管。00567EF7
地址的call指令,进去的第二个call的第一个call,看到GetModuleFileNameA
函数,所以00567EF7
的函数调用是自校验的关键函数。在GetModuleFileNameA
函数下断运行。GetModuleFileNameA
函数的意思是获取当前进程已加载模块的完整路径,该模块必须由当前进程加载。
继续F8单步,运行到此处时,可以看到eax显示C:\Program Files\QQSendFriend\Desklog.dll
。
1
| 004B2E1A |. E8 856FF5FF call qqqf3_1.00409DA4
|
F7跟进去,发现这个函数是用来创建Desklog.dll
文件的。再看下个函数:
1
| 004B2E27 |. E8 14E2FFFF call qqqf3_1.004B1040
|
跟进去,第一个call指令里有个SetFilePointer
函数,在一个文件中设置新的读取位置。第二个call指令里有个ReadFile
函数。第三个又是SetFilePointer
函数,跳出循环,第四个又是ReadFile
函数等等。但运行完整个4B1040
函数程序都没有返回火星文。那继续往下看:
1 2 3 4 5 6 7 8 9
| 004B2E27 |. E8 14E2FFFF call qqqf3_1.004B1040 004B2E2C |. 8BC7 mov eax,edi 004B2E2E |. E8 9170F5FF call qqqf3_1.00409EC4;关闭句柄 004B2E33 |. 8D55 F0 lea edx,[local.4] 004B2E36 |. 33C0 xor eax,eax 004B2E38 |. E8 2FFCF4FF call qqqf3_1.00402A6C;又是GetMouduleFileNameA 004B2E3D |. 8B45 F0 mov eax,[local.4] 004B2E40 |. 8D55 F8 lea edx,[local.2] 004B2E43 |. E8 ECF0FFFF call qqqf3_1.004B1F34;跟进去
|
在004B1F34
这个函数的第三个call语句4B1DD8
函数发现CreateFile
函数,是一个多功能的函数,可打开或创建文件或者I/O设备。继续F8,还看到了一个GetFileSize
函数用来获取文件大小。获取文件大小后的那个函数就是MD5校验算法,我们暂时没那个能力对算法进行跟踪,所以先暂时互相放过彼此。
继续F8返回到4B1DD8
处,F8去到4B1F74
处,信息窗口显示堆栈地址。
1 2 3
| 004B1F6A |. E8 69FEFFFF call qqqf3_1.004B1DD8 004B1F6F |. BE 10000000 mov esi,0x10 004B1F74 |. 8D5D EC lea ebx,[local.5]
|
1 2
| 堆栈地址=0012FCD8 ebx=0012FD54
|
数据窗口跟随,0012FCD8
一行显示的就是当前程序的MD5值。
1
| 0012FCD8 52 8C 2A 4F CB 43 39 6F 2D 6F 7A 91 FE C0 CF DC R?O薈9o-oz扊老
|
而原程序的MD5值为11ea70a3c3735c29b48552776756406a。可以把当前程序拖去WinMD5检验是否为上面的MD5值528C2A4FCB43396F2D6F7A91FEC0CFDC。
选中lea ebx,[local.5]
右键 -> 分析 -> 从模块中删除分析。将下面代码复制。
1 2 3 4 5 6 7 8 9 10 11
| 004B1F74 8D5D EC lea ebx,dword ptr ss:[ebp-0x14] 004B1F77 8D55 E8 lea edx,dword ptr ss:[ebp-0x18] 004B1F7A 33C0 xor eax,eax 004B1F7C 8A03 mov al,byte ptr ds:[ebx] 004B1F7E E8 35F1FFFF call qqqf3_1.004B10B8 004B1F83 8B55 E8 mov edx,dword ptr ss:[ebp-0x18] 004B1F86 8BC7 mov eax,edi 004B1F88 E8 3F26F5FF call qqqf3_1.004045CC 004B1F8D 43 inc ebx 004B1F8E 4E dec esi 004B1F8F ^ 75 E6 jnz short qqqf3_1.004B1F77
|
在程序中找一段空代码,比如从0056B0A0
开始。在4B1F74
修改代码jmp 0056B0A0
,将已经复制的代码都NOP掉。F8跳下去,将上面的第1行代码修改成mov dword ptr ss:[ebp-0x14],1
放到0056B0A0
。将原程序的MD5值二进制粘贴到数据窗口覆盖当前程序的MD5。
修改第1行代码为mov dword ptr ss:[ebp-0x14],0xA370EA11
。继续编写第2到4行代码:
1 2 3
| mov dword ptr ss:[ebp-0x10],0x295c73c3 mov dword ptr ss:[ebp-0xC],0x775285b4 mov dword ptr ss:[ebp-0x8],0x6a405667
|
再将上面的二进制代码复制下来,确保一一对应,成品如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 0056B0A0 C745 EC 11EA70A3 mov dword ptr ss:[ebp-0x14],0xA370EA11 0056B0A7 C745 F0 C3735C29 mov dword ptr ss:[ebp-0x10],0x295C73C3 0056B0AE C745 F4 B4855277 mov dword ptr ss:[ebp-0xC],0x775285B4 0056B0B5 C745 F8 6756406A mov dword ptr ss:[ebp-0x8],0x6A405667 0056B0BC 8D5D EC lea ebx,dword ptr ss:[ebp-0x14] 0056B0BF 8D55 E8 lea edx,dword ptr ss:[ebp-0x18] 0056B0C2 33C0 xor eax,eax 0056B0C4 8A03 mov al,byte ptr ds:[ebx] 0056B0C6 E8 ED5FF4FF call qqqf3_1.004B10B8 0056B0CB 8B55 E8 mov edx,dword ptr ss:[ebp-0x18] 0056B0CE 8BC7 mov eax,edi 0056B0D0 E8 F794E9FF call qqqf3_1.004045CC 0056B0D5 43 inc ebx 0056B0D6 4E dec esi 0056B0D7 ^ 75 E6 jnz short qqqf3_1.0056B0BF 0056B0D9 ^ E9 B36EF4FF jmp qqqf3_1.004B1F91
|
F9运行,程序成功看到不是显示火星文,说明破解自校验成功。
右键 -> 复制到可执行文件 -> 所有修改 -> 复制,保存文件。注意,有时候会出现“无法定位数据”的情况,这时就要改变空代码的位置。最好不要选程序最底端那段代码,经常不行…靠近汇编代码结束处距离5行左右最佳。