RC4算法原理 && IDA识别RC4算法

发布时间 2023-06-12 18:14:08作者: Qsons

RC4算法原理 && IDA识别RC4算法

RC4简介 && 对称密码介绍

在密码学中,RC4是一种流加密算法,密钥长度可变。加解密使用相同的密钥,隶属于对称加密算法。

  • 流密码属于对称密码算法一种,基本特征是加解密双方使用一串与明文长度相同的密钥流,与明文流组合来进行加解密密钥流通常是由某一确定状态的伪随机数发生器所产生的比特流。

RC4算法原理

RC4算法原理,一共包含二个算法,初始化算法,和伪随机密码生成算法。

RC4算法初始化部分!!!(用C表示)

初始化函数一共包含三个参数

  • 参数一是一个256长度的char型数组,一般定义为:unsigned char sBox[256];
  • 参数二是密钥,其内容可以随便定义:char key[256];
  • 参数三是密钥的长度,Len = strlen(key);

C代码实现如下图所示

/*初始化函数*/
void rc4_init(unsigned char*s,unsigned char*key, unsigned long Len)
{
    int i=0;j=0;
    unsigned char k[256] = {0};
    unsigned char tmp = 0;
    for(i=0;i<256;i++)
    {
        s[i] = i;//对s盒所有元素依次赋值1,2,3,4 ... 256,确保S-box的每个元素得到处理
        k[i] = key[i%Len];//将S_box搅乱
    }
    /*初始化过程中,密钥的功能是将S-box搅乱,i确保S-box的每个元素都得到处理,j保证S-box的搅乱是随机的。不同的S-box在经过伪随机子密码生成算法的处理后可以得到不同的子密钥序列,将S-box和明文进行xor运算,得到密文,解密过程也完成相同。*/
    for(i=0;i<256;i++)
    {
        j = (j +s[i] + k[i]) % 256;
        tmp = s[i];
        s[i] = s[j];//交换s[i]和s[j]
        s[j] = tmp;
    }
}

RC4算法加密部分!!!(用C表示)

加密部分包含3个参数

  • 参数一是上面rc4_init函数中,被搅乱的S-box
  • 参数二是需要加密的数据data;
  • 参数3是data_Len,即data的长度

C代码实现如下图所示

void rc4_crypt(unsigned char *s,unsigned char*Data,unsigned long Len)
{
    int i=0,j=0,t=0;
    unsigned long k = 0;
    unsigned char tmp;
    for(k=0;k<Len;k++)
    {
        i = (i+1) %256;
        j = (s[i] + j) % 256;
        tmp = s[i];
        s[i] = s[j];//交换s[x]和s[y]
        s[j] = tmp;
        t = (s[i] + s[j]) % 256;
        Data[k] ^= s[t];//将S-box和明文进行xor运算,得到密文,解密过程也完全相同
    }
}

​ 最终实现代码如下

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

void rc4_init(unsigned char*s,unsigned char*key,unsigned long Len)
{
    int i = 0,j=0;
    unsigned int tmp = 0;
    unsigned char k[256] = {0};
    for(i =0;i<256;i++)
    {
        s[i] = i;
        k[i] = key[i%Len];
    } 
    for(j=0;j<256;j++)
    {
        j = (s[i]+k[i] + j)%256;
        tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
    }  
}

void rc4_crypt(unsigned char*s,unsigned char*Data,unsigned int DataLen)
{
    int i=0,j=0,t=0;
    unsigned long k = 0;
    unsigned char tmp;
    for(k=0;k<DataLen;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] ^= s[t];
    }
}

int main()
{
    unsigned char s[256]={0};
    char key[256]={"xxxxxxxxx"};
    char Data[512]= "用来加密的数据";
    unsigned long len = strlen(Data);//Data_Len
    rc4_init(s,(unsigned char*)key,strlen(key));
    rc4_crypt(s,(unsigned char*)Data,len);
}

RC4算法在IDA中的识别

  • 编译版本Debug版
  • 架构为32位

首先可以看到我们首先来到了main函数

  • 虽然这里是main函数,但是因为编译器和IDA的原因,我们在IDA看到的函数名是会有所不同
  • 这里我们是先进入到main函数,然后通过jmp汇编指令跳转到_main_0函数
  • main函数中经典的三个参数传递:argc,argv,envp

image-20230612171544756

​ F5反编译后,随后进入Main_0函数

  • 可以看到整个main_0函数中,只存在2个重要函数
  • sub_4113B6函数,传入参数为v13,v12,v3
  • sub_4113BB函数,传入参数为v13,Str,v5
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  size_t v3; // eax
  size_t v5; // [esp+250h] [ebp-424h]
  char Str[4]; // [esp+25Ch] [ebp-418h] BYREF
  int v7; // [esp+260h] [ebp-414h]
  int v8; // [esp+264h] [ebp-410h]
  __int16 v9; // [esp+268h] [ebp-40Ch]
  char v10; // [esp+26Ah] [ebp-40Ah]
  char v11[497]; // [esp+26Bh] [ebp-409h] BYREF
  char v12[264]; // [esp+464h] [ebp-210h] BYREF
  char v13[260]; // [esp+56Ch] [ebp-108h] BYREF

  __CheckForDebuggerJustMyCode(&unk_41C00F);
  j_memset(v13, 0, 0x100u);
  strcpy(v12, "xxxxxxxxx");
  j_memset(&v12[10], 0, 0xF6u);
  *(_DWORD *)Str = '\xB4\xC0\xC3\xD3';
  v7 = -591146052;
  v8 = -37043019;
  v9 = -8770;
  v10 = 0;
  j_memset(v11, 0, sizeof(v11));
  v5 = j_strlen(Str);
  v3 = j_strlen(v12);
  sub_4113B6(v13, v12, v3);
  sub_4113BB(v13, Str, v5);
  return 0;
}

sub_4113B6(v13,v12,v3);

int __cdecl sub_4113B6(int a1, int a2, int a3) //这里是先call sub_4113B6函数,然后通过Jmp跳转到sub_411760函数。
{
  return sub_411760(a1, a2, a3);
}

​ sub_411760(a2,a2,a3)

void *__cdecl sub_411760(int a1, int a2, unsigned int a3)
/*
a1 ------ v13         s盒
a2 ------ v12         key
a3 ------ v3          key_Len
*/
{
  void *result; // eax
  char v4[264]; // [esp+D0h] [ebp-12Ch] BYREF   初始化算法中的s_Box盒,即原算法中的k数组
  int v5; // [esp+1D8h] [ebp-24h]
  int j; // [esp+1E4h] [ebp-18h]
  unsigned int i; // [esp+1F0h] [ebp-Ch]

  __CheckForDebuggerJustMyCode(&unk_41C00F);
  j = 0;
  v5 = 0;
  result = j_memset(v4, 0, 0x100u);
  for ( i = 0; (int)i < 256; ++i )      //可以看到,这里就是典型的rc4算法初始化的特征、
  {
    *(_BYTE *)(i + a1) = i; //对应RC4原算法的s[i] = i.不过在IDA中,是通过a1为首地址,i进行寻址.
    v4[i] = *(_BYTE *)(a2 + i % a3);//对应RC4原算法的k[i] = key[i%key_Len]
      //对于IDA里面的数组,一般是通过某个变量的地址,然后通过i来进行遍历寻址
      //如*(a1 + i) *(&a1 + i)等
    result = (void *)(i + 1);
  }
  for ( j = 0; j < 256; ++j )
  {
    j = (j + *(unsigned __int8 *)(i + a1) + (unsigned __int8)v4[i]) % 256;
      //等价于j = (j + a1[i] + k[i]) % 256.
    v5 = *(unsigned __int8 *)(i + a1);   // v5是局部变量,可以看成tmp变量.tmp = a1[i]
    *(_BYTE *)(i + a1) = *(_BYTE *)(j + a1);// a1[i] = a1[j]
    *(_BYTE *)(j + a1) = v5; // a1[j] = tmp
      //s[j] 和 s[i]的交换
    result = (void *)(j + 1);
  }
  return result;
}

​ 通过对sub4113B6函数的分析,我们可以得出以下几点:

  • v13 带指的是s盒

  • v12 带指的是Key

  • v3 带指的是key_Len

​ sub_4113BB(v13, Str, v5);

int __cdecl sub_4113BB(int a1, int a2, int a3)
{
  return sub_415380(a1, a2, a3);
  /*
  a1 ------ v13
  a2 ------ Str
  a3 ------ v5
  */
}

​ sub_415380(a1, a2, a3);

unsigned int __cdecl sub_415380(int a1, int a2, unsigned int a3)
/**/
{
  unsigned int result; // eax
  char v4; // [esp+D3h] [ebp-35h]
  unsigned int i; // [esp+DCh] [ebp-2Ch]
  int v6; // [esp+F4h] [ebp-14h]
  int v7; // [esp+100h] [ebp-8h]

  __CheckForDebuggerJustMyCode(&unk_41C00F);
  v7 = 0;
  v6 = 0;
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= a3 )
      break;
    v7 = (v7 + 1) % 256; //等价于原算法中的  i = (i + 1) % 256
    v6 = (v6 + *(unsigned __int8 *)(v7 + a1)) % 256; // j = (j + a1[v7]) % 256;
    v4 = *(_BYTE *)(v7 + a1); // tmp = a1[v7]   v7其实就是i
    *(_BYTE *)(v7 + a1) = *(_BYTE *)(v6 + a1); // a1[v7] = a1[v6]   v6是j
    *(_BYTE *)(v6 + a1) = v4; // a1[i] = tmp
    *(_BYTE *)(i + a2) ^= *(_BYTE *)((*(unsigned __int8 *)(v6 + a1) + *(unsigned __int8 *)(v7 + a1)) % 256 + a1);
      //这里其实省略了原算法中的一个步骤即t = (S[i] + S[j]) % 256。不过IDA把它都优化到了一起
      //a2等价于Data
      //Data[i] ^= (t + a1)
      //Data[i] ^= a1[t]
      //最终其实就等价于Data[i] ^= s[t]
  }
  return result;
}

​ 通过对sub_415380函数的分析可以得知以下几点:

  • Str 指的是Data,即待加密的密文

  • v13指的是初始化后的S盒

  • v5指的是Data_Len

    回到main_0函数

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  size_t v3; // eax
  size_t v5; // [esp+250h] [ebp-424h]
  char Str[4]; // [esp+25Ch] [ebp-418h] BYREF
  int v7; // [esp+260h] [ebp-414h]
  int v8; // [esp+264h] [ebp-410h]
  __int16 v9; // [esp+268h] [ebp-40Ch]
  char v10; // [esp+26Ah] [ebp-40Ah]
  char v11[497]; // [esp+26Bh] [ebp-409h] BYREF
  char v12[264]; // [esp+464h] [ebp-210h] BYREF
  char v13[260]; // [esp+56Ch] [ebp-108h] BYREF

  __CheckForDebuggerJustMyCode(&unk_41C00F);
  j_memset(v13, 0, 0x100u);
  strcpy(v12, "xxxxxxxxx");//密钥
  j_memset(&v12[10], 0, 0xF6u);
  *(_DWORD *)Str = '\xB4\xC0\xC3\xD3';//IDA会把大数组,拆分成多个小数组,那这里其实是Str,v7,v8,v9,v10都是我们要处理的密文
  v7 = -591146052;
  v8 = -37043019;
  v9 = -8770;
  v10 = 0;
  j_memset(v11, 0, sizeof(v11));
  v5 = j_strlen(Str);
  v3 = j_strlen(v12);
  sub_4113B6(v13, v12, v3);//rc4_init
  sub_4113BB(v13, Str, v5);//rc4_crypto
  return 0;
}

以上就是RC4算法在IDA中的识别