【第36节】windows原理:权限管理和内存管理

目录

一、权限管理

1.1 Windows权限管理

1.1.1 令牌

1.1.2 访问控制列表

1.2 UAC

二、内存管理

2.1 虚拟内存

2.2 堆内存的管理

2.2.1 Windows中的堆

2.2.2 涉及到的函数

2.3 虚拟内存的管理

2.3.1 申请与释放虚拟内存

2.3.2 虚拟内存的安全属性

2.3.3 在其他进程中分配虚拟内存,读取和修改虚拟内存

2.4 内存映射

2.5 虚拟内存遍历


一、权限管理

1.1 Windows权限管理

1.1.1 令牌

        Windows系统里有专门的权限管理办法,主要靠账户权限来落实。用户用某个账户登录系统后,新创建的进程就会获得和登录账户一样的权限。而这个进程创建的线程,默认和进程的权限相同。由于线程负责执行代码,是真正访问资源的单元,所以权限主要围绕进程或线程展开。在Windows系统里,这种权限被叫做令牌。

        我们创建对象时,对象一般都会带有安全描述符。安全描述符会规定哪些用户具备什么权限可以访问这个对象,哪些用户会被拒绝访问。所以,线程能不能访问对象,由线程持有的访问令牌,以及对象的安全描述符共同决定。

        我们使用计算机时得登录账户,借此和操作系统建立会话,操作系统会给这个会话分配对应的权限。在Windows操作系统里,权限被抽象成Token,也就是令牌。系统创建进程时,会把对应账户的令牌分配给进程,让进程拥有访问资源的相应权限。访问令牌里存着当前账户的SID,以及该账户拥有的权限 。

1.1.2 访问控制列表

        当进程要访问对象时,每个安全对象都配有安全描述符。安全描述符会讲清楚哪些用户能访问这个对象,哪些用户不能访问。

        安全描述符里有访问控制列表。在访问控制列表(ACL)中,通过一个个访问控制入口(ACE),就能确定每个账户具备什么权限可以访问对象,哪些账户会被禁止访问。

1.2 UAC

        在Windows Vista系统发布以后,Windows操作系统加入了UAC机制,它的全称是User Account Control,也就是用户账户控制。就算你用管理员账号登录系统,在创建进程时,系统还是会给这个进程分配一个低权限的令牌。这种令牌还有个名字,叫Filter Token,也就是过滤令牌。

        只有当程序以管理员身份启动运行,系统才会给它高权限。不过,只要是以管理员身份运行的程序,都会弹出一个选择对话框。

        在使用软件过程中,除了启动程序时会触发UAC控制,我们还能看到一些按钮带有UAC样式的图标。这种机制允许针对软件的个别特殊功能进行提权操作,大大提升了软件使用的便捷性。用户不用每次打开软件都被UAC提示框打扰,而是能按照实际需求,决定是否给软件更高的运行权限。

要实现这一机制,既需要依靠复杂的权限控制系统,也离不开特定的进程创建逻辑:
        1. 软件运行时,会先判断当前进程的运行权限。
        2. 要是进程以低权限运行,软件就会给那些需要提权操作的按钮加上UAC盾牌标志。
        3. 要是进程已经以高权限运行,软件就会隐藏提权按钮。
        4. 当用户点击提权按钮,软件会通过ShellExecuteEx()函数,以管理员权限重新启动自身进程 。

显示UAC提升按钮的示例代码

//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(GetDlgItem(hWnd,控件ID),!bFullToken);
else
    ShowWindow(GetDlgItem(hWnd,控件ID),SW_HIDE);

以管理员权限打开进程的代码

//1.隐藏当前窗口
ShowWindow(hWnd,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))
    ExitProcess(0);
else
    ShowWindow(hWnd,SW_SHOWNORMAL);

提升当前进程权限为调试权限的代码

//提升为调试权限
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);
}

二、内存管理

2.1 虚拟内存

        内存是一种硬件设备,它的读写速度比硬盘快不少。CPU运行速度特别快,需要有高速设备跟它进行数据交换,这个任务就由内存来承担。我们可以把内存看成计算机运行时的数据中转站。现在CPU性能普遍很强,增加内存容量、提高内存频率,能有效提升计算机的整体性能。

        计算机运行程序离不开内存,当前正在运行的所有程序,操作系统内核的代码和数据,还有一部分资源,都存放在内存当中。这样一来,如何合理管理内存就成了难题。理想的内存管理应该满足下面这些要求:
        1. 任何一个进程都不能随便访问其他进程的内存。
        2. 当进程需要更多内存时,系统能动态分配,灵活性要好。
        3. 每个进程的内存管理方式得统一,便于程序员使用。
        4. 内存要有不同的属性,不同属性的内存执行不同的任务。
        5. 程序员使用内存时,不用去了解这些底层设计。

        为了解决上述以及其他相关问题,Windows系统引入了虚拟内存的概念。

        不管实际的物理内存有多大,每个进程都有4GB的虚拟内存空间。每个进程对虚拟内存空间的使用方法差不多,低2GB是用户空间,高2GB是系统空间,低2GB用户代码空间的代码没办法访问高2GB系统空间。

        进程里使用的都是虚拟地址,虚拟地址到物理地址的转换由操作系统内核负责。所以,在自己的进程里,是访问不了其他进程内存的,尽管不同进程的地址看上去很像。

        一个进程的虚拟空间,只有一部分和物理内存有映射关系。Windows系统会尽量保证,不同进程用到的同一份数据,在物理内存里只存一份,然后分别映射到多个进程中,借此节省内存。

        当各个进程使用的内存总量超过了物理内存,操作系统会把物理内存中暂时用不到的数据,转移到硬盘上。

        虚拟内存到物理内存的转换,主要通过分段与分页机制实现(以分页为主)。这部分内容将在后面讲解,本节课主要熟悉用户模式下内存相关API的使用。

2.2 堆内存的管理

2.2.1 Windows中的堆

        在Windows系统里,堆被当作对象来管理。我们能创建堆,在堆上分配内存,也能销毁堆里的内存。C/C++语言里的new和malloc函数,说到底也是依靠Windows系统的堆对象来分配内存空间的。

        Windows系统在创建进程时,会给这个进程创建一个默认堆,这个默认堆没办法被销毁。有些时候,在特定的时间段或者特定任务里,会需要大量内存,而且可能是很多小碎片状的内存块。要是都用默认堆,等这些内存都用完了,释放起来就特别麻烦,得一个一个地释放。要是任务执行过程中出了异常,还可能导致部分内存碎片的句柄丢失,释放难度就更大了,因为没法把它们一个个找出来释放,这样就很容易造成内存丢失。遇到这种情况,我们可以用HeapCreate函数创建一个独立的内存堆,等使用完之后,直接用HeapDestroy函数,就能把这个内存堆里的所有内存都释放掉。

2.2.2 涉及到的函数

使用示例
1. 创建一个堆使用

HANDLE     hHeap =HeapCreate(HEAP_NO_SERIALIZE,0,0);
SYSTEM_INFO si;    //系统信息
GetSystemInfo(&si);//获取系统信息
//在堆上分配3个页面大小的内存
lpMem =HeapAlloc(hHeap,HEAP_ZERO_MEMORY,si.dwPageSize*3);
HeapFree(hHeap,HEAP_NO_SERIALIZE,lpMem);
HeapDestroy(hHeap);

2. 在已经存在的堆上申请空间

HANDLE     hHeap =GetProcessHeap();//获取已存在的堆
SYSTEM_INFO si;                     //系统信息
GetSystemInfo(&si);                 //获取系统信息
//在堆上分配3个页面大小的内存
lpMem =HeapAlloc(hHeap,HEAP_ZERO_MEMORY,si.dwPageSize*3);
HeapFree(hHeap,HEAP_NO_SERIALIZE,lpMem);
HeapDestroy(hHeap);

2.3 虚拟内存的管理

        虚拟内存按分页管理,目前一个内存页大小为4KB,因此管理内存时,通常以4KB为单位。管理虚拟内存需要用到以下函数:
 

2.3.1 申请与释放虚拟内存

申请一块虚拟内存

VirtualAlloc(
    _In_opt_LPVOID lpAddress,
    _In_SIZE_T dwSize,
    _In_DWORD flAllocationType,
    _In_DWORD flProtect
);

- 参数1:分配的起始位置,函数会自动将其对齐到整数位置。
- 参数2:要分配的内存区域大小。
- 参数3:指定这块内存是预定还是提交。
- 参数4:内存的保护属性。

BOOL
WINAPI
VirtualFree(
    LPVOID lpAddress,
    _In_SIZE_T dwSize,
    _In_DWORD dwFreeType
);

- 参数1:需要改变状态的内存区域的起始地址。
- 参数2:需要改变状态的大小。
- 参数3:设置为MEM_DECOMMIT,将内存变为保留状态,当dwSize为0时,参数1必须为VirtualAlloc得到的申请好的内存的起始地址;设置为MEM_RELEASE,释放内存,将内存变为空闲状态。

虚拟内存有三种状态:

        当成功创建一个进程内核对象后,系统内核会给它划分出一部分物理内存,同时创建一块虚拟内存。新创建的虚拟内存刚开始只是在逻辑层面存在,还没有和物理内存建立映射关系,这个时候的内存被叫做闲置(Free)内存,或者未分配(unallocated)内存。

        要是想用这些内存,就得调用VirtualAlloc()函数,从里面划出一块内存区域,这种划区域的操作就叫预定。

        系统在分配内存空间的时候,会保证分配的内存起始地址,是内存分配粒度的整数倍。在X86架构的系统里,内存分配粒度是64KB。

        虚拟内存空间里,大部分区域都是空闲的,已经提交能使用的内存,就好比是浩瀚大海里零散分布的几块陆地。所以在操作虚拟内存时,要是不注意,就容易出错。

示例代码

LPVOID lpvBase=VirtualAlloc(
    NULL,    
    1024*64*5,          //64KB*5
    MEM_RESERVE,    //预定内存区域
    PAGE_NOACCESS);//不可访问
LPVOID lpvResult=VirtualAlloc(
    lpvBase,
    1024 *64 *1,        //64KB*1
    MEM_COMMIT,      //调拨内存区域
    PAGE_READWRITE);//可读写
StringCchCopy((LPWSTR)lpvResult,_countof (L"hello!"),L"hello!");
MessageBox(NULL,(LPWSTR)lpvResult,NULL,MB_OK);

2.3.2 虚拟内存的安全属性

每个内存页都有自己的访问属性:

虚拟内存的属性可通过VirtualProtect进行修改。

2.3.3 在其他进程中分配虚拟内存,读取和修改虚拟内存

        VirtualAllocEx、ReadProcessMemory与WriteProcessMemory三个函数,可实现跨进程的内存分配、读取、写入等操作,是很多安全技术的基础函数,后续学习远程线程注入时,会详细讲解这些函数的用法。

2.4 内存映射

        文件映射(Mapping)是将文件内容映射到进程虚拟内存中的技术。映射成功的文件可以用视图(View)引用这段内存,从而实现对该段内存中文件的操作。

        使用文件映射时,需先创建映射对象,映射对象分为命名和未命名两种,命名的映射对象可进行跨进程读写。

文件映射的作用及优势:
1. 简化文件操作。
2. 文件仍在硬盘中,映射视图是一段内存,效率高。
3. 可在不同进程间共享数据。

示例代码

HANDLE hFile;      //文件句柄
HANDLE hMapFile;   //文件内存映射区域的句柄
LPVOID lpMapAddress;//内存映射区域的起始位置
//1.创建一个文件
hFile = CreateFile(L"D:\\xxxxx", GENERIC_READ | GENERIC_WRITE, 0, NULL,
    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile) return FALSE;
//2.创建文件映射
hMapFile = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0,
    GetFileSize(hFile, NULL), NULL);
if (NULL == hMapFile) return FALSE;
//3.将文件映射View
lpMapAddress = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (NULL == lpMapAddress) return FALSE;
//......
//可以使用lpMapAddress进行一些操作
//......
//4.将映射的数据写回到硬盘上
FlushViewOfFile(lpMapAddress, 0);
//5.关闭mapping对象
if (!CloseHandle(hMapFile)) return FALSE;
if (!CloseHandle(hFile)) return FALSE;

进程间通讯示例代码
进程A

//1.创建命名的文件映射
HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
    16, L"File_Mapping_Test");
if (NULL == hMapFile || INVALID_HANDLE_VALUE == hMapFile)
    return FALSE;
//2.创建View
PVOID pBuf = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 16);
if (NULL == pBuf)
    return FALSE;
//3.将共享数据复制到文件映射中
wcscpy_s((PWCHAR)pBuf, 6, L"测试文本");
//4.循环等待
while (*((PBYTE)pBuf))
    Sleep(200);
//5.取消Mapping,关闭句柄
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);

进程B

//1.打开文件Mapping
HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"File_Mapping_Test");
if (NULL == hMapFile || INVALID_HANDLE_VALUE == hMapFile)
    return FALSE;
//2.创建View
PVOID pBuf = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 16);
if (NULL == pBuf)
    return FALSE;
//3.显示共享数据
MessageBox(NULL, (LPWSTR)pBuf, L"FileMapping", MB_OK);
//4.修改共享数据
*((PBYTE)pBuf) = 0;
//5.取消Mapping,关闭句柄
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);

2.5 虚拟内存遍历

        我们可通过系统提供的API函数VirtualQueryEx()获取某进程的虚拟内存分布状态,其原型如下:

SIZE_T WINAPI VirtualQueryEx(
    _In_ HANDLE hProcess,  //进程句柄
    _In_opt_ LPCVOID lpAddress,//查询地址
    _Out_ PMEMORY_BASIC_INFORMATION lpBuffer,//内存的信息
    _In_ SIZE_T dwLength   //传出结构体的大小
);

        此函数执行后会返回一个MEMORY_BASIC_INFORMATION结构体,里面包含该内存地址的详细信息。

typedef struct _MEMORY_BASIC_INFORMATION{
    PVOID BaseAddress;         //区域地址,此区域包含传入地址
    PVOID AllocationBase;      //将参数向下取整到页面大小
    DWORD AllocationProtect;   //此区域在预定时的保护属性
    SIZE_T RegionSize;         //区域的大小
    DWORD State;               //区域的页面状态[注1]
    DWORD Protect;             //页面保护属性
    DWORD Type;                //页面类型[注2]
}MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

        注1:内存页面有三种状态,分别是MEM_FREE(闲置)、MEM_RESERVE(预订)、MEM_COMMIT(调拨)。要是页面状态是MEM_FREE,那么AllocationBase、AllocationProtect、State、Protect这几个值都没有意义。要是页面状态是MEM_RESERVE,Protect这个值就没有意义。
        注2:内存页面有MEM_IMAGE、MEM_MAPPED、MEM_PRIVATE这三种类型 。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值