标题: dos程序分析和修改的一点心得
性别:未知-离线 likelove

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 52
编号 56074
注册 2005-12-27


发表于 2025-2-8 15:20 资料 短消息 只看该作者
dos程序分析和修改的一点心得

关键词:dos游戏,三国志英杰传,dos程序分析,dos程序修改,ida


[01.静态分析与动态调试]

静态分析只用ida就好,版本不限。在winxp下,低版本的优势在于可选繁体中文字库。不过也无所谓。在新的操作系统下,我通常用total commander文件管理器F3功能来浏览文件内容,切换文本模式、16进制模式很方便,并借助互联网翻译来实现对繁体中文和日文与简中的对应。
动态调试推荐vwware环境,dos占用空间也不大,动态调试器可用softice,通过静态分析提供的网址来设置动态断点,实现调试的目的。这时,最好有两台电脑,一台开着静态分析,一台动态调试,不然来回切换屏幕比较麻烦。softice的缺点在于不支持浮点数指令,好在英杰传几乎用不到浮点指令。
动态调试用于辅助,验证你的猜测,主要还是看静态分析。

有的dos游戏是加壳的,即原始程序不可分析,需加载后在内存完成解压,只有解压后的程序才有用。这时可借助虚拟机,使用tr软件得到解压脱壳后的文件,用以分析。


顶部
性别:未知-离线 likelove

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 52
编号 56074
注册 2005-12-27


发表于 2025-2-8 15:21 资料 短消息 只看该作者
[02.dos程序的扩容]

与windows程序不同,要改dos程序限制较多,不象windows程序那样改起来自如一些。第一,原始程序文件都很紧凑,几乎没有空位置供你去发挥;第二,dos有重定位数据,这个很讨厌,只要你用到跨段的调用或者跳转,或者你改程序原有代码,这个是绕不开的,必须同步调整文件头中的重定位数据。
当然也不是没有办法。
第一种办法是静态修改,即直接改exe文件。为实现更多的功能需要对原程序作扩容,并且需要照顾到重定位,即如果动了原有代码,需要调整文件头中的重定位数据。更重要的是,你自己的代码必然也需要大量的重定位,需要在文件头中增加出相应的重定位数据。因为文件修改是一个优化的过程,这个过程会让人非常头大。
第二种办法是动态内存修改,不直接改文件。通常,使用dosbox加载,它会将exe文件加载到内存中,可在加载后直接改内存中的程序代码。按照exe文件头的组织:文件头(含重定位数据)+文件代码及数据+数据区+堆栈区,按这个框架会将代码及数据区加载到内存中。
修改内存需要对文件作扩容,有两种改法:
一是单纯在文件头中改堆栈设置参数,将堆栈区向内存高地址去推,这样你得到原堆栈区与新堆栈区的内存是原始程序未用到的,是安全的。比如将堆栈区从3dc7:0000上推为5dc7:0000,这样3dc7:0000至5dc7:0000的内存空间都是可用的,用于存放代的超过了堆栈区的空间可能会被其他程序、分配的内存使用,修改、使用不安全;
二是在一改法的基础上在exe文件尾同步增加全0内容,确保初始的新增内存内容都为0,或者放置自己修改的代码。
不管具体用哪个方式,新增的空间皆为可用空间,可在dosbox加载后对程序的内存进行操作。此方案的优点在于不用考虑重定位数据,不需要反复的去修改物理文件,大部分工作都在内存中完成,修改、恢复的自由度较大。具体可以用dosbox game trainer加脚本的方式,灵活的定制、恢复和调整。
如果原始程序文件尾有附加数据(overlay)麻烦些,需要更仔细的计算安全区。
如果原始程序文件有ovr段更麻烦,其加载顺序是未定的,比如三国志4就是这样的。如果你要改的区域处于ovr段,需要确认相应内容已经加载才能修改。

附上英杰传主程序的扩容版,只在文件头改动一处。


附件: MAIN.内存扩容.rar (2025-2-8 15:21, 98.36 K)
该附件被下载次数 15


顶部
性别:未知-离线 likelove

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 52
编号 56074
注册 2005-12-27


发表于 2025-2-8 15:23 资料 短消息 只看该作者
[03.以英杰传示例内存的扩容]
修改举例如下:
英杰传原始的堆栈区从3dc7:0000开始,我们可以修改文件头,将堆栈区上推,如改为4dc7:0000,即段址3dc7-4dc6的空间皆是我们可用的代码段,增加代码和数据可用空间10000,即十进制65535字节。

可以在dosbox trainer的ini文件头中指定新的可用段址
原始段信息如下:

[seg1]
name=sg01
base=0

[seg2]
name=sg02
base=0cf6

[seg3]
name=sg03
base=1cf2

[seg4]
name=sg04
base=2cf0

[seg5]
name=sg05
base=2f64

[seg6]
name=sg06
base=2ffc

[seg7]
name=sg07
base=307d

以上是英杰传用到的内存段。我们新增一个段,用于存放新修改的内容:

[seg8]
name=sg08
base=3dc7

后续在查找替换文本时[sg08]就对应3dc7段。

增加的空间是0-ffff,即有65535字节(10进制),可以用来做很多事情。如果不够,继续向上推堆栈
在新增空间内,也可以人为划分一些段,注意到每个段实际是10个字节(均按16进制),比如我们估算改AI算法用到200个段的空间,那可以划定一个新段sg09,用于做其他修改。

[seg9]
name=sg09
base=3fc7

如果前面的空间要不够用了,相应把本节base再调大一些就可以,如3fc7改为40c7,让出空间。

修改内存的优势在于,改文件是纯苦力活,改内存相对好点,最多的麻烦就是没改好,程序崩溃了,重新加载、修改就好。而静态修改就需要改代码、改重定位数据,保存文件,重新加载,撤销修改也没法实现,操作量多出不少。
顶部
性别:未知-离线 likelove

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 52
编号 56074
注册 2005-12-27


发表于 2025-2-8 15:24 资料 短消息 只看该作者
[04.dos程序代码的内存修改]

基本的修改办法是在要修改处放一个跨段的call或jmp,直接跳到我们设置的新空间去,完成所需修改,有时还需要运行被跳转语句覆盖的代码,再跳回或返回修改处。注意需做好寄存器、flag、函数临时变量空间等状态的保护

一个跨段修改的演示,原始内容为:

seg002:3721 1E                             push    ds
seg002:3722 68 D4 2F                       push    offset aKOanxDkmkp ; 玩家军队状况
seg002:3725 6A 00                          push    0               ; 按钮数
seg002:3727 9A 42 32 F6 0C                 call    _sub_ShowMsg    ; 玩家军队状况
seg002:372C 83 C4 06                       add     sp, 6

此处是玩家回合开始后显示“玩家军队状况”信息框,push    0表示是无按钮的,想让它加个确认按钮,点击确认后才继续,即需要把push 0改为push 1,16进制代码改从6a 00改为6a 01。我们可以直接将seg002:3725处的 6A 00改为6A 01。现在我们不这样改,跳走到新空间处去改出这个功能
那可以在地址3721那里加个jmp,跳走到[sg08]段,加一个修改处

[m33]
caption=回合信息01
s=1e68d42f6a00
r=ea0000[sg08]90
offset=20641
mode=normal

caption=回合信息01,这个是给我们自己看的,设置成什么都可以
offset=20641是在ida中看到的current location,即内存位置
s=1e68d42f6a00 是搜索在该处的原始内容,用于校验。因为jmp长跳转需要占用5字节,在本修改中占用3条汇编指令,增加本跳转会覆盖掉push    ds; push    offset aKOanxDkmkp; push    0
r=ea0000[sg08]90  对应的汇编指令为 jmp [sg08]:0000; nop; ,sg08在前述段信息中为3dc7,即跳走到3dc7:0000
mode=normal一行表示正常状态,检查s行的待搜索内容无误后才会替换。改为mode=off表示关闭本修改,即不管检查结果对不对均不修改。改为mode=force表示强制修改,即不管检查结果对不对均强制修改。

接下来需要在3dc7:0000处拼接出修改内容

[m34]
caption=回合信息02
s=0000000000000000000000
r=1e68d42f6a01ea2737[sg03]
offset=3dc70
mode=normal

offset=3dc70,需要计算得出,即16进制下 段址*10+偏移,本例是3dc7*10+0=3dc70
s=0000000000000000000000,此处应为0,如检查到不为0,需要在exe文件尾同步增加全0内容
r=1e68d42f6a01ea2737[sg03],拆解一下:
1e68d42f对应 push    ds;push    offset aKOanxDkmkp
6a01对应 push 01,是我们本次要修改的内容,相当于替换了原来的push 0,将函数调用参数从无按钮改为有一个按钮
ea2737[sg03]对应jmp [sg03]:3727,跳回到调用函数处,注意到sg03对应了ida中看到的段seg002,因为本修改器脚本的下标是从1开始,ida里面显示的段是从0开始。

修改代码后,到玩家回合后,效果显现,需要点一下确认才继续向下进行。不想修改了,在doxbox game trainer中点击恢复内存代码。

附上dosbox game trainer及三国英杰传示例脚本

更新:1005版本支持在offset中使用段名称,修复在扩容程序中会崩溃的bug

0220更新1010版:
    offset中可使用常规偏移地址,或段地址+偏移地址
    修改模式增加了basic模式,即修改后不再恢复
    增加查看内存功能,目前可显示两块内存,在程序关联后即可使用。其中段地址支持307d等直接的数字,也可以用[sg07],两者效果一样;偏移1可以是正常的地址,如5508,与前面的段地址配合,则显示类似307d:5508处开始的数据,也可以用p5508、P5508、*5508表示这是一个段指针,这时会从类似307d:5508处取段数据,再配合后面的偏移2显示该处数据。

[ 本帖最后由 likelove 于 2025-2-20 23:48 编辑 ]


附件: dosboxgametrainer1004_sany_script.rar (2025-2-8 15:24, 14.38 K)
该附件被下载次数 17


附件: dosboxgametrainer1005_sany_script.rar (2025-2-14 21:43, 14.8 K)
该附件被下载次数 13


附件: dosboxgametrainer1010_sany_script.zip (2025-2-20 23:48, 18.61 K)
该附件被下载次数 11
顶部
性别:未知-离线 likelove

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 52
编号 56074
注册 2005-12-27


发表于 2025-2-8 15:25 资料 短消息 只看该作者
[05.dos程序的写屏、中断]
dos程序并没有api可用,动画是通过对显存的读写和IO接口来直接写屏实现的。需要对照VGA显示器的调用约定来实现,操作较复杂。并且如果你使用softice来逐行跟踪运行的话,写屏的一个点大概率是写不上的,需要通过g命令来执行完对应的io语句才行。
dos还有音效、动画定时器的问题。英杰传里是通过改写中断的方式来实现的,就是reko3ibm加载那几个SBOPL2.COM GRPDRV.EXE TFDED.COM 等文件作为对应的驱动,提供了相应接口,在main中通过相应的中断调用实现,还需要进一步分析。
另外,英杰传用到了自修改技术(smc),第一次看到时简直震惊了。正常功能的调用是通过函数的实现的。比如画个长方形,函数带4个参数,表示左上的坐标和宽、高 DrawRectangle(X, Y, Width, Height),调用时传相应参数即可。但英杰传中有些参数是通过直接修改相应函数里面相应指令的内存代码实现的。
举个例子:

seg000:51C7                loc_51C7:                               ; DATA XREF: s_1D50_SMCSetArg+3Dw
seg000:51C7 81 E9 28 00                    sub     cx, 28h ;

这里看到cx寄存器会减去28,然而s_1D50会修改这个值,实际运行后不一定会减去多少,取决于被设置为多少:

seg000:1D7B 8A 46 0A                       mov     al, [bp+arg_4_W]
...
seg000:1D8D A2 C9 51                       mov     byte ptr ds:loc_51C7+2, al ; 这里,宽度被改写

所以,真正去看个别函数时,还需要看它是不是被改了 T_T
顶部
性别:未知-离线 likelove

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 52
编号 56074
注册 2005-12-27


发表于 2025-2-8 15:27 资料 短消息 只看该作者
附上目前ida分析文件,还有不少猜测的地方


附件: mm.rar (2025-2-8 15:27, 832.18 K)
该附件被下载次数 20
顶部
性别:未知-离线 clubjack

Rank: 1
组别 百姓
级别 在野武将
功绩 0
帖子 11
编号 544597
注册 2021-5-11


发表于 2025-2-8 17:35 资料 短消息 只看该作者
回复 #1 likelove 的帖子

不明觉厉, 但真的太厉害了!
顶部
性别:未知-离线 漫漫苦短

Rank: 1
组别 百姓
级别 在野武将
功绩 0
帖子 42
编号 545816
注册 2023-12-25


发表于 2025-2-12 19:19 资料 短消息 只看该作者
这几天研读了你的内容,解开了我对16位程序的一些疑惑,另外还有些需要向likelove前辈请教的地方。

QUOTE:
静态分析只用ida就好,版本不限。在winxp下,低版本的优势在于可选繁体中文字库。不过也无所谓。在新的操作系统下,我通常用total commander文件管理器F3功能来浏览文件内容,切换文本模式、16进制模式很方便,并借助互联网翻译来实现对繁体中文和日文与简中的对应。
动态调试推荐vwware环境,dos占用空间也不大,动态调试器可用softice,通过静态分析提供的网址来设置动态断点,实现调试的目的。

total commander我试了一下,F3的时候能看到16进制编码,修改要调到F4模式下,又看不到16进制编码,感觉不如直接用UE来修改。我现在使用的是CE修改器来查看dosbox中16位程序内存,然后用CE的读写断点来调试,你能提供几张softice的截图看看是怎么设置断点以及单步调试,我比较一下哪种更好。

QUOTE:
第二种办法是动态内存修改,不直接改文件。
修改内存需要对文件作扩容,有两种改法:
一是单纯在文件头中改堆栈设置参数,将堆栈区向内存高地址去推,这样你得到原堆栈区与新堆栈区的内存是原始程序未用到的,是安全的。比如将堆栈区从3dc7:0000上推为5dc7:0000,这样3dc7:0000至5dc7:0000的内存空间都是可用的,用于存放代的超过了堆栈区的空间可能会被其他程序、分配的内存使用,修改、使用不安全;

这个扩容版的改法只用将文件头的那里改掉吗?然后内存结构就变成了代码段+数据堆栈段+空置段+外部数据段,就可以在空置段上加代码和数据了对吗?本来以为是在seg006和dseg中间重新加一个段,不过我看CE内存中的DS和SS的地址都没变,以为没啥用。

QUOTE:
二是在一改法的基础上在exe文件尾同步增加全0内容,确保初始的新增内存内容都为0,或者放置自己修改的代码。

这个方法有用吗,你看我下面的代码分析,新增的内容不会被堆区覆盖吗?

seg000:BCE6                                public start
seg000:BCE6                start           proc far
seg000:BCE6 B4 30                          mov     ah, 30h
seg000:BCE8 CD 21                          int     21h             ; DOS - GET DOS VERSION
seg000:BCE8                                                        ; Return: AL = major version number (00h for DOS 1.x)
seg000:BCEA 3C 02                          cmp     al, 2
seg000:BCEC 73 05                          jnb     short loc_1BCF3
seg000:BCEE 33 C0                          xor     ax, ax          ; DOS VER 太小运行不了
seg000:BCF0 06                             push    es
seg000:BCF1 50                             push    ax
seg000:BCF2 CB                             retf
seg000:BCF3                loc_1BCF3:                              ; CODE XREF: start+6↑j
seg000:BCF3 BF 7D 40                       mov     di, seg dseg    ;用不用修改这个数值?
...
seg000:BD06 8E D7                          mov     ss, di
...
seg000:BD70 16                             push    ss
seg000:BD71 07                             pop     es
seg000:BD72                                assume es:dseg
seg000:BD72 FC                             cld
seg000:BD73 BF 26 51                       mov     di, 5126h
seg000:BD76 B9 A0 D4                       mov     cx, 0D4A0h
seg000:BD79 2B CF                          sub     cx, di
seg000:BD7B 33 C0                          xor     ax, ax
seg000:BD7D F3 AA                          rep stosb               ; 堆区(SS:5126 - SS:D49F)初始化为0
...
seg000:BD9A 16                             push    ss
seg000:BD9B 1F                             pop     ds              ; 堆和栈处于为同一段
seg000:BD9C FF 36 54 47                    push    word_44F24
seg000:BDA0 FF 36 52 47                    push    argv            ; argv
seg000:BDA4 FF 36 50 47                    push    argc            ; argc
seg000:BDA8 9A CC 9F F6 1C                 call    _main



QUOTE:
dos程序并没有api可用,动画是通过对显存的读写和IO接口来直接写屏实现的。需要对照VGA显示器的调用约定来实现,操作较复杂。

关于你的dosbox trainer工具我有个Issues,offset=的后面也增加写成[sg01]XXXX这种形式的功能,不然换算太麻烦了。

我看你的ida分析文件有大量VGA和EGA开头命名的函数,想问一下你是有这些函数的源代码与反汇编代码进行对照的吗,或者说你是怎么命名的?

QUOTE:
另外,英杰传用到了自修改技术(smc),第一次看到时简直震惊了。英杰传中有些参数是通过直接修改相应函数里面相应指令的内存代码实现的。

这种应该算过时的技术,说简单点就是代码是无保护的,代码可以自我修改,这部分代码好像只在seg000出现,怀疑是由8位程序的一些原有代码改写过来的,因为其中有大量重复代码,却没有用上rep, loop这样的循环结构。
实际上我们现在的一些32位64位程序,一开始也会执行一部分16位程序代码,可能就相当于在16位环境下运行的32位64位程序。
顶部
性别:未知-离线 likelove

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 52
编号 56074
注册 2005-12-27


发表于 2025-2-12 22:29 资料 短消息 只看该作者


QUOTE:
原帖由 漫漫苦短 于 2025-2-12 19:19 发表
这几天研读了你的内容,解开了我对16位程序的一些疑惑,另外还有些需要向likelove前辈请教的地方。



total commander我试了一下,F3的时候能看到16进制编码,修改要调到F4模式下,又看不到16进制编码,感觉不如直接用UE来修改 ...

交流一下:

total commander的F3功能不能编辑,主要是查看方便,如文本模式,16进制模式,在编码中选简体、繁体、日文比较方便。编辑可用winhex或其他二进制工具。

扩容难度在于,dos程序不分段,没办法在程序头增加段信息,将SS值向上推比较安全,相当于原来用于堆栈区和可分配的内存现在给它占用上,我们就可以用了。在上推SS的基础上,如果增加实际代码,需在文件尾增加实际内容,并相应调整文件头中关于大小等设置,涉及重定位的,都要补上重定位数据,适用于程序修改基本定型了,不定型的话调整起来太麻烦。如果不改SS,只增加文件尾数据不安全,堆栈要用掉。

offset的问题再看看,能不能实现 争取后面把观察内存也加上

EGA VGA现在少部分是猜的,大部分是对照手册看的。位面数据与bmp格式等转换还不会

SMC技术在病毒中常见,第一次在正常程序中见到,确实比较惊讶。相比于函数参数调用方式不太安全,需改写完马上去调用,每次调用前都要重新设置一遍。也不知道是怎样编程实现的,汇编中可加lable,C语言不知道怎么实现出来
顶部
性别:男-离线 神雕小侠

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 忠义校尉
功绩 2
帖子 240
编号 44352
注册 2005-7-27


发表于 2025-2-13 20:24 资料 短消息 只看该作者
只能初步领会扩容的意思,我会抓紧学习的
顶部
性别:未知-离线 likelove

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 52
编号 56074
注册 2005-12-27


发表于 2025-2-20 23:51 资料 短消息 只看该作者


QUOTE:
原帖由 漫漫苦短 于 2025-2-12 19:19 发表
...offset=的后面也增加写成[sg01]XXXX这种形式的功能,不然换算太麻烦了。 ...

已更新。
顶部
性别:未知-离线 漫漫苦短

Rank: 1
组别 百姓
级别 在野武将
功绩 0
帖子 42
编号 545816
注册 2023-12-25


发表于 2025-2-22 19:59 资料 短消息 只看该作者
回复 #11 likelove 的帖子

已下载最新版本的,里面的观察内存的功能做的不错,而且还有用指针进行二次计算段地址的功能很好用,然后利用内存修改的方式还可以实现下人工断点,这样就能发挥观察内存的威力了
顶部

正在浏览此帖的会员 - 共 1 人在线




当前时区 GMT+8, 现在时间是 2025-4-2 05:44
京ICP备2023018092号 轩辕春秋 2003-2023 www.xycq.org.cn

Powered by Discuz! 5.0.0 2001-2006 Comsenz Inc.
Processed in 0.015381 second(s), 9 queries , Gzip enabled

清除 Cookies - 联系我们 - 轩辕春秋 - Archiver - WAP