3-混淆加密

明确几点:

  1. 为什么要混淆加密?因为杀软在静态扫描木马文件的时候会进行特征匹配,而cs和msf或者一些无恶意但是被黑客用于测试的shellcode的特征已被杀软收集,所以为了过静态查杀,有必要对shellcode进行混淆加密

  2. 下面提到的各种加密方式不要求掌握原理,但是要明白如何使用,且要搞懂数据类型是如何变换的

  3. 加密版,是自己留存不对外公布的,加密版的有些数据是要自己填写的,比如明文(大部分情况是shellcode)、keyiv公钥。解密版,是出现在shellcode加载器中,用于解密数据的,解密版的有些数据是要自己填写的,比如密文keyiv私钥

  4. 从这一小节开始到第三章-防御规避之前我都不会提到木马的免杀效果如何,因为不使用防御规避技术,谈免杀木马的效果怎么样都是毫无意义的(个人认为)。不过,学完前两章,你的免杀木马可以过一些普通的杀软。

32位的calc的shellcode

0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64, 0x8b, 0x50,
0x30, 0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28, 0x0f, 0xb7, 0x4a, 0x26,
0x31, 0xff, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7,
0xe2, 0xf2, 0x52, 0x57, 0x8b, 0x52, 0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78,
0xe3, 0x48, 0x01, 0xd1, 0x51, 0x8b, 0x59, 0x20, 0x01, 0xd3, 0x8b, 0x49, 0x18, 0xe3,
0x3a, 0x49, 0x8b, 0x34, 0x8b, 0x01, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0x0d, 0x01,
0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x03, 0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75, 0xe4, 0x58,
0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b, 0x0c, 0x4b, 0x8b, 0x58, 0x1c, 0x01, 0xd3,
0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24, 0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a,
0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a, 0x8b, 0x12, 0xeb, 0x8d, 0x5d, 0x6a, 0x01, 0x8d,
0x85, 0xb2, 0x00, 0x00, 0x00, 0x50, 0x68, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb,
0xf0, 0xb5, 0xa2, 0x56, 0x68, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x3c, 0x06, 0x7c,
0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x53,
0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00

64位的calc的shellcode

如果不能弹出计算器,可能是编译程序位数不对。

一、base64编码

Base64 编码对于大多数人来说应该是一个相对熟悉的编码方式。它是一种将二进制数据转换为字符串格式的编码方式,常用于在需要以文本形式传输数据的场景中。

在免杀中它的作用是

  1. 一定的混淆作用

  2. 方便传输二进制数据,因为Base64 编码的字符串是由可打印字符组成的,这使得它在许多情况下非常适合传输二进制数据。这一个功能我认为才是最重要的

下面我将介绍在C/C++、C#、Go中如何使用Base64编码

1.1 C++

由于C++的标准库中没有实现base64编码,想要实现base64编码有两种方法

  1. 自实现base64编码

  2. 使用第三分库,如OpenSSL

因为base64编码的实现不是太复杂,所以我只介绍第一种方式:自实现base64编码。

加密版

解密版

1.2 CSharp

在 C# 中,Base64 编码和解码可以通过 System.Convert 类非常方便地实现。

  1. Convert.ToBase64String可以将无符号的字节数组转换为等效的base64字符串

  2. Convert.FromBase64String 可以将base64字符串转换为无符号的字节数组

  3. 在C# 中无符号字节数组为的数据类型为 byte[]

加密版

解密版

1.3 Go

在 Go 语言中,可以使用内置的 encoding/base64 包来实现 Base64 编码和解码

加密版

解密版

二、AES加密

AES(高级加密标准)是一种对称加密算法,广泛用于数据保护,它适用于大量数据传输过程中需要加密的场景。

2.1 C++

因为C++的标准库没有实现AES加密算法,又因为自实现AES加密算法太过复杂,我推荐借助第三方库OpenSSL来完成AES加密与解密。当然后面介绍的RSA和RC4也需要用到OpenSSL,所以请读者提前下载,并给Visual Studio添加OpenSSL,这一点很关键。

如何给给Visual Studio添加OpenSSL,详见:Windows下给Visual Studio添加OpenSSL - p爬 - 博客园arrow-up-right

下载地址:Win32/Win64 OpenSSL Installer for Windows - Shining Light Productionsarrow-up-right

请下载Win64版。当然Win32和Win64都可以下载,只是两个同时存在时,环境配置和各个库之间的切换很复制,不推荐两个同时下载。

加密版

运行结果

  1. 要将加密后的密文、密钥 (Key)、初始化向量 (IV)复制到解密版代码的相应位置处。

  2. 因为密钥 (Key)和初始化向量 (IV)是使用随机生成产生的,故每次运行加密版代码,密文、key和iv都是不一样的,一定要保存好Key和IV

解密版

记得填入key、iv和密文

注:

  1. std::vector 是 C++ 标准库中提供的一种动态数组容器,它属于 STL(标准模板库)的一部分。std::vector 提供了一个可变大小的数组,可以在运行时动态地增加或减少其大小。就个人编程习惯而言,我是觉得 std::vector 挺好用的。

  2. std::vector 有两个方法在免杀中用的比较多,就拿本例来说

    • decryptedtext.size() //获取动态数组的大小

    • decryptedtext.data() //获取动态数组的首地址,指针类型是 unsigned char*

运行结果

2.2 CSharp

在 C# 中实现 AES 加密相对简单,可以使用 System.Security.Cryptography 命名空间中的类

AES的具体代码用例,详见:Aes 类 (System.Security.Cryptography) | Microsoft Learnarrow-up-right

加密版

解密版

数据类型转换

  1. 加密:原始shellcode(字节数组)->base64编码1后(字符串)->AES加密(字节数组)->base64编码2后(字符串)

  2. 解密:字符串->base64解码2后(字节数组)->AES解密后(字符串)->base64解码1后(字节数组,就是原始的shellcode)

2.3 Go

可以用官方的标准库 crypto 来实现,但是因为还要额外地写一些代码(填充函数和解填充函数),所以我推荐用别人高度封装好的加密库:GitHub - forgoer/openssl:OpenSSL 库的函数包装,用于对称和非对称加密和解密。arrow-up-right

安装,命令行输入

加密版

解密版

数据类型转换

  1. 加密:原始shellcode(字节数组)->AES加密后(字节数组)->base64编码后(字符串)

  2. 解密:字符串->base64解码后(字节数组)->AES解密后(字符串,就是原始的shellcode)

三、RSA加密

RSA(Rivest-Shamir-Adleman)是一种广泛使用的公钥加密算法,主要用于安全数据传输。在免杀中可以用于加密shellcode和AES的keyiv

3.1 C++

在 C++ 中实现 RSA 加密可以使用一些现成的库,比如 OpenSSL。

  1. 密钥生成脚本

  1. RSA加密脚本

运行结果

  1. RSA解密脚本

数据类型转换

  1. 加密:原始shellcode(字节数组)->RSA加密(字节数组)

  2. 解密:密文(字节数组)->RSA解密(字节数组,就是shellcode)

3.2 CSharp

在 C# 中实现 RSA 加密相对简单,可以使用 System.Security.Cryptography 命名空间中的类

注意

  1. 如果待解密和待加密的数据超过了密钥长度,就需要将数据进行分割

  2. 在RSA算法中,公钥用于加密,私钥用于解密,所以私钥要保存好。

解决参考: 1、RSA不限长度非对称加密解密C#-阿里云开发者社区 (aliyun.com)arrow-up-right 2、C# RSA加密解密,对字符串长度没有限制_c# 加密字符串超出长度-CSDN博客arrow-up-right

  1. 密钥生成脚本

记得复制RSA的公钥和私钥

  1. RSA加密脚本

  1. RSA解密脚本

数据类型转换

  1. 加密:原始shellcode(字节数组)->base64编码(字符串)->RSA加密(字符串)

  2. 解密:字符串->RSA解密(字符串)->base64解码(字节数组,就是原始的shellcode)

3.3 Go

使用Go的 crypto 标准库实现RSA加密/解密

代码参考:Golang 实现RSA加密解密 - ZhiChao& - 博客园arrow-up-right

  1. 生成公钥和私钥

  1. 加密

  1. 解密

用之前记得填写ciphertext.txt的路径

tmd,搞了一下午才发现是不能直接复制控制台输出的RSA加密的base64字符串,不然就是解密失败。算了,反正以后的加密shellcode都是要作分离处理的。

数据类型转换

  1. 加密:原始shellcode(字节数组)->强转string类型->RSA加密后(string类型)->base64编码后(字符串)->写入文件后(字节数组)

  2. 解密:读取文件后(字节数组)->强转string类型->base64解码后(字节数组)->RSA解密后(字节数组,就是shellcode)

四、RC4加密

RC4 已被认为不再安全,不过在免杀中我们不需要RC4的加密性,而是要求RC4加密的shellcode能在一段时间内杀软不能识别出来

4.1 C++

OpenSSL 库中提供了对 RC4 加密算法的实现。虽然 RC4 已被认为不再安全,OpenSSL 仍然保留了对它的支持,以兼容旧系统和应用程序。

RC4 支持任意长度的密钥,但通常建议使用 128 位(16 字节)密钥。

加密版

解密版

数据类型转换

  1. 加密:原始shellcode(字节数组)->RC4加密后(字节数组)

  2. 解密:密文(字节数组)->RC4解密后(字节数组,就是shellcode)

4.2 CSharp(自实现)

其实在 RC4 加密算法中,加密和解密实际上是使用相同的函数代码。这是因为 RC4 是一个对称加密算法,使用相同的密钥和相同的算法进行加密和解密。

加密版

解密版

数据类型转换

  1. 加密:原始shellcode(字节数组)->RC4加密后(字节数组)->base64编码(字符串)

  2. 解密:密文(字符串)->base64解码(字节数组)->RC4解密后(字节数组,就是shellcode)

4.3 Go

可以使用 Go 的 crypto/rc4 库实现 RC4 加密和解密

加密版

解密版

数据类型转换

  1. 加密:原始shellcode(字节数组)->RC4加密后(字节数组)->hex编码(字符串)

  2. 解密:密文(字符串)->hex解码(字节数组)->RC4解密后(字节数组,就是shellcode)

五、XOR(异或)

XOR 加密是一种简单而有效的加密方法。XOR 加密的基本思想是将明文与一个密钥进行逐位异或(XOR)操作,从而生成密文。要解密密文,只需再次与相同的密钥进行 XOR 操作即可恢复明文。因为实现比较简单,所以C++、C#、GO均是自实现XOR加密。

5.1 C++

加密版

解密版

数据类型转换

  1. 加密:原始shellcode(字节数组)->XOR加密后(字节数组)

  2. 解密:密文(字节数组)->XOR解密后(字节数组,就是shellcode)

5.2 CSharp

加密版

解密版

数据类型转换

  1. 加密:原始shellcode(字节数组)->XOR加密后(字节数组)->base64编码(字符串)

  2. 解密:密文(字符串)->base解码(字节数组)->XOR解密后(字节数组,就是shellcode)

5.3 Go

加密版

解密版

数据类型转换

  1. 加密:原始shellcode(字节数组)->XOR加密后(字节数组)->base64编码(字符串)

  2. 解密:密文(字符串)->base解码(字节数组)->XOR解密后(字节数组,就是shellcode)

六、SGN工具

正如作者所说:“对于进攻性安全社区来说,shikata ga nai 编码器的原始实现被认为是最好的 shellcode 编码器(到目前为止)。”

前面我们所说的各种加密算法大多都是用于通信加密,而SGN是专门用于生成静态不可检测的二进制有效负载。它的强大无需我多言,看github的star数就可窥一二

生成的 shellcode 通常包含自我解码的逻辑,这意味着它在执行时会首先解码自身,然后执行原始的 shellcode,这样我们的代码可以不用像前面的各种加密算法一样包含需要在代码中完成加密/解密,大大减少了代码量

项目地址: GitHub - EgeBalci/sgn: Shikata ga nai (仕方がない) encoder ported into go with several improvementsarrow-up-right

  1. 利用CS生成payload.bin文件

之前一直拿弹计算器的shellcode作为例子,但是弹计算器的shellcode经过SGN工具编码之后好像不能正常运行?所以我换成CS的shellcode了。记得选择的是Raw的shellcode

  1. SGN编码shellcode

加密后的文件为 payload.bin.sgn

  1. 利用010 Editor 导出shellcode

当然你也可以在代码中直接读取加密后的文件。

导出为C代码

  1. 复制粘贴shellcode,执行加载器代码

成功上线CS

七、小结

常用的方法

  1. C++中自实现的printHex函数输出0x格式的shellcode我觉得挺好用的。

  2. C++中的 std::vector<unsigned char> 挺好的,这个动态数组容器提供了很多实用的方法,比如size()和data()方法,但是我在网上查阅资料时发现,很少人在免杀用 std::vector<unsigned char>。萝卜青菜,各有所爱,看个人编程习惯吧。

  3. C# 中Encoding

    • Encoding.UTF8:用于UTF-8编码。

    • Encoding.Unicode:用于UTF-16编码(小端)。

    • Encoding.ASCII:用于ASCII编码。

    • Encoding.UTF32:用于UTF-32编码。

  • 重要方法

    • GetBytes(string s):将字符串转换为字节数组。

    • GetString(byte[] bytes):将字节数组转换为字符串。

    • GetByteCount(string s):获取转换为字节数组所需的字节数。

    • GetCharCount(byte[] bytes):获取从字节数组转换为字符串所需的字符数。

  1. C# 的 Convert 类,有两个方法挺常用的

  • Convert.FromBase64String(string s):将一个字符串从Base64编码转换为字节数组。

  • Convert.ToBase64String(byte[] inArray):将字节数组转换为Base64编码的字符串。

  1. Go的 encoding/hex

    • hex.Decode(str string):将十六进制字符串解码为字节数组。

    • hex.Encode(array []byte)

  2. Go的string()是强制转换为字符串类型的方法、[]byte()是强制转换为字节数组的方法

至此常用的加密/解密算法和编码/解码技术已经讲完了,还有很多加密/解密、编码/解码算法还未提及,这些算法都可以加密/解密、编码/解码shellcode,但是用我介绍的这些算法就可以让杀软识别不出我们的shellcode,不必花太多时间在这上面。

补充:还有一个Hex编码/解码也是挺常见的,在正文中我未正式的提出,但是不少的代码都有所涉及,这里我就不过多介绍了,请读者到网上查阅相关资料。