【文章标题】: QQ聊天记录查看器 5.3 算法分析
【文章作者】: 斜阳残雪
【作者邮箱】: zlm324@126.com
【编写语言】: Delphi
【软件介绍】: 可以绕过密码直接读取QQ的本地数据文件查看聊天记录
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
上网闲逛的时候发现的,觉得应该有点用处,就载下来玩儿。
使用发现要注册,未注册版只给2次试用机会(太抠门了),注册码输入错误有提示。
PEiD查壳,没有发现,编程语言为Delphi。
用OD载入,下bp MessageBoxA,注册失败后断下,向上看到这里:
0046EEF0 /. 55 push ebp
0046EEF1 |. 8BEC mov ebp, esp
0046EEF3 |. 83C4 F4 add esp, -0C
0046EEF6 |. 33C9 xor ecx, ecx
0046EEF8 |. 894D F4 mov dword ptr [ebp-C], ecx
0046EEFB |. 8955 F8 mov dword ptr [ebp-8], edx
0046EEFE |. 8945 FC mov dword ptr [ebp-4], eax
0046EF01 |. 33C0 xor eax, eax
0046EF03 |. 55 push ebp
0046EF04 |. 68 AFEF4600 push 0046EFAF
0046EF09 |. 64:FF30 push dword ptr fs:[eax]
0046EF0C |. 64:8920 mov dword ptr fs:[eax], esp
0046EF0F |. 8D55 F4 lea edx, dword ptr [ebp-C]
0046EF12 |. 8B45 FC mov eax, dword ptr [ebp-4]
0046EF15 |. 8B80 F0020000 mov eax, dword ptr [eax+2F0]
0046EF1B |. E8 3496FCFF call 00438554
0046EF20 |. 8B55 F4 mov edx, dword ptr [ebp-C]
0046EF23 |. B8 E89C4800 mov eax, 00489CE8
0046EF28 |. E8 A353F9FF call 004042D0
0046EF2D |. A1 E89C4800 mov eax, dword ptr [489CE8]
0046EF32 |. E8 2DFDFFFF call 0046EC64 ; 关键Call
0046EF37 |. A3 EC9C4800 mov dword ptr [489CEC], eax
0046EF3C 833D EC9C4800>cmp dword ptr [489CEC], 0 ;比较标志位
0046EF43 74 34 je short 0046EF79 ;跳走则失败
0046EF45 6A 00 push 0
0046EF47 |. A1 E89C4800 mov eax, dword ptr [489CE8]
0046EF4C |. E8 DB57F9FF call 0040472C
0046EF51 |. 50 push eax
0046EF52 |. 68 BCEF4600 push 0046EFBC
0046EF57 |. 8B45 FC mov eax, dword ptr [ebp-4]
0046EF5A |. E8 75FCFCFF call 0043EBD4
0046EF5F |. 50 push eax ; |hOwner
0046EF60 |. E8 4380F9FF call <jmp.&user32.MessageBoxA> ; \MessageBoxA
0046EF65 |. A1 E89C4800 mov eax, dword ptr [489CE8]
0046EF6A |. E8 A1FEFFFF call 0046EE10
0046EF6F |. 8B45 FC mov eax, dword ptr [ebp-4]
0046EF72 |. E8 755DFEFF call 00454CEC
0046EF77 |. EB 20 jmp short 0046EF99
0046EF79 |> 6A 00 push 0
0046EF7B |. A1 E89C4800 mov eax, dword ptr [489CE8]
0046EF80 |. E8 A757F9FF call 0040472C
0046EF85 |. 50 push eax
0046EF86 |. 68 C8EF4600 push 0046EFC8
0046EF8B |. 8B45 FC mov eax, dword ptr [ebp-4]
0046EF8E |. E8 41FCFCFF call 0043EBD4
0046EF93 |. 50 push eax ; |hOwner
0046EF94 |. E8 0F80F9FF call <jmp.&user32.MessageBoxA> ; \MessageBoxA(出错信息)
0046EF99 |> 33C0 xor eax, eax
0046EF9B |. 5A pop edx
0046EF9C |. 59 pop ecx
0046EF9D |. 59 pop ecx
0046EF9E |. 64:8910 mov dword ptr fs:[eax], edx
0046EFA1 |. 68 B6EF4600 push 0046EFB6
0046EFA6 |> 8D45 F4 lea eax, dword ptr [ebp-C]
0046EFA9 |. E8 CE52F9FF call 0040427C
0046EFAE \. C3 retn
跟进关键Call:
0046EC64 /$ 55 push ebp
0046EC65 |. 8BEC mov ebp, esp
0046EC67 |. 83C4 C8 add esp, -38
0046EC6A |. 8945 FC mov dword ptr [ebp-4], eax
0046EC6D |. 8B45 FC mov eax, dword ptr [ebp-4]
0046EC70 |. E8 A75AF9FF call 0040471C
0046EC75 |. 33C0 xor eax, eax
0046EC77 |. 55 push ebp
0046EC78 |. 68 92ED4600 push 0046ED92
0046EC7D |. 64:FF30 push dword ptr fs:[eax]
0046EC80 |. 64:8920 mov dword ptr fs:[eax], esp
0046EC83 |. 33C0 xor eax, eax
0046EC85 |. 8945 F8 mov dword ptr [ebp-8], eax
0046EC88 |. 8B45 FC mov eax, dword ptr [ebp-4]
0046EC8B |. E8 A458F9FF call 00404534 ; 取注册码长度
0046EC90 |. 83F8 10 cmp eax, 10 ; 判断是否为16位
0046EC93 |. 0F8C E3000000 jl 0046ED7C ;不是就完蛋
0046EC99 |. 8B45 FC mov eax, dword ptr [ebp-4]
0046EC9C |. BA A8ED4600 mov edx, 0046EDA8 ; ASCII "1163659294813585"
0046ECA1 E8 D259F9FF call 00404678
0046ECA6 0F84 D0000000 je 0046ED7C
0046ECAC |. 8B45 FC mov eax, dword ptr [ebp-4]
0046ECAF |. BA C4ED4600 mov edx, 0046EDC4 ; ASCII "0386848021608060"
0046ECB4 |. E8 BF59F9FF call 00404678
0046ECB9 0F84 BD000000 je 0046ED7C
0046ECBF |. 8B45 FC mov eax, dword ptr [ebp-4]
0046ECC2 |. BA E0ED4600 mov edx, 0046EDE0 ; ASCII "8319E4005F00PYG0"
0046ECC7 |. E8 AC59F9FF call 00404678
0046ECCC 0F84 AA000000 je 0046ED7C
0046ECD2 |. 8B45 FC mov eax, dword ptr [ebp-4]
0046ECD5 |. BA FCED4600 mov edx, 0046EDFC ; ASCII "0566838690673180"
0046ECDA |. E8 9959F9FF call 00404678
0046ECDF 0F84 97000000 je 0046ED7C
0046ECE5 |. 33C0 xor eax, eax
0046ECE7 |. 8945 DC mov dword ptr [ebp-24], eax ; 第一数据段清空
0046ECEA |> 8B45 FC /mov eax, dword ptr [ebp-4] ;eax赋注册码的起始位置
0046ECED |. 8B55 DC |mov edx, dword ptr [ebp-24] ;edx取计数器
0046ECF0 |. 8A0410 |mov al, byte ptr [eax+edx] ;送入一位注册码
0046ECF3 |. E8 24FFFFFF |call 0046EC1C ; Ascii码的处理(详见后面分析)
0046ECF8 |. 8B55 DC |mov edx, dword ptr [ebp-24]
0046ECFB |. 884415 CB |mov byte ptr [ebp+edx-35], al ;处理过的Asc码送入内存数据块
0046ECFF |. FF45 DC |inc dword ptr [ebp-24] ;计数器增1
0046ED02 |. 837D DC 10 |cmp dword ptr [ebp-24], 10 ;是否到16了?
0046ED06 |.^ 75 E2 \jnz short 0046ECEA
第一节处理后的数据不妨命名为串1
0046ED08 |. 33C0 xor eax, eax
0046ED0A |. 8945 E0 mov dword ptr [ebp-20], eax ; 第二数据段清空
0046ED0D |> 8B45 E0 /mov eax, dword ptr [ebp-20] ;eax为第一个计数器
0046ED10 |. 03C0 |add eax, eax ;乘2
0046ED12 |. 8A4405 CC |mov al, byte ptr [ebp+eax-34] ;取上一节处理后得到的字符串的一位
;最后一次使用的是上一节的注册码长度"16"!
0046ED16 |. C1E0 04 |shl eax, 4 ; 相当于eax*2^4
0046ED19 |. 8B55 E0 |mov edx, dword ptr [ebp-20] ;edx是第二个计数器
0046ED1C |. 03D2 |add edx, edx ;乘2
0046ED1E |. 024415 CB |add al, byte ptr [ebp+edx-35] ;取上一节处理后得到的字符串的一位与当前eax相加
;最后一次是"0"!
0046ED22 |. 8B55 E0 |mov edx, dword ptr [ebp-20]
0046ED25 |. 884415 EF |mov byte ptr [ebp+edx-11], al ;处理后的位送入内存数据块
0046ED29 |. FF45 E0 |inc dword ptr [ebp-20] ;计数器增1
0046ED2C |. 837D E0 09 |cmp dword ptr [ebp-20], 9 ;是否到9了?
0046ED30 |.^ 75 DB \jnz short 0046ED0D
第二节处理后的数据不妨称为串2
0046ED32 |. 8A45 F2 mov al, byte ptr [ebp-E] ;al赋值为串2的第4位
0046ED35 |. 3245 EF xor al, byte ptr [ebp-11] ;与串2的第1位异或
0046ED38 |. 8845 E6 mov byte ptr [ebp-1A], al ;存入数据(不妨称为串3)
0046ED3B |. 8A45 F0 mov al, byte ptr [ebp-10] ;取串2的第2位
0046ED3E |. 3245 F6 xor al, byte ptr [ebp-A] ;与串2的第8位异或
0046ED41 |. 8845 E7 mov byte ptr [ebp-19], al ;存入数据
0046ED44 |. 8A45 F1 mov al, byte ptr [ebp-F] ;取串2的第3位
0046ED47 |. 3245 F4 xor al, byte ptr [ebp-C] ;与串2的第6位异或
0046ED4A |. 8845 E8 mov byte ptr [ebp-18], al ;存入数据
0046ED4D |. 8A45 F5 mov al, byte ptr [ebp-B] ;取串2的第7位
0046ED50 |. 3245 F3 xor al, byte ptr [ebp-D] ;与串2的第5位异或
0046ED53 |. 8845 E9 mov byte ptr [ebp-17], al ;存入数据
0046ED56 |. 807D E6 38 cmp byte ptr [ebp-1A], 38 ;判断串3的第一位是否为38H
0046ED5A 75 1B jnz short 0046ED77 ;不是则失败
0046ED5C 807D E7 6E cmp byte ptr [ebp-19], 6E ;判断串3的第一位是否为6EH
0046ED60 75 15 jnz short 0046ED77
0046ED62 807D E8 4E cmp byte ptr [ebp-18], 4E ;判断串3的第一位是否为4EH
0046ED66 75 0F jnz short 0046ED77
0046ED68 807D E9 1A cmp byte ptr [ebp-17], 1A ;判断串3的第一位是否为1AH
0046ED6C 75 09 jnz short 0046ED77
0046ED6E |. C745 F8 FFFFF>mov dword ptr [ebp-8], -1 ;标志位赋值
0046ED75 |. EB 05 jmp short 0046ED7C
0046ED77 33C0 xor eax, eax
0046ED79 8945 F8 mov dword ptr [ebp-8], eax
0046ED7C 33C0 xor eax, eax
0046ED7E |. 5A pop edx
0046ED7F |. 59 pop ecx
0046ED80 |. 59 pop ecx
0046ED81 |. 64:8910 mov dword ptr fs:[eax], edx
0046ED84 |. 68 99ED4600 push 0046ED99
0046ED89 |> 8D45 FC lea eax, dword ptr [ebp-4]
0046ED8C |. E8 EB54F9FF call 0040427C
0046ED91 \. C3 retn
0046ED92 .^ E9 0D4FF9FF jmp 00403CA4
0046ED97 .^ EB F0 jmp short 0046ED89
0046ED99 . 8B45 F8 mov eax, dword ptr [ebp-8]
0046ED9C . 8BE5 mov esp, ebp
0046ED9E . 5D pop ebp
0046ED9F . C3 retn
Ascii码的处理Call:
0046EC1C /$ 55 push ebp
0046EC1D |. 8BEC mov ebp, esp
0046EC1F |. 51 push ecx
0046EC20 |. 8845 FF mov byte ptr [ebp-1], al
0046EC23 |. 807D FF 30 cmp byte ptr [ebp-1], 30 ; "0"
0046EC27 |. 72 10 jb short 0046EC39
0046EC29 |. 807D FF 39 cmp byte ptr [ebp-1], 39 ; "9"
0046EC2D |. 77 0A ja short 0046EC39
0046EC2F |. 8A45 FF mov al, byte ptr [ebp-1]
0046EC32 |. 2C 30 sub al, 30 ; 是数字的话ascii减30h
0046EC34 |. 8845 FD mov byte ptr [ebp-3], al
0046EC37 |. EB 1C jmp short 0046EC55
0046EC39 |> 807D FF 41 cmp byte ptr [ebp-1], 41 ; "A"
0046EC3D |. 72 12 jb short 0046EC51
0046EC3F |. 807D FF 46 cmp byte ptr [ebp-1], 46 ; "F"
0046EC43 |. 77 0C ja short 0046EC51
0046EC45 |. 8A45 FF mov al, byte ptr [ebp-1]
0046EC48 |. 2C 41 sub al, 41 ; 是A-F字母的话ascii减41h加Ah
0046EC4A |. 04 0A add al, 0A
0046EC4C |. 8845 FD mov byte ptr [ebp-3], al
0046EC4F |. EB 04 jmp short 0046EC55
0046EC51 |> C645 FD FF mov byte ptr [ebp-3], 0FF ; 啥也不是的话变FFh(255)
0046EC55 |> 8A45 FD mov al, byte ptr [ebp-3]
0046EC58 |. 8845 FE mov byte ptr [ebp-2], al
0046EC5B |. 8A45 FE mov al, byte ptr [ebp-2]
0046EC5E |. 59 pop ecx
0046EC5F |. 5D pop ebp
0046EC60 \. C3 retn
整个分析过程如上,前面4个16位字串应该是作者加入的黑名单,因为如果输入的注册码与之相同的话就注册失败。
在第三个里面见到了“PYG”的字样,想必是某位大侠的手笔。根据Ascii处理Call的分析,所有的A-F之外的字母都被处理成255,
那么其实我们把“PYG”改成任意A-F外的其他字母,就可以随便注册了。
接下来是注册码的整个处理过程,这个程序比较麻烦的地方在于处理的时候寄存器一直没有出现过有用的数据,而是作为计数器用了。
而真正的重要数据存放在内存里面,用ebp作为注册码的起始位置,这给我们的调试带来了不便。
处理的方法是在堆栈窗口点右键,“地址”,“相对于ebp”。这样,我们就可以很清楚的看到地址与ebp的关系。
再在堆栈窗口点右键,选择“在数据窗口”跟踪,看到了吗?整个注册码处理过程一目了然。那些ebp-n什么的再也不能干扰我们了。
接下来我们讨论破解的问题:
想爆破的话很容易的,把黑名单那里的跳转nop掉,或者把串3的判断跳转nop掉都可以。
但爆破不是我们的终极目标,所以继续分析:
我用VB翻译了一下关键Call的代码:
'The first section:
dim char1 as string
dim char2 as string
dim char3 as string
dim sn as string
dim z1 as integer:dim z2 as integer
dim al as integer
for i=1 to 16
char1=char1 & modify(mid(sn,i,1))
next i
char1=char1 & chr(0) & chr(16)
'The second section:
for i=0 to 8
z1=i:z1=z1+z1
al=asc(mid(char1,z1+2,1)):if al>255 then al=al-256 '其实这句if可以省略了,因为字符串1中ascii最大才为255,所以这里不会溢出。
al=al*2^4:if al>255 then al=al-256
z2=i:z2=z2+z2
al=al+asc(mid(char1,z2+1,1)):if al>255 then al=al-256
z2=i
char2=char2 & chr(al)
next i
z=asc(mid(char2,4,1)) xor asc(mid(char2,1,1)):if z>255 then z=z-256
char3=char3 & chr(z)
z=asc(mid(char2,2,1)) xor asc(mid(char2,8,1)):if z>255 then z=z-256
char3=char3 & chr(z)
z=asc(mid(char2,3,1)) xor asc(mid(char2,6,1)):if z>255 then z=z-256
char3=char3 & chr(z)
z=asc(mid(char2,7,1)) xor asc(mid(char2,5,1)):if z>255 then z=z-256
char3=char3 & chr(z)
if mid(char3,1,1)<>chr(56) then die
if mid(char3,2,1)<>chr(110) then die
if mid(char3,3,1)<>chr(78) then die
if mid(char3,4,1)<>chr(26) then die
end
function modify(z as string) as string
if z>="0" and z<="9" then
modify=chr(asc(z)-48)
else
if z>="A" and z<="F" then modify=chr(asc(z)-65+10) else modify=chr(255)
end if
end function
上面的代码是我为了让大家更好地看清楚流程而直接翻译的,没有任何优化。
而这段代码也只是供大家看,并不能真正执行成功。因为vb的位操作太弱,我也懒得写太多的判断。
还有就是在>127的情况下,chr()函数会自动变成chr(0),即Print asc(chr(135))结果为0,应该是跟unicode有关,
目前我想到的解决办法也就只有改字符串为数值型数组了,把Ascii码直接写在数组里面。
但发现这个问题的时候这段代码已经写完了,我也就懒得再改了,反正又不用它,呵呵:)
现在我们可以总结出一个注册码的处理流程:
1.将注册码挨位处理一遍,按照Ascii处理关键Call中的规则(也就是上段vb代码的modify函数)
2.将1处理过的注册码偶数位左移4位(相当于乘2^4),加上其前一位(奇数位)
3.把2处理得到的注册码的4与1、2与8、3与6、7与5位分别异或,生成长度为4的字符串3
4.判断串3的各个位是否符合要求
下面我们来写注册机:
根据上面分析,我们依次还原流程第3步的那4个xor:
根据第一节的处理流程,我们知道,第一节处理过后,得到的数据串中应该只含有0~9这几个数字(不考虑A-F之外的特殊处理的字母)。
所以第二节处理后的数据串里面最小值应该是0×16+0=0,最大值应该是9×16+9=153。
可以看出第三节处理所用的数据只应该在0~153之间。
我们依照上述分析写出注册机的流程如下:
1.随机生成一个0~153的数
2.1生成的数与对应的key number进行xor(key number即38、6E、4E、1A),如果得到的数不在0~153之间则重新计算(因为0~153之外的值无法被还原成注册码处理流程2中的数字)
3.把2中xor得key number的两个数字拆分成a×16+b的形式
4.把3中得到的4个数字按相应位置写回数据串1(即注册码)
用VB写出注册机如下:
'新建标准exe,放一个TextBox,名称为TxtKey,放两个Button,名称分别为CmdGen、CmdCopy。
Dim data(16) As Integer
Private Sub CmdCopy_Click()
Clipboard.Clear
Clipboard.SetText TxtKey.Text
End Sub
Private Sub CmdGen_Click()
For i = 1 To 16: data(i) = 0: Next i '清空数组
Call KeyGen(2, 8, 56) '分别以key number和计算所用的位置值来反向生成注册码段
Call KeyGen(4, 16, 110)
Call KeyGen(6, 12, 78)
Call KeyGen(10, 14, 26)
TxtKey.Text = ""
For i = 1 To 16
TxtKey.Text = TxtKey.Text & Hex(data(i)) '拼接注册码
Next i
End Sub
Private Sub Form_Load()
Me.Icon = LoadPicture("")
End Sub
Sub KeyGen(a As Integer, b As Integer, key As Integer)
Dim x As Integer
Dim y As Integer
Dim y1 As Integer
Dim x1 As Integer
Dim y2 As Integer
Dim x2 As Integer
Randomize Timer
1 x = Int(154 * Rnd) '原型为x=0+int((153-0+1)*rnd)
y = x Xor key
If y > 153 Then GoTo 1
y1 = x Mod 16: x1 = (x - y1) / 16 '拆分
y2 = y Mod 16: x2 = (y - y2) / 16
data(a) = x1: data(a - 1) = y1 '写回
data(b) = x2: data(b - 1) = y2
End Sub
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)