安卓逆向-unidbg补系统调用(一)

下雨天
10月14日发布 /正在检测是否收录...

补系统调用(一)之得物

apk:得物

版本:4.94.0

目标方法:com.shizhuang.stone.main.SzSdk.lf

目标方法实现:libszstone.so

  • 方法实现如下:
public static native byte[] lf(String str, int i2, int i3);

1. 基本框架

  • 基本的jni环境如下,这里不讨论;
package com.kanxue2w.sana;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
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.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;

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

public class dewu extends AbstractJni {

    public static AndroidEmulator emulator;
    public static Memory memory;
    public static VM vm;
    public static Module module;

    public dewu() {
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.shizhuang.duapp")
                .build();

        memory = emulator.getMemory();

        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File("apks/kanxue2w/du/du.apk"));
        vm.setJni(this);
//        vm.setVerbose(true);
        new AndroidModule(emulator, vm).register(memory);
        DalvikModule dm = vm.loadLibrary("szstone", true);
        dm.callJNI_OnLoad(emulator);
        module = dm.getModule();

    }

    public void call_lf() {
        List<Object> list = new ArrayList<>(10);
        // jnienv
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(new StringObject(vm, "awt0bapt/data/user/0/com.shizhuang.duapp/filesbav4.94.0bavn486bcndewubc")));
        list.add(1);
        list.add(0);

        Number number = module.callFunction(emulator, 0x302fd, list.toArray());

        String res = (String) vm.getObject(number.intValue()).getValue();
        System.out.println("call_lf-->> " + res);
    }


    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;": {
//                System.out.println("<<<--- dvmClass --->>>"+dvmClass);
                return dvmClass.newObject(null);
            }
            case "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;": {
                System.out.println("getString方法 dvmClass -->>" + dvmClass);
                System.out.println("getString方法 参数2 -->>" + varArg.getObjectArg(1).getValue());
                return new StringObject(vm, "8drd14lf");
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }


    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "android/app/ActivityThread->getApplication()Landroid/app/Application;": {
                return vm.resolveClass("android/app/Application").newObject(null);
            }
            case "android/app/Application->getPackageManager()Landroid/content/pm/PackageManager;": {
                return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
            }
            case "android/app/Application->getContentResolver()Landroid/content/ContentResolver;": {
                return vm.resolveClass("android/content/ContentResolver").newObject(null);
            }
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    public static void main(String[] args) {
        dewu du = new dewu();
        du.call_lf();
    }
}

2. clock_gettime

  • 依据上述的代码骨架,尝试继续运行,会出现以下报错;
[17:10:10 070]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:534) - handleInterrupt intno=2, NR=263, svcNumber=0x0, PC=RX@0x40117b88[libc.so]0x40b88, LR=RX@0x400f0ef3[libc.so]0x19ef3, syscall=null
java.lang.UnsupportedOperationException: clk_id=2
    at com.github.unidbg.linux.ARM32SyscallHandler.clock_gettime(ARM32SyscallHandler.java:1760)
    at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:411)
    at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:343)
    at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)
  • 首先分析这个错误是什么意思?一般来说,svcNumber等于0即代表系统调用,NR则是调用号,并且结合报错路径结合判断,它是一个系统调用的异常,去看看什么调用;

image-20250325172716825

  • 很显然,它也做了提示,这里也可以去异常位置看看;

image-20250325172814926

  • 这里证明,不只是一个结果,其实 Unidbg 对 clock_gettime 做了模拟实现,只是并没有做到完美的形态,它做到了一些分支,可以去看看;

image-20250325173247942

  • 那么需要知道这个系统调用具体是在做什么吧,问问ai;
// clock_gettime 是 Linux 系统中的一个高精度时间获取系统调用,用于从指定类型的时钟源中获取当前时间戳,支持纳秒级精度;
// 函数原型与参数:
#include <time.h>
int clock_gettime(clockid_t clk_id, struct timespec *tp);

//参数说明:
// clk_id:时钟类型标识符,决定时间来源(见下文详解)。
// tp:指向 struct timespec 的指针,用于存储获取的时间值。
// 该结构体包含:
// tv_sec:自 Unix Epoch(1970年1月1日00:00:00 UTC)的秒数。
// tv_nsec:纳秒级精度的时间(范围:0~999,999,999)。
// 返回值:成功返回 0,失败返回 -1 并设置 errno。

// timespec 结构体
struct timespec {
    long tv_sec;  // 秒时间戳
    long tv_nsec; // 余下的纳秒时间戳
};
  • 如果想获取毫秒级时间戳,可以像下面这样写:
long long gettime3(){
    struct timespec t = {0, 0};
    clock_gettime(CLOCK_REALTIME, &t);
    return (t.tv_sec*1000) + (t.tv_nsec/1000000);
}
  • 参数1是时钟类型标识符,在此是2,大致有以下的选择;
#define CLOCK_REALTIME 0 //当前的实际时间,是自 1970年1月1日(UNIX 时间纪元)以来经过的秒数,受到系统时钟调整的影响
#define CLOCK_MONOTONIC 1 //自系统启动以来经过的时间
#define CLOCK_PROCESS_CPUTIME_ID 2 //当前进程消耗的 CPU 时间
#define CLOCK_THREAD_CPUTIME_ID 3 //当前线程消耗的 CPU 时间
#define CLOCK_MONOTONIC_RAW 4 //类似于CLOCK_MONOTONIC,但是它不会受到任何系统时间调整或时钟源变化的影响
#define CLOCK_REALTIME_COARSE 5 //类似于 CLOCK_REALTIME,但是提供较低精度的时间
#define CLOCK_MONOTONIC_COARSE 6 //类似于 CLOCK_MONOTONIC,但提供较低精度的时间
#define CLOCK_BOOTTIME 7 //系统启动以来经过的时间,包含了系统休眠时间
#define CLOCK_REALTIME_ALARM 8 //当前时间,但以时间报警机制的精度为准
#define CLOCK_BOOTTIME_ALARM 9 //系统启动以来的时间,类似于 CLOCK_BOOTTIME,但它同样考虑了定时器或闹钟机制的精度。
#define CLOCK_SGI_CYCLE 10 //SGI(Silicon Graphics)的专有时钟
#define CLOCK_TAI 11 //国际原子时
  • 简而言之,时钟可以分为 真实时间、CPU 时间、开机时间、从不确定的某个时间点开始计时 这四大类;
  • Unidbg、Qiling、ExAndroidNativeEmu 等模拟器,其实都是对它有所实现,但又没有实现每一个时钟;
  • CLOCK_REALTIME这个时钟,也就是真实时间,来看看各个工具如何实现:
# ExAndroidNativeEmu 通过 Python 内置 API 获得了秒级时间戳,然后赋给 tv_sec,tv_nsec 则是置空
if clk_id == CLOCK_REALTIME:
    # Its time represents seconds and nanoseconds since the Epoch.
    clock_real = calendar.timegm(time.gmtime())

    mu.mem_write(tp_ptr + 0, int(clock_real).to_bytes(self.__ptr_sz, byteorder='little'))
    mu.mem_write(tp_ptr + self.__ptr_sz, int(0).to_bytes(self.__ptr_sz, byteorder='little'))
    return 0
# 这么做不太好,意味着获取返回的真实时间其精度只有秒,和语义有一定偏差

# qiling 则是完全没有管其他类型的时钟,只当成真实时间去处理
def ql_syscall_clock_gettime(ql, clock_gettime_clock_id, clock_gettime_timespec, *args, **kw):
    ql.log.info(f"clock_gettime(clock_id={clock_gettime_clock_id}, tp={hex(clock_gettime_timespec)})")
    now = datetime.now().timestamp()
    tv_sec = floor(now)
    tv_nsec = floor((now - floor(now)) * 1e6)
    tp = timespec(tv_sec= tv_sec, tv_nsec=tv_nsec)
    ql.mem.write(clock_gettime_timespec, bytes(tp))
    ql.log.debug(f"timespec(tv_sec={tv_sec}, tv_nsec={tv_nsec})")
    return 0
# 相比 ExAndroidNativeEmu,它更完善一些,对 tv_nsec 也做了赋值 

# Unidbg 在CLOCK_REALTIME时钟下,通过 Java 标准库获取了毫秒级时间戳,把秒的部分赋给 tv_sec,再毫秒部分乘 1000000 赋给 tv_nsec。和 Qiling 的逻辑完全一致
long offset = clk_id == CLOCK_REALTIME ? System.currentTimeMillis() * 1000000L : System.nanoTime() - nanoTime;
  • 简而言之,这几款工具都做了一定程度上的模拟,但都不齐全不完善,当然不止是上面的这种情况,那么这里的问题是clk_id=2的情况,2 对应于 CLOCK_PROCESS_CPUTIME_ID 即进程的 CPU 时间,这里我们也破罐子破摔,并入 CLOCK_THREAD_CPUTIME_ID;这是龙哥的原话,那么我们也这么来,因为3,即:
#define CLOCK_PROCESS_CPUTIME_ID 2 //当前进程消耗的 CPU 时间
#define CLOCK_THREAD_CPUTIME_ID 3 //当前线程消耗的 CPU 时间
  • 它俩是可以类似的,我们就按这个方案来处理,所以这里应该在源码里修改吗?当然不选择这样做,这是一种不优雅的做法,和补jni环境的时候类似,我们自实现系统调用处理器,新建一个类来做处理;
package com.kanxue2w.sana.dewu;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.linux.ARM32SyscallHandler;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import unicorn.ArmConst;

public class deWuSyscallHandler extends ARM32SyscallHandler {

    public deWuSyscallHandler(SvcMemory svcMemory) {
        super(svcMemory);
    }

    protected int clock_gettime(Backend backend, Emulator<?> emulator) {
        int clk_id = backend.reg_read(ArmConst.UC_ARM_REG_R0).intValue();
        Pointer tp = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R1);
        if (clk_id == 2) {
            tp.setInt(0, 0);
            tp.setInt(4, 1);
            return 0;
        }
        return super.clock_gettime(backend, emulator);
    }
}
  • 这里依据上面的逻辑,完全模仿着unidbg的处理就行了,在我们之前的代码需要使用这个系统调用,换成如下写法:
package com.kanxue2w.sana.dewu;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidARMEmulator;
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.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.unix.UnixSyscallHandler;
import com.github.unidbg.virtualmodule.android.AndroidModule;

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

public class dewu extends AbstractJni implements IOResolver {

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {

        System.out.println("sana凑崎纱夏 file open:" + pathname);
        return null;
    }

    public static AndroidEmulator emulator;
    public static Memory memory;
    public static VM vm;
    public static Module module;

    public dewu() {
        // 重点start
        // 创建模拟器实例 AndroidEmulatorBuilder 的参数表示是否是 64 位的
        AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false) {
            @Override
            public AndroidEmulator build() {
                return new AndroidARMEmulator(processName, rootDir, backendFactories) {
                    @Override
                    protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) {
                        return new deWuSyscallHandler(svcMemory);
                    }
                };
            }
        };
        // 重点end
        emulator = builder
//                .for32Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.shizhuang.duapp")
                .build();

        memory = emulator.getMemory();

        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File("apks/kanxue2w/du/du.apk"));
        vm.setJni(this);
        vm.setVerbose(true);
        new AndroidModule(emulator, vm).register(memory);
        DalvikModule dm = vm.loadLibrary("szstone", true);
        dm.callJNI_OnLoad(emulator);
        module = dm.getModule();
    }

    public void call_lf() {
        List<Object> list = new ArrayList<>(10);
        // jnienv
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(new StringObject(vm, "awt0bapt/data/user/0/com.shizhuang.duapp/filesbav4.94.0bavn486bcndewubc")));
        list.add(1);
        list.add(0);
        Number number = module.callFunction(emulator, 0x302fd, list.toArray());
        String res = (String) vm.getObject(number.intValue()).getValue();
        System.out.println("call_lf-->> " + res);
    }


    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;": {
                return dvmClass.newObject(null);
            }
            case "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;": {
                System.out.println("getString方法 dvmClass -->>" + dvmClass);
                System.out.println("getString方法 参数2 -->>" + varArg.getObjectArg(1).getValue());
                return new StringObject(vm, "8drd14lf");
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }


    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "android/app/ActivityThread->getApplication()Landroid/app/Application;": {
                return vm.resolveClass("android/app/Application").newObject(null);
            }
            case "android/app/Application->getPackageManager()Landroid/content/pm/PackageManager;": {
                return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
            }
            case "android/app/Application->getContentResolver()Landroid/content/ContentResolver;": {
                return vm.resolveClass("android/content/ContentResolver").newObject(null);
            }
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    public static void main(String[] args) {
        dewu du = new dewu();
        du.call_lf();
    }
}
  • 运行看看会有什么情况;
[16:59:52 968]  INFO [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:1911) - openat dirfd=-100, pathname=/proc/sys/kernel/random/boot_id, oflags=0x20000, mode=0
[16:59:52 973]  INFO [com.github.unidbg.linux.AndroidSyscallHandler] (AndroidSyscallHandler:456) - Return default pipe pair.
[16:59:52 973]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:534) - handleInterrupt intno=2, NR=190, svcNumber=0x0, PC=RX@0x40118b5c[libc.so]0x41b5c, LR=RX@0x401065cb[libc.so]0x2f5cb, syscall=null
[16:59:52 974]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:534) - handleInterrupt intno=2, NR=358, svcNumber=0x0, PC=RX@0x40118db0[libc.so]0x41db0, LR=RX@0x40106635[libc.so]0x2f635, syscall=null
java.lang.AbstractMethodError: com.github.unidbg.linux.file.PipedWriteFileIO
    at com.github.unidbg.file.AbstractFileIO.dup2(AbstractFileIO.java:160)
    at com.github.unidbg.linux.ARM32SyscallHandler.dup3(ARM32SyscallHandler.java:2124)
    at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:501)
    at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:343)
    at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

3. popen

  • 新的报错非常多,首先关键信息主要有以下内容:
[16:59:52 973]  INFO [com.github.unidbg.linux.AndroidSyscallHandler] (AndroidSyscallHandler:456) - Return default pipe pair.
[16:59:52 973]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:534) - handleInterrupt intno=2, NR=190, svcNumber=0x0, PC=RX@0x40118b5c[libc.so]0x41b5c, LR=RX@0x401065cb[libc.so]0x2f5cb, syscall=null
[16:59:52 974]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:534) - handleInterrupt intno=2, NR=358, svcNumber=0x0, PC=RX@0x40118db0[libc.so]0x41db0, LR=RX@0x40106635[libc.so]0x2f635, syscall=null
java.lang.AbstractMethodError: com.github.unidbg.linux.file.PipedWriteFileIO
  • 先看系统调用,这里有两处;
NR=190 vfork
NR=358 dup3
popen内部会调用vfork和dup3 这种错误:com.github.unidbg.linux.file.PipedWriteFileIO 直接到popen去处理
  • 龙哥原话:为什么这里有多个系统调用会WARN?事实上这是库函数popen惹的祸,只要读者看到com.github.unidbg.linux.file.PipedWriteFileIO报错,那么就应该直接转入处理popen的逻辑;
  • 这就是典型的popen问题,我们这里要了解三个小知识:

    • 基本语义和用法?
    • 它是什么意思?
    • Unidbg 为什么会在这个库函数上出错?
    • Unidbg 里如何处理这个问题?
  • 首先,popen 在 Android Native 上是一个高频函数,远非getrusage这类函数可比,设备信息收集、环境检测都可以用到它;函数原型如下:
FILE *popen(const char *command, const char *type);
  • 其次它是什么意思?
popen 是 C 标准库中的一个函数(属于 文件操作接口,而非直接的系统调用),用于创建一个管道(pipe)并启动子进程执行指定的命令。其核心功能是 在程序中调用外部命令并与其输入/输出交互,类似于在终端中执行命令并捕获结果;
  • popen 以创建管道的方式启动一个进程, 并调用 shell,像下面这样使用,比如这里在作用上等价于 adb shell 里输入getprop ro.build.id
std::string cmd = "getprop ro.build.id";
char value[PROP_VALUE_MAX] = {0};
FILE* file = popen(cmd.c_str(), "r");
fread(value, PROP_VALUE_MAX, 1, file);
pclose(file);
  • 其次为什么 Unidbg 会在这个库函数上出错,popen 在底层原理上是创建了一个新进程,然后通过管道去做通信,返回数据;但 Unidbg 在多进程相关的系统调用处理上十分不完善,无法支撑popen正常的执行它的底层逻辑;
  • 最后是 Unidbg 里如何处理好 popen,作者提供过一种好思路,就是越过创建多线程的相关逻辑,只处理好管道的返回值,来让它的功能基本满足;
  • 下面做演示,这可以当成固定处理模式,遇到了就这么做就行;
  • 第一步是用 Dobby 或任意 Hook 工具,在库函数的层面 Hook 获取到 popen 的 command,放到 emulator 的全局变量里,方便后续形成对应。我这里使用的是 xhook;
public void hookPopen(){
    IxHook xHook = XHookImpl.getInstance(emulator);
    xHook.register("libszstone.so", "popen", new ReplaceCallback() {
        @Override
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
            RegisterContext registerContext = emulator.getContext();
            String command = registerContext.getPointerArg(0).getString(0);
            emulator.set("command", command);
            System.out.println("sana command-->>"+command);
            return HookStatus.RET(emulator, originFunction);
        }
    }, true);
    // 使其生效
    xHook.refresh();
}
  • 看看运行结果是什么,然后再根据提示去补;

image-20250327152355737

  • 我们在第一个自实现类里实现pipe2方法,重写 pipe2,根据当前的 command 返回从 adb shell 获取到的值,以及出现 popen 内部会使用的 vfork 和 wait4 系统调用,并依次在代码里进行解释;
package com.kanxue2w.sana.dewu;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.context.EditableArm32RegisterContext;
import com.github.unidbg.linux.ARM32SyscallHandler;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.linux.file.DumpFileIO;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import unicorn.ArmConst;

import java.util.concurrent.ThreadLocalRandom;

public class deWuSyscallHandler extends ARM32SyscallHandler {

    public deWuSyscallHandler(SvcMemory svcMemory) {
        super(svcMemory);
    }

    @Override
    protected boolean handleUnknownSyscall(Emulator<?> emulator, int NR) {
        switch (NR) {
            // 仿照源码写
            case 190:
                vfork(emulator);
                return true;
            case 114:
                wait4(emulator);
                return true;
        }
        return super.handleUnknownSyscall(emulator, NR);
    }


    protected int clock_gettime(Backend backend, Emulator<?> emulator) {
        int clk_id = backend.reg_read(ArmConst.UC_ARM_REG_R0).intValue();
        Pointer tp = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R1);
        if (clk_id == 2) {
            tp.setInt(0, 0);
            tp.setInt(4, 1);
            return 0;
        }
        return super.clock_gettime(backend, emulator);
    }

    @Override
    protected int pipe2(Emulator<?> emulator) {
        EditableArm32RegisterContext context = (EditableArm32RegisterContext)
                emulator.getContext();
        Pointer pipefd = context.getPointerArg(0);
        int write = getMinFd();
        this.fdMap.put(write, new DumpFileIO(write));
        int read = getMinFd();
        String command = emulator.get("command");
        System.out.println("sana pipe2 command-->>" + command);
        // stdout中写入popen command 应该返回的结果
        String stdout = "\n";
        switch (command) {
            // 这里根据command来判断 给它返回shell里的结果 
            case "stat /data": {
                stdout = "  File: /data\n" +
                        "  Size: 4096     Blocks: 16      IO Blocks: 512 directory\n" +
                        "Device: 10305h/66309d    Inode: 2        Links: 53\n" +
                        "Access: (0771/drwxrwx--x)       Uid: ( 1000/  system)   Gid: ( 1000/  system)\n" +
                        "Access: 1970-01-20 05:08:00.000000000 +0800\n" +
                        "Modify: 2024-11-19 21:13:13.707334006 +0800\n" +
                        "Change: 1970-05-22 00:15:12.503333433 +0800";
            }
        }

        this.fdMap.put(read, new ByteArrayFileIO(0, "pipe2_read_side", stdout.getBytes()));
        pipefd.setInt(0, read);
        pipefd.setInt(4, write);
        context.setR0(0);
        return 0;
    }

    // 补足相应的系统调用报错 根据调用号查看
    private void vfork(Emulator<?> emulator) {
        EditableArm32RegisterContext context = emulator.getContext();
        int childPid = emulator.getPid() +
                ThreadLocalRandom.current().nextInt(256);
        int r0 = childPid;
        System.out.println("vfork pid-->>" + r0);
        context.setR0(r0);
    }

    // 补足相应的系统调用报错 根据调用号查看
    private void wait4(Emulator<?> emulator) {
        return;
    }
}
  • 尝试运行,看看会有什么反应;

image-20250327163524103

  • 大概截图一点,可以看到,还有多个case的情况,需要补齐,这里不管了,因为下一个异常已经出来了;
  • 另外,包括文件访问的处理也并没有做;
又是系统调用,依旧去查看,// 这是文件访问
[16:24:07 447]  INFO [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:1122) - stat64 pathname=/data/data/com.microvirt.launcher, LR=RX@0x400024c9[libszstone.so]0x24c9

[16:24:07 456]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:534) - handleInterrupt intno=2, NR=54, svcNumber=0x0, PC=RX@0x40118aa8[libc.so]0x41aa8, LR=RX@0x4011e7f1[libc.so]0x477f1, syscall=null
java.lang.AbstractMethodError: com.github.unidbg.linux.file.TcpSocket: request=0x8927, argp=0xbffff470
    at com.github.unidbg.file.AbstractFileIO.ioctl(AbstractFileIO.java:57)
    at com.github.unidbg.linux.ARM32SyscallHandler.ioctl(ARM32SyscallHandler.java:2045)
    at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:210)
    at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:343)
    at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

4. ioctl

[16:24:07 456]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:534) - handleInterrupt intno=2, NR=54, svcNumber=0x0, PC=RX@0x40118aa8[libc.so]0x41aa8, LR=RX@0x4011e7f1[libc.so]0x477f1, syscall=null
java.lang.AbstractMethodError: com.github.unidbg.linux.file.TcpSocket: request=0x8927, argp=0xbffff470
    at com.github.unidbg.file.AbstractFileIO.ioctl(AbstractFileIO.java:57)
    at com.github.unidbg.linux.ARM32SyscallHandler.ioctl(ARM32SyscallHandler.java:2045)
    at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:210)
  • NR=54 对应 ioctl ,在 Android Native 里,它常用来获取 MAC 地址。它的参数 request用于指令操作类型,对于各种各样的文件和需求,有不同的request,Unidbg 首先预处理了其中的一部分,但这里报错的 0x8927 尚未处理;
  • 一个最简单的处理办法就是来到报错点com.github.unidbg.file.AbstractFileIO.ioctl(AbstractFileIO.java:64),添加判断request=0x8927 的情况,让其返回0;
@Override
public int ioctl(Emulator<?> emulator, long request, long argp) {
    // add ioctl start
    //        if (log.isTraceEnabled()) {
    //            emulator.attach().debug();
    //        }
    if (request == 0x8927){
        return 0;
    }
    // add ioctl end
    throw new AbstractMethodError(getClass().getName() + ": request=0x" + Long.toHexString(request) + ", argp=0x" + Long.toHexString(argp));
}
  • 根据龙哥的方式也可以:一个最简单的处理办法就是来到报错点com.github.unidbg.file.AbstractFileIO.ioctl(AbstractFileIO.java:64),将抛出异常改为直接返回 -1,即函数调用失败之意;摆烂可耻但有用;
@Override
public int ioctl(Emulator<?> emulator, long request, long argp) {
    if (log.isTraceEnabled()) {
        emulator.attach().debug();
    }
    return -1;
}
© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏
评论 抢沙发
OωO
取消