句柄降權繞過CallBacks檢查

看到前輩們相關的文章,不太明白什么是句柄降權,于是專門去學習一下,過程有一點波折。

句柄降權

什么是句柄

當一個進程利用名稱來創建或打開一個對象時,將獲得一個句柄,該句柄指向所創建或打開的對象。以后,該進程無須使用名稱來引用該對象,使用此句柄即可訪問。這樣做可以顯著地提高引用對象的效率。句柄是一個在軟件設計中被廣泛使用的概念。例如,在C運行庫中,文件操作使用句柄來表示,每當應用程序創建或打開一個文件時,只要此創建或打開操作成功,則C運行庫返回一個句柄。以后應用程序對文件的讀寫操作都使用此句柄來標識該文件。而且,如果兩個應用程序以共享方式打開了同一個文件,那么,它們將分別得到各自的句柄,且都可以通過句柄操作該文件。盡管兩個應用程序得到的句柄的值并不相同,但是這兩個句柄所指的文件卻是同一個。因此,句柄只是一個對象引用,同一個對象在不同的環境下可能有不同的引用(句柄)值。

上文中的"對象"指的是內核對象,我們在R3中所使用的文件、進程、線程在內核中都有對應內核對象。應用層每次創建或打開進程、文件都會對相應的內核對象創建一個句柄。當多個進程同時打開一個文件時,該文件在內核中只會存在一個文件內核對象,但每個進程都有一個各自的文件句柄,每個句柄會增加內核對象的引用計數,只有當內核對象的引用計數為0時,內核對象才會釋放。

私有句柄表

eprocess指向一個ObjectTableObjectTbale中存在TableCode,這個指向的是這個進程的私有句柄表。同時ObjectTable中還有一個HandleTableList,這個是一個鏈表,通過HandleTableList?成員遍歷得到所有進程的ObjectTable地址

我們的目標是獲取到_object_header結構體,這個結構體才是句柄的真正內容。但是不同版本系統下的取法不太一樣,win7是直接指向句柄,win10則需要做一些偏移,這些偏移google沒有資料,大多都是通過IDA靜態分析函數才能得到。

win中有一些根據_handle_table_entry獲取進程句柄的函數,我這里沒有做過多分析,直接使用前輩分析后的經驗。分析目標ntoskrnl.exe下的ObpEnumFindHandleProcedure函數,可以看到如下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

__int64?__fastcall ObpEnumFindHandleProcedure(

????????_HANDLE_TABLE *handle_table,

????????_HANDLE_TABLE_ENTRY *handle_table_entry,

????????HANDLE?a3,

????????HANDLE?*object_header)

{

??unsigned?__int8?v5;?// bl

??HANDLE?v7;?// rbx

??_DWORD *v8;?// rcx

??__int64?v9;?// r11

??int?v10[10];?// [rsp+0h] [rbp-28h] BYREF

??if?( !*object_header || *object_header == (HANDLE)((handle_table_entry->LowValue >> 16) & 0xFFFFFFFFFFFFFFF0ui64) )

??{

????v7 = object_header[1];

????if?( !v7

??????|| v7 == (HANDLE)ObTypeIndexTable[(unsigned?__int8)ObHeaderCookie ^ *(unsigned?__int8?*)(((handle_table_entry->LowValue >> 16) & 0xFFFFFFFFFFFFFFF0ui64)

?????????????????????????????????????????????????????????????????????????????????????????????+ 0x18) ^ (unsigned?__int64)(unsigned?__int8)((unsigned?__int16)(WORD1(handle_table_entry->LowValue) & 0xFFF0) >> 8)] )

????{

??????v8 = object_header[2];

??????if?( !v8 )

????????goto?LABEL_11;

??????v9 = (handle_table_entry->LowValue >> 17) & 7;

??????if?( (*(_DWORD *)(&handle_table_entry->4 + 1) & 0x2000000) != 0 )

????????LOBYTE(v9) = v9 | 8;

??????if?( *v8 == (v9 & 7) && v8[1] == (*(_DWORD *)(&handle_table_entry->4 + 1) & 0x1FFFFFF) )

LABEL_11:

????????v5 = 1;

??????else

????????v5 = 0;

????}

????else

????{

??????v5 = 0;

????}

??}

??else

??{

????v5 = 0;

??}

??_InterlockedExchangeAdd64(&handle_table_entry->VolatileLowValue, 1ui64);

??_InterlockedOr(v10, 0);

??if?( handle_table->HandleContentionEvent.Value )

????ExfUnblockPushLock(&handle_table->HandleContentionEvent, 0i64);

??return?v5;

}

可以在開頭部分看到(handle_table_entry->LowValue >> 16) & 0xFFFFFFFFFFFFFFF0ui64),這樣才能獲取到句柄內容,獲取到_object_header.

但是我自己嘗試的時候沒有獲取到,直到我注意到帖子里面最后得到的值開頭都是0xffff,這說明右移前面不是補充0,而是補充1

所以地址計算實際是:(handle_table_entry->LowValue >> 16) & 0xFFFFFFFFFFFFFFF0ui64) + 0xffff000000000000

得到的就是_OBJECT_HEADER,這表示一個句柄頭,句柄體在body的位置,我系統版本的偏移是0x30,進程句柄的話就是_eprocess結構體

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

//0x38 bytes (sizeof)

struct?_OBJECT_HEADER

{

????LONGLONG?PointerCount;??????????????????????????????????????????????????//0x0

????union

????{

????????LONGLONG?HandleCount;???????????????????????????????????????????????//0x8

????????VOID* NextToFree;???????????????????????????????????????????????????//0x8

????};

????struct?_EX_PUSH_LOCK Lock;??????????????????????????????????????????????//0x10

????UCHAR?TypeIndex;????????????????????????????????????????????????????????//0x18

????union

????{

????????UCHAR?TraceFlags;???????????????????????????????????????????????????//0x19

????????struct

????????{

????????????UCHAR?DbgRefTrace:1;????????????????????????????????????????????//0x19

????????????UCHAR?DbgTracePermanent:1;??????????????????????????????????????//0x19

????????};

????};

????UCHAR?InfoMask;?????????????????????????????????????????????????????????//0x1a

????union

????{

????????UCHAR?Flags;????????????????????????????????????????????????????????//0x1b

????????struct

????????{

????????????UCHAR?NewObject:1;??????????????????????????????????????????????//0x1b

????????????UCHAR?KernelObject:1;???????????????????????????????????????????//0x1b

????????????UCHAR?KernelOnlyAccess:1;???????????????????????????????????????//0x1b

????????????UCHAR?ExclusiveObject:1;????????????????????????????????????????//0x1b

????????????UCHAR?PermanentObject:1;????????????????????????????????????????//0x1b

????????????UCHAR?DefaultSecurityQuota:1;???????????????????????????????????//0x1b

????????????UCHAR?SingleHandleEntry:1;??????????????????????????????????????//0x1b

????????????UCHAR?DeletedInline:1;??????????????????????????????????????????//0x1b

????????};

????};

????ULONG?Reserved;?????????????????????????????????????????????????????????//0x1c

????union

????{

????????struct?_OBJECT_CREATE_INFORMATION* ObjectCreateInfo;????????????????//0x20

????????VOID* QuotaBlockCharged;????????????????????????????????????????????//0x20

????};

????VOID* SecurityDescriptor;???????????????????????????????????????????????//0x28

????struct?_QUAD Body;??????????????????????????????????????????????????????//0x30

};

0x18位置的TypeIndex表示這個句柄對應的對象是一個什么類型的對象,比如文件、進程、線程等,0x30的Body就是便指向了該句柄對應的對象結構。若句柄對應的對象是一個進程對象那么0x30的位置存的就是對應進程對象的_EPROCESS的結構,可以從這個結構便獲得進程名、進程ID等等信息。

所以在內核中有一個鏈表存放了每一個進程的私有句柄表。

TableCode句柄表

句柄表是以頁為單位,兩層句柄表則是第一層存放第二層的指針,一般只有系統進程才會打開那么多的句柄,惡意進程通常只有一層。win10的機器上tablecode是存放了一頁的handle_table_entry,每一個16字節,一頁大小是4k,所以一頁最多256個句柄。(32位系統的是一頁512個句柄)

怎么判斷句柄表有幾層?

TableCode的最后2個bit表示層數(有的文章說是3個bit,我也不確定),但是目前我看最多的也只有兩層,下面分別是0層和1層的情況。

?可以看到tablecode句柄表的內容不是句柄,而是_handle_table_entry,這不是一個結構體,是一個union

從handle_table_entry到句柄還需要一些額外的計算變化,同時一個進程句柄的權限就標注在每個句柄對應的這個結構體當中

Windbg 調試

以手動的方式從一個eprocess內存看到他下面的句柄表

windbg以內核附加模式連接上虛擬機/真機后,首先我們需要一個EPROCESS結構體地址,使用!process 0 0查看所有進程的基本信息

1

2

3

4

5

6

7

8

9

10

11

3: kd> !process 0 0

**** NT ACTIVE PROCESS DUMP ****

PROCESS ffffbf8eaa092040

????SessionId: none? Cid: 0004??? Peb: 00000000? ParentCid: 0000

????DirBase: 001ad000? ObjectTable: ffffd00b12a10840? HandleCount: 1715.

????Image: System

PROCESS ffffbf8eab1ea080

????SessionId: 0? Cid: 01d8??? Peb: bbe935c000? ParentCid: 01cc

????DirBase: 2364c9000? ObjectTable: ffffd00b145784c0? HandleCount: 462.

????Image: csrss.exe

PROCESS的值就是EPROCESS的地址值, 我這里選用csrss.exe的PROCESS值ffffbf8eab1ea080

定位ObjectTable的值

1

2

3: kd> dt ffffbf8eab1ea080 nt!_EPROCESS -y object

???+0x570 ObjectTable : 0xffffd00b`145784c0 _HANDLE_TABLE

再進一步查看_HANDLE_TABLE結構體,定位TableCode的值

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

3: kd> dt 0xffffd00b`145784c0 _HANDLE_TABLE

ntdll!_HANDLE_TABLE

???+0x000 NextHandleNeedingPool : 0x800

???+0x004 ExtraInfoPages?? : 0n0

???+0x008 TableCode??????? : 0xffffd00b`16d58001

???+0x010 QuotaProcess???? : 0xffffbf8e`ab1ea080 _EPROCESS

???+0x018 HandleTableList? : _LIST_ENTRY [ 0xffffd00b`167057d8 - 0xffffd00b`140fde18 ]

???+0x028 UniqueProcessId? : 0x1d8

???+0x02c Flags??????????? : 0x12

???+0x02c StrictFIFO?????? : 0y0

???+0x02c EnableHandleExceptions : 0y1

???+0x02c Rundown????????? : 0y0

???+0x02c Duplicated?????? : 0y0

???+0x02c RaiseUMExceptionOnInvalidHandleClose : 0y1

???+0x030 HandleContentionEvent : _EX_PUSH_LOCK

???+0x038 HandleTableLock? : _EX_PUSH_LOCK

???+0x040 FreeLists??????? : [1] _HANDLE_TABLE_FREE_LIST

???+0x040 ActualEntry????? : [32]??""

???+0x060 DebugInfo??????? : (null)

得到TableCode的值是0xffffd00b`16d58001,注意TableCode最后的一位是1,這表示有兩層頁表,第一層的值是指向的第二層的指針,所以先查看第一層句柄指針表

1

2

3

4

5

6

7

8

9

2: kd> dq 0xffffd00b`16d58000

ffffd00b`16d58000? ffffd00b`16667000 ffffd00b`16d59000

ffffd00b`16d58010? ffffd00b`19a4c000 00000000`00000000

ffffd00b`16d58020? 00000000`00000000 00000000`00000000

ffffd00b`16d58030? 00000000`00000000 00000000`00000000

ffffd00b`16d58040? 00000000`00000000 00000000`00000000

ffffd00b`16d58050? 00000000`00000000 00000000`00000000

ffffd00b`16d58060? 00000000`00000000 00000000`00000000

ffffd00b`16d58070? 00000000`00000000 00000000`00000000

看到有3個二層句柄表,我們選用第一張表

1

2

3

4

5

6

7

8

9

2: kd> dq ffffd00b`16667000

ffffd00b`16667000? 00000000`00000000 00000000`00000000

ffffd00b`16667010? bf8eaaee`9430ffff 00000000`001f0003

ffffd00b`16667020? bf8eaaee`ae30fffb 00000000`001f0003

ffffd00b`16667030? bf8eab18`25b0fffd 00000000`00000001

ffffd00b`16667040? bf8eaaec`6890ffc3 00000000`001f0003

ffffd00b`16667050? bf8eab1e`ba40ffc3 00000000`000f00ff

ffffd00b`16667060? bf8eab18`bb00ffff 00000000`00100002

ffffd00b`16667070? bf8eab18`1b20ffff 00000000`00000001

可以看到從ffffd00b`16667010開始每16字節表示一個_handle_table_entry union體

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

2: kd> dt _handle_table_entry ffffd00b`16667010

nt!_HANDLE_TABLE_ENTRY

???+0x000 VolatileLowValue : 0n-4643586224107225089

???+0x000 LowValue???????? : 0n-4643586224107225089

???+0x000 InfoTable??????? : 0xbf8eaaee`9430ffff _HANDLE_TABLE_ENTRY_INFO

???+0x008 HighValue??????? : 0n2031619

???+0x008 NextFreeHandleEntry : 0x00000000`001f0003 _HANDLE_TABLE_ENTRY

???+0x008 LeafHandleValue? : _EXHANDLE

???+0x000 RefCountField??? : 0n-4643586224107225089

???+0x000 Unlocked???????? : 0y1

???+0x000 RefCnt?????????? : 0y0111111111111111 (0x7fff)

???+0x000 Attributes?????? : 0y000

???+0x000 ObjectPointerBits : 0y10111111100011101010101011101110100101000011 (0xbf8eaaee943)

???+0x008 GrantedAccessBits : 0y0000111110000000000000011 (0x1f0003)

???+0x008 NoRightsUpgrade? : 0y0

???+0x008 Spare1?????????? : 0y000000 (0)

???+0x00c Spare2?????????? : 0

(handle_table_entry->LowValue >> 16) & 0xFFFFFFFFFFFFFFF0ui64)獲取_object_header結構體,記得前面填充的要是1,計算一下值

1

2

3

In [2]:?hex(((0xbf8eaaee9430ffff?>>?0x10) &?0xFFFFF??????????????????????????????

???...: FFFFFFFFFF0) | ((0xffff) <<?48))?????????????????????????????????????????

Out[2]:?'0xffffbf8eaaee9430'

0xbf8eaaee9430ffffffffd00b16667010的值,對應的就是handle_table_entry->LowValue,得到地址0xffffbf8eaaee9430也就是_object_header

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

3: kd> dt _object_header 0xffffbf8eaaee9430

nt!_OBJECT_HEADER

???+0x000 PointerCount???? : 0n32768

???+0x008 HandleCount????? : 0n1

???+0x008 NextToFree?????? : 0x00000000`00000001 Void

???+0x010 Lock???????????? : _EX_PUSH_LOCK

???+0x018 TypeIndex??????? : 0xaa?''

???+0x019 TraceFlags?????? : 0?''

???+0x019 DbgRefTrace????? : 0y0

???+0x019 DbgTracePermanent : 0y0

???+0x01a InfoMask???????? : 0x8?''

???+0x01b Flags??????????? : 0?''

???+0x01b NewObject??????? : 0y0

???+0x01b KernelObject???? : 0y0

???+0x01b KernelOnlyAccess : 0y0

???+0x01b ExclusiveObject? : 0y0

???+0x01b PermanentObject? : 0y0

???+0x01b DefaultSecurityQuota : 0y0

???+0x01b SingleHandleEntry : 0y0

???+0x01b DeletedInline??? : 0y0

???+0x01c Reserved???????? : 0

???+0x020 ObjectCreateInfo : 0xfffff807`28e53780 _OBJECT_CREATE_INFORMATION

???+0x020 QuotaBlockCharged : 0xfffff807`28e53780 Void

???+0x028 SecurityDescriptor : (null)

???+0x030 Body???????????? : _QUAD

可以看到整個object_header的內容,這只是句柄的頭部,句柄的內容還在0x30偏移的位置,由于我調試發現這一個進程句柄,所以直接用eprocess展示這個句柄內容。

1

2

3

4

5

6

7

8

9

3: kd> dt _EPROCESS 0xffffbf8eaaee9430 + 0x30

nt!_EPROCESS

???+0x000 Pcb????????????? : _KPROCESS

???+0x438 ProcessLock????? : _EX_PUSH_LOCK

???+0x440 UniqueProcessId? : 0xffffbf8e`aaee9ba0 Void

???+0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffffbf8e`aaec6370 - 0xffffbf8e`ac4330e0 ]

???+0x458 RundownProtect?? : _EX_RUNDOWN_REF

???+0x460 Flags2?????????? : 0xcdb1a88a

???...

這樣就通過進程的私有句柄表獲得了被打開句柄進程的信息。

判斷句柄類型

怎么樣從一個句柄頭(_object_header)判斷出這是一個進程句柄(process handle),文件句柄(file handle)還是設備句柄(device handle)

這個不同版本的系統判斷方法不一樣,win7/8/8.1是一樣的 win10則不同。網絡上大多都是win7, win8的我在這篇外網文章上才找到win10的判斷方法

win7/8/8.1

這幾個版本的_object_header結構大致如下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//0x38 bytes (sizeof)

struct?_OBJECT_HEADER

{

????LONGLONG?PointerCount;??????????????????????????????????????????????????//0x0

????union

????{

????????LONGLONG?HandleCount;???????????????????????????????????????????????//0x8

????????VOID* NextToFree;???????????????????????????????????????????????????//0x8

????};

????struct?_EX_PUSH_LOCK Lock;??????????????????????????????????????????????//0x10

????UCHAR?TypeIndex;????????????????????????????????????????????????????????//0x18

????UCHAR?TraceFlags;???????????????????????????????????????????????????????//0x19

????UCHAR?InfoMask;?????????????????????????????????????????????????????????//0x1a

????UCHAR?Flags;????????????????????????????????????????????????????????????//0x1b

????union

????{

????????struct?_OBJECT_CREATE_INFORMATION* ObjectCreateInfo;????????????????//0x20

????????VOID* QuotaBlockCharged;????????????????????????????????????????????//0x20

????};

????VOID* SecurityDescriptor;???????????????????????????????????????????????//0x28

????struct?_QUAD Body;??????????????????????????????????????????????????????//0x30

};

一般直接TypeIndex表示這個句柄的類型index。相同的類型這個值會相同,還可以進一步查看這個index在nt!object_type這個表中的具體信息

?

上面就是Typeindex = 7對應的意義,是進程句柄

win10

win10的Typeindex就不一樣了,測試會發現,哪怕都是進程句柄這個Typeindex的值也會不同。

需要將3個單字節的值異或起來,Typeindex ^ nt!ObHeaderCookie ^ 地址的第二個字節

?

最后得到的才是真的Typeindex

句柄降權/提權

一個句柄的權限,表示句柄擁有者對這個句柄的操作權限,權限有以下幾種

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

#define PROCESS_TERMINATE????????????????? (0x0001)?

#define PROCESS_CREATE_THREAD????????????? (0x0002)?

#define PROCESS_SET_SESSIONID????????????? (0x0004)?

#define PROCESS_VM_OPERATION?????????????? (0x0008)?

#define PROCESS_VM_READ??????????????????? (0x0010)?

#define PROCESS_VM_WRITE?????????????????? (0x0020)?

#define PROCESS_DUP_HANDLE???????????????? (0x0040)?

#define PROCESS_CREATE_PROCESS???????????? (0x0080)?

#define PROCESS_SET_QUOTA????????????????? (0x0100)?

#define PROCESS_SET_INFORMATION??????????? (0x0200)?

#define PROCESS_QUERY_INFORMATION????????? (0x0400)?

#define PROCESS_SUSPEND_RESUME???????????? (0x0800)?

#define PROCESS_QUERY_LIMITED_INFORMATION? (0x1000)?

#define PROCESS_SET_LIMITED_INFORMATION??? (0x2000)?

#if (NTDDI_VERSION >= NTDDI_VISTA)

#define PROCESS_ALL_ACCESS??????? (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | \0xFFFF)

當我們通過OpenProcess以多種權限申請打開進程時便將多種權限或運算就得到了我們想要的權限值,我們的目的是為了降低句柄擁有者對我們要保護的進程的操作權限,那最簡單暴力的方法便是把handle_table_entry->GrantedAccessBits的值修改成我們設定的值,直接讓句柄擁有者對我們的進程操作權限被修改。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

//0x10 bytes (sizeof)

union?_HANDLE_TABLE_ENTRY

{

????volatile?LONGLONG?VolatileLowValue;?????????????????????????????????????//0x0

????LONGLONG?LowValue;??????????????????????????????????????????????????????//0x0

????struct

????{

????????struct?_HANDLE_TABLE_ENTRY_INFO*?volatile?InfoTable;????????????????//0x0

????LONGLONG?HighValue;?????????????????????????????????????????????????????//0x8

????union?_HANDLE_TABLE_ENTRY* NextFreeHandleEntry;?????????????????????????//0x8

????????struct?_EXHANDLE LeafHandleValue;???????????????????????????????????//0x8

????};

????LONGLONG?RefCountField;?????????????????????????????????????????????????//0x0

????ULONGLONG?Unlocked:1;???????????????????????????????????????????????????//0x0

????ULONGLONG?RefCnt:16;????????????????????????????????????????????????????//0x0

????ULONGLONG?Attributes:3;?????????????????????????????????????????????????//0x0

????struct

????{

????????ULONGLONG?ObjectPointerBits:44;?????????????????????????????????????//0x0

????ULONG?GrantedAccessBits:25;?????????????????????????????????????????????//0x8

????ULONG?NoRightsUpgrade:1;????????????????????????????????????????????????//0x8

????????ULONG?Spare1:6;?????????????????????????????????????????????????????//0x8

????};

????ULONG?Spare2;???????????????????????????????????????????????????????????//0xc

};

image-20240427101135672

代碼實現防止CE讀取進程內存

現在假設一個場景,我們打開一個記事本(notepad.exe),然后用CE去讀取這個進程的內存。我們的目標是保護這個記事本進程,讓降低CE中已經打開的記事本進程句柄權限,讓CE無法再繼續讀取內存。

首先CE打開目標進程

?

我這里直接寫死進程號了,使用PsLookupProcessByProcessId獲取指定PID的eprocess結構體

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath)

{

????PEPROCESS eprocess = NULL;

????NTSTATUS? status?? = PsLookupProcessByProcessId((HANDLE)0xab4, &eprocess);

????if?(!NT_SUCCESS(status)) {

????????kprintf("Open process? unsuccessfully!\r\n");

????????return?STATUS_UNSUCCESSFUL;

????}

????ObDereferenceObject(eprocess);

????ProtectProcessHandleByEprocess(eprocess);

????pDriver->DriverUnload = DriveUnload;

????return?STATUS_SUCCESS;

}

然后開始寫ProtectProcessHandleByEprocess函數,這個函數才是主要的邏輯,傳入指定eprocess,然后遍歷鏈表所有的句柄表,匹配是否相同,如果相同則修改權限。

我首先定義兩個結構體,方便后面編程

1

2

3

4

5

6

7

8

9

10

11

12

13

typedef?struct?HANDLE_TABLE_ENTRY

{

????UINT64?LowValue;

????UINT32?GrantedAccessBits;

????UINT32?Spare2;

} *PHANDLE_TABLE_ENTRY, HANDLE_TABLE_ENTRY;

/// @brief 存放每個進程的信息

typedef?struct?PROCESS_HANDLE_OBJECT

{

????PEPROCESS?????????? eprocess;

????PHANDLE_TABLE_ENTRY table_code;

} *PPROCESS_HANDLE_OBJECT, PROCESS_HANDLE_OBJECT;

然后給這兩個結構體定義了幾個方法

  • CheckHandleTableEntry:檢查HANDLE_TABLE_ENTRY的值是否合法
  • NewProcessHandleObject:新建一個PROCESS_HANDLE_OBJECT結構體
  • FreeProcessHandleObject:釋放一個PROCESS_HANDLE_OBJECT結構體
  • HandleEntryTable2ObjectHeader: 計算單個handle_table_entry轉化成object_header地址

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

/// @brief

/// 檢查一個PHANDLE_TABLE_ENTRY中的數值是否合法,LowValue是否為0,合法返回TRUE,否則返回FALSE

/// @param pHandleTableEntry PHANDLE_TABLE_ENTRY指針

/// @return 合法返回TRUE,否則返回FALSE

BOOLEAN?CheckHandleTableEntry(PHANDLE_TABLE_ENTRY pHandleTableEntry)

{

????if?(!pHandleTableEntry->LowValue) {

????????return?FALSE;

????}

????return?TRUE;

}

/// @brief

/// 新建一個PROCESS_HANDLE_OBJECT結構體。傳入eprocess地址或者handle_table地址,二者至少其一

/// 創建成功返回結構體指針,失敗則返回NULL

/// @param pEprocess eprocess地址或者NULL

/// @param pHandleTable _handle_table地址或者NULL

/// @return 創建成功返回結構體指針,失敗則返回NULL

PPROCESS_HANDLE_OBJECT NewProcessHandleObject(PEPROCESS pEprocess,

??????????????????????????????????????????????PVOID64?? pHandleTable)

{

????UINT64?????????????????uTableCode;

????PPROCESS_HANDLE_OBJECT ptr;

????if?(pEprocess == NULL && pHandleTable == NULL) {

????????return?NULL;

????}

????if?(pEprocess == NULL) {

????????pEprocess = *(PUINT64)((PUCHAR)pHandleTable +

???????????????????????????????WIN10_21H1_X64_QUOTOPROCESS_OFFSET);

????}

????if?(pHandleTable == NULL) {

????????pHandleTable =

????????????*(PUINT64)((PUCHAR)pEprocess + WIN10_21H1_X64_OBJECTTABLE_OFFSET);

????}

????uTableCode =

????????*(PUINT64)((PUINT8)pHandleTable + WIN10_21H1_X64_TABLECODE_OFFSET);

????ptr = ExAllocatePool(NonPagedPool,?sizeof(PROCESS_HANDLE_OBJECT));

????if?(ptr == NULL) {

????????kprintf("[!] Alloc struct PROCESS_HANDLE_OBJECT faild\r\n");

????????return?NULL;

????}

????ptr->eprocess?? = pEprocess;

????ptr->table_code = uTableCode;

}

/// @brief 銷毀PROCESS_HANDLE_OBJECT結構體,傳入一個對應指針

/// @param pProcessHandlePbject PROCESS_HANDLE_OBJECT的指針

/// @return

VOID?FreeProcessHandleObject(PPROCESS_HANDLE_OBJECT pProcessHandlePbject)

{

????pProcessHandlePbject->eprocess?? = NULL;

????pProcessHandlePbject->table_code = 0;

????ExFreePool(pProcessHandlePbject);

}

/// @brief 傳入一個HANDLE_TABLE_ENTRY結構體的地址,計算出ObjectHeader地址

/// @param addr HANDLE_TABLE_ENTRY結構體的地址

/// @return 返回ObjectHeader地址

ULONG64?HandleEntryTable2ObjectHeader(PHANDLE_TABLE_ENTRY addr)

{

????return?((addr->LowValue >> 0x10) & 0xFFFFFFFFFFFFFFF0) + 0xFFFF000000000000;

}

然后看一下ProtectProcessHandleByEprocess函數,傳入eprocess地址后,首先計算出來_object_table地址,然后計算出來HandleTableList地址

1

2

3

4

pHandleTable =

????*(PUINT64)((PCHAR)pEprocess + WIN10_21H1_X64_OBJECTTABLE_OFFSET);

pPriList = (PLIST_ENTRY64)((PUCHAR)pHandleTable +

???????????????????????????WIN10_21H1_X64_HANDLETABLELIST_OFFSET);

然后遍歷鏈表,我們把鏈表上每一個節點都創建一個PROCESS_HANDLE_OBJECT結構體,因為鏈表上每一個節點代表一個進程,每一個進程都有一張或者多張句柄表,我們先將鏈表上每一個節點的部分信息收集好后放在一個數組中,方便我們后續遍歷操作

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

/// @brief

/// 傳入一個PLIST_ENTRY64,會遍歷這個鏈表,每個鏈表節點會生成一個對應的PROCESS_HANDLE_OBJECT指針

/// 組成一個數組,存放指針,存放到ObjArr

/// @param pHandleList Handle_list鏈表

/// @param ObjArr PPROCESS_HANDLE_OBJECT* 指針

/// @return 返回一個指針數組,數組元素是PROCESS_HANDLE_OBJECT指針

NTSTATUS CreateProcessObjArrByHandleList(PLIST_ENTRY64??????????? pHandleList,

?????????????????????????????????????????PPROCESS_HANDLE_OBJECT** ObjArr)

{

????PLIST_ENTRY64?????????? pTmp;

????UINT64??????????????????cout = 0;

????PPROCESS_HANDLE_OBJECT* pProcessObjArr;

????// 獲取鏈表節點數量,用于申請內存塊大小

????pTmp = pHandleList;

????do?{

????????pTmp = pTmp->Flink;

????????cout += 1;

????}?while?(pTmp != pHandleList);

????pProcessObjArr = ExAllocatePoolZero(

????????NonPagedPool, (cout + 1) *?sizeof(PPROCESS_HANDLE_OBJECT), POOL_TAG);

????if?(!pProcessObjArr) {

????????kprintf("[!] Alloc process handle obj array failed\r\n");

????????return?STATUS_ALLOCATE_BUCKET;

????}

????// 遍歷鏈表獲取節點信息,并創建ProcessHandleObject結構體

????for?(size_t?i = 0; i < cout; i++) {

????????pProcessObjArr[i] = NewProcessHandleObject(

????????????NULL, ((PUCHAR)pTmp - WIN10_21H1_X64_HANDLETABLELIST_OFFSET));

????????pTmp = pTmp->Flink;

????}

????*ObjArr = pProcessObjArr;

????return?STATUS_SUCCESS;

}

然后開始遍歷這個數組,具體每一個進程都遍歷它的句柄表再來對比,關鍵邏輯在如下的FilterObjByEprocess函數中

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

/// @brief 傳入需要保護的進程eprocess,保護程序句柄

/// @param pEprocess PEPROCESS地址

/// @return

NTSTATUS ProtectProcessHandleByEprocess(PEPROCESS pEprocess)

{

????PVOID64???????????????? pHandleTable;

????PLIST_ENTRY64?????????? pPriList, pTmp;

????UINT64??????????????????cout;

????PPROCESS_HANDLE_OBJECT* ObjArr;

????NTSTATUS??????????????? status;

????pHandleTable =

????????*(PUINT64)((PCHAR)pEprocess + WIN10_21H1_X64_OBJECTTABLE_OFFSET);

????pPriList = (PLIST_ENTRY64)((PUCHAR)pHandleTable +

???????????????????????????????WIN10_21H1_X64_HANDLETABLELIST_OFFSET);

????kprintf("[+] EPROCESS: %p\r\n[+] handle object: %p\r\n[+] handle table "

????????????"list: %p\r\n",

????????????pEprocess,

????????????pHandleTable,

????????????pPriList);

????status = CreateProcessObjArrByHandleList(pPriList, &ObjArr);

????if?(!NT_SUCCESS(status)) {

????????kprintf("[!] CreateProcessObjArrByHandleList error");

????????return?STATUS_UNSUCCESSFUL;

????}

????for?(size_t?i = 0; ObjArr[i] != 0; i++) {

????????// kprintf("[+] Obj[%d]: %llx\r\n", i, ObjArr[i]);

????????// DisplayProcessHandleObj(ObjArr[i]);

????????kprintf("[+] Use handle process imagename: %s; eprocess: %p\r\n",

????????????????(PUCHAR)ObjArr[i]->eprocess + EPROCESS_IMAGE_OFFSET,

????????????????ObjArr[i]->eprocess);

????????FilterObjByEprocess(ObjArr[i], pEprocess);

????}

????FreeProcessObjArr(ObjArr);

????return?STATUS_SUCCESS;

}

下面看我們怎么遍歷一個進程節點的句柄表,也就是FilterObjByEprocess函數。

這里我只考慮一層和兩次的句柄表,至于三層的不考慮。大部分惡意軟件都只有一層句柄表,CE有兩層。所以我們分兩種來處理,一種是只有一層句柄表的,一種是兩層句柄表的

  • FilterOneTableByEprocess
  • FilterTWOTabelByEprocess

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

/// @brief 遍歷一層/兩層句柄表,判斷其中是否有目標句柄進程pEprocess

/// 如果有則返回TRUE, 否則返回FALSE

/// @param pProcessHandleObj 需要遍歷的pProcessHandleObj的結構體

/// @param pEprocess 目標進程句柄

/// @return

BOOLEAN?FilterObjByEprocess(PPROCESS_HANDLE_OBJECT pProcessHandleObj,

????????????????????????????PEPROCESS????????????? pEprocess)

{

????UINT64??????????????tablecode;

????PHANDLE_TABLE_ENTRY pHandleTableEntry;

????PVOID64???????????? pObjHeader;

????tablecode = pProcessHandleObj->table_code;

????switch?(tablecode & TABLE_LEVEL_MASK)

????{

????case?TABLE_LEVEL_ZERO:

????????return?FilterOneTableByEprocess(pEprocess, tablecode);

????????break;

????case?TABLE_LEVEL_ONE:

????????return?FilterTWOTabelByEprocess(pEprocess, tablecode);

????????break;

????default:

????????break;

????}

????return?FALSE;

}

FilterOneTableByEprocess需要傳入兩個參數,一個是需要保護的eprocess地址,一個是一層的句柄表tablecode,大致流程如下

檢查傳入的tablecode有沒有異常,有異常的跳過

tablecode其實就是_handle_table_entry數組,所以把所有_handle_table_entry轉換成對應的_object_header

然后提取每個_object_header的body對比是否等于我們的目標EPROCESS,如果等于表示找到了,就修改權限

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

/// @brief 針對單張句柄表的情況,匹配目標eprocess,如果匹配到則修改句柄權限

/// @param pEprocess 目標eprocess結構體指針

/// @param tablecode 單張句柄表的tablecode

/// @return

BOOLEAN?FilterOneTableByEprocess(PEPROCESS pEprocess,?UINT64?tablecode) {

????PHANDLE_TABLE_ENTRY pHandleTableEntry;

????PVOID64???????????? pObjHeader;

????pHandleTableEntry = tablecode;

????for?(size_t?i = 0; i < PAGE_HANDLE_MAX; i++) {

????????// 如果tablecode有異常則跳過這個

????????if?(!CheckHandleTableEntry(&pHandleTableEntry[i])) {

????????????continue;

????????}

????????// 通過_handle_table_entry計算_object_header地址

????????pObjHeader = HandleEntryTable2ObjectHeader(&pHandleTableEntry[i]);

????????// Option: Check this object is process?

????????if?(!IsProcess(pObjHeader)) {

????????????continue;

????????}

????????// Compare whether the two eprocess variables are the same

????????if?((PVOID64)((PUCHAR)pObjHeader + HANDLE_BODY_OFFSET) == pEprocess) {

????????????kprintf("[+] Found tablecode: %llx; object_handle: %p; "

????????????????????"handle_table_entry: %p;\r\n",

????????????????????tablecode,

????????????????????pObjHeader,

????????????????????&pHandleTableEntry[i]);

????????????// 取消句柄的讀寫權限

????????????ModfiyGrantedAccessBits(&pHandleTableEntry[i]);

????????????return?TRUE;

????????}

????}

????return?FALSE;

}

修改權限也很簡單,直接去掉內存讀和內存寫的權限

1

2

3

4

5

6

7

8

9

/// @brief 修改handle_entry_table的GrantedAccessBits權限,句柄的內存讀寫權限

/// @param pHandleTableEntry

/// @return

NTSTATUS ModfiyGrantedAccessBits(PHANDLE_TABLE_ENTRY pHandleTableEntry)

{

????pHandleTableEntry->GrantedAccessBits &=

????????~(PROCESS_VM_READ | PROCESS_VM_WRITE);

????return?STATUS_SUCCESS;

}

上面就是一層句柄的修改了,這樣改完CE還是能讀取內存,因為CE是兩層句柄表,所以要再處理一下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

/// @brief 遍歷兩層的句柄表,判斷其中是否有目標句柄進程pEprocess

/// 如果有則返回TRUE, 否則返回FALSE

/// @param pProcessHandleObj 需要遍歷的pProcessHandleObj的結構體

/// @param pEprocess 目標進程句柄

/// @return

BOOLEAN?FilterTWOTabelByEprocess(PEPROCESS pEprocess,?UINT64?tablecode) {

????PUINT64?tables;

?????

????tables = tablecode & TABLE_CODE_MASK;

????for?(size_t?i = 0; tables[i] != 0; i++) {

????????if?(FilterOneTableByEprocess(pEprocess, tables[i])){

????????????return?TRUE;

????????}

????}

????return?FALSE;

}

這樣整個代碼邏輯就完全了,下面放一下全部的代碼,分兩個文件,寫的比較難看。

  • main.c
  • header.c

main.c

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#include "header.h"

VOID?DriveUnload(PDRIVER_OBJECT pDriver)

{

????kprintf("Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath)

{

????PEPROCESS eprocess = NULL;

????NTSTATUS? status?? = PsLookupProcessByProcessId((HANDLE)0xab4, &eprocess);

????if?(!NT_SUCCESS(status)) {

????????kprintf("Open process? unsuccessfully!\r\n");

????????return?STATUS_UNSUCCESSFUL;

????}

????ObDereferenceObject(eprocess);

????ProtectProcessHandleByEprocess(eprocess);

????pDriver->DriverUnload = DriveUnload;

????return?STATUS_SUCCESS;

}

header.c

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

#pragma once

#include <ntifs.h>

#define WIN10_21H1_X64_OBJECTTABLE_OFFSET 0x570

#define WIN10_21H1_X64_HANDLETABLELIST_OFFSET 0x18

#define WIN10_21H1_X64_TABLECODE_OFFSET 0x8

#define WIN10_21H1_X64_QUOTOPROCESS_OFFSET 0x10

#define TABLE_LEVEL_MASK 3

#define TABLE_LEVEL_ZERO 0

#define TABLE_LEVEL_ONE 1

#define TABLE_LEVEL_TWO 2

#define PAGE_HANDLE_MAX 256

#define EPROCESS_IMAGE_OFFSET 0x5A8

#define HANDLE_BODY_OFFSET 0x30

#define TYPE_INDEX_OFFSET 0x18

#define TABLE_CODE_MASK 0xFFFFFFFFFFFFFFF8

#define POOL_TAG 'axe'

// GrantedAccessBits

#define PROCESS_VM_READ (0x0010)

#define PROCESS_VM_WRITE (0x0020)

/**

?* 下面兩個值是通過調試系統得到的

?*? OB_HEADER_COOKIE可以使用`db nt!ObHeaderCookie l1`得到

?*? PROCESS_TYPE通過計算得到當前系統的PROCESS的type index值為7

?* */

#define OB_HEADER_COOKIE 0x21

#define PROCESS_TYPE 7

#define kprintf(...) \

????KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, __VA_ARGS__))

typedef?struct?HANDLE_TABLE_ENTRY

{

????UINT64?LowValue;

????UINT32?GrantedAccessBits;

????UINT32?Spare2;

} *PHANDLE_TABLE_ENTRY, HANDLE_TABLE_ENTRY;

/// @brief 存放每個進程的信息

typedef?struct?PROCESS_HANDLE_OBJECT

{

????PEPROCESS?????????? eprocess;

????PHANDLE_TABLE_ENTRY table_code;

} *PPROCESS_HANDLE_OBJECT, PROCESS_HANDLE_OBJECT;

VOID?DisplayProcessHandleObj(PPROCESS_HANDLE_OBJECT pHandleObj)

{

????kprintf("[+] eprocess: %p; table_code: %p; image_name: %15s\r\n",

????????????pHandleObj->eprocess,

????????????pHandleObj->table_code,

????????????(PUCHAR)(pHandleObj->eprocess) + EPROCESS_IMAGE_OFFSET);

}

/// @brief

/// 檢查一個PHANDLE_TABLE_ENTRY中的數值是否合法,LowValue是否為0,合法返回TRUE,否則返回FALSE

/// @param pHandleTableEntry PHANDLE_TABLE_ENTRY指針

/// @return 合法返回TRUE,否則返回FALSE

BOOLEAN?CheckHandleTableEntry(PHANDLE_TABLE_ENTRY pHandleTableEntry)

{

????if?(!pHandleTableEntry->LowValue) {

????????return?FALSE;

????}

????return?TRUE;

}

/// @brief

/// 新建一個PROCESS_HANDLE_OBJECT結構體。傳入eprocess地址或者handle_table地址,二者至少其一

/// 創建成功返回結構體指針,失敗則返回NULL

/// @param pEprocess eprocess地址或者NULL

/// @param pHandleTable _handle_table地址或者NULL

/// @return 創建成功返回結構體指針,失敗則返回NULL

PPROCESS_HANDLE_OBJECT NewProcessHandleObject(PEPROCESS pEprocess,

??????????????????????????????????????????????PVOID64?? pHandleTable)

{

????UINT64?????????????????uTableCode;

????PPROCESS_HANDLE_OBJECT ptr;

????if?(pEprocess == NULL && pHandleTable == NULL) {

????????return?NULL;

????}

????if?(pEprocess == NULL) {

????????pEprocess = *(PUINT64)((PUCHAR)pHandleTable +

???????????????????????????????WIN10_21H1_X64_QUOTOPROCESS_OFFSET);

????}

????if?(pHandleTable == NULL) {

????????pHandleTable =

????????????*(PUINT64)((PUCHAR)pEprocess + WIN10_21H1_X64_OBJECTTABLE_OFFSET);

????}

????uTableCode =

????????*(PUINT64)((PUINT8)pHandleTable + WIN10_21H1_X64_TABLECODE_OFFSET);

????ptr = ExAllocatePool(NonPagedPool,?sizeof(PROCESS_HANDLE_OBJECT));

????if?(ptr == NULL) {

????????kprintf("[!] Alloc struct PROCESS_HANDLE_OBJECT faild\r\n");

????????return?NULL;

????}

????ptr->eprocess?? = pEprocess;

????ptr->table_code = uTableCode;

}

/// @brief 銷毀PROCESS_HANDLE_OBJECT結構體,傳入一個對應指針

/// @param pProcessHandlePbject PROCESS_HANDLE_OBJECT的指針

/// @return

VOID?FreeProcessHandleObject(PPROCESS_HANDLE_OBJECT pProcessHandlePbject)

{

????pProcessHandlePbject->eprocess?? = NULL;

????pProcessHandlePbject->table_code = 0;

????ExFreePool(pProcessHandlePbject);

}

/// @brief 傳入一個HANDLE_TABLE_ENTRY結構體的地址,計算出ObjectHeader地址

/// @param addr HANDLE_TABLE_ENTRY結構體的地址

/// @return 返回ObjectHeader地址

ULONG64?HandleEntryTable2ObjectHeader(PHANDLE_TABLE_ENTRY addr)

{

????return?((addr->LowValue >> 0x10) & 0xFFFFFFFFFFFFFFF0) + 0xFFFF000000000000;

}

/// @brief 傳入一個ObjectHeader地址,判斷是否是進程對象,如果是則返回TRUE,

/// 不是則返回FALSE

/// @param Address 句柄頭的地址,也就是_object_header結構體地址

/// @return 如果是則返回TRUE, 不是則返回FALSE

BOOLEAN?IsProcess(PVOID64 Address)

{

????UINT8 uTypeIndex;

????UINT8 uByte;

????uByte????? = ((ULONG64)Address >> 8) & 0xff;

????uTypeIndex = *(PCHAR)((PCHAR)Address + TYPE_INDEX_OFFSET);

????uTypeIndex = uTypeIndex ^ OB_HEADER_COOKIE ^ uByte;

????if?(uTypeIndex == PROCESS_TYPE) {

????????return?TRUE;

????}

????return?FALSE;

}

/// @brief 匹配進程的imageName,如果和指定的ImageName相同則返回

/// @param Address _object_header的地址

/// @param Name 需要匹配的程序名稱

/// @return 如果這個是進程句柄且是目標進程則返回TRUE,否則返回FALSE

BOOLEAN?IsProcessName(PVOID64 Address,?PUCHAR?Name)

{

????PVOID64 pEprocess;

????PUCHAR??ImageName;

????if?(!IsProcess(Address)) {

????????return?FALSE;

????}

????pEprocess = ((PCHAR)Address + HANDLE_BODY_OFFSET);

????ImageName = (PUCHAR)pEprocess + EPROCESS_IMAGE_OFFSET;

????if?(strstr(ImageName, Name) == NULL) {

????????return?FALSE;

????}

????return?TRUE;

}

/// @brief

/// 傳入一個PLIST_ENTRY64,會遍歷這個鏈表,每個鏈表節點會生成一個對應的PROCESS_HANDLE_OBJECT指針

/// 組成一個數組,存放指針,存放到ObjArr

/// @param pHandleList Handle_list鏈表

/// @param ObjArr PPROCESS_HANDLE_OBJECT* 指針

/// @return 返回一個指針數組,數組元素是PROCESS_HANDLE_OBJECT指針

NTSTATUS CreateProcessObjArrByHandleList(PLIST_ENTRY64??????????? pHandleList,

?????????????????????????????????????????PPROCESS_HANDLE_OBJECT** ObjArr)

{

????PLIST_ENTRY64?????????? pTmp;

????UINT64??????????????????cout = 0;

????PPROCESS_HANDLE_OBJECT* pProcessObjArr;

????// 獲取鏈表節點數量,用于申請內存塊大小

????pTmp = pHandleList;

????do?{

????????pTmp = pTmp->Flink;

????????cout += 1;

????}?while?(pTmp != pHandleList);

????pProcessObjArr = ExAllocatePoolZero(

????????NonPagedPool, (cout + 1) *?sizeof(PPROCESS_HANDLE_OBJECT), POOL_TAG);

????if?(!pProcessObjArr) {

????????kprintf("[!] Alloc process handle obj array failed\r\n");

????????return?STATUS_ALLOCATE_BUCKET;

????}

????// 遍歷鏈表獲取節點信息,并創建ProcessHandleObject結構體

????for?(size_t?i = 0; i < cout; i++) {

????????pProcessObjArr[i] = NewProcessHandleObject(

????????????NULL, ((PUCHAR)pTmp - WIN10_21H1_X64_HANDLETABLELIST_OFFSET));

????????pTmp = pTmp->Flink;

????}

????*ObjArr = pProcessObjArr;

????return?STATUS_SUCCESS;

}

/// @brief 釋放ProcessObject指針數組的內容

/// @param ObjArr PPROCESS_HANDLE_OBJECT數組

/// @return

VOID?FreeProcessObjArr(PPROCESS_HANDLE_OBJECT* ObjArr)

{

????for?(size_t?i = 0; ObjArr[i] != 0; i++) {

????????FreeProcessHandleObject(ObjArr[i]);

????????ObjArr[i] = NULL;

????}

????// ExFreePoolWithTag(&ObjArr, POOL_TAG);

}

/// @brief 傳入一個_object_header指針打印body是_eprocess的ImageName字符內容

/// @param ObjectHeader

/// @return

VOID?ShowImageNameByObjectHeader(PVOID64 ObjectHeader)

{

????PVOID64 pEprocess;

????PUCHAR??ImageName;

????pEprocess = ((PUCHAR)ObjectHeader + HANDLE_BODY_OFFSET);

????ImageName = (PUCHAR)pEprocess + EPROCESS_IMAGE_OFFSET;

????kprintf("[+] ImageName: %15s\r\n", ImageName);

}

/// @brief 修改handle_entry_table的GrantedAccessBits權限,句柄的內存讀寫權限

/// @param pHandleTableEntry

/// @return

NTSTATUS ModfiyGrantedAccessBits(PHANDLE_TABLE_ENTRY pHandleTableEntry)

{

????pHandleTableEntry->GrantedAccessBits &=

????????~(PROCESS_VM_READ | PROCESS_VM_WRITE);

????return?STATUS_SUCCESS;

}

/// @brief 針對單張句柄表的情況,匹配目標eprocess,如果匹配到則修改句柄權限

/// @param pEprocess 目標eprocess結構體指針

/// @param tablecode 單張句柄表的tablecode

/// @return

BOOLEAN?FilterOneTableByEprocess(PEPROCESS pEprocess,?UINT64?tablecode) {

????PHANDLE_TABLE_ENTRY pHandleTableEntry;

????PVOID64???????????? pObjHeader;

????pHandleTableEntry = tablecode;

????for?(size_t?i = 0; i < PAGE_HANDLE_MAX; i++) {

????????// 如果tablecode有異常則跳過這個

????????if?(!CheckHandleTableEntry(&pHandleTableEntry[i])) {

????????????continue;

????????}

????????// 通過_handle_table_entry計算_object_header地址

????????pObjHeader = HandleEntryTable2ObjectHeader(&pHandleTableEntry[i]);

????????// Option: Check this object is process?

????????if?(!IsProcess(pObjHeader)) {

????????????continue;

????????}

????????// Compare whether the two eprocess variables are the same

????????if?((PVOID64)((PUCHAR)pObjHeader + HANDLE_BODY_OFFSET) == pEprocess) {

????????????kprintf("[+] Found tablecode: %llx; object_handle: %p; "

????????????????????"handle_table_entry: %p;\r\n",

????????????????????tablecode,

????????????????????pObjHeader,

????????????????????&pHandleTableEntry[i]);

????????????// 取消句柄的讀寫權限

????????????ModfiyGrantedAccessBits(&pHandleTableEntry[i]);

????????????return?TRUE;

????????}

????}

????return?FALSE;

}

/// @brief 遍歷兩層的句柄表,判斷其中是否有目標句柄進程pEprocess

/// 如果有則返回TRUE, 否則返回FALSE

/// @param pProcessHandleObj 需要遍歷的pProcessHandleObj的結構體

/// @param pEprocess 目標進程句柄

/// @return

BOOLEAN?FilterTWOTabelByEprocess(PEPROCESS pEprocess,?UINT64?tablecode) {

????PUINT64?tables;

?????

????tables = tablecode & TABLE_CODE_MASK;

????for?(size_t?i = 0; tables[i] != 0; i++) {

????????if?(FilterOneTableByEprocess(pEprocess, tables[i])){

????????????return?TRUE;

????????}

????}

????return?FALSE;

}

/// @brief 遍歷一層/兩層句柄表,判斷其中是否有目標句柄進程pEprocess

/// 如果有則返回TRUE, 否則返回FALSE

/// @param pProcessHandleObj 需要遍歷的pProcessHandleObj的結構體

/// @param pEprocess 目標進程句柄

/// @return

BOOLEAN?FilterObjByEprocess(PPROCESS_HANDLE_OBJECT pProcessHandleObj,

????????????????????????????PEPROCESS????????????? pEprocess)

{

????UINT64??????????????tablecode;

????PHANDLE_TABLE_ENTRY pHandleTableEntry;

????PVOID64???????????? pObjHeader;

????tablecode = pProcessHandleObj->table_code;

????switch?(tablecode & TABLE_LEVEL_MASK)

????{

????case?TABLE_LEVEL_ZERO:

????????return?FilterOneTableByEprocess(pEprocess, tablecode);

????????break;

????case?TABLE_LEVEL_ONE:

????????return?FilterTWOTabelByEprocess(pEprocess, tablecode);

????????break;

????default:

????????break;

????}

????return?FALSE;

}

/// @brief 傳入需要保護的進程eprocess,保護程序句柄

/// @param pEprocess PEPROCESS地址

/// @return

NTSTATUS ProtectProcessHandleByEprocess(PEPROCESS pEprocess)

{

????PVOID64???????????????? pHandleTable;

????PLIST_ENTRY64?????????? pPriList, pTmp;

????UINT64??????????????????cout;

????PPROCESS_HANDLE_OBJECT* ObjArr;

????NTSTATUS??????????????? status;

????pHandleTable =

????????*(PUINT64)((PCHAR)pEprocess + WIN10_21H1_X64_OBJECTTABLE_OFFSET);

????pPriList = (PLIST_ENTRY64)((PUCHAR)pHandleTable +

???????????????????????????????WIN10_21H1_X64_HANDLETABLELIST_OFFSET);

????kprintf("[+] EPROCESS: %p\r\n[+] handle object: %p\r\n[+] handle table "

????????????"list: %p\r\n",

????????????pEprocess,

????????????pHandleTable,

????????????pPriList);

????status = CreateProcessObjArrByHandleList(pPriList, &ObjArr);

????if?(!NT_SUCCESS(status)) {

????????kprintf("[!] CreateProcessObjArrByHandleList error");

????????return?STATUS_UNSUCCESSFUL;

????}

????for?(size_t?i = 0; ObjArr[i] != 0; i++) {

????????// kprintf("[+] Obj[%d]: %llx\r\n", i, ObjArr[i]);

????????// DisplayProcessHandleObj(ObjArr[i]);

????????kprintf("[+] Use handle process imagename: %s; eprocess: %p\r\n",

????????????????(PUCHAR)ObjArr[i]->eprocess + EPROCESS_IMAGE_OFFSET,

????????????????ObjArr[i]->eprocess);

????????FilterObjByEprocess(ObjArr[i], pEprocess);

????}

????FreeProcessObjArr(ObjArr);

????return?STATUS_SUCCESS;

}

結果

運行后,打印內容大致如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

...

[+] Use handle process imagename: sppsvc.exe; eprocess: FFFFC40D2B969080

[+] Use handle process imagename: SppExtComObj.E; eprocess: FFFFC40D26F19080

[+] Use handle process imagename: svchost.exe; eprocess: FFFFC40D2BA1C080

[+] Use handle process imagename: slui.exe; eprocess: FFFFC40D2CBF4080

[+] Use handle process imagename: svchost.exe; eprocess: FFFFC40D24B43080

[+] Use handle process imagename: backgroundTask; eprocess: FFFFC40D2CCE2080

[+] Use handle process imagename: HxTsr.exe; eprocess: FFFFC40D2AD7E080

[+] Use handle process imagename: backgroundTask; eprocess: FFFFC40D295E5080

[+] Use handle process imagename: CompatTelRunne; eprocess: FFFFC40D2AD42080

[+] Use handle process imagename: RuntimeBroker.; eprocess: FFFFC40D2CA6F080

[+] Use handle process imagename: RuntimeBroker.; eprocess: FFFFC40D2CC3D080

[+] Use handle process imagename: svchost.exe; eprocess: FFFFC40D2B966080

[+] Use handle process imagename: \; eprocess: FFFFF8066AB8E038

[+] Found tablecode: ffff9981206e3000; object_handle: FFFFC40D2CBE4050; handle_table_entry: FFFF9981206E3430;

[+] Use handle process imagename: Registry; eprocess: FFFFC40D23D26080

[+] Use handle process imagename: smss.exe; eprocess: FFFFC40D24711040

[+] Use handle process imagename: csrss.exe; eprocess: FFFFC40D24599080

[+] Use handle process imagename: wininit.exe; eprocess: FFFFC40D2542E080

[+] Use handle process imagename: csrss.exe; eprocess: FFFFC40D25432140

[+] Found tablecode: ffff9981262f6000; object_handle: FFFFC40D2CBE4050; handle_table_entry: FFFF9981262F6110;

[+] Use handle process imagename: services.exe; eprocess: FFFFC40D254CB080

[+] Use handle process imagename: lsass.exe; eprocess: FFFFC40D254D70C0

[+] Use handle process imagename: winlogon.exe; eprocess: FFFFC40D255820C0

[+] Use handle process imagename: svchost.exe; eprocess: FFFFC40D25428080

[+] Use handle process imagename: fontdrvhost.ex; eprocess: FFFFC40D25D6E140

[+] Use handle process imagename: fontdrvhost.ex; eprocess: FFFFC40D25D9D140

[+] Use handle process imagename: svchost.exe; eprocess: FFFFC40D25DBB2C0

[+] Found tablecode: ffff9981206e8000; object_handle: FFFFC40D2CBE4050; handle_table_entry: FFFF9981206E8390;

...

然后此時CE已經不能查看內存了

但是這只是當前這個CE進程的,如果關掉這個CE進程,再打開新的CE并且Open記事本進程就又可以重新讀取了。因為在新的CE進程中,還沒有修改句柄表中記事本進程的權限,所以還需要對抗。

對抗句柄降權思路

反復修改權限

防護方是通過驅動來遍歷私有句柄表,然后修改攻擊者進程的私有句柄表中指向被保護進程的句柄屬性。

那我們也可以寫一個驅動來不停的修改我們自身進程的私有句柄權限,將句柄權限改成full control

斷鏈

防護方是通過遍歷私有句柄鏈表來查找攻擊方的私有句柄表,那我們可以將私有句柄從鏈表上斷掉,放置一個空/假的私有句柄表結構體或者直接讓我們的進程私有句柄表從鏈表上斷開

ObRegisterCallbacks 保護

實際上上述的句柄降權/提權都是針對callbacks保護來做的,很多廠商是使用這個微軟公開的API來保護自身進程句柄的權限.

ObRegisterCallbacks 例程為線程、進程和桌面句柄操作注冊一系列回調例程。也就是我們可以給我們進程句柄設定一個回調函數,當我們的進程句柄被NtOpenProcess打開后,就會執行這個回調函數。如果我們在回調函數中修改這個句柄的權限,那么任何進程獲取我們進程句柄將得到修改過后權限的句柄。

1

2

3

4

NTSTATUS ObRegisterCallbacks(

??[in]? POB_CALLBACK_REGISTRATION CallbackRegistration,

??[out]?PVOID?????????????????????*RegistrationHandle

);

1

[in] CallbackRegistration

指向?OB_CALLBACK_REGISTRATION?結構的指針,該結構指定回調例程和其他注冊信息的列表。

1

[out] RegistrationHandle

指向變量的指針,該變量接收標識注冊的回調例程集的值。 調用方將此值傳遞給?ObUnRegisterCallbacks?例程,以取消注冊回調集。

我們看一下OB_CALLBACK_REGISTRATION結構體

1

2

3

4

5

6

7

typedef?struct?_OB_CALLBACK_REGISTRATION {

??USHORT????????????????????Version;

??USHORT????????????????????OperationRegistrationCount;

??UNICODE_STRING??????????? Altitude;

??PVOID?????????????????????RegistrationContext;

??OB_OPERATION_REGISTRATION *OperationRegistration;

} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;

其中OperationRegistration參數是指向OB_OPERATION_REGISTRATION結構的數組的指針。 每個結構指定?ObjectPreCallback?和?ObjectPostCallback?回調例程以及調用例程的操作類型。

ObjectPreCallback就是發生進程或線程句柄操作,操作系統會調用?ObjectPreCallback?例程

ObjectPostCallback就是發生進程或線程句柄操作,操作系統會調用?ObjectPostCallback?例程

所以我們的操作函數也就是放在這兩個數組當中。

ObRegisterCallbacks?例程使用此結構。 此例程的?CallBackRegistration?參數是指向包含?OB_CALLBACK_REGISTRATION?結構的緩沖區的指針,該結構后跟一個或多個?OB_OPERATION_REGISTRATION?結構的數組。

在傳遞給?ObRegisterCallback?的每個OB_OPERATION_REGISTRATION結構中,調用方必須提供一個或兩個回調例程。 如果此結構的?PreOperation?和?PostOperation?成員均為?NULL,則回調注冊操作將失敗。

為了保證注冊成功,我們可以在不用的操作上注冊一個空的函數。比如我要注冊Pre的,我也寫一個空的Post

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/14602.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/14602.shtml
英文地址,請注明出處:http://en.pswp.cn/web/14602.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

什么是DNS緩存投毒攻擊,有什么防護措施

隨著企業組織數字化步伐的加快&#xff0c;域名系統&#xff08;DNS&#xff09;作為互聯網基礎設施的關鍵組成部分&#xff0c;其安全性愈發受到重視。然而&#xff0c;近年來頻繁發生的針對DNS的攻擊事件&#xff0c;已經成為企業組織數字化發展中的一個嚴重問題。而在目前各…

go string 實現

在go中string是不可變的&#xff0c;這意味著對string發生改變的操作實際上都是通過分配新的string去實現的 在string內存分配上&#xff0c;對于小對象分配到棧&#xff0c;大對象分配到堆中 string在go中的結構其實很簡單&#xff0c;就是一個指向實際數據的指針以及字符串…

用于與 HTTP 服務器通信的函數

用于與 HTTP 服務器通信的函數 Plant Simulation 提供了許多使用 HTTP 協議與 HTTP 服務器通信的函數。可使用這些函數來發送 HTTP 請求、發送數據和從 HTTP 響應中接收數據&#xff0c;以及在 HTTP 服務器上創建和刪除資源&#xff1a; httpGetRequest 發送 GET 請求。請求…

在 Visual Studio 2022 (VS2022) 中刪除 Git 分支的步驟如下

git branch -r PS \MauiApp1> git push origin --delete “20240523備份” git push origin --delete “20240523備份”

PCL 常用小知識

文章目錄 一、時間計算二、實現類似`pcl::PointCloud::Ptr`和`pcl::PointCloud`的兩個類相互轉換三、查找點云的x,y,z的極值四、知道需要保存點的索引,從原點云中拷貝點到新點云五、從點云里刪除和添加點六、對點云進行全局或局部變換七、鏈接兩個點云字段(兩點云大小必須相…

若依 ruoyi-vue 用戶賬號前后端參數校驗密碼 手機號 郵箱

前端 <el-dialog :title"title" :visible.sync"open" width"800px" append-to-body><el-form ref"form" :model"form" :rules"rules" label-width"120px"><el-row><el-col :span…

Vue3骨架屏(Skeleton)

效果如下圖&#xff1a;在線預覽 APIs 參數說明類型默認值必傳animated是否展示動畫效果booleantruefalsebutton是否使用按鈕占位圖boolean | SkeletonButtonPropsfalsefalseavatar是否顯示頭像占位圖boolean | SkeletonAvatarPropsfalsefalseinput是否使用輸入框占位圖boolea…

SOLIDWORKS二次開發服務商 慧德敏學

SOLIDWORKS是一套三維設計軟件, 采用特征建模、變量化驅動可方便地實現三維建模、裝配和生成工程圖。SOLIDWORKS軟件本身所具有的交互方式, 可以使用戶對已生成模型的尺寸、幾何輪廓和相互約束關系隨時進行修改, 而不需要編程。但要實現設計意義上的變量化繪圖和系列化設計, 需…

java-查詢字符串當中是否包含中文

文章目錄 前言java-查詢字符串當中是否包含中文 前言 如果您覺得有用的話&#xff0c;記得給博主點個贊&#xff0c;評論&#xff0c;收藏一鍵三連啊&#xff0c;寫作不易啊^ _ ^。 ??而且聽說點贊的人每天的運氣都不會太差&#xff0c;實在白嫖的話&#xff0c;那歡迎常來啊…

軟考系統架構師一些知識點記錄-1

個人隨筆 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 引言 準備去參加軟考的考試&#xff0c;但對一些概念掌握的還不夠&#xff0c;借此機會&#xff0c;整理記錄一二&#xff0c;便于自己理解掌握。 知識范圍 感覺不夠清晰的部分主要是第三篇和第四篇的部分。…

國際頂會認可!KaiwuDB 論文入選 ICDE 2024

導 讀 近日&#xff0c;KaiwuDB 與中國人民大學合作的論文 FOSS: A Self-Learned Doctor for Query Optimizer 被數據庫領域頂會The 40th IEEE International Conference on Data Engineering (ICDE 2024) 錄用啦! 論文中提出了具備自學習、自診斷能力的查詢優化器 FOSS&…

USB官方文檔怎么下載

直接登錄USB官網"https://usb.org/" 如&#xff0c;我需要查找與USB device class相關的文檔 點擊搜索后就能找到。 學習還是要以官方文檔為主&#xff0c;博客上的介紹不可信&#xff0c;USB協議規范很重要!

商品發布功能

文章目錄 1.SPU和SKU介紹1.SPU2.SKU3.兩者之間的關系 2.完成商品發布界面1.組件引入1.commoditylaunch.vue 引入到 src/views/modules/commodity下2.multiUpload.vue 引入到 src/components/upload/multiUpload.vue 2.創建菜單1.創建目錄2.創建菜單&#xff0c;注意菜單路由要匹…

go語言中同一for循環體內的多個初始變量和多個自增變量用法示例

在go語言的for循環體中&#xff0c;我們可以同時初始多個變量&#xff0c; 也可以同時多多個變量進行自增/自減操作&#xff0c; 用法如下&#xff1a; for 后面的多個初始化變量使用的是逗號分隔的批量賦值操作&#xff0c;多個變量自增自減使用 加減運算符和逗號分隔 字符…

MySQL之性能剖析和Schema與數據類型優化(一)

性能剖析總結 1.定義性能最有效的方法是響應時間2.如果無法測量就無法有效地優化&#xff0c;所以性能優化工作需要基于高質量、全方位及完整的響應時間測量3.測量的最佳開始點是應用程序&#xff0c;而不是數據庫。即使問題出在底層的數據庫&#xff0c;借助良好的測量也可以…

C++系列-友元

&#x1f308;個人主頁&#xff1a;羽晨同學 &#x1f4ab;個人格言:“成為自己未來的主人~” 我們在之前的文章有提到友元&#xff0c;我們先來看下面的這段包含了友元的代碼&#xff1a; ??#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace…

CLIP論文學習

學習來自B站bryanyzhu

jdk17安裝教程詳細(jdk17安裝超詳細圖文)

2021年9月14日JDK17 發布&#xff0c;其中不僅包含很多新語言功能&#xff0c;而且與舊版 JDK 相比&#xff0c;性能提升也非常明顯。與之前 LTS 版本的 JDK 8 和 JDK 11 相比&#xff0c;JDK17 的性能提升尤為明顯&#xff0c;本文將教你如何安裝 相比于JDK1.8&#xff0c;JD…

虛擬機網絡設置為橋接模式后未顯示網絡

本方法為&#xff0c;VMware配置正確&#xff0c;但在嘗試其他辦法后未能成功解決的人提供一種方法 本機的虛擬機使用NAT模式正常使用 但是使用橋接模式后重啟&#xff0c;未發現虛擬機內網絡設置,詳見下圖&#xff1a; 使用 ifconfig 查看網絡詳情 發現沒有ens33接口 查看硬…

雙非本科,逆襲中大廠的 Java 學習路線

從零基礎入門 Java&#xff0c;到最后秋招上岸&#xff0c;筆者也是花費了不少的經歷&#xff0c;也走了很多彎路。這一篇文章會記錄下真正有用的學習路線。 為什么要強調真正有用&#xff1f;網上的很多所謂從入門到求職&#xff0c;推薦的路線都超級長&#xff0c;零基礎的同…