pwn是什么
在 CTF 中,PWN 主要通过利用程序中的漏洞造成内存破坏以获取远程计算机的 shell,从而获得 flag。PWN 题目比较常见的形式是把一个用C/C++ 语言编写的可执行程序运行在目标服务器上,参赛者通过网络与服务器进行数据交互。因为题目中一般存在漏洞,攻击者可以构造恶意数据发送给远程服务器的程序,导致远程服务器程序执行攻击者希望的代码,从而控制远程服务器。
栈基础(64位)
寄存器
CPU内部的存储单元,用于存放从内存读取而来的数据(包括指令)和CPU运算的中间结果
不同体系结构的CPU在其内部存在的寄存器的数量、类型以及名称可能会不同,我们现在只看ADM64 CPU这一种体系结构,这种CPU在其内部存在二十多个可直接被汇编语言中使用的调度器,还有几种仅在操作系统代码中才会出现,应用层的话,通常只会用到如下三个分类共19个调度器:
通用寄存器共有rax、rbx、rcx、rdx、rsi、rdi、rbp、rsp、r8、r9、r10、r11、r12、r13、r14、r15这16个寄存器,除了 rsp、rbp CPU对它们的用途没有做特殊规定,可以自定义其用途
rsp:就是常说的栈指针,它永远指向一个进程的栈顶
rbp:保存的是栈帧在内存中的起始地址
程序计数寄存器(rip寄存器,也叫PC寄存器、IP寄存器)用来存放下一条即将用来执行的指令的地址,它决定程序执行的流程。
段寄存器(fs、gs寄存器)用来实现线程本地存储(TLS)
简单汇编
汇编语言可以让我们直接操作寄存器、栈、内存等硬件资源,从而更好理解计算机的工作原理
mov a,b #将b的值赋给a add a,b #a=a+b sub a,b #a=a-b push rax #将rax的值入栈 pop rax #将栈中的一个数据入rax
栈结构
栈由高地址向低地址延伸,两个指针:rsp:指向栈顶 rbp:指向栈低
000001 | 栈顶 | rsp | 低地址 |
---|---|---|---|
....... | |||
....... | |||
000010 | 栈低 | rbp | 高地址 |
栈常用指令
举个例子:
mov rax, 123h mov rbx, 456h push rax push rbx pop rax pop rbx
原栈
..... | 低地址 | |
---|---|---|
000111 | rsp | |
..... | 高地址 |
push rax (即rsp=rsp-8),并将rax中的数据 123h 压入到栈当中
... | 低地址 | |
---|---|---|
000123 | rsp | |
000111 | ||
... | 高地址 |
push rbx
... | 低地址 | |
---|---|---|
000456 | rsp | |
000123 | ||
000111 | ||
... | 高地址 |
pop 和上面类似,是一个弹出的过程
pop rax:将栈中的值弹出给rax, rsp=rsp+8
pop rbx:将栈中的值弹出给rbx
整体操作下来后rax和rbx的值实现了互换 栈是一个先入后出的数据结构
函数中栈的调用
int func2(){ return 3; } int func1(){ return func2(); } int main(){ return func1(); }
汇编后:
func2(): 401106 push rbp 401107 mov rbp,rsp 40110a mov eax,2 40110f pop rbp 401110 ret func1(): 401111 push rbp 401112 mov rbp,rsp 401115 call func2() 40111a pop rbp 40111b ret main(): 40111c push rbp 40111d mov rbp,rsp 401120 call func1() 401125 nop 401126 pop rbp 401127 ret
栈溢出
读取是从高地址指向低地址,但是栈又是从低地址指向高地址,在局部变量的位置写入内容,如果没有控制好输入长度,会覆盖掉返回的地址
int flow(){ char buf[8]; read(0, buf, 16); }
声明了一个名为 buf
的字符数组,大小为 8 字节。然后,通过调用 read()
函数从标准输入中读取最多 16 个字节的数据,并将其存储到 buf
数组中
需要注意的是,尽管 buf
数组的大小只有 8 字节,但 read()
函数仍然会尝试读取 16 个字节的数据。这可能导致缓冲区溢出的问题,因为读取的数据量超过了 buf
数组的容量。
栈保护
canary(金丝雀)
"Canary" 是一个随机生成的特殊值,它被插入到栈帧的布局中,位于局部变量和返回地址之间。当函数返回时,系统会检查"canary"值是否被修改,如果发现被修改,则会触发异常或终止程序的执行。
NX(堆栈不可执行)
将数据所在内存页(例如堆或栈)表示为不可执行,如果程序产生溢出而转入shellcode时,CPU就会抛出异常
ASLR和PIE(地址随机化)
随机化地址布局,使攻击者堆内存布局一无所知
标准的可执行程序需要固定的地址,并且只有被装载到这个地址才能正确执行,PIE能使程序像共享库一样在主存的任何位置加载,引入PIE的原因就是让程序能装载在随机地址,从而缓解缓冲区溢出攻击
RELRO
设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT的攻击