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...

相关推荐
还鮟2 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡3 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi003 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil5 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你5 小时前
Android View的绘制原理详解
android
移动开发者1号8 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号8 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best13 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk13 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭18 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin