初步分析
分析到一个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
按照这个思路写的代码效果如下: