分享我的一些代码,并展示一些你可以用IDA和Python完成的事情。
作为IDA Python的介绍,展示如何枚举Windows系统调用表。
对于那些不知道的,Windows上的所有系统调用都会被赋予一个ID。此ID是一个唯一值,用于指定您在执行系统调用时要调用的函数。这些ID在不同版本的Windows中尤其是在不同的服务包之间可能会有很大差异。从Windows 10开始,它们可以在发布分支中有所不同。对于正常的应用程序来说,这并不是什么大问题,因为用户空间库将始终匹配,以便为您所在的系统使用适当的ID。
如果您正在分析利用漏洞,或者如果您试图自己直接进行系统调用,则情况可能并非如此。因此,知道哪些ID映射到给定OS版本的哪些功能很方便。很长一段时间,参考Mateusz Jurczyk在他的网站上托管的表格之一是最简单的方法,但是如果你想要一个不存在的版本,你需要知道如何自己做。
我将很快解释如何手动枚举表,然后我们将自动使用Python自动处理它。
手动枚举Windows系统调用表
解析系统调用表有三个重要符号:表的基数,表的大小以及参数在堆栈上的字节数。对于这些符号的名称,和分别。对于这些符号的名称,和。在32位版本上,这些符号名称前面加下划线。
举个例子,让我们看看Windows 7 64位。这是从版本6.1.7601.24117。
基于此,我们可以看到有401个(0x191)系统调用。
如果我们查看图2中的表格,我们可以手动将函数映射到它们的ID。基于我们上面看到的,具有0x0000的ID,是0x0001,是0x0002等等。
我们需要处理两种特殊情况。如果我们正在查看,ID将作为表中函数的索引加上0x1000。另外,对于Windows 10的64位版本,Windows build 1607需要以不同的方式处理。在这些版本中,系统调用表包含的函数偏移量为四字节值,而不是八字节值。
这是从版本10.0.17134.48开始的:
处理这只意味着我们需要一次读取四个字节,然后将其添加到基地址。
在IDA中自动映射
我们先来看看我们需要调用的IDA函数:
-- 该函数将返回我们正在查看的模块中的基址。
-- 此函数将返回IDB加载的文件的名称。
-这是一个常量值,它映射为-1作为无符号整数(它也可用于测试我们是处于32位模式还是64位模式)
-- 此函数将返回给定的名称地址。
-- idc.Name的逆函数,该函数将返回给定名称的地址。
-- 该函数将返回给定地址的四字节值。
-- 该函数将返回给定地址的八字节值。
-- 该函数将枚举来自给定地址的任何数据引用。
我们首先确保我们正在查看或者:
然后我们可以确定我们需要使用哪些符号名称。接下来,我们需要测试是否需要使用下划线变体:
如果名称不存在,将返回,因此我们可以使用它来测试符号名称是否存在带或不带下划线。
现在我们有了正确的符号名称,让我们来获取表格的实际大小:
首先我们得到地址,然后我们用地址获取地址的值。
要处理的最后一个案例,Windows 10 64位案例:
将迭代表格底部的数据引用。应该有一个,除非我们正在查看Windows 10的新版本之一。在查看那些较新的Windows 10版本时,我们只需要确保添加图像的基址,我们将得到。
在这一点上,我们所需要做的就是从表格基地开始读取连续值。我们可以使用64位版本(不包括较新版本的Windows 10)和32位版本。
以下是可以打印出来的例子:
完整代码:
from idaapi import get_imagebasefrom idc import GetInputFilefrom idc import BADADDRfrom idc import Namefrom idc import LocByNamefrom idc import Dwordfrom idc import Qwordfrom idautils import DataRefsFromSERVICE_TABLE_NAME_SYMBOL_MAP = { 'ntoskrnl.exe' : ('KiServiceTable', 'KiServiceLimit'), 'win32k.sys' : ('W32pServiceTable', 'W32pServiceLimit'),}SERVICE_TABLE_NAME_BASE_MAP = { 'ntoskrnl.exe' : 0, 'win32k.sys' : 0x1000,}def _get_service_table_info(): name = GetInputFile().lower() if name not in SERVICE_TABLE_NAME_SYMBOL_MAP: return None stride = 8 table_name, limit_name = SERVICE_TABLE_NAME_SYMBOL_MAP[name] table_address = LocByName(table_name) if table_address == BADADDR: table_name = '_' + table_name limit_name = '_' + limit_name table_address = LocByName(table_name) stride = 4 if table_address == BADADDR: print 'table address failure' return None limit_address = LocByName(limit_name) limit = Dword(limit_address) base_id = SERVICE_TABLE_NAME_BASE_MAP[name] offset_base = 0 if stride == 8: for x in DataRefsFrom(table_address): # Ideally we would test out the reference here # There is a chance IDA made a mistake as it seems to treat the # contents of the table as code when it contains 4-byte offsets break else: stride = 4 offset_base = get_imagebase() return table_address, limit, stride, base_id, offset_basedef enumerate_service_table(): table_info = _get_service_table_info() if table_info is None: return table_start, limit, stride, base_id, offset_base = table_info table_end = table_start + limit * stride if stride == 4: getter = Dword else: getter = Qword for syscall_id, table_offset in enumerate(range(table_start, table_end, stride), base_id): function_offset = getter(table_offset) if function_offset == 0: continue function_address = function_offset + offset_base yield syscall_id, function_address def print_service_table(): for syscall_id, function_address in enumerate_service_table(): function_name = Name(function_address) print '%04x - %s' % (syscall_id, function_name)print_service_table()
领取专属 10元无门槛券
私享最新 技术干货