WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

其它

关于博客

面试

杂谈

Windows取内核中的函数

在写MS14-058的64位的exp的时候,为了方便想不写汇编提权,这就需要找到内核的函数地址来实现了

感觉这也可以单独写一篇文章吧就不和那篇写一起了

0x01 简介

在提权中需要使用PsLookupProcessByProcessId来找到进程的EPROCESS结构指针

再使用PsReferencePrimaryToken找到结构中token的位置

将高权限的token值覆盖掉低权限则实现了提权

0x02 提取函数

在R3中提取函数地址只需要GetProcAddress就可以,因为加载了GetProcAddress得到的函数地址和基址是对应的

比如加载进来的dll基址为0x80000000,函数的地址在0x80000100

这样的没有任何问题的

R0不一样,R0的是已经在内核加载好了,用LoadLibrary加载进来也没用因为在R3

那么下面整理下步骤

  1. ntdll.dll中找到ZwQuerySystemInformation
  2. ZwQuerySystemInformation函数得到内核模块的信息
  3. 找到需要使用的模块
  4. 取出模块在R0的基址
  5. 将这个模块LoadLibrary加载进当前进程得到在R3中的基址
  6. GetProcAddress找到需要使用的函数的地址
  7. 函数地址-R3中的基址,得到函数的偏移
  8. R0的基址+函数的偏移,得到函数在R0中实际的位置

0x02 函数和结构

先了解一下用到的函数和结构

ZwQuerySystemInformation

1
2
3
4
5
6
NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);

SystemInformationClass 选择要检索的系统信息的类型,这里需要检测内核中的模块要用到
SystemModuleInformation,宏定义为11

SystemInformation 根据第一个参数得到的信息选择不同的结构,这里是SystemModuleInformation所以用到的结构是_SYSTEM_MODULE_INFORMATION

SystemInformationLength 读取的长度

ReturnLength 实际返回的长度

SYSTEM_MODULE_INFORMATION

1
2
3
4
5
typedef struct _SYSTEM_MODULE_INFORMATION
{
ULONG ModulesCount;
SYSTEM_MODULE Modules[0];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

ModulesCount 内核中总共有多少模块

Modules SYSTEM_MODULE数组 记载了所有模块的信息

可以遍历这个数组得到所有模块的信息

SYSTEM_MODULE

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _SYSTEM_MODULE
{
HANDLE Reserved1;
PVOID Reserved2;
PVOID ImageBaseAddress;
ULONG ImageSize;
ULONG Flags;
USHORT Id;
USHORT Rank;
USHORT w018;
USHORT NameOffset;
BYTE Name[256];
} SYSTEM_MODULE, *PSYSTEM_MODULE;

这边就说一下ImageBaseAddress和Name吧

ImageBaseAddress 该模块在内核中的基址

Name 该模块完整的路径

比如\SystemRoot\system32\ntoskrnl.exe

其中SystemRoot就是c:\windows

在环境变量里面可以看到

拼接起来这个文件在c:\windows\system32\中

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

#define SystemModuleInformation 11 //宏定义SystemModuleInformation

typedef NTSTATUS(NTAPI *kZwQuerySystemInformation)(
_In_ DWORD SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);

typedef NTSTATUS(NTAPI *kPsLookupProcessByProcessId)(
IN HANDLE ProcessId,
OUT PVOID Process
);


typedef struct _SYSTEM_MODULE
{
HANDLE Reserved1;
PVOID Reserved2;
PVOID ImageBaseAddress;
ULONG ImageSize;
ULONG Flags;
USHORT Id;
USHORT Rank;
USHORT w018;
USHORT NameOffset;
BYTE Name[256];
} SYSTEM_MODULE, *PSYSTEM_MODULE;

typedef struct _SYSTEM_MODULE_INFORMATION
{
ULONG ModulesCount;
SYSTEM_MODULE Modules[0];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

kPsLookupProcessByProcessId pPsLookupProcessByProcessId = NULL;

//上面都是函数的结构和结构

int main() {
char szNtName[256];
//存放Name的值
PVOID NtBase;
//存放ImageBaseAddress
HMODULE hNtdll = LoadLibraryA("ntdll");
//ZwQuerySystemInformation在ntdll中所以要载入进程
if (hNtdll == NULL)
{
printf("[-] Load Ntdll fail!");
return 0;
}
kZwQuerySystemInformation pZwQuerySystemInformation = (kZwQuerySystemInformation)GetProcAddress(hNtdll, "ZwQuerySystemInformation");
//从ntdll中提取出ZwQuerySystemInformation
if (pZwQuerySystemInformation == NULL)
{
printf("[-] Can not found ZwQuerySystemInformation!");
return 0;
}

ULONG SystemInfoBufferSize;
pZwQuerySystemInformation(SystemModuleInformation, &SystemInfoBufferSize, 0, &SystemInfoBufferSize);
//第一次调用ZwQuerySystemInformation不是为了取值,是判断能不能读出模块信息,如果返回长度为0说明读取失败了,如果不为0则下面第二次调用
if (SystemInfoBufferSize == 0)
{
printf("[-] SystemInfoBufferSize is 0!");
}
PULONG pSystemInfoBuffer = (PULONG)LocalAlloc(LMEM_ZEROINIT, SystemInfoBufferSize);
//开出一块和读出来大小相同的内存,用于存放下次ZwQuerySystemInformation得到的结构
printf("[+] LocalAlloc:0x%p\n", pSystemInfoBuffer);
if (pSystemInfoBuffer == 0)
{
printf("[-] LocalAlloc is fail!");
return -1;
}
int ret = pZwQuerySystemInformation(SystemModuleInformation, pSystemInfoBuffer, SystemInfoBufferSize, &SystemInfoBufferSize);

//第二次调用ZwQuerySystemInformation,将结构存入前面开的内存中
if (ret)
{
printf("[-] ZwQuerySystemInformation is fail!");
return -1;
}

_SYSTEM_MODULE_INFORMATION* smi = (_SYSTEM_MODULE_INFORMATION *)pSystemInfoBuffer;
//设置一个SYSTEM_MODULE_INFORMATION指针指向前面开的内存

printf("[+] Kernel Modle found %d\n", smi->ModulesCount);

memset(szNtName, 0, 256); //内存清零
int i = 0;
while (i < smi->ModulesCount)
{
//循环打印结构的中的值
SYSTEM_MODULE* sm = (SYSTEM_MODULE *)(smi->Modules + i);
printf("[*] module %p\n", smi->Modules);
printf("[*] Reserved1 0x%p\n", sm->Reserved1);
printf("[*] Reserved2 0x%p\n", sm->Reserved2);
printf("[*] ImageBaseAddress 0x%p\n", sm->ImageBaseAddress);
printf("[*] ImageSize 0x%p\n", sm->ImageSize);
printf("[*] Flags 0x%x\n", sm->Flags);
printf("[*] Id 0x%x\n", sm->Id);
printf("[*] Rank 0x%x\n", sm->Rank);
printf("[*] w018 0x%x\n", sm->w018);
printf("[*] NameOffset 0x%p\n", sm->NameOffset);
printf("[*] Name: %s\n", sm->Name);
printf("===========================================\n");

//如果name中存在.exe和nt,那么将基址存在NtBase,因为这就是有PsLookupProcessByProcessId函数的模块,ntoskrnl.exe
if (strstr((char*)sm->Name, ".exe") && strstr((char*)sm->Name, "nt"))
{
NtBase = sm->ImageBaseAddress;
strncpy_s(szNtName, 256, strstr((char*)sm->Name, "nt"), _TRUNCATE);
//将ntoskrnl.exe存入szNtName中,用strstr函数将前面路径的去掉
break;
}

}
printf("name:%s-0x%p\n", szNtName, NtBase); //打印ntoskrnl.exe和内存中的基址
HMODULE nt = LoadLibraryA(szNtName); //在当前进程加载ntoskrnl.exe
kPsLookupProcessByProcessId PLPBP = (kPsLookupProcessByProcessId)GetProcAddress(nt, "PsLookupProcessByProcessId");
//在当前进程找到PsLookupProcessByProcessId函数地址
pPsLookupProcessByProcessId = (kPsLookupProcessByProcessId)((_int64)NtBase + ((_int64)PLPBP - (_int64)nt));
//得到偏移加上内存中的基址
printf("PsLookupProcessByProcessId Address in 0x%p\n",pPsLookupProcessByProcessId);
//输出函数真实的位置

}

下面是输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[+] LocalAlloc:0x00000211ABDEDFE0
[+] Kernel Modle found 234
[*] module 00000211ABDEDFE8
[*] Reserved1 0x0000000000000000
[*] Reserved2 0x0000000000000000
[*] ImageBaseAddress 0xFFFFF8057C400000
[*] ImageSize 0x0000000001046000
[*] Flags 0x8804080
[*] Id 0x0
[*] Rank 0x0
[*] w018 0x1
[*] NameOffset 0x0000000000000015
[*] Name: \SystemRoot\system32\ntoskrnl.exe
===========================================
name:ntoskrnl.exe-0xFFFFF8057C400000
PsLookupProcessByProcessId Address in 0xFFFFF8057C9F0F40

师傅们可以对比一下,也可以编译之后运行一下看看

代码具体整了些啥也写到注释了