首页
社区
课程
招聘
[原创]基于二进制代码的代码混淆研究
发表于: 2019-8-31 00:10 18872

[原创]基于二进制代码的代码混淆研究

2019-8-31 00:10
18872
《基于二进制代码的代码混淆研究》
0 摘要
本文首先介绍了Collberg对代码混淆的分类方式以及定义,而后介绍了一些常见的混淆策略,最后讨论了如何构建一个二进制混淆引擎。
1代码混淆研究综述
1.1 混淆的分类
早在1997年,学术界就开始了代码混淆的研究。目前使用最广泛的混淆分类方式是Collberg[1]于1997年针对JAVA程序的安全问题提出的。根据Collbreg的定义,混淆可以分为以下四种类型:布局混淆、预防性混淆、数据混淆、控制混淆。布局混淆指删除或修改源代码中的对攻击者由于的信息,如变量名、调试信息等。显然,布局混淆在二进制代码混淆中可以忽略不计。预防性混淆指预先针对特定的反编译工具,解混淆方法进行防备。
控制混淆是目前研究的重点。Collbreg将控制混淆分为以下三类:聚合、排序、计算。聚合指将逻辑上相关的计算分割,或者将逻辑上不相干的计算聚合在一起。排序指将计算在程序中的分布随机化处理。计算指插入混淆计算代码来隐藏真实的控制流,包括扩展循环条件,引入不透明谓词等。值得一提的是,Collberg提出的排序混淆在二进制代码中实现难度较大。
数据混淆被分为以下三种:聚合、重组、编码。
当然,还有许多学者对此做了有关研究,如Chenxi Wang[10],Wroblewsk[9]等。前者是基于C语言程序的,后者是基于二进制代码的。值得一提的是,目前使用非常广泛的控制流平坦化算法正是Wang提出的。

1.2 混淆的定义
Collberg将混淆描述为一个从源程序P到混淆后的程序P’的等价转换。更精确地说,合法的转换必须要满足以下两个条件:如果P终止失败或者异常终止,那么P’可以选择终止或不终止;在其它情况下,P’必须要正常终止,并且产生与P’相同的输出。这里的输出指的是用户可观测到的P的行为,对于那些不可观测到的行为,如创建文件与访问网络等,都是可以在合法转换中出现的。贾福春在[6]对该定义进行了修正,提出了弱语义等价。

2 常见混淆策略
2.1 基于数据流
2.1.1 指令移动

在二十年前,甚至更早,就出现了使用JMP IMM32指令连接乱序后的代码的混淆技术,以此来打乱代码顺序局部性。指令移动与此不同,指令移动是将基本块内的语句进行顺序重排。z0mbie[12]首次引入x86指令对象集的概念,解决了相邻指令的交换。潘雁[7]基于Wroblewski提出的x86系统指令形式化定义,论证了指令交换的充分条件,并提出了针对数学与逻辑运算指令进行交换时,还需要考虑交换律等问题。文献[7]同时提出了一种模拟退火算法的指令随机交换混淆算法。
本文提出使用定值分析,构建指令移动混淆,非相邻指令交换。以一段代码为例:
0 Mov var1.500
1 Mov eax,var1
2 Sub var1,20
3 Add eax,2018
4 Push eax
5 Call func
为了方便描述,以{变量1 : 定值来源行号1,...,变量i: 定值来源行号i}来描述指令的上下文。
1行指令可以进行移动,但不能移动到3行指令之后,因为3行指令的上下文是{eax:1,var1:2},如果强行移动到3行指令以后,那么3行指令也要跟随移动,这个过程十分复杂,可谓牵一发而动全身。1行指令可以移动到2行与3行指令之间,1行指令的上下文是{var1:0},而移动之后则其上下文变为{var1:2},为了解决这个问题,移动时需要引入一个变量保存var1:0的值。引入的变量可以储存于活跃分析以后确定的可以使用的死寄存器,或者申请的一块内存。前者是不影响上下文的指令移动,后者则是影响上下文的指令移动。
当实现不影响上下文的指令移动时,我们要确保两个条件:1.指令移动的目标处的上下文来源必须与原位置处相同;2.指令移动的目标位置必须在第一次引用了移动指令的定值变量的指令前。
实现影响上下文的指令移动则比较自由,本文便不再赘述。
请允许我在此处出戏三秒钟,z0mbie是一位在代码变形方面造诣非常深的大牛,他的个人技术站点上的资料非常值得研究。顺便在此膜一下某零前辈。画外音:一位自称是一位十二岁花季美少年的铁杆粉丝的蒙面黑衣男子路过,并膜了一下十二岁的蒙面花季美少年。
2.1.2 插入死代码

死代码分为两种,一种是永远不会被执行的代码,常与永真(假)不透明谓词结合使用,另一种是只影响死变量(死变量是在对代码进行混淆时由混淆器分析出的)的语句。以几段代码为例,简单介绍一下后者:
Mov eax,2010
Mov eax,var1

Mov eax,2018
Call stdcall_func

Mov eax,2018
Push eax
Push var1
Call Obfscall
Obfscall:
Push var1
Call stdcall_func

这是三个死代码插入的例子。毫无疑问,例1的解混淆是最为简单的,例2使用了函数返回值使用eax传递的技巧,例3的复杂度是较高的。我举出这三个例子并不是为了指出改进死代码生成算法的必要性,而是为了强调将混淆引擎设计为支持轮换调用各种混淆算法的形式的必要性。如果说死代码生成了一个变量,那么在其它混淆算法中最好设法引用该变量。
2.1.3 常量展开
常量折叠是最早的基本编译优化手段之一。这种优化的目的是将可以在编译期就能推导出结果的计算用结果来替换。

Mov eax,2018
Add eax,2

显然,如果忽略标志位的变化,该段代码等价于Mov eax,2020。

2.1.4 数据储存、编码
数据的储存方式都是约定成俗的。如果我们要储存整数1238326,用int类型是首选,当然,用可变类型也是可以的。同理,三维数组也可以降维为一维数组。更进一步地,储存一个int变量,我们可以用连续的4字节内存,也可以采用不连续的4字节内存,当然,使用后者要自己调整加减乘除等运算。
对数据进行编码也是一种混淆方案。在对编码后的数据进行操作时,有必要将数据解码,显然,这会暴露解码函数。同态加密可以解决这个问题,使用同态加密能够在数据解码前操作它们,通过对编码后的数据定义一个等价运算。目前已经有不少文献在面向混淆的视角下讨论过这种问题,但可惜这门学科所需要的知识背景远超我的能力范围,有兴趣的读者请自行研究。
2.1.5 恒等运算替换
使用布尔代数,将一些简单的运算扩展为更为复杂的逻辑运算,该技巧为许多混淆引擎采用,比如“臭名昭著”的VMP万用门。掌握基本的几个等值式就可以进行逻辑运算的推导,该部分内容较为简单,就不再做赘述。
2.1.6 模式替换
模式替换的概念很简单,混淆者定义一些较复杂的指令序列,在待混淆程序中进行语义等价的替换。
Push imm32

Lea esp,[esp-4]
Mov [esp],imm32

Push reg32
Mov reg32,esp
Xchg [esp],reg32
Pop esp
Mov esp,[imm32]

Push reg32
Xor reg32,reg32
Dec reg32
S1: Inc reg32
Cmp reg32,esp32
Jnz S1 ;假定esp的值不可能等于-1
Xchg [esp],reg32
Pop esp
Mov esp,[imm32]


聪明如你,可看出了上面四段代码的等价关系?这三个例子说明,模式替换的匹配替换过程是可以不断继续下去的。模式替换对应的就是编译优化技术中的窥孔优化。第4段代码中对Mov的变形是一位前辈指点我的,据前辈说是污点漂白的一个上不得台面的小trick。

2.1.7 常量隐藏
将常量值直接暴露在指令的操作数中,可能会向攻击者透露一些有用的信息。将代码中的明文常量值隐藏起来似乎是非常有必要的。
0 Mov eax,2018
1 Mov eax,[402024] ;地址为402024的全局变量中储存的值为2018
上述两段代码中,1行指令较之0行指令有了一点安全性上的提升,但显然还不够。为了将常量隐藏,我们可以引入一些数学技巧,如sin^2(x) + cos^2(x) = 1,以此构造一些返回值恒定的函数。再进一步,我们可以利用一些API的特性,如故意传递错误的参数,利用API返回的异常代码。如果异常代码是200,而我们需要的定值是100,那该怎么办呢?答案当然是,得到异常代码后减去100啊!
2.2基于控制流
2.2.1 函数内联,外联和混合
函数内联的定义不再赘述。函数外联指将某一函数的一部分提取出来单独构成一个函数,并将其替换为对新创建的函数的调用。
函数混合指将多个函数混合在一起。在调用混合后的函数前,由混淆代码调整上下文,来指定究竟应该执行哪个函数。以一段代码为例。
fun1() {
  s1
  ...
  sn
}
fun2() {
  k1
  ...
  kn
}
fun_mix(n) {
  if (n) {
    s1
    ....
    sn
  }
  else {
    k1
    ...
    kn
  }
  
}
当然,实际混合时不会如此简单,比如可以用多个参数来指示欲执行函数。
2.3 基于控制流与数据流
2.3.1 不透明谓词
Collberg把不透明谓词定义为一个布尔表达式。该表达式的值在混淆前是已知的,且混淆后再探明其性质需要付出一定代价。如果该表达式恒为true,则称为永真不透明谓词,记为PT。同理,还有永假不透明谓词(PF),可真可假不透明谓词(P?)。值得一提的是,可真可假不透明谓词的两条分支处的代码块在语义是上等价的,但可以使用不同的混淆策略对其进行混淆。吴伟民[2]等首次提出了N态不透明谓词。
并不是说谓词所涉及到的数学知识越多就越强大,足够复杂的谓词确实可以难倒静态分析工具,但人工检测谓词的效率有时却出奇的高。这正是Collberg[1]提出的混淆评估指标中的隐蔽性。

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

上传的附件:
收藏
免费 9
支持
分享
打赏 + 20.00雪花
打赏次数 5 雪花 + 20.00
 
赞赏  Editor   +2.00 2019/09/06 精品文章~
赞赏  orz1ruo   +5.00 2019/09/03 精品文章~
赞赏  黑手鱼   +10.00 2019/09/03 继续更新,一直有关注
赞赏  上海刘一刀   +2.00 2019/09/02
赞赏  Anskya   +1.00 2019/09/01 是萌面花季美少年哦~
最新回复 (8)
雪    币: 2635
活跃值: (5083)
能力值: ( LV9,RANK:225 )
在线值:
发帖
回帖
粉丝
2
1.指令移动应该归入基于控制流一类。
2.很多地方我一笔带过,因为在代码混淆之我见系列中已经详细论述过。
3.CFG的构建是分析的基础,但如果程序使用了__try等异常处理语句很难构建出基本块。
2019-8-31 09:11
1
雪    币: 2166
活跃值: (3226)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
3
一直挺喜欢你的这种论文类型的理论研究,研究的还比较透彻,可惜论坛规定相同主题不能设置多个精华,所以遗憾我只能给一个优秀,希望你再接再厉,而且也希望激励论坛内其他小伙伴,还是要一步一个脚印,脚踏实地的学习,逆向最忌讳的是浮躁, 勿在浮沙建高塔
最后于 2019-8-31 19:21 被xiaohang编辑 ,原因:
2019-8-31 19:20
0
雪    币: 1705
活跃值: (676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
楼主你这算是打水漂了还是打算继续攻坚啊
2019-9-1 08:54
0
雪    币: 381
活跃值: (165)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
写的不错!点赞,共勉!方便加个好友一起学习吗
2019-9-2 11:28
0
雪    币: 977
活跃值: (435)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
6
优秀
2019-9-2 22:49
0
雪    币: 26245
活跃值: (63297)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
8
打赏、点赞、收藏,三连,必须有
2019-9-6 14:27
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
有没有高手能破解脱壳App有的话加我Q:1853328299 重金,
2020-6-11 22:38
0
游客
登录 | 注册 方可回帖
返回
//