作者:JiaYu转自公众号:随口一说1.概述360Netlab的旧文《“双枪”木马的基础设施更新及相应传播方式的分析》提到了双枪木马的传播A进程中的恶意驱动kemon.sys,其中有100+个自定义加密的Ascii字符串和Unicode字符串:这只是双枪木马传播链中的一个小技术点,所以不在文章中说明是哪一种加密算法的详细介绍以及如何解密,方便分析人员更方便的进行样本分析。不过这个技术点还是蛮有意思的,特别是处于逆向工程入门阶段的朋友,可以参考一下解法。最近碰到这个驱动的一个变种,请教了车队的老司机,简单写了个小文章记录一下。感谢老司机的解惑。也欢迎各界高手多多指教,提出一些更快更准的解决方案。2.示例概览MD5:b001c32571dd72dc28fd4dba20027a882.1字符串加密驱动中使用的100+个字符串都是自定义加密的,设置好每个IRP派发函数和卸载例程后,依次对这些字符串进行解密。在IDA中打开样本,部分解密过程如下:整个解密过程的函数是sub_100038,会多次调用两个具体的解密函数:sub10003871和sub_10003898。前者解密Ascii字符串,后者解密Unicode字符串,两者都有两个参数:arg1—>待解密字符串的地址;arg2—>字符串的长度。这两个函数稍后将命名为DecryptAsciiStr和DecryptUnicodeStr。这两个函数在IDA中看到的外部参照的状态如下:2.2加密算法前面说了,算法并不复杂。以DecryptAsciiStr函数为例:反编译看看:DecryptUnicodeStr算法其实是一样的,只是因为字节组成不同,所以分开写两个解密函数:简而言之,这套解密过程其实就是:当前字节后面的具体偏移量将移位后的字节与0xC异或,然后替换当前字节,将解密后的字节写入当前位置,完成解密。我不熟悉密码学。我不知道这是不是一个众所周知的加密算法。好像是凯撒密码的改良增强版?欢迎了解这方面的朋友指教。3.解密了解了以上情况之后,就该开始对这一百多个字符串进行解密了。由于是用IDA来分析这个样本,所以理想的情况应该是把这些字符串批量解析出来,直接在IDA中呈现出来,再进行后续的分析。既然是自动化批量解密,写IDAPython应该算是最方便的方式了。最终效果如图:3.1姿势一——自己实现解密算法第一个想到的想法是:解密算法只有两种,并不复杂。你还不如直接写一个IDAPython脚本来实现这两种解密算法。解密后直接将明文字符串写入IDB文件,在IDA中呈现。两种解密算法的Python版本如下(IDB有Patch操作):这里稍微解释一下makeunicodestr的操作:old_type=idc.GetLongPrm(INF_STRTYPE)idc.SetLongPrm(idc.INF_STRTYPE,idc.ASCSTR_UNICODE)idc.MakeStr(argv[0],argv[0]+(argv[1]*2))idc.SetLongPrm(idc.INF_STRTYPE,old_type)在IDA的UI界面中,可以选择类型生成的字符串(如下图),快捷键只有一个A,对应的idc函数为idc.MakeStr(0。不过ida.MakeStr()函数默认生成的是Ascii字符串。要生成Unicode字符串,需要调用idc.SetLongPrm()函数设置字符串的类型。IDA支持的字符串类型如上图所示。对应的,idc库中的定义如下:idaapi.ASCSTR_LEN4#Pascal-style,lengthis4bytesASCSTR_ULEN2=idaapi.ASCSTR_ULEN2#Pascal-styleUnicode,lengthis2bytesASCSTR_ULEN4=idaapi.ASCSTR_ULEN4#Pascal-styleUnicode,lengthis4bytesASCSTR_LAST=idaapi.ASCSTR_LAST以#Laststringtype所,要生成Unicode你需要的string,unicode字符串SetLongPrm()函数首先设置字符串类型。其中idc.INF_STRTYPE是代表字符串类型的常量,在idc库中定义如下:用Python实现解密功能后,如何模拟这一波解密过程依次解密100+个字符串?这里可以结合IDA的xrefs和idc.PrevHead()函数中的xrefs和idc.PrevHead()函数来实现:首先通过xrefs找到调用这两个解密函数的位置;然后通过idc.PrevHead()定位到两个解密函数的参数地址,解析出参数的值;执行解密函数,将解密后的明文字符串写回IDB和makeStr。3.2姿势二——指令模拟本例中的字符串解密算法并不复杂,可以轻松编写Python版本,直接使用IDAPython脚本在IDA中批量解密。那么如果字符串解密算法比较复杂的话,用Python实现一个版本就难了?这时候不妨考虑一下指令模拟器。近年来,Unicorn作为新一代指令模拟器在业界火爆。基于Unicorn的IDA指令仿真插件也不断被开发出来,比如简单的IdaEmu和FireEye开发的功能强大的Flare-Emu。指令模拟器可以模拟汇编指令的执行,IDA中的指令模拟插件可以模拟IDA中指定指令片段的执行(需要手动指定开始指令地址和结束指令地址,并设置相关寄存器的初始状态)。这样我们就可以使用指令模拟插件在IDA中模拟上述批量解密指令的执行。解密字符串的汇编指令模拟执行完成后,自然就可以解密字符串了。本文Case指令模拟pose基于Flare-Emu。不过这个姿势有两个问题需要注意:命令模拟器无法模拟系统API,如果解密函数中有调用系统API的操作,模拟起来会费点功夫这架势配合指挥。所谓模拟指令执行,其实就是在不修改IDA中的任何数据的情况下进行模拟。这样就需要将命令模拟器执行后的明文字符串打补丁到IDB文件中,这样才能在IDA中看到明文字符串。3.2.1Hookapi第一点,IdaEmu需要自己实现相关API的功能,在命令片段中hook相应的API,才能顺利模拟。比如下图的例子,在指令片段中调用了_printf函数,那么我们需要手动实现_printf的函数,将指令片段中的_printfhook出来:而Flare-Emu做起来更方便,他们直接在框架中实现了一些基本的系统API,而不是手动实现和执行Hook操作:之所以问这个问题是因为这个kemon.sys样本中批量解密字符串的过程涉及到memcpy函数调用:在这个way,直接用Flare-Emu模拟执行应该是更方便的选择。3.2.2补丁IDB第二点是将仿真结果写回IDB文件,在IDA中显示。第一个问题是如何获取模拟执行成功的结果——明文字符串。前面描述字符串解密算法时提到,解密后的字节(Byte)会直接替换密文中的特定字节,解密出密文的前dataLen个字节,即为明文字符串。这个字节替换操作其实对应的是Unicorn指令模拟器中定义的MEM_WRITE操作,即写内存,只有这个字符串替换操作才会在字符串解密过程中写内存。巧的是,Flare-Emu提供了一个memAccessHook()接口(如下图所示),可以Hook各种内存操作:memAccessHook可以是你定义的一个函数,当访问内存进行读写时调用。它的原型如下:memAccessHook(unicornObject,accessType,memAccessAddress,memAccessSize,memookValue,orn支持的userData的Hcur)。操作如下:因此,我们在指令模拟过程中hookUC_MEM_WRITE操作,获取解密后的字节,手动patch这些字节到IDB中:=UC.UC_MEM_READ:#print("读取:",hex(memAccessAddress),memAccessSize,hex(memValue))ifaccessType==UC.UC_MEM_WRITE:#print("写入:",hex(memAccessAddress),memAccessSize,hex(memValue)))ifmemAccessSize==1:idc.PatchByte(memAccessAddress,memValue)elifmemAccessSize==2:idc.PatchWord(memAccessAddress,memValue)elifmemAccessSize==4:idc.PatchDword(memAccessAddress,memValue)PatchIDB基本操作当然是像上面的IDAPython脚本一样调用idc.PatchXXX函数写入IDB文件。在之前的PatchIDB文件中,只调用了一个idc.PatchByte()函数。实际上idc库中有四个函数可以给IDB打补丁:idc.PatchByte():Patch1Byte;idc.PatchWord():Patch2Bytes;idc.PatchDword():Patch4Bytes;idc.PatchQword():Patch8Bytes;在指令模拟器中执行Patch的操作不限于PatchByte。根据我打印出来的指令模拟过程中的内存写操作细节,可以看到涉及的Patch操作有3种(如下图):1byte,2Bytes和4Bytes,都是这是上面的mem_hook()函数中的3个。一种memAccessSize。弄清并解决了“系统APIhook”和“获取指令模拟结果及PatchIDB”这两个问题后,就可以写出准确的IDAPython脚本了。3.2.3Radare2ESIL模拟r2上还有一个强大的指令模拟模块,名字叫ESIL(EvaluableStringsIntermediateLanguage):在r2上用这个东西模拟指令解密这批字符串,就不用做了你自己就像在IDA中一样既然你已经手写了IDAPython脚本,你只需要通过r2命令配置几个相关的参数。下面两张图是r2中通过命令模拟批量解密这些字符串的前后对比:具体操作方法我就不多说了,有兴趣的朋友可以自行摸索。4.总结文中介绍了两种基本方法,批量解密kemon.sys中大量自定义加密字符串,即恶意驱动kemon。原理很简单,介绍有点啰嗦。我希望把每一个关键细节描述清楚。两种方法对应的IDAPython脚本已经上传到Github供参考:https://github.com/0xjiayu/decrypt_CypherStr_kemonsys5。参考资料https://en.wikipedia.org/wiki/Caesar_cipherhttps://github.com/tmr232/idapython/blob/master/python/idc.pyhttps://unicorn-engine.orghttps://github.com/36hours/idaemuhttps://github.com/fireeye/flare-emuhttps://github.com/unicorn-engine/unicorn/blob/master/bindings/python/unicorn/unicorn_const.py#L64
