vm虚拟机保护技术简介EzMachine例题-vm逆向分析

news2024/11/24 16:49:20

文章目录

  • 前言
  • 0x1 虚拟机保护技术原理
    • 0x1A 关于调用约定
    • 0x1B Handler
    • 0x1C 指令
  • 0x2 vm虚拟机逆向 实战[GKCTF 2020]EzMachine
    • 题目分析,花指令去除
    • Handler分析
    • 脚本编写

前言

关于虚拟机逆向的知识网上很少,我看了几篇感觉都看不太明白,最后还是想起自己有本《加密与解密》(大坑a);书中的第20章 虚拟机的设计,第21章 VMProtect 逆向和还原浅析(完全看不懂还);结合网上的几篇文章特此学习,顺带记录分享一下。
书中暂时还不能理解的诸如20.4托管代码的异常处理的内容我就没有记录下来(留待以后继续探索),希望目前整理的部分笔记可以对你有所帮助,有不明白的地方欢迎评论私信交流,求大佬指定。

**注:**写完文章后,自己又看了一篇0x1的内容,发现好像学不到什么,如果想看VM逆向过程的可以直接看0x2的内容,我写的很详细了,下一篇应该会专门再出几道vm re的详细wp,练习一下,真的累a。

0x1 虚拟机保护技术原理

关于这里讨论的虚拟机和VMware之类的虚拟机是不同的东西,它是一种基于虚拟机的代码保护技术,准确地说,这里讨论的虚拟机是一种解释执行系统(例如Visual Basic 6 中的PCODE编译方式)。现在的一些动态语言(例如Ruby、Python、Lua和.NET等)从某种角度来说也是解释执行的。

Visual Basic 6 中的PCODE(P-Code,也称为Pseudo Code)编译方式是一种中间代码编译方法,用于将Visual Basic代码编译成中间代码,而不是直接生成本机机器代码。这种中间代码可以在运行时由VB6的解释器执行。
Python字节码是Python程序的一种中间表示形式,类似于P-Code或Java字节码。可见python字节码详解,Bytecode反编译过程

虚拟机保护技术就是将基于x86汇编系统的可执行代码转换为字节码指令系统的代码,以达到保护原有指令不被轻易逆向和篡改的目的。
这种指令执行系统和Intel的x86指令系统不在同一个层次中。例如,80x86汇编指令是在CPU里执行的,而字节码指令系统是通过解释指令执行的(这里的字节码指令系统是建立在x86指令系统上的)。

虚拟机执行时的情况:
在这里插入图片描述

在上图中,有几个组成部分:

  • VStartVM部分初始化虚拟机
  • VMDispatcher部分调度这些Handler。调度执行完后会返回VMDispatcher,形成循环
  • Bytecode是由指令执行系统定义的一套指令和数据组成的一串数据流。
  • Handler是一段小程序或者一段过程

如果将上面看作一个CPU,那么Bytecode就是CPU中执行的二进制代码,VMDispatcher就是CPU执行调度器,每个Handler就是CPU所支持的一条指令。

0x1A 关于调用约定

VStartVM的工作是初始化虚拟机,在VStartVM将真实环境压入栈之后会生成一个VMDispatcher标签,当Handler执行完毕就会返回这里,形成一个循环,所以VStartVM也叫做“dispatcher”(调度器)。

在这里dispatcher会获取字节码,然后从JUMP表中寻找相应的Handler并跳转过去继续执行。
调用方法:

push 指向字节码的起始地址
jmp VStartVM

在这个过程中,有着如下的三个约定:

  • edi 指向VMContext的起始值
  • esi 指向字节码的地址
  • ebp 指向VM栈地址

注:这些约定是整个执行循环都要遵守的。

VMContext 即“虚拟环境结构”,其中存放了一些需要使用的值。
具体如:

struct VMContext
{
	DOWRD v_eax;
	DOWRD v_ebx;
	DOWRD v_ecx;
	DOWRD v_edx;
	DOWRD v_esi;
	DOWRD v_edi;
	DOWRD v_ebp;
	DOWRD v_efl;    	// 符号寄存器
	DOWRD v_esp;
}

0x1B Handler

这里的handler指得是一段小程序或者一段过程,而非windows中的句柄,handler是由dispatcher调度的。

Handler分为俩大类:

  • 辅助Handler : 用于执行一些重要的、基本的指令;
  • 普通Handler : 用于执行一些普通的x86指令。

在这里插入图片描述

如上图所示:
在执行过程中,指令由普通Hadnler处理,源操作数和目的操作数都有栈辅助Handler处理。

这样写有什么好处吗?

  • 好处就是不必为指令的每一种形式都写一个模拟的Handler。
    执行一个add指令可能有不同的形式,如果先将操作数传给栈辅助Handler,那么当执行到add、指令时,操作数就已经是以一个立即数的形式存放在栈中了,这样add Handler就不必考虑操作数从哪里来,只需要用这个操作数直接加法操作就好了。

0x1C 指令

将需要描述的x86指令分类,按功能可以分为普通指令、栈指令、流指令、不可模拟指令4类。

  • 普通指令包括算术指令,数据传输指令等。(add,mov)
  • 栈指令主要指push pop等进行栈操作的指令
  • 流指令是指jmp、call、retn等会改变程序执行流程的指令
  • 不可模拟指令就是无法再次模拟的指令,例如int 3,sysenter,in,out等。

指令按操作数可以分为无操作数指令、单操作数指令、双操作数指令和多操作数指令,如下表:

在这里插入图片描述

0x2 vm虚拟机逆向 实战[GKCTF 2020]EzMachine

上面对虚拟机的原理,调用约定,Handler和指令进行了简单的介绍。写完感觉好像对读者也没什么用。。。。
下面总结复现道CTF中虚拟机逆向的实战分析。

关于ctf中虚拟机逆向的思路:

  • 先找到虚拟机的入口或者opcode(Bytecode)的位置。
  • 分析虚拟机的dispatcher和各个Hadnler
  • 逆向各Handler,进一步分析虚拟机的流程

题目分析,花指令去除

在这里插入图片描述

每拿到一个题目总是先查一查文件信息,IDA分析

在这里插入图片描述
打开来看到个花指令,将B8改为90(nop)掉就好,之后C修复一下代码,用P创建函数 ,反编译一下。

得到main函数:
在这里插入图片描述

分析main函数,while(1)循环,就像是 dispatcher调度器和Handler之间的循环。 下面俩个地址有一个应该就是存放在内存中的opcode,点进去观察;

发现byte_D49A0 里面存放的就是opcode,全部取出来备用。
在这里插入图片描述

再点开off_D48F4看看,

在这里插入图片描述

可以发现 它用 i 作为索引,调用了很多函数,推测这个应该就是dispatcher。

在这里插入图片描述

Handler分析

现在开始分析各个Handler,也就是dispatcher里面的各个函数指令;

  • 第一个
    在这里插入图片描述
    只进行了eip++的操作,说明是 nop指令;

  • 第二个
    在这里插入图片描述
    这里取了俩个机器码,然后eip+3说明这条指令占3个字节;data点进去可以看到一些数据
    在这里插入图片描述
    说明这一条指令模拟的应该mov指令,例如 mov reg,data

  • 第三个和第四个
    在这里插入图片描述
    在这里插入图片描述
    这俩个结合一起看,dword_D5BAC其实就是stack栈,在push_reg中先将result指向stack,再取一个机器码放入v1中,dword_D5BC8是栈中的位置, 然后再将 v1放入 stack栈中 dword_D5BC8处。然后返回栈的指针result。
    看push_data,大致上逻辑差不多,就是 这里取的v0不是直接压入栈中,而是作为data的索引,从data中取出一个数据压入栈中,所有这里应该是push_data。

  • 第五个
    在这里插入图片描述
    这里先将stack栈顶的值弹出来赋给 v0,取一个机器码存在 v1 里面,-- i 是因为栈顶已经弹出了一个值,然后result从data里面取出一个值,应该是寄存器的地址,最后将弹出来的值 v0 赋给寄存器。所以这里实现的是 pop reg

  • 第六个
    在这里插入图片描述
    这里有很多put函数,和数据,应该是模拟的printf 函数

  • 第七个和第八个九十十一个。都差不多
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    这里几个都差不多,就分析第七个,这里很明显实现的是add的功能,取一个机器码作为data的索引取出了一个数据存放在v0里,后面又取了一次存放在result中,最后俩个相加。后面的同理

  • 第十二个
    在这里插入图片描述
    这里eip直接等于result了说明是无条件跳转。

  • 第十三个
    在这里插入图片描述
    对于这里,取了俩个值,然后做了减法操作,得到result。模拟的是一个比较cmp的操作。

  • 第十四个和第十五个。
    在这里插入图片描述
    在这里插入图片描述
    模拟的是一个JZ跳转的的操作,等于0就跳转,不然的话就eip+3接着下一条指令
    同理下一条就是JNZ的跳转指令,不等于0就跳转。

  • 第十六个十七个
    在这里插入图片描述
    在这里插入图片描述
    jg指令是大于 0 就跳转。jl指令是小于0就跳转。

  • 第十八个
    在这里插入图片描述
    有一个gets和一个strlen,最后返回的是strlen的值,模仿的应该是strlen

  • 第十九个
    在这里插入图片描述

  • 最后几个
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

最后一个是exit,前俩个我也不是很理解。全部的分析差不多就是这样。

脚本编写

编写一个脚本,将dispatcher调度器使用起来,也就是通过机器码opcode去调度handler的过程,

opcode = [0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01,
          0x01, 0x11, 0x0C, 0x00, 0x01, 0x0D, 0x0A, 0x00, 0x01, 0x03,
          0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x02, 0x00,
          0x01, 0x00, 0x11, 0x0C, 0x00, 0x02, 0x0D, 0x2B, 0x00, 0x14,
          0x00, 0x02, 0x01, 0x01, 0x61, 0x0C, 0x00, 0x01, 0x10, 0x1A,
          0x00, 0x01, 0x01, 0x7A, 0x0C, 0x00, 0x01, 0x0F, 0x1A, 0x00,
          0x01, 0x01, 0x47, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x06,
          0x00, 0x01, 0x0B, 0x24, 0x00, 0x01, 0x01, 0x41, 0x0C, 0x00,
          0x01, 0x10, 0x24, 0x00, 0x01, 0x01, 0x5A, 0x0C, 0x00, 0x01,
          0x0F, 0x24, 0x00, 0x01, 0x01, 0x4B, 0x0A, 0x00, 0x01, 0x01,
          0x01, 0x01, 0x07, 0x00, 0x01, 0x01, 0x01, 0x10, 0x09, 0x00,
          0x01, 0x03, 0x01, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01,
          0x06, 0x02, 0x01, 0x0B, 0x0B, 0x00, 0x02, 0x07, 0x00, 0x02,
          0x0D, 0x00, 0x02, 0x00, 0x00, 0x02, 0x05, 0x00, 0x02, 0x01,
          0x00, 0x02, 0x0C, 0x00, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00,
          0x02, 0x00, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x05, 0x00, 0x02,
          0x0F, 0x00, 0x02, 0x00, 0x00, 0x02, 0x09, 0x00, 0x02, 0x05,
          0x00, 0x02, 0x0F, 0x00, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00,
          0x02, 0x02, 0x00, 0x02, 0x05, 0x00, 0x02, 0x03, 0x00, 0x02,
          0x03, 0x00, 0x02, 0x01, 0x00, 0x02, 0x07, 0x00, 0x02, 0x07,
          0x00, 0x02, 0x0B, 0x00, 0x02, 0x02, 0x00, 0x02, 0x01, 0x00,
          0x02, 0x02, 0x00, 0x02, 0x07, 0x00, 0x02, 0x02, 0x00, 0x02,
          0x0C, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02,
          0x01, 0x13, 0x01, 0x02, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x01,
          0x0E, 0x5B, 0x00, 0x01, 0x01, 0x22, 0x0C, 0x02, 0x01, 0x0D,
          0x59, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x4E,
          0x00, 0x01, 0x03, 0x00, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00,
          0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00]
opcode_key = {
    0: 'nop',
    1: 'mov reg data',
    2: 'push data',
    3: 'push_reg',
    4: 'pop_reg',
    5: 'printf',
    6: 'add_reg_reg1',
    7: 'sub_reg_reg1',
    8: 'mul',
    9: 'div',
    10: 'xor',
    11: 'jmp',
    12: 'cmp',
    13: 'je',
    14: 'jne',
    15: 'jg',
    16: 'jl',
    17: 'scan_strlen',
    18: 'mem_init',
    19: 'stack_to_reg',
    20: 'load_input',
    0xff: 'exit'}
count = 0
code_index = 1
for x in opcode:  # 读取每一个opcode
    if count % 3 == 0:
        print(str(code_index) + ':', end='')
        print(opcode_key[x], end=' ')
        code_index += 1
    elif count % 3 == 1:
        print(str(x) + ',', end='')
    else:
        print(str(x))
    count += 1

输出结果如下:

1:mov reg data 3,3
2:printf 0,0
3:scan_strlen 0,0
4:mov reg data 1,17
5:cmp 0,1
6:je 10,0
7:mov reg data 3,1
8:printf 0,0
9:exit 0,0
10:mov reg data 2,0
11:mov reg data 0,17
12:cmp 0,2
13:je 43,0
14:load_input 0,2
15:mov reg data 1,97
16:cmp 0,1
17:jl 26,0
18:mov reg data 1,122
19:cmp 0,1
20:jg 26,0
21:mov reg data 1,71
22:xor 0,1
23:mov reg data 1,1
24:add_reg_reg1 0,1
25:jmp 36,0
26:mov reg data 1,65
27:cmp 0,1
28:jl 36,0
29:mov reg data 1,90
30:cmp 0,1
31:jg 36,0
32:mov reg data 1,75
33:xor 0,1
34:mov reg data 1,1
35:sub_reg_reg1 0,1
36:mov reg data 1,16
37:div 0,1
38:push_reg 1,0
39:push_reg 0,0
40:mov reg data 1,1
41:add_reg_reg1 2,1
42:jmp 11,0
43:push data 7,0
44:push data 13,0
45:push data 0,0
46:push data 5,0
47:push data 1,0
48:push data 12,0
49:push data 1,0
50:push data 0,0
51:push data 0,0
52:push data 13,0
53:push data 5,0
54:push data 15,0
55:push data 0,0
56:push data 9,0
57:push data 5,0
58:push data 15,0
59:push data 3,0
60:push data 0,0
61:push data 2,0
62:push data 5,0
63:push data 3,0
64:push data 3,0
65:push data 1,0
66:push data 7,0
67:push data 7,0
68:push data 11,0
69:push data 2,0
70:push data 1,0
71:push data 2,0
72:push data 7,0
73:push data 2,0
74:push data 12,0
75:push data 2,0
76:push data 2,0
77:mov reg data 2,1
78:stack_to_reg 1,2
79:pop_reg 0,0
80:cmp 0,1
81:jne 91,0
82:mov reg data 1,34
83:cmp 2,1
84:je 89,0
85:mov reg data 1,1
86:add_reg_reg1 2,1
87:jmp 78,0
88:mov reg data 3,0
89:printf 0,0
90:exit 0,0
91:mov reg data 3,1
92:printf 0,0
93:exit 0,0
94:nop 

大概转换成汇编的样子,加一点注释;

mov reg3, 3
printf (...)
scanf(input)strlen(input)  # 输入并计算长度放入reg0中
mov reg1, 17
cmp reg0, reg1          # 等于17
je 10
mov reg3, 1
printf(...)
exit()
mov reg2, 0           # 跳到这里 
mov reg0, 17				
cmp reg0, reg2
je 43					# 看到43行去,4041是做一个类似于reg2++的操作 je是等于跳转,说明这里是循环17次
reg1 = load_input[ebx] 			# 循环17次正好读取 17 个输入
mov reg1, 'a'
cmp reg0, reg1
jl 26						# 小于 ‘a’ 就跳到26
mov reg1 'z'
cmp reg0, reg1
jg 26						# 大于’z'就跳到26
mov reg1, 'G'
xor reg0, reg1				# reg0 ^G’
mov reg1 1
add reg0, reg1				# reg0 + 1
jmp 36
mov reg1 'A'
cmp reg0, reg1	
jl 36						# 小于 ‘A'就跳到 36
mov reg1 'Z'
cmp reg0, reg1				# 大于 ‘Z'就跳到 36
jg 36
mov reg1, 'k'
xor reag0, reg1				# ^ 'k'
mov reg1, 1
sub reg0, reg1				# -1
mov reg1, 16  				#
div reg0, reg1				# reg0 / 16		
push reg1					# 压入整除的数字
push reg0					# 再压入余数		
mov reg1, 1			
add reg2, reg1				# reg2++
jmp 11
压入一长串数据

































mov reg2 1				
stack to reg 1,2			# 不太理解这里什么意思 , 好像是拆成
pop reg0
cmp reg0, reg1				# 比对	
jne 91						# 不等于就失败
mov reg1 34					# 将 17个拆成了 34个 包括整数和余数
cmp reg2 reg1				
je 89
mov reg1 1
add reg2 reg1
add reg2, reg1
jmp 78					
mov reg3 0				# 返回值为0	
printf(...)				# 失败
exit					# 退出	
mov reg3 1				# 返回值为1
pintf(...)				# 成功
exit()					# 退出

data = [0x7,0xd,0x0,0x5,0x1,0xc,0x1,0x0,0x0,0xd,0x5,0xf,0x0,0x9,0x5,0xf,0x3,0x0,0x2,0x5,0x3,0x3,0x1,0x7,0x7,0xb,0x2,0x1,0x2,0x7,0x2,0xc,0x2,0x2,]
data = data[::-1]
flag = ''
for i in range(0, 34, 2):
    temp = data[i] + data[i+1]*16
    x = ((temp+1) ^ 75)
    y = ((temp-1) ^ 71)
    if 65 <= x <= 90:    # 'A'-'Z'
        flag += chr(x)
    elif 97 <= y <= 122:   # 'a' - 'z'
        flag += chr(y)
    else:
        flag += chr(temp)    # 没有处于'a'-'z'或'A'-'Z'之间
print(flag)

# flag{Such_A_EZVM}

累死了。。。。

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

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

相关文章

如何设置3D模型的凹凸贴图?

1、凹凸贴图的原理&#xff1f; 凹凸贴图&#xff08;bump mapping&#xff09;是一种计算机图形技术&#xff0c;用于增强表面的视觉效果&#xff0c;使其看起来具有凹凸感&#xff0c;而实际上并没有改变模型的几何形状。 凹凸贴图的原理基于光照模型。通常&#xff0c;我们…

分布式理论和分布式锁知识点总结

文章目录 (一) 分布式理论算法和协议1&#xff09;CAP理论总结 2&#xff09;BASE理论BASE 理论的核心思想基本可用软状态最终一致性 3&#xff09;Paxos算法Basic Paxos 算法4&#xff09; Raft算法1 拜占庭将军 5&#xff09;Gossip协议 (二) 分布式锁分布式锁应该具备哪些条…

U盘RAW格式怎么恢复 U盘RAW格式怎么改过来

当我们遇到U盘变成raw格式时&#xff0c;首先需要了解的是&#xff0c;U盘的raw格式通常是由于文件系统损坏或病毒感染引起的。当U盘变成raw格式时&#xff0c;将导致无法正常访问其中数据。因此&#xff0c;需要我们手动恢复U盘中的相关数据&#xff0c;那么下面就来为大家介绍…

亚信科技:发挥自我优势深入AIGC,并购整合高瞻远瞩致力未来路

【科技明说 &#xff5c; 重磅专题】 亚信科技在IT提供商领域中是一个低调的前行者&#xff0c;在全球通信及大型企业市场中扮演着重要的角色。对于近年来如火如荼AI方面的投入与研究&#xff0c;亚信科技是否也很重视呢&#xff1f; 事实上&#xff0c;是肯定的回答。 在我看…

C++ stack queue 的模拟实现

1.为什么选择 deque 作为 stack 和 queue 的默认容器呢&#xff1f; stack 是一种后进先出的特殊线性数据结构&#xff0c;因此只要具有 push_back() 和 pop_back() 操作的线性结构&#xff0c;都可 以作为 stack 的底层容器&#xff0c;比如 vector 和 list 都可以&#xff1b…

常用字符串函数拓展

文章目录 字符串拓展函数strncpystrncatstrncmpstrstrstrtokstrerrormemcpymemmovememcmpmemset 库函数模拟实现memmoveqsort 我们在学习C语言时已经学习了一些常见的字符串函数&#xff0c;但这还不能满足我们的需求&#xff0c;为此我们拓展了几个常用的字符串函数。 字符串拓…

leetCode 169. 多数元素 + 摩尔投票法

169. 多数元素 - 力扣&#xff08;LeetCode&#xff09; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 class Solution { public…

【教学类-40-02】A4骰子纸模制作2.0(统计表、棋盘)

作品展示 背景需求 上次做了一个骰子1.0&#xff08;纸盒插口式样&#xff09;&#xff0c;但是无论是裁剪纸模&#xff08;去掉白边&#xff09;&#xff0c;还是凹造型&#xff08;立体、黏贴&#xff09;&#xff0c;4/5大班幼儿都感到困难。因此我想让纸模更简单。 1、裁…

OpenCV学习(六)——图像算术运算(加法、融合与按位运算)

图像算术运算 6. 图像算术运算6.1 图像加法6.2 图像融合6.3 按位运算 6. 图像算术运算 6.1 图像加法 OpenCV加法是饱和运算Numpy加法是模运算 import cv2 import numpy as npx np.uint8([250]) y np.uint8([10])# OpenCV加法 print(cv2.add(x, y)) # 25010 260 > 255…

基于Threejs开发的3D点位编辑器

简介 编辑器可以让用户在3D场景中添加、编辑和删除点位&#xff0c;并且支持上传参考模型、多点位类型的添加、上传、编辑、下载和删除、场景视图中点位的拖拽、场景配置等功能。 注&#xff1a;所有操作均在本地。 技术栈 three.js&#xff1a;一个用于创建3D图形的JavaScr…

AN动画基础——摄像头

【AN动画基础——摄像头】 摄像头功能基本动画景深效果 实战 本篇内容&#xff1a;了解摄像头 重点内容&#xff1a;摄像头应用 工 具&#xff1a;Adobe Animate 2022 摄像头功能 在动画制作中&#xff0c;摄像头用于模拟真实摄影过程的视角选择和镜头运动。 摄像头可以决定观…

机器学习-特征选择:如何使用互信息特征选择挑选出最佳特征?

一、引言 特征选择在机器学习中扮演着至关重要的角色&#xff0c;它可以帮助我们从大量的特征中挑选出对目标变量具有最大预测能力的特征。互信息特征选择是一种常用的特征选择方法&#xff0c;它通过计算特征与目标变量之间的互信息来评估特征的重要性。 互信息是信息论中的一…

scratch绘制彩虹灯柱 2023年9月中国电子学会图形化编程 少儿编程 scratch编程等级考试三级真题和答案解析

目录 scratch绘制彩虹灯柱 一、题目要求 1、准备工作 2、功能实现 二、案例分析

无需编程,小白也能建立个人网站

想要搭建一个属于自己的网站&#xff0c;但又不懂编程&#xff1f;别担心&#xff0c;现在有一个简单的方法可以帮助你轻松实现这个愿望。只需要几个简单的步骤&#xff0c;就可以让小白也能搭建出一个漂亮的网站。 首先&#xff0c;登录乔拓云账号&#xff0c;点击网站搭建进入…

【华为OD:C++机试】Day-1

目录 &#x1f337;1. 统计监控、需要打开多少监控器&#xff1a; &#x1f337;2. 阿里巴巴找黄金宝箱&#xff1a; &#x1f337;3. 事件推送&#xff1a; &#x1f337;4. 分苹果&#xff1a; &#x1f337;5. 乱序整数序列两数之和绝对值最小&#xff1a; &#x1f337;6.卡…

【影刀演示_发送邮件的格式化HTML留存】

发送邮件的格式化HTML留存 纯文本&#xff1a; 亲爱的小张: 端午节将至&#xff0c;公司为了感谢大家一年以来的辛勤工作和付出&#xff0c;特别为大家准备了京客隆超市福利卡&#xff0c;希望为大家带来些许便利和节日的喜悦。 以下是您的福利卡卡号和密码&#xff0c;请您…

2.预备知识

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 知识框架No.1 数据预处理一、N维数组样例二、创建数组三、访问元素四、数据操作五、数据预处理六、D2L注意点 No.2 线性代数一、标量二、向量1、基本操作2、空间表示3、乘法 三、矩阵1、基本操作2、乘法3、空间表示4、乘法5、范数6、…

大部分人都不知道产品说明书有这些特点

企业网站产品说明书是企业展示产品信息的重要工具。它不仅提供了清晰的产品介绍、详细的技术规格、直观的使用指南和专业的设计与排版&#xff0c;还能帮助企业主、市场营销人员和潜在客户更好地了解产品。 | 1.清晰的产品介绍 产品特点、功能和优势 通过清晰的产品介绍&…

如何和安装Windows10系统教程(最新最详细)

目录 一.简介 二.安装步骤 软件&#xff1a;Windows 10版本&#xff1a;1909语言&#xff1a;简体中文大小&#xff1a;4.95G安装环境&#xff1a;Win10/Win8/Win7(64位&#xff09;硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff09;下载通道①丨百度网盘&#xff1a…