记某APP登录逆向解密过程

admin 2024年4月25日19:42:04评论6 views字数 5381阅读17分56秒阅读模式
声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。
请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关。

来源:先知社区,作者:小*咔

现在只对常读和星标的公众号才展示大图推送,建议大家把潇湘信安设为星标”,否则可能看不到了

0x00 前言

最近在学习APP逆向相关的知识,刚好拿到了一个APP目标,该APP登录过程存在加密,所以记录下逆向破解的过程。

0x01 流程

先介绍下拿到该APP后续所做的一些工作流程
选择相应版本安装到测试机当中进行抓包,查看数据包分析登录请求包,找到需要进行逆向的字段反编译APP,得到java代码寻找关键字,进行hook验证还原算法

0x02 逆向破解

安装目标APP
#启动adb服务adb start-server#安装目标APPadb install 目标app.ap
抓包
配置代理,确保手机和电脑处于同一个网络中

电脑端:配置BP代理端口

手机端:代理设置
打开APP,进行登录抓包,发现请求包中存在加密的字段
反编译APP
将APP拖入jadx进行反编译

逆向密码加密
因为请求的是登录的数据包,所以我们搜索login/login.ashx关键字

双击进入,可以看到是在UserModel接口下绑定了一个常量
查找用例,双击进入,定位到代码位置,我们可以看到密码加密的位置就是SecurityUtil.encodeMD5(str3)
点击encodeMD5(str3)跳进声明进行查看,查看代码发现是典型的MD5加密

如果此时不确定是否是MD5加密时,可以利用Python代码进行确认,对123456进行加密

import hashlib

md5 = hashlib.md5()md5.update(b"123456")print(md5.hexdigest())
手机端应用也使用123456进行登录,查看数据包,发现两个值相同,可以确认时MD5加密

如果还想更确认,还可以通过hook确认加密位置

手机端启动frida
adb shellsucd /data/local/tmp/ls./frida-server-16.1.7-an

进行端口转发

import subprocess subprocess.getoutput("adb forward tcp:27042 tcp:27042")subprocess.getoutput("adb forward tcp:27043 tcp:27043")
下面就是进行hook代码的编写,首先找到encodeMD5(str3)的包名
接着寻找类名
运行hook代码,手机端输入账号密码进行登录,可以看到和我们抓包的结果是一样的
逆向签名_sign
搜索关键字:"_sign,发现五个位置存在,通过观察可发现前四个属于同一个类,最后一个是单独的

这时需要分析是否是第一个toSign进行加密的,hook toSign确认位置是否正确

寻找toSign方法的包和类
运行hook脚本,并在手机端进行登录
点击登录后,hook脚本并未执行,说明_sign加密不是这里
继续寻找确认是LaunchModel下的常量位置:public static final String KEY_SIGN = "_sign";
查找用例,发现很多都是signByType做加密,所以需要hook确认是否是这个位置
此时发现lambda$initRequestCommonParams$0和signByType传入的都是相同的参数i,所以直接hook lambda$initRequestCommonParams$0方法即可,继续寻找包名和类名
编写hook脚本运行,然后进行登录,发现打印了内容

逆向udid

通过上述步骤发现了加密的位置,后面需要破解SignManager.INSTANCE.signByType是如何加密的以及udid是怎么来的

跳到声明,找到udid的位置

udid加密过程:通过SecurityUtil.encode3Des,传入了context和字符串

首先了解下字符串都包含什么
getIMEI(context)                          # 手机的IMEIHiAnalyticsConstant.REPORT_VAL_SEPARATOR  # 固定的字符串就是 | System.nanoTime()                         # 手机开机时间 HiAnalyticsConstant.REPORT_VAL_SEPARATOR  # 固定的字符串就是 | SPUtils.getDeviceId()                     # 设备id号

在了解一下context是什么东西

在安卓(Android)开发中,Context是一个非常重要的概念,它代表了应用程序的当前状态信息。每个Android应用程序都有一个Context,它允许应用程序访问系统资源和执行各种操作。Context通常是由Android系统传递给应用程序的各个组件(如Activity、Service、BroadcastReceiver等),以便它们能够与系统和其他组件进行交互。

# getContext() 获取这个对象

Context的主要作用包括:

1、访问资源:通过Context,您可以访问应用程序的资源,如布局文件、字符串、图片等。这是因为Context持有对应用程序资源的引用,使您能够在应用程序中加载和使用这些资源。


2、启动组件:通过Context,您可以启动其他组件,如ActivityServiceBroadcastReceiver等。例如,您可以使用Context启动一个新的Activity来打开新的界面

3、获取系统服务:通过Context,您可以获取系统级别的服务,例如获取系统的传感器、网络状态、存储管理等。这些服务是通过系统提供的服务注册表(Service Registry)来获取的。

4、应用程序级别的操作:Context还可以用于执行应用程序级别的操作,如发送广播、获取应用程序包名、获取应用程序的数据目录等。

getIMEI(context)

public static String getIMEI(Context context) {    if (PermissionsCheckerUtil.hasReadPhoenStatePermission(context)) {        String imei = SPUtils.getIMEI();  # 从xml中 读取 SharedPreference中读取,一开始没有        if (imeiIsNull(imei)) {# 手机设备id和网卡的mac地址混合得到一个串            imei = ((TelephonyManager) context.getSystemService("phone")).getDeviceId();            if (imeiIsNull(imei)) {                String macAddress = ((WifiManager) context.getSystemService(NetworkUtil.NETWORK_TYPE_WIFI)).getConnectionInfo().getMacAddress();                if (macAddress != null) {                    try {                    } catch (UnsupportedEncodingException e) {                        e.printStackTrace();                        # 如果手机拿不到网卡信息,就返回一个随机uuid                        # return UUID.randomUUID().toString();

                        context = getIMEIbyAndroidIDandUUID(context);                    }                    if (macAddress.length() > 0 && !isInBlackList(macAddress)) {                        context = UUID.nameUUIDFromBytes(macAddress.getBytes("utf8")).toString();                        imei = context;                    }                }                # 获取随机uuid                context = getIMEIbyAndroidIDandUUID(context);                imei = context;            }            if (!imeiIsNull(imei)) {                SPUtils.saveIMEI(imei); # 只要生成过以后,以后直接放到SharedPreference中xml中            }        }        return imei;    }    return "sssss";}
HiAnalyticsConstant.REPORT_VAL_SEPARATOR
System.nanoTime(),java中是一个前系统时间的纳秒数,在安卓中这个表示手机开机时间
import randomnano_time = random.randint(5136066335773,7136066335773)

SPUtils.getDeviceId()

public static String getDeviceId() {        return getSpUtil().getString(KEY_DEVICE_ID, "");    }

#这个值经过测试发现是固定的,向后端发送请求:/tradercloud/v100/push/regdevice.ashx 拿回来存入的
SecurityUtil.encode3Des加密

从源码中可以看出这里是DES加密,有key和iv值,它所加密的明文就是上文中提到的字符串

寻找iv值 iv = "appapich"
寻找key值,从图中可以看到是在String desKey = AHAPIHelper.getDesKey(context);获取key值
继续跟踪getSignDeskey
找到get3desKey,可以发现这里是使用JNI开发的,通过so生成的,所以后面需要逆向libnative-lib文件
如果去逆向so文件有点太麻烦了,一般DES加密的key是固定的,所以我们可以通过多次hook得到DES的key值,如果发现就是固定的,那么就可以直接拿来使用了。
运行hook脚本,多次点击登录,可以得到相同的key值 DesKey值:
appapiche168comappapiche168comap

最后使用代码实现udid加密

# pip install pycryptodomeimport base64from Crypto.Cipher import DES3

BS = 8pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

# 3DES的MODE_CBC模式下只有前24位有意义key = b'appapiche168comappapiche168comap'[0:24]iv = b'appapich'

plaintext = pad("cf15599b-5e93-3be5-a705-a39403227dfd|13359325995159|366586").encode("utf-8")

# 使用MODE_CBC创建ciphercipher = DES3.new(key, DES3.MODE_CBC, iv)result = cipher.encrypt(plaintext)res = base64.b64encode(result)print(res)
最终逆向_sign
treeMap.put("_sign", SignManager.INSTANCE.signByType(i, treeMap));
最后我们需要查看signByType是如何加密的
  • str:因为i是1,str=KEY_V2 KEY_V2是固定的值:W@oC!AH_6Ew1f6%8
  • sb.append(str):字符串拼接:先把固定字符串W@oC!AH_6Ew1f6%8拼接上,之后把循环的字典在进行拼接,后面又出现sb.append(str)是把字符串W@oC!AH_6Ew1f6%8拼到最后,然后使用MD5进行加密。

整合代码即可得到_sign的值

import hashlib

def md5(data_string):    obj = hashlib.md5()    obj.update(data_string.encode('utf-8'))    return obj.hexdigest()

data = "W@oC!AH_6Ew1f6%8"

data_dict = {    "_appid": "atc.android",    "appversion": "3.37.0",    "channelid": "csy",    "pwd": "e10adc3949ba59abbe56e057f20f883e",    "udid": "fBricFHfQGIN%2Bam5jCTzG5yze/fO/pLP%2BIZgbuZKaGN0HiRwd1IPVQeCzaqV QpGO0zsKKfLxUlSpiG6FGQaNxA==",    "username": "13111111111"}

result = "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])

un_sign_string = f"{data}{result}{data}"sign = md5(un_sign_string).upper()print(sign)
文章来源:先知社区(小*咔)原文地址:https://xz.aliyun.com/t/14306

关注我们

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月25日19:42:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   记某APP登录逆向解密过程https://cn-sec.com/archives/2688248.html

发表评论

匿名网友 填写信息