密码学-常见加密算法逆向学习

加密算法分为多种形式,一种是单向散列算法,也叫做hash算法,该算法常用于数字签名与完整性检测,常见的散列算法有MD5,SHA,RIPE_MD,HAVAL,N_Hash这几种,另一种则是对称加密算法,对称加密算法加密与解密一般可使用同一个函数进行,算法强度依赖于算法密钥,常见的对称加密算法有,RC4,TEA,IDEA,BlowFish,AES等。

MD5 消息摘要算法

MD5算法是消息摘要算法,也是单项散列算法,其作用是将一个任意长度的消息压缩成固定长度,该算法默认会产生一个128位的消息摘要,常用于验证消息完整性以及数字签名等。

逆向识别方式:

消息摘要初始化时,会用4个变量来辅助计算消息摘要,这些寄存器会被初始化为:

A=>01234567h B=>89abcdefh C=>fedcba98h d=>76543210h

其主要加密代码是这样的,先是初始化这四个变量,然后再更新。

	MD5Init(&context);
	MD5Update(&context,szName,dtLength);
	MD5Update(&context,szTeam,lstrlen(szTeam));
	MD5Final(szHash, &context);

可以看到,识别的关键就是,找到这四个关键常数,也就基本上能够确定,目标使用的是MD5算法了。

这四个常数,在内存中也是顺序存储的,很好识别到。

最后调用call计算散列值,并将散列值保存在edx中,数据窗口观察。

源代码是这样的,对比一下,学习识别方法。

总体上反编译对比,可读性很高了已经。

IDA分析一下看看 MD5Init 四个常数没变化。

该算法的变形通常有三个地方,1.改变初始化时用到的四个常数,2.改变填充的方法,3.改变hash变换的处理过程。

ShA 安全散列算法

Sha系列算法,又叫做安全散列算法,其包括 sha-1,sha-256,sha-384,sha-512总共这四种,分别产生160/256/384/512位的散列值,该算法与MD4算法设计原理相同,但安全性更高一些。

以sha-1为例,其会产生160位消息摘要,在对消息处理之前,初始散列值H用5个32位双子进行初始化,可以通过识别这些双字压缩常数来确定是否是该算法。

h0=> 67452301h h1=>efcdab89h h2=>98badcfeh h3=>10325476h h4=>c3d2e1f0h

以下代码,就是标准的sha-1散列初始化标志。

接着是对用户名进行hash处理部分,循环,循环次数取决于用户输入的用户名长度,经过循环后,最后将结果存入eax指向的位置。

首先通过循环的方式,将用户输入的用户名进行sha1_process 处理。

然后调用sha1_hash函数对字符串进行hash运算,此处是对用户名进行循环亦或操作,异或后存入 ss:[esp+eax*1+0x40]

再将亦或后的值,与第二个字符串进行异或操作。

最后执行,亦或操作结束,此处记下004014FF地址,载入IDA分析看看。

下半部分的分析。

剩下的其他算法识别特征如下。


RC4 流密码(对称加密)

RC4算法与1987年诞生,该算法属于对称加密中的流密码算法,广泛用于SSL,WEP等,尽管该算法安全性不太强,但在实际应用中还是可以使用的,据了解,病毒喜欢使用这类算法对自身或对目标文件进行加密,因为该算法简单,不会占用太大的存储空间。

加解密原理: RC4生成一种称为密钥流的伪随机流,RC4由伪随机数生成器(KSA)和异或运算(PRGA)组成,首先RC4会将一个256字节的数组进行初始化,RC4一个字节一个字节地加解密。给定一个密钥,伪随机数生成器接受密钥并产生一个S盒。S盒用来加密数据,而且在加密过程中S盒会变化。

rc4在初始化之前,需要定义一个加密密钥,其源代码如下定义。

在汇编中通常会出现在初始化S盒之前,所以我们需要记下这个密钥。

S盒初始化后长度是256字节,格式如下所示。

跟进401000看看是如何循环填充盒子的,首先循环256次,填充盒子,接着对盒子再次打乱。

调用加密函数后,S盒中的值会再次发生变化。

最后循环取出加密后的值并放入编辑框中。

另一种RC4加密方式,使用vs2013编译代码,载入查看对照源码学习。

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

/*初始化S盒*/
void InitSbox(unsigned char sbox[]) {
	for (int i = 0; i < 256; i++)  sbox[i] = i;
}

/*密钥填充256数组*/
void KeyExpansion(unsigned char key[], char *k, int len) {
	if (len <= 256) {
		for (int i = 0; i < 256; i++) key[i] = k[i % len];
	}
	if (len > 256) {
		for (int i = 0; i < 256; i++) key[i] = k[i];
	}
}

/*打乱S盒*/
void UpsetSbox(unsigned char sbox[], unsigned char key[]) {
	int j = 0;
	unsigned char temp;
	int n;
	for (int i = 0; i < 256; i++) {
		n = j + (int)sbox[i] + (int)key[i];
		j = n % 256;
		temp = sbox[i];
		sbox[i] = sbox[j];
		sbox[j] = temp;
	}
}

/*加解密数据*/
void DataProcess(unsigned char sbox[], FILE *fp1, FILE *fp2) {
	int i, j;
	i = 0; j = 0;
	char ch = fgetc(fp1);
	while (ch != EOF) {
		i = (i + 1) % 256;
		int temp2 = j + (int)sbox[i];
		j = temp2 % 256;
		unsigned char temp;
		temp = sbox[i];
		sbox[i] = sbox[j];
		sbox[j] = temp;
		int temp1 = (int)sbox[i] + (int)sbox[j];
		int t = temp1 % 256;
		char k = sbox[t];
		char cipherchar = ch ^ k;
		fputc(cipherchar, fp2);
		ch = fgetc(fp1);
	}
}

/*加密总函数*/
void DataEncrypt(char *k, unsigned char *key, unsigned char *sbox, FILE *fp1, FILE *fp2) {
	int len = strlen(k);
	KeyExpansion(key, k, len);
	InitSbox(sbox);
	UpsetSbox(sbox, key);
	DataProcess(sbox, fp1, fp2);
	fclose(fp1);
	fclose(fp2);
}

int main()
{
	char *k = (char *)malloc(25 * sizeof(char));
	unsigned char key[256] = { 0x00 };
	unsigned char sbox[256] = { 0x00 };
	FILE *fp1, *fp2;

	fp1 = fopen("c://bbb.txt", "r");
	fp2 = fopen("c://ccc.txt", "w");

	cin >> k;
	DataEncrypt(k, key, sbox, fp1, fp2);
}

代码中打开文件,然后加密后写入到文件,可以下readfile回溯到main函数的位置,需要回溯多次才能看到main.

执行 KeyExpansion 密钥填充256数组。

接着就是执行填充 InitSbox 初始化S盒,盒子的初始化就是从1开始向后填充,每次递增,填充长度就是256

填充完成后,通过mov edx, dword ptr ss:[ebp-0x4]传递S盒首地址,让UpsetSbox打乱盒子的顺序,进入call 0x01241100继续分析。

这个地方,我们记下地址,放入IDA中分析一下看看,F5一下。



CRC 循环冗余校验码

该算法全称为CRC32循环冗余校验码,其主要作用适用于校验数据的完整性,CRC32多为32位,利用CRC32多项式的值从04C11DB7h或者EDB88320h中生成一张CRC32码表,该表具有256个元素,然后就可以根据数据表来计算字符串或者文件的CRC32值。

识别的关键点,就是找到初始化时动态生成CRC32码表的地方,找到该表就找到了具体的细节,如下CRC32的常见写法。

#include <stdio.h>  
#include <stdlib.h>  
#include <windows.h>  

DWORD CRC32(BYTE* ptr, DWORD Size)
{
	DWORD crcTable[256], crcTmp1;

	// 动态生成CRC-32表
	for (int i = 0; i<256; i++)
	{
		crcTmp1 = i;
		for (int j = 8; j>0; j--)
		{
			if (crcTmp1 & 1) crcTmp1 = (crcTmp1 >> 1) ^ 0xEDB88320L;
			else crcTmp1 >>= 1;
		}
		crcTable[i] = crcTmp1;
	}
	// 计算CRC32值
	DWORD crcTmp2 = 0xFFFFFFFF;
	while (Size--)
	{
		crcTmp2 = ((crcTmp2 >> 8) & 0x00FFFFFF) ^ crcTable[(crcTmp2 ^ (*ptr)) & 0xFF];
		ptr++;
	}
	return (crcTmp2 ^ 0xFFFFFFFF);
}

int main(int argc,char *argv[])
{
	char* ptr = "hello lyshark";
	DWORD size = sizeof("hello lyshark");

	DWORD ret = CRC32((BYTE *)ptr, size);
	printf("CRC32 = %x 
", ret);

	system("pause");
	return 0;
}

跟随到生成的位置,在002610E6处下断点,然后运行每点击一次运行,eax中就会存储一个CRC32值,直到循环完成256次为止。

运行完256次以后,生成的码表如下所示。

static const ub4 crctab[256] = {
  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
  0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
  0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
  0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
  0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
  0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
  0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
  0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
  0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
  0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
  0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
  0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
  0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
  0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
  0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
  0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
  0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
  0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
  0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
  0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
  0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
  0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
  0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
  0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
  0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
  0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
  0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
  0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
  0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
  0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
  0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
  0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
  0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
  0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
  0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
  0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
  0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
  0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
  0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
  0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
  0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
};

TEA 分组加密算法

TEA算法是一种分组加密算法,它的实现非常简单,通常只需要很精短的几行代码,该算法分组长度为64位,密钥长度为128位,其作者推荐使用32次循环加密,即64轮,识别该算法的关键点应该在于,第一是加密循环次数一般为32次,其次识别delta中的压缩常数,该常数由黄金分割点得到。

第一个加密代码格式如下,编译代码载入反汇编器中观察特征。

#include <stdio.h>
#include <Windows.h>

void encrypt(unsigned int *A, unsigned int *B) {
	int j;
	unsigned int v0 = A[0], v1 = A[1], sum = 0, delta = 0x9E3779B9;
	for (j = 0; j<32; j++) {
		v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + B[sum & 3]);
		sum += delta;
		v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + B[(sum >> 11) & 3]);

	}
	A[0] = v0;
	A[1] = v1;
}

void  decrypt(unsigned int *A, unsigned int *B) {
	int j;
	unsigned int v0 = A[0], v1 = A[1], delta = 0x9E3779B9, sum = delta * 32;
	for (j = 0; j<32; j++) {
		v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + B[(sum >> 11) & 3]);
		sum -= delta;
		v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + B[sum & 3]);
	}
	A[0] = v0;
	A[1] = v1;
}


int main(int argc,char *argv[])
{
	unsigned int A[2], B[4], i;

	// 输入加密明文  4920616D 20353831
	for (i = 0; i < 2; i++)
		scanf("%X", &A[i]);
	// 输入加密密钥 31373031 30353831 73656375 72697479
	for (i = 0; i < 4; i++)
		scanf("%x", &B[i]);

	// 加密后输出
	encrypt(A, B);
	for (i = 0; i < 2; i++)
		printf("加密后的数据: %X ", A[i]);


	// 请输入解密密文 F5EE41C1 F31464F7
	for (i = 0; i < 2; i++)
		scanf("%X", &A[i]);

	// 输入解密密钥 31373031 30353831 73656375 72697479
	for (i = 0; i < 4; i++)
		scanf("%x", &B[i]);
	decrypt(A, B);

	 for(i = 0; i < 2;i++)
		printf("解密后的数据: %X ",A[i]);
	system("pause");
	return 0;
}

第一步下断点,这里可以下一个scanf断点,然后可以回溯到程序凌空,直接调用,调用加密算法之前会将加密的密钥与待加密数据一同压入传递到加密函数中。

继续跟进加密函数,如下通过分析后得出结论,第一次循环时sub eax, 0x61C88647中存储的就是我们的压缩常数,第二次循环时此值就会发生变化,其次,经过分析,代码mov ecx, dword ptr ds:[ebx+ecx*4] ecx中的值始终会以72697479 73656375 30353831 31373031轮询这变化,当一轮过后继续轮询下一轮,轮询(反向)的就是我们输入的密钥,默认循环32次。

循环结束后,通过push dword ptr ss:[ebp+esi*4-0x1C]取值并压栈,然后打印出加密后的数值,解密的过程与加密相同。

通过IDA分析一下算法的逻辑,基本上就是异或运算的合集,仔细分析并不难理解。

该加密算法简单容易实现,但是算法容易受到相关密钥攻击,所以一般会配合MD5等算法混合加密使用,如下代码是由一个TEA及MD5算法保护的案例,分析一下学习学习,大体源代码如下,首先使用md5对用户输入的名字进行散列,散列后拷贝到内存,将散列值作为TEA_KEY对用户输入的dwMessage消息进行加密,加密后将结果拷贝到szBuffer中,然后对szBuffer进行异或处理,最后对比用户hash与加密hash是否一致。

首先下断,下一个GetDlgItemTextA然后输入注册码,注册,回溯到程序领空,然后向下单步,注意call发现,md5压缩常数,可断定采用了md5算法。

继续跟随,分析首先将用户名,等通过md5散列,将得到的值当作密钥密码来使用,并将消息传递到 call 0x00401000 继续进行TEA加密。

最后将加密后的数据,与输入的数据对比,相同则完成注册。



AES 高级加密标准算法

AES 高级加密标准算法,其发展是从1997年开始的,AES其主要是用于替换DES而产生的,该算法具有128位的分组长度,支持192/256位的密钥长度,其算法仅支持128/192/256的密钥长度,分别称作AES-128,AES-192,AES-256。

PEID: https://down.52pojie.cn/?query=peid

首先使用PEID查看,发现其案例中包含有MD5算法的64个常量元素T表,还包括AES的S盒,以及逆S盒,根据特征可知其程序使用了MD5与AES进行加密保护的。

首先下一个GetDlgItemTextA命令,当用户输入数据提交时会断下,回溯一层,首先看到的时代码中使用了MD5算法对用户名进行了散列值计算,此处将产生128位hash散列,并将结果保存在edx指向的内存中暂存。

对比一下源代码中AES的密钥部分,会发现,密钥就是在AES初始化时被读入内存的,这里需要留意。

以下数据窗口中,一共有128位,可判断时AES密钥,具体版本AES-128。

直接跟进 call 0x00401EC0 也就是AES初始化的CALL,看看是如何初始化的。

AES加密循环轮数,与加密过程,根据不同的密钥长度,AES会经历不同的轮数,以AES-128为例,看下表,该算法会经历至少10次轮询对其数组进行变换,最终将轮询结果复制到输出数组中,即得到最终密文。

AES-128 经历10次轮询 / AES-192 经历12次轮询 / AES-256 经历14次轮询

接着是初始化AES加密子密钥生成的循环部分,首先拷贝用于密钥扩展的轮常量Rcon,该轮常量也可以用来识别该算法。

加密轮询主要由4部分 SubBytes(),ShiftRows(),MixColumns(),AddRoundKey() 组成。

subbyte()表示字节代换,其实是一个简单的查表操作,AES定义了一个16*16字节的S盒,该盒中,每个字节元素的高4位作为行标,低4位作为列标,并取出相应的元素作为SubBytes操作的结果。

跟进call,进入到SubByte中,可看到查表操作。

出call后,以下地方需要识别注意。

本案例中的密钥扩展函数中,同时生成了用于解密的子密钥,继续单步向下,可看到 aes_encrypt加密函数。

直接F7 跟进到 call 0x004023A0 程序先对工作模式判断,本例ECB模式,直接继续调用 aes_ecb_encrypt 也就是 call 0x004025F0

继续跟进004025F0首先AES加密过程通常是通过查询4个表(T0,T1,T2,T3)来实现的,加密前首先会运行一次AddRoundKey该函数的作用是,将状态中的元素与轮密钥通过简单异或运算,轮密钥是由用户输入的密钥扩展而来的,同样可以看作一个状态数组。

上方代码运行四次过后,密钥与输入的激活码进行了异或操作,第一次打乱,接着继续向下执行轮函数,本案例中使用了128位算法,故需要执行10轮加密,先来循环9次,同样的此处的四张表地址,也可以作为识别AES算法的依据。

如下是最后一次,最后一次为特殊的处理,总共凑齐10次循环,最后一轮主要执行的是MixColumns操作。

S盒为256个元素的数组,即1个字节(0x00~0xff)可以表示的数量,正向S盒中初始化的元素为。

unsigned char S[256] = {
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
};

解密时逆字节替换就是使用逆S盒进行字节替换,逆S盒为:

unsigned char inv_S[256] = {
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
};

下面是从网上找到的一段AES加密代码,我们编译后研究一下识别方式,其原理同上。

展开代码
#include <stdio.h>
#include <windows.h>

/*s盒矩阵:The AES Substitution Table*/// 256 位的密匙256 位支持长度为32 个字符
static const unsigned char sbox[256] = {
	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,
};
//逆向S 盒矩阵
static const unsigned char contrary_sbox[256] = {
	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,
};
/*轮常量表 The key schedule rcon table*/
static const unsigned char Rcon[10] = {
	0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36 };

//辅助函数
/*有限域*2乘法 The x2time() function */
static unsigned char x2time(unsigned char x)
{
	if (x & 0x80)
	{
		return (((x << 1) ^ 0x1B) & 0xFF);
	}
	return x << 1;
}
/*有限域*3乘法 The x2time() function */
static unsigned char x3time(unsigned char x)
{
	return (x2time(x) ^ x);
}
/*有限域*4乘法 The x4time() function */
static unsigned char x4time(unsigned char x)
{
	return (x2time(x2time(x)));
}
/*有限域*8乘法 The x8time() function */
static unsigned char x8time(unsigned char x)
{
	return (x2time(x2time(x2time(x))));
}
/*有限域9乘法 The x9time() function */
static unsigned char x9time(unsigned char x)	//9:1001
{
	return (x8time(x) ^ x);
}
/*有限域*B乘法 The xBtime() function */
static unsigned char xBtime(unsigned char x)	//B:1011
{
	return (x8time(x) ^ x2time(x) ^ x);
}
/*有限域*D乘法 The xDtime() function */
static unsigned char xDtime(unsigned char x)	//D:1101
{
	return (x8time(x) ^ x4time(x) ^ x);
}
/*有限域*E乘法 The xEtime() function */
static unsigned char xEtime(unsigned char x)	//E:1110
{
	return (x8time(x) ^ x4time(x) ^ x2time(x));
}
/*第三类操作:列混合操作 MixColumns: Process the entire block*/
static void MixColumns(unsigned char *col)//列混合
{
	unsigned char tmp[4], xt[4];
	int i;
	for (i = 0; i<4; i++, col += 4)  //col代表一列的基地址,col+4:下一列的基地址
	{
		tmp[0] = x2time(col[0]) ^ x3time(col[1]) ^ col[2] ^ col[3];	//2 3 1 1
		tmp[1] = col[0] ^ x2time(col[1]) ^ x3time(col[2]) ^ col[3];	//1 2 3 1
		tmp[2] = col[0] ^ col[1] ^ x2time(col[2]) ^ x3time(col[3]);	//1 1 2 3
		tmp[3] = x3time(col[0]) ^ col[1] ^ col[2] ^ x2time(col[3]);	//3 1 1 2
//修改后的值 直接在原矩阵上修改
		col[0] = tmp[0];
		col[1] = tmp[1];
		col[2] = tmp[2];
		col[3] = tmp[3];
	}
}
//逆向列混淆
static void Contrary_MixColumns(unsigned char *col)
{
	unsigned char tmp[4];
	unsigned char xt2[4];//colx2
	unsigned char xt4[4];//colx4
	unsigned char xt8[4];//colx8
	int x;
	for (x = 0; x<4; x++, col += 4)
	{
		tmp[0] = xEtime(col[0]) ^ xBtime(col[1]) ^ xDtime(col[2]) ^ x9time(col[3]);
		tmp[1] = x9time(col[0]) ^ xEtime(col[1]) ^ xBtime(col[2]) ^ xDtime(col[3]);
		tmp[2] = xDtime(col[0]) ^ x9time(col[1]) ^ xEtime(col[2]) ^ xBtime(col[3]);
		tmp[3] = xBtime(col[0]) ^ xDtime(col[1]) ^ x9time(col[2]) ^ xEtime(col[3]);
		col[0] = tmp[0];
		col[1] = tmp[1];
		col[2] = tmp[2];
		col[3] = tmp[3];
	}
}
/*第二类操作:行移位:行左循环移位 ShiftRows:Shifts the entire block*/
static void ShiftRows(unsigned char *col)//正向行移位
{
	unsigned char t;
	t = col[1]; col[1] = col[5]; col[5] = col[9]; col[9] = col[13]; col[13] = t;
	t = col[2]; col[2] = col[10]; col[10] = t;
	t = col[6]; col[6] = col[14]; col[14] = t;
	t = col[15]; col[15] = col[11]; col[11] = col[7]; col[7] = col[3]; col[3] = t;
}
//逆向行移位
static void Contrary_ShiftRows(unsigned char *col)
{
	unsigned char t;
	t = col[13]; col[13] = col[9]; col[9] = col[5]; col[5] = col[1]; col[1] = t;
	t = col[2]; col[2] = col[10]; col[10] = t;
	t = col[6]; col[6] = col[14]; col[14] = t;
	t = col[3]; col[3] = col[7]; col[7] = col[11]; col[11] = col[15]; col[15] = t;
}
/*第一类操作:s盒字节代换替换 SubBytes*/
static void SubBytes(unsigned char *col)//字节代换
{
	int x;
	for (x = 0; x<16; x++)
	{
		col[x] = sbox[col[x]];
	}
}
//逆向字节代换
static void Contrary_SubBytes(unsigned char *col)
{
	int x;
	for (x = 0; x<16; x++)
	{
		col[x] = contrary_sbox[col[x]];
	}
}
/*第四类操作:轮密钥加 AddRoundKey*/
static void AddRoundKey(unsigned char *col, unsigned char *expansionkey, int round)//密匙加
{
	//扩展密钥:44*32bit =11*4* 4*8 =  16字节*11轮,每轮用16字节密钥
	//第0轮,只进行一次轮密钥加
	//第1-10轮,轮密钥加
	int x;
	for (x = 0; x<16; x++)	//每1轮操作:4*32bit密钥 = 16个字节密钥
	{
		col[x] ^= expansionkey[(round << 4) + x];
	}
}
/* AES加密总函数 10轮4类操作 Encrypt a single block with Nr Rounds(10,12,14)*/
void AesEncrypt(unsigned char *blk, unsigned char *expansionkey, int Nr)//加密一个区块
{
	//输入blk原文,直接在上面修改,输出blk密文
	//输入skey:
	//输入Nr = 10轮
	int round;
	//第1轮之前:轮密钥加
	AddRoundKey(blk, expansionkey, 0);
	//第1-9轮:4类操作:字节代换、行移位、列混合、轮密钥加
	for (round = 1; round <= (Nr - 1); round++)
	{
		SubBytes(blk);		//输入16字节数组,直接在原数组上修改
		ShiftRows(blk);		//输入16字节数组,直接在原数组上修改
		MixColumns(blk);	//输入16字节数组,直接在原数组上修改
		AddRoundKey(blk, expansionkey, round);
	}
	//第10轮:不进行列混合
	SubBytes(blk);
	ShiftRows(blk);
	AddRoundKey(blk, expansionkey, Nr);
}
//AES 解密总函数
void Contrary_AesEncrypt(unsigned char *blk, unsigned char *expansionkey, int Nr)
{
	int x;
	/* unsigned char *contrary_key=key;
	for(x=0;x<11;x++,key+=16)
	Contrary_MixColumns(key);*/
	AddRoundKey(blk, expansionkey, Nr);
	Contrary_ShiftRows(blk);
	Contrary_SubBytes(blk);
	for (x = (Nr - 1); x >= 1; x--)
	{
		AddRoundKey(blk, expansionkey, x);
		Contrary_MixColumns(blk);
		Contrary_ShiftRows(blk);
		Contrary_SubBytes(blk);
	}
	AddRoundKey(blk, expansionkey, 0);
}
/*//密钥编排,16字节--->44列32bit密钥生成--> 11组16字节:分别用于11轮 轮密钥加运算
Schedule a secret key for use.
*outkey[] must be 16*15 bytes in size
*Nk==number of 32 bit words in the key,e.g.,4,6,8
*Nr==number of rounds,e.g.,10,12,14
*/
void ScheduleKey(unsigned char *inkey, unsigned char *outkey, int Nk, int Nr)//安排一个保密密钥使用
{
	//inkey:初始16字节密钥key
	//outkey:11组*16字节扩展密钥expansionkey
	//Nk:4列
	//Nr:10轮round
	unsigned char temp[4], t;
	int x, i;
	/*copy the key*/
	//第0组:[0-3]直接拷贝
	for (i = 0; i<(4 * Nk); i++)
	{
		outkey[i] = inkey[i];
	}
	//第1-10组:[4-43]
	i = Nk;
	while (i<(4 * (Nr + 1))) //i=4~43 WORD 32bit的首字节地址,每一个4字节
	{//1次循环生成1个字节扩展密钥,4次循环生成一个WORD
	 //temp:4字节数组:代表一个WORD密钥
	 /*temp=w[i-1]*/
	 //i不是4的倍数的时候
	 //每个temp = 每个outkey32bit = 4字节
		for (x = 0; x<4; x++)
			temp[x] = outkey[(4 * (i - 1)) + x];	//i:32bit的首字节地址
													//i是4的倍数的时候
		if (i%Nk == 0)
		{
			/*字循环:循环左移1字节 RotWord()*/
			t = temp[0]; temp[0] = temp[1]; temp[1] = temp[2]; temp[2] = temp[3]; temp[3] = t;
			/*字节代换:SubWord()*/
			for (x = 0; x<4; x++)
			{
				temp[x] = sbox[temp[x]];
			}
			/*轮常量异或:Rcon[j]*/
			temp[0] ^= Rcon[(i / Nk) - 1];
		}
		/*w[i] = w[i-4]^w[i-1]*/
		for (x = 0; x<4; x++)
		{
			outkey[(4 * i) + x] = outkey[(4 * (i - Nk)) + x] ^ temp[x];
		}
		++i;
	}
}
int main(void) {
	/*
	pt:原文16字节-->密文
	key:原密钥16字节
	skey:密钥扩展44long
	sbox:s盒
	*/

	unsigned char pt[17], key[17];
	unsigned char expansionkey[15 * 16];
	int i;
	int j;
	printf("输入需要加密字符串: 
");//输入无格式的字符串字符个数不得少于六个!
	scanf("%s", pt);
	printf("输入加密密钥: 
");//输入加密钥匙密匙个数不得低于六个!
	scanf("%s", key);

	/*加密*/
	ScheduleKey(key, expansionkey, 4, 10);	//1、密钥扩展生成
	AesEncrypt(pt, expansionkey, 10);		//2、AES 加密
	printf("加密后的数据:  ");	//输出密码文件

	for (i = 0; i < 16; i++)
		printf("%02x ", pt[i]);
	printf("
");

	/*解密*/
	Contrary_AesEncrypt(pt, expansionkey, 10);//AES 解密
	printf("解密后的数据: ");//将解密文件输出
	for (i = 0; i < 16; i++)
		printf("%c ", pt[i]);

	system("pause");
	return 0;
}

RSA 大素数算法

RSA算法是一个既能够用于数据加密也能够用于数据签名的算法,其应用非常的广泛,算法安全性上面具有一定的可信度,该算法的安全性依赖于大整数因式分解,也就是说该算法的安全性依赖于密钥的位数,其位数也一直在增加,一般的RSA算法需要使用1024位或者更长的模数才能保障安全性,这个算法具体我也不太懂,只是学习一下如何识别吧。

先来看一下,PEID对其算法的识别情况,提示是大整数,后端源代码如下。

这里如果要是想破解,可以将里面的模数n替换,首先替换成我们自己的n,然后用自己的d来编写注册机。

如果算法中的RSA模n的长度是128位,那么可以使用软件进行因式分解,首先,通过跟踪分析得到n,再将n因式分解,求出私钥d,进而编写出注册机。

如上我们需要关注两点,一个是n一个则是e,提取出来之后使用RSA-Tool工具,分别填入modulus内容,然后分解因式。

n = 0x80C07AFC9D25404D6555B9ACF3567CF1 / e = 0x10001

即可得到 n = pq = A554665CC62120D3 x C75CB54BEDFA30AB 然后通过欧几里得扩展算法,即可得到d,使用RSATools工具,在PrivateExponentd上面输入e,然后点击Calc.D,即可计算出。d = 651A40B9739117EF505DBC33EB8F442D

假设输入用户名lyshark,其ASCII值 m = 6C79736861726Bh 生成注册码c的加密算法为c = m(d) mod n

6C79736861726B (651A40B9739117EF505DBC33EB8F442D) mod 80C07AFC9D25404D6555B9ACF3567CF1

使用BigIntegerCalculator 大数计算器,填入得到的xyz,计算后即可得到注册码。

可知用户 lyshark 其对应的注册码就是 D729A2A4683F1E03BB5A0067B51B33A 这里密码学中关于公式的相关内容我直接跳过了,什么欧几里得,啥的烂七八糟的,我不太懂也不想懂,会干活就行,管他算法是怎么做出来的,唯一的目的是怎么破了他。

总结: 常见的加密算法就这么几个,当然在加密与解密算法篇还介绍了其他的一些冷门算法知识,我没有全部学下来,只是找了几个重点看了看,如果你对其他冷门算法感兴趣,可以自己去看《加密与解密 - 解密篇》这本书,如果想要更深入的分析可以去看《密码编码学与网络安全》。


版权声明: 本博客,文章与代码均为学习时整理的笔记,博客中除去明确标注有参考文献的文章,其他文章【均为原创】作品,转载请务必【添加出处】,您添加出处是我创作的动力!

警告:如果您恶意转载本人文章,则您的整站文章,将会变为我的原创作品,请相互尊重!
原文地址:https://www.cnblogs.com/LyShark/p/13407677.html