重定位的原理&实现

PE文件重定位原理与符号地址修正实战 重定位(Relocation)是可执行文件实现地址无关性与动态加载的核心机制,其本质是将编译生成的节内偏移(Section Offset)映射为运行时有效的相对虚拟地址(RVA)。该过程依赖PE文件结构中的节对齐(Section Alignment)、导入表(IAT/INT)组织及重定位表(.reloc)协同工作,通过DIR32绝对修正与REL32相对跳转两类语义完成指令与数据地址的动态适配。技术价值在于支撑ASLR安全特性、多实例隔离及模块化DLL绑定,广泛应用于Windows驱动开发、恶意代码分析、PE 阅读详情
重定位
  病毒自身的重定位是病毒代码在得以顺利运行前应解决的最基本问题。病毒代码在运行时同样也要引用一些数据,比如API
函数的名字、杀毒软件的黑名单、系统相关的特殊数据等,由于病毒代码在宿主进程中运行时的内存地址是在编译汇编代码时无法预知的,而病毒在感染不同的宿主时其位于宿主中的准确位置同样也无法提前预知,因此病毒就要在运行时动态确定其引用数据的地址,否则,引用数据时几乎肯定会发生错误。对于普通的PE文件比如动态链接库而言,在被加载到不同地址处时由加载器根据PE中一个被称为重定位表的特殊结构动态修正引用数据指令的地址,而重定位表是由编译器在编译阶段生成的,因此动态链接库本身无需为此做任何额外处理。病毒代码则不同,必须自己动态确定需引用数据的地址。比如一段病毒代码被加载在0x400000处,地址0x401000处的一条语句及其引用的数据定义如下所示,相关地址是编译器在编译时计算得到的,这里假设编译时预设的基地址也是0x400000:
401000:
mov eax,dword ptr [402035]
......
402035:
db "hello
world!",0
  如果病毒代码在宿主中也加载到基地址0x400000,显然是能够正常执行的,但如果这段代码被加载在基地址0x500000
运行时则出错,对病毒而言,这是大多数时候都会遇到的情况,因为指令中引用的仍然是0x402035这个地址。如果病毒代码不是在宿主进程中而是作为一个具有重定位表的独立PE文件运行,正常情况下由系统加载器根据重定位表表项将
mov eax,dword ptr [402035]中的0x402035修改为正确值0x502305,这样这句代码就变成了mov eax, dword ptr
[5402035],程序也就能准确无误地运行了。不过很可惜,对在其它进程内运行病毒代码而言,必须采取额外的手段、付出额外的代价感染宿主PE文件时就及时加以解决,否则将导致宿主进程无法正常运行。
  至少有两种方法可以解决重定位的问题:
  A)第一种方法就是利用上述PE
文件重定位表项的特殊作用构造相应的重定位表项。在感染目标PE文件时,将引用自身数据的需要被重定位的地址全部写入目标PE文件的重定位表中,如果目标PE
无任何重定位表项(如用MS linker
的/fixed)则创建重定位表节并插入新的重定位项;若已经存在重定位表项,则在修改已存在的重定位表节,在其中插入包含了这些地址的新表项。重定位的工作就完全由系统加载器在加载PE文件的时候自动进行了。重定位表项由PE
文件头的DataDirectory数据中的第6 个成员
IMAGE_DIRECTORY_ENTRY_BASERELOC
指向。该方法需要的代码稍多,实现起来也相对比较复杂,另外如果目标文件无重定位表项(为了减小代码体积,这种情况也不少见),处理起来就比较麻烦,只有用高级语言编写病毒才常用该种方法,在一般的PE
病毒中很少使用。
  B)利用Intel X86体系结构的特殊指令,call
或fnstenv等指令动态获取当前指令的运行时地址,计算该地址与编译时预定义地址的差值(被称为delta
offset),再将该差值加到原编译时预定的地址上,得到的就是运行时数据的正确地址。对于intel x86指令集而言,在书写代码时,通过将delta
offset
放在某个寄存器中,然后通过变址寻址引用数据就可以解决引用数据重定位的难题。还以上例说明,假如上述指令块被操作系统映射在0x500000处那么代码及其在内存中的地址将变为:
501000:
mov eax,dword ptr [402035]
......
502035:
db "hello
world!",0
  显然,mov
指令引用的操作数地址是不正确的,如果我们知道了mov指令运行时地址是0x501000,那么计算该地址和编译时该指令预设地址的差值:0x501000-0x401000
= 0x100000。很显然指令引用的实际数据地址应该为0x402035+0x100000 =
0x502035。从上例可以看出,只要能够在运行时确定某条指令动态运行时的地址,而其编译时地址已知,我们就能够通过将delta offset
加到相应的地址上正确重定位任何代码或数据的运行时地址。原理如图3 所示:

图3:delta iffset

 通常只要在病毒代码的开始计算出delta offset,通过变址寻址的方式书写引用数据的汇编代码,即可保证病毒代码在运行时被正确重定位。假设ebp
包含了delta offset,使用如下变址寻址指令则可保证在运行时引用的数据地址是正确的:
;ebp 包含了delta offset 值
401000:
mov eax,dword ptr [ebp+0x402035]
......
402035:
db "hello world!",0
  在书写源程序时可以采用符号来代替硬编码的地址值,上述的例子中给出的不过是编译器对符号进行地址替换后的结果。现在的问题就转换成如何获取delta
offset的值了,显然:
call delta
delta:
pop ebp
sub ebp,offset delta
  在运行时就动态计算出了delta offset
值,因为call要将其后的第一条指令的地址压入堆栈,因此pop ebp 执行完毕后ebp 中就是delta的运行时地址,减去delta的编译时地址“offset
delta”就得到了delta offset 的值。除了用明显的call
指令外,还可以使用不那么明显的fstenv、fsave、fxsave、fnstenv等浮点环境保存指令进行,这些指令也都可以获取某条指令的运行时地址。以fnstenv
为例,该指令将最后执行的一条FPU 指令相关的协处理器的信息保存在指定的内存中,结构如下图4 所示:

图4:浮点环境块的结构
  该结构偏移12字节处就是最后执行的浮点指令的运行时地址,因此我们也可以用如下一段指令获取delta
offset:
fpu_addr:
fnop
call GetPhAddr
sub ebp,fpu_addr
GetPhAddr:
sub esp,16
fnstenv [esp-12]
pop ebp
add esp,12
ret
  delta offset 也不一定非要放在ebp 中,只不过是ebp
作为栈帧指针一般过程都不将该寄存器用于其它用途,因此大部分病毒作者都习惯于将delta offset 保存在ebp
中,其实用其他寄存器也完全可以。
  在优化过的病毒代码中并不经常直接使用上述直接计算delta offset
的代码,比如在Elkern开头写成了类似如下的代码:
call _start_ip
_start_ip:
pop ebp
;...
;使用
call
[ebp+addrOpenProcess-_start_ip]
;...
addrOpenProcess dd 0
;而不是
call _start_ip
_start_ip:
pop ebp
sub ebp,_start_ip
call
[ebp+addrOpenProcess]
  为什么不采用第二种书写代码的方式?其原因在于尽管第一种格式在书写源码时显得比较罗嗦,
但是addrOpenProcess-_start_ip 是一个较小相对偏移值,一般不超过两个字节,因此生成的指令较短,而addrOpenProcess在32
Win32编译环境下一般是4
个字节的地址值,生成的指令也就较长。有时对病毒对大小要求很苛刻,更多时候也是为了显示其超俗的编程技巧,病毒作者大量采用这种优化,对这种优化原理感兴趣的读者请参阅Intel手册卷2中的指令格式说明。
  API
函数地址的获取

  在能够正确重定位之后,病毒就可以运行自己代码了。但是这还远远不够,要搜索文件、读写文件、进行进程枚举等操作总不能在有Win32
API
的情况下自己用汇编完全重新实现一套吧,那样的编码量过大而且兼容性很差。
  Win9X/NT/2000/XP/2003系统都实现了同一套在各个不同的版本上都高度兼容的Win32
API,因此调用系统提供的Win32 API实现各种功能对病毒而言就是自然而然的事情了。所以接下来要解决的问题就是如何动态获取Win32
API的地址。最早的PE病毒采用的是预编码的方法,比如Windows 2000 中CreateFileA
的地址是0x7EE63260,那么就在病毒代码中使用call [7EE63260h]调用该API,但问题是不同的Windows 版本之间该API
的地址并不完全相同,使用该方法的病毒可能只能在Windows
2000的某个版本上运行。
  因此病毒作者自然而然地回到PE结构上来探求解决方法,我们知道系统加载PE 文件的时候,可以将其引入的特定DLL
中函数的运行时地址填入PE的引入函数表中,那么系统是如何为PE引入表填入正确的函数地址的呢?答案是系统解析引入DLL
的导出函数表,然后根据名字或序号搜索到相应引出函数的的RVA(相对虚拟地址),然后再和模块在内存中的实际加载地址相加,就可以得到API
函数的运行时真正地址。在研究操作系统是如何实现动态PE文件链接的过程中,病毒作者找到了以下两种解决方案:
  A)在感染PE
文件的时候,可以搜索宿主的函数引入表的相关地址,如果发现要使用的函数已经被引入,则将对该API
的调用指向该引入表函数地址,若未引入,则修改引入表增加该函数的引入表项,并将对该API
的调用指向新增加的引入函数地址。这样在宿主程序启动的时候,系统加载器已经把正确的API
函数地址填好了,病毒代码即可正确地直接调用该函数。
  B)系统可以解析DLL 的导出表,自然病毒也可以通过这种手段从DLL
中获取所需要的API地址。要在运行时解析搜索DLL 的导出表,必须首先获取DLL 在内存中的真实加载地址,只有这样才能解析从PE
的头部信息中找到导出表的位置。应该首先解析哪个DLL 呢?我们知道Kernel32.DLL几乎在所有的Win32
进程中都要被加载,其中包含了大部分常用的API,特别是其中的LoadLibrary 和GetProcAddress 两个API可以获取任意DLL
中导出的任意函数,在迄今为止的所有Windows 平台上都是如此。只要获取了Kernel32.DLL在进程中加载的基址,然后解析Kernel32.DLL
的导出表获取常用的API 地址,如需要可进一步使用Kernel32.DLL 中的LoadLibrary 和GetProcAddress 两个API
更简单地获取任意其他DLL 中导出函数的地址并进行调用。
 
ROS2机械臂MPC轨迹规划实战:从MoveIt 2插件配置到参数调优全流程 本文详细介绍了ROS2机械臂MPC轨迹规划的全流程,从MoveIt 2插件配置到参数调优。通过模型预测控制(MPC)技术,实现高精度运动控制,特别适合复杂装配任务。文章包含环境搭建、插件部署、参数设计及实战调优方法,帮助开发者快速掌握ROS2机械臂的MPC轨迹规划技术。 阅读详情

相关推荐

物理专业书库模板(1):物理学书单(适合物理专业本科生、研究生、科研工作者、物理爱好者...)

物理专业书库模板(1):物理学书单(适合物理专业本科生、研究生、科研工作者、物理爱好者...) 求赞求喜欢求分享,建议关注,防止文章丢失。

hepyang的博客 2179

病毒重定位的基本思路和方法

熟悉Masm32的使用,熟悉病毒重定位的基本思路和方法,在 HelloWorld.exe 中添加一段代码, 该段代码满足以下几个条件:1.该段代码弹出一个对话框 (标题: 武大信安病毒重定位,内容: 姓 名+学号后四位) 2.该段代码同时包括代码和字符串数据。3. 该段代码可以插入到.text节的任意指令之间,而不需要修改该段代 码中的任何字节。

weixin_49816179的博客 676

Protege本体建模实战:从零构建电影知识图谱并导入Neo4j

本体建模是知识图谱构建的基石,通过类、属性、实例和约束系统描述领域知识,为数据提供可复用的语义骨架。OWL作为本体描述语言的标准,配合Protege工具可实现可视化建模,并借助推理机验证逻辑一致性,提升模型可靠性。在工程落地中,本体模型常需导入图数据库如Neo4j,以支持高效的关联查询与业务应用。本文以影视领域为背景,完整演示Protege中定义类层级、创建对象/数据属性、填充实例,以及导出OWL文件并通过Python脚本转换为Neo4j节点与关系的全过程,同时梳理命名空间、匿名节点、编码等易踩坑点,帮助读

weixin_29047545的博客 281

代码重定位的理解

先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题,二维码如下: 一 代码的组成 程序至少包含:代码段+数据段 代码段:.text 数据段:一般的全局变量,初值不为0的经过初始化的全局变量 如: char g_char ='A';...

myselfzhangji的博客 2430

从申请到落地:解锁OAI骨科数据集的实战指南与避坑经验

本文是一份详尽的OAI骨科数据集实战指南,重点解析了从NDA账号申请、权限审批到数据定位与高效下载的全流程。文章分享了使用login.gov注册、Chrome浏览器避坑、nda-tools命令行批量下载等核心技巧,并总结了常见问题排查方法,旨在帮助医学影像与骨关节炎研究者高效解锁这一宝贵数据资源。

weixin_29313547的博客 327

数字IC后端PR阶段Innovus,ICC,ICC2修复short万能脚本分享

数字IC后端PR阶段Innovus,ICC,ICC2修复short万能脚本分享

weixin_37584728的博客 3458

病毒代码重定位技术

2. 避免破坏宿主文件功能:如果病毒直接使用固定的地址,可能会覆盖宿主文件的关键代码或数据,导致宿主文件无法正常运行,从而容易被用户察觉。相对地址是基于当前指令或数据的位置进行计算的,这样当病毒在宿主文件中的位置发生变化时,相对地址的计算结果仍然能够正确指向所需的资源。4. 动态链接库劫持:某些病毒通过劫持系统的动态链接库(DLL),修改其中的函数调用地址,从而实现重定位和自身代码的执行。这些方法使得病毒能够在不同的感染环境中保持功能,增加了其隐蔽性和生存能力,同时也给病毒的检测和清除带来了更大的挑战。

m0_57836225的博客 622

扣子工作流实战演练

例如,你可以在代码节点的输入中引用大模型节点的输出,这样代码节点就可以使用大模型节点的输出。扣子的工作流节点并不是固定的,而是由一系列可以任意添加的节点组成的,每个节点都有其特定的功能和用途。扣子工作流中常见的节点类型如图4-2所示。在图像生成节点中设置模型为人像,输入参数名为input,变量值引用开始节点的renwu,正向提示词为{{input}},如图4-19所示。然后添加两个节点,选择“图像生成”和“画板”,将“开始”节点、“图像生成”节点、“画板”节点、“结束”节点依次串联,如图4-17所示。

夏天又到了的专栏 1587

病毒的重定位技术

<br />病毒的重定位技术<br /><br />  病毒的重定位技术应该也算是最基本和最有用的技术之一了,其实重定位技术只要是对外壳开发比较熟悉的朋友都应该很熟悉了。重定位顾名思义重新定位地址。上篇文章中我们已经讲解到了由于我们被感染对象的不同,我们部分定义的变量被插入的地址也不同。所以这就需要我们重新定位,也就是今天的主题——重定位技术。<br /><br />  首先我们来看<br /><br />假如我们定义一段变量  <br />    <br />  szText  db 'Virus Del

why 2501

【蓝凌表单】JSP组件常用语法整理

【代码】【蓝凌表单】JSP组件常用语法整理。

烧硬盘 709

航空电子系统标准全景图:ARP4754A、ARP4761、DO-178C、DO-254 的协同作用

通过 ARP4754A、ARP4761、DO-178C 和 DO-254 的相互配合,确保航空电子系统的安全性和功能性在开发过程中得到充分保障。每一个文件在不同层面发挥作用,共同构成了一个完整的、系统的开发和验证框架,确保航空电子设备达到适航要求。这些标准不仅帮助开发人员有条理地开发复杂的航空系统,还确保所有系统组件(软件和硬件)在整个生命周期内都能满足最严格的安全和可靠性要求。

qq_41854911的博客 1812

初探计算机病毒(4)--重定位

   上一篇解决了病毒程序调用API的问题,那么这是不是就万事大吉了呢?不,还有个问题需要解决,就是重定位。     病毒作为一段代码附加到正常的程序中的,其位置可能是随机的。比如病毒编译完成后,其运行首地址为0x04000000,但是附加到正常的程序中加载到内存中其首地址可能就变成0x08000000,那么很多直接访问内存的指令就会发生错误。比如加载到0x04000000时,指令mov eax,

长弓落日的专栏 1726

AI测试入门:基于 RAG 的 LLM 应用程序的测试方法「详细介绍」

一个典型的 RAG 架构包括一个检索器(对知识库进行矢量搜索检索上下文的组件)和一个生成器(从检索器中获取检索上下文以构建提示并生成自定义 LLM 响应作为最终输出的组件)。在大多数情况下, 知识库”由存储在向量数据库(如 ChromaDB)中的向量嵌入组成。

blues_C的博客 1870

韦东山嵌入式第一期学习笔记DAY_12——13_6重定位_清除BSS段的C函数实现

作者:GWD 时间:2019.7.13 一、课程内容 摘要:本节课用汇编传参和符号表传参两种方式各写了一个代码实现用C实现代码重定位与bss段清0;讲解了符号表的原理及引用方式。 1、问:符号表是什么? 2、符号表怎么使用 二、学习要点: 1、问:C函数怎么使用lds文件中的变量abc? 答: 1)、在C函数中声明改变量为extern类型, 比如:extern int abc; 2、使用时, 要...

gongweidi的博客 383

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

ELF格式的重定位原理分析

前面有篇文章分析了ELF格式,也只是让我们对目标文件有了一个大概的了解,并没有说明一个十分重要的问题:重定位,今天重新看了下重定位的资料,终于弄懂了重定位的过程,下面来做一个分析。 我们将使用下面两个源代码中的文件a.c和b.c展开分析: //a.c extern int shared; int main() { int a=100; swap(&a,&shared); } //b.c i

GetnextWindow的专栏 6471

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

PE结构&基址重定位表

重定位表定位 重定位表为数据目录中注册的数据类型之一,其描述信息处于数据目录的第6个目录项中, 重定位表所在地址RVA=0x1F000 重定位数据大小=39C RVA转FOA后可以得到文件偏移地址为0x9800 重定位表项IMAGE_BASE_RELOCATION 与导入表类似,重定位表指针指向的位置就是一个数组,而不像导出表一样只有一个结构,这个数组的每一项都是如下结构: IMAGE_BASE_RELOCATION STRUCT VirtualAddress dd ? ;重定位内存页的起始RV

寻梦&之璐 8608

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

PE文件重定位

PE格式是Windows环境下可执行文件(如:exe,dll)的格式,而Windows下面的程序,例如动态链接库无法加载到它本身期望加载的地址的时候,便会发生重定位。那么,重定位是如何实现的呢?   一.PE文件格式的结构 PE文件主要是由PE头和PE体组成的,其中,PE头主要由DOS头, DOS存根,NT头:签名,NT头:文件头,NT头:可选头,节头组成, 而剩下的各节区组成PE体。其

lookerson的专栏 4562

LLVM Cpu0 新后端5 静态重定位 动态重定位

LLVM手动开发一个新后端的系列课程的记录分享

lml435035844的博客 1517

U-Boot的重定位实现机制

获取当前芯片平台的相关信息 为了深入了解ARM 64位芯片架构,笔者为u-boot添加了archinfo命令,以获取CPU当前的工作状态等信息。不过在增加完整的64位ARM架构信息查看功能之前,笔者首先增加了简单的获取当前PC指针及栈指针的函数: diff --git a/arch/arm/lib/arch-info.c b/arch/arm/lib/arch-info.c new file mode 100644 index 00000000..b9bc9fd0 --- /dev/null +++ b/a

yeholmes的专栏 2808

程序运行代码动态重定位原理

程序编译时候我们会指定一个链接地址(也就是程序定位的运行地址),编译后,cpu的pc指针指向这个地址开始执行,就时没为题的。 但是我们现在想把该段程序运行时(没法对该段程序进行再次指定地址的编译了)挪动到其它内存区域(程序动态加载功能),要想该段代码仍然能正常运行,就需要对该段代码进行重定位,而不是简单的拷贝该段代码到指定内存区域即可。 ...

biao2488890051的博客 2441

[u-boot] uboot启动过程中的代码重定位

直到我在找到了这一句,段的链接地址是定义的地址 加载地址(Load Address)和运行地址(Running Address) 早期的启动阶段、uboot还没打开MMU,即使后面MMU Enable的情况下,也是做的一一映射(虚拟地址 = 物理地址),在这个前提下,加载地址就是运行地址 uboot是被前级Bootloader加载到RAM上的,通常情况下,前级BL也是把镜像搬移到运行地址上去 uboot中的第一次重定位 和,这段地址空间是如何生成的?保存的是什么数据?如何进行的重定位?重定位的目的

qinmoge8586的博客 2354

操作系统实验3 页式地址重定位模拟

【实验名称】页式地址重定位模拟 【实验目的】 1.掌握和理解页式地址重定位模拟的基本思路和实现过程; 2. 理解页式地址重定位技术在多道程序设计中的作用和意义。 【实验原理】 页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小...

一半西瓜的博客 1万+

【译】《可执行文件背后的原理》—— 第6章 重定位:修补代码

- 《Under The Hood Of Executables》是一本深入探讨C程序从源代码到可执行二进制文件转换的指南。本书详细介绍了ELF格式、链接和加载过程,并通过实用示例、交互式图表和分步教程帮助读者理解这些概念。内容包括编译和链接的各个方面,并提供现实世界的应用和性能优化见解。读者可以通过克隆GitHub仓库或访问官方网站获取详细内容。本书采用MIT许可证,欢迎贡献者参与,并感谢所有支持此项目的贡献者和开源社区。

一介草民,只为生活。 1164

ARM Uboot经历——Uboot代码重定位

Uboot重定位是uboot启动后的一个重要功能,重定位的目的是为了让uboot运行在速度性能更好的RAM上,一般是从外部RAM搬移到内部RAM。重定位这块说简单也简单,说复杂也复杂,主要涉及到编译和链接等相关原理才能很好的理解整个过程。另外,理解了ARM的重定位,对于Kernel启动过程的重定位也就理解了。 编译器惹的祸 uboot重定位不是简单的copy,归根结底在于gcc的编译和链接过程

luciferful的专栏 9173

Linux动态库原理(二)重定位

Linux动态库原理(二)重定位 前面一章《Linux动态库工作原理详解》比较简单浅显的对 Linux 的工作原理进行了阐述,今天打算从 Linux 动态库在加载过程中符号的重定位(Relocation)的角度,更加深入的讲解 Linux 动态库的工作原理。 在1980s SunOS 将动态库引入到 UNIX,后来又将 ELF(Executable and Linkable) 格式引入到了

hudaliquan的专栏 3754

PE结构-重定位表

首先,先来说一下为什么需要重定位表,先来观察一段程序 00401000 &gt;/$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL 00401002 |. 68 1D204000 push 0040201D ...

小喇叭儿滴滴的吹 538
上一篇: EXE文件的重定位表起作用
下一篇: 通过病毒原理较好的阐述了如何在pe中添加一个section
xuplus
博客等级 码龄19年 140粉丝 107原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值