原文:http://hengch.blog.163.com/blog/static/107800672011611114819500/
两年前,我在写USB的文章时,多次提到了DOSUSB这个东东,这两年也没有关注这方面的变化,最近,有机会重新进入DOSUSB的官方网站(www.dosusb.net),欣喜地发现,这个网站不仅依然存在,而且还有所发展,相继推出了DOSUSB 2.0和DOSUSB3.0,但是不再免费(以前,DOSUSB的二进制代码是免费的,但源代码收费),USB 3.0还不怎么常用(至少在运行DOS的机器上),所以,本文仅对DOSUSB 2.0的免费版做了一个简单的分析,介绍其限制方法,并提出了一个非常简单的破解方法。
题外话,我觉着DOSUSB的收费稍微贵了点,一个单机授权要收取65欧元,无限量版本收费550欧元,源代码收费高达1000欧元。
整个二进制文件长度为34211字节,也不是很大,所以反编译跟踪并不是十分困难。
先要学习一下DOSUSB的文档,关于免费版的限制,有如下说明:
The free version for download on the internet is a demo version of DOSUSB. This version will stop working after 20 minutes of operation. Calls by an application program will be unsuccessful then after that period. DOSUSB can also be unloaded and loaded again for five times only. After that the PC has to be booted again before loading DOSUSB.
按照文档说明,免费版有两种限制方式,其一:只能用20分钟;其二:20分钟后如果你还想用,可以卸载DOSUSB,然后再加载DOSUSB,但是这种做法只能重复5次,之后,你必须重新启动DOS。
其实,他只有一条限制,就是第一条,如果能让DOSUSB不受20分钟的限制,也就不存在第二条限制了。
DOSUSB是通过一个软中断来提供服务的,缺省是int 65h,就是说,当运行DOSUSB 20分钟以后,再调用int 65h时会失败,所以,这个20分钟的限制主要应该在中断程序中,而且应该在中断程序的开始部分;但是,要比较时间,DOSUSB在启动时肯定要记录启动时的时间,这部分程序应该在DOSUSB的初始化程序中。
综合起来说,DOSUSB在启动时,首先记录启动时间,然后在中断程序中,读取当前时间,并与启动时间进行比较,从而决定是否继续执行中断程序,大致就是这么一个原理,很简单。
下面准备进入实战,DOSUSB实际上只有一个可执行文件:dosusb.com,我们使用DEBUG这个最常用的调试程序来反汇编dosusb.com,但是由于这个文件中有大量的32位代码,而dos 6.22配的debug程序是16位的,所以好多代码反汇编不了,在实际过程中我是在DR-DOS下使用debug完成反汇编的,因为DR-DOS下的DEBUG是32位的,不过这个程序也是可以在DOS6.22下运行的,如果你没有这个程序也懒得下载DR-DOS,可以在下面地址下载这个debug程序,为了和dos 6.22下的debug区别,我把它叫做debug32。
整个反编译工作实在虚拟机下完成的,当然也可以在一般的运行DOS的机器上完成。另外,我发现DOSUSB在Virsual Box下不能运行,会出现虚拟机崩溃的现象,没有研究其原因,也许就是有个中断冲突等小问题,所以,所有的运行试验都是在一个运行DOS的机器上完成的,实际环境为DOS 6.22。
我们先来看一下启动DOSUSB时的实际情况:
图1
在最上面的信息中,非常明确地指出,这是一个没有注册的版本,是有时间限制的Demo版本。
然后,我们运行一下随DOSUSB一起发行的usbview程序,在我的机器上运行结果如下:
图2
图3
图4
图5
运行这个程序是为了说明DOSUSB是可以使用的,可以和20分钟后DOSUSB禁止使用时做比较。
我们再看一下20分钟后,DOSUSB不能使用时的情况,我们再次运行usbview程序:
图6
图7
现在我们开始用debug反汇编dosusb.com这个程序
在dos提示符下输入:debug dosusb.com(也许是debug32 dosusb.com)
下面是在我的虚拟机下反汇编出来的部分汇编代码,我们只选择了一些和这篇文章话题相关的代码,重要的地方都做了详细的注释,很多的子程序并没有列在这里,但注释中说明了其功能,为了说明方便,都加了行号:
行号 内存地址 二进制码 反汇编指令 注释
------------------------------------------------------------------------------------------------
001 3760:0100 E93D05 JMP 0640
002 ......(这里是一块数据区)
003 3760:0640 BBA386 MOV BX,86A3 ; 新分配的段落块(16字节)数量
004 3760:0643 C1EB04 SHR BX,04 ; ES=内存块段地址
005 3760:0646 43 INC BX
006 3760:0647 B44A MOV AH,4A
007 3760:0649 CD21 INT 21 ; 修改分配的内存块
008 3760:064B B8626B MOV AX,6B62
009 3760:064E 050008 ADD AX,0800 ; 重新设置堆栈,栈底在6b62h
010 3760:0651 8BE0 MOV SP,AX ; 堆栈长度为800h
011 3760:0653 E8DE72 CALL 7934 ; 判断运行环境,设置内存策略
012 ............(这里省略若干行代码)
013 3760:069B E82673 CALL 79C4 ; 显示DOSUSB的版本信息
014 3760:069E BEBD5E MOV SI,5EBD ; 指向字符串:Time restricted Demo Version
015 3760:06A1 E83223 CALL 29D6 ; 显示字符串
016 3760:06A4 60 PUSHA
017 3760:06A5 B42C MOV AH,2C ; DOS功能:取时间
018 3760:06A7 CD21 INT 21 (DOS)
019 3760:06A9 890E0360 MOV [6003],CX ; CH=小时,CL=分钟
020 3760:06AD 61 POPA
021 3760:06AE 60 PUSHA
022 3760:06AF 8B0E0360 MOV CX,[6003]
023 3760:06B3 33C0 XOR AX,AX
024 3760:06B5 8AC5 MOV AL,CH ; 小时(0--23)
025 3760:06B7 B33C MOV BL,3C ; 3ch=60,把小时数换算成分钟数
026 3760:06B9 F6E3 MUL BL ; AX中为当前时间的小时部分转换的分钟数
027 3760:06BB B500 MOV CH,00
028 3760:06BD 03C1 ADD AX,CX ; 加上当前时间的分钟数,为相对于凌晨的分钟数
029 3760:06BF A30F79 MOV [790F],AX ; word [790fh]存储启动程序时的分钟数
030 3760:06C2 1E PUSH DS
031 3760:06C3 33C0 XOR AX,AX
032 3760:06C5 8ED8 MOV DS,AX
033 3760:06C7 A0FE04 MOV AL,[04FE] ; 0:04feh在BIOS数据区,用这个位置记录程序的启动次数
034 3760:06CA 3C0A CMP AL,0A ; 当启动次数达到10次时,将不能再次启动,需要重新启动计算机
035 3760:06CC 7214 JB 06E2 ; <10次启动,可以运行
036 3760:06CE 90 NOP
037 3760:06CF 90 NOP
038 3760:06D0 1F POP DS
039 3760:06D1 BEE55E MOV SI,5EE5 ; Terminal Demo Version!
040 3760:06D4 E8FF22 CALL 29D6 ; 显示一个以\0结尾的字符串
041 3760:06D7 BEE15F MOV SI,5FE1 ; 回车,换行
042 3760:06DA E8F922 CALL 29D6 ; 显示一个以\0结尾的字符串
043 3760:06DD 61 POPA
044 3760:06DE B44C MOV AH,4C ; DOS功能,退出应用程序
045 3760:06E0 CD21 INT 21 (DOS)
046 3760:06E2 FEC0 INC AL ; 将启动次数+1后存回0:04feh的位置
047 3760:06E4 A2FE04 MOV [04FE],AL
048 3760:06E7 1F POP DS
049 3760:06E8 61 POPA
050 3760:06E9 E9947C JMP 8380 ; 继续进行初始化
1、从001--012行,与我们本文的话题没有什么联系,总之,dosusb.com的初始化过程最后要执行到从013行开始的这段程序。
2、013行调用了位于794ch处的子程序,这段子程序显示出了图1中的第一行信息:DOSUSB driver by ......
3、014、015行,显示信息:Time restricted Demo Version,也就是图1中,第二行的信息。
4、017、018行执行DOS功能,得到系统时间,CH=小时,CL=分钟,暂时存放在位于[6003h]的两个字节中
5、024-028,计算把当前时间的小时数 X 60 + 分钟数,得出的数值应该是当前时间相对于当前00:00的分钟数
6、029行把上面计算得到的分钟数存放在位于[790fh]的两个字节中,790fh这个地址很重要,后面还要用到
7、031-033行把位于0:04FEh位置的一个字节放在AL中,实际上,dosusb.com程序使用0:04FEh这个位置来存放程序的启动次数
8、034行比较启动次数,如果小于10,则转到046行(035行),把启动次数+1后存回0:04FEh这个位置
9、如果启动次数大于等于10,则执行036-045行这段程序,显示一些错误信息后退出应用程序。
现在我们总结一下上面这段程序:
1、dosusb.com启动时间相对于当天0点的分钟时存在起始地址为790Fh的两个字节(一个word)中
2、启动次数存在地址为0:04FEh的一个字节中,这个区域位于BIOS数据区,所以,如果不重新启动DOS,存放的数值不会变化
3、dosusb.com允许重新加载10次,而不是其文档中说的5次
4、可以断定int 65h中的某个位置会使用地址为790Fh中存放的dosusb.com的启动时间
下面我们的问题是,如何找到dosusb.com启动后int 65h的入口地址呢?当然,我们可以继续反编译dosusb.com,然后认真地分析器初始化部分,不过,那样做真的是太累了(当然,你可以去试一下),我们有更简单的办法。
思路是,在DOS下启动dosusb.com,然后在debug下用dos的35h号功能写上2行代码,执行一下读出int 65h的地址,我们看到的那个偏移地址,应该和我们用debug反编译dosusb.com时的偏移地址一致,这样,我们就可以定位dosusb.com中int 65h代码的具体位置了。
下面是实际操作时的截屏:
图8
在上面屏幕上,我们看到的执行结果中BX中的值就是我们想要的偏移地址,我们现在再回到用debug反编译dosusb.com里面,看看这个偏移地址下的程序是啥东西。
行号 内存地址 二进制码 反汇编指令 注释
------------------------------------------------------------------------------------------------
001 3760:06EC EB09 JMP 06F7
002 3760:06EE 44 4F 53 55 53 42 0A 03-18 2E DOSUSB....
003 3760:06F7 2E CS:
004 3760:06F8 803E3E6B00 CMP BYTE PTR [6B3E],00
005 3760:06FD 740E JZ 070D
006 3760:06FF 90 NOP
007 3760:0700 90 NOP
008 3760:0701 EA00000000 JMP 0000:0000
009 3760:0706 2E CS:
010 3760:0707 80263E6B00 AND BYTE PTR [6B3E],00
011 3760:070C CB RETF
012 3760:070D 2E CS:
013 3760:070E 800E3E6B01 OR BYTE PTR [6B3E],01
014 3760:0713 60 PUSHA
015 3760:0714 06 PUSH ES
016 3760:0715 1E PUSH DS
017 3760:0716 52 PUSH DX
018 3760:0717 1E PUSH DS
019 3760:0718 0E PUSH CS
020 3760:0719 0E PUSH CS
021 3760:071A 1F POP DS
022 3760:071B 07 POP ES
023 3760:071C BF426B MOV DI,6B42
024 3760:071F A30D60 MOV [600D],AX
025 3760:0722 1F POP DS
026 3760:0723 8BF2 MOV SI,DX
027 3760:0725 FC CLD
028 3760:0726 B92000 MOV CX,0020
029 3760:0729 F3 REPZ
030 3760:072A A4 MOVSB
031 3760:072B 0E PUSH CS
032 3760:072C 1F POP DS
033 3760:072D 60 PUSHA
034 3760:072E 1E PUSH DS
035 3760:072F 0E PUSH CS
036 3760:0730 1F POP DS
037 3760:0731 F8 CLC
038 3760:0732 B402 MOV AH,02 ; 取CMOS时间
039 3760:0734 CD1A INT 1A (BIOS Clock)
040 3760:0736 7308 JNB 0740 ; 执行BIOS成功
041 3760:0738 90 NOP
042 3760:0739 90 NOP
043 3760:073A 1F POP DS
044 3760:073B 61 POPA
045 3760:073C 7251 JB 078F
046 3760:073E 90 NOP
047 3760:073F 90 NOP
048 3760:0740 53 PUSH BX
049 3760:0741 8AC5 MOV AL,CH ; 时间的小时部分
050 3760:0743 E85523 CALL 2A9B ; 将小时的BCD码转换成十进制数
051 3760:0746 8AF8 MOV BH,AL
052 3760:0748 8AC1 MOV AL,CL ; 时间的分钟部分
053 3760:074A E84E23 CALL 2A9B ; 将分钟的BCD码转换成十进制数
054 3760:074D 8AD8 MOV BL,AL
055 3760:074F 8BCB MOV CX,BX
056 3760:0751 5B POP BX
057 3760:0752 33C0 XOR AX,AX
058 3760:0754 8AC5 MOV AL,CH ; 时间的小时数
059 3760:0756 B33C MOV BL,3C ; 60,
060 3760:0758 F6E3 MUL BL ; 将小时换算成分钟数
061 3760:075A B500 MOV CH,00
062 3760:075C 03C1 ADD AX,CX ; AX=时间的分钟数
063 3760:075E 2E CS:
064 3760:075F 2B060F79 SUB AX,[790F] ; [790Fh]存着程序启动时时间的分钟数
065 3760:0763 3D1400 CMP AX,0014 ; 20分钟
066 3760:0766 1F POP DS
067 3760:0767 61 POPA
068 3760:0768 7225 JB 078F ; 启动不到20分钟
069 3760:076A 90 NOP
070 3760:076B 90 NOP
071 3760:076C 60 PUSHA
072 3760:076D BEE55E MOV SI,5EE5 ; 指向字符串:Terminate Demo Version
073 3760:0770 E86322 CALL 29D6 ; 显示字符串
074 3760:0773 BE015F MOV SI,5F01 ; 指向字符串:please unload DOSUSB
075 3760:0776 E85D22 CALL 29D6 ; 显示字符串
076 3760:0779 BEE15F MOV SI,5FE1 ; 指向一个仅有回车换行的字符串
077 3760:077C E85722 CALL 29D6 ; 显示回车换行
078 3760:077F B8E803 MOV AX,03E8 ; 1000
079 3760:0782 E8F521 CALL 297A ; 该子程序根据AX的值,延迟若干毫秒。延迟1秒
080 3760:0785 61 POPA
081 3760:0786 C606476B42 MOV BYTE PTR [6B47],42
082 3760:078B 90 NOP
083 3760:078C E9E905 JMP 0D78
084 ............
085 3760:0D78 8B1EA061 MOV BX,[61A0]
086 3760:0D7C 83FB00 CMP BX,+00
087 3760:0D7F 7406 JZ 0D87
088 3760:0D81 90 NOP
089 3760:0D82 90 NOP
090 3760:0D83 B43E MOV AH,3E
091 3760:0D85 CD21 INT 21 (DOS)
092 3760:0D87 2E CS:
093 3760:0D88 80263E6B00 AND BYTE PTR [6B3E],00
094 3760:0D8D BE426B MOV SI,6B42
095 3760:0D90 1E PUSH DS
096 3760:0D91 58 POP AX
097 3760:0D92 5A POP DX
098 3760:0D93 1F POP DS
099 3760:0D94 1E PUSH DS
100 3760:0D95 07 POP ES
101 3760:0D96 50 PUSH AX
102 3760:0D97 1F POP DS
103 3760:0D98 8BFA MOV DI,DX
104 3760:0D9A FC CLD
105 3760:0D9B B92000 MOV CX,0020
106 3760:0D9E F3 REPZ
107 3760:0D9F A4 MOVSB
108 3760:0DA0 06 PUSH ES
109 3760:0DA1 1F POP DS
110 3760:0DA2 07 POP ES
111 3760:0DA3 61 POPA
112 3760:0DA4 CF IRET
这段程序,我们的注释要少一些,因为大多和本文话题不相干。
1、003-005行,比较[6b3eh]的值是否为0,肯定要为0,所以会转到012行,去执行;如果不为0,执行008行的代码会导致死机。
2、[6b3eh]就是一个标志,012-013行会把这个标志的bit 0置1,使其不为0,如果中断可以正常退出,该标志会被清0,如果不为0,表明上一次的中断时非正常退出的,这时,就会产生死机。
3、038-039行是我们期待的,调用了一个BIOS功能,取得了系统实时时钟的当前时间。
4、049-062行,同样把取得的时间换算成了相对于当天0点的分钟数,以便和当初dosusb.com启动时取得的时间进行对比。
5、064-065行,判断当前时间与程序启动时间只差是否小于20分钟,如果小于20分钟则继续处理中断(068行),如果大于20分钟,则执行069-083行。
6、069-083行,显示了一些信息,然后等待一秒种后085行后面的程序,这段程序只是处理了一些中断返回前的一些善后工作,并无其他。
总结一下,我们终于找到了限制20分钟的地方,其关键就是064--068行这几句的比较。
下面,我们看看有没有什么办法来解决dosusb.com这个20分钟限制的问题,其实,经过以上的分析实在是太简单了,我们注意一下第068行,指令是:JB 078F,含义是如果小于20分钟则跳转到078Fh这个地址去继续执行,如果我们可以把这句改成JMP 078F,那么不管比较结果如何,都会跳转到078Fh去执行,自然20分钟的限制也就没有了。
下面我们具体改改看:
图9
这个就是整个的修改过程,这样改完之后,你的dosusb.com就不会再有20分钟的限制了,可以说就什么限制都没有了,道理在上面用了那么大的篇幅都已经说过了,当然,你还可以改的更好,比如把那些涉及DEMO版本的提示改一改,让dosusb.com看上去更像是一个正式版的样子,这些可以自己去做,比起本文的内容,那些太简单了。
本文目的在于可以让广大的DOS爱好者可以使用DOSUSB自己开发出一些涉及USB设备的驱动程序或者应用程序,希望读者不要把它用于商业目的,请尊重开发者的劳动,如果用于商业目的,还是希望想DOSUSB的开发者付费,对于个人来说,DOSUSB的费用确实有些贵,但对一个商业项目来说,几十欧元对成本来说基本上和没有差不多。谢谢!