當有人問我這個問題的時候,我真的是一臉懵逼,我只知道編譯的時候儘量少的引用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,暫時我也沒找到其他的解決辦法。