第四课——去程序自校验

破解豪迪群发器。

打开注册页面,随便输入注册码,会出现以下:

程序无壳,直接破解。右键->中文搜索引擎->智能搜索。找到“已注册版本”,应该是主页页面中央的红字部分。点进去,发现上面有比较和跳转指令。在跳转指令处下断,运行程序,点击注册,发现程序停在断点处,说明找对地方了。这个对比语句比较的是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行左右最佳。