目录
0 总体结构
1 文件头
2 数据
2.1 system section
a) system section page header
b) Section Map page data
c) SectionInfo page data
2.2 Data section
0 总体结构
从整体上看,一个dwg文件可分为2部分:文件头和数据,数据部分在物理上分页(page)存储的,在逻辑上由一个或多个页组成一个段(section),如图所示:
1 文件头
文件头长度为0x100个字节,布局如下:
地址 | 长度(byte) | 描述 |
0x00 | 7 | "AC1018\0" |
0x07 | 4 | 0x00 |
0x0B | 1 | 主版本号 |
0x0C | 1 | 0x00或0x01或0x03 |
0x0D | 4 | 预览图片的地址 |
0x11 | 1 | Dwg version |
0x12 | 1 | Dwg maintance version |
0x13 | 2 | Codepage index |
0x15 | 1 | 0x00 |
0x16 | 1 | App version |
0x17 | 1 | App maintance version |
0x18 | 4 | 安全标志 0x0001:加密数据(除了AcDb:Preview and AcDb:SummaryInfo) 0x0002:加密属性(用于AcDb:Preview and AcDb:SummaryInfo) 0x0010:sign data 0x0020:add timestamp |
0x1C | 4 | 未知 |
0x20 | 4 | SummaryInfo地址,points to summary info page + page header size(0x20) |
0x24 | 4 | VBA工程地址 |
0x28 | 4 | 0x00000080,(似乎是2004header的地址) |
0x2C | 0x54 | 全是0x00 |
0x80 | 0x6C | 加密的数据,称为2004header |
0xEC | 0x14 | 填充数据(据说是魔术字节序列的前0x14字节,未做考证) |
开始的7个字节是DWG版本标志串,以AC开头,后跟4个数字表述版本。对应关系为:
标注串 | AutoCAD的发行版本 |
AC1012 | R13 |
AC1014 | R14 |
AC1015 | 2000 - 2003 |
AC1018 | 2004 - 2006 |
AC1021 | 2007 - 2009 |
AC1024 | 2010 - 2012 |
AC1027 | 2013 - 2017 |
AC1032 | 2018 - 2020 |
从AutoCAD2000开始,dwg版本号每3年做一次升级,所以AC标志串中的数字并不是连续的。
0x80处的0x6C个字节的加密数据,与下列0x6C个魔术字节逐一做XOR运算,即可解密。
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
这0x6C个魔术字节可以用下面的代码得到
unsigned char pc[0x6c];
int randseed = 1;
for (int i=0; i<0x6c; i++) {
randseed *= 0x343fd;
randseed += 0x269ec3;
pc[i] = (unsigned char)(randseed>>0x10);
}
经过以上运算解密后的数据布局如下表,称之为ACAD2004HeaderData,这段数据在文件尾部还有一份,即SecondHeaderData。
字地址 | 长度(byte) | 描述 |
0x00 | 12 | "AcFssFcAJMB\0" 标志串 |
0x0C | 4 | 0x00 (long) header address |
0x10 | 4 | 0x6c(long) header size |
0x14 | 4 | 0x04 (long) |
0x18 | 4 | Root tree node gap |
0x1C | 4 | Lowermost left tree node gap |
0x20 | 4 | Lowermost right tree node gap |
0x24 | 4 | 未知(long) |
0x28 | 4 | Last section id |
0x2C | 4 | Last section address |
0x30 | 4 | 0x00 |
0x34 | 4 | Second header address |
0x38 | 4 | 0x00 |
0x3C | 4 | Gap 总数 |
0x40 | 4 | Section总数 |
0x44 | 4 | 0x20 (long) |
0x48 | 4 | 0x80(long) |
0x4C | 4 | 0x40(long) |
0x50 | 4 | Page Map Id |
0x54 | 4 | Page Map Address 该值是以数据部分为起点的偏移,在整个文件中的偏移需要加上文件头的大小(0x100) |
0x58 | 4 | 0x00 |
0x5C | 4 | Section map Id |
0x60 | 4 | Section page array size |
0x64 | 4 | Gap array size |
0x68 | 4 | CRC32 (long) |
在这些数据当中,对解析最重要的当属标红的3个数据了。
(5.25,补充一下文件头的解析)
解析2004的文件头比较简单,只需要很少的代码。先定义如下的文件头结构:
#pragma pack(push, 1)
typedef struct _tagDwg2004Header
{ // 务必保持成员的顺序!
char version[7]; // 0x00: "AC1018\0"
char x07_unknown_0[4]; // 0x07: 4zeros
char is_maint; // 0x0B
char x0c_0_1_3; // 0x0C: 0 or 1 or 3
uint32_t thumbnail_addr; // 0x0D
char dwg_version; // 0x11
char dwg_maint_version; // 0x12
short codepage; // 0x13
char x15_unknown_0; // 0x15
char app_version; // 0x16
char app_maint_version; // 0x17
uint32_t security_type; // 0x18
uint32_t x1c_unknown_0; // 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: 0
union
{
char encrypted_data[0x6c]; // 0x80
struct _r2004
{
char file_id_string[12]; // "AcFssFcAJMB\0"
uint32_t header_address; // 0x00
uint32_t header_size; // 0x6c
uint32_t x14_unknown_0; // 0
uint32_t root_tree_node_gap;
uint32_t lowermost_left_tree_node_gap;
uint32_t lowermost_right_tree_node_gap;
uint32_t x24_uknown;
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; // offset from DATA, not include this HEADER size
uint32_t section_map_id;
uint32_t section_array_size;
uint32_t gap_array_size;
uint32_t crc32;
} r2004;
};
char padding[0x14];
} Dwg2004Header; // total 0x100 bytes
#pragma pack(pop)
解析的代码:
{
// 说明:m_header的类型是上面定义的Dwg2004Header,DWG_DATA是dwg文件的原始数据
memcpy(&m_header, DWG_DATA, sizeof(Dwg2004Header));
// decrypt r2004 header
int rseed = 1;
for (int i = 0; i < 0x6c; i++)
{
rseed *= 0x343fd;
rseed += 0x269ec3;
m_header.encrypted_data[i] ^= (rseed >> 0x10);
}
}
2 数据
dwg的数据是分段(section)组织的,文件中涉及到的数据段有:
/// \enum Section type of R2004+
typedef enum _tagDwg2004SectionType
{
SECTION_UNKNOWN = 0, ///< The very first 160 byte
SECTION_HEADER = 1, ///< AcDb:Header
SECTION_AUXHEADER = 2, ///< AcDb:AuxHeader
SECTION_CLASSES = 3, ///< AcDb:Classes
SECTION_HANDLES = 4, ///< AcDb:Handles
SECTION_TEMPLATE = 5, ///< AcDb:Template
SECTION_OBJFREESPACE = 6, ///< AcDb:ObjFreeSpace
SECTION_OBJECTS = 7, ///< AcDb:AcDbObjects
SECTION_REVHISTORY = 8, ///< AcDb:RevHistory
SECTION_SUMMARYINFO = 9, ///< AcDb:SummaryInfo
SECTION_PREVIEW = 10, ///< AcDb:Preview
SECTION_APPINFO = 11, ///< AcDb:AppInfo
SECTION_APPINFOHISTORY = 12, ///< AcDb:AppInfoHistory
SECTION_FILEDEPLIST = 13, ///< AcDb:FileDepList
SECTION_SECURITY, ///< AcDb:Security, if stored with a password
SECTION_VBAPROJECT, ///< AcDb:VBAProject
SECTION_SIGNATURE, ///< AcDb:Signature
SECTION_ACDS, ///< AcDb:AcDsPrototype_1b = 12 (ACIS datastorage)
SECTION_SECTIONMAP, ///< system section: section map
SECTION_PAGEMAP, ///< system section: page map
} Dwg2004SectionType;
所有的section中,page map段和section map段,这2个section至关重要,必须先根据ACAD2004HeaderData中标红的三个参数从文件中读取,再根据读到的结果,一一读取不同的数据段予以解析。
首先从page map address处读取pagen map,然后从此map中取得section map Id对应的段(即section map section)的地址,并从文件中读出它的数据。这两种section统称为system section,其他的section则称为data section。之后,由这两兄弟配合就可以通过section name确定各种section的数据页在文件中的位置,根据描述信息予以读取。
system section都只有一页,而data section 至少有一页。
2.1 system section
system section的数据是按页(page)组织的。一个page包含2部分,页头和页数据:
项目 | 长度 |
header | 0x14bytes |
data | 压缩数据,长度由header.CompDataSize指定 |
其中header对于两种system section来说,结构是一样的,差别在于data的结构的不同。
a) system section page header
Header的结构:
地址(偏移) | 长度(byte) | 含义 |
0x00 | 4 | Page 类型,有2种system page: page map page: 0x41630e3b section map page: 0x4163003b |
0x04 | 4 | 解压后的数据长度 (DecompDataSize) |
0x08 | 4 | 压缩后的数据长度 (CompDataSize) |
0x0C | 4 | 压缩类型(0x02) |
0x10 | 4 | CRC |
当 page.CompDataSize==0时,这个system section就结束了,后面也不会再跟随page data。如下图所示就是system section的组织形式:
b) Page Map page data
page map段的页数据经解压缩后,为多个(page number, page size)数据对,其中的page number从1开始计数,该数据对记录的是文件中的各数据页的编号和大小。
地址 | 长度 | 描述 |
0x00 | 4 | page number, 1-based |
0x04 | 4 | page size |
这种数据对一直重复,直到本页的数据耗尽。在读取时,可以顺便计算出各section在文件中的绝对偏移地址:第一个读到的section的地址总是从文件头后(即0x100处)开始,后续读到的section的偏移可从其前一个section的偏移和大小计算得到。
c) Section Mappage data
从上面的pagemap只能得到已知某个编号的page的位置和大小,无法得到该page的具体信息,包括压缩否,加密否等等,这时就该section map上场了。section map的page.data解压缩后的布局如下,这是一个复合结构:
偏移 | 长度 | 含义 | ||
0x00 | 4 | Number of descriptions(num_descs) | ||
0x04 | 4 | Compressed?(1=no, 2=yes, normally 2) | ||
0x08 | 4 | Max_size(normally 0x7400) | ||
0x0C | 4 | Excrypted?(0=no, 1=yes, 2=unknown) | ||
0x10 | 4 | Num descriptions 2 | ||
共有num_descs组 | 0x00 | 8 | Size of section | |
0x08 | 4 | Number of pages(num_pages) | ||
0x0C | 4 | Max decompress size | ||
0x10 | 4 | 未知(会不会是long型的Max decompress size的高位?) | ||
0x14 | 4 | Compressed?(1=no,2=yes,normally 2) | ||
0x18 | 4 | Section type | ||
0x1C | 4 | Encrypted?(0=no,1=yes,2=unknow) | ||
0x20 | 64 | Section name ("AcDbObjects"等等) | ||
共有 num_pages组 | 0x00 | 4 | page number | |
0x04 | 4 | page data size | ||
0x08 | 8 | Start offset |
对system section的归纳图示:
下面是一个解析实例中,关于AcDbObjects的section map的描述。
Description No.7
------------------
size: 0x2b0e9
num_pages: 0x6
max_decomp_size: 0x7400
unknown2: 0x1
compressed: 0x2
type: 0x7
encrypted: 0x0
name: AcDb:AcDbObjects
No.1
.............
page number: 6
data size: 0x30ea
offset: 0x0
unknown: 0x0
No.2
.............
page number: 7
data size: 0x7f0
offset: 0x7400
No.3
.............
number: 8
data size: 0x628
offset: 0xe800
No.4
.............
page number: 9
data size: 0xf1f
offset: 0x15c00
No.5
.............
page number: 10
data size: 0xcc0
offset: 0x1d000
No.6
.............
page number: 11
data size: 0xe0c
offset: 0x24400
2.2 Data section page
data section page的布局为:
项目 | 长度 | 含义 |
Header | 0x20 | 加密头 |
Data | DataSize | 数据 |
Padding | page size - DataSzie -0x20 | 填充 |
对加密头解密后的数据结构为:
偏移 | 长度 | 含义 |
0x00 | 4 | Tag of data section (0x4163043B) |
0x04 | 4 | Section type |
0x08 | 4 | DataSize, Data的长度 |
0x0C | 4 | page size 本page的总长度(包括header,data和padding) |
0x10 | 8 | offset,Data解压后在解压缓冲中的存放偏移 |
0x18 | 4 | Check sum 1 |
0x1C | 4 | Check sum 2 |
加密头的解密方法:
假设0x20长度的头数据存放在header[8]中,offset是本page在dwg文件中的绝对偏移,可从page map中得到。
int32_t header[8];
int32_t mask = 0x4164536b ^ offset;
for (int i=0; i<8; ++i)
{
header[i] ^= mask;
}
从中可以看出Autodesk也挺鸡贼的,还整了一个动态加密。
Data section page的图示:
当要读取某种类型的数据时,先从Setion map中查找此类型的description,依据description中的page列表,然后透过page map定位到各page,依次读取page数据,将之解压且存放到目的缓冲的startoffset处,最后拼接出此类型数据的数据流供后续解析。
大致过程如下:
附一个实例示意图:
关于dwg文件的结构性的解析,至此就算完成了。