做 pwn 或者 reverse 的一些基础
程序的编译与链接
编译:由 C 语言代码生成汇编代码
汇编:由汇编代码生成机器码
链接:将多个机器码的目标文件链接成一个可执行文件
Linux 下的可执行文件格式 ELF
可执行文件:.out
动态链接库:.so
静态链接库:.a
Windows 下的可执行文件格式 PE
可执行文件:.exe
动态链接库:.dll
静态链接库:.lib
ELF 文件结构
节视图 (磁盘中划分程序)、段视图 (内存中)
32 位系统虚拟内存大小为 4GB (2 的 32 次方)
并且内核空间 (kernal) 共享,下面的用户空间为 3GB,上面的内核空间为 1GB
操作系统 (arch 为例):1. linux 内核 (不同发行版都一样)。 2. 软件是由 GUN 开发的 (ls, cd 等) 3. arch 提供软件源 4. 桌面环境也是一个用户态程序 (也是一个软件)
内核是用于管理硬件的,所以内核只要在内存中装载一份,复用的思想
glic 动态链接库在内存中也只有一份
64 位操作系统 256TB, 内核 128TB, 用户 128TB
内存从低位向高位写
进程虚拟地址空间
代码语言:javascript复制用户空间中分为(从上往下地址由高到低)
内核空间
栈空间(Stack):动态存储区, 控制程序流
Memory Mapping Region:映射一个虚拟空间(glic)
堆空间(heap):动态存储区, 满足用户动态内存申请(malloc)
data段:静态存储区
code段:静态存储区
堆空间从低地址向高地址增长
栈空间从高地址向低地址增长
段和节:
·代码段包含了代码和只读数据
.test节(用户的代码实现)
.rodata节
.hash节
.dynsym节
.dynstr节
.plt节
.rel.got节
......
·数据段包含了可读可写数据
.data节(一般性数据)
.dynamic节(动态链接所需要的结构)
.got节
.got.plt节
.bss节(全局偏移量的地址)
......
·栈段
·一个段包含多个节(意思是从磁盘到内存,一个段会变为多个节)
·段视图用于进程的内存区域的rwx权限划分
·节视图用于ELF文件 编译链接时 与 在磁盘上存储时 的文件结构的组织
程序的装载与进程的执行
磁盘上的可执行文件要先到内存,才能被 CPU 执行
从程序变为虚拟内存中
代码语言:javascript复制int glb;
char* str = "Hello world";
int sum(int x, int y)
{
int t = x y;
}
int main
{
sum(1, 2); (调用函数消耗的内存空间在栈中)
void* ptr = malloc(0x100);
read(0, ptr, 0x100); // input "deadbeef"
return 0;
}
编译链接执行 载入内存
代码语言:javascript复制Kernel:
Stack: t、ptr (程序进程执行栈)
Shared libraries:
Heap: "deadbeef" (存放堆分配的空间)
Bss: glb (存放未初始化的全局变量)(不占用磁盘空间但是占用内存空间)
Data: str (保存只读、不可写、不可执行的数据)
Text: main、sum、"Hello world!" (以机器码的形式存放函数)
Unused:
型参x、y的:
amd64: CPU寄存器中
x86: Stack中
代码语言:javascript复制大端序和小端序
小端序(LSB):
·低地址存放数据低位、高地址存放数据高位(高"ABC"低, 内存中低到高: CBA)
·主要关注
大端序(MSB):
·低地址存放数据高位、高地址存放数据低位(高"ABC"低, 内存中低到高: ABC)
代码语言:javascript复制amd64寄存器结构
rax: 8Bytes (rax表示64bits(8Bytes)长的寄存器) 64位
eax: 4Bytes (eax表示32bits(4Bytes)长的寄存器, 取rax的低四字节来操作) 32位
ax: 2Bytes (rx的低二字节) 16位
ah: 1Bytes (ax中的高一位字节)
al: 1Bytes (ax中的第一位字节)
部分寄存器的功能
RIP: PC, 存放下一条指令的偏移地址
RSP: 存放当前栈帧的栈顶偏移地址
RBP: 存放当前栈帧的栈底偏移地址
RAX: 通用寄存器或存放函数返回值
代码语言:javascript复制静态链接的程序的执行过程
user mode
$ ./binary
frok()
execve("./binary", *argv[], *envp[])
kernel mode
-----------------------------------
sys_execve()
do_execve()
search_binary_handler()
load_elf_binary()
-----------------------------------
user mode
_start
main()
代码语言:javascript复制动态链接的程序的执行过程
user mode
$ ./binary
frok()
execve("./binary", *argv[], *envp[])
kernel mode
-----------------------------------
sys_execve()
do_execve()
search_binary_handler()
load_elf_binary()
-----------------------------------
user mode
ld.so //库函数偏移地址
_start
__libc_start_main()
_init
main()
x86&amd64 汇编基础简述
函数状态涉及到: esp, ebp, eip esp 存储函数调用栈的栈顶地址 ebp 存储当前函数状态的基地址 eip 存储下一条执行的指令的地址
指令格式 | Base | Index | Scale | | ——————— | —– | —– | | 0 | 2 | 4 | | 偏移地址: 0 2*4 = 8 | | |
假设位移为: 0x10
则地址为: 0 2*4 0x10 = 24
代码语言:javascript复制·MOV:
MOV DEST SRC 把源操作数传给目标(源操作数和被操作数不能同时是内存)
·LEA: 把源操作数的有效地址送给指定的寄存器
LEA EBX ASC: 取ASC的地址存放至EBX寄存器中
·ADD/SUB: 目的操作数 /- 源操作数 -> 目的操作数地址
·PUSH: 将一个寄存器中的数据入栈,然后RSP减一个字节
例如: push $2
分解为: sub 长度 %rsp
mov $2 (%rsp)
·POP: 出栈用一个寄存器接受数据, 然后RSP减一个字节
例如: pop 目的地址
分解为: mov (%rsp) 目的地址
add 长度 %rsp
·CMP
目的操作数减去源操作数
·JMP
·J[Condition]:
·CALL: 将目标地址压栈, 然后JMP
例如: call reg
分解为: push RIP
JMP reg
·LEAVE: 函数返回时, 回复父函数栈帧的指令
MOV ESP, EBP
POP EBP
·RET: 在函数返回时, 控制程序执行流返回父函数的指令
POP RIP(实际不存在的)
两种汇编格式
intel | AT&T |
---|---|
mov eax, 8 | movl $8, �x |
mov ebx, 0ffffh | movl $0xffff, �x |
int 80h | int $0x80 |
mov eax, [ecx] | movl (�x), �x |
[ ]: 取目的地址中的数值
intel
操作符重载
intel 汇编格式
代码语言:javascript复制[base index*scale]Disp
byte ptr
mov qword ptr [rsp], 0xfffff
代码语言:javascript复制sum:
push ebp
mov ebp, esp
mov eax, [ebp 12]
add eax, [ebp 8]
pop ebp
retn
AT&T
在操作符后面会加上操作数的大小描述
AT&T 汇编格式
代码语言:javascript复制Disp(base, index, scale)
movq $0xffff, (%rsp)
C 语言 | 数据类型 | 汇编代码后缀 |
---|---|---|
char | 字节 | b byte |
short | 字 | w word |
int | 双字 | l |
long int | 双字 | l |
long long int | 无 | 无 |
char* | 双字 | l |
float | 单精度 | s |
double | 双精度 | l |
long double | 扩展精度 | t |
sum:
pushl �p
movl %esp, �p
movl 12(�p), �x
addl 8(�p), �x
popl �p
ret