逆向学习-基于对AES算法的学习,进行了 强网杯2023-dotdot题目的复现

发现自己对AES算法的理解非常不好,于是在学习的过程中,顺便复现了一下dotdot这道题目,参考了许多文章和视频,学习到了很多知识,很好的一道题目。(涉及考点:[白盒AES的攻破方法][反序列化漏洞的初步了解][RC4加密算法解出正确的文件][TEA加密解出dat文件反序列化缺失数据])

这篇博客首先会介绍一下AES的算法,然后会有这道题目的复现过程。

AES加密介绍

【基础概念】

高级加密标准,原型为Rijndael加密算法(Rain Doll)

它支持128(10轮),192(12),256(14)位密钥长度

明文的长度固定为128位(16个字节)

基本处理单元是字节(以有限域上的多项式表示)

(字节)byte - 由8(比特)bit序列构成

数据的表示方法(4x4矩阵排列)

【加密方式】

(以128位为例)

·大致流程


进行最终轮的过程没有列混合步骤

·初始变换

明文与密钥对应位置按字节异或操作

形成的结果作为第零轮的密钥

·循环运算

①字节代换

输入数据(明文)根据s盒进行数据代换

s盒(16x16大小)256个位置的表格

更换之前的数据是19,我们就在s盒对应的行和列查找数据,进行替换

②行移位(向左移位,依次增加)

③列混合

已有固定不变的正矩阵(长下面这样)

左乘正矩阵

④轮密钥加

上一步得到的结果与子密钥矩阵(初始子密钥进行密钥扩展)进行异或操作

{异或操作的原理}

就是转化为二进制那样子。然后计算完再转化为相应的进制数据

{密钥拓展的原理}

一列一列进行拓展(定义wi列,然后看i是不是4的倍数,进行不同的运算)

不同的列数进行不同的运算

若不是4的倍数

w5的运算就是w1异或w4,

如果是4的倍数

要进行T函数进行加密再异或

{T函数的原理}

(1)字循环(输入字进行循环)

(2)字节代换

对上一步字循环的结果进行s盒替换

(3)轮常量异或

常量就是Rcon[i],就是第i列

Rcon[i]

前两步得到的结果一列一列进行异或操作

T函数进行完之后,记得再与-4列进行异或

【解密方式】

就是把上述过程反过来一遍(解密的第一轮没有列混合逆向)

【逆向特征】

AES题目的解密方式是一样的,但是有两种形式出现

·普通AES

找到密钥,找到密文,逆向脚本,进行解密。形式在反编译中大致如下

·白盒AES

其实加密的效果与AES是完全一致的,但是这种方式的密钥在没用工具的情况下是找不到的,要利用下面题目的工具求解密钥,再求明文。反编译中大致如下

public static void AAA(byte[] aaa, byte[] bbb)
{
	for (int i = 0; i < 9; i++)
	{
		Program.GGG(aaa);
		for (int j = 0; j < 4; j++)
		{
			uint num = Program.v11[i, 4 * j, (int)aaa[4 * j]];
			uint num2 = Program.v11[i, 4 * j + 1, (int)aaa[4 * j + 1]];
			uint num3 = Program.v11[i, 4 * j + 2, (int)aaa[4 * j + 2]];
			uint num4 = Program.v11[i, 4 * j + 3, (int)aaa[4 * j + 3]];
			uint num5 = (uint)Program.v12[i, 24 * j, (int)(num >> 28 & 15U), (int)(num2 >> 28 & 15U)];
			uint num6 = (uint)Program.v12[i, 24 * j + 1, (int)(num3 >> 28 & 15U), (int)(num4 >> 28 & 15U)];
			uint num7 = (uint)Program.v12[i, 24 * j + 2, (int)(num >> 24 & 15U), (int)(num2 >> 24 & 15U)];
			uint num8 = (uint)Program.v12[i, 24 * j + 3, (int)(num3 >> 24 & 15U), (int)(num4 >> 24 & 15U)];
			aaa[4 * j] = (byte)((int)Program.v12[i, 24 * j + 4, (int)num5, (int)num6] << 4 | (int)Program.v12[i, 24 * j + 5, (int)num7, (int)num8]);
			num5 = (uint)Program.v12[i, 24 * j + 6, (int)(num >> 20 & 15U), (int)(num2 >> 20 & 15U)];
			num6 = (uint)Program.v12[i, 24 * j + 7, (int)(num3 >> 20 & 15U), (int)(num4 >> 20 & 15U)];
			num7 = (uint)Program.v12[i, 24 * j + 8, (int)(num >> 16 & 15U), (int)(num2 >> 16 & 15U)];
			num8 = (uint)Program.v12[i, 24 * j + 9, (int)(num3 >> 16 & 15U), (int)(num4 >> 16 & 15U)];
			aaa[4 * j + 1] = (byte)((int)Program.v12[i, 24 * j + 10, (int)num5, (int)num6] << 4 | (int)Program.v12[i, 24 * j + 11, (int)num7, (int)num8]);
			num5 = (uint)Program.v12[i, 24 * j + 12, (int)(num >> 12 & 15U), (int)(num2 >> 12 & 15U)];
			num6 = (uint)Program.v12[i, 24 * j + 13, (int)(num3 >> 12 & 15U), (int)(num4 >> 12 & 15U)];
			num7 = (uint)Program.v12[i, 24 * j + 14, (int)(num >> 8 & 15U), (int)(num2 >> 8 & 15U)];
			num8 = (uint)Program.v12[i, 24 * j + 15, (int)(num3 >> 8 & 15U), (int)(num4 >> 8 & 15U)];
			aaa[4 * j + 2] = (byte)((int)Program.v12[i, 24 * j + 16, (int)num5, (int)num6] << 4 | (int)Program.v12[i, 24 * j + 17, (int)num7, (int)num8]);
			num5 = (uint)Program.v12[i, 24 * j + 18, (int)(num >> 4 & 15U), (int)(num2 >> 4 & 15U)];
			num6 = (uint)Program.v12[i, 24 * j + 19, (int)(num3 >> 4 & 15U), (int)(num4 >> 4 & 15U)];
			num7 = (uint)Program.v12[i, 24 * j + 20, (int)(num & 15U), (int)(num2 & 15U)];
			num8 = (uint)Program.v12[i, 24 * j + 21, (int)(num3 & 15U), (int)(num4 & 15U)];
			aaa[4 * j + 3] = (byte)((int)Program.v12[i, 24 * j + 22, (int)num5, (int)num6] << 4 | (int)Program.v12[i, 24 * j + 23, (int)num7, (int)num8]);
			num = Program.v13[i, 4 * j, (int)aaa[4 * j]];
			num2 = Program.v13[i, 4 * j + 1, (int)aaa[4 * j + 1]];
			num3 = Program.v13[i, 4 * j + 2, (int)aaa[4 * j + 2]];
			num4 = Program.v13[i, 4 * j + 3, (int)aaa[4 * j + 3]];
			num5 = (uint)Program.v12[i, 24 * j, (int)(num >> 28 & 15U), (int)(num2 >> 28 & 15U)];
			num6 = (uint)Program.v12[i, 24 * j + 1, (int)(num3 >> 28 & 15U), (int)(num4 >> 28 & 15U)];
			num7 = (uint)Program.v12[i, 24 * j + 2, (int)(num >> 24 & 15U), (int)(num2 >> 24 & 15U)];
			num8 = (uint)Program.v12[i, 24 * j + 3, (int)(num3 >> 24 & 15U), (int)(num4 >> 24 & 15U)];
			aaa[4 * j] = (byte)((int)Program.v12[i, 24 * j + 4, (int)num5, (int)num6] << 4 | (int)Program.v12[i, 24 * j + 5, (int)num7, (int)num8]);
			num5 = (uint)Program.v12[i, 24 * j + 6, (int)(num >> 20 & 15U), (int)(num2 >> 20 & 15U)];
			num6 = (uint)Program.v12[i, 24 * j + 7, (int)(num3 >> 20 & 15U), (int)(num4 >> 20 & 15U)];
			num7 = (uint)Program.v12[i, 24 * j + 8, (int)(num >> 16 & 15U), (int)(num2 >> 16 & 15U)];
			num8 = (uint)Program.v12[i, 24 * j + 9, (int)(num3 >> 16 & 15U), (int)(num4 >> 16 & 15U)];
			aaa[4 * j + 1] = (byte)((int)Program.v12[i, 24 * j + 10, (int)num5, (int)num6] << 4 | (int)Program.v12[i, 24 * j + 11, (int)num7, (int)num8]);
			num5 = (uint)Program.v12[i, 24 * j + 12, (int)(num >> 12 & 15U), (int)(num2 >> 12 & 15U)];
			num6 = (uint)Program.v12[i, 24 * j + 13, (int)(num3 >> 12 & 15U), (int)(num4 >> 12 & 15U)];
			num7 = (uint)Program.v12[i, 24 * j + 14, (int)(num >> 8 & 15U), (int)(num2 >> 8 & 15U)];
			num8 = (uint)Program.v12[i, 24 * j + 15, (int)(num3 >> 8 & 15U), (int)(num4 >> 8 & 15U)];
			aaa[4 * j + 2] = (byte)((int)Program.v12[i, 24 * j + 16, (int)num5, (int)num6] << 4 | (int)Program.v12[i, 24 * j + 17, (int)num7, (int)num8]);
			num5 = (uint)Program.v12[i, 24 * j + 18, (int)(num >> 4 & 15U), (int)(num2 >> 4 & 15U)];
			num6 = (uint)Program.v12[i, 24 * j + 19, (int)(num3 >> 4 & 15U), (int)(num4 >> 4 & 15U)];
			num7 = (uint)Program.v12[i, 24 * j + 20, (int)(num & 15U), (int)(num2 & 15U)];
			num8 = (uint)Program.v12[i, 24 * j + 21, (int)(num3 & 15U), (int)(num4 & 15U)];
			aaa[4 * j + 3] = (byte)((int)Program.v12[i, 24 * j + 22, (int)num5, (int)num6] << 4 | (int)Program.v12[i, 24 * j + 23, (int)num7, (int)num8]);
		}
	}
	Program.GGG(aaa);
	for (int k = 0; k < 16; k++)
	{
		aaa[k] = Program.v14[9, k, (int)aaa[k]];
	}
	for (int l = 0; l < 16; l++)
	{
		bbb[l] = aaa[l];
	}
}

 强网杯2023-dotdot复现

首先就是对于使用工具的选择,经过IDE检查原来我们不止要看程序是多少位,还可以看到之前在WP中看到的.NET结构,然后放进dnSpy中去分析,

在网上简单查了一下关于逆向.NET的信息

是一种编程语言,这里的就是C#语言,

我们放进dnspy

在这条目录下就是我们要分析的东西

基于本题是关于AES加密的题目,我们先看看每条函数是干啥的

先进的BBB 进去看是个判断明文长度是否为16的函数

[白盒AES的攻破方法]

白盒AES突破关键点就是获取不让我们得到的原始密钥

DFA (Differential Fault Analysis) 分析

找回消失的密钥 --- DFA分析白盒AES算法

使用这种攻击手段,通过改变最后两轮(第八轮,第九轮)列混合的中间数据,然后使得最后的结果出现四个字节的差异,进行每一位中间数据的改变,得到十六组有差异的加密结果,再使用phoenixAES 工具对这些数据进行分析,分析出第十轮(最后一轮)密钥,然后再使用Stark工具破解出第一轮的原始密钥,话不多说 我们实践一下去。

第一步使用DFA攻击生成缺陷数据

从dnSpy中去找到白盒AES加密的源码,然后在相应位置进行破坏

看一篇WP的意思应该就是在原来加密代码的基础上,在进行一些c#代码的书写以及完善

在dnpay中直接动调起来就可以,动调的过程中 进入到AAA然后进入GGG 开始引用这个密文

这九次循环,就是排除了最后一次特殊加密的加密,我们修改数据就要让他停止在这个循环外面,然后修改此时的aaa数据

输入数据:asdfghjklasdfghj

下面第一次出错的原因可能是,没进GGG函数

81312F165839D90CC121819C061ED69F  原始要修改的数据
45B21E5DE3880F13A82069E85C17E808  第十轮加密的正确形式
91B21E5DE3880F13A82069E85C17E808  (仅有1字节的错误,说明我们修改的时机太晚了 需要早些修改)
这里分析出错的原因是断点打早了 没有进GGG函数


8139819F5821D616C11E2F0C0631D99C  这个才是要修改的数据
45B21E5DE3880F13A82069E85C17E808  正确形式还是这个
39B21E5DE3880F13A82069E85C17E808  (第二次尝试还是失败了 看来不是这个问题 在提早一次试试)


84ADC522F6A38D0992166A0DA023913C  这个试试
45B21E5DE3880F13A82069E85C17E808
DDB21E5DE3880F62A82013E85C25E808  果然在这里我们成功得到了第一字节攻击的结果,下面我们再攻击15次
45B21EE0E388DE13A85669E86517E808  2
45B2605DE3110F13242069E85C17E894  3
45091E5DE5880F13A82069355C174208  4
45401E5D7F880F13A82069165C17B008  5
64B21E5DE3880F48A82065E85C07E808  6
45B21EBDE3881913A82369E80717E808  7
45B2A85DE3F80F13E62069E85C17E8AC  8
45B2715DE3110F131A2069E85C17E8B9  9
458B1E5D17880F13A82069E45C177F08  10
91B21E5DE3880FDAA820A0E85C06E808  11
45B21EB6E388A613A81269E8B317E808  12
45B21E93E388B213A8EF69E84D17E808  13
45B2C45DE38D0F13012069E85C17E877  14
45E31E5D62880F13A820699E5C177908  15
E7B21E5DE3880FABA82026E85CB4E808  16






第二步下载phoenixAES 库 然后进行第十轮密钥的求解

上一步获取了经过修改的加密数据,放进python求解

(学习在Pychorm中导入下载好的本地库)

进去这些目录当中,右键资源管理库打开,然后把下载好的本地库放到这个目录下就可使用了

找到了第十轮密钥 EA9F6BE2DF5C358495648BEAB9FCFF81

第三步 恢复原始密钥 利用stark

将下载好的文件夹放入linux虚拟机中,进行安装(在文件夹终端中输入make指令,就会生成相应的文件)

输入密钥 轮数

得到原始密钥

51574232303233486170707947616D65

QWB2023HappyGame

解出了密钥然后找密文在什么地方,(在dnspy中找了好久的数据,动调也找不到,但是在dotpeek中就可以找到相应的数据)

不知道为啥在dnspy中看不到CCC对比的密文

我们使用AES脚本解出来原文就是

WelcomeToQWB2023

[反序列化漏洞的初步了解]

进行完AES的解密之后,再看看这个程序下面是干什么的ccc进行了加密结果的比较,进入下一步ddd,对于文件的读取并赋值到了v7,进入eee对v7数据进行了RC4加密,然后进入了memorystream与binaryformatter中,

对于读取的License.dat文件,一开始我们对这个文件有什么作用是一点也不了解,后来大概是知道他是许可证的意思,可能就是只要这个许可证是正常的,那么就会实现一定的功能。

反序列化漏洞?

在网上查询了大致的文章之后,找到了一些关键词,序列化,物理内存,memory(内存),Binary

在好多地方找报错信息都没找到(因为按照wp做的题目,所以知道考点是什么,),然后在调试页面打开了输出选项

找到了我们想要的报错信息 以及出现错误的地点

引发异常: 'System.Runtime.Serialization.SerializationException' in mscorlib.dll
其他信息: 二进制流“0”不包含有效的 BinaryHeader。这可能是由于无效流,或由于在序列化和反序列化之间的对象版本更改。

出现如上报错,之前一直中断不到出现错误的地方,原来很多设置上的选项非常影响我们的分析。(调试-窗口 选项中有个-异常设置选项,我们需要把所有可能出现异常选择中断的选项全部勾选上 然后就可以在异常的一方停下来了)

[RC4加密算法解出正确的文件]

这一步的主要问题就是如何将存储在.dat文件中的数据进行RC4解密

我们在网上找到的解密脚本一般都是直接把数据写在代码中,提取文件数据就需要使用file等函数

#include<stdio.h>
#include<string.h>
#include <Windows.h>   //这个头文件很重要,关系到一些例如PBYTE ,PIMAGE_DOS_HEADER相关指针的调用 

/*
RC4初始化函数
*/
void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len_k)
{
	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_k];
	}
	for (i = 0; i < 256; i++) {
		j = (j + s[i] + k[i]) % 256;
		tmp = s[i];
		s[i] = s[j];
		s[j] = tmp;
	}
}

/*
RC4加解密函数
unsigned char* Data     加解密的数据
unsigned long Len_D     加解密数据的长度
unsigned char* key      密钥
unsigned long Len_k     密钥长度
*/
void rc4_crypt(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) //加解密
{
	unsigned char s[256];
	rc4_init(s, key, Len_k);
	int i = 0, j = 0, t = 0;
	unsigned long k = 0;
	unsigned char tmp;
	for (k = 0; k < Len_D; k++) {
		i = (i + 1) % 256;
		j = (j + s[i]) % 256;
		tmp = s[i];
		s[i] = s[j];
		s[j] = tmp;
		t = (s[i] + s[j]) % 256;
		Data[k] = Data[k] ^ s[t];
	}
}
int main()
{
	字符串密钥
	//unsigned char key[] = "zstuctf";
	//unsigned long key_len = sizeof(key) - 1;
	数组密钥
	unsigned char key[] = {};
	unsigned long key_len = sizeof(key);

	加解密数据
	//unsigned char data[] = { 0x7E, 0x6D, 0x55, 0xC0, 0x0C, 0xF0, 0xB4, 0xC7, 0xDC, 0x45,
	//	0xCE, 0x15, 0xD1, 0xB5, 0x1E, 0x11, 0x14, 0xDF, 0x6E, 0x95,
	//	0x77, 0x9A, 0x12, 0x99 };
	加解密
	//rc4_crypt(data, sizeof(data), key, key_len);

	//for (int i = 0; i < sizeof(data); i++)
	//{
	//	printf("%c", data[i]);
	//}
	//printf("\n");
	//return;
	
	//设计成对文件数据的读取

	unsigned char key[17] = "WelcomeToQWB2023";
	unsigned long len_key = 16;
	//c语言中我们使用指针类型去找我们文件的地址,用到了FILE* fp(意思就是file place 文件地址),紧接着使用fopen函数打开文件,rb就是可读的意思
	FILE* fp = fopen("License.dat", "rb");
	//这里可以进行一下判断,看看文件是否正常打开
	if (fp != NULL)
	{
		printf("文件读取成功!\n");
	}
	//计算文件的大小,使用fseek
	fseek(fp, 0, SEEK_END);//第二个位置是相对于第三个位置的偏移量,这里直接找到了文件最后位置 ,文件指示器指向了最后的位置
	//ftell用来指示当前文件指示器相对的偏移量,因为这里在最后,所以就是文件的大小了。 
	int size = ftell(fp);
	//*分配虚拟的空间大小, 
	//首先接着用fseek来放到初始位置
	fseek(fp, 0, SEEK_SET);
	//使用malloc函数来定义一段新的空间,函数中填写的大小就是刚才得到的, 
	//返回的值是一个void*指针,所以要求强制类型转换 
	PBYTE data = (PBYTE)malloc(size);
	//我们将文件数据复制到这个新定义的区域 ,使用了fread文件 
	fread(data, size, 1, fp);
	//获取了文件内容以及大小,我们放入这个解密函数中
	rc4_crypt(data, size, key, len_key);
	//这里解密完我们还要把生成好的写入生成新的文件,看看怎么做的
	//首先生成一个新文件
	fp = fopen("new_License.dat", "wb");//把文件指示器指向新文件的开头部分

	if (fp == NULL)
	{
		printf("文件创建失败了");
	}
	//把加密后的数据写入新的文件中
	fwrite(data, size, 1, fp);
	//善始善终,我们关闭打开的文件,释放生成的空间
	fclose(fp);
	free(data);
}

RC4的脚本是在网上找的,涉及到的文件数据读取写入操作是复习了一下当时写PEloader代码的时候,用到的file相关函数,然后在vs里面不知道怎么运行,文件放到什么位置,于是乎直接放到dev里面跑了一遍(还是质朴一点的好运行),

生成了我们想要的解密文件,放入010里面去看看

解密后的看起来就十分的正常,然后我们在去dnspy中找找看,反序列化出现报错的内存地址是多少

具体地址要找到对应的参数,那这个参数是什么呢?

二进制流“0”不包含有效的 BinaryHeader报错是这个,说二进制中有无效的0,如果第一次碰到这种情况肯定是懵的,根本不知道信息残缺这回事,做过一次这个也算是有了一种思路吧。后来自己就慢慢找参数,可算是找到报错位置的参数是什么了

局部变量-MemoryStream-position 这个位置中终于是找到了错误位置地址,0x294,我们返回010

这里就是294,但是我们怎么把缺失的数据填写进去呢,填写的具体位置,顺序是什么

看到了引用函数FFF的地方,我们的思路就进入了下一步解密

[TEA加密解出dat文件反序列化缺失数据]

这里有些数据提取,要仔细阅读反编译代码中的关系,然后去找数据,TEA就是要找到关键的密文以及key

在反编译代码中 若没有一眼看到数据 主要就是通过对比cmp函数看看是否进行了赋值操作,这里的开头结尾就是进行了比较,从v6得到了key,v28得到了密文。进一步去交叉引用v6上下文,我们发现v6就是上一步得到的原文WelcomeToQWB2023

数据类型转化

进行TEA解密的时候,我们之前数据类型转化一直搞不太懂,这里又复习了一下,

脚本
#include <stdio.h>
#include <string.h>

void decodeTEA(unsigned int* v,  long long num ,unsigned int key[])
{
	unsigned int sum = 32 * num;
	unsigned int v0 = v[0];
	unsigned int v1 = v[1];
	for (int i = 0; i < 32; i++)
	{
		v1 -= ((v0 << 4) + key[2]) ^ (v0 + sum) ^ ((v0 >> 5) + key[3]);
		v0 -= ((v1 << 4) + key[0] )^ (v1 + sum) ^ ((v1 >> 5) + key[1]);
		sum -= num;
	}
	v[0] = v0;
	v[1] = v1;
}

int main()
{
	unsigned char enflag[25] = { 69,
	  182,
	  171,
	  33,
	  121,
	  107,
	  254,
	  150,
	  92,
	  29,
	  4,
	  178,
	  138,
	  166,
	  184,
	  106,
	  53,
	  241,
	  42,
	  191,
	  23,
	  211,
	  3,
	  107 };
	unsigned char k[] = "WelcomeToQWB2023"; /*这里要实现字符类型16个数据转化为4个数据,
	联想到char类型大小为1字节byte(8bit),int类型为4字节,只要进行一下数据类型转化就可以实现
	(key个数为4)*/
	unsigned int key[4] = { 0 };
	unsigned int flag[6] = { 0 };
	

	for (int j = 0; j < 4; j++)
	{
		key[j] = *((unsigned int*)k + j);
		/*这里进行的优先级顺序为,先把unsigned char* 的k转化为unsigned int *,这里表示的是指针,
		所以加的j相当于一下向后4字节,进行完之后,再用*取地址符取值,赋值 */
	}
	for (int i = 0; i < 6; i ++)
	{
		flag[i] = *((unsigned int*)enflag + i);
	}
	for (int i = 0; i <6 ; i += 2)
	{
		decodeTEA(flag+i, 3735928559, key);
		
	}
	printf("解密结果为:\n");
	printf("%s", flag);
	
	return 0;
		//dotN3t_Is_1nt3r3sting
}
[反序列化数据恢复]

]

先胡乱填一下,然后再RC4,把上面这个dat文件放进去之后并不会正确运行,研究了一下午到底怎么正确填写,是有什么技巧吗。反复观看wp 发现了两处端倪

这段反序列化的空缺处有这两个数据,一篇师傅说这个代表len,去网上查阅也没找到相关的解释,就是在补充数据的时候,06 06 00 00 00 ()这个括号里的位置填写的是下面字符串的长度 ,然后06 07也是这个效果,填完长度之后 后面再跟上字符串内容,发现正好补充在06 07的前面,后面也使数据恰好补充完整(和内存相关的知识,反序列化,看来还要好好学一学呢)

拿这个dat文件去rc4补充,然后就可以正常运行出flag了

通过这道题学到了很多 ,满打满算用了三四天时间去学习,学习了两种算法,用到了很多工具,对TEA数据类型转化,文件数据读取写入,vs,dnspy的使用也更加熟悉了。很好的一道题!

flag{d0tN3t_I5_Ea57_2_y09!G00d_Luck}

 参考资料:

【参考WP】

C语言实现TEA系列加解密算法_tea_encrypt(uint8_t * data, uint32_t * len, uint32-CSDN博客

第七届强网杯赛题dotdot复现 - z221x-blog

https://www.cnblogs.com/gaoyucan/p/17914856.html

【出题人本人的文章】

[原创]强网杯2023 dotdot 题解及设计思路-CTF对抗-看雪-安全社区|安全招聘|kanxue.com

【白盒AES的文章】

找回消失的密钥 --- DFA分析白盒AES算法 - 奋飞安全

白盒加密 – To1in's blog

【AES学习视频】

【AES加密算法】| AES加密过程详解| 对称加密| Rijndael-128| 密码学| 信息安全_哔哩哔哩_bilibili

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值