LLVM16-有bug不建议折腾,究极复杂;16及以上的版本可以玩
本文第一次公布的时候是LLVM12版本,所以最初也是以这个版本的配置来写的,当时因为LLVM在windows上有诸多bug,所以做了很多在高版本下不必要的工作。后续随着版本迭代也做过一些修改,但很多不必要的工作没有删去。大家在高版本下玩的时候可以省去很多步骤
准备工作
如果想要在Windows下编译LLVM Pass需要Build好的LLVM完整项目bin与lib,和build后的include,同时需要源码的include目录。所以前面部分还是要按照
LLVM 编译与First Pass
编译得到LLVM。这里大家切记cmake要开启 LLVM_ENABLE_PLUGINS 选项,之前我没开这选项怎么都编译不上(甚至逆了两天opt 像傻逼一样
opt加载插件
LLVM已经逐步开始普及New Pass了,并在17+逐步淘汰Legacy Pass,故准备集成New Pass的Plugin。
这里暂时不对NewPass的加载流程以及与LegacyPass的区别做赘述,有兴趣的同学可以自行根据这些资料学习:
- LLVM New pass
- LLVM NewPassManager
- LLVM PassManager对C++程序设计的思考
- llvm NewPassManager API分析及适配方案
- llvm PassManager的变更及动态注册Pass的加载过程
仍先写一个HelloWorld Pass
|
|
在clang中,PassPlugin.cpp中的PassPlugin::Load函数会遍历 “-Xclang -fpass-plugin” 传入的pass模块,我们必须要导出我们pass插件模块llvmGetPassPluginInfo函数以便clang去调用从而加载插件中的pass。但是我们不可以使用 __declspec(dllexport) 导出函数,会报链接类型错误。
要导出这个函数,只能通过.def模块定义文件指定导出该函数。新建一个 export.def 文件
LIBRARY QVMProtect
EXPORTS
llvmGetPassPluginInfo
接下来,设计CMakeLists.txt,三个路径大家自行替换
|
|
然后建立一个工程文件夹,执行 cmake .. 即可生成.sln工程文件。用vs打开该工程,使用Debug x64模式即可编译得到Pass的.dll模块文件
然后,将.c文件编译成.bc文件,然后使用opt去加载
PS C:\Users\Qfrost\Desktop\code\LLVM\ollvm-12.x-main\llvm\lib\Transforms\MyPass\WindowsPass\QVMProtect\Debug> clang++ -emit-llvm -c target.cpp -o target.bc
PS C:\Users\Qfrost\Desktop\code\LLVM\ollvm-12.x-main\llvm\lib\Transforms\MyPass\WindowsPass\QVMProtect\Debug> opt --load-pass-plugin=./NewPassHelloWorld.dll --passes='NewPassHelloWorld' target.bc -o target.bc
NewPassHelloWorld add Pass
NewPassHelloWorld add Pass
NewPassHelloWorld Loaded
MyPass:target.bc
PS C:\Users\Qfrost\Desktop\code\LLVM\ollvm-12.x-main\llvm\lib\Transforms\MyPass\WindowsPass\QVMProtect\Debug> clang .\target.bc -o target.exe
PS C:\Users\Qfrost\Desktop\code\LLVM\ollvm-12.x-main\llvm\lib\Transforms\MyPass\WindowsPass\QVMProtect\Debug> .\target.exe
hello world
opt加载插件加载成功。
clang加载插件
但是我们知道,有些场景下就不可能允许你使用opt加载插件中转(如使用vs编译程序甚至编译驱动。所以需要clang直接加载opt插件。这里感谢 @Chord 告诉我可以使用clang -Xclang -fpass-plugin="<pass path>“进行加载。我们只需要在插件的导出函数 llvmGetPassPluginInfo 中注册clang pass的 registerPipelineStartEPCallback 就可以了,相当方便。
|
|
但是使用这一一种方法有一个缺陷就是无法使用-mllvm进行传参。也就是-mllvm后面带的参数没有办法传递到pass,原因可能是加载时机的问题。参数不能传递就使控制哪些Pass加载哪些不加载变得十分复杂,每次都得重新编译模块文件。
还有一个问题需要注意,在LLVM16-的版本,这个方法在windows下有bug,插件模块加载不了,这也是我之前说非常不建议在LLVM16以下的版本的Windows上折腾的原因
魔改Clang代码自动加载Pass DLL
用新版LLVM其实就不需要这个方法了,当时记录这个方法主要是因为旧版LLVM无法加载clang插件,而每次重新编译LLVM又太慢了,故采用了这样一个这种的办法。
核心思想就是,修改clang代码,在clang编译程序生成IR后链接前,插入一段代码,加载一个固定的.dll文件,并调用该dll的一个固定的导出函数,加载到PassManager中。故可以剥离出.dll的设计与编译过程,实现出固定的接口,这样每次只需要将所有的Pass编译入该dll中并替换即可。
简单说下步骤
- 对前面的dll添加一个固定导出接口函数
|
|
- 修改 clang/lib/CodeGen/BackendUtil.cpp 在EmitAssemblyHelper::EmitAssemblyWithNewPassManager函数内的找到 if (!CodeGenOpts.DisableLLVMPasses) 在判断体末尾加上下方代码使其固定加载你的.dll Plugin的clangAddPass导出函数,同时在该文件头部 #include<windows.h>
|
|
使用MSBuild增量编译,并将生成好的.dll Plugin文件放置到Build后的bin文件夹下。使用clang编译程序,可以看到Pass加载成功
PS C:\Users\Qfrost\Desktop\code\LLVM\ollvm-12.x-main\llvm\lib\Transforms\MyPass\WindowsPass\QVMProtect\Debug> clang .\target.cpp -o target.exe
[QVMProtect] : QVMProtect.dll Load Successfully
NewPassHelloWorld Loaded
MyPass:.\target.cpp
编译驱动
希望用clang来编译Windows驱动并加载混淆Pass模块进行混淆。用LLVM编译驱动最麻烦的地方在于需要指定编译器为clang但是链接器是MSVC(链接器不用MSVC也行但巨麻烦)。之前有参考看雪论坛厂子哥和大表哥的方法,但配出来有点问题。
gmh大佬为了让LLVM兼容MSVC的一些特性,开发了 llvm-msvc ,这个项目实际上就是魔改了LLVM使其支持一些参数并为Visual Studio配置工具集使其编译时走它的clang和lld。因为现在新版LLVM已经可以直接加载clang pass模块了,所以其实都可以不需要自己去编译clang(如果不考虑开发的话
-
直接安装 llvm-msvc 项目,(如果向换用自己的clang可以修改注册表 HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\LLVM\LLVM-MSVC 指定 llvm-msvc 使用的clang为我们自己的clang)
-
然后随便打开一个项目 项目->属性->常规->平台工具集 设置为 LLVM-MSVC_v14X_KernelMode
-
项目属性的命令行中添加加载自己的混淆Pass DLL
编译,即可看到成功输出了Pass加载的提示并生成出驱动文件