[原創]看雪CTF2017第六題 Ericky-apk詳細writeup(從一個安卓新手的角度)

anhkgg發表於2019-02-25

概述

本題是安卓cm,目測肯定需要除錯so。

準備工具:

  1. ApkIde改之理(其他類似的也行,能夠反編譯apk,得到jar,so等)
  2. IDA(用於除錯so),需要6.x以上,忘了是x幾,我用的6.6
  3. 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動態除錯技術,然後用上了,很好用!

跟著走

下面開始照著做。

  1. 連上手機(或者模擬器),使用adb devices看看成功連上沒有
  2. adb push ../dbgsrv/android_server /sdcard/sv,教程是直接放入/data/data,一般許可權不夠
  3. 然後進入shell,adb shell,輸入su,獲得root許可權,然後cp /sdcard/sv /data/data/sv
  4. 修改sv許可權,chmod 777 /data/data/sv
  5. 執行sv,/data/data/sv,預設監聽到23946埠,Listening on port #23946。這步有個細節,不能直接adb shell /data/data/sv,這樣許可權不夠,無法讀取到程式資訊,需要adb shell; su; /data/data/sv
  6. 再開一個cmd,然後執行adb forward tcp:23946 tcp:23946
  7. 執行一個idaq.exe,然後在選單debugger->attach->remote Armlinux/android debugger,輸入localhost, 23946,ok
  8. 彈出程式框,按下Alt+T,輸入chen,搜尋到1808 [32] com.miss.rfchen,ok
  9. 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

特徵:

  1. 每跳轉一個分支,基本都要一段花(記為A段),就是從上面程式碼中註釋開始的問題
  2. 進行幾個跳轉後,到了結束位置,跳入有效程式碼
  3. 有效程式碼開頭一般也有加一段花(記為B段)
  4. 在A段話指令中,指令地址是向下增長的,也就是A開始往下拉一段,就能找到結束位置
  5. 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

總結

  1. 花指令嚴重影響分析速度
  2. 對arm指令不熟
  3. 第一次完整分析一個apk,還進入了so,感覺不錯
  4. 最後差幾分鐘,沒提交到答案,我很不開心,嗚嗚~~~

只能寫下此篇wp,希望對大家有幫助

參考:

  1. 安卓APP動態除錯技術--以IDA為例
  2. http://luleimi.blog.163.com/blog/static/175219645201210922139272/
  3. http://blog.csdn.net/zhangmiaoping23/article/details/43445797
  4. http://www.cnblogs.com/liujiahi/archive/2011/03/22/2196401.html
  5. http://cncc.bingj.com/cache.aspx?q=arm++IT+EQ&d=4981012666125942&mkt=zh-CN&setlang=zh-CN&w=YEX3ioizXLDZGmlpVDBGFh_dhhHpfnYj




[推薦]看雪企服平臺,提供安全分析、定製專案開發、APP等級保護、滲透測試等安全服務!

上傳的附件:

相關文章