V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
zhaoboy666
V2EX  ›  Python

爬虫工程师的 unidbg 入门教程

  •  
  •   zhaoboy666 · 2019-12-28 12:30:38 +08:00 · 6056 次点击
    这是一个创建于 1796 天前的主题,其中的信息可能已经有所发展或是发生改变。
    • 现在很多的 app 使用了 so 加密,以后会越来越多。爬虫工程师可能会直接逆向 app,看 java 代码, 完成 java 层的算法破解, 但是如果遇到 so 该怎么办呢?可能你会直接破解 so,但是真的会有很多爬虫工程师会去并且会破解 so 吗?有时候我们可以不用破解 so,利用很多大佬写好的轮子即可完成 so 的调用。
    • 说到调用,就有很多方法了,比如用 frida 的 rpc、xposed+andserver、 再者就是 unicorn+web 框架等等,今天要说的并不是这些,而是 unidbg,这框架有什么好的地方呢?看看介绍。

    介绍(来自逸飞)

    unidbg 是一个基于 unicorn 的逆向工具,可以黑盒调用安卓和 iOS 中的 so 文件。unidbg 是一个标准的 java 项目。

    由于现在的大多数 app 把签名算法已经放到了 so 文件中,所以要想破解签名算法,必须能够破解 so 文件。但是我们知道,C++ 的逆向远比 Java 的逆向要难得多了,所以好多时候是没法破解的,那么这个时候还可以采用 hook 的方法,直接读取程序中算出来的签名,但是这样的话,需要实际运行这个应用,需要模拟器或者真机,效率又不是很高。

    unidbg 就是一个很巧妙地解决方案,他不需要直接运行 app,也无需逆向 so 文件,而是通过在 app 中找到对应的 JNI 接口,然后用 unicorn 引擎直接执行这个 so 文件,所以效率也比较高。

    • 这里重要的是目前利用 unidbg+springboot 做成了 web 服务。

    食用

    案例来自 JXU2QkQyYXBwJTIwdjQuMTYuMA== 对于该 app 而言,是非常适合入门的一个 app,未加固、算法简单、很容易找到 so 的 jni。 先去凯神的 github 上下载https://github.com/zhkl0228/unidbg 下载完毕用 idea 打开,等待 maven 下载完毕。我这里已经创建好 du 的文件。

    上个代码看着比较方便,代码中有很多注释

    public class du extends AbstractJni {
    
        //ARM 模拟器
        private final ARMEmulator emulator;
        //vm
        private final VM vm;
        //载入的模块
        private final Module module;
    
        private final DvmClass TTEncryptUtils;
    
        //初始化
        public du() throws IOException {
            //创建 app 进程,这里其实可以不用写的,我这里是随便写的,使用 app 本身的进程就可以绕过进程检测
            emulator = new AndroidARMEmulator("com.du.du");
            Memory memory = emulator.getMemory();
            //作者支持 19 和 23 两个 sdk
            memory.setLibraryResolver(new AndroidResolver(23));
            memory.setCallInitFunction();
            //创建 DalvikVM,利用 apk 本身,可以为 null
            //如果用 apk 文件加载 so 的话,会自动处理签名方面的 jni,具体可看 AbstractJni,利用 apk 加载的好处,
    //        vm = emulator.createDalvikVM(new File("src/test/resources/du/du4160.apk"));
    我这里没有用到 apk,主要是没有检测其他因素。
            vm = emulator.createDalvikVM(null);
            //加载 so,使用 armv8-64 速度会快很多,这里是 so 的文件路径,其实也可以利用 apk 自身的。
            DalvikModule dm = vm.loadLibrary(new File("src/test/resources/du/libJNIEncrypt.so"), false);
            //调用 jni
            dm.callJNI_OnLoad(emulator);
            module = dm.getModule();
            //加载 so 的那个类
            TTEncryptUtils = vm.resolveClass("com/duapp/aesjni/AESEncrypt");
        }
    
    
        //关闭模拟器
        private void destroy() throws IOException {
            emulator.close();
            System.out.println("destroy");
        }
    
        public static void main(String[] args) throws IOException {
            du t = new du();
            t.encodeByte();
            t.destroy();
        }
    
        private String encodeByte() {
            //调试
            // 这里还支持 gdb 调试,
            //emulator.attach(DebuggerType.GDB_SERVER);
            //附加调试器
    //        emulator.attach(DebuggerType.SIMPLE);
    //        emulator.traceCode();
            //这里是打断点,原地址 0x00005028->新地址 0x40005028 新地址需要改成 0x4 
    //        emulator.attach().addBreakPoint(null, 0x40001188);//encode 地址
    //        emulator.attach().addBreakPoint(null, 0x40000D10);
            Number ret = TTEncryptUtils.callStaticJniMethod(emulator, "getByteValues()Ljava/lang/String;");
            long hash = ret.intValue() & 0xffffffffL;
            StringObject st1 = vm.getObject(hash);
            //*这里要处理下字符串
            String byteString = st1.getValue();
            StringBuilder builder = new StringBuilder(byteString.length());
            for (int i = 0; i < byteString.length(); i++) {
                if (byteString.charAt(i) == '0') {
                    builder.append('1');
                } else {
                    builder.append('0');
                }
            }
            //获取 encodeByte 地址
            ret = TTEncryptUtils.callStaticJniMethod(emulator, "encodeByte(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
                    //传参,这里需要两个字符串,所以就传入两个参数
                    vm.addLocalObject(new StringObject(vm, "要加密的值")),
                    vm.addLocalObject(new StringObject(vm, builder.toString())));
            //ret 返回的是地址,
            hash = ret.intValue() & 0xffffffffL;
            //获得其值
            StringObject str = vm.getObject(hash);
            System.out.println(str.getValue());
            return str.getValue();
        }
    }
    

    上边代码有 jni 的类是哪一个需要知道,就是下面这个类,这个其实是和加载 so 有关系的。

    TTEncryptUtils = vm.resolveClass("com/*/aesjni/AESEncrypt"); 我们需要逆向 app,这里不细说如何在 app 中寻找加载 so 的类。如下图,encodeByte 是该 app 调用 native 层加密的入口,loadLibrary 是 java 加载 so 的方法,这个类就是上述代码中填写的。

    然后再看"encodeByte(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"这里,这是 smali 写法,不补基础,后面跟上需要传的参数, getByteValues 这个方法是毒获取的一个 01 字符串,并且在 java 层进行了处理,然后再传进encodeByte里面,encodeByte这个方法最后获取的其实并不是最终需要的,需要 md5 才是最后的 newSign。可以验证一下下。

    测试结果通过。

    最后

    启动 java 文件时候注意这个改成自己的平台!!!

    VM options: -Djava.library.path=prebuilt/os -Djna.library.path=prebuilt/os
    Where os may: linux64, win32, win64, osx64
    

    最后这个文件放在https://github.com/zhaoboy9692/dailyanalysis喜欢的可以 star,谢谢。 以上文章仅用于学习交流

    2 条回复    2019-12-29 17:44:02 +08:00
    locoz
        1
    locoz  
       2019-12-28 16:42:56 +08:00 via Android
    整挺好。可以到 bbs.nightteam.cn 也发一下,社区领域更垂直一些😂
    lusi1990
        2
    lusi1990  
       2019-12-29 17:44:02 +08:00
    不错 , 以后爬的时候就不用开个真机了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3785 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 36ms · UTC 10:37 · PVG 18:37 · LAX 02:37 · JFK 05:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.