精力一直放在Web和一些奇奇怪怪的东西上(比如 rust web应用()),突然发现好久没做逆向题了,知识点都忘记了很多,甚至IDA都不会用了(其实本来就不怎么会用())
所以重新学一下逆向的一些知识,顺便进行一些梳理和整合。
RC4算法
RC4加密算法是一种对称加密算法。
什么是对称加密算法?
对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。
RC4算法实现过程
RC4算法包括初始化算法(KSA)和随即子密码生成算法(PRGA)两部分。
KSA
我们先来看看算法中的初始化部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
c
/*初始化函数*/
void rc4_init(unsigned char*s,unsigned char*key, unsigned long Len)
{
int i=0,j=0;
char k[256]={0};
unsigned char tmp=0;
for(i=0;i<256;i++) {
s[i]=i;
k[i]=key[i%Len];
}
for(i=0;i<256;i++) {
j=(j+s[i]+k[i])%256;
tmp=s[i];
s[i]=s[j];//交换s[i]和s[j]
s[j]=tmp;
}
}
|
参数1是一个256长度的char型数组,定义为: unsigned char sBox[256];
参数2是密钥,其内容可以随便定义:char key[256];
参数3是密钥的长度,Len = strlen(key);
初始化的过程是先有一个sbox(s盒),s盒的长度是256,然后有一个随意长度的密钥,一个中间的临时变量(tmp),在初始化的过程中,先定义一个 i ,将 i 从0开始到255以此遍历填充进s盒中;然后定义一个 j ,使用j=(j+s[i]+k[i])%256,得到 j 是几;然后 i 和 j 作为s盒的索引,进行随机化处理。因此,
1
2
3
4
|
j=(j+s[i]+k[i])%256;
tmp=s[i];
s[i]=s[j];//交换s[i]和s[j]
s[j]=tmp;
|
这一部分是根据密钥来打乱s盒,i 确保S-box的每个元素都得到处理,j 保证S-box的搅乱是随机的。
PRGA
接下来我们来看看随即子密码生成算法,即加解密的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
c
/*加解密*/
void rc4_crypt(unsigned char*s,unsigned char*Data,unsigned long Len)
{
int i=0,j=0,t=0;
unsigned long k=0;
unsigned char tmp;
for(k=0;k<Len;k++)
{
i=(i+1)%256;
j=(j+s[i])%256;
tmp=s[i];
s[i]=s[j];//交换s[x]和s[y]
s[j]=tmp;
t=(s[i]+s[j])%256;
Data[k]^=s[t];
}
}
|
参数1是上边rc4_init函数中,被搅乱的S-box;
参数2是需要加密的数据data;
参数3是data的长度.
我们看到我们现在填入了要加密的内容,来分析一下加密的过程。
首先是在KSA中被打乱的s盒,这时候我们就不再需要密钥了,我们继续对s盒中的元素进行处理。
通过对s盒中的元素的索引 i 和 j 进行处理:
1
2
3
4
5
|
i=(i+1)%256;
j=(j+s[i])%256;
tmp=s[i];
s[i]=s[j];//交换s[x]和s[y]
s[j]=tmp;
|
这一部分,我们使 i 加一,得到新的 i 然后 j 等于 j 加上新的 i 索引的数字,然后 i 和 j 定位了新的位置,然后通过t=(s[i]+s[j])%256;,来得到密钥流,最后使用密钥流和要加密的数据进行异或得到密文。
即每收到一个字节,就进行while循环。通过一定的算法定位S盒中的一个元素,并与输入字节异或,得到k。循环中还改变了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
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
|
c
//程序开始
#include<stdio.h>
#include<string.h>
typedef unsigned longULONG;
/*初始化函数*/
void rc4_init(unsigned char*s, unsigned char*key, unsigned long Len)
{
int i = 0, j = 0;
char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i<256; i++)
{
s[i] = i;
k[i] = key[i%Len];
}
for (i = 0; i<256; i++)
{
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j];//交换s[i]和s[j]
s[j] = tmp;
}
}
/*加解密*/
void rc4_crypt(unsigned char*s, unsigned char*Data, unsigned long Len)
{
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k<Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];//交换s[x]和s[y]
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] ^= s[t];
}
}
int main()
{
unsigned char s[256] = { 0 }, s2[256] = { 0 };//S-box
char key[256] = { "justfortest" };
char pData[512] = "这是一个用来加密的数据Data";
unsigned long len = strlen(pData);
int i;
printf("pData=%s\n", pData);
printf("key=%s,length=%d\n\n", key, strlen(key));
rc4_init(s, (unsigned char*)key, strlen(key));//已经完成了初始化
printf("完成对S[i]的初始化,如下:\n\n");
for (i = 0; i<256; i++)
{
printf("%02X", s[i]);
if (i && (i + 1) % 16 == 0)putchar('\n');
}
printf("\n\n");
for (i = 0; i<256; i++)//用s2[i]暂时保留经过初始化的s[i],很重要的!!!
{
s2[i] = s[i];
}
printf("已经初始化,现在加密:\n\n");
rc4_crypt(s, (unsigned char*)pData, len);//加密
printf("pData=%s\n\n", pData);
printf("已经加密,现在解密:\n\n");
//rc4_init(s,(unsignedchar*)key,strlen(key));//初始化密钥
rc4_crypt(s2, (unsigned char*)pData, len);//解密
printf("pData=%s\n\n", pData);
return 0;
}
//程序完
|
这时候有人就要问了,你写这么绕,这么模糊谁能看得懂啊?
所以我们来举几个例子帮助理解:
加密流程详解
一、数学实现
- 初始化S盒(Key Scheduling Algorithm, KSA)
-
初始状态:S盒是一个长度为256的数组(S[0], S[1], …, S[255]),初始时按顺序填充0到255:
1
2
|
plaintext
S[0] = 0, S[1] = 1, ..., S[255] = 255
|
-
密钥预处理:如果密钥长度不足256字节,循环重复密钥直到填满256字节。例如密钥是KEY(3字节),则扩展为K E Y K E Y K E Y ...。
-
洗牌算法:
1
2
3
4
5
|
python
j = 0
for i in 0到255:
j = (j + S[i] + 密钥[i % 密钥长度]) % 256 # 用密钥决定交换位置
交换 S[i] 和 S[j]
|
关键点:密钥的每个字节都会影响洗牌过程,但前几个字节的影响可能被后续步骤覆盖。
- 生成密钥流(Pseudo-Random Generation Algorithm, PRGA)
二、举个具体例子
示例1:初始化S盒
假设密钥是[0x01, 0x02, 0x03](3字节):
-
初始化S盒
:
1
2
|
plaintext
S = [0,1,2,3, ..., 255]
|
-
洗牌过程
(前3步演示):
-
i=0
:
- j = (0 + S[0] + Key[0]) % 256 = (0 + 0 + 1) % 256 = 1
- 交换 S[0] 和 S[1] → S变为
[1,0,2,3, ..., 255]
-
i=1
:
- j = (1 + S[1] + Key[1]) % 256 = (1 + 0 + 2) % 256 = 3
- 交换 S[1] 和 S[3] → S变为
[1,3,2,0, ..., 255]
-
i=2
:
- j = (3 + S[2] + Key[2]) % 256 = (3 + 2 + 3) % 256 = 8
- 交换 S[2] 和 S[8] → S[8]的值被换到位置2。
-
…(持续到i=255)
示例2:生成密钥流
假设初始化后的S盒为 [3, 1, 4, 0, 2, 5, ...](简化举例):
-
第1个密钥字节
:
- i = (0 + 1) = 1
- j = (0 + S[1]) = 0 + 1 = 1
- 交换 S[1] 和 S[1](无变化)
- t = (S[1] + S[1]) = 1 + 1 = 2
- 密钥字节 = S[2] = 4 → 密钥流第一个字节是4
-
第2个密钥字节
:
- i = 2
- j = (1 + S[2]) = 1 + 4 = 5
- 交换 S[2] 和 S[5] → S[2]=5, S[5]=4
- t = (5 + 4) = 9 → 密钥字节 = S[9]
- …
例题
接下来我们看看RC4算法在CTF中的具体实现
[BabyAlgorithm](https://buuoj.cn/challenges#[第五章 CTF之RE章]BabyAlgorithm)
导入IDA,查看main函数
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
|
c
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 result; // rax
int i; // [rsp+Ch] [rbp-E4h]
char v5[16]; // [rsp+10h] [rbp-E0h] BYREF
char s[64]; // [rsp+20h] [rbp-D0h] BYREF
char v7[64]; // [rsp+60h] [rbp-90h] BYREF
char v8[72]; // [rsp+A0h] [rbp-50h] BYREF
unsigned __int64 v9; // [rsp+E8h] [rbp-8h]
v9 = __readfsqword(0x28u);
memset(v8, 0, 0x40uLL);
v8[0] = 0xC6;
v8[1] = 0x21;
v8[2] = 0xCA;
v8[3] = 0xBF;
v8[4] = 0x51;
v8[5] = 0x43;
v8[6] = 0x37;
v8[7] = 0x31;
v8[8] = 0x75;
v8[9] = 0xE4;
v8[0xA] = 0x8E;
v8[0xB] = 0xC0;
v8[0xC] = 0x54;
v8[0xD] = 0x6F;
v8[0xE] = 0x8F;
v8[0xF] = 0xEE;
v8[0x10] = 0xF8;
v8[0x11] = 0x5A;
v8[0x12] = 0xA2;
v8[0x13] = 0xC1;
v8[0x14] = 0xEB;
v8[0x15] = 0xA5;
v8[0x16] = 0x34;
v8[0x17] = 0x6D;
v8[0x18] = 0x71;
v8[0x19] = 0x55;
v8[0x1A] = 8;
v8[0x1B] = 7;
v8[0x1C] = 0xB2;
v8[0x1D] = 0xA8;
v8[0x1E] = 0x2F;
v8[0x1F] = 0xF4;
v8[0x20] = 0x51;
v8[0x21] = 0x8E;
v8[0x22] = 0xC;
v8[0x23] = 0xCC;
qmemcpy(&v8[0x24], "3S1", 3);
v8[0x28] = 0x40;
v8[0x29] = 0xD6;
v8[0x2A] = 0xCA;
v8[0x2B] = 0xEC;
v8[0x2C] = 0xD4;
puts("Input flag: ");
__isoc99_scanf("%63s", s);
if ( strlen(s) == 0x2D )
{
strcpy(v5, "Nu1Lctf233");
sub_400874(v5, s, v7);
for ( i = 0; i <= 0x2C; ++i )
{
if ( v7[i] != v8[i] )
{
puts("GG!");
return 0LL;
}
}
puts("Congratulations!");
result = 0LL;
}
else
{
puts("GG!");
result = 0LL;
}
return result;
}
|
注意下面两个函数都有一个共同的特征%256,这是RC4算法的典型特征
sub_400874 中,sub_40067A()和sub_400753()两个函数都是加密算法,
sub_40067A()是流密钥的生成,
sub_400753()是加密。
sub_400874:
1
2
3
4
5
6
7
8
9
10
|
__int64 __fastcall sub_400874(__int64 a1, __int64 a2, __int64 a3)
{
char v5[264]; // [rsp+20h] [rbp-110h] BYREF
unsigned __int64 v6; // [rsp+128h] [rbp-8h]
v6 = __readfsqword(0x28u);
sub_40067A(a1, v5);
sub_400753(v5, a2, a3);
return 0LL;
}
|
sub_40067A(流密钥生成):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
__int64 __fastcall sub_40067A(const char *a1, __int64 a2)
{
int v3; // [rsp+10h] [rbp-10h]
int i; // [rsp+14h] [rbp-Ch]
int j; // [rsp+18h] [rbp-8h]
int v6; // [rsp+1Ch] [rbp-4h]
v6 = strlen(a1);
v3 = 0;
for ( i = 0; i <= 255; ++i )
*(_BYTE *)(i + a2) = i;
for ( j = 0; j <= 255; ++j )
{
v3 = (*(unsigned __int8 *)(j + a2) + v3 + a1[j % v6]) % 256;
sub_400646(j + a2, a2 + v3);
}
return 0LL;
}
|
sub_400753(加密):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
__int64 __fastcall sub_400753(__int64 a1, const char *a2, __int64 a3)
{
int v5; // [rsp+24h] [rbp-1Ch]
int v6; // [rsp+28h] [rbp-18h]
size_t v7; // [rsp+30h] [rbp-10h]
size_t v8; // [rsp+38h] [rbp-8h]
v5 = 0;
v6 = 0;
v7 = 0LL;
v8 = strlen(a2);
while ( v7 < v8 )
{
v5 = (v5 + 1) % 256;
v6 = (v6 + *(unsigned __int8 *)(v5 + a1)) % 256;
sub_400646(v5 + a1, a1 + v6);
*(_BYTE *)(a3 + v7) = a2[v7] ^ *(_BYTE *)((unsigned __int8)(*(_BYTE *)(v5 + a1) + *(_BYTE *)(v6 + a1)) + a1);
++v7;
}
return 0LL;
}
|
本题的密钥是 Nu1Lctf233
加密后的密文是 v8,即
1
|
[0xc6,0x21,0xca,0xbf,0x51,0x43,0x37,0x31,0x75,0xe4,0x8e,0xc0,0x54,0x6f,0x8f,0xee,0xf8,0x5a,0xa2,0xc1,0xeb,0xa5,0x34,0x6d,0x71,0x55,0x8,0x7,0xb2,0xa8,0x2f,0xf4,0x51,0x8e,0xc,0xcc,0x33,0x53,0x31,0x0,0x40,0xd6,0xca,0xec,0xd4 ]
|
这个数组的解出来是乱码,这里base64加utf-8编码就可以得到密文
1
2
3
4
5
6
7
8
|
python
import base64
a=[0xc6,0x21,0xca,0xbf,0x51,0x43,0x37,0x31,0x75,0xe4,0x8e,0xc0,0x54,0x6f,0x8f,0xee,0xf8,0x5a,0xa2,0xc1,0xeb,0xa5,0x34,0x6d,0x71,0x55,0x8,0x7,0xb2,0xa8,0x2f,0xf4,0x51,0x8e,0xc,0xcc,0x33,0x53,0x31,0x0,0x40,0xd6,0xca,0xec,0xd4]
s=""
for i in a:
s+=chr(i)
print(s)
print(str(base64.b64encode(s.encode('utf-8')), 'utf-8'))
|
密文:w4Yhw4rCv1FDNzF1w6TCjsOAVG/Cj8Ouw7hawqLDgcOrwqU0bXFVCAfCssKoL8O0UcKODMOMM1MxAEDDlsOKw6zDlA==
贴上搜到的脚本,拿到 flag
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
|
python
import base64
def rc4_main(key = "init_key", message = "init_message"):
print("RC4解密主函数调用成功")
print('\n')
s_box = rc4_init_sbox(key)
crypt = rc4_excrypt(message, s_box)
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
print("原来的 s 盒:%s" % s_box)
print('\n')
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
print("混乱后的 s 盒:%s"% s_box)
print('\n')
return s_box
def rc4_excrypt(plain, box):
print("调用解密程序成功。")
print('\n')
plain = base64.b64decode(plain.encode('utf-8'))
plain = bytes.decode(plain)
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
print("res用于解密字符串,解密后是:%res" %res)
print('\n')
cipher = "".join(res)
print("解密后的字符串是:%s" %cipher)
print('\n')
print("解密后的输出(没经过任何编码):")
print('\n')
return cipher
a=[0xc6,0x21,0xca,0xbf,0x51,0x43,0x37,0x31,0x75,0xe4,0x8e,0xc0,0x54,0x6f,0x8f,0xee,0xf8,0x5a,0xa2,0xc1,0xeb,0xa5,0x34,0x6d,0x71,0x55,0x8,0x7,0xb2,0xa8,0x2f,0xf4,0x51,0x8e,0xc,0xcc,0x33,0x53,0x31,0x0,0x40,0xd6,0xca,0xec,0xd4]
s=""
for i in a:
s+=chr(i)
s=str(base64.b64encode(s.encode('utf-8')), 'utf-8')
rc4_main("Nu1Lctf233", s)
|
参考:
RC4加密算法与逆向方法简析
RC4加密算法
RC4加密算法及逆向方法初探
[BUUCTF_N1book_RE_第五章 CTF之RE章]BabyAlgorithm
最后,家人们,补药用rust写脚本😭😭😭 🤡🤡🤡