看雪论坛
发新帖

[原创]不用导出任何函数的DLL劫持注入,完美!

lononan 2017-3-13 15:48 5146

.

最近在写SOCKS5代理,用的是LSP.代理成功了,但是因为LSP模块没办法隐藏,各种三方模块啊

删除PE头,强制删除文件,游戏都会闪退


想来想去,就想到这个法子,HOOK LdrLoadDll返回的模块句柄

把返回句柄替换成真实的DLL句柄,这种劫持的方式好处就是不用任何导出函数

不管什么是DLL劫持都可以,只要你能替换原来的DLL,LSP也挺方便的,哈哈哈


不过这种lsp劫持注入,只能用API HOOK来完成 SOCKS5代理

基本框架已经测试通过,抽空来分享一下劫持注入部分的代码


首先是HOOK部分

PBYTE gethookadd(PBYTE pLdrLoad) //搜索LdrLoadDll返回
{
	BYTE bsearch[3] = {0xC2,0x10,0x00};
	while (TRUE)
	{
		if (memcmp(pLdrLoad, bsearch,3)==0)
		{
			break;
		}
		pLdrLoad++;
	}
	return pLdrLoad;
}
void hookLdrLoadDll(bool bHook)
{
	if (pLdrLoadDll==NULL)
	{
		pLdrLoadDll = (PBYTE)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "LdrLoadDll");
		pLdrLoadDll = gethookadd(pLdrLoadDll);
		VirtualProtect(pLdrLoadDll, 10, PAGE_EXECUTE_READWRITE, NULL);
	}
	if (bHook )
	{
		if (!pVEH1) //如果没注册VEH就注册一个,全局变量
			pVEH1 = AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)FirstVectExcepHandler);
	
		BYTE bNewcode[1] = { 0xCC };
		WriteProcessMemory(GetCurrentProcess(), pLdrLoadDll, bNewcode, 1, NULL);
	}
	else
	{
		BYTE bNewcode[1] = { 0xC2 };
		WriteProcessMemory(GetCurrentProcess(), pLdrLoadDll, bNewcode, 1, NULL);
		if (pVEH1)//删除VEH
		{
			RemoveVectoredExceptionHandler(pVEH1);//这里好像删除不成功,所以写道卸载模块的线程里了,删除不成功就卸载不了模块
		//	MessageBoxA(0, "删除VRH",0,0);
		}	
	}
}

本来之前用的是inlinehook,XP测试的,弄成功了发现XP WIN7 WIN10中的HOOK地址汇编代码不一样,

而HOOK地址中 只有三个字节,所以就想到了用VEH来HOOK,只需要修改一个字节就行了


然后是处理HOOK部分:

 NTSTATUS NTAPI NewLdrLoadDll(
	IN PWCHAR               PathToFile OPTIONAL,
	IN ULONG                Flags OPTIONAL,
	IN PUNICODE_STRING      ModuleFileName,
	OUT PHANDLE             ModuleHandle)
{
	 if (*ModuleHandle == m_Module)//如果返回的句柄是自身模块句柄
	 {
		 *ModuleHandle = LoadLibrary(L"NTDLL.DLL");//假设劫持NTDLL
		 hookLdrLoadDll(false);//结束HOOK	
//		 MessageBox(0, ModuleFileName->Buffer, 0, 0);
		 FreeLib();
	 }
	 return 0;
}
LONG CALLBACK FirstVectExcepHandler(_In_ PEXCEPTION_POINTERS ExceptionInfo)//异常处理
{
	switch (ExceptionInfo->ExceptionRecord->ExceptionCode)
	{
	case STATUS_BREAKPOINT:
	{
		if (ExceptionInfo->ContextRecord->Eip==(DWORD)pLdrLoadDll)//如果是我们的HOOK地址 
		{
			ExceptionInfo->ContextRecord->Eip = (DWORD)NewLdrLoadDll;//跳转到处理函数
			return EXCEPTION_CONTINUE_EXECUTION;
		}
		
	}
	}
	return EXCEPTION_CONTINUE_SEARCH;
}


因为RET的时候堆栈各种参数跟函数入口的时候都是一样了,

所以这里的HOOK函数(NewLdrLoadDll)直接跟LdrLoadDll一样就行了

先判断LdrLoadDll返回的句柄是不是自己的,如果是,说明我们的DLLMain函数执行完成,要返回了

然后把返回的句柄替换成被劫持的DLL

然后还原HOOK地址,完成我们想要的操作(比如内存注入自己,创建独立的工作线程),

最后卸载自身模块,不留痕迹


卸载模块部分:

void FreeLib() //退出线程
{
	BYTE bCode[100] = { 106,80,184,0,0,16,0,255,208,104,0,0,1,0,184,0,16,0,0,255,208,194,4,0 };
	DWORD i=0;
	
	//删除VEH
	shellcodeAppend(bCode, 0x68, (LONG)pVEH1, 4, TRUE);//push pVEH1  
	shellcodeAppend(bCode, 0xB8, (LONG)RemoveVectoredExceptionHandler, 4, false);//mov eax,RemoveVectoredExceptionHandler
	shellcodeAppend(bCode, 0xFFD0, 0, 0, false);//call  eax
	//等待50毫秒
	shellcodeAppend(bCode, 0x68, 0x50, 4, false);//push 50 
	shellcodeAppend(bCode, 0xB8, (LONG)Sleep,4,false);//mov eax,sleep
	shellcodeAppend(bCode, 0xFFD0, 0,0,false);//call  eax
	//卸载自身模块
	shellcodeAppend(bCode, 0x68, (LONG)m_Module, 4, false);//push m_Module 
	shellcodeAppend(bCode, 0xB8, (LONG)FreeLibrary, 4, false);//mov eax,FreeLibrary
	shellcodeAppend(bCode, 0xFFD0, 0, 0, false);//call  eax
	//返回 
	LONG lCodeLen= shellcodeAppend(bCode, 0xC20400, 0, 0, false);//ret 04
	
	LPVOID lpADD = VirtualAlloc(NULL, lCodeLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (lpADD)
	{
	//	memLoadself(); //内存加载自身,论坛有源码,自己去弄一个,
	//	内存注入那通过DLLMain传入lpADD,DllMain(code,0,lpADD),然后再执行释放内存lpADD,哈哈,完美!
		RtlCopyMemory(lpADD, bCode, lCodeLen);
		CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)lpADD, 0, 0, NULL);
	}
}


因为DLL不能自己Free自己,所以申请一块独立的内存,使用线程来卸载自己

测试的时候,发现hookLdrLoadDll(false),竟然不能删除VEH,导致线程不能卸载模块

然后把删除VEH也添加到了线程中处理


写着写着shellcode,发现好烦啊,都是用od写好汇编代码,复制数据出来

再RtlCopyMemory修改shellcode参数

因为这个特别烦,就是撸了几把LOL,

回来就写了个shellcode组装的函数 shellcodeAppend()

看着用起来挺方便,拿出来个大家瞧瞧

/**
*  功能描述:拼接 shelllcode汇编指令
*   bCode shellcode指针
*   lHead 汇编指令头
*   lData 汇编指令数据
*   dLen  数据长度
*   bReset 重新设置指令指针
*
*  返回 shellcode 长度
*/
LONG shellcodeAppend(PBYTE bCode, LONG lHead, LONG lData, LONG dLen, BOOL bReset)
{
	static LONG lcurp = 0;//当前指针
	LONG i = 0;
	LONG hLen = 4;//最多4位
	if (bReset)//重置
		lcurp = 0;
	for (i = 1; i < 4; i++) //计算汇编头位数
	{
		if ((lHead >> (i * 8)) == 0)
		{
			hLen = i;
			break;
		}
	}
	for (i = 0; i <hLen; i++)
	{
		bCode[lcurp + i] = ((PBYTE)(&lHead))[hLen - i - 1];
	}
	lcurp += hLen;
	//	RtlCopyMemory(bCode + lcurp, &lData, dLen);
	for (i = 0; i <dLen; i++)
	{
		bCode[lcurp + i] = ((PBYTE)(&lData))[i];
	}
	lcurp += dLen;
	return lcurp;
}

 

哈哈,一下子心情又好了

我把一句汇编代码分为两部分,比如:

7700A65C    8B4C24 10       mov     ecx,dword ptr ss:[esp+0x10]

7700A660    68 58750700     push    0x77558

 mov   ecx,dword ptr ss:[esp+0x10] 我把数据 8B4C24 看做汇编指令头,数据 10 是汇编指令数据

 push    0x77558 我把数据 68 看做汇编指令头,数据 0x00077558 是汇编指令数据

调用的时候就写成:

// 最后一个参数=TRUE 初始化 bCode 当前指针为 0 

shellcodeAppend(bCode, 0x8B4C24, 0x10, 4, TRUE);

// 最后一个参数=TRUE 初始化 bCode 当前指针为 0 

shellcodeAppend(bCode, 0x68, 0x77558 , 4, TRUE);

一般头部最长不过4个字节,所以shellcodeAppend把头部写成 整数型 

数据段一般最长也不过4个字节,所以也定义成4字节,

少见的超过4字节的就是 

dword ptr ds:[0xXXXXXXX],0xXXXXXXXX

这种就不能直接用 shellcodeAppend 一次完成了,要分两次

不过,既然shellcode是我们自己编写的,干嘛非要用这种,哈哈,脑残


最后 DLLMain() 中调用就可以了 :

#include "stdafx.h"
#include <winternl.h>
void hookLdrLoadDll(bool bHook);
HMODULE m_Module = NULL;
PVOID pVEH1 = NULL;
PBYTE pLdrLoadDll = NULL;
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		if ((DWORD)lpReserved== 0)//lpReserved 内存注入的时候,我们给个参数传进来的是卸载模块线程地址
		{
			m_Module = hModule;
			hookLdrLoadDll(true);
			break;
		}
		else
		{
			//这里是内存注入
			//	VirtualFree(lpReserved, 0, MEM_RELEASE);
			//然后启动线程啊之类的
			//RtlZeroMemory(hModule, 0x1000);//清空PE头啊
			//CreateThread();
			break;
		}
		
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}


一直没工作做,有公司招人吗.像俺这种没文凭,没各种证的人,会的东西挺多,不敢说精通.

.

.






上传的附件:
本主题帖已收到 1 次赞赏,累计¥1.00
最新回复 (27)
飞舞霓裳 2017-3-13 16:15
2
String拥有一个特殊点叫:String对象的内容不可改变! 在调用诸如String对象的replace()等方法时,不是在原Sting对象的基础上改变对象内容,而是创建了一个新的String对象把调用的方法后返回的结果放在这个新的String对象中 代码示例如下: String str3 = "LOVO JAVA"; str3.replaceAll("JAVA", "J2EE"); 打印输出:LOVO JAVA System.out.println(str3; String str3 = "LOVO JAVA"; String str = str3.replaceAll("JAVA", "J2EE"); 打印输出:LOVO J2EE System.out.println(str); 开发工具不全的朋友,可jiaqun163201006
Handjay 2017-3-13 17:17
3
可以使用在注入的dll中开启一个线程使用FreeLibraryAndExitThread函数, 释放dll. 直接退出线程, 释放dll 并且不返回!
lolslol 2017-3-13 18:24
4
能不能把源码附件发上来呀
lononan 2017-3-13 19:20
5
Handjay 可以使用在注入的dll中开启一个线程使用FreeLibraryAndExitThread函数, 释放dll. 直接退出线程, 释放dll 并且不返回!
这个真的可以吗,线程没退出,DLL模块能Free掉?
1
lhglhg 2017-3-15 08:20
6
能不能把源码附件发上来呀
1
fengyunabc 2017-3-15 20:25
7
先马后看!
Callback 2017-3-15 21:09
8
只需要独立的内存就可以free自己
1
次元有名 2017-3-15 22:08
9
Mark,感谢分享
9
kvllz 2017-3-15 23:04
10
可以挂内存查询,不解释
御剑残风 2017-3-15 23:44
11
赞个,mark
Sam.com 2017-3-16 02:37
12
学习一下~~谢谢
hackoflife 2017-3-16 09:41
13
mark,收藏
lononan 2017-3-16 13:00
14
kvllz 可以挂内存查询,不解释[em_19]
他扫描内存块是有条件限制的,最简单的就是PE头,去掉PE头大部分的就很安全了,易语言除外
清明雨 2017-3-16 14:31
15
内存加载自身代码能不能给个,找不到。
水木之 2017-3-16 21:57
16
挺巧妙~~~
9
tishion 2017-3-17 16:58
17

哦, 楼主还要Hook pLdrLoadDll

我这有个更巧妙的方法,直接替换moudle list就行了,抽空我写篇文章


https://gist.github.com/tishion/96272231c54a42862569

/**
 ** tishion#163.com
 ** 2016-01-28 18:52:34
 **/
PLDR_DATA_TABLE_ENTRY GetModuleEntryInLoadOrderModuleList(HMODULE hMod)
{
 PPEB pPeb = NULL;
 // Get the base address of PEB struct
 __asm
 {
  push eax
  mov eax, fs:[0x30]
  mov pPeb, eax
  pop eax
 }
 // Get pointer value of PEB_LDR_DATA
 PPEB_LDR_DATA pLdr = pPeb->Ldr;
 // And get header of the InLoadOrderModuleList
 PLIST_ENTRY pHeaderOfModuleList = &(pLdr->InLoadOrderModuleList);
 if (pHeaderOfModuleList->Flink == pHeaderOfModuleList)
 {
  // Something was wrong
  return NULL;
 }
 PLDR_DATA_TABLE_ENTRY pEntry = NULL;
 PLIST_ENTRY pCur = pHeaderOfModuleList->Flink;
 // Find Entry of the fake module
 do
 {
  pEntry = CONTAINING_RECORD(pCur, LDR_DATA_TABLE_ENTRY, InLoadOrderModuleList);
  // Ok, got it
  if (pEntry->BaseAddress == hMod)
  {
   break;
  }
  pEntry = NULL;
  pCur = pCur->Flink;
 } while (pCur != pHeaderOfModuleList);
 return pEntry;
}
/**
 ** Replace the return value of LoadLibrary
 **
 **/
BOOL LoadFogModule(HMODULE hModule, LPCTSTR pOrigiModPath)
{
 // First, we must get the Entry of the fake module
 PLDR_DATA_TABLE_ENTRY pEntryOfFakeMod = GetModuleEntryInLoadOrderModuleList(hModule);
 
 // Then, load the original module
 HMODULE hOrigiMod = ::LoadLibrary(pOrigiModPath);
 if (NULL != pEntryOfFakeMod && NULL != hOrigiMod)
 {
  // Now we need to find the Entry of the original module
  PLDR_DATA_TABLE_ENTRY pEntryOfOrigiMod = GetModuleEntryInLoadOrderModuleList(hOrigiMod);
  /*
   * Tish is the key statement which will replace the return value of LoadLibrary.
   * At the end of LoadLibrary, it will return the base address which get from the entry of fake module.
   * And then the process will import all the functions and variables it needs according to the base address.
   * Because we have replaced the address with the base address of real original module,
   * so it will work well, that is to say the process can get all valid imported functions and variables
   * from the original module.
   */
  pEntryOfFakeMod->BaseAddress = pEntryOfOrigiMod->BaseAddress;
  // Then we must remove the fake module entry from all the module list, or it will lead the process crash
  // remove it from InLoadOrderModuleList
  pEntryOfFakeMod->InLoadOrderModuleList.Blink->Flink = pEntryOfFakeMod->InLoadOrderModuleList.Flink;
  pEntryOfFakeMod->InLoadOrderModuleList.Flink->Blink = pEntryOfFakeMod->InLoadOrderModuleList.Blink;
  // remove it from InInitializationOrderModuleList
  pEntryOfFakeMod->InInitializationOrderModuleList.Blink->Flink = pEntryOfFakeMod->InInitializationOrderModuleList.Flink;
  pEntryOfFakeMod->InInitializationOrderModuleList.Flink->Blink = pEntryOfFakeMod->InInitializationOrderModuleList.Blink;
  // remove it from InMemoryOrderModuleList
  pEntryOfFakeMod->InMemoryOrderModuleList.Blink->Flink = pEntryOfFakeMod->InMemoryOrderModuleList.Flink;
  pEntryOfFakeMod->InMemoryOrderModuleList.Flink->Blink = pEntryOfFakeMod->InMemoryOrderModuleList.Blink;
  return TRUE;
 }
 return FALSE;


靴子 2017-3-17 20:06
18

ls的地址打不开!

9
tishion 2017-3-18 09:10
19
靴子 ls的地址打不开!
可能国内被墙了。。
lononan 2017-3-18 14:47
20
tishion 哦, 楼主还要Hook pLdrLoadDll我这有个更巧妙的方法,直接替换moudle list就行了,抽空我写篇文章https://gist.github.com/tishi ...
大神到处有啊
lononan 2017-3-18 14:55
21
清明雨 内存加载自身代码能不能给个,找不到。
http://m.pediy.com/showthread.php?t=203910
StriveXjun 2017-3-20 10:09
22
赞个 好思路
henghengheng 2017-3-21 10:39
23
学习下看不懂哈
carry丶 2017-3-30 09:38
24
楼主 看你帖子貌似你也在搞dxf 请教一下关于过木马加载有什么办法吗
amily 2017-5-29 10:14
25
我用lsp劫持没出现三方啊?
lononan 2017-5-30 19:39
26
Handjay 可以使用在注入的dll中开启一个线程使用FreeLibraryAndExitThread函数, 释放dll. 直接退出线程, 释放dll 并且不返回!
这个还真可以了啊,孤陋寡闻了,确实很方便!
wujincai 2017-6-2 08:51
27
mark
HelloCrack 2017-6-12 08:30
28
上面的代码。是不是要先unhook再loadlibrary,而且要加绝对路径。
不然又反复加载同一个模块了。
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 知道创宇带宽支持 | 微信公众号:ikanxue
Time: 0.018, SQL: 12 / 京ICP备10040895号-17