前言
最近在研究某某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中註冊的回撥。
Activity基類的onStart才是真正註冊OnClick回撥的地方
Base64解碼圖為了方便理解eq中涉及的魔改的Base64編碼,這裡先貼上一直Base64解碼的草稿圖
下圖是Base64編碼4個字元'{6*的解碼草稿圖,畫的不是很好,主要是方便我自己理解寫解碼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部分==========\teq是由sub_784動態註冊\tini_array部分==========\t#解碼36長度字串byte_4020:\tbyte_4020 =650f909c-7217-3647-9331-c82df8b98e98+0x00(結束符)\t#解碼Base64為的64+1個編碼字元byte_4050= \tbase64Chars = byte_4050 =!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\\';+0x00(結束符)\t#app偽裝java類的名稱byte_40A0:\tclassName_Sign= android/support/v7/app/AppCompiatActivity +0x00(結束符)sub_784部分============\t\t獲取RC4的金鑰演算法============:\t\tv5=演算法01 = 刪除(byte_4040中的"-") =650f909c721736479331c82df8b98e98\t\tv4=演算法02 =刪除byte_4020中的”-“後,特殊運算又加上”-“符號=89e89b8f-d28c-1339-7463-7127c909f056\t\t自定義演算法03==========\tv6=v17=自定義演算法03 = 特殊演算法處理後 =36f36b3c-a03e-4996-8759-8408e626c215\t\t\t演算法引數01:v4\t\t演算法引數02:unk_23DE(10位長度) =2409715836\t\t演算法引數03:unk_23D8(16位長度)=dbeafc2409715836\t\tRC4魔改部分======\t(已經逆向演算法)\t\tRC4第1步:v48:拷貝&unk_B4D863E8中的256個字元到v48(應視是RC4演算法的魔改資料)\t\tRC4第2步:v49:計算RC4 的臨時256自己T向量,公式:iK[i]=(byte)aKey.charAt((i % aKey.length()));\t\tRC4第3步:V48狀態向量S進行置換操作V48\t\tRC4第4步:RC4產生金鑰流iOutputChar,然後return new String(iOutputChar)??????\t\t\tv27 =33 ,即v6[3];\t\t\t\t\tRC4第5步:產生金鑰流--其他還結合魔改Base64運算\t\t\t分配輸入密碼對應的base64位元組的儲存空間\t\t\tBase64演算法的字典\t\t\t\tBase64魔改部分======(已經逆向演算法)\t\tBase64的字典部分-64個字元+1個結束符\t\t每4個字元中第0個與0x6異或,第0個與0xF異或\t\t switch (i%4){ case 0: base64char = (char) (iAscii ^0x07); break; case 2: base64char = (char) (iAscii ^0xF); break; default: base64char =encodeChars[i]; }\t\t
JNI_OnLoad 程式碼邏輯分析RegisterNatives1:搜尋JNI_OnLoad,雙擊進入函式體
2:匯入Jni.h檔案。選單路徑:ida/File/Load file/Parse C header file
3:修改引數型別為_JavaVM
4:按g快捷鍵直接跳轉到0x4014
找到eq註冊對應的sub_784函式
當然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(結束符)
eq函式的分析(即sub_784函式)ida定位到sub_784,修改引數型別,並做了動態除錯後,梳理sub_784的邏輯。
unk_23E8字串(RC4魔改演算法的初始化256個字元S狀態向量)
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指向的內容
把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 加密演算法 * /file/2020/10/07/20201007232722_3.jpg * RC4加密演算法的原理及實現 :/file/2020/10/07/20201007232723_4.jpg */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 * 程式碼來源:/file/2020/10/07/20201007232726_5.jpg.html */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=ý\u001eN\tÊ\u0003çñ
÷" 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-變形金剛:http://blog.syang.xyz/2019/04/kanxue-transformer/
HHHso大神[原創] KCTF 2019 Q1 第二題 有的放矢:/file/2020/10/07/20201007232731_6.jpg :http://ronpa.top/2018/11/27/%E7%BB%8F%E5%85%B8%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86RC4%E5%88%86%E6%9E%90/
用 Java 實現的 Rc4 加密演算法(C++):https://www.cnblogs.com/java-stack/archive/2010/10/25/11952676.html
詳解Java中的Base64原理跟用法:https://blog.csdn.net/qq_36522306/article/details/80753099
線上工具ASCII碼對照表:https://tool.oschina.net/commons?type=4
線上進位制轉換器:https://tool.oschina.net/hexconvert/
感受CTF變形金剛題目設計非常巧妙,通過本例的跟蹤分析學到不到逆向與反逆向技術;提升自己對加密演算法的理解。