免杀之shellcode混淆

公众号原文:免杀之Shell混淆

背景

所谓杀软的静态扫描 ,就是通过提取磁盘中一个文件的特征码,然后与杀软自身报错的病毒库的特征码匹配,如果满足匹配结果,那么杀软将会将其认定为恶意文件。所有让我们的马面目全非,让杀软匹配不了,不认识我们的马,是过掉静态扫描的本质。所谓让马面目全非也就是——混淆。

介绍

现在的杀软不仅仅会对恶意文件进行一个静态扫描、分析其特征以外,杀软还会将文件丢到虚拟沙箱中去运行,挂一个hook去跟踪、监控调用的api函数并判断其行为,有的杀软甚至是可以扫描内存的(比如卡巴斯基),shellcode在内存中解密后就很容易暴露,这加大了与杀软对抗的难度。所以仅仅是shellcode混淆已经很难再bypass掉各种杀软了,但是这并不意外着shellcode混淆就已失去意义了!!

在做免杀过程中,第一步要解决的问题是静态免杀,因为只有先过了静态的免杀才能去考虑后面的的抗沙箱、动态免杀、内存躲避等技术,所以shellcode混淆技术在静态免杀中是由其重要的,也是必须要学习的一门免杀技术。学了混淆之后,下一步再去结合其他动态免杀的技术,才能更加完美的过点杀软!

另外这篇文章是在stagerless的条件下,即非分离的,毕竟stager分离的是自带静态免杀的。

注意:在loader中硬编码的shellcode是加密的,否则会被监测出来并弹窗(杀软报警);shellcode的解密是在memcpy之后开始的,也就是加载进内存之后开始解密,如果在加载之前进行解密,这无异于裸奔。

base64编码混淆

原理:即对原始的shellcode进行base64编码,让其与原始的shellcode“样貌”不一,掩盖掉原始shellcode的静态特征,防止杀软辨识出来。

使用base64混淆需要注意,编码和解码的算法都需要对应,否则编码之后,再解码就无法还原了,所以这里建议不要使用网上的在线编码平台,建议自己写一个编码、解码算法。

注:编码之后的shellcode大小是原来的3/4倍或者+1~2,目前只进行base64编码是没有什么用处了,需要结合其他的方法进行混淆。

import base64

def base64_encode(data):

# 将字符串编码为字节流

encoded_bytes = base64.b64encode(data.encode('utf-8'))

# 将字节流解码为字符串

encoded_string = encoded_bytes.decode('utf-8')

return encoded_string

def base64_decode(encoded_string):

# 将字符串编码为字节流

encoded_bytes = encoded_string.encode('utf-8')

# 将字节流解码为字符串

decoded_bytes = base64.b64decode(encoded_bytes)

# 将字节流解码为字符串

decoded_string = decoded_bytes.decode('utf-8')

return decoded_string

# 测试加密和解密

data = "shellcode"

encoded_data = base64_encode(data)

print("编码后:", encoded_data)

decoded_data = base64_decode(encoded_data)

print("解码后:", decoded_data)

运行结果截图如下,不过现在base64单独使用的话是过不了杀软的静态的,还是要结合其他加密算法的!

AES加密

原理:自己创建一个key,使用这个key对原始shellcode进行加密,修改其“样貌”,修改其静态特征。

使用AES加密最好让密钥是一个动态获取的值,或者可以通过请求来获取,通过密钥协商来获取等待。最好不是硬编码到代码中的,否则杀软容易获取到key自行解密来识别。(比如key=“123456”)

注:编码之后的shellcode大小是(shellcode-size+1)/2

from Crypto.Cipher import AES

from Crypto.Util.Padding import pad, unpad

from Crypto.Random import get_random_bytes

def encrypt(plaintext, key):

# 使用ECB模式

cipher = AES.new(key, AES.MODE_ECB)  

ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))

return ciphertext

def decrypt(ciphertext, key):

# 使用ECB模式

cipher = AES.new(key, AES.MODE_ECB)  

plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)

return plaintext

# 生成随机密钥

key = get_random_bytes(16)

# 要加密的明文

plaintext = b"shellcode"

# 加密

ciphertext = encrypt(plaintext, key)

print("加密后:", ciphertext)

# 解密

decrypted_text = decrypt(ciphertext, key)

print("解密后:", decrypted_text)

这里AES加密使用的是最简单的ECB模式,除此之外还有更安全的加密模式,如CBC(Cipher Block Chaining)模式和CTR(Counter),这些模式引入了初始化向量(IV)和密文块之间的链接,以增加加密的安全性和随机性,当然算法也会更复杂一些。

运行后的结果截图如下,强调,key不要直接硬编码 (反例:key=b'123456')

异或加密

原理:一段二进制代码,在异或两次之后即可还原为原来的二进制码,举个例子就明白咯,如果。

所以可以自定义一段二进制码,然后异或加密之后,再memcpy加载进内存之后再进行异或一遍,解密,然后执行即可。

#include <stdio.h>

#include <string.h>

char shellcode[] = "shellcode";

void xor_encrypt_decrypt(size_t shellcodeSize) {

    for (int i = 0; i < shellcodeSize; i++)

    {

        shellcode[i] ^= 0x5b;

    }

}

int main() {

    //获取shellcode大小

    size_t shellcodeSize = strlen(shellcode);

    //第一次异或

    xor_encrypt_decrypt(shellcodeSize);

    printf("第一次异或后:%s\n", shellcode);

    //第二次异或,即复原

    xor_encrypt_decrypt(shellcodeSize);

    printf("第二次异或后:%s\n", shellcode);

    return 0;

}

由于异或实现起来比较简单这里就直接用C来写咯,结果的截图如下。

偏移量混淆

原理:这个混淆方式挺巧妙的。它不会直接存shellcode(不论加密与否)在loader中,而是存shellcode对应的字符在所有通用设备都固有的文件中的位置,生成一个存放位置的数组,再利用存放位置的数组里的元素缩影,一个个取出来去编码后的文件里面寻找对于字符,将他们重新组装成shellcode。如果还是不理解的话试试看流程图呢。

import codecs

#获取win.ini的二进制编码

with open("C:\Windows\win.ini", 'rb') as file:

    content = file.read()

new_file = codecs.encode(content, 'hex').decode()

#获取shellcode的二进制编码

with open("shellcode.bin", 'rb') as file:

    shellcode = file.read()

new_shellcode = codecs.encode(shellcode, 'hex').decode()

#遍历查找对应的位置,并添加到数字list中

list = []

for a in new_file:

    offset = new_file.find(a)

    list.append(offset)

print(list)

获取到的list(存在位置索引的数组)部分截图如下,之后把数组放在loader中之后在自己写对应的还原算法即可

uuid混淆

原理:将shellcode的字节数用空字节填充为16的倍数,然后对没连续的16个字节的shellcode进去uuid加密,然后再在memcpy进去内存之后,进行解密执行即可

import  uuid

#多写几个shellcode,体会一下uuid加密的前提是位数为16的倍数

shellcode = b'shellcode,shellcode,shellcode,shellcode'

def convertToUUID(shellcode):

    #如果shellcode的长度不足16位数,则需要用空字节填充shellcode为16的倍数

    if len(shellcode)%16 !=0:

        #计算出需要填充空字节的数量

        addNullbyte = b"\x00" * (16-(len(shellcode)%16))

        #拼接需要填充空字节的数量

        shellcode += addNullbyte

    uuids = []

    # 每次取出shellcode的16个字节进行uuid转换

    for i in range(0,len(shellcode),16):

        uuidString = str(uuid.UUID(bytes_le=shellcode[i:i+16]))

        uuids.append(uuidString.replace("'","\""))

    return uuids

u = convertToUUID(shellcode)

print(str(u).replace("'","\""))

以上代码运行结果截图如下

推荐工具:https://github.com/Bl4ckM1rror/FUD-UUID-Shellcode.git

小白可能会遇到的困惑:在使用对shellcode进行混淆的时候,要注意将加密的代码与加载器分开,不能放在一个代码文件中,否则就没有作用了,这样静态是过不了的,需要在自己的电脑上,额外运行加密算法对shellcode进行加密,然后把加密后shellcode放进加载器(不分离的情况下)进去加载,流程图如下

了解了以上的混淆方式后,可能有的师傅会想,既然要让杀软认不出来,那就多加密,结合多种加密算法,甚至自研一种更加复杂的加密算法不就更能过掉静态扫描了吗?当然不是,因为杀软有个东西叫做熵,当加密算法复杂了以后,程序的熵会增大,这有可能会引起杀软的嫌疑,另外算法的复杂度讲究够用就行,不需要太过于复杂,有时候一个异或就足以bypass某绒。

总结:以上方式单独使用已经很难过杀软了,但是为什么要学习呢,因为过静态免杀还是有用的,除此之外可以相互结合着使用,让加密的算法更加复杂,比如我先base64加密了再去uuid等,让自己的算法复杂起来,提高静态免杀通过率。

另外要说的是,虽然加密算法有的是用的python,有的是用的c写的,但是你的解密算法可以用任意的语言,比如用python加密,我用c来解密,用c加密,用c来解密都可以,只要加密解密算法是对应的加密后可以还原回来就行!

以上就是这篇文章的全部了,之后不出意外会更新一篇偏移混淆的免杀实战篇,因为感觉偏移混淆挺有意思的,相信肯定还有更多、更具技巧性、更有意思的混淆技术,等我学会了给各位分享!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值