飄云閣(PYG官方論壇)

 找回密碼
 加入論壇

QQ登錄

只需一步,快速開始

掃一掃,訪問微社區

查看: 2776|回復: 34
打印 上一主題 下一主題

[Android] 戀戀APK之登錄時DES加密分析

[復制鏈接]
  • TA的每日心情
    慵懶
    2018-9-30 10:36
  • 簽到天數: 101 天

    [LV.6]常住居民II

    跳轉到指定樓層
    樓主
    發表于 2018-4-6 22:27:20 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
    本帖最后由 Bu棄 于 2018-4-6 22:27 編輯

    大家好呀,好久都沒在論壇發帖了(好像我一共也沒發幾個帖,尷尬)。因為最近對Android的逆向有興趣,所以去網上找了下教程。好像論壇也有。叫《無名Android逆向》,感興趣的可以去看看。
    此次發帖算是筆記吧。記錄自己怎么跟著視頻一步一步的分析,也算是一個作業。畢竟 看懂了 !=  你會了!
    教程第一課是分析一款戀戀的交友APK。具體是分析它登錄時是如何把信息加密,然后發送到服務器的。廢話不多說,開搞吧。我盡量寫詳細點,以下有些思路和內容是視頻里面的,我借鑒下,希望不會被噴。另外有些詞把握得不是很準。有些地方或許說法有些錯誤,希望大家多多指正。畢竟我也是個小白,正在學習ing。。。。
       一、需要的工具
           JEB、IDA(分析so文件)、Fiddler(抓包)、任意一款Android模擬器/手機
       二、開整吧
           1.首先使用抓包工具Fiddler,抓下此APK登錄時發送的數據包。設置如下。
                                 

             
                        2. 設置模擬器網絡
                              
                      3.登錄后的封包(用戶名為123456789  密碼為[email protected]#$%^)
                            
                      4.從上面的封包中,我們看到了請求的url(POST后面跟著的那串),還有加密后的用戶名和密碼。下面我們就使用url字符串(http://mob.imlianai.com/call.do?cmd=mobileUser.login )搜索,定位到關鍵點。
                      5.打開JEB,把APK拖入,然后切換到字符串。搜索上面抓到的url(http://mob.imlianai.com/call.do?cmd=mobileUser.login )其實我們這里只需要搜索mobileUser.login 就行了。
                      6.雙擊找到的字符串,來到匯編界面。
                         
                    7.點擊任意一行,按TAB鍵,到java代碼界面
                      
                   8.這里是switch語句。主要應該是根據參數來選擇操作的連接地址。我們往下找找。看看有沒有什么我們需要的。
                   9.在下面很多地方,我們都發現了類似這樣的代碼:
    [Java] 純文本查看 復制代碼
               JSONObject v1_1 = new JSONObject();   //創建一個構建JSON字符串的對象
                v1_1.put("xxxx", xxxx);   //往里面加入key/value形式的鍵值對
                v1_1.put("xxxx", xxxx);
                v0 = com.a.a.a.f.a.a(v1_1.toString()).getBytes(); //com.a.a.a.f.a.a(v1_1.toString()) 就是具體的加密邏輯了
    

                   10.com.a.a.a.f.a.a()這個方法就是具體的加密邏輯了。接下來我們著重分析下。我們雙擊這個方法,進入到方法實現位置,具體代碼如下:
    [Java] 純文本查看 復制代碼
    public class a {
        private static final byte[] a;
        private static final IvParameterSpec b;
        private static String c;
        private static Key d;
    
        static {
            a.a = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
            a.b = new IvParameterSpec(a.a);
            a.c = "hqi/FjjcBxA=";
            a.d = null;
        }
    
        public static String a(String arg1) { //這就是加密的方法體
            return Jni.getInstance().encryptString(arg1);
        }
    }
    
                                     
                    11.觀察上面的代碼,發現其又調用了Jni中的encryptString方法。把Json字符串傳遞過去。我們照樣雙擊這個方法。得到如下代碼
    [Java] 純文本查看 復制代碼
        public String encryptString(String arg9) {
            String v0_1;
            if(arg9 == null || arg9.length() == 0) { // 如果什么都沒輸入的話,就把v0_1置為空串,返回。否則則進行加密操作
                v0_1 = "";
            }
            else {
                String v2 = this.encode(arg9);  //調用本類中的encode方法。這個方法主要是把字符串的每個字符的字節碼轉換成16進制形式。例如 ‘a’ 的ASCII是[color=#333333]97,經過這個方法后,就變成了61[/color]
                int v3 = v2.length();
                StringBuffer v4 = new StringBuffer();
                if(v3 < 500) {    //如果v2的長度小于500.則執行下面的邏輯。否則執行else中的。我們現在就只看下面的邏輯,先不看else中的邏輯
                    v4.append(this.getEncryptString(v2, true)); //調用this.getEncryptString(v2, true)加密。具體細節我們后面說
                }
                else {
                    int v0;
                    for(v0 = 1; v0 < v3 / 500 + 1; ++v0) {
                        if(v3 % 500 == 0 && v0 == v3 / 500) {
                            v4.append(this.getEncryptString(v2.substring((v0 - 1) * 500, v0 * 500), true));
                            break;
                        }
    
                        v4.append(this.getEncryptString(v2.substring((v0 - 1) * 500, v0 * 500), false));
                    }
    
                    if(v3 % 500 == 0) {
                        goto label_15;
                    }
    
                    v4.append(this.getEncryptString(v2.substring((v0 - 1) * 500, v3), true));
                }
    
            label_15:
                v0_1 = DESencryption.getEncString(v4.toString(), this.getEncryptString("a", true).substring(0, 8));  //使用DES對加密后的數據再進行加密。DES的key是一個固定的值。因為用到了getEncryptString()方法,所以等分析完這個方法我們再提
            }
            return v0_1;
        }
    
    

                三、分析this.getEncryptString(v2, true)                  

                       1.getEncryptString代碼如下:
    [Java] 純文本查看 復制代碼
    private native String getEncryptString(String arg1, boolean arg2) {}  //native為原生態方法,一般是調用C++、C語言代碼
    

                       2.上面代碼是通過JNI調用C++或者C語言代碼。有關于這方面的知識。我也是小白,所以也不怎么懂。大家可以去百度下。既然這里是通過JNI調用,那么我們怎么知道他調用的是哪個so文件呢?在這個類上面的static靜態代碼塊中聲明出來了。如下:
    [Java] 純文本查看 復制代碼
        static {
            Jni.hexString = "0123456789ABCDEF";  //這個先不管。這個是用于上面說的encode方法把字符串的每個字符的字節碼轉換成16進制形式的。
            System.loadLibrary("jni"); //這個就是加載so的庫了。so的名字是libjni.so。字符串jni前面再加上lib。
        }
    
    

                        3.在APK中的\lib\armeabi下有個libjni.so文件。我們現在把它拖入ida中。
                        4.在IDA載入完成后,進入Exports中。如果沒有Exports視圖,則需要進行如下操作就行了。Exports中會顯示所有的導出函數

                            
                                     
                        5.因為供Java調用的API有個明顯的特征:Java_包名_方法名。我們Ctrl+F搜索下Java開頭的。很幸運,這個里面只有一個。而且發現方法名也是和Java中聲明的native方法名是一樣的。
                                  
                         6.雙擊進去吧。就進入了匯編頁面。匯編代碼頁面我就不貼了。直接按F5變成C代碼,我們再分析吧。總體分析如下。
    [C++] 純文本查看 復制代碼
    int __fastcall Java_com_jni_Jni_getEncryptString(_JNIEnv *a1, JNIInvokeInterface *a2, int inputStr, int inputBool) //你們一點進去可能方法定義不是這樣的。你只需要導入下jni.h就行。具體操作,在第7點中給出
    {
      _JNIEnv *v4; // [email protected]
      int str; // [email protected]
      int v6; // [email protected]
      const char *v7; // [email protected]
      size_t v8; // [email protected]
      _JNIEnv *v9; // [email protected]
      char *v10; // [email protected]
      jstring (__cdecl *v11)(JNIEnv *, const char *); // [email protected]
      int result; // [email protected]
      char *s; // [sp+0h] [bp-828h]@1
      int v14; // [sp+4h] [bp-824h]@1
      char dest; // [sp+Ch] [bp-81Ch]@3
      int v16; // [sp+80Ch] [bp-1Ch]@1
    
      v4 = a1;
      str = inputStr;
      v14 = inputBool;
      v16 = _stack_chk_guard;
      g_env = a1;
      s = (char *)initAddStr();                     // 初始化一個字符串。待會我們再分析
      v7 = (const char *)jstringTostring((int)v4, str, v6);// 調用JNI的方法,把Java中的String變成C中的char *
      v8 = strlen(s);                               // 求出初始化字符串的長度
      if ( strlen(v7) + v8 <= 0x7FF )               // 轉成C的inputStr的長度和s的長度<0x7ff(2047)如果小于則拼上s.否則不拼
      {
        memset(&dest, 0, 0x7FFu);                   // 往dest這個地址填充0x7FF個0
        strcat(&dest, v7);                          // 這里是把v7的值,也就是inputStr轉成Char的值賦給dest
        if ( v14 )                                  // 第2個傳參也就是inputBool為true的時候,就在后面跟上初始值s。否則不跟
          strcat(&dest, s);
        v9 = v4;                                    // v9 = JNIEnv
        v10 = &dest;
        v11 = v4->functions->NewStringUTF;          // v11 =  NewStringUTF:把C的char* 轉換成Java中的String
      }
      else
      {
        v9 = v4;
        v10 = (char *)v7;
        v11 = v4->functions->NewStringUTF;          // v11 =  NewStringUTF:把C的char* 轉換成Java中的String
      }
      result = ((int (__fastcall *)(_JNIEnv *, char *))v11)(v9, v10);// 調用NewStringUTF方法,把v10轉換成String
      if ( v16 != _stack_chk_guard )
        _stack_chk_fail(result);
      return result;
    }
    

                       7.導入jni.h文件。此文件在java_home/jdk/lib下。當然你直接導會報錯。我們需要修改一下。具體修改方法百度下吧。我待會會提供個。導入操作如下:
                         
                     8.現在我們逐步來分析下。首先看方法聲明中的參數。int __fastcall Java_com_jni_Jni_getEncryptString(_JNIEnv *a1, JNIInvokeInterface *a2, int inputStr, int inputBool)。參數列表函數如下:                              1)_JNIEnv * :這個參數是固定的。傳入的是Dalvik虛擬的函數表。具體可參考   https://www.cnblogs.com/gavanwanggw/p/6907893.html
                                  2)JNInvokeInterface * :這個參數也是固定的。傳入的是正在調用這個方法的類。
                                  3)int :這個是函數的參數,也就是java傳遞過來的參數,因為在Java中第一個參數傳的是String,而C中沒有String。所以這個應該是char*指針。
                                  4)int:這個同樣是Java傳遞過來的參數。在Java中傳的是boolean。C中沒有。所以用int代替。
                    9.分析 s = (char *)initAddStr(); 。我們點開這個函數。他內部如下
    [C++] 純文本查看 復制代碼
    int initAddStr()
    {
      int v0; // [email protected]
      int v1; // [email protected]
    
      if ( !isInit ) //這里是如果初始化一次了,就不需要再執行了。也就是這里只會執行一次。
      {
        v0 = initInflect((int)jniStr);  //在第10點中分析
        key = jstringTostring((int)g_env, v0, v1); //調用方法,把java的String變成C語言中的char*
        isInit = 1;
      }
      return key;
    }
    
    

                     10.分析initInflect((int)jniStr)。蠶食為jniStr。那么jniStr是什么呢?我們雙擊下jniStr。發現如下:
                         
                           那么,jniStr = “/key=i im lianai” 。接下來我們看看initInflect方法的內部吧
    [C++] 純文本查看 復制代碼
    int __fastcall initInflect(int a1)
    {
      int *v1; // [email protected]
      int v2; // [email protected]
      bool v3; // [email protected]
      int v4; // [email protected]
      int v5; // [email protected]
      int (__fastcall *v6)(int *, const char *); // [email protected]
      const char *v7; // [email protected]
      int v8; // [email protected]
      int v9; // [email protected]
      int v10; // [email protected]
      int v12; // [sp+Ch] [bp-1Ch]@1
    
      v12 = a1;
      v1 = g_env;
      v2 = (*(int (__fastcall **)(_DWORD *, const char *))(*g_env + 24))(g_env, "com/Reflect");
      v4 = v2;
      v3 = v2 == 0;
      v5 = *v1;
      if ( v3 )
      {
        v6 = *(int (__fastcall **)(int *, const char *))(v5 + 668);
        v7 = "jclass";
        return v6(v1, v7);
      }
      v8 = (*(int (__fastcall **)(int *, int, const char *, const char *))(v5 + 452))(
             v1,
             v4,
             "func",
             "(ILjava/lang/String;)Ljava/lang/String;");
      v9 = *v1;
      v10 = v8;
      if ( !v8 )
      {
        v6 = *(int (__fastcall **)(int *, const char *))(v9 + 668);
        v7 = "method";
        return v6(v1, v7);
      }
      (*(void (__fastcall **)(int *, int))(v9 + 668))(v1, v12);
      return _JNIEnv::CallStaticObjectMethod(v1, v4, v10, 10);
    }
    
    

                          好吧。這里我就不一行一行分析了,因為我也不會。但是大致意思是看懂了。意識是調用com.Reflect類中的func方法。參數就是“/key=i im lianai”我們接下來回到JEB看吧。
                     11. 代碼如下:
    [Java] 純文本查看 復制代碼
    package com;
    
    public class Reflect {
        private static String hexString;
        public static String tmp;
    
        static {
            Reflect.tmp = " alien";
            Reflect.hexString = "0123456789ABCDEF";
        }
    
        public Reflect() {
            super();
        }
    
        private static String encode(String arg5) {//arg5 = “/key=i im lianai alien”   這里就是上面的把字符串中的字母變為16進制的代碼了。我就不一行一行讀了
            byte[] v1 = arg5.getBytes();
            StringBuilder v2 = new StringBuilder(v1.length * 2);
            int v0;
            for(v0 = 0; v0 < v1.length; ++v0) {
                v2.append(Reflect.hexString.charAt((v1[v0] & 240) >> 4));
                v2.append(Reflect.hexString.charAt((v1[v0] & 15) >> 0));
            }
    
            return v2.toString();
        }
    
        public static String func(int arg2, String arg3) {//這個就是IDA中調用的方法
            return Reflect.encode(String.valueOf(arg3) + Reflect.tmp);  //這里調用了此類中的encode方法,傳入的是我們傳遞過來的參數“/key=i im lianai” 加上此類提供的一個靜態字符串" alien"<注意前面有個空格>,綜合起來也就是“/key=i im lianai alien”
        }
    }
    
    

                         12.總結下initInflect((int)jniStr)的作用:傳入“/key=i im lianai alien”把其中的每個字母轉成16進制字符串。最后的結果是“2F6B65793D6920696D206C69616E616920616C69656E” 這個是固定的。
                         13.其他的都分析完了。現在我們總結下getEncryptString()這個方法.這個方法說的就是把傳入的字符串,如果長度小于0x7FF的就在后面拼上“2F6B65793D6920696D206C69616E616920616C69656E”。
                         14.so部分到此結束,這個so挺容易。做的事也容易。就是拼下字符串。然后我們回到JEB中的java代碼中。看類Jni的encryptString的最后。
    [Java] 純文本查看 復制代碼
     v0_1 = DESencryption.getEncString(v4.toString(), this.getEncryptString("a", true).substring0, 8));   //這個就是最終的結果了。v4就是經過so處理過的字符串,第二個參數是“a”去讓so處理,在截取他的0~8位作為DES加密的key。加密后就是結果了。
    
    

                        15.DES算法我不懂。所以里面的就不看了。我們可以直接把這些代碼拷出來到Eclipse中。然后使用Java代碼驗證我們的分析是否正確。Java代碼如下:
    [Java] 純文本查看 復制代碼
    public class Test01 {
        public static void main(String[] args) throws Exception {
           String jsonHex = getHexString("{\"pwd\":\"[email protected]#$%^\",\"number\":\"1234567890\"}"); //登錄時的JSON字符串
           String initStringHex = getHexString("/key=i im lianai alien");//固定的字符串
           String soString = jsonHex+initStringHex;
           String ret = DESencryption.getEncString(soString, "a2F6B657");
           System.out.println(ret);
        }
    
        /**
         * 這里是復制JEB中的把字符串中每個字符轉換成16進制字符串
         * @param str
         * @return
         * @throws Exception
         */
        public static String getHexString(String str) throws Exception {
            byte[] v1 = str.getBytes("UTF-8");
            String hexString = "0123456789ABCDEF";   
            StringBuilder v2 = new StringBuilder(v1.length * 2);
            int v0;
            for(v0 = 0; v0 < v1.length; ++v0) {
                v2.append(hexString.charAt((v1[v0] & 240) >> 4));
                v2.append(hexString.charAt((v1[v0] & 15) >> 0));
            }
           return v2.toString();
        }
    }
    
    

                     16.最后結果如下:
                
                     
                    17.總結下流程:
                            1)用戶輸入用戶名和密碼。把他么封裝成JSON字符串
                            2)把JSON字符串的每個字符,變成16進制形式
                            3)調用so庫。拼接固定字符串"2F6B65793D6920696D206C69616E616920616C69656E"
                            4)調用DES加密。密碼是固定的"a2F6B6579",要加密的數據就是第3)步拼接出來的。
                            5)我也是小白,各位親噴。有地方說錯的,歡迎批評指正。
                            6)這個能吐槽下論壇的這個帖子的富文本編輯器么?為嘛不支持TAB鍵。。。。還有排版好蛋疼。。。。
                            6)收工,洗澡睡覺。謝謝大家。
                   考慮到有些小伙伴需要《無名Android逆向》所以這里就直接給出了。按道理不會違規吧。如果有違規,麻煩版主大人@下我,我改正。
                    《無名Android逆向》鏈接: https://pan.baidu.com/s/1yb-1OJWJPkDFjJQZ5Fh-4w 密碼: 39y5


                   第一課的課件里面也有。有的不需要這個課程,我也直接給下課件的分享吧。
                    課件 鏈接: https://pan.baidu.com/s/1N-GYBBS4x9hZeKu_T3pjPg 密碼: phgi






    評分

    參與人數 5威望 +15 飄云幣 +18 收起 理由
    nociabx + 1 PYG有你更精彩!
    losers + 2 很給力!
    Rooking + 8 很給力!
    gfjykldd + 2 PYG有你更精彩!
    wai1216 + 10 + 10 贊一個!

    查看全部評分

    本帖被以下淘專輯推薦:

    分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友 微信微信
    收藏收藏10 轉播轉播 分享分享 分享淘帖1 頂 踩 友情贊助 微信分享
  • TA的每日心情
    慵懶
    2018-9-30 10:36
  • 簽到天數: 101 天

    [LV.6]常住居民II

    沙發
     樓主| 發表于 2018-4-6 22:29:34 | 只看該作者
    沙發自己來
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    2016-6-16 14:07
  • 簽到天數: 10 天

    [LV.3]偶爾看看II

    藤椅
    發表于 2018-4-6 23:26:33 | 只看該作者
    666 就喜歡看協議分析 精了!
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    4 天前
  • 簽到天數: 1132 天

    [LV.10]以壇為家III

    板凳
    發表于 2018-4-7 13:55:34 | 只看該作者
    小白前來學習了
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    12 小時前
  • 簽到天數: 1068 天

    [LV.10]以壇為家III

    報紙
    發表于 2018-4-7 16:56:39 | 只看該作者
    謝謝大神提供這么好的學習資料好好學習天天向上
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    5 天前
  • 簽到天數: 229 天

    [LV.7]常住居民III

    地板
    發表于 2018-4-7 19:59:36 | 只看該作者

    厲害啊樓主,謝謝樓主分享
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    開心
    5 天前
  • 簽到天數: 229 天

    [LV.7]常住居民III

    7#
    發表于 2018-4-7 20:00:46 | 只看該作者

    謝謝大神提供這么好的學習資料
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    奮斗
    2018-12-1 19:39
  • 簽到天數: 277 天

    [LV.8]以壇為家I

    8#
    發表于 2018-4-7 22:51:21 | 只看該作者
    圍觀大神 受益匪淺
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情

    2018-12-8 20:47
  • 簽到天數: 80 天

    [LV.6]常住居民II

    9#
    發表于 2018-4-9 17:02:49 | 只看該作者
    Bu棄師傅厲害呀,研究加密算法了。

    點評

    我是一渣渣,算法部分都跳過了  詳情 回復 發表于 2018-4-9 22:40
    回復 支持 反對

    使用道具 舉報

  • TA的每日心情
    慵懶
    2018-9-30 10:36
  • 簽到天數: 101 天

    [LV.6]常住居民II

    10#
     樓主| 發表于 2018-4-9 22:40:07 | 只看該作者
    666888tzq 發表于 2018-4-9 17:02
    Bu棄師傅厲害呀,研究加密算法了。

    我是一渣渣,算法部分都跳過了
    回復 支持 反對

    使用道具 舉報

    您需要登錄后才可以回帖 登錄 | 加入論壇

    本版積分規則

    關閉

    站長推薦上一條 /1 下一條

    快速回復 返回頂部 返回列表
    上海快3开奖结果查询