首页 > 技术文章 > android逆向奇技淫巧十九:unidbg模拟执行和trace x音so代码(四)

theseventhson 2021-08-01 23:26 原文

   静态分析sscronet和metasec_ml走不通了,换个思路继续搞!

    1、直接打开从内存dump的so,报错如下:

    

    本以为只是简单破坏了文件头,于是用010editor比对内存dump的so和安装包原始的so,看看哪些地方被破坏了,手动挨个恢复;结果发现两个so长度都不同了(结构都被改了),而且差一点有几十个,挨个恢复效率太低;

 

  继续从内存dump另一个mestasec_ml文件,用ida打开遇到同样问题,只能用010editor看看,结果大失所望:

  •   加密的字段还是没解密
  •        字段的位置都变了
  •        文件长度、结构同样都变了,这壳加的真是一绝!

    

   按照以往so加密的经验或套路:OLLVM对关键字符串加密,然后在init或init_array解密!这里从内存dump出来的应该是解密后的,但这里并未解密,可能的原因有:(1)使用时才解密,用完后又重新加密  (2)这些加密字段根本就不重要,是故意用来转移注意力的! 这里暂时不继续,换模拟器执行试试!

    2、用模拟器的本意是想hook内存写的代码,一旦发现写了内存就记录写的地址和内容,然后把这些内容写回原so文件,相当于打个补丁

  (1)这里直接使用看雪hanbingle大佬的补丁脚本(我加了些注释),如下:

import logging
import sys
import unicorn
import struct
from androidemu.emulator import Emulator
import binascii

# Configure logging
logging.basicConfig(
    stream=sys.stdout,
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)

logger = logging.getLogger(__name__)

# Initialize emulator
modify_map={}
emulator = Emulator(vfp_inst_set=True)
def UC_HOOK_MEM_WRITE(mu,type,address,size,value,user_data):
    #print("address:"+str(address))
    bytearray=struct.pack("I",value)[:size]#写入内存的内容转换成字节数组
    modify_map[address]=bytearray#放入map中
    logger.info("address:" + str(hex(address)) + "-------content:" + str(bytearray))
    if(size==1):#如果只改了一个字节,很有可能是解密字符串的,先打印看看
        logger.info(chr(value))
    return
filepath="example_binaries/libmetasec_ml.so"
#filepath="example_binaries/obf.so"
try:
    emulator.mu.hook_add(unicorn.UC_HOOK_MEM_WRITE,UC_HOOK_MEM_WRITE)#一旦写内存,就调用UC_HOOK_MEM_WRITE函数
    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)
    orisofile=open(filepath,'rb')
    content=orisofile.read()#读取原始so的内容
    orisofile.close()
    for i in modify_map:
        value=modify_map[i]
        base=lib_module.base
        if(i>=base and i<=(base+lib_module.size)):
            #代码段和数据段并不是连续存放的,还有0x1000的偏移
            offset=i-base-0x1000
            #原始的内容打断重新拼接
            content=content[:offset]+value+content[offset+len(value):]
            #logger.info("offset:"+offset+"-------content:"+binascii.b2a_hex(value))
            logger.info("offset:" + str(hex(offset)) + "-------content:" + str(value))
    fixso=filepath+".fix"
    fixfile=open(fixso,'wb')
    fixfile.write(content)
    fixfile.close()
except Exception as e:
    print(e)

  从androidNativeEmu日志看,init_array的函数在挨个执行了;但是执行到第三个的时候报错:unhandled syscall 0x142(322),原来还有系统调用的模拟还未覆盖到!想要继续执行,需要补全这个系统调用,这里暂时不管,换ExAndroidNativeEmu试试!

    

    ExAndroidNativeEmu遇到了内存错误:

      

     无奈继续换unidbg试试,原理同样是hook写内存的代码,一旦发现写了内存,立即记录写的位置和内容;执行的脚本参考文章末尾链接1;从执行日志看来:

  •   这两个so还有调用关系,metasec_ml调用了sscronet的3个函数,分别是Cronet_Engine_Create、Cronet_Engine_SetOpaque和Cronet_Engine_Destroy,既然都被调用了,可以用frida跟踪一下函数的调用栈
  •        还是因为内存中的so结构已经被改变,简单粗暴写回原位置已经不行了,代码一直报错!

     

   继续分析上面提到的那3个函数。第一个Create函数,F5的代码如下:函数没有参数,返回了v1;从代码看,v1貌似是个结构体,不同的偏移有不同的取值

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;
}

  所以这里Create函数的hook代码:

var Cronet_Engine_Create = Module.findExportByName("libsscronet.so","Cronet_Engine_Create")
    console.log("Cronet_Engine_Create in libsscronet.so addr:"+Cronet_Engine_Create)
    Interceptor.attach(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
                    }))
        }
    })

   正当我得意洋洋,想着马上就可以根据栈回溯找到metasec_ml中关键的函数位置时,上述这段代码运行报错,错误提示:Error: expected a pointer;于时逐行代码检查,发现函数地址是null;然后用这个代码查找导出函数,发现一个都没有,这就奇怪了:ida能找到导出函数,为啥frida就找不到了?

var exports = Module.enumerateExportsSync("libsscronet.so");
console.log("export:"+exports);

  继续用Module.findBaseAddress,发现连libsscronet.so的基址都是null;同理,libmetasec_ml.so的基址也是null;难道是我的api用错了?用同样的api,随机选了其他so文件,比如libc、libutils、libgui、libLLVM、libstagefright、libinput等都能找到基址,唯独这两个so找不到,个人猜测是故意的,不知道基址找不到是不是因为内存中soinfo结构体被更改导致(感觉有点像windows下pe结构体断链;这里我就不纠结了,直接手动通过cat /proc/pid/maps|grep sscronet方式获取到libsscronet的基址是0x0cc00000,再从ida里查到Cronet_Engine_Create的偏移是0xF2D0C,所以hook代码改为:

    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
                    }))
        }
    })

   然鹅遗憾的是并未按照预期打印函数的调用栈,估计是时机不对;

  回到unidbg:这3个函数调用的位置都在日志显示了,分别如下:

.text:00012F0C
.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}

  无一例外都发生在dlsym之后的STR语句,把dlsym的返回值从R0存放到内存中;这里有两个思路:(1)frida或ida看看寄存器的值  (2)unidbg来trace;这里选择用unidbg来trace(trace代码在文章末尾)!刚开始trace就遇到了问题:从日志看,代码已经进入jni_onload函数执行(我是想看看registerNative在哪)是在新生成线程,然后就一直卡这里了,没有任何新日志,不知道哪里出了问题!

   这里新建一个类,implements CodeHook接口,完成 public void hook(Backend backend, long address, int size, Object user)方法,把每条执行的代码地址都记录在案;重新开始后一共执行了大约15分钟,执行了约570万行代码,通过日志来看,发现卡住的元凶了:如下图,从107万行代码开始,一直在重复:貌似在等待互斥锁,如果没有得到就一直等!怪不得代码卡这里了;一不做二不休,直接NOP掉CMP和BNE这两行代码,然后继续用unidbg来trace,这次果然一次性绕过了这个“死循环”!(经多方打探和证实,这是由于unidbg不支持多线程导致的;哎,模拟环境终究还是模拟环境,比不了真机......)

  

  然后用参考链接1或2的idapython脚本给so的代码作色和打补丁(脚本用到了sark),把没用的代码都nop掉,恢复so文件最初是的样子,就能顺利F5和找jni函数了,结果遇到了一堆的问题!先是找不到sark,发现是ida引用的路径不对,所以手动用sys.path.append指定了sark的安装路径;接着又是dll load failed报错,按照很多人建议重新安装了python、然后用python3.dll替换,特么这次又报如下错误:

   

   这个错误花了大半天时间也没找到解决办法,目前暂时卡这里了,后续要么解决这个bug,要么换其他的思路继续跟踪!这次就暂时先到这里吧,我继续想想怎么搞idapyhton的这个bug!

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

  3、思路总结:

      (1)既然原始的so有很多加密的字符串,好多重要函数的栈也不平衡,ida无法静态分析,那就从内存dump呗!从内存dump出来后发现:

    • 字符串还是没解密(也有可能用的时候解密,用完后再次加密)
    • elf文件头也被破坏了,ida都没法分析
    • elf文件结构也变了(字符串的位置被挪动)

  (2)原始so的文件结构还没被破坏(如果改变,连操作系统的linker都不认识了,彻底没法加载),所以想着从这里突破:先用unidbg来trace,记录执行代码的地址;然后用ida的脚本打patch,把没有被执行过的代码都nop掉,这样来去掉OLLVM的混淆和被破坏的栈平衡!目前trace成功了,得到了代码执行的log,但是用idapython执行patch脚本时报错,需要解决这个bug!

     (3)这里还有一绝要再次提起:frida的api找不到sscronet和metasec_ml这两个so的基址,但是其他so却能找到!以前在windows下做外挂,为了躲避搜查,外挂的dll或驱动经常采用断链的方式让自己的各种结构体脱离操作系统管理的链表,第三方调用系统api查找时会遍历链表,这时就找不到了!不知道这里是不是用了同样的方式!

  (4)idapython代码(末尾参考链接1和2都有,原理都一样:通过sark标记)

# -*- coding: utf-8 -*-
import sark
import idc
import sys

#sys.path.append('D:\\BaiduNetdiskDownload\\douyin\\venv\\Lib\\site-packages')

def patch_nop(addr):
    '''
    将指定地址的字节修改成nop
    '''
    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:
                patch_nop(line.ea)

def do_ollvm_bcf():
    '''
    ollvm虚假控制流处理
    '''
    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__":
    do_ollvm_bcf()
    # patch_code()

  unidbg trace的代码(大部分参考末尾链接1的):

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 {
        DynarmicLoader.useDynarmic();
    }


    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();
        vm.setVerbose(true);
        vm.setJni(this);

        try {
            trace = new DeStrWriteHook(false);

            final Memory memory=emulator.getMemory();
            //这里的android版本指定后会自动load相关的so
            LibraryResolver resolver = new AndroidResolver(23);
            memory.setLibraryResolver(resolver);
            //设置内存写入的监控
            emulator.getBackend().hook_add_new(trace,1,0,emulator);

            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.getBackend().hook_add_new(codeTrace,module.base+0x7c50,module.base+0x84170,emulator);
            /*emulator.traceCode(module.base + 0x7c50, module.base + 0x84170, new TraceCodeListener() {
                @Override
                public void onInstruction(Emulator<?> emulator, long address, Capstone.CsInsn insn) {
                    DeCodeHook.write2file("\\unidbg-master\\libs\\traceA.log"
                            ,Long.toHexString(address-module.base));
                }
            });*/


            log.info("---------------before call jni_onload");
            vm.callJNI_OnLoad(emulator,module);
            log.info("---------------after call jni_onload");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    public static void writeFile(byte[] data,String savefile){
        try {
            FileOutputStream fos=new FileOutputStream(savefile);
            BufferedOutputStream bos=new BufferedOutputStream(fos);
            bos.write(data,0,data.length);
            bos.flush();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

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

    public static void main(String[] args){
        decryptOLLVM destr=new decryptOLLVM();
        //因为so文件的结构发生变化,简单粗暴地写回原so文件地址已经不行了,会报错;所以这里以下的代码都可以注释掉,没必要了(其他没改文件结构的so是可以直接用来打patch的);
        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()];
                System.arraycopy(sodata,0,start,0,offset.intValue());
                //然后把后半部分的大小计算出来
                int endsize=sodata.length-offset.intValue()-item.getValue().length;
                //把后半部分的数据填充上
                byte[] end=new byte[endsize];
                System.arraycopy(sodata,offset.intValue()+item.getValue().length,end,0,endsize);
                //将三块位置的数据填充上
                baos.write(start,0,start.length);
                baos.write(item.getValue(),0,item.getValue().length);
                baos.write(end,0,end.length);
                //最后把so保存起来
                sodata=baos.toByteArray();
            }
        }
        writeFile(baos.toByteArray(),savepath);
    }
}
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){
        this.base=base;
    }

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


    @Override
    public void hook(Backend backend, long address, int size, Object user) {
        // 打印当前指令地址,注意需要将实际地址减去so基地址得到代码在文件中的偏移
        //System.out.println(String.format("0x%x",address-this.base));
        write2file("\\unidbg-master\\libs\\trace.log",String.format("0x%x\r\n",address-this.base));
    }

    @Override
    public void onAttach(Unicorn.UnHook unHook) {

    }

    @Override
    public void detach() {

    }
}
package com.unicorncourse08;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.BackendException;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.BackendException;
import com.github.unidbg.arm.backend.ReadHook;
import com.github.unidbg.arm.backend.WriteHook;//这才是正确的类,unicorn.WriteHook是错的
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.PrintStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/*
* unidbg-master\\unidbg-api\\src\\main\\jav\a\com\\github\\unidbg\\TraceMemoryHook.java  有demo代码
* */
class DeStrWriteHook implements WriteHook {

    private final boolean read;
    private Unicorn.UnHook unHook;

    DeStrWriteHook(boolean read) {
        this.read = read;
    }
    PrintStream redirect;
    TraceWriteListener traceWriteListener;
    //保存的写入数据地址和写入的数据
    public Map<Long,byte[]> dstr_datas=new HashMap<Long,byte[]>();

    /**
     * long类型转byte[] (大端)
     * @param n
     * @return
     */
    public static byte[] longToBytesBig(long n) {
        byte[] b = new byte[8];
        b[7] = (byte) (n & 0xff);
        b[6] = (byte) (n >> 8  & 0xff);
        b[5] = (byte) (n >> 16 & 0xff);
        b[4] = (byte) (n >> 24 & 0xff);
        b[3] = (byte) (n >> 32 & 0xff);
        b[2] = (byte) (n >> 40 & 0xff);
        b[1] = (byte) (n >> 48 & 0xff);
        b[0] = (byte) (n >> 56 & 0xff);
        return b;
    }
    /**
     * long类型转byte[] (小端)
     * @param n
     * @return
     */
    public static byte[] longToBytesLittle(long n) {
        byte[] b = new byte[8];
        b[0] = (byte) (n & 0xff);
        b[1] = (byte) (n >> 8  & 0xff);
        b[2] = (byte) (n >> 16 & 0xff);
        b[3] = (byte) (n >> 24 & 0xff);
        b[4] = (byte) (n >> 32 & 0xff);
        b[5] = (byte) (n >> 40 & 0xff);
        b[6] = (byte) (n >> 48 & 0xff);
        b[7] = (byte) (n >> 56 & 0xff);
        return b;
    }

    @Override
    public void hook(Backend backend, long address, int size, long value, Object user) {
        if (read) {
            return;
        }
        try {
            Emulator<?> emulator = (Emulator<?>) user;
            if (traceWriteListener == null || traceWriteListener.onWrite(emulator, address, size, value)) {
                //将写入的地址和写入的数据保存下来,先转long为小端序
                byte[] writedata=longToBytesLittle(value);
                byte[] resizeWriteData=new byte[size];
                //将指定大小的数据保存
                System.arraycopy(writedata,0,resizeWriteData,0,size);
                dstr_datas.put(address,resizeWriteData);
                /*if(size==1){
                    System.out.println(String.format("address:0x%x,content:%s",address,bytetoString(writedata)));
                }*/
                //System.out.println("address:"+Long.toHexString(address)+"---------content:"+  Long.toString(value));
            }
        } catch (BackendException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public void onAttach(Unicorn.UnHook unHook) {
        if (this.unHook != null) {
            throw new IllegalStateException();
        }
        this.unHook = unHook;
    }

    @Override
    public void detach() {
        if (unHook != null) {
            unHook.unhook();
            unHook = null;
        }
    }

    //https://www.cnblogs.com/Free-Thinker/p/4694854.html
    public static String bytetoString(byte[] bytearray) {
        String result = "";
        char temp;

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

   4.  最后:静态分析查看metasec_ml的导入函数时发现了很多字符串相关的操作api,如下:好多都是字符串操作的系统api,会不会用这些api操作字符串了?

   选了几个常用的api hook,代码如下:

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

  很遗憾也没发现那4个关键字段(也有可能是我hook的api少了,需要继续尝试其他的api);

 

参考:

1、http://missking.cc/2020/11/03/unicorn2/  unidbg执行so中的init_array

2、https://blog.csdn.net/weixin_46734340/article/details/118857038   Android逆向-实战so分析-某洲_v3.5.8_unidbg学习

3、https://bbs.pediy.com/thread-266999.htm    分析unidbg(unidbgMutil)多线程机制

推荐阅读