首页
社区
课程
招聘
[原创]去除反混淆后程序中的冗余汇编代码
发表于: 2024-7-7 15:02 3045

[原创]去除反混淆后程序中的冗余汇编代码

2024-7-7 15:02
3045

一、前言

在之前去混淆的过程中,发现去混淆的程序中,伪代码与原程序伪代码相差无几,然而如果仔细观察汇编代码的话,可以发现存在较多的冗余代码,即混淆过程中产生的垃圾代码。那么如何去除这些冗余汇编指令呢?

二、基于Liveness Analysis的想法

先给出我们需要解决一个例子(这是一个虚假控制流混淆中的部分代码,其中标*的为冗余汇编指令):

  mov     rax, [rbp+var_10]
  movsxd  rcx, [rbp+var_14]
  movsx   eax, byte ptr [rax+rcx]
  add     eax, [rbp+var_18]
  mov     [rbp+var_18], eax
* mov     rax, offset x
* mov     eax, [rax]
* mov     rcx, offset y
* mov     ecx, [rcx]
* mov     edx, eax
* sub     edx, 1
* imul    eax, edx
* and     eax, 1
* cmp     eax, 0
* setz    al
* cmp     ecx, 0Ah
* setl    cl 
* or      al, cl    
* test    al, 1      
* jnz     loc_401235

最开始的想法是从第20行的汇编指令开始,跟踪与该指令以及后续指令相关的指令。如何跟踪呢?简单想就是跟踪这些指令使用到的寄存器。

2.1 Liveness Analysis

Liveness Analysis是一种数据流分析技术,用于确定程序中的每个变量在程序的不同点上是否“活跃”。“活跃”的意思是变量在某个程序点后将被使用,且在这之前没有被重新定义。这项技术在编译器优化、寄存器分配和死代码消除中起着关键作用。

本文章中主要用到了里面的defuse集合的想法。

  • def集合:当前汇编指令显式和隐式改变的寄存器、内存的集合。
  • use集合:当前汇编指令显式和隐式使用的寄存器、内存的集合。

2.2 改进Liveness Analysis以适配

然而从图中看,似乎Liveness Analysis技术没有考虑到标志寄存器的改变和使用。对于Liveness Analysis技术能否解决我所预设的问题,我也不太清楚。因此在此基础上,进行如下改进:

  • 引入标志寄存器来跟踪相关指令,这对于一些条件指令的跟踪是有帮助的。
  • 对于内存操作数(例如[rbp+var_10]),还应提取其表达式中的寄存器,存入use集合中。因为我认为既要跟踪内存地址是怎么来的,也要跟踪内存地址对应的值是怎么来的。
  • 对于指令中操作数既不是寄存器,也不是内存的(例如var_4、全局变量x),应认为它们是常量,即不加入use集合中。
  • 由于指令中出现的寄存器名可能是同一寄存器的不同位数区分(例如rax, eax, ax, ah, al),统一将它们转换成最大位数的寄存器名。

对刚才那个例子进行defuse集合的分析,结果如下:

  mov     rax, [rbp+var_10]              def={rax}       use={[rbp+var_10], rbp}         
  movsxd  rcx, [rbp+var_14]              def={rcx}       use={[rbp+var_14], rbp}
  movsx   eax, byte ptr [rax+rcx]        def={rax}       use={[rax+rcx], rax, rcx}
  add     eax, [rbp+var_18]              def={rax}       use={rax, [rbp+var_18], rbp} 
  mov     [rbp+var_18], eax              def={[rbp+var_18]}       use={rax, rbp}//理应认为[rbp+var_18]的寻址用到了rbp
* mov     rax, offset x                  def={rax}       use={}//认为x是具体值,不加入到use集合中
* mov     eax, [rax]                     def={rax}       use={rax, [rax]}//既要知道内存地址对应的值从哪来的,也要知道内存地址从哪来的
* mov     rcx, offset y                  def={rcx}       use={}//认为y是具体值,不加入到use集合中
* mov     ecx, [rcx]                     def={rcx}       use={rcx, [rcx]}
* mov     edx, eax                       def={rdx}       use={rax}
* sub     edx, 1                         def={rdx}       use={rdx}
* imul    eax, edx                       def={rax}       use={rax, rdx}
* and     eax, 1                         def={rax}       use={rax}
* cmp     eax, 0                         def={rflags}     use={rax}
* setz    al                             def={rax}       use={rflags}
* cmp     ecx, 0Ah                       def={rflags}     use={rcx}
* setl    cl                             def={rcx}       use={rflags}
* or      al, cl                         def={rax}       use={rax, rcx}
* test    al, 1                          def={rflags}     use={rax}
* jnz     loc_401235                     def={}          use={rflags}

具体跟踪的初步想法是:

  1. 给定某一指令 f99K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">III ,初始化跟踪集合 94fK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">trace=ustrace = use_Itrace=useI (指令 d7eK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">IIIa51K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">useuseuse 集合),初始化相关指令列表 ea3K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">relevantInsns=[I]relevantInsns = [I]relevantInsns=[I]
  2. 往上追溯每条指令,如果 d1fK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">tracedetrace \cap def_{I_i} \neq \emptysettracedefIi= ,那么认为指令 074K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">I_iIi 是相关指令,将指令 247K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">I_iIi 加入到 d6cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">relevantInsnsrelevantInsnsrelevantInsns中, 同时在 e0dK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">tracetracetrace 集合删除交集中的元素,并将指令 a45K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">I_iIi56cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">useuseuse 集合中的元素加入进来,即进行如下运算:509K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">trace=(trace(tracede))ustrace = (trace - (trace \cap def_{I_i})) \cup use_{I_i}trace=(trace(tracedefIi))useIi 。如果 953K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">tracede=trace \cap def_{I_i} = \emptysettracedefIi= ,那么认为指令 2d3K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">I_iIi 是不相关指令,不进行任何操作。
  3. 在追溯的过程中,如果 8feK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4M7K6i4K6u0W2L8%4u0Y4i4K6u0r3x3e0V1&6z5q4)9J5c8V1#2S2N6r3S2Q4x3V1k6y4j5i4c8Z5e0f1H3`.">trace=trace = \emptysettrace= 或者待跟踪指令集为空,则认为跟踪结束。

感觉说的有些混乱,就那上面这个例子解释一下吧。首先我们假设跟踪的指令为jnz loc_401235,后续步骤如下:

初始化: trace = {rflags}	relevantInsns = [20]	(为了简洁,用行号代替指令)	
往上分析:
19: trace ∩ use = {rflags},  是相关指令。   更新 trace = {rax}, relevantInsns = [19, 20]
18: trace ∩ use = {rax},     是相关指令。   更新 trace = {rax, rcx}, relevantInsns = [18, 19, 20]
17: trace ∩ use = {rcx},     是相关指令。   更新 trace = {rax, rflags}, relevantInsns = [17, 18, 19, 20]
16: trace ∩ use = {rflags},  是相关指令。   更新 trace = {rax, rcx}, relevantInsns = [16, 18, 19, 20]
15: trace ∩ use = {rax},     是相关指令。   更新 trace = {rcx, rflags}, relevantInsns = [15, 16, 18, 19, 20]
14: trace ∩ use = {rflags},  是相关指令。   更新 trace = {rax, rcx}, relevantInsns = [14, 15, 16, 18, 19, 20]
13: trace ∩ use = {rax},     是相关指令。   更新 trace = {rax, rcx}, relevantInsns = [13, 14, 15, 16, 18, 19, 20]
12: trace ∩ use = {rax},     是相关指令。   更新 trace = {rax, rcx, rdx}, relevantInsns = [12, 13, 14, 15, 16, 18, 19, 20]
11: trace ∩ use = {rdx},     是相关指令。   更新 trace = {rax, rcx, rdx}, relevantInsns = [11, 12, 13, 14, 15, 16, 18, 19, 20]
10: trace ∩ use = {rdx},     是相关指令。   更新 trace = {rax, rcx}, relevantInsns = [10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
9: trace ∩ use = {rcx},      是相关指令。   更新 trace = {rax, rcx, [rcx]}, relevantInsns = [9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
8: trace ∩ use = {rcx},      是相关指令。   更新 trace = {rax, [rcx]}, relevantInsns = [8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
7: trace ∩ use = {rax},      是相关指令。   更新 trace = {rax, [rax], [rcx]}, relevantInsns = [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
6: trace ∩ use = {rax},      是相关指令。   更新 trace = {[rax], [rcx]}, relevantInsns = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
5: trace ∩ use = {},         不是相关指令。     trace = {[rax], [rcx]}, relevantInsns = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
4: trace ∩ use = {},         不是相关指令。     trace = {[rax], [rcx]}, relevantInsns = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
3: trace ∩ use = {},         不是相关指令。  	 trace = {[rax], [rcx]}, relevantInsns = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
2: trace ∩ use = {},         不是相关指令。   	 trace = {[rax], [rcx]}, relevantInsns = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
1: trace ∩ use = {},         不是相关指令。  	 trace = {[rax], [rcx]}, relevantInsns = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20]
待分析指令集为空,结束跟踪

对于上述例子,我们的想法成功的追踪到了所有冗余指令。然而当存在cmovcc指令时,我们的想法并不是很理想。

2.3 cmovcc指令所带来的问题

以下是控制流平坦化混淆的垃圾汇编指令(其中标*的为冗余汇编指令)。

* mov edx, [rbp+var_4]			def={rdx} 			use={[rbp+var_4], rbp}
* mov eax, 4A...h 				def={rdx} 			use={}
* mov ecx, 32..h				def={rcx} 			use={}
* cmp edx, 2					def={rflags} 		use={rdx}
* cmovnz eax, ecx				def={rax} 			use={rcx,rflags}	或者	def={}		use={rflags}
* mov [rbp+var_1c], eax  		def={[rbp+var_1c]} 	use={rax, rbp}
* jmp loc_401418 				def={} 				use={}

由于cmovnz存在执行和不执行的情况,因此需要分两种情况讨论:

  • 当标志寄存器满足cmovnz的条件时,执行cmovnz指令,此时def={rax}, use={rcx,rflags}
  • 当标志寄存器不满足cmovnz的条件时,不执行cmovnz指令,此时def={}, use={rflags}

[注意]看雪招聘,专注安全领域的专业人才平台!

收藏
免费 6
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回