如何隐藏导入表

本文探讨了如何隐藏PE文件的导入表,通过修改PE结构并利用LdrLoadDll函数动态加载DLL。通过研究PEB和LDR_DATA_TABLE_ENTRY结构,找到了在没有导入表的情况下调用DLL函数的方法。然而,这种方法在不同操作系统环境下可能存在兼容性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当有人问我这个问题的时候,我真的是一脸懵逼,我只知道编译的时候尽量少的引用dll可以减少导入表的量。

后来尝试一个正常的程序,将PE结构的导入表大小和位置全部设置为0:
该程序只负责调用MessageBox(实际是MessaheBoxW,或者MessageBoxA,但是大家都懂,不作区分):
这里写图片描述

正常的导入表

将导入表RVA和大小设置为0

然后运行,炸掉了,很正常:
这里写图片描述

看来不会是一个特别简单的问题。

经过研究,是要通过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了。

总结一下流程:

Created with Raphaël 2.3.0 fs:[0x30]拿到PEB地址 PEB偏移0xC位置拿到PEB_LDR_DATA地址 PEB_LDR_DATA拿到链表首地址 通过链表拿到LDR_DATA_TABLE_ENTRY地址 判断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,暂时我也没找到其他的解决办法。

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值