編輯:關於Android編程
一、簡介
這個題目是別人面試UC優視集團Android逆向工程師一職位的面試題,相比較前面的面試題1,增加了一些難度。

二、題目分析
1.使用JEB程序對UC-crackme-2.apk進行反編譯分析,函數clacSnFuntion就是對用戶輸入的注冊碼進行校驗的。

2.校驗用戶輸入的用戶名和注冊碼的函數clacSnFuntion是在Native層實現的,具體的實現在libclacSn.so庫中。

3.在Native層,對注冊碼校驗函數clacSnFuntion的實現進行分析,在IDA靜態分析中發現Java_com_ucweb_crackme140522_MainActivity_clacSnFuntion函數中校驗用戶的注冊碼的具體函數CheckRegisterCode的代碼被加密了是程序動態運行時在JNI_OnLoad函數中對該函數的代碼進行解密的。


4.在JNI_OnLoad函數中,對加密的校驗函數CheckRegisterCode的代碼進行解密的過程,首先獲取動態加載庫"libclacSn.so"的內存映射地址,修改模塊"libclacSn.so"的內存保護屬性,獲取system/lib/libc.so庫文件中的導出函數ptrace的調用地址對程序進行反調試(需要Nop調用),然後在"libclacSn.so"庫文件的內存映射區中進行函數CheckRegisterCode的特征碼的匹配尋找進行函數加密代碼的定位,對用戶注冊碼校驗函數CheckRegisterCode的代碼進行解密。





5.過掉獲取system/lib/libc.so庫文件中的導出函數ptrace的調用地址對程序進行反調試的方法是將函數ptrace的調用Nop調用即可。


6.校驗函數CheckRegisterCode的代碼解密函數DecryptDataOfAddressOffset_20140522的具體解密的實現。

7.動態調試分析代碼解壓後的校驗函數CheckRegisterCode的代碼邏輯,需要代碼解密的函數CheckRegisterCode的實際地址為A9B891B4。

8.來分析一下CheckRegisterCode函數的實現,函數CheckRegisterCode將獲取到的用戶手機DeviceId進行MD5加密算法處理然後和用戶輸入的用戶名稱進行計算得到最終用戶需要輸入的注冊碼,在函數memcmp處下斷點即函數memcmp的第1個參數就是需要輸入的注冊碼。





7.MD5算法的在ARM匯編下的典型特征:
在沒有算法特征函數以及符號的情況下MD5函數的還是有著典型的特征的。

8.CheckRegisterCode函數的代碼的還原。
signed int __fastcall CheckRegisterCode_A9B891B4(int lpIMEIBuffer, int lpUserNameBuffer, int lpRegSnBuffer)
{
int i; // r4@1
int nChkNumber; // lr@1
signed int v5; // r11@1
signed int v6; // r9@1
int _lpUserNameBuffer; // r10@1
void *hFileHandle; // r0@4
void *_hFileHandle; // r4@4
signed int result; // r0@5
void *time; // r11@6
unsigned int _nStrUserNameLenth; // r4@10
int szIMEIBuffer; // r6@12
int j; // r3@13
int dwFirstIMEIBuffer; // r0@15
MD5_CTX *lp_state; // r5@15
unsigned int index; // r2@15
unsigned int padlen; // r2@16
int m; // r2@20
int n; // r3@20
char *lpRegUserBuff; // r5@22
int k; // r4@22
int v23; // r2@23
int v24; // r3@23
int _lpIMEIBuffer; // [sp+8h] [bp-4Ch]@1
int _lpRegSnBuffer; // [sp+Ch] [bp-48h]@1
int nStrUserNameLenth; // [sp+10h] [bp-44h]@1
unsigned int nTime; // [sp+10h] [bp-44h]@12
unsigned int nStrImeiLength; // [sp+14h] [bp-40h]@1
int v30; // [sp+18h] [bp-3Ch]@1
int v31; // [sp+1Ch] [bp-38h]@1
int v32; // [sp+20h] [bp-34h]@1
int v33; // [sp+24h] [bp-30h]@1
int v34; // [sp+28h] [bp-2Ch]@1
int v35; // [sp+2Ch] [bp-28h]@1
int szFileNameBuffer; // [sp+30h] [bp-24h]@1
int v37; // [sp+34h] [bp-20h]@1
int v38; // [sp+38h] [bp-1Ch]@1
int v39; // [sp+3Ch] [bp-18h]@1
int v40; // [sp+40h] [bp-14h]@1
int v41; // [sp+44h] [bp-10h]@1
char lpTime[4]; // [sp+48h] [bp-Ch]@1
int v43; // [sp+4Ch] [bp-8h]@1
unsigned int t; // [sp+50h] [bp-4h]@6
int szRegUserBuff; // [sp+54h] [bp+0h]@1
MD5_CTX *state[4]; // [sp+154h] [bp+100h]@15
int count[2]; // [sp+164h] [bp+110h]@15
_DWORD szDecrypt_16[5]; // [sp+1ACh] [bp+158h]@15
int v49; // [sp+1BCh] [bp+168h]@15
int lpInt; // [sp+1C0h] [bp+16Ch]@1
int v51; // [sp+1C4h] [bp+170h]@1
int v52; // [sp+1C8h] [bp+174h]@1
int v53; // [sp+1CCh] [bp+178h]@1
int v54; // [sp+1D0h] [bp+17Ch]@1
char bits; // [sp+1D4h] [bp+180h]@15
int _nChkNumber; // [sp+1DCh] [bp+188h]@1
i = 0;
nChkNumber = (*_stack_chk_guard)[0];
v35 = 0;
v41 = 0;
v31 = 0x1F314341;
v37 = 0x103C2233;
v32 = 0x20014131;
v38 = 0xF61283B;
v33 = 0x50423331;
v39 = 0x1320363B;
v34 = 0x11520E;
v5 = 0x40081311;
v6 = 0x3371601E;
_lpIMEIBuffer = lpIMEIBuffer;
_lpUserNameBuffer = lpUserNameBuffer;
_lpRegSnBuffer = lpRegSnBuffer;
_nChkNumber = nChkNumber;
v40 = 0x5E2120;
*(_DWORD *)lpTime = 0x503C0052;
szFileNameBuffer = 0x40081311;
v30 = 0x3371601E;
v43 = 0;
memset_0(&szRegUserBuff, 0, 0x100); // 保存用戶輸入的注冊碼
v51 = 0;
v52 = 0;
v53 = 0;
v54 = 0;
lpInt = 0;
nStrImeiLength = Strlen(_lpIMEIBuffer); // 獲取設備機器碼的字符串長度
nStrUserNameLenth = Strlen(_lpUserNameBuffer);// 獲取用戶輸入用戶名的長度
//
while ( 1 ) // 解密需要加載的庫文件/system/lib/libc.so
{
*(int *)((char *)&szFileNameBuffer + i) = v6 + v5;
i += 4;
if ( i == 24 )
break;
v5 = *(int *)((char *)&szFileNameBuffer + i);
v6 = *(int *)((char *)&v30 + i);
} // =========================================
//
*(_DWORD *)lpTime = 'emit';
hFileHandle = (void *)dlopen(&szFileNameBuffer, 0);// 加載系統庫文件/system/lib/libc.so
_hFileHandle = hFileHandle;
if ( hFileHandle )
{
time = dlsym(hFileHandle, lpTime); // 獲取庫函數time的調用地址
dlclose(_hFileHandle);
((void (__fastcall *)(unsigned int *))time)(&t);// 調用函數time獲取系統時間
//
if ( Strlen(_lpRegSnBuffer) != 0x10 ) // 注冊碼必須是16位
goto Jmp_FalseLenth; //
//
_nStrUserNameLenth = nStrUserNameLenth;
if ( nStrUserNameLenth >= (signed int)nStrImeiLength )// 用戶輸入的用戶名的長度大於設備機器碼的長度的情況
_nStrUserNameLenth = nStrImeiLength; // 用戶名的長度取設備機器碼的長度
//
nTime = t / 0x14; // 系統時間t/0x14
szIMEIBuffer = j_dlmalloc(nStrImeiLength + 1);// 為保存設備機器碼分配內存
memset_0(szIMEIBuffer, 0, nStrImeiLength + 1);// 緩沖區清零
((void (__fastcall *)(unsigned int *))time)(&t);// 再次調用函數time獲取系統時間t
*(_DWORD *)szIMEIBuffer ^= (signed int)t / 0x14 ^ nTime;
strcpy_0(szIMEIBuffer, _lpIMEIBuffer);
if ( (signed int)_nStrUserNameLenth > 0 )
{
j = 0;
do
{
*(_BYTE *)(szIMEIBuffer + j) ^= *(_BYTE *)(_lpUserNameBuffer + j);
++j;
}
while ( j != _nStrUserNameLenth );
}
((void (__fastcall *)(_DWORD))time)(&t); // 再次調用函數time獲取系統時間t
dwFirstIMEIBuffer = *(_DWORD *)szIMEIBuffer;// 取設備機器碼的首DWORD數據
szDecrypt_16[1] = 0;
szDecrypt_16[2] = 0;
szDecrypt_16[3] = 0;
*(_DWORD *)szIMEIBuffer = dwFirstIMEIBuffer ^ (signed int)t / 0x14 ^ nTime;
v49 = 0; //
//
lp_state = (MD5_CTX *)state; // ============================================
state[0] = (MD5_CTX *)0x67452301;
state[1] = (MD5_CTX *)0xEFCDAB89;
state[2] = (MD5_CTX *)0x98BADCFE;
count[0] = 0;
state[3] = (MD5_CTX *)0x10325476;
szDecrypt_16[0] = 0; // 保存szIMEIBuffer經過MD5加密後的結果
count[1] = 0; // MD5算法的初始化,調用MD5Init函數
// ============================================
MD5Update_A9B88B78((MD5_CTX *)state, szIMEIBuffer, nStrImeiLength);// 調用MD5的MD5算法的Update函數,szIMEIBuffer保存原結果
// ============================================
MD5Encode_A9B88C64((int)&bits, (int)count, 8u);
index = ((unsigned int)count[0] >> 3) & 0x3F;
padlen = index > 0x37 ? 120 - index : 56 - index;
MD5Update_A9B88B78((MD5_CTX *)state, 0xA9B8CF44, padlen);
MD5Update_A9B88B78((MD5_CTX *)state, (int)&bits, 8u);
MD5Encode_A9B88C64((int)szDecrypt_16, (int)state, 16u);// 調用MD5算法的MD5Final函數,szDecrypt保存MD5加密後的結果
// ============================================
do
{
LOBYTE(lp_state->count[0]) = 0;
lp_state = (MD5_CTX *)((char *)lp_state + 1);
}
while ( (_DWORD *)lp_state != szDecrypt_16 );//
//
m = 16;
n = 0;
do
{
*((_BYTE *)&lpInt + n) = m ^ *((_BYTE *)szDecrypt_16 + n);
++n;
m = (m + 1) & 0xFF;
}
while ( n != 16 ); //
//
lpRegUserBuff = (char *)&szRegUserBuff; // 用戶輸入的用戶名
k = 0;
do
{
v23 = *((_BYTE *)&lpInt + k);
v24 = k++ + 16;
j_sprintf(lpRegUserBuff, (const char *)dword_A9B8AC58, v23 ^ v24);// 格式化字符串%02X
lpRegUserBuff += 2;
}
while ( k != 16 ); //
//
((void (__fastcall *)(_DWORD))time)(&t); // 再次調用函數time獲取系統時間t
szRegUserBuff ^= nTime ^ (signed int)t / 0x14;// 生成用戶輸入的注冊碼**********************
free(szIMEIBuffer); //
//
if ( !memcmp_0((int)&szRegUserBuff, _lpRegSnBuffer, 16) )// 對16位用戶輸入的注冊碼進行校驗
result = 1; // 返回值為1,此種情況說明,用戶輸入的注冊碼正確
else
Jmp_FalseLenth:
result = 0;
}
else
{
result = -1;
}
if ( _nChkNumber != (*_stack_chk_guard)[0] )
j___stack_chk_fail_0(result);
return result;
}
//說明 用戶需要輸入的注冊碼是有用戶的手機的設備ID和用戶輸入的用戶名稱經過算法生成的。
Android開發筆記之Android中數據的存儲方式(二)
我們在實際開發中,有的時候需要儲存或者備份比較復雜的數據。這些數據的特點是,內容多、結構大,比如短信備份等。我們知道SharedPreferences和Files(文本文
Android中刪除Preference詳解
Android的設置界面實現比較簡單,有時甚至只需要使用一個簡單的xml文件即可.聲明簡單,但是如何從PreferenceScreen或者PreferenceCatego
仿知乎日報第六篇:為MainFragement加載數據
一.前面講了,MainFragment的布局就是一個ViewPager,而ViewPager的一個個頁面就是首頁,日常心理學,用戶推薦日報,電影日報,不許無聊,設計日報,
Android開發技巧——大圖裁剪
本篇內容是接上篇《Android開發技巧——定制仿微信圖片裁剪控件》 的,先簡單介紹對上篇所封裝的裁剪控件的使用,再詳細說明如何使用它進行大圖裁剪