DLL注入

使用注册表来注入DLL

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\
中的 AppInit_Dlls键可能会包含 一个DLL的文件名或一组DLL的文件名,我们将其设为我们要注入的DLL,之后我们为了让其执行,还必须创建一个名为 LoadAppInit_Dlls的键,类型为DWORD,值设为 1。当User32.dll被映射到一个新的进程时,会收到 DLL_PROCESS_ATTACH通知,当User32.dll对它进行处理的时候,会取得上述注册表键的值,并调用LoadLibrary来载入这个字符串中指定的每个DLL。当系统载入每个DLL的时候,会调用它们的 DllMain函数,并将参数fdwReason的值设为DLL_PROCESS_ATTACH,这个每个DLL就能够对自己进行初始化了。由于被注入的DLL是在进程生命期的早起被注入的,因此我们在调用函数的时候应该慎重。调用Kernel32.dll中的函数应该没有问题,但是调用其他DLL中的函数可能会导致问题,甚至可能会导致蓝屏。User32.dll不会检查每个DLL的载入或初始化是否成功。

优点: 方便
缺点: 1.必须是使用了User32.dll的进程,所有GUI可以,但CUI不可。
2.所有GUI进程中的线程都在执行我们的代码,容器易崩溃。
3.不能只在特定的需要的时候加载到特定的需要的进程上。

使用Windows挂钩来注入DLL

我们可以用挂钩来将一个DLL注入到进程的地址空间中。

SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInstDll,0); 这是通过调用 SetWindowsHookEx安装了一个 WH_GETMESSAGE的挂钩。 GetMsgProc是一个函数的地址,在窗口即将处理一条消息的时候,系统应用调用这个函数。第三个参数是一个DLL,这个DLL中包含了GetMsgProc函数。在windows 中,hInstDll的值是进程地址空间中DLL被映射到虚拟内存地址。最后一个参数0表示要给哪个线程安装挂钩。0是要给系统中的所有GUI线程安装挂钩的意思。

注意,当系统把挂钩过滤函数所在的DLL注入或映射到地址空间中时,会映射整个DLL,而不仅仅只是挂钩过滤函数,这意味着该DLL内的所有函数存在与进程B中,能够为进程B中的为何线程调用。

为了从另一个进程的窗口来创建一个子类窗口,我们可以先给创建窗口的线程设置一个WH_GETMESSAGE挂钩,然后当GetMsgProc函数被调用的时候,我们就可以调用SetWindowLongPtr来派生子类窗口。当然,子类窗口的窗口过程必须和GetMsgProc函数在同一个DLL中。

优点: 可以用UnhookWindowsHookEx来撤销映射。

我们不能在派生完子窗口之后马上就把挂钩清除,在子类窗口的整个生命期内,这个挂钩必须一直有效。

使用远程线程来注入DLL

注入DLL的第三种方法是使用远程线程(remote thread),它提供了最高的灵活性。

DLL注入技术要求目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL。由于我们不能轻易地控制别人进程中的线程,因此这种方法要求我们在目标进程中创建一个新的线程。由于这个线程是我们自己创建的,因此我们可以对它执行的代码加以控制。

Windows提供了GreateRemoteThread函数。用其来调用LoadLibrary函数,注入我们DLL。

问题:
1.不能直接传入LoadLibrary函数,所以要我们写一个函数来调用LoadLibrary。
2.DLL路径字符串问题,我们需要把DLL的路径字符串存放到远程进程的地址空间中去。幸运的是,Windows提供了VirtualAllocEx函数,它可以让一个进程在另一个进程的地址空间中分配一块内存。

步骤:

(1)取得注入进程ID dwRemoteProcessId;

(2)取得注入DLL的完全路径,并将其转换为宽字符模式pszLibFileName;

(3)利用Windows API OpenProcess打开宿主进程,应该开启下列选项:

  a.PROCESS_CREATE_THREAD:允许在宿主进程中创建线程;

  b.PROCESS_VM_OPERATION:允许对宿主进程中进行VM操作;

  c.PROCESS_VM_WRITE:允许对宿主进程进行VM写。

(4)利用Windows API VirtualAllocEx函数在远程线程的VM中分配DLL完整路径宽字符所需的存储空间,并利用Windows API WriteProcessMemory函数将完整路径写入该存储空间;

(5)利用Windows API GetProcAddress取得Kernel32模块中LoadLibraryW函数的地址,这个函数将作为随后将启动的远程线程的入口函数;

(6)利用Windows API CreateRemoteThread启动远程线程,将LoadLibraryW的地址作为远程线程的入口函数地址,将宿主进程里被分配空间中存储的完整DLL路径作为线程入口函数的参数以另其启动指定的DLL;

(7)清理现场。

 

示范代码:

    //打开目标进程
    HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,g_Pid);

    if (!hProcess)
    {
        AfxMessageBox(TEXT("打开进程失败"));
        return;
    }
    //在目标进程申请一段内存区域 来存放DLL路径
    LPVOID pRemoteBase=VirtualAllocEx(hProcess,NULL,0x1000,MEM_COMMIT,PAGE_READWRITE);
    if (pRemoteBase==NULL)
    {
        AfxMessageBox(TEXT("申请内存区域失败"));
        return;
    }
    //在目标进程中写入DLL路径 写入的长度要+1 字符串终止符
    if (!WriteProcessMemory(hProcess,pRemoteBase,(LPTSTR)(LPCTSTR)DllPath,DllPath.GetLength()+1,NULL))
    {
        AfxMessageBox(TEXT("进程写入失败"));
        //失败就释放原先申请的内存区域 撤销内存页的提交状态
        VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT);
        return;
    }
    //得到LoadLibraryA的函数地址  因为Kernel32的加载地址在每个应用程序中都一样
    LPTHREAD_START_ROUTINE pfn=(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32.dll"),"LoadLibraryA");
    //创建远程线程 执行加载
    HANDLE hRemoteThread = CreateRemoteThread(hProcess,NULL,0,pfn,pRemoteBase,0,NULL);
    if (hRemoteThread==NULL)
    {
        AfxMessageBox(TEXT("创建远程线程失败"));
        //释放原先申请的内存区域 撤销内存页的提交状态
        VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT);
        return;

    }
    AfxMessageBox(TEXT("成功注入"));
    //等待线程退出
    WaitForSingleObject(hRemoteThread,-1);
    //释放原先申请的内存区域 撤销内存页的提交状态
    VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT);
    //关闭句柄
    CloseHandle(hRemoteThread);
    CloseHandle(hProcess);
复制代码
使用木马DLL来注入DLL

如果熟悉exe和DLL的文件格式,我们可以用我们仿造的.dll把程序原本要载入的.dll替换掉。

把DLL作为调试器来注入

这种方法要求我们对被调试线程的CONTEXT结构进行操作,这也意味着我们必须编写与CPU相关的代码。其原理是系统载入一个被调试程序的时候,会在被调试程序的地址空间准备完毕之后,且被调试程序的主线程尚未开始执行任何代码之前,自动通知调试器。这是,调试器可以强制将一些代码注入到被调试程序的地址空间中,然后让被调试程序的主线程去执行这些代码。

使用CreateProcess来注入代码

如果要注入代码的进程是由我们的进程生成的,那么事情就比较好办了。我们的进程(父进程)可以在创建新进程的时候将它挂起。这种方法允许我们改变子进程的状态,同时又不影响它的执行,因为它根本还没有开始执行。但是,父进程也会得到子进程的主线程的句柄。通过这个句柄,可以对线程执行的代码进行修改。

优点: 1.它在应用程序开始之前得到地址空间;
2.由于我们的应用程序不是调试器,因此我们可以非常容易地对应用程序和注入DLL进行调试;
3.这种方法同时适用于控制台应用程序和GUI应用程序。
缺点: 1.只有当我们的代码在父进程中的时候,我们才能使用这种方法;
2.我们必须为不同的CPU平台做相应的修改。
 
 

 

远程注入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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值