其他
通过对PsSetCreateProcessNotifyRoutineEx的逆向分析得出的结果来实现反进程监控
本文为看雪论坛优秀文章
看雪论坛作者ID:1900
1
前言
2
逆向分析PsSetCreateProcessNotifyRoutine
PAGE:005947DE ; NTSTATUS __stdcall PsSetCreateProcessNotifyRoutine(PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, BOOLEAN Remove)
PAGE:005947DE public PsSetCreateProcessNotifyRoutine
PAGE:005947DE PsSetCreateProcessNotifyRoutine proc near
PAGE:005947DE ; CODE XREF: sub_6FC83F+2B↓p
PAGE:005947DE ; sub_798D02+22↓p
PAGE:005947DE
PAGE:005947DE NotifyRoutine = dword ptr 8
PAGE:005947DE Remove = byte ptr 0Ch
PAGE:005947DE
PAGE:005947DE mov edi, edi
PAGE:005947E0 push ebp
PAGE:005947E1 mov ebp, esp
PAGE:005947E3 push 0 ; 将0压入栈中
PAGE:005947E5 push dword ptr [ebp+Remove] ; 将Remove压入栈中
PAGE:005947E8 push [ebp+NotifyRoutine] ; 将Routine压入栈中
PAGE:005947EB call PspSetCreateProcessNotifyRoutine
PAGE:005947F0 pop ebp
PAGE:005947F1 retn 8
PAGE:005947F1 PsSetCreateProcessNotifyRoutine endp
PspSetCreateProcessNotifyRoutine proc near
PAGE:005947F9 ; CODE XREF: PsSetCreateProcessNotifyRoutineEx+D↑p
PAGE:005947F9 ; PsSetCreateProcessNotifyRoutine+D↑p
PAGE:005947F9
PAGE:005947F9 arg_Routine = dword ptr 8
PAGE:005947F9 arg_Remove = dword ptr 0Ch
PAGE:005947F9 arg_Zero = byte ptr 10h
PAGE:005947F9
PAGE:005947F9 mov edi, edi
PAGE:005947FB push ebp
PAGE:005947FC mov ebp, esp
PAGE:005947FE cmp byte ptr [ebp+arg_Remove], 0 ; 判断Remove是否为0
PAGE:00594802 push ebx
PAGE:00594803 push esi
PAGE:00594804 push edi
PAGE:00594805 jz loc_594905 ; 为0则跳转,当要增加回调的时候,会传入FALSE,所以这里会跳转
PAGE:00594905 loc_594905: ; CODE XREF: PspSetCreateProcessNotifyRoutine+C↑j
PAGE:00594905 cmp [ebp+arg_Zero], 0 ; 判断第三个参数是否为0
PAGE:00594909 jz short loc_59491E
PAGE:0059490B push [ebp+arg_Routine]
PAGE:0059490E call sub_56F869
PAGE:00594913 test eax, eax
PAGE:00594915 jnz short loc_59491E
PAGE:00594917 mov eax, STATUS_ACCESS_DENIED
PAGE:0059491C jmp short loc_59496B
PAGE:0059491E loc_59491E: ; CODE XREF: PspSetCreateProcessNotifyRoutine+110↑j
PAGE:0059491E ; PspSetCreateProcessNotifyRoutine+11C↑j
PAGE:0059491E xor eax, eax ; 将eax清0
PAGE:00594920 cmp [ebp+arg_Zero], al ; 将传入的第三个参数与al做比较
PAGE:00594923 setnz al ; 将zf位取反然后赋值给al
PAGE:00594926 push eax ; 将eax入栈
PAGE:00594927 push [ebp+arg_Routine] ; 将要设置的回调函数地址入栈
PAGE:0059492A call AllocateAssign
PAGE:0059492F mov ebx, eax ; 将分配的内存的地址赋给ebx
PAGE:00594931 test ebx, ebx ; 判断ebx是否为0
PAGE:00594931 ; 为0则为eax赋值内存不够的返回值并跳到函数结束
PAGE:00594933 jnz short loc_59493C
PAGE:00594935 mov eax, STATUS_INSUFFICIENT_RESOURCES
PAGE:0059493A jmp short loc_59496B
PAGE:005946D7 AllocateAssign proc near ; CODE XREF: PsSetLoadImageNotifyRoutine+E↑p
PAGE:005946D7 ; DbgkLkmdRegisterCallback+53↑p ...
PAGE:005946D7
PAGE:005946D7 arg_NotifyRoutine= dword ptr 8
PAGE:005946D7 arg_Zero = dword ptr 0Ch
PAGE:005946D7
PAGE:005946D7 mov edi, edi
PAGE:005946D9 push ebp
PAGE:005946DA mov ebp, esp
PAGE:005946DC push 'brbC' ; Tag
PAGE:005946E1 push 0Ch ; NumberOfBytes
PAGE:005946E3 push PagedPool ; PoolType
PAGE:005946E5 call ExAllocatePoolWithTag
PAGE:005946EA test eax, eax
PAGE:005946EC jz short loc_5946FD
PAGE:005946EE mov ecx, [ebp+arg_NotifyRoutine]
PAGE:005946F1 and dword ptr [eax], 0 ; 分配到的内存低4位清0
PAGE:005946F4 mov [eax+4], ecx ; 分配到的内存的中间4位赋值为函数地址
PAGE:005946F7 mov ecx, [ebp+arg_Zero]
PAGE:005946FA mov [eax+8], ecx ; 分配到的内存的最高4位赋值为传入的参数,此时为0
PAGE:005946FD
PAGE:005946FD loc_5946FD: ; CODE XREF: AllocateAssign+15↑j
PAGE:005946FD pop ebp
PAGE:005946FE retn 8
PAGE:005946FE AllocateAssign endp
PAGE:0059493C loc_59493C: ; CODE XREF: PspSetCreateProcessNotifyRoutine+13A↑j
PAGE:0059493C mov esi, offset ProcessFuncArray ; 将进程数组地址赋给esi
PAGE:00594941 xor edi, edi ; 为edi清0
PAGE:00594943
PAGE:00594943 loc_594943: ; CODE XREF: PspSetCreateProcessNotifyRoutine+165↓j
PAGE:00594943 push 0 ; 将0压入栈中
PAGE:00594945 mov ecx, ebx ; 分配到的内存的地址赋值给ecx
PAGE:00594947 mov eax, esi ; 数组地址赋值给eax
PAGE:00594949 call SetArray
PAGE:0059494E test al, al
PAGE:00594950 jnz short loc_594972 ; 函数返回值不为0,说明设置成功
PAGE:00594952 add edi, 4 ; 将edi加4
PAGE:00594955 add esi, 4 ; 数组的地址加4,也就是获得下一个元素的地址
PAGE:00594958 cmp edi, 100h ; 判断是否小于0x100,小于的话。使用新地址来调用上面的函数继续设置
PAGE:0059495E jb short loc_594943 ; 这里可以得出结论,这个数组一共0x100 % 4 = 0x40
PAGE:0059495E ; 也就是说,最多可以设置64个进程创建的回调函数
PAGE:00594960 push ebx ; Buffer
PAGE:00594961 call FreeAllocate
PAGE:00594966 mov eax, STATUS_INVALID_PARAMETER ; 如果执行到这里说明赋值失败
PAGE:00594966 ; 接下来就释放内存,返回值设为失败
PAGE:0059496B
PAGE:0059496B loc_59496B: ; CODE XREF: PspSetCreateProcessNotifyRoutine+A8↑j
PAGE:0059496B ; PspSetCreateProcessNotifyRoutine+10A↑j ...
PAGE:0059496B pop edi
PAGE:0059496C pop esi
PAGE:0059496D pop ebx
PAGE:0059496E pop ebp
PAGE:0059496F retn 0Ch
PAGE:00594706 SetArray proc near ; CODE XREF: IoRegisterPriorityCallback+5B↑p
PAGE:00594706 ; IoUnregisterPriorityCallback+4C↑p ...
PAGE:00594706
PAGE:00594706 var_8 = dword ptr -8
PAGE:00594706 var_4 = dword ptr -4
PAGE:00594706 arg_Zero = dword ptr 8
PAGE:00594706
PAGE:00594706 mov edi, edi
PAGE:00594708 push ebp
PAGE:00594709 mov ebp, esp
PAGE:0059470B push ecx ; 此时ecx是分配的地址,eax是数组的地址
PAGE:0059470C push ecx
PAGE:0059470D push ebx
PAGE:0059470E push esi
PAGE:0059470F push edi
PAGE:00594710 mov esi, eax ; 将数组地址赋给esi
PAGE:00594712 test ecx, ecx ; 判断分配地址是否为0
PAGE:00594714 jz short loc_594726
PAGE:00594716 push 8
PAGE:00594718 pop edx
PAGE:00594719 call ExAcquireRundownProtectionEx ; 为了让调用者安全访问对象,这里不是重点,过
PAGE:0059471E test al, al
PAGE:00594720 jz loc_5947D0 ; 返回为0就代表函数运行失败,会退出函数
PAGE:00594726
PAGE:00594726 loc_594726: ; CODE XREF: SetArray+E↑j
PAGE:00594726 mov ebx, [esi] ; 将数组中的内容取出赋给ebx
PAGE:00594728 mov eax, ebx ; 将内容赋给eax
PAGE:0059472A jmp short loc_594749
PAGE:00594749 loc_594749: ; CODE XREF: SetArray+24↑j
PAGE:00594749 xor eax, [ebp+arg_Zero] ; 将得到的值与0进行异或,与7做比较,小于7则跳转.
PAGE:0059474C cmp eax, 7 ; 所以这两条合起来看的话,就是除了低3位,其他都为0,否则就跳转
PAGE:0059474F jbe short loc_59472C ; 可以得出结论,数组中元素高29位用来判断是否还未使用。
PAGE:0059474F ; 如果为0,说明还未使用
PAGE:0059472C loc_59472C: ; CODE XREF: SetArray+49↓j
PAGE:0059472C test ecx, ecx ; ecx是申请的地址,判断是否为0,为0则跳转
PAGE:0059472E jz short loc_594737
PAGE:00594730 mov eax, ecx ; 将ecx赋给eax
PAGE:00594732 or eax, 7 ; 将低三位置1,这里要注意,在将申请到的内存地址复制到数组之前,会将地址的低3位置1
PAGE:00594735 jmp short loc_594739
PAGE:00594737 ; ---------------------------------------------------------------------------
PAGE:00594737
PAGE:00594737 loc_594737: ; CODE XREF: SetArray+28↑j
PAGE:00594737 xor eax, eax ; 如果ecx是0,那就会跳转到这里执行,也就是eax会被变成0
PAGE:00594739
PAGE:00594739 loc_594739: ; CODE XREF: SetArray+2F↑j
PAGE:00594739 mov edx, eax ; 将或运算得到的值赋给edx
PAGE:0059473B mov edi, esi ; 取出数组对应元素地址给edi
PAGE:0059473D mov eax, ebx ; ebx保存的是数组中的内容,赋给eax
PAGE:0059473F lock cmpxchg [edi], edx ; edi是数组地址,而eax是数组元素,所以eax==[edi]
PAGE:0059473F ; 那么此时,edx,也就是计算以后的地址会赋值给[edi],也就是赋值到数组中
PAGE:00594743 cmp eax, ebx ; 由于上面的赋值操作,这里会成立跳转
PAGE:00594745 jz short loc_594751
PAGE:00594747 mov ebx, eax
PAGE:00594751 loc_594751: ; CODE XREF: SetArray+3F↑j
PAGE:00594751 mov edi, ebx ; ebx中是数组中原来的内容,将他赋值给edi
PAGE:00594753 and edi, 0FFFFFFF8h ; 将低3位清0
PAGE:00594756 cmp edi, [ebp+arg_Zero] ; 判断是否为0,不为0则跳转退出函数,函数执行失败,返回值为0
PAGE:00594759 jnz short loc_5947C4 ; 这里就是判断原来的高29位是不是0,不是的话,函数执行失败
PAGE:0059475B test edi, edi ;判断edi是否位0
PAGE:0059475D jz short loc_5947C0 ; 为0则跳转,函数执行成功,返回值eax=1
PAGE:0059475F mov esi, large fs:124h ; 接下来的内容和APC相关,这里不讨论
PAGE:00594766 dec word ptr [esi+86h]
PAGE:0059476D xchg eax, [ebp+var_4]
PAGE:00594770 mov ecx, dword_53C7D8
PAGE:00594776 and ecx, 1
PAGE:00594779 xchg eax, [ebp+var_8]
PAGE:0059477C test ecx, ecx
PAGE:0059477E jz short loc_594794
PAGE:00594780 mov ecx, offset dword_53C7D8
PAGE:00594785 call ExfAcquirePushLockExclusive
PAGE:0059478A mov ecx, offset dword_53C7D8
PAGE:0059478F call ExfReleasePushLockExclusive
程序会申请一块0xC大小的内存,内存中低4为是0,中间4为是要设置的回调函数的地址,高4为根据是否是Ex函数来设置为是0还是1。
从整型数组ProcessArray中按顺序依次查看对应的数组的元素的位置是不是可以用来保存分配的内存地址。
而这个位置能不能存储这个地址,取决于这个位置中保存的内容的高29位是不是0,如果是0就用来保存申请到的地址。
3
反进程监控的实现
PUINT32 SearchProcessArray()
{
UNICODE_STRING uStrFuncName;
PUCHAR pPsSetCreateProessNotifyRoutine = NULL;
PUCHAR pPspCreateProcessNotifyRoutine = NULL;
PUINT32 pProcessArray = NULL;
//获取PsSetCreateProcessNotifyRoutine函数地址
RtlInitUnicodeString(&uStrFuncName, L"PsSetCreateProcessNotifyRoutine");
pPsSetCreateProessNotifyRoutine = (PUCHAR)MmGetSystemRoutineAddress(&uStrFuncName);
//获取PspCreateProcessNotifyRoutine函数的地址,由于程序执行到最后的ret语句会使用0xC2,所以这里用它作为结束
while (pPsSetCreateProessNotifyRoutine && *pPsSetCreateProessNotifyRoutine != 0xC2)
{
//根据0xE8来获取函数地址
if (*pPsSetCreateProessNotifyRoutine == 0xE8)
{
pPspCreateProcessNotifyRoutine = pPsSetCreateProessNotifyRoutine + 5 + *(PUINT32)(pPsSetCreateProessNotifyRoutine + 1);
break;
}
pPsSetCreateProessNotifyRoutine++;
}
//获取回调函数数组的地址,由于程序执行到最后的ret语句会使用0xC2,所以这里用它作为结束
while (pPspCreateProcessNotifyRoutine && *pPspCreateProcessNotifyRoutine != 0xC2)
{
//根据特征码来获取数组的地址
if (*pPspCreateProcessNotifyRoutine == 0xEB &&
*(pPspCreateProcessNotifyRoutine + 1) == 0x2F &&
*(pPspCreateProcessNotifyRoutine + 2) == 0xBE)
{
pProcessArray = (PUINT32)*(PUINT32)(pPspCreateProcessNotifyRoutine + 3);
break;
}
pPspCreateProcessNotifyRoutine++;
}
return pProcessArray;
}
NTSTATUS status = STATUS_SUCCESS;
PUINT32 pProcessArray = NULL, pFuncAddr = NULL;
UINT32 i = 0;
pProcessArray = SearchProcessArray();
if (pProcessArray != NULL)
{
for (i = 0; i < 0x40; i++)
{
if (pProcessArray[i] & ~0x7)
{
pFuncAddr = (PUINT32)(pProcessArray[i] & ~0x7 + 4);
status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)*pFuncAddr, TRUE);
if (NT_SUCCESS(status))
{
DbgPrint("成功删除进程的回调函数, 函数地址0x%X\r\n", *pFuncAddr);
}
}
}
}
else DbgPrint("没有找到进程数组\r\n");
4
运行结果
看雪ID:1900
https://bbs.pediy.com/user-home-835440.htm
# 往期推荐
3.全网最详细CVE-2014-0502 Adobe Flash Player双重释放漏洞分析
4.基于linker实现so加壳补充-------从dex中加载so
6.BCTF2018-houseofatum-Writeup题解
球分享
球点赞
球在看
点击“阅读原文”,了解更多!