Window权限管理
UAC
在
OnInitDialog()
函数中初始化: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// 1. 获得本进程的令牌
HANDLE hToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return false;
// 2. 获取提升类型
TOKEN_ELEVATION_TYPE ElevationType = TokenElevationTypeDefault;
BOOL bIsAdmin = false;
DWORD dwSize = 0;
if (GetTokenInformation(hToken, TokenElevationType, &ElevationType,
sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) {
// 2.1 创建管理员组的对应SID
BYTE adminSID[SECURITY_MAX_SID_SIZE];
dwSize = sizeof(adminSID);
CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID, &dwSize);
// 2.2 判断当前进程运行用户角色是否为管理员
if (ElevationType == TokenElevationTypeLimited) {
// a. 获取连接令牌的句柄
HANDLE hUnfilteredToken = NULL;
GetTokenInformation(hToken, TokenLinkedToken, (PVOID)&hUnfilteredToken,
sizeof(HANDLE), &dwSize);
// b. 检查这个原始的令牌是否包含管理员的SID
if (!CheckTokenMembership(hUnfilteredToken, &adminSID, &bIsAdmin))
return false;
CloseHandle(hUnfilteredToken);
}
else {
bIsAdmin = IsUserAnAdmin();
}
CloseHandle(hToken);
}
// 3. 判断具体的权限状况
BOOL bFullToken = false;
switch (ElevationType) {
case TokenElevationTypeDefault: /* 默认的用户或UAC被禁用 */
if (IsUserAnAdmin()) bFullToken = true; // 默认用户有管理员权限
else bFullToken = false;// 默认用户不是管理员组
break;
case TokenElevationTypeFull: /* 已经成功提高进程权限 */
if (IsUserAnAdmin()) bFullToken = true; //当前以管理员权限运行
else bFullToken = false;//当前未以管理员权限运行
break;
case TokenElevationTypeLimited: /* 进程在以有限的权限运行 */
if (bIsAdmin) bFullToken = false;//用户有管理员权限,但进程权限有限
else bFullToken = false;//用户不是管理员组,且进程权限有限
}
// 4. 根据权限的不同控制按钮的显示
if (!bFullToken)
Button_SetElevationRequiredState(m_Button.m_hWnd,
!bFullToken);
else
::ShowWindow(m_Button.m_hWnd, SW_SHOW);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void CUACDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
// 1. 隐藏当前窗口
ShowWindow(SW_HIDE);
// 2. 获取当前程序路径
WCHAR szApplication[MAX_PATH] = { 0 };
DWORD cchLength = _countof(szApplication);
QueryFullProcessImageName(GetCurrentProcess(), 0,
szApplication, &cchLength);
// 3. 以管理员权限重新打开进程
SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) };
sei.lpVerb = L"runas"; // 请求提升权限
sei.lpFile = szApplication; // 可执行文件路径
sei.lpParameters = NULL; // 不需要参数
sei.nShow = SW_SHOWNORMAL; // 正常显示窗口
if (ShellExecuteEx(&sei))
exit(0);
else
ShowWindow(SW_SHOWNORMAL);
}提权:
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//提升为调试权限
BOOL EnableDebugPrivilege(BOOL fEnable) {
BOOL fOK = FALSE;
HANDLE hToken;
//以修改权限的方式打开进程的令牌
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
//令牌权限结构体
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
//获得LUID
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
//修改权限
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOK = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return(fOK);
}
//遍历权限
void ShowPrivilege()
{
//打开访问令牌
HANDLE hToken;
//OpenProcessToken函数用来打开与进程相关联的访问令牌
//GetCurrentProcess获取当前进程的一个伪句柄
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
if (!hToken)
{
printf("令牌打开失败\n");
return;
}
//查询令牌中的权限
DWORD dwSize;
//第一次调用是为了获取数据大小
GetTokenInformation(hToken, TokenPrivileges, NULL, NULL, &dwSize);
char* pBuf = new char[dwSize] {};
//第二次调用就可以获取数据了
GetTokenInformation(hToken, TokenPrivileges, pBuf, dwSize, &dwSize);
TOKEN_PRIVILEGES* pTp = (TOKEN_PRIVILEGES*)pBuf;
//权限个数
DWORD dwCount = pTp->PrivilegeCount;
//pTp->Privileges:存储权限的数组
LUID_AND_ATTRIBUTES* pLaa = pTp->Privileges;
for (int i = 0; i < dwCount; i++, pLaa++)
{
char strName[100] = {};
DWORD dwLen = sizeof(strName);
LookupPrivilegeNameA(0, &pLaa->Luid, strName, &dwLen);
//pLaa->Attributes:0表示关闭,1表示默认开启,2:开启,3:默认开启
printf("权限:【%s】-状态:【%d】\n", strName, pLaa->Attributes);
}
//释放内存
delete pBuf;
}
int main()
{
EnableDebugPrivilege(TRUE);
ShowPrivilege();
system("pause");
return 0;
}
虚拟内存
无论物理内存多大,每个进程都有4GB的虚拟内存空间
每个进程在虚拟内存空间的使用上都是相似的,低2GB是用户空间,高2GB是系统空间,低2GB的用户代码空间的代码无法访问高2GB的系统空间
在进程中使用的全部是虚拟地址,具体虚拟地址到物理地址的转换由操作系统内核完成,故无法在自己的进程中访问到其他进程的内存
一个进程的虚拟空间只有一部分与物理内存有映射关系,并且 Windows 尽量保证对于不同进程的同一份数据,在物理内存中只有一份,分别映射到多个进程中,从而节约内存
当各个进程所使用的内存数量超出物理内存时,操作系统还能将物理内存中暂时用不到的数据交换到硬盘中
堆内存的管理 :可以通过
HeapCreate
创建一个堆,当应用完后直接HeapDestroy
将该内存堆所有内存释放掉1
2
3
4
5
6//创建一个堆使用
HANDLE WINAPI HeapCreate(
_In_ DWORD flOptions,
_In_ SIZE_T dwInitialSize,
_In_ SIZE_T dwMaximumSize
);第一参数
flOptions
表示对堆的操作如何进行,可以是0,HEAP_NO_SERIALIZE
,HEAP_GENERATE_EXCEPTIONS
,HEAP_CREATE_ENABLE_EXECUTE
默认情况下,对堆的访问会依次进行,多个线程会从同一个堆中分配释放内存,堆数据不被破坏。
但在多线程情况下,要尽量避免使用HEAP_NO_SERIALIZE
。如果想在堆中放可执行代码,必须使用HEAP_CREATE_ENABLE_EXECUTE
第二参数dwInitialSize
表示开始时分给堆的字节数。
第三参数dwMaximumSize
表示所能增长到的最大大小,如果指定为0的话,则堆可以在需要的情况下不断增大。从堆里分配内存块,需要调用
HeapAlloc()
函数1
2
3
4
5LPVOID WINAPI HeapAlloc(
_In_ HANDLE hHeap,
_In_ DWORD dwFlags,
_In_ SIZE_T dwBytes
);第二参数用来指定一些标志,会对分配结果产生影响。目前只支持这三个参数,
HEAP_ZERO_MEMORY
,HEAP_GENERATE_EXCEPTIONS
,HEAP_NO_SERIALIZE
HEAP_ZERO_MEMORY
是把内存清空HEAP_GENERATE_EXCEPTIONS
告诉系统,如果没有足够的空间,就抛出异常HEAP_NO_SERIALIZE
用来强制系统不要把这次分配结果与其他线程的访问排列起来,有可能破坏堆的完整示例:
1
2
3
4
5
6
7
8//创建一个可增长的堆
HANDLE hHeap = HeapCreate(0, 0, 0);
SYSTEM_INFO si; //系统信息
GetSystemInfo(&si); // 获取系统信息
//在堆上分配3个页面大小的内存
LPVOID lpMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 3/*si.dwPageSize * 3*/);
HeapFree(hHeap, 0, lpMem);
HeapDestroy(hHeap);1
2
3
4
5
6
7
8//获取默认堆
HANDLE hHeap = GetProcessHeap();
SYSTEM_INFO si; //系统信息
GetSystemInfo(&si); // 获取系统信息
//在堆上分配3个页面大小的内存
LPVOID lpMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, si.dwPageSize * 3);
HeapFree(hHeap, 0, lpMem);
HeapDestroy( hHeap );//默认堆是不能被销毁的
虚拟内存的管理:虚拟内存是按照分页来管理的,一个内存页是4KB
整体上,虚拟内存有3种状态:空闲的,保留的,提交的
每一页的内存都有自己的访问属性:
属性 描述 PAGE_EXECUTE 可执行 PAGE_EXECUTE_READ 可读可执行 PAGE_EXECUTE_READWRITE 可写可执行 PAGE_EXECUTE_WRITECOPY 可执行,写时复制 PAGE_NOACCESS 不可访问 PAGE_READONLY 只读 PAGE_READWRITE 可读可写 PAGE_WRITECOPY 写时复制 虚拟内存的属性可通过
VirtualProtect
来修改1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//申请一块虚拟内存
HANDLE VirtualAlloc(
_In_opt_ LPVOID lpAddress, //分配的起始地址,函数会自动对齐到整数位置
_In_SIZE_T dwSize, //要分配的内存区域的大小
_In_DWORD flAllocationType, //这块内存是要预定还是提交
_In_DWORD flProtect //内存的保护属性
);
//VirtualAllocEx函数的作用是在指定进程的虚拟空间保留或提交内存区域,除非指定MEM_RESET参数,否则将该内存区域置0
LPVOID VirtualAllocEx(
HANDLE hProcess, //申请内存所在的进程句柄
LPVOID lpAddress, //保留页面的内存地址,一般用NULL自动分配
SIZE_T dwSize, //欲分配的内存大小,字节单位,注意实际分配的内存大小是页内存大小的整数倍
DWORD flAllocationType,//MEM_COMMIT,为特定的页面区域分配内存中或磁盘的页面文件中的物理存储,......
DWORD flProtect//PAGE_READWRITE区域可被应用程序读写,......
);1
2
3
4
5
6
7
8
9
10
11
12
13
14//释放一块内存
BOOL WINAPI VirtualFree(
LPVOID lpAddress, //需要改变状态的内存区域的起始地址
_In_SIZE_T dwSize, //需要改变状态的大小
_In_DWORD dwFreeType //设为MEM_DECOMMIT,则将内存变为保留状态,当dwSize为0,参数1必须为VirtualAlloc得到的申请好的内存的起始地址;设为MEM_RELEASE,则释放内存,将内存变为空闲状态
);
//用VirtualFreeEx 在其它进程中释放申请的虚拟内存空间
BOOL WINAPI VirtualFreeEx(
HANDLE hProcess,//目标进程的句柄。该句柄必须拥有 PROCESS_VM_OPERATION 权限
LPVOID lpAddress,//指向要释放的虚拟内存空间首地址的指针,如果 dwFreeType 为 MEM_RELEASE, 则该参数必须为VirtualAllocEx的返回值
SIZE_T dwSize,//虚拟内存空间的字节数。如果 dwFreeType 为 MEM_RELEASE,则 dwSize 必须为0 . 按 VirtualAllocEx申请时的大小全部释放。如果dwFreeType 为 MEM_DECOMMIT, 则释放从lpAddress 开始的一个或多个字节 ,即 lpAddress +dwSize
DWORD dwFreeType //释放类型
);示例1:跨进程读写数据
VirtualAllocEx
,ReadProcessMemory
,WriteProcessMemory
三个函数可实现跨进程的内存分配,读取,写入等操作,是很多安全技术的基础函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void temp()
{
DWORD dwPid;
scanf_s("%d", &dwPid);//需要输入一个进程ID
//打开一个已存在的进程对象,并返回进程的句柄,PROCESS_ALL_ACCESS-获取所有权限
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
//加强版函数,跨进程申请内存
LPVOID lpBuf = VirtualAllocEx(hProcess, NULL, 1, MEM_COMMIT, PAGE_READWRITE);
//跨进程写内存
DWORD dwWrite;
WriteProcessMemory(hProcess, lpBuf, "Hello World",
sizeof("Hello World"), &dwWrite);
char szBuf[100] = {};
//跨进程读内存
DWORD dwRead;
ReadProcessMemory(hProcess, lpBuf, szBuf, sizeof(szBuf), &dwRead);
printf("%s\n", szBuf);
VirtualFreeEx(hProcess, lpBuf, 0, MEM_RELEASE);
}运行此程序,输入另一个程序的进程ID,并附加在OD中
VS中单步调试,获取
lpBuf
的地址,0x001d0000,在OD中查看内容为空,继续单步调试,到跨进程写内存,再查看
发现写入成功,继续运行程序
发现读取成功
示例2:在本进程中读写数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14int main()
{
//temp();
//申请本进程的虚拟内存
LPVOID lpBuf = VirtualAlloc(NULL, 1, MEM_COMMIT, PAGE_READWRITE);
//修改内存属性
DWORD dwOld;
//修改内存的保护属性为可读可写
VirtualProtect(lpBuf, 1, PAGE_READWRITE, &dwOld);
memcpy(lpBuf, "Hello World", sizeof("Hello World"));
printf("%s", lpBuf);
VirtualFree(lpBuf, 0, MEM_RELEASE);//释放虚拟内存
return 0;
}
内存映射:文件映射是一种将文件内容映射到进程虚拟内存中的技术
文件映射的作用及优势:
- 可让文件操作变得简单
- 文件还是在硬盘中,映射视图是一段内存,效率高
- 可以在不同的进程间共享数据
API 说明 GetSystemInfo 获取系统信息,用于确定分配粒度 CreateFileMapping 创建一个 mapping 对象 OpenFileMapping 打开已命名的 mapping 对象(可跨进度) UnmapViewOfFile 取消文件映射 MapViewOfFile 将 mapping 对象的文件映射到内存 FlushViewOfFile 将映射在内存中的文件写会到硬盘 1
2
3
4
5
6
7
8HANDLE WINAPI CreateFileMapping(
_In_HANDLE hFile,//指定欲在其中创建映射的一个文件句柄,若为0xFFFFFFFF(-1,即INVALID_HANDLE_VALUE)表示在页面文件中创建一个可共享的文件映射
_In_opt_LPSECURITY_ATTRIBUTES lpAttributes,//它指明返回的句柄是否可以被子进程所继承,指定一个安全对象,在创建文件映射时使用。如果为NULL(用ByVal As Long传递零),表示使用默认安全对象
_In_DWORD flProtect,//
_In_DWORD dwMaximumSizeHigh,//文件映射的最大长度的高32位
_In_DWORD dwMaximumSizeLow,//文件映射的最大长度的低32位,如这个参数和dwMaximumSizeHigh都是0,就用磁盘文件的实际长度
_In_opt_LPCTSTR lpName//指定文件映射对象的名字,如存在这个名字的一个映射,函数就会打开它,用vbNullString可以创建一个无名的文件映射
);
示例:
1 |
|
进程间的通讯示例:
1 | //进程间通讯(共享内存),发送信息“Hello World” |
1 | //接收前一个进程的消息 |
虚拟内存遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18SIZE_T WINAPI VirtualQueryEx(
_In_ HANDLE hProcess, //进程句柄
_In_opt_ LPCVOID lpAddress, //查询地址
_Out_ PMEMORY_BASIC_INFORMATION lpBuffer, //内存的信息
_In_ SIZE_T dwLengyh //传出结构体的大小
);
//此函数执行后会返回一个MEMORY_BASIC_INFORMATION结构体,里面包含有关于此内存地址的详细信息
typedef struct _MEMORY_BASIC_INFORMATION{
PVOID BaseAddress;//将参数向下取整到页面大小
PVOID AllocationBase;//区域地址,包含传入地址
DWORD AllocationProtect;//此区域在预定时的保护属性
SIZE_T RegionSize;//区域的大小
DWORD State;//区域的页面状态
DWORD Protect;//页面保护属性
DWORD Type;//页面类型
}MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
//注:页面状态可以是MEM_FREE(闲置),MEM_RESERVE(预定),MEM_COMMIT(调拨),若为MEM_FREE,则AllocationBase,AllocationProtect,State,Protect的值都将无效,若为MEM_RESERVE,则Protect的值无效
//注:页面类型可以为MEM_IMAGE,MEM_MAPPED或MEM_PRIVATE
1 |
|