《内窥镜-反混淆虚拟化加固的安卓程序-更新.pdf》由会员分享,可在线阅读,更多相关《内窥镜-反混淆虚拟化加固的安卓程序-更新.pdf(41页珍藏版)》请在三个皮匠报告上搜索。
1、内窥镜-反混淆虚拟化加固的安卓程序演讲人:余帘虚拟化加固简介及研究背景01Rhino逆向RHINO字节码的特殊案例02加固后的程序结构更常见的虚拟化加固程序模式03恢复原始字节码基于已知模式进行逆向04观点和总结安卓程序加固趋势05目录/CONTENTS虚拟化加固是指将原始程序中包含的代码指令“编译”成由一组特定的自定义指令组成的字节码,并在自定义的虚拟机上执行该字节码。它是混淆手段中强度较高的一种,常用于:虚拟化加固简介非法用途:隐藏恶意行为,提高分析和执法难度合法用途:反剽窃、防抄袭,保护软件知识产权等近年来,安卓系统上使用虚拟化加固技术对应用进行混淆的案例持续增加。虚拟化加固安卓应用与加
2、固PC程序的区别在于,安卓存在多层次的架构,加固器普遍会利用Java Native Interface(JNI)进行跨层函数调用,而PC程序不存在这种特征。虚拟化加固安卓应用研究背景后续,我们又遭遇了多款在野的安卓恶意程序,它们使用了更为常见的虚拟化加固模式,并且加固器是闭源的。前述的逆向方法对这些程序不再适用,因此我们采用了一种更加通用的方法进行逆向,还原出混淆前的字节码数月前,我们发现一个安卓恶意样本的主要逻辑被编译成字节码,在Rhino JS引擎上执行,且源码字段被故意置空了。这种类型的混淆不是严格意义上的vmp但有相似之处。我们通过分析Rhino引擎的解释执行过程,能够逆向还原出部分原
3、始语义。Custom Virtual MachineInterpreter虚拟化加固简介及研究背景01Rhino逆向RHINO字节码的特殊案例02加固后的程序结构更常见的虚拟化加固程序模式03恢复原始字节码基于已知模式进行逆向04观点和总结安卓程序加固趋势05目录/CONTENTSRhino字节码案例-JS on AndroidonCreate()of Entry Activity handleIntent()doExecution()executeScriptWithContinuations()decrypt()这个恶意程序主Activity的onCreate()加载并执行一个Interp
4、retedFunction对象,该对象只保存字节码而非源码,使用interpretLoop()中一个有大量分支的switch-case语句解释执行。不像Dalvik字节码已有多款工具能够把它反编译成人类易读的java,目前没有现成工具可以逆向Rhino字节码Rhino字节码的生成Js源码抽象语法树(AST)字节码解析生成解释执行”foobar.split().reverse().join();rootExpressionStatementFunctionCallPropertyGetStringLiteral“”FunctionCallName“join”PropertyGetFunction
5、CallNamePropertyGet“reverse”StringLiteral“”StringLiteral“foobar”Name“split”Icode_LINEIcode_REG_STR_C0STRINGIcode_REG_STR_C1Icode_PROP_AND_THISIcode_REG_STR_C2STRINGIcode_REG_IND_C1CALLIcode_REG_STR_C3Icode_PROP_AND_THISIcode_REG_IND_C0CALLIcode_REG_STR1Icode_PROP_AND_THISIcode_REG_STR1STRINGIcode_RE
6、G_IND_C1CALLIcode_POP_RESULTRETURN_RESULT-42-16-4341-3338-44-16-3238-45-16-4541-33逆向-从字节码重建ASTIcode_LINEIcode_REG_STR_C0STRINGIcode_REG_STR_C1Icode_PROP_AND_THISIcode_REG_STR_C2STRINGIcode_REG_IND_C1CALLIcode_REG_STR_C3Icode_PROP_AND_THISIcode_REG_IND_C0CALLIcode_REG_STR1Icode_PROP_AND_THISIcode_REG
7、_STR1STRINGIcode_REG_IND_C1CALLIcode_POP_RESULTRETURN_RESULT-42-16-4341-3338-44-16-3238-45-16-4541-33-42-16-43-42-16-43Key reverse logicitsStringTable”foobar”split”012StringLiteral“foobar”stackrootExpressionStatementFunctionCallPropertyGetStringLiteral“”FunctionCallName“join”PropertyGetFunctionCallN
8、amePropertyGet“reverse”StringLiteral“”StringLiteral“foobar”Name“split”Rhino provides a toSource()method that transform an AST back into source code recursively J”foobar.split().reverse().join();stackstackPropertyGetStringLiteral“foobar”Name“split”虚拟化加固简介及研究背景01Rhino逆向RHINO字节码的特殊案例02加固后的程序结构更常见的虚拟化加固
9、程序模式03恢复原始字节码基于已知模式进行逆向04观点和总结安卓程序加固趋势05目录/CONTENTS更常见的虚拟化加固场景上述Case的加固方式属于特例。更常见的虚拟化加固场景存在如下特征:加固器闭源 虚拟机在native库中实现,并以JNI形式调用 进行针对特定应用的随机化上述特征导致了更高的逆向难度加固程序结构:虚拟机入口/出口虚拟机入口:切换上下文、拷贝寄存器等分发器处理器循环/虚拟机出口:切换回原始上下文加固程序结构:分发器和处理程序虚拟机入口分发器处理器循环/虚拟机出口指令分发循环获取一条虚拟指令,并对其进行解码在处理程序地址表中进行查找调用处理程序&add_handler&sub
10、_handler&mul_handler&div_handler&xor_handler处理程序地址表指令分发循环查找逆向的挑战和难点对于在野恶意程序,仅能获取二进制文件,无法获取程序的源码/原始Dalvik字节码此外,由于无法获取闭源加固器的源码,缺乏有关虚拟指令和虚拟机机制的信息控制流非常复杂,因此静态和动态分析都很耗时又一个挑战许多受虚拟化加固的程序使用特定于应用程序的加密参数生成虚拟化指令,并且处理程序表中的地址顺序是随机的因此,人工分析虚拟化加固的程序A的结果不能重复使用于经过相同加固器混淆的程序BApp虚拟化加固器&add_handler&sub_handler&mul_handl
11、erxeax10 x05xe5&div_handler虚拟化指令1处理程序地址表1xb1x30 x2dxaf虚拟化指令2&if_eq_handler&get_handler&put_handler&invoke_handler处理程序地址表2已加固程序1已加固程序2同样的 app 和加固器不同的混淆加固后程序多次运行加固后的程序结构更常见的虚拟化加固程序模式03恢复原始字节码基于已知模式进行逆向04目录/CONTENTS虚拟化加固简介及研究背景01Rhino逆向RHINO字节码的特殊案例02观点和总结安卓程序加固趋势05假设和前提条件我们的逆向方法仅适用于以下假设和前提成立的情况:被逆向的加固
12、器是公开可访问的(无论是通过本地还是云端的形式),我们可以使用它来加固任何自定义的应用程序加固器遵循“将 Dalvik 字节码转换为Native函数”的典型模式在这种情况下,鉴于 Dalvik 字节码可以使用现有工具轻松转换为 Java,我们的目标是从混淆后的程序恢复出 Dalvik 字节码加固后的程序加固x04x00 xa9x7f虚拟化指令处理程序地址表原始Dalvik字节码xeax10 x05xe5&add_handler&sub_handler&mul_handler&div_handler&nop_handler&mov_handler&return_handler&const_han
13、dlerreturn handler.mul handler.mov handler.处理程序对应的指令序列加固后的程序x53x01x00 xc0 xb1x50 x08x8e执行过程处理程序地址表原始Dalvik字节码x04x00 xa9x7f虚拟化指令xeax10 x05xe5&add_handler&sub_handler&mul_handler&div_handler&nop_handler&mov_handler&return_handler&const_handlerreturn handler.mul handler.mov handler.处理程序对应的指令序列指令分发循环x53
14、x01x00 xc0 xb1x50 x08x8e执行过程续处理程序地址表原始Dalvik字节码x04x00 xa9x7f虚拟化指令xeax10 x05xe5&add_handler&sub_handler&mul_handler&div_handler&nop_handler&mov_handler&return_handler&const_handlerreturn handler.mul handler.mov handler.处理程序对应的指令序列指令分发循环x53x01x00 xc0 xb1x50 x08x8e在野应用程序的逆向过程处理程序地址表原始Dalvik字节码???x04x00
15、 xa9x7f虚拟化指令xeax10 x05xe5&?_handler&?_handler&?_handler&?_handler&?_handler&?_handler&?_handler&?_handler?handler.?handler.?handler.处理程序对应的指令序列指令分发循环逆向加固过程中保持不变的部分为了保持混淆后,程序的语义仍与原始程序相同,每个处理程序都是从一组简单的原始Dalvik字节码转换而来的虽然中间步骤存在随机化与应用特定的加密参数,但原始Dalvik字节码和处理程序内容之间的关系是固定的(见下页图表)此外,混淆后的函数通常使用与原始程序相同的方式传递参数加
16、固过程中保持不变的部分-续处理程序地址表原始Dalvik字节码?mul?mov?x04x00 xa9x7f虚拟化指令xeax10 x05xe5&?_handler&?_handler&?_handler&?_handler&?_handler&?_handler&?_handler&?_handlerreturn handler.mul handler.mov handler.指令分发循环处理程序对应的指令序列学习映射规则根据上述内容,我们可以构建应用程序,对其进行混淆,并执行混淆程序,以学习原始Dalvik字节码和被执行的处理程序指令序列之间的映射关系。然后再将学习到的规则应用于逆向其他在野
17、捕获应用程序(分析执行处理程序的跟踪日志)。学习映射关系将学习到的映射关系应用于逆向在野应用程序学习映射规则阶段的问题1.确定每个函数的虚拟化指令。2.找出虚拟化指令和处理程序地址之间的关系3.通过内容识别处理程序,以便在执行实际应用程序时识别每个处理程序。学习映射关系建议的解法1.确定每个函数对应的虚拟化指令 Hook 并跟踪用于注册Native方法的函数2.找出虚拟化指令和处理程序地址之间的映射关系 收集动态跟踪信息,并从其中定位分发器和处理程序地址,建立映射3.通过处理程序的内容识别它们,以便在处理在野应用程序的跟踪日志时识别 生成“软件基因”以识别每个处理程序。1.虚拟化指令混淆前混淆
18、后虚拟化指令1.确定函数对应的虚拟化指令Hook 并跟踪传递给 env-RegisterNatives()的参数,以获取函数的签名和虚拟指令的地址Hook函数名签名虚拟化指令onCreate(Landroid/os/Bundle;)Vx12x20 x00 x00Tx00Dxd0 x00invoke(Landroidx/compose/runtime/Composer;I)Vx01x00 x04x00 xa7x20rx00 x05x00 x00 x06部分虚拟化指令与函数对应关系2.虚拟化指令与处理程序地址的映射跳转在函数内部进行,需要观察函数内部的执行流程,所以仅仅Hook函数的入口和出口是不
19、够的一种可能的方法是动态调试,但它需要大量繁琐的手动工作,并且需要应对反调试机制因此,我们使用插桩+分析跟踪(Trace)日志的方式来建立映射关系指令级插桩和跟踪的工具和框架DBI:Valgrind,DynamoRIO,QBDI,Intel Pin模拟器:Unicorn,UnidbgFrida-Stalker考虑因素是否支持安卓:排除Intel Pin 和Valgrind补充环境:使用模拟器时需要QBDI或其他能够进行指令级插桩、跟踪的框架皆可定位分发器和处理程序表分发器首先将处理程序的地址加载到一个寄存器中,然后使用“br”跳转到该地址。该地址指向代码段,但本身存储在数据段中,更确切地说,是
20、在处理程序地址表中。跳转到处理程序之后,将执行该处理程序的内容,即指令序列。只需对代码块开头进行插桩处理程序的指令序列分发器的部分指令处理程序表中的一个地址加密的字节码和处理程序地址的映射上述函数每次运行的跟踪日志,都会建立一条虚拟指令、和它对应的处理程序之间的映射关系处理程序地址表Dump得到的虚拟化指令3.识别每个处理程序对于在野的被加固应用,由于拿不到原始dex文件,并且处理程序表中的地址顺序又是随机的,仅凭处理程序的地址无法识别一个处理程序,即无法知道它是哪条虚拟指令对应的处理程序因此,在学习映射的阶段,我们就需要基于处理程序的内容,去标识每一个处理程序如何判断这些指令对应h_50处理
21、程序?使用已学习的映射关系来逆向恢复应用程序处理程序的”软件基因”我们使用“hash(hex(指令序列)”作为处理程序的软件基因,但指令序列需要先经过以下处理步骤:将序列截断,仅包括第一条“br”指令之前的部分。将在不同程序中具有不同对应机器码的指令替换为固定序列。这种指令包括跳转指令(例如b、bl)和PC寄存器相对指令(例如adrp)等。处理程序的指令序列hexed软件基因整合汇总:逆向在野应用执行野外应用程序以获取与执行的虚拟化指令相对应的基因签名,然后使用已学习到的映射关系将其恢复为Dalvik字节码虚拟化加固简介及研究背景01Rhino逆向RHINO字节码的特殊案例02加固后的程序结构
22、更常见的虚拟化加固程序模式03恢复原始字节码基于已知模式进行逆向04观点和总结安卓程序加固趋势05目录/CONTENTS廉价打包器在Android恶意软件混淆中的应用我们注意到恶意软件使用廉价的基于虚拟机的打包器的趋势。最著名的商业打包器之一每年收费18000元,而一些廉价的打包器每年仅收费约30元。这对于价格敏感的恶意软件作者很有吸引力大部分近期发现的虚拟化加固的恶意软件样本,都是使用廉价加固器混淆的。安卓上的另一种常见混淆技术隐藏原始Dalvik字节码数据,并在执行期间将dex文件动态释放到内存中是另一种广泛使用的混淆技术,它使得静态分析无法发挥作用。对于这种混淆技术,现有的反混淆工具会从
23、内存中搜索并提取Dex数据。然而,这种搜索内存型的反混淆工具无法逆向受虚拟化加固的应用程序,因为在虚拟化加固的程序执行时,原始的dex从未出现在内存中。要点总结综上,我们提出了针对虚拟化加固的安卓应用程序进行逆向的两种不同情况的方法:对于使用Rhino字节码的特定类型的混淆,由于解释器是开源的,我们分析虚拟机,重构AST,并从字节码中恢复源代码对于更常见和普遍的情况,即加固器并非开源,我们通过构建应用程序、收集执行跟踪日志,并使用软件基因,来学习原始字节码和处理程序执行序列之间的映射关系,然后将学习到的关系应用于恢复在野的虚拟化加固应用程序的语义。参考链接https:/web.archive.org/web/20230531155247/https:/blog.autojs.org/2022/08/24/encryption/https:/ H A N KY O UF O RY O U RW A T C H I N G