首页
社区
课程
招聘
一种通过后端编译优化脱混淆壳的方法
发表于: 2020-7-11 21:22 23036

一种通过后端编译优化脱混淆壳的方法

2020-7-11 21:22
23036

以前做过pc端逆向,一直没有学过android,4月的时候,读了一下 my1988 对混淆壳的分析,很感兴趣,决定深入研究。

花了大约3个月的时间基本理清了混淆壳的原理,简单写了一个app,fastvm, 针对libmakeurl2.4.9.so 可以生成原始的cfg流图,和优化过的cfg流图, 以下是函数sub_342d的 ]

优化过的如下:

#预备知识
写这个app的时候主要参考了3本书

代码里很多注释会有 P245,这样的写法,一般是代表1, 2中的某一本的第245页,
@aar 指的1
@MCIC 指的3

这个分析的很多了,我就不赘述了

因为我们要通过编译后端优化彻底干掉混淆,必须得自己实现数据流,所以无法使用别人的反汇编引擎,我们通过实现一个简单的正则表达式引擎来对elf的代码进行反汇编

我们重新实现一个正则表达式引擎,并增加自己的命名变量,举例:

cmp

我们定义一个正则表达式如下:

支持重复

图片描述

先不管o的定义,我们看thumb_inst_cmp里面支持了 lm3和ln3,这句表达式的意义是:

我们在读取一个二进制串时,他以 0100 0010 0开始,接下去的三位提取到lm变量中,后面的3位提取到 ln变量中,这条指令用thumb_inst_cmp进行深度解析。这些变量都存放在一个arm_inst_ctx的结构中,

我们基于这个规则,完整的定义出整个arm thumb的指令集(暂时只定义了需要的部分)

然后基于此,按 nfs -> dfs -> 2d-trans的方式来生成整个状态机图

nfa的如下:
thumb指令nfa状态机

nfa在归纳同字符集可到达的边,生成dfa, thumb指令dfa状态机

PS. 不知道为什么,看雪把我图片压缩了,想看具体大图,可以看着 状态机大图
详细分析可以参考任意一本形式语言与自动机理论,或者 龙书, 《parsing techniques》都可。

语法分析反而比较简单,因为汇编语言不存在特殊的语法,唯一特殊的指令是it指令,在ida中arm的it指令是不会做切分,和上下的代码完整的放到一个block中,比如
it示例

但是假如我们需要用编译优化去掉混淆,必须得把it指令做为一个单独的bcond指令来处理,比如上图中的代码,转换成c语言后如下:

基于此,上图中的指令,被我们转换成如下格式:
it转换过后的

b<cond>指令没啥太多需要注意的,他末尾跟着的指令指向他的false_label分支,它的跳转指向它的true_label分支,这一点上和it是相反的

因为要做后端的数据流分析,必须给出所有指令的def和use关系, 举几个例子说明需要注意的地方,因为是直接在arm的汇编上做的def和use,所以实际的代码写起来很多的corner case非常难处理,举一些典型的例子说明下

进入函数前,需要定义r0-r3, sp, pc寄存器

出函数前,需要使用r0-r1, sp, pc寄存器

生成了def和use数据以后,就可以方便的做活跃性分析了,活跃性分析本身的算法很简单,伪代码如下, @MCIC.P232:
活跃性分析伪代码

对照代码,参考fastvm中的minst.c:minst_blk_liveness_calc

和上面的代码差不多是一一对应的

在进行了活跃性分析以后,死代码删除就非常简单了,对应的c代码在minst.c:minst_blk_dead_code_elim中,原理就是:

某条指令被def,但是不在它的出口活跃列表中,则可以删除,举例

因为r0被定义了二次,导致了指令1中的r0,在到达2的时候已经死了。也就是不在它的出口活跃列表中了。

到达主要是研究,某条指令最远到达过什么地方,它在常量传播的分析中非常有用
假如你也打算深入分析混淆壳,建议认真读下 @MCIC的17章,Dataflow Analysis。

在上图中b就可以被优化为2,同时cmp的结果也是为1,按算法上的描述就是
假如某条指令被常量定值以后,后续所有使用过此条指令的代码都要根据自己的语义进行跟新

判断是否有混淆,我们需要遍历所有cfg节点,然后判断其cmp指令的左右2个参数,是否大部分都是常数,假如是的话,则认为其处在混淆cfg的某一个节点中,那么直接找到其支配节点即可。

ps. 这里为了方便,我直接偷懒把一个函数中前缀最多而且大于7的节点,当作了混淆状态机的支配节点了。

这里有个简单的办法,就是先过滤所有前缀节点小于7的可以直接抛弃,因为混淆函数的支配节点前驱节点都特别多,比如:

图片描述
图片描述
图片描述

支配节点的算法,我没实现,但是原理上比较简单,可以参考@MCIC.C18(Loop Optimization)

这里简单介绍下什么是支配节点

有些书籍上可能也翻译为必经节点,也就是从外部节点进入到内层循环时必须经历的节点,这个节点非常有用

先看如下代码:

这个代码,我们手工尝试对他做混淆

我们简单审读下这个代码,把a定义为状态寄存器,只要你跟着状态寄存器走,整个循环自然会被全部展开。

上面的例子只是演示了简单的statment该如何混淆,那么如何实现循环,判断呢?

判断和循环从原理上来说都是一样的,只是判断以后,跳转点,跳到前面的某点上。从头开始,在来个例子,我们对上面的例子进行拓展:

混淆过后

大家简单肉眼跟读下这个代码,发现整个混淆壳其实就是一个稍微复杂的常量状态机。只要你顺着状态寄存器走,很容易就能跟踪出整个脉络

my1988在他的帖子my1988算法里面提到过一个简单的算法,把混淆过的代码的cfg分为2部分,一部分为 控制节点 一部分为 状态节点,然后连接 状态节点,删除 控制节点,这个算法是有问题的,因为假如原代码里面,散落了一,二条代码在 控制节点内,就会丢代码了。

不过我们只需要简单改造即可。

代码: minst_dob_analyze

参考代码 minst_csm_expand

为什么要拓展说一下,参考以下的代码:

混淆过后

我们看一下,到了cfg2的时候,a被b定值了,但是b实际上有2个值的,假如你要进行反混淆,必须得跟踪出唯一的定值路径才行,但是从cfg2开始已经不可分析了,那如何处理呢?

把cfg2改为如何形式:

有人看到这里,可能感觉很奇怪,为什么不直接改成如下形式呢

这是和编译优化的到达定值的生成有关,到达定值分析很蠢的,他只有对明确的定值语句,才能给出数据流分析。他无法识别

在某种程度上是等价的。

参考: minst_blk_classify

我们把cfg节点分为3类:

分类算法非常简单,用dfs即可:

循环遍历所有不为csm_out的bcond节点,也就是有条件跳转的节点

然后分析其跳转指令参考的cmp指令的,2个寄存器的值,分析其是否为常数,是的话,进入步骤5,定值点跟踪

在进入定值点跟踪时,我们先简单说一下一些术语

图片描述

以下的示例代码来自于 libmakeurl2.4.9.so的jni_OnLoad,ida f5以后,代码如下:
图片描述

v2是混淆的状态寄存器

这个是IDA F5后的分析结果,我们在做实际的编译优化时,是在cfg流图上做的,我们用上面语法分析优化后得到的cfg流图操作一遍:

图片描述

连接新的cfg和cfg15,最后得到的图如下

图片描述

cfg51就是新生成的节点,里面有大量的指令是死的,比如 216, 219, 222都是重复定义的,后面的编译优化分析都可以彻底干掉,上图中的cfg51被编译优化后变成了,如下形式:

优化后cfg51

然后我们在找v2的另外的定值点,重复这个过程

遍历所有的cfg,一直到找不到找不到可以跟踪的定值点结束,也就是最开始的那副优化过的图像。

一般优化到最后以后,大量的节点都找不到前驱节点都可以删除,后面优化的就是最终的图像了。

这里是一个更复杂的例子,因为图像太大了,我直接放在github上。

sub_367c optimization

我对ssa不怎么熟悉,感觉表达能力和普通的数据流分析差不多,所以没上

我对android下调试还不熟悉,小米的手机root需要申请权限,很久都没发权限邀请给我

很困难,一开始用了别名分析,结果错误的优化了某些变量,想用别名分析,可能需要深入的分析每个函数之间的调用关系才行

 
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-7-13 09:48 被baikaishiu编辑 ,原因:
收藏
免费 25
支持
分享
最新回复 (31)
雪    币: 529
活跃值: (1005)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享!!!
2020-7-12 02:59
0
雪    币: 19950
活跃值: (4942)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2020-7-12 07:36
0
雪    币: 4883
活跃值: (18890)
能力值: ( LV13,RANK:317 )
在线值:
发帖
回帖
粉丝
4
感谢分享~
2020-7-12 09:56
0
雪    币: 47
活跃值: (418)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
5
2020-7-13 10:17
0
雪    币: 2907
活跃值: (1301)
能力值: ( LV12,RANK:215 )
在线值:
发帖
回帖
粉丝
6
2020-7-13 10:44
0
雪    币: 4005
活跃值: (2193)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
2020-7-13 10:49
0
雪    币: 344
活跃值: (314)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
感谢分享 楼主 有想过生成llvm ir吗
2020-7-13 10:53
0
雪    币: 2458
活跃值: (3328)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
9
hasking 感谢分享 楼主 有想过生成llvm ir吗
有的,接下去要生成标准的elf格式的so和对抗其他的一些混淆,不想写了, 正在考虑这个东西,
2020-7-13 11:21
0
雪    币: 498
活跃值: (4206)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
好狠
2020-7-13 13:28
0
雪    币: 1867
活跃值: (3973)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
11
2020-7-13 14:13
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
12
2020-7-13 14:51
0
雪    币: 116
活跃值: (530)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
狠人
2020-7-13 15:31
0
雪    币: 29
活跃值: (499)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
14
腻害!为楼主的耐心点赞。其实基于静态的数据流分析,修复 CFG 以达到自动反混淆 OLLVM FLA 之类的技术是有的。提一下我的个人建议哈,如有不对欢迎斧正:
1. 最好不要基于特定的指令集做分析,而应当将之转换为统一的 IR,这样编写的反混淆工具才能跨 ISA 使用;这块 IDA 的 microcode 和 GHIDRA 的 p-code 都是非常不错的参考;
2. 提到数据流分析就不得不提及 SSA, 它本身就是为了简化数据流分析而来的,我个人建议还是使用它,最直接带来的好处是 def-use chain 变得非常简单;当然也有负面影响,比如引入了 phi-node,学习和处理成本较高(特别是修改 CFG 之后得手工修复 DFG),但瑕不掩瑜。作为对比, IDA microcode 是 non-SSA, GHIDRA p-code 是 SSA, 在反混淆这类需要大量数据流分析的场景,后者更得心应手;
3. 数据流分析中最让人头疼的问题之一是“涉及到栈操作“的数据流追踪,这里面的学问太多了(Load Gurad, Store Guard, Stack pointer Normalize, etc),IDA 在这块非常保守,GHIDRA 相对激进一些;我猜测你的别名分析出错应该跟这块有关;
4. 欢迎探讨编译器 and 反编译器相关内容,国内搞这块的兄弟真的太少了
2020-7-13 18:22
1
雪    币: 2458
活跃值: (3328)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
15
非常感谢回复,先回答前面2个问题
2020-7-13 18:55
1
雪    币: 2458
活跃值: (3328)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
16
万抽抽 腻害!为楼主的耐心点赞。其实基于静态的数据流分析,修复 CFG 以达到自动反混淆 OLLVM FLA 之类的技术是有的。提一下我的个人建议哈,如有不对欢迎斧正: 1. 最好不要基于特定的指令集做分析 ...

>> 1. 最好不要基于特定的指令集做分析,而应当将之转换为统一的 IR,这样编写的反混淆工具才能跨 ISA 使用;这块 IDA 的 microcode 和 GHIDRA 的 p-code 都是非常不错的参考;


你说的很对,我这边已经在考虑转成更加标准的ir来处理了,可能会使用 llvm的ir。


当时ida的microcode考虑过,我觉得ida得比llvm得好,不过我因为付不起昂贵的正版费用,所以可能不会采用这个。


>> 2. 提到数据流分析就不得不提及 SSA, 它本身就是为了简化数据流分析而来的,我个人建议还是使用它,最直接带来的好处是 def-use chain 变得非常简单;当然也有负面影响,比如引入了 phi-node,学习和处理成本较高(特别是修改 CFG 之后得手工修复 DFG),但瑕不掩瑜。作为对比, IDA microcode 是 non-SSA, GHIDRA p-code 是 SSA, 在反混淆这类需要大量数据流分析的场景,后者更得心应手;


谢谢建议,我在实际使用得时候有几个问题也感觉必须得用ssa

1. 基于数据流得分析,可能是我数据结构实现得有点问题,到后面节点过多时处理得非常慢,据说ssa性能非常高

2. 感觉在查询某些定值得使用之类得小代码上,远不如ssa那么方便


这边已经完整得读完了ssa得算法,心理大概有数了。


>> 3. 数据流分析中最让人头疼的问题之一是“涉及到栈操作“的数据流追踪,这里面的学问太多了(Load Gurad, Store Guard, Stack pointer Normalize, etc),IDA 在这块非常保守,GHIDRA 相对激进一些;我猜测你的别名分析出错应该跟这块有关;


这个问题挺复杂,我详细说下,我这边是已经查出了别名分析失败得原因,但是有些非常难克服的问题需要函数间优化,所以我后来就关闭掉了


如下一段代码,假设是从ida f5以后得

int a;
int b;
int c;

r0 = &a;

test();


以上代码,有几个问题


  1. 你不确认r0是否是test的传入参数

  2. 即使你确认了r0是test的传入参数,你也不确认它是否会只修改a,而不修改b 和c,因为真实的代码可能是这样的

struct {
   int x;
   int y;
   int z;
} point;

r0 = &point;
test(r0);

function test(point *p)
{
   p->x = 1;
   p->y = 2;
   p->z = 3;
}


因为这个情况,当你发现了有函数调用时,假如里面传入了一个局部变量的地址,很可能从这个局部变量开始后的所有局部变量都是属于同一个结构的,都会被修改,更加复杂的是有些变态的实现,这个局部变量以前的地址也会被修改。


所以假如真的上别名分析优化,它必须保证在2个别名之间绝对不能出现函数调用,所以我后面直接关闭掉了别名分析的代码。只能处理一些简单的,比如


ldr r0, [sp + 4]

....

中间不能出现函数调用

....

str r0, [sp + 4]


综上所述,假如你无法分析出某个调用函数的参数列表,碰到函数调用,你的别名就容易出错,我后面会尝试读更多的文章,看下有没好的解决办法

最后于 2020-7-13 19:12 被baikaishiu编辑 ,原因:
2020-7-13 19:10
1
雪    币: 1841
活跃值: (1290)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
17
2020-7-13 20:57
0
雪    币: 3735
活跃值: (1188)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
18
膜!
2020-7-15 16:30
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
19

IDA 的 microcode 提供了完整的 def-use chain 和 value-set analysis 的 api, 使用他的话可以很快实现这个思路,虽然 API 不是很好用。

(我就是这么干的,干啥啥不行,调接口第一名。


LLVM 是 SSA-IR,microcode 没有使用 SSA,在处理一些 phi-node 展开的代码确实会比较蛋疼,不知道使用 SSA 的 反编译 IR 是不是就会好一些。

ghidra 的 pcode 没研究过, 不过 binary ninja  的 ir 可以考虑一下,其也支持提供 SSA 形式的 IR,并且也已经做了 vsa

最后于 2020-7-15 16:45 被葫芦娃编辑 ,原因:
2020-7-15 16:41
0
雪    币: 2458
活跃值: (3328)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
20
葫芦娃 IDA&nbsp;的&nbsp;microcode&nbsp;提供了完整的&nbsp;def-use chain&nbsp;和&nbsp;value-s ...
1. 我有尝试过ida的microcode,但是我的ida有点问题,后来被我关闭了

2. 关于ir,我的minst.h其实是打算用来放自己定义的ir,但是因为优先级不高,而且暂时不需要很深刻的定义,写了一半就放在那,后来会严格的定义出非常规范的ir,所有的优化都是基于ir来做。现在正在让代码自动重新生成elf格式的so在努力。
2020-7-15 22:56
0
雪    币: 316
活跃值: (758)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
同样都有IDA 你却能做出如此之事 是在让我羞愧难当  学习了
2020-7-16 09:45
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
22
baikaishiu 1. 我有尝试过ida的microcode,但是我的ida有点问题,后来被我关闭了 2. 关于ir,我的minst.h其实是打算用来放自己定义的ir,但是因为优先级不高,而且暂时不需要很深刻的定 ...
 赞, 我每次自己写 IR 到最后都会发现 “写得什么玩意,还不如直接用现成的”
2020-7-16 10:50
0
雪    币: 2458
活跃值: (3328)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
23
葫芦娃 [em_63] 赞, 我每次自己写 IR 到最后都会发现 “写得什么玩意,还不如直接用现成的”
····我也打算用现成得啊,昨天翻了下ghidra得ir,发现和arm还挺接近得,打算直接把他得形式抄过来···
2020-7-16 11:42
0
雪    币: 3372
活跃值: (762)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
24
感谢分享~
2020-7-18 19:19
0
雪    币: 1705
活跃值: (676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
老哥用的啥画图软件,强烈需要
2020-8-4 00:02
0
游客
登录 | 注册 方可回帖
返回
//