【外挂编程】外挂编程技术揭秘(三)DLL注入

引言:

一个礼拜没有更新这个主题文章了,这周三看到一个微信推送。某某某博士由于写外挂被抓。当时心里那是一个崩溃,不过之前写了这个主题,还是会继续写完,不能半途而废。不过了解这本技术的千万不要拿去捞偏门。我刚写这篇文章的时候在想这个主题要不要改一改。叫《我们一起写外挂吧,会被抓那种》,^ - ^,玩笑了。开始我们今天的内容吧,我们还是继续讲内存挂,不过我们今天的主题是讲注入。

文中介绍的注入形式都会有一个小例子。

代码地址:

https://github.com/Diamonds-ZhaoYu/hz_example

 

开篇

所谓的dll注入,其实就是在dll注入到另外一个程序地址空间。然后该程序会把这个dll作为动态链接库调用,视为程序的一部分。

dll注入有很多种形式可以注入:apc注入、message注入、注册列表注入、远程线程注入、输入法注入、LSP劫持注入。

 

(一)apc注入:

在一个进程执行过程中、调用SleepEx或者WaitForSingleObjectEx函数时、系统会产生软中断。这时候线程在次被唤醒时、此线程会首先执行apc队列中被注册的函数、利用QueueUserAPC这个函数、并且去执行我们的dll加载代码 ,进而完成我们注入dll的目的。 (二)message注入: message注入其实就是windows消息钩子钩子注入、SetWindowsHook函数是windows消息处理机制的一个平台、应用程序可以指定监视指定窗口的某种消息。它可以拦截进程的消息到指定的dll中导出函数、这个特性我们就可以将dll注入到程序中。

 

(三)注册列表注入:

在进程中每一个进程加载user32.dll时、会受到DLL_PROCESS_ATTACH通知。在进行处理时、会取到注册列表键值HKEY_LOCAL_MACHINE\Software\Microsoft\windowsNT\CurrentVresion\Windows\AppInit_Dlls,并且会调用LoadLibrary去加载dll、若将参数值fdwReason设置为DLL_PROCESS_ATTACH时对自己会初始化。这样我们去加键值就会加载我们的dll、实现注入。

(四)远程线程注入 远程线程注入是window病毒用的广泛的一种、远程线程注入其实比较简单其实就是利用CreateRemoteThread函数创建远程线程调用LoadLibrary去加载我们我们的dll,实现注入。远程线程注入在之前的QQ尾巴病毒就是利用这个,还有多,如果有兴趣我们可以写一个类似的小程序。

 

(五)输入法注入:

输入法注入原理是利用系统切换输入法时、需要输入字符。系统会把这个输入法所需要的ime文件加载到进程中、而且利用ime文件只存放在C:盘window目录中的system32目录中的特殊dll、因此我们可以利用这个特性调用LoadLibrary加载自己的dll文件、实现注入。

 

(六)LSP劫持注入

其实这个是利用windows程序加载自己目录的dll特性,伪造系统的dll,然后在我们的dll内去调用系统原来的函数。进而实现劫持注入到程序,这样我们就可以为所欲为。

 

源码解析:

注入程序:

注入程序主要目的就是远程注入DLL到被注入进程中,被注入进程在这里可以是我们的游戏。

 

注入DLL:

注入DLL中,可以开发一个窗口资源。被注入到游戏中后,这个窗口资源可以通过比如说home键直接唤出窗口,该窗口被系统认定为被注入程序的子程序。

 

被注入程序:

被注入程序这里,其实就是我们的游戏,或者如果是小病毒,则是我们病毒的宿主。

 

启动窗口

 

 

唤出窗口

 

启动窗口主要代码:

注入程序触发段:

 

    case WM_COMMAND:
    if( !g_bSubclassed) {
        InjectDll( hStart );
        if( g_bSubclassed ) 
            ::SetDlgItemText( hDlg, IDC_BUTTON, "卸载" );
    }
    else {
            UnmapDll();
            if( !g_bSubclassed ) 
            ::SetDlgItemText( hDlg, IDC_BUTTON, "插入" );
    }
    break;

注入程序:

int InjectDll( HWND hWnd )
{	
	g_hWnd = hWnd;

	// Hook "*.exe"
	g_hHook = SetWindowsHookEx( WH_CALLWNDPROC,(HOOKPROC)HookProc,
								hDll, GetWindowThreadProcessId(hWnd,NULL) );
	if( g_hHook==NULL )
		return 0;
	
	// By the time SendMessage returns, 
	// the START button has already been subclassed
	SendMessage( hWnd,WM_HOOKEX,0,1 );

	return g_bSubclassed;
}

 

DLL唤出窗口主要代码:

读取金币方法:

/**
* 读取金币方法
*/
void ReadMoney(HWND hwnd)
{
			
	HWND wgHwnd =  ::FindWindow(NULL,TEXT("植物大战僵尸中文版"));

	if (wgHwnd ==NULL)
	{			
		return ;
	}
	DWORD procid;
	::GetWindowThreadProcessId(wgHwnd,&procid);//获得进程ID


	HANDLE prochandle=::OpenProcess(PROCESS_ALL_ACCESS,false,procid);//打开进程
					
	DWORD sunny; 
	
	DWORD baseAddr = SUNNY_NUMBER_ADDR;
	DWORD stackAddr;
	DWORD dataAdrr;

	阳光基址读取读基地址
	ReadProcessMemory (prochandle,(void *)baseAddr,&stackAddr,4,0);      	

	//阳光基址 + 一级偏移 = 一级基址
	stackAddr = stackAddr + 0x768 ;

	//读二级偏移基地址
	ReadProcessMemory(prochandle,(void *)stackAddr,&dataAdrr,4,0);		

	//二级偏移 + 二级偏移 = 阳光地址
	dataAdrr = dataAdrr + 0x5560 ;

	//读取阳光数据
	ReadProcessMemory(prochandle,(void *)dataAdrr,&sunny,4,0);	

	TCHAR szText1[MAX_PATH] = {0};
	sprintf(szText1,TEXT("%d"),sunny);
	SetDlgItemText(hwnd,IDC_EDIT1,szText1);
				
	::CloseHandle(prochandle);
}

修改金币方法:

/**
* 修改阳光
*/
void ChangeSunny(HWND hwnd)
{
	HWND wgHwnd =  ::FindWindow(NULL,TEXT("植物大战僵尸中文版"));
	if (wgHwnd ==NULL)
	{			
		return ;
	}

	DWORD procid;
	::GetWindowThreadProcessId(wgHwnd,&procid);//获得进程ID
	
	HANDLE prochandle = ::OpenProcess(PROCESS_ALL_ACCESS,false,procid);//打开进程
	
	DWORD sunny; 
	
	DWORD baseAddr = SUNNY_NUMBER_ADDR;
	DWORD stackAddr;
	DWORD dataAdrr;

	阳光基址读取读基地址
	ReadProcessMemory (prochandle,(void *)baseAddr,&stackAddr,4,0);      	

	//阳光基址 + 一级偏移 = 一级基址
	stackAddr = stackAddr + 0x768 ;


	//读二级偏移基地址
	ReadProcessMemory(prochandle,(void *)stackAddr,&dataAdrr,4,0);		


	//二级偏移 + 二级偏移 = 阳光地址
	dataAdrr = dataAdrr + 0x5560 ;

	//获取文本框输入
	TCHAR szText1[MAX_PATH] = {0};
	GetDlgItemText(hwnd,IDC_EDIT1,szText1,MAX_PATH);
	sunny = (DWORD)atoi(szText1);
	
	//写入数据
	WriteProcessMemory(prochandle, (void *)dataAdrr, &sunny, 4, 0);	

	CloseHandle(prochandle);
}

其余部分大家可以看github上源代码,有完成源代码。

 

 

结尾:

 

虽然我们学会了如何注入程序到dll,但是离真正的高级编程还远。注入dll其实是一种反查杀策略。就好比把dll看成我们是一伙的不排斥你。

后面章节我们会讲外挂的反游戏保护驱动。谢谢~

 

 

远程注入DLL方法有很多种,也是很多木马病毒所使用的隐藏进程的方法,因为通过程序加载的DLL在进程管理器是没有显示的.这里介绍一种用 CreateRemoteThread 远程建立线程的方式注入DLL. 首先,我们要提升自己的权限,因为远程注入必不可免的要访问到目标进程的内存空间,如果没有足够的系统权限,将无法作任何事.下面是这个函数是用来提升我们想要的权限用的. function EnableDebugPriv: Boolean; var hToken: THandle; tp: TTokenPrivileges; rl: Cardinal; begin Result := false; //打开进程令牌环 OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken); //获得进程本地唯一ID if LookupPrivilegeValue(nil, 'SeDebugPrivilege', tp.Privileges[0].Luid) then begin tp.PrivilegeCount := 1; tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; //调整权限 Result := AdjustTokenPrivileges(hToken, false, tp, SizeOf(tp), nil, rl); end; end; 关于 OpenProcessToken() 和 AdjustTokenPrivileges() 两个 API 的简单介绍: OpenProcessToken():获得进程访问令牌的句柄. function OpenProcessToken( ProcessHandle: THandle; //要修改访问权限的进程句柄 DesiredAccess: DWORD; //指定你要进行的操作类型 var TokenHandle: THandle//返回的访问令牌指针 ): BOOL; AdjustTokenPrivileges() :调整进程的权限. function AdjustTokenPrivileges( TokenHandle: THandle; // 访问令牌的句柄 DisableAllPrivileges: BOOL; // 决定是进行权限修改还是除能(Disable)所有权限 const NewState: TTokenPrivileges; { 指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组, 数据组的每个项指明了权限的类型和要进行的操作; } BufferLength: DWORD; //结构PreviousState的长度,如果PreviousState为空,该参数应为 0 var PreviousState: TTokenPrivileges; // 指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息 var ReturnLength: DWORD //实际PreviousState结构返回的大小 ) : BOOL; 远程注入DLL其实是通过 CreateRemoteThread 建立一个远程线程调用 LoadLibrary 函数来加载我们指定的DLL,可是如何能让远程线程知道我要加载DLL呢,要知道在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都是相互独立的。所我们需要在远程进程的内存空间里申请一块内存空间,写入我们的需要注入DLL 的路径. 需要用到的 API 函数有: OpenProcess():打开目标进程,得到目标进程的操作权限,详细参看MSDN function OpenProcess( dwDesiredAccess: DWORD; // 希望获得的访问权限 bInheritHandle: BOOL; // 指明是否希望所获得的句柄可以继承 dwProcessId: DWORD // 要访问的进程ID ): THandle; VirtualAllocEx():用于在目标进程内存空间中申请内存空间以写入DLL的文件名 function VirtualAllocEx( hProcess: THandle; // 申请内存所在的进程句柄 lpAddress: Pointer; // 保留页面的内存地址;一般用nil自动分配 dwSize, // 欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍 flAllocationType: DWORD; flProtect: DWORD ): Pointer; WriteProcessMemory():往申请到的空间中写入DLL的文件名 function WriteProcessMemory( hProcess: THandle; //要写入内存数据的目标进程句柄 const lpBaseAddress: Pointer; //要写入的目标进程的内存指针, 需以 VirtualAllocEx() 来申请 lpBuffer: Pointer; //要写入的数据 nSize: DWORD; //写入数据的大小 var lpNumberOfBytesWritten: DWORD //实际写入的大小 ): BOOL; 然后就可以调用 CreateRemoteThread 建立远程线程调用 LoadLibrary 函数来加载我们指定的DLL. CreateRemoteThread() //在一个远程进程中建立线程 function CreateRemoteThread( hProcess: THandle; //远程进程的句柄 lpThreadAttributes: Pointer; //线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针 dwStackSize: DWORD; //线程栈大小,以字节表示 lpStartAddress: TFNThreadStartRoutine; // 一个TFNThreadStartRoutine类型的指针,指向在远程进程中执行的函数地址 lpParameter: Pointer; //传入参数的指针 dwCreationFlags: DWORD; //创建线程的其它标志 var lpThreadId: DWORD //线程身份标志,如果为0, 则不返回 ): THandle; 整个远程注入DLL的具体实现代码如下: function InjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: Pointer; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin lpThreadId := 0; // 计算LoadLibraryW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'LoadLibraryW'); // 启动远程线程LoadLbraryW,通过远程线程调用创建新的线程 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 如果执行成功返回 True; if (hRemoteThread 0) then Result := true; // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end; 接下来要说的是如何卸载注入目标进程中的DLL,其实原理和注入DLL是完全相同的,只是远程调用调用的函数不同而已,这里要调用的是FreeLibrary,代码如下: function UnInjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; // 进程注入和取消注入其实都差不多,只是运行的函数不同而已 var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: PChar; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId, dwHandle: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin // 计算GetModuleHandleW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'GetModuleHandleW'); //使目标进程调用GetModuleHandleW,获得DLL在目标进程中的句柄 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 等待GetModuleHandle运行完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 获得GetModuleHandle的返回值,存在dwHandle变量中 GetExitCodeThread(hRemoteThread, dwHandle); // 计算FreeLibrary的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'FreeLibrary'); // 使目标进程调用FreeLibrary,卸载DLL hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, Pointer(dwHandle), 0, lpThreadId); // 等待FreeLibrary卸载完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 如果执行成功返回 True; if hRemoteProcess 0 then Result := true; // 释放目标进程中申请的空间 VirtualFreeEx(hRemoteProcess, pszLibFileRemote, Length(DllFullPath) + 1, MEM_DECOMMIT); // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end;
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值