0x1 关于BlackObfuscator
BlackObfuscator是基于dex2jar开发的Dex控制流混淆,开源地址:https://github.com/CodingGay/BlackObfuscator,现有的混淆一般只支持处理变量名、类名,这样做对抗是远远不够的。由于对于控制流此类资料较少,加上也没有现成方案,所以当时就自己研究了一下,现在出来分享一下设计思路。
0x2 源码
BlackObfuscator的核心模块为dex-obfuscator,源码目录:
├── IRObfuscator.java
├── LBlock.java
├── ObfDic.java
├── ObfuscatorConfiguration.java
├── RebuildIfResult.java
└── chain
├── FlowObfuscator.java
├── IfObfuscator.java
├── SubObfuscator.java
└── base
├── BaseObfuscatorChain.java
└── ObfuscatorChain.java
我目前只做了
- 控制流混淆
- if块的混淆
- 指令运算混淆
对应的是源码chain目录下的三个混淆器。
切入点是通过dex2jar会将指令转换为ir指令,通过转换出来的ir指令进行混淆。
指令运算混淆
switch (v1.vt) {
case LOCAL:
if (v1.valueType.charAt(0) == 'I') {
if (v2.vt == Value.VT.ADD) {
if (v2.getOp2().vt == Value.VT.CONSTANT) {
// a=b+c a=b-(-c)
int increment = (Integer) ((Constant) v2.getOp2()).value;
v2.vt = Value.VT.SUB;
v2.setOp2(Exprs.nInt(-increment));
}
} else if (v2.vt == Value.VT.SUB) {
if (v2.getOp2().vt == Value.VT.CONSTANT) {
// a=b-c a=b+(-c)
int increment = (Integer) ((Constant) v2.getOp2()).value;
v2.vt = Value.VT.ADD;
v2.setOp2(Exprs.nInt(-increment));
}
} else if (v2.vt == Value.VT.XOR) {
// a=a^b (a ^ r) ^ (b ^ r)
// seed
Local local = newLocal("xor", "I");
newStmts.add(Stmts.nAssign(local, Exprs.nInt(new Random().nextInt(1000))));
Local left = newLocal("xor_left", "I");
newStmts.add(Stmts.nAssign(left, Exprs.nXor(v2.getOp1(), local, "I")));
v2.setOp1(left);
Local right = newLocal("xor_right", "I");
newStmts.add(Stmts.nAssign(right, Exprs.nXor(v2.getOp2(), local, "I")));
v2.setOp2(right);
}
}
break;
}
可以看到核心就是将简单的计算,转换为一些不容易看出的计算方式并且结果不变
例:
a=b+c 转换为 a=b-(-c)
a=b-c 转换为 a=b+(-c)
a=a^b 转换为 (a ^ r) ^ (b ^ r)
if块混淆
top.niunaijun.obfuscator.chain.IfObfuscator#reBuild0
这一块代码比较长,并且不是很好理解,我只讲设计思路。
if(i < 0) {
Log.d(TAG, "条件成立")
} else {
Log.d(TAG, "条件不成立")
}
可以将其拓展
int i = 0;
int index = 0;
while(true) {
switch(index) {
case 0:
index = 1;
break;
case 1:
if(i < 0) {
index = 2;
} else {
index = 3;
}
break;
case 2:
Log.d(TAG, "条件成立");
break;
case 3:
Log.d(TAG, "条件不成立");
break;
}
}
这是一种丐版的方式,实际上BlackObfuscator还增加了隐性的index,通过index为字符串,然后通过字符串的hashCode进行switch。
控制流混淆
一样的,代码比较难理解,我们直接看结果。代码可以自己去慢慢感受。
int a = 10;
int b = 20;
a = a + 5;
b = b + 5;
Log.d(TAG, "这里是a : " + a);
Log.d(TAG, "这里是B : " + b);
可以将它拓展为
int a = 0;
int b = 0;
int index = 0;
while(true) {
switch(index) {
case 0:
a = 10;
index = 1;
break;
case 1:
b = 20;
index = 2;
break;
case 2:
a = a + 5;
index = 3;
break;
case 3:
b = b + 5;
index = 4;
break;
case 4:
Log.d(TAG, "这里是a : " + a);
index = 5;
break;
case 5:
Log.d(TAG, "这里是B : " + b);
index = 6;
break;
default:
return;
}
}
这也是一种丐版,实际上可能是这样
String str = "۫ۨ۬ۤۤۡ۬۬ۦۛۥۧ۠ۘۧۢۨ۟ۧۛۜۘۢۘۦۘ۫ۥۧ";
while (true) {
switch ((str.hashCode() ^ 213) ^ 0x5e9d8e28) {
case -2112984833:
a = null;
str = "۠ۨۜۘ۫ۨ۟ۙۦ۠۟ۡۗ۬ۜۨ۬ۘۦۘ";
break;
case -1764939362:
return;
case -1465172033:
needX86Bridge = false;
str = "ۜۢۗۚۢۧ۬ۙۛۢ۬۟ۨۚۦۚۦۖ";
break;
case -1331622716:
f = null;
str = "ۜۨۜۥۛۥ۠ۦۡۤ۫ۥۧۛۜۘ";
break;
case -187618826:
returnIntern = true;
str = "ۙۡ۠ۤۨۤۤ۠۠ۥ۬ۨۜۛۗ۬ۘۘۡۧۜ۫ۜۘ";
break;
case 97430221:
i = new ConcurrentHashMap();
str = "ۖۡۖۘۙۗۘۖۚ۬ۛ۬ۥۤۨۡۘ۟ۙۚۚۡۨۘ";
break;
case 165757335:
h = null;
str = "۫۬ۖۘۙۜ۠ۘۢۙۧۢۛ۠ۡۙۜۖۗ";
break;
case 265781367:
d = null;
str = "ۙۙ۫ۚۛ۠ۙۜ۫ۦۙۜۘ۬ۚۥۦۦۥۘۜۤۦۘۙۛ۟";
break;
case 780928495:
g = null;
str = "ۡۧۛ۬ۘۖۛۗۛۥ۟ۨۗۦۤۨۦۢۛۙ۠ۚۙ۟۠ۛۖ";
break;
case 864246795:
b = "libjiagu";
str = "ۖۚۘۘۗۡۘۜۨۖ۠۫ۘۜۤ۬ۘۗۜ۬ۛۡۘۜۚ۠";
break;
case 1015028737:
e = null;
str = "ۢ۫ۘۘۧۨۧۗۥۛۡۤ۟ۤۧۨ";
break;
case 1129108171:
loadFromLib = false;
str = "ۘ۬۟۫۟ۧ۟ۨۢ۟ۢ۠ۚۥۡ۫ۡۘۖۨۜۘۗۖۧ";
break;
}
}
0x3 总结
这里可以看到,我们控制流混淆的宗旨就是要用100句代码实现10句代码的功能。BlackObfuscator不足的地方就是太过单调,还可以进一步优化的地方可以是
- 在switch块中插入无用代码,使代码更加难以阅读。
- 将代码抽离出改switch块,移至别的地方执行
看到此处比较简单,但是你别忘记了,BlackObfuscator可是支持深度设定的,何为深度设定?说大白话就是套娃混淆。
一句指令混淆 a=a^b 转换为 (a ^ r) ^ (b ^ r)。在下一次混淆时将会变成(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r),再下一次则是(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)(a ^ r) ^ (b ^ r)
上面这个举例只是做演示,展示混淆的套娃能力。
以上就是BlackObfuscator的设计思路和原理。由于dex2jar本身是有问题的,所以本库也不会再怎么跟进了,有兴趣的同学可以尝试用dex2lib等库进行定制会更好一点,原理是一样的。
您好牛奶君,我想在您的blackbox开元代码上进行商业化变现,请问需要您这边同意或者授权码?
盼复~
人家都说了使用了BlackBox的工具类,调用了部分api,作者不能与你合作哦
Milk你好,有个电商项目想找你聊聊,看看是否有合作机会。给你发了邮件但是还没有收到你的回复。不知道我邮件是不是进了垃圾箱,所以又特地在你博客留言。 不知道能否加个QQ或者微信沟通一下? 谢谢
额 啊这 不会吧 还真的只是浅谈啊?😅😅😅
代码的话自己对照看就行了,都是某个库API的操作。讲起来没什么意义
那个对ir的statement进行重构的操作是调用的库的api吗?请问具体是哪个库啊?还有就是进行if混淆好像要画控制流图啥的 那个具体大概是怎么个操作法呀?
首先你可以看看比如自己加一句代码,用statement怎么操作。后面的就是看上面我列的拓展思路一句一句拼起来就好了。因为里面全都是ir的操作,所以列出来没有实际用处。思路就是上面的思路。按照思路一句一句把代码拼起来就可以了。
请问ir操作用的库叫什么名字呀 我好先搜搜教程系统学学.还有就是if语句混淆成while-switch结构的控制流程图是怎么设计的呀 能简要说说不?
你写一个,然后看就知道了,其实就是一个goto 又回到上面的语句,就是个while。至于你说ir这个东西,是dex2jar原本项目里的。自己照葫芦画瓢即可。
......
就是你在我提到那几个控制器里面,打个断点,就能知道其中原理了。代码就是用来实现思路的过程而已,核心还是你的思想。