DEX VMP 101: Recovering Android DEX Virtualization and a Security Assessment
About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.
This research is published for educational and defensive purposes. The target sample is anonymized throughout.
DEX VMP 101: Recovering Android DEX Virtualization and a Security Assessment
Foreword
This is the first post in my VMP series. The next one is a long way off.
There’s basically one existing writeup on dex VMP restoration worth mentioning, and it’s very thorough — but also very complex. That analysis is built almost entirely on static data-flow analysis, which is overkill for dex VMP. It’s a sledgehammer on a fly, and the sledgehammer wasn’t mine to borrow, which was the annoying part.
There’s also a conference talk on dex VMP by an engineer from a major security vendor, but I couldn’t fully follow it — my skills are limited. The heavyweights in front of me make it look easy; I’d learn it one day and forget it the next.
VMP basics for beginners
VMP is basically the peak of code obfuscation in reverse engineering — the top milestone. Dex VMP is, in my view, one of the simpler VMPs, so we’ll start here.
The design philosophy of VMP
VMP is a virtualization protection shell. If it’s your first time hearing it, it’ll probably stump you — it’s really just a virtual execution machine, like the Java virtual machine. They work on the same principle. (The “VM” is virtualization; the “P” is protection — hence VMP.) This kind of virtual machine generally has two parts:

How the VMP execution engine works
Although VMP’s design philosophy is no different from other virtual machines, it’s very crude — basically just able to convert the virtual machine’s bytecode and run it. Pseudocode:
while(1){
// once inside the VM it starts reading bytecode; unless a quit instruction is hit, it runs forever
bytecode_ins = read(bytecode_buff); // keep reading instructions
exe(bytecode_ins) // run the instruction
}
It loops like this forever.
VMP packers
Inside the VMP execution engine you can see a bytecode_buff. Why can the VM understand it? Because the packer converts the source code into the VM’s bytecode, then packages the VM and the bytecode together into the target software.
The reverse-engineering goal
For a VMP, our goal is exactly one thing: restore it. Whether it becomes C, assembly, or Java, the point is to remove the VMP. That’s the pragmatic goal. So this post’s purpose is how to restore dex VMP back to Java code.
Analyzing the program
The Java-layer entry point
First look at the entry function. If you’ve seen the dex VMP writeup I mentioned earlier, you know that this pro.getxxx is really the entry to the dex VMP function, and the first argument represents the current function — using that number to index the already-virtualized code to execute.

Then look at the function’s definition. If you’re experienced and often write Java interacting with other code, you’ll notice this is Java↔C interop code. Writing all these types lets it return any Java object type from the C layer. It’s fine if you don’t spot it.

Finding the native address of a Java method
I didn’t want to write this section, but it’s actually simple. Lots of people hook NativeRegister, and many packers bypass NativeRegister registration through all sorts of tricks — but it’s unnecessary. A Java method’s native address fundamentally can’t be hidden. Sharing a Frida script to find a native method’s address.
var offset = -1
// this function corrects the offset
function get_android_api_jnioffset() {
if(offset != -1){
return offset
}
var native_addr = Module.findExportByName("libandroid_runtime.so", "_Z32android_os_Process_getUidForNameP7_JNIEnvP8_jobjectP8_jstring")
// console.log("native_addr:",native_addr)
var className = "android.os.Process";
var classResult = Java.use(className).class;
var methodArr = classResult.getDeclaredMethods();
for (var i = 0; i < methodArr.length; i++) {
var methodName = methodArr[i].toString();
var flags = methodArr[i].getModifiers()
if (flags & 256) {
if(methodName.indexOf("getUidForName")!= -1){
var artmethod = methodArr[i].getArtMethod();
for (var i = 0; i < 30; i=i+1) {
var jni_native_addr = Memory.readPointer(ptr(artmethod + i))
if(native_addr.equals(jni_native_addr)){
offset = i
return i
}
}
}
}
}
return -1
}
// iterate over all native methods of a class and print their addresses
function get_jni_native_method_addr(classResult) {
var jnioffset = get_android_api_jnioffset()
var methodArr = classResult.getDeclaredMethods();
for (var i = 0; i < methodArr.length; i++) {
var methodName = methodArr[i].toString();
var flags = methodArr[i].getModifiers()
if (flags & 256) {
var artmethod = methodArr[i].getArtMethod();
var native_addr = Memory.readPointer(ptr(artmethod + jnioffset))
// find the artmethod offset on this system, then use 20 + offset
var module
var offset
console.log("methodName->", methodName);
try {
module = Process.getModuleByAddress(native_addr)
offset = native_addr - module.base
console.log("Func.offset==", module.name, offset);
} catch (err) {
}
console.log("Func.getArtMethod->native_addr:", native_addr); // print the native function address called by the java method's jni function
console.log("Func.flags->", flags);
}
}
}
Analyzing the getobjresult native function
This is our main analysis target. I’ll skip how to find the native function and the .so dynamic dispatch — just find the native function address and start the analysis flow.
Initial debugging
With the package in hand and the target function located, I used the Frida script above to find the function’s offset, attached IDA to the process, and set a breakpoint there. Sure enough, the function broke here. Tracing it crashed the program immediately — there’s anti-debugging. No matter; stop all the other threads and trace. The getobjresult function sometimes runs to completion and sometimes doesn’t. But the trace did complete — 900k lines the first time, 27 million the second. The traced code length and flow were different both times. After some (relatively easy) assembly analysis, I found the inconsistency starts at this point:
.text:000000000001AD18 FC 6F BA A9 STP X28, X27, [SP,#-0x10+var_50]!
.text:000000000001AD1C FA 67 01 A9 STP X26, X25, [SP,#0x50+var_40]
.text:000000000001AD20 F8 5F 02 A9 STP X24, X23, [SP,#0x50+var_30]
.text:000000000001AD24 F6 57 03 A9 STP X22, X21, [SP,#0x50+var_20]
.text:000000000001AD28 F4 4F 04 A9 STP X20, X19, [SP,#0x50+var_10]
.text:000000000001AD2C FD 7B 05 A9 STP X29, X30, [SP,#0x50+var_s0]
.text:000000000001AD30 FD 43 01 91 ADD X29, SP, #0x50
.text:000000000001AD34 FF 83 07 D1 SUB SP, SP, #0x1E0
.text:000000000001AD38 48 D0 3B D5 MRS X8, #3, c13, c0, #2
.text:000000000001AD3C E8 07 00 F9 STR X8, [SP,#8]
.text:000000000001AD40 08 15 40 F9 LDR X8, [X8,#0x28]
.text:000000000001AD44 F9 03 01 AA MOV X25, X1
.text:000000000001AD48 FA 03 00 AA MOV X26, X0
.text:000000000001AD4C A8 03 1A F8 STUR X8, [X29,#var_60]
.text:000000000001AD50 37 A0 40 A9 LDP X23, X8, [X1,#8]
.text:000000000001AD54 29 0C 40 F9 LDR X9, [X1,#0x18]
.text:000000000001AD58 E8 5B 00 F9 STR X8, [SP,#0xB0]
.text:000000000001AD5C 28 14 40 F9 LDR X8, [X1,#0x28]
.text:000000000001AD60 E9 0F 00 F9 STR X9, [SP,#0x18]
.text:000000000001AD64 E8 5F 00 F9 STR X8, [SP,#0xB8]
.text:000000000001AD68 28 00 40 F9 LDR X8, [X1]
.text:000000000001AD6C E8 57 00 F9 STR X8, [SP,#0xA8]
.text:000000000001AD70 28 8F 44 78 LDRH W8, [X25,#0x48]!
.text:000000000001AD74 68 00 00 34 CBZ W8, loc_1AD80
.text:000000000001AD74
.text:000000000001AD78 E8 03 00 32 MOV W8, #1
.text:000000000001AD7C 28 E0 01 39 STRB W8, [X1,#0x78]
.text:000000000001AD7C
.text:000000000001AD80
.text:000000000001AD80 loc_1AD80 ; CODE XREF: bl_x8+5C↑j
.text:000000000001AD80 F4 02 40 79 LDRH W20, [X23]
.text:000000000001AD84 28 60 02 91 ADD X8, X1, #0x98
.text:000000000001AD88 F6 02 00 B0 ADRP X22, #data_rel@PAGE
.text:000000000001AD8C E8 53 00 F9 STR X8, [SP,#0xA0]
.text:000000000001AD90 88 1E 40 92 AND X8, X20, #0xFF
.text:000000000001AD94 D6 C2 19 91 ADD X22, X22, #data_rel@PAGEOFF
.text:000000000001AD98 38 40 02 91 ADD X24, X1, #0x90
.text:000000000001AD9C C8 7A 68 F8 LDR X8, [X22,X8,LSL#3] ; @64[X22 + (X8 << 0x3)], X8
.text:000000000001ADA0 29 00 03 91 ADD X9, X1, #0xC0
.text:000000000001ADA4 F8 A7 08 A9 STP X24, X9, [SP,#0x88]
.text:000000000001ADA8 29 A0 02 91 ADD X9, X1, #0xA8
.text:000000000001ADAC E9 4F 00 F9 STR X9, [SP,#0x98]
.text:000000000001ADB0 29 80 02 91 ADD X9, X1, #0xA0
.text:000000000001ADB4 3B C0 02 91 ADD X27, X1, #0xB0
.text:000000000001ADB8 E9 67 07 A9 STP X9, X25, [SP,#0x70]
.text:000000000001ADBC 29 40 03 91 ADD X9, X1, #0xD0
.text:000000000001ADC0 E9 37 00 F9 STR X9, [SP,#0x68]
.text:000000000001ADC4 E1 27 00 F9 STR X1, [SP,#0x48]
.text:000000000001ADC8 FA 43 00 F9 STR X26, [SP,#0x80]
.text:000000000001ADCC FB 1F 00 F9 STR X27, [SP,#0x38]
.text:000000000001ADD0 00 01 1F D6 BR X8
Why paste this assembly? Actually I didn’t have to. But if one day you want to tackle VMP and you see this br x8 pattern — constantly reading x23’s content and adding the x22 address — that’s dex VMP. I surveyed a few similar samples and they all look like this.
Then you enter completely unreadable out-of-order execution.

There are a lot of diagrams, all scrambled. At first I thought it was obfuscation, but after analysis I found it wasn’t. The execution order just flies all over the place. For a normal function — or a C-compiled function — you recognize it by its prologue and epilogue and balanced stack, a complete function. But this one is all over the place, no prologue, no epilogue.
Identifying the protection scheme
For an unknown packer, brute-forcing it entirely from assembly — especially 27 million lines of it — is way too hard. So the first step is to identify it, then go find an expert’s method to treat it accordingly. How to identify it? Keep running and guessing; this takes a while. After analysis I found it contains a huge number of JNI functions:
switch ( v63 )
{
case 'B':
v72 = (v55->functions->NewByteArray)(v55, v57, a3, a4, a5, a6, a7, a8);
goto LABEL_24;
case 'C':
v72 = (v55->functions->NewCharArray)(v55, v57, a3, a4, a5, a6, a7, a8);
goto LABEL_24;
case 'D':
v72 = (v55->functions->NewDoubleArray)(v55, v57, a3, a4, a5, a6, a7, a8);
goto LABEL_24;
case 'F':
v72 = (v55->functions->NewFloatArray)(v55, v57, a3, a4, a5, a6, a7, a8);
goto LABEL_24;
case 'I':
v72 = (v55->functions->NewIntArray)(v55, v57, a3, a4, a5, a6, a7, a8);
goto LABEL_24;
case 'J':
v72 = (v55->functions->NewLongArray)(v55, v57, a3, a4, a5, a6, a7, a8);
goto LABEL_24;
case 'S':
v72 = (v55->functions->NewShortArray)(v55, v57, a3, a4, a5, a6, a7, a8);
goto LABEL_24;
case 'Z':
v72 = (v55->functions->NewBooleanArray)(v55, v57, a3, a4, a5, a6, a7, a8);
LABEL_24:
cls_name = v58->functions->FindClass(v58, v100);
functions = v58->functions;
v103 = v58;
cls_name_2 = cls_name;
v105 = v58;
obj = *(a39 + 8 * a22);
functions->ExceptionClear(&v105->functions);
free(v100);
method_id = v103->functions->GetMethodID(&v103->functions, cls_name_2, method_name, fun_sign);
free(fun_sign);
if ( method_id )
{
a40 = 0.0;
v57 = a32;
if ( v95 )
{
switch ( *return_type )
{
case 'B':
v110 = env->functions->CallNonvirtualByteMethodA(env, obj, cls_name_2, method_id, args);
goto LABEL_74;
case 'C':
v115 = env->functions->CallNonvirtualCharMethodA(env, obj, cls_name_2, method_id, args);
goto LABEL_87;
case 'D':
env->functions->CallNonvirtualDoubleMethodA(env, obj, cls_name_2, method_id, args);
goto LABEL_89;
case 'F':
env->functions->CallNonvirtualFloatMethodA(env, obj, cls_name_2, method_id, args);
goto LABEL_78;
case 'I':
v112 = env->functions->CallNonvirtualIntMethodA(env, obj, cls_name_2, method_id, args);
goto LABEL_81;
case 'J':
v113 = COERCE_DOUBLE(env->functions->CallNonvirtualLongMethodA(env, obj, cls_name_2, method_id, args));
goto LABEL_84;
case 'L':
case '[':
v108 = env;
v109 = COERCE_DOUBLE(env->functions->CallNonvirtualObjectMethodA(env, obj, cls_name_2, method_id, args));
goto LABEL_68;
case 'S':
v117 = env->functions->CallNonvirtualShortMethodA(env, obj, cls_name_2, method_id, args);
goto LABEL_92;
case 'V':
env->functions->CallNonvirtualVoidMethodA(env, obj, cls_name_2, method_id, args);
goto LABEL_93;
case 'Z':
v114 = env->functions->CallNonvirtualBooleanMethodA(env, obj, cls_name_2, method_id, args);
goto LABEL_96;
default:
goto LABEL_97;
}
}
switch ( *return_type )
{
case 'B':
v110 = CallNonvirtualByteMethodV(env, obj, cls_name_2, method_id);
LABEL_74:
v44 = v110;
goto LABEL_75;
case 'C':
v115 = CallNonvirtualCharMethodV(env, obj, cls_name_2, method_id);
LABEL_87:
v44 = v115;
Lots and lots of it. Analyzing this data while running, I found it calls a lot of Java code, all kinds, many I’ve never even used.
-
First characteristic: it uses a large number of JNI functions and basically nothing else.
-
Second: the function pasted above — no normal function call has this many parameters and this many JNI method calls. It’s clearly a general-purpose C function for calling any Java method.
-
Third: when analyzing the entry earlier I ran into a constant function list. Through cross-references I found many functions point into this constant function list.

Something felt off, so I counted: exactly 256. That number is subtle — and not normal.
-
Fourth: analyzing what these functions do, they look more like executing a single Dalvik instruction (I’m very familiar with dalvik; I hand-wrote smali for a while).
Case basically closed. 256 instructions correspond to 256 Dalvik opcodes, even if many are unused.
Analyzing the VMP framework
Earlier I said the goal is to restore a certain piece of code. For VMP, the only way to restore is to analyze the whole VMP framework, analyze its functional meaning, then manually convert a given VM instruction into Java code. In plain terms: the reverse process of a VMP.
- The VMP basic instruction unit. That 256-function-address array from earlier — I immediately judged it as the VMP’s basic instruction unit. It was too coincidental, and it costs nothing to try (for reverse engineering, guessing-and-testing is also a very important method).
- The VM’s running mode
while(1){
// once inside the VM it starts reading bytecode; unless a quit instruction is hit, it runs forever
bytecode_ins = read(bytecode_buff); // keep reading instructions
exe(bytecode_ins) // run the instruction
}
The one I wrote earlier is a simple execution sketch: it necessarily loops continuously to execute the basic instruction units; unless a basic unit exits, it loops forever. But during actual analysis I found no while loop — no reading instructions inside a while and jumping to different basic units based on the instruction. (The cost of a wrong guess is doubting your own skill.)
Then I studied the trace to see how instructions are linked — how one instruction jumps to the next. (Studied many instructions; long functions had no gaps and I couldn’t tell where the exit was, but luckily there were some short ones.)
.text:00000076F68DBDD4 nop ; DATA XREF: .data.rel.ro:00000076F6938940↓o
.text:00000076F68DBDD4 F7 0A 00 91 ADD X23, X23, #2 ; monitor:loc_76F690C534↓j
.text:00000076F68DBDD8 F4 02 40 79 LDRH W20, [X23]
.text:00000076F68DBDDC 88 1E 40 92 AND X8, X20, #0xFF
.text:00000076F68DBDE0 C8 7A 68 F8 LDR X8, [X22,X8,LSL#3]
.text:00000076F68DBDE4 00 01 1F D6 BR X8
.text:00000076F68E0C0C C9 02 00 B0 ADRP X9, #translator_8_12_2@PAGE
.text:00000076F68E0C10 CB 02 00 B0 ADRP X11, #translator_12_16_2@PAGE
.text:00000076F68E0C14 29 01 47 F9 LDR X9, [X9,#translator_8_12_2@PAGEOFF]
.text:00000076F68E0C18 6B 45 47 F9 LDR X11, [X11,#translator_12_16_2@PAGEOFF]
.text:00000076F68E0C1C 88 2E 48 D3 UBFX X8, X20, #8, #4
.text:00000076F68E0C20 8A 3E 4C D3 UBFX X10, X20, #0xC, #4
.text:00000076F68E0C24 28 69 68 38 LDRB W8, [X9,X8]
.text:00000076F68E0C28 69 69 6A 38 LDRB W9, [X11,X10]
.text:00000076F68E0C2C 28 6D 1C 33 BFI W8, W9, #4, #0x1C
.text:00000076F68E0C30 13 1D 00 13 SXTB W19, W8
.text:00000076F68E0C34 F3 00 F8 36 TBZ W19, #0x1F, loc_76F68E0C50
.text:00000076F68E0C34
.text:00000076F68E0C38 28 03 40 39 LDRB W8, [X25]
.text:00000076F68E0C3C A8 00 20 36 TBZ W8, #4, loc_76F68E0C50
.text:00000076F68E0C3C
.text:00000076F68E0C40 E8 5B 40 F9 LDR X8, [SP,#arg_B0]
.text:00000076F68E0C44 E0 27 40 F9 LDR X0, [SP,#arg_48]
.text:00000076F68E0C48 17 01 1F F8 STUR X23, [X8,#-0x10]
.text:00000076F68E0C4C F4 C8 00 94 BL sub_76F691301C
.text:00000076F68E0C4C
.text:00000076F68E0C50
.text:00000076F68E0C50 loc_76F68E0C50 ; CODE XREF: goto+28↑j
.text:00000076F68E0C50 ; goto+30↑j
.text:00000076F68E0C50 F7 C6 33 8B ADD X23, X23, W19,SXTW#1
.text:00000076F68E0C54 F4 02 40 79 LDRH W20, [X23]
.text:00000076F68E0C58 88 1E 40 92 AND X8, X20, #0xFF
.text:00000076F68E0C5C C8 7A 68 F8 LDR X8, [X22,X8,LSL#3]
.text:00000076F68E0C60 00 01 1F D6 BR X8
Won’t paste more. Their tails are all the same: x20 is the current instruction, x22 is data_rel, x23 is the instruction data table. It computes the next instruction’s address from the previous instruction’s content, then reads the data and the instruction-handling address, and jumps via x8. It’s not a while-loop model. Pseudocode of the running model:
data_rel= instruction data array(fun1,fun2,fun3)
fun1(){
XXXXXX body
next_instruction_address=read(data_rel(current data)+fun1's instruction length)
bl next_instruction_address
}
fun2(){
XXXXXX body
next_instruction_address=read(data_rel(current data)+fun2's instruction length)
bl next_instruction_address
}
fun3(){
XXXXXX body
next_instruction_address=read(data_rel(current data)+fun3's instruction length)
bl next_instruction_address
}
call fun1
Once it starts executing, unless it hits an exit instruction, it runs forever. I saw this pattern in the Android DVM source code before — this way of computing instruction length, dalvik can indeed do it. Every instruction length can be hardcoded in advance; there’s no variable-length possibility.
Analyzing the instruction functions
We know the basic instruction units and their functions. Now analyze the basic instruction units one by one, and reverse-convert a given VMP’d Java function’s VMP instructions.
- Naked functions. All functions are naked, unlikely to be pure assembly since they still contain JNI functions. These naked functions directly scramble IDA’s disassembly.
- Out-of-order function code. Many instruction functions are in normal order, but most have an error handler in the middle, and these error handlers all branch to the same location. Bluntly: the instruction-handler function’s start address and end address are contiguous, but there’s an error-handling instruction in the middle that jumps far away, making IDA misidentify the function. Nop that error handler and it analyzes normally.
- DEX character-encoding support. I didn’t expect this one — analyzed for a long time and couldn’t figure out what a very convoluted chunk of code did. For someone who chases elegance and has OCD, that’s unacceptable. Then I ran it and found it resembles an old character-encoding format. Not knowing it doesn’t matter; it doesn’t affect anything. If you’re curious:
v62 = (*(*a30 + 0x58LL) + *(*(*a30 + 16LL) + 4 * v59));
do
v64 = *v62++;
while ( (v64 & 0x80000000) != 0 );
- Matching VMP instruction functions to dalvik instructions. Some people call this an instruction comparison table — doesn’t matter. My friend’s post already covered many restoration methods, though I didn’t fully get the exact meaning; it looked like data-flow analysis + VMP instruction analysis. But in practice I used manual analysis + deduction. Not fast, but the speed is acceptable, and not too hard. I’ll skip the details here; the main thing is familiarity with dalvik instructions. I’ll write later with other things why this isn’t hard.
- Instruction parameter matching. VMP encrypts the original register meaning and decrypts it at runtime. Registers mark this register’s offset on the stack. This kind of VMP has its own defined stack, but the positions of instruction parameters are the same as real dalvik during execution — just one extra conversion step. The code below has
translator_8_12and similar — that’s the conversion.
Restoring the VMP instructions
I heard a major vendor’s restoration used a comparison table. But by the point I’d reversed to, writing a comparison table was a waste of time — and you have to change opcode one by one. Can’t be bothered. I directly wrote a small opcode→smali IDA script that converts VMP instructions into smali, then writes them into the corresponding smali project decompiled from the dex, and recompiles. Pasting the script directly — this is a semi-automated + manual instruction-restoration script, for your reference.
import idaapi
global_table = 0x78D05D8E80
fun_id = 158
fun_addr_ref = 0x0
instr_fun_table = 0x0
# 0x1b4
translator_8_12=[3,2,1,0,7,6,5,4,0xb,0xA ,9, 8, 0xF, 0xE, 0xD, 0xC]
translator_12_16=[0xB, 0xA, 9, 8, 0xF, 0xE, 0xD, 0xC, 3, 2, 1, 0, 7, 6, 5, 4]
def aget(instr_addr,x10):
print("aget","arg:")
return 4
def const_4(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
arg0 = translator_8_12[(instr_0_2 >> 8) & 0xF]
arg1 = translator_12_16[instr_0_2 >> 12]
print("const/4 v%d, %d"%(arg0,arg1))
return 2
def const_16(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
arg0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
print("const/16 v%d, %d"%(arg0,instr_2_4))
return 4
def const(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
arg0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
print("const v%d, %d"%(arg0,instr_2_4))
return 6
def const_string(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
v60 = instr_0_2 >> 12
v61 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF
v63 = 16 * (translator_12_16[v60] & 0xF)
v75 = v63 | v61
#
a38_index_11 = read_mem_to_Int(x10 + 0x58, 8)
a38_index_2 = read_mem_to_Int(x10 + 0x10, 8)
v3 = read_mem_to_Int(instr_2_4 * 4 + a38_index_2, 4)
string = v3+ a38_index_11
# print("string addr",hex(string),hex(instr_addr))
# cla_len = read_mem_to_Int(string, 1)
# return_type = idaapi.dbg_read_memory(string+1, cla_len).decode('utf-8')
#
# print("const-string ",v75,return_type)
print("const-string v%d, %s"%(v75,hex(string)))
return 4
def xor(instr_addr,x10):
print("xor","arg:")
return 4
def move_16(instr_addr,x10):
print("move-16","arg:")
return 2
def iput_object(instr_addr,x10):
print("iput_object","arg:")
return 4
def aput_object(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v37 = (instr_0_2 >> 8) & 0xF
v38 = instr_0_2 >> 12
v40 = translator_8_12[v37];
v41 = translator_12_16[v38];
arg_v1 = v40 & 0xF | (16 * (v41 & 0xF))
arg_v2 = read_mem_to_Int(instr_addr+2, 1)
arg_v3 = read_mem_to_Int(instr_addr+3, 1)
print("aput-object v%d, v%d, v%d"%(arg_v1,arg_v2,arg_v3))
return 4
def goto(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v3 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
if v3 >= 128:
int_num = (v3 - 256)*2
else:
int_num = v3 *2
print("goto","arg:",hex(instr_addr+int_num),int_num)
return 2
def rsub(instr_addr,x10):
print("rsub","arg:")
return 4
def cmp(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v1 = read_mem_to_Int(instr_addr+2, 1)
v2 = read_mem_to_Int(instr_addr+3, 1)
v3 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
print("cmp","arg:",v3,v1,v2)
return 4
def invoke_super(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_add_4 = read_mem_to_Int(instr_addr+4, 2)
v63 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF)); # handle one byte (8 bit) of data as register conversion
register_size = v63 >> 4
arg1 = instr_add_4 & 0xF
arg2 = instr_add_4 >> 4 & 0xF
arg3 = (instr_add_4 >> 8) & 0xf
arg4 = instr_add_4 >> 12
arg5 = v63 & 0xF
ref_index = read_mem_to_Int(instr_addr + 2, 2)
classname_str = get_class_name(x10,ref_index)
method_name_str = get_method_name(x10,ref_index)
type_name_str = get_proto_type_name(x10, ref_index)
# arg_name_str = get_method_arg(x10,ref_index)
# print("invoke_direct class:",classname_str,"method:",method_name_str,"method_ref_index",hex(ref_index),"type:",type_name_str,"register:",register_size,"arg1:",arg1,"arg2:",arg2,"arg3:",arg3,"arg4:",arg4,"arg5:",arg5)
print("invoke-super ",classname_str+"->"+method_name_str+type_name_str,"method_ref_index",hex(ref_index),"register:",register_size,"arg1:",arg1,"arg2:",arg2,"arg3:",arg3,"arg4:",arg4,"arg5:",arg5)
return 6
def invoke_direct(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_add_4 = read_mem_to_Int(instr_addr+4, 2)
v63 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF)); # handle one byte (8 bit) of data as register conversion
register_size = v63 >> 4
arg1 = instr_add_4 & 0xF
arg2 = instr_add_4 >> 4 & 0xF
arg3 = (instr_add_4 >> 8) & 0xf
arg4 = instr_add_4 >> 12
arg5 = v63 & 0xF
ref_index = read_mem_to_Int(instr_addr + 2, 2)
classname_str = get_class_name(x10,ref_index)
method_name_str = get_method_name(x10,ref_index)
type_name_str = get_proto_type_name(x10, ref_index)
# arg_name_str = get_method_arg(x10,ref_index)
# print("invoke-direct class:",classname_str,"method:",method_name_str,"method_ref_index",hex(ref_index),"type:",type_name_str,"register:",register_size,"arg1:",arg1,"arg2:",arg2,"arg3:",arg3,"arg4:",arg4,"arg5:",arg5)
print("invoke-direct ",classname_str+"->"+method_name_str+type_name_str,"method_ref_index",hex(ref_index),"register:",register_size,"arg1:",arg1,"arg2:",arg2,"arg3:",arg3,"arg4:",arg4,"arg5:",arg5)
return 6
def invoke_static(instr_addr, x10): # first byte is the instruction (function offset), second byte is the register, third/fourth bytes are the method offset
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_add_4 = read_mem_to_Int(instr_addr+4, 2)
v63 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF)); # handle one byte (8 bit) of data as register conversion
register_size = v63 >> 4
arg1 = instr_add_4 & 0xF
arg2 = instr_add_4 >> 4 & 0xF
arg3 = (instr_add_4 >> 8) & 0xf
arg4 = instr_add_4 >> 12
arg5 = v63 & 0xF
ref_index = read_mem_to_Int(instr_addr + 2, 2)
classname_str = get_class_name(x10,ref_index)
method_name_str = get_method_name(x10,ref_index)
type_name_str = get_proto_type_name(x10, ref_index)
# arg_name_str = get_method_arg(x10,ref_index)
# print("invoke-static class:",classname_str,"method:",method_name_str,"method_ref_index",hex(ref_index),"type:",type_name_str,"register:",register_size,"arg1:",arg1,"arg2:",arg2,"arg3:",arg3,"arg4:",arg4,"arg5:",arg5)
print("invoke-static ",classname_str+"->"+method_name_str+type_name_str,"method_ref_index",hex(ref_index),"register:",register_size,"arg1:",arg1,"arg2:",arg2,"arg3:",arg3,"arg4:",arg4,"arg5:",arg5)
# print("CallStaticVoidMethod arg_name_str:",arg_name_str)
return 6
def invoke_interface(instr_addr,x10): # first byte is the instruction (function offset), second byte is the register, third/fourth bytes are the method offset
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_add_4 = read_mem_to_Int(instr_addr+4, 2)
v63 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF)); # handle one byte (8 bit) of data as register conversion
register_size = v63 >> 4
arg1 = instr_add_4 & 0xF
arg2 = instr_add_4 >> 4 & 0xF
arg3 = (instr_add_4 >> 8) & 0xf
arg4 = instr_add_4 >> 12
arg5 = v63 & 0xF
ref_index = read_mem_to_Int(instr_addr + 2, 2)
classname_str = get_class_name(x10,ref_index)
method_name_str = get_method_name(x10,ref_index)
type_name_str = get_proto_type_name(x10, ref_index)
# arg_name_str = get_method_arg(x10,ref_index)
print("invoke-interface ",classname_str+"->"+method_name_str+type_name_str,"method_ref_index",hex(ref_index),"register:",register_size,"arg1:",arg1,"arg2:",arg2,"arg3:",arg3,"arg4:",arg4,"arg5:",arg5)
# print("CallStaticVoidMethod arg_name_str:",arg_name_str)
return 6
def invoke_virutal(instr_addr,x10): # first byte is the instruction (function offset), second byte is the register, third/fourth bytes are the method offset
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_add_4 = read_mem_to_Int(instr_addr+4, 2)
v63 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF)); # handle one byte (8 bit) of data as register conversion
register_size = v63 >> 4
arg1 = instr_add_4 & 0xF
arg2 = instr_add_4 >> 4 & 0xF
arg3 = (instr_add_4 >> 8) & 0xf
arg4 = instr_add_4 >> 12
arg5 = v63 & 0xF
ref_index = read_mem_to_Int(instr_addr + 2, 2)
classname_str = get_class_name(x10,ref_index)
method_name_str = get_method_name(x10,ref_index)
type_name_str = get_proto_type_name(x10, ref_index)
# arg_name_str = get_method_arg(x10,ref_index)
print("invoke-virtual ",classname_str+"->"+method_name_str+type_name_str,"method_ref_index",hex(ref_index),"register:",register_size,"arg1:",arg1,"arg2:",arg2,"arg3:",arg3,"arg4:",arg4,"arg5:",arg5)
# print("CallStaticVoidMethod arg_name_str:",arg_name_str)
return 6
def rem_doule(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
instr_2_4 = read_mem_to_Int(instr_addr + 2, 2)
print("rem-doule",hex(v_arg_0),hex(instr_2_4))
return 4
def move_result_object(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
instr_2_4 = read_mem_to_Int(instr_addr + 2, 2)
print("move-result-object v%d"%v_arg_0)
return 2
def MonitorEnter(instr_addr,x10):
print("MonitorEnter","arg:")
return 2
def return_object(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
print("return-object v%d"%v_arg_0)
return 2
def move_object(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF];
v_arg_1 = translator_12_16[instr_0_2 >> 12]
print("move-object v%d, v%d"%(v_arg_0,v_arg_1))
return 2
def new_instance(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
ref_index = read_mem_to_Int(instr_addr + 2, 2)
dex_base_addr = read_mem_to_Int(x10 + 0x58, 8)
dex_type_off = read_mem_to_Int(x10 + 0x18, 8)
dex_string_list_off = read_mem_to_Int(x10 + 0x10, 8)
classname_str = byIndexGet_dex_type_name(dex_base_addr,dex_type_off,dex_string_list_off,ref_index)
print("new-instance v%d, %s"%(v_arg_0,classname_str))
return 4
def move_result(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
print("move-result v%d"%v_arg_0)
return 2
def new_array(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_size = translator_12_16[instr_0_2 >> 12]
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF];
ref_index = read_mem_to_Int(instr_addr + 2, 2)
dex_base_addr = read_mem_to_Int(x10 + 0x58, 8)
dex_type_off = read_mem_to_Int(x10 + 0x18, 8)
dex_string_list_off = read_mem_to_Int(x10 + 0x10, 8)
classname_str = byIndexGet_dex_type_name(dex_base_addr,dex_type_off,dex_string_list_off,ref_index)
print("new-array v%d, v%d, %s"%(v_arg_0,v_arg_size,classname_str))
return 4
def if_ne(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
arg1 = translator_8_12[(instr_0_2 >> 8) & 0xF];
arg2 = translator_12_16[instr_0_2 >> 12];
print("if-ne v%d, v%x, %x"%(arg1,arg2,instr_addr+instr_2_4*2))
return 4
def if_eqz(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
v63 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF)); # handle one byte (8 bit) of data as register conversion
print("if-eqz v%d, %x"%(v63,instr_addr+instr_2_4*2))
return 4
def if_gt(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
arg1 = translator_8_12[(instr_0_2 >> 8) & 0xF];
arg2 = translator_12_16[instr_0_2 >> 12];
print("if-gt v%d, v%x, %x"%(arg1,arg2,instr_addr+instr_2_4*2))
return 4
def if_lt(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
arg1 = translator_8_12[(instr_0_2 >> 8) & 0xF];
arg2 = translator_12_16[instr_0_2 >> 12];
print("if-lt v%d, v%x, %x"%(arg1,arg2,instr_addr+instr_2_4*2))
return 4
def if_lez(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
v63 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF)); # handle one byte (8 bit) of data as register conversion
print("if-lez v%d, %x"%(v63,instr_addr+instr_2_4*2))
return 4
def if_nez(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
instr_2_4 = read_mem_to_Int(instr_addr+2, 2)
v63 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF)); # handle one byte (8 bit) of data as register conversion
print("if-nez v%d, %x"%(v63,instr_addr+instr_2_4*2))
return 4
def iput(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
filed_index = read_mem_to_Int(instr_addr+2, 2)
v_arg_0 = translator_12_16[instr_0_2 >> 12]
v_arg_1 = translator_8_12[(instr_0_2 >> 8) & 0xF]
dex_filed_off = read_mem_to_Int(x10 + 0x20, 8)
class_index = read_mem_to_Int(8 * filed_index + dex_filed_off, 2)
filed_type_index = read_mem_to_Int(8 * filed_index + dex_filed_off+2, 2)
name_string_index = read_mem_to_Int(8 * filed_index + dex_filed_off+4, 2)
dex_base_addr = read_mem_to_Int(x10 + 0x58, 8)
dex_type_off = read_mem_to_Int(x10 + 0x18, 8)
dex_string_list_off = read_mem_to_Int(x10 + 0x10, 8)
class_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, class_index)
filed_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, filed_type_index)
name_string = byIndexGet_dex_string_name(dex_base_addr,dex_string_list_off,name_string_index)
# print("iput-object ",v_arg_0,v_arg_1,class_type_string,"->",name_string,";",filed_type_string,"filed_index:",filed_index)
print("iput-object v%d, v%d, %s"%(v_arg_1,v_arg_0,class_type_string+"->"+name_string+":"+filed_type_string+" filed_index: "+filed_index))
return 4
def aget_object(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
arg_1 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
arg_v2 = read_mem_to_Int(instr_addr+2, 1)
arg_v3 = read_mem_to_Int(instr_addr+3, 1)
print("aget-object v%d, v%d, v%d"%(arg_1,arg_v2,arg_v3))
return 4
def iget_object(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
filed_index = read_mem_to_Int(instr_addr+2, 2)
v_arg_0 = translator_12_16[instr_0_2 >> 12]
v_arg_1 = translator_8_12[(instr_0_2 >> 8) & 0xF]
dex_filed_off = read_mem_to_Int(x10 + 0x20, 8)
class_index = read_mem_to_Int(8 * filed_index + dex_filed_off, 2)
filed_type_index = read_mem_to_Int(8 * filed_index + dex_filed_off+2, 2)
name_string_index = read_mem_to_Int(8 * filed_index + dex_filed_off+4, 2)
dex_base_addr = read_mem_to_Int(x10 + 0x58, 8)
dex_type_off = read_mem_to_Int(x10 + 0x18, 8)
dex_string_list_off = read_mem_to_Int(x10 + 0x10, 8)
class_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, class_index)
filed_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, filed_type_index)
name_string = byIndexGet_dex_string_name(dex_base_addr,dex_string_list_off,name_string_index)
print("iget-object v%d, v%d, %s"%(v_arg_1,v_arg_0,class_type_string+"->"+name_string+":"+filed_type_string+" filed_index: "+filed_index))
return 4
def check_cast(instr_addr,x10):
type_index = read_mem_to_Int(instr_addr+2, 2)
dex_base_addr = read_mem_to_Int(x10 + 0x58, 8)
dex_type_off = read_mem_to_Int(x10 + 0x18, 8)
dex_string_list_off = read_mem_to_Int(x10 + 0x10, 8)
type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, type_index)
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
print("check-cast v%d, %s"%(v_arg,type_string))
return 4
def sget_object(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
filed_index = read_mem_to_Int(instr_addr+2, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
dex_filed_off = read_mem_to_Int(x10 + 0x20, 8)
class_index = read_mem_to_Int(8 * filed_index + dex_filed_off, 2)
filed_type_index = read_mem_to_Int(8 * filed_index + dex_filed_off+2, 2)
name_string_index = read_mem_to_Int(8 * filed_index + dex_filed_off+4, 2)
dex_base_addr = read_mem_to_Int(x10 + 0x58, 8)
dex_type_off = read_mem_to_Int(x10 + 0x18, 8)
dex_string_list_off = read_mem_to_Int(x10 + 0x10, 8)
class_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, class_index)
filed_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, filed_type_index)
name_string = byIndexGet_dex_string_name(dex_base_addr,dex_string_list_off,name_string_index)
print("sget-object v%d, %s"%(v_arg_0,class_type_string+"->"+name_string+":"+filed_type_string))
return 4
def sget_boolean(instr_addr, x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
filed_index = read_mem_to_Int(instr_addr+2, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
dex_filed_off = read_mem_to_Int(x10 + 0x20, 8)
class_index = read_mem_to_Int(8 * filed_index + dex_filed_off, 2)
filed_type_index = read_mem_to_Int(8 * filed_index + dex_filed_off+2, 2)
name_string_index = read_mem_to_Int(8 * filed_index + dex_filed_off+4, 2)
dex_base_addr = read_mem_to_Int(x10 + 0x58, 8)
dex_type_off = read_mem_to_Int(x10 + 0x18, 8)
dex_string_list_off = read_mem_to_Int(x10 + 0x10, 8)
class_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, class_index)
filed_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, filed_type_index)
name_string = byIndexGet_dex_string_name(dex_base_addr,dex_string_list_off,name_string_index)
print("sget-boolean v%d, %s"%(v_arg_0,class_type_string+"->"+name_string+":"+filed_type_string))
return 4
def nop(instr_addr,x10):
print("nop")
return 2
def return_void(instr_addr,x10):
print("return-void")
return 2
def throw(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
print("throw v%d"%v_arg_0)
return 2
def throw_2(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
v_arg_0 = translator_8_12[(instr_0_2 >> 8) & 0xF] & 0xF | (16 * (translator_12_16[instr_0_2 >> 12] & 0xF))
print("move-exception v%d"%v_arg_0)
return 2
def array_length(instr_addr,x10):
instr_0_2 = read_mem_to_Int(instr_addr, 2)
arg_0 = translator_12_16[instr_0_2 >> 12]
arg_1 = translator_8_12[(instr_0_2 >> 8) & 0xF]
print("array-length v%d, v%d"%(arg_1,arg_0))
return 2
switch = {0x0:iput, 0x1:invoke_super, 0x2:aget, 0x3:xor, 0x4:move_16, 0x5:iput_object, 0x5a:nop, 0xd:goto, 0xda:aput_object , 0x7:rsub, 0x8:cmp, 0x1e:if_lez, 0x2b:if_nez, 0x3e:if_eqz, 0x3c:if_gt,0x21:if_lt,0x44:const_16, 0xc3:const,
0x5e:move_result , 0x68:invoke_direct, 0xA6:invoke_virutal, 0xAA:new_instance, 0xc7:move_result_object, 0xbb:return_object, 0x92:return_void, 0xfc:move_object, 0x79:invoke_interface, 0xf9:check_cast,
0xf0:const_4, 0xfa:if_ne, 0x93:sget_object, 0xc2:sget_boolean, 0x9a:invoke_static, 0x9b:const_string, 0x1f:new_array, 0x19:iget_object, 0x9d:MonitorEnter,0xc4:array_length,0x6b:aget_object,0xf5:throw,0xee:throw_2}
def ByFunIDgetFunAddr(id):
offset_mem = int.from_bytes(idaapi.dbg_read_memory(4*id+global_table,4),byteorder='little')
method_addr = global_table+offset_mem
print("fun_id:",hex(id),"method_addr:",hex(offset_mem),"method_addr:",hex(method_addr))
return method_addr
def read_mem_to_String(addr,size):
mem = idaapi.dbg_read_memory(addr,size)
return mem
def read_mem_to_Int(addr, size):
mem = int.from_bytes(idaapi.dbg_read_memory(addr,size),byteorder='little')
return mem
def get_proto_type_name(x10, type_index):
dex_method_off = read_mem_to_Int(x10 + 0x28, 8)
dex_string_list_off = read_mem_to_Int(x10 + 0x10, 8)
dex_type_off = read_mem_to_Int(x10 + 0x18, 8)
dex_base_addr = read_mem_to_Int(x10 + 0x58, 8)
dex_proto_off = read_mem_to_Int(x10 + 0x30, 8)
proto_index = read_mem_to_Int(8 * type_index + dex_method_off + 2, 2)
return_type_index = read_mem_to_Int(dex_proto_off + 12 * proto_index + 4,4)
pararm_off = read_mem_to_Int(dex_proto_off + 12 * proto_index + 8,4)
sign_type_string_list = "("
if pararm_off != 0:
pararm_size = read_mem_to_Int(dex_base_addr+pararm_off,4)
# pararm_mem_total = pararm_size * 2
# for i in pararm_size:
for i in range(pararm_size):
pararm_type_index = read_mem_to_Int(dex_base_addr + pararm_off+4+i*2, 2)
pararm_type_string = byIndexGet_dex_type_name(dex_base_addr, dex_type_off, dex_string_list_off, pararm_type_index)
sign_type_string_list =sign_type_string_list+pararm_type_string
return_type_string = byIndexGet_dex_type_name(dex_base_addr,dex_type_off,dex_string_list_off,return_type_index)
sign_type_string_list=sign_type_string_list+")"+return_type_string
return sign_type_string_list
def byIndexGet_dex_type_name(dex_base_addr,dex_type_off,dex_string_list_off,pararm_type_index):
string_index = read_mem_to_Int(pararm_type_index * 4 + dex_type_off, 4)
type_string = byIndexGet_dex_string_name(dex_base_addr,dex_string_list_off,string_index)
return type_string
def byIndexGet_dex_string_name(dex_base_addr,dex_string_list_off,string_index):
v3 = read_mem_to_Int(string_index * 4 + dex_string_list_off, 4)
cla_len = read_mem_to_Int(dex_base_addr+v3,1)
class_str = idaapi.dbg_read_memory(dex_base_addr+v3+1, cla_len).decode('utf-8')
print("byIndexGet_dex_string_name:",hex(dex_base_addr+v3))
return class_str
def get_class_name(x10,class_index):
a38_index_5 = read_mem_to_Int(x10 + 0x28, 8)
a38_index_2 = read_mem_to_Int(x10 + 0x10, 8)
a38_index_3 = read_mem_to_Int(x10 + 0x18, 8)
a38_index_11 = read_mem_to_Int(x10 + 0x58, 8)
v1 = read_mem_to_Int(8 * class_index + a38_index_5, 2)
v2 = read_mem_to_Int(v1 * 4 + a38_index_3, 4)
v3 = read_mem_to_Int(v2 * 4 + a38_index_2, 4)
class_str_addr = v3+a38_index_11
cla_len = read_mem_to_Int(class_str_addr, 1)
class_str = idaapi.dbg_read_memory(class_str_addr+1, cla_len).decode('utf-8')
# print("classname len:",cla_len)
return class_str
def get_method_name(x10, method_index):
a38_index_5 = read_mem_to_Int(x10 + 0x28, 8)
a38_index_2 = read_mem_to_Int(x10 + 0x10, 8)
a38_index_3 = read_mem_to_Int(x10 + 0x18, 8)
a38_index_11 = read_mem_to_Int(x10 + 0x58, 8)
v1 = read_mem_to_Int(8 * method_index + a38_index_5 + 4, 2)
v3 = read_mem_to_Int(v1 * 4 + a38_index_2, 4)
method_name_str_addr = v3 + a38_index_11
method_name_str_len = read_mem_to_Int(method_name_str_addr, 1)
method_name_str = idaapi.dbg_read_memory(method_name_str_addr+1, method_name_str_len).decode('utf-8')
return method_name_str
def get_method_arg(x10, index):
# (v70 + * (v69 + 4LL * * (v68 + 4LL * * (v81 + v84 + 2))));
a38_index_5 = read_mem_to_Int(x10 + 0x28, 8)
a38_index_2 = read_mem_to_Int(x10 + 0x10, 8)
a38_index_3 = read_mem_to_Int(x10 + 0x18, 8)
a38_index_11 = read_mem_to_Int(x10 + 0x58, 8)
a38_index_6 = read_mem_to_Int(x10 + 0x30, 8)
v0 = read_mem_to_Int(8 * index + a38_index_5 + 2, 2)
v80 = read_mem_to_Int(a38_index_6 + 12 * v0 + 8,4)
print("arg_addr a38_index_5:",hex(a38_index_5))
print("arg_addr a38_index_2:",hex(a38_index_2))
print("arg_addr a38_index_3:",hex(a38_index_3))
print("arg_addr a38_index_11:",hex(a38_index_11))
print("arg_addr a38_index_6:",hex(a38_index_6))
print("arg_addr v80:",v80)
v81 = v80 + a38_index_11
# print("arg_addr v81:",v81)
v1 = read_mem_to_Int(v81+2, 4)
v2 = read_mem_to_Int(v1 * 4 + a38_index_3, 4)
v3 = read_mem_to_Int(v2 * 4 + a38_index_2, 4)
return_type = v3+a38_index_11
# cla_len = read_mem_to_Int(return_type, 1)
# return_type = idaapi.dbg_read_memory(return_type+1, cla_len).decode('utf-8')
print("arg_addr:",return_type)
return return_type
# def instr_handle(instr_addr,x10):
# offset_mem = read_mem(instr_addr,2) & 0xff
# instr_len = dexinstr[offset_mem](instr_addr,x10)
#
# return instr_len
def get_segment_address(segment_name):
seg = idaapi.get_segm_by_name(segment_name)
if seg is not None:
return seg.start_ea, seg.end_ea
else:
return None
# how to use
def main():
method_addr = ByFunIDgetFunAddr(fun_id)
register_size = read_mem_to_Int(method_addr, 2)
ins_size = read_mem_to_Int(method_addr+2, 2)
insns_size = read_mem_to_Int(method_addr+12, 4)
print("method registers_size:",hex(register_size))
print("method ins_size:",hex(ins_size))
print("method insns_size:",hex(insns_size))
method_addr_2 = ByFunIDgetFunAddr(fun_id+1)
funSize = method_addr_2 - method_addr
print("method_size:",hex(funSize))
instr_addr = method_addr+0x10
print("method_end:",hex(instr_addr+insns_size*2 ))
while(1):
offset = read_mem_to_Int(instr_addr, 2) & 0xff # first byte
instr_fun_addr = instr_fun_table+offset*8
print("-----------------------------------")
print("instr_fun_addr:", hex(instr_fun_addr),hex(offset))
instr_handle = switch[offset]
print("instr_addr:", hex(instr_addr))
print(hex(offset))
if(instr_handle == None):
break
instr_len = instr_handle(instr_addr, fun_addr_ref)
instr_addr = instr_addr + instr_len
print(hex(offset))
def init_var():
global global_table
global instr_fun_table
global fun_addr_ref
bss_start, bss_end = get_segment_address(".bss")
qword_78B9BFCDC8 = read_mem_to_Int(bss_start + 0x2B8, 8)
global_table = read_mem_to_Int(bss_start + 0x270, 8)
yaq2__sec = read_mem_to_Int(bss_start + 0x2B0, 8)
v6 = yaq2__sec+ fun_id *12
v6_index_1 = read_mem_to_Int(v6 + 4, 4)
v6_index_2 = read_mem_to_Int(v6 + 8, 4)
fun_addr_ref = read_mem_to_Int(8 * (v6_index_2 - 1) + qword_78B9BFCDC8, 8)
instr_fun_table, data_rel_end = get_segment_address(".data.rel.ro")
print("qword_78B9BFCDC8", hex(qword_78B9BFCDC8))
print("global_table", hex(global_table))
print("v6_index_1", hex(v6_index_1))
print("v6_index_2", hex(v6_index_2))
print("fun_addr_ref", hex(fun_addr_ref))
if __name__ == '__main__':
init_var()
main()
I won’t post the screenshot. Let me roughly explain this script’s principle and usage.
The script must be called after the VMP has finished loading. Find the restored dex and the corresponding VMP instruction address, and you can directly loop-translate them into smali instructions.
- The dex file’s skeleton (memory address), to help parse and decompile the auxiliary dalvik instructions.
- Convert a specific function’s VMP instructions, computed via the function index and the VMP rules.
- Function length: manual analysis + finding the return-type instruction.
Each concrete instruction has its concrete length written in the code. Every smali function ends with a smali return instruction (return-type instruction) — look at any smali project and you’ll see each function ends with a return instruction. It could be one of the returns; combine with jump instructions to analyze.
A security assessment of DEX VMP
Why it’s easy to attack
DEX VMP shares the same principle as binary VMP, but its reversing difficulty is nowhere near the same league. Why?
- DEX VMP’s foundation is really just JNI functions. Way too many people are familiar with those, and there are plenty of ways to analyze and trace them. Someone experienced and patient enough can even analyze code purely by tracing the JNI calls.
- DEX VMP’s framework design and runtime principle are basically the same as the DVM interpreter mode I read about back in the day.
- DEX VMP’s instructions have a one-to-one relationship with dalvik instructions — really just a simple mapping. Which means if you know dalvik instructions well enough, you effectively know the DEX VMP instructions.
For a VMP, if you know its program, know its instructions, and know how to test it (treat the DEX VMP as a black box and probe it through JNI), then even without manually analyzing every single instruction, you can still restore it gradually through pure test-based analysis.
Known mitigations
These are the ways DEX VMP could raise its security bar, given that it is reversible:
- Dynamically rotate the instruction mapping table across versions.
- Add one more mapping layer on top of the instruction parameters.
Those are the only two I know. If the DEX VMP mapping table has already been cracked, neither of these would survive automated restoration.
It feels like cracking an encrypted protocol
You know the feeling of reverse engineering a communication protocol and cracking its encryption/decryption? DEX VMP is an algorithm with a certain regularity — one that can be brute-force analyzed and cracked.
Dalvik instructions that are easy to analyze
I didn’t write out the concrete VMP-instruction restoration in detail above. Let me do it here — some experience with dalvik instructions, or rather, with decompiling and analyzing them.
Dalvik instructions are very easy to analyze, and there’s basically no value in protecting them, because ordinary protection can’t withstand static analysis tools. This has a lot to do with its design.
Strong contextual correlation — mostly compiler-generated, no low-level dalvik adversarial tricks
Some dalvik instructions are tightly coupled. For example:
0000: invoke-static {}, Lcom/hepta/davlik/JvmRuntime;->getClassLoaderList()[Ljava/lang/ClassLoader; # method@0012
0003: move-result-object v0
If the preceding invoke-static has a return value, it will always be followed by this instruction — you could even say they’re one instruction.
001e: const-string v2, "/libjvmRuntime.so" # string@0006
0020: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; # method@0028
Once a variable is defined, there’s a high probability it’s used on the very next instruction — not absolute, but likely.
000b: new-instance v1, Ljava/lang/StringBuilder; # type@001f
000d: invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V # method@0026
This is the code for creating an object: it immediately calls the init method.
return-void
return-object
return vAA
A function must end with a return instruction — though hitting a return doesn’t always mean the function ends. Still, when doing manual restoration, using context to locate the function’s end is extremely valuable.
Explicit types — instructions have almost no ambiguity
There are many instructions that perform the same operation but on different types.

This aget instruction has different variants for different types. There’s a corresponding index in the instruction, so you can even directly look up the type and reverse-engineer what type the instruction operates on. This design also indirectly shows that a dalvik instruction has exactly one interpretation — no ambiguity, unlike assembly or C where you don’t know the type and don’t know exactly what the operation is, only that some data was manipulated. Even for virtual functions, where in Java you might not tell which class it actually calls, in dalvik the class type is hardcoded in the index.
Few real functional instructions
Dalvik claims 256 opcodes, but many are unused, and others are the same function for different types.
switch (opcode.format) {
case Format10t:
return new FixDexBackedInstruction10t(dexFile, opcode, instructionStartOffset,reader);
case Format10x:
return new DexBackedInstruction10x(dexFile, opcode, instructionStartOffset); //
case Format11n:
return new FixDexBackedInstruction11n(dexFile, opcode, instructionStartOffset,reader);
case Format11x:
return new FixDexBackedInstruction11x(dexFile, opcode, instructionStartOffset,reader);
case Format12x:
return new FixDexBackedInstruction12x(dexFile, opcode, instructionStartOffset,reader);
case Format20bc:
return new FixDexBackedInstruction20bc(dexFile, opcode, instructionStartOffset,reader);
case Format20t:
return new FixDexBackedInstruction20t(dexFile, opcode, instructionStartOffset,reader);
case Format21c:
return new FixDexBackedInstruction21c(dexFile, opcode, instructionStartOffset,reader);
case Format21ih:
return new FixDexBackedInstruction21ih(dexFile, opcode, instructionStartOffset,reader);
case Format21lh:
return new FixDexBackedInstruction21lh(dexFile, opcode, instructionStartOffset,reader);
case Format21s:
return new FixDexBackedInstruction21s(dexFile, opcode, instructionStartOffset,reader);
case Format21t:
return new FixDexBackedInstruction21t(dexFile, opcode, instructionStartOffset,reader);
case Format22b:
return new FixDexBackedInstruction22b(dexFile, opcode, instructionStartOffset,reader);
case Format22c:
return new FixDexBackedInstruction22c(dexFile, opcode, instructionStartOffset,reader);
case Format22cs:
return new FixDexBackedInstruction22cs(dexFile, opcode, instructionStartOffset,reader);
case Format22s:
return new FixDexBackedInstruction22s(dexFile, opcode, instructionStartOffset,reader);
case Format22t:
return new FixDexBackedInstruction22t(dexFile, opcode, instructionStartOffset,reader);
case Format22x:
return new FixDexBackedInstruction22x(dexFile, opcode, instructionStartOffset,reader);
case Format23x:
return new FixDexBackedInstruction23x(dexFile, opcode, instructionStartOffset,reader);
case Format30t:
return new FixDexBackedInstruction30t(dexFile, opcode, instructionStartOffset,reader);
case Format31c:
return new FixDexBackedInstruction31c(dexFile, opcode, instructionStartOffset,reader);
case Format31i:
return new FixDexBackedInstruction31i(dexFile, opcode, instructionStartOffset,reader);
case Format31t:
return new FixDexBackedInstruction31t(dexFile, opcode, instructionStartOffset,reader);
case Format32x:
return new FixDexBackedInstruction32x(dexFile, opcode, instructionStartOffset,reader);
case Format35c:
return new FixDexBackedInstruction35c(dexFile, opcode, instructionStartOffset,reader);
case Format35ms:
return new FixDexBackedInstruction35ms(dexFile, opcode, instructionStartOffset,reader);
case Format35mi:
return new FixDexBackedInstruction35mi(dexFile, opcode, instructionStartOffset,reader);
case Format3rc:
return new FixDexBackedInstruction3rc(dexFile, opcode, instructionStartOffset,reader);
case Format3rmi:
return new FixDexBackedInstruction3rmi(dexFile, opcode, instructionStartOffset,reader);
case Format3rms:
return new FixDexBackedInstruction3rms(dexFile, opcode, instructionStartOffset,reader);
case Format45cc:
return new FixDexBackedInstruction45cc(dexFile, opcode, instructionStartOffset,reader);
case Format4rcc:
return new FixDexBackedInstruction4rcc(dexFile, opcode, instructionStartOffset,reader);
case Format51l:
return new FixDexBackedInstruction51l(dexFile, opcode, instructionStartOffset,reader);
case PackedSwitchPayload:
return new FixDexBackedPackedSwitchPayload(dexFile, instructionStartOffset,reader);
case SparseSwitchPayload:
return new FixDexBackedSparseSwitchPayload(dexFile, instructionStartOffset,reader);
case ArrayPayload:
return new FixDexBackedArrayPayload(dexFile, instructionStartOffset,reader);
default:
throw new ExceptionWithContext("Unexpected opcode format: %s", opcode.format.toString());
These are the dalvik instructions the smali parser understands — that’s the whole set, not a lot.
Instructions are function-scoped — nicely encapsulated
For dalvik instructions to run, they must live inside a method — they can’t exceed a function’s range, and definitely can’t do assembly-style global jumps or dynamic jump addresses. Every function is a sealed black box. Sure, there might be class variables and such, but for code-meaning analysis there’s basically no problem.
Future
Usually this is the part where you brag, but I found the brag might actually come true — so I’ll leave it as a teaser.
Closing
By the way, this script combined with manual semi-automated swapping can restore function by function — I’ve tested it. The sample I picked was just some random one; many samples look roughly the same and follow the same reversing approach.
Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).
Get in touch