利用vstruct解析二进制数据
dezehang 2024-11-22 13:04 1 浏览
Vstruct是一个纯粹由Python语言编写的模块,可用于二进制数据的解析和序列化处理。实际上,Vstruct是隶属于vivisect项目的一个子模块,该项目是由[Invisig0th Kenshoto](http://visi.kenshoto.com/viki/MainPage)发起的,专门用来处理二进制分析。 Vstruct的开发和测试已经有许多年头了,并且已经集成到了许多生成环境下的系统中了。此外,这个模块不仅简单易学,而且重要的是,它还非常有趣!
您还在使用struct模块火急火燎地手工编写脚本吗?太苦逼了,不如使用vstruct吧!利用vstruct开发的代码,往往更具有陈述性或声明性,更加简明易懂,这是因为在编写二进制解析代码时通常会带有大量样板代码,而vstruct却不会出现这种情况。声明性代码强调的是二进制分析的下列重要方面:偏移,大小和类型。这使得基于vstruct的解析器更易于长期维护。
##0x00 安装vstruct
[Vstruct](https://github.com/vivisect/vivisect/tree/master/vstruct)模块是[vivisect](https://github.com/vivisect/vivisect)项目的一个组成部分,目前该项目与Python 2.7保持兼容,当然,面向Python 3.x的vivisect分支目前正在开发之中。
由于vivisect的子项目不是用兼容setuptools的setup.py文件分发的,所以你需要自己下载vstruct的源代码目录,并将其放入你的Python路径目录中,比如当前目录下:
```
$ git clone https://github.com/vivisect/vivisect.git vivisect
$ cd vivisect
$ python
In [1]: import vstruct
In [2]: vstruct.isVstructType(str)
Out[2]: False
```
当然,通过setup.py来声明vstruct依赖的Python模块是非常麻烦的事情,因此为方便起见,我提供了一个[PyPI](https://pypi.python.org/pypi)镜像包,名为vivisect-vstruct-wb,这样的话,大家就可以直接利用pip命令来安装vstruct了:
```
$ mkdir /tmp/env
$ virtualenv -p python2 /tmp/env
$ /tmp/env/bin/pip install vivisect-vstruct-wb
$ /tmp/env/bin/python
In [1]: import vstruct
In [2]: vstruct.isVstructType(str)
Out[2]: False
```
我已经对这个镜像进行了更新,现在它既支持Python 2.7也支持Python 3.0的解释程序,以便于读者在将来的工程中继续使用vivisect-vstruct-wb。另外,遇到问题时,千万不要忘了到Visi的GitHub上去看看有没有现成的答案。
##0x01 Vstruct入门
下面的例子相当于大家学编程语言时的“Hello World !”程序,它使用vstruct来解析字节串中的小端模式的32位无符号整数:
```
In [1]: import vstruct
In [2]: u32 = vstruct.primitives.v_uint32()
In [3]: u32.vsParse(b"\x01\x02\x03\x04")
In [4]: hex(u32)
Out[4]: '0x4030201'
```
请注意观察上面代码是如何创建v_uint32类型实例、如何使用.vsParse()方法解析字节串,以及如何像处理原生Python类型实例那样来处理最后的结果的。为了更安全起见,我要显式地将解析后的对象转换成一个纯Python类型:
```
In [5]: type(u32)
Out[5]: vstruct.primitives.v_uint32
In [6]: python_u32 = int(u32)
In [7]: type(python_u32)
Out[7]: int
In [8]: hex(python_u32)
Out[8]: '0x4030201'
```
事实上,每个vstruct操作都被定义为一个以vs为前缀的方法,几乎在所有由vstruct派生的解析器中,都能找到这些方法的身影。虽然我最常用的是.vsParse()和.vsSetLength()这两个方法,但是我们最好熟悉所有方法的使用方法。下面是对每种方法的简单总结:
- .vsParse()——从字节串解析实例。
- .vsParseFd()——从文件类型的对象中解析实例(必然用到.read()方法)。
- .vsEmit()——将实例序列化为字节串。
- .vsSetValue()——利用原生Python实例为实例赋值。
- .vsGetValue()——复制实例的数据,并将其作为原生Python实例。
- .vsSetLength()——设置数组类型如v_str的长度。
- .vsIsPrim()——如果实例为简单的primitive类型,则返回True。
- .vsGetTypeName()——取得存放实例类型名称的字符串。
- .vsGetEnum()——取得v_number实例关联的v_enum实例,如果存在的话。
- .vsSetMeta()——(内部方法)。
- .vsCalculate()——(内部方法)。
- .vsGetMeta()——(内部方法)。
目前为止,vstruct看上去就像是struct.unpack的转基因克隆,所以,接下来我们有必要介绍它更酷的功能。
##0x02 Vstructs的高级特性
Vstruct解析器通常是基于类的。这个模块提供了一组基本数据类型(例如v_uint32和v_wstr分别用于DWORD和宽字符串),以及一个相应的机制来将这些类型组合成更加高级的数据类型(VStructs)。首先,我们先来介绍基本的数据类型:
- Vstruct.primitives.v_int8——有符号整数。
- vstruct.primitives.v_int16
- vstruct.primitives.v_int24
- vstruct.primitives.v_int32
- vstruct.primitives.v_int64
- Vstruct.primitives.v_uint8 -无符号整数。
- vstruct.primitives.v_uint16
- vstruct.primitives.v_uint24
- vstruct.primitives.v_uint32
- vstruct.primitives.v_uint64
- vstruct.primitives.long
- vstruct.primitives.v_float
- vstruct.primitives.v_double
- vstruct.primitives.v_ptr
- vstruct.primitives.v_ptr32
- vstruct.primitives.v_ptr64
- vstruct.primitives.v_size_t
- Vstruct.primitives.v_bytes——有确定长度的原始字节序列。
- Vstruct.primitives.v_str——有明确长度的ASCII字符串。
- Vstruct.primitives.v_wstr——有明确长度的宽字符串。
- Vstruct.primitives.v_zstr——以NULL为终止符的ASCII码字符串。
- Vstruct.primitives.v_zwstr——以NULL为终止符的宽字符串。
- vstruct.primitives.GUID
- Vstruct.primitives.v_enum——用来说明整数类型。
- Vstruct.primitives.v_bitmask——用来说明整数类型。
复杂的解析器可以通过定义vstruct.VStruct类的子类来开发,因为vstruct.VStruct类可以包含众多变量,而这些变量可以是vstruct基本类型或高级类型的实例。好吧,我承认这句话有点绕口,那就一点一点来逐步消化吧!
```
Complex parsers are developed by defining subclasses of the `vstruct.VStruct`
class…
class IMAGE_NT_HEADERS(vstruct.VStruct):
def __init__(self):
vstruct.VStruct.__init__(self)
```
[源代码](https://github.com/vivisect/vivisect/blob/master/vstruct/defs/pe.py#L130)
在这个例子中,我们使用vstruct定义了一个Windows可执行文件的PE头部。我们的解析器名为IMAGE_NT_HEADERS,它是从类vstruct.VStruct那里派生出来的。我们必须在__init__()方法中显式调用父类的构造函数,具体形式可以是vstruct.VStruct.__init__(self)或者super(IMAGE_NT_HEADERS, self).__init__()。
```
…that contain member variables that are instances of `vstruct` primitives…
class IMAGE_NT_HEADERS(vstruct.VStruct):
def __init__(self):
vstruct.VStruct.__init__(self)
self.Signature = vstruct.pimitives.v_bytes(size=4)
```
[源代码](https://github.com/vivisect/vivisect/blob/master/vstruct/defs/pe.py#L130)
IMAGE_NT_HEADERS实例的第一个成员变量是一个v_bytes实例,它可以存放4字节内容。v_bytes通常用来存放无需进一步解析的原始字节序列。在本例中,成员变量.Signature的作用是,在解析有效PE文件时存放魔法序列“PE\x00\x00”。
在定义这个类的时候,还可以添加其他的成员变量,以用于解析二进制数据中不同部分的序列。类VStruct会记录成员变量的声明顺序,并处理其他相关的记录工作。唯一需要你去做的事情就是决定以哪种顺序来使用这些类型。够简单吧!
当某种结构在各种子结构中都要用到的时候,你可以将它们抽象成可重用的Vstruct类型,之后就可以像使用vstruct基本类型那样来使用它们了。
```
[Complex parsers are developed by defining classes that contain] other complex `VStruct` types.
class IMAGE_NT_HEADERS(vstruct.VStruct):
def __init__(self):
vstruct.VStruct.__init__(self)
self.Signature = v_bytes(size=4)
self.FileHeader = IMAGE_FILE_HEADER()
```
[源代码](https://github.com/vivisect/vivisect/blob/master/vstruct/defs/pe.py#L130)
当Vstruct实例解析二进制数据遇到复杂的成员变量时,可以通过递归方式用子解析器来解决。在本例中,成员变量.FileHeader就是一种复合的类型,其定义见[这里](https://github.com/vivisect/vivisect/blob/master/vstruct/defs/pe.py#L80)。IMAGE_NT_HEADERS解析器首先会遇到.Signature字段的四个字节,然后,它把解析控制权传递给复合解析器IMAGE_FILE_HEADER。我们需要检查这个类的定义,以便确定其大小和布局情况。
我的建议是,开发多个Vstruct类,每个类负责文件格式的一小部分,然后使用一个更高级别的VStruct将它们组合起来。这样做的话,调试起来会更加容易一些,因为解析器的每一部分都可以单独进行检验。无论用什么方法,一旦定义好了一个Vstruct,你就可以通过文档开头部分描述的模式来解析数据了。
```
In [9]:
with open("kernel32.dll", "rb") as f:
bytez = f.read()
In [10]: hexdump.hexdump(bytez[0xf8:0x110])
Out[10]:
00000000: 50 45 00 00 4C 01 06 00 62 67 7D 53 00 00 00 00 PE..L...bg}S....
00000010: 00 00 00 00 E0 00 0E 21 .......!
In [11]: pe_header = IMAGE_NT_HEADERS()
In [12]: pe_header.vsParse(bytez[0xf8:0x110])
In [13]: pe_header.Signature
Out[13]: b'PE\x00\x00'
In [14]: pe_header.FileHeader.Machine
Out[14]: 332
```
在执行第9条命令的时候,我们打开了一个PE样本文件,并将其内容读入到了一个字节串中。在执行第10条命令的时候,我们用十六进制的形式展示了PE头部开头部分的一些内容。在执行第11条命令的时候,我们创建了一IMAGE_NT_HEADERS类的实例,但是需要注意的是,它还没有包含任何解析过的数据。此后,我们利用第12条命令显式解析了一个存放PE头部的字节串。通过第13和14条命令,我们展示了解析实例的成员的内容。需要注意的是,当我们访问一个嵌入的复合Vstruct时,我们可以继续进一步索引其内部内容,但是当我们访问一个基本类型成员时,我们得到的是原生Python的数据形式。说句实在话,这真是太方便了!
在进行调试的时候,我们可以通过.tree()方法把被解析数据以人类可读的形式打印出来:
```
In [15]: print(pe_header.tree())
Out[15]:
00000000 (24) IMAGE_NT_HEADERS: IMAGE_NT_HEADERS
00000000 (04) Signature: 50450000
00000004 (20) FileHeader: IMAGE_FILE_HEADER
00000004 (02) Machine: 0x0000014c (332)
00000006 (02) NumberOfSections: 0x00000006 (6)
00000008 (04) TimeDateStamp: 0x537d6762 (1400727394)
0000000c (04) PointerToSymbolTable: 0x00000000 (0)
00000010 (04) NumberOfSymbols: 0x00000000 (0)
00000014 (02) SizeOfOptionalHeader: 0x000000e0 (224)
00000016 (02) Characteristics: 0x0000210e (8462)
```
##0x03 Vstruct高级主题
**条件性的成员**
由于Vstruct的布局是在该类型的__init__()构造函数中定义的,所以,它能够对这些参数进行交互,并能够选择性的包含某些成员。举例来说,一个Vstruct在32位平台和64位平台上可以有不同的行为,如下所示:
```
class FooHeader(vstruct.VStruct):
def __init__(self, bitness=32):
super(FooHeader, self).__init__(self)
if bitness == 32:
self.data_pointer = v_ptr32()
elif bitness == 64:
self.data_pointer = v_ptr64()
else:
raise RuntimeError("invalid bitness: {:d}".format(bitness))
```
这是一种非常强大的技术,尽管要想正确使用需要一点点小技巧。重要的是要了解它们的布局是何时最终确定下来的,何时用于估计,何时用于二进制数据的解析。当__init__()被调用时,这个实例并不会访问待解析的数据。只有当.vsParse()被调用时,成员变量中才会填上待解析的数据。因此,VStruct构造函数无法通过引用成员实例的内容来决定如何继续下面的解析工作。举例来说,下面的代码是行不通的:
```
class BazDataRegion(vstruct.VStruct):
def __init__(self):
super(BazDataRegion, self).__init__()
self.data_size = v_uint32()
# NO! self.data_size doesn't contain anything yet!!!
self.data_data = v_bytes(size=self.data_size)
```
**回调函数**
为了正确地处理动态解析器,我们需要使用vstruct的回调函数。当一个VStruct实例完成了一个成员区段的解析时,它会检查这个类是否具有一个前缀为pcb_(解析器的回调函数)的同名方法,如果有的话,就会调用这个方法。同时,这个方法名称的其他部分就是刚才解析的区段的名称;举例来说,一旦BazDataRegion.data_size被解析完,名为BazDataRegion.pcb_data_size的方法就会被调用,当然,前提是这个方法确实存在。
这一点非常重要,因为当回调函数被调用时,VStruct实例已经被待解析的数据填充了一部分了,举例来说:
```
In [16]:
class BlipBlop(vstruct.VStruct):
def __init__(self):
super(BlipBlop, self).__init__()
self.aaa = v_uint32()
self.bbb = v_uint32()
self.ccc = v_uint32()
def pcb_aaa(self):
print("pcb_aaa: aaa: %s\n" % hex(self.aaa))
def pcb_bbb(self):
print("pcb_bbb: aaa: %s" % hex(self.aaa))
print("pcb_bbb: bbb: %s\n" % hex(self.bbb))
def pcb_ccc(self):
print("pcb_ccc: aaa: %s" % hex(self.aaa))
print("pcb_ccc: bbb: %s" % hex(self.bbb))
print("pcb_ccc: ccc: %s\n" % hex(self.ccc))
In [17]: bb = BlipBlop()
In [18]: bb.vsParse(b"AAAABBBBCCCC")
Out[18]:
pcb_aaa: aaa: 0x41414141
pcb_bbb: aaa: 0x41414141
pcb_bbb: bbb: 0x42424242
pcb_ccc: aaa: 0x41414141
pcb_ccc: bbb: 0x42424242
pcb_ccc: ccc: 0x43434343
```
这就意味着,我们可以推迟一个类的布局的最终初始化,直到某些二进制数据解析完成为止。下面是实现一个规定大小的缓冲区的正确方法:
```
In [19]:
class BazDataRegion2(vstruct.VStruct):
def __init__(self):
super(BazDataRegion2, self).__init__()
self.data_size = v_uint32()
self.data_data = v_bytes(size=0)
def pcb_data_size(self):
self["data_data"].vsSetLength(self.data_size)
In [20]: bdr = BazDataRegion2()
In [21]: bdr.vsParse(b"\x02\x00\x00\x00\x99\x88\x77\x66\x55\x44\x33\x22\x11")
In [22]: print(bdr.tree())
Out[22]:
00000000 (06) BazDataRegion2: BazDataRegion2
00000000 (04) data_size: 0x00000002 (2)
00000004 (02) data_data: 9988
```
在第19条命令中,我们声明了一个结构,它具有一个头字段(.data_size),指示随后的原始数据(.data_data )的大小。因为当时我们还没有这个待解析的头部的值。之后,__init__()被调用,我们使用了一个名为.pcb_data_size()的回调函数,它将在解析.data_size区段时被调用。当这个回调函数执行时,会更新.data_data字节数组的大小,以便使用正确的字节数量。在执行第20条命令的时候,我们创建了一个解析器的实例,然后,利用第21条命令对一个字符串进行了解析处理。虽然我们传入了13个字节,但是我们希望只用其中的6个字节:4字节用于uint32型变量.data_size,2字节用于字节数组.data_data。而其余的字节则不做处理。在执行第22条命令的时候,结果表明我们的解析器对二进制数据进行了正确的解析。
请注意,在执行回调函数.pcb_data_size()期间,我们使用方括号访问了Vstruct实例中名为.data_data的对象。之所以这样做,是因为我们既想要修改子实例本身,但是又不想从子实例中取得待解析的具体值的缘故。要想弄清楚到底应该使用哪种技术 (self.field0.xyz 或 self["field0"].xyz),需要读者自己在实践中摸索一下,但通常来说,如果你想要解析一个具体值的话,就应该避免使用方括号。
##0x04 小结
在我们开发可维护的二进制代码解析器的时候,vstruct模块是一个强大的助手。它能够去除开发过程带来的大量样本代码。我特别喜欢使用vstruct解析恶意软件的C2协议、数据库索引和二进制的XML文件。如果读者感兴趣的话,我建议大家通过下列项目来学习vstruct解析器:
- Vstruct的定义,地址为https://github.com/vivisect/vivisect/tree/master/vstruct/defs。
- Python-cim,地址为https://github.com/fireeye/flare-wmi/blob/master/python-cim/cim/cim.py和https://github.com/fireeye/flare-wmi/blob/master/python-cim/cim/objects.py。
- Python-sdb,地址为https://github.com/williballenthin/python-sdb/blob/master/sdb/sdb.py。
- 上一篇:了解 .Net 中的垃圾回收
- 下一篇:Go小知识新解
相关推荐
- WIN10系统如何安装UG10.0
-
随着科技的不断进步与更新,现在有很多公司己经安装上了WIN10的系统以及使用UG10.0了,但很多人反映WIN10系统安装UG10.0不好装,以下详细介绍一下1如果WIN10系统没有自带有JAVA需...
- 自学UG编程的心得分享
-
为什么有的人3个月学会基本的UG建模画图编程,有的断断续续3——5年才学会,还有的人干了7年的加工中心还不会电脑画图编程。这是什么原因?1.顾虑太多,什么都想得到,什么都想一起抓,总是上班加班没时间,...
- UG/NX 绘制一个捞笊(zhào)模型,或者也可以叫它漏勺?
-
今天我们来看看这个模型,起因是群里有小伙伴说要做一个捞笊的模型,看见这名字直接给我整懵了,然后他发了张家里漏勺的图片才知道原来这玩意还有个这种名字。这东西相信每个小伙伴家里都有吧,它的建模方法也比较...
- 再也不用为学UG编程发愁了!380集最新UG资料免费送
-
上期发的UG教程很多粉丝都领到了,收获越来越多的好评!有你们一直陪伴真的很高兴,谢谢各位粉丝!为了给大家提供更优质的资源,这两个月都在整理你们最关心的UG资源,都是多位编程工厂老师傅的工厂实战精华,真...
- 优胜原创UG_3-4-5轴后处理下载
-
反复上机调试,安全稳定可靠,请放心使用2020.11.21,修复YSUG4-5轴后处理锁轴输出...
- 青华模具学院-UG10.0安装文件说明
-
青华模具学院分享:今天我们来跟大家一起学习NX10.0版本的安装方法,网上有很多这个版本的安装视频以及方法图文,但到最终安装软件时仍有很多新手对安装仍然感到头痛,基于这样的情况,我们特别就NX10.0...
- UGnx10安装说明
-
温馨提示,安装前,请退出杀毒软件,关闭防火墙,因为这些软件可能阻断NX主程序和许可程序间的通信,导致安装后,软件无法启动。1、解压下载后的压缩包,右键,选择‘’解压到UGNX10_64位正式版(csl...
- 正版UG软件,正版UG代理,正版软件和盗版软件的区别
-
大家都知道,UG软件是制造业必不可少的一款三维软件,广泛应用于:CAE(有限元分析),CAD(产品设计/模具设计),CAM(计算机辅助制造编程),那么有人不禁要问了,正版软件和盗版软件在使用上有明显区...
- 非常全面的UG加工模块中英对照(图标注释)
-
大家好,我是粥粥老师,听说很多同学都在学习UG但是没有学习资料和安装包,今天粥粥老师就全部打包好免费发放给你们,那么怎么获取全套资料图档安装包呢领取途径①关注②评论、点赞、转发③私信“UG或者...
- 腾讯自研Git客户端 UGit|Git 图形界面客户端
-
支持平台:#Windows#macOS腾讯推出的一款Git图形界面客户端,简化了Git的使用流程,特别适合处理大型项目和文件。支持直接提交和推送操作,避免在大规模项目中由于远程频繁变更而导致...
- 经典收藏:UG重用库的一些不为人知小技巧
-
免费领取UG产品编程、UG多轴UG模具编程、安装包安装教程图档资料关注私信我“领取资料”,即可免费领取完整版,感谢支持,爱你们哟,么么主题:UG后处理+仿真+外挂UG重用库的正确使用方法:首先有...
- UG编程常用指令G、M代码,快收藏好
-
今天给大家分享数控编程常用的指令代码,希望对正在学习路上的你带来一丝丝帮助。最好的方法就是转发到自己空间,方便以后学习。对了,如果你还需要其他UG教程学习资料,CNC加工中心的一些参数,以及UG画图,...
- UG NX7.0中文版从入门到精通
-
Unigraphics(简称UG)是一套功能强大的CAD/CAE/CAM应用软件,UGNX7是其最新版本。《UGNX7从入门到精通(中文版)》以UGNX7为平台,从工程应用的角度出发,通过基...
- 经典UG建模基础练习图纸
-
UG是目前工作中比较优秀拥有大量用户的一款机械模具产品行业三维设计软件,cam加工丶软件支持全中文汉化;能够带给用户更为非凡的设计与加工新体验。很多朋友私信小编问有没有UG建模练习图纸,今天给大家分享...
- UG NC软件基础操作,如何设置UG草图精度
-
默认情况下我们绘制草图一般只保留一位小数,即使你输入多位小数软件也会自动四舍五入,这个你做一些国标的图还好,国标以毫米为单位,一般保留小数点后一位就够了,但如果你做的图是英制单位,那么保留一位小数肯定...
- 一周热门
- 最近发表
- 标签列表
-
- 微信开发者工具 (41)
- amd驱动 (55)
- 下载qqhd (28)
- cad2014 (75)
- cad2014注册机 (30)
- 魔兽争霸官方对战平台 (43)
- cad2007 (31)
- directx下载 (29)
- 桌面备忘录 (51)
- msvcr100.dll下载 (24)
- office2010下载 (42)
- ipscan (20)
- 思源黑体下载 (38)
- kernel32.dll下载 (63)
- office2010下载免费完整版 (28)
- 微信开发者工具下载 (54)
- powerdesigner下载 (46)
- python3下载 (23)
- photoshop官方免费版 (55)
- ch340串口驱动 (52)
- 爱奇艺万能播放器官方下载 (30)
- ps软件官方下载中文版 (51)
- ultraedit下载 (32)
- 360一键root (32)
- ug下载 (64)