«
LibXL 算法分析

时间:2023-12-11    作者:范文泉    分类: 逆向


【软件介绍】: LibXL is a library that can read and write Excel files. It doesn't require Microsoft Excel and .NET framework, combines an easy to use and powerful features.

LibXL 可以原格式读写 Excel ,不需要电脑安装 Office, 具体使用请参照官网的文档:http://www.libxl.com/documentation.html
好久没练手了, 这次分析下算法. 看下 libxl.dll 中的导出函数 xlBookSetKeyA(BookHandle handle, const char name, const char key);
很容易找到关键地方。编写个测试 exe 进入 xlBookSetKey 开始调试。我只简单描述下关键地方.

10032640  /$  55            push    ebp                              ;  xlBookSetKey 函数入口
10032641  |.  8BEC          mov     ebp, esp
10032643  |.  6A FF         push    -0x1
10032645  |.  68 CA4D3810   push    10384DCA
1003264A  |.  64:A1 0000000>mov     eax, dword ptr fs:[0]
10032650  |.  50            push    eax

下面 是对用户名,注册码长度判断

10032687  |.  85DB          test    ebx, ebx                         ;  用户名是否为空?
10032689  |.  0F84 C2040000 je      10032B51
1003268F  |.  85C0          test    eax, eax                         ;  注册码是否为空?
10032691  |.  0F84 BA040000 je      10032B51
10032697  |.  50            push    eax
10032698  |.  8D4D 98       lea     ecx, dword ptr [ebp-0x68]
1003269B  |.  E8 B02DFDFF   call    10005450                         ;  求注册码长度
100326A0  |.  837D AC 28    cmp     dword ptr [ebp-0x54], 0x28       ;  注册码长度 是否 为 40 位?
100326A4  |.  C645 FC 01    mov     byte ptr [ebp-0x4], 0x1
100326A8  |.  C686 44030000>mov     byte ptr [esi+0x344], 0x0
100326AF  |.  8D4D 98       lea     ecx, dword ptr [ebp-0x68]
100326B2  |.  0F85 94040000 jnz     10032B4C
100326B8  |.  6A 08         push    0x8
100327C4  |.  E8 67D0FFFF   call    1002F830                         ;  用户名字符串 翻转
100327C9  |.  56            push    esi
100327CA  |.  E8 01083300   call    10362FD0
100327CF  |.  83C4 14       add     esp, 0x14
100327D2  |.  6A FF         push    -0x1
100327D4  |.  6A 00         push    0x0
100327D6  |.  8D8D 7CFFFFFF lea     ecx, dword ptr [ebp-0x84]
100327DC  |.  51            push    ecx
100327DD  |.  8B8D ECFEFFFF mov     ecx, dword ptr [ebp-0x114]
100327E3  |.  81C1 A4090000 add     ecx, 0x9A4
100327E9  |.  E8 A2EBFCFF   call    10001390
100327EE  |.  83EC 1C       sub     esp, 0x1C
100327F1  |.  8D95 7CFFFFFF lea     edx, dword ptr [ebp-0x84]
100327F7  |.  8BCC          mov     ecx, esp
100327F9  |.  89A5 E8FEFFFF mov     dword ptr [ebp-0x118], esp
100327FF  |.  52            push    edx
10032800  |.  E8 4BEDFCFF   call    10001550
10032805  |.  8D85 28FFFFFF lea     eax, dword ptr [ebp-0xD8]
1003280B  |.  50            push    eax
1003280C  |.  E8 2F750300   call    10069D40                         ;  翻转后的用户名, 求 MD5 值
10032811  |.  83C4 20       add     esp, 0x20

下面一段代码 取出32位注册码的第1,3,5,7,9,11,13,15,17,19,21,23,25位,并将取出的字符连接成字符串

100328E0  |> /83FE 20       /cmp     esi, 0x20
100328E3  |. |73 43         |jnb     short 10032928
100328E5  |. |83FE 1A       |cmp     esi, 0x1A
100328E8  |. |73 1B         |jnb     short 10032905
100328EA  |. |56            |push    esi
100328EB  |. |8D8D F0FEFFFF |lea     ecx, dword ptr [ebp-0x110]
100328F1  |. |E8 6A730300   |call    10069C60
100328F6  |. |0FB600        |movzx   eax, byte ptr [eax]
100328F9  |. |50            |push    eax                             ; /Arg1
100328FA  |. |8D8D 0CFFFFFF |lea     ecx, dword ptr [ebp-0xF4]       ; |
10032900  |. |E8 6BD5FFFF   |call    1002FE70                        ; \libxl.1002FE70
10032905  |> |8D4E 01       |lea     ecx, dword ptr [esi+0x1]
10032908  |. |51            |push    ecx
10032909  |. |8D8D F0FEFFFF |lea     ecx, dword ptr [ebp-0x110]
1003290F  |. |E8 4C730300   |call    10069C60
10032914  |. |0FB610        |movzx   edx, byte ptr [eax]
10032917  |. |52            |push    edx                             ; /Arg1
10032918  |. |8D8D 44FFFFFF |lea     ecx, dword ptr [ebp-0xBC]       ; |
1003291E  |. |E8 4DD5FFFF   |call    1002FE70                        ; \libxl.1002FE70
10032923  |. |83C6 02       |add     esi, 0x2
10032926  |.^\EB B8         \jmp     short 100328E0

对上一步取出的字符串 求 MD5,并截取 前 16 位,比较 md5 值的 前 16 位 是否是 3f8bfcaff330c39f

1003298E  |.  8038 33       cmp     byte ptr [eax], 0x33             ;  3
10032991  |.  0F85 41010000 jnz     10032AD8
10032997  |.  6A 01         push    0x1
10032999  |.  8D4D D0       lea     ecx, dword ptr [ebp-0x30]
1003299C  |.  E8 BF720300   call    10069C60
100329A1  |.  8038 66       cmp     byte ptr [eax], 0x66             ;  f
100329A4  |.  0F85 2E010000 jnz     10032AD8
100329AA  |.  6A 02         push    0x2
100329AC  |.  8D4D D0       lea     ecx, dword ptr [ebp-0x30]
100329AF  |.  E8 AC720300   call    10069C60
100329B4  |.  8038 38       cmp     byte ptr [eax], 0x38             ;  8
100329B7  |.  0F85 1B010000 jnz     10032AD8
100329BD  |.  6A 03         push    0x3
100329BF  |.  8D4D D0       lea     ecx, dword ptr [ebp-0x30]
100329C2  |.  E8 99720300   call    10069C60
100329C7  |.  8038 62       cmp     byte ptr [eax], 0x62             ;  b
100329CA  |.  0F85 08010000 jnz     10032AD8
100329D0  |.  6A 04         push    0x4
100329D2  |.  8D4D D0       lea     ecx, dword ptr [ebp-0x30]
100329D5  |.  E8 86720300   call    10069C60
100329DA  |.  8038 66       cmp     byte ptr [eax], 0x66             ;  f
...............................
...............................

以下代码, 可知注册码第 27, 29, 31 位满足关系

1002EE13 |. 0FBE71 1C movsx esi, byte ptr [ecx+0x1C]
1002EE17 |. 81C6 79070000 add esi, 0x779
1002EE1D |. 83F8 1E cmp eax, 0x1E
1002EE20 |. 73 09 jnb short 1002EE2B
1002EE22 |. E8 6B3C3300 call 10362A92
1002EE27 |. 8B4424 24 mov eax, dword ptr [esp+0x24]
1002EE2B |> 8B4C24 14 mov ecx, dword ptr [esp+0x14]
1002EE2F |. 396C24 28 cmp dword ptr [esp+0x28], ebp
1002EE33 |. 73 04 jnb short 1002EE39
1002EE35 |. 8D4C24 14 lea ecx, dword ptr [esp+0x14]
1002EE39 |> 57 push edi
1002EE3A |. 0FBE79 1E movsx edi, byte ptr [ecx+0x1E]
1002EE3E |. 83EF 69 sub edi, 0x69
1002EE41 |. 83F8 1A cmp eax, 0x1A
1002EE44 |. 73 05 jnb short 1002EE4B
1002EE46 |. E8 473C3300 call 10362A92
1002EE4B |> 8B4424 18 mov eax, dword ptr [esp+0x18]
1002EE4F |. 396C24 2C cmp dword ptr [esp+0x2C], ebp
1002EE53 |. 73 04 jnb short 1002EE59
1002EE55 |. 8D4424 18 lea eax, dword ptr [esp+0x18]
1002EE59 |> 0FBE50 1A movsx edx, byte ptr [eax+0x1A]
1002EE5D |. 8D8C37 87F8FF>lea ecx, dword ptr [edi+esi-0x779]
1002EE64 |. 3BD1 cmp edx, ecx
1002EE66 |. 75 28 jnz short 1002EE90
1002EE68 |. 81FE DD070000 cmp esi, 0x7DD
1002EE6E |. 7E 0F jle short 1002EE7F
1002EE70 |. 81FE DF070000 cmp esi, 0x7DF
1002EE76 |. 7C 18 jl short 1002EE90
1002EE78 |. 75 0D jnz short 1002EE87
1002EE7A |. 83FF 03 cmp edi, 0x3
1002EE7D |. EB 06 jmp short 1002EE85
1002EE7F |> 81FE DC070000 cmp esi, 0x7DC

由上面可得知 注册码第27, 29, 31 位 满足以下关系

第29个字符串 d  0x64 + 0x779 = 0x7DD   -> ESI 
第31个字符串 o  0x6F-0x69  = 0x6       -> EDI 
第27个字符串 j  0x6A             

ESI + EDI - 0x779 = 0x6A 
if  ESI <= 0x7DD then
     if ESI < 0x7DC then
           [ebx+0x9F8]= 0x1   失败
     else
          [ebx+0x99C]= 0  成功
    end if
else
     if ESI < 0x7DF then
          [ebx+0x9F8]= 0x1  失败 
    else
         if ESI != 0x7DF then
            [ebx+0x99C]= 0  成功
         else
              if EDI < 0x3 then
                  [ebx+0x9F8]= 0x1  失败
             else
                  [ebx+0x99C]= 0 成功
             end if
         end if   
     end if
end if

算法总结:

  1. 注册码格式:windows-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(windows- 后面 32 位)

  2. 32位注册码的第1,3,5,7,9,11,13,15,17,19,21,23,25位是固定值, 分别是 22200ce06b66a

  3. 32位注册码的第2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28,30, 32位是:用户名字符串,经过翻转, 求出 MD5 值, 然后 取 前 16 位

  4. 32位注册码的第27, 29, 31 位满足以下关系:

    (1) ASC(29位)+ASC(31位)-0x69 = ASC(27位字母)

    (2) ASC(29位) >= 0x63 并且 ASC(29位) 不能等于 0x65, 而且 当 ASC(29位) = 0x66 时,ASC(31位) >=0x6C

以下是注册机源码的关键部分(使用 PowerBASIC 语言 img)

function GetRegCode(byval hWnd as dword, byref edtstr as asciiz) as STRING
        local i as LONG
        local oMD5 as iMD5
        Dim bReg(32) as BYTE
        local sName as asciiz * 260
        local szMd5 as ASCIIZ * 40
        local szChar as ASCIZ * 10
        dim p as byte ptr
        dim pRegCode as ASCIIZ ptr
        if CheckIsDBCS(edtstr) = 1 THEN
                MessageBox hWnd, "用户名不能包含中文。", "提示!", %MB_OK or %MB_ICONEXCLAMATION
        END IF

        for i = len(edtstr) to 1 step -1
                sName = sName & Mid$(edtstr, i, 1)
        NEXT
        oMD5 = class "MD5"
        szMd5 = LCASE$(oMD5.calc(sName))
        ARRAY ASSIGN bReg() = &H32,&H00,&H32,&H00,&H32,&H00,&H30,&H00,&H30,&H00,&H63,&H00,&H65,&H00,&H30,&H00,&H36,&H00,&H62,&H00,&H36,&H00,&H36,&H00,&H61,&H00,&H00,&H00,&H00,&H00,&H00,&H00,&H00
        p = varptr(szMd5)
        for i = 1 to 31
                bReg(i) = @p
                p=p+1
                i=i+1
        NEXT
        szChar = GetThreeChar()
        p = varptr(szChar)
        bReg(26) = @p
        p = p+1
        bReg(28) = @p
        p = p+1
        bReg(30) = @p

        pRegCode = varptr(bReg(0))

        function = "windows-" & @pRegCode
END FUNCTION

function GetThreeChar() as STRING
        local char27 as ASCIIZ * 2
        local char29 as ASCIIZ * 2
        local char31 as ASCIIZ * 2

        char29 = Get29()
        char31 = Get31(char29)
        char27 = chr$(ASC(char29) + ASC(char31) - 105)

        function = char27 & char29 & char31
END FUNCTION

function Get29() as string
        local char29 as ASCIIZ * 2
        randomize
        char29 = chr$(int(rnd*24 + 99))
        if asc(char29) = 101 THEN  ' e
                Get29()
        END IF

        function = char29
END FUNCTION

function Get31(byref char as asciiz) as STRING
        local char31 as ASCIIZ * 2
        randomize
        char31 = chr$(int(rnd*26 + 202- ASC(char)))
        if ASC(char) <> 102 THEN
                if ASC(char31) >= 97 and ASC(char31) < 123 THEN
                        function = char31
                else
                        Get31(char)
                END IF
        else
                if ASC(char31) >= 108 and ASC(char31) < 123 THEN
                        function = char31
                else
                        Get31(char)
                END IF
        END IF
END FUNCTION