花指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
MyStack segment stack
db 256 dup(?)
MyStack ends

MyData segment
MY_MSG db "Hello World!",0dh,0ah,'$'
MyData ends

MyCode segment
MAIN:
mov ax,MyData
mov ds,ax
mov es,ax
jmp LABEL1
db 0b8h ;花指令

LABEL1:
mov dx,offset MY_MSG
mov ah,09h
int 21h

mov ax,4c00h
int 21h
MyCode ends

end MAIN

这种程序可以正常运行,但是反汇编代码时会分析错误,因为有数据的干扰

正常运行

image-20240806215705785

编译器会把b8当成代码进行分析,从而导致后面分析处于出来的代码都是错的

image-20240806215726814

但是如果找到花指令并从正确的地方开始分析,则分析出来的代码是正确的

image-20240806215854581

解决方法是找到花指令并nop

不会影响正常分析

image-20240806220037951

函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
MyStack segment stack
db 256 dup(?)
MyStack ends

MyData segment
MY_MSG1 db "Hello World1!",0dh,0ah,'$'
MY_MSG2 db "Hello World2!",0dh,0ah,'$'
MyData ends

MyCode segment
MAIN:
mov ax,MyData
mov ds,ax
mov es,ax

mov ax,09h ;参数2
push ax
mov ax,offset MY_MSG1 ;参数1 从右往左边入栈
push ax
call SHOW_HELLO
; add sp 4 ;stdcall调用约定

mov ax,09h
push ax
mov ax,offset MY_MSG2
push ax
call SHOW_HELLO
; add sp 4

mov ax,4c00h
int 21h

SHOW_HELLO:
push ax ;保存环境
push cx
push dx

mov bp,sp
mov cx,0
mov dx,word ptr [bp+8] ;访问参数1
mov ah,byte ptr [bp+10] ;访问参数2
int 21h

pop dx
pop cx
pop ax ;恢复环境
; pop ax
; jmp ax ==> pop ip

; pop ax
; add sp,4
; push ax
; ret
retn 4 ;mov ip,[sp] add sp,4 c调用约定

MyCode ends

end MAIN

调用 SHOW_HELLO 函数来显示 MY_MSG1MY_MSG2

通过改变参数实现对函数的重复利用

1
2
3
4
5
6
mov ax,09h ;参数2
push ax
mov ax,offset MY_MSG1 ;参数1 从右往左边入栈
push ax
call SHOW_HELLO
; add sp 4 ;stdcall调用约定
  • mov ax,09h: 将 09h(参数2)加载到 ax 寄存器。
  • push ax: 将 ax 寄存器的值压入堆栈。
  • mov ax,offset MY_MSG1: 将 MY_MSG1 的偏移地址(参数1)加载到 ax 寄存器。
  • push ax: 将 ax 寄存器的值压入堆栈。
  • call SHOW_HELLO: 调用 SHOW_HELLO 子程序。
  • ; add sp 4: (注释掉的代码)在 stdcall 调用约定中,子程序返回时恢复堆栈指针。
1
2
3
4
5
6
mov ax,09h
push ax
mov ax,offset MY_MSG2
push ax
call SHOW_HELLO
; add sp 4
  • mov ax,09h: 将 09h(参数2)加载到 ax 寄存器。
  • push ax: 将 ax 寄存器的值压入堆栈。
  • mov ax,offset MY_MSG2: 将 MY_MSG2 的偏移地址(参数1)加载到 ax 寄存器。
  • push ax: 将 ax 寄存器的值压入堆栈。
  • call SHOW_HELLO: 调用 SHOW_HELLO 子程序。
  • ; add sp 4: (注释掉的代码)在 stdcall 调用约定中,子程序返回时恢复堆栈指针。

SHOW_HELLO 子程序(函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SHOW_HELLO:
push ax ;保存环境
push cx
push dx

mov bp,sp
mov cx,0
mov dx,word ptr [bp+8] ;访问参数1
mov ah,byte ptr [bp+10] ;访问参数2
int 21h

pop dx
pop cx
pop ax ;恢复环境
; pop ax
; jmp ax ==> pop ip

; pop ax
; add sp,4
; push ax
; ret
retn 4 ;mov ip,[sp] add sp,4 c调用约定
  • push ax: 保存 ax 寄存器的值到堆栈。
  • push cx: 保存 cx 寄存器的值到堆栈。
  • push dx: 保存 dx 寄存器的值到堆栈。

设置基指针寄存器

1
2
3
4
5
mov bp,sp
mov cx,0
mov dx,word ptr [bp+8] ;访问参数1
mov ah,byte ptr [bp+10] ;访问参数2
int 21h
  • mov bp,sp: 将当前堆栈指针 sp 的值移动到基指针寄存器 bp
  • mov cx,0: 清空 cx 寄存器。
  • mov dx,word ptr [bp+8]: 读取堆栈中偏移 bp+8 位置的值(参数1)到 dx 寄存器。(因为前面还压了3个寄存器的值和函数运行完后i哦返回的地址进栈,所以参数1被压倒下面)
  • mov ah,byte ptr [bp+10]: 读取堆栈中偏移 bp+10 位置的值(参数2)到 ah 寄存器。(同理参数2被往下压)
  • int 21h: 调用 DOS 中断 21h,显示字符串。

恢复环境并返回

1
2
3
4
5
6
7
8
9
10
11
pop dx
pop cx
pop ax ;恢复环境
; pop ax
; jmp ax ==> pop ip

; pop ax
; add sp,4
; push ax
; ret
retn 4 ;mov ip,[sp] add sp,4 c调用约定
  • pop dx: 从堆栈中恢复 dx 寄存器的值。
  • pop cx: 从堆栈中恢复 cx 寄存器的值。
  • pop ax: 从堆栈中恢复 ax 寄存器的值。
  • retn 4: 返回调用者,并清理堆栈中的4个字节(两个参数)。(平衡堆栈)

运行结果

image-20240807192506317

调试

运行完call指令后,IP的地址改为call后面的地址,堆栈中存放了call指令接下来的指令的地址,还有前面压入栈的两个参数

image-20240807193111067

image-20240807193327561

ax、cx、dx的值被保存到堆栈

image-20240807193615477

从堆栈中取出参数,运行函数得出结果

image-20240807194018812

堆栈被平衡,ax、cx、dx的值被还原,程序回到函数调用后要执行的指令的地方

image-20240807194519138