进程隐藏对抗

进程隐藏作用

1.使用户无法在任务管理器中看到进程

2.躲避安全软件的信息查询

进程隐藏有两种实现方式:

1.通过hook 查询进程api来过滤要保护的进程,例如ZwQuerySystemInformation;

2.是通过抹除系统结构中关于进程的信息,如断链EPROCESS中的ActiveProcessLinks(进程活跃链表)等;

 

第一种方法没啥好说的,哪个API查就hook哪个API,虽说都没啥用但文章主要描述第二种,还能熟悉熟悉内核结构。

 

断链实现:

在内核中很多数据使用_LIST_ENTRY,双向循环链表作为数据结构

typedef struct _LIST_ENTRY 
{
       struct _LIST_ENTRY *Flink; 
       struct _LIST_ENTRY *Blink; 
} LIST_ENTRY, *PLIST_ENTRY;

 

 

首先要先了解EPROCESS,和KPROCESS两个重要的结构体; 每个windows进程在0环都有这样一个对应的结构体:

nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK 进程锁
+0x0a0 CreateTime : _LARGE_INTEGER 进程创建时间
+0x0a8 ExitTime : _LARGE_INTEGER 进程结束时间
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : Ptr32 Void PID进程id
+0x0b8 ActiveProcessLinks : _LIST_ENTRY 活跃进程链表 双向循环链表 可断链
+0x0c0 ProcessQuotaUsage : [2] Uint4B 物理页相关统计
+0x0c8 ProcessQuotaPeak : [2] Uint4B
+0x0d0 CommitCharge : Uint4B cpu占用信息
+0x0d4 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x0d8 CpuQuotaBlock : Ptr32 _PS_CPU_QUOTA_BLOCK cpu占用信息
+0x0dc PeakVirtualSize : Uint4B 虚拟内存相关统计
+0x0e0 VirtualSize : Uint4B
+0x0e4 SessionProcessLinks : _LIST_ENTRY
+0x0ec DebugPort : Ptr32 Void 调试相关≠0则被调试
+0x0f0 ExceptionPortData : Ptr32 Void
+0x0f0 ExceptionPortValue : Uint4B
+0x0f0 ExceptionPortState : Pos 0, 3 Bits
+0x0f4 ObjectTable : Ptr32 _HANDLE_TABLE 句柄表 内核对象
+0x0f8 Token : _EX_FAST_REF
+0x0fc WorkingSetPage : Uint4B
+0x100 AddressCreationLock : _EX_PUSH_LOCK
+0x104 RotateInProgress : Ptr32 _ETHREAD
+0x108 ForkInProgress : Ptr32 _ETHREAD
+0x10c HardwareTrigger : Uint4B
+0x110 PhysicalVadRoot : Ptr32 _MM_AVL_TABLE
+0x114 CloneRoot : Ptr32 Void
+0x118 NumberOfPrivatePages : Uint4B
+0x11c NumberOfLockedPages : Uint4B
+0x120 Win32Process : Ptr32 Void
+0x124 Job : Ptr32 _EJOB
+0x128 SectionObject : Ptr32 Void
+0x12c SectionBaseAddress : Ptr32 Void
+0x130 Cookie : Uint4B
+0x134 Spare8 : Uint4B
+0x138 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x13c Win32WindowStation : Ptr32 Void
+0x140 InheritedFromUniqueProcessId : Ptr32 Void 父进程id
+0x144 LdtInformation : Ptr32 Void
+0x148 VdmObjects : Ptr32 Void
+0x14c ConsoleHostProcess : Uint4B
+0x150 DeviceMap : Ptr32 Void
+0x154 EtwDataSource : Ptr32 Void
+0x158 FreeTebHint : Ptr32 Void
+0x160 PageDirectoryPte : Uint8B
+0x168 Session : Ptr32 Void
+0x16c ImageFileName : [15] UChar 进程名最多存16个字符,截断
+0x17b PriorityClass : UChar
+0x17c JobLinks : _LIST_ENTRY
+0x184 LockedPagesList : Ptr32 Void
+0x188 ThreadListHead : _LIST_ENTRY 双向循环链表
+0x190 SecurityPort : Ptr32 Void
+0x194 PaeTop : Ptr32 Void
+0x198 ActiveThreads : Uint4B 活跃线程数量
+0x19c ImagePathHash : Uint4B
+0x1a0 DefaultHardErrorProcessing : Uint4B
+0x1a4 LastThreadExitStatus : Int4B
+0x1a8 Peb : Ptr32 _PEB PEB(process ENVIRONMENT BLOCK 进程环境块)
+0x1ac PrefetchTrace : _EX_FAST_REF
+0x1b0 ReadOperationCount : _LARGE_INTEGER
+0x1b8 WriteOperationCount : _LARGE_INTEGER
+0x1c0 OtherOperationCount : _LARGE_INTEGER
+0x1c8 ReadTransferCount : _LARGE_INTEGER
+0x1d0 WriteTransferCount : _LARGE_INTEGER
+0x1d8 OtherTransferCount : _LARGE_INTEGER
+0x1e0 CommitChargeLimit : Uint4B
+0x1e4 CommitChargePeak : Uint4B
+0x1e8 AweInfo : Ptr32 Void
+0x1ec SeAuditProcessCreationInfo : 进程路径_SE_AUDIT_PROCESS_CREATION_INFO
+0x1f0 Vm : _MMSUPPORT
+0x25c MmProcessLinks : _LIST_ENTRY
+0x264 HighestUserAddress : Ptr32 Void
+0x268 ModifiedPageCount : Uint4B
+0x26c Flags2 : Uint4B
+0x26c JobNotReallyActive : Pos 0, 1 Bit
+0x26c AccountingFolded : Pos 1, 1 Bit
+0x26c NewProcessReported : Pos 2, 1 Bit
+0x26c ExitProcessReported : Pos 3, 1 Bit
+0x26c ReportCommitChanges : Pos 4, 1 Bit
+0x26c LastReportMemory : Pos 5, 1 Bit
+0x26c ReportPhysicalPageChanges : Pos 6, 1 Bit
+0x26c HandleTableRundown : Pos 7, 1 Bit
+0x26c NeedsHandleRundown : Pos 8, 1 Bit
+0x26c RefTraceEnabled : Pos 9, 1 Bit
+0x26c NumaAware : Pos 10, 1 Bit
+0x26c ProtectedProcess : Pos 11, 1 Bit 保护进程
+0x26c DefaultPagePriority : Pos 12, 3 Bits
+0x26c PrimaryTokenFrozen : Pos 15, 1 Bit
+0x26c ProcessVerifierTarget : Pos 16, 1 Bit
+0x26c StackRandomizationDisabled : Pos 17, 1 Bit
+0x26c AffinityPermanent : Pos 18, 1 Bit
+0x26c AffinityUpdateEnable : Pos 19, 1 Bit
+0x26c PropagateNode : Pos 20, 1 Bit
+0x26c ExplicitAffinity : Pos 21, 1 Bit
+0x26c Spare1 : Pos 22, 1 Bit
+0x26c ForceRelocateImages : Pos 23, 1 Bit
+0x26c DisallowStrippedImages : Pos 24, 1 Bit
+0x26c LowVaAccessible : Pos 25, 1 Bit
+0x26c RestrictIndirectBranchPrediction : Pos 26, 1 Bit
+0x26c AddressPolicyFrozen : Pos 27, 1 Bit
+0x26c SpeculativeStoreBypassDisable : Pos 28, 1 Bit
+0x270 Flags : Uint4B
+0x270 CreateReported : Pos 0, 1 Bit
+0x270 NoDebugInherit : Pos 1, 1 Bit
+0x270 ProcessExiting : Pos 2, 1 Bit
+0x270 ProcessDelete : Pos 3, 1 Bit
+0x270 Wow64SplitPages : Pos 4, 1 Bit
+0x270 VmDeleted : Pos 5, 1 Bit
+0x270 OutswapEnabled : Pos 6, 1 Bit
+0x270 Outswapped : Pos 7, 1 Bit
+0x270 ForkFailed : Pos 8, 1 Bit
+0x270 Wow64VaSpace4Gb : Pos 9, 1 Bit
+0x270 AddressSpaceInitialized : Pos 10, 2 Bits
+0x270 SetTimerResolution : Pos 12, 1 Bit
+0x270 BreakOnTermination : Pos 13, 1 Bit
+0x270 DeprioritizeViews : Pos 14, 1 Bit
+0x270 WriteWatch : Pos 15, 1 Bit
+0x270 ProcessInSession : Pos 16, 1 Bit
+0x270 OverrideAddressSpace : Pos 17, 1 Bit
+0x270 HasAddressSpace : Pos 18, 1 Bit
+0x270 LaunchPrefetched : Pos 19, 1 Bit
+0x270 InjectInpageErrors : Pos 20, 1 Bit
+0x270 VmTopDown : Pos 21, 1 Bit
+0x270 ImageNotifyDone : Pos 22, 1 Bit
+0x270 PdeUpdateNeeded : Pos 23, 1 Bit
+0x270 VdmAllowed : Pos 24, 1 Bit
+0x270 CrossSessionCreate : Pos 25, 1 Bit
+0x270 ProcessInserted : Pos 26, 1 Bit
+0x270 DefaultIoPriority : Pos 27, 3 Bits
+0x270 ProcessSelfDelete : Pos 30, 1 Bit
+0x270 SetTimerResolutionLink : Pos 31, 1 Bit
+0x274 ExitStatus : Int4B
+0x278 VadRoot : _MM_AVL_TABLE
+0x298 AlpcContext : _ALPC_PROCESS_CONTEXT
+0x2a8 TimerResolutionLink : _LIST_ENTRY
+0x2b0 RequestedTimerResolution : Uint4B
+0x2b4 ActiveThreadsHighWatermark : Uint4B
+0x2b8 SmallestTimerResolution : Uint4B
+0x2bc TimerResolutionStackRecord : Ptr32 _PO_DIAG_STACK_RECORD
+0x2c0 SequenceNumber : Uint8B
+0x2c8 CreateInterruptTime : Uint8B
+0x2d0 CreateUnbiasedInterruptTime : Uint8B
+0x2d8 SecurityDomain : Uint8B

_EPROCESS的第一个成员是KPROCESS

 

nt!_KPROCESS

  +0x000 Header           : _DISPATCHER_HEADER  可等待内核对象

  +0x010 ProfileListHead  : _LIST_ENTRY    

  +0x018 DirectoryTableBase : Uint4B     CR3 通过线性地址以及分页模式定位物理基地址

  +0x01c LdtDescriptor    : _KGDTENTRY  

  +0x024 Int21Descriptor  : _KIDTENTRY  历史遗留

  +0x02c ThreadListHead   : _LIST_ENTRY 双向循环链表

  +0x034 ProcessLock      : Uint4B  内核进程锁

  +0x038 Affinity         : _KAFFINITY_EX  cpu亲核性

  +0x044 ReadyListHead    : _LIST_ENTRY  进程就绪列表

  +0x04c SwapListEntry    : _SINGLE_LIST_ENTRY交互磁盘

  +0x050 ActiveProcessors : _KAFFINITY_EX  活跃核心

  +0x05c AutoAlignment    : Pos 0, 1 Bit  对齐

  +0x05c DisableBoost     : Pos 1, 1 Bit

  +0x05c DisableQuantum   : Pos 2, 1 Bit 关闭时间碎片

  +0x05c ActiveGroupsMask : Pos 3, 1 Bit

  +0x05c ReservedFlags    : Pos 4, 28 Bits

  +0x05c ProcessFlags     : Int4B

  +0x060 BasePriority     : Char  基础优先级8,进程下所有线程最低的优先级

  +0x061 QuantumReset     : Char时间碎片

  +0x062 Visited          : UChar

  +0x063 Unused3          : UChar

  +0x064 ThreadSeed       : [1] Uint4B

  +0x068 IdealNode        : [1] Uint2B

  +0x06a IdealGlobalNode  : Uint2B

  +0x06c Flags            : _KEXECUTE_OPTIONS

  +0x06d AddressPolicy    : UChar

  +0x06e IopmOffset       : Uint2B

  +0x070 Unused4          : Uint4B

  +0x074 StackCount       : _KSTACK_COUNT

  +0x078 ProcessListEntry : _LIST_ENTRY

  +0x080 CycleTime        : Uint8B

  +0x088 KernelTime       : Uint4B  

  +0x08c UserTime         : Uint4B

  +0x090 VdmTrapcHandler  : Ptr32 Void //虚拟8086模式下使用

其中在_EPROCESS我们需要关注的是

ActiveProcessLinks 进程活跃链表

SessionProcessLinks 会话链表

ObjectTable 私有句柄表

其中在_KPROCESS我们需要关注的是

ProcessListEntry 进程链表

 

想要隐藏进程把所在数据的链表进程断链即可

//断链关键代码
FORCEINLINE
BOOLEAN
MyRemoveEntryList(
    _In_ PLIST_ENTRY Entry
)

{

    PLIST_ENTRY PrevEntry;
    PLIST_ENTRY NextEntry;

    NextEntry = Entry->Flink;
    PrevEntry = Entry->Blink;
    if ((NextEntry->Blink != Entry) || (PrevEntry->Flink != Entry)) {
        return FALSE;
    }

    PrevEntry->Flink = NextEntry;
    NextEntry->Blink = PrevEntry;
    return (BOOLEAN)(PrevEntry == NextEntry);
}
//


	//断链 ProcessListEntry

	PLIST_ENTRY ProfileListHead = (PLIST_ENTRY)((PUCHAR)mypEProcess + 0x18);
	MyRemoveEntryList(ProfileListHead);


	//断链   struct _LIST_ENTRY ThreadListHead;                                      //0x30
	PLIST_ENTRY ThreadListHead = (PLIST_ENTRY)((PUCHAR)mypEProcess + 0x30);
	MyRemoveEntryList(ProfileListHead);


	//断链struct_LIST_ENTRYSessionProcessLinks
	PLIST_ENTRY essionProcessLinks = (PLIST_ENTRY)((PUCHAR)mypEProcess + get_eprocess_session_offset());
	MyRemoveEntryList(essionProcessLinks);

  	//断链 private ObjectTable
	_HANDLE_TABLE* table= (_HANDLE_TABLE*)((PUCHAR)mypEProcess + get_eprocess_objecttable_offset());
	 MyRemoveEntryList(table->HandleTableList);

 

当然仅仅断链是不够的,因为操作系统还有一张全局句柄表PspCidTable,其中存储了线程和进程,我们还需要把我们的进程从全局句柄表中抹除,可以使用ExDestroyHandle

_int64 __fastcall ExDestroyHandle(__int64 a1, __int64 a2, __int64 a3)
{
  unsigned int v6; // ebx

  if ( *(_QWORD *)(a1 + 96) )
    ExpUpdateDebugInfo(a1, KeGetCurrentThread(), a2, 2i64);
  v6 = ExSweepSingleHandle(a1, a3);
  ExpFreeHandleTableEntry(a1, a2, a3);
  return v6;
}

 

	if (instance->fn_get_os_build_number() == 7600 || instance->fn_get_os_build_number() == 7601) table_code = ((PHANDLE_TABLE_W7)table)->TableCode;
	else table_code = table->TableCode;

	auto level = table_code & 3;

	if (level == 1) {

		return (PHANDLE_TABLE_ENTRY)(*(uint64_t*)(table_code - 1 + 8 * (u_handle >> 10)) + 4 * (u_handle & 0X3FF));
	}
	else if (level == 2) {

		return (PHANDLE_TABLE_ENTRY)(*(uint64_t*)(*(uint64_t*)(table_code - 2 + 8 * (u_handle >> 19)) + 8 * (u_handle >> 10 & 0X1FF)) + 4 * (u_handle & 0x3ff));

	}
	else {

		return (PHANDLE_TABLE_ENTRY)(table_code + 4 * (u_handle & 0x3ff));

	}

注:高版本对全局句柄表有加密

实验:

隐藏前DebugView 8404

进程隐藏对抗

隐藏后

进程隐藏对抗

需要注意,关闭进程前需要还原全局句柄表,不然会蓝屏;

其次在高版本系统上PG增加了对隐藏进程的检测,有概率触发蓝屏,不过没那么快,运气好能挺好几个小时;

具体的隐藏进程完整代码可以参考一份开源的:https://github.com/Oxygen1a1/HideProcess

那么问题来了,如果恶意软件隐藏自己的进程我们如何查询呢?

思考:进程是给用户看的,CPU不认识进程,代码也是跑在线程上面的 ,最重要的是cpu线程的调度是基于某些链表进行调度的,如果把cpu调度链表也断了,进程也就跑不起来了,可谓是真正实现了无痕进程隐藏;其次内核中有很多链表都能回溯到进程信息。

本人采用的方法是通过遍历枚举线程去回溯到进程,在 _ETHREAD中_CLIENT_ID储存了当前线程所属的进程id

//0x10 bytes (sizeof)
struct _CLIENT_ID
{
    VOID* UniqueProcess;                                                    //0x0
    VOID* UniqueThread;                                                     //0x8
};

查询恶意隐藏进程思路:正常调用API遍历进程并存储表A,暴力枚举线程回溯进程并存储表B,如果B中的进程在A中找不到基本可以判断进程进行了隐藏。

这里为了省事和便于观看就简写代码:

	PETHREAD Thread = NULL;
	NTSTATUS status;;
  //直接假设句柄表是三级,问就是图省事
	for (size_t i = 0; i < 1024*1024*512; i++)
	{
		status=PsLookupThreadByThreadId(i, &Thread);
		if (NT_SUCCESS(status))
		{

			ULONG64 UniqueProcess = *(ULONG64*)((PUCHAR)Thread + 0x4c8);
		//	DbgPrintEx(77, 0, "%d\r\n", UniqueProcess);
			if (UniqueProcess == 8404)
			{
				DbgPrintEx(77, 0, "找到隐藏的进程 %d\r\n", UniqueProcess);
				break;

			}
		}

	  }

进程隐藏对抗

总的来说如果对抗同在内核层隐藏进程什么用没有,有机会下期更新 进程伪装对抗