GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

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

 

无标题123

 

 

 

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 000x00780F00
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 中的具体行号和报错文本贴出即可继续收敛。

 

posted on 2026-06-14 05:54  GKLBB  阅读(4)  评论(0)    收藏  举报