关于加密壳
加密壳程序对二进制程序的加密大体上可以分为数据加密、代码加密、算法加密、数据加密一般是指对程序中已有的数据进行加密的过程,一般会在合适的实际对数据进行解密(如在所有引用该数据的地方放置数据解密逻辑);同理,代码加密一般是指对程序代码段中的指令进行加密变换过程,一般会等到真正需要执行目标代码时才对其进行解密(该过程一般使用代码自修改即SMC技术)。一般加壳程序往往会将这两种加密方式与RSA等成熟密码学算法相结合,实现对软件“授权系统”及其关键数据的保护,例如,有的开发者不会选择单独编译已去除关键功能的demo版本应用程序给用户试用,而是选择使用加密壳程序对关键功能进行依赖授权的加密保护,这样当用户未购买正确密钥时,他无法使用软件的关键功能、访问关键数据。许多加密壳软件便提供了祝贺样的功能,以供开发者使用。
虚拟机保护vm
CTF中更常见的加密技术是算法加密。算法加密更偏重算法的混淆、模糊与隐藏,其中最常见的方式便是虚拟机保护。虚拟机(Virtual Machine,VM)保护的大范围使用最早出现在加密壳软件中,是一些加密壳的最强保护手段,其中最具代表性的是VMProtect。VMProtect除了提供常规的数据加密、代码加密和其他反调试等功能,还能在汇编指令层面对程序逻辑进行虚拟化,将开发者指定的代码段中所有的汇编指令转变为自行编写的一套指令集中的指令,并在实际执行时由自行编写的虚拟机执行器进行模拟执行。注意,这与VMWare等虚拟机程序并非同一概念。VMWare等虚拟机程序规模更加庞大,目的是虚拟出一整套硬件设备,从而支持操作系统等软件的执行,而虚拟机保护壳规模相对较小,目的是虚拟出一整套硬件设备,从而支持操作系统等软件的运行,而虚拟机保护壳规模相对较小,目的是尽可能地对原始程序代码、算法逻辑你进行混淆、模糊和隐藏。
基于虚拟机保护的加密壳发展至今,已经能达到极其复杂的加密混淆效果,要对保护过后的程序进行还原已经变得极其困难,并且将耗费大量时间。在CTF中,我们经常看到的VN实际上是经过简化、抽象之后的,一般不会针对x86、x64等真实CPU的汇编指令进行虚拟化。一般,出题人会针对题目中的椒盐算法设计一套精简的指令集。例如,要实现一个移位密码可能需要用到加、模等运算,于是便可以设计一个包含加、模等运算指令的指令集,将校验算法用自己设计的指令集中的指令实现,再将其汇编为该指令集的机器码(俗称虚拟字节码),最后将这些字节码交给编写好的虚拟CPU执行函数进行执行。要对这类题目进行逆向,可以对其虚拟CPU执行哈函数进行逆向,还原出该虚拟架构的指令集,然后编写反汇编代码对虚拟字节码进行反汇编,最后根据反汇编的结果,分析题目真正的校验算法,获得flag。
举个栗子
De1ctf2019中的逆向题signalvm_de1ta。该题本身为一个Linux下的可执行程序,通过前置逆向分析工作可以发现它不太常规地通过signa、ptrace等机制实现了一个VM执行函数,由于其主逻辑函数代码较多,不对其进行展示。我们需要的是根据ptrace的原理将这部分代码逻辑理清楚,然后还原出该虚拟架构的指令集,并编写反汇编代码。逆向完ptrace部分的逻辑后,写出如下反汇编脚本:
1.py
该脚本还原了原题中虚拟机执行函数的逻辑,从而能够解析虚拟字节码,并将其反汇编为更易阅读的形式。运行该脚本,我们能够得到以下输出:
![SJ(IQ_G8A0SIVGX~]D3AS.png
我们便可以直接根据反汇编的结果对程序的求解算法进行分析,但是其汇编指令较多,虽然分析的难度已经降低不少,依旧存在些许困难。在编写反汇编器时,有意将输出语句的格式转化为了类C语言的语法格式,目的是利用优化能力极强的编译器对这些汇编语句进行“反编译”。所以,我们可以对上述反汇编结果进一步整理,最终整理为如下可供C编译器编译的格式:
1.cpp
选用C编译器(如MSVC)配置好的优化选项,编译上述代码为可执行程序后,再使用IDA的HexRays插件对main_logic()函数进行反编译,可以得到如下伪代码(已重命名部分变量):
![N$5PLX)8Q~]F~)F9}M23BC.png![S9Y]1NHJ{(N959071ZZL2SR.png
此时的代码中算法逻辑已经清晰可见,编译器帮助我们完美地完成了优化工作。该程序内置了一个字符数组,观察生成flag(solution)的算法可以发现,这个字符数组的结构应该是一个如下所示的三角形:
生成flag的算法即从该三角形顶点(第一个字符)出发,通过穷举找到到达地城的具有最大和的一条路径,直接使用动态规划进行求解即可:
2.py
使用python运行求解脚本,输出如下:
至此,我们便完成了这道VM逆向题的求解。注意,CTF中非所有VM类题型都需要使用这种方法进行解题,对于虚拟字节码数量较小、VM执行器逻辑较为简单的题目而言,一种极为高效的方法是在调试是跟踪与记录运行的指令(俗称“打log”),可以依赖的工具有IDAPython、GDBscript或各类Hook框架。这种方式不需要对VM执行器进行完整逆向,虽然不能完整地还原出验证逻辑,但能帮助我们窥探出一部分的运算逻辑,有经验的逆向选手借此甚至可以推测出完整的逻辑,从而快速完成解题。因此,在实际竞赛中,我们要灵活处理各种情况,找到最优的解题方式进行解题。