-
-
[原创]Enigma Virtual Box 分析(KCTF2025 第九题 智斗邪首)
-
发表于: 2025-9-18 13:11 3128
-
首先运行htg_Crackme.exe后 附加到进程

然后调试->窗口->模块 列出内存加载的模块

最后保存模块

查看脱壳后的文件,发现.NET.VirtualAddress和.NET.Size都不为0

说明Enigma在运行时修改了0x178和0x17c的值

可以先手动修改.NET.VirtualAddress值为0x400和.NET.Size为0x48,.NET的反编译代码就出来了

虽然可以看到.NET的代码,但是程序无法运行

如何恢复所有的被Enigma修改的PE头,可以在.NET.VirtualAddress的0x00000178处下内存的写断点;
程序断在了sub_1407E5560这个恢复PE头的函数;
首先是恢复PE NumberOfSections = 2 的代码
然后是.text PointerToRawData对齐.text VirtualAddress 和 .rsrc PointerToRawData对齐.rsrc VirtualAddress
最后将.NET.VirtualAddress设置为0x2000和.NET.Size设置为0x48 ,其他的值设置为0;
通过分析可知,AddressOfEntryPoint Exception.VirtualAddress Exception.Size TLS.VirtualAddress TLS.Size 这五个的值为硬编码0;其余的值是通过cs:qword_14084D050+偏移地址动态获取的;
继续下内存的写断点,程序断在了loc_1407AD150处;
loc_1407AD150是一系列的读值赋值操作
动态分析其值的来源发现其来自0x00000001407A8068开始的地址,位于enigma1的TlsCallbacks处;

根据以上的分析可以编写静态脱壳代码:
运行结果
mov rax, [rbp+var_28]sub word ptr [rax+6], 2 ; 0x0000000140000086 = 0x2 (PE NumberOfSections)mov rax, [rbp+var_28]movzx eax, word ptr [rax+6]sub eax, 1test eax, eaxjl short loc_1407E5630mov rax, [rbp+var_28]sub word ptr [rax+6], 2 ; 0x0000000140000086 = 0x2 (PE NumberOfSections)mov rax, [rbp+var_28]movzx eax, word ptr [rax+6]sub eax, 1test eax, eaxjl short loc_1407E5630loc_1407E5600:add [rbp+var_10], 1mov r9, [rbp+var_28]mov edx, [rbp+var_10]imul r8, rdx, 28h ; '('mov rcx, [rbp+var_28]mov edx, [rbp+var_10]imul rdx, 28h ; '('mov edx, [rcx+rdx+114h] ; 0x0000000140000194(.text VirtualAddress) ; 0x00000001400001BC(.rsrc VirtualAddress)mov [r9+r8+11Ch], edx ; 0X000000014000019C = 0X2000(.text PointerToRawData) ; 0X00000001400001C4 = 0X7A6000(.rsrc PointerToRawData)cmp eax, [rbp+var_10]jg short loc_1407E5600loc_1407E5600:add [rbp+var_10], 1mov r9, [rbp+var_28]mov edx, [rbp+var_10]imul r8, rdx, 28h ; '('mov rcx, [rbp+var_28]mov edx, [rbp+var_10]imul rdx, 28h ; '('mov edx, [rcx+rdx+114h] ; 0x0000000140000194(.text VirtualAddress) ; 0x00000001400001BC(.rsrc VirtualAddress)mov [r9+r8+11Ch], edx ; 0X000000014000019C = 0X2000(.text PointerToRawData) ; 0X00000001400001C4 = 0X7A6000(.rsrc PointerToRawData)cmp eax, [rbp+var_10]jg short loc_1407E5600loc_1407E5630:mov r8, [rbp+var_28]mov rax, [rbp+var_28]movzx eax, word ptr [rax+6]imul rdx, rax, 28h ; '('mov rcx, [rbp+var_28]mov rax, [rbp+var_28]movzx eax, word ptr [rax+6]imul rax, 28h ; '('mov edx, [r8+rdx+0ECh]mov eax, [rcx+rax+0E8h]lea ecx, [edx+eax]mov rax, [rbp+var_28]mov edx, [rax+38h]call sub_1407E5510 ; 计算文件大小mov rdx, [rbp+var_28] ; 0X0000000140000080mov [rdx+50h], eax ; 0X00000001400000D0 = 0X7A8000(PE SizeOfImage)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+78h]mov [rdx+90h], eax ; 0X0000000140000110 = 0X0(PE Import.VirtualAddress)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+7Ch]mov [rdx+94h], eax ; 0X0000000140000114 = 0X0(PE Import.Size)mov rax, [rbp+var_28]mov dword ptr [rax+28h], 0 ; 0X00000001400000A8 = 0X0(PE AddressOfEntryPoint)mov rax, [rbp+var_28]mov dword ptr [rax+0A0h], 0 ; 0X0000000140000120 = 0X0(PE Exception.VirtualAddress)mov rax, [rbp+var_28]mov dword ptr [rax+0A4h], 0 ; 0X0000000140000124 = 0X0(PE Exception.Size)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+80h]mov [rdx+0B0h], eax ; 0X0000000140000130 = 0X0(PE Base Reloc.VirtualAddress)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+84h]mov [rdx+0B4h], eax ; 0X0000000140000134 = 0X0(PE Base Reloc.Size)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+88h]mov [rdx+0F8h], eax ; 0X0000000140000178 = 0X2000(PE .NET.VirtualAddress)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+8Ch]mov [rdx+0FCh], eax ; 0X000000014000017C = 0X48(PE .NET.Size)mov rax, [rbp+var_28]mov dword ptr [rax+0D0h], 0 ; 0x0000000140000150 = 0x0(PE TLS.VirtualAddress)mov rax, [rbp+var_28]mov dword ptr [rax+0D4h], 0 ; 0x0000000140000154 = 0x0(PE TLS.Size)mov rax, [rbp+var_28]mov edx, [rax+54h] ; dwSizelea r9, [rbp+flOldProtect] ; lpflOldProtectmov r8d, [rbp+flOldProtect] ; flNewProtectmov rcx, cs:lpAddress ; lpAddresscall VirtualProtectloc_1407E5630:mov r8, [rbp+var_28]mov rax, [rbp+var_28]movzx eax, word ptr [rax+6]imul rdx, rax, 28h ; '('mov rcx, [rbp+var_28]mov rax, [rbp+var_28]movzx eax, word ptr [rax+6]imul rax, 28h ; '('mov edx, [r8+rdx+0ECh]mov eax, [rcx+rax+0E8h]lea ecx, [edx+eax]mov rax, [rbp+var_28]mov edx, [rax+38h]call sub_1407E5510 ; 计算文件大小mov rdx, [rbp+var_28] ; 0X0000000140000080mov [rdx+50h], eax ; 0X00000001400000D0 = 0X7A8000(PE SizeOfImage)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+78h]mov [rdx+90h], eax ; 0X0000000140000110 = 0X0(PE Import.VirtualAddress)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+7Ch]mov [rdx+94h], eax ; 0X0000000140000114 = 0X0(PE Import.Size)mov rax, [rbp+var_28]mov dword ptr [rax+28h], 0 ; 0X00000001400000A8 = 0X0(PE AddressOfEntryPoint)mov rax, [rbp+var_28]mov dword ptr [rax+0A0h], 0 ; 0X0000000140000120 = 0X0(PE Exception.VirtualAddress)mov rax, [rbp+var_28]mov dword ptr [rax+0A4h], 0 ; 0X0000000140000124 = 0X0(PE Exception.Size)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+80h]mov [rdx+0B0h], eax ; 0X0000000140000130 = 0X0(PE Base Reloc.VirtualAddress)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+84h]mov [rdx+0B4h], eax ; 0X0000000140000134 = 0X0(PE Base Reloc.Size)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+88h]mov [rdx+0F8h], eax ; 0X0000000140000178 = 0X2000(PE .NET.VirtualAddress)mov rdx, [rbp+var_28]mov rax, cs:qword_14084D050mov eax, [rax+8Ch]mov [rdx+0FCh], eax ; 0X000000014000017C = 0X48(PE .NET.Size)mov rax, [rbp+var_28]mov dword ptr [rax+0D0h], 0 ; 0x0000000140000150 = 0x0(PE TLS.VirtualAddress)mov rax, [rbp+var_28]mov dword ptr [rax+0D4h], 0 ; 0x0000000140000154 = 0x0(PE TLS.Size)mov rax, [rbp+var_28]mov edx, [rax+54h] ; dwSizelea r9, [rbp+flOldProtect] ; lpflOldProtectmov r8d, [rbp+flOldProtect] ; flNewProtectmov rcx, cs:lpAddress ; lpAddresscall VirtualProtectloc_1407AD150:mov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+68h] ; 0x1407A8068mov [rdx+68h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov rax, [rax+70h] ; 0x1407A8070mov [rdx+70h], raxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+78h] ; 0x1407A8078mov [rdx+78h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+7Ch] ; 0x1407A807Cmov [rdx+7Ch], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+80h] ; 0x1407A8080mov [rdx+80h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+84h] ; 0x1407A8084mov [rdx+84h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+88h] ; 0x1407A8088mov [rdx+88h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+8Ch] ; 0x1407A808Cmov [rdx+8Ch], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+0B0h] ; 0x1407A80B0mov [rdx+90h], eaxmov rdx, cs:qword_14084D050mov rax, cs:qword_14084D050cmp dword ptr [rax+88h], 0setnz byte ptr [rdx+94h]mov rcx, cs:qword_14084CFC0call sub_1407E5560loc_1407AD150:mov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+68h] ; 0x1407A8068mov [rdx+68h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov rax, [rax+70h] ; 0x1407A8070mov [rdx+70h], raxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+78h] ; 0x1407A8078mov [rdx+78h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+7Ch] ; 0x1407A807Cmov [rdx+7Ch], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+80h] ; 0x1407A8080mov [rdx+80h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+84h] ; 0x1407A8084mov [rdx+84h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+88h] ; 0x1407A8088mov [rdx+88h], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+8Ch] ; 0x1407A808Cmov [rdx+8Ch], eaxmov rdx, cs:qword_14084D050mov rax, [rbp+var_30]mov eax, [rax+0B0h] ; 0x1407A80B0mov [rdx+90h], eaxmov rdx, cs:qword_14084D050mov rax, cs:qword_14084D050cmp dword ptr [rax+88h], 0setnz byte ptr [rdx+94h]mov rcx, cs:qword_14084CFC0call sub_1407E5560* 修复字段 脱壳文件偏移 值来源/计算方式 描述* NumberOfSections 0x86 固定值: 0x2 节区数量设置为2* .text PointerToRawData 0x19C 原始文件偏移: 0x194 .text节区的文件偏移* .rsrc PointerToRawData 0x1C4 原始文件偏移: 0x1BC .rsrc节区的文件偏移* SizeOfImage 0xD0 计算值: (原始文件0x7A4690 + 原始文件0x7A4694) 按0x2000对齐 映像大小* AddressOfEntryPoint 0xA8 固定值: 0x0 入口点地址* Exception.VirtualAddress 0x120 固定值: 0x0 异常表虚拟地址* Exception.Size 0x124 固定值: 0x0 异常表大小* TLS.VirtualAddress 0x150 固定值: 0x0 TLS表虚拟地址* TLS.Size 0x154 固定值: 0x0 TLS表大小* Import.VirtualAddress 0x110 原始文件偏移: 0x7A4678 导入表虚拟地址* Import.Size 0x114 原始文件偏移: 0x7A467C 导入表大小* BaseReloc.VirtualAddress 0x130 原始文件偏移: 0x7A4680 重定位表虚拟地址* BaseReloc.Size 0x134 原始文件偏移: 0x7A4684 重定位表大小* .NET.VirtualAddress 0x178 原始文件偏移: 0x7A4688 .NET头虚拟地址* .NET.Size 0x17C 原始文件偏移: 0x7A468C .NET头大小* 修复字段 脱壳文件偏移 值来源/计算方式 描述* NumberOfSections 0x86 固定值: 0x2 节区数量设置为2* .text PointerToRawData 0x19C 原始文件偏移: 0x194 .text节区的文件偏移* .rsrc PointerToRawData 0x1C4 原始文件偏移: 0x1BC .rsrc节区的文件偏移* SizeOfImage 0xD0 计算值: (原始文件0x7A4690 + 原始文件0x7A4694) 按0x2000对齐 映像大小* AddressOfEntryPoint 0xA8 固定值: 0x0 入口点地址* Exception.VirtualAddress 0x120 固定值: 0x0 异常表虚拟地址* Exception.Size 0x124 固定值: 0x0 异常表大小* TLS.VirtualAddress 0x150 固定值: 0x0 TLS表虚拟地址* TLS.Size 0x154 固定值: 0x0 TLS表大小* Import.VirtualAddress 0x110 原始文件偏移: 0x7A4678 导入表虚拟地址* Import.Size 0x114 原始文件偏移: 0x7A467C 导入表大小* BaseReloc.VirtualAddress 0x130 原始文件偏移: 0x7A4680 重定位表虚拟地址* BaseReloc.Size 0x134 原始文件偏移: 0x7A4684 重定位表大小* .NET.VirtualAddress 0x178 原始文件偏移: 0x7A4688 .NET头虚拟地址* .NET.Size 0x17C 原始文件偏移: 0x7A468C .NET头大小import structimport osdef mmap(): # 定义输入和输出文件名 input_file = "./htg_Crackme.exe" output_file = "./htg_Crackme.exeunpacked.exe" try: # 打开原始文件 with open(input_file, "rb") as f_in: # 创建输出文件 with open(output_file, "wb") as f_out: # 1. 读取前0x2000字节数据到新文件 first_part = f_in.read(0x2000) f_out.write(first_part) # 2. 定位到原始文件的0x400位置 f_in.seek(0x400) # 3. 计算需要读取的字节数 read_size = 0x7a4000 - 0x400 second_part = f_in.read(read_size) f_out.write(second_part) # 4. 在新文件的0x7A5C00处填充0x00 padding_size = 0x400 padding = b'\x00' * padding_size f_out.write(padding) # 5. 定位到原始文件的0x7a4000位置 f_in.seek(0x7a4000) read_size = 0x7a4600 - 0x7a4000 third_part = f_in.read(read_size) f_out.write(third_part) # # 6. 在新文件的0x7A6600处填充0x00 padding_size = 0xA00 padding = b'\x00' * padding_size f_out.write(padding) print("\n建立内存映射完成!") except FileNotFoundError: print(f"错误: 找不到文件 {input_file}") except Exception as e: print(f"发生错误: {str(e)}")def read_byte_at_offset(file_path, offset): """从文件指定偏移读取1字节值""" with open(file_path, 'rb') as f: f.seek(offset) data = f.read(1) return struct.unpack('<B', data)[0] if data else 0def read_word_at_offset(file_path, offset): """从文件指定偏移读取2字节值""" with open(file_path, 'rb') as f: f.seek(offset) data = f.read(2) return struct.unpack('<H', data)[0] if len(data) == 2 else 0def read_dword_at_offset(file_path, offset): """从文件指定偏移读取4字节值""" with open(file_path, 'rb') as f: f.seek(offset) data = f.read(4) return struct.unpack('<I', data)[0] if len(data) == 4 else 0def write_byte_at_offset(file_path, offset, value): """向文件指定偏移写入1字节值""" with open(file_path, 'r+b') as f: f.seek(offset) f.write(struct.pack('<B', value))def write_word_at_offset(file_path, offset, value): """向文件指定偏移写入2字节值""" with open(file_path, 'r+b') as f: f.seek(offset) f.write(struct.pack('<H', value))def write_dword_at_offset(file_path, offset, value): """向文件指定偏移写入4字节值""" with open(file_path, 'r+b') as f: f.seek(offset) f.write(struct.pack('<I', value))def fix_pe_header(original_file, unpacked_file): """ 修复脱壳后文件的PE头 从原始带壳文件读取值,写入脱壳后文件的PE头相应位置 """ print("开始修复PE头...") # PE NumberOfSections 修复为2 write_word_at_offset(unpacked_file, 0x86, 2) print(f"修复 NumberOfSections: 0x2 (写入脱壳文件 0x86)") # PE .text PointerToRawData 和 .text VirtualAddress 对齐 # PE .rsrc PointerToRawData 和 .rsrc VirtualAddress 对齐 field_mappings = [ (0x194, 0x19C, ".text PointerToRawData"), (0x1BC, 0x1C4, ".rsrc PointerToRawData") ] # 处理每个字段映射 for original_file_offset, pe_header_offset, description in field_mappings: # 从原始文件读取值 value = read_dword_at_offset(original_file, original_file_offset) # 写入脱壳后文件 write_dword_at_offset(unpacked_file, pe_header_offset, value) print(f"修复 {description}: 0x{value:08X} (从原始文件 0x{original_file_offset:08X} 写入脱壳文件 0x{pe_header_offset:08X})") struct_rva = 0x7A4600 size_image_value = read_dword_at_offset(original_file, struct_rva + 0x90) size_image_value_ = read_dword_at_offset(original_file, struct_rva + 0x94) size_image = size_image_value + size_image_value_ mod_value = size_image % 0x2000 size_image = size_image + 0x2000 - mod_value field_mappings = [ (size_image, 0xD0, "SizeOfImage"), (0x0, 0xA8, "AddressOfEntryPoint"), (0x0, 0x120, "Exception.VirtualAddress"), (0x0, 0x124, "Exception.Size"), (0x0, 0x150, "TLS.VirtualAddress"), (0x0, 0x154, "TLS.Size") ] # 处理每个字段映射 for value, pe_header_offset, description in field_mappings: # 写入脱壳后文件 write_dword_at_offset(unpacked_file, pe_header_offset, value) print(f"修复 {description}: 0x{value:08X} (写入脱壳文件 0x{pe_header_offset:08X})") struct_rva = 0x7A4600 # 根据实际文件偏移调整 # 定义需要修复的字段映射 # 格式: (原始文件结构体偏移, 脱壳文件PE头偏移, 描述) field_mappings = [ (0x78, 0x110, "Import.VirtualAddress"), (0x7C, 0x114, "Import.Size"), (0x80, 0x130, "BaseReloc.VirtualAddress"), (0x84, 0x134, "BaseReloc.Size"), (0x88, 0x178, ".NET.VirtualAddress"), (0x8C, 0x17C, ".NET.Size") ] # 处理每个字段映射 for struct_offset, pe_header_offset, description in field_mappings: # 计算原始文件中的绝对偏移 original_file_offset = struct_rva + struct_offset # 从原始文件读取值 value = read_dword_at_offset(original_file, original_file_offset) # 写入脱壳后文件 write_dword_at_offset(unpacked_file, pe_header_offset, value) print(f"修复 {description}: 0x{value:08X} (从原始文件 0x{original_file_offset:08X} 写入脱壳文件 0x{pe_header_offset:08X})") print("PE头修复完成!")def fix(): # 文件路径 original_file = "./dnSpy-net-win64/Crackme.exe" # 原始带壳文件 unpacked_file = "./dnSpy-net-win64/Crackme_unpacked.exe" # 脱壳后的文件 # 检查文件是否存在 if not os.path.exists(original_file): print(f"错误: 找不到原始文件 {original_file}") exit(1) if not os.path.exists(unpacked_file): print(f"错误: 找不到脱壳文件 {unpacked_file}") exit(1) # 修复PE头 fix_pe_header(original_file, unpacked_file)if __name__ == "__main__": mmap() fix()import structimport osdef mmap(): # 定义输入和输出文件名 input_file = "./htg_Crackme.exe" output_file = "./htg_Crackme.exeunpacked.exe" try: # 打开原始文件 with open(input_file, "rb") as f_in: # 创建输出文件 with open(output_file, "wb") as f_out: # 1. 读取前0x2000字节数据到新文件 first_part = f_in.read(0x2000) f_out.write(first_part) # 2. 定位到原始文件的0x400位置 f_in.seek(0x400) # 3. 计算需要读取的字节数 read_size = 0x7a4000 - 0x400 second_part = f_in.read(read_size) f_out.write(second_part) # 4. 在新文件的0x7A5C00处填充0x00 padding_size = 0x400 padding = b'\x00' * padding_size f_out.write(padding)[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
赞赏
- [原创]NETGEAR 路由器环境模拟与漏洞分析 2127
- [原创]TD路由器环境模拟与漏洞分析 2487
- [原创]TD路由器固件分析 2535
- [原创]TD路由器固件解密 1479
- [原创]DLINK路由器命令注入漏洞从1DAY到0DAY 2972