【文章标题】: 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