初步分析
分析到一个E语言的外挂, 但是说的是过检测的外挂, 分析一下, 直接ida一把梭大概看到有用的信息, 内部测试3-A.exe explorer.exe
打开外挂后看到的功能大概有这几个, 加载过检, 开始过检, 伪装进程, 隐藏进程这几个, 大概就能猜到肯定用到了驱动
跑起来后, 第一个功能是开始过检测, 发现修改了R3层的应用层访问的状态为拒绝
直接打开process explorer, 可以发现进程的Protection属性变成了PsProtectedSignerWinTcb-Light, 这里的保护和今年的腾讯游戏安全初赛的那个是一样的PPL
直接windbg看一下进程的EPROCESS结构, 可以看到Protection为0x61 -> 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;
直接把这个位置修改为0即可
CmRegisterCallback
直接开始分析一下这个东西具体都干什么了吧, 可以打开了解到这个是一个E语言的东西, 并且他一定是有一个通信码在和什么东西通信的, 因为有一个点击事件, 直接ida开始分析, 打开后直接可以看到了一个地址,R3反正也是通信做的, 大部分功能应该是在驱动里,直接去驱动分析看一下
发现直接download可以的, 发现这个东西在疯狂套娃, ExAllocatePoolWithTag后创建一个系统线程, 线程的入口点就是申请的内存的位置, 由这个sys中藏的一个PE的oep的位置开始执行
直接双机调试开始搞一下, 还是老地方ioploaddriver, 直接下断释放的函数可以看到这个位置是一个PE
在StartRoutine下断看一下主要在做什么
经过分析后, 可以分析到它通过设置CmRegisterCallback注册表回调, 把代码放在这里开始执行用于隐藏自己的驱动代码
分析BE的时候发现实际上是有对这种情况进行检测的, BE中分别对进程线程回调, 以及注册表回调判断回调函数的其实位置是不是被hook, 是不是jmp reg的跳转来判断是不是有问题
直接打开BE逆向发现他取了CmUnRegisterCallback的特征, 因为这里存有相应的表, 里面存放了相对的地址和cookie
lea rdx, [rsp+38h]
lea rcx, [ntkrnlmp!CallbackListHead (fffff8015ea60920)]
直接跑通, 写个代码测试一下可不可以找到相应的位置
#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过去的地址是无模块地址
相应的BE的检测的伪代码
开始过检按钮
相应的回调的位置的check已经可以搞定, 继续分析点击过检测的时候一定会和驱动层进行通信, 直接在这个位置下断即可
点击开始过检, 可以知道流程为控制码0822 -> 0814, 调试可以知道这里通过获取到Protection的offset获取的分别是自己本身的程序和smss.exe的Protection属性进行了交换
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
因为smss.exe的EPROCESS他获取了后, 可以拿到该进程的保护属性和父进程的pid, 第二个操作就是交换了父进程的pid, 可以看到InheritedFromUniqueProcessId等于了4
取消就是explorer.exe的ParentCid和Protection赋值给了该被保护的程序
伪装进程按钮
获取自身和另一个进程的名字, 把名字复制到被保护的进程里
可以看到名字已经被修改成了explorer.exe
主要这个模块处理了很多peb里的信息这里省略一下有点多, github有一个比较好的项目很像这个模块https://github.com/zhuhuibeishadiao/PathModification
进程隐藏按钮
发现比较有趣的事情, 好像ydark还是可以扫描到这个被隐藏的进程, 但是任务管理器的没了
看一下他是怎么做的, 他直接把+0x2e8 UniqueProcessId : (null)清空了, 所以任务管理器看不见
但是ydark为什么能看见, 研究了一下是通过句柄表来做的进程的遍历, 可以使用PspCidTable来做这个事情
遍历PspCidTable枚举进程
PspCidTable这个位置需要通过特征码匹配来看一下是什么值
通过PsLookupProcessByProcessId的E8 call获取到地址后, 直接dq看一下, 这里大概说一下思路和找寻的方法, 代码就不贴了
dq pspcidtable获取了表的地址后, 实际上的类型是_HANDLE_TABLE类型, 可以看到tablecode的低两位代表句柄表的层数, 最多有三层
随便进一个看一下, 可以看到是加密的情况
win10的解密是>> 0x10 & 0xfffffffffffffff0
解密后看一下EPROCESS, 发现是第一个的system
通过这个就可以遍历出id和对应的object, 但是如果要遍历进程的, 还要进行筛选需要知道一个东西叫做OBJECT_TYPE和OBJECT_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
这里的解密可以看一下ObGetObjectType
那么实际上的解密就是0xfa ^ 0x8f ^ 0x72 = 0x7
按照这个思路写的代码效果如下: