某音35.2.0,简单记录结合AI对抖音自定义VMP进行编写VMP插件进行可视化还原的过程

1.逆向VMP结构

这部分是去年年底完成的,所以分析大部分是手动完成的,如今看来,AI应该只需要一个小时就可以完整还原整个VMP结构以及handle对应指令

post请求密文/ttEncrypt

可以hook 控制com.ss.android.common.applog.sendEncryptLog的方法

https://hyb.life/archives/207

1.确定这个方法对post请求进行了加密

加密方式是gzip+EncryptorUtil.ttEncrypt

2.逆向这个ttEncrypt方法

package com.bytedance.frameworks.encryptor;

import com.GlobalProxyLancet;
import com.bytedance.common.profilesdk.ProfileManager;
import com.bytedance.qss.common.catcher.CatcherTrace;

/* loaded from: classes.dex */
public class EncryptorUtil {
public static native byte[] ttEncrypt(byte[] bArr, int i);

static {
try {
GlobalProxyLancet.com_ss_android_ugc_aweme_lancet_LoadSoLancet_loadLibrary_com_ss_android_ugc_aweme_lancet_JatoBoostLancet_loadLibrary_com_ss_android_ugc_aweme_lancet_ExcludeSoLoadLancet_loadLibrary("Encryptor");
} catch (UnsatisfiedLinkError e) {
CatcherTrace.reportThrowable(e, "c.b.fr.encryptor.EncryptorUtil", "<clinit>", "java/lang/UnsatisfiedLinkError", ProfileManager.VERSION);
GlobalProxyLancet.com_ss_android_ugc_aweme_lancet_ThrowableLancet_thrPrintStackTrace(e);
}
}

public static byte[] LIZ(byte[] bArr, int i) {
if (bArr != null && i > 0) {
try {
if (bArr.length == i) {
return ttEncrypt(bArr, i);
}
} catch (Throwable th) {
CatcherTrace.reportThrowable(th, "c.b.fr.encryptor.EncryptorUtil", "enc1", "java/lang/Throwable", ProfileManager.VERSION);
}
}
return null;
}
}
var EncryptorUtil = Java.use("com.bytedance.frameworks.encryptor.EncryptorUtil");
EncryptorUtil["ttEncrypt"].implementation = function (bArr, i) {
console.log(`EncryptorUtil.ttEncrypt is called: bArr=${bArr}, i=${i}`);
let result = this["ttEncrypt"](bArr, i);
console.log(`EncryptorUtil.ttEncrypt result=${result}`);
return result;
};

hook,不过这里hook的时候是有frida检测的应该,这里我没有选择去找frida检测,可以直接正常启动,然后再把这个hook代码注释给取消掉,就可以正常hook到内容,,

接下来就可以分析so里面的内容然后进行同构了

可以看到这里是传入了数组和数组大小,返回大小应该是原本数大小+118

这里又把我们的输入绑定到一个数组,或者说结构体,然后传入这个vm虚拟化的函数

简直无从下手

unidbg trace

package Douyin_ttEncrypt;
import capstone.Capstone;
import capstone.api.Instruction;
import com.alibaba.fastjson.util.IOUtils;
import com.github.unidbg.*;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.UnHook;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.arm.context.Arm32RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.debugger.Debugger;
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.memory.Memory;
import com.sun.jna.Pointer;
import unicorn.ArmConst;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.annotation.Native;
import java.nio.charset.StandardCharsets;


public class Douyin_ttEncrypt extends AbstractJni {
private final AndroidEmulator emulator;
//模拟器对象,负责指令执行,内存管理,系统调用等
private final DvmClass NativeApi;
//调用的类
private final VM vm;
private final Module module;
//Dalvik VM环境,加载和运行APK/DEX代码
public Douyin_ttEncrypt(){
emulator=AndroidEmulatorBuilder.for64Bit()
.addBackendFactory(new Unicorn2Factory(true))//后端,指令执行引擎
.setProcessName("com.example.test")
.build();


Memory memory =emulator.getMemory();
//获取内存管理模块
memory.setLibraryResolver(new AndroidResolver(23));
vm= emulator.createDalvikVM(new File("D:\\Android\\DOUYIN\\douyin.apk"));
vm.setJni(this);
vm.setVerbose(true);
memory.setCallInitFunction(true);
memory.addModuleListener(new ModuleListener() {
@Override
public void onLoaded(Emulator<?> emulator, Module module) {
if(module.name.contains("Encryptor")){
String tracefile="D:\\Android\\DOUYIN\\trace.txt";
PrintStream pp= null;
try {
pp = new PrintStream(new FileOutputStream(tracefile),true);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
emulator.traceCode(module.base, module.base+module.size).setRedirect(pp);
}
}
});

//打印DAlivik调用日志
DalvikModule dm= vm.loadLibrary("Encryptor",true);
//addBreakPoint(address, callback)
NativeApi =vm.resolveClass("com.bytedance.frameworks.encryptor.EncryptorUtil");
//获取JAVA层类的引用
dm.callJNI_OnLoad(emulator);
//执行JNI_Onload
module = dm.getModule();
}
public byte[] calls(){
byte[] arg1=new byte[]{31,-117,8,0,0,0,0,0,0,0,-19,-108,-51,78,-61,48,12,-57,95,5,-7,-36,67,-57,62,-46,-11,-54,-103,39,64,40,114,91,-45,69,-22,-102,-30,-92,69,-45,-76,119,39,-3,100,-107,-42,49,-127,-128,105,-30,84,-57,78,-100,-65,127,113,-3,-76,7,-86,40,-73,16,66,81,102,-103,100,122,45,-55,88,-16,-96,64,-58,-83,-127,112,-33,6,84,2,-31,76,-84,-126,-27,98,-66,-14,-59,-3,122,-18,65,-100,41,119,82,90,-75,-91,113,108,-27,-127,50,-14,69,51,-91,-84,-53,-68,62,-39,120,48,-74,-86,-94,102,101,98,-54,-87,77,-22,-127,101,-107,-90,-60,-46,-18,-118,54,-118,-123,-110,-58,50,90,74,119,16,10,-47,31,112,42,-79,40,100,-122,101,30,111,106,-115,-75,-78,-118,-40,40,-99,-69,88,53,119,62,-116,-114,60,-93,-75,-52,84,-60,-40,122,-75,-111,9,89,84,89,119,41,96,-98,-80,118,122,14,7,-17,3,9,83,34,35,76,82,-110,102,-93,-33,70,80,78,-22,-23,-10,14,-54,103,-66,55,73,-81,-35,-101,-105,-37,-120,24,66,-73,-79,-66,-94,87,-45,61,-125,-84,22,-16,93,-106,63,-64,-24,-52,-45,-117,17,-65,-29,-106,114,95,83,102,-105,117,-42,103,21,119,-87,90,19,77,83,6,49,107,126,-48,9,-123,-2,93,99,63,-110,49,-104,82,104,-54,56,118,38,124,-95,-23,38,-53,12,-2,-88,33,-101,-4,-58,34,95,-58,-79,71,95,-121,-4,6,98,-1,127,-113,-48,77,16,58,-47,101,-65,80,-17,57,-22,-109,-51,117,-114,70,32,-124,-65,20,-30,100,-34,54,-74,-2,-97,87,-109,-13,106,-96,119,107,-13,-86,41,44,-104,125,103,94,13,108,-82,120,94,117,101,94,-11,-68,26,56,-34,-52,-68,26,-102,-21,-7,29,79,-113,63,86,97,9,0,0};
int i=387;
DvmObject<?> obj = NativeApi.callStaticJniMethodObject(
emulator,
"ttEncrypt([BI)[B",
arg1, i
);
byte[] ret = (byte[]) obj.getValue();
return ret;
}

public static void main(String[] args){
Douyin_ttEncrypt xv=new Douyin_ttEncrypt();

byte[] result=xv.calls();
System.out.println("result");

for(byte i:result) {
System.out.print(i+",");
}
}

}

成功trace出,接下来就是慢慢分析然后同构代码

先结合trace文件逆向一下每一个的分发块,handle试试

这个反编译代码给我的第一感觉就是这个不只有一个分发块??

通过cfg可以暂时确定主要分发块是这个

vmp handle

第一次逆vmp,一步一步来吧

回到sub_2AC4这个函数

注意到这里调用函数的时候传了5个参数,第五个参数是一个地址,是SP, #0x28

而这里很小的函数却开辟了0x520的栈空间,可以推测出后面的虚拟机函数会用到这个栈

分发器这个过程还需要挨个去读汇编,确定哪些位是干什么的

W11[2] : W12[28] .TEXT:0000000000002D04 W11,W16,#0X1A ;W11 ; W16 >> 0X1A,即右移26 LSR W15,W12,#0X2000000 ;W15 保留 W12第 30 位 .TEXT:0000000000002D08 AND W2,W14,W2; 或运算后,W2取 W12 第 13,14,15,32位 TEXT:0000000000002D0C ORR --> W11[0-1] 三 W12[26:27] TEXT:0000000000002D10 W11取W12第27,28位 W11, W12, #0X1A, #2 BFXIL W14保留W12第31位 TEXT:0000000000002D14 W14, W12, #0X4000000 AND 取 W12第13,14,15,16,32位 TEXT:0000000000002D18 ORR W10,W2,W10 ; W10取 TEXT:0000000000002D1C W11, W11, W15,LSR#26 J W11 - ORR 1]] (W15 >> 26) ---> W11[3] - W12[29] TEXT:00000000000002D20 X2,X3,X27 ; X2指向实际的跳转地址 ADD W9, W12, #0X15, #5 ; W9 - W12[21:25] .TEXT:0000000000002D24 UBFX UBFX W8 W12[16:20] .TEXT:000000000002D28 W8,W12,#0X10,#5 ; ; JUMPTABLE 0000000000002E94 CASE 58 W16 :W12 &0X800000 低31位清0,保留第3! TEXT:0000000000002D2C W13,W12,#0X80000000 AND W18-W12&0X40000000 低26位清0,保留第27位 W18[26] W12[26] W18,W12,#0X4000000 TEXT:0000000000002D30 AND 低27位清0,保留第28位 W17[27]: W12[27] .TEXT:0000000000002D34 AND W17,W12,#0X800000 W17 W12&0X 0X8000000 .TEXT:0000000000002D38 W14 >> 26) W11, W11, W14,LSR#26 -> W11[4] 三 W12[30] ORR WI1 : W11 ( X2 J SWITCH JUMP BR .TEXT:0000000000002D3C .TEXT:000000000002D40 TEXT:0000000000002D40 -->

这里的分析过程需要结合前面的函数传参

最终的总结结果是,从x0取出了4字节保存w12中,x0就是前面的

之后w12[0-6]低6位就是取出的地址索引,取出地址索引定位出跳转地址,w8=x12[16:20],w19=w12[21-25]

w10=w12[31,12:15] w11=w12[26:30]

结合trace的内容去分析有多少个

#!/usr/bin/env python3
import re

path = r"D:\Android\DOUYIN\trace.txt"

pat = re.compile(r'0x12002cd4:\s*"and w10, w12, #0x3f".*?=>\s*w10=(0x[0-9a-fA-F]+)\b')
dictt=dict()
with open(path, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
m = pat.search(line)
if m:
dictt[m.group(1)] = dictt.get(m.group(1), 0) + 1
print(dictt)
#{'0x15': 27, '0x17': 54, '0xb': 51, '0x28 ': 38, '0x18': 2, '0x34': 1, '0x7': 1, '0x11': 2, '0x30': 2, '0xc': 1}

先随便尝试一个低6位的值拿去寻找跳转的哪个地址,拿出23

打印一下调用顺序

opcode 0x15
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0xb
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x15
opcode 0x17
opcode 0x28
opcode 0xb
opcode 0x18
opcode 0xb
opcode 0x34
opcode 0x7
opcode 0xb
opcode 0x17
opcode 0xb
opcode 0x17
opcode 0xb
opcode 0x17
opcode 0xb
opcode 0x17
opcode 0xb
opcode 0x17
opcode 0xb
opcode 0x17
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x17
opcode 0x15
opcode 0xb
opcode 0xb
opcode 0x17
opcode 0xb
opcode 0x17
opcode 0x17
opcode 0x28
opcode 0x17
opcode 0x15
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x15
opcode 0x17
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x17
opcode 0x28
opcode 0x17
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x28
opcode 0x17
opcode 0x28
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x15
opcode 0x15
opcode 0x17
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x28
opcode 0x17
opcode 0x28
opcode 0x17
opcode 0x17
opcode 0x28
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x15
opcode 0x15
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x28
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x15
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x11
opcode 0x17
opcode 0x28
opcode 0x17
opcode 0x30
opcode 0x28
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x15
opcode 0x28
opcode 0x11
opcode 0x15
opcode 0x17
opcode 0x30
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x17
opcode 0x28
opcode 0x15
opcode 0x17
opcode 0x28
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x28
opcode 0x18
opcode 0xb
opcode 0x15
opcode 0xc
opcode 0x17
opcode 0x17
opcode 0x15
opcode 0x28
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x17
opcode 0x15
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x15
opcode 0x17
opcode 0xb
opcode 0xb
opcode 0xb
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0x28
opcode 0xb
opcode 0x15

opcode 0x17 STR Xn , [Xd + imm]


这个是起始地址

结果是0xa6a0+23*4=A6FC

text:0000000000002E50             loc_2E50                                ; CODE XREF: sub_2C18+124↑j
.text:0000000000002E50 ; DATA XREF: .rodata:000000000000A6F0↓o ...
.text:0000000000002E50 8A 15 00 12 AND W10, W12, #0x3F ; '?' ; jumptable 0000000000002D3C cases 20,23,27,35,36,48,54,58
.text:0000000000002E54 4A 51 00 51 SUB W10, W10, #0x14 ; switch 39 cases
.text:0000000000002E58 5F 99 00 71 CMP W10, #0x26
.text:0000000000002E5C E8 60 00 54 B.HI def_2D3C ; jumptable 0000000000002D3C default case, cases 1,3-6,8,9,13,16,18,22,28,31,34,39,44,47,60,62
.text:0000000000002E5C ; jumptable 0000000000002D88 default case, cases 16-25,27-29,31,34-37,39,41,44,46-48,50-52
.text:0000000000002E5C ; jumptable 0000000000002E08 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000002E5C ; jumptable 0000000000002E38 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000002E5C ; jumptable 0000000000002E98 default case, cases 21,22,24-26,28-34,37-47,49-53,55-57
.text:0000000000002E5C ; jumptable 0000000000002FD0 default case, cases 1-9,11,13-54,56,58,60
.text:0000000000002E5C ; jumptable 00000000000030B4 cases 5,10,20,24,35,42
.text:0000000000002E5C ; jumptable 00000000000031C0 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000002E5C ; jumptable 0000000000003208 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000002E5C ; jumptable 0000000000003F60 cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000002E5C ; jumptable 0000000000003F9C default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000002E60 8B 2D 06 53 UBFX W11, W12, #6, #6 ; w11=w12[11:6]
.text:0000000000002E64 8C 0D 14 12 AND W12, W12, #0xF000 ; w12=w12[12:15]
.text:0000000000002E68 4C 7E 14 33 BFXIL W12, W18, #0x14, #0xC ; w12[0:11]=w18[20:31]
.text:0000000000002E6C 8B 01 0B 2A ORR W11, W12, W11 ; w11=w12| w11
.text:0000000000002E70 A9 4E 29 8B ADD X9, X21, W9,UXTW#3 ; x9=x21+(uint64)w9*8,去x21按8字节元素取值
.text:0000000000002E74 EA 7A AA B8 LDRSW X10, [X23,X10,LSL#2] ; x10=[x23+(x10*4)]
.text:0000000000002E78 6B 51 51 2A ORR W11, W11, W17,LSR#20 ; w11=w11| w17>>20
.text:0000000000002E7C 29 05 40 F9 LDR X9, [X9,#8] ; X9 = *(uint64*)(X9 + 8)
.text:0000000000002E80 6B 51 50 2A ORR W11, W11, W16,LSR#20 ; W11 |= (W16 >> 20)
.text:0000000000002E84 6B 51 4F 2A ORR W11, W11, W15,LSR#20 ; W11 |= (W15 >> 20)
.text:0000000000002E88 6B 51 4E 2A ORR W11, W11, W14,LSR#20 ; W11 |= (W14 >> 20)
.text:0000000000002E8C 6B 51 4D 2A ORR W11, W11, W13,LSR#20 ; W11 |= (W13 >> 20)
.text:0000000000002E90 4A 01 17 8B ADD X10, X10, X23 ; X10 = X23 + X10
.text:0000000000002E94 29 A1 2B 8B ADD X9, X9, W11,SXTH ; X9 = X9 + sign_extend_16(W11)
.text:0000000000002E98 40 01 1F D6 BR X10 ; switch jumptext:0000000000002E50 loc_2E50 ; CODE XREF: sub_2C18+124↑j
.text:0000000000002E50 ; DATA XREF: .rodata:000000000000A6F0↓o ...
.text:0000000000002E50 8A 15 00 12 AND W10, W12, #0x3F ; '?' ; jumptable 0000000000002D3C cases 20,23,27,35,36,48,54,58
.text:0000000000002E54 4A 51 00 51 SUB W10, W10, #0x14 ; switch 39 cases
.text:0000000000002E58 5F 99 00 71 CMP W10, #0x26
.text:0000000000002E5C E8 60 00 54 B.HI def_2D3C ; jumptable 0000000000002D3C default case, cases 1,3-6,8,9,13,16,18,22,28,31,34,39,44,47,60,62
.text:0000000000002E5C ; jumptable 0000000000002D88 default case, cases 16-25,27-29,31,34-37,39,41,44,46-48,50-52
.text:0000000000002E5C ; jumptable 0000000000002E08 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000002E5C ; jumptable 0000000000002E38 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000002E5C ; jumptable 0000000000002E98 default case, cases 21,22,24-26,28-34,37-47,49-53,55-57
.text:0000000000002E5C ; jumptable 0000000000002FD0 default case, cases 1-9,11,13-54,56,58,60
.text:0000000000002E5C ; jumptable 00000000000030B4 cases 5,10,20,24,35,42
.text:0000000000002E5C ; jumptable 00000000000031C0 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000002E5C ; jumptable 0000000000003208 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000002E5C ; jumptable 0000000000003F60 cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000002E5C ; jumptable 0000000000003F9C default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000002E60 8B 2D 06 53 UBFX W11, W12, #6, #6 ; w11=w12[11:6]
.text:0000000000002E64 8C 0D 14 12 AND W12, W12, #0xF000 ; w12=w12[12:15]
.text:0000000000002E68 4C 7E 14 33 BFXIL W12, W18, #0x14, #0xC ; w12[0:11]=w18[20:31]
.text:0000000000002E6C 8B 01 0B 2A ORR W11, W12, W11 ; w11=w12| w11
.text:0000000000002E70 A9 4E 29 8B ADD X9, X21, W9,UXTW#3 ; x9=x21+(uint64)w9*8,去x21按8字节元素取值
.text:0000000000002E74 EA 7A AA B8 LDRSW X10, [X23,X10,LSL#2] ; x10=[x23+(x10*4)]
.text:0000000000002E78 6B 51 51 2A ORR W11, W11, W17,LSR#20 ; w11=w11| w17>>20
.text:0000000000002E7C 29 05 40 F9 LDR X9, [X9,#8] ; X9 = *(uint64*)(X9 + 8)
.text:0000000000002E80 6B 51 50 2A ORR W11, W11, W16,LSR#20 ; W11 |= (W16 >> 20)
.text:0000000000002E84 6B 51 4F 2A ORR W11, W11, W15,LSR#20 ; W11 |= (W15 >> 20)
.text:0000000000002E88 6B 51 4E 2A ORR W11, W11, W14,LSR#20 ; W11 |= (W14 >> 20)
.text:0000000000002E8C 6B 51 4D 2A ORR W11, W11, W13,LSR#20 ; W11 |= (W13 >> 20)
.text:0000000000002E90 4A 01 17 8B ADD X10, X10, X23 ; X10 = X23 + X10
.text:0000000000002E94 29 A1 2B 8B ADD X9, X9, W11,SXTH ; X9 = X9 + sign_extend_16(W11)
.text:0000000000002E98 40 01 1F D6 BR X10 ; switch jump

这里就是按照w10的值去重新计算opcode再取值,我们再取一次

x10=(w12&0x3f) -0x14

x10=[x23+(x10*4)]

就是0x8014+4*3

跳转到0x8020就是

W8 = W12[16:20],至于这里的x21的值挺困惑的,有点像上下文啥的

看到这个发现,x21应该是vm的上下文环境,第一个字段就是pc

X21 +0x00 : uint64  pc;        // 当前字节码指针
X21 +0x08 : uint64 vreg[0]; // 虚拟寄存器0 (8字节)
X21 +0x10 : uint64 vreg[1];
...
X21 +0x08 + i*8 : uint64 vreg[i]

这里的x9是x21+(uint64)(W12[21:25])*8

W12[21:25]是取出虚拟寄存器的索引

然后

ADD             X9, X21, W9,UXTW#3 ; x9=x21+(uint64)w9*8

就是得到虚拟寄存器的真实地址

然后取出寄存器加上一个常数应该

ADD             X9, X9, W11,SXTH ; X9 = X9 + sign_extend_16(W11)

X9 = vXn + imm

所以这个指令就比较明显了

str

就是**STR Xn , [Xd + imm]**

为此我们还可以推断出W12[16:20]是源寄存器索引,W12[21:25] 是目标寄存器索引,其他的就是常数

opcode 0x15 ADD Xd,Xn,#imme

opcode 0x15是trace里面的第一个虚拟机指令

0xa6a0+0x15*4=00A6F4

前面似乎都是在解析常量,后面这里

.text:cc AB 22 00 91                 ADD             X11, X21, #8 ; 取出虚拟机环境指针
.text:0000000000002F68 29 F1 7D D3 LSL X9, X9, #3
.text:0000000000002F6C 69 69 A9 B8 LDRSW X9, [X11,X9] ; 取出虚拟寄存器的值
.text:0000000000002F70 4A 3D 00 13 SXTH W10, W10
.text:0000000000002F74 29 C1 2A 8B ADD X9, X9, W10,SXTW ; 加上一个常量
.text:0000000000002F78 69 59 28 F8 STR X9, [X11,W8,UXTW#3] ; 保存在w8表示的虚拟寄存器指针
.text:0000000000002F7C BF 02 00 14 B def_2D3C ; jumptable 0000000000002D3C default

这里的w8就是目标寄存器,x9就是源寄存器

ADD Xd,Xn,#imme

opcode 0x28 LOAD Xd, [Xn, #imme]

这里就是取出虚拟寄存器的值,得到x9,然后这里是加上了一个常数

这里继续跳转

text:0000000000003318 29 01 40 F9                 LDR             X9, [X9] ; jumptable 0000000000002D88 case 40
.text:000000000000331C D5 01 00 14 B loc_3A70

然后再取出一个值

text:0000000000003A70 A8 4E 28 8B                 ADD             X8, X21, W8,UXTW#3
.text:0000000000003A74
.text:0000000000003A74 loc_3A74 ; CODE XREF: sub_2C18+1A4↑j
.text:0000000000003A74 ; sub_2C18+6B0↑j ...
.text:0000000000003A74 09 05 00 F9 STR X9, [X8,#8]
.text:0000000000003A78
.text:0000000000003A78 def_2D3C ; CODE XREF: sub_2C18+C4↑j
.text:0000000000003A78 ; sub_2C18+124↑j ...
.text:0000000000003A78 A9 02 40 F9 LDR X9, [X21] ; jumptable 0000000000002D3C default case, cases 1,3-6,8,9,13,16,18,22,28,31,34,39,44,47,60,62
.text:0000000000003A78 ; jumptable 0000000000002D88 default case, cases 16-25,27-29,31,34-37,39,41,44,46-48,50-52
.text:0000000000003A78 ; jumptable 0000000000002E08 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003A78 ; jumptable 0000000000002E38 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003A78 ; jumptable 0000000000002E98 default case, cases 21,22,24-26,28-34,37-47,49-53,55-57
.text:0000000000003A78 ; jumptable 0000000000002FD0 default case, cases 1-9,11,13-54,56,58,60
.text:0000000000003A78 ; jumptable 00000000000030B4 cases 5,10,20,24,35,42
.text:0000000000003A78 ; jumptable 00000000000031C0 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003A78 ; jumptable 0000000000003208 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003A78 ; jumptable 0000000000003F60 cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003A78 ; jumptable 0000000000003F9C default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003A7C 68 02 5E F8 LDUR X8, [X19,#-0x20]
.text:0000000000003A80 20 11 00 91 ADD X0, X9, #4
.text:0000000000003A84 1F 09 00 F1 CMP X8, #2
.text:0000000000003A88 A0 02 00 F9 STR X0, [X21]
.text:0000000000003A8C E0 00 00 54 B.EQ loc_3AA8
.text:0000000000003A90 1F 0D 00 F1 CMP X8, #3
.text:0000000000003A94 80 0B 00 54 B.EQ loc_3C04
.text:0000000000003A98 1F 05 00 F1 CMP X8, #1
.text:0000000000003A9C 40 0B 00 54 B.EQ loc_3C04
.text:0000000000003AA0 1F 00 14 EB CMP X0, X20
.text:0000000000003AA4 A0 0B 00 54 B.EQ loc_3C18
.text:0000000000003AA8
.text:0000000000003AA8 loc_3AA8 ; CODE XREF: sub_2C18+E74↑j
.text:0000000000003AA8 ; sub_2C18+FFC↓j
.text:0000000000003AA8 E0 90 FF B5 CBNZ X0, loc_2CC4
.text:0000000000003AAC 5F 02 00 14 B loc_4428

从虚拟寄存器中取出,然后把前面取出的值加上常数后再取的值放进虚拟寄存器中

这里就是从内存中加载一个值放进了寄存器之中

所以这里的指令就是 LOAD Xd, [Xn, #imme]

这里的w9的源寄存器索引,w8是目的寄存器索引

opcode 0xb 0x7 ORR Xd, Xn, Xt

当opcode 等于0xb的时候,有多个可能的跳转结果,因为是基于w12与运算0xfff之后的结果

这里可以计算一下有几种结果

f = open("D:\\Android\\DOUYIN\\DOUyin\\lib\\arm64-v8a\\libEncryptor.so", "rb")

data = f.read()

start = 0x0B090
lens = 0x2d8
nums = int(lens / 4)

maps = {}

for i in range(nums):
opcode = data[start + i * 4]
op = opcode & 0x3F # 小端表示,操作码在首位
print("opcode1-> "+ str(hex(op)),end=" ")
if op==0xb:
pos = start + i * 4
w12 = int.from_bytes(data[pos:pos + 4], "little")

imm12 = w12 & 0xFFF
x = (imm12 - 0xB) & 0xFFFFFFFF
ror = ((x >> 6) | ((x << 26) & 0xFFFFFFFF)) & 0xFFFFFFFF # ROR32(x,6)
print("opcode2-> "+ str(hex(ror)),end=" ")
print("")

if op in maps.keys():
maps[op] += 1
else:
maps[op] = 1

for k, v in maps.items():
print("{0}: {1}".format(hex(k), v))


有多种结果,一种一种分析就行

opcode1-> 0x15 
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0xb opcode2-> 0x27
opcode1-> 0x18
opcode1-> 0xb opcode2-> 0x19
opcode1-> 0xc
opcode1-> 0x17
opcode1-> 0x34
opcode1-> 0x7
opcode1-> 0xb opcode2-> 0xc
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0xc
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0xc
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0xc
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0xc
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0xc
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0xc
opcode1-> 0xb opcode2-> 0xc
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0x15
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0x17
opcode1-> 0x15
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x15
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0x15
opcode1-> 0x15
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x15
opcode1-> 0x28
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0x15
opcode1-> 0x15
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x15
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x11
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0x17
opcode1-> 0x30
opcode1-> 0x28
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0x15
opcode1-> 0x15
opcode1-> 0x28
opcode1-> 0x11
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0x30
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0x28
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0x15
opcode1-> 0x28
opcode1-> 0x18
opcode1-> 0xb opcode2-> 0x19
opcode1-> 0x15
opcode1-> 0xc
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x17
opcode1-> 0x15
opcode1-> 0x28
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x17
opcode1-> 0x15
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x15
opcode1-> 0x17
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0xb opcode2-> 0x2b
opcode1-> 0xb opcode2-> 0x7
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0x28
opcode1-> 0xb opcode2-> 0x3e
opcode1-> 0x15
00000000038F8 8B 2D 00 12                 AND             W11, W12, #0xFFF ; jumptable 00000000000030B4 cases 3,7,30,46
.text:00000000000038FC 7F 29 1E 71 CMP W11, #0x78A
.text:0000000000003900 8C 13 00 54 B.GT loc_3B70
.text:0000000000003904 7F 2D 03 71 CMP W11, #0xCB
.text:0000000000003908 20 36 00 54 B.EQ loc_3FCC
.text:000000000000390C 7F 2D 07 71 CMP W11, #0x1CB
.text:0000000000003910 41 0B 00 54 B.NE def_2D3C ; jumptable 0000000000002D3C default case, cases 1,3-6,8,9,13,16,18,22,28,31,34,39,44,47,60,62
.text:0000000000003910 ; jumptable 0000000000002D88 default case, cases 16-25,27-29,31,34-37,39,41,44,46-48,50-52
.text:0000000000003910 ; jumptable 0000000000002E08 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003910 ; jumptable 0000000000002E38 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003910 ; jumptable 0000000000002E98 default case, cases 21,22,24-26,28-34,37-47,49-53,55-57
.text:0000000000003910 ; jumptable 0000000000002FD0 default case, cases 1-9,11,13-54,56,58,60
.text:0000000000003910 ; jumptable 00000000000030B4 cases 5,10,20,24,35,42
.text:0000000000003910 ; jumptable 00000000000031C0 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003910 ; jumptable 0000000000003208 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003910 ; jumptable 0000000000003F60 cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003910 ; jumptable 0000000000003F9C default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003914 AB 22 00 91 ADD X11, X21, #8
.text:0000000000003918 69 59 69 F8 LDR X9, [X11,W9,UXTW#3]
.text:000000000000391C 68 59 68 F8 LDR X8, [X11,W8,UXTW#3]
.text:0000000000003920 08 01 09 AA ORR X8, X8, X9
.text:0000000000003924 68 59 2A F8 STR X8, [X11,W10,UXTW#3]
.text:0000000000003928 54 00 00 14 B def_2D3C ; jumpt

这个指令很明显是orr 或运算的指令ORR Xd, Xn, Xt

opcode 0xb 0x27 cmp xn ,xt ; set xd,LT
.text:0000000000003F18             loc_3F18                                ; CODE XREF: sub_2C18+49C↑j
.text:0000000000003F18 ; DATA XREF: .rodata:000000000000AE4C↓o
.text:0000000000003F18 AB 22 00 91 ADD X11, X21, #8 ; jumptable 00000000000030B4 case 39
.text:0000000000003F1C 69 59 69 F8 LDR X9, [X11,W9,UXTW#3]
.text:0000000000003F20 68 59 68 F8 LDR X8, [X11,W8,UXTW#3]
.text:0000000000003F24 3F 01 08 EB CMP X9, X8
.text:0000000000003F28 68 4D 2A 8B ADD X8, X11, W10,UXTW#3
.text:0000000000003F2C E3 FE FF 54 B.CC loc_3F08 ; jmp if x9<x8
.text:0000000000003F30
.text:0000000000003F30 loc_3F30 ; CODE XREF: sub_2C18+12EC↑j
.text:0000000000003F30 1F 01 00 F9 STR XZR, [X8]
.text:0000000000003F34 D1 FE FF 17 B def_2D3C ; jumptable 0000000000002
.text:0000000000003F08 loc_3F08 ; CODE XREF: sub_2C18+1314↓j
.text:0000000000003F08 18 01 00 F9 STR X24, [X8]
.text:0000000000003F0C DB FE FF 17 B def_2D3C ; jumptable 0000000000002D3C default case, cases 1,3-6,8,9,13,16,18,22,28,31,34,39,44,47,60,62
.text:0000000000003F0C ; jumptable 0000000000002D88 def

这里相当于 cmp xn ,xt ; set xd,LT 如果小于,就设置目标寄存器为1,否则设置为0

opcode 0xb 0x19 LSL Xd, Xn , #imme

这里根据trace的来分析好了

12003888: "cmp w13, #0xd0a" => nzcv: N=1, Z=0, C=0, V=0 w13=0x64b
[16:50:10 524][libEncryptor.so 0x0388c] [8c010054] 0x1200388c: "b.gt #0x120038bc" nzcv: N=1, Z=0, C=0, V=0
[16:50:10 525][libEncryptor.so 0x03890] [bf292571] 0x12003890: "cmp w13, #0x94a" => nzcv: N=1, Z=0, C=0, V=0 w13=0x64b
[16:50:10 525][libEncryptor.so 0x03894] [8c130054] 0x12003894: "b.gt #0x12003b04" nzcv: N=1, Z=0, C=0, V=0
[16:50:10 525][libEncryptor.so 0x03898] [bf2d1971] 0x12003898: "cmp w13, #0x64b" => nzcv: N=0, Z=1, C=1, V=0 w13=0x64b
[16:50:10 525][libEncryptor.so 0x0389c] [203b0054] 0x1200389c: "b.eq #0x12004000" nzcv: N=0, Z=1, C=1, V=0
[16:50:10 525][libEncryptor.so 0x04000] [a9220091] 0x12004000: "add x9, x21, #8" x21=0xe4fff538 => x9=0xe4fff540
[16:50:10 526][libEncryptor.so 0x04004] [08f17dd3] 0x12004004: "lsl x8, x8, #3" x8=0x0 => x8=0x0
[16:50:10 526][libEncryptor.so 0x04008] [286968b8] 0x12004008: "ldr w8, [x9, x8]" x9=0xe4fff540 x8=0x0 => w8=0x0
[16:50:10 526][libEncryptor.so 0x0400c] [0821cb1a] 0x1200400c: "lsl w8, w8, w11" w8=0x0 w11=0x0 => w8=0x0
[16:50:10 526][libEncryptor.so 0x04010] [25000014] 0x12004010: "b #0x120040a4"
[16:50:10 526][libEncryptor.so 0x040a4] [087d4093] 0x120040a4: "sxtw x8, w8" x8=0x0 w8=0x0 => x8=0x0
[16:50:10 526][libEncryptor.so 0x040a8] [28592af8] 0x120040a8: "str x8, [x9, w10, uxtw #3]" x8=0x0 x9=0xe4fff540 w10=0x0 => x8=0x0
[16:50:10 527][libEncryptor.so 0x040ac] [73feff17] 0x120040ac: "b #0x12003a78"

看trace比较明显,这里的w11是一次opcode和二次opcode中间的一个计算出常数的过程

先加载一个寄存器出来,然后lsl 一定的位数,最后再放到另外一个寄存器里面去。

这里的指令就是LSL Xd, Xn , #imme

opcode 0xb 0xc add xd,xn,xt
030b8: "and w11, w12, #0xfff" w12=0xc1230b => w11=0x30b
[16:50:10 539][libEncryptor.so 0x030bc] [7f291171] 0x120030bc: "cmp w11, #0x44a" => nzcv: N=1, Z=0, C=0, V=0 w11=0x30b
[16:50:10 539][libEncryptor.so 0x030c0] [8d440054] 0x120030c0: "b.le #0x12003950" nzcv: N=1, Z=0, C=0, V=0
[16:50:10 539][libEncryptor.so 0x03950] [7f290871] 0x12003950: "cmp w11, #0x20a" => nzcv: N=0, Z=0, C=1, V=0 w11=0x30b
[16:50:10 540][libEncryptor.so 0x03954] [8c130054] 0x12003954: "b.gt #0x12003bc4" nzcv: N=0, Z=0, C=1, V=0
[16:50:10 540][libEncryptor.so 0x03bc4] [7f2d0871] 0x12003bc4: "cmp w11, #0x20b" => nzcv: N=0, Z=0, C=1, V=0 w11=0x30b
[16:50:10 540][libEncryptor.so 0x03bc8] [c00c0054] 0x12003bc8: "b.eq #0x12003d60" nzcv: N=0, Z=0, C=1, V=0
[16:50:10 541][libEncryptor.so 0x03bcc] [7f2d0c71] 0x12003bcc: "cmp w11, #0x30b" => nzcv: N=0, Z=1, C=1, V=0 w11=0x30b
[16:50:10 541][libEncryptor.so 0x03bd0] [41f5ff54] 0x12003bd0: "b.ne #0x12003a78" nzcv: N=0, Z=1, C=1, V=0
[16:50:10 541][libEncryptor.so 0x03bd4] [ab220091] 0x12003bd4: "add x11, x21, #8" x21=0xe4fff538 => x11=0xe4fff540
[16:50:10 541][libEncryptor.so 0x03bd8] [695969f8] 0x12003bd8: "ldr x9, [x11, w9, uxtw #3]" x11=0xe4fff540 w9=0x6 => x9=0x12ddd394
[16:50:10 542][libEncryptor.so 0x03bdc] [685968f8] 0x12003bdc: "ldr x8, [x11, w8, uxtw #3]" x11=0xe4fff540 w8=0x1 => x8=0xffffffffff225870
[16:50:10 542][libEncryptor.so 0x03be0] [0801098b] 0x12003be0: "add x8, x8, x9" x8=0xffffffffff225870 x9=0x12ddd394 => x8=0x12002c04
[16:50:10 542][libEncryptor.so 0x03be4] [68592af8] 0x12003be4: "str x8, [x11, w10, uxtw #3]" x8=0x12002c04 x11=0xe4fff540 w10=0x4 => x8=0x12002c04
[16:50:10 543][libEncryptor.so 0x03be8] [a4ffff17] 0x12003be8: "b #0x12003a78"

加法?,但是看反编译似乎是根据多种比较结果进行不同的运算,有乘和减法?

先暂时就看他是加法吧

Xd = Xn + Xt

opcode 0xb 0x2b blr xn
16:50:10 573][libEncryptor.so 0x03f38] [01daffb5] 0x12003f38: "cbnz x1, #0x12003a78" x1=0x0
[16:50:10 573][libEncryptor.so 0x03f3c] [882d0012] 0x12003f3c: "and w8, w12, #0xfff" w12=0x8320facb => w8=0xacb
[16:50:10 573][libEncryptor.so 0x03f40] [1ff50071] 0x12003f40: "cmp w8, #0x3d" => nzcv: N=0, Z=0, C=1, V=0 w8=0xacb
[16:50:10 574][libEncryptor.so 0x03f44] [ed031f2a] 0x12003f44: "mov w13, wzr" => w13=0x0
[16:50:10 574][libEncryptor.so 0x03f48] [28130054] 0x12003f48: "b.hi #0x120041ac" nzcv: N=0, Z=0, C=1, V=0
[16:50:10 574][libEncryptor.so 0x041ac] [1f2d2b71] 0x120041ac: "cmp w8, #0xacb" => nzcv: N=0, Z=1, C=1, V=0 w8=0xacb
[16:50:10 575][libEncryptor.so 0x041b0] [a0100054] 0x120041b0: "b.eq #0x120043c4" nzcv: N=0, Z=1, C=1, V=0
[16:50:10 575][libEncryptor.so 0x043c4] [ed030a2a] 0x120043c4: "mov w13, w10" w10=0x1f => w13=0x1f
[16:50:10 575][libEncryptor.so 0x043c8] [a84e298b] 0x120043c8: "add x8, x21, w9, uxtw #3" x21=0xe4fff538 w9=0x19 => x8=0xe4fff600
[16:50:10 576][libEncryptor.so 0x043cc] [080540f9] 0x120043cc: "ldr x8, [x8, #8]" x8=0xe4fff600 => x8=0x12002c0c
[16:50:10 576][libEncryptor.so 0x043d0] [ec031f2a] 0x120043d0: "mov w12, wzr" => w12=0x0
[16:50:10 576][libEncryptor.so 0x043d4] [ea031f2a] 0x120043d4: "mov w10, wzr" => w10=0x0
[16:50:10 577][libEncryptor.so 0x043d8] [e9030032] 0x120043d8: "orr w9, wzr, #1" w9=0x19 => w9=0x1
[16:50:10 577][libEncryptor.so 0x043dc] [5f010071] 0x120043dc: "cmp w10, #0" => nzcv: N=0, Z=1, C=1, V=0 w10=0x0
[16:50:10 578][libEncryptor.so 0x043e0] [ee079f1a] 0x120043e0: "cset w14, ne" nzcv: N=0, Z=1, C=1, V=0 => w14=0x0
[16:50:10 578][libEncryptor.so 0x043e4] [8b010e0a] 0x120043e4: "and w11, w12, w14" w12=0x0 w14=0x0 => w11=0x0
[16:50:10 578][libEncryptor.so 0x043e8] [68821ef8] 0x120043e8: "stur x8, [x19, #-0x18]" x8=0x12002c0c x19=0xe4fff670 => x8=0x12002c0c
[16:50:10 579][libEncryptor.so 0x043ec] [eb000037] 0x120043ec: "tbnz w11, #0, #0x12004408" w11=0x0
[16:50:10 579][libEncryptor.so 0x043f0] [ca000034] 0x120043f0: "cbz w10, #0x12004408" w10=0x0
[16:50:10 579][libEncryptor.so 0x04408] [7f010071] 0x12004408: "cmp w11, #0" => nzcv: N=0, Z=1, C=1, V=0 w11=0x0
[16:50:10 580][libEncryptor.so 0x0440c] [0817989a] 0x1200440c: "cinc x8, x24, eq" nzcv: N=0, Z=1, C=1, V=0 x24=0x1 => x8=0x2
[16:50:10 580][libEncryptor.so 0x04410] [68021ef8] 0x12004410: "stur x8, [x19, #-0x20]" x8=0x2 x19=0xe4fff670 => x8=0x2
[16:50:10 580][libEncryptor.so 0x04414] [bf050071] 0x12004414: "cmp w13, #1" => nzcv: N=0, Z=0, C=1, V=0 w13=0x1f
[16:50:10 581][libEncryptor.so 0x04418] [0bb3ff54] 0x12004418: "b.lt #0x12003a78" nzcv: N=0, Z=0, C=1, V=0
[16:50:10 581][libEncryptor.so 0x0441c] [08200091] 0x1200441c: "add x8, x0, #8" x0=0x1200b168 => x8=0x1200b170
[16:50:10 582][libEncryptor.so 0x04420] [a9ce2d8b] 0x12004420: "add x9, x21, w13, sxtw #3" x21=0xe4fff538 w13=0x1f => x9=0xe4fff630
[16:50:10 582][libEncryptor.so 0x04424] [4afcff17] 0x12004424: "b #0x1200354c"
[16:50:10 582][libEncryptor.so 0x0354c] [280500f9] 0x1200354c: "str x8, [x9, #8]" x8=0x1200b170 x9=0xe4fff630 => x8=0x1200b170
[16:50:10 583][libEncryptor.so 0x03550] [68021ff8] 0x12003550: "stur x8, [x19, #-0x10]" x8=0x1200b170 x19=0xe4fff670 => x8=0x1200b170
[16:50:10 583][libEncryptor.so 0x03554] [49010014] 0x12003554: "b #0x12003a78"

设置[X19,#-0x18] 为某个虚拟寄存器的值,设置 [X19,#-0x20] 的值为 2

注意到vmp的公共末尾处,当[X19 - 0x20] 的值为 1 或 3 时


.text:0000000000003C04 60 82 5E F8 LDUR X0, [X19,#-0x18]
.text:0000000000003C08 7F 02 1E F8 STUR XZR, [X19,#-0x20]
.text:0000000000003C0C A0 02 00 F9 STR X0, [X21]
.text:0000000000003C10 1F 00 14 EB CMP X0, X20
.text:0000000000003C14 A1 F4 FF 54 B.NE loc_3AA8
text:0000000000003C18 loc_3C18 ; CODE XREF: sub_2C18+E8C↑j
.text:0000000000003C18 80 00 40 F9 LDR X0, [X4]
.text:0000000000003C1C A1 00 40 F9 LDR X1, [X5]
.text:0000000000003C20 F8 03 04 AA MOV X24, X4
.text:0000000000003C24 F6 03 05 AA MOV X22, X5
.text:0000000000003C28 80 02 3F D6 BLR X20
.text:0000000000003C2C 60 02 5F F8 LDUR X0, [X19,#-0x10]
.text:0000000000003C30 3E 00 00 F0 ADRP X30, #jpt_30B4@PAGE
.text:0000000000003C34 E7 FF 81 52 MOV W7, #0xFFF
.text:0000000000003C38 66 01 80 52 MOV W6, #0xB
.text:0000000000003C3C DE C3 36 91 ADD X30, X30, #jpt_30B4@PAGEOFF
.text:0000000000003C40 07 04 A0 72 MOVK W7, #0x20,LSL#16 ; ' '
.text:0000000000003C44 06 80 A0 72 MOVK W6, #0x400,LSL#16
.text:0000000000003C48 E5 03 16 AA MOV X5, X22
.text:0000000000003C4C E4 03 18 AA MOV X4, X24
.text:0000000000003C50 F8 03 00 32 MOV W24, #1
.text:0000000000003C54 A0 02 00 F9 STR X0, [X21]
.text:0000000000003C58 60 83 FF B5 CBNZ X0, loc_2CC4
.text:0000000000003C5C F3 01 00 14 B loc_4428
.text:0000000000003AA8 loc_3AA8 ; CODE XREF: sub_2C18+E74↑j
.text:0000000000003AA8 ; sub_2C18+FFC↓j
.text:0000000000003AA8 E0 90 FF B5 CBNZ X0, loc_2CC4
.text:0000000000003AAC 5F 02 00 14 B loc_4428
text:0000000000002CC4 loc_2CC4 ; CODE XREF: sub_2C18:loc_3AA8↓j
.text:0000000000002CC4 ; sub_2C18+1040↓j ...
.text:0000000000002CC4 61 02 5E F8 LDUR X1, [X19,#-0x20]
.text:0000000000002CC8
.text:0000000000002CC8 loc_2CC8 ; CODE XREF: sub_2C18+A8↑j
.text:0000000000002CC8 0C 00 40 B9 LDR W12, [X0] ; 从x0取四个字节
.text:0000000000002CCC 3F 08 00 F1 CMP X1, #2
.text:0000000000002CD0 60 97 00 54 B.EQ loc_3FBC
.text:0000000000002CD4
.text:0000000000002CD4 loc_2CD4 ; CODE XREF: sub_2C18+13B0↓j
.text:0000000000002CD4 8A 15 00 12 AND W10, W12, #0x3F ; '?' ; 取低6位
.text:0000000000002CD8 5F FD 00 71 CMP W10, #0x3F ; switch 64 cases
.text:0000000000002CDC E8 6C 00 54 B.HI def_2D3C ; jumptable 0000000000002D3C default case, cases 1,3-6,8,9,13,16,18,22,28,31,34,39,44,47,60,62
.text:0000000000002CDC ; jumptable 0000000000002D88 default case, cases 16-25,27-29,31,34-37,39,41,44,46-48,50-52
.text:0000000000002CDC ; jumptable 0000000000002E08 default

这里是设置[X19,#-0x20]为0了,然后判断这个虚拟寄存器的值是不是0,然后跳转,注意这里的loc_2CC4是取指令的开头了

当 [X19,#-0x20] 等于 2 时

.text:0000000000003AA8             loc_3AA8                                ; CODE XREF: sub_2C18+E74↑j
.text:0000000000003AA8 ; sub_2C18+FFC↓j
.text:0000000000003AA8 E0 90 FF B5 CBNZ X0, loc_2CC4

所以这里就是跳转指令和函数调用指令这种涉及到修改pc的操作

当[X19,#-0x20] 等于 2 的时候,会在执行下一条指令之后,再末尾执行修改pc的操作,延迟修改

函数调用的时候。[X19,#-0x18] 是被设置成跳板函数地址,函数执行完之后,会从[X19,#-0x10] 恢复虚拟PC

所以这个是blr xn ,跳转去寄存器,然后把返回地址放入 lr(x30)中

函数调用

x20是跳板函数的地址

.text:0000000000002C0C             ; __int64 __fastcall sub_2C0C(__int64 (__fastcall *)(__int64), __int64)
.text:0000000000002C0C sub_2C0C ; DATA XREF: sub_2AC4+18↑o
.text:0000000000002C0C ; __unwind {
.text:0000000000002C0C E2 03 00 AA MOV X2, X0
.text:0000000000002C10 E0 03 01 AA MOV X0, X1
.text:0000000000002C14 40 00 1F D6 BR X2
.text:0000000000002C14 ; } // starts at 2C0C

opcode 0x18 cmp xn,xm

有个固定的二次跳转

.text:00000000000034A4             loc_34A4                                ; CODE XREF: sub_2C18+220↑j
.text:00000000000034A4 ; DATA XREF: .rodata:000000000000A994↓o
.text:00000000000034A4 5F 01 0B EB CMP X10, X11 ; jumptable 0000000000002E38 case 24
.text:00000000000034A8 E0 CC FF 54 B.EQ loc_2E44
.text:00000000000034AC 06 00 00 14 B loc_34C4
.text:00000000000034C4
.text:00000000000034C4 loc_34C4 ; CODE XREF: sub_2C18+228↑j
.text:00000000000034C4 ; sub_2C18+894↑j ...
.text:00000000000034C4 E9 03 1F 2A MOV W9, WZR ;w9=0
.text:00000000000034C8 0E 00 00 14 B loc_3500
.text:0000000000002E44 loc_2E44 ; CODE XREF: sub_2C18+890↓j
.text:0000000000002E44 ; sub_2C18+8A8↓j ...
.text:0000000000002E44 EB 03 1F 2A MOV W11, WZR ;w11=0
.text:0000000000002E48 E9 03 1F 2A MOV W9, WZR ;w9=0
.text:0000000000002E4C A7 01 00 14 B loc_34E8
.text:00000000000034E8 loc_34E8 ; CODE XREF: sub_2C18+234↑j
.text:00000000000034E8 EA 03 1F 2A MOV W10, WZR ;w10=0
.text:00000000000034EC EC 03 00 32 MOV W12, #1 ;w12=1
.text:00000000000034F0 08 00 00 14 B loc_3510
text:0000000000003500 loc_3500 ; CODE XREF: sub_2C18+400↑j
.text:0000000000003500 ; sub_2C18+878↑j ...
.text:0000000000003500 EC 03 1F 2A MOV W12, WZR ;w12=0
.text:0000000000003504 EA 03 1F 2A MOV W10, WZR ;w10=0
.text:0000000000003508 08 20 00 91 ADD X8, X0, #8 ;w8=x0+8
.text:000000000000350C EB 03 00 32 MOV W11, #1 ;w11=1
.text:0000000000003510
.text:0000000000003510 loc_3510 ; CODE XREF: sub_2C18+764↑j
.text:0000000000003510 ; sub_2C18+8D8↑j
.text:0000000000003510 3F 01 00 71 CMP W9, #0 ;if w9==0
.text:0000000000003514 ED 07 9F 1A CSET W13, NE ;W13 = 0
.text:0000000000003518 6B 01 0D 0A AND W11, W11, W13 ;当x10==x11时候,w11=0,否则为1
.text:000000000000351C 68 82 1E F8 STUR X8, [X19,#-0x18];把当前指令的后面两个指令放入X19,#-0x18
.text:0000000000003520 8B 00 00 37 TBNZ W11, #0, loc_3530
.text:0000000000003524 69 00 00 34 CBZ W9, loc_3530
.text:0000000000003528 88 01 0D 0A AND W8, W12, W13
.text:000000000000352C 88 00 00 34 CBZ W8, loc_353C
.text:0000000000003530
.text:0000000000003530 loc_3530 ; CODE XREF: sub_2C18+908↑j
.text:0000000000003530 ; sub_2C18+90C↑j
.text:0000000000003530 7F 01 00 71 CMP W11, #0
.text:0000000000003534 08 17 98 9A CINC X8, X24, EQ ;if w11==0 x8=x24+1,否则 x8=x24 ,x24=1
.text:0000000000003538 68 02 1E F8 STUR X8, [X19,#-0x20]
.text:000000000000353C
.text:000000000000353C loc_353C ; CODE XREF: sub_2C18+914↑j
.text:000000000000353C ; sub_2C18+10F4↓j
.text:000000000000353C 5F 05 00 71 CMP W10, #1
.text:0000000000003540 CB 29 00 54 B.LT def_2D3C ; jumptable 0000000000002D3C default case, cases 1,3-6,8,9,13,16,18,22,28,31,34,39,44,47,60,62
.text:0000000000003540 ; jumptable 0000000000002D88 default case, cases 16-25,27-29,31,34-37,39,41,44,46-48,50-52
.text:0000000000003540 ; jumptable 0000000000002E08 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003540 ; jumptable 0000000000002E38 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003540 ; jumptable 0000000000002E98 default case, cases 21,22,24-26,28-34,37-47,49-53,55-57
.text:0000000000003540 ; jumptable 0000000000002FD0 default case, cases 1-9,11,13-54,56,58,60
.text:0000000000003540 ; jumptable 00000000000030B4 cases 5,10,20,24,35,42
.text:0000000000003540 ; jumptable 00000000000031C0 default case, cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003540 ; jumptable 0000000000003208 default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003540 ; jumptable 0000000000003F60 cases 1-9,11,13-18,20-23,26-28,30-54,56,58,60
.text:0000000000003540 ; jumptable 0000000000003F9C default case, cases 1-9,11-18,20-23,26-28,30-56,58,60
.text:0000000000003544

综合分析下来

发现这个很可能是cmp xn,xm的指令,比较后还设置了相关虚拟寄存器的值以及控制变量的值,保存了pc指针(当前指令后两个指令pc到[X19,#-0x18])的值

大致就是,当[X19,#-0x20] = 1的时候,指令末尾就进行跳转指令,如果等于2 的话,会再下一条指令结束的时候执行跳转操作,所以这是条件跳转指令

当xm!=xn的时候,直接跳转到PC+8的位置,当xm ==xn的时候,跳转到PC+ #imme

opcode 0x34 nop?
[16:50:10 529][libEncryptor.so 0x02ecc] [800d1412] 0x12002ecc: "and w0, w12, #0xf000" w12=0xf001f8b4 => w0=0xf000
[16:50:10 529][libEncryptor.so 0x02ed0] [8a2d0653] 0x12002ed0: "ubfx w10, w12, #6, #6" w10=0x1f w12=0xf001f8b4 => w10=0x22
[16:50:10 529][libEncryptor.so 0x02ed4] [407e1433] 0x12002ed4: "bfxil w0, w18, #0x14, #0xc" w0=0xf000 w18=0x0 => w0=0xf000
[16:50:10 529][libEncryptor.so 0x02ed8] [0a000a2a] 0x12002ed8: "orr w10, w0, w10" w0=0xf000 w10=0x22 => w10=0xf022
[16:50:10 529][libEncryptor.so 0x02edc] [4a51512a] 0x12002edc: "orr w10, w10, w17, lsr #20" w10=0xf022 w17=0x0 => w10=0xf022
[16:50:10 530][libEncryptor.so 0x02ee0] [4a51502a] 0x12002ee0: "orr w10, w10, w16, lsr #20" w10=0xf022 w16=0x10000000 => w10=0xf122
[16:50:10 530][libEncryptor.so 0x02ee4] [4a514f2a] 0x12002ee4: "orr w10, w10, w15, lsr #20" w10=0xf122 w15=0x20000000 => w10=0xf322
[16:50:10 530][libEncryptor.so 0x02ee8] [8b150012] 0x12002ee8: "and w11, w12, #0x3f" w12=0xf001f8b4 => w11=0x34
[16:50:10 531][libEncryptor.so 0x02eec] [4a514e2a] 0x12002eec: "orr w10, w10, w14, lsr #20" w10=0xf322 w14=0x40000000 => w10=0xf722
[16:50:10 531][libEncryptor.so 0x02ef0] [7fcd0071] 0x12002ef0: "cmp w11, #0x33" => nzcv: N=0, Z=0, C=1, V=0 w11=0x34
[16:50:10 531][libEncryptor.so 0x02ef4] [4a514d2a] 0x12002ef4: "orr w10, w10, w13, lsr #20" w10=0xf722 w13=0x80000000 => w10=0xff22
[16:50:10 531][libEncryptor.so 0x02ef8] [4c090054] 0x12002ef8: "b.gt #0x12003020" nzcv: N=0, Z=0, C=1, V=0
[16:50:10 531][libEncryptor.so 0x03020] [7fd10071] 0x12003020: "cmp w11, #0x34" => nzcv: N=0, Z=1, C=1, V=0 w11=0x34
[16:50:10 531][libEncryptor.so 0x03024] [a0220054] 0x12003024: "b.eq #0x12003478" nzcv: N=0, Z=1, C=1, V=0
[16:50:10 532][libEncryptor.so 0x03478] [493d1053] 0x12003478: "lsl w9, w10, #0x10" w9=0x0 w10=0xff22 => w9=0xff220000
[16:50:10 532][libEncryptor.so 0x0347c] [297d4093] 0x1200347c: "sxtw x9, w9" x9=0xff220000 w9=0xff220000 => x9=0xffffffffff220000
[16:50:10 532][libEncryptor.so 0x03480] [7c010014] 0x12003480: "b #0x12003a70"
[16:50:10 532][libEncryptor.so 0x03a70] [a84e288b] 0x12003a70: "add x8, x21, w8, uxtw #3" x21=0xe4fff538 w8=0x1 => x8=0xe4fff540
[16:50:10 532][libEncryptor.so 0x03a74] [090500f9] 0x12003a74: "str x9, [x8, #8]" x9=0xffffffffff220000 x8=0xe4fff540 => x9=0xffffffffff220000
[16:50:10 532][libEncryptor.so 0x03a78] [a90240f9] 0x12003a78: "ldr x9, [x21]" x21=0xe4fff538 => x9=0x1200b110
[16:50:10 532][libEncryptor.so 0x03a7c] [68025ef8] 0x12003a7c: "ldur x8, [x19, #-0x20]" x8=0xe4fff540 x19=0xe4fff670 => x8=0
opcode 0x30 STORE Xn, [Xd + #imme]
[16:50:11 553][libEncryptor.so 0x02e50] [8a150012] 0x12002e50: "and w10, w12, #0x3f" w12=0xfa10c30 => w10=0x30
[16:50:11 553][libEncryptor.so 0x02e54] [4a510051] 0x12002e54: "sub w10, w10, #0x14" w10=0x30 => w10=0x1c
[16:50:11 553][libEncryptor.so 0x02e58] [5f990071] 0x12002e58: "cmp w10, #0x26" => nzcv: N=1, Z=0, C=0, V=0 w10=0x1c
[16:50:11 553][libEncryptor.so 0x02e5c] [e8600054] 0x12002e5c: "b.hi #0x12003a78" nzcv: N=1, Z=0, C=0, V=0
[16:50:11 553][libEncryptor.so 0x02e60] [8b2d0653] 0x12002e60: "ubfx w11, w12, #6, #6" w11=0x3 w12=0xfa10c30 => w11=0x30
[16:50:11 553][libEncryptor.so 0x02e64] [8c0d1412] 0x12002e64: "and w12, w12, #0xf000" w12=0xfa10c30 => w12=0x0
[16:50:11 553][libEncryptor.so 0x02e68] [4c7e1433] 0x12002e68: "bfxil w12, w18, #0x14, #0xc" w12=0x0 w18=0x4000000 => w12=0x40
[16:50:11 553][libEncryptor.so 0x02e6c] [8b010b2a] 0x12002e6c: "orr w11, w12, w11" w12=0x40 w11=0x30 => w11=0x70
[16:50:11 553][libEncryptor.so 0x02e70] [a94e298b] 0x12002e70: "add x9, x21, w9, uxtw #3" x21=0xe4fff538 w9=0x1d => x9=0xe4fff620
[16:50:11 553][libEncryptor.so 0x02e74] [ea7aaab8] 0x12002e74: "ldrsw x10, [x23, x10, lsl #2]" x10=0x1c x23=0x1200ab24 => x10=0xffffffffffff88e4
[16:50:11 553][libEncryptor.so 0x02e78] [6b51512a] 0x12002e78: "orr w11, w11, w17, lsr #20" w11=0x70 w17=0x8000000 => w11=0xf0
[16:50:11 553][libEncryptor.so 0x02e7c] [290540f9] 0x12002e7c: "ldr x9, [x9, #8]" x9=0xe4fff620 => x9=0xe4fff310
[16:50:11 553][libEncryptor.so 0x02e80] [6b51502a] 0x12002e80: "orr w11, w11, w16, lsr #20" w11=0xf0 w16=0x0 => w11=0xf0
[16:50:11 553][libEncryptor.so 0x02e84] [6b514f2a] 0x12002e84: "orr w11, w11, w15, lsr #20" w11=0xf0 w15=0x0 => w11=0xf0
[16:50:11 553][libEncryptor.so 0x02e88] [6b514e2a] 0x12002e88: "orr w11, w11, w14, lsr #20" w11=0xf0 w14=0x0 => w11=0xf0
[16:50:11 553][libEncryptor.so 0x02e8c] [6b514d2a] 0x12002e8c: "orr w11, w11, w13, lsr #20" w11=0xf0 w13=0x0 => w11=0xf0
[16:50:11 553][libEncryptor.so 0x02e90] [4a01178b] 0x12002e90: "add x10, x10, x23" x10=0xffffffffffff88e4 x23=0x1200ab24 => x10=0x12003408
[16:50:11 553][libEncryptor.so 0x02e94] [29a12b8b] 0x12002e94: "add x9, x9, w11, sxth" x9=0xe4fff310 w11=0xf0 => x9=0xe4fff400
[16:50:11 553][libEncryptor.so 0x02e98] [40011fd6] 0x12002e98: "br x10" x10=0x12003408
[16:50:11 554][libEncryptor.so 0x03408] [a84e288b] 0x12003408: "add x8, x21, w8, uxtw #3" x21=0xe4fff538 w8=0x1 => x8=0xe4fff540
[16:50:11 554][libEncryptor.so 0x0340c] [080940b9] 0x1200340c: "ldr w8, [x8, #8]" x8=0xe4fff540 => w8=0x20
[16:50:11 554][libEncryptor.so 0x03410] [280100b9] 0x12003410: "str w8, [x9]" w8=0x20 x9=0xe4fff400 => w8=0x20
[16:50:11 554][libEncryptor.so 0x03414] [99010014] 0x12003414: "b #0x12003a78"

STORE Xn, [Xd + #imme]

opcode 0xc b labe
[16:50:11 927][libEncryptor.so 0x02f80] [c15700b5] 0x12002f80: "cbnz x1, #0x12003a78" x1=0x0
[16:50:11 927][libEncryptor.so 0x02f84] [81150012] 0x12002f84: "and w1, w12, #0x3f" w12=0x800068c => w1=0xc
[16:50:11 927][libEncryptor.so 0x02f88] [3ff40071] 0x12002f88: "cmp w1, #0x3d" => nzcv: N=1, Z=0, C=0, V=0 w1=0xc
[16:50:11 927][libEncryptor.so 0x02f8c] [68570054] 0x12002f8c: "b.hi #0x12003a78" nzcv: N=1, Z=0, C=0, V=0
[16:50:11 928][libEncryptor.so 0x02f90] [83351412] 0x12002f90: "and w3, w12, #0x3fff000" w12=0x800068c => w3=0x0
[16:50:11 928][libEncryptor.so 0x02f94] [822d0653] 0x12002f94: "ubfx w2, w12, #6, #6" w2=0x12002f80 w12=0x800068c => w2=0x1a
[16:50:11 928][libEncryptor.so 0x02f98] [56000090] 0x12002f98: "adrp x22, #0x1200a000" => x22=0x1200a000
[16:50:11 928][libEncryptor.so 0x02f9c] [437e1433] 0x12002f9c: "bfxil w3, w18, #0x14, #0xc" w3=0x0 w18=0x0 => w3=0x0
[16:50:11 928][libEncryptor.so 0x02fa0] [d6b22891] 0x12002fa0: "add x22, x22, #0xa2c" x22=0x1200a000 => x22=0x1200aa2c
[16:50:11 928][libEncryptor.so 0x02fa4] [7200022a] 0x12002fa4: "orr w18, w3, w2" w3=0x0 w2=0x1a => w18=0x1a
[16:50:11 928][libEncryptor.so 0x02fa8] [c17aa1b8] 0x12002fa8: "ldrsw x1, [x22, x1, lsl #2]" x1=0xc x22=0x1200aa2c => x1=0xffffffffffff893c
[16:50:11 928][libEncryptor.so 0x02fac] [5152512a] 0x12002fac: "orr w17, w18, w17, lsr #20" w18=0x1a w17=0x8000000 => w17=0x9a
[16:50:11 929][libEncryptor.so 0x02fb0] [3052502a] 0x12002fb0: "orr w16, w17, w16, lsr #20" w17=0x9a w16=0x0 => w16=0x9a
[16:50:11 929][libEncryptor.so 0x02fb4] [0f524f2a] 0x12002fb4: "orr w15, w16, w15, lsr #20" w16=0x9a w15=0x0 => w15=0x9a
[16:50:11 929][libEncryptor.so 0x02fb8] [ee514e2a] 0x12002fb8: "orr w14, w15, w14, lsr #20" w15=0x9a w14=0x0 => w14=0x9a
[16:50:11 929][libEncryptor.so 0x02fbc] [cd514d2a] 0x12002fbc: "orr w13, w14, w13, lsr #20" w14=0x9a w13=0x0 => w13=0x9a
[16:50:11 929][libEncryptor.so 0x02fc0] [2e00168b] 0x12002fc0: "add x14, x1, x22" x1=0xffffffffffff893c x22=0x1200aa2c => x14=0x12003368
[16:50:11 929][libEncryptor.so 0x02fc4] [ea031f2a] 0x12002fc4: "mov w10, wzr" => w10=0x0
[16:50:11 929][libEncryptor.so 0x02fc8] [eb031faa] 0x12002fc8: "mov x11, xzr" => x11=0x0
[16:50:11 929][libEncryptor.so 0x02fcc] [ad751e53] 0x12002fcc: "lsl w13, w13, #2" w13=0x9a => w13=0x268
[16:50:11 929][libEncryptor.so 0x02fd0] [c0011fd6] 0x12002fd0: "br x14" x14=0x12003368
[16:50:11 930][libEncryptor.so 0x03368] [68825ff8] 0x12003368: "ldur x8, [x19, #-8]" x8=0x0 x19=0xe4fff670 => x8=0x1200b090
[16:50:11 930][libEncryptor.so 0x0336c] [eb031f2a] 0x1200336c: "mov w11, wzr" => w11=0x0
[16:50:11 930][libEncryptor.so 0x03370] [e9031f2a] 0x12003370: "mov w9, wzr" => w9=0x0
[16:50:11 930][libEncryptor.so 0x03374] [ec030032] 0x12003374: "orr w12, wzr, #1" w12=0x800068c => w12=0x1
[16:50:11 930][libEncryptor.so 0x03378] [08412d8b] 0x12003378: "add x8, x8, w13, uxtw" x8=0x1200b090 w13=0x268 => x8=0x1200b2f8
[16:50:11 930][libEncryptor.so 0x0337c] [65000014] 0x1200337c: "b #0x12003510"
[16:50:11 930][libEncryptor.so 0x03510] [3f010071] 0x12003510: "cmp w9, #0" => nzcv: N=0, Z=1, C=1, V=0 w9=0x0
[16:50:11 930][libEncryptor.so 0x03514] [ed079f1a] 0x12003514: "cset w13, ne" nzcv: N=0, Z=1, C=1, V=0 => w13=0x0
[16:50:11 930][libEncryptor.so 0x03518] [6b010d0a] 0x12003518: "and w11, w11, w13" w11=0x0 w13=0x0 => w11=0x0
[16:50:11 930][libEncryptor.so 0x0351c] [68821ef8] 0x1200351c: "stur x8, [x19, #-0x18]" x8=0x1200b2f8 x19=0xe4fff670 => x8=0x1200b2f8
[16:50:11 930][libEncryptor.so 0x03520] [8b000037] 0x12003520: "tbnz w11, #0, #0x12003530" w11=0x0
[16:50:11 930][libEncryptor.so 0x03524] [69000034] 0x12003524: "cbz w9, #0x12003530" w9=0x0
[16:50:11 930][libEncryptor.so 0x03530] [7f010071] 0x12003530: "cmp w11, #0" => nzcv: N=0, Z=1, C=1, V=0 w11=0x0
[16:50:11 930][libEncryptor.so 0x03534] [0817989a] 0x12003534: "cinc x8, x24, eq" nzcv: N=0, Z=1, C=1, V=0 x24=0x1 => x8=0x2
[16:50:11 930][libEncryptor.so 0x03538] [68021ef8] 0x12003538: "stur x8, [x19, #-0x20]" x8=0x2 x19=0xe4fff670 => x8=0x2
[16:50:11 930][libEncryptor.so 0x0353c] [5f050071] 0x1200353c: "cmp w10, #1" => nzcv: N=1, Z=0, C=0, V=0 w10=0x0
[16:50:11 930][libEncryptor.so 0x03540] [cb290054] 0x12003540: "b.lt #0x12003a78" nzcv: N=1, Z=0, C=0, V=0

vmp编码的起始地址加上一个常数存到[x19,#- 0x18], [x19,#- 0x20]设置为2,实现指令 b labe

opcode 0x7 or xd, xn, #imm
[16:50:10 533][libEncryptor.so 0x02ecc] [800d1412] 0x12002ecc: "and w0, w12, #0xf000" w12=0x84215c07 => w0=0x5000
[16:50:10 533][libEncryptor.so 0x02ed0] [8a2d0653] 0x12002ed0: "ubfx w10, w12, #6, #6" w10=0xb w12=0x84215c07 => w10=0x30
[16:50:10 533][libEncryptor.so 0x02ed4] [407e1433] 0x12002ed4: "bfxil w0, w18, #0x14, #0xc" w0=0x5000 w18=0x4000000 => w0=0x5040
[16:50:10 533][libEncryptor.so 0x02ed8] [0a000a2a] 0x12002ed8: "orr w10, w0, w10" w0=0x5040 w10=0x30 => w10=0x5070
[16:50:10 533][libEncryptor.so 0x02edc] [4a51512a] 0x12002edc: "orr w10, w10, w17, lsr #20" w10=0x5070 w17=0x0 => w10=0x5070
[16:50:10 533][libEncryptor.so 0x02ee0] [4a51502a] 0x12002ee0: "orr w10, w10, w16, lsr #20" w10=0x5070 w16=0x0 => w10=0x5070
[16:50:10 534][libEncryptor.so 0x02ee4] [4a514f2a] 0x12002ee4: "orr w10, w10, w15, lsr #20" w10=0x5070 w15=0x0 => w10=0x5070
[16:50:10 534][libEncryptor.so 0x02ee8] [8b150012] 0x12002ee8: "and w11, w12, #0x3f" w12=0x84215c07 => w11=0x7
[16:50:10 534][libEncryptor.so 0x02eec] [4a514e2a] 0x12002eec: "orr w10, w10, w14, lsr #20" w10=0x5070 w14=0x0 => w10=0x5070
[16:50:10 534][libEncryptor.so 0x02ef0] [7fcd0071] 0x12002ef0: "cmp w11, #0x33" => nzcv: N=1, Z=0, C=0, V=0 w11=0x7
[16:50:10 534][libEncryptor.so 0x02ef4] [4a514d2a] 0x12002ef4: "orr w10, w10, w13, lsr #20" w10=0x5070 w13=0x80000000 => w10=0x5870
[16:50:10 534][libEncryptor.so 0x02ef8] [4c090054] 0x12002ef8: "b.gt #0x12003020" nzcv: N=1, Z=0, C=0, V=0
[16:50:10 534][libEncryptor.so 0x02efc] [7f1d0071] 0x12002efc: "cmp w11, #7" => nzcv: N=0, Z=1, C=1, V=0 w11=0x7
[16:50:10 535][libEncryptor.so 0x02f00] [002b0054] 0x12002f00: "b.eq #0x12003460" nzcv: N=0, Z=1, C=1, V=0
[16:50:10 535][libEncryptor.so 0x03460] [ab220091] 0x12003460: "add x11, x21, #8" x21=0xe4fff538 => x11=0xe4fff540
[16:50:10 536][libEncryptor.so 0x03464] [695969f8] 0x12003464: "ldr x9, [x11, w9, uxtw #3]" x11=0xe4fff540 w9=0x1 => x9=0xffffffffff220000
[16:50:10 536][libEncryptor.so 0x03468] [4a3d0012] 0x12003468: "and w10, w10, #0xffff" w10=0x5870 => w10=0x5870
[16:50:10 536][libEncryptor.so 0x0346c] [29010aaa] 0x1200346c: "orr x9, x9, x10" x9=0xffffffffff220000 x10=0x5870 => x9=0xffffffffff225870
[16:50:10 537][libEncryptor.so 0x03470] [695928f8] 0x12003470: "str x9, [x11, w8, uxtw #3]" x9=0xffffffffff225870 x11=0xe4fff540 w8=0x1 => x9=0xffffffffff225870
[16:50:10 537][libEncryptor.so 0x03474] [81010014] 0x12003474: "b #0x12003a78"

总分析

前函数开辟了很大的栈空间,是为了给vm函数提供空间,这个空间的首地址是被存储在了x19中

ext:0000000000002C34 94 4C 40 A9                 LDP             X20, X19, [X4] ; 将0x2c0c地址和一个虚拟地址存入x20,x19

后面会涉及到对虚拟内存指针的赋值操作

我们在分析handle的时候就注意到,虚拟寄存器的索引是通过x21来找的,x21是在x21=x19-0x138的位置

所以,x21=x19-0x138是PC指针,x19-0x130就是虚拟寄存器x0,依次类推

并且在我们进行函数调用的时候

X19 - 0x20 表示控制变量,控制流程和函数调用
X19 - 0x18 用于配合控制变量保存地址
X19 - 0x10 表示函数的返回地址
X19 - 0x8 表示初始的 PC地址,即 opcode 首地址

真实函数参数指针保存在 [X19 - 0x110],即虚拟寄存器 X4。
参数 3 保存在 [X19 - 0x108],即虚拟寄存器 X5 = 0。
参数4 ,函数地址, 保存在 [X19-0x100],即虚拟寄存器 X6。
跳板函数sub_2D1C保存在 [X19- 0xF8],即虚拟寄存器 X7。

2.编写VMP插件

如果还原出了vmp的handle对应指令,又知道函数入参约定的话,按照平常CTF的VM经验,我们的做法是写一个简单的vm还原脚本,这种脚本只能还原到汇编层面,有的脚本甚至是打印输入的数据流

这里贴一个以前网上找的并且自己修改一些的简单还原脚本

import struct

g_op1 = -1
g_op2 = -1
g_op_type = -1
g_op_str = ""
g_bl = -1
g_pc = 0


def sxtw_extend(value_32):
value_32 = value_32 & 0xFFFFFFFF
if value_32 & 0x80000000:
# extended = value_32 | 0xFFFFFFFF00000000
extended = -1 * (value_32 - 0x100000000)
sign = "-"
else:
extended = value_32 & 0x00000000FFFFFFFF
sign = "+"
return extended, sign


def sxth_extend(value_32):
low_16 = value_32 & 0xFFFF
if low_16 & 0x8000:
extended = -1 * (low_16 - 0x10000)
sign = "-"
else:
extended = low_16
sign = "+"
return extended, sign


def vmp_interpreter(data):
# init data
global g_op1
global g_op2
global g_op_type
global g_op_str
global g_bl
global g_pc
g_op1 = -1;
g_op2 = -1
g_op_type = -1
g_op_str = ""
g_bl = -1

# data handle
g_op1 = data & 0x3f
if g_op1 == 0xb:
g_op_type = 2
g_op2 = ((data & 0xfff) - 0xb) >> 6
else:
g_op_type = 1

w9 = (data >> 21) & 0x1f
w8 = (data >> 16) & 0x1f
w13 = data & 0x80000000
w14 = data & 0x40000000
w15 = data & 0x20000000
w16 = data & 0x10000000
w17 = data & 0x8000000
w18 = data & 0x4000000

if g_op1 == 0x15: #done
w0 = data & 0xf000
w10 = (data >> 6) & 0x3f
w0 = (w0 & 0xFFFFF000) | ((w18 >> 0x14) & 0xfff)
w10 = w0 | w10
w10 = w10 | w17 >> 20
w10 = w10 | w16 >> 20
w10 = w10 | w15 >> 20
w10 = w10 | w14 >> 20
w10 = w10 | w13 >> 20
imm, sign = sxth_extend(w10)
if sign == "-":
# print("add x%d, x%d, #-0x%x" % (w8, w9, imm))
g_op_str = "add x%d, x%d, #-0x%x" % (w8, w9, imm)
else:
# print("add x%d, x%d, #0x%x" % (w8, w9, imm))
g_op_str = "add x%d, x%d, #0x%x" % (w8, w9, imm)

elif g_op1 == 0x28: #done
w11 = (data >> 6) & 0x3F
w12 = data & 0xf000
w12 = (w12 & 0xFFFFF000) | (w18 >> 0x14 & 0xfff)
w11 = w11 | w12
w11 = w11 | (w17 >> 20)
w11 = w11 | (w16 >> 20)
w11 = w11 | (w15 >> 20)
w11 = w11 | (w14 >> 20)
w11 = w11 | (w13 >> 20)
imm, sign = sxth_extend(w11)
if sign == "-":
g_op_str = "ldr x%d, [x%d, #-0x%x]" % (w8, w9, imm)
else:
g_op_str = "ldr x%d, [x%d, #0x%x]" % (w8, w9, imm)
elif g_op1 == 0x30: #done
w11 = (data >> 6) & 0x3F
w12 = data & 0xf000
w12 = (w12 & 0xFFFFF000) | (w18 >> 0x14 & 0xfff)
w11 = w11 | w12
w11 = w11 | (w17 >> 20)
w11 = w11 | (w16 >> 20)
w11 = w11 | (w15 >> 20)
w11 = w11 | (w14 >> 20)
w11 = w11 | (w13 >> 20)
imm, sign = sxth_extend(w11)
if sign == "-":
g_op_str = "str x%d, [x%d, #-0x%x]" % (w8, w9, imm)
else:
g_op_str = "str x%d, [x%d, #0x%x]" % (w8, w9, imm)
elif g_op1 == 0x17:#done
w11 = (data >> 6) & 0x3F
w12 = data & 0xf000
w12 = (w12 & 0xFFFFF000) | (w18 >> 0x14 & 0xfff)
w11 = w11 | w12
w11 = w11 | (w17 >> 20)
w11 = w11 | (w16 >> 20)
w11 = w11 | (w15 >> 20)
w11 = w11 | (w14 >> 20)
w11 = w11 | (w13 >> 20)
imm, sign = sxth_extend(w11)
if sign == "-":
g_op_str = "str w%d, [x%d, #-0x%x]" % (w8, w9, imm)
else:
g_op_str = "str w%d, [x%d, #0x%x]" % (w8, w9, imm)
elif g_op1 == 0x7:#done
w0 = data & 0xf000
w10 = (data >> 6) & 0x3F
w0 = (w0 & 0xFFFFF000) | (w18 >> 0x14 & 0xfff)
w10 = w10 | w0
w10 = w10 | (w17 >> 20)
w10 = w10 | (w16 >> 20)
w10 = w10 | (w15 >> 20)
w10 = w10 | (w14 >> 20)
w10 = w10 | (w13 >> 20)
g_op_str = "orr x%d, x%d, #0x%x" % (w8, w9, w10 & 0xffff)
elif g_op1 == 0x34 :#done
w0 = data & 0xf000
w10 = (data >> 6) & 0x3F
w0 = (w0 & 0xFFFFF000) | (w18 >> 0x14 & 0xfff)
w10 = w10 | w0
w10 = w10 | (w17 >> 20)
w10 = w10 | (w16 >> 20)
w10 = w10 | (w15 >> 20)
w10 = w10 | (w14 >> 20)
w10 = w10 | (w13 >> 20)
# w10 = w10 << 0x10
# imm,sign = sxtw_extend(w10)
# if sign == "-":
# g_op_str = "mov x%d, #-0x%x" % (w8, imm)
# else:
# g_op_str = "mov x%d, #0x%x" % (w8, imm)
imm = w10
g_op_str = "movz w%d, #0x%x, lsl #16 --> sxtw x%d, w%d" % (w8, imm, w8, w8)
elif g_op1 == 0x18:#done
w3 = data & 0xf000
w2 = (data >> 6) & 0x3f
w3 = (w3 & 0xFFFFF000) | ((w18 >> 0x14) & 0xfff)
w18 = w3 | w2
w17 = w18 | w17 >> 20
w16 = w17 | w16 >> 20
w15 = w16 | w15 >> 20
w14 = w15 | w14 >> 20
w13 = w14 | w13 >> 20
imm, sign = sxth_extend((w13 & 0xffff) << 2)
imm = imm + 4
imm, sign = sxtw_extend(imm)
imm = imm + g_pc
g_op_str = "cmp x%d, x%d -> b.eq #0x%x" % (w9, w8, imm)
g_bl = 1
elif g_op1 == 0xc: #done
w3 = data & 0x3fff000
w2 = (data >> 6) & 0x3f
w3 = (w3 & 0xFFFFF000) | (w18 >> 0x14 & 0xfff)
w18 = w3 | w2
w17 = w18 | w17 >> 20
w16 = w17 | w16 >> 20
w15 = w16 | w15 >> 20
w14 = w15 | w14 >> 20
w13 = w14 | w13 >> 20
w13 = w13 << 2
w13, sign = sxtw_extend(w13)
w13 = 0x4000b2c0 + w13
g_op_str = "b #0x%x" % (w13)
g_bl = 1
elif g_op1 == 0xb and g_op2 == 0x7:#done
w10 = data & 0x3f
w11 = data >> 0xb
w14 = w11 & 2
w2 = w11 & 4
w14 = (w14 & 0xFFFFFFFE) | (data >> 31) & 0x1
w14 = w14 | w2
w2 = w11 & 8
w10 = w11 & 0x10
w2 = w14 | w2
w10 = w2 | w10
g_op_str = "orr x%d, x%d, x%d" % (w10, w8, w9)
elif g_op1 == 0xb and g_op2 == 0x27:#done
g_op_str = "cmp x%d, x%d -> cset x%d, lt" % (w9, w8, w8)
elif g_op1 == 0xb and g_op2 == 0x19: #done
w10 = data & 0x3f
w11 = data >> 0xb
w14 = w11 & 2
w2 = w11 & 4
w14 = (w14 & 0xFFFFFFFE) | (data >> 31) & 0x1
w14 = w14 | w2
w2 = w11 & 8
w10 = w11 & 0x10
w2 = w14 | w2
w10 = w2 | w10
imm, sign = sxtw_extend(w10)
if w10 == w8 and imm == 0:
g_op_str = "nop"
else:
g_op_str = "lsl x%d, x%d, #0x%x" % (w10, w8, imm)
elif g_op1 == 0xb and g_op2 == 0xc: #done
w10 = data & 0x3f
w11 = data >> 0xb
w14 = w11 & 2
w2 = w11 & 4
w14 = (w14 & 0xFFFFFFFE) | (data >> 31) & 0x1
w14 = w14 | w2
w2 = w11 & 8
w10 = w11 & 0x10
w2 = w14 | w2
w10 = w2 | w10
g_op_str = "add x%d, x%d, x%d" % (w10, w8, w9)
elif g_op1 == 0xb and g_op2 == 0x2b: #done
g_op_str = "blr x%d" % (w9)
g_bl = 1
elif g_op1 == 0xb and g_op2 == 0x3e:#done
g_op_str = "ret"
g_bl = 1


def read_binary_with_index(input_path, output_path, offset, length):
global g_op1
global g_op2
global g_op_type
global g_op_str
global g_bl
global g_pc
with open(input_path, 'rb') as src_file, open(output_path, 'w') as dest_file:
content = []
insert = 0
flag = 0
src_file.seek(offset)
off = offset + 0x40000000
# off = 0
chunks = (length + 3) // 4
index_counter = 0

for _ in range(chunks):
data = src_file.read(4)
if not data:
break

little_endian_byte_array = struct.pack('<I', struct.unpack('>I', data)[0])
hex_data = little_endian_byte_array.hex()
vmp_interpreter(int.from_bytes(little_endian_byte_array, 'big'))
if flag == 0:
content.append("index address encode op1 op2 asm\n")
flag = 1
if g_op_type == 1:
if insert == 1:
content.insert(index_counter,
f"{index_counter:<3} {hex(off)} {hex_data} {hex(g_op1):<4} {g_op_str}\n")
insert = 0
else:
content.append(
f"{index_counter:<3} {hex(off)} {hex_data} {hex(g_op1):<4} {g_op_str}\n")
# dest_file.write(f"{index_counter:<3} {hex(off)} {hex_data} {hex(g_op1):<4} {g_op_str}\n")
else:
if insert == 1:
content.insert(index_counter,
f"{index_counter:<3} {hex(off)} {hex_data} {hex(g_op1):<4} {hex(g_op2):<4} {g_op_str}\n")
insert = 0
else:
content.append(
f"{index_counter:<3} {hex(off)} {hex_data} {hex(g_op1):<4} {hex(g_op2):<4} {g_op_str}\n")
# dest_file.write(f"{index_counter:<3} {hex(off)} {hex_data} {hex(g_op1):<4} {hex(g_op2):<4} {g_op_str}\n")
off = off + 4
g_pc = off
if g_bl == 1:
insert = 1
index_counter = index_counter + 1
dest_file.writelines(content)


if __name__ == "__main__":
read_binary_with_index(
input_path=r"D:\Android\DOUYIN\douyin2\back_vmp\work\vmp_final\libEncryptor.so",
output_path="output.txt",
offset=0xB090,
length=0x2d8
)

还原出来的格式是

可以看到只能看到汇编的,而且这种程度也不够,我们的思路是写一个反编译插件,达到lifter到伪代码的效果,据说可以选择LLVM或者bn的插件,这里选择的插件方式

这一part的代码就基本上是AI写的了,给他反编译的思路,如今AI能在3-4天内写出一个反编译插件确实让我感到震惊。

逆向VMP的时候,感觉分析VMP是一件费事费力的操作,就算我们拿到一份trace文件,在经过VMP保护后的程序分析起来也是极其的不便,动不动就几百万行,已经不是人能看的了,现在如果是分析纯算法,在agent的帮助下,在写几个分析VMP的skill,分析其算法可以上一层楼,笔者在使用ida mcp结合trace文件,再结合agent去分析VMP的时候,AI分析handle的能力确实令人叹为观止,大概能很好的分析出70%的VMP handle了,但是VMP还原是一个痛点,我分析某音的ttencrypt的时候是硬还原,也就是直接打印出虚拟指令,但是某音的VMP似乎有非常多VM函数,分析也是困难,干脆直接写一个架构,读着方便,而且还能反编译成伪c

Binary Ninja 架构插件怎么写

1. 架构插件

Binary Ninja 的核心思路不是“只支持固定几种 CPU”,而是把“架构”和“分析”分层:前者负责把字节翻译成指令语义,后者基于这些语义继续做 CFG、数据流、MLIL、HLIL 和反编译。所以当你面对一种自定义 ISA、虚拟机字节码,或者某个未被官方支持的处理器时,最正统的做法不是硬改现有分析结果,而是定义一个新的 Architecture。这很适合某些大厂自定义VMP的解释器架构去给他做一套对应的架构

对于逆向工程来说,这一点特别重要:

在研究VMP的时候有几种方式可以做到“反编译”其指令集,第一种就是硬解析,就跟搓一个解释器一样,

对指定的,我们已经分析好了的进行硬反编译,这样的效果当然不够灵活

还有一种就是llvmlite, 但llvmlite 只能帮你“造一个 IR 和执行/优化框架”,但不会像 Binary Ninja 自定义架构那样,天然给你接上反汇编、CFG、寄存器语义、LLIL/MLIL/HLIL 和反编译流程;做 VMP 时前者更像自建分析器,后者则是直接把虚拟 ISA 接进现成逆向平台。

2. 一个最小架构插件长什么样

官方示例和文档都表明,最小的 Python 架构插件通常包含:

  • 一个继承自 Architecture 的类;
  • 在这个类里填好架构元数据;
  • 再实现几组关键接口:反汇编文本、控制流信息、LLIL lifting。

最小骨架通常类似这样:

from binaryninja import *

class MyArch(Architecture):
## 架构名字(唯一标识)
# Binary Ninja 内部用它来区分不同架构,比如 "x86", "armv7"
name = "myarch"
endianness = Endianness.LittleEndian
address_size = 8 # 地址宽度(单位:字节)
default_int_size = 8 # 默认整数宽度(很多 IL 运算的默认 size)
instr_alignment = 1 # 指令对齐(instruction alignment)
max_instr_length = 4 # BN 在解码时最多会读这么多字节

regs = { # 寄存器定义
"sp": RegisterInfo("sp", 8),
"lr": RegisterInfo("lr", 8),
}

stack_pointer = "sp"

def get_instruction_info(self, data, addr):
pass # 作用:描述“控制流行为”

def get_instruction_text(self, data, addr):
pass # 作用:生成“反汇编文本”

def get_instruction_low_level_il(self, data, addr, il):
pass # 作用:语义 lifting(最核心)

MyArch.register()

这里最重要的不是语法,而是职责分工:

  • get_instruction_text 负责“怎么看”;
  • get_instruction_info 负责“怎么跳”;
  • get_instruction_low_level_il 负责“这条指令到底做了什么”。

官方 Part 1 把前两个作为第一阶段重点,Part 2 再把 lifting 和 BNIL 作为第二阶段重点。

3. 架构元数据需要填什么

给出的 Architecture 示例里,最开始就要填一批基础属性,包括:

  • name
  • endianness
  • address_size
  • default_int_size
  • instr_alignment
  • max_instr_length
  • regs
  • stack_pointer(如果有)

这些属性看起来很“模板化”,但其实非常关键。因为 Binary Ninja 后续所有地址计算、寄存器宽度推断、栈语义、以及一部分 lifting 行为,都依赖这里的定义。比如某音的那个 24 字节定长 VM 指令集(,就应该把:

instr_alignment = 24

max_instr_length = 24

明确写出来,让 BN 从一开始就按这门 ISA 的规则去切指令。这个设计和官方架构教程是一致的:先把“架构世界观”讲清楚,再写具体指令。

4. get_instruction_text:反汇编

官方 Part 1 把“能看到正确的反汇编文本”作为第一个可验证里程碑。Binary Ninja 并不要求你一开始就把 LLIL 写满;你完全可以先把字节解码成指令文本,再慢慢补后面的分析。

这个接口的职责很单纯:

给输入字节和地址,返回:

  • 一组 InstructionTextToken
  • 当前指令长度

也就是说,这一层只关心 “这条指令长什么样”。

例如可以先打印成:

add r29, r29, #-784

此时它的价值主要体现在两个方面:

  • 你终于可以用 Binary Ninja 的界面浏览虚拟机指令流,而不是只看 hexdump;
  • 你可以验证自己的解码器是否稳定。
5. get_instruction_info:CFG

如果说 get_instruction_text 解决的是“显示问题”,那 get_instruction_info 解决的就是“控制流问题”。

Binary Ninja 需要知道一条指令是不是:

  • 普通顺序执行;
  • 无条件跳转;
  • 条件跳转;
  • 调用;
  • 返回;
  • 间接跳转。

因此这个接口通常要返回一个 InstructionInfo,并填入:

  • length
  • add_branch(BranchType.TrueBranch, target)
  • add_branch(BranchType.FalseBranch, fallthrough)
  • add_branch(BranchType.UnconditionalBranch, target)
  • add_branch(BranchType.CallDestination, target)

等信息。官方教程里也是先做出 get_instruction_info,这样控制流图才会正确。

对于 VM 来说,这一步尤其重要。因为很多 VM 的 native dispatcher 是一坨间接跳转和跳表,但你真正想让 Binary Ninja 看到的,其实是“虚拟指令层面的 CFG”。这时你就需要在插件里把 VM 的分支语义直接告诉 BN,而不是把 native handler 的 br x12 原样照搬进去。

6. get_instruction_low_level_il

官方 Part 2 的重点就是这件事:

没有 LLIL,你只有“看起来像个反汇编器”;

有了 LLIL,你才真正进入 Binary Ninja 的分析流水线

因为 BNIL 的定义本身就是:

把具体架构的机器语义,翻译成架构无关的中间表示。

LLIL 处在这条链路的较低层,仍然接近汇编语言,但已经足够让 Binary Ninja 做控制流和数据流分析。官方概览文档明确指出,BNIL 是一组不同抽象层次的 IL 家族,其中 LLIL 是最接近原始指令语义、同时又能支撑更高层分析的关键层。

注:在分析VMP的handle的时候,我们盯着Low level IL其实我觉得远比直接看汇编好,因为IL我认为是比较解决硬还原VMP handle的解析地步的

LLIL 的目标不是“把原指令机械翻译成另一种语法”,而是把指令的语义动作表达出来。

举例来说,一条加法指令在 LLIL 里关心的不是原始 opcode 编码,而是:

  • 从哪个寄存器读;
  • 做了什么算术;
  • 结果写到哪里;
  • 是否影响控制流。

因此你在架构插件里写 LLIL 的时候,本质上是在告诉 Binary Ninja:

这条指令,不是长得像什么,

而是“实际干了什么”。

因为 Binary Ninja 的很多高级能力都不是直接从反汇编文本长出来的,而是从 IL 长出来的。
例如:
● 变量恢复;
● 条件传播;
● 一部分常量折叠;
● 更高层的控制流结构化;
● 最终 HLIL 反编译输出。
所以对 VM 插件来说,如果你只做 get_instruction_text,那就跟硬还原没什么区别;
如果你再做 get_instruction_info,那就可以还原CFG,这对我们进一步分析VMP的虚拟指令有大帮助;
而补上 get_instruction_low_level_il,就可以更好的还原。
LLIL

Binary Ninja 官方 LLIL 文档里一个非常重要的思路是:

LLIL 是树状表达(tree-based),它不是简单的字符串拼接,也不是纯文本伪代码。你通过 il.add、il.sub、il.set_reg、il.load、il.store、il.if_expr、il.jump 这些 API,去构建一棵表达“语义动作”的树。

例如一个很普通的整数加法:

src1 = il.reg(8, "r1")
src2 = il.reg(8, "r2")
expr = il.add(8, src1, src2)
il.append(il.set_reg(8, "r3", expr))

它的重点不是“对应哪条汇编”,而是这三件事已经完整表达了语义:

  • 读 r1
  • 读 r2
  • 相加并写回 r3

这种表达方式一旦建立起来,BN 后面的分析管线就能接力工作。

ADD

当前 VM 的指令格式是固定 24 字节:

  • bytes[0:8]:opcode
  • bytes[8]:b8
  • bytes[9]:b9
  • bytes[10:12]:imm16

而 0x85 的语义已经通过静态分析和 trace 确认是:

ADD64.imm R[b9] = R[b8] + sext16(imm16)

也就是说,这是一条:

  • 从 b8 指向的整型寄存器读值;
  • 取一个有符号 16 位立即数;
  • 相加;
  • 写回 b9 指向的整型寄存器。
1. get_instruction_text

文本反汇编阶段,就跟硬还原一个道理,只需要把字段拼出来:

def get_instruction_text(self, data, addr):
insn = decode_insn(data[:24])
if insn.opcode == 0x85:
tokens = [
InstructionTextToken(InstructionTextTokenType.InstructionToken, "add"),
InstructionTextToken(InstructionTextTokenType.TextToken, " "),
InstructionTextToken(InstructionTextTokenType.RegisterToken, f"r{insn.b9}"),
InstructionTextToken(InstructionTextTokenType.OperandSeparatorToken, ", "),
InstructionTextToken(InstructionTextTokenType.RegisterToken, f"r{insn.b8}"),
InstructionTextToken(InstructionTextTokenType.OperandSeparatorToken, ", "),
InstructionTextToken(InstructionTextTokenType.IntegerToken, f"#{insn.imm16}", insn.imm16),
]
return tokens, 24

这一步只是为了显示

它还没有告诉 BN 这条指令怎么参与分析。

3. LLIL

真正关键的是 lifting:

def get_instruction_low_level_il(self, data, addr, il):
insn = decode_insn(data[:24])

if insn.opcode == 0x85:
src = il.reg(8, f"r{insn.b8}")
imm = il.const(8, insn.imm16 & 0xffff if insn.imm16 >= 0 else (insn.imm16 & 0xffffffffffffffff))
expr = il.add(8, src, imm)
il.append(il.set_reg(8, f"r{insn.b9}", expr))
return 24

不过这里有一个实践上更好的版本:

如果你想明确表达“有符号立即数”,应该先做 Python 侧的符号扩展,再喂给 il.const。因为 LLIL 里最重要的是把“值语义”表达准确,而不是保留原始字段格式。

例如更稳妥地写成:

def sx16(v):
return v if v < 0x8000 else v - 0x10000

imm_val = sx16(insn.imm16_u)
src = il.reg(8, f"r{insn.b8}")
imm = il.const(8, imm_val & 0xffffffffffffffff)
il.append(il.set_reg(8, f"r{insn.b9}", il.add(8, src, imm)))

这段 LLIL 的本质是:

  • 读寄存器;
  • 做 64 位加法;
  • 写结果。

这就是官方所说的“把架构语义 lifting 到 LLIL”。

条件跳转

反汇编

假设某个 VM opcode 的语义是:

if R[b8] == R[b9]:
PC = PC + imm16
else:
PC = PC + 1

这类 case 在原生 handler 里通常会表现成:

  • 取两个寄存器;
  • 做比较;
  • 条件成立时修改 VM 的 PC;
  • 然后回到 dispatcher

但在 Binary Ninja 插件里,我们不应该把它“照着 native handler 抄写”;

而应该直接表达成 一条条件分支指令

这是自定义架构插件最核心的思想之一

get_instruction_info

这一步先告诉 BN,这是一条条件跳转:

def get_instruction_info(self, data, addr):
insn = decode_insn(data[:24])
info = InstructionInfo()
info.length = 24

if insn.opcode == OP_B_EQ:
cur_index = addr // 24
target_index = cur_index + insn.imm16
target_addr = target_index * 24
fallthrough = addr + 24

info.add_branch(BranchType.TrueBranch, target_addr)
info.add_branch(BranchType.FalseBranch, fallthrough)
return info

return info

这里的关键不是公式本身,而是:

Binary Ninja 的地址是按字节的,所以 VM 的“指令索引 PC”通常要映射回 byte offset。

这点在做固定长 VM 指令集时尤其重要。官方架构插件教程虽然大多以真实 CPU 为例,但它的接口约束天然要求地址和长度逻辑保持一致。

LLIL

LLIL 里,条件跳转最常见的写法是:

  • 先构造比较表达式;
  • 再用 if_expr;
  • 然后给 true/false 两条边分别标标签。

这是官方 LLIL 与架构插件教程里最典型的控制流 lifting 模式。

例如:

from binaryninja import LowLevelILLabel

def get_instruction_low_level_il(self, data, addr, il):
insn = decode_insn(data[:24])

if insn.opcode == OP_B_EQ:
lhs = il.reg(8, f"r{insn.b8}")
rhs = il.reg(8, f"r{insn.b9}")
cond = il.compare_equal(8, lhs, rhs)

cur_index = addr // 24
target_index = cur_index + insn.imm16
target_addr = target_index * 24
fallthrough = addr + 24

t = LowLevelILLabel()
f = LowLevelILLabel()

il.append(il.if_expr(cond, t, f))

il.mark_label(t)
existing_true = il.get_label_for_address(self, target_addr)
if existing_true is not None:
il.append(il.goto(existing_true))
else:
il.append(il.jump(il.const_pointer(8, target_addr)))

il.mark_label(f)
return 24

这段代码的意义很直接:

  • compare_equal 表达“两个寄存器是否相等”;
  • if_expr 建立条件分支;
  • true 边跳到目标;
  • false 边顺流执行下一条。

1.表驱动opcode

上面的脚本是解码器的原型。插件比它多做的只有一件事: 每条指令除了”叫什么”,还要告诉 BN”干了什么”,BN 自己把寄存器变成变量、把内存变成结构体字段, 最后吐出 output_buf = args->output_buf 这种人话。

另外把 opcode 对照表做成配置文件而不是写死在代码里——因为在libmetasec_ml的so里面也有这么一套VMP,但是里面的opcode对应的指令和我们这个so不一样!当时逆完这个打开libmetasec_ml的vmp是十分心累的。。。。

2.调原生函数

我们的VMP的函数入参是放在栈上的,所以这个调用约定的函数入参需要栈上去找,这点所以我让AI还原成了结构体的形式


3.符号执行器,将调用的原生函数计算

可以看到,这个vmp的调用原生函数是通过查表的方式,类似与间接调用的形式进行的,所以这里是写了一个符号执行器去进行的计算函数的真实值

4. 效果

Low Level IL

控制流修复

High Level IL

这是插件反编译的效果,可以看出效果是非常好的

参考资料