unidbg simulation execution



The static analysis of sscronet and metasec_ml can no longer work, so change your thinking and continue to look for it.
• Open the .so dumped file from the memory directly, and the error is reported as follows:

I thought it was just a simple destruction of the file header, so I used 010editor to compare the so of the memory dump with the original .so of the installation package to see which parts were damaged, and manually restore them one by one. It turns out that the lengths of the two SOs are different (the structure has been changed), and there are almost dozens of them, and the recovery efficiency of each is too low.

Continue to dump another mestasec_ml file from the memory, and open it with ida and encounter the same problem. I can only use 010editor to see it. The result is disappointing:

The encrypted field is still not decrypted
The positions of the fields have changed
The file length and structure have also changed. This shell is really amazing!

According to the previous experience or routine of so encryption: OLLVM encrypts the key string, and then decrypts it in init or init_array. The dump from the memory here should be decrypted, but it is not decrypted here. The possible reasons are: (1) It is decrypted when it is used, and re-encrypted after it is used up (2) These encrypted fields are not important at all, they are deliberate Used to divert attention. Don't continue here for the time being, try changing the simulator to execute it.

•	The original intention of using the simulator is to hook the code written in the memory. Once the memory is found to be written, it will record the written address and content, and then write the content back to the original so file, which is equivalent to applying a patch.

•	A patch script is used directly here (I added some comments). As follows:
import logging
import sys
import unicorn
import struct
from androidemu.emulator import Emulator
import binascii

# Configure logging
    format="%(asctime)s %(levelname)7s %(name)34s | %(message)s"

logger = logging.getLogger(__name__)

# Initialize emulator
emulator = Emulator(vfp_inst_set=True)
def UC_HOOK_MEM_WRITE(mu,type,address,size,value,user_data):
    logger.info("address:" + str(hex(address)) + "-------content:" + str(bytearray))
    emulator.load_library("./example_binaries/libc.so", do_init=False)
    emulator.load_library("./example_binaries/libdl.so", do_init=False)
    emulator.load_library("./example_binaries/libstdc++.so", do_init=False)
    emulator.load_library("./example_binaries/libm.so", do_init=False)
    lib_module = emulator.load_library(filepath, do_init=True)
    for i in modify_map:
        if(i>=base and i<=(base+lib_module.size)):
            logger.info("offset:" + str(hex(offset)) + "-------content:" + str(value))
except Exception as e:

From the androidNativeEmu log, the functions of init_array are executed one by one, but when the third one is executed, an error is reported: unhandled syscall 0x142 (322). It turns out that the simulation of system calls has not been covered yet. If you want to continue execution, you need to complete this system call. Don't care about it here, try ExAndroidNativeEmu instead.

ExAndroidNativeEmu encountered a memory error:

Helplessly continue to change unidbg to try. The principle is also to hook the code that writes the memory. Once it is found that the memory is written, immediately record the location and content of the write. From the execution log:

   These two so still have a calling relationship. Metasec_ml calls 3 functions of sscronet, namely Cronet_Engine_Create, Cronet_Engine_SetOpaque and Cronet_Engine_Destroy. Now that they are all called, you can use frida to trace the call stack of the function.
Or because the so structure in the memory has been changed, it is no longer possible to simply write it back to the original location, and the code keeps reporting errors.

Continue to analyze the 3 functions mentioned above. The first Create function, the code of F5 is as shown in the figure below. The function has no parameters and returns v1. From the code point of view, v1 looks like a structure, and different offsets have different values.

int Cronet_Engine_Create()
  int v0; // r0
  int v1; // r4
  _QWORD *v2; // r0
  _QWORD *v3; // r0

  v0 = sub_143D48(356);
  v1 = v0;
  *(_DWORD *)(v0 + 256) = 30000;
  *(_BYTE *)(v0 + 252) = 1;
  *(_BYTE *)(v0 + 16) = 0;
  *(_BYTE *)(v0 + 12) = 0;
  *(_DWORD *)v0 = &off_277914;
  *(_DWORD *)(v0 + 4) = 0;
  *(_DWORD *)(v0 + 8) = 0;
  sub_1432BA(v0 + 260);
  *(_BYTE *)(v1 + 276) = 1;
  *(_DWORD *)(v1 + 264) = 0;
  *(_DWORD *)(v1 + 268) = 0;
  *(_DWORD *)(v1 + 272) = 0;
  sub_1432BA(v1 + 280);
  *(_DWORD *)(v1 + 296) = sub_F2DF6;
  *(_DWORD *)(v1 + 300) = &unk_67B9C;
  *(_DWORD *)(v1 + 284) = 0;
  sub_143302(v1 + 304, 0, 1);
  *(_BYTE *)(v1 + 312) = 0;
  sub_143302(v1 + 316, 0, 1);
  v2 = (_QWORD *)(v1 + 340);
  *v2 = 0LL;
  v2[1] = 0LL;
  v3 = (_QWORD *)(v1 + 324);
  *v3 = 0LL;
  v3[1] = 0LL;
  return v1;

So here is the hook code of the Create function:

var Cronet_Engine_Create = Module.findExportByName("libsscronet.so","Cronet_Engine_Create")
    console.log("Cronet_Engine_Create in libsscronet.so addr:"+Cronet_Engine_Create)
        onEnter: function (args) {
            console.log('called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n')
        },onLeave: function (retval){
            console.log("Cronet_Engine_Create retval:"+hexdump(retval, {//dump指定内存
                    offset: 0,   //从何处开始
                    length: 400, //长度
                    header: true,
                    ansi: true //是否是ansi

Just when I was thinking that I could find the key function position in metasec_ml according to the stack trace, the above code reported an error, and the error message was: Error: expected a pointer. When checking the code line by line, it was found that the function address was null. Then I used this code to find the exported function and found that there was no one. This is strange, ida can find the exported function, why can't frida not find it?

var exports = Module.enumerateExportsSync("libsscronet.so");

Continue to use Module.findBaseAddress and found that even the base address of libsscronet.so is null. Similarly, the base address of libmetasec_ml.so is also null. Could it be that my api is wrong? Using the same api, randomly selected other so files, such as libc, libutils, libgui, libLLVM, libstagefright, libinput, etc., can find the base address, but these two so can not be found, personal guess is deliberate. I don't know if the base address is not found because the soinfo structure in the memory is changed (it feels a bit like the broken link of the pe structure under windows). Here I don't have to worry about it anymore, I directly get the base address of libsscronet by cat /proc/pid/maps|grep sscronet to be 0x0cc00000, and then check the offset of Cronet_Engine_Create from ida to 0xF2D0C. So the hook code is changed to:

var Cronet_Engine=0x0cc00000+0xF2D0C
    var Cronet_Engine_Create = new NativePointer(Cronet_Engine)
    console.log("Cronet_Engine_Create in libsscronet.so addr:"+Cronet_Engine_Create)
    Interceptor.attach(Cronet_Engine_Create, {
    //Interceptor.attach(Create_addr, {
        onEnter: function (args) {
            console.log('called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n')
        },onLeave: function (retval){
            console.log("Cronet_Engine_Create retval:"+hexdump(retval, {//dump指定内存
                    offset: 0,   //从何处开始
                    length: 400, //长度
                    header: true,
                    ansi: true //是否是ansi

Unfortunately, the call stack of the function is not printed as expected. It is estimated that the timing is wrong.

Back to unidbg, the locations of these 3 function calls are shown in the log, as follows:

.text:00012F0C                 loc_12F0C                               ; CODE XREF: sub_12EA0+40↑j
.text:00012F0C 018 29 6A                       LDR             R1, [R5,#(dword_9FD10 - 0x9FCF0)] ; name
.text:00012F0E 018 20 46                       MOV             R0, R4  ; handle
.text:00012F10 018 F4 F7 AE EC                 BLX             dlsym
.text:00012F14 018 69 6A                       LDR             R1, [R5,#(dword_9FD14 - 0x9FCF0)]
.text:00012F16 018 A8 60                       STR             R0, [R5,#(dword_9FCF8 - 0x9FCF0)]
.text:00012F18 018 D1 B9                       CBNZ            R1, loc_12F50
.text:00012F1A 018 18 20                       MOVS            R0, #0x18
.text:00012F1C 018 64 F0 1A FE                 BL              sub_77B54
.text:00012F50                 loc_12F50                               ; CODE XREF: sub_12EA0+78↑j
.text:00012F50 018 69 6A                       LDR             R1, [R5,#(dword_9FD14 - 0x9FCF0)] ; name
.text:00012F52 018 20 46                       MOV             R0, R4  ; handle
.text:00012F54 018 F4 F7 8C EC                 BLX             dlsym
.text:00012F58 018 A9 6A                       LDR             R1, [R5,#(dword_9FD18 - 0x9FCF0)]
.text:00012F5A 018 E8 60                       STR             R0, [R5,#(dword_9FCFC - 0x9FCF0)]
.text:00012F5C 018 A9 B9                       CBNZ            R1, loc_12F8A
.text:00012F5E 018 16 20                       MOVS            R0, #0x16
.text:00012F60 018 64 F0 F8 FD                 BL              sub_77B54
text:00012F8A                 loc_12F8A                               ; CODE XREF: sub_12EA0+BC↑j
.text:00012F8A 018 A9 6A                       LDR             R1, [R5,#(dword_9FD18 - 0x9FCF0)] ; name
.text:00012F8C 018 20 46                       MOV             R0, R4  ; handle
.text:00012F8E 018 F4 F7 70 EC                 BLX             dlsym
.text:00012F92 018 28 61                       STR             R0, [R5,#(dword_9FD00 - 0x9FCF0)]
.text:00012F94 018 5D F8 04 BB                 LDR.W           R11, [SP+0x10+var_10],#4
.text:00012F98 014 F0 BD                       POP             {R4-R7,PC}

Without exception, all occur in the STR statement after dlsym, and store the return value of dlsym from R0 to the memory. There are two ideas: (1) frida or ida to see the value of the register (2) unidbg to trace, choose here Use unidbg to trace. At the beginning of the trace, I encountered a problem: from the log, the code has entered the jni_onload function to execute (I want to see where registerNative is) is a newly generated thread, and then it has been stuck here, there is no new log, I don’t know what the problem is .

Create a new class here, implements CodeHook interface, complete the public void hook (Backend backend, long address, int size, Object user) method, and record the address of each executed code. After restarting, it executed a total of about 15 minutes and executed about 5.7 million lines of code. Judging from the log, it was found that the culprit was stuck. As shown in the figure below, starting from 1.07 million lines of code, it has been repeating, seemingly waiting for the mutex lock, if you don't get it, you will wait forever. No wonder the code is stuck here. NOP directly dropped the two lines of code, CMP and BNE, and then continued to use unidbg to trace. This time, it really bypassed this "definite loop" at one time!

Then use the idapython script to color and patch the so code (the script uses sark), remove all the useless codes, restore the original so file, and then F5 and find the jni function smoothly. But encountered a bunch of problems. sark couldn't be found, and the path referenced by ida was wrong, so I manually specified the installation path of sark with sys.path.append; then the dll load failed error was reported. According to many people’s suggestions, I reinstalled python and then used python3. dll replacement, and the following error is reported:

It took long time to find a solution for this error and failed. Either solve this bug in the follow-up, or change other ideas to continue tracking. I will continue to think about how to deal with this bug in idapyhton later.

from PyQt5 import QtCore, QtWidgets, QtGui
ImportError: DLL load failed: %1 不是有效的 Win32 应用程序。
  1. Summary:

(1) Since the original so has a lot of encrypted strings, the stacks of many important functions are not balanced, and ida cannot be statically analyzed, then dump it from the memory! After the memory dump came out, it was found:

The string is still not decrypted (it is also possible to decrypt it when it is used, and encrypt it again when it is used up)
The header of the elf file is also damaged, and ida cannot analyze it.
The elf file structure has also changed (the position of the string has been moved)

•	The original file structure of .so has not been destroyed (if it is changed, even the linker of the operating system will not be recognized and cannot be loaded at all), so I want to break through from here: first use unidbg to trace and record the address of the execution code . Then use ida's script to patch and nop all the code that has not been executed, so as to remove the confusion of OLLVM and the broken stack balance. At present, the trace is working, and the code execution log is obtained, but an error is reported when the patch script is executed with idapython, and this bug needs to be solved.

•	Here I want to mention it again: frida's api can't find the base addresses of the two sos, sscronet and metasec_ml, but other so can find them. When doing plug-ins under windows before, in order to avoid searches, plug-in dlls or drivers often use broken links to get their various structures out of the linked list managed by the operating system. The third party will traverse the linked list when calling the system api to search and can't find it. I don't know if the same method is used here.

•	idapython代码
•	idapython code
# -*- coding: utf-8 -*-
import sark
import idc
import sys


def patch_nop(addr):
    nop_code = [0x1f, 0x20, 0x03, 0xd5]
    for i in range(len(nop_code)):
        idc.patch_byte(addr+i, nop_code[i])

def patch_code():
    # 设置需要处理代码的起始地址和终止地址,并获取范围内所有汇编指令的地址
    s = 0x10CEC         # 目标方法起始地址
    e = 0x10CEC+0xFEC   # 目标方法结束地址
    funcLines = sark.lines(s, e)
    for line in funcLines:
        # 判断如果该行代码的颜色是我们标记的颜色,则进入patch逻辑,将代码patch为nop指令。
        if line.type == "code":
            if line.color != 0xEE82EE:

def do_ollvm_bcf():
    log_file = "trace.log"
    trace_addrs = []
    # 将trace指令的地址读取到trace_addrs列表中
    with open(log_file, "r") as f:
        lines = f.readlines()
        for line in lines:
            trace_addrs.append(int(line.replace("\n", ""), 16))
    for addr in trace_addrs:
        line = sark.line.Line(addr)
        line.color = 0xEE82EE   # 将执行过的指令通过修改颜色来标记出来。

if __name__ == "__main__":
    # patch_code()

unidbg trace 的代码
unidbg trace code

package com.unicorncourse08;
import capstone.Capstone;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.WriteHook;
import com.github.unidbg.arm.backend.dynarmic.DynarmicLoader;
import com.github.unidbg.linux.android.AndroidARMEmulator;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.listener.TraceCodeListener;
import com.github.unidbg.memory.Memory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import unicorn.Unicorn;

import java.io.*;
import java.util.Arrays;
import java.util.Map;

public class decryptOLLVM<mouldBase> extends AbstractJni {

    static {

    private AndroidEmulator emulator;
    private final VM vm;
    public DeStrWriteHook trace;
    public DeCodeHook codeTrace;
    public Module module;
    public static String filePath = " \\unidbg-master\\libs\\libmetasec_ml.so";
    public static String sscronet_filePath = " \\unidbg-master\\libs\\libsscronet.so";
    private static final Log log = LogFactory.getLog(DalvikModule.class);
    public decryptOLLVM(){
        emulator=new AndroidARMEmulator("com.ss.android.ugc.aweme",null,null);
        vm = emulator.createDalvikVM();

        try {
            trace = new DeStrWriteHook(false);

            final Memory memory=emulator.getMemory();
            LibraryResolver resolver = new AndroidResolver(23);

            emulator.loadLibrary(new File(sscronet_filePath),true);
            emulator.loadLibrary(new File(" \\unidbg-master\\libs\\libttboringssl.so"));
            emulator.loadLibrary(new File(" \\unidbg-master\\libs\\libttcrypto.so"));
            emulator.loadLibrary(new File(" \\unidbg-master\\libs\\libandroid.so"));
            module = emulator.loadLibrary(new File(filePath),true);
            codeTrace = new DeCodeHook(module.base);
            // 添加一个指令集hook回调,并输出当前执行指令在so文件中的偏移地址
            /*emulator.traceCode(module.base + 0x7c50, module.base + 0x84170, new TraceCodeListener() {
                public void onInstruction(Emulator<?> emulator, long address, Capstone.CsInsn insn) {

            log.info("---------------before call jni_onload");
            log.info("---------------after call jni_onload");

        } catch (Exception e) {

    public static byte[] readFile(String strFile){
            InputStream is = new FileInputStream(strFile);
            int iAvail = is.available();
            byte[] bytes = new byte[iAvail];
            return bytes;
        }catch(Exception e){
        return null ;

    public static void writeFile(byte[] data,String savefile){
        try {
            FileOutputStream fos=new FileOutputStream(savefile);
            BufferedOutputStream bos=new BufferedOutputStream(fos);
        } catch (Exception e) {

    public static String bytetoString(byte[] bytearray) {
        String result = "";
        char temp;

        int length = bytearray.length;
        for (int i = 0; i < length; i++) {
                temp = (char) bytearray[i];
                result += temp;
        return result;

    public static void main(String[] args){
        decryptOLLVM destr=new decryptOLLVM();
        String savepath="D:\\BaiduNetdiskDownload\\unidbg-master\\libs\\libnative-lib_new.so";
        * sodata这个是从文件读取的,但实际在内存中的so长度比文件读取的长,造成了下面System.arraycopy(sodata,0,start,0,offset.intValue())报indexoutofbond错误;
        * */
        byte[] sodata=readFile(filePath);
        long base_addr=destr.module.base;
        long module_size=destr.module.size;
        System.out.println(String.format("base_addr:0x%x module_size:%s end_addr:0x%x", base_addr, module_size, base_addr+module_size));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        for(Map.Entry<Long, byte[]> item : destr.trace.dstr_datas.entrySet()){
            if(item.getKey()>base_addr && item.getKey()<base_addr+module_size){

                baos = new ByteArrayOutputStream();
                Long offset=item.getKey()-base_addr-0x1000;
                System.out.println(String.format("offset:0x%x----data:%s----data:%s",offset,bytetoString(item.getValue()), Arrays.toString(item.getValue())));
                byte[] start=new byte[offset.intValue()];
                //int diffLen = start.length - sodata.length;//得到磁盘so长度和内存so长度差值
                //byte[] sodata = new byte[offset.intValue()];
                int endsize=sodata.length-offset.intValue()-item.getValue().length;
                byte[] end=new byte[endsize];
package com.unicorncourse08;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.*;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.listener.TraceReadListener;
import com.github.unidbg.listener.TraceWriteListener;

import com.github.unidbg.listener.TraceWriteListener;
import unicorn.Unicorn;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class DeCodeHook implements CodeHook{
    long base;
    public DeCodeHook(long base){

    public static void write2file(String fileName, String content) {
        try {
            // 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件
            FileWriter writer = new FileWriter(fileName, true);
        } catch (IOException e) {

    public void hook(Backend backend, long address, int size, Object user) {
        // 打印当前指令地址,注意需要将实际地址减去so基地址得到代码在文件中的偏移

    public void onAttach(Unicorn.UnHook unHook) {


    public void detach() {

  1. At the last: Static analysis found a lot of string-related operation APIs when looking at the import function of metasec_ml. As follows: Many system apis are used for string manipulation. Will these api be used to manipulate strings?

Several commonly used api hooks are selected and the code as follow:

var malloc_addr = Module.findExportByName("libc.so","malloc")
    console.log("malloc in libc.so addr:"+malloc_addr)
        onEnter: function (args) {
            var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n')
            if(backtrace.indexOf("metasec") >= 0||backtrace.indexOf("sscronet") >= 0){
                console.log('called from:\n' +backtrace + '\n')
            //console.log('called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n')
        },onLeave: function (retval){
            console.log("retval:"+hexdump(retval, {//dump指定内存
                    offset: 0,   //从何处开始
                    length: Number(this.len), //长度
                    header: true,
                    ansi: false //是否是ansi
    var memcmp_addr = Module.findExportByName("libc.so","memcmp")
    console.log("memcmp in libc.so addr:"+memcmp_addr)
        onEnter: function (args) {
            console.log('called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n')
        },onLeave: function (retval){
    var memcpy_addr = Module.findExportByName("libc.so","memcpy")
    console.log("memcpy in libc.so addr:"+memcpy_addr)
        onEnter: function (args) {
            var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n')
            if(backtrace.indexOf("metasec") >= 0||backtrace.indexOf("sscronet") >= 0){
                console.log('called from:\n' +backtrace + '\n')
                console.log("args[1]:"+hexdump(args[1], {//dump指定内存
                    offset: 0,   //从何处开始
                    length: args[2].toInt32(), //长度
                    header: true,
                    ansi: true //是否是ansi

        },onLeave: function (retval){

Unfortunately, I didn’t find those 4 key fields.

Tiktok Reverse