本文共 1597 字,大约阅读时间需要 5 分钟。
在Windows平台上,如果要写一个有意义的用户态程序(以任何方式显示数据或将数据写入设备/文件等),是几乎无法不依赖库的。
如果要让数据显示到控制台、存储数据到文件、或者绘制图形界面,一定需要操作系统的支持。Windows API是通过若干用户态的动态链接库(DLL)调用的。这些动态链接库对系统服务进行了封装,向开发者暴露稳定的接口;而系统服务暴露的接口有可能发生变化。
如果需要调用Windows NT系统服务,需要将系统服务号放进EAX寄存器,然后通过int 2Eh中断或syscall指令转移到特权等级0执行系统服务处理程序。系统服务处理程序通过系统服务描述符表(System Service Descriptor Table,简称SSDT)跳转到对应的内核函数。内核函数会依照调用约定获取栈或寄存器中的参数进行处理。
这些系统服务调用被包装成C语言函数,通过ntdll.dll导出API。其中一部分可以通过Windows DDK在驱动程序中使用,接口相对稳定;而另一部分则是仅仅导出了符号,并没有收录在文档中,这些函数的使用方式有可能在未来被微软更改。
原则上只要知道了系统服务号,就可以不依赖任何库调用Windows NT的内核函数。问题在于,由于系统调用是不对开发者开放的,同一个内核函数对应的系统服务号会随着Windows版本更新而变化;而且在32位环境中,原生32位系统服务调用与WoW64下(在64位操作系统上运行32位应用)的系统服务调用是完全不同的,需要使用特殊的系统服务号,并额外借助WoW64的状态转移机制。
理论上如果要完美解决这个问题,需要记录Windows NT内核每个版本操作系统中各个内核函数对应的系统服务号,然后通过进程环境数据动态检测系统版本号和WoW64状态决定使用哪一个系统服务号以及是否使用WoW64状态转移,还要紧盯系统新版本可能带来的变化。
因此可以得出这样的结论:哪怕开发者可以绕开Windows API的kernel32.dll和user32.dll,ntdll.dll也是几乎不可逾越的障碍,绕开这个对于实际开发来说不现实。
如果抛弃版本兼容性,确实可以轻松写出不依赖任何库的程序,但是只能在特定版本的Windows中成功运行。下面举例说明做到这一点的方法。
Windows 10使用控制台驱动程序(Console Driver)与控制台进行数据交换,因此操作控制台的方式与操作其他Windows设备的方法相似。
一般情况下,用户态程序调用Windows API的DeviceIoControl函数操作设备的驱动程序进行I/O,然而这个函数依赖kernel32.dll。出于演示目的,这里绕过Windows API,直接通过底层的NtDeviceIoControlFile函数进行系统服务调用。
下面展示如何通过操作Windows 10控制台驱动程序把Brain Kernighan著名的Hello World程序改写成不依赖任何库的形式。以下是Brain Kernighan的C语言源代码:
首先需要把NtDeviceIoControlFile的系统服务调用从ntdll.dll中抽出,从而消除对ntdll.dll的依赖。下面是这个函数的MASM汇编代码:
接下来用C语言实现字符串的输出。源代码如下:
运行Visual Studio的x64本机工具命令提示,用下列命令行进行编译:
链接后生成一个非常小的可执行文件(Visual C++2019生成的文件大小为2.5 KB)。使用Dependency Walker查看这个文件,可以看到没有依赖任何库:
在64位Windows 10(我使用的版本是Windows 1020H210.0.19042.685)的命令提示符或PowerShell中运行这个文件,即可在控制台上看到输出:
转载地址:http://qliox.baihongyu.com/