dokan2.dll

Winos 4.0

Winos 4.0

分析sub_7FF8AD1E312E

Winos 4.0

使用SetWindowPos隐藏运行时的任务栏图标,接着的主要逻辑在sub_7FF8AD1E2141函数中,跟进

Winos 4.0

前面部分是回文算法,没什么用,疑似用于混淆,直接跳过

Winos 4.0

检查MZ标志与PE标志,解析PE头找到IAT

Winos 4.0

上面一大部分是回文算法,还是没用的混淆,直接跳过,IAT HOOK实现的关键点,hook的目标函数为:sub_7FF8AD1E2108

Winos 4.0

拼接sw.dat路径

由于改样本存在反射式dll加载,通过代码逻辑可以分析得到open file与read file函数位置

Winos 4.0

sub_7FF8AD27C950函数负责open sw.dat,sub_7FF8AD25BBA0函数负责read sw.dat

Winos 4.0

修改内存属性用于运行读取sw.dat数据(shellcode)

下断点运行,获得读取数据offset:0;size:33107h

可以直接从sw.dat dump也可以通过脚本读取

def extract_saw_dat(input_file, output_file, size_hex=0x33107):
    try:
        with open(input_file, 'rb') as f_in:
            data = f_in.read(size_hex) 
        
        with open(output_file, 'wb') as f_out:
            f_out.write(data) 
        
        print(f"成功提取 {size_hex} 字节({size_hex} 字节)到 {output_file}")
    
    except FileNotFoundError:
        print(f"错误:文件 {input_file} 不存在!")
    except Exception as e:
        print(f"发生错误:{e}")
 
input_filename = 'sw.dat'     
output_filename = 'dump.dat' 
extract_saw_dat(input_filename, output_filename)

先跟进v18(filename)分析

Winos 4.0

使用了hash api加载技术,数据过多,先进行静态分析,分析刚才dump的数据

Winos 4.0

明显的PEB\TEB遍历,API hash

Winos 4.0

核心的hash 算法,这个算法是重复的,每个需要调用API都会使用到这个算法,同时hash 种子也不同

uint32_t ComputeFunctionHash(const char* funcName) {
    uint32_t hash = 0xFAF6F3EA; // 初始种子值 (-184639718 的补码表示)
    
    while (*funcName) {
        char c = *funcName;
        
        // (A-Z转为小写)
        if ((c >= 'A') && (c <= 'Z')) {
            c |= 0x20; // 等价于 c = tolower(c)
        }
        
        // 核心哈希计算:FNV-1a变种 
        hash = 16777619 * (hash ^ c); 
        
        funcName++;
    }
    
    return hash;
}

算法伪代码

hash对应API:

825981789 0x3138B27D GetModuleFileNameW kernel32.dll 获取当前进程可执行文件的完整路径。
297097716 0x11B428F4 PathRemoveFileSpecW shlwapi.dll 从文件路径中移除文件名,得到文件所在目录。
-1421191882 0xAB494D96 IsUserAnAdmin shell32.dll 检查当前用户是否为管理员。
-2061012367 0x8527F271 LoadLibraryA kernel32.dll 加载指定的DLL库(这里用于加载Shell32.dll)。
487451824 0x1D0F2CB0 ShellExecuteExW shell32.dll 执行一个操作,通过"runas"动词来请求UAC提权。
-1160475044 0xBBCEA65C memset ntdll.dll 将内存块(SHELLEXECUTEINFO结构体)清零。
1017289554 0x3CA28D52 ExitProcess kernel32.dll 终止当前(非管理员)进程。
881625753 0x348A7A99 CreateFileMappingW kernel32.dll 创建一个文件映射对象,可能用作互斥体或存放载荷。
-1411882551 0xABE8D1A9 MapViewOfFile kernel32.dll 将文件映射对象映射到当前进程的地址空间。
-194833316 0xF4625A9C memset ntdll.dll 清理内存。
-215346181 0xF3294C5B memcpy ntdll.dll 复制内存块(用于移动加密的载荷)。
-811868201 0xCFA8F7F7 CryptDecrypt advapi32.dll 核心功能:使用Windows加密API解密载荷。
771461096 0x2DF7D7E8 CreateFileW kernel32.dll 创建或打开文件(用于释放恶意DLL和数据文件)。
50438492 0x03017B2C WriteFile kernel32.dll 将数据写入文件(将解密后的载荷写入磁盘)。
-976780978 0xC58AE14E CreateServiceW advapi32.dll 持久化:创建一个新的Windows服务。
-1189699346 0xB91540EE StartServiceW advapi32.dll 启动一个服务。
513401500 0x1E99529C CreateProcessW kernel32.dll 核心功能:创建一个新进程来执行主载荷。
994584314 0x3B4958FA CloseHandle kernel32.dll 关闭一个打开的对象句柄(用于清理)。
94407602 0x05A17D92 CloseHandle kernel32.dll 关闭另一个句柄(可能是新进程的线程句柄)。
-1103439570 0xBE3C712E MessageBoxW user32.dll 显示一个消息框(用于在失败时欺骗用户)。
-332713028 0xEA2E2BBC ExitProcess kernel32.dll 在出错或完成任务后终止进程。

有了对应表,分析就很简单了

Winos 4.0

判断权限,非admin权限就进行UAC弹窗提权

Winos 4.0

sub_3641疑似网络行为,sub_3C54函数存在疑似反沙箱技术

Winos 4.0

检测系统dll47个预设的、硬编码的内存地址内容是否为WriteData

Winos 4.0解密API

Winos 4.0

创建服务

可能创建的文件:

C:\Windows\System32\BrokerClientCallback.dll

C:\Windows\System32\TimeBrokerClient.dll

C:\Windows\System32\msvchost.dat

C:\Windows\System32\system.dat

C:\Windows\System32\mswkscli.dll

C:\Windows\System32\wkscli.dll

动调只需要实现一个大致逻辑的简单loader即可

#define _CRT_SECURE_NO_WARNINGS 
#include <windows.h>
#include <stdio.h>
#include <string.h>

const char* PAYLOAD_FILE = "sw.dat";
const DWORD PAYLOAD_SIZE = 0x33107;

typedef void (*ShellcodeFunc)(char*);

int main() {
    char exeFullPath[MAX_PATH];
    char payloadPath[MAX_PATH];
    char* lastSlash = NULL;

    if (GetModuleFileNameA(NULL, exeFullPath, MAX_PATH) == 0) {
        return 1;
    }
    strncpy(payloadPath, exeFullPath, MAX_PATH);
    lastSlash = strrchr(payloadPath, '\\'); 
    if (lastSlash != NULL) {
        *(lastSlash + 1) = '\0'; 
        strcat(payloadPath, PAYLOAD_FILE); 
    }
    else {
        return 1;
    }
    printf(" file path: %s\n", payloadPath);

    FILE* file = fopen(payloadPath, "rb"); 
    if (file == NULL) {
        return 1;
    }

    void* shellcode_mem = VirtualAlloc(
        NULL,             
        PAYLOAD_SIZE,   
        MEM_COMMIT | MEM_RESERVE, 
        PAGE_EXECUTE_READWRITE 
    );

    if (shellcode_mem == NULL) {
        fclose(file);
        return 1;
    }

    size_t bytesRead = fread(shellcode_mem, 1, PAYLOAD_SIZE, file);
    if (bytesRead != PAYLOAD_SIZE) {
        fclose(file);
        VirtualFree(shellcode_mem, 0, MEM_RELEASE); 
        return 1;
    }
    fclose(file); 
    ShellcodeFunc shellcode_entry;

    shellcode_entry = (ShellcodeFunc)shellcode_mem;
    shellcode_entry(exeFullPath);


    VirtualFree(shellcode_mem, 0, MEM_RELEASE);

    return 0;
}

TimeBrokerClient.dll

…………

存在tlscallback

Winos 4.0

动调可以使用x64dbg,也可以选择不动调,直接解密shellcode就行

Winos 4.0

svchost中存储的是VirtualAlloc 函数的 RVA

Winos 4.0

Vigenère 变种解密脚本

import os

def decrypt_payload():
    input_file = "msvchost.dat"
    output_file = "decrypted.bin"

    key_string = "svchost"
    key_bytes = [ord(c) for c in key_string]
    key_bytes.append(0x00)
    
    key_len = len(key_bytes) 

    if not os.path.exists(input_file):
        return

    try:
        with open(input_file, "rb") as f:
            encrypted_data = bytearray(f.read())
        
        decrypted_data = bytearray(len(encrypted_data))

        for i in range(len(encrypted_data)):
            dec_byte = (encrypted_data[i] - key_bytes[i % key_len]) & 0xFF
            decrypted_data[i] = dec_byte

        with open(output_file, "wb") as f:
            f.write(decrypted_data)


    except Exception as e:
        print(f"[!] 发生错误: {e}")

if __name__ == "__main__":
    decrypt_payload()

写一个load进行动调

(load名称使用svchost.exe,防止存在检查行为)

#include <windows.h>
#include <iostream>
#include <fstream>
#include <vector>

int main()
{
    const char* filename = "decrypted.bin";


    std::ifstream file(filename, std::ios::binary | std::ios::ate);
    if (!file) {
        system("pause");
        return -1;
    }

    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    if (size <= 0) {
        return -1;
    }

    std::vector<char> buffer(size);
    if (!file.read(buffer.data(), size)) {
        return -1;
    }

    void* exec_mem = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    if (!exec_mem) {
        return -1;
    }

    memcpy(exec_mem, buffer.data(), size);

    ((void(*)())exec_mem)();

    return 0;
}