美文网首页移动端测试
移动端测试 - 如何快速的开发一款测试小工具

移动端测试 - 如何快速的开发一款测试小工具

作者: h080294 | 来源:发表于2019-07-19 16:38 被阅读18次

    最近在测试一些需求时感觉额外的费力,例如快速构建非重复性请求+统计结果。通过手动操作应用的方式触发请求,不但需要修改代码,还得配置相应的测试环境。使用抓包软件修改请求又需要重新计算签名,否则会导致校验失败。综上种种,需要开发一款定制的工具来辅助进行测试。

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

    主要步骤如下(本文环境均在macOS Mojave系统):

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

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

    页面布局极为简单,这里用Intellij IDEA通过GUI Form创建图形界面的方式实现。

    1、创建新的GUI Form

    项目中File -> New -> GUI Form

    2、绘制布局

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

    3、跳转到绑定的class

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

    4、初始化界面

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

    5、运行

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

    二、处理数据

    1、Java工程导入JSONObject

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

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

    2、Java调用CURL命令

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

    开始的时候,直接把整个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直接调用的动态库了。

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

    四、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,然后提交。可以成功的看到服务端的返回数据,说明修改的请求已经通过服务端的校验。

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

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

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

    六、Obfuscator-LLVM(ollvm)混淆

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

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

    Obfuscator-LLVM就是这样一个项目,它旨在提供一套开源的针对LLVM的代码混淆工具,以增加对逆向工程的难度。虽然官方的版本只更新到4.0版本 LLVM-4.0,但是一些大神在此基础上做了更新和移植。这里我们采用一个移植好的版本https://github.com/heroims/obfuscator,该作者fork原版后又加入了llvm5.0,6.0,7.0以及swift-llvm5.0的版本,并扩展了一些功能。这些已经足够满足当前项目的需要了。

    七、macOS配置ollvm环境

    介绍完ollvm,接下来就讲下如何在macOS配置ollvm环境。

    1、下载LLVM

    直接下载整理好的移植版本,我选则了llvm-8.0的版本。https://github.com/heroims/obfuscator/tree/llvm-8.0

    2、编译

    解压缩zip后,进入目录新建一个build目录,然后按照下面的命令编译。

    mkdir build
    cd build
    #如果不想跑测试用例加上-DLLVM_INCLUDE_TESTS=OFF 
    cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_CREATE_XCODE_TOOLCHAIN=ON ../obfuscator/
    make -j7
    
    3、使用

    原版ollvm中提供了3种混淆方式,分别是:

    -mllvm -fla:控制流扁平化
    -mllvm -sub:指令替换
    -mllvm -bcf:虚假控制流程

    大神移植的时候又扩展了一个字符串混淆方式,-mllvm -sobf 字符串加密,可以丰富我们的使用场景。

    直接使用编译的二进制文件

    build/bin/clang test.c -o test -mllvm -sub -mllvm -fla -mllvm -bcf
    

    也可以重新编译

    /build/bin/clang  -shared -fPIC you.c you2.c -o libxxx.so -mllvm -sub -mllvm -sobf -mllvm -fla -mllvm -bcf
    

    最后我们看下效果,混淆前:程序的流程非常清晰明了。

    混淆后:流程加入了大量干扰的模块,并且字符串也是加密的,极大的提高了静态分析的难度。

    至此,这个小工具就算完成了。

    更多有意思的内容

    相关文章

      网友评论

        本文标题:移动端测试 - 如何快速的开发一款测试小工具

        本文链接:https://www.haomeiwen.com/subject/xcktlctx.html