本例研究的是一款运行在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和部分汇编知识,任何人都可以小规模地修改程序。
本例研究的程序是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算法,所以它的加密函数也不会复杂到哪去。如果有必要的话,我们甚至可以用它的算法加密自己的字符串,然后把自制的密文放在程序里面。
本例研究的是一款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标准允许打印机回传数据。