目录
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的发行版本AC1012R13AC1014R14AC10152000 - 2003AC10182004 - 2006AC10212007 - 2009AC10242010 - 2012AC10272013 - 2017AC10322018 - 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文件的结构性的解析,至此就算完成了。