汇编里面的esp解释

最近在学加密解密,用od进行反汇编的时候,又重新对汇编了解研究了一下。。特别是对esp做了一个深入的研究。。下面是网上搜索到的。先记在这边看看。。
               

      在寄存器里面有很多寄存器虽然他们的功能和使用没有任何的区别,但是在长期的编程和使用中,在程序员习惯中已经默认的给每个寄存器赋上了特殊的含义,比 如:EAX一般用来做返回值,ECX用于记数等等。在win32的环境下EBP寄存器用与存放在进入call以后的ESP的值,便于退出的时候回复ESP 的值,达到堆栈平衡的目的。
应用以前说过的一段话:
原程序的OEP,通常是一开始以 Push EBP 和MOV Ebp,Esp这两句开始的,不用我多说大家也知道这两句的意思是以EBP代替ESP,作为访问堆栈的指针。
为什么要这样呢?为什么几乎每个程序都是的开头能?因为如果我们写过C等函数的时候就应该清楚,程序的开始是以一个主函数main()为开始的,而函数在访问的过程中最重要的事情就是要确保堆栈的平衡,而在win32的环境下保持平衡的办法是这样的:
1.让EBP保存ESP的值;
2.在结束的时候调用
mov esp,ebp
pop ebp
retn
或者是
leave
retn
两个形式是一个意思。
这样做的好处是不用考虑ESP等于多少,PUSH了多少次,要POP多少次了,因为我们知道EBP里面放的是开始时候的ESP值。
2.推广的ESP定律
在寻找OEP的时候,往往下断HW ESP-4不成功,除了壳代码将硬件断点删除了以外,很可能的情况就是因为壳代码在运行到OEP的时候他的ESP已经不再是在EP时候的ESP(12FFC4)了,这样我们下断当然是不成功的。
那么如何找到在壳到达OEP的时候的堆栈的值将是关键。
在这里我们应用的关键是
Push EBP
MOV Ebp,Esp----》关键是这句
我 来解释一下,当程序到达OEP的时候Push EBP这句对于ESP的值来说就是ESP-4,然后是ESP-4赋给了EBP,而做为保存ESP值作用的EBP寄存器在这个“最上层的程序”中的值将始终 不会改变。虽然他可能在进入子call里面以后会暂时的改变(用于子程序的堆栈平衡)但是在退出了以后依*pop ebp这一句将还原原来的EBP的值。
以这句做为突破口,就是说只要我们能断在“最上层的程序”中,就能通过观察EBP的值得到壳在JMP到OEP的时候的ESP的值了。
3.实战
来看看pespin1.1的壳,在pespin1.0的壳中,我们使用HW 12FFC0能很容易的找到stolen code的地方,但是到pespin1.1的时候,我们就不行了。用HW 12FFC0根本断不下来。
现在我们就使用这个推广的ESP定律,载入程序后来到最后的一个异常
0040ED85 2BDB sub ebx,ebx //停在这里
0040ED87 64:8F03 pop dword ptr fs:[ebx]
0040ED8A 58 pop eax
0040ED8B 5D pop ebp
0040ED8C 2BFF sub edi,edi
0040ED8E EB 01 jmp short pespin1_.0040ED91
0040ED90 C466 81 les esp,fword ptr ds:[esi-7F]
我用使用内存断点办法来到FOEP处
004010D3 0000 add byte ptr ds:[eax],al
004010D5 0000 add byte ptr ds:[eax],al
004010D7 0000 add byte ptr ds:[eax],al
004010D9 0000 add byte ptr ds:[eax],al
004010DB 0000 add byte ptr ds:[eax],al
004010DD 0000 add byte ptr ds:[eax],al
004010DF 75 1B jnz short pespin1_.004010FC //这里是FOEP
004010E1 56 push esi
004010E2 FF15 99F44000 call dword ptr ds:[40F499]
004010E8 8BF0 mov esi,eax
004010EA 8A00 mov al,byte ptr ds:[eax]
好了,这里就是“最上层的程序”的地方了,看看寄存器
EAX 00141E22
ECX 0040C708 pespin1_.0040C708
EDX 0040C708 pespin1_.0040C708
EBX 0040C708 pespin1_.0040C708
ESP 0012F978
EBP 0012F9C0 //注意这里
ESI 00141EE0
EDI 0040E5CD pespin1_.0040E5CD
EIP 004010DF pespin1_.004010DF
看到了吧,EBP=0012F9C0,我们来想象一下这个值是怎么得到的。
首先肯定是通过MOV ESP,EBP这一句,也就是说ESP这时是0012F9C0的,然而上面还有一句PUSH EBP也就是说ESP在到达OEP的时候应该是0012F9C4的。好了得到这个结论我们就能很快的找到stolen code的所在了。
重来停在最后的异常
0040ED85 2BDB sub ebx,ebx //停在这里
0040ED87 64:8F03 pop dword ptr fs:[ebx]
0040ED8A 58 pop eax
0040ED8B 5D pop ebp
0040ED8C 2BFF sub edi,edi
0040ED8E EB 01 jmp short pespin1_.0040ED91
0040ED90 C466 81 les esp,fword ptr ds:[esi-7F]
然后下断HW 0012F9C0 ,F9运行,来到这里
0040D8FB 61 popad
0040D8FC 55 push ebp
0040D8FD EB 01 jmp short pespin1_.0040D900 //停在这里
0040D8FF 318B ECEB01AC xor dword ptr ds:[ebx+AC01EBEC],ecx
0040D905 83EC 44 sub esp,44
0040D908 EB 01 jmp short pespin1_.0040D90B
0040D90A 72 56 jb short pespin1_.0040D962
0040D90C EB 01 jmp short pespin1_.0040D90F
0040D90E 95 xchg eax,ebp
0040D90F FF15 6CF34000 call dword ptr ds:[40F36C]
0040D915 EB 01 jmp short pespin1_.0040D918
于是就很快的找到了stolen code的所在了。
4.总结
上面的这个办法大概可以总结以下的步骤:
(1).直接或间接的断在“最上层的程序”的地方。
(2).得到“最上层的程序”的EBP的值。
(3).利用程序初始化的两个固定语句找到壳JMP到OEP的堆栈值。这个办法有很大的局限性,因为只有VC和delphi程序使用这个初始化的开头。
但是找到“最上层的程序”的办法除了内存断点还有很多办法,例如对于VC来说使用 bp ExitProcess也是一个很好的断点,可以直接得到EBP的数值。
5.后话
原来这个办法有很强的前提条件,不是一个很具普遍性的办法,我原来也不想单独的提出来,但是对于jney2兄弟的anti-ESP定律来说这个办法却是一个解决之道。
当然还有更多的办法,在这里我只想说很多事情有矛就有盾,没有什么办法是一定没有漏洞的,只是希望这篇文章给大家阔宽思路,起到抛砖引玉的作用。
当调用函数时
原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。
此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),
从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,
而该地址处又存储着上一层函数调用时的EBP值!


10关于变量的赋值,

能否了解到对变量的赋值过程在算法研究中是非常重要的。由于变量是用地址访问的,,因此对形如 MOV [AAA],BBB 的代码要高度关注,它通常是修改变量(地址为AAA,或AAA为寄存器时地址为AAA的值)的值为BBB(BBB为寄存器时取BBB的值)。
      在子程序内部说明的变量称为局部变量,局部变量的作用域是其所在的子程序。从汇编角度来看,局部变量就是一个临时堆栈缓存,用完释放。
00401000 >/$ 6A 04 push 4 ; /Arg2 = 00000004
00401002 |. 6A 03 push 3 ; |Arg1 = 00000003
00401004 |. E8 16000000 call 0040101F ; /Add.0040101F
00401009 |. 8BD8 mov ebx, eax
0040100B |. 6A 00 push 0 ; /ExitCode = 0
0040100D /. FF15 00204000 call [<&KERNEL32.ExitProcess>] ; /ExitProcess

0040101F /$ 55 push ebp ; 保护现场原先的EBP指针
00401020 |. 8BEC mov ebp, esp ; 设置新的EBP指针,指向栈顶
00401022 |. 83EC 04 sub esp, 4 ; 分配局部变量所有空间
00401025 |. 8B45 0C mov eax, [ebp+C] ; 调用参数2
00401028 |. 8B5D 08 mov ebx, [ebp+8] ; 调用参数1
0040102B |. 895D FC mov [ebp-4] , ebx ; 参数1放局部变量里
0040102E |. 0345 FC add eax,[ebp-4] ; 参数2与局部变量相加
00401031 |. 83C4 04 add esp, 4 ; 释放局部变量所有空间
00401034 |. 5D pop ebp ; 恢复现场的ebp指针
00401035 /. C2 0800 retn 8

    在分析汇编代码时总是要遇到无数的Call,对于这些Call,尽量要根据Call之前传递的参数和Call的返回值来判断Call的功能。传递参数的工作必须由函数调用者和函数本身来协调,计算机提供了一种被称为栈的数据结构来支持参数传递。
    当参数个数多于一个时,按照什么顺序把参数压入堆栈。函数调用后,由谁来把堆栈恢复。在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:
【例】按__stdcall约定调用函数test2(Par1, Par2)
push par2 ; 参数2
push par1 ; 参数1
call test2;
{
push ebp ; 保护现场原先的EBP指针
mov ebp, esp ; 设置新的EBP指针,指向栈顶
mov eax, [ebp+0C] ; 调用参数2
mov ebx, [ebp+08] ; 调用参数1
sub esp, 8 ; 若函数要用局部变量,则要在堆栈中留出点空间

add esp, 8 ; 释放局部变量占用的堆栈
pop ebp ; 恢复现场的ebp指针
ret 8 ; 返回(相当于ret; add esp,8)
}
其堆栈调用示意图:

1.2 返回值

在调试程序时,不要见Call就跟进,在Call之前所做的所有PUSH动作以及对寄存器的操作都可能是在给函数传递参数,而函数的返回值一般都放在EAX里面,当然这个值可能是一个指针,指向一个数据结构。从汇编角度来看,主要有如下形式:

1)通过寄存器返回函数值;
2)通过参数按引用方式返回函数值;
3)通过全局变量返回函数值;
4)通过处理器标志返回函数值;
一般情况下,由retrun操作符返回的值放在EAX寄存器之中,如果结果超过这个寄存器的位容量,那么该结果的高32位会加载到EDX寄存器中。 如果返回一个含有几百个字节的结构或者一个近似大小的对象,编译器会在不告诉程序的情况下,给函数传递一个隐式参数,这个指针指向保存的返回结果。
     若a*=4 可以使用shl a,2逻辑左移相当于乘于2的平方
    
;CBW(Convert Byte to Word):         将 AL 扩展为 AX 
;CWDE(Convert Word to Extended Double): 将 AX 扩展为 EAX 
;CDQ(Convert Doubleword to Quadword):  将 EAX 扩展为 64 位数 EDX:EAX 
;CWD(Convert Word to Doubleword):    将 AX 扩展为 DX:AX 

       若a/=2 可以是使用sar a,1算术右移相当于除于2的1次方

<think>我们想要查看ELF文件中的所有汇编指令,也就是将ELF文件中的机器码反汇编汇编指令。根据引用[1]和[3],可以使用Capstone反汇编框架来反汇编ELF文件中的机器码数据。另外,引用[4]提到可以使用GNU的反汇编器(如arm-none-eabi-objdump)来将ELF文件反汇编生成list文件。因此,有两种主要方法:方法一:使用GNU的objdump工具(适用于命令行,无需编程)方法二:使用Capstone框架(适用于编程,特别是Python)下面分别介绍这两种方法:###方法一:使用objdump工具(推荐)1.**安装交叉编译工具链**(如果目标平台是ARM等嵌入式平台,则需要安装对应的工具链)例如,对于ARM架构,可以安装`gcc-arm-none-eabi`包,其中包含了`arm-none-eabi-objdump`工具。在Ubuntu上安装:```bashsudoapt-getinstallgcc-arm-none-eabi```2.**使用objdump反汇编ELF文件**命令格式:```basharm-none-eabi-objdump-d<elf_file>```其中,`-d`选项表示反汇编代码段(textsection)中的指令。如果想查看所有段(包括数据段)的反汇编(虽然数据段反汇编出来没有意义,但有时也包含代码),可以使用`-D`选项:```basharm-none-eabi-objdump-D<elf_file>```如果希望生成更详细的输出(包括源代码和汇编的混合,需要编译时加了`-g`选项),可以加上`-S`选项:```basharm-none-eabi-objdump-d-S<elf_file>```将输出重定向到文件:```basharm-none-eabi-objdump-d<elf_file>>disassembly.txt```注意:如果目标平台是x86,则可以直接使用系统的`objdump`工具(无需交叉编译版本),命令相同:```bashobjdump-d<elf_file>```###方法二:使用Capstone框架(Python)Capstone是一个轻量级的多平台多架构的反汇编框架。我们可以使用Python来调用Capstone反汇编ELF文件。1.**安装CapstonePython绑定**```bashpipinstallcapstone```2.**编写Python脚本反汇编ELF文件**首先,我们需要解析ELF文件,提取代码段(.text段)的二进制数据,然后使用Capstone进行反汇编。以下是一个简单的示例脚本(假设ELF文件是32位ARM架构,小端序):```pythonfromcapstoneimport*importelftoolsfromelftools.elf.elffileimportELFFile#打开ELF文件withopen('your_file.elf','rb')asf:elffile=ELFFile(f)#寻找代码段(.text)code_section=elffile.get_section_by_name('.text')ifnotcode_section:print("没有找到.text段")exit(1)code_data=code_section.data()code_addr=code_section['sh_addr']#代码段的加载地址#初始化Capstone反汇编引擎(这里以ARM32为例,模式为ARM模式)md=Cs(CS_ARCH_ARM,CS_MODE_ARM)#反汇编forinstrinmd.disasm(code_data,code_addr):print("0x%x:\t%s\t%s"%(instr.address,instr.mnemonic,instr.op_str))```注意:需要安装`pyelftools`来解析ELF文件:```bashpipinstallpyelftools```根据目标架构的不同,初始化Capstone时需要选择不同的架构和模式。例如:-ARM模式(32位):`Cs(CS_ARCH_ARM,CS_MODE_ARM)`-Thumb模式:`Cs(CS_ARCH_ARM,CS_MODE_THUMB)`-64位ARM(AArch64):`Cs(CS_ARCH_ARM64,CS_MODE_ARM)`-x86-32:`Cs(CS_ARCH_X86,CS_MODE_32)`-x86-64:`Cs(CS_ARCH_X86,CS_MODE_64)`3.**处理多个代码段**上面的脚本只处理了.text段,但有些ELF文件可能有多个包含代码的段(例如,某些嵌入式程序可能有多个代码段)。我们可以遍历所有段,根据段的标志(可执行)来判断是否为代码段:```pythonforsectioninelffile.iter_sections():ifsection['sh_flags']&0x4:#SHF_EXECINSTR标志,表示可执行段code_data=section.data()code_addr=section['sh_addr']#反汇编该段forinstrinmd.disasm(code_data,code_addr):print("0x%x:\t%s\t%s"%(instr.address,instr.mnemonic,instr.op_str))```###注意事项:1.**架构匹配**:使用objdump或Capstone时,必须选择与目标ELF文件匹配的架构。例如,ARM架构的ELF文件需要用ARM的反汇编器。2.**入口点**:有时程序的入口点并不是.text段的开始,但通常.text段包含主要的代码。3.**动态链接**:对于动态链接的可执行文件,objdump可能无法直接解析动态库中的函数,但会显示PLT(过程链接表)中的跳转指令。4.**调试信息**:如果ELF文件包含调试信息(使用`-g`编译),objdump的`-S`选项可以混合显示源代码和汇编代码。总结:-对于快速查看,推荐使用objdump工具,尤其是交叉编译工具链中的objdump。-对于自动化分析或集成到工具中,可以使用Capstone框架。###相关问题:1.如何反汇编特定函数而不是整个代码段?2.如何解析ELF文件中的动态链接符号?3.如何将反汇编结果与源代码行号对应?4.Capstone支持哪些架构的反汇编?</think>### 查看ELF文件所有汇编指令的方法 要查看ELF文件中的所有汇编指令,主要有两种方法:使用反汇编工具或编程解析。以下是具体操作步骤: #### 方法一:使用GNU反汇编工具(推荐) **步骤:** 1. **安装交叉编译工具链**(针对目标架构): ```bash # ARM架构示例 sudo apt-get install binutils-arm-linux-gnueabi ``` 2. **反汇编ELF文件**: ```bash # ARM架构 arm-linux-gnueabi-objdump -d your_file.elf > disassembly.txt # x86架构 objdump -d your_file.elf > disassembly.txt ``` - `-d`:仅反汇编代码段(.text) - `-D`:反汇编所有段(包括数据段) - `-S`:混合显示源代码和汇编(需编译时加`-g`) 3. **查看反汇编结果**: ```bash less disassembly.txt ``` **输出示例**: ``` 080483c0 <main>: 80483c0: 55 push %ebp 80483c1: 89 e5 mov %esp,%ebp 80483c3: 83 ec 10 sub $0x10,%esp 80483c6: e8 a5 00 00 00 call 8048470 <printf> ; 函数调用 ``` #### 方法二:使用Capstone框架(Python编程) **步骤**: 1. **安装依赖库**: ```bash pip install capstone pyelftools ``` 2. **Python反汇编脚本**: ```python from capstone import * from elftools.elf.elffile import ELFFile def disassemble_elf(filename): with open(filename, 'rb') as f: elf = ELFFile(f) code_section = elf.get_section_by_name('.text') code = code_section.data() code_addr = code_section['sh_addr'] # 获取代码段基地址 # 初始化反汇编器(根据架构选择) md = Cs(CS_ARCH_ARM, CS_MODE_ARM) # ARM模式 # md = Cs(CS_ARCH_X86, CS_MODE_32) # x86模式 for instr in md.disasm(code, code_addr): print(f"0x{instr.address:x}: {instr.mnemonic} {instr.op_str}") if __name__ == "__main__": disassemble_elf("your_file.elf") ``` 3. **关键参数说明**: - **架构选择**: - ARM: `Cs(CS_ARCH_ARM, CS_MODE_ARM)` - Thumb: `Cs(CS_ARCH_ARM, CS_MODE_THUMB)` - x86: `Cs(CS_ARCH_X86, CS_MODE_32)` - x64: `Cs(CS_ARCH_X86, CS_MODE_64)` - **输出格式**: - `instr.address`: 指令地址 - `instr.mnemonic`: 操作码(如`mov`, `call`) - `instr.op_str`: 操作数(如`%esp, %ebp`) #### 特殊场景处理 1. **ARM伪指令处理**: - 使用`objdump`时会自动转换伪指令为真实指令[^2] - 如`ldr r0, =0x1234` → `movw r0, #0x1234` 2. **动态库函数调用**: - 对`UNDEF`符号(如`printf`)会显示为`call <printf@plt>` - 查看PLT段:`objdump -d -j .plt your_file.elf` 3. **指令集混合**(ARM/Thumb): ```python # Capstone中动态切换模式 md = Cs(CS_ARCH_ARM, CS_MODE_THUMB) md.mode = CS_MODE_ARM # 运行时切换 ``` #### 验证工具 | 工具 | 用途 | 命令示例 | |------|------|----------| | `readelf` | 查看段信息 | `readelf -S elf_file` | | `nm` | 查看符号表 | `nm -D elf_file` | | `file` | 检查文件架构 | `file elf_file` | > **注意**:对于Android/ARM平台的`.axf`文件,需使用专用工具链: > `arm-none-eabi-objdump -d file.axf > disassembly.txt`[^4] ### 示例输出解析 ```armasm 00008000 <_start>: 8000: e3a0d902 mov sp, #32768 ; 初始化栈指针 8004: eb000001 bl 8010 <main> ; 跳转到main 8008: e3a00000 mov r0, #0 ; 设置退出码 800c: ef000000 svc 0 ; 系统调用 ``` - `e3a0d902`:机器码(小端序) - `mov sp, #32768`:ARM汇编指令 - `bl`:带链接的分支指令(函数调用)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值