某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<Application> mAllApplications = (ArrayList<Application>) 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容