逆向魔法师学习法术的目标(去想,去学,去征服)

  1. des加密(已解决)
  2. frida的使用,安卓逆向
  3. 去平坦化
  4. aes加密
  5. 混淆
  6. 反调试
  7. sm4
  8. go与rust的处理
  9. linux内核与gdb
  10. z3, dfs, smc

还有每日的练题,语言和wp学习

1.try-catch与结构化异常处理(SEH)(全称[Structure Exception Handler]

seh是windows系统对c/c++程序做的语法拓展,用于处理异常事件的程序控制结构,

#include <windows.h>//包含windows中的api与seh的相关定义

seh含有-try{}-catch函数

与-try{}_final函数

由于ida的反编译语言是c/c++,于是我了解一下c/c++中的seh

主要是(windows的seh检验)(块检验)

1
2
3
4
struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD* Next; // 指向下一个节点的指针
PEXCEPTION_ROUTINE Handler; // 异常处理函数指针
};

什么是链表:像链条一样存储结构,与结构体不同的是结构体中多了个结构体指针,A结构体存了一些结构体成员还存了B结构体指针,

B结构体存了相同的结构体成员和指向C的结构体指针,以此类推

下面举例说明struct * p, *head, * tail(链表有头有尾, head标记头节点, tail标记尾节点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct *p, *head, *tail;
p = (struct STUDENT)malloc(sizeof(struct STUDENT));
head = p;//由于为空链表,head仅需标记,无需填入内容
tail = p;//尾节点为NULL
head->next = NULL;//next指向NULL
//接下来填入节点
for(int i = 0; i<n ;i++)
{
p = (struct STUDENT)malloc(sizeof(struct STUDENT));
scanf("%s", p->name);
scanf("%d", &p->id);
tail->next = p;
tail = p;//尾节点指向新的开辟的节点
tail->next = NULL;
}
p = head->next;
while(p!=NULL)
{
printf("%s, %d", p->name, ->id);
p = p->next;
}//链表的遍历

一个节点是一个有异常处理能力的作用域

1
2
3
4
5
6
7
try {</p>
// 可能抛出异常的代码</p>
throw std::runtime_error("发生错误");</p>
} catch (const std::exception& e) {</p>
// 处理异常</p>
std::cerr << "捕获到异常: " << e.what() << '\n';</p>
}
1
2
3
4
5
6
catch (const std::exception& e)
// ↑ ↑ ↑ ↑
// │ │ │ └─ 引用变量名
// │ │ └───── 异常类名
// │ └─────────────── 命名空间
// └───────────────────── 常量限定符

异常有不同类型

应用

1.异常传播

如果一个函数中的try-catch没有捕获到异常,异常会向上抛给调用者,直到被合适的catch块捕获或导致程序终止。

(try抛出的异常类型与catch的类型不匹配)

2.多catch块

可以有多个catch块来捕获不同类型的异常,按照从上至下的顺序匹配。

忽略异常传播(上层调用异常处理无法判断)

不恰当catch块

2.des加密

des是经过festial网络的块加密,并且对于标准的中字节处理是大端序处理,而des加密是对称加密,des处理字节的方式是大端序,接受64位明文和64位密文返回64位明文

那如果不够64位,根据一定的填充法填充64位

什么是festial网络?

1.将明文分为左右两个部分

2.将右半部分作为输入,通过f函数与子密钥进行运算

3.将左面的明文与f函数的输出异或,生成新的有半部分

4.左右部分进行交换,进入下一轮

PKCS#7 方案

PKCS填充方法

  • 计算需要填充的字节数 pad_len = block_size - (len(data) % block_size)
  • 每个填充字节的值都等于 pad_len
  • 如果数据长度恰好是块大小的整数倍,则额外填充一个完整的块(这是因为des解密会检索最后一个数据:解密算法可以总是去除最后一个字节的值作为填充长度,然后移除相应数量的字节)

比如“abcde”填充9-len(”abcde“)%9 = 4

即填充\x04

密钥生成机制

文献参考

DES - SGSG’s Blog

密钥是64位,但是实际上真正的密钥是56位,因为原密钥有8位是校验位(每字节最后一位为校验位,保证1出现的次数为奇数)

之后这个密钥再拆分成左右两个28位的密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pair<uint32_t, uint32_t> PC1(uint64_t key)    //pair<uint32_t, uint32_t>在utility头文件,返回相当于c结构体有两个结构变量uint_t的数据类型
{
uint8_t pc1L[] = {57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36};
uint8_t pc1R[] = {63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4};
pair<uint32_t, uint32_t> keypair{0, 0};
for(int i = 0; i<28; i++)
{
keypir.first |= (key[i]>>(64 - pc1L[i]))<<(27-i);
keypair.second |= (key[i]>>(64 - pc1R[i]))<<(27-i);
}
return 0;
}

两个生成的28位子密钥先要左旋特定位数(循环左移)(右旋就是循环右移)

pc2中有一个盒决定选择哪48位作为密钥

新的子密钥由上一个生成

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
void keyGen(pair<uint32_t, uint32_t> keypair)
{
uint8_t offest[] = {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};
for(int i = 0; i<16; i++)
{
keypair.first = leftRotate(keypair.first, offsetr[i]);
keypair.second = leftRotate(keypair.second, offset[i]);
keys[i] = PC2(((uint64_t)keypair.first<<8)|keypair.second);
}
}
uint64_t PC2(uint64_t key)
{
uint8_t pc2[]={14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32
};
uint64_t subKey = 0;
for(int i = 0; i<48; i++)
{
subKey |= ((key>>(56-pc[i]))&1)<<(47-i);
}
return subkey;
}

至此生成了16个48位密钥

至此密钥已成,将明文分为左右32位进入feistel网络

接下来为加密代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void desEncrypt(uint64_t * plain, uint64_t key)
{
keyGen(PC1(key));
uint64_t afterIp = IP(* plain);
uint32_t l = afterIP>>32, r = afterIp&0xFFFFFFFF;
for(int i = 0; i<16; i++)
{
pair<uint32_t, uint32_t> lr = goRound(l, r, Keys[i]);
l = lr.first;
r = lr.second;
}
*plain = ((uint64_t)r<<32)|l;
*plain = FP(*plain);
}

开始时对明文进行ip置换之后加密后对密文进行fp置换得到最后的密文,当然这仅仅只是简单的映射(一 一对应关系,一个集合的元素对应另一个集合的元素)

接下来就是ip与fp置换

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
uint64_t IP (uint64_t message)
{
uint64_t afterIP = 0;
uint8_t ip[] = {58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7};
for(int i = 0; i<64; i++)
{
afterIp |= ((message>>(64-ip[i]))&1)<<(63-i));
}
return afterIP;
}
uint64_t FP(uint64_t message)
{
uint64_t afterFP = 0;
uint8_t fp[] = {40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25};

for(int i = 0; i<64; i++)
{
afterFP |= ((message>>(64-fp[i]))&1)<<(63-i);
}
}

feistel轮函数如下所示

1
2
3
4
5
6
7
8
9
10
11
12
uint32_t feistel(uint32_t a, uint64_t key)
{
uint64_t t = expand(a)^key;
uint32_t afterS = S(t);
afterS = P(afterS);
return afterS;
}
pair<uint32_t, uint32_t> goRound(uint32_t l, uint32_t r, uint64_t key)
{
return({r, l^feistel(r, key)});
}

拓展与映射

先通过e盒拓展到48位与密钥异或,之后通过映射成32位,再由P盒最后一次置换输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
uint64_t expand(uint32_t a)
{
uint8_t e[] = {32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1};
uint64_t afterE = 0;
for(int i = 0, i<48; i++)
{
afterE |= ((a>>(32-e[i]))&1)<<(47-i);
}
return afterE;
}

接下来要有48位变为32位,这是映射关系

48是8*6而32是8乘以4

48位会每6位进入一个s盒(4行*16列),例如:(总共8个s盒)

  • 1-6位(总共48位中的前6位) → 输入给 S盒1

  • 7-12位 → 输入给 S盒2

    每块的第一个和第六个位用于索引行,其他4个位用于索引列,最会得到一个32位输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    uint64_t S(int64_t a)
    {
    uint32_t res = 0;
    for(int i = 0; i<8; i++)
    {
    uint8_t row = (((a>>(i*6))&0x20)>>4)|((a>>(i*6))&1);
    uint8_t col = ((a>>(i*6))&0x1E)>>1;
    res |= S_box[i][row*16+col]<<(4*i);
    }

    }

    P置换就是再次打乱S盒的输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    uint32_t P(uint64_t a)
    {
    uint8_t p[] = {16, 7, 20, 21,
    29, 12, 28, 17,
    1, 15, 23, 26,
    5, 18, 31, 10,
    2, 8, 24, 14,
    32, 27, 3, 9,
    19, 13, 30, 6,
    22, 11, 4, 25};
    uint32_t afterP = 0;
    for(int i = 0; i<32; i++)
    {
    afterP |= ((a>>(32-p[i]))&1)<<(31-i);
    }
    return afterP;
    }

    des

    小结一下

    1.des加密先由pc1函数由64位去校验位并分为左右两个28位的密钥部分

    2.之后进入keygnerate函数进行16循环,每个循环中先左旋特定位数,之后合成56位进行密钥生成

    3.接下来明文先进行ip置换,,之后将64位明文分为两个32明文,此后在这进行16次轮加密,每轮后对右面的32位密钥先进行e盒拓展再映射到s盒子在进行p盒置换

    4.即将结果first与seond部分交换后返回,共16轮

    5.最后将secnd部分提到高32位之后进行fp置换得到密文

    特征

    由于des加密十分庞大,所以可以通过观察不变的盒子来判断

    S盒

    S盒引入了非线性操作,且S盒的每一位都是设计抵抗差分和线性密码分析,一般不会修改S盒

    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
    uint8_t S_box[8][64] = 
    {{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
    0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
    4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
    15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13},
    {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
    3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
    0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
    13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9},
    {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
    13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
    13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
    1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12},
    {7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
    13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
    10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
    3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14},
    {2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
    14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
    4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
    11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3},
    {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
    10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
    9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
    4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13},
    {4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
    13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
    1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
    6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12},
    {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
    1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
    7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
    2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}};


    p盒

    p盒子的特殊设计使得每轮由同一s盒子输出的4个bit位在下一轮都能分散到4个不同的s盒进行处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    uint8_t p[] = 
    {16, 7, 20, 21,
    29, 12, 28, 17,
    1, 15, 23, 26,
    5, 18, 31, 10,
    2, 8, 24, 14,
    32, 27, 3, 9,
    19, 13, 30, 6,
    22, 11, 4, 25};

e盒是将32位拓展为48位,增强混淆能力

1
2
3
4
5
6
7
8
9
10
uint8_t e[] = 
{32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1};

但是des加密已经不安全,且加密严重依赖系统程序,且des加密为大端序,现代大多数计算机多为小端序,所以难以实现

由于为对称加密,只要知道16个子密钥就可以根据feistel网络特性将密文当输入,密钥倒着用反推回去就

3.base58

base58是在base64上去除了比较混淆的字符(0和O,小写字母l与大写字母I,+与*)

剩下的按09AZa~z排列

无法用整字节转换,所以要将一串转为base58

常见到len*138/100

这其实是len*log(256) = log(100)乘以base58所需的最大长度

base58加密的关键一步是大数除法

怎么解大数除法,这就要模拟,什么是模拟,模拟就是在纸上算然后将思路以计算机语言实现

输入是大多是字符串,例如256进制的有0x01, 0xfa, 0xff那么让他们除以两个在一起除58,得到的余数传给下一个

就类似数学中的除法,取余,这里可以想一下计算机中的十进制和数学上的除法就可以理解了

接下来看base58加密与解密

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

// Base58字符表(去除了容易混淆的字符)
static const char BASE58_ALPHABET[] =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

// ==================== Base58 编码 ====================

// Base58编码函数
char* base58_encode(const uint8_t* data, size_t data_len) {
if (data == NULL || data_len == 0) {
return NULL;
}

// 统计前导0的数量(前导0编码为'1')
size_t zeros = 0;
while (zeros < data_len && data[zeros] == 0) {
zeros++;
}

// 计算最大输出长度:log58(256) ≈ 1.3656
size_t max_output_len = (data_len - zeros) * 138 / 100 + 2;
uint8_t* buffer = (uint8_t*)calloc(max_output_len, sizeof(uint8_t));
if (buffer == NULL) return NULL;

// 核心编码算法:将256进制转换为58进制
size_t buffer_len = 0;
for (size_t i = zeros; i < data_len; i++) {
int carry = data[i];

// 将carry加到已有的58进制数中
for (size_t j = 0; j < buffer_len || carry > 0; j++) {
if (j == buffer_len) {
buffer_len++;
}
carry += 256 * buffer[j];
buffer[j] = carry % 58; //核心加密就是x*58^58(j-1)+……+x*58^0所以下面转buffer最后一位仅需模最低为乘256模58就可,而余数留着接下来计算
carry /= 58;
}
}

// 构建输出字符串
size_t output_len = zeros + buffer_len;
char* output = (char*)malloc(output_len + 1);
if (output == NULL) {
free(buffer);
return NULL;
}

// 添加前导'1'
for (size_t i = 0; i < zeros; i++) {
output[i] = BASE58_ALPHABET[0];
}

// 添加58进制数字(注意buffer是反序的)
for (size_t i = zeros; i < output_len; i++) {
output[i] = BASE58_ALPHABET[buffer[buffer_len - (i - zeros) - 1]];
}

output[output_len] = '\0';
free(buffer);
return output;
}

// ==================== Base58 解码 ====================

// 查找Base58字符对应的值
static int base58_char_value(char c) {
if (c >= '1' && c <= '9') return c - '1';
if (c >= 'A' && c <= 'H') return c - 'A' + 9;
if (c >= 'J' && c <= 'N') return c - 'J' + 17;
if (c >= 'P' && c <= 'Z') return c - 'P' + 22;
if (c >= 'a' && c <= 'k') return c - 'a' + 33;
if (c >= 'm' && c <= 'z') return c - 'm' + 44;
return -1; // 非法字符
}

// Base58解码函数
uint8_t* base58_decode(const char* encoded, size_t* output_len) {
if (encoded == NULL) {
if (output_len) *output_len = 0;
return NULL;
}

size_t encoded_len = strlen(encoded);
if (encoded_len == 0) {
if (output_len) *output_len = 0;
return NULL;
}

// 统计前导'1'的数量('1'对应值0)
size_t ones = 0;
while (ones < encoded_len && encoded[ones] == '1') {
ones++;
}

// 将Base58字符串转换为数值
uint8_t* values = (uint8_t*)malloc(encoded_len - ones);
if (values == NULL) return NULL;

for (size_t i = ones; i < encoded_len; i++) {
int value = base58_char_value(encoded[i]); //base58加密后的结果转对应的下标
if (value < 0) {
free(values);
if (output_len) *output_len = 0;
return NULL;
}
values[i - ones] = value;
}

// 计算最大输出长度:log256(58) ≈ 0.733
size_t max_output_len = (encoded_len - ones) * 733 / 1000 + 2;
uint8_t* buffer = (uint8_t*)calloc(max_output_len, sizeof(uint8_t));
if (buffer == NULL) {
free(values);
return NULL;
}

// 核心解码算法:将58进制转换为256进制
size_t buffer_len = 0;
for (size_t i = 0; i < encoded_len - ones; i++) {
int carry = values[i];

for (size_t j = 0; j < buffer_len || carry > 0; j++) {
if (j == buffer_len) {
buffer_len++;
}
carry += 58 * buffer[j]; //58进制转10进制数,然后激素那,没反序,引入新的位原来都要乘58
buffer[j] = carry % 256;
carry /= 256;
}
}

// 构建输出数据
size_t result_len = ones + buffer_len;
uint8_t* result = (uint8_t*)malloc(result_len);
if (result == NULL) {
free(values);
free(buffer);
if (output_len) *output_len = 0;
return NULL;
}

// 添加前导0
memset(result, 0, ones);

// 复制数据(注意buffer是反序的)
for (size_t i = 0; i < buffer_len; i++) {
result[ones + i] = buffer[buffer_len - i - 1];
}

if (output_len) *output_len = result_len;

free(values);
free(buffer);
return result;
}

4.python逆向

1.常见的情况(pyc文件介于源码与字节码之间,pyd文件是动态链接库)

一.pyc文件这届uncompyle6反编译(进到python3.8版本)

在线Python pyc文件编译与反编译

二.给了个txt文件里面是pyc字节码

1.读取py字节码2.根据opcode文件查询意思

三.exe打包的py文件

1.通过脚本变为结构体和文件2.再把时间属性和魔法数字放回去保存3.uncompyle6

四.pyc加花

1..uncompyle6和字节码判断是否加花2.读取co_code的长度3.去掉花并修改co_code长度4.uncompyle6

2.pyc文件直接uncompyle6反编译

1
python -m xx.py   # m为module,直接将python模块作为脚本运行,生成pyc文件

而在命令符中python .\xxx.pyc可以直接运行

3.二.给了个txt文件里面是pyc字节码

python3的开头前4个字节为魔法数字,之后4个字节为位字段(bit field),

接着4字节为时间戳,再4字节为源文件文件大小,然后为序列化的代码对象

接下来是对位字段的解释,位字段是为了能够复现编译的结果

根据python的官方文档

1
2
如果位字段为 0,则该 pyc 是传统的基于时间戳的 pyc
如果位字段的最低位被设置,则该 pyc 是基于哈希的 pyc。

有操作数指令:3 字节**(1字节操作码 + 2字节操作数)

Python 3.0-3.5

  • 依然使用 3 字节指令格式

Python 3.6+

  • 改为 2 字节指令格式:
    • 第1字节:操作码
    • 第2字节:参数(如果指令需要参数)

010editor打开pyc文件python2的前8位是python2的魔法数字,python3是前16位

1
2
3
4
5
import dis, marshal         //marshal库用来装载,dis用于反编译
f = open("1.cpython-313.pyc", "rb").read()
code = marshal.loads(f[16:]) //将序列化的代码转换为可执行的二进制文件
dis.dis(code)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0           RESUME                   0        

1 LOAD_CONST 0 (<code object check at 0x000001F74B8489D0, file "D:\homework\learn\1.py", line 1>) //放在栈顶
MAKE_FUNCTION
STORE_NAME 0 (check)

6 LOAD_NAME 1 (print)//从co_name[0]中加载print内置函数并放栈顶
PUSH_NULL //null用于错误处理和性能优化
LOAD_NAME 0 (check)
PUSH_NULL
CALL 0 //先取0个参数,之后nullpop,之后执行check返回result
CALL 1
POP_TOP
RETURN_CONST 1 (None)

LOAD_CONST将值推放到栈顶,

load_fast把这个加载局部变量

load_global是将全局变量或内置函数推到栈顶

LOAD_NAME 将关联值推送到堆栈

STORE_FAST存储局部变量(将tos(栈)储存将存到本地)

GET_iter(获取迭代器)FOR_ITER(执行迭代器)

LOAD_FAST`临时从局部变量中加载到栈中

SETUP_LOOP 28 (to 30)//循环开始,28是相对偏移30是绝对指令位置(当前指令位置+相对偏移)

BINARY_SUBTRACT tos = tos1 - tos(tos为栈顶tos1为栈的下一位)

BINARY_MOUDULO tos = tos1 % tos

BINARARY_RSHIFT num(tos = tos1>>tos) //之后原来的tos1和tos替换为新的tos

4.将一堆pyc文件打包成exe

3

看图标知道是pyc打包的程序,并非标准exe文件,是有着exe头戴着python解释器的,以pyc文件为基础的exe文件,也当然就不用ida

先修头,头文件的二进制在0xE3之前(#define TYPE_CODE 0xE3,0xE3是marshal模块对代码对象的类型标记)

直接解包python pyinstxtractor.py “绝对路径”,这里由于头被修改

python文件打包成exe文件会将pyc文件的部分信息抹去而这些信息都在struct文件中

5.加花的pyc

1..uncompyle6和字节码判断是否加花(加花会报错)

2.读取co_code的长度(指令的长度由字节码指令序列决定,指令增减后都要改大小)

len(code.co_code)一个操作3字节

3.去掉花并修改co_code长度

先uncompyle6反编译看花是什么,然后找到操作码对应的的16进制数,然后删去()

之后根据操作在内存的操作数值为多少在010editor找到对应的,修改对应的值(len-删去的字节)

opcode可在python文件夹的include文件夹opcodexxx.h文件下

4.uncompyle6

5.mfc逆向

mfc程序是根据c++的mfc库编写的程序,主要是使用xspy去解决问题

mfc要找程序基址与函数的偏移

1

主要是找message map=0x0000000140004E80(201500_ezre_dump_SCY.exe+ 0x004e80 )(消息映射表区域)

关注确定按钮即oncommand响应按钮(处理菜单、控件和快捷键)

接着找到偏移

然后使用detect it easy软件找到基址

由于mfc是消息映射,可通过导入结构体办法还原

接着开始导入结构体,在view菜单栏下的open subview->local type(本地类型)

用于存储数据库的基本类型(如struct,enum(枚举),union(联合体),typedef别名,指针类型)

插入两个符号表

1
2
3
4
5
6
7
8
9
10
struct AFX_MSGMAP_ENTRY
{
UINT nMessage;
UINT nCode;
UINT nID; // ID号
UINT nLastID;
UINT_PTR nSig;
void (*pfn)(void); // 该ID号的消息处理函数
};

1
2
3
4
5
6
struct AFX_MSGMAP
{
const AFX_MSGMAP *(__stdcall *pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY *lpEntries; // 消息处理函数映射表
};

在c syntax板块插入这两个结构体(自动识别为struct)

只好找到消息映射表的地方(基址+偏移)

2

直接alt+q先导入AFX_MSGMAP

得到 stru_140004E80 AFX_MSGMAP <offset ?GetMessageMap@CDialogEx@@MEBAPEBUAFX_MSGMAP@@XZ,
.text:0000000140004E80 ; DATA XREF: sub_140001600↑o
.text:0000000140004E80 offset qword_140004E90> ; CDialogEx::GetMessageMap(void)

而 offset qword_140004E90> 正是消息处理函数

有几个消息处理函数就导入几个AFX_MSGMAP_ENTRY(关注id和消息处理函数)

3

然后就找到了

6.aes加密

今天也是来学习aes加密(aes加密本来上半年例会内容,今天也是来学习相关内容)

先回顾一下上面的des加密,明文是64字节,密钥是64字节,密钥先变为两个28字节之后左旋然后变为48字节

明文先ip置换,之后通过festial网络加密,然后fp置换得到最后结果

当然现在des加密已被证明为不安全的,aes加密就是取代的des加密的,其为经典的块加密算法,可以使用128bit(16字节),192bit(24字节)与256bit(32字节)三种长度的密钥,而des加密仅仅是64bit(4字节的密钥),aes的数据块固定为128字节(16字节),

整个加密在4*4的矩阵(16字节),aes加密基于代换-置换网络,主要操作有轮密钥加、字节代换、行位移、列混合四种,其数值运算的相关操作都是定义在GF(28)//伽罗瓦域(计算机尽头是数学)

在这样一个有限域主要是防止数值溢出,aes使用大端序(高位地址)

GF(伽罗瓦域/有限域)

有限域顾名思义是有限的域,域又是什么域是一种具有加法和减法的集合,其可以进行加、减、乘、除(除了0)运算,其具有乘法的结合律,但是其加法和乘法的与四则运算的加法和乘法有有些不同

1.封闭性:对任意 a,b∈Fa,bF,a+b∈Fa+bF,a⋅b∈Fab∈F

2.交换律:对任意 a,b∈F,a+b=b+a,a⋅b=b⋅a(突然想到了一般的矩阵不具有交换性,可逆矩阵是有交换性的)

3.结合律:对任意 a,b,c∈Fa,b,cF,(a+b)+c=a+(b+c)(a+b)+c=a+(b+c),(a⋅b)⋅c=a⋅(b⋅c)(ab)⋅c=a⋅(bc)

4.分配律:对任意 a,b,c∈Fa,b,cF,a⋅(b+c)=a⋅b+a⋅ca⋅(b+c)=ab+ac

5.单元存在性:存在0,1∈F,使得对任意 a∈FaF,a+0=a,a⋅1=a

6逆元存在,对任意 a∈FaF

存在 −a∈F,使得 a+(−a)=0(加法逆元)

若 a≠0,存在a-1∈F,使得 a⋅a-1=1(乘法逆元)

轮密钥加

二进制中的异或相当于数学中的加发

0+0 = 0

0+1=1

1+0=1

1+1=0

文献参考

AES - SGSG’s Blog

AES加密(2): GF(256)域 - 知乎

特征

以AES128为例子,在主循环中依次执行字节替换、行位移、列混合、轮密钥加四个操作共9轮,第10轮不进行列混合(des仅有ip、fp置换还有密钥生成),不过编译器可能将这个加密操作优化的面目全非,逆向是不一定能看出来

其次是s盒,aes算法有专门为字节代换的256位s盒,标准s盒如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

const uint8_t S[16][16] = {0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01,0x67,0x2b, 0xfe,0xd7,0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};

还有十个轮常量用于密钥拓展

标准轮常量

1
2
3
4
5
const uint32_t Rcon[10] = {0x01000000, 0x02000000,
0x04000000, 0x08000000
0x10000000, 0x20000000,
0x40000000, 0x80000000
0x1b000000, 0x36000000}

数值溢出前是后一个是前一个的两倍

实现

AES首先进行密钥拓展,这与des加密相同,AES由原来的4个uint32_t为基础,拓展出10轮(毕竟是10轮加密),总计44个共11组密钥,因为加密是在4*4的矩阵(果然计算机离不开数学)上进行操作,且矩阵是列优先排列的(先填满一列再填满下一列),所以先将密文按快转化为矩阵加密完成后再转换回来

s0, s1, s2, s3, s4 ……s15, s16

变成

s0 s4 s8 s12
s1 s5 s9 s13
s2 s6 s10 s14
s3 s7 s11 s15

一开始先进性初始密钥加操作,然后循环9轮,最后进行第十轮,第十轮不进行列混合

一下为AES加密的主函数和状态矩阵的生成函数

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
void convertToStateArry(uint8_t s[16], uint8_t a[4][4])
{
for(int i = 0; i<4; i++)
{
for(int j= 0; j<4; j++)
{
a[j][i] = s[i*4+j];
}
}
}
void AES(uint8_t * message, uint64_t messagelen, uint8_t * key) //计算机蓦然出理都是小端序,而aes加密是大端序
{
extendKey(key);
uint8_t sArry[4][4];
for(int i = 0; i<messagelen; i+=16)
{
convertToStateArry(message+i, sArry); //转换为状态矩阵
addRoundKey(sArry, 0); //初始轮密钥加
for(int j = 1; j<10; j++)
{
subbytes(sArry); //字节代换
shiftRows(sArry); //行移动
columnMix(sArry); //列混合
addRound(sArry, j); //标准轮密钥加
}
subbytes(sArry);
shiftRows(sArry);
addRound(sArry, 10); //最终轮密钥加
convertTostr(sArry, message+i) //将状态矩阵转换为一元数组的密文
}
}

字节拓展操作首先把密钥按大端序转为32位字W0W3,按照以下规则拓展出W4W43

**如果是每组的开头(i 能被 4 整除):**W[i] = W[i-4] ⊕T(W[i-1])

**其他情况(i 不能被 4 整除):**W[i] = W[i-4] ⊕ W[i-1]

T是密钥拓展所用的轮函数,由字节偏移,字节代换和轮常量异或三个操作组成

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
uint32 T(uint32_t w, int round)
{
w = (w>>24)|(w<<8); //字节偏移,循环左移一个字节
uint8_t temp[4];
convertToWordArray(w, temp); //转换为字节数组
for(int i = 0; i<4; i++)
{
temp[i] = S[temp[i]>>4][temp[i]&0x0F]; //字节代换
}

w = convertToWord(temp);
w ^= Rcon[round]; //与轮常量常数异或
return w;
}
void extend(uint8_t * key)
{
for(int i = 0; i<4; i++)
{
w[i] = getBigEndian(key + i*4); //转换为大端序
}
for(int i = 4, j = 0; i<44; i++) //密钥拓展
{
if(i%4==0)
{
w[i] = w[i-4]^T(w[i-1], j);
j++;
}
else
w[i] = w[i-4]^w[i-1];
}
}

轮密钥加操作将对应的密钥与数据进行异或,因为在GF(256)下的加法与异或或等价

(其中的每一个数可表示为(1或者0)*x7+0*x6+…….+0/1*x0,为了在这里面对于加法封闭,所以加法要模2,这就使得其加法与异或效果相同)

1
2
3
4
5
6
7
8
9
10
11
12
void addRound(uint8_t sArry[4][4], int round)   //轮密钥加
{
uint8_t wArry[4];
for(int i = 0; i<4; i++)
{
convertToWordArry(w[round*4+i], wArry); //转换为字节数组
for(int j = 0; j<4; j++)
{
sArry[j][i] ^= wArry[j]; //异或操作,由于是列优先,所以先将一列异或
}
}
}

行位移就是将矩阵第一行不动,第二行循环左移1,第三行2位,第三行3位,可以直接模拟(心中想一下怎么出来的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void shiftRow(uint8_t sArry[4][4])
{
uint8_t temp = sArry[1][0];
sArry[1][0] = sArry[1][1];
sArry[1][1] = sArry[1][2];
sArry[1][2] = sArry[1][3];
sArry[1][3] = temp;

uint8_t temp1 = sArry[2][0];
uint8_t temp2 = sArry[2][1];
sArry[2][0] = sArry[2][2];
sArry[2][1] = sArry[2][3];
sArry[2][2] = temp1;
sArry[2][3] = temp2;

uint8_t temp3 = sArry[3][0];
sArry[3][0] = sArry[3][3];
sArry[3][3] = sArry[3][1];
sArry[3][2] = sArry[3][0];
sArry[3][1] = temp;
}

鸽了好几天了,接着写(开学前完了一会,今天那晚下学期资料就来学AES加密,不过有点小忘了)

字节代换就是取一个字节高四位作为行号,低四位作为列号在s盒中查表(上面密钥生成函数也有字节代换)

1
2
3
4
5
6
7
8
void subbytes(uint8_t sArry[4][4])
{
for(int i = 0; i<4; i++)
{
for(int j = 0; j<4; j++)
sWarry[i][j] = S[sWarry[j][i]>>4][sWarry[j][i]&0xF]; //字节代换
}
}

列混合操作是使用事先准备的4*4矩阵和当前矩阵进行矩阵乘法,GF(2^8)下的乘法满座交换律、结合律、分配律,其与二相乘可等价

1.左移1位

2.若果做以前最高位为1,则与0x1B异或

(在AES加密中存在不可约多项式,为了封闭性要始终保持在一个字节内,要选择一个不可约多项式取模, P(x) = x8 + x4 + x3 + x + 1,其使用16进制表示为0x11B,如果溢出则会要模P(x),使用异或(相当于减法)得到x4 + x3 + x + 1,也就是0x1B,而异或0x1B相当于把溢出的减去)

所以只要实现乘2就可以实现所有情况

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
uint8_t GFMUL(uint8_t s, int n)
{
if(n==1)
{
return s;
}
if(n == 2)
{
uint8_t rsult = s << 1;
if(s&0x80)
{
result = result^0x1b;
}
else
{
return result;
}
if(n%2==0)
return GFMUL(GFMUL(s, n/2), 2);
else
return GFMUL(s, n-1)^s;
}
}
const uint8_t coLM[4][4]j = {{2, 3, 1, 1},
{1, 2, 3, 1},
{1, 1, 2, 3},
{3, 1, 1, 2}};
void columnMix(uint8_t sArry[4][4])
{
uint8_t tempArry[4][4];
for(int i = 0; i<4; i++)
for(int j = 0; j<4; j++)
tempArry[i][j] = sArry[i][j];
for(int i = 0; i<4; i++)
{
for(int j = 0; j<4; j++)
{
sArry[j][i] = GFMUL(tempArry[0][i], colM[j][0])^
GFMUL(tempArry[1][i], colM[j][1])^
GFMUL(tempArry[2][i], colM[j][2])^
GFMUL(tempArry[3][i], colM[j][3]);
}
}
}

AES

过程就是先将明文转换为4*4的状态矩阵,遵循列优先排列,将128位的密钥变为4个32位(大端序),然后进行拓展(若果是%4为0则进行T函数,T函数中有字节偏移(循环左移1字节),之后用s盒字节替换,最后异或轮常量),生成10组密钥,然后主函数先进行一次轮密钥加,其实就是状态矩阵与初始密钥异或,然年进行九次循环,先字节代换再行位移,然后列混合,最后轮密钥加,字节代换

其实就是将状态矩阵的每个元素的高4字节为行低四字节为列用s盒替换,行位移其实就是矩阵第二行整体循环左移一个,第三行循环左移两个,第四行循环左移三个,而列混合就是矩阵的乘法,然后在GF域下的加其实就是异或

完整加密代码

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include <bits/stdc++.h>
using namespace std;
int w[44];
uint32_t getBigEndian(uint8_t a[4])
{
uint32_t res = 0;
res = (a[0] << 24) | (a[1] << 16) | (a[2] << 8) | a[3];
return res;
}
void convertToStateArray(uint8_t s[16], uint8_t a[4][4])
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
a[j][i] = s[i * 4 + j];
}
}
}
void convertToStr(uint8_t a[4][4], uint8_t s[16])
{
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
s[i * 4 + j] = a[j][i];
}
void convertToWordArray(uint32_t W, uint8_t w[4])
{
w[0] = (W >> 24) & 0xFF;
w[1] = (W >> 16) & 0xFF;
w[2] = (W >> 8) & 0xFF;
w[3] = W & 0xFF;
}
uint32_t convertToWord(uint8_t w[4])
{
uint32_t res = 0;
res = (w[0] << 24) | (w[1] << 16) | (w[2] << 8) | w[3];
return res;
}
const uint8_t S[16][16] = {0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};
const uint32_t Rcon[10] = {0x01000000, 0x02000000,
0x04000000, 0x08000000,
0x10000000, 0x20000000,
0x40000000, 0x80000000,
0x1b000000, 0x36000000};
uint32_t T(uint32_t w, int round)
{
w = (w >> 24) | (w << 8); // 字节偏移,循环左移一字节
uint8_t temp[4];
convertToWordArray(w, temp); // 转换为字节数组
for (int i = 0; i < 4; i++)
{
temp[i] = S[temp[i] >> 4][temp[i] & 0x0F]; // 字节代换
}
w = convertToWord(temp);
w ^= Rcon[round]; // 轮常量异或
return w;
}
void extendKey(uint8_t *key)
{
for (int i = 0; i < 4; i++)
{
w[i] = getBigEndian(key + i * 4); // 转大端序
}
for (int i = 4, j = 0; i < 44; i++) // 密钥拓展
{
if (i % 4 == 0)
{
w[i] = w[i - 4] ^ T(w[i - 1], j);
j++;
}
else
w[i] = w[i - 4] ^ w[i - 1];
}
}
void addRoundKey(uint8_t sArray[4][4], int round) // 轮密钥加
{
uint8_t wArray[4];
for (int i = 0; i < 4; i++)
{
convertToWordArray(w[round * 4 + i], wArray); // 转换为字节数组
for (int j = 0; j < 4; j++)
{
sArray[j][i] ^= wArray[j]; // 异或操作
}
}
}
void subbytes(uint8_t sArray[4][4])
{
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
sArray[i][j] = S[sArray[i][j] >> 4][sArray[i][j] & 0x0F]; // 字节代换
}
void shiftRows(uint8_t sArray[4][4])
{
// 第0行不移位

// 第1行左移1位
uint8_t temp = sArray[1][0];
sArray[1][0] = sArray[1][1];
sArray[1][1] = sArray[1][2];
sArray[1][2] = sArray[1][3];
sArray[1][3] = temp;

// 第2行左移2位
uint8_t temp1 = sArray[2][0];
uint8_t temp2 = sArray[2][1];
sArray[2][0] = sArray[2][2];
sArray[2][1] = sArray[2][3];
sArray[2][2] = temp1;
sArray[2][3] = temp2;

// 第3行左移3位
temp = sArray[3][0];
sArray[3][0] = sArray[3][3];
sArray[3][3] = sArray[3][2];
sArray[3][2] = sArray[3][1];
sArray[3][1] = temp;
}
const uint8_t colM[4][4] = {2, 3, 1, 1,
1, 2, 3, 1,
1, 1, 2, 3,
3, 1, 1, 2};
uint8_t GFMul(uint8_t s, int n)
{
if (n == 1)
{
return s;
}
if (n == 2)
{
uint8_t result = s << 1;

if (s & 0x80)
{
result = result ^ 0x1b;
}

return result;
}
if (n % 2 == 0)
return GFMul(GFMul(s, n / 2), 2);
else
return GFMul(s, n - 1) ^ s;
}
void columnMix(uint8_t sArray[4][4])
{
uint8_t tempArray[4][4];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
tempArray[i][j] = sArray[i][j];
for (int j = 0; j < 4; j++)
{
for (int i = 0; i < 4; i++)
{
sArray[i][j] = GFMul(tempArray[0][j], colM[i][0]) ^
GFMul(tempArray[1][j], colM[i][1]) ^
GFMul(tempArray[2][j], colM[i][2]) ^
GFMul(tempArray[3][j], colM[i][3]);
}
}
}
void AES(uint8_t *message, uint64_t messageLen, uint8_t *key)
{
extendKey(key);
uint8_t sArray[4][4];

for (int i = 0; i < messageLen; i += 16)
{
convertToStateArray(message + i, sArray); // 转换为状态矩阵
addRoundKey(sArray, 0); // 初始轮密钥加
for (int j = 1; j < 10; j++) // 1~9
{
subbytes(sArray); // 字节代换
shiftRows(sArray); // 行移位
columnMix(sArray); // 列混合
addRoundKey(sArray, j); // 轮密钥加
}
subbytes(sArray);
shiftRows(sArray);
addRoundKey(sArray, 10); // 最终轮密钥加
convertToStr(sArray, message + i); // 转换回字符串
}
}
int main()
{
char key[] = "0123456789abcdef";
char message[] = "0123456789abcdef";
AES((uint8_t *)message, strlen(message), (uint8_t *)key);
for (int i = 0; i < 16; i++)
{
printf("%02X ", message[i] & 0xFF);
}
return 0;
}

解密

因为是对称加密,AES的每一步都可以实现逆运算(s盒与逆s盒涉及一些数学知识,以后再学,先说一下前置的知识仿射变换与幂运算,还有半群、群、环、域)

逆字节替换

根据s盒准备个反s盒,之后查表替换

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

const uint8_t S2[16][16] = {0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
void deSubbytes(uint8_t sArry[4][4])
{
for(int i = 0; i<4; i++)
{
for(int j = 0; j<4; j++)
{
sArry[i][j] = S2[sArry[i][j]>>4][sArry&0x0F]; //字节代换
}
}
}

逆行位移

循环左移改为循环右移(0行不动,一行动一,二行动二,三行行三)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void deShiftRows(uint8_t sArry[4][4])
{
uint8_t temp = sArry[1][3];
sArry[1][3] = sArry[1][2];
sArry[1][2] = sArry[1][1];
sArry[1][1] = sArry[1][0];
sArry[1][0] = temp;

uint8_t temp2 = sArry[2][2];
uint8_t temp3 = sArry[2][3];
sArry[2][3] = sArry[2][1];
sArry[2][2] = sArry[2][0];
sArry[2][1] = temp3;
sArry[2][0] = temp2;

temp = sArry[3][0];
sArry[3][0] = sArry[3][1];
sArry[3][1] = sArry[3][2];
sArry[3][2] = sArry[3][3];
sArry[3][3] = temp;
}

逆列混合

矩阵乘法,所以仅需colM矩阵的逆矩阵

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
const uint8_t delColM[4][4] = 0xe, 0xb, 0xd, 0x9,
0x9, 0xe, 0xb, 0xd,
0xd, 0x9, 0xe, 0xb,
0xb, 0xd, 0x9, 0xe};
void delMixColumns(uint8_t sArry[4][4])
{
uint8_t tempArray[4][4];
for(int i = 0; i<4; i++)
{
for(int j = 0; j<4; j++)
{
tempArry[i][j] = sArry[i][j];
}
}
for(int i = 0; i<4; i++)
{
for(int j = 0; j<4; j++)
{
sArry[i][j] = GFMul(tempArry[0][j], delColM[i][0])^
GFMUL(tempArry[1][j], delColM[i][1])^
GFMUL(tempArry[2][j], delColM[i][2])^
GFMUL(tempArry[3][j], delColM[i][3]);
}
}
}

轮密钥加就是简单的异或

总的解密

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
const uint8_t S2[16][16] = {0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
void deSubBytes(uint8_t sArray[4][4])
{
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
sArray[i][j] = S2[sArray[i][j] >> 4][sArray[i][j] & 0x0F]; // 字节代换
}
void deShiftRows(uint8_t sArray[4][4])
{
// 第0行不移位

// 第1行右移1位
uint8_t temp = sArray[1][3];
sArray[1][3] = sArray[1][2];
sArray[1][2] = sArray[1][1];
sArray[1][1] = sArray[1][0];
sArray[1][0] = temp;

// 第2行右移2位
uint8_t temp1 = sArray[2][0];
uint8_t temp2 = sArray[2][1];
sArray[2][0] = sArray[2][2];
sArray[2][1] = sArray[2][3];
sArray[2][2] = temp1;
sArray[2][3] = temp2;

// 第3行右移3位
temp = sArray[3][0];
sArray[3][0] = sArray[3][1];
sArray[3][1] = sArray[3][2];
sArray[3][2] = sArray[3][3];
sArray[3][3] = temp;
}
const uint8_t deColM[4][4] = {0xe, 0xb, 0xd, 0x9,
0x9, 0xe, 0xb, 0xd,
0xd, 0x9, 0xe, 0xb,
0xb, 0xd, 0x9, 0xe};
void deMixColumns(uint8_t sArray[4][4])
{
uint8_t tempArray[4][4];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
tempArray[i][j] = sArray[i][j];
for (int j = 0; j < 4; j++)
for (int i = 0; i < 4; i++)
sArray[i][j] = GFMul(tempArray[0][j], deColM[i][0]) ^
GFMul(tempArray[1][j], deColM[i][1]) ^
GFMul(tempArray[2][j], deColM[i][2]) ^
GFMul(tempArray[3][j], deColM[i][3]);
}
void AESdecrypt(uint8_t *message, uint64_t messageLen, uint8_t *key)
{
extendKey(key);
uint8_t sArray[4][4];

for (int i = 0; i < messageLen; i += 16)
{
convertToStateArray(message + i, sArray); // 转换为状态矩阵
addRoundKey(sArray, 10); // 初始轮密钥加
deShiftRows(sArray); // 行移位
deSubBytes(sArray); // 字节代换
for (int j = 9; j >= 1; j--)
{
addRoundKey(sArray, j); // 轮密钥加
deMixColumns(sArray); // 列混合
deShiftRows(sArray); // 行移位
deSubBytes(sArray); // 字节代换
}
addRoundKey(sArray, 0); // 最终轮密钥加
convertToStr(sArray, message + i);
}
}

7.矩阵求逆

定义法矩阵求逆

由于A的伴随矩阵等于A的行列式乘以A的逆矩阵

所以现要求出A的代数余子式,然后转置求出伴随矩阵
$$
A^-1 = A^*/|A|
$$