初步分析

分析到一个E语言的外挂, 但是说的是过检测的外挂, 分析一下, 直接ida一把梭大概看到有用的信息, 内部测试3-A.exe explorer.exe alt text 打开外挂后看到的功能大概有这几个, 加载过检, 开始过检, 伪装进程, 隐藏进程这几个, 大概就能猜到肯定用到了驱动 alt text 跑起来后, 第一个功能是开始过检测, 发现修改了R3层的应用层访问的状态为拒绝 alt text 直接打开process explorer, 可以发现进程的Protection属性变成了PsProtectedSignerWinTcb-Light, 这里的保护和今年的腾讯游戏安全初赛的那个是一样的PPL alt text 直接windbg看一下进程的EPROCESS结构, 可以看到Protection0x61 -> 0110 0001b, 可以看到修改的为PsProtectedTypeProtectedLight,PsProtectedSignerWinTcb

//进程保护类型
typedef enum _PS_PROTECTED_TYPE {
    PsProtectedTypeNone = 0,
    PsProtectedTypeProtectedLight = 1,
    PsProtectedTypeProtected = 2
} PS_PROTECTED_TYPE, *PPS_PROTECTED_TYPE;

//Audit 审计标志(很少使用)

//Signer 签名了进程
typedef enum _PS_PROTECTED_SIGNER {
    PsProtectedSignerNone = 0,
    PsProtectedSignerAuthenticode,
    PsProtectedSignerCodeGen,
    PsProtectedSignerAntimalware,
    PsProtectedSignerLsa,
    PsProtectedSignerWindows,
    PsProtectedSignerWinTcb,
    PsProtectedSignerWinSystem,
    PsProtectedSignerApp,
    PsProtectedSignerMax
} PS_PROTECTED_SIGNER, *PPS_PROTECTED_SIGNER;

alt text 直接把这个位置修改为0即可

CmRegisterCallback

alt text 直接开始分析一下这个东西具体都干什么了吧, 可以打开了解到这个是一个E语言的东西, 并且他一定是有一个通信码在和什么东西通信的, 因为有一个点击事件, 直接ida开始分析, 打开后直接可以看到了一个地址,R3反正也是通信做的, 大部分功能应该是在驱动里,直接去驱动分析看一下 alt text 发现直接download可以的, 发现这个东西在疯狂套娃, ExAllocatePoolWithTag后创建一个系统线程, 线程的入口点就是申请的内存的位置, 由这个sys中藏的一个PEoep的位置开始执行 alt text 直接双机调试开始搞一下, 还是老地方ioploaddriver, 直接下断释放的函数可以看到这个位置是一个PE alt textStartRoutine下断看一下主要在做什么 alt text 经过分析后, 可以分析到它通过设置CmRegisterCallback注册表回调, 把代码放在这里开始执行用于隐藏自己的驱动代码 alt text 分析BE的时候发现实际上是有对这种情况进行检测的, BE中分别对进程线程回调, 以及注册表回调判断回调函数的其实位置是不是被hook, 是不是jmp reg的跳转来判断是不是有问题 alt text 直接打开BE逆向发现他取了CmUnRegisterCallback的特征, 因为这里存有相应的表, 里面存放了相对的地址和cookie

lea     rdx, [rsp+38h]
lea     rcx, [ntkrnlmp!CallbackListHead (fffff8015ea60920)]

alt text 直接跑通, 写个代码测试一下可不可以找到相应的位置

#include <ntddk.h>

PVOID GetCmCallbackListHead()
{
    UNICODE_STRING ustr;
    RtlInitUnicodeString(&ustr, L"CmUnRegisterCallback");
    PUCHAR pBase = (PUCHAR)MmGetSystemRoutineAddress(&ustr);
    if (!pBase) return NULL;

    for (PUCHAR p = pBase; p < pBase + 0x1000; ++p)
    {
        if (!MmIsAddressValid(p) || !MmIsAddressValid(p + 6))
            continue;

        if (p[0] == 0x48 && p[1] == 0x8D && p[2] == 0x0D &&
            p[-5] == 0x48 && p[-4] == 0x8D && p[-3] == 0x54)
        {
            INT32 disp = *(INT32*)(p + 3);
            UINT64 off = 0;
            if (disp <= 0)
                off = (UINT32)disp | 0xFFFFFFFF00000000ull;
            else
                off = (UINT32)disp;

            PUCHAR pListHead = p + off + 7;
            if (MmIsAddressValid(pListHead))
                return pListHead;
        }
    }
    return NULL;
}

extern USHORT NtBuildNumber;

void DumpCmRegisterCallback()
{
    PVOID pListHead = GetCmCallbackListHead();

    //DbgBreakPoint();

    if (!pListHead)
    {
        DbgPrint("[-] CallbackListHead not found!\n");
        return;
    }

    char* Head = (char*)pListHead;
    char* Entry = *(char**)Head;
    int idx = 0;
    ULONG_PTR Offset = 8;  // Win7 SP1及以上偏移
    if (NtBuildNumber <= 7600)
        Offset = 0;        // Win7 RTM及以下偏移

    while (Entry && Entry != Head && MmIsAddressValid(Entry))
    {
        char* p = Entry + Offset;
        ULONGLONG CallbackAddr = *((ULONGLONG*)p + 5);
        ULONGLONG Cookie = *((ULONGLONG*)p + 3);

        if (CallbackAddr >= 0x8000000000000000ull && MmIsAddressValid((PVOID)CallbackAddr))
            DbgPrint("[CMCallback %d] Callback: %p Cookie: %llx\n", idx, (PVOID)CallbackAddr, Cookie);

        Entry = *(char**)Entry;
        if (++idx > 64) break; // 防止死循环
    }
}

VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
    DbgPrint("Unload called!\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
    DbgPrint("Driver Loaded\n");
    pDriver->DriverUnload = DriverUnload;

    //DbgBreakPoint();
    DumpCmRegisterCallback();
    return STATUS_SUCCESS;
}

可以找到相应的位置, 这里的check的就是模块的头部是jmp reg, 并且reg过去的地址是无模块地址 alt text alt text 相应的BE的检测的伪代码 alt text alt text

开始过检按钮

相应的回调的位置的check已经可以搞定, 继续分析点击过检测的时候一定会和驱动层进行通信, 直接在这个位置下断即可 alt text 点击开始过检, 可以知道流程为控制码0822 -> 0814, 调试可以知道这里通过获取到Protectionoffset获取的分别是自己本身的程序和smss.exeProtection属性进行了交换

PROCESS ffffc081d05ab080
    SessionId: 1  Cid: 1010    Peb: e9f5789000  ParentCid: 0e34
    DirBase: 1406a002  ObjectTable: ffff9d8e2a8f2c80  HandleCount:  47.
    Image: A.exe

PROCESS ffffc081cd7634c0
    SessionId: none  Cid: 0208    Peb: 843a07b000  ParentCid: 0004
    DirBase: 136b7b002  ObjectTable: ffff9d8e1f3768c0  HandleCount:  53.
    Image: smss.exe

alt text alt text 因为smss.exeEPROCESS他获取了后, 可以拿到该进程的保护属性和父进程的pid, 第二个操作就是交换了父进程的pid, 可以看到InheritedFromUniqueProcessId等于了4 alt text 取消就是explorer.exeParentCidProtection赋值给了该被保护的程序 alt text

伪装进程按钮

获取自身和另一个进程的名字, 把名字复制到被保护的进程里 alt text alt text 可以看到名字已经被修改成了explorer.exe alt text 主要这个模块处理了很多peb里的信息这里省略一下有点多, github有一个比较好的项目很像这个模块https://github.com/zhuhuibeishadiao/PathModification

alt text

进程隐藏按钮

发现比较有趣的事情, 好像ydark还是可以扫描到这个被隐藏的进程, 但是任务管理器的没了 alt text 看一下他是怎么做的, 他直接把+0x2e8 UniqueProcessId : (null)清空了, 所以任务管理器看不见 alt text alt text 但是ydark为什么能看见, 研究了一下是通过句柄表来做的进程的遍历, 可以使用PspCidTable来做这个事情

遍历PspCidTable枚举进程

PspCidTable这个位置需要通过特征码匹配来看一下是什么值 alt text alt text 通过PsLookupProcessByProcessIdE8 call获取到地址后, 直接dq看一下, 这里大概说一下思路和找寻的方法, 代码就不贴了 alt text dq pspcidtable获取了表的地址后, 实际上的类型是_HANDLE_TABLE类型, 可以看到tablecode的低两位代表句柄表的层数, 最多有三层 alt text 随便进一个看一下, 可以看到是加密的情况 alt text win10的解密是>> 0x10 & 0xfffffffffffffff0 alt text 解密后看一下EPROCESS, 发现是第一个的system alt text 通过这个就可以遍历出id和对应的object, 但是如果要遍历进程的, 还要进行筛选需要知道一个东西叫做OBJECT_TYPEOBJECT_HEADER, 实际上OBJECT_HEADER是在EPROCESS结构上面的

kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int8B
   +0x008 HandleCount      : Int8B
   +0x008 NextToFree       : Ptr64 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : UChar
   +0x019 TraceFlags       : UChar
   +0x019 DbgRefTrace      : Pos 0, 1 Bit
   +0x019 DbgTracePermanent : Pos 1, 1 Bit
   +0x01a InfoMask         : UChar
   +0x01b Flags            : UChar
   +0x01b NewObject        : Pos 0, 1 Bit
   +0x01b KernelObject     : Pos 1, 1 Bit
   +0x01b KernelOnlyAccess : Pos 2, 1 Bit
   +0x01b ExclusiveObject  : Pos 3, 1 Bit
   +0x01b PermanentObject  : Pos 4, 1 Bit
   +0x01b DefaultSecurityQuota : Pos 5, 1 Bit
   +0x01b SingleHandleEntry : Pos 6, 1 Bit
   +0x01b DeletedInline    : Pos 7, 1 Bit
   +0x01c Reserved         : Uint4B
   +0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : Ptr64 Void
   +0x028 SecurityDescriptor : Ptr64 Void
   +0x030 Body             : _QUAD

那么windbg看一下这个结构, 判断类型主要的还是这个TypeIndex alt text 这里的解密可以看一下ObGetObjectType alt text 那么实际上的解密就是0xfa ^ 0x8f ^ 0x72 = 0x7 alt text 按照这个思路写的代码效果如下: alt text