有一天,我在网上下载了一张上世纪DOS时代的光盘ISO,里面有一个README.EXE的DOS可执行文件。在过去,这通常是光盘中的软件描述。
好奇想知道这光盘里面有一些什么软件,于是执行README.EXE,由于是虚拟机里面执行所以也不担心有病毒。执行的结果有点意外,文件竟然设置了访问密码。
出于好奇想知道这个光盘里有什么软件,我执行了README.EXE。因为它是在虚拟机中执行的,所以我不担心病毒。执行的结果有点出乎意料,为文件设置了访问密码。
设置了访问密码的README.EXE。
反而对这个文件的内容更好奇。
疫情期间家里玩的游戏太多了,我们不妨把破解密码的过程当成一个解谜游戏,所以我们决定花点时间尝试破解这个程序的访问密码。
为什么这些光盘说明不在文本文件中?因为方便没有中文DOS环境的用户查看,所以会有人用工具生成一个可以显示这些中文描述的DOS的EXE文件。
破解之前,先介绍一些背景和破解思路。
先说背景:
小的DOS程序一般用汇编语言开发,生成的机器码相对简单,容易用调试工具跟踪。但是对于所谓的密码处理,一般不会像现在这样用DES,AES,RC4来加密密码。通常,设置一个字符串,然后与用户输入的字符进行比较。如果字符串匹配,它将继续执行;否则,它将退出程序。
如果是用BASIC或者其他高级语言开发编译的程序,生成的机器码相对复杂。
一些防破解的DOS软件会对自己进行加密,加载到内存后在执行前再解密。对于这类程序来说,经过一个叫做“脱壳”的过程,破解起来就没那么容易了。
说完背景知识,然后说解决方案:
首先在Linux中用字符串检查程序中的字符,看看有没有类似密码的字符串。试了一下,找不到任何类似明文的密码字符串。好像密码不是明文存储在程序里的。
既然密码不是明文存储的,怎么找?
我记得我朋友跟我说过他开发的软件是怎么被一个外国人破解的。我朋友的软件要求用户输入注册码,因为用户输入注册码后,程序要对注册码进行校验,并根据校验的结果进行分支跳转。所以***程序只要弹出注册码的对话框或者提示注册码成功/失败的机器码,就可以非常接近破解目标。此时可以对存储器中存储密码进行分析,如果破解密码比较麻烦,还可以用汇编语言修改存储器中的机器指令,即使注册码验证失败,也强制跳转到注册码验证成功的程序分支。
这里有两个方案,
选项1:
跟踪程序显示“请输入密码:”或“密码无效!”Code,找到比较密码字符串的代码,这样就有可能找到存储密码的内存地址,找出密码。
选项2:
如果程序的密码处理过程比较复杂,比如使用了一些加密算法,即使找到了存储密码的内存地址也不容易分析出密码,那么可以跟踪跳转显示“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,这是DOS INT 21H函数号,用来显示存储在DS:DX中的地址内容。最后调用INT 21H,这样DOS就会在当前光标位置显示这个字符串。
所以我们只需要使用debug将README.EXE加载到内存中,搜索哪里有MOV AH,09和INT 21H指令,这样更容易找到输出字符串。
当然,如果程序中使用了大量的INT 21H输出字符串,就会搜索到大量的结果。此时,“请输入密码:”和“密码无效!”应该先搜查。这两个字符在内存中串地址,然后找出将这些串地址加载到DS:DX中的代码,可以缩小跟踪分析的范围。
DOS 的 debug 的 s 命令可以用16进制或 ASC码 搜索内存,而我们不大可能记得 INT 21H 这条汇编指令对应的X86 CPU的机器指令,因此可以用 debug 的汇编功能先输入INT 21H,然后反汇编看这条指令对应的机器指令。
DOS debug的s命令可以用十六进制或者ASC代码来搜索内存,但是我们不太可能记住INT 21H的汇编指令对应的X86 CPU的机器指令,所以可以先用debug的汇编函数输入INT 21H,然后反汇编,看看这个指令对应的机器指令。
使用debug的汇编和反汇编命令
这里可以看到,INT 21H对应的x86 CPU机器指令用十六进制表示为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位实模式下的内存寻址方式。
在这里,只要记住自述文件。EXE的代码就在这一区,先把重点放在这一区。对于一些大型的DOS程序,程序和数据可能分布在多个段中,不可能只搜索一个段。
请分别输入搜索说明。
-s 121F:0100 FFFF B4 09-s 121F:0100 FFFF CD 10
使用debug的搜索功能
将列出符合条件的地址。根据搜索结果,
121F:01B8和121F:01F3处有机器码B409(MOV AH,09),121F:01B8和121F:01BD、121F:01F处有机器码CD10(INT 21H)根据这些信息可以初步判断121F:01BD和121F:01F8处的代码调用DOS 21H显示字符串。
这时可以用 debug 的 u 命令反汇编代码看一下。
这时可以用debug的U命令反汇编代码。
使用debug的反汇编和内存查看功能。
-u 121F:01F3
从反汇编结果可以看出,这确实是一段调用DOS 21H显示字符串的代码。要显示的字符串的段偏移地址是0263。显示完这个字符串后,调用Int21的4C号(MOV AH,4C,INT 21h)函数退出程序。
我没有看到操作DS寄存器的内容。数据和代码可能都在同一段中(121F)
在上图中,使用debug d指令和字符串“Invalied password!”查看了地址121F:0263的内容保存在这个记忆里。。
此时,它离目标越来越近了。这是验证密码失败后退出程序的地方。再往上一点,应该是验证密码的地方,然后就有可能找到存放密码的地方。
这时就需要一点一点对121F:01F3(B409 MOV AH,09)前面的地址进行反汇编,由于intel x86 CPU的指令不是定长的,因此选定的反汇编起始地址不对可能会得到不正确的指令。
这时候就要把121F:01F3(B409 MOV啊,09)前面的地址一点一点拆了。由于intel x86 CPU的指令长度不固定,选择的反汇编起始地址不正确,可能导致指令不正确。
使用debug在121F:01CE反汇编代码
使用u命令一路向前反汇编。在121F:01CE中可以看到调用了DOS 21H的07函数。这段代码把键盘输入的内容读入寄存器al,然后把AL的内容和0xF0十六进制数异或运算的结果存入AL,最后把寄存器AL的内容存入寄存器SI指向的内存地址。
这样密码可能已经和F0进行了异或运算,只需要在内存中找到密码,用异或来恢复密码即可。
继续往下反汇编
继续拆解
使用debug反汇编121F:01E3处的代码
正如你在这里看到的,这是一个字符串比较的代码。具体细节请参考英特尔汇编语言。这里一般的意思是将内存0107(未指定段地址,但默认仍为121F)加载到寄存器CL中。REPZ CMPSB会根据CX(CH被XOR指令置为0)的值作为周期数来比较SI和DI所指向的内存。每比较一个字符串,SI和DI寄存器就加1,CX寄存器就减1。
MOV SI,0108MOV DI,028D
毫无疑问,在这里,此时SI和DI存储的是键盘输入的密码和程序预置密码的内存地址。至于预置密码的地址是存储在SI还是DI中,你可以向前拆解一下,看看键盘输入的密码存储在哪里,这样就可以推导出密码存储的地址。
在这个程序中,从代码中可以看到,SI被初始化为0108(没有指定段地址,默认值为121F),而0107保存的是要比较的字符串的长度,因此有理由判断密码可能保存在121F:0108。
于是执行debug 的d指令查看 121F:0107
然后执行debug的d指令检查121F:0107。
用debug检查内存
这里可以看到0107处的内存是04,后面是4个C2,这里可以肯定的判断密码长度是4,密码内容是4个C2,当然这个密码是和前面提到的0xF0进行异或运算的结果。
现在,尝试恢复密码。使用计算器或互联网查找任何带有十进制转换的网页。
转换的结果是
F0的二进制表示是1110000。
C2对应的二进制数是11000010。
对11000010和1110000做异或运算。
1111000011000010 xor001***
结果是001***,换算成十六进制就是0x32。通过查ASC代码表,对应的字符是’2’也就是说,密码是”2222″。
退出debug,再次执行README.EXE,输入密码 2222,最后密码校验通过,显示出里面的内容。
退出调试,再次执行README.EXE,输入密码2222,最后密码验证通过,显示里面的内容。
验证密码后显示的内容。
至此,这个来自上个世纪的密码已经被破解。
从文件的内容来看,光盘以软盘映像IMG格式存储文件。这可能是当时电脑店用的光盘。那时候还没有电脑城,但是每个城市都会有一两家电脑店。除了向公司出售电脑,他们还会每天向用户出售一些软件拷贝。那时候光驱还没普及,也没有u盘和移动硬盘。拷贝软件一般使用软盘,所以这些软件一般以软盘映像IMG格式存储。根据用户的目录选择要***的软件后,店员会使用HD-COPY将软件从光盘***到用户的软盘上。我还记得当时10块钱***了一张软盘。
为什么这些软件的软盘镜像文件都放在光盘上?这是因为那时候硬盘空超过1GB的电脑很少,一般都是几百MB,甚至有的电脑没有硬盘,而一张光盘就有600多MB。一个电脑专卖店一般有十几张这样的光盘,有操作系统,有汉字系统,有文字处理软件,有工具软件,游戏很少。即使有游戏,也是粗糙的、不好玩的游戏。
从列出的软件来看,这张光盘应该是比较晚的,这个密码可能印在光盘的封面上,也有可能是这张光盘本来就是在电脑商店之间流通的,为了保护光盘制作者的利益可能会加上密码。
光驱普及后,市场上也可以出售装满软件光盘,但渐渐地里面的软件就直接放在目录里了,不需要做软盘镜像格式,因为方便用户直接在光盘上运行软件安装程序。
最后,这个README.EXE实际上不是用汇编语言开发的,至少不是全部用汇编开发的。当初在Linux下查带字符串的文件时看到的“Borland & # 8221“turbo-C ”Borland的C编译器在当时非常有名,可以生成非常简洁高质量的机器码,所以用debug追查起来还是比较顺利的。
本文来自Rose情調※投稿,不代表舒华文档立场,如若转载,请注明出处:https://www.chinashuhua.cn/24/480124.html