AES加密算法逆向分析
AES加密算法介绍
高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法,对称加密算法也就是加密和解密用相同的密钥,AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同,如下表所示:
AES加密过程涉及到4种操作:字节代换(SubBytes)、行移位(ShiftRows)、列混合(MixColumns)和轮密钥加(AddRoundKey)。解密过程分别为对应的逆操作。由于每一步操作都是可逆的,按照相反的顺序进行解密即可恢复明文。加解密中每轮的密钥分别由初始密钥扩展得到。算法中16字节的明文、密文和轮密钥都以一个4x4的矩阵表示。
0x01 字节代换
AES定义了一个S盒和一个逆S盒。
AES的S盒:
状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出。例如,加密时,输出的字节S1为0x12,则查S盒的第0x01行和0x02列,得到值0xc9,然后替换S1原有的0x12为0xc9。状态矩阵经字节代换后的图如下:
AES的逆S盒:同理
0x02 行移位
行移位是一个简单的左循环移位操作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节,第3行左移3字节,逆变化同理,如下图所示:
0x03 列混合
列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵,如下图的公式所示:
逆变化同理:
0x04 轮密钥加
加密过程中,每轮的输入与轮密钥异或一次;因此,解密时再异或上该轮的密钥即可恢复输入。
0x05 密钥扩展
AESEnc.exe逆向分析
首先进入main函数,上来就是一堆var变量的赋值,我们不用管继续看后面,然后再开始了数组的赋值,这里的var_20被连续赋值了16个字节,最后加上了一个0,可以猜测这个var_20就是密钥,正好16位,改名为key,后面的var_50暂时不知道,反正是32个字节,然后又是一大堆赋值,这里的var_70一共32字节,还不知道是什么含义,暂且跳过,后面赋值的4个0也不知道啥玩意,不管他
这里scanf函数的参数是var_50,可以知道var_50应该是用户输入的明文,重新改一下名字为data,下一个函数就算aesEncrypt,他的参数一共5个,第一个参数是var_20,也就是密钥key,第二个是常数10h,也就是16,第三个是var_50,也就是data,第四个是var_90,暂时未知,第五个是常值20h,为32那么下面就是要分析加密函数内部逻辑了
1 | push rbp |
先看看开头的初始化工作,rcx赋值给Src,所以Src应该是等于密钥key,edx存储的是常数16,猜测应该是key长度keylen,也就是Size,,后面的r8和r9分别是传进来的明文输入pt,以及密文存放的位置ct,还剩下一个参数32应该就是密文明文的长度,这里把各个参数名字都改一下方便阅读
接下来是四个分支,前三个是判断密钥,明文,密文都不为空,最后一个是判断密钥长度是否大于等于16
这里对第五个参数进行了判断,先赋值给eax,再和0Fh按位与,这一步就是检查最后四位是不是0,如果最后四位是0,那么就进入下一个分支,如果不是0就退出,这里应该是要确保密文明文的长度是16的倍数,便于加密
下一个函数就是密钥扩展函数
1 | loc_401EEE: |
memcpy函数传入了三个参数,首先是一个空指针,第二个是我们的传入的密钥key,第三个是传入的密钥长度src,那么这里的作用应该是把key复制到一个新的数组里面
接下来的keyExpansion也是一共三个参数,我们已知aes的密钥扩展函数是要将密钥循环扩展到44个字,那么可以猜测传入的第三个参数r8是扩展后的密钥存储的地方
接下来把var4赋值为0,并且和inlen进行比较,再进入循环,可以看出这个var_4是控制循环的变量,而后在循环末端又有var_4+10h,可见这个大循环一共两次,那么这里一定是将明文进行分组,依次加密
再看循环内的流程,loadStateArray函数讲我们的明文pt处理后放入var_1B0中,那一步应该是初始化状态矩阵,紧随其后的addRoundKey也证明了这一点,传入的参数是var_1B0和var_18,通过回顾前面的变量定义可以知道,var_18是一个指向扩展后的aesKEY的指针,且var_18在左边的循环中每一轮都会增加10h,也就是16,所以这一步就是轮密钥加
然后下一个循环时用var_8控制的,初始值为1,每一轮加1,jle是小于等于,那左边这个循环一共执行九次,左边的大体流程是,subBytes,shiftRows,mixColumns,addRoundKey,第十次执行右边,但是少了一个mixColumns,
讲经过了10轮加密后的状态矩阵var_1B0传给var_10,也就是指向ct的指针,然后用storeStateArray复制过去
然后再跳回原来的大循环,那么总体框架就如此,这就是aes的十轮加密,每一轮都会更新状态矩阵,接下来再来分析每一个小函数的作用
0x01 subBytes
这个函数的总体结构是第一层循环4次,第二层循环四次,正好对应了16个字节的状态矩阵4行4列
1 | loc_401A03: |
循环中的RijnDael_AES_LONG_404020便是S盒
0x02 shiftRows
一共四层循环
1 | loc_401A95: |
每一层都是把数组中的四个字节取出来,每次用shl把eax左移指定位数
0x03 mixColumns
1 | push rbp |
开头是一段赋值,这里赋值的是列混合矩阵,列混合的操作就是用矩阵乘法实现的,但是这里面的流程图非常混乱,我直接看了伪代码
1 | __int64 __fastcall mixColumns(__int64 state) |
第一个循环时把状态数组state放入临时数组tmp中,而第二个循环中是把s数组的行和tmp数组的列进行分别相乘相加,也就是矩阵乘法,但是这里的乘法是在G(2^8)下得到的,函数GMul就是实现这个功能的,循环内一共有四个表达式,计算的v1保留,第二次计算的结果和v1按位异或得到v2一直重复四次,最终的结果放回状态数组state中
0x04 addRoundKey
轮密钥加的结构主要由两个循环构成,一共16次,每次都是把轮密钥的对应位和状态矩阵对应位进行异或
1 | loc_401921: |
这里重复的大段代码其实都是在矩阵中取出对应数字的操作,先计算出本轮密钥的起始地址,再赋值到r8d中,再用r8d和状态矩阵的对应位置进行异或
0x05 flag解密
在最后的循环比较中,cipher和var_90分别赋值给了edx和eax,然后比较了他们的低8位,也就是一个字节,一共循环32次,那么只需要我们加密后得到的var_90和预定义的cipher数组每一位都相等即可,我们可以使用aes的解密工具得出答案