DLL的11种注入方法

更新时间:2023-09-19 03:14:01 阅读量: 小学教育 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

闲着没事整理了一下DLL的N种注入方法,对学习外挂的朋友,应该有用!

第一种方法:

利用 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;

第二种方法:

采用CreateProcess的方法,实现起来比较复杂,但没有上面几种方法的局限性。且可以用其他工

具(VC等)调试注入的DLL。下面进行介绍。

原理如下:

1. 用CreateProcess(CREATE_SUSPENDED)启动目标进程。

2. 找到目标进程的入口,用ImageHlp中的函数可以实现。

3. 将目标进程入口的代码保存起来。

4. 在目标进程的入口写入LoadLibrary(MyDll)实现Dll的注入。

5. 用ResumeThread运行目标进程。

6. 目标进程就运行了LoadLibrary(MyDll),实现DLL的注入。

7. 目标进程运行完LoadLibrary(MyDll)后,将原来的代码写回目标进程的入口。

8. 目标进程Jmp至原来的入口,继续运行程序。

从原理上可以看出,DLL的注入在目标进程的开始就运行了,而且不是用Debug的方案,这样,就

没有上面方案的局限性了。该方案的关键在6,7,8三步,实现方法需要监视进程和DLL合作。下

面,结合代码进行分析。

在监视进程中,创建FileMapping,用来保存目标进程的入口代码,同时保证DLL中可以访问。在

第7步实现将原目标代码写回目标进程的入口。

// 监视程序和DLL共用的结构体

#pragma pack (push ,1) // 保证下面的结构体采用BYTE对齐(必须)

typedef struct {

BYTE int_PUSHAD; // pushad 0x60

BYTE int_PUSH; // push &szDLL 0x68

DWORD push_Value; // &szDLL = \的path

BYTE int_MOVEAX; // move eax &LoadLibrary 0xB8

DWORD eax_Value; // &LoadLibrary

WORD call_eax; // call eax 0xD0FF(FF D0) (LoadLibrary

(\

BYTE jmp_MOVEAX; // move eax &ReplaceOldCode 0xB8

DWORD jmp_Value; // JMP的参数

WORD jmp_eax; // jmp eax 0xE0FF(FF E0) jmp ReplaceOldCode;

char szDLL[MAX_PATH]; // \的FullPath

}INJECT_LOADLIBRARY_CODE, *LPINJECT_CODE;

#pragma pack (pop , 1)

上面结构体的代码为汇编代码,对应的汇编为:

pushad

push szDll

mov eax, &LoadLibraryA

call eax // 实现调用LoadLibrary(szDll)的代码

mov eax, oldentry

jmp eax // 实现在LoadLibrary运行完后, 跳至目标进程的入口继续运行

// FileMaping的结构体

typedef struct {

LPBYTE lpEntryPoint; // 目标进程的入口地址

BYTE oldcode[sizeof(INJECT_CODE)]; // 目标进程的代码保存

}SPY_MEM_SHARE, * LPSPY_MEM_SHARE;

准备工作:

第一步:用CreateProcess(CREATE_SUSPENDED)启动目标进程。

CreateProcessA(0, szRunFile, 0, 0, FALSE, CREATE_SUSPENDED

0, NULL, &stInfo,

&m_proInfo) ;

// 用CreateProcess启动一个暂停的目标进程

// 找到目标进程的入口点,函数如下

第二步:找到目标进程的入口,用ImageHlp中的函数可以实现。

pEntryPoint = GetExeEntryPoint(szRunFile);

LPBYTE GetExeEntryPoint(char *filename) {

PIMAGE_NT_HEADERS pNTHeader;

DWORD pEntryPoint;

PLOADED_IMAGE pImage;

pImage = ImageLoad(filename, NULL);

if(pImage == NULL)

return NULL;

pNTHeader = pImage->FileHeader;

pEntryPoint = pNTHeader->OptionalHeader.AddressOfEntryPoint + pNTHeader-

>OptionalHeader.ImageBase;

ImageUnload(pImage);

return (LPBYTE)pEntryPoint; }

// 创建FileMapping

hMap = CreateFileMapping((HANDLE)0xFFFFFFFF, NULL,

PAGE_READWRITE, 0, sizeof(SPY_MEM_SHARE), “MyDllMapView”);

// 保存目标进程的代码

第三步:将目标进程入口的代码保存起来。

LPSPY_MEM_SHARE lpMap = pMapViewOfFile(hMap,

FILE_MAP_ALL_ACCESS,

0, 0, 0);

ReadProcessMemory(m_proInfo.hProcess, pEntryPoint,

&lpMap->oldcode, sizeof(INJECT_CODE),

&cBytesMoved);

lpMap->lpEntryPoint = pEntryPoint;

// 第四步:在目标进程的入口写入LoadLibrary(MyDll)实现Dll的注入。

// 准备注入DLL的代码

INJECT_CODE newCode;

// 写入MyDll―――用全路径

lstrcpy(newCode.szDLL, szMyDll);

// 准备硬代码(汇编代码)

newCode.int_PUSHAD = 0x60;

newCode.int_PUSH = 0x68;

newCode.int_MOVEAX = 0xB8;

newCode.call_eax = 0xD0FF;

newCode.jmp_MOVEAX = 0xB8;

newCode.jmp_eax = 0xE0FF;

newCode.eax_Value = (DWORD)&LoadLibrary;

newCode.push_Value=(pEntryPoint + offsetof(INJECT_CODE,szDLL));

// 将硬代码写入目标进程的入口

// 修改内存属性

DWORD dwNewFlg, dwOldFlg;

dwNewFlg = PAGE_READWRITE;

VirtualProtectEx(m_proInfo.hProcess, (LPVOID)pEntryPoint, sizeof(DWORD), dwNewFlg,

&dwOldFlg);

WriteProcessMemory(m_proInfo.hProcess, pEntryPoint,

&newCode, sizeof(newCode),

NULL);//&dwWrited);

VirtualProtectEx(proInfo.hProcess, (LPVOID)pEntryPoint, sizeof(DWORD),

dwOldFlg, &dwNewFlg);

// 释放FileMaping 注意,不是Closehandle(hMap)

UnmapViewOfFile(lpMap);

// 继续目标进程的运行

第五步:用ResumeThread运行目标进程。

ResumeThread(m_proInfo.hThread);

在监视进程中就结束了自己的任务,剩下的第6,7,8步就需要在Dll的DllMain中进行配合。

DLL中用来保存数据的结构体

typedef struct {

DWORD lpEntryPoint; DWORD OldAddr; DWORD OldCode[4]; }JMP_CODE,* LPJMP_CODE;

static JMP_CODE _lpCode;

// 在DllMain的DLL_PROCESS_ATTACH中调用InitApiSpy函数

// 在该函数中实现第6,7,8步

第六步:目标进程就运行了LoadLibrary(MyDll),实现DLL的注入。

int WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved) {

switch(dwReason)

{

case DLL_PROCESS_ATTACH:

return InitApiSpy(); ……

// InitApiSpy函数的实现

BOOL WINAPI InitApiSpy() {

HANDLE hMap;

LPSPY_MEM_SHARE lpMem;

DWORD dwSize;

BOOL rc;

BYTE* lpByte;

// 取得FileMapping的句柄

hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, “MyDllMapView”);

if(hMap)

{

lpMem = (LPSPY_MEM_SHARE)MapViewOfFile(hMap,

FILE_MAP_ALL_ACCESS,

0, 0, 0);

if(lpMem)

{

第七步:目标进程运行完LoadLibrary(MyDll)后,将原来的代码写回目标进程的入口。

// 恢复目标进程的入口代码

// 得到mov eax, value代码的地址

_lpCode.OldAddr = (DWORD)((BYTE*)lpMem->lpEntryPoint + offsetof

(INJECT_CODE, jmp_MOVEAX));

_lpCode.lpEntryPoint = (DWORD)lpMem->lpEntryPoint;

// 保存LoadLibrary()后面的代码

memcpy(&_lpCode.OldCode, (BYTE*)lpMem->oldcode + offsetof

(INJECT_CODE, jmp_MOVEAX), 2*sizeof(DWORD));

// 恢复目标进程的入口代码

rc = WriteProcessMemory(GetCurrentProcess(), lpMem-

>lpEntryPoint, lpMem->oldcode, sizeof(INJECT_CODE), &dwSize);

lpByte = (BYTE*)lpMem->lpEntryPoint + offsetof(INJECT_CODE,

jmp_MOVEAX);

UnmapViewOfFile(lpMem);

}

CloseHandle(hMap);

}

// 实现自己Dll的其他功能,如导入表的替换

// ……

// 将LoadLibrary后面的代码写为转入处理程序中

// 指令为:mov eax, objAddress

// jmp eax

{

BYTE* lpMovEax;

DWORD* lpMovEaxValu;

WORD* lpJmp;

DWORD fNew, fOld;

fNew = PAGE_READWRITE;

lpMovEax = lpByte;

VirtualProtect(lpMovEax, 2*sizeof(DWORD), fNew, &fOld);

*lpMovEax = 0xB8;

lpMovEaxValu = (DWORD*)(lpMovEax + 1);

*lpMovEaxValu = (DWORD)&DoJmpEntryPoint;

lpJmp = (WORD*)(lpMovEax + 5);

*lpJmp = 0xE0FF; // (FF E0)

VirtualProtect(lpMovEax, 2*sizeof(DWORD), fOld, &fNew);

}

return TRUE; }

// 转入处理程序

DWORD* lpMovEax;

DWORD fNew, fOld;

void __declspec(naked) DoJmpEntryPoint () {

// 恢复LoadLibrary后面的代码

_gfNew = PAGE_READWRITE;

_glpMovEax = (DWORD*)_lpCode.OldAddr;

VirtualProtect(_glpMovEax, 2*sizeof(DWORD), _gfNew, &_gfOld);

*_glpMovEax = _lpCode.OldCode[0];

*(_glpMovEax + 1) = _lpCode.OldCode[1];

VirtualProtect(_glpMovEax, 2*sizeof(DWORD), _gfOld, &_gfNew);

第八步:目标进程Jmp至原来的入口,继续运行程序。

// 跳至目标代码的入口

_asm popad

_asm jmp _lpCode.lpEntryPoint }

这样就实现了原来的目标,将DLL的注入放在目标进程的入口运行,实现了目标进程运行之前运行

我们的注入Dll的功能。

第三种方法:

利用注册表注入

在Windows NT/2000/XP/2003中,有一个注册表键值

HKEY_LOCAL_MACHINE\\Software\\Microsoft\\WindowsHKEY_LOCAL_MACHINE\\Software\\Microsoft\\Wi

ndows NT\\CurrentVersion\\Windows\\AppInit_DLLs。当某个进程加载User32.dll时,这里面列出

的所有的DLL都将User32.dll利用LoadLibrary函数加载到该进程空间中。我们可以把自己的代码

放在一个DLL中,并加入该键值,这样就可以注入到所有使用User32.dll的进程中了。 当DLL以LoadLibrary的方式加载时,DllMain会被以DLL_PROCESS_ATTACH为原因调用,实际上

我们也只需要关心DLL_PROCESS_ATTACH BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) {

if (ul_reason_for_call == DLL_PROCESS_ATTACH) {

HANDLE f = CreateFile(L\ NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); CloseHandle(f); }

return TRUE;

}

第四种方法:

利用Windows Hooks注入

Windows系统给我们提供了一些挂钩函数,使得被挂钩的进程可以在自己处理接收到的消息之

前,先执行我们的消息处理函数,而这个消息处理函数一般会放在DLL中,来让目标进程加载,这

实际上已经达到了注入代码的效果。

一般情况下,我们把挂钩函数和消息处理函数都放在dll中: HHOOK g_hHook = NULL; HINSTANCE hInst;

BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) {

hInst = (HINSTANCE)hModule; return TRUE; }

LRESULT myKeyBrdFuncAd (int code, WPARAM wParam, LPARAM lParam) {

// To be nice, your rootkit should call the next-lower // hook, but you never know what this hook may be.

//::MessageBoxA(NULL, \ return CallNextHookEx(g_hHook, code, wParam, lParam); }

#ifdef __cplusplus // If used by C++ code,

extern \ // we need to export the C interface #endif

__declspec(dllexport) bool SetHook(DWORD dwThreadId) {

g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)myKeyBrdFuncAd, hInst, dwThreadId);

if (g_hHook == NULL) {

return false; }

return true; }

#ifdef __cplusplus }

#endif

注入进程要加载这个DLL,然后执行里面的SetHook函数,传入的参数为被注入线程的ID

typedef bool (*MYPROC) (DWORD dwThreadId); ......

HANDLE hLib = LoadLibraryA

(\if (hLib == NULL) {

printf(\}

MYPROC myProc = (MYPROC)GetProcAddress((HMODULE)hLib,\if (myProc != NULL) {

if ((*myProc)((DWORD)4860) == false) {

printf(\ } } else {

printf(\}

第五种方法:

利用特洛伊DLL进行注入

这种方法的原理就是由自己写一个与原有进程调用的DLL具有相同接口函数的DLL,再用我们

的DLL替换原有的DLL。在替换的过程中,由我们自己编写感兴趣的函数替换原有函数,而对其它

不感兴趣的函数,则以函数转发的形式调用原有DLL中的函数。这里面有个前提,就是你在编写

EnterCriticalSection(&cs);

_asm {

lea edi,g_OldNtQuerySystemInformation mov esi,g_pfNtQuerySystemInformation cld

mov ecx,5 rep movsb

lea edi,g_OldNtResumeThread mov esi,g_pfNtResumeThread cld

mov ecx,5 rep movsb }

g_NewNtQuerySystemInformation[0] = 0xe9; g_NewNtResumeThread[0] = 0xe9; _asm {

lea eax, NewNtQuerySystemInformation mov ebx, g_pfNtQuerySystemInformation sub eax, ebx sub eax, 5

mov dword ptr [g_NewNtQuerySystemInformation + 1], eax lea eax, NewNtResumeThread mov ebx, g_pfNtResumeThread sub eax, ebx sub eax, 5

mov dword ptr [g_NewNtResumeThread + 1], eax } .......

LeaveCriticalSection(&cs);

g_bHook = TRUE; }

// 还原被修改的代码 void WINAPI HookOff() {

......

g_bHook = FALSE; }

第十一种方法:

利用ring0 APC注入dll

/*注:拿APC启动进程的代码改下...本代码用VC6.0+ XP DDK编译通过...在XP SP2运行成功.....

里面用到了一些硬编码..比如直接用ETHREAD的偏移值... __asm

{ //我的机器的loadlibrary的地址一提

mov eax,0x7C801d77 -----这个地址你自己改下 或者自己分析kernel32.dll EAT

注意假使你不用硬编码....g_addr是loadlibrary的地址...但不能直接 mov eax,g_aadr

因为这时候我们的mov eax,g_aadr

已经运行在用户态...而g_aadr是一个内核变量...用户程序是访问不了内核空间的...所以应该自

己计算机mov eax,g_aadr这条指令的偏移...在驱动里copy下..替换下就OK */

//基本思路就是在目标进程的上下文中让其调用loadlibrary加载我们的dll //copy much code

////////////////////////////////////////////////////////////////////////////////// 完整代码以重新编辑....也改了下...防止干坏事

#include

#define MAX_PID 65535

/////////////////////////////////////////////////////////////////////////////////////

//////////////////// NTSTATUS

InstallUserModeApc(LPSTR DllFullPath, ULONG pTargetThread, ULONG pTargetProcess); void ApcCreateProcessEnd();

void ApcCreateProcess(PVOID NormalContext, PVOID SystemArgument1, PVOID

SystemArgument2);

typedef enum {

OriginalApcEnvironment, AttachedApcEnvironment, CurrentApcEnvironment } KAPC_ENVIRONMENT;

typedef struct _KAPC_STATE {

LIST_ENTRY ApcListHead[MaximumMode]; struct _KPROCESS *Process; BOOLEAN KernelApcInProgress; BOOLEAN KernelApcPending; BOOLEAN UserApcPending;

} KAPC_STATE, *PKAPC_STATE, *PRKAPC_STATE;

VOID

KeInitializeApc ( PKAPC Apc,

PETHREAD Thread,

KAPC_ENVIRONMENT Environment, PKKERNEL_ROUTINE KernelRoutine,

PKRUNDOWN_ROUTINE RundownRoutine, PKNORMAL_ROUTINE NormalRoutine, KPROCESSOR_MODE ProcessorMode, PVOID NormalContext );

BOOLEAN

KeInsertQueueApc ( PKAPC Apc,

PVOID SystemArgument1, PVOID SystemArgument2, KPRIORITY Increment );

NTSTATUS

PsLookupProces**yProcessId( __in HANDLE ProcessId,

__deref_out PEPROCESS *Process );

UCHAR *

PsGetProcessImageFileName( __in PEPROCESS Process

); VOID

KeStackAttachProcess ( IN PVOID Process,

OUT PRKAPC_STATE ApcState ); VOID

KeUnstackDetachProcess(

IN PRKAPC_STATE ApcState );

/////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////

//===================================================================================

====//

// InjectDll遍历进程,得到根据名字找到我们想要注入的进程,得到进程后遍历线程.找受信??线

程序//

//注意这里ETHREAD我是用XP..在其他操作系统可能一些偏移不同 //

//===================================================================================

===//

void InjectDll(LPSTR DllFullPath,LPSTR ProcessName) {

//全部定义为ULONG类型 ULONG pTargetProcess; ULONG pTargetThread; ULONG pNotAlertableThread; ULONG pSystemProcess; ULONG pTempThread;

ULONG pNextEntry, pListHead, pThNextEntry,pThListHead; ULONG pid;

PEPROCESS EProcess; NTSTATUS status;

for(pid=0; pid

status = PsLookupProces**yProcessId((HANDLE)pid,&EProcess); if((NT_SUCCESS(status))) {

if(_stricmp(PsGetProcessImageFileName(EProcess),ProcessName)==0) {

pSystemProcess=(ULONG)EProcess; pTargetProcess =pSystemProcess;

pTargetThread = pNotAlertableThread = 0; pThListHead = pSystemProcess+0x50;

pThNextEntry=*(ULONG *)pThListHead;

while(pThNextEntry != pThListHead) {

pTempThread =pThNextEntry-0x1b0; //ETHREAD if(*(char *)(pTempThread+0x164)) //受信?? {

pTargetThread =pTempThread; break; } else {

pNotAlertableThread =pTempThread; }

pThNextEntry = *(ULONG *)pThNextEntry; }

break; } } }

if(!pTargetProcess) return;

if(!pTargetThread)

pTargetThread = pNotAlertableThread;

if(pTargetThread) {

DLL时你必须知道原有DLL中的函数都有哪些,以免导至其它进程调用DLL时找不到相应的API函数

,特别是在替换系统DLL文件时更要小心。

第六种方法:

无DLL注入

在第三中方法中,我们启动远程线程时,线程函数是我们从Kernel32.dll中取得的

LoadLibrary函数的地址为线程函数的地址,其实我们可以直接将线程函数体和函数参数写入目标

进程的地址空间,然后创建远程线程。

使用这个方法时,需要注意以下几个问题:

(1) 远程线程函数体不得使用kernel32.dll,user32.dll以外的函数。因为这个两个模块在

各个进程的相对地址是一样的,如果一定要使用其它函数,则必须将函数体写入目标进程空间。

(2) 不能使用任何字符串常量,因为字符串常量是存放在PE文件里.data这个段里面的,函数

里保存的只是相对地址。

(3) 去掉编译器的/GZ编译选项,这个选项是用来Enable Stack Frame Run-Time Error

Checking。当这个选项打开时,编译器会在每个函数中加入一些代码,用来检验ESP在函数体中是

否被改变,但是这些检验函数的地址在不同PE文件中有可能是不一样的。 (4) 不得使用增量链接(incremental linking)。增量链接是编译器为了减少链接时间做的

处理,把函数体用一个JMP指令代替,这样就可以随意改变函数的内容,而不用修改CALL指令。

(5) 不要在函数体内使用超过4kb的局部变量。局部变量是存放在栈中的,例如下面这个函数

void Dummy(void) { BYTE var[256]; var[0] = 0; var[1] = 1; var[255] = 255;

}

在分配局部变量空间时是这样的 :00401000 push ebp

:00401001 mov ebp, esp

:00401003 sub esp, 00000100 ; change ESP as storage for ; local variables is needed :00401006 mov byte ptr [esp], 00 ; var[0] = 0; :0040100A mov byte ptr [esp+01], 01 ; var[1] = 1;

:0040100F mov byte ptr [esp+FF], FF ; var[255] = 255; :00401017 mov esp, ebp ; restore stack pointer :00401019 pop ebp :0040101A ret

但是当局部变量的大小超过4kb时,栈指针并不直接改版,而是调用另一个函数来分配内存,这个

函数有可能在不同进程中的地址不一样。

(6) 函数体内switch语句中的case不要超过3个,否则编译器会在PE文件中使用跳转表,而这

个跳转表有可能在目标进程中并不存在。 下面是一个无DLL注入的例子: //参数结构 ;

typedef struct _RemotePara{ PVOID dwMessageBox; wchar_t strMessageBox[12]; }RemotePara; // 远程线程执行体

DWORD __stdcall ThreadProc(RemotePara *Para) {

typedef int (/*__stdcall*/ *PMessageBox) ( HWND , LPCTSTR , LPCTSTR , UINT ); PMessageBox MessageBoxFunc = (PMessageBox)Para->dwMessageBox;

MessageBoxFunc(NULL, Para->strMessageBox, Para->strMessageBox, MB_OK); return 0 ; }

DWORD THREADSIZE=1024; DWORD pID = 4688; DWORD byte_write;

HANDLE hRemoteProcess,hThread;

RemotePara myRemotePara,*pRemotePara; void *pRemoteThread; HINSTANCE hUser32 ;

hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pID); if(!hRemoteProcess)return 0;

// 在远程进程地址空间分配虚拟内存

pRemoteThread = VirtualAllocEx(hRemoteProcess, 0, THREADSIZE, MEM_COMMIT |

MEM_RESERVE,PAGE_EXECUTE_READWRITE); if(!pRemoteThread)return 0;

// 将线程执行体ThreadProc写入远程进程

if(!WriteProcessMemory(hRemoteProcess, pRemoteThread, &ThreadProc, THREADSIZE,0))

return 0;

ZeroMemory(&myRemotePara,sizeof(RemotePara)); hUser32 = LoadLibrary(L\

myRemotePara.dwMessageBox = (PVOID)GetProcAddress(hUser32, \ wcscat(myRemotePara.strMessageBox,L\复制MessageBox函数的参数 //写进目标进程

pRemotePara =(RemotePara *)VirtualAllocEx (hRemoteProcess ,0,sizeof

(RemotePara),MEM_COMMIT,PAGE_READWRITE); if(!pRemotePara)return 0;

if(!WriteProcessMemory (hRemoteProcess ,pRemotePara,&myRemotePara,sizeof

myRemotePara,0))return 0; // 启动线程

hThread = CreateRemoteThread(hRemoteProcess ,0,0,(LPTHREAD_START_ROUTINE)

pRemoteThread ,pRemotePara,0,&byte_write); FreeLibrary(hUser32);

CloseHandle(hRemoteProcess);

第七种方法:

利用DLL劫持内存补丁技术注入

当一个可执行文件运行时,Windows加载器将可执行模块映射到进程的地址空间中,加载器分

析可执行模块的输入表,并设法找出任何需要的DLL,并将它们映射到进程的地址空间中。

由于输入表中只包含DLL名而没有它的路径名,因此加载程序必须在磁盘上搜索DLL文件。首先会

尝试从当前程序所在的目录加载DLL,如果没找到,则在Windows系统目录查找,最后是在环境变

量中列出的各个目录下查找。利用这个特点,先伪造一个系统同名的DLL,提供同样的输出表,每

个输出函数转向真正的系统DLL。程序调用系统DLL时会先调用当前目录下伪造的DLL,完成相关功

能后,再跳到系统DLL同名函数里执行。这个过程用个形象的词来描述就是系统DLL被劫持

(hijack)了。

示例DELPHI源码: Library USP10; uses

Windows, SysUtils, Classes;

{$R *.res}

ID:THandle;

ModHandle: Cardinal; POldLpkPresent: Pointer;

POldScriptApplyDigitSubstitution: Pointer; POldScriptApplyLogicalWidth: Pointer; POldScriptBreak: Pointer; POldScriptCPtoX: Pointer;

POldScriptCacheGetHeight: Pointer; POldScriptFreeCache: Pointer; POldScriptGetCMap: Pointer;

POldScriptGetFontProperties: Pointer; POldScriptGetGlyphABCWidth: Pointer; POldScriptGetLogicalWidths: Pointer; POldScriptGetProperties: Pointer; POldScriptIsComplex: Pointer; POldScriptItemize: Pointer; POldScriptJustify: Pointer; POldScriptLayout: Pointer; POldScriptPlace: Pointer;

POldScriptRecordDigitSubstitution: Pointer; POldScriptShape: Pointer;

POldScriptStringAnalyse: Pointer; POldScriptStringCPtoX: Pointer; POldScriptStringFree: Pointer;

POldScriptStringGetLogicalWidths: Pointer; POldScriptStringGetOrder: Pointer; POldScriptStringOut: Pointer; POldScriptStringValidate: Pointer; POldScriptStringXtoCP: Pointer; POldScriptString_pLogAttr: Pointer; POldScriptString_pSize: Pointer;

POldScriptString_pcOutChars: Pointer; POldScriptTextOut: Pointer; POldScriptXtoCP: Pointer; POldUspAllocCache: Pointer; POldUspAllocTemp: Pointer; POldUspFreeMem: Pointer;

procedure LpkPresent; asm jmp POldLpkPresent end;

procedure ScriptApplyDigitSubstitution; asm jmp POldScriptApplyDigitSubstitution end; procedure ScriptApplyLogicalWidth; asm jmp POldScriptApplyLogicalWidth end; procedure ScriptBreak; asm jmp POldScriptBreak end; procedure ScriptCPtoX; asm jmp POldScriptCPtoX end;

procedure ScriptCacheGetHeight; asm jmp POldScriptCacheGetHeight end; procedure ScriptFreeCache; asm jmp POldScriptFreeCache end; procedure ScriptGetCMap; asm jmp POldScriptGetCMap end;

procedure ScriptGetFontProperties; asm jmp POldScriptGetFontProperties end;

procedure ScriptGetGlyphABCWidth; asm jmp POldScriptGetGlyphABCWidth end; procedure ScriptGetLogicalWidths; asm jmp POldScriptGetLogicalWidths end; procedure ScriptGetProperties; asm jmp POldScriptGetProperties end; procedure ScriptIsComplex; asm jmp POldScriptIsComplex end; procedure ScriptItemize; asm jmp POldScriptItemize end; procedure ScriptJustify; asm jmp POldScriptJustify end; procedure ScriptLayout; asm jmp POldScriptLayout end; procedure ScriptPlace; asm jmp POldScriptPlace end;

procedure ScriptRecordDigitSubstitution; asm jmp POldScriptRecordDigitSubstitution end;

procedure ScriptShape; asm jmp POldScriptShape end;

procedure ScriptStringAnalyse; asm jmp POldScriptStringAnalyse end; procedure ScriptStringCPtoX; asm jmp POldScriptStringCPtoX end; procedure ScriptStringFree; asm jmp POldScriptStringFree end;

procedure ScriptStringGetLogicalWidths; asm jmp POldScriptStringGetLogicalWidths end; procedure ScriptStringGetOrder; asm jmp POldScriptStringGetOrder end; procedure ScriptStringOut; asm jmp POldScriptStringOut end;

procedure ScriptStringValidate; asm jmp POldScriptStringValidate end; procedure ScriptStringXtoCP; asm jmp POldScriptStringXtoCP end;

procedure ScriptString_pLogAttr; asm jmp POldScriptString_pLogAttr end;

procedure ScriptString_pSize; asm jmp POldScriptString_pSize end;

procedure ScriptString_pcOutChars; asm jmp POldScriptString_pcOutChars end; procedure ScriptTextOut; asm jmp POldScriptTextOut end; procedure ScriptXtoCP; asm jmp POldScriptXtoCP end;

procedure UspAllocCache; asm jmp POldUspAllocCache end; procedure UspAllocTemp; asm jmp POldUspAllocTemp end; procedure UspFreeMem; asm jmp POldUspFreeMem end;

exports LpkPresent,

ScriptApplyDigitSubstitution, ScriptApplyLogicalWidth, ScriptBreak, ScriptCPtoX,

ScriptCacheGetHeight, ScriptFreeCache, ScriptGetCMap,

ScriptGetFontProperties, ScriptGetGlyphABCWidth, ScriptGetLogicalWidths, ScriptGetProperties, ScriptIsComplex, ScriptItemize, ScriptJustify, ScriptLayout, ScriptPlace,

ScriptRecordDigitSubstitution, ScriptShape,

ScriptStringAnalyse, ScriptStringCPtoX, ScriptStringFree,

ScriptStringGetLogicalWidths, ScriptStringGetOrder, ScriptStringOut, ScriptStringValidate, ScriptStringXtoCP, ScriptString_pLogAttr, ScriptString_pSize,

ScriptString_pcOutChars, ScriptTextOut, ScriptXtoCP, UspAllocCache,

UspAllocTemp, UspFreeMem;

begin

ModHandle:= LoadLibrary('C:\\WINDOWS\\system32\%usp10.dll'); if ModHandle > 0 then begin

POldLpkPresent:= GetProcAddress(ModHandle, 'LpkPresent'); POldScriptApplyDigitSubstitution:= GetProcAddress(ModHandle,

'ScriptApplyDigitSubstitution');

POldScriptApplyLogicalWidth:= GetProcAddress(ModHandle,

'ScriptApplyLogicalWidth');

POldScriptBreak:= GetProcAddress(ModHandle, 'ScriptBreak'); POldScriptCPtoX:= GetProcAddress(ModHandle, 'ScriptCPtoX');

POldScriptCacheGetHeight:= GetProcAddress(ModHandle, 'ScriptCacheGetHeight'); POldScriptFreeCache:= GetProcAddress(ModHandle, 'ScriptFreeCache'); POldScriptGetCMap:= GetProcAddress(ModHandle, 'ScriptGetCMap'); POldScriptGetFontProperties:= GetProcAddress(ModHandle,

'ScriptGetFontProperties');

POldScriptGetGlyphABCWidth:= GetProcAddress(ModHandle, 'ScriptGetGlyphABCWidth'); POldScriptGetLogicalWidths:= GetProcAddress(ModHandle, 'ScriptGetLogicalWidths'); POldScriptGetProperties:= GetProcAddress(ModHandle, 'ScriptGetProperties'); POldScriptIsComplex:= GetProcAddress(ModHandle, 'ScriptIsComplex'); POldScriptItemize:= GetProcAddress(ModHandle, 'ScriptItemize'); POldScriptJustify:= GetProcAddress(ModHandle, 'ScriptJustify'); POldScriptLayout:= GetProcAddress(ModHandle, 'ScriptLayout'); POldScriptPlace:= GetProcAddress(ModHandle, 'ScriptPlace');

POldScriptRecordDigitSubstitution:= GetProcAddress(ModHandle,

'ScriptRecordDigitSubstitution');

POldScriptShape:= GetProcAddress(ModHandle, 'ScriptShape');

POldScriptStringAnalyse:= GetProcAddress(ModHandle, 'ScriptStringAnalyse'); POldScriptStringCPtoX:= GetProcAddress(ModHandle, 'ScriptStringCPtoX'); POldScriptStringFree:= GetProcAddress(ModHandle, 'ScriptStringFree'); POldScriptStringGetLogicalWidths:= GetProcAddress(ModHandle,

'ScriptStringGetLogicalWidths');

POldScriptStringGetOrder:= GetProcAddress(ModHandle, 'ScriptStringGetOrder'); POldScriptStringOut:= GetProcAddress(ModHandle, 'ScriptStringOut');

POldScriptStringValidate:= GetProcAddress(ModHandle, 'ScriptStringValidate'); POldScriptStringXtoCP:= GetProcAddress(ModHandle, 'ScriptStringXtoCP');

POldScriptString_pLogAttr:= GetProcAddress(ModHandle, 'ScriptString_pLogAttr'); POldScriptString_pSize:= GetProcAddress(ModHandle, 'ScriptString_pSize'); POldScriptString_pcOutChars:= GetProcAddress(ModHandle,

'ScriptString_pcOutChars');

POldScriptTextOut:= GetProcAddress(ModHandle, 'ScriptTextOut'); POldScriptXtoCP:= GetProcAddress(ModHandle, 'ScriptXtoCP');

POldUspAllocCache:= GetProcAddress(ModHandle, 'UspAllocCache'); POldUspAllocTemp:= GetProcAddress(ModHandle, 'UspAllocTemp'); POldUspFreeMem:= GetProcAddress(ModHandle, 'UspFreeMem'); end; begin

//添加自己的补丁内容! end; end.

第八种方法:

也是现在比较流行的注入方式

利用未公开函数InitializeLpkHooks,这个函数在网上能找到的资料更少,只有一个声明而已 。但是它名称中最后那个“Hooks”误导了我,我以为又是一个可以用来注入DLL的不错函数,用

OD反出来一看,原来只是个局部HOOK而已。虽然没太大用,还是一并写上吧,也许谁用得着呢。

InitializeLpkHooks顾名思义就是HOOK LPK的,Windows有个lpk.dll,就是支持多语言包的那么

个功能。测试发现好多程序在TextOut之前似乎是要调用lpk.dll里面的相关函数的,可能是支持

多语言的程序就需要用这个来判断到底要显示那种语言吧。而InitializeLpkHooks,就是用来

HOOK lpk.dll里面的4个函数的,这4个函数是LpkTabbedTextOut,LpkPSMTextOut,

LpkDrawTextEx,LpkEditControl。我们先打开VB,在窗体中加入以下代码吧: Private Sub Form_Load()

DLLhwnd = LoadLibrary(\ '加载DLL

DLLFunDre = GetProcAddress(DLLhwnd, \ '获取回调函数地址

LpkHooksInfo.lpHookProc_LpkTabbedTextOut = 0 LpkHooksInfo.lpHookProc_LpkPSMTextOut = 0

LpkHooksInfo.lpHookProc_LpkDrawTextEx = GetLocalProcAdress(AddressOf HookProc1) '设

置要HOOK的LPK函数

LpkHooksInfo.lpHookProc_LpkEditControl = 0 InitializeLpkHooks LpkHooksInfo End Sub

Private Sub Form_Unload(Cancel As Integer)

LpkHooksInfo.lpHookProc_LpkTabbedTextOut = 0 LpkHooksInfo.lpHookProc_LpkPSMTextOut = 0

LpkHooksInfo.lpHookProc_LpkDrawTextEx = DLLFunDre LpkHooksInfo.lpHookProc_LpkEditControl = 0 InitializeLpkHooks LpkHooksInfo FreeLibrary DLLhwnd End Sub

然后新建一个模块,在模块中加入以下代码:

Public Declare Function LoadLibrary Lib \

lpLibFileName As String) As Long

Public Declare Function GetProcAddress Lib \

lpProcName As String) As Long

Public Declare Function FreeLibrary Lib \' ----------------未公开函数--------------------------------------

Public Declare Sub InitializeLpkHooks Lib \

Type LpkHooksSetting

lpHookProc_LpkTabbedTextOut As Long lpHookProc_LpkPSMTextOut As Long lpHookProc_LpkDrawTextEx As Long lpHookProc_LpkEditControl As Long End Type

' -------------------------------

Public DLLhwnd As Long, DLLFunDre As Long Public LpkHooksInfo As LpkHooksSetting

Public Function GetLocalProcAdress(ByVal lpProc As Long) As Long GetLocalProcAdress = lpProc End Function

Function HookProc1(ByVal a1 As Long, ByVal a2 As Long, ByVal a3 As Long, ByVal a4 As

Long, ByVal a5 As Long, ByVal a6 As Long, ByVal a7 As Long, ByVal a8 As Long, ByVal

a9 As Long, ByVal a10 As Long) As Long HookProc1 = 0 End Function

运行一下看看,是不是窗体中标题栏和按钮上的文字都没有了,因为我们把函数LpkDrawTextEx替

换成自己的函数HookProc1了。这个函数有10个参数,其中几个好像是字符串指针,似乎可以用来

截获窗体要显示的文字,然后改成另一种语言的文字,我猜想,也许就是这个用途吧。哈哈,纯

属猜测。以上就是函数InitializeLpkHooks的用法了。

第九种方法:

利用输入法注入

注入DLL是做全局钩子或者拦截类软件都有可能用到的技术,如果做外挂的话我们也有可能需

要注入一个DLL到游戏进程中去干点什么“坏事”。 但我们知道现在要注入DLL是越来越难了。场

景1:制作火星文输入法外挂,原理是利用API HOOK拦截并修改输入法相关函数,需要注入一个

DLL到所有进程中,但是后来发现,在开启了瑞星的帐号保险箱后,用户将不能在QQ中输入火星文

。原因是瑞星保护了QQ进程,禁止对其注入DLL,解决方法是提示用户关闭帐号保险箱 -_-| 确

实是很降低用户体验的一个不是办法的办法。场景2:制作某游戏外挂,需要注入一个DLL到游戏

进程中去直接调用游戏函数完成某一功能。结果发现该游戏有NP保护,OpenProcess打不开,创建

远程线程也不行,试用其它方法也一一失败。遇到上面的情况,高手们自然是转到Ring0下面去,

使用驱动之类的办法来对付啦,不过吾等菜鸟可就是酒井没法子了 -_-|

不过也别太灰心,凡事总会有办法的。我想我们需要一种持久的、稳定的、不容易被安全软

件屏蔽的DLL注入方法,后来发现,输入法程序就是能完成这一任务的理想人选。输入法程序程序

到底是什么?它没有自己的进程,并且在系统还没有登录时就已被加载(在欢迎界面你也可以调

出输入法),它可以在游戏中打开,也可以在控制台程序中打开,还可以在瑞星保护下的QQ中打

开,在杀软中也可以打开,这不就是我们要找的特性吗。那么,输入法到底是什么呢?根据

Windows的规定,输入法其实就是一个DLL,不过它是一个特殊的DLL,它必须具有标准输入法程序

所规定的那些接口,输入法是由输入法管理器(imm32.dll)控制的,输入法管理器又是由

user32.dll控制的。输入法在系统目录是以IME为扩展名的文件,当在应用程序中激活某个输入法

时,输入法管理器就会在那个应用程序的进程中加载对应的IME文件,注意,加载IME文件跟加载

普通的DLL并没有本质区别,所以,可以认为,输入法其实就是注入到应用程序中的一个DLL文件

,并且,这种“注入”是不会被杀软和游戏NP拦截的(至少目前是)。现在,我们已经有了一个

注入DLL的另类方法,那就是利用输入法。具体流程是这样,首先制作一个标准输入法文件,但是

这个输入法并不完成文字输入工作,它的唯一任务就是用来注入DLL,所以称为“服务输入法”,

然后,制作一个控制程序,来控制服务输入法,当然最后还需要一个用于注入的目标DLL,这样一

共就有3个文件。开始工作后,控制程序首先将服务输入法安装到系统中,然后传递几个参数给服

务输入法,参数中包括了需要注入的DLL文件的名称和路径,然后,控制程序将服务输入法设置为

系统的默认输入法,这样新的程序一打开,服务输入法就会注入那个程序。当然,在服务输入法

安装之前打开的程序不会被注入,这时需要向系统中的所有窗口POST一条

WM_INPUTLANGCHANGEREQUEST消息,该消息可以在指定窗口中后台激活服务输入法,这样,系统中

所有拥有窗口的进程就都被我们的服务输入法注入了。服务输入法注入程序之后,就会根据控制

程序传递过来的参数加载目标DLL,这样目标DLL也就随着服务输入法一同注入到目标程序中了。

注意服务输入法是控制程序用WM_INPUTLANGCHANGEREQUEST消息在所有窗口中自动激活的,如果某

个窗口自动激活失败,你就需要在那个窗口中手工切换到服务输入法,这样才能注入进去了。至

于注入以后,你就可以在窗口中切换到别的输入法,这并不会影响已经注入进去的DLL。

第十种方法:

利用NtResumeThread 实现全局挂钩

一直是Hack 编程中永恒的主题,基本高级的Rootkit 程序多多少少都会使用Hook 技术。 似乎Hook 都被讲烂了,不论是Ring3 的还是Ring0 的网上都有例子。Ring0 的毋庸置疑当然

是全局的了,这里说说ring3 的全局hook。Ring 3 有Ring 3 的优势,稳定是压倒一切的, 因此Mcafee 和其他一些商业的安全软件都还是使用了Ring3 的Hook 技术,无论如何用户是

无法接受蓝屏和死机的。

感兴趣的可以装个Rootkit unhooker 自己看看。

1. 以往的Ring 3全局Hook

纵观网上流行的全局Hook 程序都只用了一个Windows API, SetWindowsHookEx,此函数原型:

HHOOK SetWindowsHookEx( int idHook,

HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId );

idhook 安装的钩子类型,如 WH_GETMESSAGE,WH_KEYBOARD 等 lpfn hook procedure 的指针

hmod 包含 hook procedure DLL 的handle dwThread 为0

使用这个这个API 时候有问题的,只能挂接系统中的所有G U I线程,换句通俗的话说就是有界面

的程序,Windows console 类的程序就无能为力了。

还有一种通过插入注册表来实现

HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\AppInit_DLLs

这种方法简单,但是还是只能挂钩GUI 程序,并且这个键值已经被广大HIPS 所关注,吃力不讨好 。

以上两种效果不好,因此有人有开始另外的做法,枚举所有进程,插入和挂钩 NtCreateProcess

这是非常自然的想法,似乎也把问题解决了,但是仔细思考一下,就会发现很多问题。

a. 时机不对,在NtCreateProcess函数被调用时进程并没有真正被创建,我们无法执行HOOK操作

而当NtCreateProcess返回时,进程又已经开始运行

b. 如果是Windows console 创建的进程,你如何去监控这个调用呢?这么说似乎比较抽象,你可

以这么理解,直接在命令行下,cmd,cmd,cmd .... 你可以监控到最后一个cmd 吗,如果只

用SetWindowsHookEx

c. 是否正好站在了华容道,是否足够底层。

似乎很费劲

2. 分析系统创建进程过程,寻找方法

关于这方面内容,可以参考毛德操老师的两篇文章

《漫谈兼容内核之十七:再谈Windows的进程创建》

《漫谈兼容内核之二十二:Windows线程的调度和运行》

下面是他的blog 链接: http://hi.http://m.wodefanwen.com//fatbsd/blog

CreateProcess 是 Kernel32.dll 的导出函数。

操起WinDbg,剁了一下: Windows 2003 SP2

lkd> uf CreateProcessW kernel32!CreateProcessW:

7c802474 8bff mov edi,edi 7c802476 55 push ebp 7c802477 8bec mov ebp,esp 7c802479 6a00 push 0x0

7c80247b ff752c push dword ptr [ebp+0x2c] 7c80247e ff7528 push dword ptr [ebp+0x28] 7c802481 ff7524 push dword ptr [ebp+0x24]

7c802484 ff7520 push dword ptr [ebp+0x20] 7c802487 ff751c push dword ptr [ebp+0x1c] 7c80248a ff7518 push dword ptr [ebp+0x18] 7c80248d ff7514 push dword ptr [ebp+0x14] 7c802490 ff7510 push dword ptr [ebp+0x10] 7c802493 ff750c push dword ptr [ebp+0xc] 7c802496 ff7508 push dword ptr [ebp+0x8] 7c802499 6a00 push 0x0

7c80249b e8a6ac0200 call kernel32!CreateProcessInternalW (7c82d146) 7c8024a0 5d pop ebp 7c8024a1 c22800 ret 0x28

lkd> uf CreateProcessInternalW ....

7c82cf8f ff159814807c call dword ptr [kernel32!_imp__NtCreateProcessEx (7c801498)] ....

7c82daa2 ff159414807c call dword ptr [kernel32!_imp__NtCreateThread (7c801494)] ....

7c82dbdc ff158814807c call dword ptr [kernel32!_imp__NtResumeThread (7c801488)]

大概流程如下:

Kernel32!CreateProcessW

Kernel32!CreateProcessInternalW ntdll!NtCreateProcessEx ntdll!NtCreateThread ntdll!NtResumeThread

因为进程创建后,Windows 必须为它创建一个主线程,然后等待操作系统调度它。

所以调用NtResumeThread的时候,就是我们Hook的最佳时机,因为此时创建进程的主要工作已经

完成,

但是进程并没有调度起来,呵呵,方便干坏事啊。

3. 具体代码实现

基本思路已经清晰了,这里还几个问题。

a. NtResumeThread 函数并不是创建进程才调用,我们怎么区分出哪个是创建进程时 调用的NtResumeThread呢?

其实现实起来不困难,先枚举系统进程一次,将系统进程中NtResumeThread 都挂钩上。每次拦截

本文来源:https://www.bwwdw.com/article/938h.html

Top