
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 就可以定位到。整体是这样的结构
一眼望过去了,没有混淆,没有控制流,果然练手的 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 中的数据都不加载了,我们分析得到的结果一直就够了。
末尾
继续努力吧,有机会写一下易盾的空间推理。