学PWN 栈溢出
代码语言:javascript复制https://zhuanlan.zhihu.com/p/25816426#
函数调用栈
程序运行时,内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数和局部变量
调用栈 从高地址向低地址生长 压栈时 地址变小 出栈时 地址变大
esp 栈顶指针 ebp 栈基指针 eip 下一条指令的地址
函数调用时: 参数按照逆序压栈
现代操作系统内存通常分段
- 函数调用栈(Stack Segment)
- 堆(Heap Segment)堆用于存放程序运行中动态分配的内存 malloc() 和 free() 函数
- 数据段(Data Segment) 存储已经初始化且初值不为0的全局变量和静态局部变量
- BSS段 存储未初始化或初值为0的全局变量和静态局部变量
- 代码段(Code Segment) 代码段存储可执行代码和只读常量(如常量字符串),属性可读可执行,但通常不可写
寄存器
32位
代码语言:javascript复制通用寄存器
一般寄存器(eax、ebx、ecx、edx)
eax 被称为累加寄存器
ebx 被称为基址寄存器
ecx 被称为记数寄存器
edx 被称为数据寄存器
索引寄存器(esi、edi)
esi 指向要处理的数据地址
edi 指向存放处理结果的数据地址
以及堆栈指针寄存器(esp、ebp)
特殊寄存器(被特定的汇编指令使用,不能用来任意存储数据)
段地址寄存器(ss、cs、ds、es、fs、gs)
存储内存分段地址
ss 存储函数调用栈
cs 存储代码段
ds 存储数据段
es、fs、gs 是附加的存储数据段地址
标志位寄存器(EFLAGS)
OF 数值溢出
IF 中断
ZF 运算结果为
CF 运算产生进位
以及指令指针寄存器(eip)
汇编指令
Intel 格式
寄存器名称和数值前无符号
指令名称 目标操作数 DST,源操作数 SRC
AT&T 格式
寄存器名称前加“%”,数值前加“$”:
“指令名称 源操作数 SRC,目标操作数 DST”
常用的汇编指令
代码语言:javascript复制MOV:数据传输指令,将 SRC 传至 DST,格式为
MOV DST, SRC;
PUSH:压入堆栈指令,将 SRC 压入栈内,格式为
PUSH SRC;
POP:弹出堆栈指令,将栈顶的数据弹出并存至 DST,格式为
POP DST;
LEA:取地址指令,将 MEM 的地址存至 REG ,格式为
LEA REG, MEM;
ADD/SUB:加/减法指令,将运算结果存至 DST,格式为
ADD/SUB DST, SRC;
AND/OR/XOR:按位与/或/异或,将运算结果存至 DST ,格式为
AND/OR/XOR DST,SRC;
CALL:调用指令,将当前的 eip 压入栈顶,并将 PTR 存入 eip,格式为
CALL PTR;
RET:返回指令,操作为将栈顶数据弹出至 eip,格式为
RET;
栈溢出攻击原理
代码语言:javascript复制攻击的时机:发生函数调用或者结束函数调用
攻击的方式:修改 控制程序执行指令的关键寄存器eip 的值
攻击的目标:让eip载入攻击指令的地址
让溢出数据用攻击指令来覆盖返回地址
攻击指令可以存在于溢出数据中,也可以是内存中的其它位置
代码语言:javascript复制返回地址 指向溢出数据中的一段指令(shellcode)
返回地址 指向内存中已经有的函数 (return2libc)
返回地址 执行内存中已经有的一段指令(rop)
修改返回地址,让其指向另外一个函数(hijack GOT)
shellcode
代码语言:javascript复制技术生效的前提:
1. 关闭地址随机化
2. shellcode有权限
在溢出数据内包含一段攻击指令 攻击指令一般是为了打开shell从而获得当前程序的控制权限
代码语言:javascript复制payload : padding1 address of shellcode padding2 shellcode
- padding1 随意填充(注意不要包含 “x00” ) 长度:用调试工具(例如 gdb)查看汇编 运行程序时用不断增加输入长度来试探覆盖函数的基地址
- address of shellcode 后面 shellcode 起始处的地址 用来覆盖返回地址 调试工具里查看(可以查看 ebp 的内容然后再加4(32位机),参见前面关于函数状态的解释) 但是不够确切 由运行环境决定 解决办法: padding2 里填充若干长度的 “x90”
- padding2 处的数据也可以随意填充长度可以任意
- shellcode 应该为十六进制的机器码格式
Return2libc
修改返回地址 让其指向内存中已有的某个函数
要完成的任务:在内存中确定要调用的函数地址,覆盖要攻击的函数地址 通常使用lic动态连接库中的系统级的函数获取当前进程的控制权限
代码语言:javascript复制payload: padding1 address of system() padding2 address of “/bin/sh”
- padding1 随意填充(注意不要包含 “x00” ) 长度:用调试工具(例如 gdb)查看汇编 运行程序时用不断增加输入长度来试探 覆盖函数的基地址
- address of system() system() 在内存中的地址,用来覆盖返回地址 看看程序如何调用动态链接库 首先确定动态链接库在内存的起始地址,再加上函数在动态库中的相对偏移量,最终得到函数在内存的绝对地址 ASLR 被关闭的前提下 通过调试工具在运行程序过程中直接查看 system() 的地址 查看动态库在内存的起始地址 再在动态库内查看函数的相对偏移位置 通过计算得到函数的绝对地址
- padding2 处的数据长度为4(32位机),对应调用 system() 时的返回地址
- address of “/bin/sh” 是字符串 “/bin/sh” 在内存中的地址,作为传给 system() 的参数。 在动态库里搜索这个字符串 动态库起始地址+相对偏移 将这个字符串加到环境变量里,再通过 getenv() 等函数来确定地址
Rop
覆盖返回地址来执行内存内已有的代码片段
代码语言:javascript复制payload : padding address of gadget
payload : padding address of gadget address of gadget ......
address of gadget n
payload : padding address of gadget param for gadget address of gadget param for gadget ...... address of gadget n shellcode
代码语言:javascript复制 gadget 执行完毕可以将控制权交给下一个 gadget
gadget 的最后一步应该是 RET 指令
hijack GOT
将某个函数的地址替换成另一个函数的地址
对外部函数的调用需要在生成可执行文件时将外部函数链接到程序
代码语言:javascript复制静态链接
可执行文件包含外部函数的全部代码
动态链接
可执行文件并不包含外部函数的代码
运行时将动态链接库(若干外部函数的集合)加载到内存的某个位置
发生调用时去链接库定位所需的函数
GOT 全称是全局偏移量表(Global Offset Table)
存储外部函数在内存的确切地址
GOT 存储在数据段(Data Segment)内
可以在程序运行中被修改
PLT 全称是程序链接表
存储外部函数的入口点(entry)
PLT 存储在代码段(Code Segment)内
运行之前就已经确定并且不会被修改
当程序需要调用某个外部函数时,首先到 PLT 表内寻找对应的入口点,跳转到 GOT 表中
确定函数 A 在 GOT 表中的条目位置
代码语言:javascript复制函数调用的汇编指令中找到 PLT 表中该函数的入口点位置,从而定位到该函数在 GOT 中的条目
如何确定函数 B 在内存中的地址
代码语言:javascript复制假如我们知道了函数 A 的运行时地址(读取 GOT 表内容),也知道函数 A 和函数 B 在动态链接库内的相对位置,就可以推算出函数 B 的运行时地址