当有人问我这个问题的时候,我真的是一脸懵逼,我只知道编译的时候尽量少的引用dll可以减少导入表的量。
后来尝试一个正常的程序,将PE结构的导入表大小和位置全部设置为0:
该程序只负责调用MessageBox(实际是MessaheBoxW,或者MessageBoxA,但是大家都懂,不作区分):
然后运行,炸掉了,很正常:
看来不会是一个特别简单的问题。
经过研究,是要通过PEB来自己找了。首先看这个程序依赖的DLL:
由于MessageBox是User32.dll导出的,所以,会依赖User32.dll,为了尽量减少DLL依赖,因此重新写一段代码:
这段代码,设置了入口点,并将编译器优化关掉。
编译后重新看依赖DLL:
好了至此导入表没了,那我们怎么调用其它的函数呢?
使用OD加载后,查看可执行模块:
发现默认就有几个dll。这时候就有一个关键的函数LdrLoadDll,该函数声明如下:
NTSTATUS NTAPI LdrLoadDll(
IN PWCHAR PathToFile OPTIONAL,
IN ULONG Flags OPTIONAL,
IN PUNICODE_STRING ModuleFileName,
OUT PHANDLE ModuleHandle);
这个函数和LoadLibrary是一样的效果。这个函数在ntdll.dll中导出:
我们能看到,代码就算不依赖任何DLL,程序加载后,还是会有几个DLL会一同加载,而ntdll.dll也在其中;因此思路就有了,只需要程序运行后,找到ntdll.dll然后就能找到LdrLoadDll了。-
##接下来是找ntdll.dll
运行在应用层的程序,通过fs寄存器可以拿到很多TEB中的数据,其中fs:[0x30]是PEB的地址,在PEB地址偏移处0xC的位置,是一个PEB_LDR_DATA结构的地址,PEB_LDR_DATA结构有几个LIST_ENTRY类型的变量,LIST_ENTRY是一个双向链表,这个链表的每一项,都有加载模块的相关信息,每一项都指向LDR_DATA_TABLE_ENTRY结构。在LDR_DATA_TABLE_ENTRY结构中能看到几个有用的信息:
UNICODE_STRING FullDllName;
PVOID DllBase;
FullDllName代表dll的名字,而DllBase就代表dll的加载地址。
由此就能找到我们需要的dll了。
总结一下流程:
我在这里贴上部分相关结构体的定义,你们会发现我贴上来的和MSDN上的不一样,因为这些结构体是我研究的时候从帖子上复制下来的,写博客的时候发现那帖子不见了。。。
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOL Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
我用以下代码找到了ntdll.dll:
找到ntdll.dll后,就要通过导出表来寻找函数的地址。
导出表信息在IMAGE_NT_HEADERS中IMAGE_OPTIONAL_HEADER的IMAGE_DATA_DIRECTORY类型数组的第一个元素中。
然后只要遍历找到LdrLoadDll的地址,这个地址就是内存中这个函数的地址。
知道了如何加载没有的DLL,知道如何去DLL里面找需要函数的地址,就可以不需要导入表了。
代码编译完成后,在Win7 x86的环境下能跑起来,Windows 10 x64跑不起来,因为加载User32.dll的时候,LdrLoadDll返回0xc0000135,暂时我也没找到其他的解决办法。