龙空技术网

RC4、Base64魔改看雪CTF-变形金刚学习笔记

牵手生活 195

前言:

目前大家对“c语言rc4算法代码”大致比较珍视,咱们都想要知道一些“c语言rc4算法代码”的相关知识。那么小编也在网摘上收集了一些有关“c语言rc4算法代码””的相关内容,希望姐妹们能喜欢,大家快快来学习一下吧!

前言

最近在研究某某app的数据库,发现自己在so层的调试比较薄弱,专门找了看雪的CTF-变形金刚来学习。希望在用ida调试so方面有所突破。

利用国庆期间整理成笔记。技术不成熟或许描述的不够清晰请大伙见谅。

也拜读了几位大佬的文章。

工具准备ida7.0Transformers.apkideajadxfrida小米4root手机(android6.0)app分析目标

为了熟悉该app,先介绍一下大神分析后的结果。

输入错误密码截图

比如输入密码是:12345678,会提示错误信息:Transformers:error

密码错误登陆失败的截图

输入正确密码截图

输入密码(长度16个字符)是:fu0kzHp2aqtZAuY6,会提示错误信息:Transformers:flag{android4-9}

正确密码提示

静态分析-java

把Transformers.apk拖入jadx

障眼法1:该app设计作者采用了障眼法,很容易欺骗分析人员,让我们认为OnClick的回调处理逻辑在MainActivity中。

障眼法2:在MainActivity的OnCreate中欺骗代码注册OnClick回调。主要原理是利用Activity的onStart事件会晚与OnCreate事件,从而实现在OnStart中注册的OnClick回调覆盖了OnCreate中注册的回调。

MainActivity障眼法OnCreate事件

MainActivity基类也是障眼法

Activity基类的onStart才是真正注册OnClick回调的地方

Activity基类的onStart事件才是逻辑代码

Base64解码图

为了方便理解eq中涉及的魔改的Base64编码,这里先贴上一直Base64解码的草稿图

下图是Base64编码4个字符'{6*的解码草稿图,画的不是很好,主要是方便我自己理解写解码Base64的逻辑代码。

base64解码草图

它是用64个可打印字符表示二进制所有数据方法。由于2的6次方等于64,所以可以用每6个位元为一个单元,对应某个可打印字符。我们知道三个字节有24个位元,就可以刚好对应于4个Base64单元,即3个字节需要用4个Base64的可打印字符来表示。

静态分析与动态调试跟踪liboo000oo.so

这里将分3个关键步骤:

1:JNI_OnLoad分析动态注册eq对应的函数sub_784,稍后再详细介绍。2:.fini_array部分跟踪,主要是eq函数用到的一下基础数据,比如Base64编码表、RC4加密算法用到的key初始值,之后它会通过一系列异或处理算出真正的密钥key。也稍后再详细介绍。3:eq函数的算法跟踪分析。

这里先列一下分析结果

JNI_onLoad部分==========	eq是由sub_784动态注册	ini_array部分==========	#解码36长度字符串byte_4020:	byte_4020 =650f909c-7217-3647-9331-c82df8b98e98+0x00(结束符)	#解码Base64为的64+1个编码字符byte_4050= 	base64Chars = byte_4050 =!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\';+0x00(结束符)	#app伪装java类的名称byte_40A0:	className_Sign= android/support/v7/app/AppCompiatActivity +0x00(结束符)sub_784部分============		获取RC4的密钥算法============:		v5=算法01 = 删除(byte_4040中的"-") =650f909c721736479331c82df8b98e98		v4=算法02 =删除byte_4020中的”-“后,特殊运算又加上”-“符号=89e89b8f-d28c-1339-7463-7127c909f056		自定义算法03==========	v6=v17=自定义算法03 = 特殊算法处理后 =36f36b3c-a03e-4996-8759-8408e626c215			算法参数01:v4		算法参数02:unk_23DE(10位长度) =2409715836		算法参数03:unk_23D8(16位长度)=dbeafc2409715836		RC4魔改部分======	(已经逆向算法)		RC4第1步:v48:拷贝&unk_B4D863E8中的256个字符到v48(应视是RC4算法的魔改数据)		RC4第2步:v49:计算RC4 的临时256自己T向量,公式:iK[i]=(byte)aKey.charAt((i % aKey.length()));		RC4第3步:V48状态向量S进行置换操作V48		RC4第4步:RC4产生密钥流iOutputChar,然后return new String(iOutputChar)??????			v27 =33 ,即v6[3];					RC4第5步:产生密钥流--其他还结合魔改Base64运算			分配输入密码对应的base64字节的存储空间			Base64算法的字典				Base64魔改部分======(已经逆向算法)		Base64的字典部分-64个字符+1个结束符		每4个字符中第0个与0x6异或,第0个与0xF异或		     switch (i%4){                case 0:                    base64char = (char) (iAscii ^0x07);                    break;                case 2:                    base64char = (char) (iAscii ^0xF);                    break;                default:                    base64char =encodeChars[i];            }		

JNI_OnLoad 代码逻辑分析RegisterNatives

1:搜索JNI_OnLoad,双击进入函数体

2:导入Jni.h文件。菜单路径:ida/File/Load file/Parse C header file

3:修改参数类型为_JavaVM

4:按g快捷键直接跳转到0x4014

JNI_OnLoad截图

右键菜单中可选择

找到eq注册对应的sub_784函数

sub_784其实就是eq的真正函数名称

当然frida的hook_art_so_register.js脚本快速定位eq对应sub_784

该脚本通过hook art.so 的register函数,打印动态注册的地址

//调用方式==测试okfrida -U --no-pause -f package_name -l hook_art_so_register.js

跟踪.fini_array(其实这步可以跳过)

另外根据so文件的加载流程应该是先加载init_array,然后是JNI_OnLoad

ida 中ctrl+s 进入init_array,会先执行.datadiv_decode5009363700628197108,其实就是通过一些运算得到一些初始化数据。

按f5 采用伪代码方式查看datadiv_decode5009363700628197108,根据得到一些数据。

1:解码36长度字符串byte_4020:650f909c-7217-3647-9331-c82df8b98e98+0x00(结束符)

2:魔改Base64编码的table表,为的64+1个编码字符byte_4050= !:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\';+0x00(结束符)

init_array函数解码初始数据

eq函数的分析(即sub_784函数)

ida定位到sub_784,修改参数类型,并做了动态调试后,梳理sub_784的逻辑。

sub_784逻辑梳理

unk_23E8字符串(RC4魔改算法的初始化256个字符S状态向量)

unk_23E8字符串Base64的table

unk_23E8字符串Base64的table 拷贝

unk_23E8字符串Base64的table 拷贝

unk_23E8=Sbox = [0xD7,0xDF,0x02,0xD4,0xFE,0x6F,0x53,0x3C,0x25,0x6C,0x99,0x97,0x06,0x56,0x8F,0xDE,0x40,0x11,0x64,0x07,0x36,0x15,0x70,0xCA,0x18,0x17,0x7D,0x6A,0xDB,0x13,0x30,0x37,0x29,0x60,0xE1,0x23,0x28,0x8A,0x50,0x8C,0xAC,0x2F,0x88,0x20,0x27,0x0F,0x7C,0x52,0xA2,0xAB,0xFC,0xA1,0xCC,0x21,0x14,0x1F,0xC2,0xB2,0x8B,0x2C,0xB0,0x3A,0x66,0x46,0x3D,0xBB,0x42,0xA5,0x0C,0x75,0x22,0xD8,0xC3,0x76,0x1E,0x83,0x74,0xF0,0xF6,0x1C,0x26,0xD1,0x4F,0x0B,0xFF,0x4C,0x4D,0xC1,0x87,0x03,0x5A,0xEE,0xA4,0x5D,0x9E,0xF4,0xC8,0x0D,0x62,0x63,0x3E,0x44,0x7B,0xA3,0x68,0x32,0x1B,0xAA,0x2D,0x05,0xF3,0xF7,0x16,0x61,0x94,0xE0,0xD0,0xD3,0x98,0x69,0x78,0xE9,0x0A,0x65,0x91,0x8E,0x35,0x85,0x7A,0x51,0x86,0x10,0x3F,0x7F,0x82,0xDD,0xB5,0x1A,0x95,0xE7,0x43,0xFD,0x9B,0x24,0x45,0xEF,0x92,0x5C,0xE4,0x96,0xA9,0x9C,0x55,0x89,0x9A,0xEA,0xF9,0x90,0x5F,0xB8,0x04,0x84,0xCF,0x67,0x93,0x00,0xA6,0x39,0xA8,0x4E,0x59,0x31,0x6B,0xAD,0x5E,0x5B,0x77,0xB1,0x54,0xDC,0x38,0x41,0xB6,0x47,0x9F,0x73,0xBA,0xF8,0xAE,0xC4,0xBE,0x34,0x01,0x4B,0x2A,0x8D,0xBD,0xC5,0xC6,0xE8,0xAF,0xC9,0xF5,0xCB,0xFB,0xCD,0x79,0xCE,0x12,0x71,0xD2,0xFA,0x09,0xD5,0xBC,0x58,0x19,0x80,0xDA,0x49,0x1D,0xE6,0x2E,0xE3,0x7E,0xB7,0x3B,0xB3,0xA0,0xB9,0xE5,0x57,0x6E,0xD9,0x08,0xEB,0xC7,0xED,0x81,0xF1,0xF2,0xBF,0xC0,0xA7,0x4A,0xD6,0x2B,0xB4,0x72,0x9D,0x0E,0x6D,0xEC,0x48,0xE2,0x33]

byte_24E8指向的内容

byte_24E8指向的内容

把sub_784业务逻辑直接采用在ida中注解的方式修订如下

int __fastcall sub_784(_JNIEnv *a1_env, int a2, void *a3_str_app_password){  size_t v3_4020_RC4_key_length; // r10  unsigned __int8 *v4_4020_del_handle02; // r6  _BYTE *v5_4020_del_hyphen_handle01; // r8  _BYTE *v6_byte_4020_handle3; // r11  int v7; // r0  size_t v8; // r2  char *v9; // r1  int v10; // r3  int v11_len; // r1  unsigned int v12; // r2  int v13; // r3  int v14; // r0  int v15_forJump_; // r4  unsigned __int8 v16; // r0  _BYTE *v17_4020_handle3_ptr; // r3  _BYTE *v18_tmp_byte_move; // r5  char *v19_v49_256_Rc4_T; // r4  int v20; // r5  int v21; // r1  int v22; // r0  signed int v23; // r1  int v24_S; // r2  size_t v25_app_password_length; // r0  unsigned int v26_app_password_length; // r8  unsigned int v27; // r5  _BYTE *v28_new_base64; // r0  int v29; // r3  int v30; // r10  unsigned int v31_pw_index; // r2  int v32; // r12  bool v33_isAppPW_index01; // zf  _BYTE *v34_ret; // r1  bool v35; // zf  int v36; // r3  int v37_tmp; // r1  unsigned __int8 v38_char_needTodoBase64; // r11  unsigned int v39; // lr  char v40; // r1  char *v41; // r2  int v42; // t1  unsigned int v44_new_base64_length; // [sp+4h] [bp-234h]  unsigned int v45_by_app_password_length; // [sp+8h] [bp-230h]  unsigned int v46; // [sp+10h] [bp-228h]  char *s_char_app_password; // [sp+14h] [bp-224h]  char v48_256_Rc4_S[256]; // [sp+18h] [bp-220h] RC4的S向量  char v49_256_Rc4_T[256]; // [sp+118h] [bp-120h] RC4的T向量  int v50; // [sp+218h] [bp-20h]  s_char_app_password = (char *)a1_env->functions->GetStringUTFChars(&a1_env->functions, a3_str_app_password, 0);  v3_4020_RC4_key_length = strlen(byte_4020);   // 长度0x24,即36长度  v4_4020_del_handle02 = (unsigned __int8 *)malloc(v3_4020_RC4_key_length);// byte_4020被删除"-"符号,再经过特殊处理,同时有添加回新的“-”符号                                                // 新的结果:89e89b8f-d28c-1339-7463-7127c909f056  v5_4020_del_hyphen_handle01 = malloc(v3_4020_RC4_key_length);// byte_4020中的内容删除“-” :650f909c721736479331c82df8b98e98  v6_byte_4020_handle3 = malloc(v3_4020_RC4_key_length);  _aeabi_memclr(v4_4020_del_handle02, v3_4020_RC4_key_length);  _aeabi_memclr(v5_4020_del_hyphen_handle01, v3_4020_RC4_key_length);  _aeabi_memclr(v6_byte_4020_handle3, v3_4020_RC4_key_length);  if ( v3_4020_RC4_key_length )  {    v7 = 0;    v8 = v3_4020_RC4_key_length;    v9 = byte_4020;                             // 指向36个字符串长度,在调试.fini_array已经得到:650f909c-7217-3647-9331-c82df8b98e98    do                                          // 其实就是把数据byte_4020的asci码copy到v5中byte数组    {      v10 = (unsigned __int8)*v9++;             // 取一个字符转为asci码      if ( v10 != 45 )                          // "-"的asci=45,其实就是删除“-”符号,复制byte_4020(包括“-”符号)        v5_4020_del_hyphen_handle01[v7++] = v10;      --v8;                                     // 及长度减一    }    while ( v8 );                               // 循环体结束后v5=删除(byte_4040中的"-") =650f909c721736479331c82df8b98e98                                                // ===========================算法处理01    if ( v7 >= 1 )                              // v7 =0x20;即字符串32为长度,其实就是36位byte_4020删除“-”后的长度    {      v11_len = v7 - 1;      v12 = -8;                                 // 0xFFFFFFF8,调试>>结果发现这里应该不是-8,而是一个很大的数      v13 = 0;      v14 = 0;      do      {        if ( (v13 | (v12 >> 2)) > 3 )           // v12>>2 = -2?后第一次=0x3FFFFFFE        {          v15_forJump_ = v14;        }        else        {          v15_forJump_ = v14 + 1;          v4_4020_del_handle02[v14] = 45;       // 其实就是“-”字符的asc码45        }        v16 = v5_4020_del_hyphen_handle01[v11_len--];// v5其实就是byte_4020删除了“-”符号后的:650f909c721736479331c82df8b98e98        v13 += 0x40000000;        v4_4020_del_handle02[v15_forJump_] = v16;        ++v12;        v14 = v15_forJump_ + 1;                 // 其实也就是跳过"-"      }      while ( v11_len != -1 );                  // 该循环体计算v4的结果是:89e89b8f-d28c-1339-7463-7127c909f056                                                // 处理逻辑是:byte_4020被删除"-"符号,再经过特殊处理,同时有添加回新的“-”符号                                                // ===========================算法处理02      if ( v15_forJump_ >= 0 )                  // 第一次变量是0x23,即36-1      {        v17_4020_handle3_ptr = v6_byte_4020_handle3;// 初始是0x00,v17计算后指向RC4的key:36f36b3c-a03e-4996-8759-8408e626c215        while ( 1 )        {          v18_tmp_byte_move = (_BYTE *)*v4_4020_del_handle02;// ascii的hex:第一次 =0x38(对应字符"8");第2次 =0x39(对应字符"9");就是v4逐个字节取出          if ( (unsigned __int8)((_BYTE)v18_tmp_byte_move - 97) <= 5u )// ascii(97)='a';ascii(48)='0'            break;          if ( (unsigned __int8)((_BYTE)v18_tmp_byte_move - 48) <= 9u )          {            v18_tmp_byte_move = (char *)&unk_23DE + (_DWORD)v18_tmp_byte_move - 48;// unk_23DE(10位长度)指向:2409715836            goto LABEL_18;          }LABEL_19:          *v17_4020_handle3_ptr++ = (_BYTE)v18_tmp_byte_move;// 存放到v17指向的地址          --v14;                                // 本if语句中初始是0x24,即36          ++v4_4020_del_handle02;               // 移动v4指向的指针,逐个取出          if ( !v14 )                           // 如果为0退出循环体???                                                // if语句内部的循环体;但还在if语句内            goto LABEL_20;        }                                       // end for while ( 1 )                                                //         v18_tmp_byte_move = (char *)&unk_23D8 + (_DWORD)v18_tmp_byte_move - 97;// unk_23D8(16位长度)好像是什么密码指向:dbeafc2409715836LABEL_18:        LOBYTE(v18_tmp_byte_move) = *v18_tmp_byte_move;// 这个是IDA的常用宏,相当于取变量的最低byte位来赋值        goto LABEL_19;      }                                         // end if ( v15_forJump_ >= 0 )                                                 // ??????这个if语句里到底处理了哪些逻辑                                                 // 本if语句结束用于计算v6                                                // 计算结束后                                                //    v6  = 36f36b3c-a03e-4996-8759-8408e626c215                                                //    v17 = v6的指针进行移动                                                //    v18 = v4的字节逐个取出。(会与unk_23D8的内容进行运算)                                                // 涉及参数:                                                //    1:v4,即算法1-2步骤后(byte_4020):去”-“后计算又重新加上”-“符号=89e89b8f-d28c-1339-7463-7127c909f056                                                //    2:unk_23DE(10位长度)指向:2409715836                                                //    3:unk_23D8(16位长度)指向:dbeafc2409715836                                                // ===========================算法处理03--获取RC4的密钥    }                                           // end if ( v7 >= 1 )  }                                             // end for if ( v3_RC4_key_length ) 处理后得到key*v17(即v6) 是:36f36b3c-a03e-4996-8759-8408e626c215LABEL_20:  _aeabi_memcpy8(v48_256_Rc4_S, &unk_23E8, 256);// 用于RC4魔改算法(魔改)-256字节初始化:                                                // unk_23E8指向的是256个字符:-拷贝到v48                                                // 256字符不会重复                                                // bytes:0xD7 0xDF 0x02 0xD4 0xFE 0x6F 0x53 0x3C ....                                                // chars:.....oS<%l...V....d.6.p.                                                // R0 =v48地址=0xBEE74D38                                                // =================算法处理04===RC4-01初始化256个字节的状态向量  v19_v49_256_Rc4_T = v49_256_Rc4_T;  v20 = 0;  do  {    sub_D20(v20, v3_4020_RC4_key_length);       // ???   具体做了哪些工作需要跟进去看看    v49_256_Rc4_T[v20++] = v6_byte_4020_handle3[v21];// 根据算出的key再转换临时向量T  }  while ( v20 != 256 );                         // 运算v49的值--RC4魔改的另外一个256字节向量-临时向量T                                                // 循环体算法是:                                                //         for (short i= 0;i<256;i++)//初始化S和T(根据密钥mkey)                                                //         {                                                //             iK[i]=(byte)aKey.charAt((i % aKey.length()));//i%密钥长度(取值范围为1-256);                                                //         }                                                //                                                 // =================参数:RC4密钥长度,即base_4020长度                                                // 循环体结束后===                                                //   v48:内容没有被修改                                                //   v49(只复制256字符) =36f36b3c-a03e-4996-8759-8408e626c21536f36b3c-a03e-4996-8759-8408e626c21536f36b3c-a03e-4996-8759-8408e626c21536f36b3c-a03e-4996-8759-8408e626c21536f36b3c-a03e-4996-8759-8408e626c21536f36b3c-a03e-4996-8759-8408e626c21536f36b3c-a03e-4996-8759-8408e626c21536f3                                                // =================算法处理04-===RC4-02初始化256个字节的临时向量                                                //                                                 //   v22 = (unsigned __int8)(v49_256_Rc4_T[0] - 41);// #0x33(即51)-41=0xA(即10)                                                // 正常的RC4这里是0;也就是说这里也被魔改了****  v48_256_Rc4_S[0] = v48_256_Rc4_S[v22];  v48_256_Rc4_S[v22] = -41;  v23 = 1;                                      // 正常的RC4这里是0;也就是说这里也被魔改了1;也就是第一个byte不处理***  do                                            // RC4向量--初始排列S  {    v24_S = (unsigned __int8)v48_256_Rc4_S[v23];    v22 = (v22 + (unsigned __int8)v49_256_Rc4_T[v23] + v24_S) % 256;    v48_256_Rc4_S[v23++] = v48_256_Rc4_S[v22];    v48_256_Rc4_S[v22] = v24_S;  }  while ( v23 != 256 );                         // =================算法处理04===RC-03===开始对状态向量S进行置换操作(用来打乱初始种子1)                                                //    其实就是打乱v48                                                // 魔改代码:                                                //                                                 //         j=10; //======注意=====正常这里是0,不过魔改的2019的CTF=10?????????                                                //         iS_48[0] =iS_48[j];//======注意=====这段是魔改零添加的                                                //         iS_48[j] = -41;  //======注意=====这段是魔改零添加的                                                //                                                 //                                                 //         for (int i=1;i<255;i++)//初始排列for (int i=0;i<255;i++) //2019的CTF魔改for (int i=1;i<256;i++)                                                //         {                                                //             j=(j+iS_48[i]+iK_49[i]) % 256;                                                //             int temp = iS_48[i];                                                //             iS_48[i]=iS_48[j];                                                //             iS_48[j]=temp;                                                //         }  v25_app_password_length = strlen(s_char_app_password);// 就是解密数组密码的字符串:"12345678".lenght =8  v26_app_password_length = v25_app_password_length;  v27 = (unsigned __int8)v6_byte_4020_handle3[3];// v27=0x33 =51  v45_by_app_password_length = 8 * (3 - -3 * (v25_app_password_length / 3));  v44_new_base64_length = v27 + v45_by_app_password_length / 6;  v28_new_base64 = malloc(v44_new_base64_length + 1);// 分配新的base64需要使用的字节。                                                // //==========Base64分配需要新的字节大小  if ( v26_app_password_length )                // app输入的密码长度  {    v30 = 0;    v31_pw_index = 0;                           // 循环体的index    v32 = 0;    v46 = v27;    do    {      v30 = (v30 + 1) % 256;                    // // RC4 算法的产生密钥流循环体开始      v37_tmp = (unsigned __int8)v48_256_Rc4_S[v30];      v32 = (v32 + v37_tmp) % 256;      v48_256_Rc4_S[v30] = v48_256_Rc4_S[v32];      v48_256_Rc4_S[v32] = v37_tmp;             // ==========刚刚重新分析到这里,苦逼呀      v19_v49_256_Rc4_T = (char *)(unsigned __int8)v48_256_Rc4_S[v30];      v38_char_needTodoBase64 = v48_256_Rc4_S[(unsigned __int8)(v37_tmp + (_BYTE)v19_v49_256_Rc4_T)] ^ s_char_app_password[v31_pw_index];//                                                 // ===========Base64内嵌在RC4的内部                                                // 先线程一个RC4加密后的字符                                                // 后面用这个字符进行Base64的解码操作                                                // Base64魔改的算法:                                                //   1:Base64 字典被替换                                                //   2:Base最后以为“=”会被替换成“;”                                                //   3: 对特定字符进行异或操作.每4个字符中第0个与0x6异或,第0个与0xF异或                                                //     switch (i%4){                                                //                 case 0:                                                //                     base64char = (char) (iAscii ^0x07);                                                //                     break;                                                //                                                 //                 case 2:                                                //                     base64char = (char) (iAscii ^0xF);                                                //                     break;                                                //                 default:                                                //                     base64char =encodeChars[i];                                                //                                                 //             }      if ( v31_pw_index        && (v29 = 2863311531u * (unsigned __int64)v31_pw_index >> 32, v39 = 3 * (v31_pw_index / 3), v39 != v31_pw_index) )//                                                 // 逗号运算符是指在C语言中,多个表达式可以用逗号分开,其中用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值。                                                // 影响结果:                                                //   false:其实就是index/3(取摸)!=0                                                //   true: index/3 =0                                                //   v39:index可以是3的最大倍数,0,3,6,9,12...                                                //      index=3,则v39=3;index =6,则v39=6;index=7,则v39=6      {        v33_isAppPW_index01 = v31_pw_index == 1;// 是否是appPassword[1]字符        if ( v31_pw_index != 1 )          v33_isAppPW_index01 = v39 + 1 == v31_pw_index;        if ( v33_isAppPW_index01 )              // 判断index%3=1的情况 ,即1,4,7,10        {          v34_ret = byte_4050;                  // v34第一次=0x33(51);                                                // byte_4050:存放的是base64的table字典字符64+1个                                                // byte_4050(字符长度是64+1):!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\';          v28_new_base64[v46 + v31_pw_index] = byte_4050[(unsigned __int8)v28_new_base64[v46 + v31_pw_index] | ((unsigned int)v38_char_needTodoBase64 >> 4)];          v19_v49_256_Rc4_T = &v28_new_base64[v46 + v31_pw_index];          v29 = 4 * v38_char_needTodoBase64 & 0x3C;// 0x3C =60 ,即111100二进制          v19_v49_256_Rc4_T[1] = v29;          if ( v31_pw_index + 1 >= v26_app_password_length )            goto LABEL_53;                      // 跳转到程序的倒数第2个Lable        }        else                                    // index%3=2的情况 2,5,8,11....        {          v35 = v31_pw_index == 2;          if ( v31_pw_index != 2 )            v35 = v39 + 2 == v31_pw_index;          if ( v35 )          {            v19_v49_256_Rc4_T = (char *)(v38_char_needTodoBase64 & 0xC0);// 0xC0 =192即11000000二进制,保留最高2bit            v36 = v46++ + v31_pw_index;            v28_new_base64[v36] = byte_4050[(unsigned __int8)v28_new_base64[v36] | ((unsigned int)v19_v49_256_Rc4_T >> 6)] ^ 0xF;// 0xf=15 ,即二进制1111。                                                // 这里异或好像是对Base64模拟的多余操作            v29 = (int)&v28_new_base64[v36];            *(_BYTE *)(v29 + 1) = byte_4050[v38_char_needTodoBase64 & 0x3F];// 0x3f=63 即111111二进制          }        }      }      else                                      // index/%3 =0 即0,3,6,9...      {        v28_new_base64[v46 + v31_pw_index] = byte_4050[(unsigned int)v38_char_needTodoBase64 >> 2] ^ 7;// 7的二进制四111                                                // 这里对base64进行了魔改操作        v19_v49_256_Rc4_T = &v28_new_base64[v46 + v31_pw_index];        v29 = 16 * v38_char_needTodoBase64 & 0x30;// 0x30=48,即二进制110000bit        v19_v49_256_Rc4_T[1] = v29;        if ( v31_pw_index + 1 >= v26_app_password_length )// 最后一位        {          v40 = byte_4050[v29];          *((_WORD *)v19_v49_256_Rc4_T + 1) = 15163;          goto LABEL_43;        }      }      ++v31_pw_index;    }                                           // end for if ( v26_app_password_length ) 内部的do    while ( v31_pw_index < v26_app_password_length );  }                                             // end if ( v26_strText_length ) 输入密码lenght>0                                                // ========这是时RC4-04部分的算法“产生密钥流”                                                //         v28_new_base64:同时结合if部分逐个对RC4生成的结果做魔改的Base64运算                                                //                                                 //                                                 //   while ( 1 )  {    if ( v45_by_app_password_length )    {                                           // v45 是根据输入密码长度计算,比如密码:12345678;v45=0x48u(即78)      v34_ret = (_BYTE *)(&dword_0 + 1);        // v34 =1      v19_v49_256_Rc4_T = (char *)v44_new_base64_length;      v41 = &byte_24E8;                         // v41指向的char* = 0x20(即空格)+"{9*8ga*l!Tn?@#fj'j$\g;;"      do      {        v29 = (unsigned __int8)v28_new_base64[v27++];        v42 = (unsigned __int8)*v41++;        if ( v42 != v29 )          v34_ret = 0;      }      while ( v27 < v44_new_base64_length );    // v44 =0x3Fu    }    else    {      v34_ret = (_BYTE *)(&dword_0 + 1);    }    v28_new_base64 = (_BYTE *)(_stack_chk_guard - v50);    if ( _stack_chk_guard == v50 )      break;LABEL_53:    v40 = v34_ret[v29];    v19_v49_256_Rc4_T[2] = 52;                  // 输入密码:12345678,这里是:wk4.LABEL_43:    v19_v49_256_Rc4_T[1] = v40;  }  return (unsigned __int8)v34_ret;              // sub_784 方法返回只有这么一个位置。我们侧重分析如何让该方法返回真即可}

RC4Util(魔改RC4算法)

package com.younghare.utils;/** * 用 Java 实现的 Rc4 加密算法 *  * RC4加密算法的原理及实现 : */public class RC4Util {    /**     * 加密和解密都用这一个方法。也就是说参数String aInput 可以传一个明文,也可以传一个加密后的字符串,程序会自动的识别。然后执行加解密的响应操作。     * @param aInput     * @param aKey_RC4     * @return     */    public static String HloveyRC4(String aInput,String aKey_RC4)    {        int[] iS_48 = new int[256];//状态向量S:长度为256,S[0],S[1].....S[255]。每个单元都是一个字节,算法运行的任何时候,S都包括0-255的8比特数的排列组合,只不过值的位置发生了变换;        byte[] iK_49 = new byte[256];//临时向量T(K):长度也为256,每个单元也是一个字节。如果密钥的长度是256字节,就直接把密钥的值赋给T,否则,轮转地将密钥的每个字节赋给T;        for (int i=0;i<256;i++)//初始化S,默认初始化是0-255            iS_48[i]=i;//这里经常被用来魔改 ,        //魔改的情况,即让上面的iS初始化失效        iS_48 = new int[] {0xD7,0xDF,0x02,0xD4,0xFE,0x6F,0x53,0x3C,0x25,0x6C,0x99,0x97,0x06,0x56,0x8F,0xDE,0x40,0x11,0x64,0x07,0x36,0x15,0x70,0xCA,0x18,0x17,0x7D,0x6A,0xDB,0x13,0x30,0x37,0x29,0x60,0xE1,0x23,0x28,0x8A,0x50,0x8C,0xAC,0x2F,0x88,0x20,0x27,0x0F,0x7C,0x52,0xA2,0xAB,0xFC,0xA1,0xCC,0x21,0x14,0x1F,0xC2,0xB2,0x8B,0x2C,0xB0,0x3A,0x66,0x46,0x3D,0xBB,0x42,0xA5,0x0C,0x75,0x22,0xD8,0xC3,0x76,0x1E,0x83,0x74,0xF0,0xF6,0x1C,0x26,0xD1,0x4F,0x0B,0xFF,0x4C,0x4D,0xC1,0x87,0x03,0x5A,0xEE,0xA4,0x5D,0x9E,0xF4,0xC8,0x0D,0x62,0x63,0x3E,0x44,0x7B,0xA3,0x68,0x32,0x1B,0xAA,0x2D,0x05,0xF3,0xF7,0x16,0x61,0x94,0xE0,0xD0,0xD3,0x98,0x69,0x78,0xE9,0x0A,0x65,0x91,0x8E,0x35,0x85,0x7A,0x51,0x86,0x10,0x3F,0x7F,0x82,0xDD,0xB5,0x1A,0x95,0xE7,0x43,0xFD,0x9B,0x24,0x45,0xEF,0x92,0x5C,0xE4,0x96,0xA9,0x9C,0x55,0x89,0x9A,0xEA,0xF9,0x90,0x5F,0xB8,0x04,0x84,0xCF,0x67,0x93,0x00,0xA6,0x39,0xA8,0x4E,0x59,0x31,0x6B,0xAD,0x5E,0x5B,0x77,0xB1,0x54,0xDC,0x38,0x41,0xB6,0x47,0x9F,0x73,0xBA,0xF8,0xAE,0xC4,0xBE,0x34,0x01,0x4B,0x2A,0x8D,0xBD,0xC5,0xC6,0xE8,0xAF,0xC9,0xF5,0xCB,0xFB,0xCD,0x79,0xCE,0x12,0x71,0xD2,0xFA,0x09,0xD5,0xBC,0x58,0x19,0x80,0xDA,0x49,0x1D,0xE6,0x2E,0xE3,0x7E,0xB7,0x3B,0xB3,0xA0,0xB9,0xE5,0x57,0x6E,0xD9,0x08,0xEB,0xC7,0xED,0x81,0xF1,0xF2,0xBF,0xC0,0xA7,0x4A,0xD6,0x2B,0xB4,0x72,0x9D,0x0E,0x6D,0xEC,0x48,0xE2,0x33};        //iK_49 = new int[] {0x33,0x36,0x66,0x33,0x36,0x62,0x33,0x63,0x2D,0x61,0x30,0x33,0x65,0x2D,0x34,0x39,0x39,0x36,0x2D,0x38,0x37,0x35,0x39,0x2D,0x38,0x34,0x30,0x38,0x65,0x36,0x32,0x36,0x63,0x32,0x31,0x35,0x33,0x36,0x66,0x33,0x36,0x62,0x33,0x63,0x2D,0x61,0x30,0x33,0x65,0x2D,0x34,0x39,0x39,0x36,0x2D,0x38,0x37,0x35,0x39,0x2D,0x38,0x34,0x30,0x38,0x65,0x36,0x32,0x36,0x63,0x32,0x31,0x35,0x33,0x36,0x66,0x33,0x36,0x62,0x33,0x63,0x2D,0x61,0x30,0x33,0x65,0x2D,0x34,0x39,0x39,0x36,0x2D,0x38,0x37,0x35,0x39,0x2D,0x38,0x34,0x30,0x38,0x65,0x36,0x32,0x36,0x63,0x32,0x31,0x35,0x33,0x36,0x66,0x33,0x36,0x62,0x33,0x63,0x2D,0x61,0x30,0x33,0x65,0x2D,0x34,0x39,0x39,0x36,0x2D,0x38,0x37,0x35,0x39,0x2D,0x38,0x34,0x30,0x38,0x65,0x36,0x32,0x36,0x63,0x32,0x31,0x35,0x33,0x36,0x66,0x33,0x36,0x62,0x33,0x63,0x2D,0x61,0x30,0x33,0x65,0x2D,0x34,0x39,0x39,0x36,0x2D,0x38,0x37,0x35,0x39,0x2D,0x38,0x34,0x30,0x38,0x65,0x36,0x32,0x36,0x63,0x32,0x31,0x35,0x33,0x36,0x66,0x33,0x36,0x62,0x33,0x63,0x2D,0x61,0x30,0x33,0x65,0x2D,0x34,0x39,0x39,0x36,0x2D,0x38,0x37,0x35,0x39,0x2D,0x38,0x34,0x30,0x38,0x65,0x36,0x32,0x36,0x63,0x32,0x31,0x35,0x33,0x36,0x66,0x33,0x36,0x62,0x33,0x63,0x2D,0x61,0x30,0x33,0x65,0x2D,0x34,0x39,0x39,0x36,0x2D,0x38,0x37,0x35,0x39,0x2D,0x38,0x34,0x30,0x38,0x65,0x36,0x32,0x36,0x63,0x32,0x31,0x35,0x33,0x36,0x66,0x33};        int j_v32 = 1;        for (short i= 0;i<256;i++)//初始化S和T(根据密钥mkey)        {            iK_49[i]=(byte)aKey_RC4.charAt((i % aKey_RC4.length()));//i%密钥长度(取值范围为1-256);        }        j_v32=10; //======注意=====正常这里是0,不过魔改的2019的CTF=10?????????        iS_48[0] =iS_48[j_v32];//======注意=====这段是魔改零添加的        iS_48[j_v32] = -41;  //======注意=====这段是魔改零添加的        for (int i=1;i<255;i++)//初始排列for (int i=0;i<255;i++) //2019的CTF魔改for (int i=1;i<256;i++)        {            j_v32=(j_v32+iS_48[i]+iK_49[i]) % 256;            int temp = iS_48[i];            iS_48[i]=iS_48[j_v32];            iS_48[j_v32]=temp;        }        //产生密钥流        int i_v30=0;        j_v32=0;        char[] iInputChar = aInput.toCharArray();        char[] iOutputChar = new char[iInputChar.length];        for(short index = 0;index<iInputChar.length;index++)        {            i_v30 = (i_v30+1) % 256;            j_v32 = (j_v32+iS_48[i_v30]) % 256;            int temp_v37 = iS_48[i_v30];            iS_48[i_v30]=iS_48[j_v32];            iS_48[j_v32]=temp_v37;            int t = (iS_48[i_v30]+(iS_48[j_v32] % 256)) % 256;            int iY = iS_48[t];            char iCY = (char)iY;            int int_v38 = iInputChar[index] ^ iCY;//应该是得到逐个后进行base64转化计算。。。。求考证            iOutputChar[index] = (char) int_v38;            //iOutputChar[index] =(char)( iInputChar[index] ^ iCY) ;        }        return new String(iOutputChar);    }    static byte[] ints2bytes_foriS_48(int[] iS_48){        byte[] bytes= new byte[iS_48.length];        for (int i =0;i< iS_48.length;i++){            bytes[i]= (byte) iS_48[i];        }        return bytes;    }    public static void main(String[] args) {//测试RC4算法  main_ok        String inputStr ="做个好男人!!!";        String key = "abcdefg";        System.out.println("加密前:"+inputStr);        String str = HloveyRC4(inputStr,key);        //打印加密后的字符串        System.out.println("加密后:"+str);        //打印解密后的字符串        System.out.println("解密后:"+HloveyRC4(str,key));    }}

Base64_ctf

/** * Utility to base64 encode and decode a string. * @author      Stephen Uhler * @version  1.9, 02/07/24 * 代码来源: */public class Base64_ctf {    static byte[] encodeData;    //static String charSet =  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";    static String charSet = "!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\\';";    static char equalChar = ';';    static String equalChar2 = ";;";    static {        encodeData = new byte[64];        for (int i = 0; i<64; i++) {            byte c = (byte) charSet.charAt(i);            encodeData[i] = c;        }    }    private Base64_ctf() {}    /**     * base-64 encode a string     * @param s    The ascii string to encode     * @returns    The base64 encoded result     */    public static String    encode(String s) {        return encode(s.getBytes());    }    /**     * base-64 encode a byte array     * @param src  The byte array to encode     * @returns    The base64 encoded result     */    public static String    encode(byte[] src) {        return encode(src, 0, src.length);    }    /**     * 编码原理:将3个字节转换成4个字节( (3 X 8) = 24 = (4 X 6) )     * 先读入3个字节,每读一个字节,左移8位,再右移四次,每次6位,这样就有4个字节了     * base-64 encode a byte array     * @param src  The byte array to encode     * @param start  The starting index     * @param len  The number of bytes     * @returns    The base64 encoded result     */    public static String  encode(byte[] src, int start, int length) {        byte[] dst = new byte[(length+2)/3 * 4 + length/72];        int x = 0;        int dstIndex = 0;        int state = 0;  // which char in pattern        int old = 0;  // previous byte        int len = 0;  // length decoded so far        int max = length + start;        for (int srcIndex = start; srcIndex<max; srcIndex++) {            x = src[srcIndex];            switch (++state) {//0,3,6                case 1:                    dst[dstIndex++] = encodeData[(x>>2) & 0x3f]; //0x3f = 63 = 111111二进制,8bit右移动2bit,然后只需剩下的6bit,的值来当作新的编码                    break;                case 2://1,4,7                    dst[dstIndex++] = encodeData[((old<<4)&0x30)  //0x30 =48 =110000二进制,就是上一个8bit左移4比他,清零最前面的bit。即得到的上一个字符的最后2bit                            | ((x>>4)&0xf)];  //移去4bit,剩下高位的4bit 。                    break;                case 3://2,5,8                    dst[dstIndex++] = encodeData[((old<<2)&0x3C) //0x3c =60= 111100, 上一个字符的低位4bit                            | ((x>>6)&0x3)];//下一个自己的最后高为2bit                    dst[dstIndex++] = encodeData[x&0x3F];                    state = 0;                    break;            }            old = x;//            if (++len >= 72) {//奇怪这里添加回车换行后,base64解码就会 出问题呀//                dst[dstIndex++] = (byte) '\n';  //LF 即换行//                len = 0;//            }        }        /*         * now clean up the end bytes         */        switch (state) {            case 1: dst[dstIndex++] = encodeData[(old<<4) & 0x30];//0x30 =48 =110000                dst[dstIndex++] = (byte) equalChar; //原来:'='                dst[dstIndex++] = (byte) equalChar;//原来:'='                break;            case 2: dst[dstIndex++] = encodeData[(old<<2) & 0x3c]; //0x3c =60= 111100                dst[dstIndex++] = (byte) equalChar;//原来:'='                break;        }        return new String(dst);    }    /**     * 解码原理:将4个字节转换成3个字节. 先读入4个6位(用或运算),每次左移6位,再右移3次,每次8位.     * A Base64 decoder.  This implementation is slow, and     * doesn't handle wrapped lines.     * The output is undefined if there are errors in the input.     * @param s    a Base64 encoded string     * @returns    The byte array eith the decoded result     */    public static byte[]   decode(String s) {        int end = 0;  // end state        if (s.endsWith(equalChar+"")) { //原来"="            end++;        }        if (s.endsWith(equalChar2)) {//原来"=="            end++;        }        int len = (s.length() + 3)/4 * 3 - end;        byte[] result = new byte[len];        int dst = 0;        try {            for(int src = 0; src< s.length(); src++) {                int code =  charSet.indexOf(s.charAt(src));                if (code == -1) {                    break;                }                switch (src%4) {                    case 0:                        result[dst] = (byte) (code<<2); //先产生高6bit                        break;                    case 1:                        result[dst++] |= (byte) ((code>>4) & 0x3);//0x3=11二进制,再生成2bit,要生成下一字节,所有dst++                        result[dst] = (byte) (code<<4); //产生高4bit                        break;                    case 2:                        result[dst++] |= (byte) ((code>>2) & 0xf);//                        result[dst] = (byte) (code<<6);                        break;                    case 3:                        result[dst++] |= (byte) (code & 0x3f); //0x3f =63 =111111二进制                        break;                }            }        } catch (ArrayIndexOutOfBoundsException e) {}        return result;    }    /**     * 加盐异或操作--是不是可以理解为很多加密算法的加盐都是这么操作???     * @param encodeStr     * @return     */    public static String xor_do_salt_cft(String encodeStr) {        char[] encodeChars = encodeStr.toCharArray();        String base64Str_undoSalt = "";        char base64char;        int end = 0;  // end state        if (encodeStr.endsWith(equalChar+"")) { //原来"="            end++;        }        if (encodeStr.endsWith(equalChar2)) {//原来"=="            end++;        }        for (int i=0;i<encodeChars.length-end;i++){            int iAscii = encodeChars[i];            //System.out.println(iAscii);//打印asci码            //System.out.println((char) iAscii);//字符            switch (i%4){                case 0:                    base64char = (char) (iAscii ^0x07);                    break;                case 2:                    base64char = (char) (iAscii ^0xF);                    break;                default:                    base64char =encodeChars[i];            }            base64Str_undoSalt = base64Str_undoSalt+base64char;        }        for (int i=0;i<end;i++){            base64Str_undoSalt = base64Str_undoSalt+equalChar;        }        return base64Str_undoSalt;    }    /**     * Test the decoder and encoder.     * Call as <code>Base64 [string]</code>.     */    public static void   main__ok_for_salt(String[] args) { //main__ok_for_salt        String input = "I love you, huhx  雪梅!I love you, huhx  雪梅!2I love you, huhx  雪梅!3I love you, huhx  雪梅!4I love you, huhx  雪梅!5I love you, huhx  雪梅!6I love you, huhx  雪梅!7";        //String input = "123456mm9";        System.out.println("编码前="+input);        String encodeStr = encode(input);        System.out.println("base64编码后encode= "  + encodeStr);        System.out.println("base64编码后的长度="+encodeStr.length());        System.out.println("=======================");        byte[] byteDecode = decode(encodeStr);        String decodeStr = new String(byteDecode);        System.out.println("再解码回来decode:==="   + decodeStr );        System.out.println("=======================");        System.out.println("再解码回来decode2:==="   + new String(decode("}}:sb3^l+)lvd}wga)>oe#!g6^uq5q*&+<kgb(92^}:5b3<s+(h1a)gg+_mbquaih}%y}}:sb3^l+)lvd}wga)>oe#!g6^uq" )));        System.out.println("=======================");        String ctfPassword = " {9*8ga*l!Tn?@#fj'j$\\g;;";//ctfPassword.toCharArray()        String base64_undo_salt = xor_do_salt_cft(ctfPassword);        System.out.println("undo 盐操作结果:"+base64_undo_salt);        System.out.println("=======================");        //System.out.println("todo 盐操作结果:"+xor_do_salt_cft(base64_undo_salt));        byte[] byteDecode_CtfPasswprd =decode(base64_undo_salt);        System.out.println("ctf的变形金刚Base64解密: " + base64_undo_salt  + " -> ("                + new String(byteDecode_CtfPasswprd) + ")+长度="+byteDecode_CtfPasswprd.length);    }    public static void   main(String[] args){        //String base64Str = "'{6*?gn*k![n8@,fm'e$[g4;";  //"'{6*-?gn*-k![n-8@,fm'e$[g4;"        String base64Str = "'{6*?gn*k![n8@,fm'e$[g;;";  //"'{6*===?gn*===k![n===8@,f===m'e$[g;;"        int end = 0;  // end state        if (base64Str.endsWith(equalChar+"")) { //原来"="            end++;        }        if (base64Str.endsWith(equalChar2)) {//原来"=="            end++;        }        int len = (base64Str.length() + 3)/4 * 3 - end;        //byte[] result = new byte[len];        int[] result = new int[len];//这里是为了打印看看char的数组-主要是部分不可见字符        String resultStr = "";        int dst = 0;        try {//如果不做try ..catch (ArrayIndexOutOfBoundsException e) {} 这需要len+3.//最后3个没有作用            for(int i=0;i<base64Str.length();i=i+4){                int asci00,ascc01,ascc02,ascc03; //对应00-03的ascii码                char[] b3 = base64Str.substring(i,i+4).toCharArray();                System.out.println(b3);                //第一个字节                asci00 =charSet.indexOf(b3[0]);                ascc01 = charSet.indexOf(b3[1]);                int byte00 = asci00<<2|ascc01>>4;                result[dst++] = byte00;                System.out.println("asci00<<2|ascc01>>4=" + byte00);                //==========第2个字节                ascc01 = charSet.indexOf(b3[1]);                ascc02 = charSet.indexOf(b3[2]);                int byte01 = (ascc01&0xf)<<4 | ascc02>>2 ;                result[dst++] = byte01;                System.out.println("(ascc01&0xf)<<4 | ascc02>>2="+byte01);                //==========第2个字节                ascc02 = charSet.indexOf(b3[2]);                ascc03=charSet.indexOf(b3[3]);                int byte02 = (ascc02&0x03)<<6 | ascc03;                result[dst++] = byte02;                System.out.println("(ascc02&0x03)<<6 | ascc03="+byte02);                System.out.println("===============");                resultStr = resultStr+(char)byte00+(char)byte01+(char)byte02;            }        }catch (ArrayIndexOutOfBoundsException e) {}        System.out.println("解码后:   result="+new Gson().toJson(result));//解码后好像最后2为是多余的:result=[253,30,138,78,9,202,144,3,231,241,133,159,155,247,131,62,14]        System.out.println("解码周:resultStr="+resultStr);  //"解码周:resultStr=ýŠN	ʐçñ…Ÿ›÷ƒ"        String decodeRC4Str =RC4Util_ctf01ok.HloveyRC4(resultStr,"36f36b3c-a03e-4996-8759-8408e626c215");        System.out.println("decodeRC4="+decodeRC4Str);        System.out.println("decodeRC4="+new Gson().toJson(decodeRC4Str.toCharArray()));    }}

查阅资料

syang大神的看雪CTF-变形金刚:

HHHso大神[原创] KCTF 2019 Q1 第二题 有的放矢:

经典对称加密RC4分析 :

用 Java 实现的 Rc4 加密算法(C++):

详解Java中的Base64原理跟用法:

在线工具

ASCII码对照表:

在线进制转换器:

感受

CTF变形金刚题目设计非常巧妙,通过本例的跟踪分析学到不到逆向与反逆向技术;提升自己对加密算法的理解。

分享是一种美德,牵手是一种生活方式。最后感谢今日头条提供的分享平台。

标签: #c语言rc4算法代码 #rc4算法的过程 #rc4算法的实现