安卓逆向 -- 某APP登录算法分析

admin 2022年4月18日08:48:47评论163 views字数 13565阅读45分13秒阅读模式

原文作者:旧年白白白

原文地址:https://www.52pojie.cn/thread-1622354-1-1.html


次分析只作学习交流所用,请勿用于非法用途,如若侵权,请联系删帖,谢谢!

环境

1、主机:win10
2、手机:Pixel 4 ,Android 10
3、APP版本:V5.54.0

工具

IDA、JADX、Frida、Charles

登录逆向分析

RegisterNatives

安卓逆向 -- 某APP登录算法分析


安卓逆向 -- 某APP登录算法分析


Request Params

首先抓包看一下登录包:
安卓逆向 -- 某APP登录算法分析


看一下登录参数:
安卓逆向 -- 某APP登录算法分析

verifydata{"params": "5F1D4B282F5E89548F98FC30853F4F9289D874AA82EDB0ED82330B07CF5D208ECAAF446BAEA4D4848BB7515573123514EFDEA8E6C1000FEF8F30A9901382B7119822C74B9C91394F9DDFF8B239E241F9","clienttime_ms": "1647312358378","support_face_verify": "1","dfid": "-","dev": "Pixel%204","plat": "1","pk": "7C9E722B7BCCE5B56BAB9D42902B2A4F238BAA40EACCCEECACE72C3EE46A85B1B833F7800E6D2B968EE809303485376180BD8494EBA84F006DBD050E1F876B3713DE64AFADDA4F51FCC522C6DAC0AFFC8A04AABAF47F95FE6D3F2FB6726A16FC4664AB66CE065E3FCFD7D0FACD3DCA05AA96D74DD96EC74DBF54CA72D088E42C","t1": "bfaf46951c6a5c58d4bf618c517354de","support_verify": "1","support_multi": "1","t2": "c397de1fd7e436639fa90f367c0b47d290831b64f19d803fc09a91ee47bcc1648e5a87f1f934ed09cfb7046dad52253cf564684d5bdef1d67fc91c74d6245335237a78074f157641a1ddfb5ca4a66c5d72855feae18282b981d896c6f170cb54ca7c21aad3811d69c80a73ad3482c340","key": "8d73d8b5da06bad3b466d864812d488b","username": "130*****666"}


params参数解析

通过JADX搜索“params”字符串定位到位置:

this.j.put("params", a.c(jSONObject.toString(), this.g));

进入c方法内可以看到:

public static String c(String str, String str2) throws Exception {        return a(str, "utf-8", ao.a(str2).substring(0, 32), ao.a(str2).substring(16, 32));    }

通过objection hook该方法内的a方法,得到如下结果:

(agent) [302556] Called com.xxx.fanxing.allinone.common.utils.a.a(java.lang.String, java.lang.String, java.lang.String, java.lang.String)(agent) [302556] Arguments com.xxx.fanxing.allinone.common.utils.a.a({"username":"130*****666","clienttime_ms":"1647313862199","pwd":"557998555"}, utf-8, 0dd6da40aea47d05b5f6612596fd2e7f, b5f6612596fd2e7f)(agent) [793424] Backtrace:com.xxx.fanxing.allinone.common.utils.a.a(Native Method)com.xxx.fanxing.allinone.common.utils.a.a(SourceFile:117)com.xxx.fanxing.allinone.common.utils.a.a(Native Method)com.xxx.fanxing.allinone.common.utils.a.c(SourceFile:106)com.xxx.fanxing.core.protocol.g.e.c(SourceFile:85)com.xxx.fanxing.core.protocol.g.e.c(Native Method)com.xxx.fanxing.core.protocol.g.a.d(SourceFile:76)com.xxx.fanxing.core.modul.user.login.c.a(SourceFile:50)com.xxx.fanxing.core.modul.user.login.f.a(SourceFile:228)com.xxx.fanxing.core.modul.user.ui.a.a(SourceFile:577)com.xxx.fanxing.core.modul.user.ui.a.j(SourceFile:554)com.xxx.fanxing.core.modul.user.ui.a.i(SourceFile:520)com.xxx.fanxing.core.modul.user.ui.a.onClick(SourceFile:417)android.view.View.performClick(View.java:7259)android.view.View.performClickInternal(View.java:7236)android.view.View.access$3600(View.java:801) android.view.View$PerformClick.run(View.java:27892)android.os.Handler.handleCallback(Handler.java:883)android.os.Handler.dispatchMessage(Handler.java:100)android.os.Looper.loop(Looper.java:214)android.app.ActivityThread.main(ActivityThread.java:7356)java.lang.reflect.Method.invoke(Native Method)com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

这样我们可以得到a.c(jSONObject.toString(), this.g)中两个参数的来源,

其中第一个参数为JSON结构:

{"username":"130*****666","clienttime_ms":"1647313862199","pwd":"557998555"},

第二个参数为AES的KEY,最终将KEY拆分为(0,32)和(16,32)具体生成方式如下:

this.g=com.xxx.fanxing.allinone.common.utils.a.a(com.xxx.fanxing.allinone.common.constant.c.hz() ? 128 : 64);


a方法如下:

 复制代码 隐藏代码
public static String a(int i) {
        try {
            KeyGenerator instance = KeyGenerator.getInstance("AES");
            instance.init(i);
            return a(instance.generateKey().getEncoded());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

public static String a(byte[] bArr) {
        StringBuffer stringBuffer = new StringBuffer();
        for (byte b : bArr) {
            String hexString = Integer.toHexString(b & 255);
            if (hexString.length() == 1) {
                hexString = '0' + hexString;
            }
            stringBuffer.append(hexString.toUpperCase());
        }
        return stringBuffer.toString();
    }

public static String c(String str, String str2) throws Exception {
        return a(str, "utf-8", ao.a(str2).substring(0, 32), ao.a(str2).substring(16, 32));
    }

public static String a(String str, String str2, String str3, String str4) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(str3.getBytes(str2), "AES");
        Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
        instance.init(1, secretKeySpec, new IvParameterSpec(str4.getBytes()));
        return a(instance.doFinal(str.getBytes(str2)));
    }

dfid参数解析

通过搜索字符串即可定位到如下函数:

 复制代码 隐藏代码
public String e() {
        String a2 = com.xxx.fanxing.core.common.fingerprint.a.a();
        return TextUtils.isEmpty(a2) ? "-" : a2;
    }

a2 -> share_data -> device_fingerprint
通过sharedPreferences读取了文件名为share_data里的device_fingerprint字段

pk参数解析

 复制代码 隐藏代码
hashMap.put("pk", com.xxx.fanxing.core.protocol.g.a.a(String.valueOf(currentTimeMillis), a2));

public static String a(String str, String str2) throws Exception {
        HashMap hashMap = new HashMap();
        hashMap.put("clienttime_ms", str);
        hashMap.put(ao.M, str2);
        return com.xxx.common.player.kugouplayer.a.d(hashMap);
    }

str为时间戳,str2为AES随机密钥
组成形式如下:

{"clienttime_ms":"1647829236357","key":"E96E510C296711ECEA6C85CAF6152F4D"}

最终调用native层的cc::i加密方法
安卓逆向 -- 某APP登录算法分析

PUBKEY:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD2DT4odzkDd7hMlZ7djdZQH12j38nKxriINW1MGjMry3tXheya113xwmbBOwN0GA4zTwKFauFJRzcsD0nDFq1eaatcFKeDF25R4dnQRX+4BdTwFVS8lIb8nJMluSBwK+i4Z3VF+gfZ0AqQOXda6lJ4jPBt9Ep7VXEAHXUDn9JM8wIDAQAB


Python版RSA_NOPADDING加密方法实现:

 复制代码 隐藏代码
import rsa
import base64
from Crypto.PublicKey import RSA

def zfillStrToBin(s):
    b = bytes(s.encode())
    for i in range(128 - len(b)):
        b += b''
    print(len(b))
    return b

class RsaNopadding:

    def __init__(self, key):
        self.pubkey = RSA.importKey(base64.b64decode(key))

    def encrypt(self, message):
        kLen = rsa.common.byte_size(self.pubkey.n)
        msg = zfillStrToBin(message)
        _b = rsa.transform.bytes2int(msg)
        _i = rsa.core.encrypt_int(_b, self.pubkey.e, self.pubkey.n)
        result = rsa.transform.int2bytes(_i, kLen)
        return result.hex().upper()

message = '{"clienttime_ms":"1647829236357","key":"E96E510C296711ECEA6C85CAF6152F4D"}'
msg = RsaNopadding(
    "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD2DT4odzkDd7hMlZ7djdZQH12j38nKxriINW1MGjMry3tXheya113xwmbBOwN0GA4zTwKFauFJRzcsD0nDFq1eaatcFKeDF25R4dnQRX+4BdTwFVS8lIb8nJMluSBwK+i4Z3VF+gfZ0AqQOXda6lJ4jPBt9Ep7VXEAHXUDn9JM8wIDAQAB")
print(msg.encrypt(message))

t1参数解析

 复制代码 隐藏代码
String t1 = com.xxx.common.player.kugouplayer.a.b(null);

最终调用native层的cc::d方法
安卓逆向 -- 某APP登录算法分析


这里只分析f4函数内关键步骤部分:

 复制代码 隐藏代码
int __fastcall f4(int a1)
{
LABEL_44:                                       // 走这
  v76[0] = (int)v76;
  v76[1] = (int)v76;
  v76[2] = 0;
  v77 = 0;
  v78 = 0;
  v79 = 0;
  std::string::__init((int)&v77, (int)&aEia[2], 1);
  v34 = v77;
  v35 = v78;
  v77 = 0;
  v78 = 0;
  v103 = v34;
  v104 = v35;
  v105 = v79;
  v79 = 0;
  std::string::basic_string((int)v106, (int)&v66);
  std::list<std::pair<std::string,std::string>>::push_back((int)v76, (int)&v103);
  std::string::~string((int)v106);
  std::string::~string((int)&v103);
  std::string::~string((int)&v77);
  v80 = 0;
  v81 = 0;
  v82 = 0;
  std::string::__init((int)&v80, (int)"b", 1);
  f5(&v83);                                     // daytime
  v36 = v80;
  v37 = v81;
  v38 = v82;
  v80 = 0;
  v81 = 0;
  v82 = 0;
  v103 = v36;
  v104 = v37;
  v105 = v38;
  v39 = v83;
  v40 = v84;
  v41 = v85;
  v83 = 0;
  v84 = 0;
  v85 = 0;
  v106[0] = v39;
  v106[1] = v40;
  v106[2] = v41;
  std::list<std::pair<std::string,std::string>>::push_back((int)v76, (int)&v103);
  std::string::~string((int)v106);
  std::string::~string((int)&v103);
  std::string::~string((int)&v83);
  std::string::~string((int)&v80);
  f9((int)v86, (int)v76);
  v87 = 0;
  v42 = v107;
  v88 = 0;
  v89 = 0;
  v95[0] = 0;
  v95[1] = 0;
  v95[2] = 0;
  v43 = aBdeaed243193ce;                        // v42=bdeaed243193ce1i#~DC<M[g
  do
  {
    v44 = *(_DWORD *)v43;
    v43 += 8;
    v45 = *((_DWORD *)v43 - 1);
    *(_DWORD *)v42 = v44;                       // 整个循环将bdeaed243193ce1i#~DC<M[g扩展到bdeaed243193ce1i#~DC<M[g..=2..~4
    *((_DWORD *)v42 + 1) = v45;
    v42 += 8;
  }
  while ( v43 != (const char *)&unk_D90E4 );
  v103 = 0;
  v104 = 0;
  v105 = 0;
  std::string::__init((int)&v103, (int)v107, 32);// v105=v109=bdeaed243193ce1i#~DC<M[g..=2..~4
                                                // 62 64 65 61 65 64 32 34 33 31 39 33 63 65 31 69
                                                // 23 7E 44 43 3C 4D 5B 67 7F 0E 3D 32 01 2E 7E 34
  v46 = (unsigned __int8)v103;
  if ( (v103 & 1) == 0 )
    v46 = (int)(unsigned __int8)v103 >> 1;
  if ( (v103 & 1) != 0 )
    v46 = v104;
  v47 = v46 - 2;
  do
  {
    if ( v47 < 0 )
      break;
    v48 = (v103 & 1) != 0 ? v105 : (int *)((char *)&v103 + 1);
    v49 = (char *)v48 + v47;                    // 转换v105为bdeaed243193ce11ac913bbd48d340a4
    v50 = (v103 & 1) != 0 ? v105 : (int *)((char *)&v103 + 1);
    v51 = *((_BYTE *)v50 + v47);
    v52 = (char *)&unk_D90E4 + v47 - v46;
    --v47;
    *v49 = v51 ^ v52[17];
  }
  while ( v47 != v46 - 18 );
  std::string::basic_string((int)&v100, (int)&v103);
  std::string::~string((int)&v103);
  v53 = v107;
  v54 = aAc913bbd48d340;                        // v54 = ac913bbd48d340a41234567890qwertyuiopasdfghjklzxcvbnm.
  do
  {
    v55 = *(_DWORD *)v54;
    v54 += 8;
    v56 = *((_DWORD *)v54 - 1);
    *(_DWORD *)v53 = v55;                       // 整个循环将ac913bbd48d340a41234567890qwertyuiopasdfghjklzxcvbnm.转换为ac913bbd48d340a4
    *((_DWORD *)v53 + 1) = v56;
    v53 += 8;
  }
  while ( v54 != &aAc913bbd48d340[16] );
  v103 = 0;
  v104 = 0;
  v105 = 0;
  std::string::__init((int)&v103, (int)v107, 16);// v103=v107=ac913bbd48d340a4
  std::string::basic_string((int)v99, (int)&v103);
  std::string::~string((int)&v103);
  sub_394FC(1, v86, v95, (unsigned __int8 *)&v100, v99);// AES加密
  std::string::~string((int)v99);
  std::string::~string((int)&v100);
  f11((int)&v96, (int)v95);                     // HEX格式化
  v58 = v87 & 1;
  if ( (v87 & 1) != 0 )
  {
    v57 = v89;
    v58 = 0;
  }
  else
  {
    BYTE1(v87) = v87 & 1;
  }
  if ( (v87 & 1) != 0 )
  {
    *v57 = v58;
    v88 = v58;
  }
  else
  {
    LOBYTE(v87) = v58;
  }
  std::string::reserve((int)&v87, 0);
  v59 = v96;
  v60 = v97;
  v61 = v98;
  v96 = 0;
  v97 = 0;
  v98 = 0;
  v87 = v59;
  v88 = v60;
  v89 = (_BYTE *)v61;
  std::string::~string((int)&v96);
  std::string::~string((int)v95);
  if ( (*(_BYTE *)a1 & 1) != 0 )
  {
    **(_BYTE **)(a1 + 8) = 0;
    *(_DWORD *)(a1 + 4) = 0;
  }
  else
  {
    *(_BYTE *)(a1 + 1) = 0;
    *(_BYTE *)a1 = 0;
  }
  std::string::reserve(a1, 0);
  v62 = v87;
  v63 = v88;
  v64 = (int)v89;
  v87 = 0;
  v88 = 0;
  v89 = 0;
  *(_DWORD *)a1 = v62;
  *(_DWORD *)(a1 + 4) = v63;
  *(_DWORD *)(a1 + 8) = v64;
  std::string::~string((int)&v87);
  std::string::~string((int)v86);
  std::__list_imp<std::pair<std::string,std::string>>::clear(v76);
  std::string::~string((int)&v66);
  return a1;
}

cc::d方法里为AES_256_CBC_ENCRYPT

encData: |1649905000102key:bdeaed243193ce11ac913bbd48d340a4iv:ac913bbd48d340a4

t2参数解析

 复制代码 隐藏代码
String t2 = com.xxx.common.player.kugouplayer.a.c(null);

最终调用native层的cc::e方法

 复制代码 隐藏代码
int __fastcall cc::e(_JNIEnv *a1)
{
  sub_346F4(&v33);
  v34[2] = 0;
  v34[0] = (int)v34;
  v34[1] = (int)v34;
  v35 = 0;
  v36 = 0;
  v37 = 0;
  std::string::__init((int)&v35, (int)&aEia[2], 1);
  cc::h4((int)&v38, a1);                        // 获取ANDROID_ID
  v2 = v35;
  v3 = v36;
  v35 = 0;
  v36 = 0;
  v66 = v2;
  v67 = v3;
  v68 = v37;
  v4 = v38;
  v5 = v39;
  v37 = 0;
  v38 = 0;
  v39 = 0;
  v69 = v4;
  v70 = v5;
  v71 = v40;
  v40 = 0;
  std::list<std::pair<std::string,std::string>>::push_back(v34, &v66);
  std::pair<std::string,std::string>::~pair(&v66);
  std::string::~string((int)&v38);
  std::string::~string((int)&v35);
  v41 = 0;
  v42 = 0;
  v43 = 0;
  std::string::__init((int)&v41, (int)"b", 1);
  cc::h5((int)&v44, a1);                        // 获取DeviceId
  v6 = v41;
  v7 = v42;
  v8 = v43;
  v41 = 0;
  v42 = 0;
  v43 = 0;
  v66 = v6;
  v67 = v7;
  v68 = v8;
  v9 = v44;
  v10 = v45;
  v11 = v46;
  v44 = 0;
  v45 = 0;
  v46 = 0;
  v69 = v9;
  v70 = v10;
  v71 = v11;
  std::list<std::pair<std::string,std::string>>::push_back(v34, &v66);
  std::pair<std::string,std::string>::~pair(&v66);
  std::string::~string((int)&v44);
  std::string::~string((int)&v41);
  v47 = 0;
  v48 = 0;
  v49 = 0;
  std::string::__init((int)&v47, (int)"c", 1);
  cc::h6((cc *)&v50, a1);                       // 获取network HardwareAddress
  v12 = v47;
  v13 = v48;
  v14 = v49;
  v47 = 0;
  v48 = 0;
  v49 = 0;
  v66 = v12;
  v67 = v13;
  v68 = v14;
  v15 = v50;
  v16 = v51;
  v17 = v52;
  v50 = 0;
  v51 = 0;
  v52 = 0;
  v69 = v15;
  v70 = v16;
  v71 = v17;
  std::list<std::pair<std::string,std::string>>::push_back(v34, &v66);
  std::pair<std::string,std::string>::~pair(&v66);
  std::string::~string((int)&v50);
  std::string::~string((int)&v47);
  v53 = 0;
  v54 = 0;
  v55 = 0;
  std::string::__init((int)&v53, (int)"d", 1);
  cc::h8((cc *)&v56, a1);                       // 获取手机的型号设备名称
  v18 = v53;
  v19 = v54;
  v20 = v55;
  v53 = 0;
  v54 = 0;
  v55 = 0;
  v66 = v18;
  v67 = v19;
  v68 = v20;
  v21 = v56;
  v22 = v57;
  v23 = v58;
  v56 = 0;
  v57 = 0;
  v58 = 0;
  v69 = v21;
  v70 = v22;
  v71 = v23;
  std::list<std::pair<std::string,std::string>>::push_back(v34, &v66);
  std::pair<std::string,std::string>::~pair(&v66);
  std::string::~string((int)&v56);
  std::string::~string((int)&v53);
  v59 = 0;
  v60 = 0;
  v61 = 0;
  std::string::__init((int)&v59, (int)aEia, 1);
  f5();                                         // daytime
  v24 = v59;
  v25 = v60;
  v26 = v61;
  v59 = 0;
  v60 = 0;
  v61 = 0;
  v66 = v24;
  v67 = v25;
  v68 = v26;
  v27 = v62;
  v28 = v63;
  v29 = v64;
  v62 = 0;
  v63 = 0;
  v64 = 0;
  v69 = v27;
  v70 = v28;
  v71 = v29;
  std::list<std::pair<std::string,std::string>>::push_back(v34, &v66);
  std::pair<std::string,std::string>::~pair(&v66);
  std::string::~string((int)&v62);
  std::string::~string((int)&v59);
  f9(v65, v34);                                 // 合并string
  f6(&v66, v65);                                // aes
  if ( (v66 & 1) != 0 )
    v30 = v68;
  else
    v30 = (char *)&v66 + 1;
  v31 = _JNIEnv::NewStringUTF(a1, v30);
  std::string::~string((int)&v66);
  std::string::~string((int)v65);
  std::__list_imp<std::pair<std::string,std::string>>::clear(v34);
  cc::sp<cc::RefJObject>::~sp(&v33);
  return v31;
}

f6函数内部逻辑与t1的f4函数一致
cc::e方法里为AES_256_CBC_ENCRYPT

encData: ||9eea2d301e53|Pixel 4|1649905000118key:dc8e123f07636a41361b62235fc313aciv:361b62235fc313ac

key参数解析

com.xxx.fanxing.core.protocol.g.e.c
map.put(ao.M, com.xxx.fanxing.allinone.common.utils.ao.a("" + this.b + this.e + getVersion() + String.valueOf(this.h).toLowerCase()));
ao.m = keythis.b = AppIdthis.e = AppKeythis.h = clienttime_mskey = MD5("" + AppId + AppKey + APPVersion + String.valueOf(this.h).toLowerCase())


禁止非法,后果自负

欢迎关注公众号:逆向有你

欢迎关注视频号:之乎者也吧

安卓逆向 -- 某APP登录算法分析

原文始发于微信公众号(web安全工具库):安卓逆向 -- 某APP登录算法分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月18日08:48:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   安卓逆向 -- 某APP登录算法分析http://cn-sec.com/archives/920854.html

发表评论

匿名网友 填写信息