这个公式可以拆解成以下的形式
B是由查表操作得来的,可以简化成S(A)的形式。又因为轮密钥加只是单纯的异或相当于,所以最后通过线代表示出来的简化公式如下。该公式为SubBytes、MixColumns、AddRoundKey三个轮操作的合并结果。
由于轮密钥需要通过密钥数据动态计算,所以不做识别加密算法的特征查询。
而S盒与Golois域矩阵却是固定的,但是S盒是一个16 x 16的矩阵(256字节)根据分块矩阵乘法,所以衍生出了4个T盒,用于查表。表达式如下。4个T盒常量是固定的,所以在优化的AES算法中,我们用T盒作为特征查找加密轮位置。
最终简化的轮操作公式如下,计算机中通常将每一个列向量看作一个整体,储存为一个32位无符号整数(Int32),因此每一个T表都是一个有2^8=256个Int32组成的数组。
在实践中,T表一般提前编写程序算出,然后作为一个常量数组硬编码在AES的软件实现代码中。这样,就将繁琐的伽罗瓦域上的运算和矩阵运算变成了对于计算机而言非常简单高效的查表和按位运算。我们随便在网上找一个优化过的项目查看T盒常量,例如https://android.googlesource.com/platform/external/openssh/+/idea133/rijndael.c
输入第一个常量0xc66363a5
可以看到以下常量。
根据交叉引用便可以定位到病毒优化过的AES轮操作位置。病毒作者往往都会使用开源算法对数据进行处理,所以可以通过github查询是否存在源码。
Salsa20算法
Salsa20与ChaCha算法类似,都是由Daniel J. Bernstein 开发的算法。Salsa20在2005年提出,而ChaCha在2008年提出,它相较Salsa20算法使用了新的轮函数,使其在一些架构上的扩散性和有效性增加。这两种算法都构建了一个利用基于add-rotate-xor(ARX,32位加——旋转——位异或)的伪随机函数。改算法核心函数使用了一个32字节(256位)密钥、一个8字节(64位)Nonce(只使用一次的随机数或伪随机数)和一个8字节(64位)计数器三个参数运算出一个64字节(512位)的密钥流块。我们这里以https://github.com/andres-erbsen/salsa20项目作为代码对比。
初始化State结构体
在Salsa20算法需要传入密钥、Nonce、字符串常量和明文数据流的索引值(也就是数组索引)作为参数每轮运算产生新的State块。该State块是一个4 x 4的矩阵。初始化的State块由密钥、Nonce、字符常量和要加密的数据流索引值组成。例如,下图中每个颜色的小方块代表4字节。其中明文数据流的索引值是红色的区域一共8字节,将索引高4位和低四位分别计算成两个4字节元素。常量字符串是白色部分的"expand 32-byte k",拆解成4字节块为"expa", "nd 3", "2-by"和"te k"与剩余三个参数合并成一个待加密的状态块。经过多轮运算计算出加密的64字节数据。因为Salsa20系列算法都使用"expand 32-byte k"字符串,所以可以将该字符串数据作为识别Salsa20系列算法的特征(ChaCha算法也使用该字符串)
代码实现如下:
注意:index为要被加密的数据在数组中的索引,在处理成状态矩阵数据时分别将索引值和索引右移32位值处理成4字节数据填充。
核心运算
Salsa20根据输入的State状态块进行加密运算。加密的核心函数是一个只处理16字节数据的函数QR(a, b, c, d),它每次只处理State数据中的某一行或某一列数据(a,b,c,d是输入列或行的数组元素,占用4字节)。QR的计算方法如下。我们可以看到固定的位移值,在算法中如果碰到连续固定的位移值需要注意是否是Salsa20加密算法。
b ^= (a + d) <<< 7;
c ^= (b + a) <<< 9;
d ^= (c + b) <<< 13;
a ^= (d + c) <<< 18;
轮加密操作
在Salsa20中每轮加密都需要区分奇数轮和偶数轮。奇数轮计算State矩阵的每一列数据,偶数轮计算State矩阵的每一行数据,如果每轮行列数据都进行计算则称为双轮。如下为C代码表示,数字代表在State数组中的索引对应元素。
// Odd round奇数轮
QR( 0, 4, 8, 12) // column 1
QR( 5, 9, 13, 1) // column 2
QR(10, 14, 2, 6) // column 3
QR(15, 3, 7, 11) // column 4
// Even round偶数轮
QR( 0, 1, 2, 3) // row 1
QR( 5, 6, 7, 4) // row 2
QR(10, 11, 8, 9) // row 3
QR(15, 12, 13, 14) // row 4
以下代码是Wiki百科为我们提供的代码实现,它直接使用了双轮思想。因为Salsa20代表计算20轮,而下面代码一共执行了10次循环,按照奇数轮和偶数轮顺序执行。注意代码最后一行将加密后的State块与初始化的State块做和得到最终的加密数据,因为20次轮操作是可逆的。
#include <stdint.h>
#define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
#define QR(a, b, c, d)( \
b ^= ROTL(a + d, 7), \
c ^= ROTL(b + a, 9), \
d ^= ROTL(c + b,13), \
a ^= ROTL(d + c,18))
#define ROUNDS 20
void salsa20_block(uint32_t out[16], uint32_t const in[16])
{
int i;
uint32_t x[16];
for (i = 0; i < 16; ++i)
x[i] = in[i];
// 10 loops × 2 rounds/loop = 20 rounds
for (i = 0; i < ROUNDS; i += 2) {
// Odd round
QR(x[ 0], x[ 4], x[ 8], x[12]); // column 1
QR(x[ 5], x[ 9], x[13], x[ 1]); // column 2
QR(x[10], x[14], x[ 2], x[ 6]); // column 3
QR(x[15], x[ 3], x[ 7], x[11]); // column 4
// Even round
QR(x[ 0], x[ 1], x[ 2], x[ 3]); // row 1
QR(x[ 5], x[ 6], x[ 7], x[ 4]); // row 2
QR(x[10], x[11], x[ 8], x[ 9]); // row 3
QR(x[15], x[12], x[13], x[14]); // row 4
}
for (i = 0; i < 16; ++i)
out[i] = x[i] + in[i]; //加密数据与初始化State相加得到加密后数据
}
Salsa20加密文件流完整操作
Salsa20首先输入要加密的明文数据,每64字节的明文就会产生1个64字节State加密块。(因为初始化State块受加密数据流的索引影响,也就是红色Pos区域)。
获取到论加密操作生成的最终State块后,明文与State块逐字节异或生成最终的密文。我们可以看到以下代码
完整Salsa20算法如下。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> // we use 32-bit words
// rotate x to left by n bits, the bits that go over the left edge reappear on the right
#define R(x,n) (((x) << (n)) | ((x) >> (32-(n))))
// addition wraps modulo 2^32
// the choice of 7,9,13,18 "doesn't seem very important" (spec)
#define quarter(a,b,c,d) do {\
b ^= R(d+a, 7);\
c ^= R(a+b, 9);\
d ^= R(b+c, 13);\
a ^= R(c+d, 18);\
} while (0)
void salsa20_words(uint32_t* out, uint32_t in[16]) {
uint32_t x[4][4];
int i;
for (i = 0; i < 16; ++i)
x[i / 4][i % 4] = in[i];
for (i = 0; i < 10; ++i) { // 10 double rounds = 20 rounds
// column round: quarter round on each column; start at ith element and wrap
quarter(x[0][0], x[1][0], x[2][0], x[3][0]);
quarter(x[1][1], x[2][1], x[3][1], x[0][1]);
quarter(x[2][2], x[3][2], x[0][2], x[1][2]);
quarter(x[3][3], x[0][3], x[1][3], x[2][3]);
// row round: quarter round on each row; start at ith element and wrap around
quarter(x[0][0], x[0][1], x[0][2], x[0][3]);
quarter(x[1][1], x[1][2], x[1][3], x[1][0]);
quarter(x[2][2], x[2][3], x[2][0], x[2][1]);
quarter(x[3][3], x[3][0], x[3][1], x[3][2]);
}
for (i = 0; i < 16; ++i)
out[i] = x[i / 4][i % 4] + in[i];
}
// inputting a key, message nonce, keystream index and constants to that transormation
void salsa20_block(uint8_t* out, uint8_t key[32], uint64_t nonce, uint64_t index) {
static const char c[16] = "expand 32-byte k"; // 初始化常量块
#define LE(p) ( (p)[0] | ((p)[1]<<8) | ((p)[2]<<16) | ((p)[3]<<24) )
uint32_t in[16] = { LE(c), LE(key), LE(key + 4), LE(key + 8),
LE(key + 12), LE(c + 4), nonce & 0xffffffff, nonce >> 32,
index & 0xffffffff, index >> 32, LE(c + 8), LE(key + 16),
LE(key + 20), LE(key + 24), LE(key + 28), LE(c + 12) };
uint32_t wordout[16];
salsa20_words(wordout, in);//轮加密State
int i;
for (i = 0; i < 64; ++i) //将加密用的数组元素转换成异或
out[i] = 0xff & (wordout[i / 4] >> (8 * ((i+1) % 4)));
}
// enc/dec: xor a message with transformations of key, a per-message nonce and block index
void salsa20(uint8_t* message, uint64_t mlen, uint8_t key[32], uint64_t nonce) {
int i;
uint8_t block[64];
for (i = 0; i < mlen; i++) {
if (i % 64 == 0)
salsa20_block(block, key, nonce, i / 64);
message[i] ^= block[i % 64];
}
}
//Set 2, vector# 0:
// key = 00000000000000000000000000000000
// 00000000000000000000000000000000
// IV = 0000000000000000
// stream[0..63] = 9A97F65B9B4C721B960A672145FCA8D4
// E32E67F9111EA979CE9C4826806AEEE6
// 3DE9C0DA2BD7F91EBCB2639BF989C625
// 1B29BF38D39A9BDCE7C55F4B2AC12A39
int main() {
uint8_t key[32] = { 0 };
uint64_t nonce = 0;
uint8_t msg[64] = { 0 };
memset(msg, 0x90, 64);
salsa20(msg, sizeof(msg), key, nonce);
int i; for (i = 0; i < sizeof(msg); ++i)
printf("%02X", msg[i]); printf("\n");
}
ChaCha家族算法
chacha家族算法与Salsa20算法很相似。它也是利用State加密块对明文数据流进行加密。只不过它修改了State状态块数据的组成元素和核心运算QR函数部分并减少了轮数。我们可以通过下图比较两个算法的State块之间的区别
ChaCha核心QR参数改为以下代码,这里的16、12、8、7可以帮助我们逆向时区分ChaCha和Salsa20算法。
a += b; d ^= a; d <<<= 16;
c += d; b ^= c; b <<<= 12;
a += b; d ^= a; d <<<= 8;
c += d; b ^= c; b <<<= 7;
ChaCha轮运算减少
#define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
#define QR(a, b, c, d) ( \
a += b, d ^= a, d = ROTL(d,16), \
c += d, b ^= c, b = ROTL(b,12), \
a += b, d ^= a, d = ROTL(d, 8), \
c += d, b ^= c, b = ROTL(b, 7))
#define ROUNDS 20
void chacha_block(uint32_t out[16], uint32_t const in[16])
{
int i;
uint32_t x[16];
for (i = 0; i < 16; ++i)
x[i] = in[i];
// 10 loops × 2 rounds/loop = 20 rounds
for (i = 0; i < ROUNDS; i += 2) {
// Odd round
QR(x[0], x[4], x[ 8], x[12]); // column 0
QR(x[1], x[5], x[ 9], x[13]); // column 1
QR(x[2], x[6], x[10], x[14]); // column 2
QR(x[3], x[7], x[11], x[15]); // column 3
// Even round
QR(x[0], x[5], x[10], x[15]); // diagonal 1 (main diagonal)
QR(x[1], x[6], x[11], x[12]); // diagonal 2
QR(x[2], x[7], x[ 8], x[13]); // diagonal 3
QR(x[3], x[4], x[ 9], x[14]); // diagonal 4
}
for (i = 0; i < 16; ++i)
out[i] = x[i] + in[i];
}
病毒实例算法定位
我们回到当前样本,经过之前分析我们已知Salsa20和ChaCha算法都包含常量字符串"expand 32-byte k",根据这个线索我们可以直接在IDA中搜索该字符串,但是并没有搜到相关内容。
我们可以换一种角度思考,该字符串可能被处理成字节数组形式(16进制)。我们再次搜索该字符串的16进制数据。
0x61707865, 0x3320646E, 0x79622D32, 0x6B206574
搜索第一个4字节字符串数据。
定位到两个数据
找到静态常量值位置
将数据转换成字符串观察,正是算法特征数据。
根据交叉引用找到核心算法位置,看到常量7、9、13、18正是Salsa20的核心运算。
我们观察双轮轮数为10,奇数轮偶数轮各为10轮,一共20轮。由此识别算法为Salsa20。
加密算法库的识别技巧
由于椭圆曲线加密算法内容篇幅过长,这里就不在详细讲述其原理。如果大家想要了解更多细节可以参考这个gitbook链接,该书以数学和开发人员角度思考密码学十分值得学习。
https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc#elliptic-curves-over-finite-fields-calculations
经过上述我们的分析其实我们已经能确定了加密算法所在的几个函数。在加密函数中我们可以看到可疑的加密函数,因为这个函数传入了加密用的key引起了我们的注意。
我们可以根据不断跟进函数内部,观察密钥作为参数的函数。
多次跟进可以看到来到sub_408B69这个函数内部。
查看第一个函数可以看到大量的位移运算,这时需要引起我们的注意。因为根据我们分析之前的算法我们会得到一个规律,加密操作往往是通过位移、异或等操作体现的。该处很可能是在做某种加密。根据之前分析算法总结的规律可能是在利用密钥初始化。
跟进第二个函数,我们看到了非常明显的循环异或位移,而且有明显的常量这里再次引起我们的注意。很可能在做核心轮加密运算,那么我们可以尝试在算法中寻找常量特征。
最终我们在一个for循环中发现了一处常量值。
使用谷歌搜索,切换搜索关键字。可以看到这是与椭圆曲线加密算法相关的内容。有很大可能使用的是Curve25519这种曲线做运算
按照这个思路我们继续搜索Curve25519相关的源码(如果我是开发人员我不会花费大量时间去开发一个已经存在的算法,而是选择Github找到源码项目)
打开源码项目搜索关键常量121665,可以定位到源码位置。对比两个代码可以发现很多相似之处。稍有不同点就是IDA反编译出了一个循环。
我们再根据搜索功能找源码函数的内容,发现正是IDA编译的循环。由此我们可以确定该处使用了ECC密码系统进行加密处理。具体细节可以参照源码。
参考链接
https://blog.amossys.fr/sodinokibi-malware-analysis.html#figure3
https://www.intel471.com/blog/revil-ransomware-as-a-service-an-analysis-of-a-ransomware-affiliate-operation/
https://www.vergiliusproject.com/kernels/x86/Windows%207/SP1
https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works
https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc771525(v=ws.11)
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab
https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers#what-are-security-identifiers
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab
https://www.installsetupconfig.com/win32programming/accesscontrollistaclexample6_1.html
https://bbs.pediy.com/thread-266419.htm
https://bbs.pediy.com/thread-265692.htm
https://gchq.github.io/CyberChef/
https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
https://github.com/dhuertas/AES/blob/master/aes.c
https://github.com/polymorf/findcrypt-yara/issues/34
https://android.googlesource.com/platform/external/openssh/+/idea133/rijndael.c
https://zhuanlan.zhihu.com/p/42264499
https://www.anquanke.com/post/id/85656
https://docs.microsoft.com/zh-cn/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f
https://docs.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports
https://en.wikipedia.org/wiki/Salsa20
https://github.com/andres-erbsen/salsa20
https://zhuanlan.zhihu.com/p/36326221
https://zhuanlan.zhihu.com/p/44743146
https://zhuanlan.zhihu.com/p/66794410
https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc
最后于 2021-5-17 19:08
被独钓者OW编辑
,原因: