AES加密算法介绍

高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法,对称加密算法也就是加密和解密用相同的密钥,AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同,如下表所示:
image-20220510213651221

AES加密过程涉及到4种操作:字节代换(SubBytes)、行移位(ShiftRows)、列混合(MixColumns)和轮密钥加(AddRoundKey)。解密过程分别为对应的逆操作。由于每一步操作都是可逆的,按照相反的顺序进行解密即可恢复明文。加解密中每轮的密钥分别由初始密钥扩展得到。算法中16字节的明文、密文和轮密钥都以一个4x4的矩阵表示。

技术分享

0x01 字节代换

AES定义了一个S盒和一个逆S盒。

AES的S盒:

image-20220510214505595

状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出。例如,加密时,输出的字节S1为0x12,则查S盒的第0x01行和0x02列,得到值0xc9,然后替换S1原有的0x12为0xc9。状态矩阵经字节代换后的图如下:

image-20220510214603675

AES的逆S盒:同理

image-20220510214722557

0x02 行移位

行移位是一个简单的左循环移位操作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节,第3行左移3字节,逆变化同理,如下图所示:

image-20220510215200314

0x03 列混合

列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵,如下图的公式所示:

col

逆变化同理:

image-20220510215300454

0x04 轮密钥加

加密过程中,每轮的输入与轮密钥异或一次;因此,解密时再异或上该轮的密钥即可恢复输入。

0x05 密钥扩展

技术分享

AESEnc.exe逆向分析

image-20220511002635803

首先进入main函数,上来就是一堆var变量的赋值,我们不用管继续看后面,然后再开始了数组的赋值,这里的var_20被连续赋值了16个字节,最后加上了一个0,可以猜测这个var_20就是密钥,正好16位,改名为key,后面的var_50暂时不知道,反正是32个字节,然后又是一大堆赋值,这里的var_70一共32字节,还不知道是什么含义,暂且跳过,后面赋值的4个0也不知道啥玩意,不管他

image-20220511003605298

这里scanf函数的参数是var_50,可以知道var_50应该是用户输入的明文,重新改一下名字为data,下一个函数就算aesEncrypt,他的参数一共5个,第一个参数是var_20,也就是密钥key,第二个是常数10h,也就是16,第三个是var_50,也就是data,第四个是var_90,暂时未知,第五个是常值20h,为32那么下面就是要分析加密函数内部逻辑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
push    rbp
sub rsp, 1D0h
lea rbp, [rsp+80h]
mov [rbp+150h+Src], rcx
mov dword ptr [rbp+150h+Size], edx
mov [rbp+150h+arg_10], r8
mov [rbp+150h+arg_18], r9
mov rax, [rbp+150h+arg_18]
mov [rbp+150h+var_10], rax
lea rax, [rbp+150h+var_180]
mov [rbp+150h+var_18], rax
mov [rbp+150h+var_190], 0
mov [rbp+150h+var_188], 0
mov [rbp+150h+var_1A0], 0
mov [rbp+150h+var_198], 0
mov [rbp+150h+var_1B0], 0
mov [rbp+150h+var_1A8], 0
cmp [rbp+150h+Src], 0
jz short loc_401E96

先看看开头的初始化工作,rcx赋值给Src,所以Src应该是等于密钥key,edx存储的是常数16,猜测应该是key长度keylen,也就是Size,,后面的r8和r9分别是传进来的明文输入pt,以及密文存放的位置ct,还剩下一个参数32应该就是密文明文的长度,这里把各个参数名字都改一下方便阅读

image-20220511113006065

接下来是四个分支,前三个是判断密钥,明文,密文都不为空,最后一个是判断密钥长度是否大于等于16

image-20220511122255109

这里对第五个参数进行了判断,先赋值给eax,再和0Fh按位与,这一步就是检查最后四位是不是0,如果最后四位是0,那么就进入下一个分支,如果不是0就退出,这里应该是要确保密文明文的长度是16的倍数,便于加密

下一个函数就是密钥扩展函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
loc_401EEE:
mov edx, dword ptr [rbp+150h+keylen]
lea rax, [rbp+150h+realKEY]
mov r8, rdx ; Size
mov rdx, [rbp+150h+key] ; Src
mov rcx, rax ; void *
call memcpy
lea rdx, [rbp+150h+aesKEY]
lea rax, [rbp+150h+realKEY]
mov r8, rdx
mov edx, 10h
mov rcx, rax
call keyExpansion
mov [rbp+150h+var_4], 0
jmp loc_402016

loc_402016:
mov eax, [rbp+150h+var_4]
cmp [rbp+150h+inlen], eax
ja loc_401F31

memcpy函数传入了三个参数,首先是一个空指针,第二个是我们的传入的密钥key,第三个是传入的密钥长度src,那么这里的作用应该是把key复制到一个新的数组里面

接下来的keyExpansion也是一共三个参数,我们已知aes的密钥扩展函数是要将密钥循环扩展到44个字,那么可以猜测传入的第三个参数r8是扩展后的密钥存储的地方

接下来把var4赋值为0,并且和inlen进行比较,再进入循环,可以看出这个var_4是控制循环的变量,而后在循环末端又有var_4+10h,可见这个大循环一共两次,那么这里一定是将明文进行分组,依次加密

image-20220511212912525

再看循环内的流程,loadStateArray函数讲我们的明文pt处理后放入var_1B0中,那一步应该是初始化状态矩阵,紧随其后的addRoundKey也证明了这一点,传入的参数是var_1B0和var_18,通过回顾前面的变量定义可以知道,var_18是一个指向扩展后的aesKEY的指针,且var_18在左边的循环中每一轮都会增加10h,也就是16,所以这一步就是轮密钥加

然后下一个循环时用var_8控制的,初始值为1,每一轮加1,jle是小于等于,那左边这个循环一共执行九次,左边的大体流程是,subBytes,shiftRows,mixColumns,addRoundKey,第十次执行右边,但是少了一个mixColumns,

讲经过了10轮加密后的状态矩阵var_1B0传给var_10,也就是指向ct的指针,然后用storeStateArray复制过去

然后再跳回原来的大循环,那么总体框架就如此,这就是aes的十轮加密,每一轮都会更新状态矩阵,接下来再来分析每一个小函数的作用

0x01 subBytes

image-20220511215504825

这个函数的总体结构是第一层循环4次,第二层循环四次,正好对应了16个字节的状态矩阵4行4列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
loc_401A03:
mov eax, [rbp+var_4]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rdx, rax
mov eax, [rbp+var_8]
cdqe
movzx eax, byte ptr [rdx+rax]
movzx eax, al
mov edx, [rbp+var_4]
movsxd rdx, edx
lea rcx, ds:0[rdx*4]
mov rdx, [rbp+arg_0]
add rcx, rdx
cdqe
lea rdx, RijnDael_AES_LONG_404020
movzx eax, byte ptr [rax+rdx]
mov edx, [rbp+var_8]
movsxd rdx, edx
mov [rcx+rdx], al
add [rbp+var_8], 1

循环中的RijnDael_AES_LONG_404020便是S盒

0x02 shiftRows

image-20220511221149672

一共四层循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
loc_401A95:
mov eax, [rbp+var_4]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rax, rdx
movzx eax, byte ptr [rax]
movzx eax, al
shl eax, 18h
mov edx, eax
mov eax, [rbp+var_4]
cdqe
lea rcx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rax, rcx
movzx eax, byte ptr [rax+1]
movzx eax, al
shl eax, 10h
or edx, eax
mov eax, [rbp+var_4]
cdqe
lea rcx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rax, rcx
movzx eax, byte ptr [rax+2]
movzx eax, al
shl eax, 8
or edx, eax
mov eax, [rbp+var_4]
cdqe
lea rcx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rax, rcx
movzx eax, byte ptr [rax+3]
movzx eax, al
or edx, eax
mov eax, [rbp+var_4]
cdqe
mov dword ptr [rbp+rax*4+var_20], edx
mov eax, [rbp+var_4]
cdqe
mov edx, dword ptr [rbp+rax*4+var_20]
mov eax, [rbp+var_4]
shl eax, 3
mov ecx, eax
shl edx, cl
mov eax, [rbp+var_4]
cdqe
mov r8d, dword ptr [rbp+rax*4+var_20]
mov eax, 4
sub eax, [rbp+var_4]
shl eax, 3
mov ecx, eax
shr r8d, cl
mov eax, r8d
or edx, eax
mov eax, [rbp+var_4]
cdqe
mov dword ptr [rbp+rax*4+var_20], edx
mov eax, [rbp+var_4]
cdqe
mov eax, dword ptr [rbp+rax*4+var_20]
shr eax, 18h
mov ecx, eax
mov eax, [rbp+var_4]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rax, rdx
mov edx, ecx
mov [rax], dl
mov eax, [rbp+var_4]
cdqe
mov eax, dword ptr [rbp+rax*4+var_20]
shr eax, 10h
mov ecx, eax
mov eax, [rbp+var_4]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rax, rdx
mov edx, ecx
mov [rax+1], dl
mov eax, [rbp+var_4]
cdqe
mov eax, dword ptr [rbp+rax*4+var_20]
shr eax, 8
mov ecx, eax
mov eax, [rbp+var_4]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rax, rdx
mov edx, ecx
mov [rax+2], dl
mov eax, [rbp+var_4]
cdqe
mov ecx, dword ptr [rbp+rax*4+var_20]
mov eax, [rbp+var_4]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rax, rdx
mov edx, ecx
mov [rax+3], dl
add [rbp+var_4], 1

每一层都是把数组中的四个字节取出来,每次用shl把eax左移指定位数

0x03 mixColumns

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
push    rbp
push rbx
sub rsp, 58h
lea rbp, [rsp+80h]
mov [rbp-20h+arg_0], rcx
mov [rbp-20h+var_40], 2
mov [rbp-20h+var_3F], 3
mov [rbp-20h+var_3E], 1
mov [rbp-20h+var_3D], 1
mov [rbp-20h+var_3C], 1
mov [rbp-20h+var_3B], 2
mov [rbp-20h+var_3A], 3
mov [rbp-20h+var_39], 1
mov [rbp-20h+var_38], 1
mov [rbp-20h+var_37], 1
mov [rbp-20h+var_36], 2
mov [rbp-20h+var_35], 3
mov [rbp-20h+var_34], 3
mov [rbp-20h+var_33], 1
mov [rbp-20h+var_32], 1
mov [rbp-20h+var_31], 2
mov [rbp-20h+var_14], 0
jmp short loc_401D14

开头是一段赋值,这里赋值的是列混合矩阵,列混合的操作就是用矩阵乘法实现的,但是这里面的流程图非常混乱,我直接看了伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
__int64 __fastcall mixColumns(__int64 state)
{
int v1; // ebx
int v2; // ebx
int v3; // ebx
char v5; // [rsp+20h] [rbp-60h]
char v6; // [rsp+21h] [rbp-5Fh]
char v7; // [rsp+22h] [rbp-5Eh]
char s[37]; // [rsp+23h] [rbp-5Dh]
int j; // [rsp+48h] [rbp-38h]
int i; // [rsp+4Ch] [rbp-34h]
_DWORD tmp[4]; // [rsp+50h] [rbp-30h] BYREF

v5 = 2;
v6 = 3;
v7 = 1;
s[0] = 1;
s[1] = 1;
s[2] = 2;
s[3] = 3;
s[4] = 1;
s[5] = 1;
s[6] = 1;
s[7] = 2;
s[8] = 3;
s[9] = 3;
s[10] = 1;
s[11] = 1;
s[12] = 2;
for ( i = 0; i <= 3; ++i )
{
for ( j = 0; j <= 3; ++j )
*((_BYTE *)&tmp[i - 8] + j) = *(_BYTE *)(state + 4i64 * i + j);
}
for ( i = 0; i <= 3; ++i )
{
for ( j = 0; j <= 3; ++j )
{
v1 = GMul((unsigned __int8)*(&v5 + 4 * i), (unsigned __int8)s[j + 13]);
v2 = GMul((unsigned __int8)*(&v6 + 4 * i), (unsigned __int8)s[j + 17]) ^ v1;
v3 = GMul((unsigned __int8)*(&v7 + 4 * i), (unsigned __int8)s[j + 21]) ^ v2;
*(_BYTE *)(state + 4i64 * i + j) = v3 ^ GMul((unsigned __int8)s[4 * i], (unsigned __int8)s[j + 25]);
}
}
return 0i64;
}

第一个循环时把状态数组state放入临时数组tmp中,而第二个循环中是把s数组的行和tmp数组的列进行分别相乘相加,也就是矩阵乘法,但是这里的乘法是在G(2^8)下得到的,函数GMul就是实现这个功能的,循环内一共有四个表达式,计算的v1保留,第二次计算的结果和v1按位异或得到v2一直重复四次,最终的结果放回状态数组state中

0x04 addRoundKey

image-20220511223646024

轮密钥加的结构主要由两个循环构成,一共16次,每次都是把轮密钥的对应位和状态矩阵对应位进行异或

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
loc_401921:
mov eax, [rbp+var_8]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_8]
add rax, rdx
mov edx, [rax]
mov eax, 3
sub eax, [rbp+var_4]
shl eax, 3
mov ecx, eax
shr edx, cl
mov eax, edx
mov ecx, eax
mov eax, [rbp+var_8]
cdqe
mov edx, [rbp+var_4]
movsxd rdx, edx
shl rdx, 2
add rdx, rbp
add rax, rdx
sub rax, 20h ; ' '
mov [rax], cl
mov eax, [rbp+var_4]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rdx, rax
mov eax, [rbp+var_8]
cdqe
movzx r8d, byte ptr [rdx+rax]
mov eax, [rbp+var_8]
cdqe
mov edx, [rbp+var_4]
movsxd rdx, edx
shl rdx, 2
add rdx, rbp
add rax, rdx
sub rax, 20h ; ' '
movzx ecx, byte ptr [rax]
mov eax, [rbp+var_4]
cdqe
lea rdx, ds:0[rax*4]
mov rax, [rbp+arg_0]
add rdx, rax
xor ecx, r8d
mov eax, [rbp+var_8]
cdqe
mov [rdx+rax], cl
add [rbp+var_8], 1

这里重复的大段代码其实都是在矩阵中取出对应数字的操作,先计算出本轮密钥的起始地址,再赋值到r8d中,再用r8d和状态矩阵的对应位置进行异或

0x05 flag解密

image-20220511225045842

在最后的循环比较中,cipher和var_90分别赋值给了edx和eax,然后比较了他们的低8位,也就是一个字节,一共循环32次,那么只需要我们加密后得到的var_90和预定义的cipher数组每一位都相等即可,我们可以使用aes的解密工具得出答案

image-20220511225354424