感谢无名大佬提供的思路,使用了下unicorn,编写idapython脚本去掉控制流混淆,unicorn一个字,好使,推荐大家可以用用,
1、https://github.com/obfuscator-llvm/obfuscator下载最新的ollvm分支编译,再build/bin下生成clang。
1)、 在llvm目录下创建build文件。
2)、 在build目录下行 cmake -DCMAKE_BUILD_TYPE=Release-DLLVM_INCLUDE_TESTS=OFF ../
3)、 make -j4
4)、 build目录下会生成clang的可执行文件bin、lib目录
2、将Lib\Transforms\Obfuscation\Flattening.cpp移植出来单独编译,方便测试,否则修改了源文件还得放到整个ollvm源码中编译,费时不方便测试。编译生成 libflatten.so
3、编写一个简单的llvm-test.cpp,只有一个测试函数。
4、使用clang分别进行编译,一个加载控制流混淆pass,一个不加载,在ida中进行对比下看看效果
未加控制流混淆pass:
除了入口块,还剩余4个真实块
添加控制流混淆pass:
可以看到添加了好多用来寻找真实块的无用块,统一从0xDB0块出发,找到真实块,最后统一从块0xDC0回到块0xDB0,跳转到入口块的指令都是有很明显的
特征的,后边会提到
以上是llvm生成的最终的机器指令控制流程图
llvm 通过Function.viewCFG()提供生成的中间IR指令的控制流图
在控制流混淆Pass的runOnFunction函数的前后调用f.viewCFG(),可以清晰的看到
混淆前后中间IR指令控制流图对比
对比下IR控制流图,
混淆前:
混淆后:
看下源码, https://github.com/obfuscator-llvm/obfuscator Lib\Transforms\Obfuscation\Flattening.cpp中的flatten函数
1、 第一个块 :添加 AllocaInst、StoreInst、BranchInst指令,alloca分配switchVar变量空间,store填充switchVar为随机值,bl跳转到loopEntry块
2、 loopEntry块 :添加 LoadInst、SwitchInst指令,load获取switchVar变量地址,switch根据获取的switchVar值,查表跳转
3、 loopEnd块 :添加 BranchInst指令,跳转到loopEntry块
4、 真实块 :如果是无条件分支指令,去掉终止指令br,添加StoreInst、BranchInst指令,根据真实块后继对应的switchNum用store更新
switchVar值,跳转到loopEnd块如果是条件分支指令,去掉终止指令br,添加SelectInst、StoreInst、BranchInst指令,根据SelectInst 指令选择的真实块后继对应的switchNum用store更新switchVar值,跳转到loopEnd块
5、 switchDefault块 :添加 BranchInst指令,跳转到loopEnd块
引用网上的一张图
总结控制流混淆的原理:
搭建一个switch代码框架,将真实块塞到框架case中,根据switchVar变量的值,通过switch指令寻找到真实的代码块,跟前边开启了混淆编译,中间生成
IR 流程就能匹配起来了。
控制流混淆生成的中间IR控制流图和源码是完全能对应起来的,都是通过switch指令,随机值串联起来的,但是和生成的汇编控制流图还不太一样,反汇编生 成的控制流图是通过多个块中的比较指令,最终找到真实块
Hook app输出日志函数pBA425510043BB3BE41A7E30AA69623BE
反混淆之前控制流图:
F5伪代码:外层一个大的while循环,内部通过switch case的方式寻找到真实块
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!