注:此文为我17年前的文章,发表在《电脑》杂志1991年第二期,再次录入此文时并没有验证其中的正确性,同时由于录入错误,可能会有一些错误,如发现,请及时告知,谢谢!
通常的程序都是由低地址向高地址逐条指令的顺序执行,这样的程序可以用调试程序进行反汇编,也可以方便地进行跟踪,但是如果我们程序的机器码不按这种顺序排列,而是由高地址向低地址排列,运行时也让其按这种反向顺序执行,即从高地址向低地址运行,那么这样的程序既无法跟踪,也无法反汇编。
由于8086系列微机很容易设置单步中断,给以上想法的实现带来了可能,可以设想当执行完一条指令后将产生单步中断,我们利用该中断使一条指令复原,然后执行该指令,指令执行后又产生中断,根据此次中断和上次中断的返回地址即可算出该指令的长度,这样向低地址方向就可以找到下一条指令的起始地址,然后再把指令复原,再执行......;这样,程序的运行就好像是从高地址向低地址方向运行。
显然,这样的程序不能包含有循环和向回的转移指令,因为程序运行中已将原来的代码破坏掉,转移过去已无法在回复原来的指令,另外对调用软中断的过程要做特殊处理,否则在中断中也将使程序倒运行,那是不能允许的,同时,在程序倒运行过程中,应禁止硬件中断。
下面通过一个实例来说明该方法的实现,程序中首先把A盘第39磁道0面第一扇区中偏移为10h处的一个字节改为0e9h,然后设置一段倒运行程序,倒运行程序中读出A盘第39磁道0面第一扇区的内容,并判断偏移为10h处的字节是否为0e9h,如是ax=0,否则ax=1;然后再变为正常运行状态,,并在顺序运行中判断ax的值是否为1,如不是显示“OK!”退出,否则显示“ERROR!”退出。
要进入单步状态有两个条件,一是允许单步中断int1,二是标志寄存器的8位即单步允许位置为1,本例进入单步状态没有用绝对转移指令JMP,二是用中断返回指令IRET来改变标志位并直接转移到要运行的位置,由于8086系列芯片的指令最长为6个字节,所以在INT1中每次把指令代码前5个字节移到指令代码后,这样必定能组成一条完整的指令,当遇到指令代码为0时,则认为倒运行结束,回复为正常状态;当遇到指令代码为0cdh时,表明是一条int指令,为了保证中断程序的正确运行,必须变为顺序运行方式,并在int指令执行完后再恢复成逆运行方式,逆运行部分int 13h后的7条指令就是为此目的安排的,由于倒运行部分的第一条指令不会产生int1,所以倒运行部分的第一条指令应为单字节指令或顺序放置,本例为以单字节指令cli,offaddr初始值为第一条指令的地址,这样通过int1的返回地址就可算出刚执行的指令长度,从而得到下一条指令的地址,并把该地址存入offaddr,这样循环下去便使程序逆运行起来了。为了说明逆运行中转移指令的情况,本例逆运行部分有三条转移指令,第一条在int 13h后,为jc neg_run,它是当逆运行读盘失败时,退出逆运行方式,并重新初始化逆运行指令码,再运行逆运行部分,由于出现int指令时,int 1例程已将运行方式变为顺序运行方式,正式由于这一条件的存在,才使jc neg_run指令得以实现,在一般情况下,逆运行中采用向回的转移是不允许的;第二、三条转移指令分别为jnz error和jmp exit,这两条指令均为向前转移并为近转移,同时也没有转出逆行程序部分,所以无需任何改动就可以在逆运行中实现。
程序中,标号prom_start到cont之间的代码为逆运行部分,为了说明程序的运行状况,这段程序我们以顺序方式写出,而这部分的实际代码为prom这部分数据,neg_run后的一段程序把prom这段数据移到了prom_start处,由于prom的数据实际上是prom_start部分的逆向代码(即最后一个字节放到第一字节,倒数第二个字节放到第二字节......),所以这两部分的长度一定是相等的,当我们把程序编译,并用debug观察时,会发现prom并不完全是prom_start部分程序的逆向代码,有个别字节被改动了,这些改动是必须的,本例中改动了两处,第一处为int 13h后的jc neg_run因为当代码被逆向安排后,该指令的地址已和顺序方式下的地址不同,所以转移的偏移也必然不同;另一条改动为jc neg_run指令下的第三条指令mov ax, offset gggg,原因基本是一样的,因为代码逆向安排后,gggg标号位置已发生了变化,所以offset gggg也发生了变化,必须更改。
该例改写完A盘39磁道0面1扇区后,将缓冲区buf清0,然后等待键盘输入一字符后才继续运行,当我们看到A驱动器工作灯亮后又熄灭时,便是在等待键盘输入,此时敲任意键便可进入逆运行状态,如果在修改A盘扇区时出错,将重试5次,如果仍不成功,则显示“Disk Operation Error!”,然后退出;在实验时,我们分别可以试两种情况,首先插入一张已格式化好的盘在A驱动器,运行该程序,当等待键盘输入时敲入任意键,则应显示“OK!”退出;另一种情况,当等待键盘输入时,换上一张未被该程序修改过的盘,再键入任意键,则应显示“Error!”并退出。
改程序为演示性的,但逆运行这种方式可以为自己的应用程序增添光彩,我们已把该方法运用到应用程序的加密中,效果良好。
code segment
assume cs:code, ds:code, es:code
org 100h
start:
mov ax, 0
push ds
push ax
push cs
pop ds
push cs
pop es
mov cx, 5
repeat1:
push cx
mov ax, 0201h
mov bx, offset buf
mov cx, 2701h
mov dx, 0
int 13h
pop cx
jnc edit
dec cx
jnz repeat1
error1:
mov dx, offset string3
mov ah, 09
int 21h
mov ah, 4ch
int 21h
edit:
mov si, offset buf
mov al, 0e9h
mov [si + 10h], al
mov cx, 5
repeat2:
push cx
mov ax, 0301h
mov bx, si
mov cx, 2701h
mov dx, 0
int 13h
pop cx
jnc continue
dec cx
jnz repeat2
jmp error1
continue:
mov al, 0
mov di, offset buf
mov cx, 512
cld
repz stosb
push ds
mov ax, 0
mov ds, ax
mov si, 4
cli
MOV ax, [si]
MOV es:int1_ip, ax
MOV ax, [si + 2]
MOV es:int1_cs, ax
MOV ax, offset int1
MOV [si], ax
PUSH cs
POP ax
MOV [si + 2], ax
sti
MOV ah, 0
int 16h
neg_run:
MOV cx, count
MOV si, offset prom
MOV di, offset prom_start
cld
repz movsb
pushf
POP ax
or ax, 100h
PUSH ax
PUSH cs
MOV ax, offset exit
PUSH ax
iret
prom_start:
db 0
cli
MOV ax, 0201h
MOV bx, offset buf
MOV cx, 2701h
MOV dx, 0
int 13h
JC neg_run
MOV ax, flag
PUSH ax
MOV ax, offset gggg
PUSH cs
PUSH ax
iret
gggg:
MOV si, offset buf
ADD si, 10h
cmp byte ptr [si], 0e9h
JNZ error
MOV ax, 0
JMP exit
error:
MOV ax, 1
exit:
sti
db 10 dup(?)
cont:
cmp ax, 1
JZ error2
MOV dx, offset string1
MOV ah, 09
int 21h
JMP exit1
error2:
MOV dx, offset string2
MOV ah, 09
int 21h
exit1:
cli
PUSH ds
MOV ax, 0
MOV dx, ax
MOV si, 4
MOV ax, es:int1_ip
MOV [si], ax
MOV ax, es:int1_cs
MOV [si + 2], ax
POP ds
sti
MOV ah, 4ch
int 21h
buf db 512 dup(?)
string1 db 0dh, 0ah, "OK!$"
string2 db 0dh, 0ah, "ERROR!$"
string3 db 0dh, 0ah, "Disk Operation Error!$"
int1_ip dw ?
int1_cs dw ?
prom db 0, 0fbh, 0, 1, 0b8h, 90h, 4, 0ebh, 0, 0, 0b8h, 6
db 75h, 0e9h, 3ch, 80h, 10h, 0c6h, 83h, 2, 6
db 0beh, 0cfh, 50h, 0eh, 0c9h, 0b8h, 50h, 4, 0d9h, 0a1h
db 0c0h, 72h, 13h, 0cdh, 0, 0, 0bah, 27h, 1, 0b9h, 2
db 6, 0bbh, 2, 1, 0b8h, 0fah
db 10 dup(0)
count dw 3bh
int1 proc
cli
PUSH bp
MOV bp, sp
PUSH ds
PUSH es
PUSH ax
MOV ax, [bp + 4]
MOV es, ax
PUSH cs
POP ds
MOV ax, [bp + 2]
sub ax, offaddr
sub offaddr, ax
MOV ax, offaddr
MOV [bp + 2], ax
PUSH di
PUSH si
PUSH cx
PUSH es
POP ds
MOV si, ax
cmp byte ptr [si], 0
JZ rept2
MOV di, ax
MOV cx, 5
cmp byte ptr [di], 0cdh
JNZ rept3
MOV ax, [bp + 6]
MOV flag, ax
and ax, 0feffh
MOV [bp + 6], ax
MOV cx, 19
rept3:
INC di
DEC si
rept1:
MOV al, [si]
MOV [di], al
INC di
DEC si
DEC cx
JNZ rept1
int1_exit:
POP cx
POP si
POP di
POP ax
POP es
POP ds
POP bp
sti
iret
rept2:
MOV ax, offset count
MOV [bp + 2], ax
MOV ax, [bp + 6]
and ax, 0feffh
MOV [bp + 6], ax
JMP int1_exit
offaddr dw offset exit
flag dw 0
int1 endp
code ends
END start
评论