1 總覽:
a) 查看清單文件AndroidManifest注冊的各種信息(入口類、權限、廣播、服務等)
l 發現只有入口類信息:
Ø com.test.pac.demo.MainActivity
b) 安裝程序查看大致行為
l 輸入UserName(長度為16)
l PassWord 可以輸入,也可以點擊Generate 生成
l 點擊Check按鈕驗證PassWord是否正確
確認目標:分析PassWord生成算法並寫出該算法程序,最后驗證逆向的算法是否正確。
2 逆向過程:
2.1 配置.so 調試環境
為了更好的觀察數據變化,需要配置.so調試環境
l 配置動態分析so 文件的環境
手機沒有root,額嘗試了也root不了,提示沒有匹配機型,動態調試so文件失敗。。。。。。
嗯,浪費了很多時間被多篇博客誤導了;原來可以直接使用IDA 直接附加夜神模擬器進程動態調試so文件,配置參考:
https://cloud.tencent.com/developer/article/1357098;但是其中附加方法我變了一下,原文在我機器上行不通;效果如下圖:
2.2 分析關鍵點
使用反編譯工具Jadx 查看反編譯代碼,確定關鍵調用:
分析:
1> 首先根據長度信息和控件之間的對應關系,可以確定f7997x為UserName編輯框控件、f7996w為PassWord編輯框控件。
2> 進一步可以確定getck() 是關鍵校驗算法,getbt() 關鍵密碼生成算法。
2.3 密碼校驗函數getck()
l 查看該函數信息
分析:
可以看到getck()是一個native 層的函數。PrtUtil.m7581a() 對 輸入的PassWord 進行了數據轉換:從左到右每兩個字符轉換成一個16進制數值,再強轉此數值為一個字符。例如: “31323368656c6c6f”—》“123hello”
l 進入so 文件后,發現getck() 函數有4個參數,但是實際是2個參數,,然后就有點小懵(以前還沒有分析過)
l 重新設定a1 參數類型為JNIEnv* 類型,就清晰很多了
再通過查閱網上資料,發現a2 代表返回類型,a3 、a4 分別是傳入的參數。
l 繼續分析 getck()
a) SSE向量運算-轉換UserName為其Ascii串
分析:
Ø 進入getck() 函數之后,該函數對UserName 通過一系列SSE2向量運算計算出一個數據(供后面計算摘要使用);經過分析,這里應該是優化后的代碼;實際只是求UserName串的Ascii串。
例如:
“321cba” 的Ascii:"333231636261"
--> 16進制串如下:
"\x33\x33\x33\x32\x33\x31\x36\x33\x36\x32\x36\x31"
所以此處的C代碼暫定為:
unsignedchar * GetAsAs(char * Dest,intLen)
{
unsignedchar * Ret = (unsignedchar *)malloc(Len * 2);
memset(Ret, 0, Len * 2);
for (int i = 0; i < Len*2; i+=2)
{
unsignedchar Tmp = Dest[i/2]>>4& 0x0f|0x30;
Ret[i] = Tmp;
Ret[i + 1] = Dest[i/2]& 0x0f | 0x30;
}
return Ret;
}
b) HMAC_sha256 簽名
分析:
Ø 然后,將前面計算的數據(UserName的Ascii串的HMAC 摘要簽名;並將簽名信息作為參數(參數還有用戶名、常見字符表、密碼起始地址+4、密碼起始地址)作為參數傳入sub_34CC0() –》這里我重命名KeyFunc_KKKKK()函數進一步計算。
l 詳細分析sub_34cc0() / KeyFunc_KKKKK() 函數
分析:
Ø 可以看到;此函數只是一些OpenSSL函數的調用;簡述流程就是創建Ctx對象,使用AES_256_gcm引擎;以UserName作為IV初始向量、前面計算的HMACResult簽名作為密鑰,解密出密文。(Aes_256_gcm 算法的的參考鏈接: https://www.cnblogs.com/0616--ataozhijia/p/11271433.html)
l 最后以EVP_DecryptFinal_ex() 返回值作為這次getck() 函數成功與否的標識
分析:
Ø 而EVP_DecryptFinal_ex() 成功就返回一個正數,不然就標識着失敗;這也驗證了反編譯出的源碼中的調用返回結果的判別:getck()>0 == { success;};
2.4 密碼生成函數getbt()
l 查看該函數信息:
分析:
Ø 可以看到getbt() 也是一個native層的函數;而PrtUtil.m7580a()是將目標字符串轉換成其Ascii16進制格式字符串,例如“123hello”—》“31323368656c6c6f”。特殊情況:如果字符Ascii的16進制<0x10,就會填充一個0;例如:字符Ascii的16進制為0x8 則--> 08
l getbt()函數中使用到的全局168240處的64Bytes數據分析
看到getbt() 一開始就使用了一個全局變量;一全局的8個qword = 64Bytes 的值,其起始地址168240,就分析了一下,如下:
分析:
Ø 這8個qword = 64 Bytes的值是由一開始隨機產生32 Bytes,還經過了一些計算(3層HMAC計算,后面有分析)來填充到這64Bytes的區域的。在JNI_Onload 的時候會調用這個函數。一旦程序加載成功JNI_Onload調用之后;在程序運行過程中,這64Bytes 的數值就是穩定的。(暫時認為這個時候每次程序運行都是不相等的,所以想要編寫注冊機的話,需要每次動態讀取這一塊內存,加入密碼計算過程)。(也可以使用調試查看數據,但畢竟不適合非專業人員使用)
Ø 這時候注冊機應該會涉及到Android的跨進程內存讀寫:
(參考:https://blog.csdn.net/mldxs/article/details/14486827)
Ø 進一步分析根據32Byres隨機數據填充該168240位置64Bytes的算法
分析:
n 使用了3層 HMAC 簽名算法,計算出的簽名值,用來填充168240開始的64字節區域。
l 接着計算通過167004位置的cipher表映射+計算出的特征值
分析:
Ø 結合后面的代碼,這里的HIWORD(V45) = w_FeatureDigit就是密碼的偏移2處的2Byte(這里w_FeatureDigit是重命名的),查看了 上下文,這個特征值在后面HMACMD計算、密碼的數據都用到了;所以這塊隨機數據處理后的值,目前看來不能缺少。
(前面驗證函數有詳細分析,這里就不贅述了)
l 計算HMAC SHA256簽名
分析:
Ø 這里面HMAC就使用到了 前面計算了那64Bytes 的特征值(WORD)。
l 計算UserName的Ascii串在全局映射表中計算出的特征:
l 創建一份常見字符串文本(從后面代碼看,是用來作為AAD數據)
l 調用過程函數35210() 加密
分析:
Ø 加密了UserName的Ascii串經過Map_167004計算的特征值的密文2Byte並傳出;然后將16Byte 的tag值也傳出。
l 最后組合密碼
分析:
Ø 根據剛才傳出的區域EncryptRegion,組合v45為起始的密碼區域。
3 總結歸納編寫注冊機
3.1 總結密碼的構成分析
解釋:
該密碼 由3部分組成:
Ø 第一部分:是由UserName的Ascii 串經過 全局的映射表(167004h)計算后的的2Bytes 特征值,使用Aes_256_gcm 加密后的密文2Byte值。
Ø 第二部分:原始數據是JNI_Onload里面調用函數生成32Bytes的隨機值復制兩份,然后經過3層HMAC運算結果填充到168240h處的64Byte;經167004h處的映射表計算,得出的2Byte值。
Ø 第三部分:是經過AES_256_gcm 加密算法后的16Byte Tag值
3.2 總結需讀進程內存的數據
Ø 全局64Bytes數據(168240h起始的)
解釋:此區域數據,每次程序啟動調用JNI_Onload函數后,會調用里面的
35520()函數產生隨機32Bytes數據,然后調用35330()函數將這32Bytes的的數據進行3層HMAC_SHA256簽名計算。將各層MD簽名值賦值到168240h起始的64Bytes。
Ø 全局CipherTable (167004h 起始的,也可以手工從鏡像中提取,但是都到這一步了,順道就提取了)
解釋:此表在注冊機中,在計算中提供了一種映射關系;每次程序啟動
調用JNI_Onload函數的會在168240h起始的64Bytes位置產生新的數據,而映射表,在計算中會因為不同的Bytes值,得到不同的特征值,供后面的HMAC簽名運算和密碼生成運算。
3.3 注冊機編寫思路
(1) 通過進程讀取內存數據(168240h起始的64bytes,也可順帶把那個全局映射表也取出來)
https://blog.csdn.net/mldxs/article/details/14486827
步驟:
a) 通過獲取libnative-lib.so 所在模塊的數據區段位置,來定位該數據,如下圖:
分析:該模塊具有很明顯的特征 :所屬libnative-lib.so並且具有rw權限。全局64Bytes 數據距離該區段起始位置的偏移為:1240h;全局映射表所在偏移:4h。
b) 編寫程序獲取數據(此程序在linux環境或者Cygwin中編譯好,放到Android中運行)
(2) 編寫小函數:將UserName字符串轉換成其Ascii串的Ascii串
如:“123” –》“333133323333”
代碼:
unsignedchar * GetAsAs(char * Dest,intLen) { unsignedchar * Ret = (unsignedchar *)malloc(Len * 2); memset(Ret, 0, Len * 2); for (int i = 0; i < Len*2; i+=2) { unsignedchar Tmp = Dest[i/2]>>4 &0x0f|0x30; Ret[i] = Tmp; Ret[i + 1] = Dest[i/2] & 0x0f | 0x30; } return Ret; }
// 后面就扣取代碼了
(3) 根據跨進程獲取到的全局映射表(167004h)、全局64Bytes數據(168240h)
計算映射特征值,代碼:
(4) 計算 HMAC、AES_256_gcm
(需要安裝配置openssl )
代碼:
(6) 得出結果
3.4 代碼
注: 這里 還沒有使用 進程內存讀寫,使用了讀取出來 ,應該就可以使用下面的代碼了
// OpenSSL_DE_EN.cpp : 此文件包含"main" 函數。程序執行將在此處開始並結束。
1 // 2 3 4 5 #include"pch.h" 6 7 #include<iostream> 8 9 #include<openssl/ssl.h> 10 11 #include<openssl/err.h> 12 13 #include<Windows.h> 14 15 #pragmacomment(lib,"libssl.lib") 16 17 #pragmacomment(lib,"libcrypto.lib") 18 19 unsignedchar g_Bytes[64]; 20 21 unsignedlonglongint g_4QW[4]; 22 23 24 25 // 獲取UserName 的Ascii 26 27 unsignedchar * GetAsAs(char * Dest, intLen) 28 29 { 30 31 unsignedchar * Ret = (unsignedchar *)malloc(Len * 2); 32 33 memset(Ret, 0, Len * 2); 34 35 for (int i = 0; i < Len * 2; i += 2) 36 37 { 38 39 unsignedchar Tmp = Dest[i / 2] >> 4& 0x0f | 0x30; 40 41 Ret[i] = Tmp; 42 43 Ret[i + 1] = Dest[i / 2]& 0x0f | 0x30; 44 45 } 46 47 return Ret; 48 49 } 50 51 // 加密算法 52 53 54 55 size_t__cdecl Encrypt_sub_35210(unsignedchar* key, unsignedchar * IV_, intIV_len, unsignedchar * UsualChars_AAD, intAAD_Len, unsignedchar * plainText, intplain_Len_2, void *EncryptRegion) 56 57 { 58 59 int CtxObj_; // esi 60 61 int Len_ = 0; 62 63 int v13; 64 65 unsignedchar * cipherText = (unsignedchar *)malloc(0x400); 66 67 EVP_CIPHER_CTX* CtxObj = EVP_CIPHER_CTX_new(); // 創建一個 Ctx 68 69 EVP_EncryptInit_ex(CtxObj, EVP_aes_256_gcm(), 0, 0,0);// 綁定Aes 引擎 70 71 EVP_CIPHER_CTX_ctrl(CtxObj, 9, IV_len, 0); // 設置IV 的長度 72 73 EVP_EncryptInit_ex(CtxObj, 0, 0, key, IV_); // 初始化Key密鑰 和IV初始化向量 74 75 EVP_EncryptUpdate(CtxObj, 0, &Len_, UsualChars_AAD, AAD_Len);// 提供任意AAD 數據;此方式可調用多次 76 77 EVP_EncryptUpdate(CtxObj, cipherText, &Len_, plainText, plain_Len_2);// 提供需要被加密的文本,以及接收加密后的文本 78 79 memcpy(EncryptRegion, cipherText, Len_); // 拷貝 前一步2Byte 密文 80 81 EVP_EncryptFinal_ex(CtxObj, cipherText, &v13);// 加密! 82 83 EVP_CIPHER_CTX_ctrl(CtxObj, 16, 16, cipherText);// 獲取Aes_256_gcm 此次的 Tag 16Byte 84 85 int v10 = Len_; 86 87 *(unsignedlonglong *)((char *)EncryptRegion + Len_ + 8) = *((unsignedlonglong *)&cipherText+1);// 這兩步將16Byte 的tag 值輸出到EnCryptRegion 88 89 *(unsignedlonglong *)((char *)EncryptRegion + v10) = *(unsignedlonglong *)&cipherText;; 90 91 EVP_CIPHER_CTX_free(CtxObj); 92 93 free(cipherText); 94 95 return 0; 96 97 } 98 99 100 101 102 103 int main() 104 105 { 106 107 printf("請輸入 那64Bytes 數據:\n"); 108 109 for (int i = 0; i < 4; i++) 110 111 { 112 113 scanf_s("%i64x", g_4QW + i); 114 115 } 116 117 118 119 EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); 120 121 std::cout <<"Hello World!\n"; 122 123 124 125 WORD v45[10]; 126 127 unsignedchar UserName_Bytes[32]; 128 129 //getCiphertTable 130 131 unsignedlonglong w_167004_MapTable_[1024]; // 當程序JNI_Onload()運行后,這隨機值就穩定了;這時候輸入的UserName 后Generate的密碼就是穩定的。 132 133 //get 64Bytes 134 135 unsignedlong data64Bytes; // 通過引用查看 這是一個 隨機值,賦值所在函數35520() ;在程序啟動JNI_Onload調用的時候會調用35520()函數進行初始化。 136 137 unsignedlonglong *p64Bytes = (unsignedlonglong *)&g_4QW; // 但是重新進入程序,輸入相同的UserName會發現這個值不是穩定的。就是因為JNI_ONload里面調用了35520()進行了隨機數據賦值。 138 139 int count_64 = 64; 140 141 short w_FeatureDigit = 0; 142 143 do // 計算出 程序JNIEnv_Onload程序的時候隨機出的數據64bytes 通過167004處的全局cipher表;找出的隨機數據的特征值 144 145 { // 根據 隨機出的數據;查167004處的cipher表計算出特征值 146 147 w_FeatureDigit = w_167004_MapTable_[*(unsignedchar *)p64Bytes ^ ((unsignedint)w_FeatureDigit >> 8)] ^ (w_FeatureDigit << 8); 148 149 p64Bytes = (unsigned__int64 *)((unsignedchar *)p64Bytes + 1); 150 151 --count_64; 152 153 } while (count_64); 154 155 short w_Rand64Bytes_FeatureDigit = w_FeatureDigit; 156 157 v45[1] = (WORD)w_FeatureDigit; 158 159 int v32 = 0; 160 161 162 163 // 獲取UserName 的Ascii 164 165 char Name[] = { "123456789abcdefg" }; 166 167 unsignedchar * AscData = GetAsAs(Name, 0x10); 168 169 HMAC_CTX *Ctx = HMAC_CTX_new();// 創建一個 Ctx 170 171 HMAC_Init_ex(Ctx, AscData, 32, EVP_sha256(), NULL);// 創建一個 摘要引擎 EVP_sha256 172 173 HMAC_Update(Ctx, (constunsignedchar *)&w_Rand64Bytes_FeatureDigit, 2);// 此函數 可以重復調用,將數據塊加入到 計算結果中 174 175 unsignedint len1 = 0; 176 177 unsignedchar * result = (unsignedchar*)malloc(sizeof(char) * 0x20); 178 179 HMAC_Final(Ctx, result, &len1); 180 181 int v26 = 32; // v45-32 的位置 剛好是 前面計算UserName 的Ascii串 182 183 int v27 = 0; // int16 - Word 184 185 do 186 187 v27 = w_167004_MapTable_[*((unsigned__int8 *)AscData + --v26) ^ ((unsignedint)v27 >> 8)] ^ (v27 << 8); 188 189 while (v26); 190 191 short UserNameMapFeature = v27; 192 193 unsignedchar * UsualCharNum = (unsignedchar *)malloc(0x25); 194 195 memcpy(UsualCharNum, "abcdefghijklmnopqrstuvwxyz0123456789", 0x25u);// 准備AES_GCM的AAD數據 196 197 unsignedchar * EncryptRegion_ = (unsignedchar *)malloc(0x400); 198 199 memset(&EncryptRegion_, 0, 0x400u); 200 201 Encrypt_sub_35210( 202 203 result, 204 205 (unsignedchar *)UserName_Bytes, 206 207 12, 208 209 UsualCharNum, 210 211 37, 212 213 (unsignedchar *)UserNameMapFeature, 214 215 2, 216 217 EncryptRegion_); 218 219 v45[0] = *(WORD *)EncryptRegion_; // 密碼的前2Byte 220 221 *(unsignedlonglong *)&v45[2] = *(unsignedlonglong *)(EncryptRegion_ + 4); 222 223 *(unsignedlonglong *)&v45[6] = *(unsignedlonglong *)(EncryptRegion_ + 12);// 這兩步 是復制密碼的tag 域16Byte/ / v45、v46、v47 這三個連續的一片區域就是 我們的密碼 224 225 226 227 } 228 229
參考資料:
[1] so文件調試環境搭建:
https://cloud.tencent.com/developer/article/1357098
[2] Intel白皮書SSE2相關指令查詢:
[3] HMAC 摘要簽名計算:
https://www.xuebuyuan.com/3193335.html
http://gmssl.org/docs/evp-api.html
[4] OpenSSL AES_256_gcm加解密講解
https://s0www0openssl0org.icopy.site/docs/man1.1.1/man3/EVP_CipherFinal_ex.html
http://gmssl.org/docs/evp-api.html
[5] AES 的多種加解密模式
https://www.cnblogs.com/0616--ataozhijia/p/11271433.html
[6] Android 跨進程讀取內存數據
https://blog.csdn.net/mldxs/article/details/14486827
[7] VS2017 使用OpenSSL庫