LLVM IR指令VM混淆分析

news2024/11/13 11:25:05

 未混淆编译

 编写一个最简单的测试代码,对 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 指令流程

TODO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2111875.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【佳学基因检测】如何知道一个网站是用Nginx还是Apache运行的服务器。

【佳学基因检测】如何知道一个网站是用Nginx还是Apache运行的服务器。 要确定一个的网站是由Nginx还是Apache服务器运行,可以使用以下几种方法: 1. 查看HTTP头信息 您可以通过检查网站返回的HTTP头信息来判断使用的是哪种服务器。具体步骤如下&#x…

Kafka【十三】消费者消费消息的偏移量

偏移量offset是消费者消费数据的一个非常重要的属性。默认情况下,消费者如果不指定消费主题数据的偏移量,那么消费者启动消费时,无论当前主题之前存储了多少历史数据,消费者只能从连接成功后当前主题最新的数据偏移位置读取&#…

FastAPI+Vue3零基础开发ERP系统项目实战课 20240906 上课笔记 fastapi的各种练习

回顾练习 用FastAPI写一个接口,这个接口能够返回九九乘法表的字符串。 获取九九乘法表: for i in range(1, 10):for j in range(1, i 1):print(f"{j} x {i} {j * i}", end"\t")print()# 得到字符串 talbe99 "" for …

多环境jdk安装,CentOS,统信UOS,Ubuntu,KylinOS,windows

文章目录 1.CentOS1.1yum安装1.2压缩包安装 本文档只是为了留档方便以后工作运维,或者给同事分享文档内容比较简陋命令也不是特别全,不适合小白观看,如有不懂可以私信,上班期间都是在得 1.CentOS 1.1yum安装 yum install -y jav…

Oracle VM VirtualBox 下 Ubuntu22 虚拟机配置双网络

初衷,希望在虚拟机里面配置两个网络。一个网络用来给虚拟机上互联网(浏览器,邮箱等)使用,一个网络用于虚拟机和宿主机通讯(静态IP) 1 VirtualBox 网络设置 2 宿主机网络配置 3 虚拟机内命令行配…

前端---对MVC MVP MVVM的理解

就需要从前端这些年的从无到有、从有到优的变迁过程讲一下。 1. Web1.0时代 在web1.0时代并没有前端的概念,开发一个web应用多数采用ASP.NET/Java/PHP编写,项目通常用多个aspx/jsp/php文件构成,每个文件中同时包含了HTML、CSS、JavaScript、…

SpringCloud之熔断监控HystrixDashboard和Turbine

SpringCloud之熔断监控HystrixDashboard和Turbine Hystrix-dashboard是一款针对Hystrix进行实时监控的工具,通过Hystrix Dashboard我们可以在直观地看到各 Hystrix Command的请求响应时间, 请求成功率等数据。但是只使用Hystrix Dashboard的话, 你只能看到单个应 …

chrome 插件开发入门

1. 介绍 Chrome 插件可用于在谷歌浏览器上控制当前页面的一些操作,可自主控制网页,提升效率。 平常我们可在谷歌应用商店中下载谷歌插件来增强浏览器功能,作为开发者,我们也可以自己开发一个浏览器插件来配合我们的日常学习工作…

【开源免费】基于SpringBoot+Vue.JS图书个性化推荐系统(JAVA毕业设计)

本文项目编号 T 015 ,文末自助获取源码 \color{red}{T015,文末自助获取源码} T015,文末自助获取源码 目录 一、系统介绍1.1 业务分析1.2 用例设计1.3 时序设计 二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究…

linux系统中,计算两个文件的相对路径

realpath --relative-to/home/itheima/smartnic/smartinc/blocks/ruby/seanet_diamond/tb/parser/test_parser_top /home/itheima/smartnic/smartinc/corundum/fpga/lib/eth/lib/axis/rtl/axis_fifo.v 检验方式就是直接在当前路径下,把输出的路径复制一份&#xff0…

二叉树的层次遍历(10道)

&#xff08;写给未来遗忘的自己&#xff09; 102.二叉数的层序遍历&#xff08;从上到下&#xff09; 题目&#xff1a; 代码&#xff1a; class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> r…

H桥电路及其应用

一、H桥电路简介 H桥是一种电机驱动电路&#xff0c;通过四个开关元件构成“H”型的电流路径结构。该电路能够控制负载&#xff08;如直流电机&#xff09;的电流方向&#xff0c;从而实现电机正反转和速度调节。H桥广泛应用于需要方向控制的场合&#xff0c;尤其是机器人驱动…

Java小白一文讲清Java中集合相关的知识点(六)

接上篇 添加了第二个元素“php”字符串后&#xff0c;debug查看此时的table的空间具体存储情况如下&#xff1a; 于是其将第二个待存放的元素“php”映射放入了9号索引处&#xff1b;接下来我们分析添加第三个重复元素“java”再次尝试放进去时&#xff0c;底层发生的一系列动…

13款常用AI编程工具

AI编程工具的选择和使用&#xff0c;主要取决于具体的项目需求、编程语言、以及AI任务的类型&#xff08;如机器学习、自然语言处理、计算机视觉等&#xff09;。下面是一些广泛使用的AI编程工具合集&#xff0c;涵盖了从开发、训练、到部署的各个环节&#xff1a; Jupyter Not…

Signac R|如何合并多个 Seurat 对象 (2)

引言 在本文中演示了如何合并包含单细胞染色质数据的多个 Seurat 对象。为了进行演示&#xff0c;将使用 10x Genomics 提供的四个 scATAC-seq PBMC 数据集&#xff1a; 500-cell PBMC 1k-cell PBMC 5k-cell PBMC 10k-cell PBMC 构建数据对象 接下来&#xff0c;将利用已经量化…

【计算机网络】socket编程 几个网络命令

目录 理解端口号理解源ip地址与目的IP地址认识端口号理解端口号与pid关系 理解socket编程理解网络字节序socket编程接口常见的API创建socket套接字bind绑定套接字listen开始监听accept接收请求connect建立连接recvfrom接收数据sendto发送数据 sockaddr结构sockaddr底层结构sock…

【C++】中动态链接库和静态链接库的区别

1. C 中动态链接库和静态链接库的区别 在C编程中&#xff0c;动态链接库&#xff08;Dynamic Link Library, DLL&#xff09;和静态链接库&#xff08;Static Library&#xff09;都是用来组织和重用代码的方法&#xff0c;但它们之间有几个重要的区别&#xff1a; 1.1 动态链…

【vite-plugin-vue-layouts】关于 vue-layouts 布局插件的使用和注意事项

环境&#xff1a;vue3 vuetify3 unplugin-vue-router 是怎么创建这个项目的&#xff1a; 选择它推荐的设置&#xff08;Recommend&#xff09; 问题描述 代码结构 # App.vue <template><v-app> <AppNavigator /> <RouterView /><AppFooter />…

多语言融合,全栈操控Vue + Spring Boot + SQL Server + Python部署到Windows服务器!

将一个包含Vue前端、Spring Boot后端、SQL Server数据库和Python脚本的项目部署到Windows服务器上涉及多个步骤。以下是一个详细的指南&#xff0c;帮助您完成这一过程。 前言 你是否正在寻找将Vue, Spring Boot, SQL Server和Python完美融合&#xff0c;并顺利部署到Windows服…

实时渲染技术的崛起:游戏与实时交互的新篇章

随着科技的飞速发展&#xff0c;实时渲染技术正逐步成为游戏与实时交互领域的重要驱动力。这一技术的崛起不仅极大地提升了用户体验&#xff0c;还推动了游戏、虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;等多个行业的创新发展。实时渲染技术开启…