8086有14个寄存器,物理地址 = 段地址*16+偏移地址,
寄存器 | 名称 | 说明 | 示例 |
---|---|---|---|
AX | 通用寄存器 | 16位寄存器,分高8位AH,低8位AL | |
BX | 通用寄存器 | mov ax,[bx] : 从 ds:bx 位置开始读两个字节写到ax中;mov al,[bx] : 从 ds:bx 位置开始读一个字节写到al中 | |
CX | 通用寄存器 | ||
DX | 通用寄存器 | ||
CS | 段寄存器,CS:IP指向当前要读取指令的地址,读取一条指令后IP值自动增加 | 段地址*16+偏移地址 构成20位物理地址 | |
DS | 段寄存器,存放要访问的数据段地址 | mov al,[0] : 将 ds:0 中的低8位读入al,[0]是ds的偏移地址,写入数据寄存器:mov [0],al 将ax寄存器的低8位写入ds:0;[bx] 也是偏移地址,具体的偏移值存放在bx中 | |
SS | 段寄存器,栈段,SS:SP指向栈顶 | ||
ES | 段寄存器 | ||
IP | 代码偏移地址 | ||
SP | 栈偏移地址 | ||
SI | 与BX功能相近的寄存器,不能分为两个8位寄存器 | 不能分成两个8位寄存器 | mov ax,[si] : 将起始位 ds:si 的后两个字节的值写到ax中 |
DI | 与SI相似 | 不能分成两个8位寄存器 | mov ax,[di] : 将起始位 ds:di 的后两个字节的值写到ax中 |
BP | |||
PSW |
指令 | 名称 | 说明 | 相关指令 | 指令性质 | 指令执行者 | 对应机器码 |
---|---|---|---|---|---|---|
mov | 寄存器写值,mov ax,10H : ax = 10H | |||||
add | 寄存器累加,add ax, 10H : ax += 10H | |||||
sub | 寄存器减法,sub ax, 10H : ax -= 10H | |||||
pop | 出栈 | pop ax : 将栈顶数据读入ax,修改SP = SP + 2 | ||||
push | 入栈 | push ax : SP = SP - 2, 将ax从栈顶写入 | ||||
int | ||||||
inc | 自增1 | inc bx : bx中的值自增1 | ||||
loop | loop s : 执行过程:cx = cx - 1 , 判断cx值,大于 0 跳转至 s 位置 (修改cs:ip),否则 往下执行;通常用于循环,cx存放循环次数 | |||||
and | 与运算,and ax,0 : ax&=0 | |||||
or | 或运算,or ax,1 : ax|=1 |
我使用的是AsmTools,这个工具下的debug、masm、link都是在16位windows dos下运行的,在当前的32位或64位系统中不能运行,需安装DOSBox,在DOSBox中运行汇编的工具。
指令 | 说明 | 示例 |
---|---|---|
r | 查看当前寄存器 | |
a | 写入一条汇编指令 | |
u | 查看所有待运行指令,将机器指令翻译成汇编指令 | |
t | 执行一条指令 | |
d | 查看内存单元 | -d 076A:0000 : 查看076A:0000开始的内存数据 |
e | 写入数据 | -e ds:0 11 22 33 44 55 66 : 写入数据到从ds:0开始的内存 |
p | ||
q | 退出debug |
> debug 1.exe
masm将.asm源文件编译生成.obj目标文件
例如:编译1.asm
> masm.exe 1;
link将.obj文件链接生成.exe目标文件
例如:链接1.obj
> link.exe 1;
assume cs:sg1
sg1 segment
mov ax,2000H
mov ss,ax
mov sp,0
add sp,10
pop ax
pop bx
push ax
push bx
pop ax
pop bx
mov ax,4c00H
int 21H
sg1 ends
end
解释:
以上面的代码为例进行分析,将上面的代码保存为文件1.asm,存放在AsmTools文件夹下。
执行了一句指令,为ax赋值了,代码偏移地址IP自动加3
assume cs:seg_test
a segment
db 1,2,3,4,5,6,7,8
a ends
b segment
db 1,2,3,4,5,6,7,8
b ends
c segment
db 0,0,0,0,0,0,0,0
c ends
seg_test segment
start : mov bx,a
mov ds,bx
mov bx,0H
mov si,10H
mov di,20H
mov cx,8H
and ax,0H
s : mov al,[bx]
add al,[bx+si]
mov [bx+di],al
inc bx
loop s
mov ax,4c00H
int 21h
seg_test ends
end start
assume cs:seg_test,ds:a
查看程序中的内存地址及数据:
已知默认的DS为0x075A,则程序段的位置为0x075A+256 = 0x76A,可以看到程序段的前3行为a、b、c的值。
写一个C程序,代码如下:
// code.c
int add(int i, int j)
{
return i + j;
}
int main()
{
int a = 1;
int b = 7;
a = add(a, b);
return a;
}
很简单的一段代码,先直接生成汇编看看:
> gcc -O0 -S code.c
-O是编译器优化参数,-O0不优化。执行上面的代码后,会生成一个code.s文件:
.file "code.c"
.text
.globl add
.type add, @function
add:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size add, .-add
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, -8(%rbp)
movl $7, -4(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call add
movl %eax, -8(%rbp)
movl -8(%rbp), %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0"
.section .note.GNU-stack,"",@progbits
我们继续,生成可执行文件,然后再查看可执行文件的汇编:
> gcc -O0 -c code.c
将会生成一个code.o文件,使用命令查看汇编:
> objdump -d code.o
汇编如下:
code.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <add>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 89 7d fc mov %edi,-0x4(%rbp)
7: 89 75 f8 mov %esi,-0x8(%rbp)
a: 8b 55 fc mov -0x4(%rbp),%edx
d: 8b 45 f8 mov -0x8(%rbp),%eax
10: 01 d0 add %edx,%eax
12: 5d pop %rbp
13: c3 retq
0000000000000014 <main>:
14: 55 push %rbp
15: 48 89 e5 mov %rsp,%rbp
18: 48 83 ec 10 sub $0x10,%rsp
1c: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
23: c7 45 fc 07 00 00 00 movl $0x7,-0x4(%rbp)
2a: 8b 55 fc mov -0x4(%rbp),%edx
2d: 8b 45 f8 mov -0x8(%rbp),%eax
30: 89 d6 mov %edx,%esi
32: 89 c7 mov %eax,%edi
34: e8 00 00 00 00 callq 39 <main+0x25>
39: 89 45 f8 mov %eax,-0x8(%rbp)
3c: 8b 45 f8 mov -0x8(%rbp),%eax
3f: c9 leaveq
40: c3 retq
> cl /FA code.c
生成的汇编code.asm:
; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.40629.0
TITLE D:\vscode\cpp\socket_model\linux\code.c
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC _add
PUBLIC _main
; Function compile flags: /Odtp
_TEXT SEGMENT
_b$ = -8 ; size = 4
_a$ = -4 ; size = 4
_main PROC
; File d:\vscode\cpp\socket_model\linux\code.c
; Line 8
push ebp
mov ebp, esp
sub esp, 8
; Line 9
mov DWORD PTR _a$[ebp], 1
; Line 10
mov DWORD PTR _b$[ebp], 7
; Line 11
mov eax, DWORD PTR _b$[ebp]
push eax
mov ecx, DWORD PTR _a$[ebp]
push ecx
call _add
add esp, 8
mov DWORD PTR _a$[ebp], eax
; Line 12
mov eax, DWORD PTR _a$[ebp]
; Line 13
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
; Function compile flags: /Odtp
_TEXT SEGMENT
_i$ = 8 ; size = 4
_j$ = 12 ; size = 4
_add PROC
; File d:\vscode\cpp\socket_model\linux\code.c
; Line 3
push ebp
mov ebp, esp
; Line 4
mov eax, DWORD PTR _i$[ebp]
add eax, DWORD PTR _j$[ebp]
; Line 5
pop ebp
ret 0
_add ENDP
_TEXT ENDS
END
问题:不实用汇编或函数调用,实现函数调用。
函数调用在汇编中是使用jmp指令实现的,我们只需要想办法将jmp指令插入到程序要执行的地方即可。
通过strcpy或memcpy溢出缓冲区,将jmp指令写入正确位置即可实现。
参考:缓冲区溢出之Strcpy和Memcpy
同一段c语言代码,由于编译器、平台、指令集等的不同,编译后的汇编是有区别的。如何去看懂一段汇编程序,需要先了解以下内容: