skuukzky
文章9
标签0
分类2

文章分类

scrape.center APP8逆向分析

scrape.center APP8逆向分析

scrape.center APP8 逆向分析

前言

之前在整理语雀笔记的时候,误打误撞看到了一个网站,Python 爬虫案例 | Scrape Center,后面经过了解知道了是业内大佬崔庆才的一个网站,其中有一些爬虫练手的案例,我觉得挺适合我们平时入手联系,整体难度和平时遇到的也接近相似,再次记录一下对于 app 中的第八题分析过程。

分析过程

软件没壳,直接定位 encrypt 方法,然后是一个 so 层的方法,直接去 app 目录下的 lib 中找到 arm64 的 so 文件,拖入到 ida 中反编译既可,我就在再此详细 贴图分析了。

要找的加密值是 token。长这个模样。ZjRhNTk2N2NhMzU2NTFjNDNmMWJkNTc2Mzk4MGM5MDdmOTE0ZWY5NSwxNzI0ODIxODkz

其实 base64 一下就是这样。f4a5967ca35651c43f1bd5763980c907f914ef95,1724821893

第一段 40 位,在熟知的加密中,SHA1 的结果 就为 40 位,后面就是一个时间戳了。

hook encrypt 方法 传入一个 str 和 一个 int。 “/api/movie” 和 0 其实就是抓包中的接口和 offset 的值

so 里面是静态方法,直接在到处函数中搜索 encrypt 就可以定位到。整体是这样的结构

image-20240828133850426

一眼望过去了,没有混淆,没有控制流,果然练手的 app 就是舒服。 我们也能明显的看到,函数流程中有个 sha1 的函数,那估计就没跑了,直接 hook sha1 函数入参。秒杀 ,此篇完结。

哈哈,开玩笑的,要真有这么简单,也不会单独拿出来做记录了。 sha1 的参数实际上是一个结构体,直接 hook 行不通,还需要跟进一层。

我们先通读下代码吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
__int64 __fastcall Java_com_goldze_mvvmhabit_utils_NativeUtils_encrypt(
        _JNIEnv *a1,
        __int64 a2,
        __int64 a3,
        unsigned int a4)
  // a2好像没用到    a3  '/api/movie'   a4  0
{
  int v4; // w1
  __int64 v5; // x1
  __int64 v7; // [xsp+0h] [xbp-180h]
  char *v8; // [xsp+8h] [xbp-178h]
  std::__ndk1 *v9; // [xsp+20h] [xbp-160h]
  __int64 StringUTFChars; // [xsp+28h] [xbp-158h]
  _BYTE v14[24]; // [xsp+60h] [xbp-120h] BYREF
  _QWORD v15[3]; // [xsp+78h] [xbp-108h] BYREF
  _QWORD v16[3]; // [xsp+90h] [xbp-F0h] BYREF
  _BYTE v17[24]; // [xsp+A8h] [xbp-D8h] BYREF
  _QWORD v18[3]; // [xsp+C0h] [xbp-C0h] BYREF
  _QWORD v19[3]; // [xsp+D8h] [xbp-A8h] BYREF
  __int64 v20[3]; // [xsp+F0h] [xbp-90h] BYREF
  __int64 v21[3]; // [xsp+108h] [xbp-78h] BYREF
  _BYTE v22[24]; // [xsp+120h] [xbp-60h] BYREF
  _BYTE v23[24]; // [xsp+138h] [xbp-48h] BYREF
  _BYTE v24[24]; // [xsp+150h] [xbp-30h] BYREF
  __int64 v25; // [xsp+168h] [xbp-18h]

  // 一堆变量定义 先忽略
  v25 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  // 数组声明
  sub_16370(v24);
  // GetStringUTFChars 转化c的字符串
  StringUTFChars = _JNIEnv::GetStringUTFChars((__int64)a1, a3, 0LL);
  // char数组转为string 后存入v23
  std::string::basic_string<decltype(nullptr)>(v23, StringUTFChars);
  // 存放输入
  sub_16394((__int64)v24, (__int64)v23);
 // 问了一下通义。说是析构函数 用于销毁对象并释放对象所占用的资源 那么说明 v23被删掉了
  std::string::~string(v23);
  // 同上 把 9fdLnciVh4FxQbri 转为string 并放入v22
  std::string::basic_string<decltype(nullptr)>(v22, "9fdLnciVh4FxQbri");
  sub_16394((__int64)v24, (__int64)v22);
  // 同上操作
  std::string::~string(v22);
  std::to_string(v21, (std::__ndk1 *)a4, v4);
  sub_16394((__int64)v24, (__int64)v21);
  // 同上操作
  std::string::~string(v21);
	//	获取时间
  v9 = (std::__ndk1 *)time(0LL);
  // 转化 字符串
  std::to_string(v20, v9, v5);
  sub_1645C(v24, v20);
  // join拼接将v24内容拼接 存入 v18  44LL 其实代表了44  而44的ascii值为 ,
  join(v18, v24, 44LL);
  // 进行sha1 计算
  sha1(v19, v18);
  // 删除 v18
  std::string::~string(v18);
  // 新数组声明
  sub_16370(v17);
  // 数组存放 v19
  sub_1645C(v17, v19);
   // 数组存放 v20
  sub_1645C(v17, v20);
  // 拼接 v17 ,拼接 存入v6
  join(v16, v17, 44LL);
  std::string::basic_string(v14, v16);
  // base64  存入v15
  b64encode(v15, v14);
  //删除v14
  std::string::~string(v14);
  // v15转化char数组
  v8 = (char *)sub_165EC(v15);
  // 转化 NewStringUTF
  v7 = _JNIEnv::NewStringUTF(a1, v8);
  // 删除 其他值
  std::string::~string(v15);
  std::string::~string(v16);
  sub_16610(v17);
  std::string::~string(v19);
  std::string::~string(v20);
  sub_16610(v24);
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  // 返回v7
  return v7;
}

其实大概就知道了,

整体代码在进行参数拼接和一个 sha1 的加密

/api/movie,9fdLnciVh4FxQbri,0,1724821893 –SHA1–> f4a5967ca35651c43f1bd5763980c907f914ef95 与上文一样,最后再把时间戳给加上。 结果是一致的。

如果想深度探索,可以在利用 unidbg 跑一下结果 。 附上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.spdiercenter;
import com.alibaba.fastjson.util.IOUtils;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class SpcUtil {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final DvmClass NativeHelper;

    private final boolean logging;

    SpcUtil(boolean logging) {
        this.logging = logging;
        emulator = AndroidEmulatorBuilder.for64Bit()
                .setProcessName("com.goldze.mvvmhabit")
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(); // 创建Android虚拟机
        vm.setVerbose(logging); // 设置是否打印Jni调用细节
        DalvikModule dm = vm.loadLibrary(new File("/Users/jiangxia/unidbg/apks/tujia/libnative.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
        NativeHelper = vm.resolveClass("com/goldze/mvvmhabit/utils/NativeUtils");//加载类
    }

    void destroy() {
        IOUtils.close(emulator);
        if (logging) {
            System.out.println("destroy");
        }
    }

    public void hooksha1() {
        Debugger MyDbg = emulator.attach();
        // MyDbg.addBreakPoint(module.base + 0xC18); //断点地址
//        MyDbg.addBreakPoint(module.base + 0x15F38); //断点地址
//        MyDbg.addBreakPoint(module.base + 0x15430); //断点地址
//        MyDbg.addBreakPoint(module.base + 0x17F1C); //断点地址
        MyDbg.addBreakPoint(module.base + 0x15B9C); //断点地址
    }

    public static void main(String[] args) throws Exception {
        SpcUtil test = new SpcUtil(true);
        test.hooksha1();
        test.callFunc();
        test.destroy();
    }

    void callFunc() {
        String data = "/api/movie";
        int p2 = 0;
        StringObject stringObject = NativeHelper.callStaticJniMethodObject(emulator, "encrypt(Ljava/lang/String;I)Ljava/lang/String;", data, p2);
        System.out.println(stringObject);
    }

}

代码整合就不写了,因为可能是太久了,app 中的数据都不加载了,我们分析得到的结果一直就够了。

末尾

继续努力吧,有机会写一下易盾的空间推理。

本文作者:skuukzky
本文链接:https://lpy30m.github.io/skuukzky.github.io/2024/08/28/%E9%80%86%E5%90%91/scrape-center-APP8%E9%80%86%E5%90%91%E5%88%86%E6%9E%90/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可