首页
社区
课程
招聘
[原创]MSVC编译器/Warbird初步逆向记录
发表于: 2025-10-17 22:15 3889

[原创]MSVC编译器/Warbird初步逆向记录

2025-10-17 22:15
3889

VS版本: Visual Studio 2022

写于: 2025/10/15

最近看到了隔壁大佬逆向msvc劫持编译器(碎碎念: 顺便一提似乎直接调用微软为warbird提供的接口来实现编译期混淆会更方便,相关内容下面也有写)我也突然来了兴趣(最后因为ttoil过于复杂没分析完所有IL),遂逆向了一番并编写了下列内容,希望对各位有帮助。

隔壁大佬的帖子:
https://bbs.kanxue.com/thread-288764.htm

c1.dll - 用于解析 c 代码并发出 IL

c1xx.dll - 用于解析 cpp 代码并发出 IL

c2.dll - 编译器后端, 负责优化与生成实际汇编代码

linker.exe - 链接器

cl.exe - 编译器命令行程序

主函数 cl.exe -> c1.dll -> ...... -> main_compile

第二阶段初始化

负责打开ex文件

被 ILEmitter 及其相关组件调用, 负责实际写入字节数据

似乎仅用于将 ilsExp 的读写指针 seek 到0x57的位置

用于向buffer中写入单个字节

用于写入压缩整数

此类用于发出 IL

source code -> PrimaryParser::Parse -> yacc语法分析器 -> ttoil(递归) -> ILEmitterVisitor -> ILEmitter -> ILSink -> ilsExp & ilsInit

source code -> PrimaryParser::Parse -> 解析完成 -> SymbolTableManager_t::DumpGlobalSymbols -> SymbolILWriters -> 各种Emitter -> ILSink -> LocalSymbolIL & GlobalSymbolIL

source code -> CVTSInit (初始化并开始记录) -> PrimaryParser::Parse等函数 -> 保存解析时候产生的记录 -> CVTSEnd (结束记录) -> ilsDB

用于打开 c1 生成的 IL

将 IL 文件写入obj中

将 c1 发出的 IL 文件转换为 obj

Warbird 分为两个接口 CreateObfuscator2 与 GetObfuscator

此函数用于在 linker 中初始化混淆器, 负责初始化链接期混淆

此函数用于在 c2.dll 中初始化混淆器, 负责初始化编译期混淆

以下是我在github上搜索到的一些相关代码

277K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6%4K9h3c8T1k6i4u0Y4i4K6u0r3L8i4y4$3j5K6S2Q4x3X3f1H3i4K6g2X3M7r3q4@1j5$3S2W2M7R3`.`.

3fcK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6v1j5h3y4C8e0X3!0A6M7X3g2Q4x3V1k6J5k6i4k6W2M7Y4y4W2b7K6u0p5L8r3H3`.

7b1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4y4@1i4K6u0W2k6$3W2@1K9s2g2T1i4K6u0W2j5$3!0E0i4K6u0r3g2s2u0W2k6h3E0A6i4K6u0r3j5U0b7#2y4e0u0T1k6e0M7#2x3K6N6T1k6r3u0U0x3e0p5%4x3o6k6X3k6h3k6U0x3$3f1#2k6h3k6V1x3r3t1`.

199K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4y4@1i4K6u0W2k6$3W2@1K9s2g2T1i4K6u0W2j5$3!0E0i4K6u0r3c8i4y4@1k6i4S2z5g2q4)9J5c8X3f1&6z5r3p5I4x3K6R3@1j5U0V1H3y4X3p5K6k6h3g2V1j5h3q4S2x3$3g2W2j5U0N6W2y4e0S2U0k6o6W2V1

// 第二阶段初始化
// 第一阶段 init_main1 负责处理命令行参数和各种环境变量
// 第二阶段 init_main2 负责初始化编译器内部的各种模块
init_main2();
 
// ......
 
// 各种token处理与各种可选功能
// ......
 
// /PH 在预处理时生成 #pragma file_hash
if (EmitPragmaFileHash)
    SymbolTableManager_t::DumpFileHashes(PchS);
 
// ......
 
// 一些特殊处理
if (!PchUFlag)
{
    Comment_type = 10;
    Comment_string = "/CLRNETCORE";
    op_pragma(0xB); // 下面调用了ILEmitter::EmitPragma来发出一个pragma的IL
    if (Has_com_entry)
    {
        Comment_type = 10;
        Comment_string = "/NOCOMENTRY";
        op_pragma(0xB); // 同上
    }
}
 
// ......
 
// 疑似注入头文件或处理预处理指令
InjectArtifactsBeforeInputSource();
ApplyCmdLineWarningFlags();
 
// ......
 
// 一些对预编译头的特殊处理
if (!PchUFlag)
{
    MetaDataEmitter::Initialize();
    metaEmitter = MetaDataEmitter::Instance;
}
 
// ......
 
if (!PchUFlag || WorkList_t::m_usingWorkList)
{
    StoilHeader();
    ProcessBEWarnings();
}
 
if ( PchUFile )
    EnsureFilenameIsEmitted();
 
// ......
 
// 将解析源代码并转换为IL
CallPrimaryParser();
 
// ......
 
// emit 符号数据到 GlobalSymbolIL 与 LocalSymbolIL
SymbolTableManager_t::DumpGlobalSymbols(PchS);
 
// ......
 
StoilHeader();
 
// ......
 
// IL生成完毕关闭文件句柄
ILSink::closeFile(&ilsExp);
ILSink::closeFile(&LocalSymbolIL);
ILSink::closeFile(&GlobalSymbolIL);
ILSink::closeFile(&ilsInit);
if ( IncludeDebugDataBase )
    ILSink::closeFile(&ilsDB);
 
// 清理
// ......
// 第二阶段初始化
// 第一阶段 init_main1 负责处理命令行参数和各种环境变量
// 第二阶段 init_main2 负责初始化编译器内部的各种模块
init_main2();
 
// ......
 
// 各种token处理与各种可选功能
// ......
 
// /PH 在预处理时生成 #pragma file_hash
if (EmitPragmaFileHash)
    SymbolTableManager_t::DumpFileHashes(PchS);
 
// ......
 
// 一些特殊处理
if (!PchUFlag)
{
    Comment_type = 10;
    Comment_string = "/CLRNETCORE";
    op_pragma(0xB); // 下面调用了ILEmitter::EmitPragma来发出一个pragma的IL
    if (Has_com_entry)
    {
        Comment_type = 10;
        Comment_string = "/NOCOMENTRY";
        op_pragma(0xB); // 同上
    }
}
 
// ......
 
// 疑似注入头文件或处理预处理指令
InjectArtifactsBeforeInputSource();
ApplyCmdLineWarningFlags();
 
// ......
 
// 一些对预编译头的特殊处理
if (!PchUFlag)
{
    MetaDataEmitter::Initialize();
    metaEmitter = MetaDataEmitter::Instance;
}
 
// ......
 
if (!PchUFlag || WorkList_t::m_usingWorkList)
{
    StoilHeader();
    ProcessBEWarnings();
}
 
if ( PchUFile )
    EnsureFilenameIsEmitted();
 
// ......
 
// 将解析源代码并转换为IL
CallPrimaryParser();
 
// ......
 
// emit 符号数据到 GlobalSymbolIL 与 LocalSymbolIL
SymbolTableManager_t::DumpGlobalSymbols(PchS);
 
// ......
 
StoilHeader();
 
// ......
 
// IL生成完毕关闭文件句柄
ILSink::closeFile(&ilsExp);
ILSink::closeFile(&LocalSymbolIL);
ILSink::closeFile(&GlobalSymbolIL);
ILSink::closeFile(&ilsInit);
if ( IncludeDebugDataBase )
    ILSink::closeFile(&ilsDB);
 
// 清理
// ......
// 初始化各种模块
 
// ......
 
// %Temp% + "/_CL_" + hash + "ex"
OpenExpFile();
 
// ......
 
// 打开临时文件夹里的il文件 %Temp% + "/_CL_" + hash + section
ILSink::fopen(&LocalSymbolIL, Basename, "sy", 6);
ILSink::fopen(&GlobalSymbolIL, Basename, "gl", 6);
ILSink::fopen(&ilsInit, Basename, "in", 6);
if (IncludeDebugDataBase)
    ILSink::fopen(&ilsDB, Basename, "db", 6);
 
// ......
// 初始化各种模块
 
// ......
 
// %Temp% + "/_CL_" + hash + "ex"
OpenExpFile();
 
// ......
 
// 打开临时文件夹里的il文件 %Temp% + "/_CL_" + hash + section
ILSink::fopen(&LocalSymbolIL, Basename, "sy", 6);
ILSink::fopen(&GlobalSymbolIL, Basename, "gl", 6);
ILSink::fopen(&ilsInit, Basename, "in", 6);
if (IncludeDebugDataBase)
    ILSink::fopen(&ilsDB, Basename, "db", 6);
 
// ......
// ......
 
ILSink::fopen(&ilsExp, Basename, "ex", 6)
 
// ......
 
if (!PchUFlag)
{
    ILSink::chwrite(&ilsExp, 0x5B);
    ILSink::lowrite(&ilsExp, 0x58);
    status = ILSink::seek();
    IOGlobalVal = status;
    if (status)
    {
        // handle error
    }
    ILSink::chwrite(&ilsExp, 0);
}
 
// ......
// ......
 
ILSink::fopen(&ilsExp, Basename, "ex", 6)
 
// ......
 
if (!PchUFlag)
{
    ILSink::chwrite(&ilsExp, 0x5B);
    ILSink::lowrite(&ilsExp, 0x58);
    status = ILSink::seek();
    IOGlobalVal = status;
    if (status)
    {
        // handle error
    }
    ILSink::chwrite(&ilsExp, 0);
}
 
// ......
// ......
 
// 直接跳到0x57
fseek(&ilsExp, 0x57, 0);
 
// ......
// ......
 
// 直接跳到0x57
fseek(&ilsExp, 0x57, 0);
 
// ......
// 扩容buffer
if (this->buffer_start + this->buffer_size - this->buffer_end >= 2
    || (res = Buffer::grow(this->buffer_start, 2), ok = 0, res) )
{
    ok = 1;
}
 
// 检查是否fail
fail = (ok & this->good) == 0;
this->good &= ok;
if (!fail)
    *(uint8_t *)this->buffer_end++ = ch;
// 扩容buffer
if (this->buffer_start + this->buffer_size - this->buffer_end >= 2
    || (res = Buffer::grow(this->buffer_start, 2), ok = 0, res) )
{
    ok = 1;
}
 
// 检查是否fail
fail = (ok & this->good) == 0;
this->good &= ok;
if (!fail)
    *(uint8_t *)this->buffer_end++ = ch;
// 扩容buffer
if (this->buffer_start + this->buffer_size - this->buffer_end >= 2
    || (res = Buffer::grow(this->buffer_start, 2), ok = 0, res) )
{
    ok = 1;
}
 
// 检查是否fail
fail = (ok & this->good) == 0;
this->good &= ok;
if (!fail) {
    buf = this->buffer_end;
 
    // 判断到底写入单字节还是多字节
    if (val & 0xFFFFFF80 != 0) {
 
        // 0x80 1000 0000 多字节标记
        *(uint8_t *)buf = 0x80;
 
        // 写入四字节数据
        *(uint32_t *)++this->buffer_end = val;
        this->buffer_end += 4;
    } else {
 
        // 写入单字节数据
        *(uint8_t *)buf = val;
        ++this->buffer_end;
    }
}
// 扩容buffer
if (this->buffer_start + this->buffer_size - this->buffer_end >= 2
    || (res = Buffer::grow(this->buffer_start, 2), ok = 0, res) )
{
    ok = 1;
}
 
// 检查是否fail
fail = (ok & this->good) == 0;
this->good &= ok;
if (!fail) {
    buf = this->buffer_end;
 
    // 判断到底写入单字节还是多字节
    if (val & 0xFFFFFF80 != 0) {
 
        // 0x80 1000 0000 多字节标记
        *(uint8_t *)buf = 0x80;
 
        // 写入四字节数据
        *(uint32_t *)++this->buffer_end = val;
        this->buffer_end += 4;
    } else {
 
        // 写入单字节数据
        *(uint8_t *)buf = val;
        ++this->buffer_end;
    }
}
ILEmitter::EmitOPbranch(OPTYPE,Symbol_t *);
ILEmitter::EmitOPgoto(Symbol_t *);
ILEmitter::EmitOPlabel(Symbol_t *);
ILEmitter::EmitOPname(Symbol_t *);
ILEmitter::EmitOPnbranch(Symbol_t *);
ILEmitter::EmitOPpragma(uchar);
ILEmitter::EmitOPswexp(Symbol_t *);
ILEmitter::EmitOPswitch(Symbol_t *,Expr_t *);
ILEmitter::EmitPrFilename(Filename *);
ILEmitter::EmitPragma(PRAGMA_TYPE,MessageNumber);
ILEmitter::EmitPragmaComment(PragmaComment_t::Kind_t,char const *);
ILEmitter::EmitWarningPragma(WarningState_t *,WarningState_t * const,std::vector<WarningNumber> const &);
ILEmitter::toil(s_tree *);
ILEmitter::toil_direct(s_tree *);
ILEmitter::EmitOPbranch(OPTYPE,Symbol_t *);
ILEmitter::EmitOPgoto(Symbol_t *);
ILEmitter::EmitOPlabel(Symbol_t *);
ILEmitter::EmitOPname(Symbol_t *);
ILEmitter::EmitOPnbranch(Symbol_t *);
ILEmitter::EmitOPpragma(uchar);
ILEmitter::EmitOPswexp(Symbol_t *);
ILEmitter::EmitOPswitch(Symbol_t *,Expr_t *);
ILEmitter::EmitPrFilename(Filename *);
ILEmitter::EmitPragma(PRAGMA_TYPE,MessageNumber);
ILEmitter::EmitPragmaComment(PragmaComment_t::Kind_t,char const *);
ILEmitter::EmitWarningPragma(WarningState_t *,WarningState_t * const,std::vector<WarningNumber> const &);
ILEmitter::toil(s_tree *);
ILEmitter::toil_direct(s_tree *);
sy -> LocalSymbolIL  // 本地符号信息
gl -> GlobalSymbolIL // 全局符号信息
in -> ilsInit        // 变量初始化数据
db -> ilsDB          // 调试数据库
ex -> ilsExp         // 调用 ttoil 与其下的 ILEmitterVisitor 转换过来的IL
 
// fg不由 c1.dll 产生
fg -> 命令行参数
sy -> LocalSymbolIL  // 本地符号信息
gl -> GlobalSymbolIL // 全局符号信息
in -> ilsInit        // 变量初始化数据
db -> ilsDB          // 调试数据库
ex -> ilsExp         // 调用 ttoil 与其下的 ILEmitterVisitor 转换过来的IL
 
// fg不由 c1.dll 产生
fg -> 命令行参数
wcscpy_s(file_name, 0x104, basename);
wcscat_s(file_name, 0x104, section);
 
// ......
 
utc_wfopen_s(&fs, file_name, L"rb")

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2025-10-20 21:00 被EX呵呵编辑 ,原因: 添加参考资料
收藏
免费 45
支持
分享
最新回复 (19)
雪    币: 5368
活跃值: (9619)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
真厉害
2025-10-18 12:22
0
雪    币: 8965
活跃值: (3679)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习一下
2025-10-18 16:12
0
雪    币: 1553
活跃值: (3742)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4
学习一下
2025-10-19 15:01
0
雪    币: 6173
活跃值: (2732)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
5
可以看看IR相关的东西,劫持那些是可以篡改的
2025-10-20 02:08
0
雪    币: 749
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2025-10-20 09:18
0
雪    币: 87
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
1
2025-10-20 16:30
0
雪    币: 14529
活跃值: (8574)
能力值: ( LV13,RANK:640 )
在线值:
发帖
回帖
粉丝
8
好东西
2025-10-20 17:39
0
雪    币: 637
活跃值: (5913)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
9
TeddyBe4r 可以看看IR相关的东西,劫持那些是可以篡改的
主要还是ttoil太复杂了,他c++和c的处理还是俩,之前调试了半天才找出来几个太费事了,懒得逆了
2025-10-20 19:29
0
雪    币: 637
活跃值: (5913)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
10
我个人感觉逆向后端c2的ILReader好像比ttoil好搞点,但是看他那几百个case复杂度应该也不低
2025-10-20 19:30
0
雪    币: 5611
活跃值: (3924)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
666
2025-10-21 14:41
0
雪    币: 34
活跃值: (576)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
12
666
2025-10-21 15:23
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13

总结得挺到位,ttoil确实绕,C/C++两套不同处理更添麻烦。按我的经验,直接用Warbird官方接口做编译期混淆更省事。关于各类IL文件如.sy、.gl的详细机制,可以参考思维导图,里面梳理了完整的解析流程和接口关系。

最后于 2025-10-21 17:10 被mb_cizqyhuh编辑 ,原因:
2025-10-21 16:55
0
雪    币: 144
活跃值: (1993)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
666
2025-10-21 17:03
0
雪    币: 222
活跃值: (647)
能力值: ( LV2,RANK:16 )
在线值:
发帖
回帖
粉丝
15
111
2025-10-22 18:47
0
雪    币: 1411
活跃值: (1272)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
学习
2025-10-30 20:27
0
雪    币: 273
活跃值: (150)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
学习
2025-11-20 08:42
0
雪    币: 158
活跃值: (585)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
之前泄露的 PlayReady 相关文件,里面的ARM64的.obj文件都是IL的。
2025-12-5 16:22
0
雪    币: 637
活跃值: (5913)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
19
binsys 之前泄露的 PlayReady 相关文件,里面的ARM64的.obj文件都是IL的。

他那个我之前看过因为要warbird混淆所以开了LTCG参数,warbird要在启用LTCG的情况下才能obf

最后于 2025-12-5 23:56 被EX呵呵编辑 ,原因:
2025-12-5 23:55
0
雪    币: 5204
活跃值: (6068)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
20
mark
2025-12-6 00:00
0
游客
登录 | 注册 方可回帖
返回