首页
社区
课程
招聘
[分享]手动循环句柄降权保护
发表于: 2026-3-6 16:06 794

[分享]手动循环句柄降权保护

2026-3-6 16:06
794

前言

不同于callback句柄降权保护, 前者是触发指定函数后执行回调进行降权, 后者则是对系统所有进程的句柄表进行扫描, 如命中被保护程序则直接进行降权, 并且可循环定期执行

第一步: 写遍历所有进程回调

此方法为遍历所有进程, 并接受一个callback方法基于eprocess

//遍历所有EPROCESS
void TraversalEprocess(UINT_PTR CallBackFun, ULONG64 ProtectPID)
{
    //获取当前进程
    PEPROCESS CurrentProcess = PsGetCurrentProcess();
    //获取ActiveProcessLinks活动进程链表
    PLIST_ENTRY Head = (PLIST_ENTRY)((PUCHAR)CurrentProcess + 0x2f0);
    //临时链表头
    PLIST_ENTRY Entry = Head;
    //跳过自己
    Entry = Entry->Flink;
    //do while 循环遍历链表
    do {
        //回到EPROCESS头
        PEPROCESS Process = (PEPROCESS)((PUCHAR)Entry - 0x2f0);
        //执行回调
        TraversalEprocessCallBack callback = (TraversalEprocessCallBack)CallBackFun;
        callback(Process, ProtectPID);
        
        Entry = Entry->Flink;
    } while (Entry != Head);//不等于表头则继续循环
    return FALSE;
}

第二步: 根据eprocess遍历句柄表

根据传入eprocess遍历其句柄表, 目前只实现了一二级句柄表, 因为三级很少用到就不做测试了

void TraverseHandleTable(UINT_PTR CurrentEprocess, PVOID CallBackFun, ULONG64 ProtectPID)
{
    //根据EPROCESS获取pid
    HANDLE pid = GetPidByEprocess(CurrentEprocess);
    //若pid为负数则违规 直接跳过
    if (IsNegative(pid)) return;
    //输出信息
    CHAR imageName[16] = { 0 };
    GetImageNameByEProcess(CurrentEprocess, imageName);
    PZY_PRINT("================ 开始遍历 [%s][%d] 句柄表 ================", imageName, pid);

    //获取句柄表
    HANDLE_TABLE* v_HANDLE_TABLE = (HANDLE_TABLE*)poi(CurrentEprocess + 0x418);//这里+0x418直接取硬编码 比较方便 但是不同系统可能不一致
    //如果为0则返回
    if (!v_HANDLE_TABLE) return;
    UINT_PTR TableCode = v_HANDLE_TABLE->TableCode;

    UINT64 level = TableCode & 0x3;
    UINT64 TableCodeBase = TableCode & ~3;

    if (level == 0)
    {
        HANDLE_TABLE_ENTRY* v_HANDLE_TABLE_ENTRY = TableCodeBase;
        for (int i = 0;i < 0x100;i++)
        {
            if (v_HANDLE_TABLE_ENTRY->InfoTable)
            {
                UINT64 handleCode = i * 4;
                TraverseHandleTableXx(CurrentEprocess, handleCode, v_HANDLE_TABLE_ENTRY, CallBackFun, ProtectPID);
            }
            v_HANDLE_TABLE_ENTRY += 1;
        }
    }
    else if (level == 1)
    {
        UINT_PTR v_HANDLE_TABLE_ENTRY1 = TableCodeBase;
        for (int i = 0;i < 0x100;i++)
        {
            v_HANDLE_TABLE_ENTRY1 += i * 0x8;
            HANDLE_TABLE_ENTRY* v_HANDLE_TABLE_ENTRY = poi(v_HANDLE_TABLE_ENTRY1);
            if (!v_HANDLE_TABLE_ENTRY)
            {
                break;
            }
            for (int j = 0;j < 0x100;j++)
            {
                if (v_HANDLE_TABLE_ENTRY->InfoTable)
                {
                    UINT64 handleCode = j * 4 + 0x100 * i;
                    TraverseHandleTableXx(CurrentEprocess, handleCode, v_HANDLE_TABLE_ENTRY, CallBackFun, ProtectPID);
                }
                v_HANDLE_TABLE_ENTRY += 1;
            }
        }
    }
    else if (level == 2)
    {
        DbgBreakPoint();
        for (int i = 0;i < 0x100;i++)
        {
            for (int j = 0;j < 0x100;j++)
            {
                for (int k = 0;k < 0x100;k++)
                {

                }
            }
        }
        return;
    }
    return;
}

第三步: 详细判断处理

这里依旧是使用了回调转发从TraverseHandleTableXx转入了TraverseHandleTableCallBackFun, TraverseHandleTableXx为纯转发不做任何处理就不贴出来了详细处理逻辑为对比进程句柄表中的每个进程是否和保护进程一致, 如一致则进行降权

//回调处理
void TraverseHandleTableCallBackFun(UINT_PTR CurrentEprocess, HANDLE_TABLE_ENTRY* v_HANDLE_TABLE_ENTRY,ULONG64 ProtectPID)
{
    //解析句柄表的InfoTable得到句柄对象
    UINT_PTR HandleEprocess = GetHandleObjectByHandleTableEntry(v_HANDLE_TABLE_ENTRY);
    if (!HandleEprocess) return;


    //获取受保护pid的EPROCESS
    UINT_PTR ProtectEprocess;
    if (GetEprocessByPid(ProtectPID, &ProtectEprocess) != STATUS_SUCCESS) return;

    //判断是否是要保护的程序
    if (HandleEprocess == ProtectEprocess)
    {
        //DbgBreakPoint();
        //获取当前进程pid
        HANDLE CurrentPid = GetPidByEprocess(CurrentEprocess);
        if (IsNegative(CurrentPid)) return;
        //获取当前进程名
        CHAR CurrentImageName[16] = { 0 };
        GetImageNameByEProcess(CurrentEprocess, &CurrentImageName);

        //获取受保护pid进程名
        CHAR ProtectImageName[16] = { 0 };
        GetImageNameByEProcess(ProtectEprocess, &ProtectImageName);

        //降权测试
        UINT32 OldAttributes = v_HANDLE_TABLE_ENTRY->Attributes;
        v_HANDLE_TABLE_ENTRY->Attributes = 0;
        //打印输出
        PZY_PRINT("命中 [%d][%s] 拥有受保护进程 [%d][%s] 句柄, 执行降权处理", 
            CurrentPid, CurrentImageName, ProtectPID, ProtectImageName);
    }
    return;
}

测试

开启Cheat Engine并在Cheat Engine中打开MFCApplication1进行测试, MFCApplication1为保护程序 图片描述开始: 进入遍历进程方法 图片描述触发回调 图片描述进入遍历句柄表 图片描述句柄较少 进入了一级句柄表分支 图片描述继续进入下一个回调 图片描述命中回调, 降权处理 图片描述ce搜索 图片描述但下一秒马上失效 图片描述旧句柄已完全失效, 需要ce重新加载句柄 图片描述查看日志可以发现其对cheatengine-x8进行了降权处理, 同样还有System \ csrss.exe \ svchost.exe 等含有 MFCApplication 句柄的程序也进行了处理 图片描述测试 图片描述

总结

此保护对于反作弊软件属于基操级别, 本次测试为最小化方式实现循环句柄降权, 但对于不走系统函数直接通过cr3读写内存的方式无效, 后续也会涉及如何防止通过cr3读写内存的保护, 欢迎讨论 如有错漏 请留言


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 4天前 被mb_binusgki编辑 ,原因: 补充内容
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回