检测隐藏进程
Detection of the hidden processes
俄语原文:http://wasm.ru/article.php?article=hiddndt
俄文翻译:kao
http://community.reverse-engineering.net/viewtopic.php?t=4685
中文翻译: prince
后期校验:firstrose
=============================
侦测隐藏进程
[C] Ms-Rem
2002-2005 wasm.ru - all rights reserved and reversed
许多用户都有过用Windows自带的任务管理器查看所有进程的经验,并且很多人都认为在任务管理器中隐藏进程是不可能的。而实际上,进程隐藏是再简单不过的事情了。有许多可用的方法和参考源码可以达到进程隐藏的目的。令我惊奇的是只有很少一部分的木马使用了这种技术。估计1000个木马中仅有1个是进程隐藏的。我认为木马的作者太懒了,因为隐藏进程需要进行的额外工作仅仅是对源代码的拷贝-粘贴。所以我们应该期待即将到来的会隐藏进程的木马。
自然地,也就有必要研究进程隐藏的对抗技术。杀毒软件和防火墙制造商就像他们的产品不能发现隐藏进程一样落后了。在少之又少的免费工具中,能够胜任的也只有Klister(仅运行于Windows 2000平台)了。所有其他公司关注的只有金钱(俄文译者kao注:不完全正确,FSecure的BlackLight Beta也是免费的)。除此之外,所有的这些工具都可以很容易的anti掉。
用程序实现隐藏进程探测技术,我们有两种选择:
* 基于某种探测原理找到一种隐藏的方法;
* 基于某个程序找到一种隐藏的方法,这个要简单一些。
购买商业软件产品的用户不能修改程序,这样可以保证其中绑定的程序的安全运行。因此第2种方法提到的程序就是商业程序的后门(rootkits)(例如hxdef Golden edition)。唯一的解决方案是创建一个免费的隐藏进程检测的开源项目,这个程序使用几种不同的检测方法,这样可以发现使用某一种方法进行隐藏的进程。任何一个用户都可以抵挡某程序的捆绑程序,当然那要得到程序的源代码并且按照自己的意愿进行修改。
在这篇文章中我将讨论探测隐藏进程的基本方法,列出该方法的示例代码,并创建一个能够检测上面我们提到的隐藏进程的程序。
在用户态(ring 3)检测
我们从简单的用户态(ring 3)检测开始,不使用驱动。事实上,每一个进程都会留下某种活动的痕迹,根据这些痕迹,我们就可以检测到隐藏的进程。这些痕迹包括进程打开的句柄、窗口和创建的系统对象。要避开这种检测技术是非常简单的,但是这样做需要留意进程留下所有痕迹,这种模式没有被用在任何一个公开发行的后门(rootkits)上。(不幸的是内部版本没有对我开放)。用户态方法容易实现,使用安全,并且能够得到很好的效果,因此这种方法不应该被忽略。
首先我们定义一下用到的数据,如下:
Code:
type
PProcList = ^TProcList;
TProcList = packed record
NextItem: pointer;
ProcName: array [0..MAX_PATH] of Char;
ProcId: dword;
ParrentId: dword;
end;
使用ToolHelp API获得所有进程列表
定义一下获得进程列表的函数。我们要比较这个结果和通过其他途径得到的结果:
Code:
{
Acquiring list of processes by using ToolHelp API.
}
procedure GetToolHelpProcessList(var List: PListStruct);
var
Snap: dword;
Process: TPROCESSENTRY32;
NewItem: PProcessRecord;
begin
Snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if Snap <> INVALID_HANDLE_VALUE then
begin
Process.dwSize := SizeOf(TPROCESSENTRY32);
if Process32First(Snap, Process) then
repeat
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
NewItem^.ProcessId := Process.th32ProcessID;
NewItem^.ParrentPID := Process.th32ParentProcessID;
lstrcpy(@NewItem^.ProcessName, Process.szExeFile);
AddItem(List, NewItem);
until not Process32Next(Snap, Process);
CloseHandle(Snap);
end;
end;
很明显,这不会发现任何隐藏进程,所以这个函数只可以用来做探测隐藏进程的参考。
通过使用Native API获得进程列表
再深一个层次的扫描我们要通过Native API ZwQuerySystemInformation获得进程列表。虽然在这个级别(ring 0)什么也发现不了,但是我们
仍然应该检查一下。(prince注:有点令人费解,原文如下:The next scanning level will be acquisition a list of processes through
ZwQuerySystemInformation (Native API). It is improbable that something will be found out at this level but we should check it
anyway.)
Code:
{
Acquiring list of processes by using ZwQuerySystemInformation.
}
procedure GetNativeProcessList(var List: PListStruct);
var
Inf PSYSTEM_PROCESSES;
NewItem: PProcessRecord;
Mem: pointer;
begin
Info := GetInfoTable(SystemProcessesAndThreadsInformation);
Mem := Info;
if Info = nil then Exit;
repeat
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
lstrcpy(@NewItem^.ProcessName,
PChar(WideCharToString(Info^.ProcessName.Buffer)));
NewItem^.ProcessId := Info^.ProcessId;
NewItem^.ParrentPID := Info^.InheritedFromProcessId;
AddItem(List, NewItem);
Info := pointer(dword(info) + info^.NextEntryDelta);
until Info^.NextEntryDelta = 0;
VirtualFree(Mem, 0, MEM_RELEASE);
end;
通过进程打开的句柄获得进程列表。
许多隐藏进程无法隐藏他们打开的句柄,因此我们可以通过使用ZwQuerySystemInformation函数枚举打开的句柄来构建进程列表。
Code:
{
Acquiring the list of processes by using list of opened handles.
Returns only ProcessId.
}
procedure GetHandlesProcessList(var List: PListStruct);
var
Inf PSYSTEM_HANDLE_INFORMATION_EX;
NewItem: PProcessRecord;
r: dword;
OldPid: dword;
begin
OldPid := 0;
Info := GetInfoTable(SystemHandleInformation);
if Info = nil then Exit;
for r := 0 to Info^.NumberOfHandles do
if Info^.Information[r].ProcessId <> OldPid then
begin
OldPid := Info^.Information[r].ProcessId;
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
NewItem^.ProcessId := OldPid;
AddItem(List, NewItem);
end;
VirtualFree(Info, 0, MEM_RELEASE);
end;
到现在我们已经可能发现一些东西了,但是我们不应该依赖于像隐藏进程一样简单的隐藏句柄的检查结果,尽管有些人甚至忘记隐藏他们。
通过列举创建的窗口来得到进程列表。
可以将那在系统中注册窗口的进程用GetWindowThreadProcessId构建进程列表。
Code:
{
Acquiring the list of processes by using list of windows.
Returns only ProcessId.
}
procedure GetWindowsProcessList(var List: PListStruct);
function EnumWindowsProc(hwnd: dword; PList: PPListStruct): bool; stdcall;
var
ProcId: dword;
NewItem: PProcessRecord;
begin
GetWindowThreadProcessId(hwnd, ProcId);
if not IsPidAdded(PList^, ProcId) then
begin
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
NewItem^.ProcessId := ProcId;
AddItem(PList^, NewItem);
end;
Result := true;
end;
begin
EnumWindows(@EnumWindowsProc, dword(@List));
end;
几乎没有人会隐藏窗口,因此这种检查可以检测某些进程,但是我们不应该相信这种检测。
直接通过系统调用得到进程列表。
在用户态隐藏进程,一个普遍的做法是使用代码注入(code-injection)技术和在所有进程中拦截ntdll.dll中的ZwQuerySystemInformation函数
。
ntdll中的函数实际上对应着系统内核中的函数和系统调用(Windows 2000 中的2Eh中断或者Windows XP中的sysenter指令),因此大多数简单
又有效的关于那些用户级的隐藏进程的检测方法就是直接使用系统调用而不是使用API函数。
Windows XP中ZwQuerySystemInformation函数的替代函数看起来是这个样子:
Code:
{
ZwQuerySystemInformation for Windows XP.
}
Function XpZwQuerySystemInfoCall(ASystemInformationClass: dword;
ASystemInformation: Pointer;
ASystemInformationLength: dword;
AReturnLength: pdword): dword; stdcall;
asm
pop ebp
mov eax, $AD
call @SystemCall
ret $10
@SystemCall:
mov edx, esp
sysenter
end;
由于不同的系统调用机制,Windows 2000的这部分代码看起来有些不同。
Code:
{
Системный вызов ZwQuerySystemInformation для Windows 2000.
}
Function Win2kZwQuerySystemInfoCall(ASystemInformationClass: dword;
ASystemInformation: Pointer;
ASystemInformationLength: dword;
AReturnLength: pdword): dword; stdcall;
asm
pop ebp
mov eax, $97
lea edx, [esp + $04]
int $2E
ret $10
end;
俄语原文:http://wasm.ru/article.php?article=hiddndt
俄文翻译:kao
http://community.reverse-engineering.net/viewtopic.php?t=4685
中文翻译: prince
后期校验:firstrose
=============================
侦测隐藏进程
[C] Ms-Rem
2002-2005 wasm.ru - all rights reserved and reversed
许多用户都有过用Windows自带的任务管理器查看所有进程的经验,并且很多人都认为在任务管理器中隐藏进程是不可能的。而实际上,进程隐藏是再简单不过的事情了。有许多可用的方法和参考源码可以达到进程隐藏的目的。令我惊奇的是只有很少一部分的木马使用了这种技术。估计1000个木马中仅有1个是进程隐藏的。我认为木马的作者太懒了,因为隐藏进程需要进行的额外工作仅仅是对源代码的拷贝-粘贴。所以我们应该期待即将到来的会隐藏进程的木马。
自然地,也就有必要研究进程隐藏的对抗技术。杀毒软件和防火墙制造商就像他们的产品不能发现隐藏进程一样落后了。在少之又少的免费工具中,能够胜任的也只有Klister(仅运行于Windows 2000平台)了。所有其他公司关注的只有金钱(俄文译者kao注:不完全正确,FSecure的BlackLight Beta也是免费的)。除此之外,所有的这些工具都可以很容易的anti掉。
用程序实现隐藏进程探测技术,我们有两种选择:
* 基于某种探测原理找到一种隐藏的方法;
* 基于某个程序找到一种隐藏的方法,这个要简单一些。
购买商业软件产品的用户不能修改程序,这样可以保证其中绑定的程序的安全运行。因此第2种方法提到的程序就是商业程序的后门(rootkits)(例如hxdef Golden edition)。唯一的解决方案是创建一个免费的隐藏进程检测的开源项目,这个程序使用几种不同的检测方法,这样可以发现使用某一种方法进行隐藏的进程。任何一个用户都可以抵挡某程序的捆绑程序,当然那要得到程序的源代码并且按照自己的意愿进行修改。
在这篇文章中我将讨论探测隐藏进程的基本方法,列出该方法的示例代码,并创建一个能够检测上面我们提到的隐藏进程的程序。
在用户态(ring 3)检测
我们从简单的用户态(ring 3)检测开始,不使用驱动。事实上,每一个进程都会留下某种活动的痕迹,根据这些痕迹,我们就可以检测到隐藏的进程。这些痕迹包括进程打开的句柄、窗口和创建的系统对象。要避开这种检测技术是非常简单的,但是这样做需要留意进程留下所有痕迹,这种模式没有被用在任何一个公开发行的后门(rootkits)上。(不幸的是内部版本没有对我开放)。用户态方法容易实现,使用安全,并且能够得到很好的效果,因此这种方法不应该被忽略。
首先我们定义一下用到的数据,如下:
Code:
type
PProcList = ^TProcList;
TProcList = packed record
NextItem: pointer;
ProcName: array [0..MAX_PATH] of Char;
ProcId: dword;
ParrentId: dword;
end;
使用ToolHelp API获得所有进程列表
定义一下获得进程列表的函数。我们要比较这个结果和通过其他途径得到的结果:
Code:
{
Acquiring list of processes by using ToolHelp API.
}
procedure GetToolHelpProcessList(var List: PListStruct);
var
Snap: dword;
Process: TPROCESSENTRY32;
NewItem: PProcessRecord;
begin
Snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if Snap <> INVALID_HANDLE_VALUE then
begin
Process.dwSize := SizeOf(TPROCESSENTRY32);
if Process32First(Snap, Process) then
repeat
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
NewItem^.ProcessId := Process.th32ProcessID;
NewItem^.ParrentPID := Process.th32ParentProcessID;
lstrcpy(@NewItem^.ProcessName, Process.szExeFile);
AddItem(List, NewItem);
until not Process32Next(Snap, Process);
CloseHandle(Snap);
end;
end;
很明显,这不会发现任何隐藏进程,所以这个函数只可以用来做探测隐藏进程的参考。
通过使用Native API获得进程列表
再深一个层次的扫描我们要通过Native API ZwQuerySystemInformation获得进程列表。虽然在这个级别(ring 0)什么也发现不了,但是我们
仍然应该检查一下。(prince注:有点令人费解,原文如下:The next scanning level will be acquisition a list of processes through
ZwQuerySystemInformation (Native API). It is improbable that something will be found out at this level but we should check it
anyway.)
Code:
{
Acquiring list of processes by using ZwQuerySystemInformation.
}
procedure GetNativeProcessList(var List: PListStruct);
var
Inf PSYSTEM_PROCESSES;
NewItem: PProcessRecord;
Mem: pointer;
begin
Info := GetInfoTable(SystemProcessesAndThreadsInformation);
Mem := Info;
if Info = nil then Exit;
repeat
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
lstrcpy(@NewItem^.ProcessName,
PChar(WideCharToString(Info^.ProcessName.Buffer)));
NewItem^.ProcessId := Info^.ProcessId;
NewItem^.ParrentPID := Info^.InheritedFromProcessId;
AddItem(List, NewItem);
Info := pointer(dword(info) + info^.NextEntryDelta);
until Info^.NextEntryDelta = 0;
VirtualFree(Mem, 0, MEM_RELEASE);
end;
通过进程打开的句柄获得进程列表。
许多隐藏进程无法隐藏他们打开的句柄,因此我们可以通过使用ZwQuerySystemInformation函数枚举打开的句柄来构建进程列表。
Code:
{
Acquiring the list of processes by using list of opened handles.
Returns only ProcessId.
}
procedure GetHandlesProcessList(var List: PListStruct);
var
Inf PSYSTEM_HANDLE_INFORMATION_EX;
NewItem: PProcessRecord;
r: dword;
OldPid: dword;
begin
OldPid := 0;
Info := GetInfoTable(SystemHandleInformation);
if Info = nil then Exit;
for r := 0 to Info^.NumberOfHandles do
if Info^.Information[r].ProcessId <> OldPid then
begin
OldPid := Info^.Information[r].ProcessId;
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
NewItem^.ProcessId := OldPid;
AddItem(List, NewItem);
end;
VirtualFree(Info, 0, MEM_RELEASE);
end;
到现在我们已经可能发现一些东西了,但是我们不应该依赖于像隐藏进程一样简单的隐藏句柄的检查结果,尽管有些人甚至忘记隐藏他们。
通过列举创建的窗口来得到进程列表。
可以将那在系统中注册窗口的进程用GetWindowThreadProcessId构建进程列表。
Code:
{
Acquiring the list of processes by using list of windows.
Returns only ProcessId.
}
procedure GetWindowsProcessList(var List: PListStruct);
function EnumWindowsProc(hwnd: dword; PList: PPListStruct): bool; stdcall;
var
ProcId: dword;
NewItem: PProcessRecord;
begin
GetWindowThreadProcessId(hwnd, ProcId);
if not IsPidAdded(PList^, ProcId) then
begin
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
NewItem^.ProcessId := ProcId;
AddItem(PList^, NewItem);
end;
Result := true;
end;
begin
EnumWindows(@EnumWindowsProc, dword(@List));
end;
几乎没有人会隐藏窗口,因此这种检查可以检测某些进程,但是我们不应该相信这种检测。
直接通过系统调用得到进程列表。
在用户态隐藏进程,一个普遍的做法是使用代码注入(code-injection)技术和在所有进程中拦截ntdll.dll中的ZwQuerySystemInformation函数
。
ntdll中的函数实际上对应着系统内核中的函数和系统调用(Windows 2000 中的2Eh中断或者Windows XP中的sysenter指令),因此大多数简单
又有效的关于那些用户级的隐藏进程的检测方法就是直接使用系统调用而不是使用API函数。
Windows XP中ZwQuerySystemInformation函数的替代函数看起来是这个样子:
Code:
{
ZwQuerySystemInformation for Windows XP.
}
Function XpZwQuerySystemInfoCall(ASystemInformationClass: dword;
ASystemInformation: Pointer;
ASystemInformationLength: dword;
AReturnLength: pdword): dword; stdcall;
asm
pop ebp
mov eax, $AD
call @SystemCall
ret $10
@SystemCall:
mov edx, esp
sysenter
end;
由于不同的系统调用机制,Windows 2000的这部分代码看起来有些不同。
Code:
{
Системный вызов ZwQuerySystemInformation для Windows 2000.
}
Function Win2kZwQuerySystemInfoCall(ASystemInformationClass: dword;
ASystemInformation: Pointer;
ASystemInformationLength: dword;
AReturnLength: pdword): dword; stdcall;
asm
pop ebp
mov eax, $97
lea edx, [esp + $04]
int $2E
ret $10
end;
0
相关文章