s0m1ng

二进制学习中

前言:

某些安卓题强制要求arm环境才能跑,模拟器不管是脱壳还是动调都有很多问题,最近搞了个真机重新配一遍安卓逆向环境

root手机

前言:

这道题算非常经典的vm题了,用这道题来熟悉一下vm题基本流程

正文:

创建结构体:

1

有三个函数我们点进去看一下具体逻辑

vm_init

第一个函数很明显就是vm_init的结构,我们改名为vm_init,并创建结构体帮助静态分析

struct

在空白部分右键,添加结构体

proc

结构体名就叫vm_cpu,下面按d键快捷键给它增加成员变量

光标对准vm_cpu struc点一次d是在最前面增加成员变量

对准field_0点一次d是更改一次field_0的类型,也可按y直接输入类型

对准vm_cpu ends点一次d是在末尾增加成员变量

cpu

接下来修改成员变量的名称按n快捷键,改为eax,ebx等

由于vm_cpu中有个数组叫oplist[]

我们先创建新结构体

1
2
3
4
5
struct opcode
{
QWORD _opcode;
QWORD handle;
};

这里稍微再提一下怎么确定每个成员变量占多少字节,不要看ida给你强转出来的类型,而要看上下成员之间的差值,比如(_BYTE )(a1 + 24) = -15;和(_QWORD )(a1 + 32) = sub_B5F;从a1+24到a1+32,占了8个字节。所以这里_opcode类型是qword。下面是全部改完之后的结构体

struct2

再回到第一个函数,对准函数参数列表按y,把a1类型改为vm_cpu*,然后函数就变得美观了,下面的qword_2022A8是给vm设置的栈空间,我们也可以改名vm_stack

vm_init2

第二个函数很容易看出来是vm_run,分发器控制程序执行,第三个函数是验证flag正确与否,就不展开讲了

第二个函数把参数也改成vm_cpu*

1
2
3
4
5
6
7
8
9
10
unsigned __int64 __fastcall vm_run(vm_cpu *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
a1->vm_rip = (__int64)&unk_202060;
while ( *(_BYTE *)a1->vm_rip != 0xF4 )
sub_E6E(a1);
return __readfsqword(0x28u) ^ v2;
}

我们看到逻辑已经很明确了,就是rip指向的地址对应的值不等于0xf4时,一直调用sub_E6E

1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 __fastcall sub_E6E(vm_cpu *a1)
{
int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
for ( i = 0; *(_BYTE *)a1->vm_rip != LOBYTE(a1->oplist[i]._opcode); ++i )
;
((void (__fastcall *)(vm_cpu *))a1->oplist[i].handle)(a1);
return __readfsqword(0x28u) ^ v3;
}

sub_E6E点进去就发现和我们的vm基础里的dispatcher结构一模一样,现在我们回头处理一下没命名的handle函数就可以正式逆向了

识别函数:

1.mov函数

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
unsigned __int64 __fastcall sub_B5F(vm_cpu *a1)
{
int *v2; // [rsp+28h] [rbp-18h]
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

v3 = __readfsqword(0x28u);
v2 = (int *)(a1->vm_rip + 2);
switch ( *(_BYTE *)(a1->vm_rip + 1) )
{
case 0xE1:
a1->vm_eax = *((char *)vm_stack + *v2);
break;
case 0xE2:
a1->vm_ebx = *((char *)vm_stack + *v2);
break;
case 0xE3:
a1->vm_ecx = *((char *)vm_stack + *v2);
break;
case 0xE4:
*((_BYTE *)vm_stack + *v2) = a1->vm_eax;
break;
case 0xE5:
a1->vm_edx = *((char *)vm_stack + *v2);
break;
case 0xE7:
*((_BYTE *)vm_stack + *v2) = a1->vm_ebx;
break;
default:
break;
}
a1->vm_rip += 6LL;
return __readfsqword(0x28u) ^ v3;
}

这个函数的意思是,v2是栈中偏移,相当于ss:[ebp+v2]=vm_stack[v2],当操作码=-15也就是0xF1的时候,调用这个函数,下一个地址的数(_BYTE )(a1->vm_rip+1))就是选择码,(_BYTE )(a1->vm_rip+2))就是操作数

  • 选择码=0xE1 执行mov eax ss:[ebp+v2]

  • 选择码=0xE2 执行mov ebx ss:[ebp+v2]

  • 选择码=0xE3 执行mov ecx ss:[ebp+v2]

  • 选择码=0xE4 执行mov ss:[ebp+v2] eax

  • 选择码=0xE5 执行mov edx ss:[ebp+v2]

  • 选择码=0xE7 执行mov ss:[ebp+v2] ebx

2.xor

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_A64(vm_cpu *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
a1->vm_eax ^= a1->vm_ebx;
++a1->vm_rip;
return __readfsqword(0x28u) ^ v2;
}

相当于xor eax ebx

3.read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 __fastcall sub_AC5(vm_cpu *a1)
{
const char *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
buf = (const char *)vm_stack;
read(0, vm_stack, 0x20uLL);
dword_2022A4 = strlen(buf);
if ( dword_2022A4 != 21 )
{
puts("WRONG!");
exit(0);
}
++a1->vm_rip;
return __readfsqword(0x28u) ^ v3;
}

把flag读入栈上

相当于call read,并判断flag长度

4.nop

1
2
3
4
5
6
7
8
unsigned __int64 __fastcall sub_956(vm_cpu *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
++a1->vm_rip;
return __readfsqword(0x28u) ^ v2;
}

什么都没干,rip只是加1,这是所有指令都要有的

5.mul

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_A08(vm_cpu *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
a1->vm_eax *= a1->vm_edx;
++a1->vm_rip;
return __readfsqword(0x28u) ^ v2;
}

mul eax edx

6.xchg

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 __fastcall sub_8F0(vm_cpu *a1)
{
int vm_eax; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
vm_eax = a1->vm_eax;
a1->vm_eax = a1->vm_ebx;
a1->vm_ebx = vm_eax;
++a1->vm_rip;
return __readfsqword(0x28u) ^ v3;
}

xchg eax ebx

7.自定义函数

1
2
3
4
5
6
7
8
9
unsigned __int64 __fastcall sub_99C(vm_cpu *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
a1->vm_eax = a1->vm_ecx + 2 * a1->vm_ebx + 3 * a1->vm_eax;
++a1->vm_rip;
return __readfsqword(0x28u) ^ v2;
}

实现了eax=ecx+2ebx+3eax应该是自定义指令

自己实现dispatcher,汇编层面逆向

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include<iostream>
using namespace std;
int opcode[]={
0xF5, 0xF1, 0xE1, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x20, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x01, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x21, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x22, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x03, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x23,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x24, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x25, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x26, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x27, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x28, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x09, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x29, 0x00, 0x00,
0x00, 0xF1, 0xE1, 0x0A, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x2A, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0B, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x2B, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0C,
0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2C, 0x00, 0x00, 0x00,
0xF1, 0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2D,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x2E, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0F, 0x00,
0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x2F, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x10, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x30, 0x00,
0x00, 0x00, 0xF1, 0xE1, 0x11, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x31, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x12, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x32, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x13, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x33, 0x00, 0x00,
0x00, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF5, 0xF1,
0xE1, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x01, 0x00, 0x00,
0x00, 0xF2, 0xF1, 0xE4, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xE1,
0x01, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x02, 0x00, 0x00, 0x00,
0xF2, 0xF1, 0xE4, 0x01, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x02,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x03, 0x00, 0x00, 0x00, 0xF2,
0xF1, 0xE4, 0x02, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x03, 0x00,
0x00, 0x00, 0xF1, 0xE2, 0x04, 0x00, 0x00, 0x00, 0xF2, 0xF1,
0xE4, 0x03, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x04, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x05, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4,
0x04, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x05, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xF1, 0xE4, 0x05,
0x00, 0x00, 0x00, 0xF1, 0xE1, 0x06, 0x00, 0x00, 0x00, 0xF1,
0xE2, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x08, 0x00, 0x00,
0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6, 0xF7, 0xF1,
0xE4, 0x06, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x07, 0x00, 0x00,
0x00, 0xF1, 0xE2, 0x08, 0x00, 0x00, 0x00, 0xF1, 0xE3, 0x09,
0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00, 0x00, 0xF6,
0xF7, 0xF1, 0xE4, 0x07, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x08,
0x00, 0x00, 0x00, 0xF1, 0xE2, 0x09, 0x00, 0x00, 0x00, 0xF1,
0xE3, 0x0A, 0x00, 0x00, 0x00, 0xF1, 0xE5, 0x0C, 0x00, 0x00,
0x00, 0xF6, 0xF7, 0xF1, 0xE4, 0x08, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x13, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0D, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x13, 0x00, 0x00, 0x00, 0xF1, 0xE1, 0x0E, 0x00, 0x00, 0x00,
0xF1, 0xE2, 0x12, 0x00, 0x00, 0x00, 0xF8, 0xF1, 0xE4, 0x0E,
0x00, 0x00, 0x00, 0xF1, 0xE7, 0x12, 0x00, 0x00, 0x00, 0xF1,
0xE1, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE2, 0x11, 0x00, 0x00,
0x00, 0xF8, 0xF1, 0xE4, 0x0F, 0x00, 0x00, 0x00, 0xF1, 0xE7,
0x11, 0x00, 0x00, 0x00, 0xF4
};
int main()
{
int rip=0;
int vm_eax=0;
int vm_ebx=18;
int vm_ecx=0;
int vm_edx=0;
int vm_stack[521];
for(int i=0;i<521;i++)
{
vm_stack[i]=0;
}
int len = sizeof(opcode) / sizeof(opcode[0]);
while (rip<len)
{
if(opcode[rip]==0xF1)
{
int v2=opcode[rip+2];
switch(opcode[rip+1])
{
case 0xE1:
vm_eax = vm_stack[v2];
cout<<"mov eax,stack["<<v2<<"]"<<endl;
break;
case 0xE2:
vm_ebx = vm_stack[v2];
cout<<"mov ebx,stack["<<v2<<"]"<<endl;
break;
case 0xE3:
vm_ecx = vm_stack[v2];
cout<<"mov ecx,stack["<<v2<<"]"<<endl;
break;
case 0xE4:
vm_stack[v2] = vm_eax;
cout<<"mov stack["<<v2<<"],eax"<<endl;
break;
case 0xE5:
vm_edx = vm_stack[v2];
cout<<"mov edx,stack["<<v2<<"]"<<endl;
break;
case 0xE7:
vm_stack[v2] = vm_ebx;
cout<<"mov stack["<<v2<<"],ebx"<<endl;
break;
default:
break;
}
rip+=6;
continue;
}
else if(opcode[rip]==0xF2)
{
vm_eax^=vm_ebx;
cout<<"xor eax ebx"<<endl;
}
else if(opcode[rip]==0xF5)
{
// for(int i=0;i<21;i++)
// {
// char c;
// cin>>c;
// vm_stack[i]=c;
// }
cout<<"read flag&&judge len"<<endl;
}
else if(opcode[rip]==0xf4)
{
cout<<"nop"<<endl;
}
else if(opcode[rip]==0xF6)
{
vm_eax=vm_ecx+2*vm_ebx+3*vm_eax;
cout<<"eax=ecx+2*ebx+3*eax"<<endl;
}
else if(opcode[rip]==0xF7)
{
vm_eax*=vm_edx;
cout<<"mul eax edx"<<endl;
}
else if(opcode[rip]==0xF8)
{
int t = vm_eax;
vm_eax = vm_ebx;
vm_ebx = t;
cout<<"xchg eax ebx"<<endl;
}
rip++;

}

}

这里其实可以把栈信息一起打印出来的,但是这道题非常简单没有涉及到栈的其他操作,只是简单把flag放在栈上,所以这里不打印了

结果:

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
read flag&&judge len
mov eax,stack[0]
xor eax ebx
mov stack[32],eax
mov eax,stack[1]
xor eax ebx
mov stack[33],eax
mov eax,stack[2]
xor eax ebx
mov stack[34],eax
mov eax,stack[3]
xor eax ebx
mov stack[35],eax
mov eax,stack[4]
xor eax ebx
mov stack[36],eax
mov eax,stack[5]
xor eax ebx
mov stack[37],eax
mov eax,stack[6]
xor eax ebx
mov stack[38],eax
mov eax,stack[7]
xor eax ebx
mov stack[39],eax
mov eax,stack[8]
xor eax ebx
mov stack[40],eax
mov eax,stack[9]
xor eax ebx
mov stack[41],eax
mov eax,stack[10]
xor eax ebx
mov stack[42],eax
mov eax,stack[11]
xor eax ebx
mov stack[43],eax
mov eax,stack[12]
xor eax ebx
mov stack[44],eax
mov eax,stack[13]
xor eax ebx
mov stack[45],eax
mov eax,stack[14]
xor eax ebx
mov stack[46],eax
mov eax,stack[15]
xor eax ebx
mov stack[47],eax
mov eax,stack[16]
xor eax ebx
mov stack[48],eax
mov eax,stack[17]
xor eax ebx
mov stack[49],eax
mov eax,stack[18]
xor eax ebx
mov stack[50],eax
mov eax,stack[19]
xor eax ebx
mov stack[51],eax
nop
read flag&&judge len
mov eax,stack[0]
mov ebx,stack[1]
xor eax ebx
mov stack[0],eax
mov eax,stack[1]
mov ebx,stack[2]
xor eax ebx
mov stack[1],eax
mov eax,stack[2]
mov ebx,stack[3]
xor eax ebx
mov stack[2],eax
mov eax,stack[3]
mov ebx,stack[4]
xor eax ebx
mov stack[3],eax
mov eax,stack[4]
mov ebx,stack[5]
xor eax ebx
mov stack[4],eax
mov eax,stack[5]
mov ebx,stack[6]
xor eax ebx
mov stack[5],eax
mov eax,stack[6]
mov ebx,stack[7]
mov ecx,stack[8]
mov edx,stack[12]
eax=ecx+2*ebx+3*eax
mul eax edx
mov stack[6],eax
mov eax,stack[7]
mov ebx,stack[8]
mov ecx,stack[9]
mov edx,stack[12]
eax=ecx+2*ebx+3*eax
mul eax edx
mov stack[7],eax
mov eax,stack[8]
mov ebx,stack[9]
mov ecx,stack[10]
mov edx,stack[12]
eax=ecx+2*ebx+3*eax
mul eax edx
mov stack[8],eax
mov eax,stack[13]
mov ebx,stack[19]
xchg eax ebx
mov stack[13],eax
mov stack[19],ebx
mov eax,stack[14]
mov ebx,stack[18]
xchg eax ebx
mov stack[14],eax
mov stack[18],ebx
mov eax,stack[15]
mov ebx,stack[17]
xchg eax ebx
mov stack[15],eax
mov stack[17],ebx
nop

正常一道普通的vm逆向题到这里看汇编逆向写exp就结束了,但这道题还有坑

题外话:

解题:

这道题汇编前半部分很明显不对,因为flag总长度才21,栈上怎么索引到50多了,所以交叉引用找到真正的check函数,而且汇报中有两次输入,第二次输入才是真的

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 sub_F00()
{
int i; // [rsp+Ch] [rbp-14h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
for ( i = 0; len_flag - 1 > i; ++i )
{
if ( *((_BYTE *)vm_stack + i) != byte_202020[i] )
exit(0);
}
return __readfsqword(0x28u) ^ v2;
}

exp:

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
from z3 import *
import re

# 密文(十六进制字符串)
flag = '69 45 2A 37 09 17 C5 0B 5C 72 33 76 33 21 74 31 5F 33 73 72'.split(' ')
flag = [int(x, 16) for x in flag]

# 1) 还原前面的三次交换(你在 forward 阶段做了 13<->19, 14<->18, 15<->17)
flag[15], flag[17] = flag[17], flag[15]
flag[14], flag[18] = flag[18], flag[14]
flag[19], flag[13] = flag[13], flag[19]

# 2) 用 Z3 求解 a6,a7,a8(原始 stack[6..8])
# 方程(forward)是: f6 = (s8 + 2*s7 + 3*s6) * s12 (全部为字节运算)
a6, a7, a8 = BitVecs('a6 a7 a8', 8)
s = Solver()

# 注意:这里 flag[...] 是 Python 整数(0..255),Z3 会自动把它转为相应的常量。
# 若你想更严格地把所有算术限定为 8-bit,可把常量也包装成 BitVecVal(...,8)。
s.add( BitVecVal(flag[6],8) == (a8 + (a7 << 1) + a6 * BitVecVal(3,8)) * BitVecVal(flag[12],8) )
s.add( BitVecVal(flag[7],8) == (BitVecVal(flag[9],8) + (a8 << 1) + a7 * BitVecVal(3,8)) * BitVecVal(flag[12],8) )
s.add( BitVecVal(flag[8],8) == (BitVecVal(flag[10],8) + (BitVecVal(flag[9],8) << 1) + a8 * BitVecVal(3,8)) * BitVecVal(flag[12],8) )

if s.check() == sat:
m = s.model()
# 把求得的 a6,a7,a8 写回 flag 对应位置
for v in [a6, a7, a8]:
idx = int(re.search(r'\d+', str(v)).group())
flag[idx] = m[v].as_long()

# 3) 逆向 XOR 链(forward 做了 f0 = s0^s1; f1 = s1^s2; ...; f5 = s5^s6)
for i in range(5, -1, -1):
flag[i] ^= flag[i + 1]

print('[+] flag: ', ''.join(chr(x) for x in flag))

# [+] flag: Y0u_hav3_r3v3rs3_1t!

前言:

学过计算机组成原理的应该都知道,程序的运行是靠cpu解释可执行文件中操作码来实现的功能,vm逆向顾名思义就是自己定义了小型cpu并定义了指令集,将程序的代码转换自定义的操作码(opcode),然后在程序执行时再通过解释这些操作码,选择对应的函数执行,从而实现程序原有的功能。

vm逆向基本原理:

vm_init:

虚拟机的入口函数,对虚拟机环境进行初始化,初始化一般包括

  • 寄存器初始化(eax,ebx,ecx,edx,eip)

  • 把handle函数和操作码连接在一起

  • 给虚拟机的栈空间vm_stack分配内存

vm_run:

虚拟机开始运行的地方

vm_dispatcher:

调度器,解释opcode,并选择对应的handle函数执行,当handle执行完后会跳回这里,形成一个循环。

vm_handle:

处理器,当rip走到对应操作码时调用对应操作函数,并接受操作数。

opcode :

程序可执行代码转换成的操作码

在这种情况下,如果要逆向程序,就需要对整个emulator结构进行逆向,理解程序功能,还需要结合opcode进行分析,整个程序逆向工程将会十分繁琐。这是一个一般虚拟机结构:
vm逆向

vm_lables:

有的虚拟机涉及lable的创造和调用,起到跳转目的

分析方法

在比赛中,虚拟机题目常常有两种考法:

  • 给可执行程序和opcode,逆向emulator,结合opcode文件,推出flag

  • 只给可执行程序,逆向emulator,构造opcode,读取flag

拿到一个虚拟机之后,一般有以下几个逆向过程:

  • 分析虚拟机入口,搞清虚拟机的输入,或者opcode位置

  • 理清虚拟机结构,包括Dispatcher和各个Handler

  • 逆向各个Handler,分析opcode的意义

  • 根据opcode运行时打印出对应汇编代码,根据汇编代码逻辑进行逆向

一个简单vm虚拟机实例:

vm_cpu结构体

1
2
3
4
5
6
7
8
typedef struct
{
unsigned long r1; //虚拟寄存器r1
unsigned long r2; //虚拟寄存器r2
unsigned long r3; //虚拟寄存器r3
unsigned char *eip; //指向正在解释的opcode地址
vm_opcode op_list[OPCODE_N]; //opcode列表,存放了所有的opcode及其对应的处理函数
}vm_cpu;

vm_opcode结构体

1
2
3
4
5
typedef struct
{
unsigned char opcode;
void (*handle)(void*);
}vm_opcode;

vm_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

void *vm_init()
{
vm_vpu *cpu;
cpu->r1 = 0;
cpu->r2 = 0;
cpu->r3 = 0;
cpu->eip = (unsigned char *)vm_code;//将eip指向opcode的地址

cpu->op_list[0].opcode = 0x1;
cpu->op_list[0].handle = (void (*)(void *))mov;//将操作字节码与对应的handle函数关联在一起

cpu->op_list[1].opcode = 0xf2;
cpu->op_list[1].handle = (void (*)(void *))xor;

cpu->op_list[2].opcode = 0xf5;
cpu->op_list[2].handle = (void (*)(void *))read_;

vm_stack = malloc(0x512);
memset(vm_stack,0,0x512);
}

handles(示例)

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
void mov(vm_cpu *cpu);
void xor(vm_cpu *cpu); //xor flag
void read_(vm_cpu *cpu); //call read, read the flag

void xor(vm_cpu *cpu)
{
int temp;
temp = cpu->r1 ^ cpu->r2;
temp ^= 0x12;
cpu->r1 = temp;
cpu->eip += 1; //xor指令占一个字节
}

void read_(vm_cpu *cpu)
{
char *dest = vm_stack;
read(0,dest,12); //用于往虚拟机的栈上读取数据
cpu->eip += 1; //read_指令占一个字节
}

void mov(vm_cpu *cpu)
{
//mov指令的参数都因曾在字节码也就是vm_code中,指令表示后的一个字节是寄存器表示,第二到
//第五是要mov的数据在vm_stack上的偏移
//这里只是实现了从vm_stack上取数据和存数据到vm_stack上
unsigned char *res = cpu->eip + 1; //寄存器标识
int *offset = (int *)(cpu->eip + 2); //寄存器在vm_stack上的偏移
char *dest = 0;
dest = vm_stack;

switch (*res) {
case 0xe1:
cpu->r1 = *(dest + *offset);
break;

case 0xe2:
cpu->r2 = *(dest + *offset);
break;

case 0xe3:
cpu->r3 = *(dest + *offset);
break;
case 0xe4:
{
int x = cpu->r1;
*(dest + *offset) = x;
break;

}
}

cpu->eip += 6;
//mov指令占六个字节,所以eip要向后移6位
}

vm_code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned char vm_code[] = {
0xf5,
0xf1,0xe1,0x0,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x20,0x00,0x00,0x00,
0xf1,0xe1,0x1,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x21,0x00,0x00,0x00,
0xf1,0xe1,0x2,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x22,0x00,0x00,0x00,
0xf1,0xe1,0x3,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x23,0x00,0x00,0x00,
0xf1,0xe1,0x4,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x24,0x00,0x00,0x00,
0xf1,0xe1,0x5,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x25,0x00,0x00,0x00,
0xf1,0xe1,0x6,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x26,0x00,0x00,0x00,
0xf1,0xe1,0x7,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x27,0x00,0x00,0x00,
0xf1,0xe1,0x8,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x28,0x00,0x00,0x00,
0xf1,0xe1,0x9,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x29,0x00,0x00,0x00,
0xf1,0xe1,0xa,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x2a,0x00,0x00,0x00,
0xf1,0xe1,0xb,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x2b,0x00,0x00,0x00,
0xf4
};


vm_stack

一般是一个全局数组,用于存放虚拟机的栈

1
2
vm_stack = malloc(0x512);
memset(vm_stack,0,0x512);

vm_run

1
2
3
4
5
6
7
8
9
10
11
12
void vm_run(vm_cpu *cpu)
{
/*
进入虚拟机
eip指向要被解释的opcode地址
*/
cpu->eip = (unsigned char*)opcodes;
while((*cpu->eip) != 0xf4)//如果opcode不为RET,就调用vm_dispatcher来解释执行
{
vm_dispatcher(*cpu->eip)
}
}

vm_dispatcher

1
2
3
4
5
6
7
8
9
10
11
12
void vm_dispatcher(vm_cpu *cpu)
{
int i;
for(i = 0; i < OPCODE_N; i++)
{
if(*cpu->eip == cpu->op_list[i].opcode)
{
cpu->op_list[i].handle(cpu);
break;
}
}
}

做题流程:

  • 先根据上面的特征静态分析判断函数是vm中的哪一部分,如果静态分析困难,那就动调一下

  • 找到对应部分后创建上述结构体来帮助分析

  • 自己把vm还原在vscode里,并在dispatcher部分加上打印这条指令的代码,有的时候也可以打印stack,和当前执行完指令的内存状态(各个变量的值)

  • 根据打印出的汇编指令,手动逆向

具体使用方法还需在实战中不断练习,具体做题流程可看我的vm题单真题

参考文献:

虚拟机逆向与实现-CSDN博客

虚拟机保护逆向入门 - FreeBuf网络安全行业门户

前言:

拖了很久的rust逆向都一直没学,正好编译原理要做rust语法分析器,顺便把rust语言学一下

rust相比其他编程语言的优势:

  • rust不通过GC(garbage collection)机制管理内存,例如python,golang等基于GC机制的编程语言会在exe运行时不断寻找虚拟地址中无用的内存空间.这会大大降低运行速度

  • rust使用所有权机制管理内存,这也使得它相比与手动开辟内存的c/c++更安全

我们rust在逆向中通常用于网络编程,游戏编程,wasm,嵌入式.所以写游戏外挂,实现检测外挂都必须要学习rust.

语法:

变量:

rust中变量声明要用let.rust中每个变量类型可以自己指定,也可交给编译器推断,每个变量类型可以声明可变也可声明不可变.(注:如果要声明常量类型时,常量名一定要全大写,并且必须显示指定类型.例如const MAX:u32=10;)

整数类型

  • 有符号:i8, i16, i32, i64, i128, isize

  • 无符号:u8, u16, u32, u64, u128, usize

浮点数

  • f32(32 位单精度)

  • f64(64 位双精度,默认)

布尔型

  • true

  • false

char型

字符串型

  • &str → 字符串切片(不可变)

  • String → 堆分配的可变字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() {
let n: i32= 5; // 变量后的:i32可以自己指定,也可让编译器推断
let mut i: u32 = 5; // u32 表示32位无符号整数,mut表示可变
println!("The value of n is {}",n); // println! 用来打印字符串到终端,n代表换行,!代表宏
println!("The value of i is {}",i);
i=7;
println!("The value of i is {}",i);
let x: f64 = 5.2;
println!("The value of x is {}",x);
let y: char ='d';
println!("The value of y is {}",y);
let t=true;
println!("The value of t is {}",t);
let str: &str="hello";
println!("The value of str is {}",str);
let mut s2: String = String::from("Hello");
s2.push_str(", Rust!"); // 可变字符串
println!("{}", s2);
}

输出结果:

1
2
3
4
5
6
7
8
The value of n is 5
The value of i is 5
The value of i is 7
The value of x is 5.2
The value of y is d
The value of t is true
The value of str is hello
Hello, Rust!

控制语句:

if-else if-else

1
2
3
4
5
6
7
// if
let n = 5;
if n < 0 {
println!("负数");
} else {
println!("非负数");
}

循环

  • loop 一直循环

  • while 有条件的循环

  • for

可以通过break跳出循环,也可以通过continue继续当前循环.这和c++是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// loop
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // 返回值
}
};

// while
let mut i = 3;
while i > 0 {
println!("{}", i);
i -= 1;
}

// for
let arr = [10, 20, 30];
for val in arr {
println!("{}", val);
}

Match

match的用法和if-else很像,但是要注意match要把所有情况包含在内,不然编译阶段就报错

1
2
3
4
5
6
7
let x = 5;

match x {
1 | 2 => println!("一或二"),
3..=7 => println!("三到七之间"), // 范围匹配
_ => println!("其他"),
}

函数

函数使用

基本的函数定义是fn fucnction(a:i32,b:i32) -> i32其中箭头右面的是返回值类型.rust函数表达力非常强

1
2
3
4
5
6
7
8
9
10
fn add(a:i32,b:i32)->i32{
a+b //不加分号,rust会将最后一行作为返回值
}
fn main() {
let a=1;
let b=3;
let res=add(a,b);
println!("{}",res);//println!()是rust的输出函数,其中第一个参数必须是""包裹的字符串(不能用''替代),第二个参数是占位符,占位符的值通过{}来传递
}

闭包:

闭包可以理解成python里的lambda差不多,相当于匿名函数

1
2
3
4
5
fn main() {
let sum=|a:i32,b:i32|->i32{a+b};//这里定义时是用||包裹参数,调用时和正常函数一样
let res=sum(1,2);
println!("The sum is {}",res);
}

rust复合类型

枚举:

简单来说,枚举(enum)就是用来表示“一个值可能属于几种互斥情况之一”,也就是“有限状态或选择”。

换句话说,它适合表示有多种可能性,但每次只能选一个的场景。

1
2
3
4
5
6
7
8
9
10
11
12
enum TrafficLight {
Red,
Yellow,
Green,
}

let light = TrafficLight::Red;
match light {
TrafficLight::Red => println!("停"),
TrafficLight::Yellow => println!("准备"),
TrafficLight::Green => println!("走"),
}

结构体基础:

结构体和enum不一样的点在于声明结构体时,要把内部变量的类型写出来.而enum就不用

其中对#[derive(Debug)]的解释:

部分 含义 记忆小技巧
#[] Rust 的 属性(attribute)标记,用来告诉编译器对后面的结构体/枚举做某些处理 “井号括号 → 给编译器的指令”
derive 自动 派生/生成实现 trait 的代码 “derive = 自动生成某种功能”
(Debug) 指定生成的 trait 是 Debug “Debug = 调试打印能力”
1
2
3
4
5
6
7
8
9
10
11
12
13
#[derive(Debug)]
struct Node{
x: i32,
y: i32
}
fn main() {
let n=Node{x: 1, y: 2};
let x=n.x;
let y=n.y;
println!("{}", x);
println!("{}", y);
println!("{:?}", n); //打印结构体或enum类型,要用{:?},配合结构体定义上方的#[derive(Debug)]打印结构体
}

结构体进阶:

Rust 很多地方受 JavaScript 影响,在实例化结构体的时候用 JSON 对象的 key: value 语法来实现定义:

实例化时:

结构体类名 {
字段名 : 字段值,

}

(1)在结构体内部用impl关键字实现内联函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Node{
x: i32,
y: i32,
}
impl Node{
fn area(x:i32,y:i32)->i32{
x*y
}
}
fn main() {
let n=Node{x: 1, y: 2};
let x=n.x;
let y=n.y;
let s=Node::area(x, y);
println!("{}",s);
}

(2)用结构体中self指针实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Node{
x: i32,
y: i32,
}
// impl Node{
// fn area(x:i32,y:i32)->i32{
// x*y
// }
// }
impl Node{
fn area(&self)->i32{
self.x*self.y
}
}
fn main() {
let n=Node{x: 1, y: 2};
let x=n.x;
let y=n.y;
//let s=Node::area(x, y);
let s=n.area();
println!("{}",s);
}

(3)结构体实现构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Node{
x: i32,
y: i32,
}
impl Node{
fn new(x:i32,y:i32)->Self{//默认构造函数名都叫new,但这不是个关键字
Node{
x:x,
y:y
}
}
fn area(&self)->i32{
self.x*self.y
}
}
fn main() {
let n=Node::new(1,2);
let x=n.x;
let y=n.y;
let s=n.area();
println!("{}",s);
}

(4)self的用法:

  • self 小写 = 当前对象实例指针。

  • Self 大写 = 当前类型名。

self指针也分为可变和不可变的,可变的要在self前加关键字mut

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
#[derive(Debug)]
struct Person{
name:String,
age:u32
}
impl Person{
fn new(name:String,age:u32)->Self{
Person{name,age}
}
//不可变的this指针
fn greet(&self)->String{
format!("Hello,my name is {} and I am {} years old.",self.name,self.age)
}
//可变的this指针
fn up_age(&mut self)->u32{
self.age+=1;
self.age
}
}

fn main() {
let a=Person::new("原子".to_string(), 18); //“原子”是静态str.&str类型,要转成可变的String
println!("{:#?}",a); //{:#?}是调试格式,和{:?}的区别是,{:#?}会多出缩进,方便阅读
println!("{}",a.greet());
let mut b =a;
b.up_age();
println!("{}",b.greet());
}

输出:

1
2
3
4
5
6
Person {
name: "原子",
age: 18,
}
Hello,my name is 原子 and I am 18 years old.
Hello,my name is 原子 and I am 19 years old.

self还有一个不经常用的用法:就是如果传入参数是self(不带&)的话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Node {
x: i32,
y: i32,
}

impl Node {
// 这里的 self 是按值传递,意味着把 Node 本身交给这个方法
fn into_tuple(self) -> (i32, i32) {
(self.x, self.y)
}
}

fn main() {
let n = Node { x: 1, y: 2 };
let t = n.into_tuple(); // 这里 n 被 move 走
println!("{:?}", t);

// println!("{:?}", n.x); // 报错:因为 n 的所有权已经交出,不能再用
}


元组:

1.基本定义:元组就是把多个不同类型的值组合在一起的复合类型。
语法:

1
let tup: (i32, f64, char) = (500, 6.4, 'a');

2.访问元素

有两种方式:

方式一:解构

1
2
3
4
let tup = (500, 6.4, 'a');
let (x, y, z) = tup;
println!("y 的值是: {}", y);

方式二:点语法(下标访问)

注意第一个元素下标是0

1
2
3
4
5
let tup = (500, 6.4, 'a');
println!("第一个元素是: {}", tup.0);
println!("第二个元素是: {}", tup.1);
println!("第三个元素是: {}", tup.2);

3.特点:可以包含不同类型的值

长度固定,不能改变

4.打印可利用{:?}来打印

核心机制&数据结构

栈和堆存放

  1. 栈 (Stack) 的特点
  • 后进先出 (LIFO) 的数据结构,内存分配和释放都非常快。

  • 大小在编译时必须确定。

  • 栈上的数据一般是 固定大小、生命周期明确的值

1
2
3
4
let x = 42;       // i32,大小固定 4 字节
let y = true; // bool,1 字节
let z = 'a'; // char,4 字节
let s : &str = 'hello' //静态字符串切片,长度固定,在栈上
  1. 堆 (Heap) 的特点
  • 内存大小运行时才能确定。

  • 需要手动申请(在 Rust 中由所有权系统管理,避免泄漏)。

  • 分配和释放开销比栈大,但适合存放 动态大小或不确定大小的数据

所有权机制

堆和栈上数据都有所有权的这个概念,但是栈上数据拷贝时不会move(转移所有权),而是使用copy(复制一个样本),堆会move

栈上的数据:如果它的类型实现了 Copy trait(比如 i32、bool、char、浮点数、简单元组),那么赋值时不会发生“严格意义上的 move”,而是直接 复制一份值。

  • 所以原变量不会失效,看起来像“转移没事”。

实际上这不是“move”,而是 copy。

1
2
3
4
5
6
7
fn main() {
let x = 10;
let y = x; // Copy,不是 move
println!("x={}, y={}", x, y); // x 还能用
}


堆上的数据:比如 StringVec,它们没实现 Copy,赋值时会发生 move

  • 所有权转移后,原变量会失效,防止两个变量同时指向同一块堆内存。
1
2
3
4
5
6
7
8
fn main() {
let s1 = String::from("hi");
let s2 = s1; // Move
// println!("{}", s1); // 报错:s1 已经失效
println!("{}", s2); // 只有 s2 能用了
}


引用和可变引用:

引用 (Reference)

  • 引用本质上就是 借用 (borrow)

  • 借用不会转移所有权,值的所有者依然是原来的变量。

  • 分为:

    不可变引用 (&T):可以有多个,但不能和可变引用同时存在。

    可变引用 (&mut T):只能有一个,且不能和不可变引用共存。(可变引用要求被引用的变量是可变的)

1
2
3
4
5
fn main() {
let a:String ="hello world".to_string();
let r1=&a; //这里就算拷贝给r1,也能成功输出
println!("{}",a);
}

clone的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
#[derive(Debug,Clone)] //必须结构体里的所有字段都是可拷贝的,才可像正常u32,i32那样使用
struct man{
name:String,
age:u32
}
fn main() {
let a = man{
name:"原子".to_string(),
age:18
};
let m=a.clone();
println!("{:?}",a);
}

clone就相当于c++中的深拷贝,解决了两个指针指向同一块内存的问题,所以clone之后就可以正常赋值,并接着使用

生命周期:

生命周期用语法 'a 表示:

1
2
3
4
fn example<'a>(s: &'a str) {
println!("{}", s);
}

当结构体里有引用时,必须标注生命周期:

1
2
3
4
5
6
7
8
9
10
struct Person<'a> {
name: &'a str,
age: u32,
}

fn main() {
let name = String::from("Alice");
let p = Person { name: &name, age: 20 }; // name 生命周期必须 ≥ p 生命周期
}

生命周期的核心思想:引用永远不能比它指向的数据活得长

编译器在编译期检查生命周期,保证安全。

'a 是标识符,用来关联多个引用的生命周期。

常用数据结构:

String:

&str:是String类型的一个切片.长度确定放在栈上.

String一般长度不确定,放在堆上

创建:

1
2
3
4
let s1 = String::new();              // 空字符串
let s2 = String::from("hello"); // 从字面量创建
let s3 = "world".to_string(); // &str 转 String

添加:

1
2
3
4
let mut s = String::from("Hello");
s.push('!'); // 添加单个字符
s.push_str(" World"); // 添加字符串切片

拼接:

1
2
3
4
5
6
7
let s1 = String::from("Hello");
let s2 = String::from("World");

// 使用 + 或 format! 宏
let s3 = s1 + &s2; // s1 被移动,s2 被借用
let s4 = format!("{} {}", s2, "!!!"); // 不移动任何变量

获取长度和容量

1
2
3
4
let s = String::from("hello");
println!("length: {}", s.len()); // 字节数
println!("capacity: {}", s.capacity()); // 堆上分配的容量

删除内容

1
2
3
4
let mut s = String::from("Hello World");
s.pop(); // 删除最后一个字符
s.clear(); // 清空整个字符串

索引与切片

1
2
3
4
5
let s = String::from("hello");
// let c = s[0]; // String 不支持直接索引
let slice = &s[0..2]; // 切片,返回 &str,必须按字节边界


查找和替换

1
2
3
4
5
6
7
let s = String::from("hello world");

println!("{}", s.contains("world")); // true
println!("{}", s.find("world").unwrap()); // 6,找到索引
let new_s = s.replace("world", "Rust");
println!("{}", new_s); // hello Rust

分割字符串

1
2
3
4
let s = String::from("a,b,c");
let v: Vec<&str> = s.split(',').collect();
println!("{:?}", v); // ["a", "b", "c"]

遍历

1
2
3
4
5
6
7
8
9
10
11
12
let s = String::from("hello");

// 遍历字符
for c in s.chars() {
println!("{}", c);
}

// 遍历字节
for b in s.bytes() {
println!("{}", b);
}

Vector:

创建:

1
2
3
let mut v: Vec<i32> = Vec::new();   // 空 vector
let mut v = vec![1, 2, 3]; // 使用宏 vec! 初始化

添加元素

1
2
3
4
let mut v = Vec::new();
v.push(10); // 尾部插入
v.push(20);

访问元素

1
2
3
4
5
6
let v = vec![1, 2, 3, 4];

println!("{}", v[0]); // 下标访问 (可能 panic 越界)
println!("{:?}", v.get(2)); // 安全访问 -> Some(3)
println!("{:?}", v.get(10)); // None,不会 panic

修改元素

1
2
3
let mut v = vec![10, 20, 30];
v[1] = 200;

删除元素

1
2
3
4
let mut v = vec![1, 2, 3, 4];
v.pop(); // 删除最后一个 -> Some(4)
v.remove(0); // 删除指定下标 -> 返回删除的元素 (这里删除 1)

遍历

1
2
3
4
5
6
7
8
9
10
let v = vec![10, 20, 30];

for x in &v { // 只读遍历
println!("{}", x);
}

for x in &mut v { // 可修改遍历
*x += 1;
}

HashMap:

相当于c++中stl里的map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::collections::HashMap;

fn main() {
// 1. 创建一个 HashMap
let mut scores = HashMap::new();

// 2. 增加(插入)元素
scores.insert("Alice", 10);
scores.insert("Bob", 20);

// 3. 修改(如果 key 已存在,会覆盖旧值)
scores.insert("Alice", 30); // Alice 的值从 10 -> 30

// 4. 访问(用 get,返回 Option<&V>)
if let Some(score) = scores.get("Alice") {
println!("Alice 的分数是 {}", score);
}

// 5. 删除(移除某个 key)
scores.remove("Bob");

println!("{:?}", scores);
}