前面讨论了沙箱的目标,今天开始落实代码实现,从功能实现的先后顺序上,我们优先实现4个回调:进程回调、镜像回调、线程回调、注册表回调,今天实现进程回调。
注册进程回调函数的关键API是PsSetCreateProcessNotifyRoutine,如果要使回调生效,需要在VS链接器命令行中添加 /INTEGRITYCHECK :
该函数在高系统中还有Ex版本,读者自行查阅,函数原型如下:
NTSTATUS PsSetCreateProcessNotifyRoutine(
_In_ PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
_In_ BOOLEAN Remove
);
参数说明:
NotifyRoutine:我们自己的回调函数地址,函数原型如下:
VOID ProcessNotify(PEPROCESS Process, HANDLE hProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
Remove: 是否移除目标回调。我们是注册新的,所以是FALSE;
Status=PsSetCreateProcessNotifyRoutineEx(ProcessNotify, FALSE);
下面是我们的回调代码实现,核心功能是获取当前创建的进程PID、全路径、命令行、父进程PID、父进程全路径信息。需要注意的是,这里获取的父进程信息有可能是假的,最常见的就是启动服务、shellcode注入启动或者借助UpdateProcThreadAttribute伪造父进程启动。这类的伪装,我们需要在后续的InfinityHook里实现发现并进行关联标注。
//该API的使用需要声明
extern "C" PVOID NTAPI PsGetProcessWow64Process(PEPROCESS Process);
VOID ProcessNotify(PEPROCESS Process, HANDLE hProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
return;
//进程创建
if (CreateInfo)
{
//判断进程类别
BOOLEAN bwow64 = PsGetProcessWow64Process(Process) ? TRUE : FALSE;
char filename[256] = { 0 };
char cmdline[256] = { 0 };
char parpath[256] = { 0 };
StringUnicodeToAnsi(CreateInfo->ImageFileName, filename, CreateInfo->ImageFileName->Length);
StringUnicodeToAnsi(CreateInfo->CommandLine, cmdline, CreateInfo->CommandLine->Length);
StringToLower(filename);
StringToLower(cmdline);
char ctime[64] = { 0 };
GetTimeString(ctime);
GetProcPathByPid((HANDLE)CreateInfo->ParentProcessId, parpath);
char sfomat[] = "{\"st\":\"ProcessNotify进程启动\",\"type\":%d,\"id\":%d,\"time\":\"%s\",\"ppid\":%d,\"ppath\":\"%s\",\"pid\":%d,\"path\":\"%s\",\"cmd\":\"%s\"}\n";
char* cbuf = (char*)kMalloc(0x1000);
sprintf(cbuf, sfomat, 1, 1, ctime, CreateInfo->ParentProcessId, parpath, hProcessId, filename, cmdline);
DbgPrint("[procK]%s", cbuf);
kFree(cbuf);
}
else
{
//进程销毁
}
}
所使用到的自定义函数:
PVOID kMalloc(SIZE_T size)
{
PVOID pTmp = (PVOID)ExAllocatePoolWithTag(NonPagedPool, size, 'clm3');
if (pTmp)
{
RtlZeroMemory(pTmp, size);
}
return pTmp;
}
VOID kFree(PVOID pMem)
{
if (pMem)
{
ExFreePoolWithTag(pMem, 'clm3');
pMem = 0;
}
}
BOOLEAN GetProcPathByPid(HANDLE hdPid, char* cPath)
{
BOOLEAN bOk = FALSE;
PEPROCESS pEpro = 0;
PFILE_OBJECT FileObject=0;
PUNICODE_STRING ImageName;
char parname[512] = { 0 };
if ((int)hdPid == 4)
{
char system[] = "System";
strcpy(cPath, system);
bOk = TRUE;
}
else
{
auto spath = GetProcPathByPid((ULONG)hdPid);
if (spath.length() > 0)
{
strcpy(cPath, spath.c_str());
bOk = TRUE;
}
else
{
if (NT_SUCCESS(PsLookupProcessByProcessId(hdPid, &pEpro)))
{
if (NT_SUCCESS(PsReferenceProcessFilePointer(pEpro, &FileObject)))
{
if (NT_SUCCESS(SeLocateProcessImageName((PEPROCESS)pEpro, &ImageName)))
{
StringUnicodeToAnsi(ImageName, parname, ImageName->Length);
StringToLower(parname);
strcpy(cPath, parname);
bOk = TRUE;
}
}
if (FileObject)
ObDereferenceObject(FileObject);
}
if (pEpro)
ObDereferenceObject(pEpro);
}
}
return bOk;
}
模板函数:
template <typename U, typename A>
BOOLEAN StringUnicodeToAnsi(U pUnicode, A szAnsiBuf, int nABuflen)
{
ANSI_STRING ansiBuf;
BOOLEAN bResult = FALSE;
ansiBuf.Buffer = (char*)kMalloc(2048);
ansiBuf.MaximumLength = 2048;
if (MmIsAddressValid((PVOID)pUnicode) && MmIsAddressValid(szAnsiBuf))
{
auto Status = RtlUnicodeStringToAnsiString(&ansiBuf, (PUNICODE_STRING)pUnicode, FALSE);
if (NT_SUCCESS(Status))
{
if (ansiBuf.Length < nABuflen)
{
memcpy(szAnsiBuf, ansiBuf.Buffer, ansiBuf.Length);
bResult = TRUE;
}
}
}
kFree(ansiBuf.Buffer);
return bResult;
}
template <typename T>
NTSTATUS StringToLower(T szBuf)
{
NTSTATUS st;
if (szBuf && MmIsAddressValid(szBuf))
{
while (*szBuf)
{
if (*szBuf >= 'A' && *szBuf <= 'Z')
{
*szBuf += 0x20;
}
szBuf++;
}
st = STATUS_SUCCESS;
}
else
{
st = STATUS_UNSUCCESSFUL;
}
return st;
}