算法描述 
  
     
在AES算法中密钥长度、分组长度和轮数的对应关系如下:
  
      
          密钥长度(Nk个32位双字) 
          分组长度(Nb个32位双字) 
          轮数(Nr) 
       
   
  
      
          AES-128 
          4 
          4 
          10 
       
      
          AES-192 
          6 
          4 
          12 
       
      
          AES-256 
          8 
          4 
          14 
       
   
对AES算法来说,输入分组、输出分组及状态分组的长度都是128比特,即Nb = 4.
加密过程 
  
     
总体 
  
     
将输入复制到状态数组中,在进行一次初始轮密钥相加操作之后,执行Nr次轮函数,对状态数组进行变换,其中最后一轮不同于前Nr-1轮.将最终的状态数组复制到输出数组中,即得到最终的密文.
轮函数由4部分组成,分别是subBytes(),shiftRows(),mixColumns(),addRoundKey().
其加密过程的伪代码如下:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
  
//对AES-128加密来说,Nk = 4,Nb = 4,Nr = 10
 void  AES_XXX_Encrypt ( uint8  in [ 4 * Nb ],  uint8  out [ 4 * Nb ],  uint8  key [ 4 * Nk ])  { 
	//将输入复制到状态数组
  uint8  state [ 4 * Nb ]  =  in ; 
 	//密钥扩展
  uint32  dw [ Nb * ( Nr + 1 )]  =  keyExpansion ( key [ 4 * Nk ]); 
	//轮密相加
  addRoundKey ( state ,  dw [ 0 ,  Nb - 1 ]); 
 	for  ( int  round  =  1 ;  round  <  Nr ;  ++ round )  { 
 		subBytes ( state );  //字节代换
  shiftRows ( state );  //行移位
 mixColumns ( state );  //列混合
 addRoundKey ( state ,  dw [ round * Nb ,  ( round + 1 ) * Nb - 1 ]);  //轮密相加 dw[4-7],dw[8-11]...
 } 
 	subBytes ( state ); 
 	shiftRows ( state ); 
 	addRoundKey ( state ,  dw [ Nr * Nb ,( Nr + 1 ) * Nb - 1 ]); 
 	out  =  state ; 
 }  
 
密钥扩展(keyExpansion) 
  
     
通过对用户输入的128位、192位或者256位的密钥进行处理,共生成Nb*(Nr+1)个32位双字,为加解密算法的轮函数提供轮密钥.
其代码表示如下:
 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
 80
 81
 82
 83
 84
 85
 86
 87
 88
  
void  XorWords ( uint8 *  a ,  uint8 *  b ,  uint8 *  c ) 
{ 
	int  i ; 
 	for  ( i  =  0 ;  i  <  4 ;  i ++ ) 
 	{ 
 		c [ i ]  =  a [ i ]  ^  b [ i ]; 
 	} 
 } 
 uint8  xtime ( uint8  b )     // multiply on x
 { 
	return  ( b  <<  1 )  ^  ((( b  >>  7 )  &  1 )  *  0x1b ); 
 } 
 void  Rcon ( uint8 *  a ,  int  n ) 
{ 
	int  i ; 
 	uint8  c  =  1 ; 
 	for  ( i  =  0 ;  i  <  n  -  1 ;  i ++ ) 
 	{ 
 		c  =  xtime ( c ); 
 	} 
 
 	a [ 0 ]  =  c ; 
 	a [ 1 ]  =  a [ 2 ]  =  a [ 3 ]  =  0 ; 
 } 
 void  SubWord ( uint8 *  a ) 
{ 
	int  i ; 
 	for  ( i  =  0 ;  i  <  4 ;  i ++ ) 
 	{ 
 		a [ i ]  =  S [ a [ i ]  /  16 ][ a [ i ]  %  16 ]; 
 	} 
 } 
 void  RotWord ( uint8 *  a ) 
{ 
	uint8  c  =  a [ 0 ]; 
 	a [ 0 ]  =  a [ 1 ]; 
 	a [ 1 ]  =  a [ 2 ]; 
 	a [ 2 ]  =  a [ 3 ]; 
 	a [ 3 ]  =  c ; 
 } 
 //密钥扩展
 void  KeyExpansion ( uint8  key [ 4  *  Nk ],  uint8  w [ 4  *  Nb  *  ( Nr  +  1 )]) 
{ 
	uint8 *  temp  =  new  uint8 [ 4 ]; 
 	uint8 *  rcon  =  new  uint8 [ 4 ]; 
 
 	int  i  =  0 ; 
 	while  ( i  <  4  *  Nk ) 
 	{ 
 		w [ i ]  =  key [ i ]; 
 		i ++ ; 
 	} 
 
 	i  =  4  *  Nk ; 
 	while  ( i  <  4  *  Nb  *  ( Nr  +  1 )) 
 	{ 
 		temp [ 0 ]  =  w [ i  -  4  +  0 ]; 
 		temp [ 1 ]  =  w [ i  -  4  +  1 ]; 
 		temp [ 2 ]  =  w [ i  -  4  +  2 ]; 
 		temp [ 3 ]  =  w [ i  -  4  +  3 ]; 
 
 		if  ( i  /  4  %  Nk  ==  0 ) 
 		{ 
 			RotWord ( temp ); 
 			SubWord ( temp ); 
 			Rcon ( rcon ,  i  /  ( Nk  *  4 )); 
 			XorWords ( temp ,  rcon ,  temp ); 
 		} 
 		else  if  ( Nk  >  6  &&  i  /  4  %  Nk  ==  4 ) 
 		{ 
 			SubWord ( temp ); 
 		} 
 
 		w [ i  +  0 ]  =  w [ i  -  4  *  Nk ]  ^  temp [ 0 ]; 
 		w [ i  +  1 ]  =  w [ i  +  1  -  4  *  Nk ]  ^  temp [ 1 ]; 
 		w [ i  +  2 ]  =  w [ i  +  2  -  4  *  Nk ]  ^  temp [ 2 ]; 
 		w [ i  +  3 ]  =  w [ i  +  3  -  4  *  Nk ]  ^  temp [ 3 ]; 
 		i  +=  4 ; 
 	} 
 
 	delete [] rcon ; 
 	delete [] temp ; 
 }  
 
字节代换(subBytes) 
  
     
实际上就是一个简单的查表操作.AES定义了一个16x16字节的S-box,以状态数组中的每个字节元素的高4位为行标,低4位为列标,取出相应的元素作为subBytes操作的结果.例如,16进制值{21},高4位为2,低4位为1,取S-box中行标为2、列表为1的值组成16进制值{FD},则{21}被替换为{FD}.
其代码表示如下:
1
 2
 3
 4
 5
 6
 7
 8
  
void  subBytes ( uint8 ( * state )[ 4 ])  { 
	/* i: row, j: col */ 
 	for  ( int  i  =  0 ;  i  <  4 ;  ++ i )  { 
 		for  ( int  j  =  0 ;  j  <  4 ;  ++ j )  { 
 			state [ i ][ j ]  =  S [ state [ i ][ j ]]; 
 		} 
 	} 
 }  
 
行移位(shiftRows) 
  
     
状态数组的第1行保持不变,第2行循环左移1字节,第3行循环左移2字节,第4行循环左移3字节.例如,对状态:
  
      
          F6 
          A6 
          82 
          A4 
       
   
  
      
          14 
          8C 
          48 
          7F 
       
      
          46 
          EA 
          26 
          19 
       
      
          C1 
          53 
          A7 
          14 
       
   
进行行移位之后,结果为:
  
      
          F6 
          A6 
          82 
          A4 
       
   
  
      
          8C 
          48 
          7F 
          14 
       
      
          26 
          19 
          46 
          EA 
       
      
          14 
          C1 
          53 
          A7 
       
   
其代码表示如下:
 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
  
//循环左移
 void  leftLoop4int ( uint32  array [ 4 ],  int  step )  { 
	uint32  temp [ 4 ]; 
 	for  ( int  i  =  0 ;  i  <  4 ;  i ++ ) 
 		temp [ i ]  =  array [ i ]; 
 
 	int  index  =  step  %  4  ==  0  ?  0  :  step  %  4 ; 
 	for  ( int  i  =  0 ;  i  <  4 ;  i ++ )  { 
 		array [ i ]  =  temp [ index ]; 
 		index ++ ; 
 		index  =  index  %  4 ; 
 	} 
 } 
 //行移位
 void  shiftRows ( uint8  array [ 4 ][ 4 ])  { 
	uint32  rowTwo [ 4 ],  rowThree [ 4 ],  rowFour [ 4 ]; 
 	//复制状态矩阵的第2,3,4行
  for  ( int  i  =  0 ;  i  <  4 ;  i ++ )  { 
		rowTwo [ i ]  =  array [ 1 ][ i ]; 
 		rowThree [ i ]  =  array [ 2 ][ i ]; 
 		rowFour [ i ]  =  array [ 3 ][ i ]; 
 	} 
 	//循环左移相应的位数
  leftLoop4int ( rowTwo ,  1 ); 
	leftLoop4int ( rowThree ,  2 ); 
 	leftLoop4int ( rowFour ,  3 ); 
 
 	//把左移后的行复制回状态矩阵中
  for  ( int  i  =  0 ;  i  <  4 ;  i ++ )  { 
		array [ 1 ][ i ]  =  rowTwo [ i ]; 
 		array [ 2 ][ i ]  =  rowThree [ i ]; 
 		array [ 3 ][ i ]  =  rowFour [ i ]; 
 	} 
 }  
 
列混合(mixColumns) 
  
     
先把状态矩阵初始状态复制一份到tmpArray中,然后将tmpArray与M矩阵相乘,其中M存放的是要乘的常数矩阵数组,GMul函数定义了矩阵相乘时的乘法,加法则直接通过异或来实现.
其代码表示如下:
 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
  
uint8  GMul ( uint8  u ,  uint8  v )  { 
    uint8  p  =  0 ; 
 
     for  ( int  i  =  0 ;  i  <  8 ;  ++ i )  { 
         if  ( u  &  0x01 )  {     //
  p  ^=  v ; 
        } 
 
         int  flag  =  ( v  &  0x80 ); 
         v  <<=  1 ; 
         if  ( flag )  { 
             v  ^=  0x1B ;  /* x^8 + x^4 + x^3 + x + 1 */ 
         } 
 
         u  >>=  1 ; 
     } 
 
     return  p ; 
 } 
 int  mixColumns ( uint8 ( * state )[ 4 ])  { 
	uint8  tmpArray [ 4 ][ 4 ]; 
 	uint8  M [ 4 ][ 4 ]  =  {  { 0x02 ,  0x03 ,  0x01 ,  0x01 }, 
 					   { 0x01 ,  0x02 ,  0x03 ,  0x01 }, 
 					   { 0x01 ,  0x01 ,  0x02 ,  0x03 }, 
 					   { 0x03 ,  0x01 ,  0x01 ,  0x02 }  }; 
 
 	/* copy state[4][4] to tmp[4][4] */ 
 	for  ( int  i  =  0 ;  i  <  4 ;  ++ i )  { 
 		for  ( int  j  =  0 ;  j  <  4 ;  ++ j )  { 
 			tmpArray [ i ][ j ]  =  state [ i ][ j ]; 
 		} 
 	} 
 
 	for  ( int  i  =  0 ;  i  <  4 ;  ++ i )  { 
 		for  ( int  j  =  0 ;  j  <  4 ;  ++ j )  { 
 			state [ i ][ j ]  =  GMul ( M [ i ][ 0 ],  tmpArray [ 0 ][ j ])  ^  GMul ( M [ i ][ 1 ],  tmpArray [ 1 ][ j ]) 
 				^  GMul ( M [ i ][ 2 ],  tmpArray [ 2 ][ j ])  ^  GMul ( M [ i ][ 3 ],  tmpArray [ 3 ][ j ]); 
 		} 
 	} 
 
 	return  0 ; 
 }  
 
轮密相加(addRoundKey) 
  
     
将状态数组中的元素与轮密钥通过简单的异或运算相加.轮密钥是由用户输入的密钥通过密钥扩展生成的,同样可以看成一个状态数组.
其代码表示如下:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
  
#define BYTE(x, n) (((x) >> (8 * (n))) & 0xff)
 void  addRoundKey ( uint8 ( * state )[ 4 ],  const  uint32 *  key )  { 
	uint8  k [ 4 ][ 4 ]; 
 
 	/* i: row, j: col */ 
 	for  ( int  i  =  0 ;  i  <  4 ;  ++ i )  { 
 		for  ( int  j  =  0 ;  j  <  4 ;  ++ j )  { 
 			k [ i ][ j ]  =  ( uint8 ) BYTE ( key [ j ],  3  -  i );   /* copy uint32 key[4] to uint8 k[4][4] */ 
 			state [ i ][ j ]  ^=  k [ i ][ j ]; 
 		} 
 	} 
 }  
 
解密过程 
  
     
总体 
  
     
加密算法的逆过程就是解密算法.因此,解密算法的轮函数也是由4部分组成,分别是invShiftRows、invSubBytes、invMixColumns、addRoundKey.
其解密过程的伪代码如下:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
  
//对AES-128解密来说, Nk = 4, Nb = 4, Nr = 10
 void  AES_XXX_Decrypt ( uint8  in [ 4  *  Nb ],  uint8  out [ 4  *  Nb ],  uint8  key [ 4  *  Nk ])  { 
	//将输入复制到状态数组
  uint8  state [ 4  *  Nb ]  =  in ; 
 	//密钥扩展
  uint32  dw [ Nb  *  ( Nr  +  1 )]  =  keyExpansion ( key [ 4  *  Nk ]); 
	//轮密相加
  //此时使用的秘钥是加密时使用秘钥的倒序
 addRoundKey ( state ,  dw ); 
 	for  ( int  i  =  1 ;  i  <  10 ;  ++ i )  { 
 		dw  +=  4 ; 
 		invSubBytes ( state ); 
 		invShiftRows ( state ); 
 		addRoundKey ( state ,  dw ); 
 		invMixColumns ( state ); 
 	} 
 
 	invSubBytes ( state ); 
 	invShiftRows ( state ); 
 	addRoundKey ( state ,  dw  +  4 ); 
 	out  =  state ; 
 }  
 
密钥扩展(keyExpansion) 
  
     
加密过程中的密钥扩展结果倒序就是解密过程中使用的密钥.
逆字节代换(invSubBytes) 
  
     
是字节代换的逆过程,和字节代换一样,逆字节代换只进行简单的查表操作.
AES同时定义了一个逆S盒(Inverse S-Box).
逆行移位(invShiftRows) 
  
     
是行移位的逆过程,即状态数组中的后3行执行相应的右移操作.
逆列混合(invMixColumns) 
  
     
和列混合的原理相同,区别在于它们使用了不同的多项式.逆列混合使用的系数矩阵如下:
1
 2
 3
 4
  
uint8_t  M [ 4 ][ 4 ]  =  {{ 0x0E ,  0x0B ,  0x0D ,  0x09 }, 
                   { 0x09 ,  0x0E ,  0x0B ,  0x0D }, 
                    { 0x0D ,  0x09 ,  0x0E ,  0x0B }, 
                    { 0x0B ,  0x0D ,  0x09 ,  0x0E }};   
 
它与列混合使用的矩阵互为逆矩阵.
轮密相加(addRoundKey) 
  
     
轮密相加的逆过程就是它本身,因为异或操作是其本身的逆.
实战演练 
  
     
选题 
  
     
本题选自2022年看雪KCTF的第三题-石像病毒.这是一道利用Windows异常机制来修改标准算法,考察选手对加密算法的熟悉程度.
2022-KCTF-第三题-石像病毒.rar 
总体 
  
     
首先经过简单动态调试,去掉Main函数异常干扰代码,IDA反编译结果如下:
 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
  
int  __cdecl  main_0 ( int  argc ,  const  char  ** argv ,  const  char  ** envp ) 
{ 
  void  * v3 ;  // esp
  int  result ;  // eax
 char  v5 ;  // [esp-10h] [ebp-1048h]
 struc_Luo  Src ;  // [esp+0h] [ebp-1038h] BYREF
 CPPEH_RECORD  ms_exc ;  // [esp+1020h] [ebp-18h]
   v3  =  alloca ( 0x1020 ); 
   memset ( & Src ,  0xCCu ,  sizeof ( Src )); 
   ms_exc . registration . ScopeTable  =  ( PSCOPETABLE_ENTRY )(( int ) ms_exc . registration . ScopeTable  ^  __security_cookie ); 
   memset ( Src . szFlag_78 ,  0 ,  sizeof ( Src . szFlag_78 )); 
   Src . result_48  =  0 ; 
   Src . field_4C  =  0 ; 
   Src . field_50  =  0 ; 
   Src . field_54  =  0 ; 
   Src . field_58  =  0 ; 
   Src . field_5C  =  0 ; 
   Src . field_60  =  0 ; 
   Src . field_64  =  0 ; 
   Src . field_68  =  0 ; 
   Src . field_6C  =  0 ; 
   gets_s ( Src . szFlag_78 ,  0xFA0u ); 
   if  (  strlen ( Src . szFlag_78 )  ==  0x20  ) 
   { 
     Src . field_40  =  0x200 ; 
     strcpy ( & Src . field_24 ,  "Enj0y_1t_4_fuuuN" ); 
     * ( _WORD  * )(( char  * ) & Src . field_34  +  1 )  =  0 ; 
     HIBYTE ( Src . field_34 )  =  0 ; 
     Src . field_8  =  0 ; 
     Src . field_C  =  0 ; 
     Src . field_10  =  0 ; 
     Src . field_14  =  0 ; 
     Src . field_18  =  0 ; 
     Src . field_0  =  0x200 ; 
     sub_401109 ( & Src . field_24 ,  0x10u ,  ( int ) & Src . field_8 ); // MD5
  sub_401104 ( & Src . field_8 ,  0x10u ,  ( int ) Src . szFlag_78 ,  ( int ) & Src . result_48 ,  0x20 ); // AES
 if  (  ! memcmp ( & Src . result_48 ,  byte_40B200 ,  0x20u )  ) 
      printf ( "OK \n " ,  v5 ); 
     else 
       printf ( "NO \n " ,  v5 ); 
     result  =  0 ; 
   } 
   else 
   { 
     printf ( "NO \n " ,  v5 ); 
     result  =  0 ; 
   } 
   return  result ; 
 }  
 
可以看到本题简单明了,首先要求我们输入的字符串长度等于0x20,然后通过对字符串"Enj0y_1t_4_fuuuN"计算MD5值,将其作为密钥,利用AES算法加密我们输入的值,看是否和内置的值相同.若相同,则正确.
  
  
    这里用到MD5和AES都是利用了Windows异常机制对其标准过程进行了修改.
   
 
MD5 
  
     
利用IDA插件Findcrypt查找算法常量. 
 
交叉引用MD5相关算法常量. 
 
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
  
. text : 00402 C60  sub_402C60       proc  near                ;  CODE  XREF :  sub_4010FA ↑ j 
. text : 00402 C60                  push     ebp 
. text : 00402 C61                  mov      ebp ,  esp 
. text : 00402 C63                  mov      eax ,  4 
. text : 00402 C68                  imul     ecx ,  eax ,  2 Ah  ;  '*' 
. text : 00402 C6B                  mov      MD5_Constants_40B298 [ ecx ],  0 D4AF3085h 
. text : 00402 C75                  mov      eax ,  1 
. text : 00402 C7A                  pop      ebp 
. text : 00402 C7B                  retn 
. text : 00402 C7B  sub_402C60       endp  
 
发现这里修改了MD5常量表中的第(0x2A + 1 = 43)项的值为0x0D4AF3085.
验证. 
 
在x64Dbg中定位到sub_401109函数,看魔改后的MD5计算出来的值.
可以看到上面计算出来的值为:2F65B1FF31ED86D09A285C0F4048059D
找一份标准MD5算法,修改常量表的第43项值为0x0D4AF3085,然后查看是否和上面计算出来的结果相同.
AES 
  
     
总体 
  
     
首先利用程序中的特征,在Github上搜索一份跟程序中相近的AES源码. 
 
https://github.com/lmshao/AES 
AES-master.zip 
在IDA中查看下AES相关函数. 
 
 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
  
int  __cdecl  sub_401104 ( void  * Src ,  size_t  Size ,  int  a3 ,  int  a4 ,  int  a5 ) 
{ 
  return  sub_402070 (( uint8_t  * ) Src ,  Size ,  ( uint8_t  * ) a3 ,  ( uint8_t  * ) a4 ,  a5 ); 
 } 
 int  __cdecl  sub_402070 ( uint8_t  * key ,  size_t  keyLen ,  uint8_t  * in ,  uint8_t  * out ,  int  inLen ) 
{ 
  int  j ;  // [esp+4h] [ebp-1C8h]
  unsigned  int  i ;  // [esp+8h] [ebp-1C4h]
 int  v8 [ 6 ];  // [esp+10h] [ebp-1BCh] BYREF
 int  state [ 11 ];  // [esp+28h] [ebp-1A4h] BYREF
 char  * v10 ;  // [esp+54h] [ebp-178h]
 uint8_t  * _out ;  // [esp+58h] [ebp-174h]
 char  v12 [ 360 ];  // [esp+60h] [ebp-16Ch] BYREF
   _out  =  out ; 
   v10  =  v12 ; 
   state [ 6 ]  =  0 ; 
   state [ 7 ]  =  0 ; 
   state [ 8 ]  =  0 ; 
   state [ 9 ]  =  0 ; 
   state [ 0 ]  =  0 ; 
   state [ 1 ]  =  0 ; 
   state [ 2 ]  =  0 ; 
   state [ 3 ]  =  0 ; 
   v8 [ 0 ]  =  0 ; 
   v8 [ 1 ]  =  0 ; 
   v8 [ 2 ]  =  0 ; 
   v8 [ 3 ]  =  0 ; 
   if  (  ! key  ||  ! in  ||  ! out  ) 
     return  0xFFFFFFFF ; 
   if  (  keyLen  >  0x10  ) 
     return  0xFFFFFFFF ; 
   if  (  inLen  %  0x10u  ) 
     return  0xFFFFFFFF ; 
   memcpy ( state ,  key ,  keyLen );                    // 密钥长度是0x10,可以看出用的是AES-128加密
  keyExpansion_4010BE (( int ) state ,  0x10 ,  ( int ) v12 ); // 密钥扩展 
 for  (  i  =  0 ;  i  <  inLen ;  i  +=  0x10  ) 
  { 
     sub_401145 (( uint8_t  * ) v8 ,  in ); 
     addRoundKey_401186 ( v8 ,  v10 );                 // 轮密相加
  for  (  j  =  1 ;  j  <  0xA ;  ++ j  )                  // 轮数(Nr) = 10,可以也看出这里用的是AES-128加密
 { 
      v10  +=  0x10 ; 
       subBytes_401172 (( int ) v8 );                  // 字节代换
  sub_40122B (( int ) v8 ); 
      mixColumns_40100F (( int ) v8 );                // 列混合
  addRoundKey_401186 ( v8 ,  v10 );               // 轮密相加
 } 
    subBytes_401172 (( int ) v8 ); 
     sub_40122B (( int ) v8 ); 
     addRoundKey_401186 ( v8 ,  v10  +  0x10 ); 
     sub_40125D (( int ) v8 ,  _out ); 
     _out  +=  0x10 ; 
     in  +=  0x10 ; 
     v10  =  v12 ; 
   } 
   return  0 ; 
 }  
 
这里我们输入的字符串是"XiaLuoHun12345675434567876543Hun",长度是0x20.
密钥扩展 
  
     
用IDA查看密钥扩展函数. 
 
切换到反汇编窗口,可以看到有两处异常.
接下来查看下异常处理代码.
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
  
int  sub_40108C () 
{ 
  return  sub_402330 (); 
 } 
 int  sub_402330 () 
{ 
  RijnDael_AES_LONG_40B000 [ 0x71 ]  ^=  RijnDael_AES_LONG_40B000 [ 0xA3 ]; 
   RijnDael_AES_LONG_40B000 [ 0xA3 ]  ^=  RijnDael_AES_LONG_40B000 [ 0x71 ]; 
   RijnDael_AES_LONG_40B000 [ 0x71 ]  ^=  RijnDael_AES_LONG_40B000 [ 0xA3 ]; 
   return  1 ; 
 }  
 
可以发现这里修改了AES的S-Box,以上修改相当于在标准AES算法源码中修改如下:
 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
  
int  keyExpansion ( const  uint8_t  * key ,  uint32_t  keyLen ,  AesKey  * aesKey )  { 
     if  ( NULL  ==  key  ||  NULL  ==  aesKey ){ 
         printf ( "keyExpansion param is NULL \n " ); 
         return  - 1 ; 
     } 
 
     if  ( keyLen  !=  16 ){ 
         printf ( "keyExpansion keyLen = %d, Not support. \n " ,  keyLen ); 
         return  - 1 ; 
     } 
 
     uint32_t  * w  =  aesKey -> eK ; 
     uint32_t  * v  =  aesKey -> dK ; 
 
     /* keyLen is 16 Bytes, generate uint32_t W[44]. */ 
 
     /* W[0-3] */ 
     for  ( int  i  =  0 ;  i  <  4 ;  ++ i )  { 
         LOAD32H ( w [ i ],  key  +  4 * i ); 
     } 
 
     /* W[4-43] */ 
     for  ( int  i  =  0 ;  i  <  10 ;  ++ i )  { 
 
         //修改开始------------
  S [ 0x71 ]  ^=  S [ 0xA3 ]; 
		S [ 0xA3 ]  ^=  S [ 0x71 ]; 
 		S [ 0x71 ]  ^=  S [ 0xA3 ]; 
         //修改结束-----------
          w [ 4 ]  =  w [ 0 ]  ^  MIX ( w [ 3 ])  ^  rcon [ i ]; 
         w [ 5 ]  =  w [ 1 ]  ^  w [ 4 ]; 
         w [ 6 ]  =  w [ 2 ]  ^  w [ 5 ]; 
         w [ 7 ]  =  w [ 3 ]  ^  w [ 6 ]; 
         w  +=  4 ; 
     } 
 
     w  =  aesKey -> eK + 44  -  4 ; 
     for  ( int  j  =  0 ;  j  <  11 ;  ++ j )  { 
 		//修改开始------------
  S [ 0x71 ]  ^=  S [ 0xA3 ]; 
		S [ 0xA3 ]  ^=  S [ 0x71 ]; 
 		S [ 0x71 ]  ^=  S [ 0xA3 ]; 
 		//修改结束-----------
          for  ( int  i  =  0 ;  i  <  4 ;  ++ i )  { 
             v [ i ]  =  w [ i ]; 
         } 
         w  -=  4 ; 
         v  +=  4 ; 
     } 
 
 	//修改开始------------
  //根据S-Box计算逆S-Box
 calc_InvS_Box (); 
	//修改结束-----------
      return  0 ; 
 }  
 
  
  
    修改了AES的S-Box,我们也要同步修改AES的inv_S-Box.
   
 
1
 2
 3
 4
 5
 6
 7
 8
 9
  
//由AES的S盒计算逆S盒.
 void  calc_InvS_Box ()  { 
	unsigned  char  sbox_inv [ 256 ]  =  {  0  }; 
 	for  ( int  i  =  0 ;  i  <  256 ;  i ++ ) 
 	{ 
 		unsigned  char  temp1  =  S [ i ]; 
 		inv_S [ temp1 ]  =  i ; 
 	} 
 }  
 
行移位 
  
     
用IDA查看下与行移位相关的代码.
既然F5不行,那就切换到反汇编窗口看下.
这里我们注意到用到了右移,正常来说加密过程中的行移位用的是左移,解密过程中用的是右移.再加上动态调试结果,确认这里是将标准AES加密过程中的行移位修改为了逆行移位.与此同时需要修改解密过程中的逆行移位为行移位.
列混合 
  
     
用IDA伪代码查看列混合中的Gmul函数好像没问题,但切到汇编窗口就会看到有异常处理.
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
  
int  sub_401249 () 
{ 
  return  sub_4023D0 (); 
 } 
 int  sub_4023D0 () 
{ 
  byte_40B200 [ 0x10 ]  =  0xF4 ; 
   byte_40B200 [ 0x11 ]  =  0xF2 ; 
   return  1 ; 
 }  
 
byte_40B200里面存放的是最终AES计算出来的结果要比对的值,在这里进行了修改.
计算Flag 
  
     
既然知道了程序修改标准算法的地方,那我们就来写代码计算下Flag.
最终得到的Flag为:flag{db5c6a8dfec4d0ec5569899640}
参考链接 
  
     
AES算法描述及C语言实现 
AES加密算法原理的详细介绍与实现 
AES 
AES