我们拿到一个elf文件的时候,会放到linux环境,并且checksec查看有什么保护,我们做的简单的题目自然是什么保护都没有,但是稍微难一点就是给你开一个保护,这篇短文就简单讲一下遇到一个保护的时候,应该采取什么思路
核心思路:RET2LIBC / ROP 链
最简单的:checksec之后出现disable: 这意味着数据所在的内存页可以被执行
输入缓冲区 → 写入 shellcode → 覆盖返回地址 → 跳转到 shellcode → 执行这个不细说,接下来的NX enable就是说栈不可执行:
先说最简单的情况,出现明文shellcode,我们的思路是:
覆盖返回地址为shellcode地址
高地址
+------------------------+
| ... |
+------------------------+
| 返回地址 (RIP) | <-- 我们把它改成 system("cat flag") 地址
+------------------------+
| 保存的 RBP | <-- 被覆盖
+------------------------+
| 局部变量 |
+------------------------+
| buf | <-- 从这里开始输入
+------------------------+
低地址
payload:输入 "A"*填充 + system_cat_flag_addr拓展一下:
如果没有system('cat ./flag')或者system('bin/bash')这种只用写他们函数执行的地址到返回地址就能getshell的函数的时候:就需要构造ROP链
高地址
+------------------------+
| ... |
+------------------------+
| 返回地址 | <--rsp #没有完整的shellcode,就拼出shellcode,返回地址改为跳转pop_rdi的地址
+------------------------+
| 保存的 RBP | <-- 被覆盖
+------------------------+
| 局部变量 |
+------------------------+
| buf | <-- 从这里开始输入
+------------------------+
低地址
这里解释一下啊pop rdi的作用:
pop rdi # 这条指令做两件事:
# 1. mov rdi, [rsp] ← 把 rsp 指向的8字节给 rdi
# 2. add rsp, 8 ← rsp 增加8,指向下一个位置
简单来说就是把栈顶值给rdi寄存器,然后栈顶地址指向栈的下一个地址(高地址)
之所以要跳转到pop_rdi的地址,是因为system()函数需要参数,而参数需要提前放在对应的寄存器里
**64 位程序调用约定**
第1个参数 → RDI
第2个参数 → RSI
第3个参数 → RDX
第4个参数 → RCX
第5个参数 → R8
第6个参数 → R9
剩下的参数 → 压入栈
所以执行完pop rdi之后我们需要的system()里面的参数已经设置完了,下一条就应该是执行system函数
执行完pop rdi之后,下一条指令按**规定是ret**
ret:
mov rip, [rsp] ; 把 rsp 指向的8字节值赋给 rip 寄存器
add rsp, 8 ; rsp 向上移动8字节
简单来说就是栈顶指针rsp指向的地址作为下一条指令执行的地址,然后栈顶地址指向栈的下一个地址
所以只需要system_addr在binsh_addr 后面就可以执行完整shellcode
最后附图:
高地址
+---------------------------+
| system_addr |
+---------------------------+
| binsh_addr |
+---------------------------+
| pop_rdi_addr |
+---------------------------+
| 保存的 RBP (被覆盖) |
+---------------------------+
| 局部变量 |
+---------------------------+
| buf |
+---------------------------+
低地址