看雪CTF2017第六題 Ericky-apk writeup(安卓so逆向)

概述

題目入口:http://ctf.pediy.com/game-fight-36.htm

本題是安卓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+p->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

轉載請註明出處:https://anhkgg.github.io/kxctf2017-writeup6

參考:
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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章