概述
这是一系列文章的第一部分,重点是理解 Visual Basic 6.0 (VB6)
代码,以及恶意软体作者和研究人员在这方面使用的策略和技术。
摘要
这份文件是对许多 VB6
恶意软体如何在可执行文件中嵌入二进制资料的各种方法的持续整理。
主要有四个类别:
- 基于字串的编码
- 隐藏在程式实际操作码中的数据
- 隐藏在
VB6
文件格式某些部分中的数据 - 位于正常
PE
结构周围的数据
最初我只打算讨论隐藏在文件格式中的数据,但为了完整性,决定全部涵盖。
文件格式中的数据是一个特例,我觉得最有趣的是,因为这些数据可以交错在一组复杂的未记录结构中,这需要高级知识和精细解析才能检测。在这种情况下,很难确定数据的来源,甚至无法认出这些缓冲区的存在。
资源数据
第一个技术是语言内建的标准方法,即从资源区域加载数据。VB6
附带了一个附加组件,允许用户将 .RES
文件添加到项目中。此文件编译到可执行文件的资源区中,便于轻松加载二进制数据。
这是一个众所周知的标准技术。
附加数据
这种技术非常古老,已被各种编程语言使用。为了彻底性,将再次提及此技术,并链接到一个公共实现[1],以便简化使用。
十六进制字串缓冲
恶意软体通常会构建一串十六进制字符,后来再将其转换回二进制数据。转换通常包括各种文本操作,如解密或剥除垃圾字符序列。额外的字符序列通常用于防止 AV自动识别数据为十六进制字串。
在 VB6 的上下文中,有几个限制。IDE 只允许一行最多 1023
字符,且 VB 的行连接语法 &_
也限于 25
行。出于这些原因,你常常会看到大块数据以以下格式嵌入:
在编译的二进制文件中,每个字串片段都是作为独立片段存储,容易识别。更快的变体可能会将每个元素保存在字符串数组中,这样聚合只会发生一次。
这是一个众所周知的标准技术。它通常出现在 VBA
、VB6
和许多其他语言编写的恶意软体中。无法透过命令行编译绕过行长限制。
图像中的二进制数据
有多种方法可以将无损数据嵌入图像格式中。最常见的方式是在 BITMAP
图像的结构中直接嵌入数据。位图可以直接保存于 VB6
的图像和图片控件中。以这种方式嵌入的数据将在编译之前保存在 .FRX
表单资源文件中。一旦编译,它将保存在目标表单元素的二进制属性字段中。使用特殊工具生成这样的图像可以直接嵌入到表单中。
以下是一个公共示例[2],从这样的位图中提取数据:
提取的图像将显示为一系列不同颜色的方块和像素。请注意,这不是隐写术。
许多工具可以理解如何从二进制文件中提取嵌入的图像。由于图像数据仍包含 BITMAP
标头,因此无需解析 VB6
文件格式本身。这种技术是公共且常用的。提取后数据通常会被解密。
Chr 字串
类似于 C 语言恶意软体中发现的混淆,字串可以根据个别字节值在运行时构建。常见示例如下:
在汇编层级,这会将每个字节值拆分,并与一系列操作码并排,使得自动检测或显示字串变得困难。对于原生 VB6 代码来说,则呈现如下:
在 P-Code 中,它的显示如下:
这是一个众所周知的标准技术,通常出现在 VBA 及 VB6 恶意软体中。
数字数组
数字数组是一种在恶意软体中相当标准的技术,用于在程序的操作码中分散二进制数据。这与 Chr 技术类似,但可以以更紧凑的格式保存数据。最常见的数据类型是 4字节长整型
和 8 字节货币
类型。这种技术的主要优势在于,可以轻松使用数学运算来即时解密数据。
原生:
P-Code:
原生:
P-Code:
这种技术不如其他方法流行,但有著悠久的使用历史。我第一次见到它是在 Flash ActionScript 的漏洞中。
表单属性
表单和嵌入的 GUI 元素可以将编译的数据作为其属性的一部分。最常用的属性包括 Form.Caption
、Textbox.Text
以及任何元素的
Tag 属性。
由于这些属性通常是通过 IDE 输入的,因此通常被发现只包含 ASCII 数据,稍后会解码为二进制数据。
然而,开发者可以使用几种技术将二进制数据直接嵌入这些属性中。
虽然可以通过十六进制编辑器编辑 .FRX
表单资源文件中的原始数据,但这来有其限制,如无法处理嵌入的空值。另一种解决方案是在编译后插入数据。这种技术涉及保留一个由 ASCII文本组成的大缓冲区,并具有起始和结束标记。然后,可以对编译的可执行文件运行嵌入工具,以填充缓冲区中的真实二进制数据。
利用表单元素属性来存放基于文本的数据是一种常见做法,并且已在 VBA
、VB6
甚至 PDF
脚本中出现。经过后处理步骤嵌入的二进制数据也在野外观察到。在 P-Code 和原生中,访问这些属性将通过 COM
对象 VTable
调用进行。
来自 Semi-VBDecompiler 的源代码, 每种不同的控件类型(包括
ActiveX
)都有其自己的解析器,用于这些编译的属性字段。根据所使用的工具,结果会有所不同,能否显示数据。Semi-Vbdecompiler提供了将属性二进制数据导出到磁碟以进行手动探查的选项。这可能是揭示这种嵌入的二进制数据所需的。
UserControl 属性
上述技术中存在一个特例,即内建的 UserControl
类型。这个控件用于托管可重用的视觉元素,以及创建
OCX
。该控件有两个事件,这些事件传递其内部二进制设置的 PropertyBag
对象。可以通过 IDE的属性页面轻松设置这些二进制数据。这种机制可用于存储任何类型的二进制数据,包括整个文件系统。这种技术的公共示例可在此查看[3]。嵌入的数据将保存在每个
UserControl
实例的属性中。
二进制字串
编译的 VB6
可执行文件将内部字串存储为长度前缀。类似于表单属性技术,这些条目可以在后编译过程中修改,以包含任意的二进制数据。为了区分这些数据块和其他二进制数据,必须对
VB6
文件格式进行深入理解和复杂解析。
通过此技术嵌入的最长字串受到 IDE 行长的限制,为 2042
字节((1023 bytes – 2 for quotes) *2 forunicode
)。
VB6
恶意软体可以正常访问这些字串而不需要特别的加载程序。对于它来说,源代码只是不过是 str = "binary data"
。
IDE 可以处理多个 Unicode 字符,这些字符可以在源码中嵌入以进行编译。使用后处理技术可全量嵌入二进制数据。
错误行号
VB6 允许开发者嵌入行号,以便在出错时可以访问,以协助确定错误位置。这些错误行号信息保存在字节码流外部的一个单独表中。
可以通过 Erl()
函数访问错误行号。 VB6 限制每个函数的行号为 0xFFFF
,且行号的值必须在 0-0xFFFF
范围内。由于这种技术嵌入的数据有限,短字符串(如密码和网页地址)最有可能被使用。
运行以下代码时,它将输出消息 “secret”:
需要对 VB6
文件格式有深入的了解,才能将这些数据从文件的其他部分辨别出来。嵌入的数据是顺序的且易于阅读,如果没有以其他方式编码的话。
函数主体
AddressOf
运算符允许 VB6
可以轻松获取模组中公共函数的地址。可以包括一个伪函数,该函数仅用占位指令填充,以在可执行文件的
.text
区域创建空白缓冲。这个缓冲区可以轻松地用 CopyMemory
调用加载到字节数组中。可以使用简单的后编译嵌入来填充任意数据。
对于 P-Code 编译,AddressOf
返回加载器存根的偏移量以及结构偏移量。P-Code 编译将需要几个额外步骤,但仍然是可行的。
参考文献
[1] 嵌入的文件附加到执行档 – theTrik:
https://github.com/thetrik/CEmbeddedFiles
[2] 在 Bitmap 图像中嵌入二进制数据 – theTrik:
[3] UserControl 二进制数据嵌入 – theTrik:
https://github.com/thetrik/ctlBinData
标签:、、、、、