学习逆向安全的必备基础: 汇编的初步了解

news2024/11/15 4:59:03

什么是汇编

汇编语言是一种低级编程语言,它使用简单的助记符来表示计算机底层的机器指令。

汇编语言是直接与计算机硬件交互的,它能够控制计算机中的每一个细节。

由于汇编语言非常低级,所以编写汇编程序通常比较困难。不过,汇编语言的优点是它能够非常有效地利用计算机的硬件资源,因此它在某些应用中仍然很常用,例如在系统编程和嵌入式系统开发中


学汇编需了解的知识

二进制与十六进制的转换

二进制十六进制
00000
00011
00102
00113
01004
01015
01106
01117
10008
10019
1010A
1011B
1100C
1101D
1110E
1111F

数据宽度

计算机中的数据都有硬件限制的长度限制,称为数据宽度, 超出宽度的数据将被舍弃

以下表格是常见的数据宽度及其存储范围

数据单位数据宽度存储范围(十六进制)
位(bit)1位
字节(byte)8位0~0xFF
字(word)16位0~0xFFFF
双字(word)32位0~0xFFFFFFFF

有符号数的编码规则

在计算机内存中, 正数以原码存储, 负数以补码存储


原码

原码是一种二进制数字表示方法,它的二进制表示就是它的绝对值的二进制表示, 其中最高位为符号位

例如, 十进制数 7 的二进制原码表示就是 00000111,十进制数 -7 的二进制原码表示就是10000111,其中最高位为符号位


反码

  • 正数: 反码和原码相同
  • 负数: 符号位为1, 其余位对原码取反

补码

  • 正数: 补码与原码相同
  • 负数: 反码加1

举个例子

− 7 { 原码 : 10000111 反码 : 11111000 补码 : 11111001 -7\begin{cases} \text{原码}:10000111\\ \text{反码}:11111000\\ \text{补码}:11111001\\ \end{cases} 7 原码:10000111反码:11111000补码:11111001

十进制数 -7 的二进制原码表示就是 10000111

它的反码除了符号位不变, 其余位对原码取反, 即为11111000

它的补码在反码的基础上+1,即为11111001


位运算

位运算有四种, 分别是与运算、或运算、异或运算、非运算

  • 与运算: 两个位都为1, 结果才为1
  • 或运算: 两个位之中有一个1, 结果就是1
  • 异或运算: 两个位不相同, 结果才为1
  • 非运算: 0就是1, 1就是0, 属于单目运算

通用寄存器

通用寄存器通常是计算机中比较常用的寄存器,它们可以用来存储程序执行过程中的中间结果、临时数据等

通用寄存器的名称和数量取决于所使用的计算机体系结构, 此处重点描述x86架构计算机的通用寄存器, 一共有八个, 如下表格详细的描述了各个寄存器的用途

32位通用寄存器16位通用寄存器8位通用寄存器描述
EAXAXAH、AL主要用于存储算术运算结果,如加法、减法、乘法和除法运算。它还可以用来存储系统调用的返回值
ECXCXCH、CL主要用于循环计数器,如在循环中存储计数器的值
EDXDXDH、DL主要用于存储数据,如在算术运算中的乘法和除法运算中存储运算数。它还可以用来存储 I/O 操作的结果
EBXBXBH、BL主要用于存储基址,如在寻址运算中存储基址地址。它还可以用来存储函数参数和局部变量
32位通用寄存器16位通用寄存器描述
ESPSP堆栈指针寄存器,用于存储堆栈的顶部地址
EBPBP基址指针寄存器,用于存储当前函数的基址地址
ESISI源指针寄存器,用于存储内存操作的源地址
EDIDI目的指针寄存器,用于存储内存操作的目的地址

堆栈

什么是堆栈

堆栈是一种先进后出(FILO)的数据结构。它由一组连续的内存空间组成,可以用来存储数据。常见的操作包括压栈和弹栈。

  • 压栈(push):将一个数据项压入堆栈的顶部
  • 弹栈(pop):将堆栈顶部的数据项弹出堆栈

简单来说,堆栈就是一块内存,在程序启动的时候操作系统会为其分配内存供其执行


在OllyDBG调试界面中,ESP寄存器表示栈顶指针, 每执行一次push后指针减4;执行一次pop指针加4; EBP寄存器表示栈底指针

1


堆栈平衡

如果返回父程序, 当我们在堆栈中进行操作时,要保证在执行ret这条指令前esp所指向的地址是不变的

如果通过堆栈传递参数从而导致的esp变化,在函数执行完毕后要恢复成原先esp的值

要实现堆栈平衡有两种方式, 分别是内平栈和外平栈


外平栈

1

如上图所示,首先通过push压栈传递了两个参数, 分别是立即数1和立即数2, 因为堆栈压入两个栈, 那么栈顶ESP所指向的值自然也是立即数2

随后执行call指令调用了地址为004F145的函数, 此时堆栈会将call指令地址的下一行地址(即004F12C)压入堆栈, 那么栈顶ESP指向的值变成了004F12C

由于向堆栈传递了参数从而导致了ESP的变化, 那么后面需要恢复成原先的ESP, 在函数内部执行retn指令后会将执行pop EIP(即将ESP指向的值传递给EIP, 然后将ESP+4), 随后在函数外部再通过add指令将ESP+8来恢复到原先的ESP, 以此实现堆栈的外部平衡

image-20230129154636729


内平栈

1

与外平栈不同的是, 内平栈是在函数内部来实现堆栈平衡, 如上图所示, 通过执行retn 0x8指令来恢复至原先ESP, 此条命令相当于pop EIPadd esp,8


通过ebp寻址

上述我们讲到的内平栈和外平栈用到的都是ESP寻址, 但是缺点是esp会随着堆栈的一些操作而发生变化, 因此可以使用ebp寻址的方法去弥补这种缺点

如下图所示, 是通过ebp寻址的一种方法, 下面我会详细描述ebp寻址的流程

1


在函数内部, 首先push ebp向堆栈压入个ebp, 然后让ebp=esp, 此时EBP和ESP都指向同一个值, 这个值是原先EBP的值

image-20230129164308330


执行`sub esp,0x10`指令来扩大堆栈的存储空间, 可以通过[ebp+?]来表示堆栈的参数, 例如立即数2即可用[EBP+8]来表示

image-20230129165207023


函数操作结束后执行`mov esp,ebp`指令让esp=ebp, 此时esp和ebp再次指向一个同一个值

最后, 执行pop ebpretn 0x8指令来恢复原先ESP, 以此实现堆栈平衡

image-20230129165852753




标志寄存器

什么是标志寄存器

标志寄存器是一个特殊的寄存器,用于存储当前状态(特别是在执行算术和逻辑运算时)。标志寄存器通常包括一些标志位,用于指示运算的结果,例如溢出(OVERFLOW)、等于零(ZERO)、小于零(NEGATIVE)等。程序执行完一个运算后,标志寄存器会更新相应的标志位,以指示运算结果。程序可以根据标志寄存器中的标志位执行不同的操作,从而达到条件判断的效果

如下表格是常见的标志寄存器:

标志寄存器描述
CF用于判断无符号整数运算是否溢出。如果算术操作导致最高有效位产生进位,则置1,否则为0。
OF用于判断有符号整数运算是否溢出。如果溢出,则置1,否则为0。
PF如果运算结果的最低有效字节包含偶数个1位,则置1,否则为0。
AF如果第3位产生了进位,则置1,否则为0。
ZF如果运算结果为0,则置1,否则为0。
SF记录有符号整数的最高有效位。0表示正数,1表示负数。
DF用于控制串指令(例如MOVS、CMPS、SCAS、LODS和STOS)。如果置为0,则串指令自动递增;如果置为1,则串指令自动递减。

使用实例

1.通过判断ZF是否为1来确定两个值是否相等

使用sub指令有一个弊端, 即会修改eax的值

mov eax,100
mov ecx,100
sub eax,ecx  

cmpsub指令相似,但是不会修改eax的值

mov eax,100
mov ecx,100
cmp eax,ecx  

2.通过判断ZF是否为1来确定值是否为0

如下所示给eax赋值0, 然后通过AND指令让eax与自己进行与运算, 执行结果为0, 则ZF位为1

但这种方式有一个弊端,与运算后的执行结果会返回给eax,也就是说会修改eax的值

mov eax,0
AND EAX,EAX

TEST与AND指令不同的是,执行结果不会返回给eax
mov eax,0         
TEST EAX,EAX   

OD中不同的断点

INT3断点

INT3断点也被称为CC断点, 它在程序的代码流中设置了INT 3指令,当执行到该指令时,CPU会中断执行并传递给调试器。调试器可以捕获这个中断,并对程序进行调试, 此类断点有一个弊端, 容易被软件内的程序检测到, 从而失效

在Ollydbg调试中可直接按F2进行设置

1


硬件断点

硬件断点是在硬件层次上实现的断点。它通过在处理器的debug registers上设置断点,从而捕获程序的某个特定点的执行

硬件断点通常比软件断点更快, 不会被程序代码覆盖, 且不容易被检测到, 由于需要使用处理器的特殊功能,硬件断点的数量通常有限,因此不能随意使用


内存断点

内存断点是在调试过程中用于监控内存数据的一种断点,也称作内存观察断点, 且不容易被检测到

内存断点可以被设置在内存访问断点或内存写入断点上,其工作原理是通过将所设地址的访问或写入权限设置为不可访问/不可写,当试图访问或写入该地址时,就会产生异常,OllyDbg软件可以捕获此异常,并通过比较异常地址是否与断点地址相同来判断是否到达了断点,如果是,则软件会停止执行

由于每次出现异常Ollydbg都要判断是否是断点, 这影响到了OD的工作效率, 因此在OD中内存断点只能设置一个


消息断点

消息断点是OllyDbg中的一种断点,它主要用于调试Windows程序的消息循环。消息断点通过在程序处理消息时产生中断来实现。例如,可以设置在窗口创建时或在处理WM_PAINT消息时产生中断,从而调试窗口的绘制过程

Windows程序的消息循环是指窗口程序运行的核心部分。它负责处理程序所有的输入消息,例如鼠标点击和键盘输入,以及窗口重绘和其他系统消息。消息循环在程序启动时开始,并不断运行,直到程序退出。消息循环通过不断调用GetMessage函数获取消息,并通过DispatchMessage函数将消息发送到程序的消息处理程序中

消息断点与INT3断点不同的是, INT3断点可以在程序启动之前设置, 而消息断点只能在窗口创建后设置


条件断点

条件断点是一种特殊的断点,它允许用户设置断点在特定条件下才被触发。例如,如果一个寄存器的值等于某个特定值,或者一个内存地址的值等于另一个特定值,断点就会被触发。在调试中,这种断点可以用来在特定条件下停止程序并对代码进行分析,以找出问题

在OllyDBG中可通过按快捷键shift+F2来设置条件断点

image-20230130194734639


OD内存窗口的指令

单字节查看内存的数据

db 0019FF74  

1


双字节查看内存的数据
dw 0019FF74  

1


四字节查看内存的数据
dd 0019FF74  

1




常用汇编指令

算数指令

ADD

将ecx与eax相加后返回给eax

ADD eax,ecx 

SUB

eax减去ecx后返回给eax

SUB eax,ecx 

AND

将eax与ecx进行与运算后返回给eax

AND eax,ecx 

OR

将eax与ecx进行或运算后返回给eax

OR eax,ecx 

XOR

将eax与ecx进行异或运算后返回给eax

XOR eax,ecx

NOT

将eax与ecx进行非运算后返回给eax

NOT eax,ecx 

数据传输指令

MOV

MOV 指令是将一个源数据移动到一个目的数据位置。它是一条单纯的数据传送指令,不执行任何其他操作


如下为MOV指令常用操作:

1.立即数到内存

mov byte ptr ds:[内存编号],1

2.立即数到寄存器
mov eax,1

3.寄存器到寄存器(需要相同位数)
mov eax,ecx  #将ecx的值存到eax里

4.寄存器存到内存,又称"向内存中写入数据"
mov dword ptr ds:[内存地址],eax 
mov byte ptr ds:[内存地址],al 
mov byte ptr ds:[ecx],0x12345678  

5.内存存到寄存器中,又称"读取内存数据"
mov eax,dword ptr ds:[内存地址] 
mov eax,dword ptr ds:[ecx] 

MOVS

MOVS 指令也是将一个源数据移动到一个目的数据位置,但是它是一条字符串操作指令,可以连续地将多个数据从一个地址移动到另一个地址。它会自动递增源和目的地址的指针,以支持字符串的处理

MOVS和MOV的区别: MOV 指令是一条单纯的数据传送指令,而 MOVS 指令是一条字符串操作指令,支持字符串的处理


MOVS指令只能将内存的数据移动到另一个内存中, 且编写格式只能如下所示:
movs byte ptr es:[edi],byte ptr ds:[esi] #可简写成MOVSB 
movs word ptr es:[edi],word ptr ds:[esi] #可简写成MOVSW 
movs dword ptr es:[edi],dword ptr ds:[esi] #可简写成MOVSD 

每次执行movs命令后,edi和esi的值都会发生变化,变化取决于DF位的值,若D为的值为0,那么执行movsb命令后就会加1,执行movd命令后就会加4;D位的值为1时则会减去

1


STOS

STOS 指令是一种 x86 指令,它通过使用内存操作将存储在 EAX 中的值复制到 ES:(E)DI 指向的内存单元中, 并且增加DI的值。 STOS 指令通常用于字符串操作和数据初始化


将AI/AX/EAX的值存储到[EDI]指定的存储单元中
stos byte ptr es:[edi] #简写成stosb
stos word ptr es:[edi] #简写成stosw
stos dword ptr es:[edi] #简写成stosd

栈操作指令

PUSH

PUSH指令用于向栈顶压入数据, 它会把操作数压入栈顶,并且让栈顶指针(SP)向下移动相应的数据大小(如:操作数为32位整数,则SP向下移动4个字节)


将立即数3压栈到栈顶指针处,随后栈顶指针减4
push 3 

将内存数据压栈到栈顶指针处
push ptr ds:[内存地址]

POP

POP指令的作用是从栈顶弹出一个数据,并将其存储在指定的寄存器或内存位置中。


将堆栈中栈顶指针所指向的值出栈给eax寄存器,随后栈顶指针加4
pop eax 

将堆栈中栈顶指针所指向的值出栈给内存, 随后栈顶指针加4
pop ptr ds:[内存地址] 

控制指令

REP

rep指令按计数寄存器(ECX)中指定的次数重复执行汇编指令,执行完后ecx的值归0

REP前缀可以和某些字符串指令(例如MOVS,STOS,CMPS等)配合使用,以实现多次执行相同的操作,减少代码的冗长度

mov ecx,10  #设置ecx为十六进制的10,即执行次数为16次
rep movsd

CALL

修改EIP寄存器的值,并且push下一行地址(即将下一行的值赋给esp)

call 立即数/内存/寄存器  

转移指令

JMP

JMP指令会修改EIP寄存器的值,EIP寄存器的值表示cpu下一次要执行的地址, 也可以理解成跳到指定地址

jmp 立即数/内存/寄存器  

JCC指令

JCC指令是"jump conditional code"的缩写,是一类条件转移指令,它在特定条件下跳转到指定的地址

JCC指令需要根据标志位的值判断是否执行跳转操作,因此它是汇编程序员编写程序的常用指令之一

jcc指令描述检查符号位对应的C语言
JZ(jump if zero)
JE(jump if equal)
结果为0或结果相等则跳转ZF=1if (i == 0);
if (i == j);
JNZ(jump if not zero)
JNE(jump if equal)
结果不等于0或结果不相等则跳转ZF=0if (i != 0);
if (i != j);
JS(jump if sign)结果为负则跳转SF=1if (i < 0);
JNS(jump if not sign)结果为正则跳转SF=0if (i > 0);
JP(jump if Parity)
JPE(jump if Parity Even)
若1出现次数为偶数则跳转PF=1
JNP(jump if not Parity)
JPO(jump if Parity Odd)
若1出现次数为奇数则跳转PF=0
JO(jump if overflow)溢出则跳转OF=1
JNO(jump if not overflow)无溢出则跳转OF=0
JC(jump if carry)
JB(jump if below)
JNAE(jump if not above equal)
若进位则跳转;
若低于则跳转;
若不高于等于则跳转
CF=1if (i < j);
JBE(jump if below equal)
JNA(jump if not above)
若低于等于则跳转;
若不高于则跳转;
ZF=1或CF=1if (i <= j);
JA(jump if above)
JNBE(jump if not below equal)
若高于则跳转;
若不低于等于则跳转
ZF=0而且CF=0if(i>j);
JNC(jump if not carry)
JNB(jump if not below)
JAE(jump if above equal)
若不进位则跳转;
若不低于则跳转;
若高于等于则跳转
CF=0if (i >= j);

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

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

相关文章

微信怎样开发小程序【公司企业小程序开发】

现在很多公司企业都有自己的小程序&#xff0c;没有小程序的公司企业也会寻找开发小程序的途径。那么今天就给大家简单介绍微信怎样开发小程序&#xff0c;希望对需要开发小程序的公司企业有帮助。 一、注册小程序账号 有一个小程序账号是必须的&#xff0c;小程序账号可以在…

那些外贸老鸟们都在认真使用的8个实用小工具

在我们日常的外贸工作中&#xff0c;有很多地方都可以用到一些实用外贸小工具去提高工作效率&#xff0c;突破局部限制。是否能够灵活的应用这些实用外贸小工具&#xff0c;是一位成熟优秀外贸业务员的衡量标准之一。第一个&#xff1a;知识信息整理和CRMhttps://www.notion.so…

Linux(六)基础I/O

引言 C语言进阶 文件管理 上一篇文章详细回顾了C语言方面关于文件操作的一些库函数&#xff0c;比如输入输出重定向fscanf、fprintf&#xff0c;对于文件内容以字符形式读取的fgetc、fputc&#xff0c;对于文件内容以字符串形式读取的fgets、fputs&#xff0c;对于二进制文件的…

ieee会议论文从手稿到发表

0. 前言 在创新点得到认可之后就可以准备发论文了&#xff0c;这个一定要早点&#xff0c;可以给自己设置一个明确的deadline&#xff0c;毕竟ddl是第一生产力。 1. 确定发什么期刊、会议 一定要符合学校的毕业要求&#xff0c;有一些水的学校并不认。时间看能不能赶上学校毕…

物联网智慧消防对比传统消防具有哪些优势?

随着科技的进步和城市化进程的加快&#xff0c;传统消防已经满足不了社会发展的需求&#xff0c;智慧消防应运而生&#xff0c;目前智慧消防已经成为消防安全管理的核心&#xff0c;物联网时代的到来&#xff0c;让智慧消防迎来了更大的发展机遇&#xff0c;变得更加智慧化、系…

手机网站建设怎么做?【手机网站制作】

对于很多公司企业来说&#xff0c;做网站建设都是优先考虑PC端的网站建设&#xff0c;但是某些公司企业可能对于PC端网站的需求不高&#xff0c;倒是更有需要做移动端网站&#xff0c;也就是我们常说的手机网站。那么关于手机网站建设又是怎么做的呢&#xff1f;本文给大家做一…

软件测试员年底总结怎么写?所有问题都帮你梳理好了!

临近年底&#xff0c;很多公司都有年终总结环节&#xff0c;核心目的发现今年的不足&#xff0c;进而总结经验&#xff0c;更好地用以指导明年的工作。当然&#xff0c;即使公司没有要求&#xff0c;对于测试岗位来说&#xff0c;一年一度的总结不可或缺。假如你是测试负责人&a…

<使用Python自定义生成简易二维码>——《Python项目实战》

目录 1.问题导引 2.实现步骤 &#xff08;1&#xff09;查找并安装第三方库qrcode &#xff08;2&#xff09;编写代码并嵌入内置信息 &#xff08;3&#xff09;使用扫码工具读取信息 后记&#xff1a;●由于作者水平有限&#xff0c;文章难免存在谬误之处&#xff0c;敬…

【Tkinter】终于把StringVar讲明白了

文章目录简介Label使用StringVarEntry输入简介 初学者在使用tkinter时常犯的一个错误就是 def changeText(evt):evt.text "new Text"毕竟在创建控件时&#xff0c;text是出现频率很高的参数&#xff0c;换言之&#xff0c;我们会默认控件中有text这个属性&#xf…

CDGA/CDGP数据治理认证班将于2/4正式开课,报名从速!

新的一年&#xff0c;从考证开始&#xff0c;为职场竞争增添更多优势&#xff01; 做数据行业的话&#xff0c;当然推荐考个DAMA-CDGA/CDGP数据管理证书啦&#xff01; DAMA是全球唯一数据管理方面权威性认证&#xff0c;帮助数据从业者提升数据管理能力。 DAMA认证为数据管理专…

【c语言进阶】文件操作(下)

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; c语言学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我…

[NOIP2008 提高组] 笨小猴

题目描述 笨小猴的词汇量很小&#xff0c;所以每次做英语选择题的时候都很头疼。但是他找到了一种方法&#xff0c;经试验证明&#xff0c;用这种方法去选择选项的时候选对的几率非常大&#xff01; 这种方法的具体描述如下&#xff1a;假设 maxn 是单词中出现次数最多的字母的…

Python 中当前时间表示方法详解

在 Python 中获取当前时间是许多与时间有关的操作的一个很好的起点。一个非常重要的用例是创建时间戳。在本教程中&#xff0c;你将学习如何用 datetime 模块获取、显示和格式化当前时间。我们将学习如何读取当前时间的属性&#xff0c;如年份、分钟或秒。为了使时间更容易阅读…

函数的求导法则——“高等数学”

今天&#xff0c;小雅兰的内容是函数的求导法则&#xff0c;上篇博客我们知道了导数的定义、导数的几何意义及可导与连续关系&#xff0c;这篇博客我们来仔细学习一下求导法则&#xff0c;下面&#xff0c;就让我们进入导数的世界吧 一、函数的和、差、积、商的求导法则 二、反…

Beryl Li 代表 YGG 出席 2023 年世界经济论坛会议

Yield Guild Games&#xff08;YGG&#xff09;联合创始人 Beryl Li 代表 YGG 参加了 2023 年 1 月 16 日至 20 日在瑞士达沃斯举行的 2023 年世界经济论坛年会 &#xff08;WEF23&#xff09;&#xff0c;在全球舞台上分享区块链、通证化、数字资产监管、治理和价值创造的潜力…

C++语法复习笔记-4. C++基本容器

文章目录1.数组声明与定义数组的开闭区间差一错误左闭右开非对称区间原则数组的增删改查一维数组二维数组面向对象的动态数组-vector自动扩容增删改查2. 字符串字符串变量与常量unicode编码字符串指针表示方法指针的表示方法char[]和char* 的区别数组每个值可改指针指向的字符串…

Yann LeCun 新作!大幅超越 MAE,图像语义表示卷出新高度

文&#xff5c;CV酱计算机视觉中&#xff0c;有两种常见的从图像中进行自我监督学习的方法&#xff1a;基于不变性的方法和生成方法。基于不变性的预训练方法优化编码器&#xff0c;使其产生相似的嵌入&#xff0c;用于同一图像的两个或多个视图&#xff0c;其中图像视图通常使…

基于Java+Spring的图书管理系统详细设计和实现

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

【项目精选】基于Web的机票预订系统

文章目录 1 摘 要2 系统相关技术概述2.1 Java web2.2 三大框架SSM2.3 前端框架AngularJS2.4 数据库MySQL2.5 数据库Redis2.6 开发工具Eclipse 3 需求分析3.1 系统实现目标3.2 系统功能分析3.3 系统用列图 4 系统总体设计4.1 软件架构设计4.2 总体功能模块设计4.3 数据库设计4…

python小游戏——怀念经典坦克大战代码

♥️作者&#xff1a;小刘在这里 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放&#xff0c;愿所有的美好&#…