Unidbg如何秒解CTF-Android题目?
WMBa0 发表于 河南 CTF 1811浏览 · 2024-01-27 15:10

本文没有分享Unidbg最基础东西,比如Unidbg是什么,主要写了Unidbg如何应用。
实战过好,不禁感叹,Unidbg在模拟so这块真的是太方便啦。
完整Unidbg脚本会放在后面,本文的重点在于使用Unidbg的分析so能力,关于Unidbg的安装可以自行搜索。

创建一个项目框架

Java层分析

使用aapt获得对应的启动Activity

aapt dump badging apk名字

开始分析代码

可以看到,重点是分析so的j和p方法

so分析

有两个so文件,先看app使用的libj.so

分析 libj.so

搜索JNI_onload,说明是静态注册函数

j方法

先分析 j 方法,发现没有参数,尝试使用Unidbg跑一遍(完整脚本在后面,这里不占用文章内容了)

int __fastcall Java_an_droid_j_MainActivity_j(JNIEnv *a1)
{
  int i; // r1
  int v2; // r0
  int v3; // r0
  char v5[32]; // [sp-40h] [bp-70h] BYREF
  _BYTE v6[36]; // [sp-20h] [bp-50h] BYREF
  JNIEnv *v7; // [sp+4h] [bp-2Ch]
  int *v8; // [sp+8h] [bp-28h]
  int v9; // [sp+Ch] [bp-24h]
  int v10; // [sp+10h] [bp-20h]
  _BYTE *v11; // [sp+14h] [bp-1Ch]
  int v12; // [sp+1Ch] [bp-14h] BYREF

  v8 = &v12;
  v7 = a1;
  for ( i = -1178200092; ; i = 52119689 )
  {
    do
    {
      v3 = i;
      i = 1445388760;
    }
    while ( v3 == -1178200092 );
    if ( v3 == 52119689 )
      break;
    v11 = v6;
    strcpy(v5, "FlagLostHelpMeGetItBack");
    v10 = 30;
    v9 = 97;
    v5[29] = 0;
    *(_WORD *)&v5[27] = 0;
    v5[24] = 0;
    *(_WORD *)&v5[25] = 0;
    v5[30] = 80;
    qmemcpy(v6, v5, 0x1Eu);
    v2 = (int)(*v7)->NewStringUTF(v7, v6);
    *v8 = v2;
  }
  return *v8;
}
public String func_j(){
    DvmClass dvmClass=vm.resolveClass("an.droid.j.MainActivity");
    DvmObject<?> object = dvmClass.newObject(null);
    DvmObject<?> object1 = object.callJniMethodObject(emulator, "j()Ljava/lang/String;");
    return object1.getValue().toString();
}

就说这个函数其实并没有什么用,总是返回固定值!

p方法

看不了一点,加了混淆


这里可以使用葫芦娃大佬的插件,Obpo(https://github.com/obpo-project/obpo-plugin)​

使用插件来进行恢复即可(详细使用开看Obpo文档)

耐心等待


发现只是经过一个运算,然后输出对应的值,为了方便以后运算,这里也写出对应的Unidbg方法

public int func_p(int args){
    DvmClass dvmClass=vm.resolveClass("an.droid.j.MainActivity");
    DvmObject<?> object=dvmClass.newObject(null);
    int object1=object.callJniMethodInt(emulator,"p(I)I",args);
    return object1;
}

init方法

该方法在java层没有被调用,但不代表没有用处

jstring __fastcall Java_an_droid_j_MainActivity_init(JNIEnv *a1, jobject a2, int a3)

可以看到,它接收一个参数a3,返回值为string类型


其实就是将输入的int类型,分别取高四位与低四位作为两个数据,然后计算一个含“libinit”​的字符串并返回

但是加密比较复杂,我们猜测将正确的zygote值传入,看师傅能得到flag

先尝试传递 zygote=9999,看看结果是怎样的?


可以看到输出了含有libinit的字符串

猜测输入正确的zygote值,也将得到flag

获得正确的zygote值

正确的zygote值要执行99999次才能得到,并且调用了libj.so的p方法,我们也可以使用Unidbg来模拟获得这个值

这里写出p方法对应的Unidbg

public int func_p(int args){
    DvmClass dvmClass=vm.resolveClass("an.droid.j.MainActivity");
    DvmObject<?> object=dvmClass.newObject(null);
    int object1=object.callJniMethodInt(emulator,"p(I)I",args);
    return object1;
}

然后使用Unidbg写出执行脚本

int zygote = 1357024680;
long start =System.currentTimeMillis();
for(int i=0;i<99999;i++){
    zygote =mylesson4.func_p(zygote);
}
System.out.println("99999次后的zygote的值:"+zygote);

但是跑了好长时间,人麻了

将zygote值填入init函数

lesson4 mylesson4=new lesson4(apkFilePath,soFilePath,apkProcessname);
int temp = 1738911344;
System.out.println("flag{" + mylesson4.func_init(temp) + "}");

这就是Unidbg的强大之处,直接模拟,调用so方法一把嗦

完整脚本

开始编写Unidbg脚本(模板复制一下,稍作修改就可以)

package com.lesson4;

import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;

// 导入通用且标准的类库
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.lession1.oasis;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


public class lesson4 extends AbstractJni{
    private final AndroidEmulator emulator; //android模拟器
    private final VM vm;//vm虚拟机
    private final Module module;
    private  final Memory memory;
    private  final DalvikModule dm;
    //将该类封装起来,以后直接套用模板
    public lesson4(String apkFilePath,String soFilePath,String apkProcessname) throws IOException {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(apkProcessname).build();

        //.addBackendFactory(new DynarmicFactory((true)))  下面会创建一个快速模拟器实例,加载速度快,但是某些特性不支持
        //.setProcessName()设置进程名,避免原进程对进程名进行检验


        // 获取模拟器的内存操作接口
        memory = emulator.getMemory();
        // 设置系统类库解析  支持19和23,因为在main/resources/android只集成了两个版本
        memory.setLibraryResolver(new AndroidResolver(23));


        // 创建Android虚拟机,传入APK,可以过掉签名校验,路径比如:"unidbg-android\\src\\test\\java\\com\\lesson1\\123.apk"
        vm = emulator.createDalvikVM(new File(apkFilePath));
        vm.setVerbose(false); // 打印日志,会在调用初始化JNI_onload打印一些信息,默认:false

        // 加载目标SO
        dm = vm.loadLibrary(new File(soFilePath), true); // 加载so到虚拟内存,第二个参数:是否需要初始化
        //获取本SO模块的句柄
        module = dm.getModule();

        vm.setJni(this); //设置Jni,防止报错
        //创建完后,需要调用JNI_onload函数
        //dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad,进行动态注册某些函数。如果都是静态注册,那就不用调用这个函数

        //本次样本连个 JNI_onLoad都没有

    }

    //这个是模拟 bak_libj.so的j方法
    public String func_j(String method,double args){
        DvmClass dvmClass=vm.resolveClass("an.droid.j.MainActivity");
        DvmObject<?> object=dvmClass.newObject(null);
        //获得一个DvmObject对象
        //DvmObject object= ProxyDvmObject.createObject(vm,"an.droid.j"); //因为我创建的类全包名和原app不一样,所以换一种方式来寻找到对应的类对象
        DvmObject object1=object.callJniMethodObject(emulator,method,args);
        String return_value=object1.getValue().toString();
        return return_value;

    }


    //下面两个是模拟 libj.so的init、p和j方法
    //均使用了动态获得dvmclass的方式
    public int func_p(int args){
        DvmClass dvmClass=vm.resolveClass("an.droid.j.MainActivity");
        DvmObject<?> object=dvmClass.newObject(null);
        int object1=object.callJniMethodInt(emulator,"p(I)I",args);
        return object1;
    }

    public String func_init(int args){
        DvmClass dvmClass=vm.resolveClass("an.droid.j.MainActivity");
        DvmObject<?> object=dvmClass.newObject(null);
        //方法签名在对应的so文件中推导出,其实不难的,看参数和看返回值
        DvmObject<?> object1 = object.callJniMethodObject(emulator, "init(I)Ljava/lang/String;", args);
        return object1.getValue().toString();
    }
    public String func_j(){
        DvmClass dvmClass=vm.resolveClass("an.droid.j.MainActivity");
        DvmObject<?> object = dvmClass.newObject(null);
        DvmObject<?> object1 = object.callJniMethodObject(emulator, "j()Ljava/lang/String;");
        return object1.getValue().toString();
    }


    //创建一个main函数
    public static void main(String[] args) throws IOException {
        // 1、需要调用的so文件所在路径
        String soFilePath = "unidbg-android/src/test/java/com/lesson4/libj.so";
        // 2、APK的路径
        String apkFilePath="unidbg-android/src/test/java/com/lesson4/a.apk";
        // 3、apk进程名
        String apkProcessname="an.droid.j";

        lesson4 mylesson4=new lesson4(apkFilePath,soFilePath,apkProcessname);
        int temp = 1738911344;
        System.out.println("flag{" + mylesson4.func_init(temp) + "}");

//        int zygote = 1357024680;
//        long start =System.currentTimeMillis();
//        for(int i=0;i<99999;i++){
//            zygote =mylesson4.func_p(zygote);
//        }
//        System.out.println("99999次后的zygote的值:"+zygote);



    }

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