关于mcafee的进程了解WEB安全

时间:2022-11-25 00:54:30 作者:墨子渔 综合材料 收藏本文 下载本文

“墨子渔”通过精心收集,向本站投稿了5篇关于mcafee的进程了解WEB安全,下面是小编帮大家整理后的关于mcafee的进程了解WEB安全,欢迎阅读,希望大家能够喜欢。

篇1:关于mcafee的进程了解WEB安全

刚才看到有朋友问mcafee进程的问题

所以我去查了些资料和自己总结了一些

写了出来希望大家喜欢!!!

mcafee里总共有7个进程

1:frameworkservice.exe: 这个进程可以在系统的服务里找到

对应的服务为“mcafee framework”

它是用来做什么的呢?

其实它是mcafee的后台框架进程,mcafee产品的共享组件框架.

虽然关闭该服务不影响mcafee使用,但该服务会对升级产生影响

如:当你打开了网络防火墙时,升级mcafee,就会提示该文件需要访问网络.

2: Mcshield.exe 它是mcafee的核心进程,对应的服务为 network Associates McShield

所以关闭此服务 mcafee就不能使用.

3:naPrdMgr:它是与frameworkservice.exe关联在一起的进程

如果关闭了frameworkservice.exe 它也会消失.

4:Tbmon.exe: 它是错误报告进程.

5:shstat.exe: 系统栏内的软件图标,关闭了它系统任处于保护状态.

6:UpdataUl.exe:该进程用于自动升级.我想大家都应该知道

7:Vstskmgr.exe:这个进程属于系统服务.对应名为 network Associates Task Manager

p.s.mcafee是一款很不错的杀毒软件,而且是免费的.

像国内许多的木马都能查杀,如海洋顶端等等

但是不知道海洋顶端可以不,我一会去试试.

图做的不好 请大家不要笑话我!!!

篇2:检测隐藏进程WEB安全

Detection of the hidden processes

俄语原文:wasm.ru/article.php?article=hiddndt

俄文翻译:kao

community.reverse-engineering.net/viewtopic.php?t=4685

中文翻译: prince

后期校验:firstrose

=============================

侦测隐藏进程

[C] Ms-Rem

-2005 wasm.ru - all rights reserved and reversed

许多用户都有过用Windows自带的任务管理器查看所有进程的经验,并且很多人都认为在任务管理器中隐藏进程是不可能的,而实际上,进程隐藏是再简单不过的事情了。有许多可用的方法和参考源码可以达到进程隐藏的目的。令我惊奇的是只有很少一部分的木马使用了这种技术。估计1000个木马中仅有1个是进程隐藏的。我认为木马的作者太懒了,因为隐藏进程需要进行的额外工作仅仅是对源代码的拷贝-粘贴。所以我们应该期待即将到来的会隐藏进程的木马。

自然地,也就有必要研究进程隐藏的对抗技术。杀毒软件和防火墙制造商就像他们的产品不能发现隐藏进程一样落后了。在少之又少的免费工具中,能够胜任的也只有Klister(仅运行于Windows平台)了。所有其他公司关注的只有金钱(俄文译者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;

现在有必要使用上面提到的函数而不是ntdll来枚举系统进程了。实现的代码如下:

Code:

{

Acquiring the list of processes by use of a direct system call

ZwQuerySystemInformation.

}

procedure GetSyscallProcessList(var List: PListStruct);

var

Inf PSYSTEM_PROCESSES;

NewItem: PProcessRecord;

mPtr: pointer;

mSize: dword;

St: NTStatus;

begin

mSize := $4000;

repeat

GetMem(mPtr, mSize);

St := ZwQuerySystemInfoCall(SystemProcessesAndThreadsInformation,

mPtr, mSize, nil);

if St = STATUS_INFO_LENGTH_MISMATCH then

begin

FreeMem(mPtr);

mSize := mSize * 2;

end;

until St STATUS_INFO_LENGTH_MISMATCH;

if St = STATUS_SUCCESS then

begin

Info := mPtr;

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;

Info := pointer(dword(info) + info^.NextEntryDelta);

AddItem(List, NewItem);

until Info^.NextEntryDelta = 0;

end;

FreeMem(mPtr);

end;

这种方法能检测几乎100%的用户态的后门(rootkits),例如hxdef的所有版本(包括黄金版)。

通过分析相关的句柄得到进程列表。

基于枚举句柄的方法。这个方法的实质并不是查找进程打开的句柄,而是查找同该进程相关的其他进程的句柄。这些句柄可以是进程句柄也可

以是线程句柄。当找到进程句柄,我们就可以用ZwQueryInformationProcess函数得到进程的PID。对于线程句柄,我们可以通过

ZwQueryInformationThread得到进程ID。存在于系统中的所有进程都是由某些进程产生的,因此父进程拥有他们的句柄(除了那些已经被关闭

的句柄),对于Win32子系统服务器(csrss.exe)来说所有存在的进程的句柄都是可以访问的。另外,Windows NT大量使用Job objects(prince:

任务对象?姑且这么翻译吧,有不妥的地方请指教),任务对象可以关联进程(比如属于某用户或服务的所有进程),因此当找到任务对象的句

柄,我们就可以利用它得到与之关联的所有进程的ID。使用QueryInformationJobObject和信息类的函数JobObjectBasicProcessIdList就可以

实现上述功能。利用分析进程相关的句柄得到进程列表的实现代码如下:

Code:

{

Acquiring the list of processes by analyzing handles in other processes.

}

procedure GetProcessesFromHandles(var List: PListStruct; Processes, Jobs, Threads: boolean);

var

HandlesInf PSYSTEM_HANDLE_INFORMATION_EX;

ProcessInf PROCESS_BASIC_INFORMATION;

hProcess : dword;

tHandle: dword;

r, l : integer;

NewItem: PProcessRecord;

Inf PJOBOBJECT_BASIC_PROCESS_ID_LIST;

Size: dword;

THRInf THREAD_BASIC_INFORMATION;

begin

HandlesInfo := GetInfoTable(SystemHandleInformation);

if HandlesInfo nil then

for r := 0 to HandlesInfo^.NumberOfHandles do

if HandlesInfo^.Information[r].ObjectTypeNumber in [OB_TYPE_PROCESS, OB_TYPE_JOB, OB_TYPE_THREAD] then

begin

hProcess := OpenProcess(PROCESS_DUP_HANDLE, false,

HandlesInfo^.Information[r].ProcessId);

if DuplicateHandle(hProcess, HandlesInfo^.Information[r].Handle,

INVALID_HANDLE_VALUE, @tHandle, 0, false,

DUPLICATE_SAME_ACCESS) then

begin

case HandlesInfo^.Information[r].ObjectTypeNumber of

OB_TYPE_PROCESS : begin

if Processes and (HandlesInfo^.Information[r].ProcessId = CsrPid) then

if ZwQueryInformationProcess(tHandle, ProcessBasicInformation,

@ProcessInfo,

SizeOf(PROCESS_BASIC_INFORMATION),

nil) = STATUS_SUCCESS then

if not IsPidAdded(List, ProcessInfo.UniqueProcessId) then

begin

GetMem(NewItem, SizeOf(TProcessRecord));

ZeroMemory(NewItem, SizeOf(TProcessRecord));

NewItem^.ProcessId := ProcessInfo.UniqueProcessId;

NewItem^.ParrentPID := ProcessInfo.InheritedFromUniqueProcessId;

AddItem(List, NewItem);

end;

end;

OB_TYPE_JOB : begin

if Jobs then

begin

Size := SizeOf(JOBOBJECT_BASIC_PROCESS_ID_LIST) + 4 * 1000;

GetMem(Info, Size);

Info^.NumberOfAssignedProcesses := 1000;

if QueryInformationJobObject(tHandle, JobObjectBasicProcessIdList,

Info, Size, nil) then

for l := 0 to Info^.NumberOfProcessIdsInList - 1 do

if not IsPidAdded(List, Info^.ProcessIdList[l]) then

begin

GetMem(NewItem, SizeOf(TProcessRecord));

ZeroMemory(NewItem, SizeOf(TProcessRecord));

NewItem^.ProcessId := Info^.ProcessIdList[l];

AddItem(List, NewItem);

end;

FreeMem(Info);

end;

end;

OB_TYPE_THREAD : begin

if Threads then

if ZwQueryInformationThread(tHandle, THREAD_BASIC_INFO,

@THRInfo,

SizeOf(THREAD_BASIC_INFORMATION),

nil) = STATUS_SUCCESS then

if not IsPidAdded(List, THRInfo.ClientId.UniqueProcess) then

begin

GetMem(NewItem, SizeOf(TProcessRecord));

ZeroMemory(NewItem, SizeOf(TProcessRecord));

NewItem^.ProcessId := THRInfo.ClientId.UniqueProcess;

AddItem(List, NewItem);

end;

end;

end;

CloseHandle(tHandle);

end;

CloseHandle(hProcess);

end;

VirtualFree(HandlesInfo, 0, MEM_RELEASE);

end;

不幸的是,上面提到的这些方法有些只能得到进程ID,而不能得到进程名字。因此,我们还需要通过进程ID得到进程的名称。当然,当这些进

程是隐藏进程的时候我们就不能使用ToolHelp API来实现。所以我们应该访问进程内存通过读取该进程的PEB得到进程名称。PEB地址可以用

ZwQueryInformationProcess函数获得。以上所说的功能实现代码如下:

Code:

function GetNameByPid(Pid: dword): string;

var

hProcess, Bytes: dword;

Inf PROCESS_BASIC_INFORMATION;

ProcessParametres: pointer;

ImagePath: TUnicodeString;

ImgPath: array[0..MAX_PATH] of WideChar;

begin

Result := '';

ZeroMemory(@ImgPath, MAX_PATH * SizeOf(WideChar));

hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, Pid);

if ZwQueryInformationProcess(hProcess, ProcessBasicInformation, @Info,

SizeOf(PROCESS_BASIC_INFORMATION), nil) = STATUS_SUCCESS then

begin

if ReadProcessMemory(hProcess, pointer(dword(Info.PebBaseAddress) + $10),

@ProcessParametres, SizeOf(pointer), Bytes) and

ReadProcessMemory(hProcess, pointer(dword(ProcessParametres) + $38),

@ImagePath, SizeOf(TUnicodeString), Bytes) and

ReadProcessMemory(hProcess, ImagePath.Buffer, @ImgPath,

ImagePath.Length, Bytes) then

begin

Result := ExtractFileName(WideCharToString(ImgPath));

end;

end;

CloseHandle(hProcess);

end;

当然,用户态隐藏进程的检测方法不止这些,还可以想一些稍微复杂一点的新方法(比如,用SetWindowsHookEx函数对可访问进程的注入和当

我们的DLL并成功加载后对进程列表的分析),但是现在我们将用上面提到的方法来解决问题。这些方法的优点是他们可以简单地编程实现,并

且除了可以检测到用户态的隐藏进程,还可以检测到少数的在内核态实现的隐藏进程... 要实现真正可靠的进程隐藏工具我们应该使用Windows

未公开的内核数据结构编写内核驱动程序。

内核态(Ring 0)的检测

恭喜你,我们终于开始进行内核态隐藏进程的分析。内核态的检测方法同用户态的检测方法的主要区别是所有的进程列表都没有使用API调用而

是直接来自系统内部数据结构。在这些检测方法下隐藏进程要困难得多,因为它们都是基于同Windows内核相同的原理实现的,并且从这些内核

数据结构中删除进程将导致该进程完全失效。

内核中的进程是什么?每一个进程都有自己的地址空间,描述符,线程等,内核的数据结构就涉及这些东西。每一个进程都是由EPROCESS结构

描述,而所有进程的结构都被一个双向循环链表维护。进程隐藏的一个方法就是改变进程结构链表的指针,使得链表枚举跳过自身达到进程隐

藏的目的。避开进程枚举并不影响进程的任何功能。无论怎样,EPROCESS结构总是存在的,对一个进程的正常功能来说它是必要的。在内核态

检测隐藏进程的主要方法就是对这个结构的检查。

我们应该定义一下将要储存的进程信息的变量格式。这个变量格式应该很方便地存储来自驱动的数据(附录)。结构定义如下:

Code:

typedef struct _ProcessRecord

{

ULONG Visibles;

ULONG SignalState;

BOOLEAN Present;

ULONG ProcessId;

ULONG ParrentPID;

PEPROCESS pEPROCESS;

CHAR ProcessName[256];

} TProcessRecord, *PProcessRecord;

应该为这些结构分配连续的大块的内存,并且不设置最后一个结构的Present标志。

在内核中使用ZwQuerySystemInformation函数得到进程列表。

我们先从最简单的方式开始,通过ZwQuerySystemInformation函数得到进程列表:

Code:

PVOID GetNativeProcessList(ULONG *MemSize)

{

ULONG PsCount = 0;

PVOID Info = GetInfoTable(SystemProcessesAndThreadsInformation);

PSYSTEM_PROCESSES Proc;

PVOID Mem = NULL;

PProcessRecord Data;

if (!Info) return NULL; else Proc = Info;

do

{

Proc = (PSYSTEM_PROCESSES)((ULONG)Proc + Proc->NextEntryDelta);

PsCount++;

} while (Proc->NextEntryDelta);

*MemSize = (PsCount + 1) * sizeof(TProcessRecord);

Mem = ExAllocatePool(PagedPool, *MemSize);

if (!Mem) return NULL; else Data = Mem;

Proc = Info;

do

{

Proc = (PSYSTEM_PROCESSES)((ULONG)Proc + Proc->NextEntryDelta);

wcstombs(Data->ProcessName, Proc->ProcessName.Buffer, 255);

Data->Present = TRUE;

Data->ProcessId = Proc->ProcessId;

Data->ParrentPID = Proc->InheritedFromProcessId;

PsLookupProcessByProcessId((HANDLE)Proc->ProcessId, &Data->pEPROCESS);

ObDereferenceObject(Data->pEPROCESS);

Data++;

} while (Proc->NextEntryDelta);

Data->Present = FALSE;

ExFreePool(Info);

return Mem;

}

以这个函数做参考,任何内核态的隐藏进程都不会被检测出来,但是所有的用户态隐藏进程如hxdef是绝对逃不掉的。

在下面的代码中我们可以简单地用GetInfoTable函数来得到信息。为了防止有人问那是什么东西,下面列出完整的函数代码。

Code:

/*

Receiving buffer with results from ZwQuerySystemInformation.

*/

PVOID GetInfoTable(ULONG ATableType)

{

ULONG mSize = 0x4000;

PVOID mPtr = NULL;

NTSTATUS St;

do

{

mPtr = ExAllocatePool(PagedPool, mSize);

memset(mPtr, 0, mSize);

if (mPtr)

{

St = ZwQuerySystemInformation(ATableType, mPtr, mSize, NULL);

} else return NULL;

if (St == STATUS_INFO_LENGTH_MISMATCH)

{

ExFreePool(mPtr);

mSize = mSize * 2;

}

} while (St == STATUS_INFO_LENGTH_MISMATCH);

if (St == STATUS_SUCCESS) return mPtr;

ExFreePool(mPtr);

return NULL;

}

我认为这段代码是很容易理解的...

利用EPROCESS结构的双向链表得到进程列表。

我们又进了一步。接下来我们将通过遍历EPROCESS结构的双向链表来得到进程列表。链表的表头是PsActiveProcessHead,因此要想正确地枚举

进程我们需要找到这个并没有被导出的符号。在这之前我们应该知道System进程是所有进程列表中的第一个进程。在DriverEntry例程开始时我

们需要用PsGetCurrentProcess函数得到当前进程的指针(使用SC管理器的API或者ZwLoadDriver函数加载的驱动始终都是加载到System进程的

上下文中的),BLink在ActiveProcessLinks中的偏移将指向PsActiveProcessHead。像这样:

Code:

PsActiveProcessHead = *(PVOID *)((PUCHAR)PsGetCurrentProcess + ActiveProcessLinksOffset + 4);

现在就可以遍历这个双向链表来创建进程列表了:

Code:

PVOID GetEprocessProcessList(ULONG *MemSize)

{

PLIST_ENTRY Process;

ULONG PsCount = 0;

PVOID Mem = NULL;

PProcessRecord Data;

if (!PsActiveProcessHead) return NULL;

Process = PsActiveProcessHead->Flink;

while (Process != PsActiveProcessHead)

{

PsCount++;

Process = Process->Flink;

}

PsCount++;

*MemSize = PsCount * sizeof(TProcessRecord);

Mem = ExAllocatePool(PagedPool, *MemSize);

memset(Mem, 0, *MemSize);

if (!Mem) return NULL; else Data = Mem;

Process = PsActiveProcessHead->Flink;

while (Process != PsActiveProcessHead)

{

Data->Present = TRUE;

Data->ProcessId = *(PULONG)((ULONG)Process - ActPsLink + pIdOffset);

Data->ParrentPID = *(PULONG)((ULONG)Process - ActPsLink + ppIdOffset);

Data->SignalState = *(PULONG)((ULONG)Process - ActPsLink + 4);

Data->pEPROCESS = (PEPROCESS)((ULONG)Process - ActPsLink);

strncpy(Data->ProcessName, (PVOID)((ULONG)Process - ActPsLink + NameOffset), 16);

Data++;

Process = Process->Flink;

}

return Mem;

}

为了得到进程名称、ID和父进程ID,我们利用它们在EPROCESS结构中的偏移地址(pIdOffset, ppIdOffset, NameOffset, ActPsLink)。这些

偏移随着Windows系统版本的不同而不同,因此我们要在进程检测程序的代码中进行区分后得到他们正确的值(附录)。

任何一个通过API截取方式隐藏的进程都将被上面这个方法检测出来。但是如果进程是通过DKOM(直接处理内核对象 - Direct Kernel Object

Manipulation)方式隐藏,那这个方法就失效了,因为这种进程都被从进程链表中删掉了。

通过列举调度程序(scheduler)中的线程得到进程列表。

对付这种隐藏进程(俄文翻译kao注:这个地方原文写的比较模糊,作者大概的意思应该是“使用DKOM的方式检测隐藏进程”)的其中一种检测

方式是通过调度程序(scheduler)中的线程列表来得到进程列表。Windows 2000有三个维护线程的双向链表(KiWaitInListHead,

KiWaitOutListHead, KiDispatcherReadyListHead)。前面两个链表包含等待某种事件的线程,最后面的链表包含的是等待执行的线程。我们

处理这些链表,根据线程链表结构ETHREAD中的偏移就可以得到一个线程的ETHREAD指针(俄文翻译kao注:原文中这句话实在是太难懂了,希望

我翻译的正确)。这个结构包括了很多进程相关指针,也就是结构_KPROCESS *Process(0x44, 0x150)和结构_EPROCESS *ThreadsProcess

(0x22C, 这仅是Windows 2000中的偏移量)。前面两个指针对于一个线程的功能性没有任何影响,因此可以很容易修改它们来隐藏进程。相反,

第三个指针是当切换地址空间时调度程序(schedler)使用的指针,所以这个指针是不能修改的。我们就用它来找到拥有某个线程的进程。

Klister就是使用了这种检测方法,它的最大的缺点就是只能在Windows 2000平台上工作(但是在这个平台上某个补丁包也会让它失效)。导致

这个情况发生的原因就是这种程序使用了硬编码的线程链表地址,而在每个补丁包中这些地址可能都是不同的。

在程序中使用硬编码地址是很糟糕的解决方案,操作系统的升级就会使你的程序无法正常工作,要尽量避免使用这种检测方法。所以应该通过

分析那些使用了这些链表的内核函数来动态地得到它们的地址。

首先我们试试看在Windows 2000平台上找出KiWaitInListHead和KiWaitOutListHead.使用链表地址的函数KeWaitForSingleObject代码如下:

Code:

.text:0042DE56 mov ecx, offset KiWaitInListHead

.text:0042DE5B test al, al

.text:0042DE5D jz short loc_42DE6E

.text:0042DE5F cmp byte ptr [esi+135h], 0

.text:0042DE66 jz short loc_42DE6E

.text:0042DE68 cmp byte ptr [esi+33h], 19h

.text:0042DE6C jl short loc_42DE73

.text:0042DE6E mov ecx, offset KiWaitOutListHead

我们使用反汇编器(用我写的LDasm)反汇编KeWaitForSingleObject函数来获得这些地址。当索引(pOpcode)指向指令“mov ecx,

KiWaitInListHead”,(pOpcode + 5)指向的就是指令“test al, al”,(pOpcode + 24)指向的就是“mov ecx, KiWaitOutListHead”。

这样我们就可以通过索引(pOpcode + 1)和(pOpcode + 25)正确地得到KiWaitInListHead和KiWaitOutListHead的地址了。搜索地址的代码

如下:

Code:

void Win2KGetKiWaitInOutListHeads

{

PUCHAR cPtr, pOpcode;

ULONG Length;

for (cPtr = (PUCHAR)KeWaitForSingleObject;

cPtr < (PUCHAR)KeWaitForSingleObject + PAGE_SIZE;

cPtr += Length)

{

Length = SizeOfCode(cPtr, &pOpcode);

if (!Length) break;

if (*pOpcode == 0xB9 && *(pOpcode + 5) == 0x84 && *(pOpcode + 24) == 0xB9)

{

KiWaitInListHead = *(PLIST_ENTRY *)(pOpcode + 1);

KiWaitOutListHead = *(PLIST_ENTRY *)(pOpcode + 25);

break;

}

}

return;

}

在Windows 2000平台下我们可以用同样的方法得到KiDispatcherReadyListHead, 搜索KeSetAffinityThread函数:

Code:

.text:0042FAAA lea eax, KiDispatcherReadyListHead[ecx*8]

.text:0042FAB1 cmp [eax], eax

搜索KiDispatcherReadyListHead函数的代码:

Code:

void Win2KGetKiDispatcherReadyListHead()

{

PUCHAR cPtr, pOpcode;

ULONG Length;

for (cPtr = (PUCHAR)KeSetAffinityThread;

cPtr < (PUCHAR)KeSetAffinityThread + PAGE_SIZE;

cPtr += Length)

{

Length = SizeOfCode(cPtr, &pOpcode);

if (!Length) break;

if (*(PUSHORT)pOpcode == 0x048D && *(pOpcode + 2) == 0xCD && *(pOpcode + 7) == 0x39)

{

KiDispatcherReadyListHead = *(PVOID *)(pOpcode + 3);

break;

}

}

return;

}

不幸的是,Windows XP内核完全不同于Windows 2000内核。XP下的调度程序(scheduler)只有两个线程链表:KiWaitListHead和

KiDispatcherReadyListHead。我们可以通过搜索KeDelayExecutionThread函数来查找KeWaitListHead:

Code:

.text:004055B5 mov dword ptr [ebx], offset KiWaitListHead

.text:004055BB mov [ebx+4], eax

搜索代码如下:

Code:

void XPGetKiWaitListHead()

{

PUCHAR cPtr, pOpcode;

ULONG Length;

for (cPtr = (PUCHAR)KeDelayExecutionThread;

cPtr < (PUCHAR)KeDelayExecutionThread + PAGE_SIZE;

cPtr += Length)

{

Length = SizeOfCode(cPtr, &pOpcode);

if (!Length) break;

if (*(PUSHORT)cPtr == 0x03C7 && *(PUSHORT)(pOpcode + 6) == 0x4389)

{

KiWaitInListHead = *(PLIST_ENTRY *)(pOpcode + 2);

break;

}

}

return;

}

最困难的是查找KiDispatcherReadyListHead。主要的问题是KiDispatcherReadyListHead的地址并没有被任何一个导出的函数使用。因此就要

用更加复杂的搜索算法搞定它。就从KiDispatchInterrupt函数开始,我们感兴趣的地方只有这里:

Code:

.text:00404E72 mov byte ptr [edi+50h], 1

.text:00404E76 call sub_404C5A

.text:00404E7B mov cl, 1

.text:00404E7D call sub_404EB9

这段代码中的第一个函数调用指向的就是包含KiDispatcherReadyListHead引用的函数。尽管如此,搜索KiDispatcherReadyListHead的地址却

变的更加复杂,因为这个函数的相关代码在Windows XP SP1和SP2中是不同的。在SP2中它是这个样子:

Code:

.text:00404CCD add eax, 60h

.text:00404CD0 test bl, bl

.text:00404CD2 lea edx, KiDispatcherReadyListHead[ecx*8]

.text:00404CD9 jnz loc_401F12

.text:00404CDF mov esi, [edx+4]

And in SP1:

SP1中是这样的:

Code:

.text:004180FE add eax, 60h

.text:00418101 cmp [ebp+var_1], bl

.text:00418104 lea edx, KiDispatcherReadyListHead[ecx*8]

.text:0041810B jz loc_418760

.text:00418111 mov esi, [edx]

仅仅查找一个“lea”指令是不可靠的,因此我们也应该检查“lea”后面的指令(LDasm中的IsRelativeCmd函数)。搜索

KiDispatcherReadyListHead的全部代码如下:

Code:

void XPGetKiDispatcherReadyListHead()

{

PUCHAR cPtr, pOpcode;

PUCHAR CallAddr = NULL;

ULONG Length;

for (cPtr = (PUCHAR)KiDispatchInterrupt;

cPtr < (PUCHAR)KiDispatchInterrupt + PAGE_SIZE;

cPtr += Length)

{

Length = SizeOfCode(cPtr, &pOpcode);

if (!Length) return;

if (*pOpcode == 0xE8 && *(PUSHORT)(pOpcode + 5) == 0x01B1)

{

CallAddr = (PUCHAR)(*(PULONG)(pOpcode + 1) + (ULONG)cPtr + Length);

break;

}

}

if (!CallAddr || !MmIsAddressValid(CallAddr)) return;

for (cPtr = CallAddr; cPtr < CallAddr + PAGE_SIZE; cPtr += Length)

{

Length = SizeOfCode(cPtr, &pOpcode);

if (!Length) return;

if (*(PUSHORT)pOpcode == 0x148D && *(pOpcode + 2) == 0xCD && IsRelativeCmd(pOpcode + 7))

{

KiDispatcherReadyListHead = *(PLIST_ENTRY *)(pOpcode + 3);

break;

}

}

return;

}

找到线程链表地址之后我们就可以非常简单地枚举出那些进程了,代码如下:

Code:

void ProcessListHead(PLIST_ENTRY ListHead)

{

PLIST_ENTRY Item;

if (ListHead)

{

Item = ListHead->Flink;

while (Item != ListHead)

{

CollectProcess(*(PEPROCESS *)((ULONG)Item + WaitProcOffset));

Item = Item->Flink;

}

}

return;

}

CollectProcess是一个非常有用的函数,它可以增加一个进程到进程列表中去。

通过拦截系统调用得到进程列表。

任何一个进程都要通过API来和系统进行交互,而大多数交互都通过系统调用传递给了内核。当然,进程也可以不使用任何API而存在,但是这

样一来它也就不能做任何有用(或有害)的事情。一般而言,我们的思路是使用系统调用管理器拦截系统调用,然后得到管理器中当前进程的

EPROCESS指针。应该在某段时间收集指针列表,这个表不会包含信息收集时没有使用任何系统调用的进程(比如,进程的线程都处于等待状态

)。

Windows 2000平台使用2Eh中断进行系统调用,因此我们需要修改IDT中的相应的中断描述符来拦截系统调用,这就要用sidt指令得到IDT在内存

中的位置。该指令返回这样一个结构:

Code:

typedef struct _Idt

{

USHORT Size;

ULONG Base;

} TIdt;

修改2Eh中断向量的代码如下:

Code:

void Set2kSyscallHook()

{

TIdt Idt;

__asm

{

pushad

cli

sidt [Idt]

mov esi, NewSyscall

mov ebx, Idt.Base

xchg [ebx + 0x170], si

rol esi, 0x10

xchg [ebx + 0x176], si

ror esi, 0x10

mov OldSyscall, esi

sti

popad

}

}

当然在卸载驱动之前还要保存原始状态的信息:

Code:

void Win2kSyscallUnhook()

{

TIdt Idt;

__asm

{

pushad

cli

sidt [Idt]

mov esi, OldSyscall

mov ebx, Idt.Base

mov [ebx + 0x170], si

rol esi, 0x10

mov [ebx + 0x176], si

sti

xor eax, eax

mov OldSyscall, eax

popad

}

}

Windows XP使用sysenter/sysexit指令(出现在Pentium 2处理器中)实现系统调用。这些指令的功能由model-specific registers(MSR)控制

。系统调用管理器的地址保存在MSR寄存器,SYSENTER_EIP_MSR(0x176)中。用rdmsr指令读取MSR寄存器,同时设置ECX = 要读取的寄存器的号

码,结果保存在两个积存器EDX:EAX中。在我们这里,SYSENTER_EIP_MSR积存器是32位积存器,所以EDX为0,EAX内是系统调用管理器的地址。

同样地,我们也可以用wrmsr指令写MSR积存器。有一个地方需要注意:当写32位MSR积存器的时候,EDX应该被清空,否则将引起异常并且导致

系统立即崩溃。

考虑到所有的事情之后,替代系统调用管理器的代码如下:

Code:

void SetXpSyscallHook()

{

__asm

{

pushad

mov ecx, 0x176

rdmsr

mov OldSyscall, eax

mov eax, NewSyscall

xor edx, edx

wrmsr

popad

}

}

恢复原始的系统调用管理器代码:

Code:

void XpSyscallUnhook()

{

__asm

{

pushad

mov ecx, 0x176

mov eax, OldSyscall

xor edx, edx

wrmsr

xor eax, eax

mov OldSyscall, eax

popad

}

}

Windows XP的另外一个特性是它既可以使用sysenter也可以使用int 2Eh来进行系统调用,所以我们要替换这两种情况下的系统调用管理器。

我们的新的系统调用管理器应该得到当前进程的EPROCESS指针,并且如果是一个新的进程,我们要把这个新的进程加到我们的进程列表中。

新的系统调用管理器代码如下:

Code:

void __declspec(naked) NewSyscall()

{

__asm

{

pushad

pushfd

push fs

mov di, 0x30

mov fs, di

mov eax, fs:[0x124]

mov eax, [eax + 0x44]

push eax

call CollectProcess

pop fs

popfd

popad

jmp OldSyscall

}

}

得到进程列表的这段代码应该在某个时间段内工作,所以我们有这样的问题:如果在列表中的进程被删除掉,在随后的时间内我们将保留一些

无效指针,结果就是检测隐藏进程失败或者导致系统BSOD,

解决这个问题的办法是,用PsSetCreateProcessNotifyRoutine函数注册我们的回调

函数,这个回调函数将会在系统创建或者销毁一个进程的时候被调用。当进程被销毁时,我们也应该把它从我们的表中删除掉。

回调函数的原型如下:

Code:

VOID

(*PCREATE_PROCESS_NOTIFY_ROUTINE) (

IN HANDLE ParentId,

IN HANDLE ProcessId,

IN BOOLEAN Create

);

安装回调函数的代码如下:

Code:

PsSetCreateProcessNotifyRoutine (NotifyRoutine, FALSE);

取消回调函数的代码:

Code:

PsSetCreateProcessNotifyRoutine (NotifyRoutine, TRUE);

这里有一个问题,回调函数总是在系统被销毁的时候创建,因此我们不可能直接在这个回调函数中删除进程列表中的相应进程。这样我们就要

用系统的work items,首先调用IoAllocateWorkItem函数为work item分配内存,然后调用IoQueueWorkItem函数(俄文翻译者kao注:这一句我

不太确定...)将任务放置到工作线程队列中。在处理过程中我们不仅仅从进程列表中删除掉已经终止的进程,而且还要加入新创建的线程。处

理代码如下:

Code:

void WorkItemProc(PDEVICE_OBJECT DeviceObject, PWorkItemStruct Data)

{

KeWaitForSingleObject(Data->pEPROCESS, Executive, KernelMode, FALSE, NULL);

DelItem(&wLastItem, Data->pEPROCESS);

ObDereferenceObject(Data->pEPROCESS);

IoFreeWorkItem(Data->IoWorkItem);

ExFreePool(Data);

return;

}

void NotifyRoutine(IN HANDLE ParentId,

IN HANDLE ProcessId,

IN BOOLEAN Create)

{

PEPROCESS process;

PWorkItemStruct Data;

if (Create)

{

PsLookupProcessByProcessId(ProcessId, &process);

if (!IsAdded(wLastItem, process)) AddItem(&wLastItem, process);

ObDereferenceObject(process);

} else

{

process = PsGetCurrentProcess();

ObReferenceObject(process);

Data = ExAllocatePool(NonPagedPool, sizeof(TWorkItemStruct));

Data->IoWorkItem = IoAllocateWorkItem(deviceObject);

Data->pEPROCESS = process;

IoQueueWorkItem(Data->IoWorkItem, WorkItemProc, DelayedWorkQueue, Data);

}

return;

}

这是一个相对可靠的隐藏进程的检测方式,然而虽然没有进程能够不倚赖系统调用,但还是有一些进程可以在很长一段时间处于等待状态不进

行系统调用,我们无法检测出这样的进程。

只要想做,躲避开这种检测方式还是很容易的。想要做到这一点,那就需要改变隐藏进程的系统调用方式(重定向到另外一个中断或者GDT中的

调用门)。在Windows XP下做这个工作是相当简单的,因为可以给ntdll.dll中的KiFastSystemCall函数打补丁和创建一个相应的系统调用门。

在Windows 2000平台下就稍微有点难度了,因为int 2Eh调用分散遍及整个ntdll,但是找到并patch所有的地方也并不是很复杂。综上所述,依

赖于这种检测方式可不是聪明之举。

通过遍历句柄表得到进程列表。

如果你曾经尝试过利用删除PsActiveProcesses链表中的进程节点来隐藏进程,可能你会注意到当你调用ZwQuerySystemInformation函数枚举句

柄的时候,隐藏进程的句柄也会被枚举出来,并且还能被检测出它的ProcessId。这是因为为了方便枚举句柄,所有的句柄表都是由一个双向链

表HandleTableList维护的。Windows 2000下HANDLE_TABLE结构在链表中的偏移等于0x054,Windows XP下为0x01C,链表由

HandleTableListHead开始。HANDLE_TABLE结构包括它的宿主进程的指针(QuotaProcess),Windows 2000下这个偏移等于0x00C,Windows XP

下这个偏移为0x004。通过遍历这个句柄链表我们就可以构建进程列表了。

首先我们得找到HandleTableListHead。反汇编内核显示它的引用定位在函数的深处,所以前面我们用过的反汇编代码的方法已经不能在这里使

用了。要找到HeadleTableListHead,我们要注意到HandleTableListHead是一个全局的内核变量,因此它一定是在内核文件的某一个段

(Section)里面,并且HandleTableList的其他成员是在动态分配的内存中,所以总是受到内核地址空间的限制。根据这些,我们需要得到任

何一个进程的HandleTable的指针,然后遍历链表直到找到定位在这个内核地址空间的成员,那么这个成员就是HandleTableListHead了。

我们使用ZwQuerySystemInformation和SystemModuleInformation类计算系统内核的基址和大小。它将返回一个所有已经加载了的模块的描述符

表,并且这个表的第一个成员始终是“system”。综上所述,查找HandleTableListHead的代码如下:

Code:

void GetHandleTableListHead()

{

PSYSTEM_MODULE_INFORMATION_EX Info = GetInfoTable(SystemModuleInformation);

ULONG NtoskrnlBase = (ULONG)Info->Modules[0].Base;

ULONG NtoskrnlSize = Info->Modules[0].Size;

PHANDLE_TABLE HandleTable = *(PHANDLE_TABLE *)((ULONG)PsGetCurrentProcess() + HandleTableOffset);

PLIST_ENTRY HandleTableList = (PLIST_ENTRY)((ULONG)HandleTable + HandleTableListOffset);

PLIST_ENTRY CurrTable;

ExFreePool(Info);

for (CurrTable = HandleTableList->Flink;

CurrTable != HandleTableList;

CurrTable = CurrTable->Flink)

{

if ((ULONG)CurrTable >NtoskrnlBase && (ULONG)CurrTable < NtoskrnlBase + NtoskrnlSize)

{

HandleTableListHead = CurrTable;

break;

}

}

}

这段代码是非常通用的,它可以运行于任何Windows NT版本的系统上,并且不仅可以用来查找HandleTableListHead,也可以用于其他类似的结

构。

得到HandleTableListHead地址后我们就可以遍历句柄表并基于这些信息来构建进程列表了。

Code:

void ScanHandleTablesList()

{

PLIST_ENTRY CurrTable;

PEPROCESS QuotaProcess;

for (CurrTable = HandleTableListHead->Flink;

CurrTable != HandleTableListHead;

CurrTable = CurrTable->Flink)

{

QuotaProcess = *(PEPROCESS *)((PUCHAR)CurrTable - HandleTableListOffset + QuotaProcessOffset);

if (QuotaProcess) CollectProcess(QuotaProcess);

}

}

F-Secure Black Light和KProcCheck的最后一个版本用的就是这种检测方法。我想你将会很轻松地找到对付这种检测的方法。

通过扫描PspCidTable得到进程列表。

有一件有趣的事情需要注意:如果仅仅把进程节点从PsActiveProcesses链表中删除,它不能够防止使用API函数OpenProcess打开进程。这样就

有一种检测进程的方法就是尝试穷举Pid然后调用OpenProcess。我不推荐这个方法,因为它没有任何优点,我甚至想说这是一种“ ”方案

。不过它的存在意味着在系统中除了通过PsActiveProcesses得到进程列表之外还可以通过调用OpenProcess。当穷举ProcessId的时候我们会注

意到一个进程可以被几个不同的Pid打开,这暗示可能存在着有点像HANDLE_TABLE的另一个进程列表。为了证明这个的猜想,我们来看看

ZwOpenProcess函数:

Code:

PAGE:0049D59E ; NTSTATUS __stdcall NtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess,

POBJECT_ATTRIBUTES ObjectAttributes,PCLIENT_ID ClientId)

PAGE:0049D59E public NtOpenProcess

PAGE:0049D59E NtOpenProcess proc near

PAGE:0049D59E

PAGE:0049D59E ProcessHandle = dword ptr 4

PAGE:0049D59E DesiredAccess = dword ptr 8

PAGE:0049D59E bjectAttributes= dword ptr 0Ch

PAGE:0049D59E ClientId = dword ptr 10h

PAGE:0049D59E

PAGE:0049D59E push 0C4h

PAGE:0049D5A3 push offset dword_413560 ; int

PAGE:0049D5A8 call sub_40BA92

PAGE:0049D5AD xor esi, esi

PAGE:0049D5AF mov [ebp-2Ch], esi

PAGE:0049D5B2 xor eax, eax

PAGE:0049D5B4 lea edi, [ebp-28h]

PAGE:0049D5B7 stosd

PAGE:0049D5B8 mov eax, large fs:124h

PAGE:0049D5BE mov al, [eax+140h]

PAGE:0049D5C4 mov [ebp-34h], al

PAGE:0049D5C7 test al, al

PAGE:0049D5C9 jz loc_4BE034

PAGE:0049D5CF mov [ebp-4], esi

PAGE:0049D5D2 mov eax, MmUserProbeAddress

PAGE:0049D5D7 mov ecx, [ebp+8]

PAGE:0049D5DA cmp ecx, eax

PAGE:0049D5DC jnb loc_520CDE

PAGE:0049D5E2 loc_49D5E2:

PAGE:0049D5E2 mov eax, [ecx]

PAGE:0049D5E4 mov [ecx], eax

PAGE:0049D5E6 mov ebx, [ebp+10h]

PAGE:0049D5E9 test bl, 3

PAGE:0049D5EC jnz loc_520CE5

PAGE:0049D5F2 loc_49D5F2:

PAGE:0049D5F2 mov eax, MmUserProbeAddress

PAGE:0049D5F7 cmp ebx, eax

PAGE:0049D5F9 jnb loc_520CEF

PAGE:0049D5FF loc_49D5FF:

PAGE:0049D5FF cmp [ebx+8], esi

PAGE:0049D602 setnz byte ptr [ebp-1Ah]

PAGE:0049D606 mov ecx, [ebx+0Ch]

PAGE:0049D609 mov [ebp-38h], ecx

PAGE:0049D60C mov ecx, [ebp+14h]

PAGE:0049D60F cmp ecx, esi

PAGE:0049D611 jz loc_4CCB88

PAGE:0049D617 test cl, 3

PAGE:0049D61A jnz loc_520CFB

PAGE:0049D620 loc_49D620:

PAGE:0049D620 cmp ecx, eax

PAGE:0049D622 jnb loc_520D0D

PAGE:0049D628 loc_49D628:

PAGE:0049D628 mov eax, [ecx]

PAGE:0049D62A mov [ebp-2Ch], eax

PAGE:0049D62D mov eax, [ecx+4]

PAGE:0049D630 mov [ebp-28h], eax

PAGE:0049D633 mov byte ptr [ebp-19h], 1

PAGE:0049D637 loc_49D637:

PAGE:0049D637 or dword ptr [ebp-4], 0FFFFFFFFh

PAGE:0049D63B loc_49D63B:

PAGE:0049D63B

PAGE:0049D63B cmp byte ptr [ebp-1Ah], 0

PAGE:0049D63F jnz loc_520D34

PAGE:0049D645 loc_49D645:

PAGE:0049D645 mov eax, PsProcessType

PAGE:0049D64A add eax, 68h

PAGE:0049D64D push eax

PAGE:0049D64E push dword ptr [ebp+0Ch]

PAGE:0049D651 lea eax, [ebp-0D4h]

PAGE:0049D657 push eax

PAGE:0049D658 lea eax, [ebp-0B8h]

PAGE:0049D65E push eax

PAGE:0049D65F call SeCreateAccessState

PAGE:0049D664 cmp eax, esi

PAGE:0049D666 jl loc_49D718

PAGE:0049D66C push dword ptr [ebp-34h] ; PreviousMode

PAGE:0049D66F push ds:stru_5B6978.HighPart

PAGE:0049D675 push ds:stru_5B6978.LowPart ; PrivilegeValue

PAGE:0049D67B call SeSinglePrivilegeCheck

PAGE:0049D680 test al, al

PAGE:0049D682 jnz loc_4AA7DB

PAGE:0049D688 loc_49D688:

PAGE:0049D688 cmp byte ptr [ebp-1Ah], 0

PAGE:0049D68C jnz loc_520D52

PAGE:0049D692 cmp byte ptr [ebp-19h], 0

PAGE:0049D696 jz loc_4CCB9A

PAGE:0049D69C mov [ebp-30h], esi

PAGE:0049D69F cmp [ebp-28h], esi

PAGE:0049D6A2 jnz loc_4C1301

PAGE:0049D6A8 lea eax, [ebp-24h]

PAGE:0049D6AB push eax

PAGE:0049D6AC push dword ptr [ebp-2Ch]

PAGE:0049D6AF call PsLookupProcessByProcessId

PAGE:0049D6B4 loc_49D6B4:

正如你看到的,这段代码拷贝给定的指针,检查是否指向用户地址空间,核对访问权限和是否有“SetDebugPrivilege”的权限,然后从

CLIENT_ID结构中找到ProcessId并传递给PsLookupProcessByProcessId函数,PsLookupProcessByProcessId的功能是得到ProcessId的EPROCESS

。函数的其余部分对我们来说没什么用,现在我们来看看PsLookupProcessByProcessId:

Code:

PAGE:0049D725 public PsLookupProcessByProcessId

PAGE:0049D725 PsLookupProcessByProcessId proc near

PAGE:0049D725

PAGE:0049D725

PAGE:0049D725 ProcessId = dword ptr 8

PAGE:0049D725 Process = dword ptr 0Ch

PAGE:0049D725

PAGE:0049D725 mov edi, edi

PAGE:0049D727 push ebp

PAGE:0049D728 mov ebp, esp

PAGE:0049D72A push ebx

PAGE:0049D72B push esi

PAGE:0049D72C mov eax, large fs:124h

PAGE:0049D732 push [ebp+ProcessId]

PAGE:0049D735 mov esi, eax

PAGE:0049D737 dec dword ptr [esi+0D4h]

PAGE:0049D73D push PspCidTable

PAGE:0049D743 call ExMapHandleToPointer

PAGE:0049D748 mov ebx, eax

PAGE:0049D74A test ebx, ebx

PAGE:0049D74C mov [ebp+ProcessId], STATUS_INVALID_PARAMETER

PAGE:0049D753 jz short loc_49D787

PAGE:0049D755 push edi

PAGE:0049D756 mov edi, [ebx]

PAGE:0049D758 cmp byte ptr [edi], 3

PAGE:0049D75B jnz short loc_49D77A

PAGE:0049D75D cmp dword ptr [edi+1A4h], 0

PAGE:0049D764 jz short loc_49D77A

PAGE:0049D766 mov ecx, edi

PAGE:0049D768 call sub_4134A9

PAGE:0049D76D test al, al

PAGE:0049D76F jz short loc_49D77A

PAGE:0049D771 mov eax, [ebp+Process]

PAGE:0049D774 and [ebp+ProcessId], 0

PAGE:0049D778 mov [eax], edi

PAGE:0049D77A loc_49D77A:

PAGE:0049D77A push ebx

PAGE:0049D77B push PspCidTable

PAGE:0049D781 call ExUnlockHandleTableEntry

PAGE:0049D786 pop edi

PAGE:0049D787 loc_49D787:

PAGE:0049D787 inc dword ptr [esi+0D4h]

PAGE:0049D78D jnz short loc_49D79A

PAGE:0049D78F lea eax, [esi+34h]

PAGE:0049D792 cmp [eax], eax

PAGE:0049D794 jnz loc_52388A

PAGE:0049D79A loc_49D79A:

PAGE:0049D79A mov eax, [ebp+ProcessId]

PAGE:0049D79D pop esi

PAGE:0049D79E pop ebx

PAGE:0049D79F pop ebp

PAGE:0049D7A0 retn 8

以上我们所看到的,证实了存在像HANDLE_TABLE一样组织结构的第2个进程列表。这个表叫做PspCidTable,它包括进程和线程的列表,

PsLookupProcessThreadByCid函数和PsLookupThreadByThreadId函数都用到了这个表。我们看到,句柄和句柄表的指针被传递给了

ExMapHandleToPointer函数,该函数(在句柄有效的情况下)返回一个指向描述给定句柄的表的一个元素 - HANDLE_TABLE_ENTRY。当我们用

PDBdump分析完ntoskrnl.pdb并且得到分析日志后,会得到如下结果:

Code:

struct _HANDLE_TABLE_ENTRY {

// static data ------------------------------------

// non-static data --------------------------------

/**/ /*|0x4|*/ void* Object;

/**/ /*|0x4|*/ unsigned long ObAttributes;

/**/ /*|0x4|*/ struct _HANDLE_TABLE_ENTRY_INFO* InfoTable;

/**/ /*|0x4|*/ unsigned long Value;

/**/ /*|0x4|*/ unsigned long GrantedAccess;

/**/ /*|0x2|*/ unsigned short GrantedAccessIndex;

/**/ /*|0x2|*/ unsigned short CreatorBackTraceIndex;

/**/ /*|0x4|*/ long NextFreeTableEntry;

};//

We can recover HANDLE_TABLE_ENTRY structure from this:

我们还原一下HANDLE_TABLE_ENTRY结构的C代码:

Code:

typedef struct _HANDLE_TABLE_ENTRY

{

union

{

PVOID Object;

ULONG ObAttributes;

PHANDLE_TABLE_ENTRY_INFO InfoTable;

ULONG Value;

};

union

{

union

{

ACCESS_MASK GrantedAccess;

struct

{

USHORT GrantedAccessIndex;

USHORT CreatorBackTraceIndex;

};

};

LONG NextFreeTableEntry;

};

} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

怎么使用它呢?首先,我们比较感兴趣的是Object域的内容,它是被句柄描述的目标指针和这个表的给定元素的用法标志(我将稍后解释这句

话)。GrantedAccess域指定了通过这个句柄对目标的访问权限许可,这个很有趣。比如,以只读方式打开一个文件,修改这个域之后就可以写

这个文件了。这个方法可以用在对一些正在被读/写的文件的访问上(比如,正在被其他进程锁定的文件)。应该回到我们的问题上来了 - 通

过对PspCidTable的分析得到进程句柄列表。

要分析它我们得了解句柄表的格式,这样才能遍历这个列表。在这个地方Windows 2000和Windows XP有着巨大的不同。由于句柄表格式不尽相

同,所以我们应该把操作系统分类进行分析。

因为Windows 2000的句柄表相对简单一些,所以我们先分析它。先来看看ExMapHandleToPointer函数:

Code:

PAGE:00493285 ExMapHandleToPointer proc near

PAGE:00493285

PAGE:00493285

PAGE:00493285 HandleTable = dword ptr 8

PAGE:00493285 Handle = dword ptr 0Ch

PAGE:00493285

PAGE:00493285 push esi

PAGE:00493286 push [esp+Handle]

PAGE:0049328A push [esp+4+HandleTable]

PAGE:0049328E call ExpLookupHandleTableEntry

PAGE:00493293 mov esi, eax

PAGE:00493295 test esi, esi

PAGE:00493297 jz short loc_4932A9

PAGE:00493299 push esi

PAGE:0049329A push [esp+4+HandleTable]

PAGE:0049329E call ExLockHandleTableEntry

PAGE:004932A3 neg al

PAGE:004932A5 sbb eax, eax

PAGE:004932A7 and eax, esi

PAGE:004932A9 loc_4932A9:

PAGE:004932A9 pop esi

PAGE:004932AA retn 8

PAGE:004932AA ExMapHandleToPointer endp

这里我们调用搜索HANDLE_TABLE的函数ExMapHandleToPointer以及设置Lock Bit的ExLockHandleTableEntry函数。要了解句柄表的内部结构我

们必须反汇编这些函数。先从ExpLookupHandleTableEntry函数开始:

Code:

PAGE:00493545 ExpLookupHandleTableEntry proc near

PAGE:00493545

PAGE:00493545

PAGE:00493545 HandleTable = dword ptr 0Ch

PAGE:00493545 Handle = dword ptr 10h

PAGE:00493545

PAGE:00493545 push esi

PAGE:00493546 push edi

PAGE:00493547 mov edi, [esp+Handle]

PAGE:0049354B mov eax, 0FFh

PAGE:00493550 mov ecx, edi

PAGE:00493552 mov edx, edi

PAGE:00493554 mov esi, edi

PAGE:00493556 shr ecx, 12h

PAGE:00493559 shr edx, 0Ah

PAGE:0049355C shr esi, 2

PAGE:0049355F and ecx, eax

PAGE:00493561 and edx, eax

PAGE:00493563 and esi, eax

PAGE:00493565 test edi, 0FC000000h

PAGE:0049356B jnz short loc_49358A

PAGE:0049356D mov eax, [esp+HandleTable]

PAGE:00493571 mov eax, [eax+8]

PAGE:00493574 mov ecx, [eax+ecx*4]

PAGE:00493577 test ecx, ecx

PAGE:00493579 jz short loc_49358A

PAGE:0049357B mov ecx, [ecx+edx*4]

PAGE:0049357E test ecx, ecx

PAGE:00493580 jz short loc_49358A

PAGE:00493582 lea eax, [ecx+esi*8]

PAGE:00493585 loc_493585:

PAGE:00493585 pop edi

PAGE:00493586 pop esi

PAGE:00493587 retn 8

PAGE:0049358A loc_49358A:

PAGE:0049358A xor eax, eax

PAGE:0049358C jmp short loc_493585

PAGE:0049358C ExpLookupHandleTableEntry endp

除此之外,我们来看看从ntoskrnl.pdb中得到的HANDLE_TABLE结构:

Code:

struct _HANDLE_TABLE {

// static data ------------------------------------

// non-static data --------------------------------

/**/ /*|0x4|*/ unsigned long Flags;

/**/ /*|0x4|*/ long HandleCount;

/**/ /*|0x4|*/ struct _HANDLE_TABLE_ENTRY*** Table;

/**/ /*|0x4|*/ struct _EPROCESS* QuotaProcess;

/**/ /*|0x4|*/ void* UniqueProcessId;

/**/ /*|0x4|*/ long FirstFreeTableEntry;

/**/ /*|0x4|*/ long NextIndexNeedingPool;

/**/ /*|0x38|*/ struct _ERESOURCE HandleTableLock;

/**/ /*|0x8|*/ struct _LIST_ENTRY HandleTableList;

/**/ /*|0x10|*/ struct _KEVENT HandleContentionEvent;

}; //

根据这些数据我们用C语言还原这个结构:

Code:

typedef struct _WIN2K_HANDLE_TABLE

{

ULONG Flags;

LONG HandleCount;

PHANDLE_TABLE_ENTRY **Table;

PEPROCESS QuotaProcess;

HANDLE UniqueProcessId;

LONG FirstFreeTableEntry;

LONG NextIndexNeedingPool;

ERESOURCE HandleTableLock;

LIST_ENTRY HandleTableList;

KEVENT HandleContentionEvent;

} WIN2K_HANDLE_TABLE , *PWIN2K_HANDLE_TABLE ;

显而易见,句柄表由对象表的三个层次的索引组成。现在我们再来看看ExLookhandleTableEntry函数:

Code:

PAGE:00492E2B ExLockHandleTableEntry proc near

PAGE:00492E2B

PAGE:00492E2B

PAGE:00492E2B var_8 = dword ptr -8

PAGE:00492E2B var_4 = dword ptr -4

PAGE:00492E2B HandleTable = dword ptr 8

PAGE:00492E2B Entry = dword ptr 0Ch

PAGE:00492E2B

PAGE:00492E2B push ebp

PAGE:00492E2C mov ebp, esp

PAGE:00492E2E push ecx

PAGE:00492E2F push ecx

PAGE:00492E30 push ebx

PAGE:00492E31 push esi

PAGE:00492E32 xor ebx, ebx

PAGE:00492E34 loc_492E34:

PAGE:00492E34 mov eax, [ebp+Entry]

PAGE:00492E37 mov esi, [eax]

PAGE:00492E39 test esi, esi

PAGE:00492E3B mov [ebp+var_8], esi

PAGE:00492E3E jz short loc_492E89

PAGE:00492E40 jle short loc_492E64

PAGE:00492E42 mov eax, esi

PAGE:00492E44 or eax, 80000000h // set WIN2K_TABLE_ENTRY_LOCK_BIT

PAGE:00492E49 mov [ebp+var_4], eax

PAGE:00492E4C mov eax, [ebp+var_8]

PAGE:00492E4F mov ecx, [ebp+Entry]

PAGE:00492E52 mov edx, [ebp+var_4]

PAGE:00492E55 cmpxchg [ecx], edx

PAGE:00492E58 cmp eax, esi

PAGE:00492E5A jnz short loc_492E64

PAGE:00492E5C mov al, 1

PAGE:00492E5E loc_492E5E:

PAGE:00492E5E pop esi

PAGE:00492E5F pop ebx

PAGE:00492E60 leave

PAGE:00492E61 retn 8

PAGE:00492E64 loc_492E64:

PAGE:00492E64 mov eax, ebx

PAGE:00492E66 inc ebx

PAGE:00492E67 cmp eax, 1

PAGE:00492E6A jb loc_4BC234

PAGE:00492E70 mov eax, [ebp+HandleTable]

PAGE:00492E73 push offset unk_46D240 ; Timeout

PAGE:00492E78 push 0 ; Alertable

PAGE:00492E7A push 0 ; WaitMode

PAGE:00492E7C add eax, 5Ch

PAGE:00492E7F push 0 ; WaitReason

PAGE:00492E81 push eax ; Object

PAGE:00492E82 call KeWaitForSingleObject

PAGE:00492E87 jmp short loc_492E34

PAGE:00492E89 loc_492E89:

PAGE:00492E89 xor al, al

PAGE:00492E8B jmp short loc_492E5E

PAGE:00492E8B ExLockHandleTableEntry endp

这段代码检查了HANDLE_TABLE_ENTRY结构的Object成员的第31位,设置该位,如果该位被设置,意味着等待HANDLE_TABLE的

HandleContentionEvent。对我们来说设置TABLE_ENTRY_LOCK_BIT才是最重要的,因为它是目标地址的一部分,如果标志位没有设置,我们就会

得到无效句柄。现在我们明白了句柄表的格式,可以写代码来遍历这个表了:

Code:

void ScanWin2KHandleTable(PWIN2K_HANDLE_TABLE HandleTable)

{

int i, j, k;

PHANDLE_TABLE_ENTRY Entry;

for (i = 0; i < 0x100; i++)

{

if (HandleTable->Table)

{

for (j = 0; j < 0x100; j++)

{

if (HandleTable->Table[j])

{

for (k = 0; k < 0x100; k++)

{

Entry = &HandleTable->Table[j][k];

if (Entry->Object)

ProcessObject((PVOID)((ULONG)Entry->Object | WIN2K_TABLE_ENTRY_LOCK_BIT));

}

}

}

}

}

}

这段代码处理了所有表中的成员,并且为每一个成员调用了ProcessObject函数。ProcessObject函数检测成员类型并且恰当地处理了它们。这

个函数代码如下:

Code:

void ProcessObject(PVOID Object)

{

POBJECT_HEADER bjectHeader = OBJECT_TO_OBJECT_HEADER(Object);

if (ObjectHeader->Type == *PsProcessType) CollectProcess(Object);

if (ObjectHeader->Type == *PsThreadType) ThreadCollect(Object);

}

我们已经了解了Windows 2000下的句柄表结构,现在开始分析Windows XP的表结构。从反汇编ExpLookupHandleTableEntry函数开始:

Code:

PAGE:0048D3C1 ExpLookupHandleTableEntry proc near

PAGE:0048D3C1

PAGE:0048D3C1

PAGE:0048D3C1 HandleTable = dword ptr 8

PAGE:0048D3C1 Handle = dword ptr 0Ch

PAGE:0048D3C1

PAGE:0048D3C1 mov edi, edi

PAGE:0048D3C3 push ebp

PAGE:0048D3C4 mov ebp, esp

PAGE:0048D3C6 and [ebp+Handle], 0FFFFFFFCh

PAGE:0048D3CA mov eax, [ebp+Handle]

PAGE:0048D3CD mov ecx, [ebp+HandleTable]

PAGE:0048D3D0 mov edx, [ebp+Handle]

PAGE:0048D3D3 shr eax, 2

PAGE:0048D3D6 cmp edx, [ecx+38h]

PAGE:0048D3D9 jnb loc_4958D6

PAGE:0048D3DF push esi

PAGE:0048D3E0 mov esi, [ecx]

PAGE:0048D3E2 mov ecx, esi

PAGE:0048D3E4 and ecx, 3 // ecx - table level

PAGE:0048D3E7 and esi, not 3 // esi - pointer to first table

PAGE:0048D3EA sub ecx, 0

PAGE:0048D3ED jnz loc_48DEA4

PAGE:0048D3F3 lea eax, [esi+eax*8]

PAGE:0048D3F6 loc_48D3F6:

PAGE:0048D3F6 pop esi

PAGE:0048D3F7 loc_48D3F7:

PAGE:0048D3F7 pop ebp

PAGE:0048D3F8 retn 8

PAGE:0048DEA4 loc_48DEA4:

PAGE:0048DEA4 dec ecx

PAGE:0048DEA5 mov ecx, eax

PAGE:0048DEA7 jnz loc_52F57A

PAGE:0048DEAD shr ecx, 9

PAGE:0048DEB0 mov ecx, [esi+ecx*4]

PAGE:0048DEB3 loc_48DEB3:

PAGE:0048DEB3 and eax, 1FFh

PAGE:0048DEB8 lea eax, [ecx+eax*8]

PAGE:0048DEBB jmp loc_48D3F6

PAGE:0052F57A loc_52F57A:

PAGE:0052F57A shr ecx, 13h

PAGE:0052F57D mov edx, ecx

PAGE:0052F57F mov ecx, [esi+ecx*4]

PAGE:0052F582 shl edx, 13h

PAGE:0052F585 sub eax, edx

PAGE:0052F587 mov edx, eax

PAGE:0052F589 shr edx, 9

PAGE:0052F58C mov ecx, [ecx+edx*4]

PAGE:0052F58F jmp loc_48DEB3

再来看看ntoskrnl.pdb中的HANDLE_TABLE:

Code:

struct _HANDLE_TABLE {

// static data ------------------------------------

// non-static data --------------------------------

/**/ /*|0x4|*/ unsigned long TableCode;

/**/ /*|0x4|*/ struct _EPROCESS* QuotaProcess;

/**/ /*|0x4|*/ void* UniqueProcessId;

/**/ /*|0x10|*/ struct _EX_PUSH_LOCK HandleTableLock[4];

/**/ /*|0x8|*/ struct _LIST_ENTRY HandleTableList;

/**/ /*|0x4|*/ struct _EX_PUSH_LOCK HandleContentionEvent;

/**/ /*|0x4|*/ struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo;

/**/ /*|0x4|*/ long ExtraInfoPages;

/**/ /*|0x4|*/ unsigned long FirstFree;

/**/ /*|0x4|*/ unsigned long LastFree;

/**/ /*|0x4|*/ unsigned long NextHandleNeedingPool;

/**/ /*|0x4|*/ long HandleCount;

/**/ /*|0x4|*/ unsigned long Flags;

/**/ /*|0x1|*/ unsigned char StrictFIF0:1;

}; //

利用以上信息还原该结构:

Code:

typedef struct _XP_HANDLE_TABLE

{

ULONG TableCode;

PEPROCESS QuotaProcess;

PVOID UniqueProcessId;

EX_PUSH_LOCK HandleTableLock[4];

LIST_ENTRY HandleTableList;

EX_PUSH_LOCK HandleContentionEvent;

PHANDLE_TRACE_DEBUG_INFO DebugInfo;

LONG ExtraInfoPages;

ULONG FirstFree;

ULONG LastFree;

ULONG NextHandleNeedingPool;

LONG HandleCount;

LONG Flags;

UCHAR StrictFIFO;

} XP_HANDLE_TABLE, *PXP_HANDLE_TABLE;

从上面的表中可以很明显看出ExpLookupHandleTableEntry函数从HANDLE_TABLE结构中得到TableCode的值,并基于其低2位的内容计算出表的层

次数。其余的位指向第1层表。因此Windows XP下的HANDLE_TABLE可以拥有1到3个层次,每一个层次的表的大小为1FFh。当表中记录的数量增加

时,系统会自动增加层数。很明显,当表中记录的数量超过0x200的时候表就会拥有两层,当大于0x40000时增加到第3层。不知道当销毁对象时

系统会不会减少表的层数,我没有注意到这件事。

Windows XP下没有ExLockHandleTableEntry函数,因此表中相应的模块被定位在ExMapHandleToPointer函数中。反汇编这个函数看看它做了什

么?

Code:

PAGE:0048F61E ExMapHandleToPointer proc near

PAGE:0048F61E

PAGE:0048F61E

PAGE:0048F61E var_8 = dword ptr -8

PAGE:0048F61E var_4 = dword ptr -4

PAGE:0048F61E HandleTable = dword ptr 8

PAGE:0048F61E Handle = dword ptr 0Ch

PAGE:0048F61E

PAGE:0048F61E mov edi, edi

PAGE:0048F620 push ebp

PAGE:0048F621 mov ebp, esp

PAGE:0048F623 push ecx

PAGE:0048F624 push ecx

PAGE:0048F625 push edi

PAGE:0048F626 mov edi, [ebp+Handle]

PAGE:0048F629 test di, 7FCh

PAGE:0048F62E jz loc_4A2A36

PAGE:0048F634 push ebx

PAGE:0048F635 push esi

PAGE:0048F636 push edi

PAGE:0048F637 push [ebp+HandleTable]

PAGE:0048F63A call ExpLookupHandleTableEntry

PAGE:0048F63F mov esi, eax

PAGE:0048F641 test esi, esi

PAGE:0048F643 jz loc_4A2711

PAGE:0048F649 mov [ebp+var_4], esi

PAGE:0048F64C loc_48F64C:

PAGE:0048F64C mov ebx, [esi]

PAGE:0048F64E test bl, 1

PAGE:0048F651 mov [ebp+var_8], ebx

PAGE:0048F654 jz loc_508844

PAGE:0048F65A lea eax, [ebx-1]

PAGE:0048F65D mov [ebp+Handle], eax

PAGE:0048F660 mov eax, [ebp+var_8]

PAGE:0048F663 mov ecx, [ebp+var_4]

PAGE:0048F666 mov edx, [ebp+Handle]

PAGE:0048F669 cmpxchg [ecx], edx

PAGE:0048F66C cmp eax, ebx

PAGE:0048F66E jnz loc_50884C

PAGE:0048F674 mov eax, esi

PAGE:0048F676 loc_48F676:

PAGE:0048F676 pop esi

PAGE:0048F677 pop ebx

PAGE:0048F678 loc_48F678:

PAGE:0048F678 pop edi

PAGE:0048F679 leave

PAGE:0048F67A retn 8

PAGE:0048F67A ExMapHandleToPointer endp

ExpLookuphandleTableEntry函数返回指向HANDLE_TABLE_ENTRY的指针后,我们要检查Object域的低字节,如果是被设置了的,说明是被清除了

的(俄文翻译者kao注:希望我翻译的对...),如果该位没有被设置,我们要等到它被设置了为止。因此当得到对象地址的时候我们不应该设

置高位(Windows 2000平台),而是要清除低位。综上所述,扫描表的代码如下:

Code:

void ScanXpHandleTable(PXP_HANDLE_TABLE HandleTable)

{

int i, j, k;

PHANDLE_TABLE_ENTRY Entry;

ULONG TableCode = HandleTable->TableCode & ~TABLE_LEVEL_MASK;

switch (HandleTable->TableCode & TABLE_LEVEL_MASK)

{

case 0 :

for (i = 0; i < 0x200; i++)

{

Entry = &((PHANDLE_TABLE_ENTRY)TableCode);

if (Entry->Object) ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));

}

break;

case 1 :

for (i = 0; i < 0x200; i++)

{

if (((PVOID *)TableCode))

{

for (j = 0; j < 0x200; j++)

{

Entry = &((PHANDLE_TABLE_ENTRY *)TableCode)[j];

if (Entry->Object) ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));

}

}

}

break;

case 2 :

for (i = 0; i < 0x200; i++)

{

if (((PVOID *)TableCode))

{

for (j = 0; j < 0x200; j++)

{

if (((PVOID **)TableCode)[j])

{

for (k = 0; k < 0x200; k++)

{

Entry = &((PHANDLE_TABLE_ENTRY **)TableCode)[j][k];

if (Entry->Object)

ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));

}

}

}

}

}

break;

}

}

我们已经明白对象表格式了。现在想要枚举进程我们还需要得到PspCidTable的地址。也许你已经猜到了,我们要在

PsLookupProcessByProcessId函数中搜索,这个函数中的第1个函数调用包含着PspCidTable的地址。代码如下:

Code:

void GetPspCidTable()

{

PUCHAR cPtr, pOpcode;

ULONG Length;

for (cPtr = (PUCHAR)PsLookupProcessByProcessId;

cPtr < (PUCHAR)PsLookupProcessByProcessId + PAGE_SIZE;

cPtr += Length)

{

Length = SizeOfCode(cPtr, &pOpcode);

if (!Length) break;

if (*(PUSHORT)cPtr == 0x35FF && *(pOpcode + 6) == 0xE8)

{

PspCidTable = **(PVOID **)(pOpcode + 2);

break;

}

}

}

现在我们知道怎样处理PspCidTable了,可以非常容易地查看到所有进程的表中的所有元素,分析那些属于隐藏进程的对象,就像我们在用户态

做的一样,如果你已经理解了前面所讲的东西,你一定可以做得到。

中文参考:《JIURL玩玩Win2k进程线程篇 HANDLE_TABLE》(jiurl.nease.net/document/JiurlPlayWin2k/PsHandleTable.htm)

感谢:firstrose, JIURL, linhanshi.

篇3:了解wdfmgr.exe是什么进程

wdfmgr.exe

进程文件:wdfmgr或者wdfmgr.exe

进程名称:windows driver foundation manager

描述:

wdfmgr.exe是微软microsoftwindowsmediaplayer10播放器的相关程序。该进程用于减少兼容性问题。这不是纯粹的系统程序,但是如果终止它,可能会导致不可知的问题。

出品者:microsoft

属于:microsoft wnidows media player 进程文件:wdfmgr或者wdfmgr.exe

进程名称:windows driver foundation manager

描述:

wdfmgr.exe是微软microsoftwindowsmediaplayer10播放器的相关程序。该进程用于减少兼容性问题。这不是纯粹的系统程序,但是如果终止它,可能会导致不可知的问题。

出品者:microsoft

属于:microsoft wnidows media player

系统进程:否 后台进程:是

使用网络:否

硬件相关:否

常见错误:未知

内存使用:未知

安全等级:0

间谍软件:否

广告软件:否

病毒:否

木马:否

篇4:了解wdfmgr.exe是什么进程

wdfmgr.exe进程其程序文件是微软为其发布的Microsoft Windows Media Player 10(一款音乐、视频播放器)定义的一个驱动程序组件,它支持与硬件播放器保持内容同步(比如MP3、MP4等硬件音频视频播放设备)。spoolsv.exe是什么进程?

显然,Wdfmgr.exe是一个安全的进程,它在Windows系统中被描述为:Windows User Mode Driver Framewor(Windows用户模式驱动程序框架),如果你使用的是Windows XP/系统安装了Windows Media Player 10那么Wdfmgr.exe程序文件便会一起被安装,不过在后来的Windows 7系统中并没有此进程,笔者预计其类似功能被其它进程所代替。

终止Wdfmgr.exe

在默认情况下该进程的“启动类型”设置为“自动”运行。不过如果你的电脑没有连接其他多媒体设备,或对它的运行耿耿于怀那么可以考虑通过以下方法将其终止。如果要,只需要将Windows User Mode Driver Framework 服务的“启动类型”设置为“禁用”或“手动”便可终止Wdfmgr.exe进程的自动运行。操作方法也很简单,点几下鼠标就可以搞定:

依次单击“开始”→“运行”,在运行输入框中键入services.msc然后单击“确定”;

在服务列表中,右键单击“Windows User Mode Driver Framework”,然后单击“属性”。

单击“常规”选项卡。

在“启动类型”框中,单击“禁用”或“手动”,然后单击“确定”。

在“文件”菜单上,单击“退出”。

如果以后有了硬件播放器,并且希望它与你电脑上的Windows Media Player 10多媒体播放器保持内容同步,可以再通过以上方法将“启动类型”设置改回“自动”来启用Windows User Mode Driver Framework服务。

病毒与安全

虽然Wdfmgr.exe进程并非Windows系统自身的系统进程,但由于也算是微软发布的一个较常见进程,故而很多木马病毒开始将自己伪装成Wdfmgr.exe一样或略有不同的名称来欺瞒用户。中毒的系统常常会伴有以下状况:

任务管理器中看到同时有多个在运行(相当可疑);

无法通过上文中的方法将其终止(相当可疑);

100%的CPU使用率(很可疑);

[了解wdfmgr.exe是什么进程]

篇5:巧用金山木马专杀管理进程及启动项WEB安全

对进程及启动项的管理是许多计算机用户经常遇到的问题,许多人发现在安装了较多的应用程序后,系统运行变得越来越慢,或经常提示系统资源不足,导致某些程序无法运行,这就需要删除或修改某些恶意的或是不需要的启动项,将一些影响启动速度及不安全的程序从启动列表中移除;而面对Windows系统中那些繁多的进程时,你知道它们都有什么作用吗?如果你怀疑电脑中了木马病毒,你知道哪些可能是木马程序?哪些是系统进程吗?对系统进程及启动项进行有效的管理可以提高Windows的启动速度及安全性能。

金山木马专杀工具提供了比Windows任务管理器和系统配置程序更为直观易用的管理功能,有经验的用户可以直接从进程中查找当前运行的病毒并手动结束该进程。用户还可以利用此功能非常方便地修改启动项配置。

进程管理:安装《金山毒霸2005安全组合装》后,点击“金山毒霸木马专杀”快捷方式图标,打开其主界面。单击主界面的“系统进程管理”或者在菜单栏中选择“功能→进程管理”,都可进入进程管理的页面,

金山木马专杀2005的进程管理页面,由系统当前运行的进程以及当前进程加载的模块两个显示框组成。在系统当前运行的进程显示框中,列出系统正在运行的所有进程信息,包括进程名、ID、所在路径、该进程的简单描述等。双击选中的进程将会显示该进程的属性。

上传进程:发现可疑进程,用户可以选中该进程,并单击“上传选定进程文件”,在弹出的可疑文件上报方式中选择“通过网络直接将其上报给北京金山软件有限公司”或者“保存在磁盘中,通过其他途径提供给北京金山软件有限公司”,单击确定即可。

结束进程:您也可以在发现可疑进程后,单击“结束选定进程”的方式来结束进程的运行。

在当前进程加载模块显示框中显示该进程所加载的模块并列出这些模块的相关信息,包括模块名及所在路径,双击选中的模块后将显示该模块的属性。

启动项目:单击主界面的“启动项目”或者在菜单栏中选择“功能→启动项目”,即可进入启动项目页面。金山木马专杀2005启动项目页面列出注册表和开始菜单启动项、ini文件里面的所有随机启动项目,并能对相关启动项目进行禁用(不随机启动,可以恢复进行启用)、删除等操作。

在实际使用过程中,感觉金山木马专杀2005的这个功能效果不错,经常性的对系统进程及启动项进行管理,相信对于提高您的Windows操作系统的启动速度及安全性会有更多的帮助。

巧用金山木马专杀管理进程及启动项WEB安全

Apache防止攻击WEB安全

例程讲解Web数据库安全防护

了解近义词

快速了解关于IDS和IPS的安全区别 IDS

Access数据库安全策略之ASP式WEB安全

明察秋毫 密切监视注册表的一举一动WEB安全

伟大的进程作文

微软杰作,给桌面配个“保险箱”WEB安全

采取针对性措施保障 Unix 服务器的安全WEB安全

关于mcafee的进程了解WEB安全(共5篇)

欢迎下载DOC格式的关于mcafee的进程了解WEB安全,但愿能给您带来参考作用!
推荐度: 推荐 推荐 推荐 推荐 推荐
点击下载文档 文档为doc格式
点击下载本文文档