规避检测(共五章):第五章

2023-11-20 12:50:14 浏览数 (1)

一、基于时间的沙盒规避技术

沙盒模拟通常持续很短的时间,因为沙盒加载了数千个样本。仿真 时间很少超过3-5分钟。因此,恶意软件可以利用这一事实来避免检测:它可能会执行 在开始任何恶意活动之前长时间延迟。

为了抵消这种情况,沙盒可以实现操纵时间和执行延迟的功能。沙箱具有睡眠跳过功能,可将延迟替换为非常短的值。这应该强制恶意软件启动 它在分析超时之前的恶意活动。

但是,这也可用于检测沙盒。

一些指令和API函数的执行时间也存在一些差异, 可用于检测虚拟环境。

1.延迟执行

执行延迟用于避免在模拟期间检测到恶意活动。

1.1简单的延迟操作

代码语言:javascript复制
int iResult;
DWORD timeout = delay;
DWORD OK = TRUE;

SOCKADDR_IN sa = { 0 };
SOCKET sock = INVALID_SOCKET;


do {
    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr("8.8.8.8");    
    sa.sin_port = htons(80);

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET) {
        OK = FALSE;
        break;
    }

    // setting socket timeout
    unsigned long iMode = 1;
    iResult = ioctlsocket(sock, FIONBIO, &iMode);

    iResult = connect(sock, (SOCKADDR*)&sa, sizeof(sa));
    if (iResult == false) {
        OK = FALSE;
        break;
    }

    iMode = 0;
    iResult = ioctlsocket(sock, FIONBIO, &iMode);
    if (iResult != NO_ERROR) {
        OK = FALSE;
        break;
    }

    // fd set data
    fd_set Write, Err;
    FD_ZERO(&Write);
    FD_ZERO(&Err);
    FD_SET(sock, &Write);
    FD_SET(sock, &Err);
    timeval tv = { 0 };
    tv.tv_usec = timeout * 1000;

    // 检查套接字是否准备就绪,此调用应占用超时毫秒
    select(0, NULL, &Write, &Err, &tv);
    
    if (FD_ISSET(sock, &Err)) {
        OK = FALSE;
        break;
    }

} while (false);

if (sock != INVALID_SOCKET)
    closesocket(sock);
代码语言:javascript复制
VOID CALLBACK TimerFunction(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
    bProcessed = TRUE;
}

VOID timing_timeSetEvent(UINT delayInSeconds)
{
    
    UINT uResolution;
    TIMECAPS tc;
    MMRESULT idEvent;

   
    timeGetDevCaps(&tc, sizeof(TIMECAPS));
    uResolution = min(max(tc.wPeriodMin, 0), tc.wPeriodMax);

    
    idEvent = timeSetEvent(
        delayInSeconds,
        uResolution,
        TimerFunction,
        0,
        TIME_ONESHOT);

    while (!bProcessed){
        
        Sleep(0);
    }

    
    timeKillEvent(idEvent);

    
    timeEndPeriod(uResolution);
}

1.2 使用任务调度程序延迟执行

此方法既可用于延迟执行,也可用于逃避沙盒跟踪:

代码语言:javascript复制
$tm = (get-date).AddMinutes(10).ToString("HH:mm")
$action = New-ScheduledTaskAction -Execute "some_malicious_app.exe"
$trigger = New-ScheduledTaskTrigger -Once -At $tm
Register-ScheduledTask TaskName -Action $action -Trigger $trigger

1.3 仅在特定日期运行

恶意软件样本可能会检查当前日期,并仅在特定日期执行恶意操作。例如 这种技术被用于Sazoora恶意软件, 检查当前日期并验证该日期是 16 日、17 日还是 18 日 给定月份。

2.睡眠跳过检测

这种类型的技术通常针对监视器睡眠跳过功能和其他时间操纵 可在沙盒中使用的技术,以跳过恶意软件执行的长时间延迟。

2.1 使用不同方法的并行延迟

这些技术背后的想法是并行执行不同类型的延迟并测量经过的时间。

代码语言:javascript复制
DWORD StartingTick, TimeElapsedMs;
LARGE_INTEGER DueTime;
HANDLE hTimer = NULL;
TIMER_BASIC_INFORMATION TimerInformation;
ULONG ReturnLength;

hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
DueTime.QuadPart = Timeout * (-10000LL);

StartingTick = GetTickCount();
SetWaitableTimer(hTimer, &DueTime, 0, NULL, NULL, 0);
do
{
    Sleep(Timeout/10);
    NtQueryTimer(hTimer, TimerBasicInformation, &TimerInformation, sizeof(TIMER_BASIC_INFORMATION), &ReturnLength);
} while (!TimerInformation.TimerState);

CloseHandle(hTimer);

TimeElapsedMs = GetTickCount() - StartingTick;
printf("Requested delay: %d, elapsed time: %dn", Timeout, TimeElapsedMs);

if (abs((LONG)(TimeElapsedMs - Timeout)) > Timeout / 2)
    printf("Sleep-skipping DETECTED!n");

2.2 使用不同的方法测量时间间隔

我们需要执行将在沙盒中跳过的延迟,并使用不同的方法测量经过的时间。 而 Cuckoo 监视器则钩住了 GetTickCount()、GetLocalTime()、GetSystemTime() 和 让他们返回跳过的时间,我们仍然可以找到没有处理的时间测量方法监控:

代码语言:javascript复制
LARGE_INTEGER StartingTime, EndingTime;
LARGE_INTEGER Frequency;
DWORD TimeElapsedMs;

QueryPerformanceFrequency(&Frequency);
QueryPerformanceCounter(&StartingTime);

Sleep(Timeout);

QueryPerformanceCounter(&EndingTime);
TimeElapsedMs = (DWORD)(1000ll * (EndingTime.QuadPart - StartingTime.QuadPart) / Frequency.QuadPart);

printf("Requested delay: %d, elapsed time: %dn", Timeout, TimeElapsedMs);

if (abs((LONG)(TimeElapsedMs - Timeout)) > Timeout / 2)
    printf("Sleep-skipping DETECTED!n");
代码语言:javascript复制
ULONGLONG tick;
DWORD TimeElapsedMs;

tick = GetTickCount64();
Sleep(Timeout);
TimeElapsedMs = GetTickCount64() - tick;

printf("Requested delay: %d, elapsed time: %dn", Timeout, TimeElapsedMs);

if (abs((LONG)(TimeElapsedMs - Timeout)) > Timeout / 2)
    printf("Sleep-skipping DETECTED!n");

我们还可以使用我们自己的GetTickCount实现来检测睡眠跳过。在下一个代码示例中,我们将直接从 KUSER_SHARED_DATA 结构获取即时报价计数。这样,即使 GetTickCount()函数被挂接,我们也可以获得原始的即时报价计数值:

代码语言:javascript复制
#define KI_USER_SHARED_DATA         0x7FFE0000
#define SharedUserData  ((KUSER_SHARED_DATA * const) KI_USER_SHARED_DATA)
#define MyGetTickCount() ((DWORD)((SharedUserData->TickCountMultiplier * (ULONGLONG)SharedUserData->TickCount.LowPart) >> 24))

// ...
StartingTick = MyGetTickCount();
Sleep(Timeout);
TimeElapsedMs = MyGetTickCount() - StartingTick;

printf("Requested delay: %d, elapsed time: %dn", Timeout, TimeElapsedMs);

if (abs((LONG)(TimeElapsedMs - Timeout)) > Timeout / 2)
    printf("Sleep-skipping DETECTED!n");

2.3 使用不同的方法获取系统时间

此方法与前一种方法类似。我们尝试获取当前系统,而不是测量间隔,使用不同方法的时间:

代码语言:javascript复制
SYSTEM_TIME_OF_DAY_INFORMATION  SysTimeInfo;
ULONGLONG time;
LONGLONG diff;

Sleep(60000);
GetSystemTimeAsFileTime((LPFILETIME)&time);

NtQuerySystemInformation(SystemTimeOfDayInformation, &SysTimeInfo, sizeof(SysTimeInfo), 0);
diff = time - SysTimeInfo.CurrentTime.QuadPart;
if (abs(diff) > 10000000)
    printf("Sleep-skipping DETECTED!n);

2.4 调用延时函数后检查延时值是否发生变化

睡眠跳过通常以较小的间隔替换延迟值来实现。 让我们看一下 NtDelayExecution 函数。延迟值使用指针传递给此函数:

代码语言:javascript复制
NTSYSAPI NTSTATUS NTAPI
NtDelayExecution(
    IN BOOLEAN              Alertable,
    IN PLARGE_INTEGER       DelayInterval );

因此,我们可以检查函数执行后延迟间隔的值是否发生变化。 如果该值与初始值不同,则跳过延迟。

代码语言:javascript复制
LONGLONG SavedTimeout = Timeout * (-10000LL);
DelayInterval->QuadPart = SavedTimeout;
status = NtDelayExecution(TRUE, DelayInterval);
if (DelayInterval->QuadPart != SavedTimeout)
    printf("Sleep-skipping DETECTED!n");

2.5 使用绝对超时

对于执行延迟的 Nt-函数,我们可以使用相对延迟间隔或绝对超时时间。延迟间隔的负值表示相对超时,正值表示绝对超时。高级 API 函数(如 WaitForSingleObject()或 Sleep())以相对间隔运行。因此,沙盒开发人员可能不关心绝对超时并错误地处理它们。在沙盒中,这种延迟被跳过,但跳过的时间和刻度被错误地计算。这可以使用检测睡眠跳过。

代码语言:javascript复制
void SleepAbs(DWORD ms)
{
    LARGE_INTEGER SleepUntil;

    GetSystemTimeAsFileTime((LPFILETIME)&SleepUntil);
    SleepTo.QuadPart  = (ms * 10000);
    NtDelayExecution(TRUE, &SleepTo);
}

2.6 从另一个进程中获取时间

沙盒中的睡眠跳过不是系统范围的。因此,如果存在执行延迟,时间就会移动 在不同的过程中具有不同的速度。延迟后,我们应该同步进程并进行比较 两个进程中的当前时间。测量时间值的巨大差异表明进行了睡眠跳过。

3.虚拟机和主机中的时间测量差异

某些 API 函数和指令的执行在 VM 和通常的 主机系统。这些特性可用于检测虚拟环境。

3.1 RDTSC(使用 CPUID 强制虚拟机退出)

代码语言:javascript复制
BOOL rdtsc_diff_vmexit()
{
    ULONGLONG tsc1 = 0;
    ULONGLONG tsc2 = 0;
    ULONGLONG avg = 0;
    INT cpuInfo[4] = {};

    //浅试10秒
    for (INT i = 0; i < 10; i  )
    {
        tsc1 = __rdtsc();
        __cpuid(cpuInfo, 0);
        tsc2 = __rdtsc();

        // Get the delta of the two RDTSC
        avg  = (tsc2 - tsc1);
    }

    //我们重复了这个过程10次,以确保我们的检查尽可能可靠
    avg = avg / 10;
    return (avg < 1000 && avg > 0) ? FALSE : TRUE;
}

3.2 RDTSC(带有 GetProcessHeap 和 CloseHandle 的锁定版本)

代码语言:javascript复制
#define LODWORD(_qw)    ((DWORD)(_qw))
BOOL rdtsc_diff_locky()
{
    ULONGLONG tsc1;
    ULONGLONG tsc2;
    ULONGLONG tsc3;
    DWORD i = 0;

   
    for (i = 0; i < 10; i  )
    {
        tsc1 = __rdtsc();

        
        GetProcessHeap();

        tsc2 = __rdtsc();

        
        CloseHandle(0);

        tsc3 = __rdtsc();

    
        if ((LODWORD(tsc3) - LODWORD(tsc2)) / (LODWORD(tsc2) - LODWORD(tsc1)) >= 10)
            return FALSE;
    }

    
    return TRUE;
}

4.使用不同的方法检查系统上次启动时间

此技术是通用操作系统查询:检查系统正常运行时间是否短和 WMI:检查上次启动时间部分中所述的技术的组合。根据用于获取系统上次启动时间的方法,测量的沙盒操作系统正常运行时间也可能 小(几分钟),或者相反,太大(几个月甚至几年),因为系统通常会恢复 从分析开始后的快照。

我们可以通过比较上次启动时间的两个值来检测沙箱,这两个值是通过 WMI 和 NtQuerySystemInformation(SystemTimeOfDayInformation 获取的:

代码语言:javascript复制
bool check_last_boot_time()
{
    SYSTEM_TIME_OF_DAY_INFORMATION  SysTimeInfo;
    LARGE_INTEGER LastBootTime;
    
    NtQuerySystemInformation(SystemTimeOfDayInformation, &SysTimeInfo, sizeof(SysTimeInfo), 0);
    LastBootTime = wmi_Get_LastBootTime();
    return (wmi_LastBootTime.QuadPart - SysTimeInfo.BootTime.QuadPart) / 10000000 != 0; // 0 seconds
}

5.使用无效参数调用可能挂钩的延迟函数

NtDelayExecution 函数的第二个参数是指向延迟间隔值的指针。在内核模式下, NtDelayExecution 函数验证此指针,还可以返回以下值:

  • STATUS_ACCESS_VIOLATION - 如果该值不是有效的用户模式地址
  • STATUS_DATATYPE_MISALIGNMENT - 如果地址未对齐(DelayInterval & 3 != 0)

在沙盒中,可能无法正确处理 NtDelayExecution 和类似函数的输入参数。如果我们使用 DelayInterval 的未对齐指针调用 NtDelayExecution,通常它会返回STATUS_DATATYPE_MISALIGNMENT。但是,在沙盒中,延迟间隔的值可能会复制到新变量,没有适当的检查。在这种情况下,将执行延迟,返回值将被STATUS_SUCCESS。这可用于检测沙盒。

代码语言:javascript复制
__declspec(align(4)) BYTE aligned_bytes[sizeof(LARGE_INTEGER) * 2];
DWORD tick_start, time_elapsed_ms;
DWORD Timeout = 10000; //10 seconds
PLARGE_INTEGER DelayInterval = (PLARGE_INTEGER)(aligned_bytes   1); //unaligned
NTSTATUS status;

DelayInterval->QuadPart = Timeout * (-10000LL);
tick_start = GetTickCount();
status = NtDelayExecution(FALSE, DelayInterval);
time_elapsed_ms = GetTickCount() - tick_start;
// 如果指针未对齐,则不应执行延迟
if (time_elapsed_ms > 500 || status != STATUS_DATATYPE_MISALIGNMENT )
    printf("Sandbox detectedn");

另一方面,如果为延迟间隔设置了无法访问的地址,则应STATUS_ACCESS_VIOLATION返回代码。这也可用于检测沙盒。

代码语言:javascript复制
if (NtDelayExecution(FALSE, (PLARGE_INTEGER)0) != STATUS_ACCESS_VIOLATION)
    printf("Sandbox detected");

二、WMI 检测方法

Windows 管理界面 (WMI) 查询是获取操作系统和硬件信息的另一种方法。WMI 使用 COM 接口及其方法。

标准 COM 函数用于处理查询。它们按下面描述的顺序调用,可以分为 6 个步骤。

1. COM初始化:

  • CoInitialize/CoInitializeEx

2. 创建所需的接口实例:

  • CoCreateInstance/CoCreateInstanceEx

3. 通过具有以下功能的接口实例连接到特定服务:

  • ConnectServer

4. 获取服务的方法并使用以下函数设置它们的参数:

  • Method (获取方法)
  • Put (设置参数)

5. 从服务中检索信息,并使用以下功能执行服务的方法。左边的函数是右边函数的代理 - 在内部调用:

  • ExecQuery -> IWbemServices_ExecQuery (检索信息)
  • ExecMethod -> IWbemServices_ExecMethod (执行方法)

ExecMethodAsync -> IWbemServices_ExecMethodAsync (execute method)

6. 使用以下函数检查查询结果:

  • [enumerator]->Next
  • [object]->Get

1.通用 WMI 查询

由于 WMI 提供了另一种收集系统信息的方法,因此它可用于执行其他文章中描述的规避技术:

BOOL number_cores_wmi() { IWbemServices *pSvc = NULL; IWbemLocator *pLoc = NULL; IEnumWbemClassObject *pEnumerator = NULL; BOOL bStatus = FALSE; HRESULT hRes; BOOL bFound = FALSE; // Init WMI bStatus = InitWMI(&pSvc, &pLoc); if (bStatus) { // 如果成功,则执行所需的查询 bStatus = ExecWMIQuery(&pSvc, &pLoc, &pEnumerator, _T("SELECT * FROM Win32_Processor")); if (bStatus) { // 从查询中获取数据 IWbemClassObject *pclsObj = NULL; ULONG uReturn = 0; VARIANT vtProp; // 迭代我们的枚举器 while (pEnumerator) { hRes = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if (0 == uReturn) break; // 获取 Name 属性的值 hRes = pclsObj->Get(_T("NumberOfCores"), 0, &vtProp, 0, 0); if (V_VT(&vtProp) != VT_NULL) { // 做我们的比较 if (vtProp.uintVal < 2) { bFound = TRUE; break; } // 释放当前结果对象 VariantClear(&vtProp); pclsObj->Release(); } } // Cleanup pEnumerator->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); } } return bFound; } BOOL disk_size_wmi() { IWbemServices *pSvc = NULL; IWbemLocator *pLoc = NULL; IEnumWbemClassObject *pEnumerator = NULL; BOOL bStatus = FALSE; HRESULT hRes; BOOL bFound = FALSE; INT64 minHardDiskSize = (80LL * (1024LL * (1024LL * (1024LL)))); // Init WMI bStatus = InitWMI(&pSvc, &pLoc); if (bStatus) { // 如果成功,则执行所需的查询 bStatus = ExecWMIQuery(&pSvc, &pLoc, &pEnumerator, _T("SELECT * FROM Win32_LogicalDisk")); if (bStatus) { // Get the data from the query IWbemClassObject *pclsObj = NULL; ULONG uReturn = 0; VARIANT vtProp; // 迭代我们的枚举器 while (pEnumerator) { hRes = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if (0 == uReturn) break; // 获取 Name 属性的值 hRes = pclsObj->Get(_T("Size"), 0, &vtProp, 0, 0); if (V_VT(&vtProp) != VT_NULL) { if (vtProp.llVal < minHardDiskSize) { // Less than 80GB bFound = TRUE; break; } VariantClear(&vtProp); pclsObj->Release(); } } // Cleanup pEnumerator->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); } } return bFound; }

2. 使用 WMI 从跟踪中转义

WMI 提供了一种创建新进程和计划任务的方法。沙盒通常使用 CreateProcessInternalW 函数挂钩来跟踪子进程。但是,当您使用 WMI 创建进程时,函数 CreateProcessInternalW 不会在父进程中调用。因此,沙盒可能不会跟踪使用 WMI 创建的进程,并且不会记录其行为。

2.1 使用 WMI 启动进程

可以使用带“Create” 方法的“Win32_Process” 类使用 WMI 创建新进程:

代码语言:javascript复制
CoInitializeEx(NULL, COINIT_MULTITHREADED);

// 设置常规 COM 安全级别
hres = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL);
if (FAILED(hres) && hres != RPC_E_TOO_LATE)
    break;

// 创建 WbemLocator 的实例
CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&wbemLocator);
wbemLocator->ConnectServer(CComBSTR("ROOT\CIMV2"), NULL, NULL, NULL, 0, NULL, NULL, &wbemServices);

// 获取对象Win32_Process
wbemServices->GetObject(CComBSTR("Win32_Process"), 0, NULL, &oWin32Process, &callResult);
wbemServices->GetObject(CComBSTR("Win32_ProcessStartup"), 0, NULL, &oWin32ProcessStartup, &callResult);
oWin32Process->GetMethod(CComBSTR("Create"), 0, &oMethCreate, &oMethCreateSignature);
oMethCreate->SpawnInstance(0, &instWin32Process);
oWin32ProcessStartup->SpawnInstance(0, &instWin32ProcessStartup);
// 设置进程的启动信息
instWin32ProcessStartup->Put(CComBSTR("CreateFlags"), 0, &varCreateFlags, 0);
instWin32Process->Put(CComBSTR("CommandLine"), 0, &varCmdLine, 0);
instWin32Process->Put(CComBSTR("CurrentDirectory"), 0, &varCurDir, 0);
CComVariant varStartupInfo(instWin32ProcessStartup);
instWin32Process->Put(CComBSTR("ProcessStartupInformation"), 0, &varStartupInfo, 0);
wbemServices->ExecMethod(CComBSTR("Win32_Process"), CComBSTR("Create"), 0, NULL, instWin32Process, &pOutParams, &callResult);

2.2 通过 WMI 使用任务计划程序启动进程 (Windows 7)

该技术与“时间”中“使用任务计划程序延迟执行”一节中所述的基本上相同。WMI 只是提供了另一种计划任务的方法。

可以使用带有“Create”方法的“Win32_ScheduledJob”类使用 WMI 创建新任务。

但是,“Win32_ScheduledJob” WMI 类旨在与 AT 命令一起使用,该命令自 Windows 8 起已弃用。

在 Windows 8 及更高版本中,仅当注册表项“HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionScheduleConfiguration”具有类型为 REG_DWORD 的值“EnableAt”=“1”时,才能使用 WMI 创建计划作业。因此,这种技术不太可能在野外找到。

代码语言:javascript复制
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=Impersonate}!\" & strComputer & "rootcimv2")
Set objSWbemDateTime = CreateObject("WbemScripting.SWbemDateTime")
objSWbemDateTime.SetVarDate(DateAdd("n", 1, Now()))
Set objNewJob = objWMIService.Get("Win32_ScheduledJob")
errJobCreate = objNewJob.Create("malware.exe", objSWbemDateTime.Value, False, , , True, "MaliciousJob")

3.检查上次启动时间

如果在从快照还原 VM 后立即查询上次启动时间,则 WMI 数据库可能包含创建 VM 快照时保存的值。如果快照是在一年前创建的,则即使沙盒更新了上次启动时间,计算出的系统正常运行时间也将是一年。

此事实可用于检测从快照还原的虚拟机。此外,上次启动时间中的任何异常都可以用作沙盒指示器:

  • 系统正常运行时间过长(数月甚至数年)
  • 系统正常运行时间很短(不到几分钟)
  • 使用其他方法获取的上次启动时间与使用 WMI 获取的上次启动时间不同
代码语言:javascript复制
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\" & strComputer & "rootcimv2")
Set colOperatingSystems = objWMIService.ExecQuery ("Select * from Win32_OperatingSystem")
 
For Each objOS in colOperatingSystems
    dtmBootup = objOS.LastBootUpTime
    dtmLastBootUpTime = WMIDateStringToDate(dtmBootup)
    dtmSystemUptime = DateDiff("n", dtmLastBootUpTime, Now)
    Wscript.Echo "System uptime minutes: " & dtmSystemUptime
Next
 
Function WMIDateStringToDate(dtm)
    WMIDateStringToDate =  CDate(Mid(dtm, 5, 2) & "/" & _
        Mid(dtm, 7, 2) & "/" & Left(dtm, 4) & " " & Mid (dtm, 9, 2) & ":" & _
        Mid(dtm, 11, 2) & ":" & Mid(dtm, 13, 2))
End Function

4.检查网络适配器上次重置时间

我们需要检查是否有任何适配器是很久以前最后一次重置的。这可能表示应用程序正在从快照还原的虚拟机中运行:

代码语言:javascript复制
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\" & strComputer & "rootcimv2")
Set colOperatingSystems = objWMIService.ExecQuery ("Select * from Win32_NetworkAdapter")
 
For Each objOS in colNetworkAdapters
    dtmLastReset = objOS.TimeOfLastReset
    dtmLastResetTime = WMIDateStringToDate(dtmLastReset)  'WMIDateStringToDate function from the previous example
    dtmAdapterUptime = DateDiff("n", dtmLastResetTime, Now)
    Wscript.Echo "Adapter uptime minutes: " & dtmAdapterUptime
Next

锦鲤安全

一个安全技术学习与工具分享平台

点分享

点收藏

点点赞

点在看

0 人点赞