应用安全 --- IDA签名 之 Sig文件解析

variant_mask = 0x780F00 怎么理解
node_length = 32,说明完整匹配模式有 32 个位置。
variant_mask = 0x780F00 表示其中 8 个位置是可变字节,也就是模板里显示的 ..。
可变位置是:
| 编号方式 | 可变位置 |
|---|---|
| 从 0 开始数 | 9, 10, 11, 12, 20, 21, 22, 23 |
| 从 1 开始数 | 10, 11, 12, 13, 21, 22, 23, 24 |
所以完整 32 字节模式是:
49 89 D0 0F B6 01 48 8D 0D .. .. .. .. 48 8B 14 C1 4C 8D 0D .. .. .. .. 49 63 0C 81 4C 01 C9 E9
通俗理解:
固定字节必须完全相等;
.. 位置可以变化,不参与精确匹配。
5. 32 字节模式逐位置解释
| 模式位置 0-based | 模式位置 1-based | 是否固定 | 对应字节 | 含义 |
|---|---|---|---|---|
| 0 | 1 | 固定 | 49 |
必须匹配 |
| 1 | 2 | 固定 | 89 |
必须匹配 |
| 2 | 3 | 固定 | D0 |
必须匹配 |
| 3 | 4 | 固定 | 0F |
必须匹配 |
| 4 | 5 | 固定 | B6 |
必须匹配 |
| 5 | 6 | 固定 | 01 |
必须匹配 |
| 6 | 7 | 固定 | 48 |
必须匹配 |
| 7 | 8 | 固定 | 8D |
必须匹配 |
| 8 | 9 | 固定 | 0D |
必须匹配 |
| 9 | 10 | 可变 | .. |
跳过,不固定匹配 |
| 10 | 11 | 可变 | .. |
跳过,不固定匹配 |
| 11 | 12 | 可变 | .. |
跳过,不固定匹配 |
| 12 | 13 | 可变 | .. |
跳过,不固定匹配 |
| 13 | 14 | 固定 | 48 |
必须匹配 |
| 14 | 15 | 固定 | 8B |
必须匹配 |
| 15 | 16 | 固定 | 14 |
必须匹配 |
| 16 | 17 | 固定 | C1 |
必须匹配 |
| 17 | 18 | 固定 | 4C |
必须匹配 |
| 18 | 19 | 固定 | 8D |
必须匹配 |
| 19 | 20 | 固定 | 0D |
必须匹配 |
| 20 | 21 | 可变 | .. |
跳过,不固定匹配 |
| 21 | 22 | 可变 | .. |
跳过,不固定匹配 |
| 22 | 23 | 可变 | .. |
跳过,不固定匹配 |
| 23 | 24 | 可变 | .. |
跳过,不固定匹配 |
| 24 | 25 | 固定 | 49 |
必须匹配 |
| 25 | 26 | 固定 | 63 |
必须匹配 |
| 26 | 27 | 固定 | 0C |
必须匹配 |
| 27 | 28 | 固定 | 81 |
必须匹配 |
| 28 | 29 | 固定 | 4C |
必须匹配 |
| 29 | 30 | 固定 | 01 |
必须匹配 |
| 30 | 31 | 固定 | C9 |
必须匹配 |
| 31 | 32 | 固定 | E9 |
必须匹配 |
重点:文件里只保存了 24 个固定字节,因为 8 个可变位置不保存具体值。
6. 固定字节 concrete_bytes 对应关系
原始固定字节区:
0x49-0x60:
49 89 D0 0F B6 01 48 8D 0D 48 8B 14 C1 4C 8D 0D 49 63 0C 81 4C 01 C9 E9
它不是连续的 32 字节机器码,而是“去掉可变位置后的 24 个固定字节”。
| concrete 偏移 | 字节 | 放回 32 字节模式的位置 |
|---|---|---|
0x49 |
49 |
0 |
0x4A |
89 |
1 |
0x4B |
D0 |
2 |
0x4C |
0F |
3 |
0x4D |
B6 |
4 |
0x4E |
01 |
5 |
0x4F |
48 |
6 |
0x50 |
8D |
7 |
0x51 |
0D |
8 |
0x52 |
48 |
13 |
0x53 |
8B |
14 |
0x54 |
14 |
15 |
0x55 |
C1 |
16 |
0x56 |
4C |
17 |
0x57 |
8D |
18 |
0x58 |
0D |
19 |
0x59 |
49 |
24 |
0x5A |
63 |
25 |
0x5B |
0C |
26 |
0x5C |
81 |
27 |
0x5D |
4C |
28 |
0x5E |
01 |
29 |
0x5F |
C9 |
30 |
0x60 |
E9 |
31 |
模块是什么
叶子下面可以挂多个 MODULE,再用 CRC、引用、尾部字节等继续区分。
1. 是否还要读取“尾部字节约束”
对应:
bit1 = READ_TAIL_BYTES
通俗解释:
前面的 32 字节模式还不够区分函数时,IDA 可以再额外检查几个指定位置的字节。
它不是简单指“函数最后几个字节”,更准确地说是:
在函数的某些相对偏移位置,再抽查几个字节。
比如假设有两个函数前 32 字节几乎一样:
函数 A:
49 89 D0 ... 前 32 字节一样
函数 B:
49 89 D0 ... 前 32 字节也一样
IDA 只靠前 32 字节可能分不清。
这时 .sig 可以额外保存类似:
偏移 0x35 处必须是 0x90
偏移 0x41 处必须是 0xC3
这样 IDA 再多检查几个字节,就能确认到底是哪一个函数。FLIRT 的匹配通常会经过前部模式、CRC、额外尾部/判别字节等步骤来缩小候选函数。
但是你这个文件:
parse_flags = 0x00
bit1 = 0
所以表示:
没有尾部字节约束。
IDA 不需要继续读取 tail bytes。
在你的文件里,识别这个函数只靠:
32 字节模式 + 当前模块信息 + 函数名记录
没有额外抽查字节。
2. 是否还要读取“引用函数信息”
对应:
bit2 = READ_REFERENCED_FUNCS
通俗解释:
这个签名除了记录当前函数名,还可以记录它关联/引用到的其他函数信息。
比如一个库函数内部可能调用了另一个库函数:
函数 A 内部调用 函数 B
.sig 里可以附加记录:
在某个相对位置,存在一个被引用函数 B
这样 IDA 可以利用“函数之间的关系”辅助识别,或者给相关位置补充名称。
可以把它理解成:
不只看这个函数自己的字节,
还看它和其他函数之间有没有关系。
但是你这个文件:
parse_flags = 0x00
bit2 = 0
所以表示:
没有引用函数信息。
IDA 不需要继续读取 referenced funcs。
也就是这个 .sig 只告诉 IDA:
匹配到这个模式后,把当前函数命名为这个 Rust 符号名。
没有再告诉 IDA:
它还引用了谁;
它还关联了哪个函数;
它还需要给其他函数命名。
3. 同一个 CRC 下面是否还有其他模块
对应:
bit3 = MORE_MODULES_SAME_CRC
通俗解释:
当前 CRC 分组下面,是否还有另一个函数模块记录?
在 FLIRT 里,一个叶子节点下面可能有多个候选函数。
这些候选函数可能前 32 字节模式相同,CRC 也相同,于是它们会被放在同一个 CRC 组下面。
类似这样:
同一个 pattern
└── CRC = 0x1234
├── module 1: 函数 A
├── module 2: 函数 B
└── module 3: 函数 C
如果 bit3 = 1,意思是:
当前 module 读完了,
但同一个 CRC 下面还有下一个 module,
继续读。
但是你这个文件:
parse_flags = 0x00
bit3 = 0
所以表示:
同一个 CRC 下面没有其他模块了。
你的文件实际是:
CRC 组:
crc_len = 0
crc16 = 0x0000
└── module 1
└── public name 1
只有一个模块,没有第二个。
4. 后面是否还有新的 CRC 组 / 模块组
对应:
bit4 = MORE_MODULES
通俗解释:
当前 CRC 组结束后,是否还有另一个新的 CRC 组?
比如一个叶子节点下面可能有多组 CRC:
同一个 pattern
├── CRC 组 1: crc16 = 0x1111
│ └── 函数 A
├── CRC 组 2: crc16 = 0x2222
│ └── 函数 B
└── CRC 组 3: crc16 = 0x3333
└── 函数 C
如果 bit4 = 1,意思是:
当前 CRC 组结束了,
但后面还有新的 CRC 组,
继续读下一个 module group。
但是你这个文件:
parse_flags = 0x00
bit4 = 0
所以表示:
后面没有新的 CRC 组了。
整个 module list 结束。
掩码如何计算
variant_mask = 0x780F00 怎么理解
node_length = 32,说明完整匹配模式有 32 个位置。
variant_mask = 0x780F00 表示其中 8 个位置是可变字节,也就是模板里显示的 ..。
可变位置是:
| 编号方式 | 可变位置 |
|---|---|
| 从 0 开始数 | 9, 10, 11, 12, 20, 21, 22, 23 |
| 从 1 开始数 | 10, 11, 12, 13, 21, 22, 23, 24 |
所以完整 32 字节模式是:
49 89 D0 0F B6 01 48 8D 0D .. .. .. .. 48 8B 14 C1 4C 8D 0D .. .. .. .. 49 63 0C 81 4C 01 C9 E9
通俗理解:
固定字节必须完全相等;
.. 位置可以变化,不参与精确匹配。
5. 32 字节模式逐位置解释
| 模式位置 0-based | 模式位置 1-based | 是否固定 | 对应字节 | 含义 |
|---|---|---|---|---|
| 0 | 1 | 固定 | 49 |
必须匹配 |
| 1 | 2 | 固定 | 89 |
必须匹配 |
| 2 | 3 | 固定 | D0 |
必须匹配 |
| 3 | 4 | 固定 | 0F |
必须匹配 |
| 4 | 5 | 固定 | B6 |
必须匹配 |
| 5 | 6 | 固定 | 01 |
必须匹配 |
| 6 | 7 | 固定 | 48 |
必须匹配 |
| 7 | 8 | 固定 | 8D |
必须匹配 |
| 8 | 9 | 固定 | 0D |
必须匹配 |
| 9 | 10 | 可变 | .. |
跳过,不固定匹配 |
| 10 | 11 | 可变 | .. |
跳过,不固定匹配 |
| 11 | 12 | 可变 | .. |
跳过,不固定匹配 |
| 12 | 13 | 可变 | .. |
跳过,不固定匹配 |
| 13 | 14 | 固定 | 48 |
必须匹配 |
| 14 | 15 | 固定 | 8B |
必须匹配 |
| 15 | 16 | 固定 | 14 |
必须匹配 |
| 16 | 17 | 固定 | C1 |
必须匹配 |
| 17 | 18 | 固定 | 4C |
必须匹配 |
| 18 | 19 | 固定 | 8D |
必须匹配 |
| 19 | 20 | 固定 | 0D |
必须匹配 |
| 20 | 21 | 可变 | .. |
跳过,不固定匹配 |
| 21 | 22 | 可变 | .. |
跳过,不固定匹配 |
| 22 | 23 | 可变 | .. |
跳过,不固定匹配 |
| 23 | 24 | 可变 | .. |
跳过,不固定匹配 |
| 24 | 25 | 固定 | 49 |
必须匹配 |
| 25 | 26 | 固定 | 63 |
必须匹配 |
| 26 | 27 | 固定 | 0C |
必须匹配 |
| 27 | 28 | 固定 | 81 |
必须匹配 |
| 28 | 29 | 固定 | 4C |
必须匹配 |
| 29 | 30 | 固定 | 01 |
必须匹配 |
| 30 | 31 | 固定 | C9 |
必须匹配 |
| 31 | 32 | 固定 | E9 |
必须匹配 |
重点:文件里只保存了 24 个固定字节,因为 8 个可变位置不保存具体值。
6. 固定字节 concrete_bytes 对应关系
原始固定字节区:
0x49-0x60:
49 89 D0 0F B6 01 48 8D 0D 48 8B 14 C1 4C 8D 0D 49 63 0C 81 4C 01 C9 E9
它不是连续的 32 字节机器码,而是“去掉可变位置后的 24 个固定字节”。
| concrete 偏移 | 字节 | 放回 32 字节模式的位置 |
|---|---|---|
0x49 |
49 |
0 |
0x4A |
89 |
1 |
0x4B |
D0 |
2 |
0x4C |
0F |
3 |
0x4D |
B6 |
4 |
0x4E |
01 |
5 |
0x4F |
48 |
6 |
0x50 |
8D |
7 |
0x51 |
0D |
8 |
0x52 |
48 |
13 |
0x53 |
8B |
14 |
0x54 |
14 |
15 |
0x55 |
C1 |
16 |
0x56 |
4C |
17 |
0x57 |
8D |
18 |
0x58 |
0D |
19 |
0x59 |
49 |
24 |
0x5A |
63 |
25 |
0x5B |
0C |
26 |
0x5C |
81 |
27 |
0x5D |
4C |
28 |
0x5E |
01 |
29 |
0x5F |
C9 |
30 |
0x60 |
E9 |
31 |
010editor模板解析
//------------------------------------------------ //--- 010 Editor Binary Template // File: FLIRT_SIG_full_010Editor.bt // Version: 1.3 (console full dump, stricter mask checks) // Purpose: Parse IDA FLIRT IDASGN signature files (v5-v10) and print all parsed records // Notes: Parses uncompressed Trie bodies. If FEATURE_COMPRESSED is set, // the compressed body is mapped as raw zlib payload and must be // decompressed externally before the Trie can be expanded. // Colors: Uses adaptive Template Styles (010 Editor v14+). // File Mask: *.sig // ID Bytes: 49 44 41 53 47 4E //------------------------------------------------ LittleEndian(); // ----------------------------------------------------------------------------- // Adaptive color legend (010 Editor v14+) // ----------------------------------------------------------------------------- // sHeading1 : FLIRT file header // sSection1 : Trie nodes and pattern bytes // sSection2 : Leaf module groups and CRC data // sSection3 : Public function records and names // sSection4 : Referenced function records and names // sMarker : Tail-byte constraints // sData : Generic variable-length integer payloads // Accent variants highlight key bytes inside each category. // Styles automatically adapt to light/dark themes and can be customized under // Theme/Color Options -> Template Styles. // ----------------------------------------------------------------------------- // Constants // ----------------------------------------------------------------------------- #define FEATURE_STARTUP 0x0001 #define FEATURE_CTYPE_CRC 0x0002 #define FEATURE_2BYTE_CTYPE 0x0004 #define FEATURE_ALT_CTYPE_CRC 0x0008 #define FEATURE_COMPRESSED 0x0010 #define PF_MORE_PUBLIC_NAMES 0x01 #define PF_READ_TAIL_BYTES 0x02 #define PF_READ_REFERENCED_FUNCS 0x04 #define PF_MORE_MODULES_SAME_CRC 0x08 #define PF_MORE_MODULES 0x10 #define FF_LOCAL 0x02 #define FF_UNRESOLVED_COLLISION 0x08 #define MAX_NODE_LEN 0x40 #define MAX_SCAN_NAME 4096 #define MAX_REASONABLE_COUNT 1000000 // ----------------------------------------------------------------------------- // Global statistics // ----------------------------------------------------------------------------- local uint32 g_nodes = 0; local uint32 g_leaves = 0; local uint32 g_modules = 0; local uint32 g_publics = 0; local uint32 g_tails = 0; local uint32 g_refs = 0; local uint32 g_max_depth = 0; local int g_verbose = 2; // 0=summary, 1=node/module summary, 2=print all parsed records local uint32 g_pattern_size = 0; // copied from header for sanity checks // ----------------------------------------------------------------------------- // Utility functions // ----------------------------------------------------------------------------- string Indent(int depth) { local string s; local int i; s = ""; if (depth > 48) depth = 48; for (i = 0; i < depth; i++) s += " "; return s; } uint64 PopCount64(uint64 v) { local uint64 n; n = 0; while (v != 0) { n += (v & 1); v >>= 1; } return n; } uint64 MaskForNodeLen(int node_len) { if (node_len <= 0) return 0; if (node_len >= 64) return 0xFFFFFFFFFFFFFFFF; return (((uint64)1 << node_len) - 1); } uint64 PopCountNodeMask(int node_len, uint64 variant_mask) { local uint64 n; local int i; local uint64 bit; n = 0; for (i = 0; i < node_len && i < 64; i++) { bit = ((uint64)1 << (node_len - 1 - i)); if ((variant_mask & bit) != 0) n++; } return n; } string VariantPos0Text(int node_len, uint64 variant_mask) { local string out; local string tmp; local int i; local int first; local uint64 bit; out = ""; first = 1; for (i = 0; i < node_len && i < 64; i++) { bit = ((uint64)1 << (node_len - 1 - i)); if ((variant_mask & bit) != 0) { if (!first) out += ","; SPrintf(tmp, "%d", i); out += tmp; first = 0; } } if (out == "") return "<none>"; return out; } string VariantPos1Text(int node_len, uint64 variant_mask) { local string out; local string tmp; local int i; local int first; local uint64 bit; out = ""; first = 1; for (i = 0; i < node_len && i < 64; i++) { bit = ((uint64)1 << (node_len - 1 - i)); if ((variant_mask & bit) != 0) { if (!first) out += ","; SPrintf(tmp, "%d", i + 1); out += tmp; first = 0; } } if (out == "") return "<none>"; return out; } string BytesToText(int64 pos, uint64 len, int strip_final_nul) { local string out; local string tmp; local uint64 i; local ubyte b; out = ""; if (strip_final_nul && len > 0 && ReadUByte(pos + len - 1) == 0) len--; for (i = 0; i < len; i++) { b = ReadUByte(pos + i); if (b >= 0x20 && b <= 0x7E) { SPrintf(tmp, "%c", b); } else { SPrintf(tmp, "\\x%02X", b); } out += tmp; } return out; } int ScanUntilControl(int64 pos) { local int n; local ubyte b; n = 0; while ((pos + n) < FileSize() && n < MAX_SCAN_NAME) { b = ReadUByte(pos + n); if (b < 0x20) break; n++; } if (n == MAX_SCAN_NAME) Warning("FLIRT name scan reached MAX_SCAN_NAME; file may be malformed."); return n; } string PatternText(int node_len, uint64 variant_mask, int64 concrete_pos) { local string out; local string tmp; local int i; local int j; local uint64 bit; out = ""; j = 0; for (i = 0; i < node_len; i++) { bit = ((uint64)1 << (node_len - 1 - i)); if ((variant_mask & bit) != 0) { out += ".."; } else { SPrintf(tmp, "%02X", ReadUByte(concrete_pos + j)); out += tmp; j++; } if (i + 1 < node_len) out += " "; } return out; } string BytesHex(int64 pos, uint64 len) { local string out; local string tmp; local uint64 i; out = ""; for (i = 0; i < len; i++) { SPrintf(tmp, "%02X", ReadUByte(pos + i)); out += tmp; if (i + 1 < len) out += " "; } return out; } string FeatureText(uint16 f) { local string s; s = ""; if ((f & FEATURE_STARTUP) != 0) s += "STARTUP|"; if ((f & FEATURE_CTYPE_CRC) != 0) s += "CTYPE_CRC|"; if ((f & FEATURE_2BYTE_CTYPE) != 0) s += "2BYTE_CTYPE|"; if ((f & FEATURE_ALT_CTYPE_CRC) != 0) s += "ALT_CTYPE_CRC|"; if ((f & FEATURE_COMPRESSED) != 0) s += "COMPRESSED|"; if (s == "") return "none"; return s; } string ParseFlagText(ubyte f) { local string s; s = ""; if ((f & PF_MORE_PUBLIC_NAMES) != 0) s += "MORE_PUBLIC_NAMES|"; if ((f & PF_READ_TAIL_BYTES) != 0) s += "READ_TAIL_BYTES|"; if ((f & PF_READ_REFERENCED_FUNCS) != 0) s += "READ_REFERENCED_FUNCS|"; if ((f & PF_MORE_MODULES_SAME_CRC) != 0) s += "MORE_MODULES_SAME_CRC|"; if ((f & PF_MORE_MODULES) != 0) s += "MORE_MODULES|"; if (s == "") return "none"; return s; } string FunctionFlagText(ubyte f) { local string s; s = ""; if ((f & FF_LOCAL) != 0) s += "LOCAL|"; if ((f & FF_UNRESOLVED_COLLISION) != 0) s += "UNRESOLVED_COLLISION|"; if (s == "") return "none"; return s; } string SafeName(string s) { if (s == "") return "<empty>"; return s; } // ----------------------------------------------------------------------------- // FLIRT variable-length integer encodings // ----------------------------------------------------------------------------- // read_multiple_bytes(): // 0xxxxxxx -> 1 byte // 10xxxxxx xxxxxxxx -> 2 bytes // 110xxxxx xxxxxxxx xxxxxxxx xxxxxxxx -> 4 bytes // 111xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx -> 5 bytes // In the 5-byte form, b0 is only a marker; the next four bytes are the value. // The 4-byte form masks with 0x3F; because b0 has prefix 110, bit5 is zero. // ----------------------------------------------------------------------------- typedef struct { ubyte b0 <format=hex, style=sDataAccent>; if ((b0 & 0x80) == 0) { // one-byte encoding } else if ((b0 & 0xC0) != 0xC0) { ubyte b1_2 <format=hex, style=sData>; } else if ((b0 & 0xE0) != 0xE0) { ubyte b1_4 <format=hex, style=sData>; ubyte b2_4 <format=hex, style=sData>; ubyte b3_4 <format=hex, style=sData>; } else { ubyte b1_5 <format=hex, style=sData>; ubyte b2_5 <format=hex, style=sData>; ubyte b3_5 <format=hex, style=sData>; ubyte b4_5 <format=hex, style=sData>; } } VLI_MULTI <read=FmtVLIMulti, style=sData>; uint64 GetVLIMulti(VLI_MULTI &v) { if ((v.b0 & 0x80) == 0) return v.b0; if ((v.b0 & 0xC0) != 0xC0) return (((uint64)(v.b0 & 0x7F)) << 8) | v.b1_2; if ((v.b0 & 0xE0) != 0xE0) return (((uint64)(v.b0 & 0x3F)) << 24) | (((uint64)v.b1_4) << 16) | (((uint64)v.b2_4) << 8) | ((uint64)v.b3_4); return (((uint64)v.b1_5) << 24) | (((uint64)v.b2_5) << 16) | (((uint64)v.b3_5) << 8) | ((uint64)v.b4_5); } string FmtVLIMulti(VLI_MULTI &v) { local string s; SPrintf(s, "%Lu (0x%LX)", GetVLIMulti(v), GetVLIMulti(v)); return s; } // Legacy compact integer used by v5-v8 offsets. typedef struct { ubyte b0 <format=hex, style=sDataAccent>; if ((b0 & 0x80) != 0) ubyte b1 <format=hex, style=sData>; } VLI_MAX2 <read=FmtVLIMax2, style=sData>; uint64 GetVLIMax2(VLI_MAX2 &v) { if ((v.b0 & 0x80) == 0) return v.b0; return (((uint64)(v.b0 & 0x7F)) << 8) | v.b1; } string FmtVLIMax2(VLI_MAX2 &v) { local string s; SPrintf(s, "%Lu (0x%LX)", GetVLIMax2(v), GetVLIMax2(v)); return s; } // v9-v10 use read_multiple_bytes() for offsets and lengths; older versions // use the compact one/two-byte encoding. struct ENCODED_VALUE (int version) { if (version >= 9) VLI_MULTI multi <style=sData>; else VLI_MAX2 legacy <style=sData>; }; uint64 GetEncodedValue(ENCODED_VALUE &v, int version) { if (version >= 9) return GetVLIMulti(v.multi); return GetVLIMax2(v.legacy); } // ----------------------------------------------------------------------------- // Header // ----------------------------------------------------------------------------- typedef struct { char magic[6] <style=sHeading1Accent>; ubyte version <style=sHeading1Accent>; ubyte arch <style=sHeading1Accent>; uint32 file_types <format=hex, style=sHeading1>; uint16 os_types <format=hex, style=sHeading1>; uint16 app_types <format=hex, style=sHeading1>; uint16 features <format=hex, style=sHeading1Accent>; uint16 old_n_functions <style=sHeading1>; uint16 header_crc16 <format=hex, style=sHeading1Accent>; ubyte ctype[12] <format=hex, style=sHeading1>; ubyte library_name_len <style=sHeading1Accent>; uint16 ctypes_crc16 <format=hex, style=sHeading1Accent>; if (version >= 6) uint32 n_functions <style=sHeading1>; if (version >= 8) uint16 pattern_size <style=sHeading1>; if (version > 9) uint16 unknown_v10 <format=hex, style=sHeading1>; } FLIRT_HEADER <read=FmtHeader, style=sHeading1>; string ArchName(ubyte arch) { local string s; switch (arch) { case 0: return "x86"; case 12: return "MIPS"; case 13: return "ARM"; case 15: return "PowerPC"; case 19: return ".NET"; case 23: return "SPARC"; case 31: return "IA64"; case 60: return "Dalvik"; default: SPrintf(s, "arch_%u", arch); return s; } } uint32 HeaderFunctionCount(FLIRT_HEADER &h) { if (h.version >= 6) return h.n_functions; return h.old_n_functions; } string FmtHeader(FLIRT_HEADER &h) { local string s; SPrintf(s, "IDASGN v%u arch=%s funcs=%u features=0x%04X", h.version, ArchName(h.arch), HeaderFunctionCount(h), h.features); return s; } // ----------------------------------------------------------------------------- // Leaf records: public names, tail bytes and referenced functions // ----------------------------------------------------------------------------- struct PUBLIC_FUNCTION (int version) { ENCODED_VALUE offset_delta(version) <style=sSection3Accent>; local ubyte probe; local int64 name_pos; local int name_len; local ubyte meta_value; probe = ReadUByte(FTell()); meta_value = 0; if (probe < 0x20) { ubyte function_flags <format=hex, style=sSection3Accent, comment="Optional metadata: bit1=local/static, bit3=unresolved collision">; meta_value = function_flags; } name_pos = FTell(); name_len = ScanUntilControl(name_pos); if (name_len > 0) char name[name_len] <style=sSection3Accent>; // The first control byte after the name is not a NUL terminator in the // general case. It is the module parsing flag byte. ubyte parse_flags <format=hex, style=sSection3Accent, comment="bit0=more public names, bit1=tail bytes, bit2=refs, bit3=more same CRC, bit4=more CRC groups">; }; string PublicName(PUBLIC_FUNCTION &f) { return BytesToText(f.name_pos, f.name_len, 0); } struct TAIL_BYTE (int version) { ENCODED_VALUE relative_offset(version) <style=sMarker>; ubyte value <format=hex, style=sMarkerAccent>; }; struct TAIL_LIST (int version, int depth) { local string ind; local uint32 i; local uint32 count; ind = Indent(depth); count = 1; if (version >= 8) { ubyte count_u8 <style=sMarkerAccent>; count = count_u8; } if (g_verbose >= 2) Printf("%s[TAIL_LIST] count=%u\n", ind, count); for (i = 0; i < count; i++) { TAIL_BYTE tail(version) <style=sMarker>; g_tails++; if (g_verbose >= 2) { Printf("%s [TAIL #%u] rel_off=0x%LX value=0x%02X\n", ind, g_tails, GetEncodedValue(tail.relative_offset, version), tail.value); } } }; struct REFERENCED_FUNCTION (int version) { ENCODED_VALUE relative_offset(version) <style=sSection4>; ubyte short_name_len <style=sSection4Accent>; local uint64 full_name_len; local int64 name_pos; local int negative_offset; full_name_len = short_name_len; if (short_name_len == 0) { VLI_MULTI extended_name_len <style=sSection4Accent>; full_name_len = GetVLIMulti(extended_name_len); } if (full_name_len > MAX_SCAN_NAME) Warning("Referenced function name is unusually long; file may be malformed."); name_pos = FTell(); if (full_name_len > 0) ubyte raw_name[full_name_len] <style=sSection4Accent>; negative_offset = (full_name_len > 0 && ReadUByte(name_pos + full_name_len - 1) == 0); }; string ReferencedName(REFERENCED_FUNCTION &f) { return BytesToText(f.name_pos, f.full_name_len, f.negative_offset); } struct REFERENCED_LIST (int version, int depth) { local string ind; local uint32 i; local uint32 count; ind = Indent(depth); count = 1; if (version >= 8) { ubyte count_u8 <style=sSection4Accent>; count = count_u8; } if (g_verbose >= 2) Printf("%s[REFERENCED_LIST] count=%u\n", ind, count); for (i = 0; i < count; i++) { REFERENCED_FUNCTION ref(version) <style=sSection4>; g_refs++; if (g_verbose >= 2) { Printf("%s [REF #%u] rel_off=0x%LX name=%s%s\n", ind, g_refs, GetEncodedValue(ref.relative_offset, version), SafeName(ReferencedName(ref)), ref.negative_offset ? " negative_offset=yes" : ""); } } }; struct MODULE (int version, ubyte crc_length, uint16 crc16_value, int depth) { local string ind; local int more_public; local uint64 running_offset; local ubyte terminal_flags; ind = Indent(depth); ENCODED_VALUE module_length(version) <style=sSection2Accent>; g_modules++; if (g_verbose >= 1) { Printf("%s[MODULE #%u] file_off=0x%LX crc_len=%u crc16=0x%04X module_len=0x%LX\n", ind, g_modules, startof(module_length), crc_length, crc16_value, GetEncodedValue(module_length, version)); } running_offset = 0; terminal_flags = 0; more_public = 1; while (more_public && !FEof()) { PUBLIC_FUNCTION public_fn(version) <style=sSection3>; running_offset += GetEncodedValue(public_fn.offset_delta, version); terminal_flags = public_fn.parse_flags; g_publics++; if (g_verbose >= 1) { Printf("%s [PUBLIC #%u] file_off=0x%LX offset=0x%LX delta=0x%LX parse_flags=0x%02X{%s} meta=0x%02X{%s} name=%s\n", ind, g_publics, startof(public_fn), running_offset, GetEncodedValue(public_fn.offset_delta, version), public_fn.parse_flags, ParseFlagText(public_fn.parse_flags), public_fn.meta_value, FunctionFlagText(public_fn.meta_value), SafeName(PublicName(public_fn))); } more_public = ((terminal_flags & PF_MORE_PUBLIC_NAMES) != 0); } if ((terminal_flags & PF_READ_TAIL_BYTES) != 0) TAIL_LIST tails(version, depth + 1) <style=sMarker>; else if (g_verbose >= 2) Printf("%s [TAIL_LIST] none\n", ind); if ((terminal_flags & PF_READ_REFERENCED_FUNCS) != 0) REFERENCED_LIST refs(version, depth + 1) <style=sSection4>; else if (g_verbose >= 2) Printf("%s [REFERENCED_LIST] none\n", ind); local ubyte final_parse_flags; final_parse_flags = terminal_flags; }; struct MODULE_GROUP (int version, int depth) { local string ind; local uint16 crc16_value; local int more_same_crc; local ubyte last_flags; ind = Indent(depth); ubyte crc_length <style=sSection2Accent>; ubyte crc16_hi <format=hex, style=sSection2>; ubyte crc16_lo <format=hex, style=sSection2>; crc16_value = (((uint16)crc16_hi) << 8) | crc16_lo; if (g_verbose >= 1) Printf("%s[MODULE_GROUP] file_off=0x%LX crc_len=%u crc16=0x%04X\n", ind, startof(crc_length), crc_length, crc16_value); more_same_crc = 1; last_flags = 0; while (more_same_crc && !FEof()) { MODULE module(version, crc_length, crc16_value, depth) <style=sSection2>; last_flags = module.final_parse_flags; more_same_crc = ((last_flags & PF_MORE_MODULES_SAME_CRC) != 0); } local int more_crc_groups; more_crc_groups = ((last_flags & PF_MORE_MODULES) != 0); }; struct MODULE_LIST (int version, int depth) { local string ind; local int more_groups; ind = Indent(depth); if (g_verbose >= 1) Printf("%s[MODULE_LIST] file_off=0x%LX\n", ind, FTell()); more_groups = 1; while (more_groups && !FEof()) { MODULE_GROUP group(version, depth) <style=sSection2>; more_groups = group.more_crc_groups; } if (g_verbose >= 1) Printf("%s[/MODULE_LIST] end_off=0x%LX\n", ind, FTell()); }; // ----------------------------------------------------------------------------- // Trie nodes // ----------------------------------------------------------------------------- struct FLIRT_NODE; struct FLIRT_NODE (int version, int is_root, int depth, uint32 prefix_len) { local string ind; local uint64 variant_mask; local uint64 fixed_count; local uint64 active_mask; local uint64 ignored_mask_bits; local int64 concrete_pos; local uint64 child_count_value; local uint32 i; ind = Indent(depth); if ((uint32)depth > g_max_depth) g_max_depth = depth; g_nodes++; variant_mask = 0; fixed_count = 0; active_mask = 0; ignored_mask_bits = 0; concrete_pos = FTell(); if (!is_root) { ubyte node_length <style=sSection1Accent>; if (node_length > MAX_NODE_LEN) Warning("FLIRT node length is greater than 64; parser will continue conservatively."); if (node_length < 0x10) { VLI_MAX2 variant_mask_short <style=sSection1>; variant_mask = GetVLIMax2(variant_mask_short); } else if (node_length <= 0x20) { VLI_MULTI variant_mask_low <style=sSection1>; variant_mask = GetVLIMulti(variant_mask_low); } else if (node_length <= 0x40) { VLI_MULTI variant_mask_high <style=sSection1>; VLI_MULTI variant_mask_low <style=sSection1>; variant_mask = (GetVLIMulti(variant_mask_high) << 32) | GetVLIMulti(variant_mask_low); } active_mask = variant_mask & MaskForNodeLen(node_length); ignored_mask_bits = variant_mask & (~MaskForNodeLen(node_length)); if (ignored_mask_bits != 0) Warning("Variant mask contains bits outside node_length; ignored for fixed_count/pattern output."); fixed_count = node_length - PopCountNodeMask(node_length, active_mask); concrete_pos = FTell(); if (fixed_count > (FileSize() - FTell())) Warning("Node fixed byte count exceeds remaining file size; file may be malformed."); if (fixed_count > 0) ubyte concrete_bytes[fixed_count] <format=hex, style=sSection1Accent>; if ((g_pattern_size != 0) && (prefix_len + node_length > g_pattern_size)) Warning("Trie prefix length exceeds header pattern_size; check node parsing or malformed input."); if (g_verbose >= 1) { Printf("%s[NODE #%u] file_off=0x%LX depth=%u segment_len=%u prefix_len=%u/%u variant_mask=0x%LX active_mask=0x%LX fixed_count=%Lu wildcards=%Lu\n", ind, g_nodes, startof(node_length), depth, node_length, prefix_len + node_length, g_pattern_size, variant_mask, active_mask, fixed_count, PopCountNodeMask(node_length, active_mask)); Printf("%s wildcard_pos0 : %s\n", ind, VariantPos0Text(node_length, active_mask)); Printf("%s wildcard_pos1 : %s\n", ind, VariantPos1Text(node_length, active_mask)); Printf("%s pattern : %s\n", ind, PatternText(node_length, active_mask, concrete_pos)); Printf("%s concrete_bytes : %s\n", ind, fixed_count ? BytesHex(concrete_pos, fixed_count) : "<none>"); } prefix_len += node_length; } else if (g_verbose >= 1) { Printf("[ROOT NODE] file_off=0x%LX\n", concrete_pos); } VLI_MULTI child_count <style=sSection1Accent>; child_count_value = GetVLIMulti(child_count); if (g_verbose >= 1) Printf("%s child_count : %Lu\n", ind, child_count_value); if (child_count_value > MAX_REASONABLE_COUNT) Warning("Unreasonable FLIRT child count; file may be malformed."); if (child_count_value == 0) { g_leaves++; if (g_verbose >= 1) Printf("%s[LEAF #%u] prefix=%u/%u\n", ind, g_leaves, prefix_len, g_pattern_size); MODULE_LIST modules(version, depth + 1) <style=sSection2>; } else { for (i = 0; i < child_count_value; i++) FLIRT_NODE child(version, 0, depth + 1, prefix_len) <style=sSection1>; } }; // ----------------------------------------------------------------------------- // Main // ----------------------------------------------------------------------------- Printf("============================================================\n"); Printf("IDA FLIRT IDASGN parser for 010 Editor\n"); Printf("============================================================\n"); FLIRT_HEADER hdr <style=sHeading1>; if (Strncmp(hdr.magic, "IDASGN", 6) != 0) { Warning("Not an IDASGN FLIRT signature file."); } else if (hdr.version < 5 || hdr.version > 10) { Warning("Unsupported FLIRT signature version. Expected v5-v10."); } else { char library_name[hdr.library_name_len] <style=sHeading1Accent>; if (hdr.version >= 8) g_pattern_size = hdr.pattern_size; else g_pattern_size = 32; Printf("[HEADER] file size : 0x%LX (%Lu)\n", FileSize(), FileSize()); Printf("[HEADER] magic : %s\n", BytesToText(startof(hdr.magic), 6, 0)); Printf("[HEADER] version : %u\n", hdr.version); Printf("[HEADER] architecture : %s (%u)\n", ArchName(hdr.arch), hdr.arch); Printf("[HEADER] file types : 0x%08X\n", hdr.file_types); Printf("[HEADER] os types : 0x%04X\n", hdr.os_types); Printf("[HEADER] app types : 0x%04X\n", hdr.app_types); Printf("[HEADER] features : 0x%04X (%s)\n", hdr.features, FeatureText(hdr.features)); Printf("[HEADER] old funcs : %u\n", hdr.old_n_functions); Printf("[HEADER] functions : %u\n", HeaderFunctionCount(hdr)); Printf("[HEADER] header crc16 : 0x%04X\n", hdr.header_crc16); Printf("[HEADER] ctype[12] : %s\n", BytesHex(startof(hdr.ctype), 12)); Printf("[HEADER] lib name len : %u\n", hdr.library_name_len); Printf("[HEADER] ctypes crc16 : 0x%04X\n", hdr.ctypes_crc16); if (hdr.version >= 8) Printf("[HEADER] pattern size : %u\n", hdr.pattern_size); if (hdr.version > 9) Printf("[HEADER] unknown v10 : 0x%04X\n", hdr.unknown_v10); Printf("[HEADER] library name : %s\n", BytesToText(startof(library_name), hdr.library_name_len, 0)); Printf("[BODY] starts at : 0x%LX\n", FTell()); if ((hdr.features & FEATURE_COMPRESSED) != 0) { ubyte compressed_zlib_payload[FileSize() - FTell()] <format=hex, style=sMarkerAccent, comment="Compressed zlib Trie body. Decompress externally before expanding Trie records.">; Printf("[BODY] compressed zlib payload: off=0x%LX len=0x%LX\n", startof(compressed_zlib_payload), sizeof(compressed_zlib_payload)); Warning("FEATURE_COMPRESSED is set. Header is parsed, but the Trie body remains zlib-compressed."); } else { FLIRT_NODE root(hdr.version, 1, 0, 0) <style=sSection1>; Printf("============================================================\n"); Printf("[SUMMARY] nodes : %u\n", g_nodes); Printf("[SUMMARY] leaves : %u\n", g_leaves); Printf("[SUMMARY] modules : %u\n", g_modules); Printf("[SUMMARY] public names : %u\n", g_publics); Printf("[SUMMARY] tail bytes : %u\n", g_tails); Printf("[SUMMARY] references : %u\n", g_refs); Printf("[SUMMARY] max depth : %u\n", g_max_depth); Printf("[SUMMARY] final offset : 0x%LX / 0x%LX\n", FTell(), FileSize()); if (FTell() != FileSize()) Warning("The template did not consume the full file. Check version, compression flag or malformed data."); } }
已完成。你上传的原模板对这个单函数样本能够“读出部分内容”,但有四处基础假设不正确:文件头字段错位、VLI 规则不完整、Trie 被当成固定 32 字节原始模式、叶子记录被简化为固定结构。遇到多分支 Trie、通配掩码、多个模块、尾部字节、引用函数或压缩 .sig 时,会发生连续错位。
可直接使用的文件
下载优化后的 010 Editor 模板:FLIRT_SIG_full_010Editor.bt
下载压缩体展开辅助脚本:flirt_sig_expand.py
下载由粘贴字节还原的示例文件:Unnamed_sample_library.sig
新版模板按照公开实现中的 FLIRT 结构重新整理,覆盖未压缩 v5–v10 .sig:完整文件头、不同长度的变长整数、递归 Trie、通配掩码、CRC 分组、模块长度、公开函数名、局部函数标记、冲突标记、尾部字节和引用函数列表。公开解析实现同样将树节点、模块与函数信息分层解析,而不是将 Trie 直接当作“32 字节模式 + 固定叶子”。(GitHub)
你的样本实际解析结果
| 偏移 | 元素 | 解析值 |
|---|---|---|
0x00 |
魔数 | IDASGN |
0x06 |
格式版本 | 0x0A,即 v10 |
0x07 |
处理器架构 | 0x00 |
0x08 |
文件类型掩码 | 0xFFFFFFFF |
0x0C |
操作系统掩码 | 0xFFFF |
0x0E |
应用类型掩码 | 0xFFFF |
0x10 |
特征标志 | 0x0000,未压缩 |
0x12 |
旧版函数计数 | 0 |
0x14 |
头部 CRC16 | 0x0000 |
0x16 |
C 类型区 | 12 字节全零 |
0x22 |
库名长度 | 0x16,即 22 字节 |
0x23 |
C 类型 CRC16 | 0x0000 |
0x25 |
函数总数 | 1 |
0x29 |
最大模式长度 | 0x0020,即 32 字节 |
0x2B |
v10 扩展字段 | 0x0000 |
0x2D |
库名 | Unnamed sample library |
0x43 |
根节点子节点数量 | 1 |
0x44 |
子节点模式长度 | 0x20,即 32 字节 |
0x45 |
通配掩码编码 | C0 78 0F 00 → 0x00780F00 |
0x49 |
实际存储的确定字节 | 24 字节 |
0x61 |
当前节点子节点数量 | 0,说明进入叶子模块 |
0x62 |
CRC 覆盖长度 | 0 |
0x63 |
CRC16 | 0x0000 |
0x65 |
函数长度 | 0x24,即 36 字节 |
0x66 |
函数偏移增量 | 0 |
0x67 |
函数名称 | Rust 符号名 |
0xC4 |
解析标志 | 0x00,记录结束 |
0xC5 |
最终位置 | 与文件总长度完全一致,无残余字节 |
完整恢复后的 32 字节模式为:
49 89 D0 0F B6 01 48 8D 0D .. .. .. .. 48 8B 14
C1 4C 8D 0D .. .. .. .. 49 63 0C 81 4C 01 C9 E9
其中 .. 是通配字节。你的原模板把 C0 78 0F 00 和后面的内容误认为模式本体的一部分,根本原因是没有解析节点通配掩码。FLIRT 的设计目标是从函数开头构建可匹配的签名,并允许将容易变化的字节排除出稳定模式;Hex-Rays 的官方说明也强调了函数签名匹配和变体字节处理的重要性。(docs.hex-rays.com)
原模板中需要修正的关键点
1. 库名长度不是 uint32
你的模板中使用了:
uint32 library_name_len;
对于这个 v10 样本,正确字段是单字节:
uchar library_name_len;
因此固定头部在库名之前共 45 字节,库名从 0x2D 开始。原结构会将后续字段吞入长度值,使 Trie 起始位置整体错位。
2. VLI 不是简单的 1、2、3、5 字节规则
正确的多字节 VLI 读取逻辑需要按照首字节高位分支处理。公开实现中,第三种编码占用 4 字节,而不是原模板中的 3 字节。通配掩码 C0 78 0F 00 正是这类编码的直接例子。(GitHub)
3. Trie 节点不是直接存储完整模式
当前样本中:
01 根节点有 1 个子节点
20 节点模式长度为 32
C0 78 0F 00 通配掩码
... 仅保存非通配位置的 24 个确定字节
00 节点没有下一层子节点,进入模块记录
新版模板会递归解析节点,并将缺失位置恢复为 ..。010 Editor 的模板语言支持递归结构和带参数结构,因此可以将 Trie 作为真正的树形结构显示。(Sweetscape)
4. 叶子并非固定六字节头部
叶子区域实际由 CRC 分组、模块记录、公开函数记录和可选扩展信息组成。名称后面的控制字节决定是否还存在:
| 位 | 含义 |
|---|---|
0x01 |
同一模块还有公开函数名 |
0x02 |
存在尾部字节信息 |
0x04 |
存在引用函数信息 |
0x08 |
同一 CRC 分组还有模块 |
0x10 |
还有后续 CRC 分组 |
新版模板会根据这些标志继续推进,不再依赖函数名后的固定布局。(GitHub)
使用方式
在 010 Editor 中打开 .sig 文件,然后运行下载的模板:
Templates → Run Template
选择:
FLIRT_SIG_full_010Editor.bt
模板会在结果树中展示 Header、Library Name、Trie Root、递归 Node、CRC Group、Module、Public Function、Tail Bytes 和 Referenced Functions。
遇到压缩 .sig 时
FLIRT 的特征标志 0x0010 表示 Trie 数据经过压缩。新版模板会识别并提示,不会将压缩流误解析为节点。公开解析实现同样先检查压缩特征位,再对后续数据解压。(GitHub)
010 Editor 模板环境不适合直接将解压后的字节数组重新作为完整二进制流递归映射,因此附带了一个展开脚本。010 Editor 社区讨论中也提到,模板内部直接处理解压字节流存在限制,通常需要借助外部处理步骤。(010 Editor Forum)
运行:
python flirt_sig_expand.py input.sig input.expanded.sig
再使用模板打开:
input.expanded.sig
辅助脚本会保留原文件头和库名,展开压缩 Trie,并清除压缩标志。该脚本已经用当前样本构造的压缩版本完成往返校验。
校验说明
我使用与模板相同的解析逻辑,对你粘贴的 197 字节样本进行了逐字段验证:解析从 0x00 开始,在 0xC5 精确结束,没有残余字节,也没有越界读取。当前运行环境没有安装 010 Editor,因此无法在软件界面内执行最终编译测试;若 010 Editor 对某一行提示语法错误,将 Output 中的具体行号和报错文本贴出即可继续收敛。
浙公网安备 33010602011771号