您的位置:首页 > 其它

更新了已经过测试-Windows下如何改写目标进程的窗口函数来注入DLL

2011-02-21 20:37 555 查看
Windows的UI线程简单地说就是创建了窗口的线程,

其创建的窗口都有窗口函数,在这里,我介绍一个改写

UI线程窗口过程来注入DLL的方法,需要调用VirtualAllocEx,

但不需要GetThreadContext和SetThreadContext

也不调用CreateRemoteThread.

对付一些常用的杀毒软件应该没有问题,下面是具体代码,包含

说明

一:主控程序

注入Dll.cpp

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
#define INJECTDLL_NAME "c://dlltest.dll"

//0x00cd0000,不用这个地址了,0x00f00000地址我测过,对于Mspaint.exe(画图程序),

//WordPad.exe(写字板程序)和我们无辜的Notepad.exe(记事本),都好用。至于

//其它的进程,请读者们多试试吧
#define REMOTE_BASEADDR 0x00f00000
typedef HMODULE (__stdcall *pLoadLibrary)(LPCSTR lpLibFileName);
typedef HANDLE (__stdcall *pCreateThread)(
LPSECURITY_ATTRIBUTES lpThreadAttributes,/
DWORD dwStackSize,/
LPTHREAD_START_ROUTINE lpStartAddress,/
LPVOID lpParameter,/
DWORD dwCreationFlags,/
LPDWORD lpThreadId
);
typedef HANDLE (__stdcall *lpOpenThread)( DWORD dwDesiredAccess, /
BOOL bInheritHandle, /
DWORD dwThreadId);

//要覆盖目标窗口函数的函数

long __stdcall myProc(HWND ,UINT ,WPARAM ,LPARAM )
{
//获取CreateThread 函数地址,绝对地址,在每个进程中都一样

//关于如何获取CreateThread 函数和LoadLibrary的绝对地址

//的办法其实很简单:

// DWORD p1 = (DWORD)::GetProcAddress(::GetModuleHandle("Kernel32.dll"),

// "CreateThread")

// DWORD p2 = (DWORD)::GetProcAddress(::GetModuleHandle("Kernel32.dll"),

// "LoadLibraryA")

//将 p1和p2的值打印出来直接使用就行。

pCreateThread func = pCreateThread(2088830679);

//当该函数在目标进程被当做窗口函数调用时,下面一行代码将

//在目标进程内创建线程,也就是在本进程内创建线程,可不是

//CreateRemoteThread 创建远程线程哦,这一点请看家体会。

//将LoadLibrary的绝对地址(2088770939)

//作为线程入口函数地址
//REMOTE_BASEADDR(0x00f00000)地址就是指定的分配虚拟内存的地址,不能用变量
//因为用了变量就产生寻址操作了,在目标进程中就读取不到正确的
//值了。REMOTE_BASEADDR(0x00f00000)地址处主控程序写入"c://dlltest.dll"-->具体在下面讲述
func(0,0,LPTHREAD_START_ROUTINE(2088770939),LPVOID(REMOTE_BASEADDR),0,0);
return 0;
}

//控制函数

//参数必须是 UNICODE 字符串 const unsigned short 等价于 const wchar_t

void Inject_WndProc(const unsigned short *pszWndClassName)
{
BYTE *pStart;
BOOL bOk;
DWORD dErr;
HANDLE hfileMap;
LPVOID pAddr;
HANDLE hThread = 0;
LPVOID pRemote = 0;
DWORD dWriten = 0;
//之前定义长度是1024,测试时发现除了Notepad.exe可以成功之外
//MsPaint.exe和WordPad.exe都不成功,原因就是 1024长度太大

//导致除了覆盖了目标的窗口程序外,把当前目标线程的执行代码也覆盖

//掉了,导致唤醒目标线程后目标线程立即崩溃,改成110字节就好多了,
//因为我们注入覆盖的源函数体没多少字节,一个类型转换(3,4字节)和一个
//函数调用(4次参数进栈,一次跳转和清理堆栈,这几部大概是2*4+5
// + n, n肯定小于90字节),总的算来110长度足够了。
//对于一些进程的窗口函数如果里面仅仅是调用另外一个函数的话那就需要
//继续调小这个数值了,一般的窗口过程几乎都包含switch和if等分支语句
//这样就足够了
BYTE sCodes[110];
memset(sCodes,0,sizeof(sCodes));

//通过窗口类名来调用 FindWindowW,避免目标窗口标题经常变化导致
//FindWindowW(NULL,"window title") 失效
HWND hRemote = ::FindWindowW(pszWndClassName,NULL);

//获取目标进程窗口类的注册窗口函数地址,不能用GetWindowLongW
//因为GetWindowLongW不能跨进程
DWORD lpWndProc = (DWORD)::GetClassLongW(hRemote,GCL_WNDPROC);

//获取目标窗口所属进程和创建者线程
DWORD id,old;
DWORD threadId = ::GetWindowThreadProcessId(hRemote,&id);
LPVOID pMemRemote = 0;
lpOpenThread pOpenThread = 0;
HANDLE hProcess = ::OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ |
PROCESS_VM_WRITE,
FALSE,id);
if(!hProcess)
{
MessageBox(0,"OpenProcess failed!",0,0);
return;
}

//从REMOTE_BASEADDR(0x00f00000)地址处分配虚拟内存,此绝对地址可以通过
//::VirtualAllocEx 调用获取,
//但为了不在注入的函数代码中产生寻址操作,因此使用绝对地址。
//Notepad.exe,MSPaint.exe,WordPad.exe可以使用这个绝对地址,
//别的进程需要同过 ::VirtualAllocEx 来获取。
//后面提供获取这个绝对地址的函数
pMemRemote = ::VirtualAllocEx(hProcess,(LPVOID) REMOTE_BASEADDR,
8192,MEM_RESERVE|MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
if(pMemRemote != (LPVOID)REMOTE_BASEADDR)
{
if(pMemRemote)
{
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
}
MessageBox(0,"VirtualAllocEx failed!",0,0);
goto DOEXIT;
}

//将"C://dlltest.dll"写入
bOk = ::WriteProcessMemory(hProcess,pMemRemote,INJECTDLL_NAME,
strlen(INJECTDLL_NAME),&dWriten);
if(!bOk)
{
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
MessageBox(0,"WriteProcessMemory failed!",0,0);
goto DOEXIT;
}

//获取OpenThread函数地址
pOpenThread = (lpOpenThread)::GetProcAddress(::GetModuleHandle("Kernel32.dll"),
"OpenThread");
if(!pOpenThread)
{
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
MessageBox(0,"GetProcAddress failed!",0,0);
goto DOEXIT;
}
hThread = pOpenThread(THREAD_SUSPEND_RESUME|SYNCHRONIZE,FALSE,threadId);

//挂起目标线程
::SuspendThread(hThread);

//创建共享内存
hfileMap = ::CreateFileMappingW(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,
4096,L"注入dlltest范例");
if(!hfileMap)
{
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
::ResumeThread(hThread);
MessageBox(0,"CreateFileMapping failed!",0,0);
goto DOEXIT;
}
//映射共享内存地址
pAddr = ::MapViewOfFile(hfileMap,FILE_MAP_ALL_ACCESS,0,0,4096);
if(!pAddr)
{
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
::CloseHandle(hfileMap);
::ResumeThread(hThread);
MessageBox(0,"MapViewOfFile failed!",0,0);
goto DOEXIT;
}

//将目标窗口函数地址所在内存页面更改为 PAGE_EXECUTE_READWRITE
bOk = ::VirtualProtectEx(hProcess,(LPVOID)lpWndProc,8192,PAGE_EXECUTE_READWRITE,&old);
if(!bOk)
{
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
::CloseHandle(hfileMap);
::ResumeThread(hThread);
MessageBox(0,"VirtualProtectEx failed!",0,0);
}

//将目标窗口函数地址的110字节读入sCodes,保证dll中DllMain恢复之用
bOk = ::ReadProcessMemory(hProcess,(LPVOID)lpWndProc,sCodes,110,&dWriten);
if(!bOk)
{
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
::CloseHandle(hfileMap);
::ResumeThread(hThread);
MessageBox(0,"ReadProcessMemory failed!",0,0);
goto DOEXIT;
}

pStart = (BYTE*)pAddr;
//将目标窗口句柄写入共享内存中
::CopyMemory(pStart,&hRemote,sizeof(HWND));
pStart += sizeof(HWND);
//将目标窗口函数110字节内容写入共享内存中
::CopyMemory(pStart,sCodes,110);
pStart += 110;
//仅为调试时验证之用
::CopyMemory(pStart,"#$#",3);

//将目标窗口过程函数覆盖
bOk = ::WriteProcessMemory(hProcess,(LPVOID)lpWndProc,(LPVOID)myProc,
110,&dWriten);
if(!bOk)
{
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
::CloseHandle(hfileMap);
::ResumeThread(hThread);
MessageBox(0,"GetProcAddress failed!",0,0);
goto DOEXIT;
}

//唤醒目标线程
::ResumeThread(hThread);

//以下是向目标线程消息队列发送WM_PAINT消息,从而
//调用被覆盖的窗口函数。
::PostMessage(hRemote,WM_PAINT,0,0);

//等待目标线程结束,只要不退出,睡眠也行
//本想在主控程序和dll中用事件内核对象做个同步
//当DllMain函数结束后通知主控程序,但考虑同步的问题
//不属于注入dll的范畴,就懒得写了,其实很容易实现。
::WaitForSingleObject(hThread,-1);
::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
DOEXIT:
::CloseHandle(hThread);
::CloseHandle(hProcess);
}

//程序入口 WinMain
int APIENTRY WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
//Notepad.exe 记事本 进程的主窗口类名是 "NOTEPAD",
//WordPad.exe 写字板 进程的主窗口类名是 "WordPadClass"

//MsPaint.exe 画图 进程的主窗口类名是 "MsPaintApp"

//注意:必须是主窗口,不能是子窗口,就是说像 BUTTON,EDIT,STATIC
//等子窗口都不行,主窗口类名通过Spy++获取
Inject_WndProc(L"NOTEPAD");
return 0;
}

//重点说明一下:主控程序不能在Debug模式下运行,因为

//Debug模式下 myProc函数结束处包含了堆栈的验证代码

//覆盖目标窗口函数后运行时会导致堆栈的验证代码无法通过

//产生异常,因此只能在Release下运行。

二:dll程序

dlltest.cpp

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
//g_WndPRoc保存窗口函数地址
WNDPROC g_WndPRoc = 0;
BOOL g_IsRestore = FALSE;
//hook目标窗口函数的函数
LRESULT CALLBACK newWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//拦截一下 WM_SYSCOMMAND 弹个消息框
if(g_WndPRoc)
{
/*
//unhook 目标窗口函数,记住不要破坏,要和谐,这里仅仅是测试一下,所以注释掉了
//能直观的体现出效果,否则应该去掉注释,还原目标窗口函数
if(g_IsRestore)
{
::SetWindowLongW(hWnd,GWL_WNDPROC,(long)g_WndPRoc);
return 1;
}
*/
LRESULT lr = ::CallWindowProcW(g_WndPRoc,hWnd,uMsg,wParam,lParam);
if(WM_SYSCOMMAND == uMsg)
{
MessageBox(0,"newWndProc in dlltest.dll",0,0);
}
return lr;
}
return 1;
}

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
if(DLL_PROCESS_DETACH == ul_reason_for_call)
{
MessageBox(0,"Exit",0,0);
}
if(DLL_PROCESS_ATTACH == ul_reason_for_call)
{
__try
{
DWORD len;
char sBuf[128];
memset(sBuf,0,sizeof(sBuf));
SYSTEMTIME sm;
::GetLocalTime(&sm);
_snprintf(sBuf,127,"dlltest.dll load log %04d-%02d-%02d %02d:%02d:%02d/r/n",
sm.wYear,sm.wMonth,sm.wDay,sm.wHour,sm.wMinute,sm.wSecond);
HANDLE hFile = ::CreateFile("C://testdll.log",GENERIC_WRITE,
FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
::SetFilePointer(hFile,0,0,FILE_END);
::WriteFile(hFile,sBuf,strlen(sBuf),&len,0);
::CloseHandle(hFile);
//以上是记录日志,这里不能调用UI函数,例如MessageBox等,因为会导致
//窗口函数重入,崩溃。

//以下打开共享内存,恢复窗口函数
HANDLE hfileMap = ::OpenFileMappingW(FILE_MAP_READ,FALSE,L"注入dlltest范例");
LPVOID pAddr = ::MapViewOfFile(hfileMap,FILE_MAP_READ,0,0,4096);
HWND hMain;
BYTE *pStart = (BYTE*)pAddr;
BYTE sCodes[110];
//从共享内存中读取目标窗口句柄
::CopyMemory(&hMain,pStart,sizeof(HWND));
pStart += sizeof(HWND);
//从共享内存中读取目标窗口函数110字节内容
::CopyMemory(sCodes,pStart,110);
pStart += 110;

//hook窗口过程,使得杀毒软件进程如果卸载本dll的话
//则窗口过程就失效,导致进程崩溃!(慎用之,因为
//经测试发现写字板程序的一菜单些功能失效了,原因就是dll的
//内存模式与exe的内存模式不一样,也就是说在dll函数中
//C的malloc、C++的new经常会失败,原因也是如此,因此

//还是不hook窗口过程好,记住不要搞破坏,要和谐 呵呵)
//
//
//先hook窗口过程好,因为SetWindowLongW函数保证
//了目标窗口函数调用结束后如果消息队列不空,则立即执行
//新的窗口函数。因为在目标窗口函数未恢复之前,目标窗口
//执行的是我们覆盖的函数,因此如果不先重定向窗口函数
//就CopyMemory的话,则有可能破坏目标线程的执行代码
//造成崩溃。

DWORD wndProc = (DWORD)::GetClassLongW(hMain,GCL_WNDPROC);
g_WndPRoc = (WNDPROC)wndProc;
g_IsRestore = FALSE;
//注释掉,不hook DWORD dOld = ::SetWindowLongW(hMain,GWL_WNDPROC,(long)newWndProc);

//恢复窗口函数的110字节
::CopyMemory((LPVOID)wndProc,sCodes,110);
g_IsRestore = TRUE;

}
__except(1)
{

}

}
return TRUE;
}

三:提供一个工具函数获取目标进程可以分配虚拟内存的地址的函数

//使用下面函数得到的绝对地址值,将这个值用在上面主控程序中

LPVOID GetProcessAddr(DWORD dProcessID)
{
HANDLE hProcess = ::OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ |
PROCESS_VM_WRITE,
FALSE,dProcessID);
if(!hProcess)
{
MessageBox(0,"OpenProcess failed!",0,0);
return NULL;
}

LPVOID pMemRemote = ::VirtualAllocEx(hProcess,NULL,
8192,MEM_RESERVE|MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
if(!pMemRemote)
{
::CloseHandle(hProcess);
MessageBox(0,"VirtualAllocEx failed!",0,0);
return NULL;
}

::VirtualFreeEx(hProcess,pMemRemote,0,MEM_RELEASE);
::CloseHandle(hProcess);
return pMemRemote;
}

以上代码都经过本人测试,确认运行无误。

后记:从开始有想法开始到今天完成了这个注入dll的比较“新”
(应该是有不少人早就实现了,只不过没拿出来共享而已)
的方法用了3天时间,其实技术上没多少难度,关键是要对
windows操作系统的窗口消息、进程、线程、动态链接库,
内存管理,共享内存等等都熟练,记住,不能仅仅是了解,
必须深入理解到熟练API编程等。知识的积累很重要,对于
一些初入门的开发人员来说做到深入理解了操作系统就等于
迈进了一大步,希望这篇文章(都是代码,不正规的文章啊...汗!)
能给予IT行业内追求技术发展的同行们一点点帮助。

这个方法目前的缺点很明显,就是用的API和内存的绝对地址值,
没有用到经典的shellcode的计算偏移量的技术,因为我的
汇编语言语法和编写格式都忘了,CPU寄存器也已经不会
用了,因此就没有用汇编去写一些计算偏移量的代码(看看吧
只是不用就都忘了,想用的时候也不容易了),希望读者也能给
与我这方面的帮助,在此不胜感谢!

我的EMail: tanglg@neusoft.com (公司邮箱,建议使用,因为目前在职)
tanglg00@sina.com (私人邮箱,不建议使用,
因为公司屏蔽了私人邮箱,
或许某天辞职了就会用到了)

附调试dlltest.dll的方法:
使用VC6(VC2005/VC2010没试过)创建win32 动态链接库
将 dlltest.cpp中的代码以Build 模式 Debug 编译链接,设置链接
输出为:c:/dlltest.dll
设置调试程序为 C:/windows/system32/notepad.exe。
最后F5启动调试,在dllMain里添加断点

直接运行编译链接(Release)好的主控程序(VC6->win32 Application)
如果注入成功,就会进入dll调试断点。很简单就这么玩儿。

注意xp下一定要用拥有超级用户权限的用户才能注入成功。

在此重申:不可以用本人提供的方法从事
计算机软件破坏违法的行为,
这里仅仅是技术共享。如有违
法的事情发生,与本人无关
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: