VM逆向,一篇就够了(下)

news2024/11/24 17:47:14

实战三

d3sky

这一题需要了解一些TLS相关知识

TLS回调函数

TLS(Thread Local Storage,线程局部存储)是各线程的独立的存储空间,TLS回调函数是指,每当创建或终止进程的线程时会自动调用执行的函数,且调用执行优先于EP代码,该特征使它可以作为一种反调试技术使用。

为什么要讲这东西呢,因为一开始分析main函数时感觉静态分析比较吃力就想着动态调试分析一下,然后就停在了这里,这是触发了一个访问异常,sub_4C1050是由线程回调函数(TlsCallback_0)调用的

 

 

圈起来的那里是判断程序是否处于调试状态,如果是调试状态则执行下面的mov ecx,[eax]指令。如果不是处于调试状态则走另一个分支,触发一个除零异常,跳转到处理函数之后程序正常运行。对抗方法很简单,直接在IsDebuggerPresent结束之后修改eax的值,将1修改为0。

踩了个坑大家注意一下,如果修改的是跳转逻辑也就是将jz改为jnz,在调试状态程序也是不能正常运行的,因为IsDebuggerPresent的返回值被存储在了[ebp+var1C]处,下面的idiv 除指令除的就是这个位置,试想我们修改了跳转逻辑成功避开了那段会触发访问异常的指令,但是往下运行却没能触发除零异常,自然不能跳转到程序原本的执行流程上去。

分析main函数

这个VM题和前两个又很大的区别,他没有类似mov、xor、push、pop这样的操作,只有一条与非指令opcode[v6] = ~(opcode[v7] & opcode[v8]),而通过与非指令可以实现所有的逻辑运算。

 

乍一看很唬人,跟着动调几轮就能理清逻辑了,opcode[2]、opcode[7]、opcode[19]是三个标志位。

通过动态调试很容易判断出当opcode[2]为1时,会在控制台输出,当opcode[7]为1时会读取输入,在读取完所有的数据之前,opcode[19]不会为1,结合puts("wrong")可以推测出他是flag检测位。

后面这一组着重分析一下

 

opcode[0]可以看作是虚拟机的pc指针,而每次循环都会解密三个单位的指令(这里第一次RC4理解为解密更符合逻辑一点,也就是将原始的opcode认为是加密处理过的)。取出来之后,pc指针加三,可以理解为每条指令长度为3。之后会将opcode进行复原。然后执行核心的与非指令。

在调试的过程中通过十六进制窗口那里能得到输入存储的位置

 

翻译

接下来就是翻译了,和前面两个题目一样,我们需要做的是打印下来,而打印又有两种思路,一是将代码直接copy过来,编译执行一下,打印出所有的与非逻辑。二是借助idapython,由于之前都是用的第一种方法,这道题目就使用idapython来打印指令了。

想要实现的效果就是

1

opcode[11]=~(opcode[20]&opcode[20])      opcode[11]=~( 1 & 1 )

根据汇编代码编写idapython脚本

 

1

2

3

4

5

6

7

8

9

10

11

12

import idc

var_8=-8

var_C=-0x0C

v6=idc.get_reg_value('eax')

ebp_=idc.get_reg_value('ebp')

v7=idc.get_wide_word(ebp_+var_8)

v8=idc.get_wide_word(ebp_+var_C)

opcode_base=0xBE4018

value0=idc.get_wide_word(opcode_base+v7*2)       #这里需要注意,v7要乘2,因为opcode是word数组

value1=idc.get_wide_word(opcode_base+v8*2)

print('opcode[%d]=~(opcode[%d]&opcode[%d])'%(v6,v7,v8),'     opcode[%d]=~( %d & %d )'%(v6,value0,value1))

我们只关心对输入的判断,所以前面的逻辑不用理会,使用idapython在输入结束时提醒

 

 

1

2

3

4

5

6

7

8

9

import idc

var_24=-0x24

ebp_=idc.get_reg_value('ebp')

num=idc.get_wide_word(ebp_+var_24)

if(num==0x24):

   print('-------------------------------------END-------------------------------------')

   print('-------------------------------------END-------------------------------------')

   print('-------------------------------------END-------------------------------------')

  

输入:123456789012345678901234567890123456~

输出如下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

-------------------------------------END-------------------------------------

opcode[7]=~(opcode[8]&opcode[8])      opcode[7]=~( 126 & 126 )                 

opcode[2808]=~(opcode[7]&opcode[7])      opcode[2808]=~( 65409 & 65409 )       

opcode[11]=~(opcode[2772]&opcode[2772])      opcode[11]=~( 49 & 49 )      //取反     

opcode[11]=~(opcode[2773]&opcode[11])      opcode[11]=~( 50 & 65486 )

opcode[12]=~(opcode[2773]&opcode[2773])      opcode[12]=~( 50 & 50 )

opcode[12]=~(opcode[2772]&opcode[12])      opcode[12]=~( 49 & 65485 )

opcode[17]=~(opcode[12]&opcode[11])      opcode[17]=~( 65534 & 65533 )

opcode[11]=~(opcode[2774]&opcode[2774])      opcode[11]=~( 51 & 51 )

opcode[11]=~(opcode[2775]&opcode[11])      opcode[11]=~( 52 & 65484 )

opcode[12]=~(opcode[2775]&opcode[2775])      opcode[12]=~( 52 & 52 )

opcode[12]=~(opcode[2774]&opcode[12])      opcode[12]=~( 51 & 65483 )

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( 65532 & 65531 )

opcode[11]=~(opcode[17]&opcode[17])      opcode[11]=~( 3 & 3 )

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 7 & 65532 )

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 7 & 7 )

opcode[12]=~(opcode[17]&opcode[12])      opcode[12]=~( 3 & 65528 )

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( 65535 & 65531 )

opcode[11]=~(opcode[2809]&opcode[2809])      opcode[11]=~( 36 & 36 )

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 4 & 65499 )

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 4 & 4 )

opcode[12]=~(opcode[2809]&opcode[12])      opcode[12]=~( 36 & 65531 )

opcode[19]=~(opcode[12]&opcode[11])      opcode[19]=~( 65503 & 65535 )

观察输出不难发现(49、50、51、52,正是前四个输入1234的ascii),应该是把我们的输入按照四字节一组进行加密的。也能看出,我们的输入存储在从opcode[2772]这个位置。

与非运算十六进制观察更方便一些

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

-------------------------------------END-------------------------------------

opcode[7]=~(opcode[8]&opcode[8])      opcode[7]=~( 7e & 7e )

opcode[2808]=~(opcode[7]&opcode[7])      opcode[2808]=~( ff81 & ff81 )      //保存最后一位输入至opcode[2808]

      

opcode[11]=~(opcode[2772]&opcode[2772])      opcode[11]=~( 31 & 31 )       //opcode[11]=~(input[0]&input[0])

opcode[11]=~(opcode[2773]&opcode[11])      opcode[11]=~( 32 & ffce )       //opcode[11]=~(input[1]&opcode[11])

opcode[12]=~(opcode[2773]&opcode[2773])      opcode[12]=~( 32 & 32 )       //opcode[12]=~(input[1]&input[1])

opcode[12]=~(opcode[2772]&opcode[12])      opcode[12]=~( 31 & ffcd )       //opcode[12]=~(input[0]&opcode[12])

opcode[17]=~(opcode[12]&opcode[11])      opcode[17]=~( fffe & fffd )       //opcode[17]=~(opcode[12]&opcode[11])

opcode[11]=~(opcode[2774]&opcode[2774])      opcode[11]=~( 33 & 33 )       //opcode[11]=~(input[2]&input[2])

opcode[11]=~(opcode[2775]&opcode[11])      opcode[11]=~( 34 & ffcc )       //opcode[11]=~(input[3]&opcode[11])

opcode[12]=~(opcode[2775]&opcode[2775])      opcode[12]=~( 34 & 34 )       //opcode[12]=~(input[3]&input[3])

opcode[12]=~(opcode[2774]&opcode[12])      opcode[12]=~( 33 & ffcb )       //opcode[12]=~(input[2]&opcode[12])

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( fffc & fffb )       //opcode[18]=~(opcode[12]&opcode[11])

opcode[11]=~(opcode[17]&opcode[17])      opcode[11]=~( 3 & 3 )             //opcode[11]=~(opcode[17]&opcode[17])

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 7 & fffc )          //opcode[11]=~(opcode[18]&opcode[11])

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 7 & 7 )             //opcode[12]=~(opcode[18]&opcode[18])

opcode[12]=~(opcode[17]&opcode[12])      opcode[12]=~( 3 & fff8 )          //opcode[12]=~(opcode[17]&opcode[12])

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( ffff & fffb )       //opcode[18]=~(opcode[12]&opcode[11])

//注意下面是进行比较

opcode[11]=~(opcode[2809]&opcode[2809])      opcode[11]=~( 24 & 24 )       //opcode[11]=~(cpdata[0]&cpdata[0])

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 4 & ffdb )          //opcode[11]=~(opcode[18]&opcode[11])

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 4 & 4 )             //opcode[12]=~(opcode[18]&opcode[18])

opcode[12]=~(opcode[2809]&opcode[12])      opcode[12]=~( 24 & fffb )       //opcode[12]=~(cpdata[0]&opcode[12])

opcode[19]=~(opcode[12]&opcode[11])      opcode[19]=~( ffdf & ffff )       //opcode[19]=~(opcode[12]&opcode[11])

最下面可以推测应该是比较数据,opcode[2809]距opcode[2772]正好相差37个单位,是输入的长度。

把与非指令还原成高级指令

1

2

3

4

5

6

7

8

9

10

11

opcode[11]=~(input[0]&input[0])                //input[0]取反 0000 0000 0011 0001--> 1111 1111 1100 1110

opcode[11]=~(input[1]&opcode[11])              //input[1]与取反之后的input[0]进行与操作 与操作保留都是1的位,而~input[0]的1是由0变来的

1100 1110 & 0011 0010 -->0000 0010  这一步就是将input[0]为0input[1]为1的位 置1  0000 0010 然后取反(有点异或的意思)   1111 1101

opcode[12]=~(input[1]&input[1])                //input[1]取反 0011 0010 -- 1100 1101

opcode[12]=~(input[0]&opcode[12])              //input[0]为1input[1]为0的位 置1 0000 0001   然后取反 1111 1110

opcode[17]=~(opcode[12]&opcode[11])            //保留都是1的位 1111 1100 然后取反 0000 0011   就是 input[0]^input[1]的结果

最后一条指令就是将input[0]为0input[1]为1的位置1 同时将input[0]为1input[1]为0的位置1  合并一下就是相异为1 相同为0

上面六条等价为高级指令

opcode[17]=input[0]^input[1]

第一组idapython打印的指令就等价为

1

2

3

4

opcode[17]=input[0]^input[1]

opcode[18]=input[2]^input[3]

opcode[18]=opcode[17]^opcode[18]

opcode[19]=cpdata[0]^opcode[18]  

结合这里

 

检测时要保证opcode为0,两个数相同异或结果为0,所以最后一步opcode[19]=cpdata[0]^opcode[18] 就是检测cpdata[0]和opcode[18]是否相等。

那么合理的推测就是:

1

2

3

4

5

6

cpdata[0]==input[0]^input[1]^input[2]^input[3]

cpdata[1]==input[1]^input[2]^input[3]^input[4]

…………

cpdata[33]==input[33]^input[34]^input[35]^input[36]

?????这里数据不够了 不知道是循环到input[0]那里还是继续往后取数据,先写一个解出前面的试试

cpdada[36]=input[]

z3脚本

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

from z3 import *

# 创建符号变量

input = [BitVec('input_%d' % i, 16) for i in range(37)]

# 已知的结果

cpdata = [0x0024, 0x000B, 0x006D, 0x000F, 0x0003, 0x0032, 0x0042, 0x001D,

    0x002B, 0x0043, 0x0078, 0x0043, 0x0073, 0x0030, 0x002B, 0x004E,

    0x0063, 0x0048, 0x0077, 0x002E, 0x0032, 0x0039, 0x001A, 0x0012,

    0x0071, 0x007A, 0x0042, 0x0017, 0x0045, 0x0072, 0x0056, 0x000C,

    0x005C, 0x004A, 0x0062, 0x0053, 0x0033]

# 添加异或约束

constraints = []

for i in range(34):

    constraints.append(cpdata[i] == (input[i] ^ input[i+1] ^ input[i+2] ^ input[i+3]))

# 创建解决器

solver = Solver()

# 添加约束

solver.add(constraints)

# 求解

if solver.check() == sat:

    model = solver.model()

    solution = [model[input[i]].as_long() for i in range(37)]

    print("Solution found:")

    print(solution)

else:

    print("No solution found.")

#[80, 65, 50, 7, 127, 39, 80, 11, 78, 87, 15, 61, 38, 108, 52, 13, 101, 119, 81, 32, 78, 72, 8, 60, 69, 107, 0, 95, 78, 83, 85, 13, 121, 119, 15, 93, 111]

明显是错误,有可能是推测错了,经过验证z3打印的结果是满足cpdata[0]==input[0]^input[1]^input[2]^input[3] 的,那我们将其patch进去,就能得到第二组比较的方法,将得到的全部数据patch进去就能打印到与cpdata[34]的比较

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

opcode[7]=~(opcode[8]&opcode[8])      opcode[7]=~( 7e & 7e )

opcode[2808]=~(opcode[7]&opcode[7])      opcode[2808]=~( ff81 & ff81 )

opcode[11]=~(opcode[2772]&opcode[2772])      opcode[11]=~( 50 & 50 )        //input[0]

opcode[11]=~(opcode[2773]&opcode[11])      opcode[11]=~( 41 & ffaf )        //input[1]

opcode[12]=~(opcode[2773]&opcode[2773])      opcode[12]=~( 41 & 41 )

opcode[12]=~(opcode[2772]&opcode[12])      opcode[12]=~( 50 & ffbe )       

opcode[17]=~(opcode[12]&opcode[11])      opcode[17]=~( ffef & fffe )

opcode[11]=~(opcode[2774]&opcode[2774])      opcode[11]=~( 32 & 32 )        //input[2]

opcode[11]=~(opcode[2775]&opcode[11])      opcode[11]=~( 7 & ffcd )         //input[3]

opcode[12]=~(opcode[2775]&opcode[2775])      opcode[12]=~( 7 & 7 )

opcode[12]=~(opcode[2774]&opcode[12])      opcode[12]=~( 32 & fff8 )

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( ffcf & fffa )

opcode[11]=~(opcode[17]&opcode[17])      opcode[11]=~( 11 & 11 )

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 35 & ffee )

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 35 & 35 )

opcode[12]=~(opcode[17]&opcode[12])      opcode[12]=~( 11 & ffca )

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( ffff & ffdb )

opcode[11]=~(opcode[2809]&opcode[2809])      opcode[11]=~( 24 & 24 )

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 24 & ffdb )

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 24 & 24 )

opcode[12]=~(opcode[2809]&opcode[12])      opcode[12]=~( 24 & ffdb )

opcode[19]=~(opcode[12]&opcode[11])      opcode[19]=~( ffff & ffff )

opcode[11]=~(opcode[2773]&opcode[2773])      opcode[11]=~( 41 & 41 )          //input[1]

opcode[11]=~(opcode[2774]&opcode[11])      opcode[11]=~( 32 & ffbe )          //input[2]

opcode[12]=~(opcode[2774]&opcode[2774])      opcode[12]=~( 32 & 32

opcode[12]=~(opcode[2773]&opcode[12])      opcode[12]=~( 41 & ffcd )

opcode[17]=~(opcode[12]&opcode[11])      opcode[17]=~( ffbe & ffcd )

opcode[11]=~(opcode[2775]&opcode[2775])      opcode[11]=~( 7 & 7 )            //input[3]

opcode[11]=~(opcode[2776]&opcode[11])      opcode[11]=~( 35 & fff8 )          //input[4]

opcode[12]=~(opcode[2776]&opcode[2776])      opcode[12]=~( 35 & 35 )

opcode[12]=~(opcode[2775]&opcode[12])      opcode[12]=~( 7 & ffca )

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( fffd & ffcf )

opcode[11]=~(opcode[17]&opcode[17])      opcode[11]=~( 73 & 73 )

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 32 & ff8c )

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 32 & 32 )

opcode[12]=~(opcode[17]&opcode[12])      opcode[12]=~( 73 & ffcd )

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( ffbe & ffff )

opcode[11]=~(opcode[2810]&opcode[2810])      opcode[11]=~( b & b )

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 41 & fff4 )

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 41 & 41 )

opcode[12]=~(opcode[2810]&opcode[12])      opcode[12]=~( b & ffbe )

opcode[19]=~(opcode[12]&opcode[11])      opcode[19]=~( fff5 & ffbf )

…………

opcode[11]=~(opcode[2806]&opcode[2806])      opcode[11]=~( 60 & 60 )          //input[34]

opcode[11]=~(opcode[2807]&opcode[11])      opcode[11]=~( 43 & ff9f )            //input[35]

opcode[12]=~(opcode[2807]&opcode[2807])      opcode[12]=~( 43 & 43 )    

opcode[12]=~(opcode[2806]&opcode[12])      opcode[12]=~( 60 & ffbc )

opcode[17]=~(opcode[12]&opcode[11])      opcode[17]=~( ffdf & fffc )

opcode[11]=~(opcode[2808]&opcode[2808])      opcode[11]=~( 7e & 7e )          //input[36]

opcode[11]=~(opcode[2772]&opcode[11])      opcode[11]=~( 41 & ff81 )           //input[0]

opcode[12]=~(opcode[2772]&opcode[2772])      opcode[12]=~( 41 & 41 )

opcode[12]=~(opcode[2808]&opcode[12])      opcode[12]=~( 7e & ffbe )

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( ffc1 & fffe )

opcode[11]=~(opcode[17]&opcode[17])      opcode[11]=~( 23 & 23 )

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 3f & ffdc )

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 3f & 3f )

opcode[12]=~(opcode[17]&opcode[12])      opcode[12]=~( 23 & ffc0 )

opcode[18]=~(opcode[12]&opcode[11])      opcode[18]=~( ffff & ffe3 )

opcode[11]=~(opcode[2843]&opcode[2843])      opcode[11]=~( 62 & 62 )

opcode[11]=~(opcode[18]&opcode[11])      opcode[11]=~( 1c & ff9d )

opcode[12]=~(opcode[18]&opcode[18])      opcode[12]=~( 1c & 1c )

opcode[12]=~(opcode[2843]&opcode[12])      opcode[12]=~( 62 & ffe3 )

opcode[19]=~(opcode[12]&opcode[11])      opcode[19]=~( ff9d & ffe3 )

不出所料,第一组opcode[19]得到的值是0,又继续往下执行了一组还是正确的,直接拉到最后可以看到执行的逻辑是

1

cpdata[34]==input[34]^input[35]^input[36]^input[0]

循环执行了,更改一下z3脚本即可

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

from z3 import *

# 创建符号变量

input = [BitVec('input_%d' % i, 8) for i in range(37)]

# 已知的结果

cpdata = [0x0024, 0x000B, 0x006D, 0x000F, 0x0003, 0x0032, 0x0042, 0x001D,

    0x002B, 0x0043, 0x0078, 0x0043, 0x0073, 0x0030, 0x002B, 0x004E,

    0x0063, 0x0048, 0x0077, 0x002E, 0x0032, 0x0039, 0x001A, 0x0012,

    0x0071, 0x007A, 0x0042, 0x0017, 0x0045, 0x0072, 0x0056, 0x000C,

    0x005C, 0x004A, 0x0062, 0x0053, 0x0033]

# 添加异或约束

constraints = []

for i in range(34):

    constraints.append(cpdata[i] == (input[i] ^ input[i+1] ^ input[i+2] ^ input[i+3]))

# 创建解决器

solver = Solver()

# 添加约束

solver.add(constraints)

solver.add(cpdata[34] == (input[34] ^ input[35] ^ input[36] ^ input[0]))

solver.add(cpdata[35] == (input[35] ^ input[36] ^ input[0] ^ input[1]))

solver.add(cpdata[36] == (input[36] ^ input[0] ^ input[1] ^ input[2]))

solver.add(input[36]==126)    #题目中对最后一位的限制

# 求解

if solver.check() == sat:

    model = solver.model()

    solution = [model[input[i]].as_long() for i in range(37)]

    print("Solution found:")

    print(solution)

else:

    print("No solution found.")

#[65, 95, 83, 105, 110, 57, 49, 101, 95, 73, 110, 83, 55, 114, 85, 99, 116, 105, 48, 78, 95, 86, 105, 82, 84, 117, 97, 49, 95, 77, 52, 99, 104, 105, 110, 51, 126]

#A_Sin91e_InS7rUcti0N_ViRTua1_M4chin3~

终于结束了!!!

这道题似乎完全不同于前面的那种VM类型的题目,他没有结构体,没有vm_init、没有vm_start等等,但是思想上是一致的,都是用vm提供的指令来还原程序逻辑(只是在这道题目中只有一种指令),都很好的抵抗了静态分析,而且仔细回味一下,opcode[11]、opcode[12]、opcode[17]、opcode[18],其实就是虚拟机的寄存器,也能找到对应的抽象的init、start等。

 

初识VMP

通过这三个题目我们对虚拟机保护技术有了一定的了解,这项技术运用到实际的生产工作中就有了VMP(VMProtect)。

什么是VMProtect

VMProtect是一个软件保护程序,应用于软件保护和加固领域,VMProtect通过使应用程序的代码和逻辑变得复杂来对抗逆向,主要的保护机制包括:虚拟化变异组合保护

 

我们主要了解一下虚拟化,VMProtect首先会将受保护的代码转换为等价的虚拟代码片段,然后交由虚拟机执行,该虚拟机是VMProtect嵌入到受保护程序中的,因此受保护的程序不需要第三方库活模块即可运行。更为夸张的是,VMProtect允许使用多个不同的虚拟机来保护同一个应用程序的不同代码片段,这大大增加了破解难度。

优缺点

优点:

  • 保护程度高,极难破解(没有绝对安全的程序,但如果破解程序所需的成本大于程序本身,程序自然就安全了)
  • 保护后,虚拟机和新命令集将内置到受保护的应用程序中,并且不需要任何其他库和模块即可工作。
  • 支持多种语言,以及主流操作系统:Windows、macOS、Linux

缺点:

  • 执行效率低(参考上面的题目,一条printf语句就对应十几条vm指令)
  • 过于消耗系统资源

之后有时间了应该会深入了解一下VMP,尝试对抗低版本的vmp保护,跟着大佬们分析分析源码。(本来这篇文章的后半部分是尝试逆向一个我自己写的受VMProtect保护的程序的,可是一个54kb的程序直接膨胀到了13mb,一时间有些恍惚)

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

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

相关文章

蓝桥杯Web开发【国赛】2022年真题

1.水果拼盘 目前 CSS3 中新增的 Flex 弹性布局已经成为前端页面布局的首选方案,本题可以使用 Flex 属性快速完成布局。 1.1 题目问题 建议使用 flex 相关属性完成 css/style.css 中的 TODO 部分。 禁止修改圆盘的位置和图片的大小。相同颜色的水果放在相同颜色的…

[Unity报错] The type or namespace name ‘Newtonsoft‘ could not be found

简介 Unity在跑别人的代码时,控制台报了以下错误 The type or namespace name Newtonsoft could not be found 鉴于这块资料较少,写一下教程帮助后来者。 报错的原因主要是因为缺少Newtonsoft.json这个包,导致Unity在using该库时出现错误。…

振动盘配上智能相机后实现全自动化

#智能相机 #振动盘 振动盘配上智能相机后的全自动化技术正在各个行业蓬勃发展。振动盘是一种利用震动作用将物料快速分离和输送的装置,而智能相机的高精度图像识别技术能够准确探测和捕捉物料。将两者结合起来,不仅能提高生产效率,还能降低人…

AI爆文写作:标题需要什么?情绪炸裂,态度要激烈,行为要夸张!

现在这个传播环境下,在公域中,轻声细语,慢慢的说,无法吸引到注意,没有人搭理。 标题要需要情绪张扬,态度激烈,行为夸张,大声喧闹。 唐韧的用户群是互联网产品经理,阅读量…

开源实用!猫抓媒体嗅探浏览器插件

CatCatch:网络资源,一触即发 - 精选真开源,释放新价值。 概览 CatCatch是一个专为浏览器设计的资源嗅探扩展,旨在帮助用户轻松捕获和分析网页中的各种资源。无论是视频、音频还是其他类型的文件,猫爪都能提供直观的界…

了解K8s集群kubectl命令进行陈述式资源管理

前言 在 Kubernetes 集群中,通过陈述式和声明式资源管理是确保应用程序高效运行的关键。认识这两种管理方法,能够更好地掌握 Kubernetes 集群的运维和管理。 目录 一、K8s 资源管理操作分类 1. 陈述式 2. 声明式 3. K8s 集群管理常用命令概览 二…

五分钟”手撕“图书管理系统

前言: 图书馆管理系统需要结合JavaSE的绝大部分知识,是一个很好的训练项目。 为了让大家更加方便的查阅与学习,我把代码放开头,供大家查询。 还有对代码的分析,我将以类为单位分开讲解。 目录 全部代码 Main类 Us…

【NumPy】NumPy实战入门:线性代数(dot、linalg)与随机数(numpy.random)详解

🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…

redis--redis Cluster

简介 解决了redis单机写入的瓶颈问题,即单机的redis写入性能受限于单机的内存大小、并发数量、网卡速率等因素无中心架构的redis cluster机制,在无中心的redis集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连…

高阶路由过渡处理方案 —— 浏览器堆栈主动介入

目录 01: 前言 02: VueRouter 过渡动效可行性分析 03: 主动介入浏览器堆栈管理,分析可行性方案 04: 主动介入浏览器堆栈管理 05: 基于 GSAP 实现高阶路由过渡动画分析 06: 基于 GSAP 实现高阶路由过渡动画处理 07: 通用组件:navbar 构建方案分析…

202206青少年软件编程(Python)等级考试试卷(三级)

第 1 题 【单选题】 下图所示, 有一个名为"书目.csv"的文件。 小明针对这个文件编写了 5 行代码,请问, 代码运行到最后打印在屏幕上的结果是? ( ) with open(书目.csv, r, encoding=utf-8) as f:for line in f.readlines

Rabbitmq-Windows 安装

第一步:下载并安装erlang 1.原因:RabbitMQ服务端代码是使用并发式语言Erlang编写的,安装Rabbit MQ的前提是安装Erlang 2.下载地址:http://www.erlang.org/downloads 3.双击,点next就可以 4.选…

【网络技术】【Kali Linux】Wireshark嗅探(十四)QUIC(快速UDP互联网连接)协议报文捕获及分析

往期 Kali Linux 上的 Wireshark 嗅探实验见博客: 【网络技术】【Kali Linux】Wireshark嗅探(一)ping 和 ICMP 【网络技术】【Kali Linux】Wireshark嗅探(二)TCP 协议 【网络技术】【Kali Linux】Wireshark嗅探&…

【Unity2D:Animator】为角色添加动画效果

一、添加Animator组件并创建Animator Controller文件 1. 添加Animator组件: 2. 在Assets-Art文件夹中新建一个名为Animations的文件夹,用来存储所有动画资源 3. 在Animations文件夹中新建一个名为Player的文件夹,再创建一个名为Animators的文…

2024电工杯数学建模A题Matlab代码+结果表数据教学

2024电工杯A题保姆级分析完整思路代码数据教学 A题题目:园区微电网风光储协调优化配置 以下仅展示部分,完整版看文末的文章 %A_1_1_A % 清除工作区 clear;clc;close all;warning off; %读取参数%正常读取 % P_LOADxlsread(附件1:各园区典…

springboot vue 开源 会员收银系统 (2) 搭建基础框架

前言 完整版演示 前面我们对会员系统https://blog.csdn.net/qq_35238367/article/details/126174288进行了分析 确定了技术选型 和基本的模块 下面我们将从 springboot脚手架开发一套收银系统 使用脚手架的好处 不用编写基础的rabc权限系统将工作量回归业务本身生成代码 便于…

盲人社会适应性训练:打开生活的新篇章

在现代社会的快节奏中,每一位成员都在寻求更好的方式来适应环境,对于盲人群体而言,这种适应性尤为关键。盲人社会适应性训练作为一个旨在提升盲人生活质量和独立性的系统性过程,正逐步受到广泛关注。在这一过程中,一款…

Jmeter+prometheus+grafana性能测试

文章目录 Jmeterprometheusgrafana性能测试背景目标设计思路原理案例启发 Jmeterprometheusgrafana性能测试 背景 ​ 在现代社会中,人们对于应用程序的响应速度和性能体验提出了越来越高的要求。无论是电子商务网站、社交媒体平台还是企业级软件系统,都…

Playwright教程

Playwright简介 支持多数浏览器 在Chromium,Firefox和WebKit上进行测试。Playwright拥有适用于所有现代浏览器的完整API覆盖,包括Google Chrome和Microsoft Edge(带有Chromium),Apple Safari(带有WebKit&a…

VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION--论文笔记

论文笔记 论文来源 Very Deep Convolutional Networks for Large-Scale Image Recognition 代码来源 还没上传 数据集 这里采用的是猫狗数据集 还没上传 1论文摘要的翻译 在这项工作中,我们研究了卷积网络深度对其在大规模图像识别设置中的准确性的影响。我…