[原創]看雪CTF2017第六題 Ericky-apk詳細writeup(從一個安卓新手的角度)
概述
本題是安卓cm,目測肯定需要除錯so。
準備工具:
- ApkIde改之理(其他類似的也行,能夠反編譯apk,得到jar,so等)
- IDA(用於除錯so),需要6.x以上,忘了是x幾,我用的6.6
- adb(ApkIde改之理就有)
反編譯
將6-Ericky kanxue.apk拖進ApkIDE改之理,等待編譯(沒有加殼),ok。
在右側樹結構欄中,找到smali->android->com->miss->rfchen,列表中就是java層的主要函式。
點選MainActivity.smali,然後點選工具欄中jd-gui.exe,抓到java原始碼檢視。
public class MainActivity extends Activity { private EditText ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ = null; private Button ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ = null; protected void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130968603); this.ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ = ((Button)findViewById(2131427415)); this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ = ((EditText)findViewById(2131427416)); this.ﹶˊﹶˊﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶﹶˊﹶﹶﹶˊﹶˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶﹶˊˊˊˊˊˊﹶˊˊﹶﹶﹶˊˊﹶﹶˊˊﹶﹶˊˊˊﹶˊﹶˊﹶˊﹶﹶˊﹶﹶˊˊˊﹶﹶˊﹶﹶﹶﹶˊﹶﹶˊˊﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶˊˊﹶﹶﹶﹶˊˊˊˊˊﹶﹶˊˊﹶˊﹶ.setOnClickListener(new View.OnClickListener() { public void onClick(View paramView) { MainActivity.this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ(); } }); } public void ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ() { String str = this.ˊˊﹶˊﹶﹶﹶˊﹶˊﹶˊˊˊˊˊˊˊﹶﹶﹶﹶﹶˊﹶﹶˊˊˊﹶﹶﹶˊﹶˊˊﹶﹶﹶˊˊˊﹶˊﹶﹶﹶˊﹶﹶˊﹶﹶﹶﹶﹶˊﹶﹶﹶˊˊﹶﹶˊˊﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶˊﹶﹶˊˊˊﹶˊˊﹶﹶˊˊˊﹶﹶˊˊˊﹶˊˊﹶﹶˊﹶˊﹶﹶˊﹶﹶˊﹶﹶﹶˊˊˊˊﹶﹶﹶﹶˊﹶˊˊˊ.getText().toString().trim(); StringBuilder localStringBuilder = new StringBuilder(); localStringBuilder.append(str); if (utils.check(localStringBuilder.toString().trim())) { Toast.makeText(this, MainActivity.1.utils.dbcb("뙩あ嵓ﳈ"), 0).show(); return; } Toast.makeText(this, MainActivity.1.utils.dbcb("뙸ぞ崌ﳯ�핣�晌鬗㞕뵯"), 0).show(); } }
這混淆的函式名我也是醉了,但這都不重要。輸入key之後,然後點選按鈕,進入OnClick,呼叫了上面程式碼中第二個函式(什麼?我怎麼知道的,因為它們哪個...點號...的函式名相同!!)。
然後呼叫了utils.check來驗證,成功提示!這裡成功和錯誤提示的字串做過變換,透過utils.dbcb解密,不細看了,不重要!
進入utils.java,看到載入了so,呼叫的是這個so的匯出函式,看反編譯目錄lib/armeabi-v7a(只提供了arm的so,要有個x86的好了),知道這個so是librf-chen.so。
//典型的NDK呼叫,查查就知道了! package com.miss.rfchen; public class utils { static { System.loadLibrary(MainActivity.1.utils.dbcb("ᐲེ雒�蹬")); } public static native boolean check(String paramString); }
那麼重點來了,要分析librf-chen.so的check函式,才能搞定此題。
準備除錯
早上提前學習了一下so除錯方法,找到了看雪安卓大神的教程,就是參考中的IDA動態除錯技術,然後用上了,很好用!
跟著走
下面開始照著做。
- 連上手機(或者模擬器),使用adb devices看看成功連上沒有
- adb push ../dbgsrv/android_server /sdcard/sv,教程是直接放入/data/data,一般許可權不夠
- 然後進入shell,adb shell,輸入su,獲得root許可權,然後cp /sdcard/sv /data/data/sv
- 修改sv許可權,chmod 777 /data/data/sv
- 執行sv,/data/data/sv,預設監聽到23946埠,Listening on port #23946。這步有個細節,不能直接adb shell /data/data/sv,這樣許可權不夠,無法讀取到程式資訊,需要adb shell; su; /data/data/sv
- 再開一個cmd,然後執行adb forward tcp:23946 tcp:23946
- 執行一個idaq.exe,然後在選單debugger->attach->remote Armlinux/android debugger,輸入localhost, 23946,ok
- 彈出程式框,按下Alt+T,輸入chen,搜尋到1808 [32] com.miss.rfchen,ok
- F9執行
\ApkIDEz> .\adb.exe shell shell@your phone:/ $ su su root@your phone:/ # /data/data/sv /data/data/sv IDA Android 32-bit remote debug server(ST) v1.17. Hex-Rays (c) 2004-2014
在介面中輸入key,然後點選按鈕,此時librf-chen.so才載入,然後ctrl+s,alt+t,輸入librf找到librf-chen.so的基地址資訊(記為base),記下來。
用另一個ida開啟librf-chen.so,找到check匯出函式的偏移地址00002814,計算base+00002814,然後g在IDA偵錯程式中輸入該地址,加上斷點。
check(_JNIEnv *,_jclass *,_jstring *) 00002814
IDA基本除錯快捷鍵和OD一樣:
F9: 執行 F8: 步過 F7:步入
F9,跑起來,然後再次點選按鈕,就斷下來,進入了check。
下面就是跟和除錯的過程了,看資料,看流程,分析演算法!
arm彙編基礎
得提前有個準備,看看arm指令,瞭解基本的指令,函式呼叫方式,下面列幾個,更多的就看參考中的文章了
MOVS 同x86的mov LDR 載入記憶體資料到暫存器 STR 暫存器資料存入記憶體 B/BL 跳轉/函式呼叫 TST/CMP 比較 ADD/SUB 加/減
然後最主要的,函式呼叫的引數傳遞。arm預設使用的fastcall,透過r0,r1,r2,r3傳遞引數,超過4個引數,使用堆疊傳遞,r0也儲存返回值。
關鍵點跟蹤
在check斷下之後,先是一段資料初始化,先濾過,然後blt sub_2874,進入關鍵函式
然後看到透過MOVS,STR將一些字元放入了記憶體。
.text:0000288A 000 01 60 STR R1, [R0] .text:0000288C 000 4A 20 MOVS R0, #'J' .text:0000288E 000 79 21 MOVS R1, #'y' .text:00002890 000 AD F8 22 00 STRH.W R0, [SP,#arg_22] .text:00002894 000 AD F8 24 10 STRH.W R1, [SP,#arg_24] .text:00002898 000 75 21 MOVS R1, #'u' .text:0000289A 000 AD F8 26 10 STRH.W R1, [SP,#arg_26] .text:0000289E 000 33 21 MOVS R1, #'3'
接著就看讓我恐懼的一幕,b loc_2898開始各種跳轉,指令操作,然後剛跳完又是一個b xxx,接著各種跳轉,毫無疑問,這是一段花指令了。
.text:0000289E B loc_2898
花指令結構
經過多次跟蹤,噁心到快吐的時候,終於看出話指令的基本結構了:
.text:00002BE8 PUSH.W {R4-R10,LR} .text:00002BEC POP.W {R4-R10,LR} .text:00002BF0 B sub_2C1A ;開始 PUSH.W {R4-R10,LR} .text:00002BEC BD E8 F0 47 POP.W {R4-R10,LR} .text:00002BF0 13 E0 B sub_2C1A --------------------------------------------------------------------------- .text:00002BF2 BD E8 F0 47 POP.W {R4-R10,LR} .text:00002BF6 05 E0 B sub_2C04 --------------------------------------------------------------------------- .text:00002BF8 00 F1 01 00 ADD.W R0, R0, #1 .text:00002BFC 0A E0 B loc_2C14 --------------------------------------------------------------------------- .text:00002BFE 1B 46 MOV R3, R3 .text:00002C00 0E E0 B loc_2C20 ======================================= .text:00002C02 10 E0 B sub_2C26 ;跳到快執行的位置 ======================================= .text:00002C04 B1 B5 PUSH {R0,R4,R5,R7,LR} .text:00002C06 01 E0 B loc_2C0C --------------------------------------------------------------------------- .text:00002C08 12 46 MOV R2, R2 .text:00002C0A 01 E0 B loc_2C10 .text:00002C0C 82 B0 SUB SP, SP, #8 .text:00002C0E FB E7 B loc_2C08 --------------------------------------------------------------------------- .text:00002C10 02 B0 ADD SP, SP, #8 .text:00002C12 F1 E7 B loc_2BF8 --------------------------------------------------------------------------- .text:00002C14 A0 F1 01 00 SUB.W R0, R0, #1 .text:00002C18 F1 E7 B loc_2BFE ======================================= .text:00002C1A 2D E9 F0 47 PUSH.W {R4-R10,LR} .text:00002C1E E8 E7 B loc_2BF2 --------------------------------------------------------------------------- .text:00002C20 BD E8 B1 40 POP.W {R0,R4,R5,R7,LR} .text:00002C24 ED E7 B sub_2C02 ======================================= .text:00002C26 2D E9 F0 47 PUSH.W {R4-R10,LR} .text:00002C2A BD E8 F0 47 POP.W {R4-R10,LR} .text:00002C2E FF E7 B sub_2C30 ;進入有效程式碼,一般是接著的地址 .text:00002C30 PUSH {R0,R4,R5,R7,LR} ;開始一般會有一段對稱沒啥作用的話指令 .text:00002C32 SUB SP, SP, #8 .text:00002C34 MOV R2, R2 .text:00002C36 ADD SP, SP, #8 .text:00002C38 ADD.W R0, R0, #1 .text:00002C3C SUB.W R0, R0, #1 .text:00002C40 MOV R3, R3 .text:00002C42 POP.W {R0,R4,R5,R7,LR} .text:00002C46 ADD.W R1, R1, #1 .text:00002C4A SUB.W R1, R1, #1 .text:00002C4E STRH.W R0, [SP,#arg_30] .text:00002C52 MOVS R0, #0x44 .text:00002C54 PUSH.W {R4-R10,LR} .text:00002C58 POP.W {R4-R10,LR} .text:00002C5C B sub_2C86
特徵:
- 每跳轉一個分支,基本都要一段花(記為A段),就是從上面程式碼中註釋開始的問題
- 進行幾個跳轉後,到了結束位置,跳入有效程式碼
- 有效程式碼開頭一般也有加一段花(記為B段)
- 在A段話指令中,指令地址是向下增長的,也就是A開始往下拉一段,就能找到結束位置
- B端一般無跳轉,但是對稱程式碼有多又少
所以根據特徵,去除話指令也挺方便,我使用的IDA的patch功能手工去花的,指令碼牛可以寫個指令碼。
所有花指令填充的00 bf(NOP),然後就可以F5了。
關鍵點跟蹤2
然後接著除錯跟蹤。
接著上面,後續會接著向該段記憶體填充字元(非直接填充,還有個段演算法,根據初始話的0x20的值來做的),我沒有仔細跟蹤演算法了,透過對些記憶體關鍵點下斷,然後跳出迴圈位置下斷,下面0000357A就是迴圈位置,如此多次之後,迴圈結束。
.text:00003576 000 B4 F1 FF 3F CMP.W R4, #0xFFFFFFFF .text:0000357A 000 3F F7 74 AD BGT.W loc_3066
檢視該記憶體資料:
5F019020 4A 00 79 00 75 00 33 00 43 00 4A 00 6C 00 56 00 J.y.u.3.C.J.l.V. 5F019030 44 00 53 00 47 00 51 00 21 00 0A 00 00 00 00 00 D.S.G.Q.!.......
接著跳過一段花之後,呼叫了bl sub_19FC,跟入,發現結果和剛才那段基本一直,也是將字元寫入記憶體,並且記憶體就是剛才那段,只是每次都有一個1偏移。
.text:0000364A 000 FE F7 D7 F9 BL sub_19FC ... librf_chen.so:5EFFB52E ORR.W R3, LR, R2,LSL#1 librf_chen.so:5EFFB532 LDRB.W R0, [R8,R5,LSL#1] librf_chen.so:5EFFB536 ADDS R2, #1 librf_chen.so:5EFFB538 STRB.W R0, [R12,R3] ;也是前面的位置,但是加了個1偏移
同樣,結束之後,檢視記憶體,透過後面分析,知道這段字元就是key加密變換之後要對比的字串。
5ED12020 4A 50 79 6A 75 70 33 65 43 79 4A 6A 6C 6B 56 36 JPyjup3eCyJjlkV6 5ED12030 44 6D 53 6D 47 48 51 3D 21 21 0A 0A 00 00 00 00 DmSmGHQ=!!......
子過程返回之後,接著b進入另一段。調了這麼久,我們輸入的key去哪裡了?下面來了!
text:00003680 000 D9 F8 00 00 LDR.W R0, [R9] 之前傳入的參_JNIEnv .text:00003684 000 41 46 MOV R1, R8 之前傳入的引數,_jclass .text:00003686 000 00 22 MOVS R2, #0 .text:00003688 000 00 24 MOVS R4, #0 .text:0000368A 000 D0 F8 A4 32 LDR.W R3, [R0,#0x2A4] libdvm.so:_Z20dvmDecodeIndirectRefP6ThreadP8_jobject+F55 .text:0000368E 000 48 46 MOV R0, R9 this指標 .text:00003690 000 98 47 BLX R3 libdvm.so:_Z20dvmDecodeIndirectRefP6ThreadP8_jobject+F55,返回輸入的key的記憶體
先來看看check介面:
check(_JNIEnv *,_jclass *,_jstring *) 00002814
check引數在剛進入就被儲存了,現在在00003680位置取出來,返回了我們輸入的key到R0中(看註釋)。
5DC4BEC0 31 32 33 34 35 36 00 40 10 00 00 00 4B 00 00 00 123456.@....K...
然後,又呼叫了一個子過程來處理key,我這裡先沒有跟入,直解F8,看了返回值
.text:00003792 000 16 F0 09 FB BL sub_19DA8 .text:00003796 000 01 46 MOV R1, R0 ; key .text:00003798 000 DF F8 A4 04 LDR.W R0, =(unk_20020 - 0x38D2)
65 4B 2F 30 36 38 71 52 00 00 00 00 C0 BE C4 5D eK/068qR
基本確認是加密函式,然後又把該結果和JPyjup3eCyJjlkV6DmSmGHQ=!!進行對比。
.text:000038CE 000 78 44 ADD R0, PC ; 儲存了JPyjup3eCyJjlkV6DmSmGHQ=!! .text:000038D0 .text:000038D0 AGAIN_18 ; CODE XREF: sub_2874+10D .text:000038D0 000 0A 5D LDRB R2, [R1,R4];R1儲存了eK/068qR 取出一個字元 .text:000038D2 000 03 5D LDRB R3, [R0,R4];取出一個字元 .text:000038D4 000 93 42 CMP R3, R2 .text:000038D6 000 40 F0 6B 80 BNE.W loc_39B0 ; jmp 3A1A .text:000038DA 000 01 34 ADDS R4, #1 .text:00003942 000 18 2C CMP R4, #0x18 .text:00003944 000 C4 D1 BNE AGAIN_18 .text:000039AC 000 01 20 MOVS R0, #1 .text:000039AE 000 3B E1 B loc_3C28 .text:00003A86 000 00 28 CMP R0, #0 .text:00003A88 000 00 F0 67 80 BEQ.W TAG_FAILED .text:00003C26 000 00 20 MOVS R0, #0
取出一個字元進行比較,不同則跳轉,相同R4加1,繼續比價直到超過0x18(也就是加密結果長度0x18),都相同了R0=1
看看不同時跳轉的程式碼,sub_27C8是一個類似魚strstr的程式碼,我本以為加密之後結果可以部分匹配也行,結果我錯了,作者坑人,因為這個sub_27C8就算返回1,也就是部分匹配成功了,也會進入00003C26,R0=0。
.text:00003A1A 000 78 44 ADD R0, PC ; result .text:00003A1C 000 FE F7 D4 FE BL sub_27C8 ; 在result中找key,找到匹配的一段,返回匹配位置,否則返回0
所以加密結果必須是0x18,和JPyjup3eCyJjlkV6DmSmGHQ=!!完全匹配(0x18位元組)
演算法
現在重新跟入加密子過程sub_19DA8,看看是怎麼個演算法。
.text:00019DA8 sub_19DA8 ; CODE XREF: sub_2874+F1E .text:00019DA8 .text:00019DA8 var_10 = -0x10 .text:00019DA8 .text:00019DA8 000 2D E9 F0 43 PUSH.W {R4-R9,LR} .text:00019DAC 01C 03 AF ADD R7, SP, #0xC .text:00019DAE 01C AD F5 81 6D SUB.W SP, SP, #0x408 .text:00019DB2 424 81 B0 SUB SP, SP, #4 .text:00019DB4 428 81 46 MOV R9, R0 .text:00019DB6 428 DF F8 5C 05 LDR.W R0, =(__stack_chk_guard_ptr - 0x19DBE) .text:00019DBA 428 78 44 ADD R0, PC ; __stack_chk_guard_ptr .text:00019DBC 428 00 68 LDR R0, [R0] ; __stack_chk_guard .text:00019DBE 428 00 68 LDR R0, [R0] .text:00019DC0 428 47 F8 10 0C STR.W R0, [R7,#var_10] .text:00019DC4 428 00 F0 AA FA BL sub_1A31C ; .text:00019DC4 ; 返回199319124851! .text:00019DC8 428 80 46 MOV R8, R0 .text:00019DCA 428 48 46 MOV R0, R9
先透過sub_1A31C子函式返回了一串字元199319124851!,演算法和生成JPyjup3eCyJjlkV6DmSmGHQ=!!字元類似,不再細說。
.text:00019F80 428 20 46 MOV R0, R4 ; size .text:00019F82 428 E7 F7 14 EC BLX malloc //分配記憶體來儲存第一次加密結果 .text:00019F86 428 21 46 MOV R1, R4 .text:00019F88 428 05 46 MOV R5, R0 text:00019FF0 428 E7 F7 E2 EB BLX __aeabi_memclr;清零 .text:00019FF4 428 6C 46 MOV R4, SP .text:00019FF6 428 08 21 MOVS R1, #8 ; a2 .text:00019FF8 428 20 46 MOV R0, R4 ; result .text:00019FFA 428 42 46 MOV R2, R8 ; str .text:0001A0C8 428 EB F7 8C FA BL sub_55E4 ; str = "199310124851!" .text:0001A0C8 ; a2 長度+2 .text:0001A0CC 428 20 46 MOV R0, R4 ; p .text:0001A0CE 428 31 46 MOV R1, R6 ; key_len .text:0001A0D0 428 4A 46 MOV R2, R9 ; key .text:0001A0D2 428 2B 46 MOV R3, R5 ; pKeyResult
然後分配了一段記憶體,用於儲存第一次加密的key結果。 呼叫sub_55E4,將199310124851!透過變換放入一個8位元組+0x100*4的陣列(初始化為0-0x100)空間,挺繞的,由於這個函式跟key沒有多大關係,所以咩必要細究是怎麼做的,可以直接將計算後記憶體dump出來用後面的逆運算(其實我沒用上)。
.text:0001A13A 428 EA F7 A0 FA BL sub_467E;第一次加密變換 .text:0001A13E 428 28 46 MOV R0, R5
然後sub_467E進行第一次加密變換,將key和前面的8位元組+0x100*4的陣列組隊的xor,細節直接看程式碼(完整的我會放idb):
v4 = p->unk_0; v5 = p->unk_4; if ( key_len >> 3 ) // 8 >> 3 = 1 { v6 = -(key_len >> 3); // -2 v7 = pKeyResult + 8 * (key_len >> 3); // 2*8 key1 = key; do { ++v6; v9 = (unsigned __int8)(v4 + 1); // 1 v10 = p->index[v9]; // p->Index[1] v11 = v5 + v10; // 0>Index[1] v12 = p->index[v11]; p->index[v9] = v12; p->index[v11] = v10; *(_BYTE *)pKeyResult = p->index[(unsigned __int8)(v10 + v12)] ^ *(_BYTE *)key1; v13 = (unsigned __int8)(v4 + 2); // 2 v14 = p->index[v13]; // p->Index[2] ...
這裡我沒有暫時沒有滲入理解,直接進入第二次加密運算。
.text:0001A222 428 01 44 ADD R1, R0 ;長度 .text:0001A224 428 28 46 MOV R0, R5 ;第一次加密結果 .text:0001A226 428 EB F7 69 FC BL sub_5AFC ;第二次加密 .text:0001A22A 428 3B 49 LDR R1, =(__stack_chk_guard_ptr - 0x1A300)
進入sub_5AFC,將key每3個位元組一組,進行<<8
拼接,也就是a1<<16+a2<<8+a3
,舉個例子0xaa,0xbb,0xcc=>0xaabbcc
然後拼接結果v15再左移, 如果是3個字元拼接的,這裡v16是3,v19=v15 << 8 * (3 - v16)
也就左移0,也就是不左移; 如果是兩個字元或者一個字元拼接的,這裡就需要左移8或者16位,說白了就是需要構成0x112233的結構。
然後v19進行4次移位,取aAbcdefghijklmn字元放入結果記憶體中。其實就是v19按6位進行分割(分別右移0x12,0xc,0x6,0x0,&03f),分割的值作為index,去aAbcdefghijklmn中對應字元,儲存。 如果v16<3
,也就是此次拼接沒有3個字元,這裡index=0x40
,也就是增加額外的"="用於結果。
if ( _R10 > 0 ) // len>0 { i = 0; p1 = p; do { if ( i >= _R10 ) { v16 = 0; v15 = 0; } else { ii = 0; v15 = 0; do { v15 = *(_BYTE *)(key + i + ii) | (v15 << 8);// // v15 = key[i] | 0<<8 // v15 = key[i+1] | v15<<8 // v15 = key[i+1] | v15<<8 v16 = ii + 1; if ( ii + 1 > 2 ) // 0, 1 break; v17 = i + ii++; } while ( v17 + 1 < _R10 ); i += v16; // v16 = 1, 2, 3 // i += v16, 下次計算使用的i } j = 0; v19 = v15 << 8 * (3 - v16); v20 = 0x12; do { if ( v16 < j ) index = 0x40; else index = (v19 >> v20) & 0x3F; v20 -= 6; *((_BYTE *)p1 + j++) = aAbcdefghijklmn[index]; } while ( j != 4 ); // 每4位元組 p1 = (char *)p1 + 4; } while ( i < _R10 ); }
逆向演算法
演算法大致明白了,結果又是JPyjup3eCyJjlkV6DmSmGHQ=(取了0x18位元組)。那麼將第二次加密進行求逆。 先找JPyjup3eCyJjlkV6DmSmGHQ=每位元組在'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='中的index。
k = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' r = 'JPyjup3eCyJjlkV6DmSmGHQ=' #//!!' idd = [] def get_index_in_k(c): for i in range(0, len(k)): c1 = k[i:i+1] if c1 == c: return i return -1 def cc(): j = 0 for i in range(0, len(r)): c1 = r[i: i+1] index = get_index_in_k(c1) idd.append(index) #儲存序號 print '%d: %c %d %x' % (i+1, c1, index, index )
結果是:
1: J 9 9 2: P 15 f 3: y 50 32 4: j 35 23 5: u 46 2e 6: p 41 29 7: 3 55 37 8: e 30 1e 9: C 2 2 10: y 50 32 11: J 9 9 12: j 35 23 13: l 37 25 14: k 36 24 15: V 21 15 16: 6 58 3a 17: D 3 3 18: m 38 26 19: S 18 12 20: m 38 26 21: G 6 6 22: H 7 7 23: Q 16 10 24: = 64 40
然後每4個index一組,來自於v19的4次右移,那麼反過來4個一組,左移相加就是v19
for i in range(0, len(idd), 4): a1 = idd[i] << 0x12 a2 = idd[i+1] << 0xc a3 = idd[i+2] << 0x6 a4 = 0 if idd[i+3] == 0x40: a4 = 0 else: a4 = idd[i+3] << 0 a = a1+ a2+a3+a4 rrr.append(a) print '%d: %x' % (i, a)
得到結果:
0: 24fca3 4: ba9dde 8: b2263 12: 96457a 16: e64a6 20: 1874
然後我們又知道v19其實是v15拼接的,所以拆開就得到v15(第一次加密結果),可以看到key長度應該是17。
24 fc a3 ba 9d de 0b 22 63 96 45 7a 0e 64 a6 18 74
然後接著求第一次加密的逆運算,看程式碼,好多啊,怎麼辦,難道要求逆,好難! 好吧,不裝了,其實不難,我們看前面說的第一次加密其實就是分組xor! xor好啊,xor好啊...我們知道xor兩次會將結果還原,想到了什麼?! 是的,既然我們拿到第一次加密結果,那讓他再和哪個8位元組+0x100*4的陣列再xor一次不久可以了,但是要重寫這個加密程式碼貌似也挺麻煩的,怎麼辦?!
這裡我是這麼做的,在除錯中,第一次加密前,將key的值(本來是輸入)修改為上面得到的第一次加密結果,然後開始第一次加密運算,這樣不就完美的完成了一次求逆嗎,哈哈!
具體操作,對1A13A下斷,輸入key(必須是17位,否則修改記憶體時可能會掛),確認,斷下來,此時r2就是key
5E127B20 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 1234567890123456 5E127B30 37 00 6D 5F 1B 00 00 00 00 00 00 00 00 00 00 00 7.m_............
然後在hex視窗,f2修改記憶體,輸入上面的24 fc...,然後f2確認修改。
5E127B20 24 FC A3 BA 9D DE 0B 22 63 96 45 7A 0E 64 A6 18 $. 5E127B30 74 A9 12 5E 0F 00 1F 00 FF FF 1F 00 0F 00 00 t..^..
然後f8。看看結果:
5E127B38 6D 61 64 65 62 79 65 72 69 63 6B 79 39 34 35 32 madebyericky9452 5E127B48 38 00 73 00 11 10 00 00 62 00 69 00 6C 00 69 00 8.s.....b.i.l.i.
答案就是:madebyericky94528
總結
- 花指令嚴重影響分析速度
- 對arm指令不熟
- 第一次完整分析一個apk,還進入了so,感覺不錯
- 最後差幾分鐘,沒提交到答案,我很不開心,嗚嗚~~~
只能寫下此篇wp,希望對大家有幫助
參考:
- 安卓APP動態除錯技術--以IDA為例
- http://luleimi.blog.163.com/blog/static/175219645201210922139272/
- http://blog.csdn.net/zhangmiaoping23/article/details/43445797
- http://www.cnblogs.com/liujiahi/archive/2011/03/22/2196401.html
- http://cncc.bingj.com/cache.aspx?q=arm++IT+EQ&d=4981012666125942&mkt=zh-CN&setlang=zh-CN&w=YEX3ioizXLDZGmlpVDBGFh_dhhHpfnYj
相關文章
- [原創]看雪CTF2017第二題lelfeiCM的writeup2019-02-25TF2
- 看雪安卓容器2019-01-14安卓
- 看雪安卓研修班,安卓逆向2020-12-21安卓
- 看雪·眾安 2021 KCTF 秋季賽 | 第六題設計思路及解析2021-12-01
- 阿里雲CTF逆向題“尤拉”詳細Writeup2024-04-30阿里
- 【詳解】換一個角度看Socket的資料讀寫2019-02-03
- [原創] Linux ptrace詳細分析系列(一)2021-02-02Linux
- 從模運算的角度看原碼和補碼2020-12-11
- 從另一個角度看拉普拉斯變換2020-10-12
- 從NewSQL的角度看Apache ShardingSphere2020-09-09SQLApache
- 看雪CTF.TSRC 2018 團隊賽 第六題 『追凶者也』 解題思路2018-12-23
- 看雪.紐盾 KCTF 2019 Q3 | 第六題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q2 | 第六題點評及解題思路2019-07-01
- 看雪·深信服 2021 KCTF 春季賽 | 第六題設計思路及解析2021-05-21
- ChatGPT 怎麼註冊最新詳細教程 新手小白一看就會2023-02-14ChatGPT
- 安卓第一個作品 檔案管理器 附原始碼帶詳細註釋2018-06-14安卓原始碼
- 從原始碼角度看ContentProvider2019-03-01原始碼IDE
- [譯] 從設計師的角度看 Redux2018-07-11Redux
- 從微服務的角度看,如何 Be Cloud Native2018-12-25微服務Cloud
- 從程式設計師的角度看 123062021-03-09程式設計師
- 從JDK原始碼角度看Long2019-03-01JDK原始碼
- 從JDK原始碼角度看Integer2019-03-04JDK原始碼
- 從JDK原始碼角度看Float2019-02-26JDK原始碼
- 從 JDK 原始碼角度看 Boolean2019-02-20JDK原始碼Boolean
- 藍橋杯-AB路線(詳細原創)2024-05-29
- 從經濟模型角度看比特幣和以太坊存在的問題2019-06-06模型比特幣
- 換個角度看原型鏈2019-04-15原型
- 從12個方面詳細解讀Pulsar的主題與訂閱2023-03-14
- 世界是平的嗎?——從不同角度看前端2019-02-24前端
- 從 generator 的角度看 Rust 非同步程式碼2022-02-25Rust非同步
- 【雜談】從實現角度看ChannelFuture2020-05-01
- 從巨集觀的角度看 Gradle 的工作過程2019-04-02Gradle
- 安卓開發第一步:安卓面試題2020-11-14安卓面試題
- SiamBAN詳細分析,一看就懂!2020-10-04
- 一個五年的後端開發老鳥給新手的 12 條忠告【原創】2019-06-14後端
- 『看雪眾測』這次的眾測物件是看雪!2018-03-21物件
- 從微服務治理的角度看RSocket,. Envoy和. Istio2019-01-02微服務
- 從區塊鏈的角度看企業協作2018-03-08區塊鏈