Unidbg Trace 反 OLLVM 控制流平坦化(fla)

版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/

目标 so 分析

目标方法反汇编视图如下

F5 反汇编代码如下,很明显通过 fla 隐藏了真实的执行流

ini 复制代码
__int64 __usercall dynamicBase64Encode@<X0>(const unsigned __int8 *a1@<X0>, unsigned __int64 a2@<X1>, __int64 a3@<X8>)
{
  int v3; // w8
  int v4; // w8
  unsigned __int8 *v5; // x0
  int v6; // w8
  unsigned __int8 *v7; // x0
  __int64 result; // x0
  int i; // [xsp+20h] [xbp-60h]
  unsigned __int64 v11; // [xsp+30h] [xbp-50h]
  int v12; // [xsp+3Ch] [xbp-44h]
  int v13; // [xsp+40h] [xbp-40h]
  char v16[24]; // [xsp+60h] [xbp-20h] BYREF
  __int64 v17; // [xsp+78h] [xbp-8h]

  v17 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  generateDynamicBase64Alphabet(a2);
  sub_29574(a3);
  v13 = 0;
  v12 = -6;
  v11 = 0LL;
  do
  {
    if ( v11 >= a2 )
      v3 = 491;
    else
      v3 = 26962;
    for ( i = v3; ; i = 24464 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            while ( 1 )
            {
              while ( 1 )
              {
                while ( 1 )
                {
                  while ( i == 491 )
                  {
                    if ( v12 <= -6 )
                      v6 = 4827;
                    else
                      v6 = 2995;
                    i = v6;
                  }
                  if ( i != 2995 )
                    break;
                  v7 = (unsigned __int8 *)sub_2E280(v16, (v13 << 8 >> (v12 + 8)) & 0x3F);
                  std::string::push_back(a3, *v7);
                  i = 4827;
                }
                if ( i != 4827 )
                  break;
                i = 32391;
              }
              if ( i != 5705 )
                break;
              v5 = (unsigned __int8 *)sub_2E280(v16, (v13 >> v12) & 0x3F);
              std::string::push_back(a3, *v5);
              v12 -= 6;
              i = 24464;
            }
            if ( i != 9961 )
              break;
            ++v11;
            i = 29358;
          }
          if ( i != 16827 )
            break;
          i = 9961;
        }
        if ( i != 24464 )
          break;
        if ( v12 < 0 )
          v4 = 16827;
        else
          v4 = 5705;
        i = v4;
      }
      if ( i != 26962 )
        break;
      v13 = a1[v11] | (v13 << 8);
      v12 += 8;
    }
  }
  while ( i == 29358 );
  result = std::string::~string(v16);
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return result;
}

unidbg 中调用目标 so 函数

把 so 放到 resource 目录下

关于 unidbg 的详细介绍可以参考这篇文章:unidbg 加载 so 并调用 so 中函数

通过 unidbg 加载 so 并执行 so 中目标函数

java 复制代码
package com.cyrus.example;

import com.alibaba.fastjson.util.IOUtils;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
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.linux.android.dvm.jni.ProxyClassFactory;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class TraceDemo {

    public static void main(String[] args) {
        TraceDemo demo = new TraceDemo();
        demo.run();
        demo.destroy();
    }

    private void destroy() {
        IOUtils.close(emulator);
    }

    private final AndroidEmulator emulator;

    private TraceDemo() {
        // 创建一个 64 位的 Android 模拟器实例
        emulator = AndroidEmulatorBuilder
                .for64Bit()  // 设置为 64 位模拟
                .setProcessName("com.cyrus.example") // 进程名称
                .build();    // 创建模拟器实例

        // 获取模拟器的内存实例
        Memory memory = emulator.getMemory();

        // 创建一个库解析器,并设置 Android 版本为 23(Android 6.0)
        LibraryResolver resolver = new AndroidResolver(23);

        // 将库解析器设置到模拟器的内存中,确保加载库时能够解析符号
        memory.setLibraryResolver(resolver);

    }

    public void run() {
        // 创建一个 Dalvik 虚拟机实例
        VM vm = emulator.createDalvikVM();
        // 启用虚拟机的调试输出
        vm.setVerbose(true);

        vm.setDvmClassFactory(new ProxyClassFactory());

        // 加载 so 到 Dalvik 虚拟机中,并设置为需要自动初始化库
        DalvikModule module = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/cyrus/lib_ollvm_fla_base64.so"), true);

        // 调用 JNI_Onload
        module.callJNI_OnLoad(emulator);

        // 注册 Base64Activity 类
        DvmClass dvmClass = vm.resolveClass("com/cyrus/example/base64/Base64Activity");
        
        // 创建 Java 对象
        DvmObject object = dvmClass.newObject(null);
        
        // 调用 jni 方法 dynamicBase64Encode 并传参 ByteArray
        DvmObject result = object.callJniMethodObject(emulator, "dynamicBase64Encode([B)Ljava/lang/String;", "lskvIIF".getBytes());

        System.out.println("result:" + result);
    }
}

unidbg trace

在 callJniMethodObject 方法之前添加如下代码,trace 指令执行和内存读写,并把 trace log 输出到文件。

ini 复制代码
// 日志保存到文件
PrintStream fileOut;
try {
    // 获取当前时间,格式化为 yyyyMMdd_HHmmss
    String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String filename = "trace_log_" + timestamp + ".txt";

    fileOut = new PrintStream(new FileOutputStream(filename));
} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
}
System.setOut(fileOut);
System.setErr(fileOut);

// trace 指令
emulator.traceCode();
// trace 内存读取
emulator.traceRead();
// trace 内存写入
emulator.traceWrite();

得到 trace log,包含指令流、寄存器变化、内存读写信息

算法还原

根据真实的执行指令流去还原算法执行过程

ini 复制代码
#include <cstdint>
#include <cstdlib>
#include <string>
#include <cstring>

alignas(16) const __int128 xmmword_14B00 = (__int128) 0x4000000000000000LL << 64 | 0x51; // 全局 128-bit 常量 xmmword_14B00
alignas(16) __int128 xmmword_54C30;  // / 全局 128-bit 变量 xmmword_54C30
char *qword_54C40 = nullptr;         // 存储 Base64 码表指针


void sub_23F30() {
    // 分配 80 字节(0x50)内存
    char *v0 = new char[0x50];

    // 复制 xmmword_14B00 的内容到 xmmword_54C30
    xmmword_54C30 = xmmword_14B00;

    // 保存 Base64 码表地址
    qword_54C40 = v0;

    // 复制标准 Base64 码表到 v0
    strcpy(v0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
}


std::string dynamicBase64Encode(const unsigned __int8 *a1, __int64 a2) {

    // 输出
    std::string a3;

    __int128 *v6; // x0
    __int128 v7; // q0
    __int64 v8; // x8
    char *v9; // x12
    __int64 v10; // x14
    char v11; // w12
    char *v12; // x11
    __int64 v13; // x26
    int v14; // w25
    int v15; // w8
    char *v16; // x27
    unsigned int v17; // w28
    int v18; // w8
    char *v19; // x9
    __int128 v20; // [xsp+0h] [xbp-20h] BYREF
    void *v21; // [xsp+10h] [xbp-10h]
    __int64 v22; // [xsp+18h] [xbp-8h]

    // 访问 CPU 状态信息
    // v22 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);

    v6 = (__int128 *) operator new(0x50uLL);

    *(__int64 *) &v7 = 0x2020202020202020LL;
    *((__int64 *) &v7 + 1) = 0x2020202020202020LL;

    v8 = 0LL;
    v21 = v6;
    *((uint8_t *) v6 + 64) = 0;
    *v6 = v7;
    v6[1] = v7;
    v6[2] = v7;
    v6[3] = v7;
    v20 = xmmword_14B00;
    do {
        v9 = (char *) qword_54C40;
        v10 = a2 & 0x3F ^ v8;
        if ((xmmword_54C30 & 1) == 0)
            v9 = (char *) &xmmword_54C30 + 1;
        v11 = v9[v8++];
        if ((v20 & 1) != 0)
            v12 = (char *) v21;
        else
            v12 = (char *) &v20 + 1;
        v12[v10] = v11;
    } while (v8 != 64);

//    *a3 = 0LL;
//    a3[1] = 0LL;
//    a3[2] = 0LL;
    a3.clear(); // 清空字符串

    if (a2) {
        v13 = 0LL;
        v14 = 0;
        v15 = -6;
        if ((v20 & 1) != 0)
            v16 = (char *) v21;
        else
            v16 = (char *) &v20 + 1;
        do {
            v14 = a1[v13] | (v14 << 8);
            if (v15 < -8) {
                v15 += 8;
            } else {
                v17 = v15 + 14;
                do {
                    v17 -= 6;
                    a3.push_back((unsigned __int8) v16[(v14 >> v17) & 0x3FLL]);
                } while (v17 > 5);
                v15 = v17 - 6;
            }
            ++v13;
        } while (v13 != a2);
        if ((unsigned int) v15 > 0xFFFFFFFA) {
            v18 = v14 << 8 >> (v15 + 8);
            if ((v20 & 1) != 0)
                v19 = (char *) v21;
            else
                v19 = (char *) &v20 + 1;
            a3.push_back((unsigned __int8) v19[v18 & 0x3E]);
        }
    }
    if ((v20 & 1) != 0)
        operator delete(v21);

    return a3;
}


int main() {
    // 初始化全局变量
    sub_23F30();

    // 要编码的原始数据
    std::string input = "lskvIIF";

    // 调用 Base64 编码函数
    std::string result = dynamicBase64Encode(reinterpret_cast<const uint8_t*>(input.c_str()), input.size());

    printf("input: %s\n", input.c_str());
    printf("result: %s", result.c_str());
    return 0;
}

输出如下:

完整源码

java 复制代码
package com.cyrus.example;

import com.alibaba.fastjson.util.IOUtils;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
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.linux.android.dvm.jni.ProxyClassFactory;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TraceDemo {

    public static void main(String[] args) {
        TraceDemo demo = new TraceDemo();
        demo.run();
        demo.destroy();
    }

    private void destroy() {
        IOUtils.close(emulator);
    }

    private final AndroidEmulator emulator;

    private TraceDemo() {
        // 创建一个 64 位的 Android 模拟器实例
        emulator = AndroidEmulatorBuilder
                .for64Bit()  // 设置为 64 位模拟
                .setProcessName("com.cyrus.example") // 进程名称
                .build();    // 创建模拟器实例

        // 获取模拟器的内存实例
        Memory memory = emulator.getMemory();

        // 创建一个库解析器,并设置 Android 版本为 23(Android 6.0)
        LibraryResolver resolver = new AndroidResolver(23);

        // 将库解析器设置到模拟器的内存中,确保加载库时能够解析符号
        memory.setLibraryResolver(resolver);
    }

    public void run() {
        // 创建一个 Dalvik 虚拟机实例
        VM vm = emulator.createDalvikVM();
        // 启用虚拟机的调试输出
        vm.setVerbose(true);

        vm.setDvmClassFactory(new ProxyClassFactory());

        // 加载 so 到 Dalvik 虚拟机中,并设置为需要自动初始化库
        DalvikModule module = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/cyrus/lib_ollvm_fla_base64.so"), true);

        // 调用 JNI_Onload
        module.callJNI_OnLoad(emulator);

        // 注册 Base64Activity 类
        DvmClass dvmClass = vm.resolveClass("com/cyrus/example/base64/Base64Activity");

        // 创建 Java 对象
        DvmObject object = dvmClass.newObject(null);


        // 日志保存到文件
        PrintStream fileOut;
        try {
            // 获取当前时间,格式化为 yyyyMMdd_HHmmss
            String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
            String filename = "trace_log_" + timestamp + ".txt";

            fileOut = new PrintStream(new FileOutputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        System.setOut(fileOut);
        System.setErr(fileOut);

        // trace 指令
        emulator.traceCode();
        // trace 内存读取
        emulator.traceRead();
        // trace 内存写入
        emulator.traceWrite();

        // 调用 jni 方法 dynamicBase64Encode 并传参 ByteArray
        DvmObject result = object.callJniMethodObject(emulator, "dynamicBase64Encode([B)Ljava/lang/String;", "lskvIIF".getBytes());

        System.out.println("result:" + result);
    }

}

完整源码地址:github.com/CYRUS-STUDI...

相关推荐
雨白11 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk11 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING12 小时前
RN容器启动优化实践
android·react native
恋猫de小郭14 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker20 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴20 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农2 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos