ollvm的混淆反混淆和定制修改
最近各大杀毒公司陆续都出了混淆,网上关于ollvm的资料比较少,于是就有了这篇文章,这篇文章介绍,android的native代码,也就是so和linux的c/c++代码均可使用的混淆工具ollvm的编译,混淆,反混淆,和反反混淆。
第一篇.ollvm的编译环境搭建------混淆
教你搭建编译和使用ollvm3.4 3.5 3.6的环境,非常详细
第二篇.ollvm的还原---反混淆
根据网上的一些文章,对ollvm混淆后的代码进行还原,写下我详细的心得和代码注释和环境搭建(目前只能还原linux x86,对于arm有兴趣的可以进一步研究)
第三篇.ollvm的定制---反反混淆
由于公司原因,这里介绍修改后的结果
====================第一篇ollvm的编译环境搭建------混淆======================
一、androidNDK搭建ollvm环境和使用
注意这里有编译环境和编译后的版本,ubuntu64位的系统依然可以使用ndk32,但是只能编译clang64,所以你不需要ndk64就不需要编译64位的ollvm64
1.编译ollvm 32位
版本有三个我们选择obfuscator-llvm-3.4解压得到文件夹obfuscator-llvm-3.4
ollvm的下载地址https://github.com/obfuscator-llvm/obfuscator/tree/llvm-3.4
ndk选择android-ndk-r10b-linux-x86.tar.bz2和
环境选择ubuntu14.0.4 x32 or x64
(0)安装ndk
将ndk解压到/opt/android/ndk/
解压后的目录
/opt/android/ndk/android-ndk-r10e
$ sudo gedit /etc/profile,在文件末尾加入如下内容:
#set NDK env
export NDK_HOME=/opt/android/ndk/android-ndk-r10b
export PATH=$NDK_HOME:$PATH
$ source /etc/profile 使之生效
(1)编译ollvm的工具
apt-get install cmake
sudo apt-get install g++
正式编译ollvm
cd obfuscator-llvm-3.4
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE:String=Release ../
make –j4 (不要复制哦,手动输入命令)(注意这里一定要加j4,如果只是make –j他默认只会用一个cpu然后会卡到蛋疼的)
注意:分配内存和cpu多点不然卡死,我这里是8G内存+(2处理器数量每个处理器2个核心)
编译完后得到二进制程序都在build/bin和build/lib
(2)下面来配置32位的ndk
[1]打开ndk的toolchains目录新建目录obfuscator-llvm-3.4
并将llvm-3.3目录下的prebuilt目录和文件 config.mk、setup.mk和setup-common.mk拷贝到obfuscator-llvm-3.4目录中
然后替换obfuscator-llvm-3.4/prebuilt/linux-x86下的bin和lib为我们编译好的bin和lib
然后将下面文件复制一份,改名称如下,比如arm-linux-androideabi-clang3.4复制一行改名为arm-linux-androideabi-obfuscator3.4
arm-linux-androideabi-clang3.4-> arm-linux-androideabi-obfuscator3.4
mipsel-linux-android-clang3.4-> mipsel-linux-android-obfuscator3.4
x86-clang3.4-> x86-obfuscator3.4
分别修改以上三个文件的 setup.mk 中的 LLVM_NAME ,即将其指定到开始建立的obfuscator-llvm-3.4目录,也就是把
把LLVM_NAME := llvm-$(LLVM_VERSION)改成LLVM_NAME := obfuscator-llvm-$(LLVM_VERSION)
如果是配置64位的ndk配置,还要额外修改$NDK_PATH/build/core/setup-toolchain.mk文件,在NDK_64BIT_TOOLCHAIN_LIST := 加入 obfuscator 对应的NDK_TOOLCHAIN_VERSION
NDK_64BIT_TOOLCHAIN_LIST := obfuscator3.4 clang3.6 clang3.5 clang3.4 4.9
2.使用
在Application.mk中指定编译器名字:
NDK_TOOLCHAIN_VERSION := obfuscator3.4
在Android.mk中设置混淆参数:
LOCAL_CFLAGS += -mllvm -sub -mllvm -bcf -mllvm –fla
正常编译ndk就行了
例子
Application.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
APP_ABI := armeabi
NDK_TOOLCHAIN_VERSION := obfuscator
include $(BUILD_EXECUTABLE)
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_SRC_FILES := hello.c
LOCAL_CFLAGS += -mllvm -sub -mllvm -bcf -mllvm –fla 混淆参数
LOCAL_ARM_MODE := arm
include $(BUILD_EXECUTABLE)
hello.c
#include <stdio.h>
int main(int argc, char **args){
int a=1;
int b=0;
if(a>b){
printf("snow:%d\n", a);
}
else{
printf("test:%d\n", b);
}
return 0;
}
3.更多使用
bcf可以配合下面参数使用
-mllvm -perBCF=20: 对所有函数都混淆的概率是20%,默认100%
-mllvm -boguscf-loop=3: 对函数做3次混淆,默认1次
-mllvm -boguscf-prob=40: 代码块被混淆的概率是40%,默认30%
给某个函数单独加入混淆;注意注意经过我测试只有ollvm3.5和ollvm3.6可以使用单独函数加混淆
int main(int argc, char **args)__attribute((__annotate__(("bcf"))));
linux可执行文件搭建ollvm环境和使用
环境跟前面那个一样,在编译完毕ollvm之后有目录obfuscator-llvm-3.4/build/bin/此目录下面有一个clang,指向clang-3.4我们编译linux可执行的程序可以用
obfuscator-llvm-3.4/build/bin/clang xx.c –o xx –mllvm –fla 就是控制流平展了
=========================第二篇.ollvm的还原---反混淆===================
一、网上的Decllvm的分析
是F8LEFT写的一个工具,这个工具出现在吾爱破解2016的安全挑战赛第七题的解答里面
此工具给的demo有AliLLVM.py针对阿里第二届安全挑战赛crackme3哦
下面只是说这个工具的使用和原理
1.入口360LLVM.py文件
if __name__ == "__main__":
print("============360LLVMStart=================")
ins = C360LLVM()
reg = ArmReg()
dbgEng = DbgEngine(reg, ins)
fd = open("F:/trace.log", "w+")
dbgEng.start_run(GetRegValue("PC"), 1000, fd)
fd.close()
del dbgEng
del reg
del ins
print("============360LLVMEnd=================")
大致原理这是一个ida的脚本,运行需要动态调试程序才行,根据程序运行的时候把寄存器的参数打印下来并且写到txt文件里面,方便分析,这个用处不是太大,只是利用ida打印程序流程,但是可以通吃NDK和linux的混淆,顺便感谢一下他的脚本里面包含很多ida api的使用哦!
二、又找的的另外一篇利用符号执行
参考文章https://security.tencent.com/index.php/blog/msg/1122 利用符号执行去除控制流平坦化
这篇文章的办法可以完美的恢复控制流平坦化但是只是linux的64和32位可执行文件x86格式的混淆后完美还原,对于android的so和可执行文件不行,为arm格式的指令找不到规则不像x86那样找到规则,需要我们自己分析规则
注意作者给的deflat.py依赖的barf只能运行于linux64位,所以就算在linux32位上混淆了,我们也要拿到linux64位上运行deflat.py脚本哦!!!
我的电脑是ubuntu14.04 x64
1.搭建环境和运行
[1]安装python
ubuntu自带了Python 2.7.6
[2]安装pip和??
apt-get install python-pip
sudo apt-get install python-dev libffi-dev build-essential
[3]安装barf
下载barf解压cd到目录运行
python setup.py install
[4]安装angr
sudo pip install angr 报错,再次安装sudo apt-get install python-dev libffi-dev build-essential
[5]运行
python deflat.py check_passwd_flat 0x400530
注意在ubuntu14.0.4 x32环境运行出错
ImportError: ERROR: fail to load the dynamic library
因为deflat.py会去调用BARF,BARF这货只能在linux x64位运行坑爹啊,但是我们可以在x32上编译了,拿到x64上面跑,
这里我们自己编译和混淆在ubuntu14.04_x32位下面在ubuntu14.0.4_64里面还原混淆
[1]编译
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int check_password(char *passwd)
{
int i, sum = 0;
for (i = 0; ; i++)
{
if (!passwd[i])
{
break;
}
sum += passwd[i];
}
if (i == 4)
{
if (sum == 0x1a1 && passwd[3] > 'c' && passwd[3] < 'e' && passwd[0] == 'b')
{
if ((passwd[3] ^ 0xd) == passwd[1])
{
return 1;
}
puts("Orz...");
}
}
else
{
puts("len error");
}
return 0;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
puts("error");
return 1;
}
if (check_password(argv[1]))
{
puts("Congratulation!");
}
else
{
puts("error");
}
return 0;
}
gcc check_passwd.c -o check_passwd
[2]混淆控制流平坦
root/桌面/software/my _compile_ollvm/obfuscator-llvm-3.4/build/bin/clang-3.4 check_passwd.c -o check_passwd_32_flat -mllvm -fla
[3]拿到x64位上还原混淆
python deflat.py check_passwd_32_flat 0x80488B0 //main函数
[4]全混淆
python deflat.py check_passwd_32_flat_sub_bcf 0x8048420
依然可以还原-fla
2. deflat.py脚本分析心得和我的注释
在ida函数中可以看见很多块,一块一块的
#第一步:找出6大块,可以用静态分析得到
序言 保留
主分发器 去掉
预处理器 去掉
retn块 保留
真实块 保留
无用块 去掉
[1]序言和主分发器,函数开始就是序言,序言下面紧接着就是主分发器
[2]预处理器
ida查找办法谁调用了主分发器,谁就是预处理器(在主分发器按x,看见的就是预处理器)
[3]真实块,
ida查找办法所有调用预处理器的块都是预处理器(在预处理器按x查看到的都是真实块)
[4]retn 块
ida查找办法,没有后继的块就是,也就是下线没有分支的
[5]无用块
其余均是无用块
#第二步:找出真实块和序言和retn之间的调用关系,必须动态运行
#难点一:使用symbolic_execution找出真实块和序言的调用关系,必须使用他的引擎运行或者动态运行
如下的关系
#执行到这里已经获得了原函数的调用关系,下面是恢复之后的关系,真实块7->retn返回块
#0x8048a82: ['0x8048a95'] 真实块6 : retn返回块
#0x8048a65: ['0x8048a82'] 真实块5 : 真实块6
#0x8048a48: ['0x8048a82'] 真实块4 : 真实块6
#0x80488b0: ['0x80489d4'] 序言 : 真实块1
#0x80489d4: ['0x8048a1b', '0x80489f7'] 真实块1 :真实块3,真实块2
#0x8048a95: [] retn返回块
#0x80489f7: ['0x8048a95'] 真实块2 : retn返回块
#0x8048a1b: ['0x8048a65', '0x8048a48'] 真实块3 :真实块5,真实块4
下面是修复后他们的调用图
#第三步:nop掉无用块和主分发器和预处理器,可以静态分析得到
#第四步:修复真实块序言部分的跳转指令,修复办法,
#情况一:有一个childs,也就是一个后继的块,找到块的最后一条指令,将其抹掉后改成新的jmp,jmp到自己的childs
#情况二:有多个childs的
下面是我的代码注释
#coding: UTF-8
from barf.barf import BARF
import angr
import simuvex
import pyvex
import claripy
import struct
import sys
def get_retn_predispatcher(cfg):
global main_dispatcher
for block in cfg.basic_blocks:
if len(block.branches) == 0 and block.direct_branch == None:
retn = block.start_address
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)