jar反编译工具_移动端测试 - 记录一次快速开发的测试小工具

熟悉移动端手动测试的同学,在实际工作中一定会遇到一些感觉额外的费力需求,例如快速构建非重复性请求+统计结果。这时通过手动操作应用的方式触发请求,不但需要修改代码,可能还得配置相应的测试环境。而使用抓包软件修改请求后又需要重新计算签名,否则会导致校验失败。

正巧,最近的工作也遇到这种情况,综上种种,需要开发一款定制的工具来辅助进行测试。

项目是以Android客户端的请求为基础,所以设计的思路是用java写一个简单的GUI并处理数据,这样更方便修改请求。同时native层负责签名,最后将项目打包成jar直接使用。

本文主要记录了开发前后的过程和思路,供有需要的伙伴参考,完全适用于代码经验不足的小白,大佬们可以直接略过。

主要步骤如下(本文环境为macOS Catalina系统):

1、Intellij IDEA通过GUI Form创建图形界面
2、Java层处理数据
3、C实现加密算法并编译成动态库
4、项目打成jar包
5、jar包调用动态库
6、Obfuscator-LLVM(ollvm)混淆

一、开发简单的GUI交互界面

页面布局极为简单,这里用Intellij IDEA通过GUI Form创建图形界面的方式实现,适用于小白。当然建议有过相关swing经验的使用自定义的布局,更加灵活、更加适应开发的需要。

1、创建新的GUI Form

项目中File -> New -> GUI Form

0b969474e8f265d31d19844d1d2582f8.png

2、绘制布局

在右侧点击控件,然后再点击想要放置的位置,就可以添加控件了,控件的属性可以在左侧的窗口中设置,例如preferredSize等等。

e2abc4e6b6a8644bfe613f297245b40f.png

3、跳转到绑定的class

选中一个控件,使用快捷键 fn + F4 跳转到Form绑定的class类。

e2128010c2f59c08dcecaffe73775080.png

4、初始化界面

选中panel,快捷键⌘N键Generate,点击上图中的Form main(),即可创建main() 并且初始化界面。

098367959b69dbb94f3a05585ada0f92.png

5、运行

Run一下Main()就可以看到我们创建的GUI了。接下来就是设置各种监听事件和逻辑处理。

ef771d2c603971b93650cf71b9b547f0.png

二、处理数据

1、Java工程导入JSONObject

处理服务端返回的数据时需要用到json库的相关方法,因此需要导入库。

这里直接从Git下载https://github.com/stleary/JSON-java,在工程中新建package -> org.json,然后把所有文件拷贝org.json目录下,就可以使用了。

25c3ea308585590963a864d7bed96fbe.png

2、Java调用CURL命令

原始数据的采集是curl格式,偷点懒代码中也是直接调用curl命令。

fc140084784b4e1356958b4c48a92e4e.png

开始的时候,直接把整个curl命令字符串传了进去,但是总是不能顺利执行。后面查了下资料,需要把curl命令切割成数组,再执行才能成功。

# 错误
String curl = "curl xxxxx";
Process process = Runtime.getRuntime().exec(curl);

# 正确
String[] cmd = {"curl", "-H", "Host: www.xxx.com"};
Process process = Runtime.getRuntime().exec(cmd);

3、巧用正则表达式处理数据

上面说到要把原始的curl命令拆成数组,这里就需要正则上场了。

仔细观察curl命令,发现可以按照空格来切割字符串。唯一需要注意的是单引号内的空格需要保留,以免破坏某些诸如设备信息等数据的格式。这里有个取巧的方法,既然需要按照空格拆分又不能拆分单引号里面的内容,那么可以把单引号之外的空格全部替换为某个字符,然后按照这个特定字符再切割curl命令就可以了。

匹配单引号之外的所有空格。

# 伪代码
 regex = " (?=([^']*['][^']*['])*[^']*$)";
 command.replaceAll(regex, "awsomeReg");
 String[] curl = command.split("awsomeReg");

三、Java调用动态库

1、调用动态库

在Java中调用动态库也很单,使用System.load(file name)或者System.loadLibrary(lib-name)。但是这两种方法都不够灵活,后面还会再讲到。

2、编译动态库文件

加密算法是在Android native实现的,动态库也是基于arm的,所以不能直接在mac上使用,需要手动重新编译一下。得益于mac系统的便利性,省去了很多环境配置的麻烦。

首先生成.h文件,接下来可以用gcc来进行编译,编译成功后会生成libxxx.jnilib文件,这个就是我们能够通过java直接调用的动态库了。

c28e46b44ae399b239fa668e50d93c81.png

但是这样编译的话第一次会报缺少jni_md.h的错误。不过不用担心,这个问题很好解决,把对应的.h文件拷贝到jdk的include目录就好了。

b8042b9f137f020c9f0f70f1adf033d8.png

四、Intellij Idea将Java项目打成jar包

总体上只需要四步就能完成打包了:

1、菜单:File->project stucture
2、在弹窗最左侧选中Artifacts->"+",选jar,选择from modules with dependencies,然后会有配置窗口出现,配置完成后,勾选Build on make >ok保存
3、然后菜单:Build->make project
4、最后在项目目录下去找输出的jar包(路径在你添加Artifacts的时候设置的路径)

五、jar包调用动态库

这里有很多坑,运行jar的时候会发现,无法直接加载Jar包中的动态库。

这与JVM的一个系统变量有关,可以通过打印System.getProperty("java.library.path")查看。我们在任意一个目录执行jar文件的时候,系统实际上是找不到这个libxxx.jnilib(so,dll等等)动态库文件的。为了方便,我们又不可能同时提供一个jar文件和一个动态库,这样太傻了。

回过头来看System.load(String filename)这个方法,它是从本地文件系统中以指定的文件名加载代码文件,并且文件名参数必须是完整的路径名。所以我们可以在执行jar的时候,通过流的形式写出动态库到本地,然后再加载调用。

    static {
        try {
            InputStream in = Your.class.getClassLoader().getResourceAsStream("libxxx.jnilib");
            File ffile = new File("");
            String filePath = null;
            filePath = ffile.getAbsolutePath() + File.separator
                    + "libxxx.jnilib";
            File jnilib = new File(filePath);
            FileOutputStream out = new FileOutputStream(jnilib);
            int i;
            byte[] buf = new byte[1024];
            try {
                while ((i = in.read(buf)) != -1) {
                    out.write(buf, 0, i);
                }
            } finally {
                in.close();
                out.close();
            }
            System.load(jnilib.getAbsolutePath());
            jnilib.deleteOnExit();
        } catch (Throwable e) {
            try {
            } catch (Throwable ee) {
                throw ee;
            }
        }
    }

这样,我们只需要一个jar文件,就可以搞定所有事情了。运行一下jar包,在json中把version改为0.1,然后提交。可以成功的看到服务端的返回数据,说明修改的请求已经通过服务端的校验。

c23f82d804b4ce9c877532f3f1c0e2bd.png

通过这个小测试工具,可以脱离客户端做一些实时的网络请求;也可以针对特定的接口做边界值的验证;还可以做一些安全和渗透测试等等。

对于批量甚至是组合的请求,虽然GUI不能满足,但是利用工程本身却非常容易实现。例如我需要测试一个推荐策略(100页不能有重复内容),那么至少得刷上百次带翻页接口,手动完成该任务并记录结果显然不太现实。但是利用工程就容易的多,修改请求分页数据的参数,再记录返回值并对比有无重复就可以了。

所以从这里我们看出,最关键的地方不在于数据怎么组织,而是如何进行校验,也就是所谓的sign值。Java由于其容易被反编译,现在的应用已经几乎没有再把签名算法放在java层了,都统一交给了native层去做。但是需要注意的是,native代码如果不进行混淆加密,也是非常容易被反编译的。接下来就聊聊native代码的保护。

六、Obfuscator-LLVM(ollvm)混淆

我们都知道java代码容易被反编译,为了让反编译后的代码难以读懂,通常采用混淆的方式。但即使这样,一些关键的代码也难以隐藏。例如之前分析的某app协议,通过反编译工具能够清晰的看到被还原的代码。

9dcc000d19542d61d0a0412c2d466191.png

同样的,native代码也可以被反编译。如下图所示,虽然易读性要差一些,但关键的逻辑方法都能看到,甚至加密的key也能读到。懂一点点汇编语言就能摸清程序的执行流程,会动态调式的更加毫无阻拦。由此可见native代码的安全性也不容忽视。

5ac056ddbbd5ef2204663729c5636734.png

经过ollvm混淆后,native层代码会更加安全。关于如何在mac上搭建ollvm环境,可以参考我另一篇文章。

Hao John:Mac搭建Android NDK ollvm环境​zhuanlan.zhihu.com

最后只展示一下混淆后的反编译效果

bbf70a00522272599b0c5e1f40fd0039.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值