VirtualAllocEx函数

VirtualAllocEx
2009-09-18 23:00

[翻译整理] M4orz3r..

Blog...Http://Hi.baidu.com/M4orz3r

转载请注明... 水平有限,翻译错误之处请留言告知...

功能:在指定进程的虚拟地址空间中保留或开辟一段区域..除非MEM_RESET被使用,否则这个函数将会初始化那段内存为0.

函数原型:

LPVOID WINAPI VirtualAllocEx(
  __in      HANDLE hProcess,   //需要在其中分配空间的进程的句柄.
  __in_opt  LPVOID lpAddress,  //想要获取的地址区域..
  __in      SIZE_T dwSize,      //要分配的内存大小.
  __in      DWORD flAllocationType, //内存分配的类型
  __in      DWORD flProtect        //内存页保护.
);

参数:

hProcess:

需要在其中非配空间的进程的句柄..这个句柄必须拥有PROCESS_VM_OPERATION访问权限.获取更多信息请参考Process Security And Access Rights..

lpAddress.:

指向一个你想要获取分配的地址区域..如果你要保留一段内存区域..这个函数会轮询地址,到最近的分配粒度...如果你要提交的内存已经预留.那么这个地址会在最近的页边界..为了确定这个页的大小和在本机上的内存分配粒度,可以使用GetSystemInfo..如果这个参数为NULL,那么这个函数会自己决定如何分配..

dwSize:

要分配的内存区域的大小.以byte为单位..如果这个参数为NULL,该函数会轮询dwSize到下一个页的边界.如果不为NULL,这个函数会在任意的页中分配由lpAddress 大盘dwSize所指定的byte大小的地址.这意味着一个2字节的范围可能会跨越页边界而分配为两个页..

flAllocationType :

内存分配的类型,这个参数必须包含以下其中一种.

MEM_COMMIT                                                                     
0x1000

分配指定的物理内存或在磁盘上的内存分页文件.这个函数会初始化它们为0.

要保留或提交页请调用VirtualAllocEx 并设置MEM_COMMIT | MEM_RESERVE..

如果你试图提交一个未保留的页.这个函数将会失败.返回错误代码为ERROR_INVALID_ADDRESS.

尝试提交一个已提交的页不会导致函数失败.这意味着你提交页时需要首先确定每一页的当前状态.

MEM_RESERVE
0x2000

在指定进程的虚拟地址空间中保留一段在物理内存或磁盘上的内存分页文件中的内存区域.

你可以通过VirtualAllocEx附带MEM_COMMIT提交保留页.要保留和提交页请调用VirtualAllocEx并附带MEM_COMMIT |MEM_RESERVE..

其他的内存分配功能有malloc,和LocalAlloc .在内存释放以前,不能使用预留的内存..

MEM_RESET
0x80000

这个数据表明,对lpAddress和dwSize所指定的内存已不再感兴趣.这个页将不能够再读出或写入数据..无论什么时候这个内存块被使用过将不能销毁....这个值也不能再被用于其它值.. 使用这个值不能保MEM_RESET的区域包含0..如果想要指定的区域包含0..销毁内存,并重新定义它..

当你使用MEM_RESET时,VirtualAllocEx函数将会忽略fProtect的值...但无论如何,你扔必须设置fProtect的值.

如果你使用MEM_RESET并且这个区域的内存是由一个文件映射的..VirtualAllocEx返回一个错误..如果有它是一个分页文件,那么只能允许共享查看...

此参数还可以指定为以下值:

MEM_LARGE_PAGES
0x20000000

使用large page support分配内存.这个尺寸和对齐方式必须是这个最大页的最低倍数.要获取这个值,请使用GetLargePageMinimum函数..

MEM_PHYSICAL
0x400000

分配一段具有读写权限的物理内存..这个值是独一无二的..使用Address Windowing Extensions内存..

这个值必须为MEM_RESERVE不能为其他值...

MEM_TOP_DOWN
0x100000

在最高地址分配内存..

flprotect:

在分配的内存页区域中进行内存保护....如果这个页being committed 你可以指定memory protection constants中的任意一个..

保护属性指定,保护的页不能跟指定分配的页冲突..

返回值:

如果函数成功,返回值为分配页区域的基地址...如果失败返回NULL。获取更多错误信息请调用GetLastError...

附注:

每一个页都有一个关联的页状态.VirtualAllocEx函数可以执行以下操作.

    提交一个保留页区域..

    保留一个空闲页区域.

     同时保留和提交一个空闲页.

VirtualAllocEx不能保留一个已保留的页.它可以提交一个早已提交的页.这意味着你可以提交的页面范围,不管它是否有一个早已提交的页,这个函数也不会失败..

你可以使用VirtualAllocEx来预留一个页块..并且可以创建一个额外的调用VirtualAllocEx来从预留的块中提交一个单独的页..这回使得进程保留一段区域.它并不会消耗虚拟地址空间的物理内存.直到它被需要..

如果lpAddress参数不为NULL.这个函数使用lpAddress 和dwSize 参数来计算所需分配的区域. 全部区域的页状态必须兼容指定的flAllocationType参数.否则函数失败,并且没有内存被分配.这个兼容性要求并不能阻止早已提交的页.见上面列表.

要执行动态生成的代码,要使用VirtualAllocEx来分配内存,并且VirtualAllocEx必须被授予PAGE_EXECUTE权限..


该VirtualAllocEx函数可用于保留在一个特定进程的虚拟地址空间的地址窗口扩展(AWE)的内存区域..这种内存区域,可以被映射到虚拟内存不足的应用程序中去..MEM_PHYSICAL MEM_RESERVE必须被设定. MEM_COMMIT必须不能设置.页保护必须设置为PAGE_READWRITE.

VirtualFreeEx函数可以撤销一个已提交的页.发布的页.或同时解除和释放一个提交的页.同样也可以释放一个保留的页.使他成为一个空闲的页.

远程注入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、付费专栏及课程。

余额充值