学破解第118天,《C++之RC4算法加密解密》学习
从小学到大专(计算机网络技术专业),玩过去的,所以学习成绩惨不忍睹,什么证书也没考,直到找不到工作才后悔,不知道怎么办才好。
2017年12月16日,通过19元注册码注册论坛账号,开始做伸手党,潜水一年多,上来就是找软件。(拿论坛高大上的软件出去装X)
2018年8月某一天,报名了华中科技大学网络教育本科(计算机科学与技术专业)2018级秋季。(开始提升学历)
2019年6月17日,不愿再做小菜鸟一枚,开始零基础学习破解。(感谢小糊涂虫大哥在我刚开始学习脱壳时,录制视频解答我的问题)
2020年7月7日,感谢H大对我的鼓励,拥有了第一篇获得优秀的文章。(接下来希望学习逆向,逆天改命)
坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-1208234-1-1.html
立帖为证!--------记录学习的点点滴滴
本文对应视频教程地址:https://www.52pojie.cn/thread-1221530-1-1.html
0x1RC4算法的基本介绍
1.密钥流:RC4算法的关键是依据明文和密钥生成相应的密钥流,密钥流的长度和明文的长度是相应的。也就是说明文的长度是500字节,那么密钥流也是500字节。当然,加密生成的密文也是500字节。由密文第i字节=明文第i字节^密钥流第i字节;
2.状态向量S:长度为256。S[0],S[1].....S[255]。每一个单元都是一个字节。算法执行的不论什么时候。S都包含0-255的8比特数的排列组合,仅仅只是值的位置发生了变换;
3.暂时向量T:长度也为256,每一个单元也是一个字节。
假设密钥的长度是256字节。就直接把密钥的值赋给T,否则,轮转地将密钥的每一个字节赋给T。
4.密钥K:长度为1-256字节。注意密钥的长度keylen与明文长度、密钥流的长度没有必定关系。通常密钥的长度趣味16字节(128比特)。
0x2RC4算法的实现原理
1.初始化S和T,这个时候的S显然是均匀分布的,呈线性增长,然后用密钥K对对T表进行打乱,此时T表中的数据就能用K表中的数据表示。
for i=0 to 255 do
S[i]=i;
T[i]=K[ i mod keylen ];
2.初始排列S,用j+S[i]+T[i]的值对256取余,然后把值再给j,接下来用S[i]中的值交换S[j]中的值
j=0;
for i=0 to 255 do
j= ( j+S[i]+T[i])mod256;
swap(S[i],S[j]);
3.产生密钥流
i,j=0;
for r=0 to len do //r为明文长度,r字节
i=(i+1) mod 256;
j=(j+S[i])mod 256;
swap(S[i],S[j]);
t=(S[i]+S[j])mod 256;
k[r]=S[t];
0x3RC4算法的C++实现
1.按照上述过程,首先我要初始化S盒,作为子密钥(密钥流),然后进行加解密,解密的时候也需要它的。
/*
初始化函数
参数1是一个256长度的char型数组,定义为: unsigned char sBox[256];
参数2是密钥,其内容可以随便定义:char key[256];
参数3是密钥的长度,Len = strlen(key);
返回值:0表示成功
*/
int rc4_init(unsigned char*s, unsigned char*key, unsigned long Len)
{
int i = 0, j = 0;
//这里要定义为无符号char类型,防止出现负数,负数作为数组下标......
unsigned 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盒的值
s[i] = s[j];//交换s[i]和s[j]
s[j] = tmp;
}
//这里还应该添加代码保存子密钥,稍后会提到
return 0;
}
2.接下来就应该是加解密函数了,使用密钥流对明文进行异或加解密。(这里说加解密是因为两次异或后,值会还原,也就是解密了)
/*
加解密函数
参数1是上边rc4_init函数中,被搅乱的S盒
参数2是明文data
参数3是data的长度
返回值:0表示成功
*/
int rc4_crypt(unsigned char*s, unsigned char*data, unsigned long Len)
{
int i = 0, j = 0, t = 0;
unsigned char tmp;
for (int k = 0; k < Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];//交换s[i]和s[j]
s[j] = tmp;
t = (s[i] + s[j]) % 256;
data[k] ^= s[t];//将明文与密钥流(打乱的s盒)进行异或加解密
}
return 0;
}
3.现在是不是就可以进行使用RC4加密了呢?再看看,我们S和初始化后在加密函数中会再次打乱进行异或,而我们解密的时候还需要初始化时候的密钥流进行解密。所以我们应该在定义一个全局变量T盒,用来保存子密钥(密钥流),然后在初始化函数中添加代码。
unsigned char T[256] = {0};
//这里还应该添加代码保存子密钥,稍后会提到
for (int i = 0; i < 256; i++)
{
T[i] = s[i];//字符串拷贝到全局变量T表中
}
4.现在我们就能使用RC4对字符串进行RC4加解密了。(同样,这也可以用于对文本文件加密)
#include "main.h"
using namespace std;
unsigned char T[256] = { 0 };//T盒
/*
初始化函数
参数1是一个256长度的char型数组,定义为: unsigned char sBox[256];
参数2是密钥,其内容可以随便定义:char key[256];
参数3是密钥的长度,Len = strlen(key);
返回值:0表示成功
*/
int rc4_init(unsigned char*s, unsigned char*key, unsigned long Len)
{
int i = 0, j = 0;
//这里要定义为无符号char类型,防止出现负数
unsigned char t[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i < 256; i++) {
s[i] = i;//下标为几,s[i]就对应几
t[i] = key[i%Len];//将密钥逐个字节赋值给T盒
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + t[i]) % 256;
tmp = s[i];//打乱S盒的值
s[i] = s[j];//交换s[i]和s[j]
s[j] = tmp;
}
//用T盒保留S盒初始化后的值
for (int i = 0; i < 256; i++)
{
T[i] = s[i];
cout << "0x" << hex << (int) T[i] << ',';
}
cout << endl;
return 0;
}
/*
加解密函数
参数1是上边rc4_init函数中,被搅乱的S盒
参数2是明文data
参数3是data的长度
返回值:0表示成功
*/
int rc4_crypt(unsigned char*s, unsigned char*data, unsigned long Len)
{
int i = 0, j = 0, t = 0;
unsigned char tmp;
for (int k = 0; k < Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];//交换s[i]和s[j]
s[j] = tmp;
t = (s[i] + s[j]) % 256;
data[k] ^= s[t];//将明文与密钥流(打乱的s盒)进行异或加解密
}
return 0;
}
unsigned int main()
{
char key[] = "reverse";//密钥
char data[] = "小菜鸟一枚";//明文
unsigned char s[256];//定义S盒
rc4_init(s, (unsigned char *)key, strlen(key));//初始化
cout << "加密前的明文:" << data << endl;
cout << "key:" << key << endl;
cout << "==========RC4加解密==========" << endl;
rc4_crypt(s, (unsigned char *)data, strlen(data));//加密
cout << "加密后的密文:" << data << endl;
rc4_crypt(T, (unsigned char *)data, strlen(data));//解密
cout << "解密后的明文:" << data << endl;
system("pause");
return 0;
}
5.最后再来看看生成的子密钥能不能配合我们的密钥将密文解密,还是声明一个长度为256的数组,然后把子密钥的十六进制形式放进去,是不是能不能解密密文。
unsigned char TT[256] = {
0x41,0x36,0x50,0x1e,0x55,0xa6,0x11,0x80,0x2c,0x63,0xe5,0x62,0xfa,0x1a,0xd3,0x6b,
0x4,0x27,0xb0,0x42,0x12,0x3f,0xba,0x88,0xc0,0x20,0x5a,0x5f,0xed,0x6f,0x39,0x87,0x83,
0xad,0x10,0xc9,0x19,0x1c,0x78,0x38,0xac,0x3a,0x1f,0x66,0xa5,0x9a,0x6a,0xef,
0x9b,0xea,0x81,0xd6,0xf8,0x58,0x32,0x9d,0x8d,0x98,0x24,0xfc,0xe4,0x29,0x40,0x71,
0x65,0x72,0x7a,0xb2,0xe6,0xb4,0x48,0xff,0xcf,0xf3,0x18,0xc1,0xc8,0xf5,0xa8,0x6c,
0x7f,0x70,0x6e,0xe9,0x5,0x60,0xf7,0x2b,0xa2,0xf6,0xdf,0xa,0xc,0x79,0xa0,0xb5,0x91
,0xe8,0xd4,0x9c,0x9,0x1b,0xc6,0xb7,0x84,0x23,0xcd,0x3d,0x4b,0xa1,0x2,0x3c,0x4f,
0xb9,0x25,0xaf,0xab,0x93,0x54,0xa4,0xca,0x9e,0x4a,0x68,0x8b,0xd5,0x21,0xa9,0xc2,
0x4c,0x33,0x52,0x0,0x57,0x95,0xb6,0x15,0xbc,0x8c,0xd0,0x4d,0xe7,0xc4,0xe3,0x5e,
0xae,0xfb,0xe2,0xdb,0xf0,0x51,0x94,0x3b,0x7b,0x1,0x2d,0x3,0x14,0x97,0xf9,0xbf,0xd2
,0x8f,0x77,0x6,0x56,0x92,0x8a,0xc3,0x74,0xd,0x47,0x31,0x73,0xec,0x2f,0x4e,0x69,
0xc5,0xb8,0x90,0x22,0x82,0xe0,0xcc,0x28,0xdc,0xcb,0xbb,0xb,0x1d,0x7c,0xc7,0xd1,0x9f,
0x17,0xde,0x85,0xfd,0x89,0xd8,0xbe,0x49,0xb1,0x16,0x5c,0xa7,0x99,0x3e,0x7e,0xda,
0x45,0x76,0xce,0xf2,0x8,0xf,0x59,0x26,0xee,0xbd,0x61,0xeb,0x53,0x8e,0x64,0xdd
,0x7,0xd9,0xe,0xb3,0x13,0xf4,0x67,0xa3,0xf1,0x46,0x2e,0x37,0x43,0xd7,0x6d,0x44,0x96,
0x35,0xfe,0x5b,0x5d,0x86,0x7d,0xe1,0x34,0x2a,0x30,0xaa,0x75
};
rc4_crypt(TT, (unsigned char *)data, strlen(data));//解密
cout << "解密后的明文:" << data << endl;
成功输出:小菜鸟一枚,说明我这次的学习是成功的,加解密都没问题了。
6.补充一下,我这里为了尝试能不能直接用密钥流加解密,是使用密钥流直接进行解密的,如果有密钥,那么我们按照与加密同样的步骤,将原始密钥初始化,然后执行加解密函数进行解密,看上去很简单的算法,原理看上去还是有些迷糊,主要还是在模余和交换值的地方不知道为什么那么做。
0x4使用OD分析RC4加密的程序
1.将随书文件RC4Sample.exe载入OD,程序运行起来,发现如图所示(禁用了编辑器代码,图在最后面)!
2.当message(信息)改变后,对应的cipherTEXT(密文)也一起改变,先试试GetDlgItemTextA,不敢怎么样他也得获取文本框的内容才能运算吧,然后运行程序,对话框还没显示出来就被断下来了,不用慌,Alt+F9多来几次,返回到用户代码
004012BD |. C64424 1C 01 mov byte ptr ss:[esp+0x1C],0x1 ; |
004012C2 |. C64424 1D 23 mov byte ptr ss:[esp+0x1D],0x23 ; |
004012C7 |. C64424 1E 45 mov byte ptr ss:[esp+0x1E],0x45 ; |
004012CC |. C64424 1F 67 mov byte ptr ss:[esp+0x1F],0x67 ; |
004012D1 |. C64424 20 89 mov byte ptr ss:[esp+0x20],0x89 ; |
004012D6 |. C64424 21 AB mov byte ptr ss:[esp+0x21],0xAB ; |
004012DB |. C64424 22 CD mov byte ptr ss:[esp+0x22],0xCD ; |
004012E0 |. C64424 23 EF mov byte ptr ss:[esp+0x23],0xEF ; |
004012E5 |. FF15 B0604000 call dword ptr ds:[<&USER32.GetDlgItemTe>; \GetDlgItemTextA
004012EB |. 8BE8 mov ebp,eax
004012ED |. 85ED test ebp,ebp
004012EF |. 75 1A jnz short RC4Sampl.0040130B
004012F1 |. 68 40704000 push RC4Sampl.00407040 ; /Text = "please input name"
004012F6 |. 6A 6F push 0x6F ; |ControlID = 6F (111.)
004012F8 |. 56 push esi ; |hWnd = 003A0352 ('RC4 Sample',class='#32770')
004012F9 |. FF15 B4604000 call dword ptr ds:[<&USER32.SetDlgItemTe>; \SetDlgItemTextA
不用回去跑,看寄存器窗口,因为ebp(pediy的长度)不为0,所以下面jnz会跳走
EAX:00000005
ESP:0012F4D4
而ESP+4*EAX的地址对应的字符串就是message(信息)的文本pediy
3.继续向下走,就看到加密函数了
00401319 |. 6A 08 push 0x8
0040131B |. F3:AB rep stos dword ptr es:[edi]
0040131D |. 8D4C24 10 lea ecx,dword ptr ss:[esp+0x10]
00401321 |. 8D9424 180200>lea edx,dword ptr ss:[esp+0x218]
00401328 |. 51 push ecx ; 0012F4E0:pediy
00401329 |. 52 push edx ; 0012F6E8
0040132A |. E8 D1FCFFFF call RC4Sampl.00401000 ; 初始化S盒,注意常数0x100,也就是256
0040132F |. 8D4424 20 lea eax,dword ptr ss:[esp+0x20] ; 取出pediy给eax
00401333 |. 55 push ebp ; pediy的长度入栈
00401334 |. 8D8C24 240200>lea ecx,dword ptr ss:[esp+0x224] ; 0012F6E8这个地址给ecx
0040133B |. 50 push eax ; 待加密数据pediy
0040133C |. 51 push ecx ; S盒
0040133D |. E8 2EFDFFFF call RC4Sampl.00401070 ; 加密函数
00401342 |. 83C4 18 add esp,0x18 ; 销毁局部变量
00401345 |. 33F6 xor esi,esi
00401347 |. 85ED test ebp,ebp ; 判断pediy长度是否为0
00401349 |. 7E 24 jle short RC4Sampl.0040136F ; 小于等于0跳走
0040134B |. 8DBC24 140100>lea edi,dword ptr ss:[esp+0x114]
00401352 |> 33D2 /xor edx,edx
00401354 |. 8A5434 14 |mov dl,byte ptr ss:[esp+esi+0x14]
00401358 |. 52 |push edx
00401359 |. 68 38704000 |push RC4Sampl.00407038 ; ASCII "%02X"
0040135E |. 57 |push edi
0040135F |. E8 3C000000 |call RC4Sampl.004013A0 ; 将加密后的文本变成2位16进制表示
4.接下来就是把密文设置到cipherTEXT文本框中,补充一下加密函数里面几个这样的指令,要把它当成数组(或者是一连串存储空间来看)来看。
004010A3 |. 8B4CB8 08 |mov ecx,dword ptr ds:[eax+edi*4+0x8] ; 0012F6E8[edi]
004010BB |. 8B4C88 08 |mov ecx,dword ptr ds:[eax+ecx*4+0x8] ; 0012F6E8[ecx]
0x5总结
1.RC4算法采用的是异或运算,因此加密函数也就是解密函数。
2.使用key,初始化S盒,形成密钥流,然后执行加密函数加密,每次加密都会改变S盒中的内容。
3.破解RC4算法时,可以想办法取得加密时密钥流,初始化后的S盒,然后在执行一次加密函数,密文就自己解密了。
4.RC4作为对称加密算法,加密前和加密后的文本长度不变,但是要想找到key还是很难的。
0x6参考资料:
https://baike.baidu.com/item/RC4/3454548?fr=aladdin
https://www.cnblogs.com/gambler/p/9075415.html
《加密解密第四版》P231-P233
PS:善于总结,善于发现,找到分析问题的思路和解决问题的办法。虽然我现在还是零基础的小菜鸟一枚,也许逆天改命我会失败,但也有着成功的可能,只要还有希望,就决不放弃!