模块隐藏(LDR_MODULE链 与 PE特征)

比较常见的模块隐藏方法有抹去模块的PE头,断开进程的LDR_MODULE链,Hook模块枚举函数等。后面备注VAD隐藏。

//测试EXE,加载WaiGua.dll,再隐藏。
#include 
#include 
#include 
typedef struct _LSA_UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;

typedef struct _LDR_MODULE
  {
      LIST_ENTRY InLoadOrderModuleList;
      LIST_ENTRY InMemoryOrderModuleList;
      LIST_ENTRY InInitializationOrderModuleList;
      PVOID BaseAddress;
      PVOID EntryPoint;
      ULONG SizeOfImage;
      UNICODE_STRING FullDllName;
      UNICODE_STRING BaseDllName;
      ULONG Flags;
      USHORT LoadCount;
      USHORT TlsIndex;
      LIST_ENTRY HashTableEntry;
      ULONG TimeDateStamp;
 } LDR_MODULE, *PLDR_MODULE, *PLML;

// typedef struct _LE{
//   DWORD Flink;
//   DWORD Blink;
// }LE, LIST_ENTRY;

BOOL BreakLdrModuleLink(DWORD dwBaseAddr)
{
  PLDR_MODULE pLMFNode = NULL, pLNode = NULL ;
  PLDR_MODULE pLMHNode = NULL, pLMPNode = NULL;
  PLDR_MODULE pLMTNode = NULL;
  BOOL bSuccess = FALSE;
	
  //获取LDR_MODULE链的头指针
	__asm{

		pushad
		pushfd
		xor edx, edx
		mov ebx, fs:[edx + 0x30] 
		mov ecx, [ebx + 0x0C] 
		lea edx, [ecx + 0x0C]
		mov ecx, [ecx + 0x0C] 
		mov pLMHNode, edx 
		mov pLMFNode, ecx
		popfd
		popad

	}

	//查找目标
	PLDR_MODULE pLMNode = pLMFNode;
	pLMPNode = pLMHNode;
	do{
		//比较是否是目标模块
		if( (DWORD)pLMNode->BaseAddress == dwBaseAddr)
		{
			bSuccess = TRUE;
			break;
		}
		pLMPNode = pLMNode;
		pLMNode = (PLDR_MODULE)pLMNode->InLoadOrderModuleList.Flink;
	}while(pLMNode != pLMHNode);

	if( !bSuccess )
	{
		OutputDebugString("cannot find the dest module!");
		return bSuccess; //未找到目标模块
	}
	
	//断开InLoadOrderModuleList链
	//重建Flink
	pLMTNode = (PLDR_MODULE)pLMNode->InLoadOrderModuleList.Flink;
	pLMPNode->InLoadOrderModuleList.Flink = (PLIST_ENTRY)pLMTNode;
    //重建Blink
	((PLDR_MODULE)(pLMNode->InLoadOrderModuleList.Flink))->InLoadOrderModuleList.Blink  = 
	pLMNode->InLoadOrderModuleList.Blink;
    
   //断开InMemoryOrderModuleList链
   //重建Flink
	pLMPNode->InMemoryOrderModuleList.Flink = 
	pLMNode->InMemoryOrderModuleList.Flink;
	//重建Blink
	pLMTNode = (PLML)(pLMNode->InMemoryOrderModuleList.Flink - sizeof(LIST_ENTRY));
	pLMTNode->InMemoryOrderModuleList.Blink =  
	pLMNode->InMemoryOrderModuleList.Blink;

   //断开InInitializationOrderModuleList链
   //重建Flink
	pLMPNode->InInitializationOrderModuleList.Flink =  
	pLMNode->InInitializationOrderModuleList.Flink;

	//重建Blink
	pLMTNode = (PLML)(pLMNode->InInitializationOrderModuleList.Flink - 2*sizeof(LIST_ENTRY));
	pLMTNode->InInitializationOrderModuleList.Blink  = pLMNode->InInitializationOrderModuleList.Blink;
}

void ModuleHide(HMODULE hInjectDll)
{
	DWORD dwOldProtect;
	VirtualProtect((LPVOID)hInjectDll,1024,PAGE_READWRITE, &dwOldProtect);
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER) hInjectDll;
	
	//抹去MZ标志
	pDosHeader->e_magic = 0;
	
	//DOS头后面就是PE头
	//PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader+1);
	PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((CHAR*)hInjectDll + pDosHeader->e_lfanew);
	//抹去PE标志
	pNtHeader->Signature = 0;

	VirtualProtect((LPVOID)hInjectDll,1024,dwOldProtect, &dwOldProtect);
	
	//断开LDR_MODULE
	BreakLdrModuleLink((DWORD)hInjectDll);
}

void EnumModule(void)
{
	// TODO: Add your control notification handler code here
	char szBuffer[256*100] = "";
	char szModuFile[240] = "";
	char szTmpBuffer[256] = "";


		MODULEENTRY32 moudle;
		HANDLE handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,0);
		if (handle == INVALID_HANDLE_VALUE)
		{
			printf("枚举模块失败!");
			return;
		}
	    int i = 1;
		if ( Module32First(handle,&moudle))
		{ 
			do
			{
				sprintf(szModuFile,"[%d]Address: 0x%x, Name: %s \r\n", i, moudle.modBaseAddr, moudle.szModule);
				strcat(szBuffer,szModuFile);
				i++;
			}while(Module32Next(handle,&moudle));
		}
		CloseHandle(handle);

	printf(szBuffer);

}
void main()
{
	HMODULE hDll = LoadLibrary("WaiGua.dll");
	printf("this waigua.dll load at:0x%x\n",hDll);
	ModuleHide(hDll);
	EnumModule();
	getchar();
}


 

//WaiGua.dll
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		MessageBoxA(NULL, "the simple inject success", "Dll Inject", MB_OKCANCEL);
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}


模块隐藏(LDR_MODULE链 与 PE特征)_第1张图片

 

备注:

摘要(转自网络):
一、从PEB的Ldr链中消失

                              引用内容

lkd> dt _PEB 7ffdc000 //当前PEB的地址
nt!_PEB
    ...
    +0x00cLdr              : 0x001a1e90 _PEB_LDR_DATA   //这里指向Ldr结构
lkd> dt _PEB_LDR_DATA 0x001a1e90 //这个结构里有三个链表的表头
nt!_PEB_LDR_DATA
    +0x000Length            :0x28
    +0x004 Initialized      : 0x1 ''
    +0x008SsHandle          : (null)
    +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x1a1ec0 -0x1a34f8 ]
    +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x1a1ec8- 0x1a3500 ]
    +0x01c InInitializationOrderModuleList : _LIST_ENTRY [0x1a1f28 - 0x1a3508 ]
    +0x024 EntryInProgress   : (null)


这里看到有三个链表,其实三个链表的内容是一样的,但是链表的顺序不一样,分别按加载顺序、内存顺序、初始化顺序排列。
每一个DLL由一个LDR_DATA_TABLE_ENTRY结构描述,但是第一个结构被链入了三个链表。取一个来看看:

引用内容

lkd> dt _LDR_DATA_TABLE_ENTRY 0x1a34f8
nt!_LDR_DATA_TABLE_ENTRY
    +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x1a1e9c -0x1a3450 ]
    +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x1a1ea4 -0x1a3458 ]
    +0x010 InInitializationOrderLinks : _LIST_ENTRY [0x1a1eac - 0x1a3460 ]
    +0x018DllBase           :0x20000000
    +0x01cEntryPoint        : (null)
    +0x020 SizeOfImage      : 0x549000
    +0x024 FullDllName      : _UNICODE_STRING "C:\WINDOWS\system32\xpsp2res.dll"
    +0x02c BaseDllName      : _UNICODE_STRING "xpsp2res.dll"
   ......//省略部分内容


随便取一个链表进行遍历,根据DllBase找到自己的DLL之后,从三个链中RemoveEntryList就可以了,这样所有使用PEB->Ldr结构来枚举DLL链表的就无法找到了。
由于大部分ARK对隐藏DLL的查找并不是很重视(比如RKU,Gmer,XueTr,Atool,NIAP),因此该方法就可以bypass很多ARK了,还是有一定市场的~
但对IceSword是个例外

二、从VAD树中消失
IceSword 在枚举进程模块时使用的是ZwQueryVirtualMemory,查询的InfoClass是MemorySectionName(或者叫 MemoryMappedFilenameInformation,值为2)。NtQueryVirtualMemory首先判断Vad->ControlArea->FilePointer是否有效,若有效则调用ObQueryNameString查询此文件对象的名 称,最终由文件系统完成此次查询工作。
关于VAD的详细知识,可以参考《JIURL玩玩Win2k内存篇 VAD》,这里不作为重点,知道是平衡二叉树就可以了,树的根结点在EPROCESS中。

引用内容

lkd> dt _EPROCESS 83f915b8  
nt!_EPROCESS
    ...
    +0x11cVadRoot           :0x84079c08


该成员是一个MMVAD类型的结构,而成员LeftChild和RightChild分别是该结点的左子结点和右子结点。

引用内容

lkd> dt _MMVAD 0x84079c08
nt!_MMVAD
    +0x000 StartingVpn      : 0x8e0
    +0x004EndingVpn         : 0x8e0
    +0x008Parent            :(null)
    +0x00cLeftChild         : 0x843b1128 _MMVAD//左孩子
    +0x010RightChild        : 0x840bf4a0 _MMVAD//右孩子
    +0x014u                : __unnamed //标志位
    +0x018 ControlArea      : (null)
    +0x01c FirstPrototypePte : (null)
    +0x020 LastContiguousPte : (null)
    +0x024u2                :__unnamed


要 对目标DLL实施隐藏时,先获取该DLL基址,然后遍历VAD树,根据MMVAD->StartingVpn做匹配(StartingVpn实际上 是内存地址的高20位,比如0x7c800000在这里将只显示为0x7c800)找到目标DLL的VAD结构(这里以kernel32.dll为例,其 加载地址正为0x7c800000):

引用内容

lkd> dt _MMVAD 84174a18
nt!_MMVAD
    +0x000 StartingVpn      : 0x7c800
    +0x004EndingVpn         : 0x7c91b
    +0x008Parent            :0x841223a0 _MMVAD
    +0x00cLeftChild         : 0x84120470 _MMVAD
    +0x010RightChild        : 0x841a4790 _MMVAD
    +0x014u                : __unnamed
    +0x018 ControlArea      : 0x876d0b88 _CONTROL_AREA //关键域
    +0x01c FirstPrototypePte : 0xe177d6f0 _MMPTE
    +0x020 LastContiguousPte : 0xfffffffc _MMPTE
    +0x024u2                :__unnamed
lkd> dt _CONTROL_AREA 0x876d0b88
nt!_CONTROL_AREA
    ...
    +0x024 FilePointer      : 0x876d0b10 _FILE_OBJECT外//目标在这里


好 了,看到FILE_OBJECT了,这时你应该会想到系统是从这里取到的文件名吧,没错,这儿就是我们要动手脚的地方。根据小伟、Sysnap等人的测 试,只要把ControlArea->FilePointer->FileName.Buffer填0就可以实现该DLL的隐藏(根据字符串 的特性,实际上只需要把第一个字符填0就可以了),此时ZwQueryVirtualMemory将返回0xC0000039错误,即“指定的路径无 效”,自然也就枚举不到了。而且对于那些共享的dll,如系统的ntdll.dll,kernel32.dll或在不同进程中被加载2次或以上的dll, 虽然是在不同进程中,但是使用的是同一个共享的ControlArea结构,因此只需要改一个,那么在所有进程中都将实现隐藏,这对于隐藏全局钩子类型的dll显然是非常方便的。
IS是在ZwQueryVirtualMemory查完全无法枚举到DLL时才采用枚举PEB的方法,因此结合前面的Ldr断链法,足以搞定N多ARK了。
我所说的“从VAD树中消失”只是使该VAD的信息从IS的查询结果中消失,而并不是真的摘掉该VAD~~
值得一说的是,Sysnap的Yas Kit在检测隐藏DLL方面也是比较强的,但是对于动了VAD的,似乎也无能为力~

三、抹掉PE特征
有的工具可以直接枚举所有有效内存并检查PE标记来确定是否有隐藏DLL,因此需要把PE特征抹掉来对抗之,方法很简单,把PE头整个填零就可以了。
这种方法主要是作为前两种方法的补充,单独搞是没有意义的。

再来说说非正规方式加载的DLL,怎么个非正规呢?其实就是不用LoadLibrary,自已实现Loader的功能.
实 现Loader功能之后,不管你是Load别的DLL,或者DLL自已Load自己(老V的ReloadAndRun同样适用于DLL),在Load完成 后,不会出现在PEB->Ldr链中,它的VAD也不会与FILE_OBJECT发生任何关系,然后再抹掉PE特征,隐藏效果与上面的正规隐藏法相 当,甚至要更隐蔽一些~~
再极端一点,DLL也可以完全不要,注入具有相同功能的shellcode然后开线程执行就可以了,只是shellcode写起来麻烦一点而已,写Loader也麻烦啊~~

 

 

http://bbs.pediy.com/archive/index.php?t-125571.html

http://lwglucky.blog.51cto.com/1228348/282331

 

你可能感兴趣的:(游戏)