第78章 加密狗

78.1 例1:PowerPC平台的MacOS Classic程序

本例研究的是一款运行在PowerPC平台上的MacOS Classic程序。研发这款程序的公司早已经不知去向,所以买家十分害怕加密狗出什么意外。

在不插入加密狗的情况下,程序会显示“Invalid Security Device”信息。非常幸运的是,这个字符串就在程序的可执行文件里。

虽然那时我即不熟悉Mac OS Classic系统,也没怎么用过PowerPC,但是还是放手一搏。

IDA可以毫无困难地打开这个程序。它判断该文件类型为“PEF (Mac OS or Be OS executable)”。标准的Mac OS Classic的程序文件的确采用了这种文件格式。

接下来,在文件里搜索错误信息的字符串时,我找到了下述指令:

...

seg000:000C87FC 38 60 00 01              li      %r3, 1
seg000:000C8800 48 03 93 41              bl      check1
seg000:000C8804 60 00 00 00              nop
seg000:000C8808 54 60 06 3F              clrlwi. %r0, %r3, 24
seg000:000C880C 40 82 00 40              bne     OK
seg000:000C8810 80 62 9F D8              lwz     %r3, TC_aInvalidSecurityDevice

...

这些都是PowerPC平台的指令。这款CPU是20世纪90年代出产的一款典型的32位RISC CPU。它的每条指令都占用4个字节(与MIPS和ARM的指令相似),指令名称还与MIPS指令相似。

为了便于演示,我把函数名称改为check1()。BL是Brach Link指令,常用于调用子函数。上述程序的关键点是BNE指令,在程序通过了加密狗认证的情况下进行跳转,否则就会在r3寄存器里加载字符串,然后报错。

在参阅了参考文献[SK95]之后,我发现r3寄存器用于存储返回值。如果返回值是64位数据,那么r4寄存器也会用于回传返回值。

另外,CLRLWI指令[1]还是当时的盲点。后来我阅读了参考文献[IBM00],获悉它是清除和传递数据的复合指令。本例的这个指令会清除r3寄存器的高24位,把结果存储在r0寄存器里。所以,它不仅相当于x86的MOVZX指令(可参见本书15.1.1节),而且还能设置相应标识位,向后面的BNE指令传递标识信息。

接下来,我们探索一下check 1()函数:

seg000:00101B40                 check1: # CODE XREF: seg000:00063E7Cp
seg000:00101B40                         # sub_64070+160p ...
seg000:00101B40 
seg000:00101B40                 .set arg_8, 8
seg000:00101B40
seg000:00101B40 7C 08 02 A6             mflr    %r0
seg000:00101B44 90 01 00 08             stw     %r0, arg_8(%sp)
seg000:00101B48 94 21 FF C0             stwu    %sp, -0x40(%sp)
seg000:00101B4C 48 01 6B 39             bl      check2
seg000:00101B50 60 00 00 00             nop
seg000:00101B54 80 01 00 48             lwz     %r0, 0x40+arg_8(%sp)
seg000:00101B58 38 21 00 40             addi    %sp, %sp, 0x40
seg000:00101B5C 7C 08 03 A6             mtlr    %r0
seg000:00101B60 4E 80 00 20             blr
seg000:00101B60                 # End of function check1

在IDA中,我们可以清楚地观察到:虽然程序的多个指令都调用了这个函数,但是调用方函数在调用结束之后只访问了r3寄存器的值。这个函数只起到调用其他函数的功能,因此它就是形实转换函数:即使函数序言和函数尾声都十分完整,但是对r3寄存器完全没有操作。据此判断,check1()函数的返回值与check()函数一致。

BLR指令[2]似乎是函数返回语句,可作为划分函数模块的标识。但是IDA能够识别并划分函数体,因此我们可以先不管它。由于本程序采用的是RISC(精简指令集)指令,调用方函数会通过链接寄存器(Link Register)向被调用方函数传递返回地址。就这些特征来看,PowerPC的程序与ARM程序有很多共同点。

check2()函数略为复杂:

seg000:00118684            check2: # CODE XREF: check1+Cp
seg000:00118684
seg000:00118684            .set var_18, -0x18
seg000:00118684            .set var_C, -0xC
seg000:00118684            .set var_8, -8
seg000:00118684            .set var_4, -4
seg000:00118684            .set arg_8,  8
seg000:00118684
seg000:00118684 93 E1 FF FC   stw     %r31, var_4(%sp)
seg000:00118688 7C 08 02 A6   mflr    %r0
seg000:0011868C 83 E2 95 A8   lwz     %r31, off_1485E8 # dword_24B704
seg000:00118690               .using  dword_24B704, %r31
seg000:00118690 93 C1 FF F8   stw     %r30, var_8(%sp)
seg000:00118694 93 A1 FF F4   stw     %r29, var_C(%sp)
seg000:00118698 7C 7D 1B 78   mr      %r29, %r3
seg000:0011869C 90 01 00 08   stw     %r0, arg_8(%sp)
seg000:001186A0 54 60 06 3E   clrlwi  %r0, %r3, 24
seg000:001186A4 28 00 00 01   cmplwi  %r0, 1
seg000:001186A8 94 21 FF B0   stwu    %sp, -0x50(%sp)
seg000:001186AC 40 82 00 0C   bne     loc_1186B8
seg000:001186B0 38 60 00 01   li      %r3, 1
seg000:001186B4 48 00 00 6C   b       exit
seg000:001186B8
seg000:001186B8           loc_1186B8:   # CODE XREF: check2+28j
seg000:001186B8 48 00 03 D5   bl      sub_118A8C
seg000:001186BC 60 00 00 00   nop
seg000:001186C0 3B C0 00 00   li      %r30, 0
seg000:001186C4
seg000:001186C4           skip:         # CODE XREF: check2+94j
seg000:001186C4 57 C0 06 3F   clrlwi. %r0, %r30, 24
seg000:001186C8 41 82 00 18   beq     loc_1186E0
seg000:001186CC 38 61 00 38   addi    %r3, %sp, 0x50+var_18
seg000:001186D0 80 9F 00 00   lwz     %r4, dword_24B704
seg000:001186D4 48 00 C0 55   bl      .RBEFINDNEXT
seg000:001186D8 60 00 00 00   nop
seg000:001186DC 48 00 00 1C   b       loc_1186F8
seg000:001186E0
seg000:001186E0           loc_1186E0:   # CODE XREF: check2+44j
seg000:001186E0 80 BF 00 00   lwz     %r5, dword_24B704
seg000:001186E4 38 81 00 38   addi    %r4, %sp, 0x50+var_18
seg000:001186E8 38 60 08 C2   li      %r3, 0x1234
seg000:001186EC 48 00 BF 99   bl      .RBEFINDFIRST
seg000:001186F0 60 00 00 00   nop
seg000:001186F4 3B C0 00 01   li      %r30, 1
seg000:001186F8
seg000:001186F8           loc_1186F8:   # CODE XREF: check2+58j
seg000:001186F8 54 60 04 3F   clrlwi. %r0, %r3, 16
seg000:001186FC 41 82 00 0C   beq     must_jump
seg000:00118700 38 60 00 00   li      %r3, 0             # error
seg000:00118704 48 00 00 1C   b       exit
seg000:00118708
seg000:00118708           must_jump:    # CODE XREF: check2+78j
seg000:00118708 7F A3 EB 78   mr      %r3, %r29
seg000:0011870C 48 00 00 31   bl      check3
seg000:00118710 60 00 00 00   nop
seg000:00118714 54 60 06 3F   clrlwi. %r0,%r3, 24
seg000:00118718 41 82 FF AC   beq     skip
seg000:0011871C 38 60 00 01   li      %r3, 1
seg000:00118720
seg000:00118720           exit:         # CODE XREF: check2+30j
seg000:00118720                         # check2+80j
seg000:00118720 80 01 00 58   lwz     %r0, 0x50+arg_8(%sp)
seg000:00118724 38 21 00 50   addi    %sp, %sp, 0x50
seg000:00118728 83 E1 FF FC   lwz     %r31, var_4(%sp)
seg000:0011872C 7C 08 03 A6   mtlr    %r0
seg000:00118730 83 C1 FF F8   lwz     %r30, var_8(%sp)
seg000:00118734 83 A1 FF F4   lwz     %r29, var_C(%sp)
seg000:00118738 4E 80 00 20   blr
seg000:00118738             # End of function check2

因为可执行文件保留了部分函数名称,所以分析的难度并非很高。例如,程序文件里有.RBEFINDNEXT() 和 .RBEFINDFIRST()等函数名。这可能是编译器留下的调试符号。虽然无法确定这种情况的具体原因,但是在不了解文件格式的情况下,我们可以参考与之类似的PE文件格式(可参考68.2.7节)。而这些函数最终都调用了.GetNextDeviceViaUSB()函数和.USBSendPKT()函数。从函数名称可以判断,它们都是访问USB加密狗的函数。

程序里甚至还直接调用了.GetNextEve3Device()函数。这个函数在20世纪90年代就非常著名。程序往往通过这个函数访问Mac设备上的ADB口,最终访问Sentinel Eve3 加密狗。

我们首先要把其他问题搁置一边,重点关注r3寄存器在函数返回前的赋值过程。在分析前面的指令时,我们已经知道,如果r3寄存器的值为零,那么程序将转向错误提示信息的信息窗口;所以,我们关注的是对r3寄存器进行非零赋值的指令。

上述程序中,有两条“li %r3,{非零值}”指令,有一条“li %r3, 0”指令。LI是Load Immediate的缩写,可见li指令的作用是“令寄存器加载立即数”。第一条指令的地址是0x001186B0。坦白地讲,如果要了解它的具体作用,还需要进一步学习PowerPC平台的汇编语言。

然而下一处简明易懂。它调用.RBEFINDFIRST()函数。如果函数验证失败,则r3的值为0,程序将跳转到exit(退出);否则,继续调用check3()函数。如果check3()函数的验证失败,那么程序将调用.RBEFINDNEXT()函数,大概是检测下一个USB口的意思吧。

前文我们介绍过“clrlwi %r0, %r3, 16”的具体功能了。要注意的是,它清除的是16位数据。这就代表着.REBFINDFIRST()函数的返回值多半也是16位数据。

此外,B(branch)指令是无条件转移指令,BEQ的触发条件和BNE相反。这些指令就不再介绍了。

下面来分析check3()函数:

`

seg000:0011873C              check3: # CODE XREF: check2+88p
seg000:0011873C
seg000:0011873C              .set var_18, -0x18
seg000:0011873C              .set var_C, -0xC
seg000:0011873C              .set var_8, -8
seg000:0011873C              .set var_4, -4
seg000:0011873C              .set arg_8,   8
seg000:0011873C
seg000:0011873C 93 E1 FF FC    stw    %r31, var_4(%sp)
seg000:00118740 7C 08 02 A6    mflr   %r0
seg000:00118744 38 A0 00 00    li     %r5, 0
seg000:00118748 93 C1 FF F8    stw    %r30, var_8(%sp)
seg000:0011874C 83 C2 95 A8    lwz    %r30, off_1485E8 # dword_24B704
seg000:00118750                .using dword_24B704, %r30
seg000:00118750 93 A1 FF F4    stw    %r29, var_C(%sp)
seg000:00118754 3B A3 00 00    addi   %r29, %r3, 0
seg000:00118758 38 60 00 00    li     %r3, 0
seg000:0011875C 90 01 00 08    stw    %r0, arg_8(%sp)
seg000:00118760 94 21 FF B0    stwu   %sp, -0x50(%sp)
seg000:00118764 80 DE 00 00    lwz    %r6, dword_24B704
seg000:00118768 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:0011876C 48 00 C0 5D    bl     .RBEREAD
seg000:00118770 60 00 00 00    nop
seg000:00118774 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:00118778 41 82 00 0C    beq    loc_118784
seg000:0011877C 38 60 00 00    li     %r3, 0
seg000:00118780 48 00 02 F0    b      exit
seg000:00118784
seg000:00118784              loc_118784: # CODE XREF: check3+3Cj
seg000:00118784 A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:00118788 28 00 04 B2    cmplwi %r0, 0x1100
seg000:0011878C 41 82 00 0C    beq    loc_118798
seg000:00118790 38 60 00 00    li     %r3, 0
seg000:00118794 48 00 02 DC    b      exit
seg000:00118798
seg000:00118798              loc_118798: # CODE XREF: check3+50j
seg000:00118798 80 DE 00 00    lwz    %r6, dword_24B704
seg000:0011879C 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:001187A0 38 60 00 01    li     %r3, 1
seg000:001187A4 38 A0 00 00    li     %r5, 0
seg000:001187A8 48 00 C0 21    bl     .RBEREAD
seg000:001187AC 60 00 00 00    nop
seg000:001187B0 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:001187B4 41 82 00 0C    beq    loc_1187C0
seg000:001187B8 38 60 00 00    li     %r3, 0
seg000:001187BC 48 00 02 B4    b      exit
seg000:001187C0
seg000:001187C0              loc_1187C0: # CODE XREF: check3+78j
seg000:001187C0 A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:001187C4 28 00 06 4B    cmplwi %r0, 0x09AB
seg000:001187C8 41 82 00 0C    beq    loc_1187D4
seg000:001187CC 38 60 00 00    li     %r3, 0
seg000:001187D0 48 00 02 A0    b      exit
seg000:001187D4
seg000:001187D4              loc_1187D4: # CODE XREF: check3+8Cj
seg000:001187D4 4B F9 F3 D9    bl     sub_B7BAC
seg000:001187D8 60 00 00 00    nop
seg000:001187DC 54 60 06 3E    clrlwi %r0, %r3, 24
seg000:001187E0 2C 00 00 05    cmpwi  %r0, 5
seg000:001187E4 41 82 01 00    beq    loc_1188E4
seg000:001187E8 40 80 00 10    bge    loc_1187F8
seg000:001187EC 2C 00 00 04    cmpwi  %r0, 4
seg000:001187F0 40 80 00 58    bge    loc_118848
seg000:001187F4 48 00 01 8C    b      loc_118980
seg000:001187F8 
seg000:001187F8              loc_1187F8: # CODE XREF: check3+ACj
seg000:001187F8 2C 00 00 0B    cmpwi  %r0, 0xB
seg000:001187FC 41 82 00 08    beq    loc_118804
seg000:00118800 48 00 01 80    b      loc_118980
seg000:00118804
seg000:00118804              loc_118804: # CODE XREF: check3+C0j
seg000:00118804 80 DE 00 00    lwz    %r6, dword_24B704
seg000:00118808 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:0011880C 38 60 00 08    li     %r3, 8
seg000:00118810 38 A0 00 00    li     %r5, 0
seg000:00118814 48 00 BF B5    bl     .RBEREAD
seg000:00118818 60 00 00 00    nop
seg000:0011881C 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:00118820 41 82 00 0C    beq    loc_11882C
seg000:00118824 38 60 00 00    li     %r3, 0
seg000:00118828 48 00 02 48    b      exit
seg000:0011882C 
seg000:0011882C              loc_11882C: # CODE XREF: check3+E4j
seg000:0011882C A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:00118830 28 00 11 30    cmplwi %r0, 0xFEA0
seg000:00118834 41 82 00 0C    beq    loc_118840
seg000:00118838 38 60 00 00    li     %r3, 0
seg000:0011883C 48 00 02 34    b      exit
seg000:00118840 
seg000:00118840              loc_118840: # CODE XREF: check3+F8j
seg000:00118840 38 60 00 01    li     %r3, 1
seg000:00118844 48 00 02 2C    b      exit
seg000:00118848
seg000:00118848              loc_118848: # CODE XREF: check3+B4j
seg000:00118848 80 DE 00 00    lwz    %r6, dword_24B704
seg000:0011884C 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:00118850 38 60 00 0A    li     %r3, 0xA
seg000:00118854 38 A0 00 00    li     %r5, 0
seg000:00118858 48 00 BF 71    bl     .RBEREAD
seg000:0011885C 60 00 00 00    nop
seg000:00118860 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:00118864 41 82 00 0C    beq    loc_118870
seg000:00118868 38 60 00 00    li     %r3, 0
seg000:0011886C 48 00 02 04    b      exit
seg000:00118870
seg000:00118870              loc_118870: # CODE XREF: check3+128j
seg000:00118870 A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:00118874 28 00 03 F3    cmplwi %r0, 0xA6E1
seg000:00118878 41 82 00 0C    beq    loc_118884
seg000:0011887C 38 60 00 00    li     %r3, 0
seg000:00118880 48 00 01 F0    b      exit
seg000:00118884
seg000:00118884                loc_118884: # CODE XREF: check3+13Cj
seg000:00118884 57 BF 06 3E    clrlwi  %r31, %r29, 24
seg000:00118888 28 1F 00 02    cmplwi  %r31, 2
seg000:0011888C 40 82 00 0C    bne    loc_118898
seg000:00118890 38 60 00 01    li     %r3, 1
seg000:00118894 48 00 01 DC    b      exit
seg000:00118898
seg000:00118898              loc_118898: # CODE XREF: check3+150j
seg000:00118898 80 DE 00 00    lwz    %r6, dword_24B704
seg000:0011889C 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:001188A0 38 60 00 0B    li     %r3, 0xB
seg000:001188A4 38 A0 00 00    li     %r5, 0
seg000:001188A8 48 00 BF 21    bl     .RBEREAD
seg000:001188AC 60 00 00 00    nop
seg000:001188B0 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:001188B4 41 82 00 0C    beq    loc_1188C0
seg000:001188B8 38 60 00 00    li     %r3, 0
seg000:001188BC 48 00 01 B4    b      exit
seg000:001188C0
seg000:001188C0              loc_1188C0: # CODE XREF: check3+178j
seg000:001188C0 A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:001188C4 28 00 23 1C    cmplwi %r0, 0x1C20
seg000:001188C8 41 82 00 0C    beq    loc_1188D4
seg000:001188CC 38 60 00 00    li     %r3, 0
seg000:001188D0 48 00 01 A0    b      exit
seg000:001188D4 
seg000:001188D4              loc_1188D4: # CODE XREF: check3+18Cj
seg000:001188D4 28 1F 00 03    cmplwi %r31, 3
seg000:001188D8 40 82 01 94    bne    error
seg000:001188DC 38 60 00 01    li     %r3, 1
seg000:001188E0 48 00 01 90    b      exit
seg000:001188E4 
seg000:001188E4              loc_1188E4: # CODE XREF: check3+A8j
seg000:001188E4 80 DE 00 00    lwz    %r6, dword_24B704
seg000:001188E8 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:001188EC 38 60 00 0C    li     %r3, 0xC
seg000:001188F0 38 A0 00 00    li     %r5, 0
seg000:001188F4 48 00 BE D5    bl     .RBEREAD
seg000:001188F8 60 00 00 00    nop
seg000:001188FC 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:00118900 41 82 00 0C    beq    loc_11890C
seg000:00118904 38 60 00 00    li     %r3, 0
seg000:00118908 48 00 01 68    b      exit
seg000:0011890C
seg000:0011890C              loc_11890C: # CODE XREF: check3+1C4j
seg000:0011890C A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:00118910 28 00 1F 40    cmplwi %r0, 0x40FF
seg000:00118914 41 82 00 0C    beq    loc_118920
seg000:00118918 38 60 00 00    li     %r3, 0
seg000:0011891C 48 00 01 54    b      exit
seg000:00118920
seg000:00118920              loc_118920: # CODE XREF: check3+1D8j
seg000:00118920 57 BF 06 3E    clrlwi %r31, %r29, 24
seg000:00118924 28 1F 00 02    cmplwi %r31, 2
seg000:00118928 40 82 00 0C    bne    loc_118934
seg000:0011892C 38 60 00 01    li     %r3, 1
seg000:00118930 48 00 01 40    b      exit
seg000:00118934
seg000:00118934              loc_118934: # CODE XREF: check3+1ECj
seg000:00118934 80 DE 00 00    lwz    %r6, dword_24B704
seg000:00118938 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:0011893C 38 60 00 0D    li     %r3, 0xD
seg000:00118940 38 A0 00 00    li     %r5, 0
seg000:00118944 48 00 BE 85    bl     .RBEREAD
seg000:00118948 60 00 00 00    nop
seg000:0011894C 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:00118950 41 82 00 0C    beq    loc_11895C
seg000:00118954 38 60 00 00    li     %r3, 0
seg000:00118958 48 00 01 18    b      exit
seg000:0011895C
seg000:0011895C              loc_11895C: # CODE XREF: check3+214j
seg000:0011895C A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:00118960 28 00 07 CF    cmplwi %r0, 0xFC7
seg000:00118964 41 82 00 0C    beq    loc_118970
seg000:00118968 38 60 00 00    li     %r3, 0
seg000:0011896C 48 00 01 04    b      exit
seg000:00118970
seg000:00118970              loc_118970: # CODE XREF: check3+228j
seg000:00118970 28 1F 00 03    cmplwi %r31, 3
seg000:00118974 40 82 00 F8    bne    error
seg000:00118978 38 60 00 01    li     %r3, 1
seg000:0011897C 48 00 00 F4    b      exit
seg000:00118980
seg000:00118980              loc_118980: # CODE XREF: check3+B8j
seg000:00118980                          # check3+C4j
seg000:00118980 80 DE 00 00    lwz    %r6, dword_24B704
seg000:00118984 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:00118988 3B E0 00 00    li     %r31, 0
seg000:0011898C 38 60 00 04    li     %r3, 4
seg000:00118990 38 A0 00 00    li     %r5, 0
seg000:00118994 48 00 BE 35    bl     .RBEREAD
seg000:00118998 60 00 00 00    nop
seg000:0011899C 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:001189A0 41 82 00 0C    beq    loc_1189AC
seg000:001189A4 38 60 00 00    li     %r3, 0
seg000:001189A8 48 00 00 C8    b      exit
seg000:001189AC
seg000:001189AC              loc_1189AC: # CODE XREF: check3+264j
seg000:001189AC A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:001189B0 28 00 1D 6A    cmplwi %r0, 0xAED0
seg000:001189B4 40 82 00 0C    bne    loc_1189C0
seg000:001189B8 3B E0 00 01    li     %r31, 1
seg000:001189BC 48 00 00 14    b      loc_1189D0
seg000:001189C0 
seg000:001189C0              loc_1189C0: # CODE XREF: check3+278j
seg000:001189C0 28 00 18 28    cmplwi %r0, 0x2818
seg000:001189C4 41 82 00 0C    beq    loc_1189D0
seg000:001189C8 38 60 00 00    li     %r3, 0
seg000:001189CC 48 00 00 A4    b      exit
seg000:001189D0 
seg000:001189D0              loc_1189D0: # CODE XREF: check3+280j
seg000:001189D0                          # check3+288j
seg000:001189D0 57 A0 06 3E    clrlwi %r0, %r29, 24
seg000:001189D4 28 00 00 02    cmplwi %r0, 2
seg000:001189D8 40 82 00 20    bne    loc_1189F8
seg000:001189DC 57 E0 06 3F    clrlwi.%r0, %r31, 24
seg000:001189E0 41 82 00 10    beq    good2
seg000:001189E4 48 00 4C 69    bl     sub_11D64C
seg000:001189E8 60 00 00 00    nop
seg000:001189EC 48 00 00 84    b      exit
seg000:001189F0
seg000:001189F0              good2:      # CODE XREF: check3+2A4j
seg000:001189F0 38 60 00 01    li     %r3, 1
seg000:001189F4 48 00 00 7C    b      exit
seg000:001189F8 
seg000:001189F8              loc_1189F8: # CODE XREF: check3+29Cj
seg000:001189F8 80 DE 00 00    lwz    %r6, dword_24B704
seg000:001189FC 38 81 00 38    addi   %r4, %sp, 0x50+var_18
seg000:00118A00 38 60 00 05    li     %r3, 5
seg000:00118A04 38 A0 00 00    li     %r5, 0
seg000:00118A08 48 00 BD C1    bl     .RBEREAD
seg000:00118A0C 60 00 00 00    nop
seg000:00118A10 54 60 04 3F    clrlwi.%r0, %r3, 16
seg000:00118A14 41 82 00 0C    beq    loc_118A20
seg000:00118A18 38 60 00 00    li     %r3, 0
seg000:00118A1C 48 00 00 54    b      exit
seg000:00118A20
seg000:00118A20              loc_118A20: # CODE XREF: check3+2D8j
seg000:00118A20 A0 01 00 38    lhz    %r0, 0x50+var_18(%sp)
seg000:00118A24 28 00 11 D3    cmplwi %r0, 0xD300
seg000:00118A28 40 82 00 0C    bne    loc_118A34
seg000:00118A2C 3B E0 00 01    li     %r31, 1
seg000:00118A30 48 00 00 14    b      good1
seg000:00118A34
seg000:00118A34              loc_118A34: # CODE XREF: check3+2ECj
seg000:00118A34 28 00 1A EB    cmplwi %r0, 0xEBA1
seg000:00118A38 41 82 00 0C    beq    good1
seg000:00118A3C 38 60 00 00    li     %r3, 0
seg000:00118A40 48 00 00 30    b      exit
seg000:00118A44
seg000:00118A44              good1:      # CODE XREF: check3+2F4j
seg000:00118A44                          # check3+2FCj
seg000:00118A44 57 A0 06 3E    clrlwi %r0, %r29, 24
seg000:00118A48 28 00 00 03    cmplwi %r0, 3
seg000:00118A4C 40 82 00 20    bne    error
seg000:00118A50 57 E0 06 3F    clrlwi.%r0, %r31, 24
seg000:00118A54 41 82 00 10    beq    good
seg000:00118A58 48 00 4B F5    bl     sub_11D64C
seg000:00118A5C 60 00 00 00    nop
seg000:00118A60 48 00 00 10    b     exit
seg000:00118A64 
seg000:00118A64              good:       # CODE XREF: check3+318j
seg000:00118A64 38 60 00 01    li    %r3, 1
seg000:00118A68 48 00 00 08    b     exit
seg000:00118A6C 
seg000:00118A6C              error:      # CODE XREF: check3+19Cj
seg000:00118A6C                          # check3+238j ...
seg000:00118A6C 38 60 00 00    li    %r3, 0
seg000:00118A70
seg000:00118A70                exit:      # CODE XREF: check3+44j
seg000:00118A70                           # check3+58j ...
seg000:00118A70 80 01 00 58    lwz   %r0, 0x50+arg_8(%sp)
seg000:00118A74 38 21 00 50    addi  %sp, %sp, 0x50
seg000:00118A78 83 E1 FF FC    lwz   %r31, var_4(%sp)
seg000:00118A7C 7C 08 03 A6    mtlr  %r0
seg000:00118A80 83 C1 FF F8    lwz   %r30, var_8(%sp)
seg000:00118A84 83 A1 FF F4    lwz   %r29, var_C(%sp)
seg000:00118A88 4E 80 00 20    blr
seg000:00118A88              # End of function check3

此函数多次调用了.RBEREAD()函数。在调用后面的这个函数之后,check3()函数又大量使用CMPLWI指令将返回值与特定的固定值进行比较。由此可见.RBEREAD()函数大体是从加密狗读取数据的函数。

另外,在调用.RBEREAD()函数之前,r3寄存器的取值不外乎0、1、8、0xA、0xB、0xC、0xD、4和5。这很可能是内存地址一类的信息。

如果使用google引擎搜索这些函数名,google的查询结果多数就是Sentinel Eve 3加密狗开发手册。

其实,在不了解其他PowerPC指令的情况下,我们照样可以分析加密狗的有关操作。毕竟认证函数就是这几个,而且“认证成功”的返回值肯定是1,“认证失败”的返回值又肯定是0。

综合上述分析,只要让check1()函数的返回值固定为1或者其他某个非零值,即可破解软件狗认证。但是因为我并不熟悉PowerPC的指令,所以我决定采取保守的修改方案:修改check2()函数中0x001186FC处和0x00118718处的转移指令。

我把0x001186FC的指令改为0x48和0,即把BEQ指令替换为无条件转移指令B。即使不参阅[IBM00]的参考手册,我们也能在程序里找到B指令的opcode。

另外,我把0x00118718处修改为一个0x60和三个0字节,将有关指令改为NOP指令。当然,这也是从原程序里找的opcode。

进行上述修改之后,在不插入加密狗的情况下,程序仍然可正常运行。

总之,借助IDA和部分汇编知识,任何人都可以小规模地修改程序。

78.2 例2: SCO OpenServer

本例研究的程序是1997年开发的面向SCO OpenServer的程序。因为年代过于久远,买家早就找不到开发商了。

这款软件的加密狗驱动程序是定制程序。这个驱动程序里有“Copyright 1989, Rainbow Technologies, Inc., Irvine, CA”和“Sentinel Integrated Driver Ver. 3.0”的字样。

在SCO OpenServer上安装驱动程序之后,硬件加密狗将会加载到文件系统的/dev目录里:

/dev/rbsl8
/dev/rbsl9
/dev/rbsl10

如果不插入加密狗,程序将会报错。而且错误信息不在可执行程序里。

好在IDA可以加载SCO OpenServer的COFF程序。

在IDA里搜索“rbsl”,然后找到了下述指令:

.text:00022AB8        public SSQC
.text:00022AB8 SSQC   proc near ; CODE XREF: SSQ+7p
.text:00022AB8
.text:00022AB8 var_44 = byte ptr -44h
.text:00022AB8 var_29 = byte ptr -29h
.text:00022AB8 arg_0  = dword ptr  8
.text:00022AB8
.text:00022AB8        push    ebp
.text:00022AB9        mov     ebp, esp
.text:00022ABB        sub     esp, 44h
.text:00022ABE        push    edi
.text:00022ABF        mov     edi, offset unk_4035D0
.text:00022AC4        push    esi
.text:00022AC5        mov     esi, [ebp+arg_0]
.text:00022AC8        push    ebx
.text:00022AC9        push    esi
.text:00022ACA        call    strlen
.text:00022ACF        add     esp, 4
.text:00022AD2        cmp     eax, 2
.text:00022AD7        jnz     loc_22BA4
.text:00022ADD        inc     esi
.text:00022ADE        mov     al, [esi-1]
.text:00022AE1        movsx   eax, al
.text:00022AE4        cmp     eax, '3'
.text:00022AE9        jz      loc_22B84
.text:00022AEF        cmp     eax, '4'
.text:00022AF4        jz       loc_22B94
.text:00022AFA        cmp     eax, '5'
.text:00022AFF        jnz     short loc_22B6B
.text:00022B01        movsx   ebx, byte ptr [esi]
.text:00022B04        sub     ebx, '0'
.text:00022B07        mov     eax, 7
.text:00022B0C        add     eax, ebx
.text:00022B0E        push    eax
.text:00022B0F        lea     eax, [ebp+var_44]
.text:00022B12        push    offset aDevSlD ; "/dev/sl%d" 
.text:00022B17        push    eax
.text:00022B18        call    nl_sprintf
.text:00022B1D        push    0                ; int
.text:00022B1F        push    offset aDevRbsl8 ; char *
.text:00022B24        call    _access
.text:00022B29        add     esp, 14h
.text:00022B2C        cmp     eax, 0FFFFFFFFh
.text:00022B31        jz      short loc_22B48
.text:00022B33        lea     eax, [ebx+7]
.text:00022B36        push    eax
.text:00022B37        lea     eax, [ebp+var_44]
.text:00022B3A        push    offset aDevRbslD ; "/dev/rbsl%d" 
.text:00022B3F        push    eax
.text:00022B40        call    nl_sprintf
.text:00022B45        add     esp, 0Ch
.text:00022B48
.text:00022B48 loc_22B48: ; CODE XREF: SSQC+79j
.text:00022B48        mov     edx, [edi]
.text:00022B4A        test    edx, edx
.text:00022B4C        jle     short loc_22b57
.text:00022B4E        push    edx               ; int
.text:00022B4F        call    _close
.text:00022B54        add     esp, 4
.text:00022B57
.text:00022B57 loc_22B57: ; CODE XREF: SSQC+94j 
.text:00022B57        push    2                  ; int 
.text:00022B59        lea     eax, [ebp+var_44]
.text:00022B5C        push    eax               ; char *
.text:00022B5D        call    _open
.text:00022B62        add     esp, 8
.text:00022B65        test    eax, eax
.text:00022B67        mov     [edi], eax
.text:00022B69        jge     short loc_22B78
.text:00022B6B
.text:00022B6B loc_22B6B: ; CODE XREF: SSQC+47j
.text:00022B6B        mov     eax, 0FFFFFFFFh
.text:00022B70        pop     ebx
.text:00022B71        pop     esi
.text:00022B72        pop     edi
.text:00022B73        mov     esp,ebp
.text:00022B75        pop     ebp
.text:00022B76        retn
.text:00022B78
.text:00022B78 loc_22B78: ; CODE XREF: SSQC+B1j
.text:00022B78        pop     ebx
.text:00022B79        pop     esi
.text:00022B7A        pop     edi
.text:00022B7B        xor     eax, eax
.text:00022B7D        mov     esp, ebp
.text:00022B7F        pop     ebp
.text:00022B80        retn
.text:00022B84
.text:00022B84 loc_22B84: ; CODE XREF: SSQC+31j
.text:00022B84        mov     al, [esi]
.text:00022B86        pop     ebx
.text:00022B87        pop     esi
.text:00022B88        pop     edi
.text:00022B89        mov     ds:byte_407224, al
.text:00022B8E        mov     esp, ebp
.text:00022B90        xor     eax, eax
.text:00022B92        pop     ebp
.text:00022B93        retn
.text:00022B94
.text:00022B94 loc_22B94: ; CODE XREF: SSQC+3Cj
.text:00022B94        mov     al, [esi]
.text:00022B96        pop     ebx
.text:00022B97        pop     esi
.text:00022B98        pop     edi
.text:00022B99        mov     ds:byte_407225, al
.text:00022B9E        mov     esp, ebp
.text:00022BA0        xor     eax, eax
.text:00022BA2        pop     ebp
.text:00022BA3        retn
.text:00022BA4
.text:00022BA4 loc_22BA4: ; CODE XREF: SSQC+1Fj
.text:00022BA4        movsx   eax, ds:byte_407225
.text:00022BAB        push    esi
.text:00022BAC        push    eax
.text:00022BAD        movsx   eax, ds:byte_407224
.text:00022BB4        push    eax
.text:00022BB5        lea     eax, [ebp+var_44]
.text:00022BB8        push    offset a46CCS   ; "46%c%c%s"
.text:00022BBD        push    eax
.text:00022BBE        call    nl_sprintf
.text:00022BC3        lea     eax, [ebp+var_44]
.text:00022BC6        push    eax
.text:00022BC7        call    strlen
.text:00022BCC        add     esp, 18h
.text:00022BCF        cmp     eax, 1Bh
.text:00022BD4        jle     short loc_22BDA
.text:00022BD6        mov     [ebp+var_29], 0
.text:00022BDA
.text:00022BDA loc_22BDA: ; CODE XREF: SSQC+11Cj
.text:00022BDA        lea     eax, [ebp+var_44]
.text:00022BDD        push    eax
.text:00022BDE        call    strlen
.text:00022BE3        push    eax             ; unsigned int
.text:00022BE4        lea     eax, [ebp+var_44]
.text:00022BE7        push    eax             ; void *
.text:00022BE8        mov     eax, [edi]
.text:00022BEA        push    eax             ; int
.text:00022BEB        call    _write
.text:00022BF0        add     esp, 10h
.text:00022BF3        pop     ebx
.text:00022BF4        pop     esi
.text:00022BF5        pop     edi
.text:00022BF6        mov     esp, ebp
.text:00022BF8        pop     ebp
.text:00022BF9        retn
.text:00022BFA        db 0Eh dup(90h)
.text:00022BFA  SSQC  endp

果然,该程序要和驱动程序通信。

而且,只有下面这个形实转换函数调用了SSQC()函数:

.text:0000DBE8        public SSQ
.text:0000DBE8 SSQ    proc near  ; CODE XREF: sys_info+A9p
.text:0000DBE8                   ; sys_info+CBp...
.text:0000DBE8
.text:0000DBE8 arg_0  = dword ptr  8
.text:0000DBE8
.text:0000DBE8        push    ebp
.text:0000DBE9        mov     ebp, esp
.text:0000DBEB        mov     edx, [ebp+arg_0]
.text:0000DBEE        push    edx
.text:0000DBEF        call    SSQC
.text:0000DBF4        add     esp, 4
.text:0000DBF7        mov     esp, ebp
.text:0000DBF9        pop     ebp
.text:0000DBFA        retn
.text:0000DBFB SSQ    endp

调用SSQ()函数的指令至少有两处。

其中一处是:

.data:0040169C _51_52_53       dd offset aPressAnyKeyT_0 ; DATA XREF: init_sys+392r
.data:0040169C                                          ; sys_info+A1r
.data:0040169C                                          ; "PRESS ANY KEY TO CONTINUE: "
.data:004016A0                 dd offset a51            ; "51"
.data:004016A4                 dd offset a52            ; "52"
.data:004016A8                 dd offset a53            ; "53"

...

.data:004016B8 _3C_or_3E       dd offset a3c            ; DATA XREF: sys_info:loc_D67Br
.data:004016B8                                          ; "3C"
.data:004016BC                 dd offset a3e            ; "3E"

; these names we gave to the labels:
.data:004016C0 answers1        dd 6B05h                 ; DATA XREF: sys_info+E7r
.data:004016C4                 dd 3D87h
.data:004016C8 answers2        dd 3Ch                   ; DATA XREF: sys_info+F2r
.data:004016CC                 dd 832h
.data:004016D0 _C_and_B        db 0Ch                   ; DATA XREF: sys_info+BAr
.data:004016D0                                          ; sys_info:OKr
.data:004016D1 byte_4016D1     db 0Bh                   ; DATA XREF: sys_info+FDr
.data:004016D2                 db     0

...

.text:0000D652                 xor    eax, eax
.text:0000D654                 mov    al, ds:ctl_port
.text:0000D659                 mov    ecx, _51_52_53[eax*4]
.text:0000D660                 push   ecx
.text:0000D661                 call   SSQ
.text:0000D666                 add    esp, 4
.text:0000D669                 cmp    eax, 0FFFFFFFFh
.text:0000D66E                 jz     short loc_D6D1
.text:0000D670                 xor    ebx, ebx
.text:0000D672                 mov    al, _C_and_B
.text:0000D677                 test   al, al
.text:0000D679                 jz     short loc_D6C0
.text:0000D67B
.text:0000D67B loc_D67B: ; CODE XREF: sys_info+106j
.text:0000D67B                 mov    eax, _3C_or_3E[ebx*4]
.text:0000D682                 push   eax
.text:0000D683                 call   SSQ
.text:0000D688                 push   offset a4g     ; "4G"
.text:0000D68D                 call   SSQ
.text:0000D692                 push   offset a0123456789 ; "0123456789"
.text:0000D697                 call   SSQ
.text:0000D69C                 add    esp, 0Ch
.text:0000D69F                 mov    edx, answers1[ebx*4]
.text:0000D6A6                 cmp    eax, edx
.text:0000D6A8                 jz     short OK
.text:0000D6AA                 mov    ecx, answers2[ebx*4]
.text:0000D6B1                 cmp    eax, ecx
.text:0000D6B3                 jz     short OK
.text:0000D6B5                 mov    al, byte_4016D1[ebx]
.text:0000D6BB                 inc    ebx
.text:0000D6BC                 test   al, al
.text:0000D6BE                 jnz    short loc_D67B
.text:0000D6C0
.text:0000D6C0 loc_D6C0: ; CODE XREF: sys_info+C1j
.text:0000D6C0                 inc    ds:ctl_port
.text:0000D6C6                 xor    eax, eax
.text:0000D6C8                 mov    al, ds:ctl_port
.text:0000D6CD                 cmp    eax, edi
.text:0000D6CF                 jle    short loc_D652
.text:0000D6D1
.text:0000D6D1 loc_D6D1: ; CODE XREF: sys_info+98j
.text:0000D6D1           ; sys_info+B6j
.text:0000D6D1                 mov    edx, [ebp+var_8]
.text:0000D6D4                 inc    edx
.text:0000D6D5                 mov    [ebp+var_8], edx
.text:0000D6D8                 cmp    edx, 3
.text:0000D6DB                 jle    loc_D641
.text:0000D6E1
.text:0000D6E1 loc_D6E1: ; CODE XREF: sys_info+16j
.text:0000D6E1           ; sys_info+51j ...
.text:0000D6E1                 pop    ebx
.text:0000D6E2                 pop    edi
.text:0000D6E3                 mov    esp, ebp
.text:0000D6E5                 pop    ebp
.text:0000D6E6                 retn
.text:0000D6E8 OK:        ; CODE XREF: sys_info+F0j
.text:0000D6E8            ; sys_info+FBj
.text:0000D6E8                 mov    al, _C_and_B[ebx]
.text:0000D6EE                 pop    ebx
.text:0000D6EF                 pop    edi
.text:0000D6F0                 mov    ds:ctl_model, al
.text:0000D6F5                 mov    esp, ebp
.text:0000D6F7                 pop    ebp
.text:0000D6F8                 retn
.text:0000D6F8 sys_info        endp

“3C”和“3E”听起来很熟:它是私有的、单功能加密-哈希函数,用于Rainbow公司生产的一款没有内存的Sentinel Pro加密狗。

本书的第34章详细介绍过哈希函数。

就这个程序而言,我们可以据此判断它仅检测加密狗的“有/无”信息。这款加密狗上不具备内存芯片,也就无法存储信息;换句话说这个程序不会向加密狗写数据。后面连续出现的双字符代码是指令代码,由SSQC()函数接收和处理。其他字符串的哈希值都以16位数字的形式存储在加密狗里。因为开发厂商的加密算法是私有算法,所以编写驱动程序替身、或者仿制硬件加密狗的做法都行不通。但是,“截获所有访问加密狗的操作、继而找到程序核对的哈希值”确实行得通。不怕麻烦的话,我们还可以摸索程序逻辑、基于私有的加密哈希函数再建一个软件,从而使用自制软件替代原有软件对数据文件进行加解密。

代码51/52/53用于选择LPT打印机接口。3x/4x用于选择相应的加密狗“系列”。不同类型的Sentinel Pro加密狗可以接在同一个LPT接口上,而区分加密狗的工作则由应用程序完成。

除了字符串“0123456789”以外,传递给哈希函数的值都是双字符的字符串。然后,函数返回的哈希值与一系列有效值进行比较。如果该值有效,全局变量ctl_model将被赋值为0xC或0xB。

此外,程序还定义了字符串“PRESS ANY KEY TO CONTINUE:”,这应当是通过加密狗认证之后的提示信息。不过整个程序没有调用过这个字符串,恐怕这属于源程序的bug吧。

接下来,我们要关注全局变量ctl_mode的读取指令。

其中一处是:

.text:0000D708 prep_sys proc near ; CODE XREF: init_sys+46Ap
.text:0000D708
.text:0000D708 var_14   = dword ptr -14h
.text:0000D708 var_10   = byte ptr -10h
.text:0000D708 var_8    = dword ptr -8
.text:0000D708 var_2    = word ptr -2
.text:0000D708
.text:0000D708          push   ebp
.text:0000D709          mov    eax, ds:net_env
.text:0000D70E          mov    ebp, esp
.text:0000D710          sub    esp, 1Ch
.text:0000D713          test   eax, eax
.text:0000D715          jnz    short loc_D734
.text:0000D717          mov    al, ds:ctl_model
.text:0000D71C          test   al, al
.text:0000D71E          jnz    short loc_D77E
.text:0000D720          mov    [ebp+var_8], offset aIeCvulnvvOkgT_ ; "Ie-cvulnvV\\\bOKG]T_"
.text:0000D727          mov    edx, 7
.text:0000D72C          jmp    loc_D7E7

...

.text:0000D7E7 loc_D7E7: ; CODE XREF: prep_sys+24j
.text:0000D7E7           ; prep_sys+33j
.text:0000D7E7          push   edx
.text:0000D7E8          mov    edx, [ebp+var_8]
.text:0000D7EB          push   20h
.text:0000D7ED          push   edx
.text:0000D7EE          push   16h
.text:0000D7F0          call   err_warn
.text:0000D7F5          push   offset station_sem
.text:0000D7FA          call   ClosSem
.text:0000D7FF          call   startup_err

如果ctl_mode为0,那么一条经过加密处理的错误信息将被传递到解密程序,从而出现在屏幕上。

这个错误信息的解密方法就是简单的XOR算法:

.text:0000A43C err_warn       proc near         ; CODE XREF: prep_sys+E8p
.text:0000A43C                                  ; prep_sys2+2Fp ...
.text:0000A43C
.text:0000A43C var_55         = byte ptr -55h
.text:0000A43C var_54         = byte ptr -54h
.text:0000A43C arg_0          = dword ptr  8
.text:0000A43C arg_4          = dword ptr  0Ch
.text:0000A43C arg_8          = dword ptr  10h
.text:0000A43C arg_C          = dword ptr  14h
.text:0000A43C
.text:0000A43C                push    ebp
.text:0000A43D                mov     ebp, esp
.text:0000A43F                sub     esp, 54h
.text:0000A442                push    edi
.text:0000A443                mov     ecx, [ebp+arg_8]
.text:0000A446                xor     edi, edi
.text:0000A448                test    ecx, ecx
.text:0000A44A                push    esi
.text:0000A44B                jle     short loc_A466
.text:0000A44D                mov     esi, [ebp+arg_C] ; key
.text:0000A450                mov     edx, [ebp+arg_4] ; string
.text:0000A453
.text:0000A453 loc_A453:                               ; CODE XREF: err_warn+28j
.text:0000A453                xor     eax, eax
.text:0000A455                mov     al, [edx+edi]
.text:0000A458                xor     eax, esi
.text:0000A45A                add     esi, 3
.text:0000A45D                inc     edi
.text:0000A45E                cmp     edi, ecx
.text:0000A460                mov     [ebp+edi+var_55], al
.text:0000A464                jl      short loc_A453
.text:0000A466
.text:0000A466 loc_A466:                       ; CODE XREF: err_warn+Fj
.text:0000A466                mov     [ebp+edi+var_54], 0
.text:0000A46B                mov     eax, [ebp+arg_0]
.text:0000A46E                cmp     eax, 18h
.text:0000A473                jnz     short loc_A49C
.text:0000A475                lea     eax, [ebp+var_54]
.text:0000A478                push    eax
.text:0000A479                call    status_line
.text:0000A47E                add     esp, 4
.text:0000A481
.text:0000A481 loc_A481:                      ; CODE XREF: err_warn+72j
.text:0000A481                push    50h
.text:0000A483                push    0
.text:0000A485                lea     eax, [ebp+var_54]
.text:0000A488                push    eax
.text:0000A489                call    memset
.text:0000A48E                call    pcv_refresh
.text:0000A493                add     esp, 0Ch
.text:0000A496                pop     esi
.text:0000A497                pop     edi
.text:0000A498                mov     esp, ebp
.text:0000A49A                pop     ebp
.text:0000A49B                retn
.text:0000A49C
.text:0000A49C loc_A49C:                      ; CODE XREF: err_warn+37j
.text:0000A49C                push    0
.text:0000A49E                lea     eax, [ebp+var_54]
.text:0000A4A1                mov     edx, [ebp+arg_0]
.text:0000A4A4                push    edx
.text:0000A4A5                push    eax
.text:0000A4A6                call    pcv_lputs
.text:0000A4AB                add     esp, 0Ch
.text:0000A4AE                jmp     short loc_A481
.text:0000A4AE err_warn       endp

因为程序对提示信息进行了加密处理(这是常见手段),所以我无法在可执行程序里直接找到错误的提示信息。

此外,程序里还有一个调用哈希函数SSQ()的地方。在调用过程中,该处指令向哈希函数传递了字符串“offln”。后续指令将返回值与0xFE81和0x12A9进行比对。如果两个值不匹配,程序将启用timer()函数(貌似是等待用户重新插入加密狗),然后在屏幕上显示另一个错误提示信息:

.text:0000DA55 loc_DA55:                                ; CODE XREF: sync_sys+24Cj
.text:0000DA55                  push    offset aOffln    ; "offln"
.text:0000DA5A                  call    SSQ
.text:0000DA5F                  add     esp, 4
.text:0000DA62                  mov     dl, [ebx]
.text:0000DA64                  mov     esi, eax
.text:0000DA66                  cmp     dl, 0Bh
.text:0000DA69                  jnz     short loc_DA83
.text:0000DA6B                  cmp     esi, 0FE81h
.text:0000DA71                  jz      OK
.text:0000DA77                  cmp     esi, 0FFFFF8EFh
.text:0000DA7D                  jz      OK
.text:0000DA83
.text:0000DA83 loc_DA83:                             ; CODE XREF: sync_sys+201j
.text:0000DA83                  mov     cl, [ebx]
.text:0000DA85                  cmp     cl, 0Ch
.text:0000DA88                  jnz     short loc_DA9F
.text:0000DA8A                  cmp     esi, 12A9h
.text:0000DA90                  jz      OK
.text:0000DA96                  cmp     esi, 0FFFFFFF5h
.text:0000DA99                  jz      OK
.text:0000DA9F
.text:0000DA9F loc_DA9F:                             ; CODE XREF: sync_sys+220j
.text:0000DA9F                  mov     eax, [ebp+var_18]
.text:0000DAA2                  test    eax, eax
.text:0000DAA4                  jz      short loc_DAB0
.text:0000DAA6                  push    24h
.text:0000DAA8                  call    timer
.text:0000DAAD                  add     esp, 4
.text:0000DAB0
.text:0000DAB0 loc_DAB0:                             ; CODE XREF: sync_sys+23Cj
.text:0000DAB0                  inc     edi
.text:0000DAB1                  cmp     edi, 3
.text:0000DAB4                  jle     short loc_DA55
.text:0000DAB6                  mov     eax, ds:net_env
.text:0000DABB                  test    eax, eax
.text:0000DABD                  jz      short error

...

.text:0000DAF7 error:                               ; CODE XREF: sync_sys+255j
.text:0000DAF7                                      ; sync_sys+274j ...
.text:0000DAF7                  mov     [ebp+var_8], offset encrypted_error_message2
.text:0000DAFE                  mov     [ebp+var_C], 17h ; decrypting key 
.text:0000DB05                  jmp     decrypt_end_print_message

...

; this name I gave to label:
.text:0000D9B6 decrypt_end_print_message:          ; CODE XREF: sync_sys+29Dj
.text:0000D9B6                                     ; sync_sys+2ABj
.text:0000D9B6                  mov     eax, [ebp+var_18]
.text:0000D9B9                  test    eax, eax
.text:0000D9BB                  jnz     short loc_D9FB
.text:0000D9BD                  mov     edx, [ebp+var_C] ; key
.text:0000D9C0                  mov     ecx, [ebp+var_8] ; string
.text:0000D9C3                  push    edx
.text:0000D9C4                  push    20h
.text:0000D9C6                  push    ecx
.text:0000D9C7                  push    18h
.text:0000D9C9                  call    err_warn
.text:0000D9CE                  push    0Fh
.text:0000D9D0                  push    190h
.text:0000D9D5                  call    sound
.text:0000D9DA                  mov     [ebp+var_18], 1
.text:0000D9E1                  add     esp, 18h
.text:0000D9E4                  call    pcv_kbhit
.text:0000D9E9                  test    eax, eax
.text:0000D9EB                  jz      short loc_D9FB

...

; this name I gave to label:
.data:00401736 encrypted_error_message2 db 74h, 72h, 78h, 43h, 48h, 6, 5Ah, 49h, 4Ch, 2 dup(47h)
.data:00401736                  db 51h, 4Fh, 47h, 61h, 20h, 22h, 3Ch, 24h, 33h, 36h,76h 
.data:00401736                  db 3Ah, 33h, 31h, 0Ch, 0, 0Bh, 1Fh, 7, 1Eh, 1Ah

可见,破解加密狗的工作十分简单:我们只需要找到相关的CMP指令,把它后面的转移指令替换为无条件转移指令即可。当然,编写自制的SCO OpenServer驱动程序也不失为一种方法。

解密错误信息

我们还能够破解源程序中的错误信息。程序里err_warn()函数所采用的解密算法非常简单。

指令清单78.1 Decryption function

.text:0000A44D                 mov     esi, [ebp+arg_C] ; key
.text:0000A450                 mov     edx, [ebp+arg_4] ; string
.text:0000A453 loc_A453:
.text:0000A453                 xor     eax, eax
.text:0000A455                 mov     al, [edx+edi] ; load encrypted byte
.text:0000A458                 xor     eax, esi      ; decrypt it
.text:0000A45A                 add     esi, 3        ; change key for the next byte
.text:0000A45D                 inc     edi
.text:0000A45E                 cmp     edi, ecx
.text:0000A460                 mov     [ebp+edi+var_55], al
.text:0000A464                 jl      short loc_A453

由此可见,程序不仅向解密函数传递了加密后的字符串,而且向它传递了加密密钥:

.text:0000DAF7 error:                            ; CODE XREF: sync_sys+255j
.text:0000DAF7                                   ; sync_sys+274j ...
.text:0000DAF7                 mov     [ebp+var_8], offset encrypted_error_message2
.text:0000DAFE                 mov     [ebp+var_C], 17h ; decrypting key
.text:0000DB05                 jmp     decrypt_end_print_message

...

; this name we gave to label manually:
.text:0000D9B6 decrypt_end_print_message:        ; CODE XREF: sync_sys+29Dj
.text:0000D9B6                                   ; sync_sys+2ABj
.text:0000D9B6                 mov     eax, [ebp+var_18]
.text:0000D9B9                 test    eax, eax
.text:0000D9BB                 jnz     short loc_D9FB
.text:0000D9BD                 mov     edx, [ebp+var_C] ; key
.text:0000D9C0                 mov     ecx, [ebp+var_8] ; string
.text:0000D9C3                 push    edx
.text:0000D9C4                 push    20h
.text:0000D9C6                 push    ecx
.text:0000D9C7                 push    18h
.text:0000D9C9                 call    err_warn

这就是简单的XOR算法:每个字节都与密钥进行XOR运算,而且每解密一个字节密钥就增加3。

为了验证这个猜想,我专门编写了一个Python脚本程序:

指令清单78.2 Python 3.x

#!/usr/bin/python
import sys

msg=[0x74, 0x72, 0x78, 0x43, 0x48, 0x6, 0x5A, 0x49, 0x4C, 0x47, 0x47,
0x51, 0x4F, 0x47, 0x61, 0x20, 0x22, 0x3C, 0x24, 0x33, 0x36, 0x76,
0x3A, 0x33, 0x31, 0x0C, 0x0, 0x0B, 0x1F, 0x7, 0x1E, 0x1A]

key=0x17
tmp=key
for i in msg:
        sys.stdout.write ("%c" % (i^tmp))
        tmp=tmp+3
sys.stdout.flush()

它解密出来的字符串正是“check security device connection”。

程序还用到了其他的加密字符串和相应密钥。然而我们不需要原始密钥就可以进行密文解密。首先,密钥实际只有1个字节。解密核心的XOR指令以字节为操作单位。其次,虽然密钥存储于ESI寄存器,但是它只用了ESI寄存器地址最低的那个字节。即使密钥大于255,但是参与演算的密钥还是不会大于1个字节的值。

最终,我们可以使用0~255之间的密钥暴力破解加密字符串。与此同时,我们要排除那些含有控制字符的解密结果。

指令清单78.3 Python 3.x

#!/usr/bin/python
import sys, curses.ascii

msgs=[
[0x74, 0x72, 0x78, 0x43, 0x48, 0x6, 0x5A, 0x49, 0x4C, 0x47, 0x47,
0x51, 0x4F, 0x47, 0x61, 0x20, 0x22, 0x3C, 0x24, 0x33, 0x36, 0x76,
0x3A, 0x33, 0x31, 0x0C, 0x0, 0x0B, 0x1F, 0x7, 0x1E, 0x1A],

[0x49, 0x65, 0x2D, 0x63, 0x76, 0x75, 0x6C, 0x6E, 0x76, 0x56, 0x5C,
8, 0x4F, 0x4B, 0x47, 0x5D, 0x54, 0x5F, 0x1D, 0x26, 0x2C, 0x33,
0x27, 0x28, 0x6F, 0x72, 0x75, 0x78, 0x7B, 0x7E, 0x41, 0x44],

[0x45, 0x61, 0x31, 0x67, 0x72, 0x79, 0x68, 0x52, 0x4A, 0x52, 0x50,
0x0C, 0x4B, 0x57, 0x43, 0x51, 0x58, 0x5B, 0x61, 0x37, 0x33, 0x2B,
0x39, 0x39, 0x3C, 0x38, 0x79, 0x3A, 0x30, 0x17, 0x0B, 0x0C],

[0x40, 0x64, 0x79, 0x75, 0x7F, 0x6F, 0x0, 0x4C, 0x40, 0x9, 0x4D, 0x5A,
0x46, 0x5D, 0x57, 0x49, 0x57, 0x3B, 0x21, 0x23, 0x6A, 0x38, 0x23,
0x36, 0x24, 0x2A, 0x7C, 0x3A, 0x1A, 0x6, 0x0D, 0x0E, 0x0A, 0x14,
0x10],

[0x72, 0x7C, 0x72, 0x79, 0x76, 0x0,
0x50, 0x43, 0x4A, 0x59, 0x5D, 0x5B, 0x41, 0x41, 0x1B, 0x5A,
0x24, 0x32, 0x2E, 0x29, 0x28, 0x70, 0x20, 0x22, 0x38, 0x28, 0x36,
0x0D, 0x0B, 0x48, 0x4B, 0x4E]]

def is_string_printable(s):
    return all(list(map(lambda x: curses.ascii.isprint(x), s)))

cnt=1
for msg in msgs:
        print ("message #%d" % cnt)
        for key in range(0,256):
                result=[]
                tmp=key
                for i in msg:
                        result.append (i^tmp)
                        tmp=tmp+3
                if is_string_printable (result):
                        print ("key=", key, "value=", "".join(list(map(chr, result))))
        cnt=cnt+1

上述程序的运行结果如下。

指令清单78.4 Results

message #1
key= 20 value= 'eb^h%|''hudw|_af{n~f%ljmSbnwlpk
key= 21 value= ajc]i"}cawtgv{^bgto}g"millcmvkqh
key= 22 value= bkd\j#rbbvsfuz!cduh|d#bhomdlujni
key= 23 value= check security device connection
key= 24 value= lifbl!pd|tqhsx#ejwjbb!'nQofbshlo
message #2
key= 7 value= No security device found
key= 8 value= An#rbbvsVuz!cduhld#ghtme?!#!'!#!
message #3
key= 7 value= Bk<waoqNUpu$'yreoa\wpmpusj,bkIjh
key= 8 value= Mj?vfnrOjqv%gxqd''_vwlstlk/clHii
key= 9 value= Lm>ugasLkvw&fgpgag^uvcrwml. 'mwhj
key= 10 value= Ol!td'tMhwx'efwfbf!tubuvnm!anvok
key= 11 value= No security device station found
key= 12 value= In#rjbvsnuz!{duhdd#r{'whho#gPtme
message #4
key= 14 value= Number of authorized users exceeded
key= 15 value= Ovlmdq!hg#'juknuhydk!vrbsp!Zy'dbefe
message #5
key= 17 value= check security device station
key= 18 value= `ijbh!td`tmhwx'efwfbf!tubuVnm!'!

虽然出现了人类语言之外的字符串,但是我们还是能够找到英语字符串。

另外,因为程序采用的解密算法是非常简单的XOR算法,所以它的加密函数也不会复杂到哪去。如果有必要的话,我们甚至可以用它的算法加密自己的字符串,然后把自制的密文放在程序里面。

78.3 例3: MS-DOS

本例研究的是一款1995年研发的MS-DOS程序,联络不上开发商了。

在从前的那个坚守DOS阵地的时代,所有的MS-DOS程序基本都在16位的8086或者80286 CPU上运行。因此大批的程序都是16位程序。这种程序的指令与本书介绍过的汇编指令大体相同,只是寄存器是16位寄存器、而且指令集略微小些罢了。

MS-DOS系统没有系统驱动程序,全部程序都可以直接访问硬件端口。故而程序中大量出现了OUT/IN指令。就当今的操作系统来说,基本上只有驱动程序才会使用这些指令,而且应用程序已经不能直接访问硬件端口了。

在当时的技术条件下,访问加密狗的MS-DOS程序必须直接访问LPT打印端口。那么我们就搜索这些端口操作指令好了:

seg030:0034               out_port proc far ; CODE XREF: sent_pro+22p
seg030:0034                                 ; sent_pro+2Ap ...
seg030:0034
seg030:0034               arg_0    =byteptr 6
seg030:0034
seg030:0034 55                     push   bp
seg030:0035 8B EC                  mov    bp, sp
seg030:0037 8B 16 7E E7            mov    dx, _out_port ; 0x378
seg030:003B 8A 46 06               mov    al, [bp+arg_0]
seg030:003E EE                     out    dx, al
seg030:003F 5D                     pop    bp
seg030:0040 CB                     retf
seg030:0040               out_port endp

上述指令的标签名称全部是笔者自行添加的。我们发现,只有下面这个函数调用了out_port()函数:

seg030:0041               sent_pro proc far ; CODE XREF: check_dongle+34p
seg030:0041
seg030:0041               var_3    = byte ptr -3
seg030:0041               var_2    = word ptr -2
seg030:0041               arg_0    = dword ptr  6
seg030:0041
seg030:0041 C8 04 00 00            enter    4, 0
seg030:0045 56                     push     si
seg030:0046 57                     push     di
seg030:0047 8B 16 82 E7            mov      dx, _in_port_1 ; 0x37A
seg030:004B EC                     in       al, dx
seg030:004C 8A D8                  mov      bl, al
seg030:004E 80 E3 FE               and      bl, 0FEh
seg030:0051 80 CB 04               or       bl, 4
seg030:0054 8A C3                  mov      al, bl
seg030:0056 88 46 FD               mov      [bp+var_3], al
seg030:0059 80 E3 1F               and      bl, 1Fh
seg030:005C 8A C3                  mov      al, bl
seg030:005E EE                     out      dx, al
seg030:005F 68 FF 00               push     0FFh
seg030:0062 0E                     push     cs
seg030:0063 E8 CE FF               call     near ptr out_port
seg030:0066 59                     pop      cx
seg030:0067 68 D3 00               push     0D3h
seg030:006A 0E                     push     cs
seg030:006B E8 C6 FF               call     near ptr out_port
seg030:006E 59                     pop      cx
seg030:006F 33 F6                  xor      si, si
seg030:0071 EB 01                  jmp      short loc_359D4
seg030:0073
seg030:0073                loc_359D3: ; CODE XREF: sent_pro+37j
seg030:0073 46                     inc      si
seg030:0074
seg030:0074                loc_359D4: ; CODE XREF: sent_pro+30j
seg030:0074 81 FE 96 00            cmp      si, 96h
seg030:0078 7C F9                  jl       short loc_359D3
seg030:007A 68 C3 00               push     0C3h
seg030:007D 0E                     push     cs
seg030:007E E8 B3 FF               call     near ptr out_port
seg030:0081 59                     pop      cx
seg030:0082 68 C7 00               push     0C7h
seg030:0085 0E                     push     cs
seg030:0086 E8 AB FF               call     near ptr out_port
seg030:0089 59                     pop      cx
seg030:008A 68 D3 00               push     0D3h
seg030:008D 0E                     push     cs
seg030:008E E8 A3 FF               call     near ptr out_port
seg030:0091 59                     pop      cx
seg030:0092 68 C3 00               push     0C3h
seg030:0095 0E                     push     cs
seg030:0096 E8 9B FF               call     near ptr out_port
seg030:0099 59                     pop      cx
seg030:009A 68 C7 00               push     0C7h
seg030:009D 0E                     push     cs
seg030:009E E8 93 FF               call     near ptr out_port
seg030:00A1 59                     pop      cx
seg030:00A2 68 D3 00               push     0D3h
seg030:00A5 0E                     push     cs
seg030:00A6 E8 8B FF               call     near ptr out_port
seg030:00A9 59                     pop      cx
seg030:00AA BF FF FF               mov      di, 0FFFFh
seg030:00AD EB 40                  jmp      short loc_35A4F
seg030:00AF
seg030:00AF                loc_35A0F: ; CODE XREF: sent_pro+BDj
seg030:00AF BE 04 00               mov      si, 4
seg030:00B2
seg030:00B2                loc_35A12: ; CODE XREF: sent_pro+ACj
seg030:00B2 D1 E7                  shl      di, 1
seg030:00B4 8B 16 80 E7            mov      dx, _in_port_2 ; 0x379
seg030:00B8 EC                     in       al, dx
seg030:00B9 A8 80                  test     al, 80h
seg030:00BB 75 03                  jnz      short loc_35A20
seg030:00BD 83 CF 01               or       di, 1
seg030:00C0
seg030:00C0                loc_35A20: ; CODE XREF: sent_pro+7Aj
seg030:00C0 F7 46 FE 08+           test     [bp+var_2], 8
seg030:00C5 74 05                  jz       short loc_35A2C
seg030:00C7 68 D7 00               push     0D7h ; '+'
seg030:00CA EB 0B                  jmp      short loc_35A37
seg030:00CC
seg030:00CC                loc_35A2C: ; CODE XREF: sent_pro+84j
seg030:00CC 68 C3 00               push     0C3h
seg030:00CF 0E                     push     cs
seg030:00D0 E8 61 FF               call     near ptr out_port
seg030:00D3 59                     pop      cx
seg030:00D4 68 C7 00               push     0C7h
seg030:00D7
seg030:00D7                loc_35A37: ; CODE XREF: sent_pro+89j
seg030:00D7 0E                     push     cs
seg030:00D8 E8 59 FF               call     near ptr out_port
seg030:00DB 59                     pop      cx
seg030:00DC 68 D3 00               push     0D3h
seg030:00DF 0E                     push     cs
seg030:00E0 E8 51 FF               call     near ptr out_port
seg030:00E3 59                     pop      cx
seg030:00E4 8B 46 FE               mov      ax, [bp+var_2]
seg030:00E7 D1 E0                  shl      ax, 1
seg030:00E9 89 46 FE               mov      [bp+var_2], ax
seg030:00EC 4E                     dec      si
seg030:00ED 75 C3                  jnz      short loc_35A12
seg030:00EF       
seg030:00EF                loc_35A4F: ; CODE XREF: sent_pro+6Cj
seg030:00EF C4 5E 06               les      bx, [bp+arg_0]
seg030:00F2 FF 46 06               inc      word ptr [bp+arg_0]
seg030:00F5 26 8A 07               mov      al, es:[bx]
seg030:00F8 98                     cbw
seg030:00F9 89 46 FE               mov      [bp+var_2], ax
seg030:00FC 0B C0                  or       ax, ax
seg030:00FE 75 AF                  jnz      short loc_35A0F
seg030:0100 68 FF 00               push     0FFh
seg030:0103 0E                     push     cs
seg030:0104 E8 2D FF               call     near ptr out_port
seg030:0107 59                     pop      cx
seg030:0108 8B 16 82 E7            mov      dx, _in_port_1 ; 0x37A
seg030:010C EC                     in       al, dx
seg030:010D 8A C8                  mov      cl, al
seg030:010F 80 E1 5F               and      cl, 5Fh
seg030:0112 8A C1                  mov      al, cl
seg030:0114 EE                     out      dx, al
seg030:0115 EC                     in       al, dx
seg030:0116 8A C8                  mov      cl, al
seg030:0118 F6 C1 20               test     cl, 20h
seg030:011B 74 08                  jz       short loc_35A85
seg030:011D 8A 5E FD               mov      bl, [bp+var_3]
seg030:0120 80 E3 DF               and      bl, 0DFh
seg030:0123 EB 03                  jmp      short loc_35A88
seg030:0125
seg030:0125                loc_35A85: ; CODE XREF: sent_pro+DAj
seg030:0125 8A 5E FD               mov      bl, [bp+var_3]
seg030:0128
seg030:0128                loc_35A88: ; CODE XREF: sent_pro+E2j
seg030:0128 F6 C1 80               test     cl, 80h
seg030:012B 74 03                  jz       short loc_35A90
seg030:012D 80 E3 7F               and      bl, 7Fh
seg030:0130
seg030:0130                loc_35A90: ; CODE XREF: sent_pro+EAj
seg030:0130 8B 16 82 E7            mov      dx, _in_port_1 ; 0x37A
seg030:0134 8A C3                  mov      al, bl
seg030:0136 EE                     out      dx, al
seg030:0137 8B C7                  mov      ax, di
seg030:0139 5F                     pop      di
seg030:013A 5E                     pop      si
seg030:013B C9                     leave
seg030:013C CB                     retf
seg030:013C               sent_pro endp

可见,这个加密狗还是Sentinel Pro出品的“哈希验证”型加密狗。通过观察程序中传递的文本字符串,我们能够查到这种加密狗信息。而且它的16位返回值最终要和固定值进行比较。

输出端口地址通常是0x378,即USB问世之前、老式打印机才用的打印终端LPT接口。在设计这种接口时,恐怕没有人会想到要从打印机接收数据,因此这种接口是单向通信接口[3]。应用程序能够通过0x379端口访问打印机的状态寄存器,获取“缺纸”“确定/ack”“繁忙”之类的信号信息。也就是说,打印机的状态寄存器是主机了解打印机工作状态的唯一途径。因此,加密狗肯定通过这个寄存器获取反馈信息,逐次轮训有关比特位罢了。

源程序的_in_port_2和_in_port_1标签,分别访问了状态字寄存器(0x379)和控制寄存器(0x37A)。

看来,通过seg030:00B9的指令,程序通过“繁忙”标志为获取返回信息:每个比特位都存储于DI寄存器,由函数尾部的指令返回。

那么,发送给输出端口的这些字节都有什么涵义?笔者没有进行深究,只知道是发送给加密狗的指令。而且就这种任务来说,也不必把各个控制指令搞清楚。

检测加密狗的指令如下:

00000000 struct_0        struc ; (sizeof=0x1B)
00000000 field_0         db 25 dup(?)               ; string(C)
00000019 _A              dw?
0000001B struct_0        ends

dseg:3CBC 61 63 72 75+_Q   struct_0 <'hello', 01122h>
dseg:3CBC 6E 00 00 00+         ; DATA XREF: check_dongle+2Eo

... skipped ...

dseg:3E00 63 6F 66 66+     struct_0 <'coffee', 7EB7h>
dseg:3E1B 64 6F 67 00+     struct_0 <'dog', 0FFADh>
dseg:3E36 63 61 74 00+     struct_0 <'cat', 0FF5Fh>
dseg:3E51 70 61 70 65+     struct_0 <'paper', 0FFDFh>
dseg:3E6C 63 6F 6B 65+     struct_0 <'coke', 0F568h>
dseg:3E87 63 6C 6F 63+     struct_0 <'clock', 55EAh>
dseg:3EA2 64 69 72 00+     struct_0 <'dir', 0FFAEh>
dseg:3EBD 63 6F 70 79+     struct_0 <'copy', 0F557h>


seg030:0145               check_dongle proc far ; CODE XREF: sub_3771D+3EP
seg030:0145
seg030:0145               var_6 = dword ptr -6
seg030:0145               var_2 = word ptr -2
seg030:0145
seg030:0145 C8 06 00 00         enter    6, 0
seg030:0149 56                  push     si
seg030:014A 66 6A 00            push     large 0          ; newtime
seg030:014D 6A 00               push     0                ; cmd
seg030:014F 9A C1 18 00+        call     _biostime
seg030:0154 52                  push     dx
seg030:0155 50                  push     ax
seg030:0156 66 58               pop      eax
seg030:0158 83 C4 06            add      sp, 6
seg030:015B 66 89 46 FA         mov      [bp+var_6], eax
seg030:015F 66 3B 06 D8+        cmp      eax, _expiration
seg030:0164 7E 44               jle      short loc_35B0A
seg030:0166 6A 14               push     14h
seg030:0168 90                  nop
seg030:0169 0E                  push     cs
seg030:016A E8 52 00            call     near ptr get_rand
seg030:016D 59                  pop      cx
seg030:016E 8B F0               mov      si, ax
seg030:0170 6B C0 1B            imul     ax, 1Bh
seg030:0173 05 BC 3C            add      ax, offset _Q
seg030:0176 1E                  push     ds
seg030:0177 50                  push     ax
seg030:0178 0E                  push     cs
seg030:0179 E8 C5 FE            call     near ptr sent_pro
seg030:017C 83 C4 04            add      sp, 4
seg030:017F 89 46 FE            mov      [bp+var_2], ax
seg030:0182 8B C6               mov      ax, si
seg030:0184 6B C0 12            imul     ax, 18
seg030:0187 66 0F BF C0         movsx    eax, ax
seg030:018B 66 8B 56 FA         mov      edx, [bp+var_6]
seg030:018F 66 03 D0            add      edx, eax
seg030:0192 66 89 16 D8+        mov      _expiration, edx
seg030:0197 8B DE               mov      bx, si
seg030:0199 6B DB 1B            imul     bx, 27
seg030:019C 8B 87 D5 3C         mov      ax, _Q._A[bx]
seg030:01A0 3B 46 FE            cmp      ax, [bp+var_2]
seg030:01A3 74 05               jz       short loc_35B0A
seg030:01A5 B8 01 00            mov      ax, 1
seg030:01A8 EB 02               jmp      short loc_35B0C
seg030:01AA
seg030:01AA                 loc_35B0A: ; CODE XREF: check_dongle+1Fj
seg030:01AA                            ; check_dongle+5Ej
seg030:01AA 33 C0               xor      ax, ax
seg030:01AC
seg030:01AC                 loc_35B0C: ; CODE XREF: check_dongle+63j
seg030:01AC 5E                  pop      si
seg030:01AD C9                  leave
seg030:01AE CB                  retf
seg030:01AE                 check_dongle endp

一般来说,应用程序在执行重要功能之前都会检验加密狗。受并行打印口和加密狗硬件特性的共同制约,验证LPT加密狗的核实操作肯定非常非常慢。所以多数软件都会存储验证结果,在一段时间内免去验证的烦恼。它们多数会通过boiostime()函数获取当前时间。

另外,程序还使用了标准C函数get_rand():

seg030:01BF               get_rand proc far ; CODE XREF: check_dongle+25p
seg030:01BF
seg030:01BF               arg_0    =word ptr 6
seg030:01BF
seg030:01BF 55                     push    bp
seg030:01C0 8B EC                  mov     bp, sp
seg030:01C2 9A 3D 21 00+           call    _rand
seg030:01C7 66 0F BF C0            movsx   eax, ax
seg030:01CB 66 0F BF 56+           movsx   edx, [bp+arg_0]
seg030:01D0 66 0F AF C2            imul    eax, edx
seg030:01D4 66 BB 00 80+           mov     ebx, 8000h
seg030:01DA 66 99                  cdq
seg030:01DC 66 F7 FB               idiv    ebx
seg030:01DF 5D                     pop     bp
seg030:01E0 CB                     retf
seg030:01E0               get_rand endp

可见,程序会把随机文本字符串发送到加密狗上,然后把加密狗的返回值与正确的哈希值进行比对。

加密狗检测主函数的调用方法如下:

seg033:087B 9A 45 01 96+     call    check_dongle
seg033:0880 0B C0            or      ax, ax
seg033:0882 74 62            jz      short OK
seg033:0884 83 3E 60 42+     cmp     word_620E0, 0
seg033:0889 75 5B            jnz     short OK
seg033:088B FF 06 60 42      inc     word_620E0
seg033:088F 1E               push    ds
seg033:0890 68 22 44         push    offset aTrupcRequiresA ; "This Software Requires a Software Lock\n"
seg033:0893 1E               push    ds
seg033:0894 68 60 E9         push    offset byte_6C7E0 ; dest
seg033:0897 9A 79 65 00+     call    _strcpy
seg033:089C 83 C4 08         add     sp, 8
seg033:089F 1E               push    ds
seg033:08A0 68 42 44         push    offset aPleaseContactA ; "Please Contact ..."
seg033:08A3 1E               push    ds
seg033:08A4 68 60 E9         push    offset byte_6C7E0 ; dest
seg033:08A7 9A CD 64 00+     call    _strcat

由此可知,破解加密狗的方法十分简单;我们只要把check_dongle()函数的返回值强制设置为0即可。例如,不妨在代码的开头处加上下列指令:

mov ax,0 
retf

细心的读者可能会想起C语言的strcpy()函数仅仅需要2个参数而已,但是这个指令却传递了4个值。

seg033:088F 1E                       push    ds
seg033:0890 68 22 44                 push    offset aTrupcRequiresA ; "This Softwar ↙
    ↘ Requires a Software Lock\n"
seg033:0893 1E                       push    ds
seg033:0894 68 60 E9                 push    offset byte_6C7E0 ; dest
seg033:0897 9A 79 65 00+             call    _strcpy
seg033:089C 83 C4 08                 add     sp, 8

这是MS-DOS特有的寻址方式。如需了解详情,请参见第94章。

如您所见,在使用strcpy()和其他函数时,操作系统都是把指针分解为一对16位值、然后再进行传递的。

在程序启动时,DS寄存器的值被设置为当前程序数据段的地址。程序里的字符串信息都存储在数据段里。

在send_pro()函数里,字符串的每个字节都被加载到seg030:00EF。与此同时,LES指令从外部传递的参数里加载ES:BX对。地址seg030:00F5处的MOV指令,在内存中ES:BX对所描述的地址上读取字节。

而seg030:00F2的指令只对16位值进行递增处理,却没有处理的值。这意味着传递给函数的字符串位于两个数据段的地址之外。


[1] CLRLWI是Clear left word immediate的缩写,用于清除指定的高/前n位,再把结果复制到目标操作符。

[2] BLR是Branch to Link Register的缩写。

[3] 本文指的是LPT并行接口。实际上,IEEE 1284标准允许打印机回传数据。