赞
踩
首先开始之前先申明一下代码只在X86环境下运行。博主使用的是VM虚拟机,操作系统是Windows XP Profession,具体信息可以写一个程序来获取:
- .386
- .model flat, stdcall
- option casemap:none
-
- include windows.inc
- include kernel32.inc
- includelib kernel32.lib
- include user32.inc
- includelib user32.lib
- include advapi32.inc
- includelib advapi32.lib
-
- .data
- dwOsInfo OSVERSIONINFO <0>
- szBuffer db 512 dup (0)
- dwIndex dd ?
- szCPUName db MAX_PATH dup (0)
- szCPUId db MAX_PATH dup (0)
- dwArchi dd ?
- dwMain dd ?
- dwSize dd ?
- szType1 dd ?
- szType2 dd ?
- .const
- szFmt db '操作系统: %s %d.%d %s', 0dh, 0ah
- db '处理器个数: %d', 0dh, 0ah
- db 'CPU名称: %s', 0dh, 0ah
- db 'CPU标识: %s', 0dh, 0ah
- db '架构: X%d', 0dh, 0ah
- db '主频: %d MHz', 0dh, 0ah, 0
- szSubKey1 db 'HARDWARE\DESCRIPTION\System\CentralProcessor', 0
- szSubKey2 db 'HARDWARE\DESCRIPTION\System\CentralProcessor\0', 0
- szNt db 'Windows NT', 0
- sz9x db 'Windows 9x', 0
- szTitle db '版本信息', 0
- szCPUNameKey db 'ProcessorNameString', 0
- szCPUIdKey db 'Identifier', 0
- szArchi db 'Platform ID', 0
- szMain db '~MHz', 0
- .code
- getProcessorsNum proc
- local @hKey, @dwIndex, @dwSize
- local @szTempBuf[MAX_PATH]:byte
- pushad
- invoke RtlZeroMemory, addr @szTempBuf, sizeof @szTempBuf
- invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, offset szSubKey1, 0, KEY_ALL_ACCESS, addr @hKey
- .if eax == ERROR_SUCCESS
- mov @dwIndex, 0
- .while TRUE
- mov @dwSize, sizeof @szTempBuf
- invoke RegEnumKeyEx, @hKey, @dwIndex, addr @szTempBuf, addr @dwSize, NULL, NULL, \
- NULL, NULL
- .break .if eax == ERROR_NO_MORE_ITEMS
- inc @dwIndex
- .endw
- .endif
- mov eax, @dwIndex
- mov dwIndex, eax
-
- invoke RegCloseKey, @hKey
- popad
- ret
-
- getProcessorsNum endp
-
-
- getProcessorsInfo proc
- local @hKey
-
- pushad
- invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, offset szSubKey2, 0, KEY_ALL_ACCESS, addr @hKey
- .if eax == ERROR_SUCCESS
- mov szType1, REG_SZ
- mov szType2, REG_DWORD
- mov dwSize, sizeof szCPUName
- invoke RegQueryValueEx, @hKey, offset szCPUNameKey, NULL, offset szType1, offset szCPUName, offset dwSize
- mov dwSize, sizeof szCPUId
- invoke RegQueryValueEx, @hKey, offset szCPUIdKey, NULL, offset szType1, offset szCPUId, offset dwSize
- mov dwSize, sizeof dwArchi
- invoke RegQueryValueEx, @hKey, offset szArchi, NULL, offset szType2, offset dwArchi, offset dwSize
- mov dwSize, sizeof dwMain
- invoke RegQueryValueEx, @hKey, offset szMain, NULL, offset szType2, offset dwMain, offset dwSize
- .endif
- invoke RegCloseKey, @hKey
- popad
- ret
-
- getProcessorsInfo endp
-
-
- start:
- call getProcessorsNum
- call getProcessorsInfo
- mov dwOsInfo.dwOSVersionInfoSize, sizeof OSVERSIONINFO
- invoke GetVersionEx, offset dwOsInfo
- mov eax, dwOsInfo.dwPlatformId
- cmp eax, VER_PLATFORM_WIN32_WINDOWS
- jnz NT
- invoke wsprintf, offset szBuffer, offset szFmt, offset sz9x, \
- dwOsInfo.dwMajorVersion, dwOsInfo.dwMinorVersion, offset dwOsInfo.szCSDVersion, \
- dwIndex, offset szCPUName, offset szCPUId, dwArchi, dwMain
- jmp FIN
- NT:
- invoke wsprintf, offset szBuffer, offset szFmt, offset szNt, \
- dwOsInfo.dwMajorVersion, dwOsInfo.dwMinorVersion, offset dwOsInfo.szCSDVersion, \
- dwIndex, offset szCPUName, offset szCPUId, dwArchi, dwMain
- FIN:
- invoke MessageBox, NULL, offset szBuffer, offset szTitle, MB_OK
- invoke ExitProcess, NULL
-
- 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来辨认,写一个如下程序:
如此一来,是什么类型格式的文件就一目了然了。源代码如下:
- .386
- .model flat, stdcall
- option casemap:none
-
- include windows.inc
- include kernel32.inc
- includelib kernel32.lib
- include user32.inc
- includelib user32.lib
- include comdlg32.inc
- includelib comdlg32.lib
-
-
- IDD_DIALOG1 equ 101
- IDC_EDI equ 1001
- IDC_BUTTO equ 1002
- IDC_TEXT equ 1003
-
- .data
- szFileName db MAX_PATH dup (?)
-
- .data?
- hWinMain dd ?
- hInstance dd ?
- hWinEdit dd ?
- hWinStatic dd ?
- lpMem dd ?
-
- .const
- szFilter db '可执行文件(*.exe;*.dll;*.scr;*.drv;*.fon)', 0, "*.exe;*.dll;*.scr;*.drv;*.fon", 0, \
- '所有文件(*.*)', '*.*', 0, 0
-
- szErrOpen db '打开PE文件失败!', 0
- szErrTitle db '错误', 0
-
- szErrFormat db '该文件不是PE文件!', 0
-
- szDll db '该文件为Dll动态链接库文件', 0
- szExe db '该文件为EXE可执行文件', 0
- szDrv db '该文件为Drv驱动文件', 0
- szNone db '无法辨认!', 0
-
- .code
-
- _GetFilePath proc
- local @stOpenFileName:OPENFILENAME
-
- invoke RtlZeroMemory, addr @stOpenFileName, sizeof @stOpenFileName
- mov @stOpenFileName.lStructSize, sizeof @stOpenFileName
- push hWinMain
- pop @stOpenFileName.hwndOwner
- mov @stOpenFileName.lpstrFilter, offset szFilter
- mov @stOpenFileName.lpstrFile, offset szFileName
- mov @stOpenFileName.nMaxFile, sizeof szFileName
- mov @stOpenFileName.Flags, OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST
- invoke GetOpenFileName, addr @stOpenFileName
- cmp eax, 0
- jnz BACK
- invoke MessageBox, hWinMain, offset szErrOpen, offset szErrTitle, MB_OK
- xor eax, eax
- BACK:
- ret
-
- _GetFilePath endp
-
-
- _ProcDlgMain proc uses ebx esi edi, hWnd, wMsg, wParam, lParam
- local @hFile, @hMapFile
- mov eax, wMsg
- .if eax == WM_COMMAND
- mov eax, wParam
- .if ax == IDC_BUTTO
- call _GetFilePath
- cmp eax, 0
- jnz SUC
- jmp FIN
- SUC:
- invoke SendMessage, hWinEdit, EM_SETSEL, 0, -1
- invoke SendMessage, hWinEdit, EM_REPLACESEL, FALSE, offset szFileName
- invoke CreateFile, offset szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, \
- OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
- .if eax
- mov @hFile, eax
- invoke CreateFileMapping, @hFile, NULL, PAGE_READONLY, 0, 0, NULL
- .if eax
- mov @hMapFile, eax
- invoke MapViewOfFile, @hMapFile, FILE_MAP_READ, 0, 0, 0
- .if eax
- mov lpMem, eax
- mov esi, lpMem
- assume esi:ptr IMAGE_DOS_HEADER
- .if word ptr [esi] != IMAGE_DOS_SIGNATURE
- invoke MessageBox, hWinMain, offset szErrFormat, offset szErrTitle, MB_OK
- jmp @F
- .endif
- add esi, dword ptr [esi + 3Ch]
- assume esi:ptr IMAGE_NT_HEADERS
- .if dword ptr [esi].Signature != IMAGE_NT_SIGNATURE
- invoke MessageBox, hWinMain, offset szErrFormat, offset szErrTitle, MB_OK
- jmp @F
- .endif
- add esi, 4
- assume esi:ptr IMAGE_FILE_HEADER
- invoke GetDlgItem, hWinMain, IDC_TEXT
- mov hWinStatic, eax
- mov ax, word ptr [esi].Characteristics
- .if ax & IMAGE_FILE_DLL
- invoke SetWindowText, hWinStatic, offset szDll
- .elseif ax & IMAGE_FILE_EXECUTABLE_IMAGE
- invoke SetWindowText, hWinStatic, offset szExe
- .elseif ax & IMAGE_FILE_SYSTEM
- invoke SetWindowText, hWinStatic, offset szDrv
- .else
- invoke SetWindowText, hWinStatic, offset szNone
- .endif
- @@:
- assume esi:nothing
- invoke UnmapViewOfFile, lpMem
- .endif
- invoke CloseHandle, @hMapFile
- .endif
-
-
- invoke CloseHandle, @hFile
- .endif
- .endif
- FIN:
- .elseif eax == WM_CLOSE
- invoke EndDialog, hWinMain, NULL
- .elseif eax == WM_INITDIALOG
- push hWnd
- pop hWinMain
- invoke GetDlgItem, hWinMain, IDC_EDI
- mov hWinEdit, eax
- invoke SendMessage, hWinEdit, EM_SETLIMITTEXT, MAX_PATH, 0
- invoke SendDlgItemMessage, hWinMain, IDC_EDI, EM_SETREADONLY, TRUE, NULL
- .else
- mov eax, FALSE
- ret
- .endif
-
- mov eax, TRUE
- ret
-
- _ProcDlgMain endp
-
-
- start:
- invoke GetModuleHandle, NULL
- mov hInstance, eax
- invoke DialogBoxParam, eax, IDD_DIALOG1, NULL, offset _ProcDlgMain, NULL
- invoke ExitProcess, NULL
-
- 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
我们来看一下实现的效果:
代码如下:
- .386
- .model flat, stdcall
- option casemap:none
-
- include windows.inc
- include kernel32.inc
- includelib kernel32.lib
- include user32.inc
- includelib user32.lib
-
-
- .data?
- dwProcessID dd ?
- dwThreadID dd ?
- hProcess dd ?
- lpLoadLibrary dd ?
- lpDllName dd ?
- szMyDllFull db MAX_PATH dup (?)
- .const
- szMyDll db '\dll.dll', 0
-
- szLoadLibrary db 'LoadLibraryA', 0
- szDllKernel db 'Kernel32.dll', 0
- szDesktopWindow db 'Program Manager', 0
- szErrOpen db '无法打开远程线程!', 0
-
- .code
- start:
- invoke GetCurrentDirectory, MAX_PATH, addr szMyDllFull
- invoke lstrcat, addr szMyDllFull, addr szMyDll
- invoke GetModuleHandle, addr szDllKernel
- invoke GetProcAddress, eax, offset szLoadLibrary
- mov lpLoadLibrary, eax
-
- invoke FindWindow, NULL, offset szDesktopWindow
- invoke GetWindowThreadProcessId, eax, offset dwProcessID
- mov dwThreadID, eax
- invoke OpenProcess, PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or \
- PROCESS_VM_WRITE, FALSE, dwProcessID
- .if eax
- mov hProcess, eax
- invoke VirtualAllocEx, hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE
- .if eax
- mov lpDllName, eax
- invoke WriteProcessMemory, hProcess, eax, offset szMyDllFull, MAX_PATH, NULL
- invoke CreateRemoteThread, hProcess, NULL, 0, lpLoadLibrary, lpDllName, 0, NULL
- invoke CloseHandle, hProcess
- .endif
- .else
- invoke MessageBox, NULL, addr szErrOpen, NULL, MB_OK
- .endif
-
- invoke ExitProcess, NULL
-
- 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。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。