SO逆向之爱库存sig

绕过更新

老版本5.7.8

image-20221015230013403

1
2
3
4
frida-ps -Ua  查看包名com.aikucun.akapp
objection -g com.aikucun.akapp explore -P ~/.objection/plugins
android hooking list activities 查看所有Activity
android intent launch_activity com.aikucun.akapp.activity.LoginActivity 跳到登录Activity不过被更新弹窗盖住

image-20221015230116924

jadx反编译搜索"立即更新",直接hook掉show方法

image-20221015225748211

1
2
3
4
5
6
7
8
9
10
function hook_sig(){
Java.perform(function(){
console.log("Entering hook")
// 干掉弹窗
Java.use("com.aikucun.akapp.widget.dialog.ConfirmDialog").show.implementation = function(){
console.log("hook show ")
}
})
}
setImmediate(hook_sig)

frida -U -f com.aikucun.akapp -l hook_sig.js –no-pause

image-20221015230152996

signV1

抓包

https://zuul.aikucun.com/aggregation-center-facade/api/app/search/product/image/switch

appid38741001
did24c29bc14b5e3ddec1bf571c844a7e78
noncestrae5a5c
timestamp1665846444
zuul1
sigc5a0da1284344f608043722f45c85dfd934c0ce7

反编译搜索”sig”

image-20221015230944548

image-20221015231027003

上frida打印参数和返回值

1
2
3
4
5
6
7
8
9
10
11
12
var MXSecurity = Java.use('com.mengxiang.arch.security.MXSecurity')

MXSecurity.signV1.implementation = function (a, b, c) {
console.log('a: ', a); // url
console.log('b: ', b); // noncestr
console.log('c: ', c); // timestamp

var res = this.signV1(a, b, c); // 加密返回
console.log('res: ', res);

return res
}

image-20221015231154659

IDA打开libmx.so,搜索java发现signV1是静态注册函数

image-20221015231525351

image-20221015231823764

拼接了appid,noncestr,timestamp,secret,url等参数后调用digest函数进行加密,参数分别是JNIEnv,SHA1加密函数,加密内容的bytearray

image-20221015232105626

如果需要还原算法,那么前提得拿到secret盐,可以通过jnitrace或者frida hook native都可以,或者直接使用unidbg黑盒调用。

Jnitrace

1
2
pip install jnitrace==3.0.8
jnitrace -l libmx.so -m spawn com.aikucun.akapp --ignore-vm > akc.log

image-20221015235243061

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
public class AkuMx1 extends AbstractJni {
// 初始化一些 apk 常量
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass mxSecurity;

// APK 路径
public String apkPath = "C:\\Users\\Administrator\\Downloads\\爱库存5.7.8.apk";
// so 文件路径
public String soPath = "unidbg-android/src/test/resources/demo/akc/libmx.so";

// 加载指定版本的系统库
private static LibraryResolver createLibraryResolver() {
return new AndroidResolver(23);
}

// 创建 android 模拟器,这里是 32 位的
private static AndroidEmulator createARMEmulator() {
return AndroidEmulatorBuilder.for32Bit().build();
}

AkuMx1() {
emulator = createARMEmulator();
final Memory memory = emulator.getMemory();
// 设置 sdk版本 23
memory.setLibraryResolver(createLibraryResolver());

//创建DalvikVM,可以载入apk,也可以为null
vm = emulator.createDalvikVM(new File(apkPath));
// 设置可以调用 jni 函数
vm.setJni(this);
// 打印 jni 函数调用具体的 log
vm.setVerbose(true);

// 加载 so 文件
DalvikModule dm = vm.loadLibrary(new File(soPath), true);
module = dm.getModule();
// 加载 java 加密函数所在 类
mxSecurity = vm.resolveClass("com/mengxiang/arch/security/MXSecurity");
}

// 关闭模拟器
private void destroy() throws IOException {
emulator.close();
System.out.println("destroy");
}

public static void main(String[] args) throws IOException {
AkuMx1 aku = new AkuMx1();
aku.run();
aku.destroy();
}

public void run() {
String a = "https://zuul.aikucun.com/aggregation-center-facade/api/app/search/product/image/switch?appid=38741001&did=24c29bc14b5e3ddec1bf571c844a7e78&noncestr=af7aff&timestamp=1665849838&zuul=1";
String b = "af7aff";
String c = "1665849838";

DvmObject<?> strRc = mxSecurity.callStaticJniMethodObject(
emulator,
"signV1(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
vm.addLocalObject(new StringObject(vm, a)),
vm.addLocalObject(new StringObject(vm, b)),
vm.addLocalObject(new StringObject(vm, c))
);
System.out.println("strRc: " + strRc.getValue());
}
}

image-20221016000642683

返回值确实空的,使用frida主动调用正常返回

1
2
3
4
5
6
7
8
9
function invoke_sig() {
Java.perform(function () {
var MXSecurity = Java.use('com.mengxiang.arch.security.MXSecurity');
var a = "https://zuul.aikucun.com/aggregation-center-facade/api/app/search/product/image/switch?appid=38741001&did=24c29bc14b5e3ddec1bf571c844a7e78&noncestr=af7aff&timestamp=1665849838&zuul=1";
var b = "af7aff";
var c = "1665849838";
console.log(MXSecurity.signV1(a, b, c));
})
}

image-20221016004530356

通过jnitrace查看libmx.so的执行流可知需要先执行Java_com_mengxiang_arch_security_MXSecurity_init,即public static final native int init(Context context, boolean z);

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
public void run() {
// 加载 context 上下文对象
DvmClass Context = vm.resolveClass("android/content/Context");
DvmObject<?> strRc1 = mxSecurity.callStaticJniMethodObject(
emulator, "init(Landroid/content/Context;Z;)I;",
// Context.newObject(null) 初始化对象,参数直接 null
vm.addLocalObject(Context.newObject(null)),
// frida hook打印入参为 false
vm.addLocalObject(DvmBoolean.valueOf(vm,false))
);
System.out.println("strRc1: " + strRc1);

String a = "https://zuul.aikucun.com/aggregation-center-facade/api/app/search/product/image/switch?appid=38741001&did=24c29bc14b5e3ddec1bf571c844a7e78&noncestr=af7aff&timestamp=1665849838&zuul=1";
String b = "af7aff";
String c = "1665849838";

DvmObject<?> strRc2 = mxSecurity.callStaticJniMethodObject(
emulator,
"signV1(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
vm.addLocalObject(new StringObject(vm, a)),
vm.addLocalObject(new StringObject(vm, b)),
vm.addLocalObject(new StringObject(vm, c))
);
System.out.println("strRc2: " + strRc2.getValue());
}

image-20221016005619011

SHA256 是 android里的,java 里是 SHA-256,我们需要重写该函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;":
StringObject type = vaList.getObjectArg(0);

String name = "";
if ("\"SHA256\"".equals(type.toString())) {
name = "SHA-256";
} else {
name = type.toString();
System.out.println("else name: " + name);
}

try {
return vm.resolveClass("java/security/MessageDigest").newObject(MessageDigest.getInstance(name));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

image-20221016005800985

1
2
3
4
5
6
7
8
if ("\"SHA256\"".equals(type.toString())) {
name = "SHA-256";
} else if ("\"SHA1\"".equals(type.toString())) {
name = "SHA-1";
} else {
name = type.toString();
System.out.println("else name: " + name);
}

image-20221016010003383

signV3

分析

使用最新版本6.2.3,搜索"sign"或者sign=

https://zuul.aikucun.com/api/gquery 查看业绩中心的数据

image-20221023112937093

appid38741001
did0347d498ac2a5433d090a52dfe9315d1
noncestr6ed25f
subuserid2aeb8f7cd6cb2aa69479c30366baf8f6
svsv3
timestamp1666495412
token831156285ba5413ba43ed83bfbdb38d0
userId2aeb8f7cd6cb2aa69479c30366baf8f6
userid2aeb8f7cd6cb2aa69479c30366baf8f6
signe1c350a82dd7d8a22d21452ce4e47ab2201493eec0bc3ff3cb7174a4ef8727b1

image-20221023105552595

尝试用objection hook一下MXSecurity的方法public static final native String signV3(@NotNull String str, @NotNull String str2, @NotNull String str3, @NotNull String str4);

1
2
3
4
5
6
com.aikucun.akapp on (Android: 10) [usb] # (agent) [374201] Called com.mengxiang.arch.utils.MD5Utils.b(java.lang.String)
(agent) [374201] Arguments com.mengxiang.arch.utils.MD5Utils.b({"variables":{},"query":" query achieveCenterSalesBoard{\n saleBoardData:achieveCenterSalesBoard {\n orderCountShop\n saleShop\n averageTransactionValue\n customerCount\n }\n }\n ","operationName":"achieveCenterSalesBoard"})
(agent) [374201] Return Value: 1d1233cbfb6d5227fce0a483fd186ba6
(agent) [302898] Called com.mengxiang.arch.security.MXSecurity.signV3(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
(agent) [302898] Arguments com.mengxiang.arch.security.MXSecurity.signV3(https://zuul.aikucun.com/api/gquery?appid=38741001&did=0347d498ac2a5433d090a52dfe9315d1&noncestr=6ed25f&subuserid=2aeb8f7cd6cb2aa69479c30366baf8f6&svs=v3&timestamp=1666495412&token=831156285ba5413ba43ed83bfbdb38d0&userId=2aeb8f7cd6cb2aa69479c30366baf8f6&userid=2aeb8f7cd6cb2aa69479c30366baf8f6, 6ed25f, 1666495412, 1d1233cbfb6d5227fce0a483fd186ba6)
(agent) [302898] Return Value: e1c350a82dd7d8a22d21452ce4e47ab2201493eec0bc3ff3cb7174a4ef8727b1

str1是url,str2是noncestr,str3是timestamp,至于str4,可以看到有时候是null,有时候是32位字符串,通过两个请求的对比,发现GET请求时,str4为null,POST请求时,str4为32位字符串,Z1由Z1生成

    String Z1 = Z1(request);
    String sb2 = sb.toString();
    Intrinsics.f(sb2, "builder.toString()");
    String signV3 = MXSecurity.signV3(sb2, substring, valueOf, Z1);

image-20221023111429219

即body的md5就是str4的值,body中的参数md5

image-20221023112848372

IDA打开MXSecurity中引入的libmx.so找到Java_com_mengxiang_arch_security_MXSecurity_signV3,最终返回的v35大概率由SHA256生成,传入digest函数的args[2]v34的类型是jByteArray

image-20221023113615683

digest上frida hook digest方法,MikManager中设置com.aikucun.akapp的启动方式是listen_wait,打开app,frida -U 爱库存 -l signv3.js

image-20221027224241387

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
function dump(name, addr, legnth) {
console.log("======================== " + name + " ============");
console.log(hexdump(addr, { length: legnth || 32 }));
}

function hook_signV3() {

// var bptr = Module.findBaseAddress("libmx.so");
var bptr = Module.findExportByName("libmx.so","_Z6digestP7_JNIEnvPKcP11_jbyteArray")
console.log("==="+bptr)
Interceptor.attach(bptr, {
onEnter: function (args) {
console.log("arg1:", args[1].readCString());
var sptr = Java.vm.tryGetEnv().getByteArrayElements(args[2]);
console.log("arg2:", sptr.readCString());
},
onLeave: function (retval) {
dump("sign", retval, 64);
}
})
}

function main() {
hook_signV3();
}


setImmediate(main)

image-20221027233940707

image-20221027233903860

SHA256的输入,它是由appidsvsnoncestrtimestampsecret(04fdc5e4d9c7420e896ee92b17c68e9f)url构成,如果是POST请求,还会用"&"与body的MD5拼接。

Python还原

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
import hashlib
import json

from urllib.parse import urlencode

def calc_signv3(url, params, body=None):
data = [
('appid', params['appid']),
('svs', params['svs']),
('noncestr', params['noncestr']),
('timestamp', params['timestamp']),
('secret', '04fdc5e4d9c7420e896ee92b17c68e9f'),
('url', url + '?' + urlencode(sorted(params.items()))),
]
data = '&'.join(f'{k}={v}' for k, v in data)
if body:
if isinstance(body, (dict, list)):
body = json.dumps(body, separators=(',', ':'))
if isinstance(body, str):
body = body.encode()

s1 = hashlib.md5(body).hexdigest()
data += '&' + s1

sign = hashlib.sha256(data.encode()).hexdigest()
return sign

Unidbg黑盒调用

image-20221028205735555

1
2
3
4
5
6
7
8
9
10
11
12
13
public void runSignV3() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);

list.add(vm.addLocalObject(new StringObject(vm,"https://zuul.aikucun.com/akucun-member-aggregation/api/v2.0/user.do?action=phonelogin&appid=38741001&did=a93e299cbe12251f10f0356d06e6db1b&noncestr=e91f4f&svs=v3&timestamp=1638341417")));
list.add(vm.addLocalObject(new StringObject(vm, "e91f4f")));
list.add(vm.addLocalObject(new StringObject(vm, "1638341417")));
list.add(vm.addLocalObject(new StringObject(vm, "5119e6c79149b40ea578fdab80489fbd")));
Number number = module.callFunction(emulator, 0x8254 + 1, list.toArray())[0];
String result = vm.getObject(number.intValue()).getValue().toString();
System.out.println("runSignV3:"+result);
}

返回JNIEnv->NewStringUTF("") was called from unidbg@0xffff0000,发现在MXSecurity 类中调用了init函数,可能用来配置一些变量

image-20221028002223455

导出函数的地址是00007F14

1
2
3
4
5
6
7
8
9
10
11
public void call_init() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
list.add(vm.addLocalObject(context));
list.add(0);
module.callFunction(emulator, 0x7F14 + 1, list.toArray());
}
aku.call_init();
aku.runSignV3();

image-20221028202628770

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
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;":
StringObject type = vaList.getObjectArg(0);

String name = "";
if ("\"MD5\"".equals(type.toString())) {
name = "MD5";
} else if ("\"SHA256\"".equals(type.toString())) {
name = "SHA-256";
} else if ("\"SHA1\"".equals(type.toString())) {
name = "SHA-1";
} else {
name = type.toString();
System.out.println("else name: " + name);
}

try {
return vm.resolveClass("java/security/MessageDigest").newObject(MessageDigest.getInstance(name));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

image-20221028205844304

接下来通过console debugger来分析输入,在类的构造函数中加入emulator.attach().addBreakPoint(module.base + 0x83FC);,将v21处设置断点

image-20221028220624064

image-20221028220348361

重新运行代码

image-20221028221200024

输入mr5打印寄存器数据

image-20221028220911578

没有完整输出,看到r6的值为0x24b,它应该是数组的长度,输入mr5 0x24b完整打印

image-20221028221328109

c-跳过断点,n-下一步,d - Varibles 窗口,m - watch(mr0-mr7, mfp, mip, msp [size],m(address) [size])

文章作者: J
文章链接: http://onejane.github.io/2022/10/15/SO逆向之爱库存sig/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏