我已经在VS2010控制台项目中编译了以下C源代码。

#include <stdio.h>
int main(int argc, char* argv[]){
    printf("hello world\n");
    return 0;
}


然后我将/MT选项用于发布模式以静态链接C运行时库。
但是,据我所知,C运行时库仍调用较低级别的系统函数-例如,C运行时函数printf最终调用Windows API API。

以及实际的函数体因此,即使我静态链接C运行时库,二进制文件也不包含
包括WriteFileWriteFile指令在内的整个例程...
核心部分仍在DLL中。下图描述了我的理解方式:



我想要的是将所有内容静态链接到单个EXE文件中。包括kernel32.dllSYSENTER消除了加载程序解析IAT和解析函数名称的必要性。

下图描述了我想要的内容:



我知道在带INT 0x2E的Linux中这很简单。我所要做的就是给选项kernel32.dll

VS2010中有这样的选项吗?如果我误会了,请纠正我。

评论

答案显然是“否”。您将必须编写自己的特定于操作系统的syscall层。问题是:为什么不使用标准的CRT?有什么优势?

谢谢。实际上,我并不是在寻找性能优势。我以这种方式,可以像删除Linux剥离的二进制文件一样删除每个符号,并使反转变得更困难。

@daehee:这使您的问题有了一个全新的视角。

总体来说,您的描述是正确的,但是您需要添加另一层,即NT本机api:fread(msvcrt)-> ReadFile(kernel32)-> NtReadFile(ntdll.dll)-> kernel

经过足够的努力,您也许可以执行嵌入式Windows功能,但随后您便会绑定到该特定的Windows版本,并且可能会侵犯MS的版权。

#1 楼

让我首先告诉您,由于众所周知的DLL的工作方式,您想要的东西将是不可能的。您可以使用PEBundle或dllpackager之类的工具尝试类似的操作,但通常(我肯定会说)会因众所周知的DLL(例如系统DLL甚至MSVC运行时DLL的不同版本)而失败。有关知名DLL的​​相关性和含义,请参见此内容。

kernel32.dll在Win32子系统中扮演非常特殊的角色,因为它有助于向子系统注册Win32线程和进程(csrss.exe) 。


实际上回答了OP的以下问题:


我并不是在寻找性能优势。我想,用这种方法,我可以像删除Linux剥离的二进制文件一样删除每个符号,并使反转变得更困难。


这样就没有意义了。您仍然只能导入单个函数,并使用复杂的方式导入DLL和/或解析函数。即隐藏从哪个DLL导入的功能。在黑客界比较流行的一件事是,对导出的函数名称进行哈希处理,然后自己遍历已加载映像的导出,对找到的每个函数名称进行哈希处理,然后与已知的哈希值进行比较。

这是一篇关于所需方法的好文章,因为shell代码对被劫持的进程中导入的函数地址一无所知。

正如Igor指出的那样,kernel32.dll将被加载到该进程中,并自动Vista的顺序也发生了变化(以前ntdll.dll是PEB的DLL列表中的第一个aka LoaderData)。因此,上面的文章中列出了确切的方法。

其他几点:


如果您不想使用LoadLibrary(或其对应的ntdll.dll)动态加载DLL,则可以在IAT中保留对单个导入函数的引用-这是某些可执行打包程序的实现方式。
,首先要解析LoadLibraryA,加载所需的DLL,然后使用已解决的GetProcAddress(或kernel32.dll上已使用并已在本文中概述的您自己的方法)加载更多功能。使其对熟练/经验丰富的逆向工程师更加困难。他们中的大多数人都会看到类似的方案;)...动态分析将轻松揭示您的技巧,并使逆向工程师能够解决它们。


作为替代,您可以诉诸于通过编写简化的反汇编程序来创建系统调用号,该反汇编程序可以将索引提取到SSDT(系统服务描述符表)中,然后自己完成其余工作。这已经有很长的记录了,因为这是人们在内核模式驱动程序中挂钩索引时通常用来在SSDT中找到索引的方式。大致来说,如果您在ntdll.dll中有指向SSDT索引的函数的指针,则应检查一下假设,然后检索适当的值。在Windows NT 4到2003(32位)中,它看起来像

  B8 ?? ?? ?? ??


其中B8代表mov eax, ????????,问号是SSDT的索引。因此,在检查了B8之后,您将跳过它并获取下一个DWORD。使用C代码的示例:但是我看不到任何优势-无论是从性能角度还是在阻止逆向工程方面都没有优势。

评论


实际上,您唯一需要的导入是GetProcAddress,因为可以通过GetProcAddress(“ LoadLibrary”)进行导入。

–彼得·弗里
13年5月23日在15:44

#2 楼

Windows内核与Linux或OS X不同,它不使用跨版本的一致syscall编号。即使发布Service Pack,数字也可能会更改。例如,NtReadFile系统调用在Windows NT 4上是0x0086,但在Windows 7上是0x0111(完整列表请参见此处)。
这就是为什么所有正确的程序都使用kernel32.dll(或ntdll.dll)执行实际调用的原因-确保DLL使用与内核匹配的syscall号。

顺便说一句,通过在IAT中不列出kernel32.dll,您将不会节省任何内容-系统加载程序始终将其映射到Win32进程中(从Windows 2000 IIRC开始)。

评论


我以为Windows系统调用编号与Linux相同。感谢您提供的信息,这对我的理解很有帮助!

–daehee
13年5月22日在10:39

@daehee:每隔一段时间就会更改一次。这就是文件列出ELF文件内核版本的原因。

– 0xC0000022L♦
2013年9月18日下午16:51

#3 楼

可能是出于MS的全部意图,不仅系统调用编号在版本之间发生了变化,而且许多DLL序数值也发生了变化。如果您希望代码在OS发行版之间都能工作,则需要绑定到WIN32并使用完整的函数名。