Debug实战教学

┌━━━━━━━━━┐
┃ 1-1 EXE 档的档头 ┃
└━━━━━━━━━┘
一般的 EXE 档有个档头 ,约占用 512 Bytes 或更多 ,它的各位元代表
的意义如下∶ (请用pctools看EXE档的档头)
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
01 0 0 ⒈ ⒈ ⒉ ⒉ ⒊ ⒊ ⒋ ⒋ ⒌ ⒌ ⒍ ⒍ ⒎ ⒎
02 ⒏ ⒏ ⒐ ⒐ A A B B C C X X X X X X
03 X X X....

0.档头一开始都是 4D 5A
⒈.总档案大小除以512後所得馀数
⒉.总档案大小除以512後所得商数加 1
⒊.档案重定位数目
⒋.此数*10 = 档头大小 ,例如 20h=512bytes
⒌.程式所须最小记忆体量(一般为0000)
⒍.程式所须最大记忆体量(一般为FFFF)
⒎.程式载入後的 SS-DS-10h
⒏.程式载入後的 SP 值
⒐.字组查验和 (一般无作用)
A.程式载入後的 IP
B.程式载入後的 CS-DS-10h
C.第一个重定位表距离档头的位置
X.重定位表 (每4Bytes一组)

----------------------------------------------------------------------------
┌━━━━━━━━━━━┐
┃ 1-2 解压缩 vs DEBUG ┃
└━━━━━━━━━━━┘
由於市面上软体众多 ,且大多附有解压程式 ,所以我们就略过不谈 ,只谈PKLITE
商业版 ,相信大家都玩过 PKLITE ,它是一种能将程式压缩後仍能够执行的软体 ,
其作用类似早期的 LZEXE ,只是它的商业版在存档时 ,少了几个重要位元 ,使得软
体无法解压缩 ,因此变成根本无法直接去改它 ,像一些国外来的软体也用它压缩 ,
其作用也只是不想被人改成随意注册版 ,可惜的是这样我们根本不能将该软体中文
化 ,所以我们必须找到方法去解压他。

且由於压缩软体太多总类 ,我们也没那么多空间去摆解压软体 ,所以用DEBUG去练
习解压才是根本的方法 ,不然硬碟迟早会不够用的。

以下范例皆使用 SYMDEB ,如您使用 DOS 所附的 DEBUG ,请提早更新 ,以免做到
假死。

---------------------------------------------------------------------------

┌━━━━━━━━━━━━━━━━━━━┐
┃ 2-1 PKLITE 商业版在记忆体的解压过程 ┃
└━━━━━━━━━━━━━━━━━━━┘

一般人都可能猜出该程式的解压动做 ,即软体载入後 ,PKLITE将该程式搬到高一点
的记忆体 ,然後解压缩时放到原PKLITE载入时的记忆体位置 ,最後执行 EXE 档头重
定位以及其相关工作的工作 ,然後执行该软体的主程式。

EXE 档内常可见到 CALL 1234:5678 ,其中 1234会跟CS有所变动 ,这就是档头重定
位表的做用 ,例如原始程式是放 CALL XXXX:5678 ,但档头会把 XXXX 改为 XXXX+CS
然後又放回原来的地方 ,变成 CALL XXXX+CS:5678。

所以我们要在 PKLITE 执行该解档头动作时中断该程式 ,并将尚未被改的原始程式
写回磁片 ,存为 DATA.$$$ , 并利用原 PKLITE 的解档头的公式 ,将档头重定位表算
出 ,并写回磁片 ,存为 HEAD.$$$ ,并将档头所有该填的资讯填回即可解压完成。

被PKLITE压过的软体载入记忆体 ---> PKLITE程式将其所有程式码搬到另一块
空白记忆体 ---> 解压缩 ,并将解出的资料放到原来 PKLITE 刚载入记忆体的
位置 ---> 解压档头移位表以及将原始程式移位 ---> 进入原始程式(RUN)

我们就是利用程式将档头移位表解压後 ,中断其程式 ,并写回原始程式码 ,以及利用
原来PKLITE的移位表运算公式 ,将移位表求出来 ,写回新档头资讯。

┌━━━━━━━━━━━━━┐
┃ 2-2 PKLITE 商业版解压法 ┃
└━━━━━━━━━━━━━┘

PKLITE商业版应该有很多不同的版本 ,因为笔者已遇到两种版本了 ,不过仍然能用
此法解压缩 ,只是您必需确认何处才是主程式进入点 ,这点就由各位去猜测了。

范例∶
笔者以手上的 XRSDOOR.EXE (V2.05) 为例 ,该软体一开始就被 PKLITE 商业版压
过 .尚未被其它软体压过 ,不过如您手上的软体被数种软体压过 ,只要最後一道是
PKLITE 商业版 ,都可用此法。

G128 ---> T --> G 1D4 暂停一下(然後请确认CS:20D是RETF)
它的程式应该是

L2 MOV ES,DX
LODSW
INC AX
JZ L1
DEC AX
JZ L2
XCHG AX,CX
L3 LODSW
XCHG AX,DI
ADD ES:[DI],BX
LOOP L3

LOOP L3
.
.
L1 .
.
.
RETF


而我们在 "L2" 处暂停 ,每个版本指令码相同 ,但 ADDRESS 不同
记住 BX 值 ,本例值为 19DA
算出 (CS-BX)*10h = 资料长度 ,本例为2618*10h=26180h ,将末4位放到CX ,其它放
到BX ,本例 BX=2 ,CX=6180 ,然後取名为 DATA.$$$
键入 "W 19DA:0" ,其中19DA是刚才记住的BX值

再重新载入并执行到程式进入点 (20D-RETF後的位置)
抄下 DS.SS.CS.IP.SP
DS=19CA
SS=3E0B
CS=19DA
IP=0
SP=80
SS-DS-10=2431h
CS-DS-10=0000h

再重新载入并执行到 "L2" 处暂停 ,找一块空白记忆体 ,填写本程式 ,本例小弟找
9000:0 的记忆体。

LL1 MOV ES,DX
LODSW
INC AX
JZ LL2
DEC AX
JZ LL1
XCHG AX,CX
LL3 LODSW
XCHG AX,DI
PUSH AX
MOV AX,ES
SUB AX,BX
MOV CS:[BP+2],AX
MOV CS:[BP],DI
ADD BP,4
POP AX
LOOP LL3

LOOP LL3
ADD DX,0FFF
JMP LL1
LL2 INT 3

修改 CS:IP 指向刚 KEYIN 的程式
然後修改 BP 值 ,将 CS:BP 指向另一块空白记忆体 ,本例 BP=5000h
再执行这段程式。
执行完後 ,BP会增加 ,将增加部份算出 ,填入 CX 内 ,并将其它 AX,BX,DX归零
然後取名 HEAD.$$$ "W CS:原BP=5000" ,退回 DOS
(笔者忘记范例的BP增加多少 ,假设为2434h好了 ,将它除4=90D记下来)

键入
DEBUG
-N HEAD.$$$
-L 120
看看 CX 值 ,再加 20 ,然後随意凑个大於CX+20的整数 ??00 ,例如原长 90D ,
凑整数等於 A00 ,修改 CX=长度(本例CX=A00) ,然後键入 "W CS:100" ,回DOS

COPY/B HEAD.$$$+DATA.$$$ UNPACK.EXE
用PCTOOLS EDIT "UNPACK.EXE" 将前两行填入以下资料 ,只填画线处

4D 5A XX XX XX XX 0D 09 A0 00 00 00 FF FF 31 24

4D 5A XX XX XX XX 0D 09 A0 00 00 00 FF FF 31 24
^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^
80 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00
^^^^^ ^^^^^ ^^^^^
其馀不必动它 ,那里该填什么 ,可参考档头资料。

现在您已解压成功啦 ,试著执行看看结果。
-------------------------------------------------------------------------
┌━━━━━━━━━━┐
┃ 3-1 LZEXE 解压缩 ┃
└━━━━━━━━━━┘
笔者认为许多人手上可能没有PKLITE商业版 ,所以再举 LZEXE 解压法

1.先执行 G2A -->T--> G109 暂停 (然後请确认 CS:155是JMP ,否则表示版本不同)
2.记住 BX 值
3.算出(CS-BX)*10h
4.将第3项的末四位放到CX ,第五位数以前的数字放到BX(不足者补零)
5.写回位址为第2项的 BX:0 ,档名 DATA.$$$ (键入"N DATA.$$$" 和 "W ????:0")
6.重新载入原程式
7.进入程式进入点 G2A-->T-->G155-->T
8.抄下 DS.SS.CS.IP.SP.
9.重新载入原程式
10.执行 G2A -->T-->G109
11.找一块空白记忆体撰写本程式 ,例如9000:0000
9000:0000 LODSB
9000:0001 OR AL,AL
9000:0003 JZ 002A
9000:0005 MOV AH,00
9000:0007 ADD DI,AX
9000:0009 MOV AX,DI
9000:000B AND DI,0FFF
9000:000F MOV CL,04
9000:0011 SHR AX,CL
9000:0013 ADD DX,AX
9000:0015 MOV ES,DX
9000:0017 PUSH AX
9000:0018 MOV AX,ES
9000:001A SUB AX,BX
9000:001C MOV CS:[BP+2],AX
9000:0020 MOV CS:[BP+0],DI
9000:0024 ADD BP,+04
9000:0027 POP AX
9000:0028 JMP 0000
9000:002A LODSW
9000:002B OR AX,AX
9000:002D JNZ 0037
9000:002F ADD DX,0FFF
9000:0033 MOV ES,DX
9000:0035 JMP 0000
9000:0037 CMP AX,0001
9000:003A JNZ 0007
9000:003C INT 3
12.修改BP值 ,使得 9000:BP 指向另一块空白记忆体 ,如BP=5000h
13.修改CS.IP使其指向本程式
14.键入"G"执行它
15.算出BP增加的长度 (现在的BP-5000h)
16.将第15项之结果放到CX暂存器内
17.将AX.BX.DX清除为零
18.存档 CS:5000h ,档名HEAD.$$$ (键入 "N HEAD.$$$" 和 "W CS:5000")
19.退回DOS ,键入 "DEBUG HEAD.$$$"
20.键入 "L 120"
21.键入 "? CX/4" 和记下此值
22.将 (CX+20h) ,然後凑整数 ,使之符合大於(CX+20) ,且末两位数为零
例如CX+20後 123变成200 567=600 4532=4600 6437=6500
23.将凑出之整数放到 CX
24.键入 "W CS:100"
25.退回DOS键入 "COPY/B HEAD.$$$+DATA.$$$ UNPACK.EXE"
26.DIR看看档案长度 ,除以512byte ,的到的商和馀数转成16进制
27.用PCTOOLS填写 UNPACK.EXE 的前20h bytes
内容如下∶
4D 5A XX XX XX XX XX XX XX XX 00 00 FF FF XX XX
^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ <-注1至注5

XX XX 00 00 XX XX XX XX 20 00 00 00 00 00 00 00
^^^^^ ^^^^^ ^^^^^ <-注6至注8

以下数字皆为低位元/高位元...别稿错唷!
注⒈━━填入26项的馀数
注⒉━━填入26项的商数加1
注⒊━━填入21项记下的数字
注⒋━━填入22项凑整数後除以10h ,如A00=A0
注⒌━━填入 8项的SS-DS-10
注⒍━━填入 8项的SP
注⒎━━填入 8项的IP
注⒏━━填入 8项的CS-DS-10

好啦 ,解压成功了 ,试著执行看看。
-------------------------------------------------------------------------
结论∶
您是不是学到很多 ,您一定遇过用很多种软体压过的软体吧 ,如先用PKLITE压过
再用LZEXE压过 ,再.....
您只须管它最後是什么程式压缩的 ,您就只须破它最後一道锁即可 ,不用一道道解
开和反压缩。

笔者文笔不是很好 ,且稍嫌杂乱 ,若您会组合语言 ,会TRACE压缩软体 ,多看几次
相信不是很难看懂的 ,不然您就实地操作看看。
┃ 1-1 真实模式 & 保护模式┃
└━━━━━━━━━━━━┘
很多学组合语言的人可能听过保护模式 ,但并不知道保护模式长的是什么样子
,只知道是有著32Bit暂存器的模式 ,其实这只是一小部份罢了。

[真实模式]
当您进入 DEBUG 後 ,您便会发现记忆体是以位移方式计算程式所在位置 ,就像
1234:5678 ,且暂存器只有16Bit , 其中又使用INT中断方式呼叫DOS所提供的工
作 ,但是由於暂存器小 ,所以您最多所能控制的记忆体量不过 1088K(FFFF:FFFF)
,当然对於真实模式大多数人都已非常了解 ,所以笔者就不多谈了。

[保护模式]
DOS当初撰写时是以8086语言来写 ,所能使用到的记忆仅仅1088K ,所以当您主机
升级时 ,电脑超过1MB的记忆体便无法使用到 ,後来就有EMS的出现 ,但是因为EMS
设计的关系 ,只能拿来摆资料 ,无法拿来摆执行程式 ,所以比较庞大的程式便切进
保护模式 ,藉以让程式能够有更大的空间 ,以笔者手上的软体而言 ,切入保护模式
的软体有 PADS2000. Novell. 两套软体 ,但是WINDOW.AUTOCADR10.PADS405这些著
名的软体并没有真的切进保护模式 ,只是利用XMS规格切页 ,功能类似EMS, 拿来摆
资料 ,并非拿来摆可执行的程式 ,且似乎只要挂上 HIMEM.SYS ,软体便不能切入保
护模式。

由於笔者无法切入286保护模式 ,所以简略带过 ,286只有16Bit暂存器 ,保护模
式长得怎样没看过 ,不过286CPU有个缺陷 ,就是切入保护模式後 ,除非重开机 ,
否则回不了DOS ,当然此尚未查证。
笔者成功的切入386保护模式 ,但由於尚无法完全控制 ,所以研究的仅一知半解
,只是想将此一消息告诉各位读者罢了.....以下所指的保护模式皆指386。

386保护模式下暂存器改为32Bit ,并从原来的 AX,BX....改成EAX,EBX....
且简化一些暂存器 ,仅剩下 EAX.EBX.ECX.EDX.ESI.EDI.EBP.ESP.EIP.FLAGS
以及 (PF ZF IF)旗标 ,和除错暂存器(笔者找不到此暂存器) ,而且记忆体分节
也比较怪异 ,没有冒号罗 ,记忆体位置从00000000-FFFFFFFF ,计32MB吧 ,而且
指令也增减了一些 ,虽然程式仍可写 int_xx ,但是皆不可呼叫 ,因为该中断程
式所执行的不再是真实模式 ,强要执行会当机。
我想大家一定想更进一步知道什么是保护模式 ,列一段程式给您看看吧 !

# R
EAX=7FFFFFFF EBX=00000000 ECX=00000000 EDX=00000000
ESI=00000000 EDI=00000000 EDI=00000000 ESP=00000000
EIP=00000416 FLAGS=00007246 (PF ZF IF)
# U 00000416
00000416 E7 0F OUT 0F,EAX
00000418 0F 85 88 FE 66 83 JNZ 836702A6
0000041E FA CLI
0000041F 020F ADD CL,[EDI]
00000421 85 80 FE 33 02 B8 TEST [EAX+B80233FE],EAX

00000421 85 80 FE 33 02 B8 TEST [EAX+B80233FE],EAX
00000427 FA CLI
00000428 00 CD ADD CH,CL
0000042A 14 B4 ADC AL,B4

# U 0000205C
0000205C 8B 1D 2F 07 00 00 MOV EBX,[0000072F]
00002062 E8 BF 15 00 00 CALL 00003626
00002067 0F B7 05 39 07 00 00 MOVZX EAX,word ptr [00000739]
^^^^^指令没打错唷

现在再来看看指令与真实模式的不同点吧 ,以下指出的是笔者测试出的部份 ,
因为市面没有书籍介绍保护模式 ,加上笔者初学保护模式 ,所以没有任何参考
资料 ,故稍嫌简陋。
至於如何切入保护模式 ,由於笔者尚未学会 ,所以无可奉告。

┌━━━━━━━┐
┃ 2-1 指令探讨 ┃
└━━━━━━━┘
真实模式下的程式执行结果笔者略过不谈 ,因为真实模式相信学过组合语言的人
都十分了解 ,所以懒得再讲一次 ,以下是保护模式执行的结果∶

# A 00020000 MOV EAX,12345678
MOV AL,FF
INT 3
# T2 ("#"是类似真实模式DEBUG的"-"号 ,T2是单步执行两次)

结果 EAX = 12345678 =123456FF
表示保护模式下也接受 AH,AL修改 ,不过好像没有程式可改前四码的指令 ,因为
386Debugger不接受 MOV EAH,?? 的指令 ,且我观看其它断落也没看到相关的指令
所以就当做没有这个指令吧。


┌━━━━━━━┐
┃ 2-2 指令探讨 ┃
└━━━━━━━┘

再来就是指令的变化罗 ,一般学过组合的人都知道 JNZ 条件跳越仅可跳一小段距
离 ,但是保护模式似乎并无此限制 ,因为我看到一些特殊的码 ,也列出来让大家
看看吧。

# U 000020B5
000020B5 0F 85 7F 00 00 00 JNZ 0000213A
# U 00002111
00002111 75 27 JNZ 0000213A

从这里可看到程式码很明显有著不同长度 ,不过条件跳越指令码双方似乎不同 ,一
个是用 "0F" 另一个用 "75" 难怪保护模式保护的软体没几个人看的懂。

对了 INT_3 似乎不是触动 0000000C-0000000F 所指的位置 ,因为我把它指向令一
块记忆体 ,然後执行 INT_3 ,竟没当机耶 ,而且也中断下来了。
不要问我为什么 ,因为我也是一知半解。

┌━━━━━━━━━┐
┃ 2-3 记忆段落换算 ┃
└━━━━━━━━━┘

接著咱们再来比较真实模式与保护模式下的记忆体位置吧∶
真实模式与保护模式记忆体的换算方式书上有介绍 ,但与笔者的所研读有点偏差 ,所
我们来看看吧 ,例如记忆体位置 1234:5678 ,转换真实模式记忆位置可看成

12340
+ 5678
-------------
179B8 = 1000:79B8 <-----这是真实的

但是保护模式下与真实模换算就有点出入
因为 0000:0000 在保护模式下所看到的位置是 FFF00000
而 1234:5678 在保护模式下的位置是 FFF179B8
所以於保护模式下要读写 VIDEO 记忆体应该是 FFFB0000 or FFFB8000
不过有个地方蛮奇怪的 ,记忆位置为 FFFF:10 的记忆体保护模式下的记忆体
资料竟是从00000000开始放起 ,令人难以相信硬体如何设计 ,看来真实模式与
保护模式要有另一套的计算公式罗。

┌━━━━━━━┐
┃ 2-4 中断向量 ┃
└━━━━━━━┘
真实模式下有几个中断是很重要的 ,稍一修改就会当机 ,如INT_8或者是
INT_1C 这些中断每秒会被呼叫 18.2 次 , 以及INT_9(键盘中断) ,这些
中断若被改 ,应该会当机 ,但是切入保护模式後 ,这些中断似乎就没用到
了 ,因为我故意将这些中断指向 0000 ,也就是将 FFF00000-FFF00100 全
部涂零 ,竟然没有当机 ,难怪 TURBO DEBUG 不怕键盘被锁死。
奇怪 ,那INT_3是指向那一块记忆体程式呢 ?

另外程式本身也有用到 CLI 指令 ,表示仍然有程式在电脑某个脚落执行著
时间计数的功能 ,只是我找不到。

我想各位一定想再多看一些保护模式下的指令吧 ,我多列出一些码 ,让各位
多了解保护模式 ,与保护模式下的指令吧。

多了解保护模式 ,与保护模式下的指令吧。

# U 2044
00002044 8B 44 24 24 MOV EAX,[ESP+24]
00002048 89 10 MOV [EAX],EDX
0000204A 8B 1D 2F 07 00 00 MOV EBX,[0000072F]
00002050 E8 D1 15 00 00 CALL 00003626
00002055 33 C0 XOR EAX,EAX
00002057 5F POP EDI
00002058 5E POP ESI
00002059 5B POP EBX
0000205A 5D POP EBP
0000205B C3 RET
0000205C 8B 1D 2F 07 00 00 MOV EBX,[0000072F]
00002062 E8 BF 15 00 00 CALL 00003626
00002067 0F B7 05 39 07 00 00MOVZX EAX,word ptr [00000739]
0000206E 5F POP EDI

接著咱们来看看其它指令 ,因为有软体是进入保护模式後 ,才去读写KeyPro
指令 指令码
OUT DX,EAX EF
CMP EDI,ESP 39 E7
OUT 6E,AL E6 6E
IN AL,DX EC
TEST [ECX],EDI 85 39
IN AL,02 E4 02
OUT 60,AL E6 60
IN AL,64 E4 64
TEST AL,02 A8 02
IN EAX,85 E5 85
IN EAX,DX ED
OUT DX,EAX EF
POPFD 9D <---指令没打错唷
NOP 90
既然真实模式与保护模式指令差不多 ,要DEBUG保护模式下的软体应该不是很难
才对 ,好啦 ,笔者讲到此为止 ,您是不是对保护模式已稍有概念了。

如果您对保护模式追踪有概念的话 ,希望您也能教教笔者 ,因为保护模式的软体
已逐渐增多 ,而我仅只有概念 ,太惨了.....