某App接口逆向过程

以下均由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的拦截器里面进行了参数加密,如图:


搜索关键词"signKey"

通过跟踪方法调用,发现最终是通过一个SecurityGuardManager的类进行了加密,如图:

最终的加密方法

网上搜索这个类基本找不到什么有用信息,最后是通过其包名com.alibaba.wireless搜索到这是阿里的一个安全项目,里面提到可以通过服务端解密,但是已经在2018年下线了,那个jar包已经下载不到了,而且这个sdk是在阿里那边动态生成的,应该包含了一些密钥等信息,于是就放弃了从服务端破解的方向。

3在app端进行破解

首先我把整个应用的安装包放在了我的项目里面,然后获取其DexClassLoader,如图:


获取DexClassloader.jpg

接着尝试调用签名方法,通过查看源码得知要调用的方法为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的一些私密内容,一些工具类的代码就不贴了,有需要的可以站内私信我

成功调起接口:


请求.jpg

你可能感兴趣的:(某App接口逆向过程)