DWG 2004 文件头解析实战:0x6C 加密数据 XOR 解密与 3 个关键偏移量定位

DWG 2004 文件头解析实战:0x6C 加密数据 XOR 解密与 3 个关键偏移量定位

1. DWG 文件头结构概述

DWG 2004 文件头是一个固定长度为 0x100 字节的数据块,位于文件起始位置。这个头部包含了版本标识、安全标志以及指向文件其他关键部分的指针。对于逆向工程师和 CAD 开发者来说,理解这个文件头的结构是解析整个 DWG 文件的第一步。

文件头的主要组成部分包括:

  • 版本标识:位于 0x00 偏移处,7 字节的字符串 "AC1018\0"
  • 安全标志:位于 0x18 偏移处,4 字节的位掩码
  • 加密数据块:位于 0x80 偏移处,0x6C 字节的 XOR 加密数据
  • 填充数据:位于 0xEC 偏移处,0x14 字节的魔术字节序列

以下是 DWG 2004 文件头的完整内存布局:

偏移量长度(字节)描述
0x007"AC1018\0" 版本标识
0x074保留字段(全零)
0x0B1主版本号
0x0C1标志位(0/1/3)
0x0D4预览图片地址
0x111DWG 版本号
0x121DWG 维护版本号
0x132代码页索引
0x151保留字段
0x161应用版本号
0x171应用维护版本号
0x184安全标志位
0x1C4未知字段
0x204SummaryInfo 地址
0x244VBA 工程地址
0x2842004 头部地址(固定为 0x80)
0x2C0x54保留字段(全零)
0x800x6C加密的 2004 头部数据
0xEC0x14填充数据

2. 0x6C 加密数据 XOR 解密算法

位于 0x80 偏移处的 0x6C 字节数据是 DWG 文件解析的关键,这部分数据使用了一种基于伪随机数生成的 XOR 加密算法。解密过程需要与特定的魔术字节序列进行逐字节异或运算。

2.1 魔术字节序列生成

魔术字节序列可以通过以下算法生成:

unsigned char magic_bytes[0x6C]; int seed = 1; for (int i = 0; i < 0x6C; i++) { seed = seed * 0x343FD + 0x269EC3; magic_bytes[i] = (unsigned char)(seed >> 0x10); }

生成的魔术字节序列如下(十六进制表示):

29 23 BE 84 E1 6C D6 AE 52 90 49 F1 F1 BB E9 EB B3 A6 DB 3C 87 0C 3E 99 24 5E 0D 1C 06 B7 47 DE B3 12 4D C8 43 BB 8B A6 1F 03 5A 7D 09 38 25 1F 5D D4 CB FC 96 F5 45 3B 13 0D 89 0A 1C DB AE 32 20 9A 50 EE 40 78 36 FD 12 49 32 F6 9E 7D 49 DC AD 4F 14 F2 44 40 66 D0 6B C4 30 B7 32 3B A1 22 F6 22 91 9D E1 8B 1F DA B0 CA 99 02

2.2 XOR 解密实现

解密过程就是将加密数据与魔术字节序列逐字节进行异或运算。以下是 C 语言实现示例:

void decrypt_2004_header(unsigned char* encrypted_data) { int seed = 1; for (int i = 0; i < 0x6C; i++) { seed = seed * 0x343FD + 0x269EC3; encrypted_data[i] ^= (seed >> 0x10); } }

2.3 解密后的数据结构

解密后的 0x6C 字节数据具有以下结构:

偏移量长度(字节)描述
0x0012"AcFssFcAJMB\0" 标志串
0x0C4头部地址(通常为 0x00)
0x104头部大小(0x6C)
0x144保留字段(通常为 0x04)
0x184根树节点间隙
0x1C4最左下树节点间隙
0x204最右下树节点间隙
0x244未知字段
0x284最后段 ID
0x2C4最后段地址
0x304保留字段(全零)
0x344第二头部地址
0x384保留字段(全零)
0x3C4间隙总数
0x404段总数
0x444保留字段(通常为 0x20)
0x484保留字段(通常为 0x80)
0x4C4保留字段(通常为 0x40)
0x504页映射 ID
0x544页映射地址(相对于数据部分的偏移)
0x584保留字段(全零)
0x5C4段映射 ID
0x604段页数组大小
0x644间隙数组大小
0x684CRC32 校验值

3. 关键偏移量定位与解析

解密后的数据中有三个关键偏移量对解析整个 DWG 文件至关重要:

  1. 页映射地址 (Page Map Address):位于 0x54 偏移处
  2. 段映射 ID (Section Map ID):位于 0x5C 偏移处
  3. 段页数组大小 (Section Page Array Size):位于 0x60 偏移处

3.1 页映射解析

页映射地址指向的是文件中的数据页索引表。需要注意的是,这个地址是相对于数据部分(即文件头之后)的偏移量,因此实际文件偏移需要加上文件头大小(0x100)。

页映射的结构如下:

typedef struct { uint32_t page_number; // 页编号(从1开始) uint32_t page_size; // 页大小 } PageMapEntry;

解析页映射的代码示例:

void parse_page_map(FILE* file, uint32_t page_map_offset) { fseek(file, 0x100 + page_map_offset, SEEK_SET); PageMapEntry entry; uint32_t current_offset = 0x100; // 第一个段从文件头后开始 while (fread(&entry, sizeof(PageMapEntry), 1, file) == 1) { printf("Page %u: size=0x%X, file_offset=0x%X\n", entry.page_number, entry.page_size, current_offset); current_offset += entry.page_size; } }

3.2 段映射解析

段映射包含了文件中所有段(section)的详细信息。要找到段映射,需要:

  1. 通过页映射找到段映射 ID 对应的页
  2. 解析该页的数据获取段映射信息

段映射页的数据结构如下:

typedef struct { uint32_t num_descriptions; // 描述数量 uint32_t compressed; // 是否压缩(1=否,2=是) uint32_t max_size; // 最大大小(通常为0x7400) uint32_t encrypted; // 是否加密(0=否,1=是) uint32_t num_descriptions_2; // 描述数量(重复) } SectionMapHeader; typedef struct { uint64_t section_size; // 段大小 uint32_t num_pages; // 页数量 uint32_t max_decomp_size; // 最大解压大小 uint32_t unknown; // 未知字段 uint32_t compressed; // 是否压缩(1=否,2=是) uint32_t section_type; // 段类型 uint32_t encrypted; // 是否加密(0=否,1=是) char name[64]; // 段名称 } SectionDescription; typedef struct { uint32_t page_number; // 页编号 uint32_t page_data_size; // 页数据大小 uint64_t start_offset; // 起始偏移 } SectionPageInfo;

3.3 完整解析流程

结合上述三个关键偏移量,完整的 DWG 文件解析流程如下:

  1. 读取并解析文件头(0x100 字节)
  2. 解密 0x80 处的 0x6C 字节加密数据
  3. 从解密数据中获取页映射地址、段映射 ID 和段页数组大小
  4. 通过页映射地址找到页映射表
  5. 通过段映射 ID 在页映射表中找到段映射页
  6. 解析段映射页获取所有段的信息
  7. 根据需要访问特定段的数据

4. 实战代码示例

以下是一个完整的 DWG 2004 文件头解析代码示例,包含结构体定义和解密算法:

#include <stdio.h> #include <stdint.h> #include <string.h> #pragma pack(push, 1) typedef struct { char version[7]; // 0x00: "AC1018\0" char x07_unknown[4]; // 0x07: 4 zeros uint8_t is_maint; // 0x0B uint8_t x0c_0_1_3; // 0x0C: 0 or 1 or 3 uint32_t thumbnail_addr;// 0x0D uint8_t dwg_version; // 0x11 uint8_t dwg_maint_version; // 0x12 uint16_t codepage; // 0x13 uint8_t x15_unknown; // 0x15 uint8_t app_version; // 0x16 uint8_t app_maint_version; // 0x17 uint32_t security_type; // 0x18 uint32_t x1c_unknown; // 0x1C uint32_t summary_info_address; // 0x20 uint32_t vba_proj_address; // 0x24 uint32_t r2004_header_address; // 0x28: 0x80 char x2c_0[0x54]; // 0x2C: 0x54 zeros union { char encrypted_data[0x6C]; // 0x80 struct { char file_id_string[12]; // "AcFssFcAJMB\0" uint32_t header_address; uint32_t header_size; uint32_t x14_unknown; uint32_t root_tree_node_gap; uint32_t lowermost_left_tree_node_gap; uint32_t lowermost_right_tree_node_gap; uint32_t x24_unknown; uint32_t last_section_id; uint64_t last_page_address; uint64_t second_header_address; uint32_t gap_amount; uint32_t page_amount; uint32_t x44_x20; uint32_t x48_x80; uint32_t x4c_x40; uint32_t page_map_id; uint64_t page_map_address; uint32_t section_map_id; uint32_t section_array_size; uint32_t gap_array_size; uint32_t crc32; } r2004; }; char padding[0x14]; // 0xEC: 0x14 bytes } Dwg2004Header; #pragma pack(pop) void decrypt_2004_header(Dwg2004Header* header) { int seed = 1; for (int i = 0; i < 0x6C; i++) { seed = seed * 0x343FD + 0x269EC3; header->encrypted_data[i] ^= (seed >> 0x10); } } void print_header_info(const Dwg2004Header* header) { printf("DWG Version: %.6s\n", header->version); printf("Security Flags: 0x%08X\n", header->security_type); printf("Page Map ID: %u\n", header->r2004.page_map_id); printf("Page Map Address: 0x%08X\n", (uint32_t)header->r2004.page_map_address); printf("Section Map ID: %u\n", header->r2004.section_map_id); printf("Section Array Size: %u\n", header->r2004.section_array_size); } int main(int argc, char** argv) { if (argc < 2) { printf("Usage: %s <dwg_file>\n", argv[0]); return 1; } FILE* file = fopen(argv[1], "rb"); if (!file) { perror("Failed to open file"); return 1; } Dwg2004Header header; if (fread(&header, sizeof(header), 1, file) != 1) { perror("Failed to read header"); fclose(file); return 1; } decrypt_2004_header(&header); print_header_info(&header); fclose(file); return 0; }

5. 调试技巧与常见问题

5.1 调试技巧

  1. 十六进制查看器:使用 HxD 或 010 Editor 等工具直接查看 DWG 文件二进制内容
  2. CRC 校验:解密后的数据包含 CRC32 校验值,可用于验证解密是否正确
  3. 逐步验证:先验证文件头基本结构,再逐步验证解密数据和关键偏移量

5.2 常见问题

  1. 解密失败:检查魔术字节序列生成算法是否正确,特别是种子值和移位操作
  2. 偏移量计算错误:注意页映射地址是相对于数据部分的偏移,需要加上 0x100
  3. 字节序问题:DWG 文件使用小端字节序,在某些平台上可能需要转换

提示:在实际项目中,建议先对小型 DWG 文件进行测试,验证解析逻辑正确性后再处理大型文件。