干货|Windows下进程操作的一些C++代码

VSole2021-08-10 10:41:44

0x01 进程遍历

因为进程是在随时进行变动的所以我们需要获取一张快照


1.1 CreateToolhelp32Snapshot

HANDLE CreateToolhelp32Snapshot(
  DWORD dwFlags,
  DWORD th32ProcessID
);

因为要获取进程第一个参数选择TH32CS_SNAPPROCESS来获取系统中所有的进程,具体可以参考[CreateToolhelp32Snapshot]:https://docs.microsoft.com/zh-cn/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot?f1url=%3FappId%3DDev16IDEF1%26l%3DZH-CN%26k%3Dk(TLHELP32%252FCreateToolhelp32Snapshot);k(CreateToolhelp32Snapshot);k(DevLang-C%252B%252B);k(TargetOS-Windows)%26rd%3Dtrue

1.2 Process32First

BOOL Process32First(
  HANDLE           hSnapshot,
  LPPROCESSENTRY32 lppe
);

第一个参数使用上面CreateToolhelp32Snapshot函数返回的句柄。第二个参数执行了PROCESSENTRY32结构的指针,它包含了进程信息。检索进程里的第一个进程信息。

1.2.1 PROCESSENTRY32

typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  CHAR      szExeFile[MAX_PATH];
} PROCESSENTRY32;

使用时候要把结构体清零。szExeFile为进程名称,其他都根据名称一样。

1.3 Process32Next

BOOL Process32Next(
  HANDLE           hSnapshot,
  LPPROCESSENTRY32 lppe
);

检索快照中的下一个进程信息。

void ListProcess() {
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe32 = { 0 };
    pe32.dwSize = sizeof(PROCESSENTRY32);
    BOOL bRet = Process32First(hSnap, &pe32);
    while (bRet)
    {
        bRet = Process32Next(hSnap, &pe32);
        printf(pe32.szExeFile);
        pid = pe32.th32ProcessID;
        wprintf(L"\r");
        printf("pid:%d", pe32.th32ProcessID);
        printf("\r-----------------------------------------");
        wprintf(L"\r");
    }
    ::CloseHandle(hSnap);
}

0x02 模块遍历

同理只需要将CreateToolhelp32Snapshot的dwFlags修改为TH32CS_SNAPMODULE,th32ProcessID参数为进程的pid,这里要先获取进程pid。

2.1 获取进程pid

typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  CHAR      szExeFile[MAX_PATH];
} PROCESSENTRY32;

通过PROCESSENTRY32结构体的th32ProcessID来获取进程pid。遍历进程通过strcmp匹配到我们的进程名就返回the32ProcessID。

int GetProcessPid(char* ProcessName) {
    Flag = CheckPorcess(ProcessName);
    if (Flag) {
        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        PROCESSENTRY32 pe32 = { 0 };
        pe32.dwSize = sizeof(PROCESSENTRY32);
        BOOL bRet = Process32First(hSnap, &pe32);
        while (bRet)
        {
            bRet = Process32Next(hSnap, &pe32);
            if (strcmp(pe32.szExeFile, ProcessName) == 0) {
                pid = pe32.th32ProcessID;
            }
        }
        CloseHandle(hSnap);
        return pid;
    }
    printf("[-]Process not found");
    return 0;
}

2.2 模块遍历

获取到了进程pid,放入CreateToolhelp32Snapshot第二个参数,但是因为考虑到进程可能根本不存在所以写一个CheckPorcess方法来判断是否存在该进程。

BOOL CheckPorcess(char* ParanetProcessName) {
    DWORD i = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe32 = { 0 };
    pe32.dwSize = sizeof(PROCESSENTRY32);
    BOOL bRet = Process32First(hSnap, &pe32);
    while (bRet)
    {
        bRet = Process32Next(hSnap, &pe32);
        if (strcmp(pe32.szExeFile, ParanetProcessName) == 0) {
            CloseHandle(hSnap);
            return TRUE;
        }
    }
    return FALSE;
}

然后遍历模块

BOOL ListModule(char* ProcessName) {
    Flag = CheckPorcess(ProcessName);
    if (Flag) {
        pid = GetProcessPid(ProcessName);
        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
        MODULEENTRY32 me32 = { 0 };
        me32.dwSize = sizeof(MODULEENTRY32);
        BOOL bRet = Module32First(hSnap, &me32);
        while (bRet)
        {
            bRet = Module32Next(hSnap, &me32);
            printf(me32.szExePath);
            printf("\r");
        }
        CloseHandle(hSnap);
        return TRUE;
    }
    
    printf("[-]Process not found");
    return FALSE;
}

0x03 遍历线程

使用TH32CS_SNAPTHREAD参数来获取,这里都大同小异了。

BOOL ListThread(char* ProcessName) {
    Flag = CheckPorcess(ProcessName);
    if (Flag) {
        pid = GetProcessPid(ProcessName);
        THREADENTRY32 te32 = { 0 };
        te32.dwSize = sizeof(THREADENTRY32);
        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, pid);
        BOOL bRet = Thread32First(hSnap, &te32);
        while (bRet)
        {
            Thread32Next(hSnap, &te32);
            if (te32.th32OwnerProcessID == pid) {
                printf(TEXT("     THREAD ID      = 0x%08X"), te32.th32ThreadID);
                printf(TEXT("     base priority  = %d"), te32.tpBasePri);
                printf(TEXT("     delta priority = %d"), te32.tpDeltaPri);
                break;
            }
        }
        CloseHandle(hSnap);
        return TRUE;
    }
    printf("[-]Process not found");
    return FALSE;
}

通过te32.th32OwnerProcessID与GetProcessPid方法获取到的pid来对比进而确定当前进程。

0x04 干进程

4.1 TerminateProcess

BOOL TerminateProcess(
  HANDLE hProcess,
  UINT   uExitCode
);

第一个参数为要结束进程的进程句柄,第二个参数为终止代码。

4.2 OpenProcess

HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL  bInheritHandle,
  DWORD dwProcessId
);

第一个参数为进程访问权限这里设置为拥有全部权限PROCESS_ALL_ACCESS,具体查看[进程访问权限]:https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights 第二个参数为是否要继承句柄。第三个参数为进程pid。

BOOL KillProcess(char* ProcessName) {
    Flag = CheckPorcess(ProcessName);
    if (Flag) {
        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        PROCESSENTRY32 pe32 = { 0 };
        pe32.dwSize = sizeof(PROCESSENTRY32);
        BOOL bRet = Process32First(hSnap, &pe32);
        while (bRet)
        {
            bRet = Process32Next(hSnap, &pe32);
            if (strcmp(pe32.szExeFile, ProcessName) == 0) {
                pid = pe32.th32ProcessID;
            }
        }
        HANDLE hProcessName = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
        bRet = TerminateProcess(hProcessName, 0);
        if (bRet) {
            printf("Kill %s success!", pe32.szExeFile);
        }
        else
        {
            printf("Kill %s fail!", pe32.szExeFile);
        }
        CloseHandle(hSnap);
        return TRUE;
    }
    printf("[-]Process not found");
    return FALSE;
}

0x05 dll注入

dll加载:1.静态调用:通过在我们的程序中添加头文件,以及lib文件来完成调用,前提就是获取dll然后还有头文件 2.动态调用:仅仅只需要一个dll即可完成调用

这里写一个Test方法

#include 
__declspec(dllexport) void Test(){
    MessageBox(NULL, NULL, NULL, NULL);
}

可以看到有一些脏数据。这里可以协商约定来解决 1.__stdcall 标准 栈传参,函数内部(被调用者)平栈 2. __cdecl c 栈传参,函数外部(调用者)平栈 3. __fastcall 快速 寄存器传参 4. __thiscall 类的thiscall调用约定,使用ecx寄存器来传递this指针

extern "C"{
    __declspec(dllexport) void __stdcall Test(){
        MessageBox(NULL, NULL, NULL, NULL);
    }
}

__stdcall是函数内部平参可以举个例子

void __stdcall test(int n1, int n2){
    
    return;
}
int main()
{
    test(1, 2);
    return 0;
}

两个返回8一个返回4

void __stdcall test(int n1, int n2){
002013C0  push        ebp  
002013C1  mov         ebp,esp  
002013C3  sub         esp,0C0h  
002013C9  push        ebx  
002013CA  push        esi  
002013CB  push        edi  
002013CC  lea         edi,[ebp-0C0h]  
002013D2  mov         ecx,30h  
002013D7  mov         eax,0CCCCCCCCh  
002013DC  rep stos    dword ptr es:[edi]  
    
    return;
}
002013DE  pop         edi  
002013DF  pop         esi  
002013E0  pop         ebx  
002013E1  mov         esp,ebp  
002013E3  pop         ebp  
002013E4  ret         8

5.1 动态调用

这里使用动态调用

流程大概就是 1.在目标进程中申请内存 2.向目标进程内存中写入shellcode(没有特征,编码比较麻烦) 3.创建远线程执行shellcode

首先打开进程获取到进程句柄

hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, rProcessId);
if (hProcess == INVALID_HANDLE_VALUE) {
    return FALSE;
}

然后再要注入的进程中申请内存

VirtualAllocEx

LPVOID VirtualAllocEx(
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);

详情查看[VirtualAllocEx]:https://docs.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex?f1url=%3FappId%3DDev16IDEF1%26l%3DZH-CN%26k%3Dk(MEMORYAPI%252FVirtualAllocEx);k(VirtualAllocEx);k(DevLang-C%252B%252B);k(TargetOS-Windows)%26rd%3Dtrue 这里要用到可读可写权限PAGE_READWRITE。

pDllAddr = VirtualAllocEx(hProcess, NULL, strlen(szDllPath) + 1, MEM_COMMIT, PAGE_READWRITE);

然后再要注入的进程里面写入数据

WriteProcessMemory

BOOL WriteProcessMemory(
  HANDLE  hProcess,
  LPVOID  lpBaseAddress,
  LPCVOID lpBuffer,
  SIZE_T  nSize,
  SIZE_T  *lpNumberOfBytesWritten
);
WriteProcessMemory(hProcess, pDllAddr, szDllPath, strlen(szDllPath) + 1, NULL);

然后获取loadlibrary的地址后就通过CreateRemoteThread加载dll地址和函数地址来调用

pfnStartAddr = GetProcAddress(::GetModuleHandle("Kernel32"), "LoadLibraryA");

最后再使用CreateRemoteThread创建远线程,注入DLL

if ((hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pfnStartAddr, pDllAddr, 0, NULL)) == NULL)
    {
        std::cout << "Injecting thread failed!" << std::endl;
        return FALSE;
    }

InjectDll函数

BOOL InjectDll(int rProcessId, const char* szDllPath) {
    HANDLE hProcess = NULL;
    LPVOID pDllAddr = NULL;
    FARPROC pfnStartAddr = NULL;
    HANDLE hRemoteThread = NULL;
    //打开进程
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, rProcessId);
    if (hProcess == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
    //在要注入的进程中申请内存
    pDllAddr = VirtualAllocEx(hProcess, NULL, strlen(szDllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
    if (!pDllAddr)
    {
        return FALSE;
    }
    //给要注入的进程中写入数据
    WriteProcessMemory(hProcess, pDllAddr, szDllPath, strlen(szDllPath) + 1, NULL);
    //获取LoadLibraryA函数的地址
    pfnStartAddr = GetProcAddress(::GetModuleHandle("Kernel32"), "LoadLibraryA");
    //使用CreateRemoteThread创建远线程,注入DLL
    if ((hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pfnStartAddr, pDllAddr, 0, NULL)) == NULL)
    {
        std::cout << "Injecting thread failed!" << std::endl;
        return FALSE;
    }
    /*CloseHandle(hProcess);
    CloseHandle(hRemoteThread);*/
    return TRUE;
}

这里我们写一个程序加一个dll来验证

DemoDll.dll

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include 
#include 
#include 
#include 
int pid = 0;
CString str;
int GetCurrentID() {
    pid = _getpid();
    str.Format("%d", pid);
    return pid;
}
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        pid = GetCurrentID();
        MessageBox(NULL, str, "title", MB_OK);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Demo.exe

#include 
#include 
void main() {
    printf("test\r");
    system("pause");
}

运行Demo.exe,然后先遍历模块

dll注入

0x06 PPID欺骗

PPID欺骗允许使用任意进程启动程序

同理先判断父进程是否存在

DWORD GetParanetProcessID(char* ParanetProcessName) {
    BOOL Check = CheckPorcess(ParanetProcessName);
    if (Check) {
        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        PROCESSENTRY32 pe32 = { 0 };
        pe32.dwSize = sizeof(PROCESSENTRY32);
        BOOL bRet = Process32First(hSnap, &pe32);
        while (bRet)
        {
            Process32Next(hSnap, &pe32);
            if (strcmp(pe32.szExeFile, ParanetProcessName) == 0) {
                ppid = pe32.th32ProcessID;
                break;
            }
        }
        CloseHandle(hSnap);
    }
    return ppid;
}
void PpidSpoofing(char* ProcessAddr,DWORD ParaentPid) {
    STARTUPINFOEXA si = { 0 };
    si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
    PROCESS_INFORMATION pi = { 0 };
    SIZE_T SizeBuff;
    HANDLE parentProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, false, ParaentPid);
    ZeroMemory(&si, sizeof(STARTUPINFOEXA));
    InitializeProcThreadAttributeList(NULL, 1, 0, &SizeBuff);
    si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, SizeBuff);
    InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &SizeBuff);
    UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), NULL, NULL);
    BOOL bRet = CreateProcessA(ProcessAddr, NULL, NULL, NULL, TRUE, CREATE_NEW_CONSOLE | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, reinterpret_cast<LPSTARTUPINFOA>(&si), &pi);
    if (!bRet) {
        printf("ProcessAddr error!");
    }
    CloseHandle(parentProcessHandle);
}

首先我们要获取父进程的进程句柄然后为进程和线程创建初始化指定的属性列表使用InitializeProcThreadAttributeList。

BOOL InitializeProcThreadAttributeList(
  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  DWORD                        dwAttributeCount,
  DWORD                        dwFlags,
  PSIZE_T                      lpSize
);

最后一个参数指定输入时lpAttributeList缓冲区的大小。si.lpAttributeList在堆中分配一块内存,分配的大小为前面的SizeBuff。然后再使用InitializeProcThreadAttributeList初始化进程和线程的属性列表最后使用UpdateProcThreadAttribute函数来更新进程和线程的指定属性,最后创建我们的进程。


sizeofchar函数
本作品采用《CC 协议》,转载必须注明作者和本文链接
在二进制里面,每一位只要大于等于?则都要向高位进一。为了方便表示,还衍生出了二进制的子类,比如八进制,十六进制等,主要是二进制向这些进制转换较为容易,而计算机平时又都处理二进制数据,因此就出现了这些常见的进制计数。信息存储大多数计算机使用的都是8位的块,或者叫字节,字节是作为计算机可寻址的最小单位。一般来说我们并不习惯于将一个字节写成八位二进制的数,而是会写成两位十六进制的数。
HOOK技术实战
2021-10-19 05:55:56
对于Windows系统,它是建立在事件驱动机制上的,说白了就是整个系统都是通过消息传递实现的。hook(钩子)是一种特殊的消息处理机制,它可以监视系统或者进程中的各种事件消息,截获发往目标窗口的消息并进行处理。所以说,我们可以在系统中自定义钩子,用来监视系统中特定事件的发生,完成特定功能,如屏幕取词,监视日志,截获键盘、鼠标输入等等。
干货 | HOOK技术实战
2021-10-16 10:09:27
基础知识对于Windows系统,它是建立在事件驱动机制上的,说白了就是整个系统都是通过消息传递实现的。钩子可以分为线程钩子和系统钩子,线程钩子可以监视指定线程的事件消息,系统钩子监视系统中的所有线程的事件消息。当前钩子处理结束后应把钩子信息传递给下一个钩子函数。PE头是固定不变的,位于DOS头部中e_ifanew字段指出位置。
FartExt是我之前学习脱壳实践时做的一个自动脱壳机,是基于FART的主动调用思想实现对特定的抽取壳进行优化处理的工具。由于原本的FART没有配置相关的,所以我增加了配置对指定app脱壳。
PVOID buffer, ULONG length, LPCWSTR contentName, HAMSISESSION amsiSession,这样一个一个去把导出函数写出。不要去直接include系统文件amsi,这样他那个文件里本来就有相关函数,这样会起冲突,直接把有些结构体粘过来就好了。
在一些开启了 Hyper-V 的电脑上,RouterOS 可能无法在 VMWare Workstation 中模拟运行或启动非常缓慢,如果遇到无法运行的情况,请酌情考虑关闭 Hyper-V,如果能成功运行但是启动缓慢,可以及时拍摄快照。
ETW的攻与防
2022-06-07 16:11:58
前言ETW全称为Event Tracing for Windows,即windows事件跟踪,它是Windows提供的原生的事件跟踪日志系统。ETW Provider会预先注册到ETW框架上,提供者程序在某个时刻触发事件,并将标准化定义的事件提供给ETW框架
本文更多的是根据调试Windows Server 2003,分析漏洞成因。但是AD域并没有对其进行强校验。通过建立与域控同名却不以\$结尾的机器账户,即DC,对域控进行欺骗。至此便得到了高权限ST。从上图中可以很明确的看到域控的机器名为WINSRVSERVER$,之后会使用WINSRVSERVER作为机器账户名进行欺骗。攻击准备工作相关准备工作不是本文重点,可以在noPac项目中学习。
如何调包Win32API函数?其实就是HookPE文件自己的IAT表。
依赖于特定硬件环境的固件无法完整模拟,需要hook掉其中依赖于硬件的函数。LD_PRELOAD的劫持对于特定函数的劫持技术分为动态注入劫持和静态注入劫持两种。网上针对LD_PRELOAD的劫持也有大量的描述
VSole
网络安全专家