【安卓逆向】xx医美登陆协议分析

xx医美登陆协议分析

安卓逆向交流学习群:692903341

1.最近在学习协议分析这块,很多时候一些apk只是简单看一下,大概看清楚流程就不想继续详细去分析了。没有很详细的去分析一个apk,想借这次写文章,梳理一下整个流程。
2.答应了一个大佬要写一篇文章发出来。
3.因为水平比较菜且时间不足,这次就选了一个简单的apk,虽然还有几个同样简单的
apk在备选之列,但是我每天出入工地的时候,电梯广告总是在播放xx医美的广告,做女人 整好 这个广告我每天都会看到几遍,那就撸它把。

这里选择了分析用账号密码登陆的登陆协议
在这里插入图片描述
测试账号:13355556666 测试密码:kkk111222

burpsuite 抓登陆包:
在这里插入图片描述
在这里插入图片描述
格式下各个字段 看着清楚一些
1.POST /v8/passport/apploginnew HTTP/1.1

2.Cookie: usersign =1568523653313812392; expires=Tue, 15‐Oct‐2019
15:59:59 GMT; Max‐Age=2631546; path=/; domain=.soyoung.com

3.Content‐Type: application/x‐www‐form‐urlencoded

4.Content‐Length: 458

5.Host: api.soyoung.com

6.Connection: close

7.Accept‐Encoding: gzip, deflate

8.User‐Agent: okhttp/3.10.0

10.xy_device_token=1402b247ff996e94f16ce53a27377309e2

11.&password=kkk111222

12.&request_id=00e09fba6857bf87ed88030d6fd6680f

13.&uuid=ffffffff‐9dbd‐915d‐ffff‐ffffde5a499b

14.&app_id=2

15.&lver=7.32.2

16.&login_name=13355556666

17.&_sign=%242a%2405%24xWMKvGsGdANgOytr88Tqf.mPsuq6BAFUnKr2ITTyWJriXaidIy
EO6

18.&device_id=175988620

19.&sys=2

20.&key=2f77dcb9f66cacdc4cb1f592974816f2

21.&uid=0

22.&sm_device_id=20190813220351b85597663f19089a4a4550cbc15f62f4014b4f1873
00ca7f

23.&pinyin=soyoung

24.&_time=1568523793

25.&cityId=0

当我看到密码是明文的时候 真的是一口血吐了出来 很久没看到这么坦诚相见的apk了这里 本来是可以直接搜 字符串 直接到到关键位置的 例如下图这样
在这里插入图片描述
但是为了处理某些字符串混淆 或者 要查找的字符串在源码中出现很多次的情况

我一般会结合monitor 的方法跟踪 相互印证 打开monitor 按下登陆键 跟踪一下方法调用
在这里插入图片描述
在这里插入图片描述
一般此类登陆协议 整理方法调用的时候 我一般会搜索 onclick() 这个按键的处理的方法 因为是按了登陆键才触发登陆流程的

onclick() 一路跟随 整理出相关调用层级:

1.onclick()

2.void onViewClick(View)

3.void a(PasswordLoginFragment)

4.void userlogin()

5.void getContent(String, String)

6.void pwdLoginRequest(String, String)

7.void passwordLoginRequest(String, String)

8.Observable passwordLoginRequest(String, String)

9.Observable post(String, HashMap<String, String>)

10.TreeMap getCommonParasm(HashMap<String, String>)

11.TreeMap getCommonParasm(HashMap<String, String> , String)

整理调用层级的时候 可以用 jadx-gui 看一下方法源码

比较有亮点的是以下两个方法

1.void passwordLoginRequest(String, String)
在这里插入图片描述
2.TreeMap getCommonParasm(HashMap<String, String> , String)
在这里插入图片描述
上面方法中 看到了登陆数据包的大量字段

这里准备动态调试结合静态分析 所以先反编译apk
在这里插入图片描述
am start ‐D com.youxiang.soyoungapp/.ui.SplashActivity
调试启动app
在这里插入图片描述
转发端口

  • adb forward tcp:8700 jdwp:20951

动态调试观察第一个 比较有料的函数 void passwordLoginRequest(String, String)
在这里插入图片描述
在这里插入图片描述
void passwordLoginRequest(String, String)

调试得知 两个参数是明文的 手机号和密码 根据调试结果 修改变量名 增加可读性

1.public Observable passwordLoginRequest(String str_phone, String
2.str_pass) {
3. HashMap map_login_data = new HashMap();
4. String str_key = FlagSpUtils.getKey(str_phone, str_pass); map_login_data.put(“login_name”, str_phone); map_login_data.put(“password”, str_pass); map_login_data.put(“key”, str_key); map_login_data.put(“sm_device_id”, SmAntiFraud.getDeviceId()); return this.post(LoginUrl.PWD_LOGIN, map_login_data);
5.}
上面的函数里有4个字段

1.login_name 明文

2.password 明文

3.key getkey返回值

4.sm_device_id getDeviceId()

getkey()方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • login_name 明文
  • password 明文
  • key md5(lavion_soyoung@2013_ + “手机号” + “_” + “密码”)
  • sm_device_id getDeviceId()

getDeviceId()
在这里插入图片描述
在这里插入图片描述
搜索公司名字后 找到了 sdk的官网
在这里插入图片描述
看起来就是这个sdk产品 生产了唯一标识 可能防范黑产表哥把
在这里插入图片描述
一路跟踪 找到产生id的函数
在这里插入图片描述
在这里插入图片描述
这里不继续分析 因为继续分析下去篇幅太大 有兴趣的小伙伴可以下载sdk 看看源码 或者自己直接逆向

第一个有字段的函数分析完毕 下面开始第二个

getCommonParasm(HashMap<String, String> hashMap, String str) 函数大部分字段在这里完成拼接
jadx-gui 反编译的源码

  • public static TreeMap<String, String>

  • getCommonParasm(HashMap<String, String> hashMap, String str) {

  • TreeMap<String, String> treeMap = new TreeMap<>(); if (hashMap !=
    null) {

  • treeMap.putAll(hashMap);

  • }

  • UserInfo user = UserDataSource.getInstance().getUser(); if
    (TextUtils.isEmpty(treeMap.get(“uid”))) {

  • treeMap.put(“uid”, user.getUid());

  • }

  • if (TextUtils.isEmpty(treeMap.get(“xy_token”))) { String xy_token =
    user.getXy_token();

  • if (!TextUtils.isEmpty(xy_token)) { treeMap.put(“xy_token”,
    xy_token);

  • }

  • }

  • if (TextUtils.isEmpty(treeMap.get(“lat”))) { treeMap.put(“lat”,
    LocationHelper.getInstance().latitude);

  • }

  • if (TextUtils.isEmpty(treeMap.get(“lng”))) { treeMap.put(“lng”,

  • LocationHelper.getInstance().longitude);

  • }

  • if (TextUtils.isEmpty(treeMap.get(“cityId”))) { treeMap.put(“cityId”,

  • LocationHelper.getInstance().district_id);

  • }

  • String string = AppPreferencesHelper.getString(“device_id”); String
    string2 =

  • AppPreferencesHelper.getString(AppPreferencesHelper.XY_DEVICE_TOKEN);
    if (TextUtils.isEmpty(uuid)) {

  • uuid = DeviceUtils.getUUID(Utils.getApp());

str));

}

  • String str2 = “2”; treeMap.put(NotificationCompat.CATEGORY_SYSTEM,
    str2); treeMap.put(“lver”, AppUtils.getAppVersionName());
    treeMap.put(“pinyin”, getChannelID(Utils.getApp())); if
    (“com.youxiang.soyoungapp.diary”.equals(str)) {
  • str2 = “40”;
  • } else if (“com.youxiang.tw”.equals(str)) { str2 = “39”;
  • } else if (“com.youxiang.soyoungapp.peri”.equals(str)) { str2 =
  • MessageConstantInterface.MessageType_Reply;
  • }
  • treeMap.put(“app_id”, str2); treeMap.put(“device_id”, string);
  • treeMap.put(AppPreferencesHelper.XY_DEVICE_TOKEN, string2);
    treeMap.put(“uuid”, uuid);
  • long currentTimeMillis = System.currentTimeMillis();
    treeMap.put("_time", (currentTimeMillis / 1000) + “”);
    treeMap.put(“request_id”, createRequestId(currentTimeMillis,
  • treeMap.put("_sign", paramsSign(getParamsString(treeMap))); return
    treeMap;

手动修改变量名 提升可读性后

  • public static TreeMap getCommonParasm(HashMap map_login_arg, String
  • str_page_name) {
  • TreeMap map_login_data = new TreeMap();
  • if(map_login_arg != null) { map_login_data.putAll(map_login_arg);
  • }
  • UserInfo userinfo = UserDataSource.getInstance().getUser();
  • if(TextUtils.isEmpty(((CharSequence)map_login_data.get(“uid”)))) {
  • map_login_data.put(“uid”, userinfo.getUid());
  • }
  • if(TextUtils.isEmpty(((CharSequence)map_login_data.get(“xy_token”))))
  • {
    13.String str_xy_token = userinfo.getXy_token();
    14.if(!TextUtils.isEmpty(str_xy_token)) {
    15.map_login_data.put(“xy_token”, str_xy_token);16 }
    17 }

18

19

if(TextUtils.isEmpty(((CharSequence)map_login_data.get(“lat”)))) {

20 map_login_data.put(“lat”, LocationHelper.getInstance().latitude);

21 }

22

23

if(TextUtils.isEmpty(((CharSequence)map_login_data.get(“lng”)))) {

24 map_login_data.put(“lng”, LocationHelper.getInstance().longitude);

25 }

26

27

if(TextUtils.isEmpty(((CharSequence)map_login_data.get(“cityId”)))) {

28 map_login_data.put(“cityId”, LocationHelper.getInstance().district_id);

29 }

30

31.String str_dev_id = AppPreferencesHelper.getString(“device_id”);
32.String str_device_token = AppPreferencesHelper.getString(“xy_device_token”);
33.if(TextUtils.isEmpty(ApiHeader.uuid)) {
34.ApiHeader.uuid = DeviceUtils.getUUID(Utils.getApp()); 35 }
36

37.String str_app_Id = “2”;
38.map_login_data.put(“sys”, “2”);
39.map_login_data.put(“lver”, AppUtils.getAppVersionName());
40.map_login_data.put(“pinyin”, ApiHeader.getChannelID(Utils.getApp()));
41.if(“com.youxiang.soyoungapp.diary”.equals(str_page_name)) {

42.str_app_Id = “40”;

43 }

44.else if(“com.youxiang.tw”.equals(str_page_name)) {

45.str_app_Id = “39”;

46 }

47.else if(“com.youxiang.soyoungapp.peri”.equals(str_page_name))

map_login_data.put(“app_id”, str_app_Id); map_login_data.put(“device_id”, str_dev_id); map_login_data.put(“xy_device_token”, str_device_token); map_login_data.put(“uuid”, ApiHeader.uuid);

long l_curtime = System.currentTimeMillis(); map_login_data.put("_time", l_curtime / 1000L + “”); map_login_data.put(“request_id”,
{
ApiHeader.createRequestId(l_curtime, str_page_name)); map_login_data.put("_sign",

ApiHeader.paramsSign(ApiHeader.getParamsString(map_login_data))); return map_login_data;

}

列出函数中字段的顺序 逐个分析

  • uid userinfo.getUid() 返回值
  • xy_token 登陆数据包未出现不理会
  • lat 登陆数据包未出现不理会
  • lng 登陆数据包未出现不理会
  • cityId LocationHelper.getInstance().district_id
  • device_id
    AppPreferencesHelper.getString(“device_id”); xy_device_token
  • AppPreferencesHelper.getString(“xy_device_token”); uuid
    ApiHeader.uuid
  • sys “2”
  • lver AppUtils.getAppVersionName()
  • pinyin ApiHeader.getChannelID(Utils.getApp()
  • app_id 对比包名给出
  • _time System.currentTimeMillis() / 1000
  • request_id ApiHeader.createRequestId(l_curtime,str_page_name)
  • _sign ApiHeader.paramsSign(ApiHeader.getParamsString(map_login_data)

整理后

//未出现成员

1
2.xy_token 登陆数据包未出现不理会
3.lat 登陆数据包未出现不理会
4.lng 登陆数据包未出现不理会

5

6 //固定值和不依赖自定义对象的成员 7 sys “2”

8._time System.currentTimeMillis() / 1000
9.app_id 对比包名给出
10

11

12.//成员被AppPreferencesHelper方法返回值赋值
13.device_id AppPreferencesHelper.getString(“device_id”);
14.xy_device_token AppPreferencesHelper.getString(“xy_device_token”);
15

16.//成员被ApiHeader 方法返回值赋值
17.uuid ApiHeader.uuid
18.pinyin ApiHeader.getChannelID(Utils.getApp()
19.request_id ApiHeader.createRequestId(l_curtime, str_page_name)
20._sign

            ApiHeader.paramsSign(ApiHeader.getParamsString(map_login_data)

21

22.//以来其他自定义对象的成员
23.uid userinfo.getUid() 返回值

24.lver AppUtils.getAppVersionName()
25.cityId LocationHelper.getInstance().district_id

这里 首先看 device_id 和 xy_device_token
在这里插入图片描述
在这里插入图片描述
跟随 getString方法 看到这里调用了 AppSpHelper.getInstance().getString(str);、

在这里插入图片描述
跟随进去之后 发现调用了 **AppSpHelper.getString()**方法
在这里插入图片描述
这里的sp成员的类型 时安卓sdk的一个接口 基于xml实现数据的存取具体可以看官方的接口文档
在这里插入图片描述
综上 可知 device_id xy_device_token 两个字段的值是直接访问 xml 文件从键值对取值
在这里插入图片描述
继续分析别的字段

uuid

ApiHeader.uuid = DeviceUtils.getUUID(Utils.getApp());
在这里插入图片描述
由上图可知uuid的算法如上 uuid = new uuid(android_id, imei.hashcode() << 20 | imsi.hashcode())

2 request_id
在这里插入图片描述
request_id的算法也比较简单 request_id = md5(包名 + 毫秒数) 3 pinyin
在这里插入图片描述
这里检测了是否被调试 如果被调试 pinyin字段的值就是 soyong 这个操作是真的6 如果未处于调试状态 会调用另一个sdk的方法 获取返回值

com.leon.channel.helper.ChannelReaderUtil::getChannel(conte xt)

这里不继续深究

4 sign
sign的值是由 ApiHeader.paramsSign决定的

在这里插入图片描述
sign字段的由来 sign = md5 (sign + “&” + login_data)

这里的login_data是之前拼凑的所有字段

uid
在这里插入图片描述
UserInfo::getuid();
在这里插入图片描述
这里的uid要么是0 要么就是数据成员uid userinfo是一个数据类 很多地方都可以调用

setuid直接设置uid

跟随过去 一般是从 json里面获取获取key对应的value 这里不做更深研究lver
在这里插入图片描述
可以看到这个lver 参数是调用api获取了版本

cityId
在这里插入图片描述
仔细跟随 获取城市id的位置
在这里插入图片描述
发现又是跟之前一样的套路
在这里插入图片描述
cityId 这个字段是 用 SharedPreferences 基于xml来获取数据

  sign字段的native方法 **getsign() 依次查看调用**

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到 getsign的参数就是 createsign的返回值 且createsign()并无参数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
createsign 并没有参数

以下是getsign源码 手动更改了一些变量名字提高可读性
在这里插入图片描述
在这里插入图片描述
核心代码在这里

大部分的字段在这里完成分析 文章待润色 暂时告一段落

猜你喜欢

转载自blog.csdn.net/YJJYXM/article/details/102940425