写在前边
三
自定义编码/加密
有时,攻击者会使用自定义的编码/加密方案,这使得难以识别加密(和密钥),也使得逆向工程更加困难。自定义编码方法之一是使用编码和加密的组合来混淆数据;etumbot是这种恶意软件的一个较好例子。
参考链接:
https://www.arbornetworks.com/blog/asert/illuminating-theetumbot-apt-backdoor/
*左右滑动查看更多
etumbot恶意软件样本在执行时,会从c2服务器获得rc4密钥;然后使用获得的rc4密钥对系统信息(如主机名、用户名和ip地址)进行加密,加密后的内容使用自定义base64进一步编码,并外流到c2。包含混淆内容的c2通信在下面的截图中显示。
关于该样本的逆向工程细节,请参考作者的演讲和视频演示:
https://cysinfo.com/12th-meetup-reversing-decrypting-malware-communications/
*左右滑动查看更多
为了对内容进行解密,需要先用自定义的base64进行解码,然后用rc4进行解密;这些步骤可以用以下python命令进行,输出显示解密后的系统信息。
> import base64
> from crypto.cipher import arc4
"e65wb24n5" > rc4_key =
"krp6okw9r90_2_kvkkcq_j5oa1d2aixt6xpefijylehvm8qmql38ctwfwuylgixmdflsofoh" > cipher_text =
'_','/').replace('-','=') > content = cipher_text.replace(
> b64_decode = base64.b64decode(content)
> rc4 = arc4.new(rc4_key)
> plain_text = rc4.decrypt(b64_decode)
> print plain_text
myhostname|administrator|192.168.1.100|no proxy|04182|
*左右滑动查看更多
一些恶意软件作者没有使用标准编码/加密算法的组合,而是实施了一个全新的编码/加密方案。这种恶意软件的一个例子是apt1集团使用的恶意软件。该恶意软件将一个字符串解密为一个url;为此,恶意软件调用一个用户定义的函数(在下面的截图中更名为decrypt_func),该函数实现了自定义加密算法。
decrypt_func接受三个参数;第一个参数是包含加密内容的缓冲区,第二个参数是将存储解密内容的缓冲区,第三个参数是缓冲区的长度。在下图中,在执行decrypt_func之前暂停了执行,它显示了第1个参数(包含加密内容的缓冲区)。
根据我们的目标,可以分析decrypt_func以了解算法的工作原理,然后按照作者的介绍编写一个解密器(见以下链接),或者可以让恶意软件为你解密内容。
参考链接:
https://cysinfo.com/8th-meetup-understanding-apt1-malware-techniques-using-malware-analysis-reverse-engineering/
*左右滑动查看更多
要让恶意软件解密内容,只需跨过decrypt_func(它将完成执行解密函数),然后检查第2个参数(存储解密内容的缓冲区)。下面的截图显示了包含恶意url的解密缓冲区(第2参数)。
前面提到的让恶意软件解码数据的技术,如果解密函数被调用的次数不多,是很有用的。如果解密函数在程序中被多次调用,那么使用调试器脚本自动解码过程会比手动操作更有效率。为了证明这一点,请考虑一个64位恶意软件样本的代码片段(参考下图)。
请注意恶意软件如何多次调用一个函数(重命名为dec_function);如果看一下代码,我们就会注意到一个加密的字符串被传递给这个函数作为第1个参数(在rcx寄存器中),执行该函数后,eax中的返回值包含存储解密内容的缓冲区的地址。
下面的截图显示了对dec_function的交叉引用;可以看到,这个函数在程序中被多次调用。
每次调用dec_function时,它都会解密一个字符串。为了解密传递给这个函数的所有字符串,我们可以写一个idapython脚本(参考以下内容)。
import idautils
import idaapi
import idc
for name in idautils.names():
if name[1] == "dec_function":
ea= idc.get_name_ea_simple("dec_function")
for ref in idautils.coderefsto(ea, 1):
idc.add_bpt(ref)
idc.start_process('', '', '')
while true:
event_code = idc.wait_for_next_event(idc.wfne_susp, -1)
if event_code < 1 or event_code == idc.process_exited:
break
rcx_value = idc.get_reg_value("rcx")
encoded_string = idc.get_strlit_contents(rcx_value)
idc.step_over()
evt_code = idc.wait_for_next_event(idc.wfne_susp, -1)
if evt_code == idc.breakpoint:
rax_value = idc.get_reg_value("rax")
decoded_string = idc.get_strlit_contents(rax_value)
print "{0} {1:>25}".format(encoded_string, decoded_string)
idc.resume_process()
*左右滑动查看更多
由于我们已经将解密函数重命名为dec_function,所以它可以从ida的名称窗口中访问。前面的脚本在名称窗口中进行迭代,以确定dec_function,并执行以下步骤。
4、自动启动调试器,当断点在dec_function处被击中时,从rcx寄存器所指向的地址读取加密的字符串。需要记住的一点是,要使ida调试器自动启动,一定要选择调试器(如本地windows调试器),可以从工具栏区域或者选择调试器|选择调试器。
5、然后步入函数,执行解密函数(dec_function),并读取返回值(rax),其中包含解密字符串的地址。随后打印出解密的字符串。
6、重复前面的步骤,对传递给dec_function的每个字符串进行解密。
运行前面的脚本后,加密的字符串和它们相应的解密字符串会显示在输出窗口中,如下图所示。从输出中可以看出,恶意软件在运行期间解密了文件名、注册表名和api函数名,以避免被怀疑。换句话说,这些是攻击者想要隐藏的字符串,以避免静态分析。
四
恶意软件解包
攻击者不遗余力地保护他们的二进制文件免受反病毒检测,并使恶意软件分析师难以进行静态分析和反向工程。恶意软件作者经常使用打包器和加密器来混淆可执行内容。打包器是一个程序,它将一个正常的可执行文件,压缩其内容,并生成一个新的混淆的可执行文件。加密器与打包器一样,不是压缩二进制文件,而是对其进行加密。换句话说,打包器或加密器将可执行文件转变为难以分析的形式。
当一个二进制文件被打包时,它透露的信息非常少;你不会发现字符串透露出任何有价值的信息,导入的函数数量会减少,程序指令会被掩盖。为了理解一个打包的二进制文件,我们需要移除应用于程序的混淆层(解包);要做到这一点,首先要了解打包器的工作原理。
当一个正常的可执行文件通过打包器时,可执行文件的内容被压缩,并且它添加了一个解包存根(解压程序)。然后,打包器将可执行文件的入口点修改为存根的位置,并生成一个新的打包可执行文件。当打包后的二进制文件被执行时,解包存根会提取原始二进制文件(在运行期间),然后通过将控制权转移到原始入口点(oep)来触发原始二进制文件的执行,如下图所描述。
要解开一个打包的二进制文件,既可以使用自动工具,也可以手动操作。自动化方法可以节省时间,但并不完全可靠(有时成功,有时不成功),而手工方法则很费时,但一旦掌握了技巧,它就是最可靠的方法。
01
手动拆包
要解开用打包器打包的二进制文件,我们通常要执行以下一般步骤。
在接下来的几个小节中,我们将详细研究这些步骤。为了演示前面的概念,我们将会使用一个用upx打包器打包的恶意软件。接下来的内容中所涉及的工具和技术将会为我们建立一个手动解包过程的概念。
upx打包器打包恶意软件参考链接:
https://upx.github.io/
(未完待续)