Base64/32/16的C语言实现加密解密

之前经常遇到base家族的各种变种加密的逆向题,然而却逆不清楚,这里就从正向到逆向去好好学习一下base家族的加解密

加密原理

原理到是不难,主要就对字符的二进制进行移位操作,分割再合并成新的10进制数,再从密码表中去对应的加密字符

这里放个链接,这个大佬已经讲的很好了:http://www.ruanyifeng.com/blog/2008/06/base64.html

base64

加密:

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
const char *base64payload = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

unsigned char *base64_encode(const char *s, const int len)
{
//取余数,如果不是3的倍数,会有2种情况
unsigned int sign = len % 3;
//计算加密后的长度
unsigned int res_len = len % 3 ? ((len) / 3 + 1) * 4 : (len) / 3 * 4;
//初始化存储空间
unsigned int i = 0, j = 0;
//i代表明文的idx,j代表密文的idx
unsigned char *res = (unsigned char *)malloc(res_len + 1);
memset(res, 0, res_len + 1);

for(i = 0, j = 0; i < len; i += 3, j += 4)
{
if(i + 2 >= len)
{//到达处理最后一组的情况时:

res[j] = (s[i] >> 2) & 0x3F;
if(sign == 1)//因为只多出一个数据,故需要补两个'='
{
res[j + 1] = ((s[i] & 0x03) << 4) & 0x3F;
res[j + 2] = 0x40;//0x40对应数字64,即为base64payload中下标为64的'='
res[j + 3] = 0x40;
}
else if(sign == 2)//因为多出两个数据,故需要补一个'='
{
res[j + 1] = (((s[i] & 0x03) << 4) | ((s[i + 1] >> 4) & 0x0F));
res[j + 2] = ((s[i + 1] & 0x0F) << 2) & 0x3F;
res[j + 3] = 0x40;
break;
}
}
res[j] = (s[i] >> 2) & 0x3F;
//0x3f的二进制是:00111111,与其&运算保证变成低6位数据
//取第一个字节的前六位,最高两位补零
res[j + 1] = (((s[i] & 0x03) << 4) | ((s[i + 1] >> 4) & 0x0F));
//0x03的二进制是:00000011,与其&运算保证变成低2位数据,再左移4位后就变成了高2位
//取第一个字节的最后两位和第二个字节的前四位,最高两位补零
res[j + 2] = (((s[i + 1] & 0x0F) << 2) | ((s[i + 2] >> 6) & 0x03));
//取第二个字节的后四位和第三个字节的前两位,最高两位补零
res[j + 3] = (s[i + 2] & 0x3F);//取第三个字节的最后六位,最高两位补零
}

for(j = 0; j < res_len; j++)
{//查找对应的加密字符
res[j] = base64payload[res[j]];
}
return res;
}

解密原理也不难,就是每次取出4个字节,然后将每个字节的字符转换成原始Base64索引表对应的索引数字,也就是编码时3字节转换成4字节的转换结果。然后使用位操作将每字节前2位去掉,重新转换成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
int find_pos(char c)
{
for(int i = 0; i < 65; i++)
if(c == base64payload[i])
return i;
return -1;
}

unsigned char *base64_decode(const char *s, const int len)
{
unsigned int res_len = len / 4 * 3;
//计算解密后的明文长度
unsigned int i = 0, j = 0;
//i代表的当前处理的组序号,j代表当前处理的组内idx
unsigned char *res = (unsigned char *)malloc(res_len + 1);
memset(res, 0, res_len + 1);
int count = len / 4;//组数

for(i = 0; i < count; i++)
{
int s_index = i * 4;
int res_index = i * 3;
int buffer[4];//用于临时存放四个字节
int sign = 0;//用于表示密文中"="的个数
for(j = 0; j < 4; j++)
{
//查找密文对应的密码表的下标
buffer[j] = find_pos(s[s_index + j]);
}
if(i == count - 1)
{//判断是否为最后一组的情况
for(j = 0; j < 4; j++)
{
if(buffer[j] == 0x40)
{//如果为'=',sign的值加一
sign++;
}
}
}

res[res_index] = ((buffer[0] & 0x3F) << 2 | (buffer[1] & 0x30) >> 4);
//& 0x30也就是& 00110000,取中间两位
//解密第一个明文字符,取第一个密文字符的6位,左移2位变高6位,后面的低2位用第二个密文字符的高2位补齐

if(sign == 2)
{//如果sign=2,说明,最后一组的密文实际上只有1个明文字符
break;
}
res[res_index + 1] = ((buffer[1] & 0x0f) << 4 | (buffer[2] & 0x3C) >> 2);
//& 0x30也就是& 00111100,取中间4位
//解密第2个明文字符,取第2个密文字符的后4位,左移4位变高4位,后面的低4位用第3个密文字符的高4位补齐

if(sign == 1)
{//如果sign=1,说明,最后一组的密文实际上有2个明文字符
break;
}

res[res_index + 2] = ((buffer[2] & 0x03) << 6 | (buffer[3] & 0x3F));
//& 0x3f也就是& 00111111,取低6位
//解密第3个明文字符,取第3个密文字符的后2位,左移6位变高2位,后面的低6位用第4个密文字符的低6位补齐

}
return res;
}

base32

加密:

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
const char *base32payload = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";

unsigned char *base32_encode(const char *s, const int len)
{
//base32采用5个字符为一组,每个字符取5位,重新组合成8个字符,再分别往密码表中寻找密文
//同样的要处理最后一组不满足5个字符的情况,用"="填充
unsigned int sign = len % 5;
unsigned int res_len = len % 5 ? ((len) / 5 + 1) * 8 : (len) / 5 * 8;
unsigned int i = 0, j = 0;
unsigned char *res = (unsigned char *)malloc(res_len + 1);
memset(res, 0, res_len + 1);
for(i = 0, j = 0; i < len; i += 5, j += 8)
{
if(i + 4 >= len)
{
res[j] = (s[i] >> 3) & 0x1F;
if(sign == 1)
{
res[j + 1] = (s[i] << 2) & 0x1C;
res[j + 2] = 0x20;
res[j + 3] = 0x20;
res[j + 4] = 0x20;
res[j + 5] = 0x20;
res[j + 6] = 0x20;
res[j + 7] = 0x20;
break;
}
else if(sign == 2)
{
res[j + 1] = ((s[i] << 2) & 0x1C) | ((s[i + 1] >> 3) & 0x18);
res[j + 2] = (s[i + 1] >> 1) & 0x1F;
res[j + 3] = (s[i + 1] << 4) & 0x10;
res[j + 4] = 0x20;
res[j + 5] = 0x20;
res[j + 6] = 0x20;
res[j + 7] = 0x20;
break;
}
else if(sign == 3)
{
res[j + 1] = ((s[i] << 2) & 0x1C) | ((s[i + 1] >> 3) & 0x18);
res[j + 2] = (s[i + 1] >> 1) & 0x1F;
res[j + 3] = ((s[i + 1] << 4) & 0x10) | ((s[i + 2] >> 4) & 0x0F);
res[j + 4] = (s[i + 2] << 1) & 0x1E;
res[j + 5] = 0x20;
res[j + 6] = 0x20;
res[j + 7] = 0x20;
break;
}
else if(sign == 4)
{
res[j + 1] = ((s[i] << 2) & 0x1C) | ((s[i + 1] >> 3) & 0x18);
res[j + 2] = (s[i + 1] >> 1) & 0x1F;
res[j + 3] = ((s[i + 1] << 4) & 0x10) | ((s[i + 2] >> 4) & 0x0F);
res[j + 4] = ((s[i + 2] << 1) & 0x1E) | ((s[i + 3] >> 3) & 0x10);
res[j + 5] = (s[i + 3] >> 2) & 0x1F;
res[j + 6] = (s[i + 3] << 3) & 0x18;
res[j + 7] = 0x20;
break;
}
}
res[j] = (s[i] >> 3) & 0x1F;
res[j + 1] = ((s[i] << 2) & 0x1C) | ((s[i + 1] >> 6) & 0x03);
res[j + 2] = (s[i + 1] >> 1) & 0x1F;
res[j + 3] = ((s[i + 1] << 4) & 0x10) | ((s[i + 2] >> 4) & 0x0F);
res[j + 4] = ((s[i + 2] << 1) & 0x1E) | ((s[i + 3] >> 3) & 0x10);
res[j + 5] = (s[i + 3] >> 2) & 0x1F;
res[j + 6] = ((s[i + 3] << 3) & 0x18) | ((s[i + 4] >> 5) & 0x07);
res[j + 7] = s[i + 4] & 0x1F;
}

for(j = 0; j < res_len; j++)
{
res[j] = base32payload[res[j]];
}
return res;
}

解密:

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
int find_idx32(char c) {
for(int i = 0; i < 31; i++) {
if(c == base32payload[i]) {
return i;
}
}
return -1;
}

unsigned char *base32_decode(const char *s, const int len)
{
unsigned int res_len = len / 8 * 5;
unsigned int i = 0, j = 0;
unsigned char *res = (unsigned char *)malloc(res_len + 1);
memset(res, 0, res_len + 1);
int count = len / 8;

for(i = 0; i < count; i++)
{
int s_index = i * 8;
int res_index = i * 5;
int buffer[8];
int sign = 0;
for(j = 0; j < 8; j++)
{
buffer[j] = find_idx32(s[s_index + j]);
}
if(i == count - 1)
{
for(j = 0; j < 8; j++)
{
if(buffer[j] == 0x20)
{
sign++;
}
}
}
res[res_index] = ((buffer[0] & 0x1F) << 3) | ((buffer[1] & 0x1C) >> 2);

if(sign == 4)
{
break;
}
res[res_index + 1] = ((buffer[1] & 0x03) << 6) | ((buffer[2] & 0x1F) << 1) | ((buffer[3] & 0x10) >> 4);
if(sign == 3)
{
break;
}
res[res_index + 2] = ((buffer[3] & 0x0F) << 4) | ((buffer[4] & 0x1E) >> 1);
if(sign == 2)
{
break;
}
res[res_index + 3] = ((buffer[4] & 0x01) << 7) | ((buffer[5] & 0x1F) << 2) | ((buffer[6] & 0x18) >> 3);
if(sign == 1)
{
break;
}
res[res_index + 4] = ((buffer[6] & 0x07) << 5) | (buffer[7] & 0x1F);
}
return res;
}

base16

加密:

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
const char *base16payload = "0123456789ABCDEF=";

unsigned char *base16_encode(const char *s, const int len)
{
//base16采用4个字符为一组,每个字符取4位,拓展为8个字符
//无论明文长度是多少,密文必定是其2倍,因此也不可能用到"="填充
unsigned int sign = len % 4;
unsigned int res_len = len * 2;
unsigned int i = 0, j = 0;
unsigned char *res = (unsigned char *)malloc(res_len + 1);
memset(res, 0, res_len + 1);
for(i = 0, j = 0; i < len; i += 4, j += 8)
{
if(i + 4 >= len)
{
res[j] = (s[i] >> 4) & 0x0F;
if(sign == 1)
{
res[j + 1] = s[i] & 0x0F;
res[j + 2] = 0x10;
res[j + 3] = 0x10;
res[j + 4] = 0x10;
res[j + 5] = 0x10;
res[j + 6] = 0x10;
res[j + 7] = 0x10;
break;
}
else if(sign == 2)
{
res[j + 1] = s[i] & 0x0F;
res[j + 2] = (s[i + 1] >> 4) & 0x0F;
res[j + 3] = s[i + 1] & 0x0F;
res[j + 4] = 0x10;
res[j + 5] = 0x10;
res[j + 6] = 0x10;
res[j + 7] = 0x10;
break;
}
else if(sign == 3)
{
res[j + 1] = s[i] & 0x0F;
res[j + 2] = (s[i + 1] >> 4) & 0x0F;
res[j + 3] = s[i + 1] & 0x0F;
res[j + 4] = (s[i + 2] >> 4) & 0x0F;
res[j + 5] = s[i + 2] & 0x0F;
res[j + 6] = 0x10;
res[j + 7] = 0x10;
break;
}
}
res[j] = (s[i] >> 4) & 0x0F;
res[j + 1] = s[i] & 0x0F;
res[j + 2] = (s[i + 1] >> 4) & 0x0F;
res[j + 3] = s[i + 1] & 0x0F;
res[j + 4] = (s[i + 2] >> 4) & 0x0F;
res[j + 5] = s[i + 2] & 0x0F;
res[j + 6] = (s[i + 3] >> 4) & 0x0F;
res[j + 7] = s[i + 3] & 0x0F;
}
for(j = 0; j < res_len; j++)
{
res[j] = base16payload[res[j]];
}
return res;
}

解密:

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
int find_idx16(char c) {
for(int i = 0; i < 15; i++) {
if(c == base16payload[i]) {
return i;
}
}
return -1;
}

unsigned char *base16_decode(const char *s, const int len)
{
unsigned int res_len = len / 2;
unsigned int i = 0, j = 0;
unsigned char *res = (unsigned char *)malloc(res_len + 1);
memset(res, 0, res_len + 1);
int count = len / 8;

for(i = 0; i < count; i++)
{
int s_index = i * 8;
int res_index = i * 4;
int buffer[8];
for(j = 0; j < 8; j++)
{
buffer[j] = find_idx16(s[s_index + j]);
}
int sign;
if(i == count - 1)
{
for(j = 0; j < 8; j++)
{
if(buffer[j] == 0x10)
{
sign++;
}
}
}

res[res_index] = ((buffer[0] & 0x0F) << 4) | (buffer[1] & 0x0F);
if(sign == 3)
{
break;
}
res[res_index + 1] = ((buffer[2] & 0x0F) << 4) | (buffer[3] & 0x0F);

if(sign == 2)
{
break;
}

res[res_index + 2] = ((buffer[4] & 0x0F) << 4) | (buffer[5] & 0x0F);
if(sign == 1)
{
break;
}
res[res_index + 3] = ((buffer[6] & 0x0F) << 4) | (buffer[7] & 0x0F);
}
return res;
}

总结

理解了加密的原理,对理解逆向这种加密算法也有更深的认识,关键点就是在于二进制的位移操作的特点,和一个加解密时用到的密码表,熟悉了这种套路,之后遇到变种的加密表或者变种的base位移操作,都会有一定的解题意识了

参考

https://qianfei11.github.io/2018/05/12/C%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0Base64%E8%A7%A3%E5%AF%86%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/

https://qianfei11.github.io/2018/12/04/C%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0Base32%E5%92%8CBase16%E8%A7%A3%E5%AF%86%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/