主机侧命令执行监测的规避
2024-12-12 14:11:0 Author: mp.weixin.qq.com(查看原文) 阅读量:2 收藏

像一些0day防御系统,当我们打出来whoami等等极具特征的命令时,会有一个基于主机侧命令执行检测,会出现告警,那我们就需要进行一些规避,用来像权限查询,判断主站上有什么东西,我们该如何进行信息收集

场景为当我们的C2上线后,或WebShell直接上线之后,我们可以用一些Windows的api进行信息收集

GetUserNameW:检索与当前线程关联的用户的名称

#include <Windows.h>
#include <stdio.h>
#include <Psapi.h>
#include <LM.h>    

int main{
        WCHAR userName[UNLEN +1];// 定义了一个宽字符数组用于存储用户名
        DWORD userNameLength = UNLEN +1;// UNLEN是一个预定义的宏,表示Windows用户名的最大长度,加1是为了存储字符串的终止符

if(GetUserNameW(userName,&userNameLength)){
// userNane是用来接收用户名的缓冲区指针 
// &userNameLength,是指向DWORD类型的指针,表示缓冲区的大小,并在成功后返回实际的用户名长度
            wprintf(L"User: %ls\\n", userName);// 如果为True则会获取当前用户的用户名,并输出
}
else{
            wprintf(L"[-] E: %d",GetLastError());// 如果为False则会打印错误信息
}
}

输出结果

这里我们用到了GetUserNameW这个Windows api(GetUserNameW是GetUserName的宽字符版本),宽字符版本使用 UTF-16 编码,适用于处理 Unicode 字符串。

这是函数定义

BOOL GetUserNameW(
  LPWSTR  lpBuffer,
  LPDWORD pcbBuffer
);

lpBuffer是一个LPWSTR类型的指向一个缓冲区的指针,用于接收当前用户的登录名。缓冲区的大小由pcbBuffer指定。

pcbBuffer是一个LPDWORD类型指向一个变量,该变量指定lpBuffer缓冲区的大小(以字符为单位)。

在函数返回时,该变量包含复制到缓冲区中的字符数,包括终止的空字符。如果缓冲区大小不够,函数会失败,并在此变量中返回所需的缓冲区大小。

返回值为:

True,lpBuffer包含当前用户的登录名

False,GetLastError函数获取更多的错误信息

这里我们可以深入,会涉及两个东西,关于beacon:

一个是cs-bof加载

一个是inline-execute

像我们一些高强的对抗的环境下,后续的信息收集一定是基于这种的情况下去进行,而不是打开cmd执行whoami这种

GetComputerNameExW:检索与本地计算机关联的 NetBIOS 或 DNS 名称。这些名称是在系统启动时建立的,即系统从注册表中读取它们

函数定义:

BOOL GetComputerNameExW(
  COMPUTER_NAME_FORMAT NameType,
  LPWSTR lpBuffer,
  LPDWORD nSize
);

NameType: 指定要获取的计算机名的格式,这个参数是一个COMPUTER_NAME_FORMAT枚举值 常用的枚举值包括:

  • • ComputerNameNetBIOS: 获取 NetBIOS 名称。

  • • ComputerNameDnsHostname: 获取 DNS 主机名。

  • • ComputerNameDnsDomain: 获取 DNS 域名。

  • • ComputerNameDnsFullyQualified: 获取 DNS 完全限定域名 (FQDN)。

  • • ComputerNamePhysicalNetBIOS: 获取物理 NetBIOS 名称。

  • • ComputerNamePhysicalDnsHostname: 获取物理 DNS 主机名。

  • • ComputerNamePhysicalDnsDomain: 获取物理 DNS 域名。

  • • ComputerNamePhysicalDnsFullyQualified: 获取物理 DNS 完全限定域名 (FQDN)。

lpBuffer: 指向一个缓冲区,用于接收计算机名称。

nSize: 指向缓冲区大小的指针。输入时为缓冲区的大小,以字符为单位;输出时为存储在缓冲区中的计算机名的字符数,包括终止的 null 字符。

返回值:

函数执行成功,返回 TRUE。

如果失败,返回 FALSE,可以使用 GetLastError 获取详细的错误信息

WCHAR hostName[UNLEN +1];
// 定义一个 WCHAR 数组 hostName 用于存储计算机的完全限定域名(FQDN)。
// UNLEN 是用户名的最大长度常量(256 字符),加上 1 是为了容纳终止的 null 字符。

DWORD hostNameLength = UNLEN +1;
// 定义一个 DWORD 类型的变量 hostNameLength,并将其初始化为 UNLEN + 1。
// 这个变量表示 hostName 数组的大小,以字符为单位。

if(GetComputerNameExW(ComputerNameDnsFullyQualified, hostName,&hostNameLength)){
    wprintf(L"Hostname: %ls\n", hostName);
}
// 调用 GetComputerNameExW 函数获取计算机的完全限定域名(FQDN)。
// 如果调用成功,输出计算机的主机名。

else{
    wprintf(L"[-] E: %d",GetLastError());
}
// 如果 GetComputerNameExW 调用失败,输出错误代码。

WCHAR dnshostName[UNLEN +1];
// 定义一个 WCHAR 数组 dnshostName 用于存储计算机的 DNS 域名。

DWORD DNShostNameLength= UNLEN +1;
// 定义一个 DWORD 类型的变量 DNShostNameLength,并将其初始化为 UNLEN + 1。
// 这个变量表示 dnshostName 数组的大小,以字符为单位。

if(GetComputerNameExW(ComputerNameDnsDomain, dnshostName,&DNShostNameLength)){
    wprintf(L"ComputerNameDnsDomain: %ls\n", dnshostName);
}
// 调用 GetComputerNameExW 函数获取计算机的 DNS 域名。
// 如果调用成功,输出 DNS 域名。

else{
    wprintf(L"[-] E: %d",GetLastError());
}
// 如果 GetComputerNameExW 调用失败,输出错误代码。

WCHAR CdnshostName[UNLEN +1];
// 定义一个 WCHAR 数组 CdnshostName 用于存储计算机的物理 DNS 域名。

DWORD CDNShostNameLength= UNLEN +1;
// 定义一个 DWORD 类型的变量 CDNShostNameLength,并将其初始化为 UNLEN + 1。
// 这个变量表示 CdnshostName 数组的大小,以字符为单位。

if(GetComputerNameExW(ComputerNamePhysicalDnsDomain,CdnshostName,&CDNShostNameLength)){
    wprintf(L"ComputerNamePhysicalDnsDomain: %ls\n",CdnshostName);
}
// 调用 GetComputerNameExW 函数获取计算机的物理 DNS 域名。
// 如果调用成功,输出物理 DNS 域名。

else{
    wprintf(L"[-] E: %d",GetLastError());
}
// 如果 GetComputerNameExW 调用失败,输出错误代码。

输出结果:

RtlGetVersion:返回有关当前正在运行的操作系统的版本信息

函数定义

NTSTATUS RtlGetVersion(
  PRTL_OSVERSIONINFOW lpVersionInformation
);

lpVersionInformation: 指向 RTL_OSVERSIONINFOW 结构的指针,该结构用于接收操作系统的版本信息

返回值

返回一个 NTSTATUS 类型的值,表示函数调用的结果。如果函数成功,返回的状态值通常为 STATUS_SUCCESS(即 0)。

结构

typedef struct _OSVERSIONINFOW {
  ULONG dwOSVersionInfoSize; // 结构的大小(以字节为单位)
  ULONG dwMajorVersion;      // 操作系统的主版本号
  ULONG dwMinorVersion;      // 操作系统的次版本号
  ULONG dwBuildNumber;       // 操作系统的构建编号
  ULONG dwPlatformId;        // 操作系统平台标识
  WCHAR szCSDVersion[128];   // 服务包信息
} RTL_OSVERSIONINFOW, *PRTL_OSVERSIONINFOW;
#include <Windows.h>
#include <stdio.h>
#include <Psapi.h>
#include <LM.h>

// 定义函数指针类型
typedef NTSYSAPI NTSTATUS(WINAPI* procRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation);

// 是将STATUS_SUCCESS作为0x00000000的一个替代符号,这样你可以在代码中使用STATUS_SUCCESS来表示操作成功
#define STATUS_SUCCESS 0x00000000

intmain(){
// 初始化一个 RTL_OSVERSIONINFOW 结构体,并将其所有成员设置为零
RTL_OSVERSIONINFOW osversioninfow ={0};

// 获取 ntdll.dll 模块的句柄,该模块包含了 Windows 的低级函数
HMODULE hntdll =GetModuleHandleA("ntdll.dll");

// 从 ntdll.dll 模块中获取 RtlGetVersion 函数的地址
procRtlGetVersion myRtlGetVersion =(procRtlGetVersion)GetProcAddress(hntdll,"RtlGetVersion");

// 如果成功检索并调用了 RtlGetVersion 函数
if(myRtlGetVersion(&osversioninfow)== STATUS_SUCCESS){
// 打印 Windows 操作系统的主版本号和次版本号
    wprintf(L"Windows Version: %lu.%lu\\n", osversioninfow.dwMajorVersion, osversioninfow.dwMinorVersion);

// 打印 Windows 操作系统的内部版本号
    wprintf(L"Windows Build: %lu\\n", osversioninfow.dwBuildNumber);
}
}

输出结果

像我们上升到xdr的对抗,走beacon内存加载,在beacon中就要完成脱钩的操作,如果没有做脱钩的话,我们就到更深一层的维度,更底层的调用。

virtualprotect为主,首先调用user32dll里面 转到kernel32dll 转到ntdll 最后通过syscall在内核中完成修改一块内存的保护属性。

GetCurrentDirectory :检索当前进程的当前目录

函数定义

DWORD GetCurrentDirectory(
  DWORD  nBufferLength,  // 指定缓冲区的大小,单位为字符
  LPTSTR lpBuffer        // 指向存储当前目录路径的缓冲区
);

nBufferLength:指定缓冲区 lpBuffer 的大小,单位是字符数。这个大小应该足够大以容纳当前目录的路径,包括终止符 '\0'。

lpBuffer: 指向一个字符数组(缓冲区),用于接收当前目录的绝对路径。

返回值:

如果函数成功,返回值是存储在缓冲区中的字符串的长度。

如果缓冲区大小不足以存储路径,函数将返回所需的缓冲区大小(包括终止符),并且不会将路径存入lpBuffer中。

如果函数调用失败,返回值为0。在这种情况下,可以调用 GetLastError 函数来获取详细的错误信息。

WCHAR currentDir[MAX_PATH +1];
// 定义一个 WCHAR 数组 currentDir 用于存储当前目录路径。
// MAX_PATH 是 Windows 系统中定义的最大路径长度(通常为 260 字符),加上 1 是为了容纳结尾的 null 字符。

DWORD currentDirLength = MAX_PATH +1;
// 定义一个 DWORD 类型的变量 currentDirLength,并将其初始化为 MAX_PATH + 1。
// 这个变量表示 currentDir 数组的大小,以字符为单位。

if(GetCurrentDirectoryW(currentDirLength, currentDir)!=0){
    wprintf(L"Pwd: %ls\\n", currentDir);
}
// 调用 GetCurrentDirectoryW 函数获取当前工作目录。
// 如果调用成功,输出当前目录路径。

else{
    wprintf(L"[-] E: %d",GetLastError());
}
// 如果 GetCurrentDirectoryW 调用失败,输出错误代码。

输出结果

GetTickCount64:检索自系统启动以来经过的毫秒数

来判断他到底会不会经常关机,如果动静不大,webshell独家 那就不需要维权

白加黑+测加载 白是在计划任务(很少的情况) --> 成本性问题 edr或xdr

常规的exe不要在有edr或xdr的服务器中进行启动

常规的exe进行维权 加启动项 计划任务 com劫持,但是我们不会有签名

侧重到webshell 隐匿性 打不打内存马

函数定义

ULONGLONG GetTickCount64(void);
这个函数不接受任何参数

返回值

返回一个 ULONGLONG 类型的值,该值表示自系统启动以来经过的时间(以毫秒为单位)

DWORD tickCount = (DWORD)GetTickCount64();
// 调用 GetTickCount64 函数获取系统启动后的运行时间(以毫秒为单位),并将其转换为 DWORD 类型。
// 注意:这里将 64 位的时间值强制转换为 32 位的 DWORD 类型,这在系统运行时间超过大约 49.7 天(即 32 位的最大值 4294967295 毫秒)后可能导致数据丢失。

DWORD dcuptime = ((tickCount / 1000) / 60);
// 将获取到的毫秒数除以 1000 转换为秒,再除以 60 转换为分钟。
// 结果是系统启动后的运行时间,以分钟为单位。

wprintf(L"Uptime: %lu minutes\\n", dcuptime);
// 使用 wprintf 函数输出系统启动后的运行时间,格式化为以分钟为单位的无符号长整型 (DWORD) 值。
// "%lu" 是用于无符号长整型的格式说明符。

输出结果

计算并输出系统的空闲时间 :

获取系统运行时间

DWORD systemUptime = GetTickCount64();

GetTickCount64 函数获取从系统启动以来的毫秒数。systemUptime 存储这个值,用来表示系统的总运行时间。

初始化变量

DWORD idleTicks = 0,
idletime = 0,
lastInputTicks = 0;

这里定义了三个 DWORD 类型的变量:

  • • idleTicks 用于存储系统空闲时间的毫秒数。

  • • idletime 用于存储计算后的空闲时间,以分钟为单位。

  • • lastInputTicks 用于存储最后一次输入操作的时间(毫秒)。

  • • 设置 LASTINPUTINFO 结构体

LASTINPUTINFO lastInputInfo;
lastInputInfo.cbSize = sizeof(lastInputInfo);
lastInputInfo.dwTime = 0;

LASTINPUTINFO 结构体用于存储最后一次输入信息

cbSize 成员设置为结构体的大小,以确保函数 GetLastInputInfo 正确读取数据

dwTime 初始化为0

获取最后一次输入信息

if (GetLastInputInfo(&lastInputInfo)) {
    lastInputTicks = lastInputInfo.dwTime;
    idleTicks = systemUptime - lastInputTicks;
}

GetLastInputInfo 函数填充 lastInputInfo 结构体。如果函数成功返回(即 if 语句中的条件为真),将 lastInputTicks 设置为最后一次输入的时间(从系统启动到最后一次输入的毫秒数)。然后计算空闲时间 idleTicks,即当前系统时间减去最后一次输入时间。

计算空闲时间(以分钟为单位)

idletime = ((idleTicks > 0) ? ((idleTicks / 1000) / 60) : 0);

这行代码首先检查 idleTicks 是否大于0。如果是,则将 idleTicks 从毫秒转换为秒(idleTicks / 1000),然后再转换为分钟(除以60)。如果 idleTicks 小于等于0,则将 idletime 设置为0。

输出空闲时间

wprintf(L"Idletime: %lu minutes\\n", idletime);

完整代码

#include <Windows.h>
#include <stdio.h>
#include <Psapi.h>
#include <LM.h>
typedef NTSYSAPI NTSTATUS(WINAPI* procRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation);
#define STATUS_SUCCESS 0x00000000

intmain(){
    DWORD tickCount =(DWORD)GetTickCount64();
    DWORD dcuptime =((tickCount /1000)/60);
    wprintf(L"Uptime: %lu minutes\n", dcuptime);

    DWORD systemUptime =GetTickCount64();
    DWORD idleTicks =0, idletime =0, lastInputTicks =0;

    LASTINPUTINFO lastInputInfo;
    lastInputInfo.cbSize =sizeof(lastInputInfo);
    lastInputInfo.dwTime =0;

if(GetLastInputInfo(&lastInputInfo)){
        lastInputTicks = lastInputInfo.dwTime;
        idleTicks = systemUptime - lastInputTicks;
}
    idletime =((idleTicks >0)?((idleTicks /1000)/60):0);

    wprintf(L"Idletime: %lu minutes\n", idletime);
    }

输出结果

我们为什么要看服务,是因为像CrowdStrike,每家公司买了之后进程名是不固定的,可以定制化,我们从进程名中不一定能看出来他是一个杀软,所以我们要看服务,有没有什么一些比较明显的服务名像monitor等等,或者复制出来到杀软识别中进行识别

打开服务控制管理器

SC_HANDLE hRemoteSvcManager = OpenSCManagerA(NULLNULL, SC_MANAGER_ENUMERATE_SERVICE);
  • • OpenSCManagerA 函数尝试以枚举服务的权限打开服务控制管理器。第一个NULL 表示操作本地计算机,第二个NULL 表示要打开的服务控制管理器数据库的名称

  • • 如果函数返回 NULL,说明打开服务控制管理器失败,进入错误处理部分。

  • • 检查打开服务控制管理器是否成功

if (hRemoteSvcManager == NULL) {
    wprintf(L"[-] E: %d", GetLastError());
}
  • • 如果 hRemoteSvcManager 为 NULL,调用 GetLastError 获取并输出错误码。

  • • 获取所需的缓冲区大小

DWORD BytesNeeded;
DWORD NumberOfServices;
DWORD ResumeHandleSize=0;
DWORD ServiceStructSize;
LPENUM_SERVICE_STATUSW lpServiceEnumStruct;

INT errVal =EnumServicesStatusW(hRemoteSvcManager, SERVICE_WIN32, SERVICE_STATE_ALL,NULL,0,&BytesNeeded,&NumberOfServices,&ResumeHandleSize);

if((errVal == ERROR_INVALID_HANDLE)||(errVal == ERROR_ACCESS_DENIED)||(errVal == ERROR_INVALID_PARAMETER)){
    wprintf(L"[-] E: %d",GetLastError());
}

函数调用

EnumServicesStatusW:这是一个 Windows API 函数,用于枚举服务控制管理器数据库中的服务。它将服务的信息存储在一个 ENUM_SERVICE_STATUSW 结构的数组中

函数定义

BOOL EnumServicesStatusW(
  SC_HANDLE                hSCManager,
  DWORD                    dwServiceType,
  DWORD                    dwServiceState,
  LPENUM_SERVICE_STATUSW   lpServices,
  DWORD                    cbBufSize,
  LPDWORD                  pcbBytesNeeded,
  LPDWORD                  lpServicesReturned,
  LPDWORD                  lpResumeHandle
);

hSCManager:

类型:SC_HANDLE

说明:服务控制管理器的句柄,可以通过 OpenSCManager 函数获得。这个句柄指定要枚举服务的服务控制管理器。

dwServiceType:

类型:DWORD

说明:指定要枚举的服务类型。可以是以下值的组合:

  • • SERVICE_WIN32_OWN_PROCESS: 枚举 Win32 服务,独立于其他服务进程运行。

  • • SERVICE_WIN32_SHARE_PROCESS: 枚举 Win32 服务,共享进程与其他服务运行。

  • • SERVICE_DRIVER: 枚举设备驱动程序服务。

  • • SERVICE_TYPE_ALL: 枚举所有服务。

dwServiceState:

类型:DWORD

说明:指定要枚举的服务状态。可以是以下值之一:

  • • SERVICE_ACTIVE: 枚举正在运行的服务。

  • • SERVICE_INACTIVE: 枚举未运行的服务。

  • • SERVICE_STATE_ALL: 枚举所有服务,不论它们是否正在运行。

lpServices:

类型:LPENUM_SERVICE_STATUSW

说明:指向一个缓冲区,该缓冲区接收 ENUM_SERVICE_STATUSW 结构数组,每个结构包含一个服务的名称和状态信息。如果缓冲区不足以存放所有数据,函数将失败,并通过 pcbBytesNeeded 返回所需的缓冲区大小。

cbBufSize:

类型:DWORD

说明:指定 lpServices 缓冲区的大小(以字节为单位)。

pcbBytesNeeded:

类型:LPDWORD

说明:指向一个变量,该变量接收 lpServices 所需的字节数。如果缓冲区太小,函数将返回 FALSE,并且该变量包含所需的缓冲区大小。

lpServicesReturned:

类型:LPDWORD

说明:指向一个变量,该变量接收枚举到的服务数量。

lpResumeHandle:

类型:LPDWORD

说明:指向一个变量,该变量包含从上次枚举未完成的地方继续的句柄。如果这是第一次调用该函数或不需要恢复句柄,应将 *lpResumeHandle 设为 0。

  • • hRemoteSvcManager:

    • • 传入之前通过 OpenSCManagerA 函数获取的服务控制管理器的句柄。

  • • SERVICE_WIN32:

    • • 表示枚举的服务类型,这里指定为 SERVICE_WIN32,意味着要枚举的服务包括 Win32 系统服务(但不包括设备驱动程序)

  • • SERVICE_STATE_ALL:

    • • 指定要枚举的服务状态,SERVICE_STATE_ALL 表示枚举所有状态的服务(包括运行、暂停、停止的服务)

  • • NULL 和 0:

    • • NULL 表示当前未分配缓冲区,0 表示缓冲区大小为 0。这是为了在第一次调用时获得所需的缓冲区大小

  • • &BytesNeeded:

    • • 传递一个指针,用于接收函数返回的实际所需缓冲区大小

  • • &NumberOfServices:

    • • 传递一个指针,用于接收函数返回的服务数量

  • • &ResumeHandleSize:

    • • 传递一个指针,用于接收恢复句柄(如果服务列表太长,需要分段处理)

报错处理

errVal

  • • 这是 EnumServicesStatusW 函数的返回值,用于检查调用是否成功。

  • • 如果返回值是 ERROR_INVALID_HANDLE(表示句柄无效)、ERROR_ACCESS_DENIED(表示访问被拒绝)或 ERROR_INVALID_PARAMETER(表示参数无效),则表示调用失败。

GetLastError()

  • • 如果函数失败,则调用 GetLastError() 函数获取具体的错误代码,并输出错误信息。

#include <Windows.h>
#include <stdio.h>
#include <Psapi.h>
#include <LM.h>
typedef NTSYSAPI NTSTATUS(WINAPI* procRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation);
#define STATUS_SUCCESS 0x00000000

intmain(){
    SC_HANDLE hRemoteSvcManager =OpenSCManagerA(NULL,NULL, SC_MANAGER_ENUMERATE_SERVICE);
if(hRemoteSvcManager ==NULL){
        wprintf(L"[-] E: %d",GetLastError());
}
else{
        DWORD BytesNeeded;
        DWORD NumberOfServices;
        DWORD ResumeHandleSize=0;
        DWORD ServiceStructSize;
        LPENUM_SERVICE_STATUSW lpServiceEnumStruct;

        INT errVal =EnumServicesStatusW(hRemoteSvcManager, SERVICE_WIN32, SERVICE_STATE_ALL,NULL,0,&BytesNeeded,&NumberOfServices,&ResumeHandleSize);
if((errVal == ERROR_INVALID_HANDLE)||(errVal == ERROR_ACCESS_DENIED)||(errVal == ERROR_INVALID_PARAMETER)){
            wprintf(L"[-] E: %d",GetLastError());
}
else{
ServiceStructSize=BytesNeeded+sizeof(ENUM_SERVICE_STATUSW);
            lpServiceEnumStruct =(LPENUM_SERVICE_STATUSW)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,ServiceStructSize*sizeof(WCHAR));
if(lpServiceEnumStruct ==NULL){
                wprintf(L"[-] E: %d",GetLastError());
}
else{
                errVal =EnumServicesStatusW(hRemoteSvcManager, SERVICE_WIN32, SERVICE_STATE_ALL, lpServiceEnumStruct,ServiceStructSize,&BytesNeeded,&NumberOfServices,&ResumeHandleSize);
if((errVal !=0)){
printf("\n\n[+] Enumerating Services [%lu]\n",NumberOfServices);
for(int i =0; i <(int)NumberOfServices; i++){
                        SC_HANDLE hSvc =OpenServiceW(hRemoteSvcManager, lpServiceEnumStruct<i>.lpServiceName, SERVICE_QUERY_CONFIG);
if(hSvc !=NULL){
                            LPQUERY_SERVICE_CONFIGW lpServiceConfig;
                            DWORD scbytesNeeded;
                            DWORD ScdwBytes;
QueryServiceConfigW(hSvc,NULL,0,&scbytesNeeded);
ScdwBytes= scbytesNeeded;
                            lpServiceConfig =(LPQUERY_SERVICE_CONFIGW)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,ScdwBytes*sizeof(WCHAR));
if(lpServiceConfig !=NULL){
                                errVal =QueryServiceConfigW(hSvc, lpServiceConfig,ScdwBytes,&scbytesNeeded);

if(errVal !=0){
                                    wprintf(L"[+] Display Name: %ls\n", lpServiceEnumStruct<i>.lpDisplayName);
                                    wprintf(L"[.] Service Name: %ls\n", lpServiceEnumStruct<i>.lpServiceName);
                                    wprintf(L"[.] Service User: %ls\n\n", lpServiceConfig->lpServiceStartName);
HeapFree(GetProcessHeap(),0, lpServiceConfig);
CloseServiceHandle(hSvc);
}
else{
                                    wprintf(L"[-] E: %d",GetLastError());
HeapFree(GetProcessHeap(),0, lpServiceConfig);
CloseServiceHandle(hSvc);
}
}
}
else{
                            wprintf(L"[-] E: %d",GetLastError());
}
}
}
else{
                    wprintf(L"[-] E: %d",GetLastError());
}
// 尤其在驱动编程中,堆一定要释放,否则就是蓝屏的问题
HeapFree(GetProcessHeap(),0, lpServiceEnumStruct);
}
}
}
}

  • • 根据之前获取的 BytesNeeded,计算出 ServiceStructSize,并使用 HeapAlloc 为服务状态结构体分配内存。

  • • 再次调用 EnumServicesStatusW 实际填充服务信息。

  • • 如果调用成功,遍历服务列表:

    • • 使用 OpenServiceW 打开每个服务,以获取其配置信息。

    • • 分配内存并调用 QueryServiceConfigW 获取服务的详细配置信息。

    • • 输出服务的显示名称、服务名称、当前状态、服务路径、用户和服务类型。

    • • 释放内存和关闭服务句柄。

  • • 如果任何步骤失败,输出错误信息。

输出结果

举一个概念

我们通过web打进去的,webshell入口点,或者反弹shell,或者cs上线,大多数我们都是以土豆系列做提权,因为大多数都是iis帐号,本地命令执行可能已经到system权限

如果是本地钓鱼上来的,个人层用UAC提权是比较多的,我们要考虑的是怎么把他们武器化到我们的cs中来

如果有CrowdStrike的环境,是不建议提权的,除非有很大的的把握,提权的行为不会被捕获到

还需要明确我们提却是为了什么,提权administrator,或者system权限,可能是为了挂驱动,而不是为了去dump hash,也就是对lsass的操作,尤其是在更高强度的对抗环境下,也不要去动lsass,在国内的话基本不用特别注意

国外的xdr,我们可以hook一些键盘记录 --> 令牌窃取 --> net use --> 这样来说和hash PTH 作用是一样的

关闭服务控制管理器句柄

CloseServiceHandle(hRemoteSvcManager);
  • • 使用 CloseServiceHandle 关闭之前打开的服务控制管理器句柄,释放相关资源

  1. 1. 打开进程令牌:获取当前进程的令牌,以便查询其权限。

  2. 2. 获取令牌信息:从令牌中获取权限信息。

  3. 3. 列出权限:遍历权限列表,并检查每个权限的状态。

#include <windows.h>
#include <stdio.h>

voidPrintPrivileges(){
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    DWORD dwSiz
    PTOKEN_PRIVILEGES pTokenPrivileges;
    BOOL result;

// 打开当前进程的令牌
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY,&hToken)){
        wprintf(L"[-] Failed to open process token. Error: %d\\n",GetLastError());
return;
}

// 获取令牌信息所需的缓冲区大小
if(!GetTokenInformation(hToken,TokenPrivileges,NULL,0,&dwSize)&&
GetLastError()== ERROR_INSUFFICIENT_BUFFER){
        pTokenPrivileges =(PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
if(pTokenPrivileges ==NULL){
            wprintf(L"[-] Failed to allocate memory for token information.\\n");
CloseHandle(hToken);
return;
}

// 获取令牌信息
if(GetTokenInformation(hToken,TokenPrivileges, pTokenPrivileges, dwSize,&dwSize)){
            wprintf(L"[+] Privileges:\\n");
for(DWORD i =0; i < pTokenPrivileges->PrivilegeCount; i++){
                LPWSTR privilegeName =NULL;
                DWORD nameSize =0;
                WCHAR privilegeNameBuffer[256];

// 获取权限名称
                nameSize =sizeof(privilegeNameBuffer)/sizeof(WCHAR);
if(LookupPrivilegeName(NULL,&pTokenPrivileges->Privileges<i>.Luid, privilegeNameBuffer,&nameSize)){
                    privilegeName = privilegeNameBuffer;
}else{
                    privilegeName =L"Unknown Privilege";
}

                wprintf(L"  - %s: %s\\n", privilegeName,(pTokenPrivileges->Privileges<i>.Attributes& SE_PRIVILEGE_ENABLED)?L"Enabled":L"Disabled");
}
}else{
            wprintf(L"[-] Failed to get token information. Error: %d\\n",GetLastError());
}

// 释放内存
HeapFree(GetProcessHeap(),0, pTokenPrivileges);
}else{
        wprintf(L"[-] Failed to get buffer size for token information. Error: %d\\n",GetLastError());
}

// 关闭令牌句柄
CloseHandle(hToken);
}

intmain(){
PrintPrivileges();
return0;
}

输出结果

OpenProcessToken用于获取指定进程的访问令牌

函数定义

BOOL OpenProcessToken(
  HANDLE  ProcessHandle,
  DWORD   DesiredAccess,
  PHANDLE TokenHandle
);

ProcessHandle:

类型:HANDLE

说明:指定要打开其访问令牌的进程句柄。可以使用 GetCurrentProcess 函数来获取当前进程的句柄,或使用 OpenProcess 获取其他进程的句柄。

DesiredAccess:

类型:DWORD

说明:指定所需的访问权限。常用的访问权限包括:

  • • TOKEN_ADJUST_PRIVILEGES:调整令牌中的权限。

  • • TOKEN_QUERY:查询令牌的权限。

  • • TOKEN_DUPLICATE:复制令牌。

  • • TOKEN_ASSIGN_PRIMARY:将令牌分配为一个进程的主令牌。

  • • TOKEN_IMPERSONATE:启用模拟权限。

  • • TOKEN_READ:读取令牌的所有标准访问权限。

  • • TOKEN_ALL_ACCESS:请求对令牌的完全访问权限。

TokenHandle:

类型:PHANDLE

说明:指向接收新打开的访问令牌句柄的指针。如果函数成功,TokenHandle 将包含一个有效的句柄,可以用于访问或修改令牌的属性。使用完句柄后,应调用 CloseHandle 释放该句柄。

打开进程令牌

if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
    wprintf(L"[-] Failed to open process token. Error: %d\\n", GetLastError());
    return;
}
  • • 使用 OpenProcessToken 函数打开当前进程的令牌,并请求 TOKEN_QUERY 权限以便查询令牌信息。

获取令牌信息所需的缓冲区大小

if (!GetTokenInformation(hToken, TokenPrivileges, NULL0, &dwSize) &&
    GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
    ...
}
  • • 使用 GetTokenInformation 函数获取缓冲区大小。第一次调用传入 NULL 来确定需要的缓冲区大小。

分配内存并获取令牌信息

pTokenPrivileges = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
if(pTokenPrivileges ==NULL){
...
}
if(GetTokenInformation(hToken,TokenPrivileges, pTokenPrivileges, dwSize,&dwSize)){
...
}
  • • 根据获取的大小分配内存,并再次调用 GetTokenInformation 来实际填充权限信息。

列出权限并输出

for (DWORD i = 0; i < pTokenPrivileges->PrivilegeCount; i++) {
    ...
    wprintf(L"  - %s: %s\\n", privilegeName, (pTokenPrivileges->Privileges<i>.Attributes & SE_PRIVILEGE_ENABLED) ? L"Enabled" : L"Disabled");
}
  • • 遍历所有权限,使用 LookupPrivilegeName 获取每个权限的名称,并检查权限是否启用。

释放内存并关闭句柄

HeapFree(GetProcessHeap(), 0, pTokenPrivileges);
CloseHandle(hToken);
  • • 释放分配的内存,并关闭令牌句柄以释放系统资源

#include <Windows.h>
#include <stdio.h>
#include <Psapi.h>
#include <LM.h>

typedef NTSYSAPI NTSTATUS(WINAPI* procRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation);

#define STATUS_SUCCESS 0x00000000

voidPrintPrivileges(){
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    DWORD dwSize;
    PTOKEN_PRIVILEGES pTokenPrivileges;
    BOOL result;

// 打开当前进程的令牌
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY,&hToken)){
        wprintf(L"[-] Failed to open process token. Error: %d\n",GetLastError());
return;
}

// 获取令牌信息所需的缓冲区大小
if(!GetTokenInformation(hToken,TokenPrivileges,NULL,0,&dwSize)&&
GetLastError()== ERROR_INSUFFICIENT_BUFFER){
        pTokenPrivileges =(PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
if(pTokenPrivileges ==NULL){
            wprintf(L"[-] Failed to allocate memory for token information.\n");
CloseHandle(hToken);
return;
}

// 获取令牌信息
if(GetTokenInformation(hToken,TokenPrivileges, pTokenPrivileges, dwSize,&dwSize)){
            wprintf(L"[+] Privileges:\n");
for(DWORD i =0; i < pTokenPrivileges->PrivilegeCount; i++){
                LPWSTR privilegeName =NULL;
                DWORD nameSize =0;
                WCHAR privilegeNameBuffer[256];

// 获取权限名称
                nameSize =sizeof(privilegeNameBuffer)/sizeof(WCHAR);
if(LookupPrivilegeName(NULL,&pTokenPrivileges->Privileges<i>.Luid, privilegeNameBuffer,&nameSize)){
                    privilegeName = privilegeNameBuffer;
}
else{
                    privilegeName =L"Unknown Privilege";
}

                wprintf(L"  - %s: %s\n", privilegeName,(pTokenPrivileges->Privileges<i>.Attributes& SE_PRIVILEGE_ENABLED)?L"Enabled":L"Disabled");
}
}
else{
            wprintf(L"[-] Failed to get token information. Error: %d\n",GetLastError());
}

// 释放内存
HeapFree(GetProcessHeap(),0, pTokenPrivileges);
}
else{
        wprintf(L"[-] Failed to get buffer size for token information. Error: %d\n",GetLastError());
}

// 关闭令牌句柄
CloseHandle(hToken);
}

intmain(){

    WCHAR userName[UNLEN +1];
    DWORD userNameLength = UNLEN +1;

if(GetUserNameW(userName,&userNameLength)){
        wprintf(L"User: %ls\n", userName);
}
else{
        wprintf(L"[-] E: %d",GetLastError());
}
    WCHAR hostName[UNLEN +1];
    DWORD hostNameLength = UNLEN +1;
if(GetComputerNameExW(ComputerNameDnsFullyQualified, hostName,&hostNameLength)){
        wprintf(L"Hostname: %ls\n", hostName);
}
else{
        wprintf(L"[-] E: %d",GetLastError());
}

    WCHAR dnshostName[UNLEN +1];
    DWORD DNShostNameLength= UNLEN +1;
if(GetComputerNameExW(ComputerNameDnsDomain, dnshostName,&DNShostNameLength)){
        wprintf(L"ComputerNameDnsDomain: %ls\n", dnshostName);
}
else{
        wprintf(L"[-] E: %d",GetLastError());
}

    WCHAR CdnshostName[UNLEN +1];
    DWORD CDNShostNameLength= UNLEN +1;
if(GetComputerNameExW(ComputerNamePhysicalDnsDomain,CdnshostName,&CDNShostNameLength)){
        wprintf(L"ComputerNamePhysicalDnsDomain: %ls\n",CdnshostName);
}
else{
        wprintf(L"[-] E: %d",GetLastError());
}

    RTL_OSVERSIONINFOW osversioninfow ={0};
    HMODULE hntdll =GetModuleHandleA("ntdll.dll");
    procRtlGetVersion myRtlGetVersion =(procRtlGetVersion)GetProcAddress(hntdll,"RtlGetVersion");
if(myRtlGetVersion(&osversioninfow)== STATUS_SUCCESS){
        wprintf(L"Windows Version: %lu.%lu\n", osversioninfow.dwMajorVersion, osversioninfow.dwMinorVersion);
        wprintf(L"Windows Build: %lu\n", osversioninfow.dwBuildNumber);
}
    WCHAR currentDir[MAX_PATH +1];
    DWORD currentDirLength = MAX_PATH +1;

if(GetCurrentDirectoryW(currentDirLength, currentDir)!=0){
        wprintf(L"Pwd: %ls\n", currentDir);
}
else{
        wprintf(L"[-] E: %d",GetLastError());
}

    DWORD tickCount =(DWORD)GetTickCount64();
    DWORD dcuptime =((tickCount /1000)/60);
    wprintf(L"Uptime: %lu minutes\n", dcuptime);

    DWORD systemUptime =GetTickCount64();
    DWORD idleTicks =0, idletime =0, lastInputTicks =0;

    LASTINPUTINFO lastInputInfo;
    lastInputInfo.cbSize =sizeof(lastInputInfo);
    lastInputInfo.dwTime =0;

if(GetLastInputInfo(&lastInputInfo)){
        lastInputTicks = lastInputInfo.dwTime;
        idleTicks = systemUptime - lastInputTicks;
}
    idletime =((idleTicks >0)?((idleTicks /1000)/60):0);

    wprintf(L"Idletime: %lu minutes\n", idletime);
    SC_HANDLE hRemoteSvcManager =OpenSCManagerA(NULL,NULL, SC_MANAGER_ENUMERATE_SERVICE);
if(hRemoteSvcManager ==NULL){
        wprintf(L"[-] E: %d",GetLastError());
}
else{
        DWORD BytesNeeded;
        DWORD NumberOfServices;
        DWORD ResumeHandleSize=0;
        DWORD ServiceStructSize;
        LPENUM_SERVICE_STATUSW lpServiceEnumStruct;

        INT errVal =EnumServicesStatusW(hRemoteSvcManager, SERVICE_WIN32, SERVICE_STATE_ALL,NULL,0,&BytesNeeded,&NumberOfServices,&ResumeHandleSize);
if((errVal == ERROR_INVALID_HANDLE)||(errVal == ERROR_ACCESS_DENIED)||(errVal == ERROR_INVALID_PARAMETER)){
            wprintf(L"[-] E: %d",GetLastError());
}
else{
ServiceStructSize=BytesNeeded+sizeof(ENUM_SERVICE_STATUSW);
            lpServiceEnumStruct =(LPENUM_SERVICE_STATUSW)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,ServiceStructSize*sizeof(WCHAR));
if(lpServiceEnumStruct ==NULL){
                wprintf(L"[-] E: %d",GetLastError());
}
else{
                errVal =EnumServicesStatusW(hRemoteSvcManager, SERVICE_WIN32, SERVICE_STATE_ALL, lpServiceEnumStruct,ServiceStructSize,&BytesNeeded,&NumberOfServices,&ResumeHandleSize);
if((errVal !=0)){
printf("\n\n[+] Enumerating Services [%lu]\n",NumberOfServices);
for(int i =0; i <(int)NumberOfServices; i++){
                        SC_HANDLE hSvc =OpenServiceW(hRemoteSvcManager, lpServiceEnumStruct<i>.lpServiceName, SERVICE_QUERY_CONFIG);
if(hSvc !=NULL){
                            LPQUERY_SERVICE_CONFIGW lpServiceConfig;
                            DWORD scbytesNeeded;
                            DWORD ScdwBytes;
QueryServiceConfigW(hSvc,NULL,0,&scbytesNeeded);
ScdwBytes= scbytesNeeded;
                            lpServiceConfig =(LPQUERY_SERVICE_CONFIGW)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,ScdwBytes*sizeof(WCHAR));
if(lpServiceConfig !=NULL){
                                errVal =QueryServiceConfigW(hSvc, lpServiceConfig,ScdwBytes,&scbytesNeeded);

if(errVal !=0){
                                    wprintf(L"[+] Display Name: %ls\n", lpServiceEnumStruct<i>.lpDisplayName);
                                    wprintf(L"[.] Service Name: %ls\n", lpServiceEnumStruct<i>.lpServiceName);
                                    wprintf(L"[.] Service User: %ls\n\n", lpServiceConfig->lpServiceStartName);
HeapFree(GetProcessHeap(),0, lpServiceConfig);
CloseServiceHandle(hSvc);
}
else{
                                    wprintf(L"[-] E: %d",GetLastError());
HeapFree(GetProcessHeap(),0, lpServiceConfig);
CloseServiceHandle(hSvc);
}
}
}
else{
                            wprintf(L"[-] E: %d",GetLastError());
}
}
}
else{
                    wprintf(L"[-] E: %d",GetLastError());
}
HeapFree(GetProcessHeap(),0, lpServiceEnumStruct);
}
}
}
CloseServiceHandle(hRemoteSvcManager);
PrintPrivileges();

return0;
}

输出结果

我们在beacon端

加载到beacon的内存中

inline-execute c:\user\27682\Project1.exe

cs-bof加载

inline-execute Project.obj 需要按照cs的标准开发

分叉和运行:fork&run

bof:可以理解为在beacon内部执行的一个插件,用来替代cs的分叉运行

木马:a.exe

我们在beacon端中输入 beacon>shell whoami

调用链如下: a.exe -> cmd.exe -> whoami.exe(传统的分叉运行进程)

我们使用 举例:InlineExecute-Assembly 去执行 GetUserNameW

我们在beacon端 beacon>InlineExecute-Assembly C:\user\27682\Project1.exe

调用链如下: a.exe 内部去执行了Project1.exe,这样就不会有多余的进程,且在进程调用链是看不见Project1.exe的

需要注意的是,因为在beacon内部执行,像万一有一些逻辑方面的问题,卡死在那里,也会导致我们的beacon会话死掉

cs的bof开发很繁琐,很多函数用不了,且需要更改为cs支持的格式,所以更推荐使用inline的方式进行执行,这个GitHub项目也比较老了,可以进行一些修改再继续使用

这个项目中涉及到了AMSI反病毒扫描接口 默认在df环境下 现在的edr/xdr是集成了AMSI的功能

会检测powershell的执行,如果可以进行绕过AMSI就可以任意执行powershell

ETW就是Windows事件记录,也可以对其进行hook

原文连接

https://www.t00ls.com/articles-72415.html


文章来源: https://mp.weixin.qq.com/s?__biz=Mzg3NzYzODU5NQ==&mid=2247484894&idx=1&sn=513ddea2f76c9a5bc7e6b7e6986b9c25&chksm=cf1ea372f8692a649a82ba31296a0367a3ed3cbe91863b71f8d217351f73503a214f6d1fe09e&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh