LLVM IR指令VM混淆分析

 未混淆编译

 编写一个最简单的测试代码, test_add函数用于对两个数相加:

int __attribute((__annotate__("vm"))) test_add(int a, int b) 
{
	int c = a + b;
	return c;
}

int main(void) {
	int c = test_add(1, 2);
	return c;
}

编译成中间代码:

 未加入混淆时编译的中间代码如下:

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @test_add(i32 %0, i32 %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i32, align 4
  store i32 %0, i32* %3, align 4
  store i32 %1, i32* %4, align 4
  %6 = load i32, i32* %3, align 4
  %7 = load i32, i32* %4, align 4
  %8 = add nsw i32 %6, %7
  store i32 %8, i32* %5, align 4
  %9 = load i32, i32* %5, align 4
  ret i32 %9
}

首先分配 3 个局部变量,参数 1 保存在 %3所在地址,参数 2 保存在 %4所在地址,接着 %3 地址取值到 %6,%4 地址取值到 %7,然后 %6 和 %7 相加保存到%8,%8 保存到 地址%5,再从 %5 地址取值到 %9,返回 %9。表达式简化后如下:

*%3 = %0

*%4 = %1

%6 = *%3

%7 = *%4

%8 = %6 + %7

*%5 = %8

%9 = *%5

return %9

实际上 %9 = %0 + %1

虚拟化混淆编译

接着加载虚拟化混淆 PASS生成处理后的 IR中间代码:

@opcodes = private global [84 x i8] c"\05\00\00\00\00\05\0C\00\00\00\01\05\04\00\00\00\05\18\00\00\00\01\05\0C\00\00\00\02,\00\00\00\05\18\00\00\00\020\00\00\00\05,\00\00\00\050\00\00\00\034\00\00\00\054\00\00\00\05$\00\00\00\01\05$\00\00\00\02,\00\00\00\05,\00\00\00\04"

define i32 @test_add(i32 %0, i32 %1) {
entry:
  %2 = alloca i32
  %3 = alloca [56 x i8]
  %4 = alloca [256 x i32]
  %5 = alloca i32
  %6 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 8
  %7 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 12
  %8 = bitcast i8* %7 to i8**
  store i8* %6, i8** %8
  %9 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 20
  %10 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 24
  %11 = bitcast i8* %10 to i8**
  store i8* %9, i8** %11
  %12 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 32
  %13 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 36
  %14 = bitcast i8* %13 to i8**
  store i8* %12, i8** %14
  %15 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 0
  %16 = bitcast i8* %15 to i32*
  store i32 %0, i32* %16
  %17 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 4
  %18 = bitcast i8* %17 to i32*
  store i32 %1, i32* %18
  store i32 0, i32* %2
  store i32 0, i32* %5
  br label %dispatch

dispatch:                                         ; preds = %loopend, %entry
  %19 = load i32, i32* %2
  %20 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %19
  %21 = load i8, i8* %20
  %22 = load i32, i32* %2
  %23 = add i32 %22, 1
  store i32 %23, i32* %2
  switch i8 %21, label %loopend [
    i8 1, label %handler_store
    i8 2, label %handler_load
    i8 3, label %handler_add
    i8 4, label %handler_ret
    i8 5, label %push_addr
    i8 6, label %store_imm1
    i8 7, label %store_imm2
    i8 8, label %store_imm4
    i8 9, label %store_imm8
  ]

handler_store:                                    ; preds = %dispatch
  %24 = load i32, i32* %2
  %25 = load i32, i32* %5
  %26 = sub i32 %25, 2
  %27 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %26
  %28 = load i32, i32* %27
  %29 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %28
  %30 = bitcast i8* %29 to i32*
  %31 = load i32, i32* %30
  %32 = sub i32 %25, 1
  %33 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %32
  %34 = load i32, i32* %33
  %35 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %34
  %36 = bitcast i8* %35 to i32**
  %37 = load i32*, i32** %36
  store i32 %31, i32* %37, align 4
  %38 = sub i32 %25, 2
  store i32 %38, i32* %5
  br label %loopend

handler_load:                                     ; preds = %dispatch
  %39 = load i32, i32* %2
  %40 = load i32, i32* %5
  %41 = sub i32 %40, 1
  %42 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %41
  %43 = load i32, i32* %42
  %44 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %43
  %45 = bitcast i8* %44 to i32**
  %46 = load i32*, i32** %45
  %47 = load i32, i32* %46, align 4
  %48 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %39
  %49 = bitcast i8* %48 to i32*
  %50 = load i32, i32* %49
  %51 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %50
  %52 = bitcast i8* %51 to i32*
  store i32 %47, i32* %52
  %53 = add i32 %39, 4
  store i32 %53, i32* %2
  %54 = sub i32 %40, 1
  store i32 %54, i32* %5
  br label %loopend

handler_add:                                      ; preds = %dispatch
  %55 = load i32, i32* %2
  %56 = load i32, i32* %5
  %57 = sub i32 %56, 2
  %58 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %57
  %59 = load i32, i32* %58
  %60 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %59
  %61 = bitcast i8* %60 to i32*
  %62 = load i32, i32* %61
  %63 = sub i32 %56, 1
  %64 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %63
  %65 = load i32, i32* %64
  %66 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %65
  %67 = bitcast i8* %66 to i32*
  %68 = load i32, i32* %67
  %69 = add nsw i32 %62, %68
  %70 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %55
  %71 = bitcast i8* %70 to i32*
  %72 = load i32, i32* %71
  %73 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %72
  %74 = bitcast i8* %73 to i32*
  store i32 %69, i32* %74
  %75 = add i32 %55, 4
  store i32 %75, i32* %2
  %76 = sub i32 %56, 2
  store i32 %76, i32* %5
  br label %loopend

handler_ret:                                      ; preds = %dispatch
  %77 = load i32, i32* %2
  %78 = load i32, i32* %5
  %79 = sub i32 %78, 1
  %80 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %79
  %81 = load i32, i32* %80
  %82 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %81
  %83 = bitcast i8* %82 to i32*
  %84 = load i32, i32* %83
  ret i32 %84

push_addr:                                        ; preds = %dispatch
  %85 = load i32, i32* %2
  %86 = load i32, i32* %5
  %87 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %85
  %88 = bitcast i8* %87 to i32*
  %89 = load i32, i32* %88
  %90 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %86
  store i32 %89, i32* %90
  %91 = add i32 %86, 1
  store i32 %91, i32* %5
  %92 = add i32 %85, 4
  store i32 %92, i32* %2
  br label %loopend

store_imm1:                                       ; preds = %dispatch
  %93 = load i32, i32* %2
  %94 = load i32, i32* %5
  %95 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %93
  %96 = load i8, i8* %95
  %97 = sub i32 %94, 1
  %98 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %97
  %99 = load i32, i32* %98
  %100 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %99
  store i8 %96, i8* %100
  %101 = sub i32 %94, 1
  store i32 %101, i32* %5
  %102 = add i32 %93, 1
  store i32 %102, i32* %2
  br label %loopend

store_imm2:                                       ; preds = %dispatch
  %103 = load i32, i32* %2
  %104 = load i32, i32* %5
  %105 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %103
  %106 = bitcast i8* %105 to i16*
  %107 = load i16, i16* %106
  %108 = sub i32 %104, 1
  %109 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %108
  %110 = load i32, i32* %109
  %111 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %110
  %112 = bitcast i8* %111 to i16*
  store i16 %107, i16* %112
  %113 = sub i32 %104, 1
  store i32 %113, i32* %5
  %114 = add i32 %103, 2
  store i32 %114, i32* %2
  br label %loopend

store_imm4:                                       ; preds = %dispatch
  %115 = load i32, i32* %2
  %116 = load i32, i32* %5
  %117 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %115
  %118 = bitcast i8* %117 to i32*
  %119 = load i32, i32* %118
  %120 = sub i32 %116, 1
  %121 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %120
  %122 = load i32, i32* %121
  %123 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %122
  %124 = bitcast i8* %123 to i32*
  store i32 %119, i32* %124
  %125 = sub i32 %116, 1
  store i32 %125, i32* %5
  %126 = add i32 %115, 4
  store i32 %126, i32* %2
  br label %loopend

store_imm8:                                       ; preds = %dispatch
  %127 = load i32, i32* %2
  %128 = load i32, i32* %5
  %129 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %127
  %130 = bitcast i8* %129 to i64*
  %131 = load i64, i64* %130
  %132 = sub i32 %128, 1
  %133 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %132
  %134 = load i32, i32* %133
  %135 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %134
  %136 = bitcast i8* %135 to i64*
  store i64 %131, i64* %136
  %137 = sub i32 %128, 1
  store i32 %137, i32* %5
  %138 = add i32 %127, 8
  store i32 %138, i32* %2
  br label %loopend

loopend:                                          ; preds = %store_imm8, %dispatch, %store_imm4, %store_imm2, %store_imm1, %push_addr, %handler_add, %handler_load, %handler_store
  br label %dispatch
}

就一个加法运算,搞成这样至于吗😩

虚拟化混淆 IR层面分析

接下来分析字节码是如何一步一步运算的:

首先分析函数的入口代码块:

entry:
  %2 = alloca i32  分配一个变量表示当前执行的字节码下标
  %3 = alloca [56 x i8]   分配虚拟内存
  %4 = alloca [256 x i32]  分配虚拟栈
  %5 = alloca i32  分配一个变量表示虚拟栈下标
  %6 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 8
  %7 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 12
  %8 = bitcast i8* %7 to i8**
  store i8* %6, i8** %8
  %9 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 20
  %10 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 24
  %11 = bitcast i8* %10 to i8**
  store i8* %9, i8** %11
  %12 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 32
  %13 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 36
  %14 = bitcast i8* %13 to i8**
  store i8* %12, i8** %14
  %15 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 0 取虚拟内存首地址
  %16 = bitcast i8* %15 to i32* 地址从 int8* 类型转换成 int32类型
  store i32 %0, i32* %16   参数 1 保存到虚拟内存首地址
  %17 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 4
  %18 = bitcast i8* %17 to i32*
  store i32 %1, i32* %18   参数 2 保存到虚拟内存第二个 4 字节处
  store i32 0, i32* %2     初始化当前字节码下标为 0
  store i32 0, i32* %5     初始化当前虚拟栈下标为 0
  br label %dispatch       跳转到 dispatch 执行

看注解,entry 块主要做了一些初始化的操作,分配虚拟内存空间和虚拟栈空间,保存参数到虚拟内存中,初始化字节码当前下标 和 虚拟栈下标为 0

接着看 dispatch 块是怎么处理的:

dispatch:                                         ; preds = %loopend, %entry
  %19 = load i32, i32* %2   取字节码下标值
  %20 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %19  取字节码地址
  %21 = load i8, i8* %20    加载一个字节码
  %22 = load i32, i32* %2   取字节码下标值
  %23 = add i32 %22, 1      字节码下标值加 1
  store i32 %23, i32* %2    保存字节码下标值
  switch i8 %21, label %loopend [ 根据字节码值进行跳转
    i8 1, label %handler_store
    i8 2, label %handler_load
    i8 3, label %handler_add
    i8 4, label %handler_ret
    i8 5, label %push_addr
    i8 6, label %store_imm1
    i8 7, label %store_imm2
    i8 8, label %store_imm4
    i8 9, label %store_imm8
  ]

dispatch 是调度器,根据opcode 执行对应的 handler 解释器,dispatch 执行取字节码,并把当前字节码指针+1,根据字节码跳转到对应的解释器执行。

接下来从字节码序列分析执行过程,字节码内容如下:

@opcodes = private global [84 x i8] c"\05\00\00\00\00\05\0C\00\00\00\01\05\04\00\00\00\05\18\00\00\00\01\05\0C\00\00\00\02,\00\00\00\05\18\00\00\00\020\00\00\00\05,\00\00\00\050\00\00\00\034\00\00\00\054\00\00\00\05$\00\00\00\01\05$\00\00\00\02,\00\00\00\05,\00\00\00\04"

字节码 opcodes是长 84 的 i8类型数组,第一个字节码是 05,根据上面的跳转是到 push_addr继续执行:

push_addr:                                        ; preds = %dispatch
  %85 = load i32, i32* %2   取当前字节码下标
  %86 = load i32, i32* %5   取当前虚拟栈下标
  %87 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %85 取当前字节码地址
  %88 = bitcast i8* %87 to i32*  当前字节码地址转成 int32*
  %89 = load i32, i32* %88.      从当前字节码地址取 4 个字节
  %90 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %86  取当前栈顶地址
  store i32 %89, i32* %90    将当前字节码取出的 4 个字节保存到栈顶
  %91 = add i32 %86, 1       虚拟栈下标+1
  store i32 %91, i32* %5     更新虚拟栈下标
  %92 = add i32 %85, 4       字节码下标+4
  store i32 %92, i32* %2     更新当前字节码下标
  br label %loopend          跳转到 loopend

看注解,这个基本块的主要作用是从当前字节码处取一个 int32值,保存到虚拟栈顶,并把虚拟栈顶+1,把当前字节码下标+4,对应的虚拟指令就是 vpush 0 . 即 字节码 0x5执行的操作是将取出字节码后面的 4 字节转为 int32,这是一个指向虚拟内存的地址,将该地址push到虚拟栈中。

那么字节码 \05\00\00\00\00\05\0C\00\00\00 对应的操作就是 vpush 0 和 vpush 12

接下来取字节码是 0x1,对应的处理 handler 是 handler_store:

handler_store:                                    ; preds = %dispatch
  %24 = load i32, i32* %2    取当前字节码地址下标
  %25 = load i32, i32* %5    取虚拟栈当前下标
  %26 = sub i32 %25, 2       虚拟栈下标 - 2
  %27 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %26 取出栈地址
  %28 = load i32, i32* %27   从栈地址取出一个int32值
  %29 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %28 值对应的虚拟内存地址
  %30 = bitcast i8* %29 to i32* 虚拟内存地址从 int8* 转 int32*
  %31 = load i32, i32* %30.     从虚拟地址取出这个 int32值
  %32 = sub i32 %25, 1       虚拟栈下标 - 1 ,后面重复前面的步骤
  %33 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %32 虚拟栈对应的地址
  %34 = load i32, i32* %33   从虚拟栈对应的地址取值,这个值是虚拟内存的下标
  %35 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %34 取虚拟内存地址
  %36 = bitcast i8* %35 to i32**  虚拟内存地址从 int8* 转成 int32**
  %37 = load i32*, i32** %36.     从虚拟内存处取出的值是一个 int32*地址
  store i32 %31, i32* %37, align 4  将前面第一次取出的值存放在这个地址处
  %38 = sub i32 %25, 2       当前虚拟栈顶指针 - 2
  store i32 %38, i32* %5     更新当前栈顶指针
  br label %loopend

看注解,这个虚拟指令 vstore 的逻辑是从当前栈顶取两个元素,这两个元素都是指向虚拟内存的地址,实际上是虚拟内存的下标,第一个元素指向虚拟内存中的一个 int32值,第二个元素指向虚拟内存中的一个 int32*地址值,然后把第一个元素取出的 int32值,保存到第二个元素取出的int32*地址处,然后把栈顶下标移动2 个。即 vstore 将虚拟内存 0 处的值,保存到虚拟内存 12 处值指向的地址处。

接下来又是两个 vpush和一个 vstore,分别是 vpush 4, vpush 24 和 vstore。

函数的 entry 处是做一些初始化的操作,就包括把参数 1 存储在虚拟地址下标 0 处,把参数 2 存储在虚拟地址下标 4 处。这几步的虚拟指令:

vpush 0
vpush 12 
vStore
vpush 4
vpush 24
vStore 

可以理解成就是把参数 1 和参数 2 分别存放到了虚拟内存 12 和 24 处的值指向的地址处。 根据函数 entry 处的分析,虚拟内存下标 12 和 24 处分别存放的是下标 8 和 20 处的地址。即最终把参数 1 和 参数 2 存放到了虚拟内存下标 8 和 20 处。

接下来是一个虚拟vpush指令,具体操作是 vpush 12.

然后后面是字节码 0x2,对应的 handler 是 handler_load:

handler_load:                                     ; preds = %dispatch
  %39 = load i32, i32* %2    取当前字节码下标
  %40 = load i32, i32* %5    取当前虚拟栈下标
  %41 = sub i32 %40, 1       当前虚拟栈下标 - 1
  %42 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %41  对应栈中的地址
  %43 = load i32, i32* %42   从栈中取出这个值,也上个虚拟指令vpush的操作数
  %44 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %43 这个值内存虚拟内存下标处
  %45 = bitcast i8* %44 to i32** 将地址从 i8* 转成 i32**
  %46 = load i32*, i32** %45     从这个虚拟内存取出的值是一个地址
  %47 = load i32, i32* %46, align 4  从这个地址处取值
  %48 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %39 当前字节码地址
  %49 = bitcast i8* %48 to i32*  转 i32*
  %50 = load i32, i32* %49.      从当前字节码处取4 字节
  %51 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %50  对应虚拟内存地址
  %52 = bitcast i8* %51 to i32*   转成 i32*
  store i32 %47, i32* %52.       将上面取的值,存放在这个虚拟地址处
  %53 = add i32 %39, 4          当前字节码下标 - 4
  store i32 %53, i32* %2        更新当前字节码下标
  %54 = sub i32 %40, 1          当前虚拟栈顶 - 1
  store i32 %54, i32* %5         并更新当前栈顶
  br label %loopend

看注解,vload 指令有一,4 字节操作数,这里具体是 vload 44, 操作逻辑是将栈顶元素对应的虚拟内存下标处,取出一个值,这个值也是一个地址,然后用这个地址在虚拟内存处取值,将这个值保存在虚拟指令操作数对应的虚拟地址下标处。上一条指令是 vpush 12, 对应这条指令的栈顶元素就是 12,虚拟内存 12 处,存放的是地址 8,地址 8 处保存了参数 1。因此这条指令执行后将参数 1 保存到了虚拟内存 44 处。

下两条指令分别是 vpush 24 和 vload 48,同上,即将参数 2 保存在虚拟地址 48 处。

接着是 vpush 44 和 vpush 48,即将两个参数再次保存到栈中。

接着是字节码 0x3,对应 handler 是   push_addr:

handler_add:                                      ; preds = %dispatch
  %55 = load i32, i32* %2   取当前字节码下标
  %56 = load i32, i32* %5   取当前栈下标
  %57 = sub i32 %56, 2      栈下标 - 2
  %58 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %57 取栈地址
  %59 = load i32, i32* %58   栈地址取值,得到虚拟内存地址
  %60 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %59 取最内存地址
  %61 = bitcast i8* %60 to i32*  虚拟内存地址转成 i32*
  %62 = load i32, i32* %61.      从虚拟内存地址处取值   
  %63 = sub i32 %56, 1       栈下标 - 1, 重复上述步骤
  %64 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %63
  %65 = load i32, i32* %64
  %66 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %65
  %67 = bitcast i8* %66 to i32*
  %68 = load i32, i32* %67   取出了第二个值
  %69 = add nsw i32 %62, %68  两个值相加
  %70 = getelementptr [84 x i8], [84 x i8]* @opcodes, i32 0, i32 %55 字节码当前地址
  %71 = bitcast i8* %70 to i32*   地址转成 i32*
  %72 = load i32, i32* %71.       字节码当前地址处取出 4 字节,是一个下标地址
  %73 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %72  虚拟内存处这个地址
  %74 = bitcast i8* %73 to i32*   虚拟内存处地址转 i32*
  store i32 %69, i32* %74.        前面计算的和保存在这个地址处
  %75 = add i32 %55, 4       当前字节码下标 + 4
  store i32 %75, i32* %2     更新当前字节码下标
  %76 = sub i32 %56, 2       虚拟栈下标 - 2
  store i32 %76, i32* %5     更新虚拟栈下标
  br label %loopend

看注解,vadd指令有一个操作数,操作逻辑是将前两个 vpush指令压入栈中的地址,取出来并从虚拟内存中取出值,相加后保存在指令操作数指向的地址中。这里具体是 vadd 52。即相加的结果保存在了虚拟内存下标 52 处。

接下来的虚拟指令分别是:

vpush 52
vpush 36
vstore 
vpush 36
vload 44
vpush 44

最后一个字节码是 0x4,对应该的 handler 是 handler_ret:

handler_ret:                                      ; preds = %dispatch
  %77 = load i32, i32* %2    取当前字节码下标
  %78 = load i32, i32* %5    当前栈下标
  %79 = sub i32 %78, 1       栈下标 - 1
  %80 = getelementptr [256 x i32], [256 x i32]* %4, i32 0, i32 %79 栈地址
  %81 = load i32, i32* %80   取出栈中的值
  %82 = getelementptr [56 x i8], [56 x i8]* %3, i32 0, i32 %81 对应虚拟内存地址
  %83 = bitcast i8* %82 to i32*  转 i32*
  %84 = load i32, i32* %83.      取出这个值
  ret i32 %84                 返回这个值

看注解,vret指令的逻辑是从栈顶取出地址,并用该地址在虚拟内存中取出值,返回这个值。

综上分析得到字节码指令序列:

vpush 0
vpush 12
vStore 
vpush 4
vpush 24
vStore 
vpush 12
vload  44
vpush 24
vload 48
vpush 44
vpush 48
vadd 52
vpush 52
vpush 36
vstore
vpush 36
vload 44
vpush 44
vret

上述字节码的逻辑就是对输入的两个参数做转换赋值,结果相加后,再进行转换保存,最后返回。功能上与混淆前的函数等价。

上述虚拟化的过程实际上是对每个 IR指令的模拟,比如使用 vpush vpush vstore 来模拟一个store 指令,使用 vpush 和 vload 模拟 load 指令,并借助一个虚拟栈和虚拟内存。来模拟全部 IR指令的过程。

反编译

混淆前的反编译代码:

控制流图:

就一个基本块,没有控制流 

 混淆后的反编译代码:

__int64 __fastcall test_add(unsigned int a1, unsigned int a2)
{
  int v2; // edx
  int v3; // edx
  int v5; // [rsp+0h] [rbp-440h]
  int v6; // [rsp+4h] [rbp-43Ch]
  unsigned __int64 v7; // [rsp+8h] [rbp-438h]
  char v8; // [rsp+10h] [rbp-430h]
  char *v9; // [rsp+14h] [rbp-42Ch]
  char v10; // [rsp+1Ch] [rbp-424h]
  char *v11; // [rsp+20h] [rbp-420h]
  char v12; // [rsp+28h] [rbp-418h]
  char *v13; // [rsp+2Ch] [rbp-414h]
  int v14[256]; // [rsp+40h] [rbp-400h]

  v9 = &v8;
  v11 = &v10;
  v13 = &v12;
  v7 = __PAIR__(a2, a1);
  v6 = 0;
  v5 = 0;
  while ( 1 )
  {
    v2 = byte_100004000[v6++];
    switch ( v2 )
    {
      case 1:
        **(_DWORD **)((char *)&v7 + v14[v5 - 1]) = *(_DWORD *)((char *)&v7 + v14[v5 - 2]);
        v5 -= 2;
        continue;
      case 2:
        *(_DWORD *)((char *)&v7 + *(signed int *)&byte_100004000[v6]) = **(_DWORD **)((char *)&v7 + v14[v5 - 1]);
        v6 += 4;
        --v5;
        continue;
      case 3:
        *(_DWORD *)((char *)&v7 + *(signed int *)&byte_100004000[v6]) = *(_DWORD *)((char *)&v7 + v14[v5 - 1])
                                                                      + *(_DWORD *)((char *)&v7 + v14[v5 - 2]);
        v6 += 4;
        v5 -= 2;
        continue;
      case 4:
        return *(unsigned int *)((char *)&v7 + v14[v5 - 1]);
      case 5:
        v3 = v6;
        v14[v5++] = *(_DWORD *)&byte_100004000[v6];
        goto LABEL_10;
      case 6:
        *((_BYTE *)&v7 + v14[v5-- - 1]) = byte_100004000[v6++];
        break;
      case 7:
        *(_WORD *)((char *)&v7 + v14[v5-- - 1]) = *(_WORD *)&byte_100004000[v6];
        v6 += 2;
        break;
      case 8:
        v3 = v6;
        *(_DWORD *)((char *)&v7 + v14[v5-- - 1]) = *(_DWORD *)&byte_100004000[v6];
LABEL_10:
        v6 = v3 + 4;
        break;
      case 9:
        *(unsigned __int64 *)((char *)&v7 + v14[v5-- - 1]) = *(_QWORD *)&byte_100004000[v6];
        v6 += 8;
        break;
      default:
        continue;
    }
  }
}

控制流图:

汇编层面分析虚拟opcode 指令流程

从函数入口开始分析:

__text:0000000100003D80 _test_add_VM    proc near               ; CODE XREF: _test_add+4↑p
__text:0000000100003D80
__text:0000000100003D80 var_440         = dword ptr -440h
__text:0000000100003D80 var_43C         = dword ptr -43Ch
__text:0000000100003D80 var_438         = qword ptr -438h
__text:0000000100003D80 var_430         = byte ptr -430h
__text:0000000100003D80 var_42C         = qword ptr -42Ch
__text:0000000100003D80 var_424         = byte ptr -424h
__text:0000000100003D80 var_420         = qword ptr -420h
__text:0000000100003D80 var_418         = byte ptr -418h
__text:0000000100003D80 var_414         = qword ptr -414h
__text:0000000100003D80 var_400         = dword ptr -400h
__text:0000000100003D80
__text:0000000100003D80                 sub     rsp, 3C0h    分配栈空间
__text:0000000100003D87                 lea     rax, [rsp+3C0h+var_430]  取地址
__text:0000000100003D8C                 mov     [rsp+3C0h+var_42C], rax  保存地址
__text:0000000100003D91                 lea     rax, [rsp+3C0h+var_424]  取地址
__text:0000000100003D96                 mov     [rsp+3C0h+var_420], rax  保存地址
__text:0000000100003D9B                 lea     rax, [rsp+3C0h+var_418]  取地址
__text:0000000100003DA0                 mov     [rsp+3C0h+var_414], rax  保存地址
__text:0000000100003DA5                 mov     dword ptr [rsp+3C0h+var_438], edi  保存参数1
__text:0000000100003DA9                 mov     dword ptr [rsp+3C0h+var_438+4], esi 保存参数2
__text:0000000100003DAD                 mov     [rsp+3C0h+var_43C], 0  初始化字节码下标
__text:0000000100003DB5                 mov     [rsp+3C0h+var_440], 0  初始化栈下标
__text:0000000100003DBD                 lea     rax, unk_100004000     字节码首地址
__text:0000000100003DC4                 lea     rcx, off_100003F74     handler 表
__text:0000000100003DCB                 jmp     short loc_100003E00 ;  跳转 dispatch

这段入口基本块的主要作用就是初始化,首先 sub     rsp, 3C0h 指令为函数执行分配栈空间,然后把 3 个局部变量的地址,保存到另外 3 个变量中。edi 和 esi 是传入的两个参数值(x86_64调用约定)保存到 rsp+3C0h+var_438处,可以认为 rsp+3C0h+var_438 地址是虚拟内存的首地址,是一个栈中的局部变量。 初始化后  rsp+3C0h+var_438 和  rsp+3C0h+var_438+4 分别保存了两个参数。

接着把 rsp+3C0h+var_43C 和  rsp+3C0h+var_440 初始化为 0,用来表示当前字节码下标和当前虚拟栈下标。最后将虚拟字节码地址保存到 rax寄存器,将 handler 地址保存到 rcx寄存器。然后跳转到地址 loc_100003E00处执行。

__text:0000000100003E00 loc_100003E00:                          ; CODE XREF: _test_add_VM+4B↑j
__text:0000000100003E00                                         ; _test_add_VM+94↓j ...
__text:0000000100003E00                 movsxd  rsi, [rsp+3C0h+var_43C] ; jumptable 0000000100003E1D default case
__text:0000000100003E05                 movzx   edx, byte ptr [rsi+rax]
__text:0000000100003E09                 inc     esi
__text:0000000100003E0B                 mov     [rsp+3C0h+var_43C], esi
__text:0000000100003E0F                 dec     edx
__text:0000000100003E11                 cmp     edx, 8          ; switch 9 cases
__text:0000000100003E14                 ja      short loc_100003E00 ; jumptable 0000000100003E1D default case
__text:0000000100003E16                 movsxd  rdx, dword ptr [rcx+rdx*4]
__text:0000000100003E1A                 add     rdx, rcx
__text:0000000100003E1D                 jmp     rdx             ; switch jump

这个基本块的作用是 dispatch,取出一个字节码,然后跳转到对应 handler 继续执行。首先从rsp+3C0h+var_43C 处取出当前字节码下标保存到 rsi寄存器。 [rsi+rax] 即当前字节码地址取出一个值,这里是取出一个 byte值保存到 edx中。然后 inc esi将当前字节码下标+1,并保存到rsp+3C0h+var_43C地址处,即将当前字节码下标移动了一个字节。然后将 dex减去 1,如果 edx的值大于8,则跳转到 dispatch,正常情况下字节码的值应该不会大于9。然后取 rcx+rdx*4 处的值保存到 rdx中,rcx 存储的是 handler 地址表,rdx是字节码-1 后的值,实际上这个字节码就是一个下标,在 handler 表中找到具体是哪个handler。取出 handler 保存到 rdx后,这个 handler 的地址实际上是一个偏移,相对于 handler表的偏移,加上 rcx后,得到实际的 handler 地址保存到 rdx中,最后 jump rdx,无条件跳转到对应 handler 执行。

我们取出内存中的字节码:

data:0000000100004000 unk_100004000   db    5                 ; DATA XREF: _test_add_VM+3D↑o
__data:0000000100004001                 db    0
__data:0000000100004002                 db    0
__data:0000000100004003                 db    0
__data:0000000100004004                 db    0
__data:0000000100004005                 db    5
__data:0000000100004006                 db  0Ch
__data:0000000100004007                 db    0
__data:0000000100004008                 db    0
__data:0000000100004009                 db    0
__data:000000010000400A                 db    1
__data:000000010000400B                 db    5
__data:000000010000400C                 db    4
__data:000000010000400D                 db    0
__data:000000010000400E                 db    0
__data:000000010000400F                 db    0
__data:0000000100004010                 db    5
__data:0000000100004011                 db  18h
__data:0000000100004012                 db    0
__data:0000000100004013                 db    0
__data:0000000100004014                 db    0
__data:0000000100004015                 db    1
__data:0000000100004016                 db    5
__data:0000000100004017                 db  0Ch
__data:0000000100004018                 db    0
__data:0000000100004019                 db    0
__data:000000010000401A                 db    0
__data:000000010000401B                 db    2
__data:000000010000401C                 db  2Ch ; ,
__data:000000010000401D                 db    0
__data:000000010000401E                 db    0
__data:000000010000401F                 db    0
__data:0000000100004020                 db    5
__data:0000000100004021                 db  18h
__data:0000000100004022                 db    0
__data:0000000100004023                 db    0
__data:0000000100004024                 db    0
__data:0000000100004025                 db    2
__data:0000000100004026                 db  30h ; 0
__data:0000000100004027                 db    0
__data:0000000100004028                 db    0
__data:0000000100004029                 db    0
__data:000000010000402A                 db    5
__data:000000010000402B                 db  2Ch ; ,
__data:000000010000402C                 db    0
__data:000000010000402D                 db    0
__data:000000010000402E                 db    0
__data:000000010000402F                 db    5
__data:0000000100004030                 db  30h ; 0
__data:0000000100004031                 db    0
__data:0000000100004032                 db    0
__data:0000000100004033                 db    0
__data:0000000100004034                 db    3
__data:0000000100004035                 db  34h ; 4
__data:0000000100004036                 db    0
__data:0000000100004037                 db    0
__data:0000000100004038                 db    0
__data:0000000100004039                 db    5
__data:000000010000403A                 db  34h ; 4
__data:000000010000403B                 db    0
__data:000000010000403C                 db    0
__data:000000010000403D                 db    0
__data:000000010000403E                 db    5
__data:000000010000403F                 db  24h ; $
__data:0000000100004040                 db    0
__data:0000000100004041                 db    0
__data:0000000100004042                 db    0
__data:0000000100004043                 db    1
__data:0000000100004044                 db    5
__data:0000000100004045                 db  24h ; $
__data:0000000100004046                 db    0
__data:0000000100004047                 db    0
__data:0000000100004048                 db    0
__data:0000000100004049                 db    2
__data:000000010000404A                 db  2Ch ; ,
__data:000000010000404B                 db    0
__data:000000010000404C                 db    0
__data:000000010000404D                 db    0
__data:000000010000404E                 db    5
__data:000000010000404F                 db  2Ch ; ,
__data:0000000100004050                 db    0
__data:0000000100004051                 db    0
__data:0000000100004052                 db    0
__data:0000000100004053                 db    4
__data:0000000100004053 __data          ends

第一个字节码是 0x5,即 handler 是handler 表中第 5 个,下标 4 处。

__text:0000000100003E8A loc_100003E8A:                          ; CODE XREF: _test_add_VM+9D↑j
__text:0000000100003E8A                                         ; DATA XREF: __text:off_100003F74↓o
__text:0000000100003E8A                 movsxd  rdx, [rsp+3C0h+var_43C] ; jumptable 0000000100003E1D case 4
__text:0000000100003E8F                 movsxd  rsi, [rsp+3C0h+var_440]
__text:0000000100003E94                 mov     edi, [rdx+rax]
__text:0000000100003E97                 mov     [rsp+rsi*4+3C0h+var_400], edi
__text:0000000100003E9B                 inc     esi
__text:0000000100003E9D                 mov     [rsp+3C0h+var_440], esi
__text:0000000100003EA1                 jmp     short loc_100003F1B

这个代码块先从 rsp+3C0h+var_43C 取值保存到 rdx中,即当前字节码下标。从 rsp+3C0h+var_440取值保存到 rsi中,即当前栈顶下标。执行第一个 handler 的时候,字节码下标是 1,栈顶下标是 0。然后将 rdx+rax 处的值保存到 edi,即从当前字节码处取出了 4 字节(edi是rdi的一半,即32 位寄存器 )。然后将edi的值保存到 rsp+rsi*4+3C0h+var_400,rsp+3C0h+var_400应该就是虚拟栈的地址 rsi*4 是当前栈顶偏移,所以这个操作就是从当前字节码处取出一个 4 字节的地址,然后保存到虚拟栈中。后面两个指令将当前栈顶下标+1后更新到了局部变量中。然后跳转到地址 loc_100003F1B处。

__text:0000000100003F1B loc_100003F1B:                          ; CODE XREF: _test_add_VM+121↑j
__text:0000000100003F1B                 add     edx, 4
__text:0000000100003F1E                 mov     [rsp+3C0h+var_43C], edx
__text:0000000100003F22                 jmp     loc_100003E00   

这里将 edx寄存器值加 4,因为前面从当前字节码处取出了一个 4 字节地址,所以需要将当前字节码的下标后移 4 个字节,然后 mov     [rsp+3C0h+var_43C], edx 更新当前字节码下标变量,最后继续跳转到 loc_100003E00 即 dispatch 处继续执行下一个字节码。

所以字节码 0x5 对应的虚拟指令是 vpush,将字节码保存的一个4 字节常量地址保存到虚拟栈,并更新字节码下标和栈顶下标。这里具体是 vpush 0

下一个字节码同样是 0x5,对应的虚拟指令是 vpush 12

下一个字节码地址是 000000010000400A,对应的字节码是 0x1,即 handler 表第一个:

__text:0000000100003E1F loc_100003E1F:                          ; CODE XREF: _test_add_VM+9D↑j
__text:0000000100003E1F                                         ; DATA XREF: __text:off_100003F74↓o
__text:0000000100003E1F                 mov     edx, [rsp+3C0h+var_440] ; jumptable 0000000100003E1D case 0
__text:0000000100003E23                 lea     esi, [rdx-2]
__text:0000000100003E26                 movsxd  rsi, esi
__text:0000000100003E29                 movsxd  rdi, [rsp+rsi*4+3C0h+var_400]
__text:0000000100003E2E                 mov     edi, dword ptr [rsp+rdi+3C0h+var_438]
__text:0000000100003E32                 dec     edx
__text:0000000100003E34                 movsxd  rdx, edx
__text:0000000100003E37                 movsxd  rdx, [rsp+rdx*4+3C0h+var_400]
__text:0000000100003E3C                 mov     rdx, [rsp+rdx+3C0h+var_438]
__text:0000000100003E41                 mov     [rdx], edi
__text:0000000100003E43                 mov     [rsp+3C0h+var_440], esi
__text:0000000100003E47                 jmp     short loc_100003E00 ;

这个handler 第一个指令是取当前栈顶下标保存到 edx寄存器,然后把下标-2 后赋值给 rsi,即栈顶后两个元素,前面两个 vpush操作正好在栈中保存了两个元素。

movsxd  rdi, [rsp+rsi*4+3C0h+var_400]指令将栈中的值赋值到 rdi,这个值实际上是虚拟内存的地址下标,mov  edi, dword ptr [rsp+rdi+3C0h+var_438]将对应的虚拟地址处的值赋值给 edi。此时完成了从栈中取出一个地址,并根据这个地址在虚拟内存中取值,保存到了 edi寄存器。

dec edx是栈顶下标 - 1 处,转给 rdx,movsxd  rdx, [rsp+rdx*4+3C0h+var_400]将虚拟栈中的值保存到了 rdx寄存器,这个值同样是一个地址下标,mov rdx, [rsp+rdx+3C0h+var_438]将对应虚拟内存中的值取出,这个值依然是一个地址,mov     [rdx], edi 即将前面取出的值保存在了这个地址处。所以这个 handler 的逻辑是,从栈顶取出两个元素,这是两个地址下标,根据第一个地址从虚拟内存取出一个值,根据第二个地址在虚拟内存取出一个地址,将第一个取的值,保存在第二个取的地址中。对应的虚拟指令是 vstore,没有操作数。

下一个字节码地址是000000010000400B,对应虚拟指令是 vpush 4

下一个字节码地址是 0000000100004010,对应虚拟指令是 vpush 24

下一个字节码地址是 0000000100004015,对应虚拟指令是 vstore

下一个字节码地址是 0000000100004016,对应虚拟指令是 vpush 12

下一个字节码地址是 000000010000401B,对应字节码是 0x2:

__text:0000000100003F27 loc_100003F27:                          ; CODE XREF: _test_add_VM+9D↑j
__text:0000000100003F27                                         ; DATA XREF: __text:off_100003F74↓o
__text:0000000100003F27                 movsxd  rdx, [rsp+3C0h+var_43C] ; jumptable 0000000100003E1D case 1
__text:0000000100003F2C                 mov     esi, [rsp+3C0h+var_440]
__text:0000000100003F30                 dec     esi
__text:0000000100003F32                 movsxd  rsi, esi
__text:0000000100003F35                 movsxd  rdi, [rsp+rsi*4+3C0h+var_400]
__text:0000000100003F3A                 mov     rdi, [rsp+rdi+3C0h+var_438]
__text:0000000100003F3F                 mov     edi, [rdi]
__text:0000000100003F41                 movsxd  r8, dword ptr [rdx+rax]
__text:0000000100003F45                 mov     dword ptr [rsp+r8+3C0h+var_438], edi
__text:0000000100003F4A                 add     edx, 4
__text:0000000100003F4D                 mov     [rsp+3C0h+var_43C], edx
__text:0000000100003F51                 mov     [rsp+3C0h+var_440], esi
__text:0000000100003F55                 jmp     loc_100003E00  

上述基本块,首先取出字节码下标保存到 rdx,取出栈顶下标保存到 esi,dec esi后赋值给 rsi,即上一个指令 push到栈顶的元素,movsxd  rdi, [rsp+rsi*4+3C0h+var_400]指令取出这个值保存到 rdi寄存器,这个值是一个虚拟内存的地址下标,mov  rdi, [rsp+rdi+3C0h+var_438]从虚拟内存取值保存到 rdi,这个 rdi同样是一个地址,mov  edi, [rdi]指令从 rdi指向的地址处取值保存到 edi。

接着指令movsxd  r8, dword ptr [rdx+rax]从当前字节码处取出 4 字节保存到 r8寄存器,这是指令的操作数,同时也是一个地址下标,mov dword ptr [rsp+r8+3C0h+var_438], edi指令将上述保存在 edi中的值保存在 r8指向的虚拟内存中。

上述的分析流程可以理解成是一个 vstore 指令,这个虚拟指令的流程是从取出栈顶元素取出虚拟地址下标,根据这个下标从虚拟内存处取值,取出的值同样是一个地址,再次从虚拟内存中取值,最后将这个值保存在操作数指向的地址中,具体虚拟指令是 vload 44

下一个字节码地址是 0000000100004020,对应虚拟指令是 vpush 24

下一个字节码地址是 0000000100004025,对应虚拟指令是 vload 48

下一个字节码地址是 000000010000402A,对应虚拟指令是 vpush 44

下一个字节码地址是 000000010000402F,对应虚拟指令地址是 vpush 48

下一个字节码地址是 0000000100004034,对应字节码是 0x3:

__text:0000000100003E49 loc_100003E49:                          ; CODE XREF: _test_add_VM+9D↑j
__text:0000000100003E49                                         ; DATA XREF: __text:off_100003F74↓o
__text:0000000100003E49                 movsxd  rdx, [rsp+3C0h+var_43C] ; jumptable 0000000100003E1D case 2
__text:0000000100003E4E                 mov     esi, [rsp+3C0h+var_440]
__text:0000000100003E52                 lea     edi, [rsi-2]
__text:0000000100003E55                 movsxd  rdi, edi
__text:0000000100003E58                 movsxd  r8, [rsp+rdi*4+3C0h+var_400]
__text:0000000100003E5D                 mov     r8d, dword ptr [rsp+r8+3C0h+var_438]
__text:0000000100003E62                 dec     esi
__text:0000000100003E64                 movsxd  rsi, esi
__text:0000000100003E67                 movsxd  rsi, [rsp+rsi*4+3C0h+var_400]
__text:0000000100003E6C                 add     r8d, dword ptr [rsp+rsi+3C0h+var_438]
__text:0000000100003E71                 movsxd  rsi, dword ptr [rdx+rax]
__text:0000000100003E75                 mov     dword ptr [rsp+rsi+3C0h+var_438], r8d
__text:0000000100003E7A                 add     edx, 4
__text:0000000100003E7D                 mov     [rsp+3C0h+var_43C], edx
__text:0000000100003E81                 mov     [rsp+3C0h+var_440], edi
__text:0000000100003E85                 jmp     loc_100003E00   ;

这个指令从栈顶取出了两个元素,是两个指向虚拟内存的地址,然后根据这两个地址取出两个 dword类型的值,相加后保存到 r8d 寄存器,然后讲结果保存到操作数指向的地址处,具体对应的虚拟指令是 vadd 52

下一个字节码地址是 0000000100004039,对应虚拟指令是 vpush 52

下一个字节码地址是 000000010000403E,对应虚拟指令是 vpush 36

下一个字节码地址是 0000000100004043,对应虚拟指令是 vstore

下一个字节码地址是 0000000100004044,对应虚拟指令是 vpush 36

下一个字节码地址是 0000000100004049,对应虚拟指令是 vload 44

下一个字节码地址是 000000010000404E,对饮虚拟指令是 vpush 44

最后一个字节码地址是 0000000100004053,对应字节码是 0x4:

__text:0000000100003F5A loc_100003F5A:                          ; CODE XREF: _test_add_VM+9D↑j
__text:0000000100003F5A                                         ; DATA XREF: __text:off_100003F74↓o
__text:0000000100003F5A                 mov     eax, [rsp+3C0h+var_440] ; jumptable 0000000100003E1D case 3
__text:0000000100003F5E                 dec     eax
__text:0000000100003F60                 cdqe
__text:0000000100003F62                 movsxd  rax, [rsp+rax*4+3C0h+var_400]
__text:0000000100003F67                 mov     eax, dword ptr [rsp+rax+3C0h+var_438]
__text:0000000100003F6B                 add     rsp, 3C0h
__text:0000000100003F72                 retn

这个基本块的第一个指令 mov eax, [rsp+3C0h+var_440] 取出栈顶下标赋值给 eax寄存器,dec eax 减 1,指向实际的栈顶元素。cdqe是一个扩展指令,将 eax双倍扩展成了 rax 8字节。接下来movsxd  rax, [rsp+rax*4+3C0h+var_400]指令从虚拟栈中取出了这个值保存到 rax寄存器,这个值是一个虚拟内存的下标地址,然后 mov eax, dword ptr [rsp+rax+3C0h+var_438] 从虚拟内存中取出了这个值保存在了 eax寄存器中,最后恢复栈大小后函数返回,根据x86_64的调用约定,函数的返回值保存在 eax寄存器中。

根据上述分析,这是一个 vret虚拟指令,操作逻辑是从栈顶取出地址,根据地址从虚拟内存取出一个值,并返回这个值。

到这里字节码执行完成,执行的字节码序列和分析 IR代码时完全一致,根据这个字节码序列可以知道,这个字节码序列的功能就是返回两个 int整数的和。

IDA脚本自动提取虚拟指令

通过上面对实际反汇编代码的分析,我们知道了每个字节码的表示和 handler 的具体逻辑,那么可以写一个脚本对 opcode 序列进行处理,提取出具体的虚拟指令序列:

TODO

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值