专业丰富的破解论坛技术交流,提供软件安全,病毒分析,脱壳破解,安卓破解,加密解密等,由无数热衷于软件爱好者共同维护
 
发新帖
楼主: 零五零八
查看: 768|回复: 0

[技术文章] 《逆向工程核心原理》-- DLL注入和卸载

[复制链接]
零五零八 发表于 2020-10-28 20:01:16 | 显示全部楼层
本帖最后由 零五零八 于 2020-10-28 20:15 编辑

DLL注入和卸载



一、DLL注入

  DLL注入:向运行中的其他进程强制插入特定的DLL文件,主要是命令其他进程自行调用LoadLibrary() API,加载用户指定的DLL文件。DLL注入与一般DLL加载的主要区别是加载的目标进程是其自身或其他进程。

1. DLL

  DLL(Dynamic Linked Library,动态链接库),DLL被加载到进程后会自动运行DllMain函数,用户可以把想要执行的额代码放到DllMain函数,每当加载DLL时,添加的代码就会自动得到执行。利用该特性可以修复程序BUG,或向程序添加新功能。

  1. // DllMain()函数
  2. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvRserved)
  3. {
  4.   switch(dwReason)
  5.   {
  6.     case DLL_PROCESS_ATTACH:
  7.       // 添加想要执行的代码
  8.       break;

  9.     case DLL_THREAD_ATTACH:
  10.       break;

  11.     case DLL_THREAD_DETACH:
  12.       break;

  13.     case DLL_PROCESS_DETACH:
  14.       break;
  15.   }

  16.   return TRUE;
  17. }
复制代码

2. DLL注入实例

使用LoadLibrary()加载某个DLL时,该DLL中的DllMain()函数会被调用执行。DLL注入的原理就是从外部促使目标进程调用LoadLibrary() API。
  • 改善功能与修复BUG
  • 消息钩取--Windows 默认提供的消息钩取功能本质上应用的就是一种DLL注入技术
  • API钩取--先创建DLL形态的钩取函数,然后注入要钩取的目标进程,主要是应用了被注入的DLL拥有目标进程内存访问权限这一特性
  • 其他应用程序--监视、管理PC用户的应用程序
  • 恶意代码--非法注入,进行代码隐藏


3. DLL注入的实现方法



1. 创建远程线程(CreateRemoteThread() API)

此处主要记录一下书上的源码分析,操作部分请自行实践。

  1. // myhack.cpp
  2. #include "windows.h"
  3. #include "tchar.h"

  4. #pragma comment(lib, "urlmon.lib")

  5. #define DEF_URL         (L"http://www.naver.com/index.html")
  6. #define DEF_FILE_NAME   (L"index.html")

  7. HMODULE g_hMod = NULL;

  8. DWORD WINAPI ThreadProc(LPVOID lParam)
  9. {
  10.     TCHAR szPath[_MAX_PATH] = {0,};

  11.     if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
  12.         return FALSE;

  13.     TCHAR *p = _tcsrchr( szPath, '\\' );
  14.     if( !p )
  15.         return FALSE;

  16.     _tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME); //参数准备

  17.     URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL); //调用函数进行URL下载

  18.     return 0;
  19. }

  20. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
  21. {
  22.     HANDLE hThread = NULL;

  23.     g_hMod = (HMODULE)hinstDLL;

  24.     switch( fdwReason )
  25.     {
  26.     case DLL_PROCESS_ATTACH :
  27.         OutputDebugString(L"<myhack.dll> Injection!!!");

  28.         //创建远程线程进行download
  29.         hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

  30.         // 需要注意,切记随手关闭句柄,保持好习惯
  31.         CloseHandle(hThread);
  32.         break;
  33.     }

  34.     return TRUE;
  35. }
复制代码
  1. // InjectDll.cpp
  2. #include "windows.h"
  3. #include "tchar.h"

  4. BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
  5. {
  6.     HANDLE hProcess = NULL, hThread = NULL;
  7.     HMODULE hMod = NULL;
  8.     LPVOID pRemoteBuf = NULL;

  9.       //确定路径需要占用的缓冲区大小
  10.     DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
  11.     LPTHREAD_START_ROUTINE pThreadProc;

  12.     // #1. 使用OpenProcess函数获取目标进程句柄(PROCESS_ALL_ACCESS权限)
  13.     if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
  14.     {
  15.         _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
  16.         return FALSE;
  17.     }

  18.     // #2. 使用VirtualAllocEx函数在目标进程中分配内存,大小为szDllName
  19.       // VirtualAllocEx函数返回的是hProcess指向的目标进程的分配所得缓冲区的内存地址
  20.     pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

  21.     // #3.  将myhack.dll路径 ("c:\\myhack.dll")写入目标进程中分配到的内存
  22.     WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

  23.     // #4. 获取LoadLibraryA() API的地址
  24.       // 这里主要利用来了kernel32.dll文件在每个进程中的加载地址都相同这一特点,所以不管是获取加载到   
  25.       // InjectDll.exe还是notepad.exe进程的kernel32.dll中的LoadLibraryW函数的地址都是一样的。这里的加载地
  26.       // 址相同指的是在同一次系统运行中,如果再次启动系统kernel32.dll的加载地址会变,但是每个进程的
  27.       // kernerl32.dll的加载地址还是一样的。
  28.       hMod = GetModuleHandle(L"kernel32.dll");
  29.     pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

  30.     // #5. 在目标进程notepad.exe中运行远程线程
  31.       // pThreadProc = notepad.exe进程内存中的LoadLibraryW()地址
  32.       // pRemoteBuf = notepad.exe进程内存中待加载注入dll的路径字符串的地址
  33.     hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
  34.     WaitForSingleObject(hThread, INFINITE);   

  35.       //同样,记得关闭句柄
  36.     CloseHandle(hThread);
  37.     CloseHandle(hProcess);

  38.     return TRUE;
  39. }

  40. int _tmain(int argc, TCHAR *argv[])
  41. {
  42.     if( argc != 3)
  43.     {
  44.         _tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
  45.         return 1;
  46.     }

  47.     // change privilege
  48.     if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
  49.         return 1;

  50.     // inject dll
  51.     if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) )
  52.         _tprintf(L"InjectDll("%s") success!!!\n", argv[2]);
  53.     else
  54.         _tprintf(L"InjectDll("%s") failed!!!\n", argv[2]);

  55.     return 0;
  56. }
复制代码

main()函数主要检查输入程序的参数,然后调用InjectDll函数。InjectDll函数是实施DLL注入的核心函数,功能是命令目标进程自行调用LoadLibrary API。重点介绍一下CreateRemoteThread()函数,该函数在进行DLL注入时会经常用到,其函数原型如下:

  1. CreateRemoteThread()
  2. HANDLE WINAPI CreateRemoteThread(
  3.   __in HANDLE    hProcess,    //目标进程句柄
  4.   __in LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  5.   __in SIZE_T dwStackSize,
  6.   __in LPTHREAD_START_ROUTNE    lpStartAddress,    //线程函数地址
  7.   __in LPVOID dwCreationFlags,    //线程参数地址
  8.   __out LPDOWRD lpThreadId
  9. );
复制代码


2. AppInit_DLLs

  第二种方法是操作注册表,Windows的注册表中默认提供了AppInit_DLLs与LoadAppInit_DLLs两个注册表项,只要将要注入DLL的路径字符串写入AppInit_DLLs项目,并在LoadAppInit_DLLs中设置值为1,重启时,系统就会将指定的DLL注入到所有运行进程中。主要原理是User32.dll被加载到进程时,会读取AppInit_DLLs注册表项,若值为1,就调用LoadLibrary()函数加载用户DLL。所以严格来说,是将注入DLL加载到使用user32.dll的进程中

  1. // myhack2.cpp
  2. // 主要作用是以隐藏模式运行IE,连接到指定网站

  3. #include "windows.h"
  4. #include "tchar.h"

  5. #define DEF_CMD  L"c:\\Program Files\\Internet Explorer\\iexplore.exe"
  6. #define DEF_ADDR L"http://www.naver.com"
  7. #define DEF_DST_PROC L"notepad.exe"

  8. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
  9. {
  10.     TCHAR szCmd[MAX_PATH]  = {0,};
  11.     TCHAR szPath[MAX_PATH] = {0,};
  12.     TCHAR *p = NULL;
  13.     STARTUPINFO si = {0,};
  14.     PROCESS_INFORMATION pi = {0,};

  15.     si.cb = sizeof(STARTUPINFO);
  16.     si.dwFlags = STARTF_USESHOWWINDOW;
  17.     si.wShowWindow = SW_HIDE;

  18.     switch( fdwReason )
  19.     {
  20.     case DLL_PROCESS_ATTACH :
  21.         if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
  22.             break;

  23.         if( !(p = _tcsrchr(szPath, '\\')) )
  24.             break;

  25.         if( _tcsicmp(p+1, DEF_DST_PROC) )
  26.             break;

  27.         wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR);
  28.         if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd,
  29.                             NULL, NULL, FALSE,
  30.                             NORMAL_PRIORITY_CLASS,
  31.                             NULL, NULL, &si, &pi) )
  32.             break;

  33.         if( pi.hProcess != NULL )
  34.             CloseHandle(pi.hProcess);

  35.         break;
  36.     }

  37.     return TRUE;
  38. }
复制代码

  将上述dll文件复制到某个位置,修改注册表项HKEY_LOCAL_MACHINE\SOFTWARE\Microdoft\Windows NT\CurrentVersion\Windows,将AppInit_DLLs项的值修改为待注入DLL的绝对路径,然后修改LoadAppInit_DLLs注册表项的值为1,重启,运行notepad.exe,就会看到DLL已经被注入。

3. 使用SetWindowsHookEx()函数

第三个方法就是消息钩取,使用SetWindowsHookEx安装钩子,将指定DLL强制注入进程。

二、DLL卸载

  DLL卸载原理:驱使目标进程调用FreeLibrary()函数,即将FreeLibrary()函数的地址传递给CreateRemoteThread()函数的lpStartAddress参数,并把待卸载的DLL句柄传递给lpParameter参数。需要注意的一点是:引用计数问题。调用一次FreeLibrary()函数,引用计数就会-1。引用计数表示的是内核对象被使用的次数。

  1. // EjectDll.exe

  2. #include "windows.h"
  3. #include "tlhelp32.h"
  4. #include "tchar.h"

  5. #define DEF_PROC_NAME    (L"notepad.exe")
  6. #define DEF_DLL_NAME    (L"myhack.dll")

  7. DWORD FindProcessID(LPCTSTR szProcessName)
  8. {
  9.     DWORD dwPID = 0xFFFFFFFF;
  10.     HANDLE hSnapShot = INVALID_HANDLE_VALUE;
  11.     PROCESSENTRY32 pe;

  12.     // 获取系统快照
  13.     pe.dwSize = sizeof( PROCESSENTRY32 );
  14.     hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

  15.     // 查找进程
  16.     Process32First(hSnapShot, &pe);
  17.     do
  18.     {
  19.         if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
  20.         {
  21.             dwPID = pe.th32ProcessID;
  22.             break;
  23.         }
  24.     }
  25.     while(Process32Next(hSnapShot, &pe));

  26.     CloseHandle(hSnapShot);

  27.     return dwPID;
  28. }

  29. BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
  30. {
  31.     TOKEN_PRIVILEGES tp;
  32.     HANDLE hToken;
  33.     LUID luid;

  34.     if( !OpenProcessToken(GetCurrentProcess(),
  35.                           TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
  36.                           &hToken) )
  37.     {
  38.         _tprintf(L"OpenProcessToken error: %u\n", GetLastError());
  39.         return FALSE;
  40.     }

  41.     if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
  42.                               lpszPrivilege,  // privilege to lookup
  43.                               &luid) )        // receives LUID of privilege
  44.     {
  45.         _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() );
  46.         return FALSE;
  47.     }

  48.     tp.PrivilegeCount = 1;
  49.     tp.Privileges[0].Luid = luid;
  50.     if( bEnablePrivilege )
  51.         tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  52.     else
  53.         tp.Privileges[0].Attributes = 0;

  54.     // Enable the privilege or disable all privileges.
  55.     if( !AdjustTokenPrivileges(hToken,
  56.                                FALSE,
  57.                                &tp,
  58.                                sizeof(TOKEN_PRIVILEGES),
  59.                                (PTOKEN_PRIVILEGES) NULL,
  60.                                (PDWORD) NULL) )
  61.     {
  62.         _tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() );
  63.         return FALSE;
  64.     }

  65.     if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
  66.     {
  67.         _tprintf(L"The token does not have the specified privilege. \n");
  68.         return FALSE;
  69.     }

  70.     return TRUE;
  71. }

  72. BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
  73. {
  74.     BOOL bMore = FALSE, bFound = FALSE;
  75.     HANDLE hSnapshot, hProcess, hThread;
  76.     HMODULE hModule = NULL;
  77.     MODULEENTRY32 me = { sizeof(me) };
  78.     LPTHREAD_START_ROUTINE pThreadProc;

  79.     // dwPID = notepad 进程ID
  80.     // 使用TH32CS_SNAPMODULE参数,获取加载到notepad进程的DLL名称
  81.     hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

  82.     bMore = Module32First(hSnapshot, &me);
  83.     for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
  84.     {
  85.         if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
  86.             !_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
  87.         {
  88.             bFound = TRUE;
  89.             break;
  90.         }
  91.     }

  92.     if( !bFound )
  93.     {
  94.         CloseHandle(hSnapshot);
  95.         return FALSE;
  96.     }

  97.     if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
  98.     {
  99.         _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
  100.         return FALSE;
  101.     }

  102.     hModule = GetModuleHandle(L"kernel32.dll");
  103.       // 获取FreeLibrary函数加载地址,并使用CreateRemoteThread进行调用
  104.     pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
  105.     hThread = CreateRemoteThread(hProcess, NULL, 0,
  106.                                  pThreadProc, me.modBaseAddr,
  107.                                  0, NULL);
  108.     WaitForSingleObject(hThread, INFINITE);   

  109.     CloseHandle(hThread);
  110.     CloseHandle(hProcess);
  111.     CloseHandle(hSnapshot);

  112.     return TRUE;
  113. }

  114. int _tmain(int argc, TCHAR* argv[])
  115. {
  116.     DWORD dwPID = 0xFFFFFFFF;

  117.     // 查找process
  118.     dwPID = FindProcessID(DEF_PROC_NAME);
  119.     if( dwPID == 0xFFFFFFFF )
  120.     {
  121.         _tprintf(L"There is no <%s> process!\n", DEF_PROC_NAME);
  122.         return 1;
  123.     }

  124.     _tprintf(L"PID of "%s" is %d\n", DEF_PROC_NAME, dwPID);

  125.     // 更改 privilege
  126.     if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
  127.         return 1;

  128.     // 注入 dll
  129.     if( EjectDll(dwPID, DEF_DLL_NAME) )
  130.         _tprintf(L"EjectDll(%d, "%s") success!!!\n", dwPID, DEF_DLL_NAME);
  131.     else
  132.         _tprintf(L"EjectDll(%d, "%s") failed!!!\n", dwPID, DEF_DLL_NAME);

  133.     return 0;
  134. }
复制代码

CreateToolhelp32Snapshot()函数主要用来获取加载到进程的模块信息,将获取的hSnapshot句柄传递给Module32First()/Module32Next()函数后,即可设置与MODULEENTRY32结构相关的模块信息,以下为该结构的详细定义:
.
  1. typedef sturc tagMODULEENTRY32
  2. {
  3.     DWORD dwSize;
  4.   DWORD th32ModuleID;        // 该模块
  5.   DWORD th32ProcessID;    // 模块拥有的进程
  6.   DWORD GlbcntUsage;        //模块中的global usage计数
  7.   DWORD ProcessUsage;      
  8.   BYTE * modBaseAddr;        //在进程的上下文中的模块的基地址
  9.   DWORD modBaseSize;        // 在modBaseAddr开始位置的模块的大小(字节为单位)
  10.   HMODULE hModule;
  11.   char szModule[MAX_MODULE_NAME32+1];    //DLL名称
  12.   char szExePath[MAX_PATH];
  13. }MODULEENTRY32;
复制代码









快速回复 返回顶部 返回列表