当前位置:网站首页>《CTF特训营》学习总结——Reverse:干扰分析技术及破解
《CTF特训营》学习总结——Reverse:干扰分析技术及破解
2022-08-05 22:50:00 【PICACHU+++】
常见的干扰逆向分析的技术有花指令、反调试、加壳、控制流混淆、双进程保护、虚拟机保护等技术。
一、花指令
1.定义
花指令只一种保护代码比较简单的方式,其原理是在原始代码中插入一段无用或者能干扰反汇编引擎的代码,这段代码并没有什么功能性作用,只是一种扰乱代码分析的手段。
2.基本思路
花指令主要影响静态分析,在IDA中表现为一些指令无法识别,导致某些函数未能识别,从而无法对这些函数进行反编译。在IDA中手动将花指令patch成nop空指令,可以去除花指令,若二进制程序中的花指令较多,那么可以通过分析花指令的特定模式,编写IDAPython脚本对花指令进行自动化搜索。
3.实例分析
花指令影响IDA反汇编
通过patch去除花指令
进一步分析这个程序,可以得到花指令的指令模式,主要是在某些特定的指令序列之后插入一到两个无用字节,通过IDA脚本自动化去除花指令代码如下:
from idaapi import *
from idc import *
from idautils import *
start_ea = 0x40100
print 'start....'
patterns = [('73,02',2), ('EB 03 ', 1), ('72 03 73 01', 1), ('74 03 75 01', 1), ('7E 03 7F
01', 1), ('74 04 75 02', 2)]
for pattern in patterns:
ea = start_ea
while True:
ea = FindBinary(ea, SEARCH_DOWN, pattern[0])
if ea == idapi.BADADDR
break
ea += len(pattern[0].replace(' ','')/2
for i in range(pattern[1]):
PatchByte(ea+i, 0x90)
MakeCode(ea+i)二、反调试
反调试是指在程序运行的过程中探测其是否处于被调试状态,若发现其正在被调试,则使其无法正常运行。在Windows下通过OD的strongOD插件可以过滤掉大多数反调试方法,所以一下主要针对Linux。
1.利用ptrace
Linux下的调试主要是通过ptrace系统调用实现的,一个进程只能被一个程序跟踪,所以若程序被跟踪之后再来调用ptrace(PTRACE_TRACEME)自然是不会成功的。
#include <stdio.h>
#include <sys/ptrace.h>
int main (int argc, char * argv[])
{
if (ptrace (PTRACE_TRACEME,0,0,0) == -1)
{
printf ( "Debugger detected\n");
return 1;
}
printf ("OK\n")
return 0 ;
}2.proc文件检测系统
读取/proce/self目录下的部分文件,根据程序在调试和费调试状态下的文件的不同来进行反调试。例如:/proce/self/status在非调试状态下,则TracePid为0,反之不为0,而是跟踪Pid进程号。
3.父进程检测
通过getppid系统调试用获取得到程序的父进程,若父进程是gbd,strace或者Itrace,则可以证明程序正在被调试。
4.实例分析
直接运行程序时,程序会提示输入密码,但是在gdb中运行时,不会有任何输出,猜测程序有反调试。

在程序.int_array中有两个函数,这两个函数会在main函数执行之前执行。

其中,sub_4007A8函数,通过ptrace对调试器进行检查,如果检查到调试器,则进入一个while死循环,对这个函数进行patch,可以跳过对调试器的检查。
三、加壳
加壳是指在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,这段代码会在执行的过程中对原始的指令进行解密还原,之后再将控制权交还给原始代码,执行原来的代码。
经过加壳的程序,其真正的代码是加密存放在二进制文件中的, 只有在执行时才从内存中解密还原出来,因此没法对加壳后的程序直接进行静态分析,所以首先需要进行软件加壳。
1.基本思路
在CTF中出现的带壳程序通常为已知的壳,因此大多数可以通过使用专用的工具或者脚本来进行脱壳,比如UPX壳,可以通过upx -d命令进行脱壳。
四、控制流混淆
对于控制流混淆的程序,没有办法直接进行静态分析,也无法进行反编译,而调试器也会陷入控制流的跳转混乱中。
1.基本思路
对于控制流混淆程序,通常采用Trace方法,通过Trace工具记录下程序运行的所有指令,然后再运行这些指令的基础上进行数据流分析。常用的Trace方法既可以使用Ptrace的暗部执行记录下运行的每一条指令地址,也可以使用二进制插桩工具,如Pin来记录。
2.实例分析
一部分汇编代码如下:

反编译代码如下:

可以发现,最后的“mov rsp,rax”更改了堆栈,程序在retn之后会根据位于E0AF8A0中的ROP链来执行。继续单步执行,可以看见程序的控制流被完全混淆了
五、双进程保护
双进程保护又称为Debug Blocker,是一种在调试模式下运行自身程序的方法,这种保护通常存在两个进程,两个进程是调试器与被调试器的关系。
1.Debug Blocker特点
(1)防止代码被调试,通常实际功能的代码运行在子进程当中,不过因为子进程已经处于调试状态,所以无法再使用其他调试器进行附加操作。
(2)父进程能够控制子进程,通过处理子进程的异常,父进程能控制子进程的代码执行流程或者对子进程进行动态修改。
(3)由于真正的功能通常位于子进程中,所以要调试子进程,就必须先断开与已有调试器的链接,但是这样之后,就没有父进程处理子进程的异常,导致子进程无法正常运行,这也是逆向Debug Blocker最难的部分。
2.基本思路
先针对父进程进行分析,了解其处理子进程的逻辑,然后对子进程进行patch,使子进程脱离主进程后仍能正确运行,最后对子进程进行分析。
因为父进程相当于一个调试器,调试器在调试程序的时候会一直循环等待,直到检测一个调试时间的发生,当调试时间发生的时候,就会调用一个与之对应的时间处理函数,调用处理函数时,调试器会暂停等待下一步的指示。
3.实例分析
汇编代码如下
通过互斥体区分父子进程
程序首先使用CreateMutexA尝试创建一个名为ALICTF:Bigtang的互斥体,成功或者失败都将转跳到不同的函数中,对于双进程而言,父进程是第一次创建,会返回成功,而子进程会因为互斥体已经存在而返回失败,所以可以知道后面的两个函数分别为parent_handle和child_handle。
首先分析父进程的处理函数parent_handle,对应的地址为4014D0,通过查看反编译代码,可以看到首先创建了子进程,然后进入调试事件循环之中,主要完成了两件事:当异常地址为4014A6时,对4014A8处的4字节进行与0x7F异常操作,并将EIP寄存器增加2,当异常地址为4014B9时,对407040处16字节进行与0x31的异或操作,并将4014B9处的2字节修改为E8B2。
父进程的主要处理逻辑(改)
然后对子进程的child_handle函数进行分析,发现4014A6处不能正常识别汇编代码,如下图,所以在地址0x4014a6处抛出非法指令异常,而父进程就能正常接收到这个异常,并对此处进行patch
子进程原始代码
patch之后的代码

当子程序运行到4014B9处再次发声异常,对407040和4014B9进行相应的patch
最后修改main函数,使其直接转跳到child_handle函数中,这样的子进程就可以脱离父进程的情况下正常运行了,也可以调用调试器对程序进行调试操作,以上的patch造作可以通过编写IDA脚本来实现:
from idc import
PatchByte(0x4014a6, 0x90)
PatchByte(0x4014a7, 0x90)
for i in range(4)
PatchByte(0x4014a8+i, Byte(0x4014a8+i)^0x7f)
for i in range(16)
PatchByte(0x407040+i, Byte(0x407040+i)^0x31)
PatchByte(0x4014b9, 0xe8)
PatchByte(0x4014ba, 0xb2)
PatchByte(0x401493, 0xeb)后面就是对子进程中的算法,进行分析,其算法逻辑如下图,可以知道首先通过TEA加密,然后与0x31进行异或操作,并与固定的字符串进行比较。

可以编写脚本进行解密
from zio import *
def u1(v):
return v & 0xFFFFFFFF
def retea(ct,key):
rest=' '
v0 = 132(ct[0:4])
v1 = 132(ct[4:8])
sum = 0x1bbcdc80
for i in range(128)
v1 = u1(v1-((v0+sum)^(16*v0+key[2])^((v0>>5)+key[3])))
v0 = u1(v0-((v1+sum)^(16*v1+key[0])^((v1>>5)+key[1])))
sum = u1(sum - 0x9e3779b9)
res = '%08x%08x' %(v0,v1)
return res
with open('./debug','rb') as f:
data = f.read()[0x7030:0x7030+0x10]
d2 = ' '.join(chr(old(d)^0x31) for d in datas
key = [0x112233,0x44556677,0x8899aabb,0xccddeeff]
flag = retea(d2[0:8],key)
flag += retea(d2[8:16],key)
print flag六、虚拟机保护
1.定义
虚拟机保护就是将代码翻译为机器和人都无法识别的一串伪代码字节流,在具体执行再对这些伪代码进行逐一翻译、解释、逐步还原为源代码并执行。
这段用于翻译伪代码并负责具体执行的子程序就称为虚拟机。
2.基本思路
对于虚拟机来说,它定义了一套属于自己的指令架构(ISA),包括寄存器集、内存和指令集。刚刚开始,通常会有一个vm_init阶段完成初始化操作,对寄存器进行初始化,对内存进行加载,然后,进入vm_run阶段,开始取指令、解析指令、然后根据操作码opcode分派处理函数。
首先逆向虚拟机,得到虚拟机的ISA,然后编写相应的反汇编工具对虚拟机指令进行反汇编,最后分析虚拟机的反汇编代码
3.实例分析
某一个程序的反编译代码

通过分析,大致可以知道3个子函数的大概功能。vm_init是对VM的寄存器进行初始化,对其反编译代码如下。
vm_init函数
load_mem是将文件argv[1]中的内容读到内存中,所以主要是分析vm_run函数,在分析vn_run函数的过程中,可以识别出各个VM寄存器所表示的意义,可以从上面的反汇编代码得知,VM有16个通用寄存器vreg、一个指令指针寄存器vpc、一个堆栈寄存器vsp和一个状态表示寄存器v_flag。
vm_run函数的反编译代码如下图
vm_run函数
通过read_byte函数获取当前vpc处的1字节作为opcode,然后根据opcode的低6比特执行不同的分支指令。此处一共又5个分支,需要逐个分析。
4.虚拟机指令集以及意义
| 操作码 | 指令意义 | 操作码 | 指令意义 |
| 0 | initvm | 13 | and regi,imm/regj |
| 1 | mov regi,imm/regj | 14 | or regi,imm/regj |
| 2 | mov regi,[regj] | 15 | xor regi,imm.regj |
| 3 | mov [regj],regi | 16 | cmp regi,imm/regj |
| 4 | pop regi | 17 | exit |
| 5 | push regi | 18 | mov regi,[regj] |
| 6 | print regi | 19 | mov [regj],regi |
| 7 | scanf regi | 20 | call imm/regi |
| 8 | ret | 21 | nop |
| 9 | jcc imm | 22 | inc regi |
| 10 | jcc regi | 23 | dec regi |
11 | add regi,imm/regj | 24 | test regi,regj |
| 12 | sub regi,imm/regj |
边栏推荐
猜你喜欢
随机推荐
MySQL中常见的嵌套查询有哪些
LeetCode 每日一题——623. 在二叉树中增加一行
EB and Varuxn's Word Chat
About CRT password decompilation
DIKW体系对AI的影响
Nanoprobes丨EnzMet 用于免疫组织化学 (IHC) 和印迹
插入排序(Insertion Sort)
Why can't the expedited fee of 5,000 yuan guarantee the delivery time?
login, register (flask)
APS Solutions for Printing Industry
三年软件工程真题
[OMV]小米摄像头无法获取Debian 11安装的OMV6的网络存储未知
What are the current pain points of domestic BLDC motor control solutions?
MySQL GROUP BY 分组后加条件HAVING使用方法
归并排序(Merge Sort)
About Invalid prop: type check failed for prop “index“. Expected String, got Undefined
C# System.Reflection.Assembly动态加载资源文件
S32K1xx 系列安全手册
【TypeScript】什么是字面量类型、类型推断、类型拓宽和类型缩小?
OCCT示例学习笔记3--Modeling项目
















