有一天在网上下载了一个上个世纪DOS年代的光盘ISO,里面有一个README.EXE的DOS可执行文件,在过去,这一般是光盘里面的软件说明。
好奇想知道这光盘里面有一些什么软件,于是执行README.EXE,由于是虚拟机里面执行所以也不担心有病毒。执行的结果有点意外,文件竟然设置了访问密码。
设置了访问密码的README.EXE
这反而更好奇这个文件里面的内容了。
疫情在家游戏玩多了,不妨把破解密码的过程当作是一次解迷游戏,于是决定花点时间尝试对这个程序的访问密码进行破解。
为什么这些光盘说明不放在一个文本文件里呢?因为这是要方便没有中文DOS环境的用户查看,因此有人会用工具生成一个可以显示这些中文说明的DOS的EXE文件。
在动手破解之前,要先介绍一些背景和破解思路。
先说背景知识:
小型的DOS程序一般都是汇编语言开发,生成机器码比较精简相对容易用debug工具跟踪,而对于所谓密码的处理一般不会像现在这样用DES、AES、RC4对密码进行加密,通常就是设一个字符串然后和用户输入的字符进行比较,如果字符串匹配上就继续执行,匹配不上就退出程序。
如果是用BASIC或者其他高级语言开发编译的程序生成的机器代码就相对复杂。
有一些防破解的DOS软件会对自身进行加密,加载到内存后先做解密再去执行,对于这类程序要先经过一种叫“脱壳”过程后才可以跟踪,破解也就不那么容易。
说完背景知识就说破解思路:
先在Linux用 strings 查看程序里面字符,看看有没疑似密码的字符串,经过尝试没发现找到像明文的密码字符串,看来密码不是以明文方式存放在程序里面的。
既然密码不是以明文方式存放的,那么怎么找到密码呢?
我想起我朋友和我说过他开发的软件是怎么被一个外国人破解的过程,朋友的软件需要用户输入注册码,因为在用户输入注册码之后,程序就要对注册码进行校验并根据校验的结果进行分支跳转,因此只要追踪程序弹出注册码的对话框或者是提示注册码成功/失败的信息的机器代码就能非常接近破解的目标,这时可以分析存放在内存中密码,如果破解密码很麻烦也可以用汇编语言修改内存中的机器指令,即使注册码校验不过也强制跳转到注册码校验成功的程序分支。
于是这里有了两个方案,
方案一:
跟踪程序显示 “Please input password:” 或 “Invalied password!” 的代码,找到对比密码字符串的代码这样就有可能找到存放密码的内存地址从而把密码找出来。
方案二:
如果程序对密码的处理过程比较复杂,例如用了一些加密算法,即使找到存放密码的内存地址也不太容易分析出密码,那么还可以跟踪跳转到显示 “Invalied password!” 的分支代码,修改跳转的指令使得即使密码校验不正确也跳转到密码校验成功的代码分支上去。
用到的调试工具是DOS下的 debug,里面有内存搜索的指令可以搜出程序中调用显示字符串的汇编代码。
在DOS的字符界面上显示字符串有三种方式
1、直接写显卡内存B800
2、调用BIOS 中断 int 10H
3、调用DOS 中断 int 21H
从程序滚动输出这些字符串来看,一般不会是用直接写显存的方式去实现,如果要这样做就要实现拷贝内存和操作显卡寄存器一整套功能,而这里仅是简单显示一些字符串,因此先排除直接写显卡内存。
经过搜索跟踪,排除了调用BIOS 中断 INT 10H,这个过程略过细节,大约过程就是去找,但没找到。
剩下就是跟踪调用DOS 中断 INT 21H显示字符串这条线索,下面是详细的过程。
用 DOS INT 21H 显示字符串的过程大概是这样:
把以$为结束符的字符串的地址放到寄存器DS:DX里,把寄存器AH设为09, 09是DOS INT 21H功能号,用于显示DS:DX存放的地址的内容,最后调用 INT 21H,这样DOS就会在当前光标的位置显示这个字符串。
因此,我们只需用debug把 README.EXE 加载到内存后搜索哪里有 MOV AH,09 和 INT 21H 这两条指令,这样就能比较容易找到输出字符串的地方。
当然了,如果程序里大量使用INT 21H输出字符串,那就会搜索很多条结果出来,这时就要先搜索 “Please input password:” 和 “Invalied password!” 这两个字符串在内存里面的地址,然后搜出加载这些字符串地址到DS:DX的代码,这样可以缩小跟踪分析的范围。
DOS 的 debug 的 s 命令可以用16进制或 ASC码 搜索内存,而我们不大可能记得 INT 21H 这条汇编指令对应的X86 CPU的机器指令,因此可以用 debug 的汇编功能先输入INT 21H,然后反汇编看这条指令对应的机器指令。
使用debug的汇编和反汇编命令
这里可以看到 INT 21H 对应的x86 CPU 机器指令用16进制表示是 CD21,MOV AH,09 对应的机器指令是B409。
有了这些准备,就可以开始用debug破解这个程序了,先用debug加载 README.EXE,然后用 r 指令查看当前的寄存器,其中
CS=121F
IP=0100
这表示CS寄存器的内容是 121F IP 寄存器为0100,这表明 README.EXE 的代码段被加载到段地址为121F,偏移地址为0100 这个地方,如果不清楚这里的意思的话,可以查资料去了解一下 intel x86 CPU 在16位实模式下内存寻址方式。
在这里,只需要记得README.EXE的代码就在这个区域里面,先重点对这个区域搜索。对于一些大型的DOS程序,程序和数据可能会分布在多个段里,这样就不能只搜索一个段了。
分别输入搜指令
-s 121F:0100 FFFF B4 09
-s 121F:0100 FFFF CD 10
使用debug的搜功能
符合条件的地址会被列出来,根据搜索的结果可以看到,
在 121F:01B8 和 121F:01F3 这两个地方有机器码B409(MOV AH,09) ,而121F:01BD和121F:01F8有机器码 CD10(INT 21H),这两组地址(121F:01B8和121F:01BD,121F:01F3和121F:01F8)靠得很近,这是很关键的线索,根据这个信息可以初步判断121F:01BD和121F:01F8这两个地方的代码调用了DOS 21H显示字符串。
这时可以用 debug 的 u 命令反汇编代码看一下。