Android 逆向技巧總結
以代表性的 crackme 為例總結相關知識點。一緒に頑張りましょう!github 倉庫ofollow,noindex">在此 。
JNI_Onload 中通過 RegisterNatives 動態註冊 jni 函式
相關函式:
signed int __fastcall JNI_OnLoad(_JavaVM *a1) ((int (__fastcall *)(_JavaVM *, _JNIEnv **, signed int))v1->functions->GetEnv)(v1, &v8, 65540) /*v1:JavaVMv8:JniEnv65540:jni version */ ((int (__fastcall *)(_JNIEnv *, char *))v3->functions->FindClass)(v3, v4) /*v3:JNIEnvv4:類名*/ ((int (__fastcall *)(_JNIEnv *, int, char **, signed int))v3->functions->RegisterNatives)(v3, v5, off_400C, 2) /*v3:JniEnvv5:FindClass得到的jclass物件off_400C:要註冊的methods2:註冊的methods個數 method的格式為:函式名 函式描述(smali格式) 函式指標 例如(in ida): DCD aHello; "hello" DCD aLjavaLangStr_1; "()Ljava/lang/String;" DCD native_hello+1 */
.init_array
根據 linker 原始碼, section 的執行順序為.preinit_array
->.init
->.init_array
。但 so 是不會執行.preinit_array
的, 可以忽略。
.init_array
是一個函式指標陣列。編寫程式碼時在函式宣告時加上__attribute__((constructor))
使之成為共享建構函式,即可使該函數出現在.init_array
section 中。
IDA 動態除錯時 ‘ctrl+s’ 檢視 section 資訊即可定位這兩個 setction,特別的,對於.init_array
,可通過搜尋Calling %s @ %p for '%s'
定位。
部分原始碼:
void soinfo::CallConstructors() { ... // DT_INIT should be called before DT_INIT_ARRAY if both are present. CallFunction("DT_INIT", init_func); CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);// CallArray 中也會呼叫 CallFunction 函式 } void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) { if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) { return; } TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name); function(); TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name); // The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures // are still writable. This happens with our debug malloc (see http://b/7941716). set_soinfo_pool_protection(PROT_READ | PROT_WRITE); }
dex 結構
修復 dexHeader & onCreate
快速簡記:
結構 | 單位結構體佔位元組 | 共計位元組 |
---|---|---|
DexHeader | - | 0x70h |
String Table | 4 | - |
Type Table | 4 | - |
Proto Table | 12 | - |
Field Table | 8 | - |
Method Table | 8 | - |
Class Def Table | 32 | - |
Data Section(含Map Section) | - | - |
例:misc.apk
hook 系統函式
dump 記憶體搜尋 flag
1. 利用 ddms 的dump HPROF file
功能 (帶箭頭的油桶圖示)
搜尋:strings easyre.sjl.gossip.easyre.hprof | grep 0ctf
2. 利用 gore
gdb 附加程序後直接執行gcore
dump,搜尋:strings core.7967 | grep 0ctf
修改 smali 程式碼
指令參考這裡:point_right:dalvik bytecode
ARM
ARM 的引數傳遞規則
R0、R1、R2、R3, 在呼叫函式時,用來存放前4個函式引數;如果函式的引數多於 4 個,則多餘引數存放在堆疊當中;
低於32位的函式返回值存於 R0。
ARM 的暫存器規則
暫存器 | 作用 |
---|---|
R0 ~ R3 | 呼叫函式時,用來存放前4個函式引數 |
R0 | 函式返回時,存放低於32位的函式返回值 |
R4 ~ R11 |
儲存區域性變數。進入函式時必須儲存所用到的區域性變數暫存器的值,在返回前必須恢復這些暫存器的值;對於函式中沒有用到的暫存器則不必進行這些操作。 在Thumb中,通常只能使用暫存器 R4~R7來儲存區域性變數, 所以函式內部通用的入棧出棧程式碼可以為: STMFD sp!,{r4-r11,lr} // body of ASM code LDMFD sp!,{r4-r11,pc} |
R12 | 用作 IP,內部呼叫暫時暫存器 |
R13 | 用作 SP,棧指標,sp 中存放的值在退出被呼叫函式時必須與進入時的值相同。 |
R14 | 用作 LR,連結暫存器,儲存函式的返回地址;如果在函式中儲存了返回地址,暫存器R14 則可以用作其他用途 |
R15 | 用作 PC,程式計數器 |
R16 | CPSR,狀態暫存器 |
各種檢測
dex 校驗
SHA1 值。
反除錯
- 讀取 /proc/pid/status 的 State 是否為 t
- 讀取 /proc/pid/status 的 TracerPid 是否不為0
- 讀取 /proc/pid/wchan 是否有 ptrace_stop
去花
去花即將規律的花指令 nop 掉並修復跳轉,ida 中的去花指令碼編寫可參考 IDA 的 idc 或 idapython API。
為了使 IDA 識別某個函式X,需要在 Functions Window統統刪除 之前函式X中誤將 junk code 識別為函式的垃圾函式,手動設定函式X的結尾 (Edit - Functions - set function end)。
函式尾部特徵:
BLX __stack_chk_fail POP {R4-R7,PC} (與函式頭 PUSH {R4-R7,LR} 對應)
加密演算法
DES 加密
對稱性加密,典型的 DES 以64 位二進位制為分組
對資料加密。
如果明文不是 64 位(16個16進位制位)的整數倍,則加密前,這段文字必須在尾部補充一些額外的位元組
。
在運算時需要根據特定的表格
以 64 位為單位對明文和祕鑰分別進行置換操作
。
RC6 加密
對稱性加密。主要操作是異或和迴圈左移
。
// Encryption/Decryption with RC6-w/r/b // // Input:Plaintext stored in four w-bit input registers A, B, C & D //r is the number of rounds //w-bit round keys S[0, ... , 2r + 3] // // Output: Ciphertext stored in A, B, C, D // // '''Encryption Procedure:''' B = B + S[0] D = D + S[1] for i = 1 to r do { t = (B*(2B + 1)) <<< lg w u = (D*(2D + 1)) <<< lg w A = ((A ⊕ t) <<< u) + S[2i] C = ((C ⊕ u) <<< t) + S[2i + 1] (A, B, C, D)=(B, C, D, A) } A = A + S[2r + 2] C = C + S[2r + 3]
RC4 加密
對稱性加密。由偽隨機數生成器和異或運算
組成。金鑰長度範圍是[1,255]。
RC4一個位元組一個位元組地加解密。給定一個金鑰,偽隨機數生成器接受金鑰併產生一個S盒。S盒用來加密資料,而且在加密過程中S盒會變化。
虛擬碼:
for i from 0 to 255 S[i] := i endfor j := 0 for( i=0 ; i<256 ; i++) j := (j + S[i] + key[i mod keylength]) % 256 swap values of S[i] and S[j] endfor i := 0 j := 0 while GeneratingOutput: i := (i + 1) mod 256//a j := (j + S[i]) mod 256 //b swap values of S[i] and S[j]//c k := inputByte ^ S[(S[i] + S[j]) % 256] output K endwhile