用IDA PYTHON走进WINDOWS内核

  • A+
所属分类:安全开发

分享我的一些代码,并展示一些你可以用IDA和Python完成的事情。

作为IDA Python的介绍,展示如何枚举Windows系统调用表。

对于那些不知道的,Windows上的所有系统调用都会被赋予一个ID。此ID是一个唯一值,用于指定您在执行系统调用时要调用的函数。这些ID在不同版本的Windows中尤其是在不同的服务包之间可能会有很大差异。从Windows 10开始,它们可以在发布分支中有所不同。对于正常的应用程序来说,这并不是什么大问题,因为用户空间库将始终匹配,以便为您所在的系统使用适当的ID。

如果您正在分析利用漏洞,或者如果您试图自己直接进行系统调用,则情况可能并非如此。因此,知道哪些ID映射到给定OS版本的哪些功能很方便。很长一段时间,参考Mateusz Jurczyk在他的网站上托管的表格之一是最简单的方法,但是如果你想要一个不存在的版本,你需要知道如何自己做。

我将很快解释如何手动枚举表,然后我们将自动使用Python自动处理它。

手动枚举Windows系统调用表

解析系统调用表有三个重要符号:表的基数,表的大小以及参数在堆栈上的字节数。对于ntoskrnl.exe这些符号的名称KiServiceTableKiServiceLimitKiArgumentTable分别。对于win32k.sys这些符号的名称W32pServiceTableW32pServiceLimitW32pArgumentTable在32位版本上,这些符号名称前面加下划线。

举个例子,让我们看看Windows 7 64位。这是从ntoskrnl.exe版本6.1.7601.24117。

用IDA PYTHON走进WINDOWS内核

基于此,我们可以看到有401个(0x191)系统调用。

用IDA PYTHON走进WINDOWS内核

如果我们查看图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 PYTHON走进WINDOWS内核

处理这只意味着我们需要一次读取四个字节,然后将其添加到基地址。

在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

用IDA PYTHON走进WINDOWS内核


然后我们可以确定我们需要使用哪些符号名称。接下来,我们需要测试是否需要使用下划线变体:

用IDA PYTHON走进WINDOWS内核


LocByNameBADADDR如果名称不存在,将返回,因此我们可以使用它来测试符号名称是否存在带或不带下划线。

现在我们有了正确的符号名称,让我们来获取表格的实际大小:

用IDA PYTHON走进WINDOWS内核

首先我们得到地址LocByName,然后我们用地址获取地址的值Dword

要处理的最后一个案例,Windows 10 64位案例:

用IDA PYTHON走进WINDOWS内核


DataRefsFrom将迭代表格底部的数据引用。应该有一个,除非我们正在查看Windows 10的新版本之一。在查看那些较新的Windows 10版本时,我们只需要确保添加图像的基址,我们将得到get_imagebase

在这一点上,我们所需要做的就是从表格基地开始读取连续值。我们可以使用Qword64位版本(不包括较新版本的Windows 10)和Dword32位版本。

以下是可以打印出来的例子:


用IDA PYTHON走进WINDOWS内核

完整代码:

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内核

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: