以下均由http://api.baidu.com代替真实的api域名,com.baidu代替真实包名
1.通过抓包抓取请求接口
使用抓包工具抓取请求接口和参数,我使用了手机端的抓包精灵工具进行了抓取
GET /content/query?releaseDate=1627315200000 HTTP/1.1
pcode: 1070
ptype: 1
signKey: 0685603c801fa2ca806e3eb3a558b78f63c7e15b
signTime: 1628131832899
nonce: 710016599
signVersion: 1
Host: api.baidu.com
Connection: Keep-Alive
Accept-Encoding: gzip
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
可以看到,该接口进行了数据加密,不能简单地进行抓取,我尝试去掉请求头参数,再次请求的到的是
{
"err_code": "12004",
"err_msg": "请求验证失败"
}
每次使用相同请求头,出现两种错误
{
"err_code": "12004",
"err_msg": "请求验证失败"
}
{
"err_code": "12003",
"err_msg": "手机时间与服务器时间不一致,请调整手机时间设置"
}
证明这个参数验签无法绕过了,于是要想办法得到加密的算法
2使用Jadx-Gui反编译apk查看关键词
使用Jadx-Gui反编译并搜索关键词"signKey",可以看到是在OKHttp的拦截器里面进行了参数加密,如图:
通过跟踪方法调用,发现最终是通过一个SecurityGuardManager的类进行了加密,如图:
网上搜索这个类基本找不到什么有用信息,最后是通过其包名com.alibaba.wireless搜索到这是阿里的一个安全项目,里面提到可以通过服务端解密,但是已经在2018年下线了,那个jar包已经下载不到了,而且这个sdk是在阿里那边动态生成的,应该包含了一些密钥等信息,于是就放弃了从服务端破解的方向。
3在app端进行破解
首先我把整个应用的安装包放在了我的项目里面,然后获取其DexClassLoader,如图:
接着尝试调用签名方法,通过查看源码得知要调用的方法为com.baidu.l.m类下的a方法(以下称sign方法),代码被混淆了。毫无意外地报错了,查看报错信息:
然后回到Jadx-Gui查看方法调用链,这是个体力活,就不多说了,
最终确定了调用sign方法前,有两个方法要先执行,分别是Log工具的初始化和SecurityGuardManager的初始化,然而事情并没有那么简单,在初始化SecurityGuardManager时报错了
ErrorCode = 110
com.alibaba.wireless.security.open.SecException: plugin main not existed
...........
...........
ErrorCode = 110
com.alibaba.wireless.security.jaq.JAQException
回到jadk搜索关键词,跟踪代码得知,是找不到libsgmain.so动态库文件,按道理来说我已经加载了外部的DexClassLoader,调用方法也是通过外部的DexClassLoader反射调用,应该不会再从我的app里加载动态库的,这里我没有深究,简单地把这个so文件放到我的jniLibs里面,继续运行,又出现了别的错误
The ClassLoaderContext is a special shared library.
ErrorCode = 123
ErrorCode = 103
com.alibaba.wireless.security.open.SecException: java.lang.UnsatisfiedLinkError: Shared library "xxx" already opened by ClassLoader 0x4f7; can't open in ClassLoader 0xfff8a3f4
就是说我们这个动态库已经被别的类加载器加载了,这里我也不明白是为什么,我猜想可以从Application入手,因为Application是贯穿整个应用的一个上下文,所以能不能构造一个外部的Application进行初始化,这里可以参考我以前的一篇加壳的文章,不知道为什么被设成违规了《App加壳之旅》,稍微改造一下
public class BaiDuCracker {
public static final String PACKAGE_NAME = "com.baidu";
private static boolean inited = false;
private static Apk apk;
public static void init(Context context) {
if (inited) {
return;
}
apk = Apk.getApk(context);
boolean loadApk = apk.loadApk("apk/baidu.apk");
if (!loadApk) {
L.Companion.e("apk加载失败");
return;
}
try {
injectApplication();
initLogger();
initSecurityComponent();
inited = true;
} catch (Exception e) {
e.printStackTrace();
inited = false;
}
}
private static void injectApplication() {
//备份当前的Application
ApplicationHolder.set(App.Companion.getInstants().getPackageName(), "", App.Companion.getInstants());
//如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
String appClassName = "com.baidu.global.MApplication";
apk.loadClass(appClassName);
/*
* ---------------------------生成Application------------------------------------
* */
//获取当前ActivityThread
Object currentActivityThread = RefInvoke.invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[]{}, new Object[]{});
//获取当前ActivityThread中的mBoundApplication变量
//mBoundApplication的作用是用来makeApplication,详见
//http://blog.csdn.net/jltxgcy/article/details/50540309
Object mBoundApplication = RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mBoundApplication");
//这里的AppBindData 其实就是mBoundApplication
//拿到里面的成员变量LoadedApk info
Object loadedApkInfo = RefInvoke.getFieldOjbect(
"android.app.ActivityThread$AppBindData",
mBoundApplication, "info");
//!!!把info的mApplication 设置成了null.否则makeApplication不会执行,会直接返回这个Application
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",
loadedApkInfo, null);
//Activity 里的currentApplication() 拿的就是这个mInitialApplication
Object oldApplication = RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mInitialApplication");
ArrayList mAllApplications = (ArrayList) RefInvoke
.getFieldOjbect("android.app.ActivityThread",
currentActivityThread, "mAllApplications");
mAllApplications.remove(oldApplication);//删除oldApplication
ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
.getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
"mApplicationInfo");
ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
.getFieldOjbect("android.app.ActivityThread$AppBindData",
mBoundApplication, "appInfo");
appinfo_In_LoadedApk.className = appClassName;
appinfo_In_AppBindData.className = appClassName;
/**
* 声明应用安装包目录
*/
try {
Field mAppDir = loadedApkInfo.getClass().getDeclaredField("mAppDir");
mAppDir.setAccessible(true);
mAppDir.set(loadedApkInfo, App.Companion.getInstants().getCacheDir() + File.separator + "app.apk");
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
/**
*这里Instrumentation 参数传空
* 所以instrumentation.callApplicationOnCreate不会执行
* 所以要我们自己手动onCreate
*/
//将mClassLoader换成包装Loader,以便能找到破解程序的类
Object mClassLoader = RefInvoke.getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mClassLoader");
if (!(mClassLoader instanceof BaiDuClassLoader)) {
ClassLoader baiDuClassLoader = ClassLoaderHolder.getDexClassLoader(APK_PATH);
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", loadedApkInfo, baiDuClassLoader);
}
Application reallyApplication = ApplicationHolder.getByTag(PACKAGE_NAME, "");
if (reallyApplication == null) {
reallyApplication = (Application) RefInvoke.invokeMethod(
"android.app.LoadedApk", "makeApplication", loadedApkInfo,
new Class[]{boolean.class, Instrumentation.class},
new Object[]{false, null});
ApplicationHolder.set(PACKAGE_NAME, "", reallyApplication);
}
//将外部Application绑定到当前线程
ApplicationHolder.bindApplcationToCurrent(reallyApplication);
}
private static void initLogger() {
boolean loadClass = apk.loadClass("com.baidu.lib.log.ILogger");
if (loadClass) {
boolean loadMethod = apk.loadMethod("init", Context.class);
if (loadMethod) {
apk.invoke(null, true, ApplicationHolder.getByTag(PACKAGE_NAME, ""));
}
}
}
private static void initSecurityComponent() {
boolean loadClass = apk.loadClass("com.baidu.lib.o.g");
if (loadClass) {
boolean loadSingletoneMethod = apk.loadMethod("a");
Object securityComponent = null;
if (loadSingletoneMethod) {
securityComponent = apk.invoke(null, true);
}
boolean loadInitMethod = apk.loadMethod("a", Context.class);
if (loadInitMethod) {
Object invoke = apk.invoke(securityComponent, false, ApplicationHolder.getByTag(PACKAGE_NAME, ""));
}
}
}
public static String sign(String path, String paramSort, long time, int nonce) {
if (!inited) {
return null;
}
boolean loadClass = apk.loadClass("com.baidu.l.m");
if (loadClass) {
boolean loadMethod = apk.loadMethod("a", String.class, String.class, long.class, int.class);
if (loadMethod) {
Object invoke = apk.invoke(null, false, path, paramSort, time, nonce);
if (invoke != null) {
return invoke.toString();
}
}
}
return null;
}
}
由于篇幅限制和我自己app的一些私密内容,一些工具类的代码就不贴了,有需要的可以站内私信我
成功调起接口: