大猿搜题 sign so 加密参数分析|unidbg
本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
前言
❝
「样本: https://www.wandoujia.com/apps/6526244」
「版本: 11.4.3」❞
charles
随便一个接口 sign
参数
「这个 app 有 360 加固,需要先脱壳」
dump dex
「使用的 yang 神的 frida-dump https://github.com/lasting-yang/frida_dump」
主要如果报了这个错误,需要给 app
文件的读权限
import os
import zipfile
import argparse
def rename_class(path):
files = os.listdir(path)
dex_index = 0
if path.endswith('/'):
path = path[:-1]
print(path)
for i in range(len(files)):
if files[i].endswith('.dex'):
old_name = path + '/' + files[i]
if dex_index == 0:
new_name = path + '/' + 'classes.dex'
else:
new_name = path + '/' + 'classes%d.dex' % dex_index
dex_index += 1
if os.path.exists(new_name):
continue
os.rename(old_name, new_name)
print('[*] 重命名完毕')
def extract_META_INF_from_apk(apk_path, target_path):
r = zipfile.is_zipfile(apk_path)
if r:
fz = zipfile.ZipFile(apk_path, 'r')
for file in fz.namelist():
if file.startswith('META-INF'):
fz.extract(file, target_path)
else:
print('[-] %s 不是一个APK文件' % apk_path)
def zip_dir(dirname, zipfilename):
filelist = []
if os.path.isfile(dirname):
if dirname.endswith('.dex'):
filelist.append(dirname)
else:
for root, dirs, files in os.walk(dirname):
for dir in dirs:
# if dir == 'META-INF':
# print('dir:', os.path.join(root, dir))
filelist.append(os.path.join(root, dir))
for name in files:
# print('file:', os.path.join(root, name))
filelist.append(os.path.join(root, name))
z = zipfile.ZipFile(zipfilename, 'w', zipfile.ZIP_DEFLATED)
for tar in filelist:
arcname = tar[len(dirname):]
if ('META-INF' in arcname or arcname.endswith('.dex')) and '.DS_Store' not in arcname:
# print(tar + " -->rar: " + arcname)
z.write(tar, arcname)
print('[*] APK打包成功,你可以拖入APK进行分析啦!')
z.close()
if __name__ == '__main__':
args = {
'dex_path': '/Users/admin/Desktop/project/user/user-frida-hook/hook/frida_dump_dex/unpack/xiaoyuansouti',
'apk_path': '/Volumes/T7/android/android-file/xiaoyuansouti-11.4.3.apk',
'output': '/Volumes/T7/android/android-file/xiaoyuansouti-new-11.4.3.apk'
}
rename_class(args['dex_path'])
extract_META_INF_from_apk(args['apk_path'], args['dex_path'])
zip_dir(args['dex_path'], args['output'])
脱完壳之后再使用以上脚本,合并 dex
生成新的 apk
文件,在反编译
java 层分析
全局搜索 sign
关键词,定位到这个函数 com.fenbi.android.leo.imgsearch.sdk.network.intercepto.SignInterceptor.intercept
继续往下分析,来到了这里 com.fenbi.android.solar.util.bn
在继续就来到了 native
函数处,会调用这个三个函数
frida hook
function getProcessId() {
var androidProcess = Java.use('android.os.Process');
return 'processId: ' + androidProcess.myTid() + ' - ';
}
function printLog() {
var result = '';
for (var i = 0; i < arguments.length; i++) {
result += arguments[i] + ' ';
}
console.log(getProcessId(), result)
}
function hook() {
var eClass = Java.use('com.fenbi.android.solar.util.e');
eClass.zcvsd1wr2t.implementation = function (a, b, c, d) {
printLog('eClass.zcvsd1wr2t.a: ', a);
printLog('eClass.zcvsd1wr2t.c: ', c);
printLog('eClass.zcvsd1wr2t.c: ', c);
printLog('eClass.zcvsd1wr2t.d: ', d);
var res = this.zcvsd1wr2t(a, b, c, d);
printLog('eClass.zcvsd1wr2t.res: ', res);
return res;
}
eClass.awi6d8sdfe.implementation = function (a, b, c, d) {
printLog('eClass.awi6d8sdfe.a: ', a);
printLog('eClass.awi6d8sdfe.c: ', c);
printLog('eClass.awi6d8sdfe.c: ', c);
printLog('eClass.awi6d8sdfe.d: ', d);
var res = this.awi6d8sdfe(a, b, c, d);
printLog('eClass.awi6d8sdfe.res: ', res);
return res;
}
eClass.fxc3fs3red.implementation = function (a, b, c, d) {
printLog('eClass.fxc3fs3red.a: ', a);
printLog('eClass.fxc3fs3red.c: ', c);
printLog('eClass.fxc3fs3red.c: ', c);
printLog('eClass.fxc3fs3red.d: ', d);
var res = this.fxc3fs3red(a, b, c, d);
printLog('eClass.fxc3fs3red.res: ', res);
return res;
}
}
Java.perform(function () {
hook();
})
执行脚本,结果保存到文件里,函数都 hook
成功
unidbg
「老规矩,先搭建架子」
package com.xiayu.xiaoyuansouti;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import java.io.File;
import java.io.IOException;
public class GetSign_v1143Test extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public DvmClass EClass;
public String apkPath = "/Volumes/T7/android/android-file/xiaoyuansouti-11.4.3.apk";
GetSign_v1143Test() {
emulator = AndroidEmulatorBuilder.for32Bit().build();
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary("RequestEncoder", true);
vm.setJni(this);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
EClass = vm.resolveClass("com/fenbi/android/solar/util/e");
}
public static void main(String[] args) {
GetSign_v1143Test getSign_v1143Test = new GetSign_v1143Test();
getSign_v1143Test.destroy();
}
private void destroy() {
try {
emulator.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
跑起来,成功报错,libandroid.so
咱们加一下
加上在跑起来,这次报了环境错误,补上就 OK 了
public void call_zcvsd1wr2t() {
String methodId = "zcvsd1wr2t(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)Ljava/lang/String;";
EClass.callStaticJniMethodObject(
emulator, methodId,
new StringObject(vm, "/ape-news/android/news"),
new StringObject(vm, "0"),
0,
new StringObject(vm, "")
);
}
直接来 call
目标函数
「中间省略一部分补环境操作。。。。。。」
这个错误,要求返回 Signature array
,这是获取 app
签名,需要返回 array
注意不要直接 vm.resolveClass("[android/content/pm/Signature")
可以使用 ArrayObject
构建返回,这样可以避免很多问题
CertificateMeta[] certificateMetas = vm.getSignatures();
DvmObject<?>[] dvmObjects = new DvmObject[certificateMetas.length];
for (int i = 0; i < certificateMetas.length; i++) {
dvmObjects[i] = vm.resolveClass("android/content/pm/Signature").newObject(certificateMetas[i]);
}
return new ArrayObject(dvmObjects);
具体代码如上,补上继续跑起来
这个是调用 Signature.toChars
函数,返回 char[]
数据
CertificateMeta certificateMeta = (CertificateMeta) dvmObject.getValue();
byte[] bytes = certificateMeta.getData();
DvmObject<?>[] dvmObjects = new DvmObject[bytes.length];
for (int i = 0; i < bytes.length; i++) {
dvmObjects[i] = vm.resolveClass("C").newObject((char) bytes[i]);
}
return new ArrayObject(dvmObjects);
补完,在跑起来,发现又报错了,不知道啥错误,点进 handler
里看看
是个未实现的 jni
函数,那咱们就去实现一下,参照 GetxxxxArrayElements
代码逻辑,写一个
比如这个 GetIntArrayElements
函数逻辑,通过 getObject
获取对象,在调用 _GetArrayCritical
函数,但是 char[]
没有实现对应的 CharArray
,有两种方法
-
1、自己根据
GetxxxxArrayElements
逻辑写一个 -
2、实现
CharArray
跟其他的xxxArray
逻辑保持一致
推荐方法 2,这样写后面可以避免很多问题(龙哥的精讲课里也提到过,基本数据的构建最好使用 unidbg 提供的 class)
新建个 CharArray
文件,代码直接复制其他的 array
改改数据类型就 OK 了
刚刚的环境,咱们使用 CharArray
来构建
jni
这里也是使用,同样的逻辑返回,继续跑起来,还会报个错误
这里在 write
的时候,循环调用 setChar
函数即可
最后在补个环境,也是成功运行出了结果