分享我的一些代码,并展示一些你可以用IDA和Python完成的事情。
作为IDA Python的介绍,展示如何枚举Windows系统调用表。
对于那些不知道的,Windows上的所有系统调用都会被赋予一个ID。此ID是一个唯一值,用于指定您在执行系统调用时要调用的函数。这些ID在不同版本的Windows中尤其是在不同的服务包之间可能会有很大差异。从Windows 10开始,它们可以在发布分支中有所不同。对于正常的应用程序来说,这并不是什么大问题,因为用户空间库将始终匹配,以便为您所在的系统使用适当的ID。
如果您正在分析利用漏洞,或者如果您试图自己直接进行系统调用,则情况可能并非如此。因此,知道哪些ID映射到给定OS版本的哪些功能很方便。很长一段时间,参考Mateusz Jurczyk在他的网站上托管的表格之一是最简单的方法,但是如果你想要一个不存在的版本,你需要知道如何自己做。
我将很快解释如何手动枚举表,然后我们将自动使用Python自动处理它。
手动枚举Windows系统调用表
解析系统调用表有三个重要符号:表的基数,表的大小以及参数在堆栈上的字节数。对于ntoskrnl.exe
这些符号的名称KiServiceTable
,KiServiceLimit
和KiArgumentTable
分别。对于win32k.sys
这些符号的名称W32pServiceTable
,W32pServiceLimit
和W32pArgumentTable
。在32位版本上,这些符号名称前面加下划线。
举个例子,让我们看看Windows 7 64位。这是从ntoskrnl.exe
版本6.1.7601.24117。
基于此,我们可以看到有401个(0x191)系统调用。
如果我们查看图2中的表格,我们可以手动将函数映射到它们的ID。基于我们上面看到的,NtMapUserPhysicalPagesScatter
具有0x0000的ID,NtWaitForSingleObject
是0x0001,NtCallbackReturn
是0x0002等等。
我们需要处理两种特殊情况。如果我们正在查看win32k.sys
,ID将作为表中函数的索引加上0x1000。另外,对于Windows 10的64位版本,Windows build 1607需要以不同的方式处理。在这些版本中,系统调用表包含的函数偏移量为四字节值,而不是八字节值。
这是从ntoskrnl.exe
版本10.0.17134.48开始的:
处理这只意味着我们需要一次读取四个字节,然后将其添加到基地址。
在IDA中自动映射
我们先来看看我们需要调用的IDA函数:
- idaapi.get_imagebase
- 该函数将返回我们正在查看的模块中的基址。
- idc.GetInputFile
- 此函数将返回IDB加载的文件的名称。
- idc.BADADDR
这是一个常量值,它映射为-1作为无符号整数(它也可用于测试我们是处于32位模式还是64位模式)
- idc.Name
- 此函数将返回给定的名称地址。
- idc.LocByName
- idc.Name的逆函数,该函数将返回给定名称的地址。
- idc.Dword
- 该函数将返回给定地址的四字节值。
- idc.Qword
- 该函数将返回给定地址的八字节值。
-idautils.DataRefsFrom
- 该函数将枚举来自给定地址的任何数据引用。
我们首先确保我们正在查看ntoskrnl.exe
或者win32k.sys
:
然后我们可以确定我们需要使用哪些符号名称。接下来,我们需要测试是否需要使用下划线变体:
LocByName
BADADDR
如果名称不存在,将返回,因此我们可以使用它来测试符号名称是否存在带或不带下划线。
现在我们有了正确的符号名称,让我们来获取表格的实际大小:
首先我们得到地址LocByName
,然后我们用地址获取地址的值Dword
。
要处理的最后一个案例,Windows 10 64位案例:
DataRefsFrom
将迭代表格底部的数据引用。应该有一个,除非我们正在查看Windows 10的新版本之一。在查看那些较新的Windows 10版本时,我们只需要确保添加图像的基址,我们将得到get_imagebase
。
在这一点上,我们所需要做的就是从表格基地开始读取连续值。我们可以使用Qword
64位版本(不包括较新版本的Windows 10)和Dword
32位版本。
以下是可以打印出来的例子:
完整代码:
from idaapi import get_imagebase from idc import GetInputFile from idc import BADADDR from idc import Name from idc import LocByName from idc import Dword from idc import Qword from idautils import DataRefsFrom SERVICE_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_base def 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()
本文始发于微信公众号(飓风网络安全):用IDA PYTHON走进WINDOWS内核
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论