ASLR 以及程序如何实际调用其函数

信息安全 视窗 开发 操作系统 外壳代码 aslr
2021-08-20 11:11:21

我正在研究保护技术,但我对 ASLR 如何在 Windows 环境中为程序工作表示怀疑。

据我所知,ASLR 的工作原理是在将模块加载到内存时随机化部分图像基地址,这样漏洞利用就不能依赖硬编码的地址。

我的问题是:假设一个程序使用模块“kernel32.dll”并将其加载到它的地址空间中。该程序中可能有 CALL 指令,例如

...
CALL some_address_inside_kernel32.dll
...
...

如果 kernel32.dll 函数地址不再可靠,程序如何知道跳转到哪里?loader 是否干预这个过程?

2个回答

您不调用内核内部的函数。内核位于另一个特权级别;普通代码无法访问其内存页面。为了跳入内核代码,应用程序代码执行一个系统调用,这需要使用一个特定的门来处理临时权限提升。在运行 Linux 的 32 位 x86 系统上,这是通过int 0x80:软件触发的中断来完成的。系统调用参数由调用者在一些特定的CPU寄存器中提供;特别是,%eax寄存器包含应用程序想要执行的系统调用的符号标识符。中断处理程序(在内核中)查看 CPU 寄存器以了解应用程序希望内核做什么(系统调用是否授予是另一个问题)。

系统调用没有 ASLR;这不是一个相关的概念。ASLR 用于DLLDLL 是加载到应用程序地址空间的一段应用程序代码,应用程序可以在其正常权限下访问;特别是,应用程序代码可以通过正常的“跳转”操作码“跳转”到 DLL 代码中(函数调用只不过是一种美化的跳转)。

由于只有在实际加载 DLL 时才能知道加载 DLL 的实际地址,因此应用程序代码遵循特殊约定,以便可以动态调整其跳转操作码以指向恰好加载 DLL 的位置。这种动态调整由动态链接器执行。该链接器使用重定位表来执行其工作:此类表中的条目描述了需要调整的操作码,以及操作码尝试访问的函数的名称。当 DLL 被加载(通过动态链接器)时,该函数在内存中的位置是已知的,并且重定位表中描述的跳转操作码被调整。

如您所见,DLL 的整个概念允许将 DLL 加载到 RAM 中的任意位置。该位置可能会随着应用程序的连续执行而有所不同。实际位置取决于哪里有足够大的可用内存“洞”来进行加载,因此它可以根据应用程序的操作、之前分配的内存量等而改变。DLL 加载“自然”意味着非固定加载地址。ASLR只是DLL 的自愿移动:动态链接器有意选择一个随机(免费)加载地址。这对应用程序(几乎)是完全透明的。

如果 kernel32.dll 函数地址不再可靠,程序如何知道跳转到哪里?loader 是否干预这个过程?

小熊给大家介绍了 ASLR 的总体概况。但是,您需要删除一些特定于 Windows 的要点:

  • 实际上,DLL总是不得不应对重定位。它们在地址空间中包含一个首选基地址,以至于在开发 win32 应用程序中的一个优化步骤来重新定位您的 DLL,因为在应用程序启动时重新定位许多 DLL 的成本会减慢速度,因为加载许多 DLL 会导致很多的碰撞。
  • 这与 Unix 的 PIC 方法形成对比,请参阅wikipedia
  • 加载 DLL 时,它会一直保留在内存中,直到关闭它的最后一个句柄。鉴于将 DLL 映射到每个进程的唯一地址位置是低效的,这意味着一旦加载,常见的系统 DLL(例如)kernel32.dll在所有应用程序中都存在于同一地址。您可以从这里的优秀答案中了解更多关于 ASLR 的内部结构因此,虽然在重新启动时可能很难预测地址kernel32.dll,但一旦找到给定启动的地址,地址就不会改变。
  • 在 Windows 架构上,kernel32.dll实际上是一个用户模式助手库。正如 Thomas 所说,进入内核模式需要系统调用。这些实际上发生在ntdll.dll(是的,另一个 DLL)中,它将各种Nt前缀函数包装到系统调用中,将它们打包并将它们触发到内核。
  • 内核驱动程序是非确定性加载的——内核确实有 ASLR。请参阅此 osr 线程此演示文稿据我了解,Windows 启动管理器会为您加载ntoskrnlhal.dll在随机位置加载。自 Server 2008 以来的驱动程序肯定总是以非确定性方式加载。