Windows 进程权限浅谈 -- 提权 / 降权

53 篇文章 1 订阅
7 篇文章 4 订阅

       在 Windows 上,用户对权限并不敏感,可能最为直观的是 UAC ,但相信很多人已经关掉了它的提示。

        

        但其实安全性早已深入了 Windows 的方方面面。Windows Vista 引入了一个称为强制完整性控制(Mandatory Integrity Controls,MIC)的新安全结构,类似于 Linux/Unix 中可用的完整性功能。在 Windows Vista 以及后续版本如Windows 11/10和Windows 8/7中,所有安全主体(用户、计算机、服务等)和对象(文件、注册表键、文件夹和资源)都被赋予MIC标签。

        权限由高到低分别为Low、Medium、High、System。

        如果登录用户是管理员账户,且开启了UAC,那么默认情况下双击一个程序时是以medium权限运行,称为受限的管理员权限,如果右键程序“以管理员权限运行”,那么程序是high权限,称为不受限的管理员权限。Low权限很少见,浏览器的某些进程就是Low权限的。Windows服务一般是System权限。

        

创建进程

        当用户尝试启动可执行文件时,会使用最低用户完整性级别和文件完整性级别创建新进程。 这意味着新进程永远不会以比可执行文件更高的完整性执行。 如果管理员用户执行低完整性程序,则新进程的令牌会以低完整性级别运行。 

        简单来说,进程启动的子进程,默认情况下权限只会等于或小于其父进程。

        如何以编程方式控制进程的执行级别?当用户启动应用程序时,其提升级别由其清单中 requestedExecutionLevel 属性的值确定,Windows 的用户帐户控制 (UAC) 会根据它采取适当的操作(例如,在需要时显示提升提示等)。但是,如果应用程序需要启动与应用程序本身执行级别不同的新进程,该怎么办?

获取当前进程执行级别

         通过 GetTokenInformation 即可获取 TOKEN_ELEVATION_TYPE。

	HANDLE hToken = NULL;
	TOKEN_ELEVATION_TYPE  tet_{};

	if (::OpenProcessToken(
		::GetCurrentProcess(),
		TOKEN_QUERY,
		&hToken))
	{

		DWORD dwReturnLength = 0;

		if (::GetTokenInformation(
			hToken,
			TokenElevationType,
			&tet_,
			sizeof(*ptet),
			&dwReturnLength)) {
			//
		}
	}

	::CloseHandle(hToken);

TOKEN_ELEVATION_TYPE  的取值分别是:

TokenElevationTypeDefault        The token does not have a linked token.        

UAC 已禁用,或者进程由标准用户(不是 Administrators 组的成员)启动。

TokenElevationTypeFull          The token is an elevated token.

 进程正在提升运行。

TokenElevationTypeLimited        The token is a limited token.

进程未提升运行。

仅当 UAC 都已启用且用户是管理员组的成员(即用户具有“拆分”令牌)时,才能返回最后两个值。

提权

        有的时候我们需要子进程以较高的权限执行以完成其功能。于是,我们需要提升子进程的权限。

        以产品更新为例,大部分时候产品主程序以在标准(非提升)级别运行,为了能够自我更新,它需要启动一个单独的更新进程,该更新进程需要提升才能正确执行升级。在这种情况下,非提升进程需要启动新的提升进程,则需要提权。

       

提升到管理员权限

  • ShellExecute

       很简单,通过 ShellExecute ,将 lpOperation 参数设置为 "runas" 即可

HINSTANCE hRet = ::ShellExecute(NULL, L"runas", pszFileName, NULL, NULL, SW_SHOWNORMAL);
if (32 < (DWORD)hRet)
{
  return TRUE;
}
return FALSE;

        当然,如果当前用户不属于管理员组,则会弹出 UAC 提示 -- 我们并不是为了绕过系统限制,仅仅是合理的请求高权限执行。

提升到 System 权限

  • PsExec 工具

PsExec 是一个轻量级的 telnet-replacement,允许您在其他系统上执行进程。PsExec 最强大的用途包括在远程系统上启动交互式命令提示符。

其中,它提供了一个非常有用的参数 

-s在系统帐户中运行远程进程。

如下命令即可将 foo.exe 以管理员权限启动,但需要首先以管理员权限执行这条命令:

PsExec.exe -i -s foo.exe

  • 系统服务

        系统中的服务进程默认都是运行在session 0下,具有 system 权限,如服务宿主进程 svchost.exe,其 Integrity 为 System。

        创建一个服务,再通过服务启动子进程,则子进程默认就是 system 权限,当然创建、启动服务也需要管理员权限。

  • 计划任务

可以通过批处理加创建最高权限(system)的计划任务

schtasks /Create /TN footask /SC DAILY /ST 01:00 /TR notepad.exe /RL HIGHEST

降权

        还是以产品更新为例,当以提升级别运行的更新程序完成更新任务,需要启动产品主进程,以标准(非提升)级别运行,此时则需要降权。

        和常识可能有点相悖,相对提权,降权难度更大一些。

降至指定权限

  • 设置ProcessToken的IntegrityLevel
#include <Windows.h>
#include <sddl.h>

#pragma comment(lib, "Advapi32.lib")

void CreateIntegritySidProcess(WCHAR* wszIntegritySid)
{
  BOOL bRet = FALSE;
  HANDLE hToken = NULL;
  HANDLE hNewToken = NULL;

  // Notepad is used as an example
  WCHAR wszProcessName[MAX_PATH] =L"C:\\Windows\\System32\\Notepad.exe";

  PSID pIntegritySid = NULL;

  TOKEN_MANDATORY_LABEL TIL = { 0 };
  PROCESS_INFORMATION ProcInfo = { 0 };
  STARTUPINFO StartupInfo = { 0 };
  ULONG ExitCode = 0;

  __try
  {
    if (FALSE == OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &hToken))
    {
      __leave;
    }
    if (FALSE == DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
      SecurityImpersonation, TokenPrimary, &hNewToken))
    {
      __leave;
    }
    if (FALSE == ConvertStringSidToSid(wszIntegritySid, &pIntegritySid))
    {
      __leave;
    }
    TIL.Label.Attributes = SE_GROUP_INTEGRITY;
    TIL.Label.Sid = pIntegritySid;

    // Set the process integrity level
    if (FALSE == SetTokenInformation(hNewToken, TokenIntegrityLevel, &TIL,
      sizeof(TOKEN_MANDATORY_LABEL)+GetLengthSid(pIntegritySid)))
    {
      __leave;
    }
    bRet = CreateProcessAsUser(hNewToken, NULL,
      wszProcessName, NULL, NULL, FALSE,
      0, NULL, NULL, &StartupInfo, &ProcInfo);
  }
  __finally
  {
    if (NULL != pIntegritySid)
    {
      LocalFree(pIntegritySid);
      pIntegritySid = NULL;
    }
    if (NULL != hNewToken)
    {
      CloseHandle(hNewToken);
      hNewToken = NULL;
    }
    if (NULL != hToken)
    {
      CloseHandle(hToken);
      hToken = NULL;
    }
  }
  printf("%ls bRet:%d\n", wszIntegritySid, bRet);//%ls打印宽字符
}


int _tmain(int argc, _TCHAR* argv[])
{
  /*
  Low (SID: S-1-16-4096),
  Medium (SID: S-1-16-8192),
  High (SID: S-1-16-12288)
  System (SID: S-1-16-16384).
  */
  //创建不同权限的进程  
  CreateIntegritySidProcess(L"S-1-16-4096");//low权限进程
  CreateIntegritySidProcess(L"S-1-16-8192");//medium权限进程
  CreateIntegritySidProcess(L"S-1-16-12288");//high权限进程
  CreateIntegritySidProcess(L"S-1-16-16384");//system权限进程
  printf("end\n");
  getchar();
  return 0;
}

        通过上述代码可知,关键是获取适当的  IntegrityLevel 。举一反三,如果我们获取到 explorer.exe 进程的Token 的 IntegrityLevel,就可以以 explorer.exe 的权限启动新进程,效果和桌面双击程序启动一样。

        另外,也可以通过 AllocateAndInitializeSid 指定 SECURITY_MANDATORY_LOW_RID 创建一个低权限的SID,这里用武稀松前辈的 Delphi 代码演示:

uses
  WinApi.Windows;

const
  SECURITY_MANDATORY_UNTRUSTED_RID = $00000000;
  SECURITY_MANDATORY_LOW_RID = $00001000;
  SECURITY_MANDATORY_MEDIUM_RID = $00002000;
  SECURITY_MANDATORY_HIGH_RID = $00003000;
  SECURITY_MANDATORY_SYSTEM_RID = $00004000;
  SECURITY_MANDATORY_PROTECTED_PROCESS_RID = $00005000;

function CreateLowIntegrityProcess(const ExeName: string;
  const Params: string = ”; TimeOut: DWORD = 0): HResult;
function GetIntegrityLevel(): DWORD;

implementation

type
  PTokenMandatoryLabel = ^TTokenMandatoryLabel;

  TTokenMandatoryLabel = packed record
    Label_: TSidAndAttributes;
  end;

function GetIntegrityLevel(): DWORD;
var
  hProcess, hToken: THandle;
  pTIL: PTokenMandatoryLabel;
  dwReturnLength: DWORD;
  dwTokenUserLength: DWORD;
  psaCount: PUCHAR;
  SubAuthority: DWORD;
begin
  Result := 0;
  dwReturnLength := 0;
  dwTokenUserLength := 0;
  pTIL := nil;

  hProcess := GetCurrentProcess();
  OpenProcessToken(hProcess, TOKEN_QUERY or TOKEN_QUERY_SOURCE, hToken);
  if hToken = 0 then
    Exit;
  if not GetTokenInformation(hToken, WinApi.Windows.TTokenInformationClass
    (TokenIntegrityLevel), pTIL, dwTokenUserLength, dwReturnLength) then
  begin
    if GetLastError = ERROR_INSUFFICIENT_BUFFER then
    Begin
      pTIL := Pointer(LocalAlloc(0, dwReturnLength));
      if pTIL = nil then
        Exit;
      dwTokenUserLength := dwReturnLength;
      dwReturnLength := 0;

      if GetTokenInformation(hToken, WinApi.Windows.TTokenInformationClass
        (TokenIntegrityLevel), pTIL, dwTokenUserLength, dwReturnLength) and
        IsValidSid((pTIL.Label_).Sid) then
      begin
        psaCount := GetSidSubAuthorityCount((pTIL.Label_).Sid);
        SubAuthority := psaCount^;
        SubAuthority := SubAuthority – 1;
        Result := GetSidSubAuthority((pTIL.Label_).Sid, SubAuthority)^;
      end;
      LocalFree(Cardinal(pTIL));
    End;
  end;

  CloseHandle(hToken);
end;

const
  userenvlib = ‘ userenv.dll ’;

function CreateEnvironmentBlock(lpEnvironment: PPointer; hToken: THandle;
  bInherit: BOOL): BOOL; stdcall; external userenvlib;
function DestroyEnvironmentBlock(lpEnvironment: Pointer): BOOL; stdcall;
  external userenvlib;

function CreateLowIntegrityProcess(const ExeName, Params: string;
  TimeOut: DWORD): HResult;
type
  _TOKEN_MANDATORY_LABEL = Record
    Label_: SID_AND_ATTRIBUTES;
  End;

  TOKEN_MANDATORY_LABEL = _TOKEN_MANDATORY_LABEL;
  PTOKEN_MANDATORY_LABEL = ^TOKEN_MANDATORY_LABEL;

const
  SECURITY_MANDATORY_LABEL_AUTHORITY: TSidIdentifierAuthority =
    (Value: (0, 0, 0, 0, 0, 16));
  SE_GROUP_INTEGRITY = $00000020;
  SE_GROUP_INTEGRITY_ENABLED = $00000040;
var
  hToken, hNewToken: THandle;
  MLAuthority: SID_IDENTIFIER_AUTHORITY;
  pIntegritySid: PSID;
  tml: TOKEN_MANDATORY_LABEL;
  si: TStartupInfo;
  pi: PROCESS_INFORMATION;
  pszCommandLine: string;
  dwCreationFlag: DWORD;
  pEnvironment: LPVOID;
begin

  Result := ERROR_SUCCESS;
  pszCommandLine := ExeName + Params;
  hToken := 0;
  hNewToken := 0;
  MLAuthority := SECURITY_MANDATORY_LABEL_AUTHORITY;
  pIntegritySid := nil;
  FillChar(tml, sizeof(tml), 0);
  FillChar(si, sizeof(si), 0);
  FillChar(pi, sizeof(pi), 0);

  si.cb := sizeof(si);
  si.lpDesktop := ‘ Winsta0 \ Default ’;
  dwCreationFlag := NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE;
  pEnvironment := nil;

  try
    // 从自己获取一个令牌
    if (not OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE or
      TOKEN_QUERY or TOKEN_ADJUST_DEFAULT or TOKEN_ASSIGN_PRIMARY, hToken)) then
    begin
      Result := GetLastError();
      Exit;
    end;

    // 复制令牌
    if (not DuplicateTokenEx(hToken, 0, nil, SecurityImpersonation,
      TokenPrimary, hNewToken)) then
    begin
      Result := GetLastError();
      Exit;
    end;

    // 创建一个低权限的SID
    if (not AllocateAndInitializeSid(MLAuthority, 1, SECURITY_MANDATORY_LOW_RID,
      0, 0, 0, 0, 0, 0, 0, pIntegritySid)) then
    begin
      Result := GetLastError();
      Exit;
    end;

    tml.Label_.Attributes := SE_GROUP_INTEGRITY;
    tml.Label_.Sid := pIntegritySid;

    // 设置这个低权限SID到令牌
    if (not SetTokenInformation(hNewToken, TokenIntegrityLevel, @tml,
      (sizeof(tml) + GetLengthSid(pIntegritySid)))) then
    begin
      Result := GetLastError();
      Exit;
    end;

    // 创建一个环境变量
    if (CreateEnvironmentBlock(@pEnvironment, hToken, FALSE)) then
      dwCreationFlag := dwCreationFlag or CREATE_UNICODE_ENVIRONMENT
    else
      pEnvironment := nil;

    // 创建一个低权限的进程
    if (not CreateProcessAsUser(hNewToken, nil, PChar(pszCommandLine), nil, nil,
      FALSE, dwCreationFlag, pEnvironment, nil, si, pi)) then
    begin
      Result := GetLastError();
      Exit;
    end;

    WaitForSingleObject(pi.hProcess, TimeOut);
  finally
    // 清理现场
    if pEnvironment <> nil then
    begin
      DestroyEnvironmentBlock(pEnvironment);
      pEnvironment := nil;
    end;

    if (hToken <> 0) then
    begin
      CloseHandle(hToken);
      hToken := 0;
    end;
    if (hNewToken <> 0) then
    begin
      CloseHandle(hNewToken);
      hNewToken := 0;
    end;
    if (pIntegritySid <> nil) then
    begin
      FreeSid(pIntegritySid);
      pIntegritySid := nil;
    end;
    if (pi.hProcess <> 0) then
    begin
      CloseHandle(pi.hProcess);
      pi.hProcess := 0;
    end;
    if (pi.hThread <> 0) then
    begin
      CloseHandle(pi.hThread);
      pi.hThread := 0;
    end;

    if (ERROR_SUCCESS <> Result) then
    begin
      SetLastError(Result);
    end
    else
    begin
      Result := ERROR_SUCCESS;
    end;
  end;
end;

  • 计划任务

        没错,还是它,创建计划任务默认不要求高权限时就可以当前用户权限启动指定程序

schtasks /Create /TN footask /SC DAILY /ST 01:00 /TR notepad.exe

        当然,创建、启动计划任务本身需要管理员权限。

像桌面一样启动进程

        

        如果当前进用户本身是低权限,但当前进程是高权限运行,需要新的子进程以当前用户身份启动,那我们就可以让 explorer 帮我们以它的上下文方式启动子进程,这里依然用 Delphi 代码演示:


uses
  ComObj, ShlObj, ActiveX, SHDocVw;


function IUnknown_QueryService(punk: IUnknown; const guidService: TGUID;
  const IID: TGUID; out Obj): HRESULT; stdcall;
  external 'ShLwApi' name 'IUnknown_QueryService';

procedure TForm1.Button1Click(Sender: TObject);
var
  hDesk: Integer;
  psw: IShellWindows;
  v, dummy: OleVariant;
  disp, pdispBackground, pdisp2: IDispatch;
  psb: IShellBrowser;
  psv: IShellView;
  psfvd: IShellFolderViewDual;
  psd: IShellDispatch2;
begin
  CoInitialize(nil);
  try
    OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER,
      IShellWindows, psw));
    disp := psw.FindWindowSW(v, dummy, SWC_DESKTOP, hDesk, SWFO_NEEDDISPATCH);
    OleCheck(IUnknown_QueryService(disp, SID_STopLevelBrowser,
      IID_IShellBrowser, psb));
    OleCheck(psb.QueryActiveShellView(psv));
    OleCheck(psv.GetItemObject(SVGIO_BACKGROUND, IDispatch,
      Pointer(pdispBackground)));
    OleCheck(pdispBackground.QueryInterface(IShellFolderViewDual, psfvd));
    OleCheck(psfvd.get_Application(pdisp2));
    OleCheck(pdisp2.QueryInterface(IShellDispatch2, psd));
    psd.ShellExecute('notepad.exe', 'C:\Windows\WindowsUpdate.log', EmptyParam,
      'open', SW_SHOWNORMAL)
  finally
    CoUninitialize;
  end;
end;

 C++ 版本示例:

#include <windows.h>
#include <shlwapi.h>
#include <shlobj.h>

#pragma comment(lib, "shlwapi.lib")

// use the shell view for the desktop using the shell windows automation to find the
// desktop web browser and then grabs its view
//
// returns:
//      IShellView, IFolderView and related interfaces

HRESULT GetShellViewForDesktop(REFIID riid, void **ppv)
{
    *ppv = NULL;

    IShellWindows *psw;
    HRESULT hr = CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw));
    if (SUCCEEDED(hr))
    {
        HWND hwnd;
        IDispatch* pdisp;
        VARIANT vEmpty = {}; // VT_EMPTY
        if (S_OK == psw->FindWindowSW(&vEmpty, &vEmpty, SWC_DESKTOP, (long*)&hwnd, SWFO_NEEDDISPATCH, &pdisp))
        {
            IShellBrowser *psb;
            hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb));
            if (SUCCEEDED(hr))
            {
                IShellView *psv;
                hr = psb->QueryActiveShellView(&psv);
                if (SUCCEEDED(hr))
                {
                    hr = psv->QueryInterface(riid, ppv);
                    psv->Release();
                }
                psb->Release();
            }
            pdisp->Release();
        }
        else
        {
            hr = E_FAIL;
        }
        psw->Release();
    }
    return hr;
}

// From a shell view object gets its automation interface and from that gets the shell
// application object that implements IShellDispatch2 and related interfaces.

HRESULT GetShellDispatchFromView(IShellView *psv, REFIID riid, void **ppv)
{
    *ppv = NULL;

    IDispatch *pdispBackground;
    HRESULT hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground));
    if (SUCCEEDED(hr))
    {
        IShellFolderViewDual *psfvd;
        hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd));
        if (SUCCEEDED(hr))
        {
            IDispatch *pdisp;
            hr = psfvd->get_Application(&pdisp);
            if (SUCCEEDED(hr))
            {
                hr = pdisp->QueryInterface(riid, ppv);
                pdisp->Release();
            }
            psfvd->Release();
        }
        pdispBackground->Release();
    }
    return hr;
}

HRESULT ShellExecInExplorerProcess(PCWSTR pszFile)
{
    IShellView *psv;
    HRESULT hr = GetShellViewForDesktop(IID_PPV_ARGS(&psv));
    if (SUCCEEDED(hr))
    {
        IShellDispatch2 *psd;
        hr = GetShellDispatchFromView(psv, IID_PPV_ARGS(&psd));
        if (SUCCEEDED(hr))
        {
            BSTR bstrFile = SysAllocString(pszFile);
            hr = bstrFile ? S_OK : E_OUTOFMEMORY;
            if (SUCCEEDED(hr))
            {
                VARIANT vtEmpty = {}; // VT_EMPTY
                hr = psd->ShellExecuteW(bstrFile, vtEmpty, vtEmpty, vtEmpty, vtEmpty);
                SysFreeString(bstrFile);
            }
            psd->Release();
        }
        psv->Release();
    }
    return hr;
}

int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        ShellExecInExplorerProcess(L"http://www.msn.com");
        CoUninitialize();
    }
    return 0;
}

        本篇内容介绍了几种提权、降权的方法,并不涉及到系统漏洞利用,抛砖引玉,希望对 Windows 进程权限方面感兴趣的朋友一起留言讨论,互相进步 🤝


参考

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

0x0007

可不可奖励我吃只毛嘴鸡 馋😋

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

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

打赏作者

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

抵扣说明:

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

余额充值