ciscn2024 androidso_re分析
北海 发表于 美国 移动安全 390浏览 · 2024-12-26 02:37

开局看看apk界面,要求输入flag,入口界面是com.example.re11113.MainActivity

jadx反编译看看源码,看到了legal函数,估计是校验方法

这个legal会校验格式是否为flag{xxxx},括号里面是32位,并且还会对括号里面的内容使用inspect进行校验

private boolean legal(String paramString) {
        return paramString.length() == 38 && paramString.startsWith("flag{") && paramString.charAt(paramString.length() - 1) == '}' && !inspect.inspect(paramString.substring(5, paramString.length() - 1));
    }

进入这个inspect函数,发现逻辑很简单,就是调用原生库进行des加密,然后再与"JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw=="进行判断。很明显我们只需要通过des解密这个字符串就好了。所以,接着问题的关键就在于iv和key是什么。
jni.getiv和jni.getkey看着名字就估计是获取iv和key的函数。获取到getiv和getkey的返回值估计就解决了

获取getiv和getkey返回值,最容易能想到的就是通过frida hook。代码也很简单

function hook_jni() {
    Java.perform(function () {
        var jniClass = Java.use("com.example.re11113.jni");
        jniClass.getiv.implementation = function () {
            var ret = this.getiv();
            console.log("getiv: " + ret);
            return ret;
        }
        jniClass.getkey.implementation = function () {
            var ret = this.getkey();
            console.log("getkey: " + ret);
            return ret;
        }
    })

}
setImmediate(hook_jni);

但是hook的时候抽象的事情就发生了。。。注释掉getkey的hook代码,只hook getiv是没问题的

但是一旦尝试hook getkey就会闪退

接下来该怎么办。要么解决闪退问题,继续hook,要么硬刚ida反编译的getkey函数中的伪代码,要么使用unidbg模拟安卓环境指向getkey

unidbg

先分析一下最简单丝滑的方法——unidbg
unidbg是一款基于Java的跨平台逆向工具,主要用于模拟执行Android和iOS应用中的 ARM、ARM64、x86 等多种指令集的代码
https://github.com/zhkl0228/unidbg
这里使用版本0.97
添加so库,创建相应的MainActivity

代码如下:

package com.re11113;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import java.io.File;

public class MainActivity {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        com.re11113.MainActivity mainActivity = new com.re11113.MainActivity();
        System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
        mainActivity.getIvAndKey();
    }
    private final AndroidEmulator emulator;
    private final DvmClass dvmClass;
    private final VM vm;
    private MainActivity() {
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new DynarmicFactory(true))
                .build();
        Memory memory = emulator.getMemory();
        LibraryResolver resolver = new AndroidResolver(23); // 自带 sdk 23 版本
        memory.setLibraryResolver(resolver);
        vm = emulator.createDalvikVM(new File("C:\\Users\\33739\\Downloads\\app-debug (1).apk"));
        //打印日志
        vm.setVerbose(true);
        DalvikModule dm = vm.loadLibrary(new File("E:\\unidbg-0.9.7\\so\\re11113\\libSecret_entrance.so"), true);
        dm.callJNI_OnLoad(emulator);
        dvmClass = vm.resolveClass("com/example/re11113/jni"); // jni 函数所在的类
    }
    private void getIvAndKey() {
        DvmObject<?> result = dvmClass.callStaticJniMethodObject(emulator, "getkey()Ljava/lang/String;");
        System.out.println("result is => " + result.getValue());
        result = dvmClass.callStaticJniMethodObject(emulator, "getiv()Ljava/lang/String;");
        System.out.println("result is => " + result.getValue());
    }
}

运行直接就拿下key了

frida attach

现在android studio里的logcat中查找闪退的原因,发现问题确实是出现在getkey,logcat中显示输入了非utf-8编码的字符'0x33 0x5c <0xbb> 0x3d 0xf7 0x58 0x7e 0xf5'。

在native中,生成一个utf-8的java字符串使用的是NewStringUTF,下面尝试hook一下这个native层方法

function hook_newStringUTF() {
    Java.perform(function () {
        var modules = Process.enumerateModules();
        var newStringUTFAddr = null;
        for (var i = 0; i < modules.length; i++) {
            var module = modules[i];
            try {
                var symbols = module.enumerateSymbols();
                for (var j = 0; j < symbols.length; j++) {
                    if (symbols[j].name.indexOf("NewStringUTF") >= 0) {
                        newStringUTFAddr = symbols[j].address;
                        console.log("Found NewStringUTF in module: " + module.name + " at: " + newStringUTFAddr + " with name: " + symbols[j].name);
                        break;
                    }
                }
            } catch (e) {
                console.warn("Failed to enumerate symbols for module: " + module.name);
            }
            if (newStringUTFAddr) {
                break;
            }
        }
        if (newStringUTFAddr) {
            Interceptor.attach(newStringUTFAddr, {
                onEnter: function(args) {
                    var inputStr = args[1].readCString();
                    console.log("NewStringUTF called with: " + inputStr);
                },
                onLeave: function(retval) {
                    console.log("NewStringUTF returning: " + retval);
                }
            });
        } else {
            console.log("NewStringUTF address not found!");
        }
    });
}

发现确实是传了不对的东西。。。

至于为什么,看了网上大佬的文章好像也没得到答案,然后使用了网上大佬的attach方法,即在在主动调用getkey前添加个附加代码,整体如下:

function hook() {
    Java.perform(function () {
        let jni = Java.use("com.example.re11113.jni");
        var iv = jni.getiv();
        console.log("IV: "+iv);
        var getKeyBase = Module.findExportByName("libSecret_entrance.so", "Java_com_example_re11113_jni_getkey");
        console.log("getkey base: " + getKeyBase);
        Interceptor.attach(getKeyBase, {
            onEnter: function(args) {
            },
            onLeave: function(retval) {
            }
        });
        var key = jni.getkey();
        console.log("Key: " + key);
    })
}

但是还是闪退崩溃了。。抽象。。可能是环境不同吧,我这里安卓10,pixel2
emm.... 莫名其妙想到了用objection附加看看,结果行了。。。。。。

太菜了,不是很懂

so分析

直接分享so中的getkey汇编或者伪代码也不是特别复杂

先从返回值入手,

返回值v15被赋值成了java字符串,这个java字符串使用v14转化的,然后又从v14往上找到v18

查看交叉引用,发现v18为"YourRC4Key"

v18在下面有没有做什么处理呢?
根据上面的"TFSecret_Key"和"YourRC4Key"猜一手jiejie函数是RC4加密,而这个"YourRC4Key"是密钥
进入jiejie小看一下
这段使用std::vector创建了一个存储 256 个整数的容器,并用一些初始值填充它。估计是s盒初始化


这部分应该就是s盒置换了


这里应该就是从s盒中取值进行计算了

退出jiejie函数,发现下面这一坨好像是对字符串的前八位进行异或操作。但是进行异或操作的变量是v20或者v21,现在要确定他们跟v18是否指向同一块地址

查看汇编代码,发现异或运行确实是对v18指向的地址进行的

所以getkey最后的返回结果就是先进行RC4加密再取前八位进行异或操作得到的字符串,


这个A8UdWaeq即为返回结果

最终结果及解决

最终得到的iv是Wf3DLups,key是A8UdWaeq
直接des解密即可

总结一下,这道题实际上不难,但是用frida hook getkey的时候会崩溃闪退,还是很搞心态的。分析一下也能扩展思路,还是很有帮助

参考文章

https://orangestarone.github.io/2024/06/23/2024CISCNandroidso-re/index.html
https://blog.node189.top/posts/androidso_re/
https://bbs.kanxue.com/thread-281916.htm?style=1

0 条评论
某人
表情
可输入 255

没有评论