一个简单的C程序反汇编解析.

合集下载

C语言中的安全性代码防篡改与反编译保护

C语言中的安全性代码防篡改与反编译保护

C语言中的安全性代码防篡改与反编译保护C语言作为一种广泛应用的编程语言,拥有强大的功能和灵活性。

然而,C语言编写的代码很容易受到恶意篡改和反编译的攻击。

为了保护代码的安全性,开发人员需要采取一些措施来防止代码的篡改和反编译。

本文将介绍C语言中常用的安全性代码防篡改与反编译保护方法。

一、常见的安全性代码防篡改方法1. 常量加密常量加密是一种常见的防止代码被篡改的方法。

在使用常量的地方,可以将常量进行加密处理,使得攻击者无法直接读取和修改常量的值。

常量加密可以通过对常量进行异或运算或者加密算法等方式来实现。

在程序运行时,需要对加密的常量进行解密,然后使用解密后的常量进行计算和操作。

2. 代码混淆代码混淆是一种将代码转换成难以理解和分析的形式的方法。

通过对代码进行重写,删除无用代码,添加冗余代码等操作,可以使代码的结构和逻辑变得复杂。

代码混淆可以增加攻击者对代码的理解和分析难度,从而提高代码的安全性。

常见的代码混淆技术包括函数内联、循环展开、代码重组等。

3. 授权验证授权验证是一种通过验证机制来保护代码安全的方法。

在代码中添加授权验证模块,用来验证代码的合法性和授权信息。

通过对授权信息进行加密处理,可以防止攻击者对授权信息的篡改和伪造。

授权验证可以通过校验码、数字签名等方式来实现,有效防止未授权的使用和修改。

二、反编译保护方法1. 逆向难度提升逆向难度提升是一种增加反编译难度的方法。

通过在代码中使用一些反调试、反跟踪和混淆技术,可以增加攻击者对代码的分析和理解难度,从而提高反编译的难度。

常见的逆向难度提升技术包括函数内联、指令替换、代码流程混淆等。

2. 加密保护加密保护是一种将代码和数据进行加密处理的方法。

通过对代码和数据进行加密,可以有效防止攻击者对代码的分析和修改。

在程序运行时,需要对加密的代码和数据进行解密,然后再进行执行和操作。

常见的加密保护技术包括代码加密、数据加密、混淆等。

3. 指令混淆指令混淆是一种将指令顺序、结构和操作进行重排和混淆的方法。

简单的汇编程序分析

简单的汇编程序分析

简单的汇编程序分析汇编程序1将这段程序保存为hello.s ,然后⽤汇编器as 把汇编程序中的助记符翻译成机器指令(汇编指令与机器指令是对应的)⽣成⽬标⽂件hello.o 。

然后⽤链接器ld 把⽬标⽂件hello.o 链接成可执⾏⽂件hello (虽然只有⼀个⽬标⽂件但是也需要经过链接才能成为可执⾏⽂件因为链接器要修改⽬标⽂件中的⼀些信息)。

这个程序只做了⼀件事就是退出,退出状态为4。

shell 中可以echo $?得到上⼀条命令的退出状态。

汇编程序中以"."开头的名称不是指令的助记符,不会被翻译成机器指令,⽽是给汇编器⼀些特殊的指⽰,称为汇编指⽰或伪操作。

.section 指⽰把代码划分成若⼲个段(section),程序被操作系统加载时,每个段被加载到不同的地址,具有不同的读写执⾏权限。

.data 段保存程序的数据是可读写的,C 程序的全局变量也属于.data 段。

上边的程序没定义数据所以.data 是空的。

.text 段保存代码,是只读和可执⾏的,后⾯那些指令都属于这个.text 段。

_start 是⼀个符号(Symbol),符号在汇编程序中代表⼀个地址,可以⽤在指令中,汇编程序经过汇编器的处理后所有的符号都被替换成它所代表的地址值。

在C 中我们可以通过变量名访问⼀个变量,其实就是读写某个地址的内存单元,我们通过函数名调⽤⼀个函数其实就是调转到该函数的第⼀条指令所在的地址,所以变量名和函数名都是符号,本质上是代表内存地址的。

.globl 指⽰告诉汇编器_start 这个符号要被链接器⽤到,所以要在⽬标⽂件的符号表中给它特殊标记。

_start 就像C 程序的main 函数⼀样特殊是整个程序的⼊⼝,链接器在链接时会查找⽬标⽂件中的_start 符号代表的地址,把它设置为整个程序的⼊⼝地址,所以每个汇编程序都要提供⼀个_start 符号并且⽤.globl 声明。

如果⼀个符号没有⽤.globl 指⽰声明这个符号就不会被链接器⽤到。

反编译初识——精选推荐

反编译初识——精选推荐

反编译初识⼀. 反编译(反汇编)简介 1.1. 为什么要反编译(汇编 assembly 反汇编 dissembly) 1.1.1. 反编译原因如下: a. 逆向破解程序,作为⼀个有逼格,素养的我肯定不会这么⼲的。

哈哈 b. 调试程序时,反汇编代码可以帮助我们理解程序(我们学习时使⽤objdump主要⽬的是这个),尤其是在理解链接脚本、链接地址等概念时 c. 把C语⾔源代码编译链接⽣成的可执⾏程序反汇编后得到对应的汇编代码,可以帮助我们理解C语⾔和汇编语⾔之间的对应关系。

⾮常有助于深⼊理解C语⾔。

1.2. 反编译命令 1.2.1. arm-linux-objdump -D led.elf > led_elf.dis 命令 a. objdump是gcc⼯具链中的反汇编⼯具,作⽤是由编译链接好的elf格式的可执⾏程序反过来得到汇编源代码 b. -D表⽰反汇编 1.2.1. 获得反编译⽂件过程 a. 此过程是基于上篇⽂章《》,前期准备参考此⽂章 b. 反编译后⽣成led_elf.disroot@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# lsled.S Makefile mkv210_image.c write2sd 说明.txtroot@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# makearm-linux-gcc -o led.o led.S -carm-linux-ld -Ttext 0x0 -o led.elf led.oarm-linux-objcopy -O binary led.elf led.binarm-linux-objdump -D led.elf > led_elf.disgcc mkv210_image.c -o mkx210./mkx210 led.bin 210.binroot@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED# ls210.bin led.elf led.o Makefile mkx210 说明.txtled.bin led_elf.dis led.S mkv210_image.c write2sdroot@ubuntu:/mnt/hgfs/windows_share/baseC/lesson1.4.13-LED#View Code 1.3. 反编译⽂件led_elf.dis分析 1.3.1. 我们选取⼀段分析 00000000 <_start>:0: e59f1060 ldr r1, [pc, #96] ; 68 <delay_loop+0x10> a. 00000000 <_start>: 表⽰标号地址、标号名字 b. 0: e59f1060表⽰指令地址、指令机器码 c. ldr r1, [pc, #96]表⽰指令机器码反汇编到的指令,由于有流⽔线的存在,此时PC值是当初指令地址+9; d. 68 <delay_loop+0x10>表⽰注释;0x68 = 96+8led.elf: file format elf32-littlearmDisassembly of section .text:00000000 <_start>:0: e59f1060 ldr r1, [pc, #96] ; 68 <delay_loop+0x10>4: e59f0060 ldr r0, [pc, #96] ; 6c <delay_loop+0x14>8: e5810000 str r0, [r1]0000000c <while>:c: e59f105c ldr r1, [pc, #92] ; 70 <delay_loop+0x18>10: e3e00008 mvn r0, #814: e5810000 str r0, [r1]18: eb00000c bl 50 <delay>1c: e59f104c ldr r1, [pc, #76] ; 70 <delay_loop+0x18> 20: e3e00010 mvn r0, #1624: e5810000 str r0, [r1]28: eb000008 bl 50 <delay>2c: e59f103c ldr r1, [pc, #60] ; 70 <delay_loop+0x18> 30: e3e00020 mvn r0, #3234: e5810000 str r0, [r1]38: eb000004 bl 50 <delay>3c: e59f102c ldr r1, [pc, #44] ; 70 <delay_loop+0x18> 40: e3e00010 mvn r0, #1644: e5810000 str r0, [r1]48: eb000000 bl 50 <delay>4c: eaffffee b c <while>00000050 <delay>:50: e59f201c ldr r2, [pc, #28] ; 74 <delay_loop+0x1c> 54: e3a03000 mov r3, #000000058 <delay_loop>:58: e2422001 sub r2, r2, #15c: e1520003 cmp r2, r360: 1afffffc bne 58 <delay_loop>64: e1a0f00e mov pc, lr68: e0200240 eor r0, r0, r0, asr #46c: 00111000 andseq r1, r1, r070: e0200244 eor r0, r0, r4, asr #474: 00895440 addeq r5, r9, r0, asr #8Disassembly of section .ARM.attributes:00000000 <.ARM.attributes>:0: 00001a41 andeq r1, r0, r1, asr #204: 61656100 cmnvs r5, r0, lsl #28: 01006962 tsteq r0, r2, ror #18c: 00000010 andeq r0, r0, r0, lsl r010: 45543505 ldrbmi r3, [r4, #-1285] ; 0x50514: 08040600 stmdaeq r4, {r9, sl}18: Address 0x00000018is out of bounds.View Code参考《朱⽼师.1.2ARM裸机课件》。

Linux下简单C语言小程序的反汇编分析

Linux下简单C语言小程序的反汇编分析

Linux下简单C语⾔⼩程序的反汇编分析韩洋原创作品转载请注明出处《Linux内核分析》MOOC课程写在开始,本⽂为因为参加MOOC相关课程⽽写的作业,如有疏漏,还请指出。

选了⼀门Linux内核分析课程,因为阅读内核代码中或多或少要涉及到At&T汇编代码的阅读,所以这⾥写下⼀个对⼀个简单C命令⾏程序的反汇编分析过程,⼀⽅⾯完成作业,另⼀⽅⾯当作练⼿。

下⾯开始:1、编写我们的C语⾔⼩程序这⾥我们使⽤简单的例⼦,代码如下:1 #include <stdio.h>23int exG(int x)4 {5return x + 5;6 }78int exF(int x)9 {10return exG(x);11 }1213int main(void)14 {15return exF(10) + 2;16 }使⽤vim等编辑器写⼊上述代码,保存到main.c,然后使⽤下⾯命令⽣成汇编源⽂件:x86系统:$gcc -S -o main.s main.cx64系统:$gcc -m32 -S -o main.s main.c因为我们这⾥以32位平台为例⼦,所以在x64机器上要加上-m32来使GCC⽣成32位的汇编源⽂件。

2、处理源⽂件执⾏完上述命令后,当前⽬录下就会有⼀个main.s的⽂件,使⽤vim打开,不需要的链接信息[以"."开头的⾏],得到如下汇编代码:1 exG:2 pushl %ebp3 movl %esp, %ebp4 movl 8(%ebp), %eax5 addl $5, %eax6 popl %ebp7 ret8 exF:9 pushl %ebp10 movl %esp, %ebp11 pushl 8(%ebp)12 call exG13 addl $4, %esp14 leave15 ret16 main:17 pushl %ebp18 movl %esp, %ebp19 pushl $1020 call exF21 addl $4, %esp22 addl $2, %eax23 leave24 ret可以看到这个⽂件⾥是GCC帮我们⽣成的汇编代码,这⾥需要说明下AT&T格式和intel格式,这两种格式GCC是都可以⽣成的,如果要⽣成intel格式的汇编代码,只需要加上 -masm=intel选项即可,但是Linux下默认是使⽤AT&T格式来书写汇编代码,Linux Kernel代码中也是AT&T格式,我们要慢慢习惯使⽤AT&T格式书写汇编代码。

c语言 位顺序反

c语言 位顺序反

c语言位顺序反
在C语言中,可以使用位运算符来实现位顺序反转。

具体来说,可以使用异或运算符(^)和左移运算符(<<)来实现。

以下是一个示例代码,演示如何使用位运算符实现位顺序反转:
```c
include <>
int reverseBits(int num) {
int result = 0;
int i;
for (i = 0; i < 32; i++) {
result = result ^ (num << i);
}
return result;
}
int main() {
int num = 0x;
int reversed = reverseBits(num);
printf("原数:0x%X\n", num);
printf("反转后:0x%X\n", reversed);
return 0;
}
```
在上面的代码中,reverseBits函数接受一个整数num作为输入,并返回其位顺序反转后的结果。

该函数使用一个循环来迭代32次,每次将num左移一位并与result进行异或运算,最终得到反转后的结果。

在主函数中,我们定义了一个整数num,并将其传递给reverseBits函数进行位顺序反转。

然后,我们打印出原数和反转后的结果。

需要注意的是,上述代码中的位顺序反转是指将整数的二进制表示中的位顺序进行反转。

例如,如果num的二进制表示为,则其位顺序反转后的结果为。

C语言逆向工程与反汇编技术解析

C语言逆向工程与反汇编技术解析

C语言逆向工程与反汇编技术解析C语言逆向工程与反汇编技术是现代计算机科学领域中的重要研究方向,它们具有广泛的应用和深远的影响。

本文将对C语言逆向工程与反汇编技术进行深入解析,介绍其原理、方法和应用。

一、C语言逆向工程的原理与方法1.1 C语言逆向工程的概念C语言逆向工程是指通过分析、理解和修改C语言代码,以了解程序的内部结构、运行机制和逻辑设计的一种技术。

它可以帮助开发人员深入了解程序的实现细节,并从中获取有用的信息,如算法、数据结构、API调用等。

1.2 C语言逆向工程的方法C语言逆向工程主要使用反汇编、调试和静态分析等方法进行。

其中,反汇编是将程序的二进制代码转换为汇编代码的过程,通过分析汇编代码,可以还原出程序的逻辑结构和运行轨迹。

调试是指通过运行程序,并在运行过程中观察和分析程序的行为,从而了解其内部机制和变量的具体取值。

静态分析是在不运行程序的情况下,直接对程序的源代码或二进制代码进行分析,以推测程序的行为和逻辑。

二、反汇编技术的原理与应用2.1 反汇编的原理反汇编是将机器码翻译成汇编指令的过程。

在计算机中,指令是由一系列二进制位组成的。

反汇编程序通过解析这些二进制位,识别出对应的指令,并将其翻译为汇编指令,以便开发人员阅读和理解。

2.2 反汇编技术的应用反汇编技术广泛应用于软件逆向工程、安全分析和漏洞挖掘等领域。

通过反汇编,可以还原出程序的源代码或类似源代码的结构,进而分析程序的逻辑和功能。

此外,反汇编技术还可以用于恶意代码分析、密码破解和软件修改等方面,提供了重要的技术支持。

三、C语言逆向工程与反汇编技术的应用案例3.1 软件安全分析使用C语言逆向工程与反汇编技术,可以对软件进行安全漏洞分析。

通过对程序进行反汇编,分析程序的执行流程和函数调用关系,可以发现潜在的漏洞和安全隐患,并采取相应的修复措施,提高软件的安全性。

3.2 软件破解与修改逆向工程与反汇编技术常常被用于软件破解和修改。

反汇编 第五节 C语言的判断与分支

反汇编 第五节 C语言的判断与分支

——啊冲第四节C语言判断与分支的反汇编If else语句•int myfunction(int c)•{•if (c>0 && c<10)•{•printf("c>0 \n");•}•else if (c>10 && c<100)•{•printf("c>10 && c<100 \n");•}•else•{•printf("其它\n");•}•return c;•}If判断都是使用cmp再加上条件跳转指令。

对于if(a && b)的情况,一般都是使用否决法。

如果a不成立,立刻跳到走,如果b不成立,同样跳走。

从这里看出,在用C语言写一系列条件比较的时候,应该注意排列这些比较,让比较次数越少越好。

cmp<条件>,<初始值>jle <下一个分支>else if和else的特点是,在开始的地方,都有一条无条件跳转指令,跳转到判断结束处,阻止前面的分支执行结束后,直接进入这个分支的可能。

这个分支能执行到的唯一途径是前面的判断条件不满足。

Else 则在jmp之后直接执行操作。

而else if则开始重复if之后的操作,用cmp比较,然后用条件跳转指令进行跳转。

Swtich case语句条件分支中,有比较特殊的情况是switch.switch 的特点是有多个判断.因为swtich 显然不用判断大于小于,所以都是je,分别跳到每个case处。

最后一个是无条件跳转,直接跳到default 处。

•switch(c)•{•case 0:•printf("c=0");•case 1:•{•printf("c=1");•break;•}•default:•printf("C是其它数");•}连续的比较与跳转让人想到switchCmp …Je ….Cmp …Je ……Jmp …至于case 和default 都非常简单.如果有break,则会增加一个无条件跳转.没有break 的情况下,没有任何循环控制代码.反汇编练习:把汇编代码还原成C语言。

VS2008 反汇编

VS2008 反汇编

VS2008反汇编小解了解反汇编的一些小知识对于我们在开发软件时进行编程与调试大有好处,下面以VS2008环境下的VC++简单介绍一下反汇编的一些小东西!如果有些解释有问题的地方,希望大家能够指出。

1、新建简单的VC控制台应用程序(对此熟悉的同学可以略过)A、打开Microsoft Visual Studio 2008,选择主菜单“File”B、选择子菜单“New”下面的“Project”,打开“New Project”对话框。

C、左边选择Visual C++下的win32,右边选择Win32 Console Application,然后输入一个工程名,点击“OK”即可,在出现的向导中,一切默认,点击Finish即可。

D、在出现的编辑区域内会出现以你设定的工程名命名的CPP文件。

内容如下:#include "stdafx.h"int _tmain(int argc, _TCHAR* argv[]){return 0;}2、VS查看汇编代码A、VC处于调试状态才能看到汇编指令窗口。

因此,可以在return 0 上设置一个断点:把光标移到return0 那一行上,然后按下F9键设置一个断点。

B、按下F5键进入调试状态,当程序停在return 0 这一行上时,打开菜单“Debug”下的“Windows”子菜单,选择“Disassembly”。

这样,出现一个反汇编的窗口,显示下面的信息:--- d:\my documents\visual studio 2008\projects\casmtest\casmtest\casmtest_main.cpp// CAsmTest.cpp : 定义控制台应用程序的入口点。

//#include "stdafx.h"int _tmain(int argc, _TCHAR* argv[]){00411370 push ebp00411371 mov ebp,esp00411373 sub esp,0C0h00411379 push ebx0041137A push esi0041137B push edi0041137C lea edi,[ebp-0C0h]00411382 mov ecx,30h00411387 mov eax,0CCCCCCCCh0041138C rep stos dword ptr es:[edi]return 0;0041138E xor eax,eax}00411390 pop edi00411391 pop esi00411392 pop ebx00411393 mov esp,ebp00411395 pop ebp00411396 ret上面就是系统生成的main函数原型,确切的说是_tmain()的反汇编的相关信息,相信学过汇编语言的肯定就能够了解它所做的操作了。

使用gcc命令把C语言程序反汇编

使用gcc命令把C语言程序反汇编

使⽤gcc命令把C语⾔程序反汇编之前看过⼀点汇编,不过现在都忘记得差不多了。

最近⼜很蛋疼地想起反汇编这个东西。

这⾥使⽤ gcc 命令对 .c ⽂件进⾏反汇编,把 C语⾔翻译成汇编语⾔先准备⼀个简单的 C 程序sum.c#include <stdio.h>int add(int, int);int mode(int, int);int main(){int a = 3, b = 2;int s = add(3, 2);int m = mode(3, 2);return0;}int add(int a, int b){return a + b;}int mode(int a, int b){return a % b;}在控制台中先进到保存 sum.c 的⽂件夹下,当然你也可以⽤绝对路径,使⽤以下命令gcc -S sum.c -o sum_at.s这个命令默认⽣成的是 AT&T 汇编,⽣成的 sum_at.s 如下.file "sum.c".text.def ___main; .scl 2; .type 32; .endef.globl _main.def _main; .scl 2; .type 32; .endef_main:LFB13:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5andl $-16, %espsubl $32, %espcall ___mainmovl $3, 28(%esp)movl $2, 24(%esp)movl $2, 4(%esp)movl $3, (%esp)call _addmovl %eax, 20(%esp)movl $2, 4(%esp)movl $3, (%esp)call _modemovl %eax, 16(%esp)movl $0, %eaxleave.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endprocLFE13:.globl _add.def _add; .scl 2; .type 32; .endef_add:LFB14:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5movl 8(%ebp), %edxmovl 12(%ebp), %eaxaddl %edx, %eaxpopl %ebp.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endprocLFE14:.globl _mode.def _mode; .scl 2; .type 32; .endef_mode:LFB15:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5movl 8(%ebp), %eaxcltdidivl 12(%ebp)movl %edx, %eaxpopl %ebp.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endprocLFE15:.ident "GCC: (i686-posix-dwarf-rev0, Built by MinGW-W64 project) 7.3.0"我之前看的是intel汇编也就是8086这种 intel 芯⽚,要转成 intel 汇编使⽤以下命令gcc -S -masm=intel sum.c -o sum_intel.s⽣成的 sum_intel.s ⽂件内容如下.file "sum.c".intel_syntax noprefix.text.def ___main; .scl 2; .type 32; .endef.globl _main.def _main; .scl 2; .type 32; .endef_main:LFB13:.cfi_startprocpush ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8mov ebp, esp.cfi_def_cfa_register 5and esp, -16sub esp, 32call ___mainmov DWORD PTR [esp+28], 3mov DWORD PTR [esp+24], 2mov DWORD PTR [esp+4], 2mov DWORD PTR [esp], 3call _addmov DWORD PTR [esp+20], eaxmov DWORD PTR [esp+4], 2mov DWORD PTR [esp], 3call _modemov DWORD PTR [esp+16], eaxmov eax, 0leave.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endprocLFE13:.globl _add.def _add; .scl 2; .type 32; .endef_add:LFB14:.cfi_startprocpush ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8mov ebp, esp.cfi_def_cfa_register 5mov edx, DWORD PTR [ebp+8]mov eax, DWORD PTR [ebp+12]add eax, edxpop ebp.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endprocLFE14:.globl _mode.def _mode; .scl 2; .type 32; .endef_mode:LFB15:.cfi_startprocpush ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8mov ebp, esp.cfi_def_cfa_register 5mov eax, DWORD PTR [ebp+8]cdqidiv DWORD PTR [ebp+12]mov eax, edxpop ebp.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endprocLFE15:.ident "GCC: (i686-posix-dwarf-rev0, Built by MinGW-W64 project) 7.3.0"等我复习下汇编再回来看这段代码到底⼲了什么。

利用反汇编手段解析C语言函数

利用反汇编手段解析C语言函数

利用反汇编手段解 析 C语言 函数
祁春 霞
( 东学院信息技 术学院, 辽 辽宁 丹 东 18 0 ) 10 3 摘 要: 利用反 汇编手段 分析 C语言程序 函数调 用过程 中堆栈的动 态分配、 变量的初始化方式等细节 , 示了高级语言程 揭
序在底层的具体 实现 , 于加深对 变量、 利 函数调用过程 的理解 , 解高级语言 中部分约定的底层根 源, 而减 少和避免使 理 从
对 数组 内容 的访 通过 『E P ‘ B +数 组首 地址 +偏 移 ‘
秘§ I ’8 8 善罄 2 秘a 嵇 2 趣 I 1臻 9 88 镪 Z 峰 ’秘 e 8 l秘l 3 8 I _g 1
p h us
l ea
珊o U 辫O毛 |
量1 的寄存器间址来完成 , 因此局部数组初始化费时但 访 问时的效率高。
在 C语 言 中不需 要进 行堆 栈平 衡 。而 在汇 编层 面
> ui a n ) o d m i ( {
i t i 6 j 乜 n = , ; fn t o ( - j ; u c i n i & '
图 2 程 序 源 代 码
上却根据调用约定来确定 由主调函数或是被调函数完 成堆栈平衡。
祁春霞 : 利用反汇编手段解析 C语 言函数
1:
2:
i t F n t o (n ,n * ) n u c i n i t i i t j

p s u h
m0V
量“b” ae被存放在全局数据区中。 当需要引用其值对数 组进行初始化时,实际是将全局数据拷贝到堆栈 中的
的编译器对 C语 言程 序的默认 函数 约定为 _dc6 cel 。 t 1 此参数人栈约定为 自 向左 ,并且对 函数名前加 “ ” 右 一 修饰符。先将 j 的地址压入堆栈 ,后将 i 的值压入堆

linux内核学习之一简单c语言反汇编

linux内核学习之一简单c语言反汇编

linux内核学习之⼀简单c语⾔反汇编(我是第⼀次发技术博客的菜鸟,恳请⼤家指导!!)⼀由简单c程序⽣成汇编代码⾸先给出本次我们要反汇编的简单c语⾔程序:(够简单吧~)在linux环境中使⽤下⾯的命令条件编译:⽣成汇编⽂件shiyan1.s:shiyan1.s的部分代码截图:全部粘贴出来如下:1 .file "shiyan1.c"2 .text3 .globl g4 .type g, @function5 g:6 .LFB0:7 .cfi_startproc8 pushl %ebp9 .cfi_def_cfa_offset 810 .cfi_offset 5, -811 movl %esp, %ebp12 .cfi_def_cfa_register 513 movl 8(%ebp), %eax14 addl $2, %eax15 popl %ebp16 .cfi_restore 517 .cfi_def_cfa 4, 418 ret19 .cfi_endproc20 .LFE0:21 .size g, .-g22 .globl f23 .type f, @function24 f:25 .LFB1:26 .cfi_startproc27 pushl %ebp28 .cfi_def_cfa_offset 829 .cfi_offset 5, -830 movl %esp, %ebp31 .cfi_def_cfa_register 532 subl $4, %esp33 movl 8(%ebp), %eax34 movl %eax, (%esp)35 call g36 leave37 .cfi_restore 538 .cfi_def_cfa 4, 439 ret40 .cfi_endproc41 .LFE1:42 .size f, .-f43 .globl main44 .type main, @function45 main:46 .LFB2:47 .cfi_startproc48 pushl %ebp49 .cfi_def_cfa_offset 850 .cfi_offset 5, -851 movl %esp, %ebp52 .cfi_def_cfa_register 553 subl $4, %esp54 movl $1, (%esp)55 call f56 addl $3, %eax57 leave58 .cfi_restore 559 .cfi_def_cfa 4, 460 ret61 .cfi_endproc62 .LFE2:63 .size main, .-main64 .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"65 .section .note.GNU-stack,"",@progbitsView Code去除链接信息的汇编代码:1 g:2 pushl %ebp3 movl %esp, %ebp4 movl 8(%ebp), %eax5 addl $2, %eax6 popl %ebp7 ret8 f:9 pushl %ebp10 movl %esp, %ebp11 subl $4, %esp12 movl 8(%ebp), %eax13 movl %eax, (%esp)14 call g15 leave16 ret17 main:18 pushl %ebp19 movl %esp, %ebp20 subl $4, %esp21 movl $1, (%esp)22 call f23 addl $3, %eax24 leave25 ret⼆汇编代码分析前提:基于32位的系统,相关的寄存器也为32位寄存器介绍:eax >> 累加器ebp >> 堆栈基指针esp >> 堆栈顶指针eip >> 指向待执⾏的下⼀条指令,相当于pc 汇编指令介绍:代码分析:我们都知道c语⾔代码是从main()函数开始执⾏的,汇编也不例外,找到main 的标志处,即第17⾏的位置。

linux x86 elf _start函数反汇编详解

linux x86 elf _start函数反汇编详解

linux x86 elf _start函数反汇编详解在Linux操作系统中,程序的入口点是由_start函数确定的。

_start函数是所有C语言和汇编语言程序的起始点。

在本文中,我们将对Linux x86 ELF中的_start函数进行反汇编解析。

首先,我们需要了解什么是反汇编。

反汇编是将机器码(即二进制代码)转换回汇编语言的过程。

通过反汇编,我们可以理解程序的执行流程,调用的函数以及与系统的交互。

在Linux x86 ELF文件的头部,有一个_entry字段,它表示程序执行的入口点地址。

该地址指向_start函数的位置。

因此,我们可以通过反汇编_start函数来理解程序的执行流程。

_start函数通常由汇编语言编写,它负责执行一些必要的设置和初始化,然后调用主函数main。

下面是_start函数的典型反汇编代码:```start:jmp crt1.o__start ; 跳转到C运行时库的_start函数```可以看出,_start函数实际上是一个跳转指令,将控制权转移到C运行时库的_start函数。

C运行时库是一个在程序启动时执行的库,它负责初始化运行时环境,并调用主函数main。

接下来,我们来看一下C运行时库中的_start函数的反汇编:```crt1.o__start:push %ebp ; 保存旧的基址mov %esp,%ebp ; 设置新的基址xor %eax,%eax ; 清零寄存器eaxcall __libc_start_main ; 调用__libc_start_main函数```_start函数开始时,首先保存旧的基址(即寄存器ebp),然后将栈指针esp的值赋给ebp,这样就建立了一个新的基址。

接着,将寄存器eax清零,这通常用于存储函数的返回值。

最后,调用__libc_start_main函数。

__libc_start_main函数是C运行时库中的一个重要函数,它负责设置一些参数,并调用主函数main。

c语言最简main函数的反汇编代码解析

c语言最简main函数的反汇编代码解析

目前我们写的最简单的Main函数如下:代码:#include "stdafx.h"int _tmain(int argc, _TCHAR* argv[]){return 0;}利用VS编译器将运行时程序反汇编,结果如下:代码:int _tmain(int argc, _TCHAR* argv[]){010C13A0 push ebp010C13A1 mov ebp,esp010C13A3 sub esp,0C0h010C13A9 push ebx010C13AA push esi010C13AB push edi010C13AC lea edi,[ebp-0C0h]010C13B2 mov ecx,30h010C13B7 mov eax,0CCCCCCCCh010C13BC rep stos dword ptr es:[edi]return 0;010C13BE xor eax,eax}010C13C0 pop edi010C13C1 pop esi010C13C2 pop ebx010C13C3 mov esp,ebp010C13C5 pop ebp010C13C6 ret看起来汇编很头疼吧,那么让我们来茅塞顿开吧。

首先了解一下以前几个未知半懂的汇编指令的含义:代码:push ebpmov ebp,esppush:把一个32位操作数压入栈中,这个操作导致esp被减4。

ebp被用来保存这个函数执行前的esp 的值,执行完毕后用ebp恢复esp。

同时,调用此函数的上层函数也用ebp做同样的事情。

所以先把ebp 压入堆栈,返回之前弹出,避免ebp被我们改动。

代码:xor eax,eaxretxor eax,eax常用来代替mov eax,0。

清零操作。

在windows中,函数返回值都是放在eax中然后返回,外部从eax中得到返回值。

这就代表return 0操作。

代码:lea edi,[ebp-0C0h]lea取得第二个参数代表的地址放入第一个参数代表的寄存器中。

反汇编教程

反汇编教程

◆高级语言特性的识别
§ 浮点处理
浮点数的处理与整数不一样,它是由
FPU完成的。float用4个字节表示,double 则用8个。常用的浮点指令:
fld 压栈
fst弹栈
fcom 比较
fadd 加法
fdiv除法
fmul 乘法
fsub 减法
◆高级语言特性的识别
§ 浮点处理
浮点处理时,先把操作数压到浮点堆栈中,比 如:
◆高级语言特性的识别
§ 参数的传递规范
2、Pascal规范 函数参数由左往右压入堆栈,由被调用的函数负责清
栈。如: int __pascal swap(int a, int b); push a push b call swap
在swap内部: 由 retn 8 来清理堆栈。
注:在VC++中已不再支持,用相似的WINAPI来代替,也就 是说普通代码是不能编出Pascal调用类型。
var_4 = dword ptr -4
在用的时候:
mov [esp + var_4], 5555
5555
生长方向
-8 -4 0 4 8 c
◆高级语言特性的识别
§ 结构体
如果定义一个局部变量的结构体实例:
struct person{ int a;
Var_8
int b; };
Var_4
Void test(void){
retn 4 可以轻松的看出ecx 是一个结构体(或类)的指针,并
访问了它的偏移为0、4的两个成员。 看一个例子: struct.idb
◆高级语言特性的识别
§ 类 (this指针)
类的成员变量和结构类似,但是它在调 用成员函数的时候会准备this指针的传递。

C++反汇编第六讲,认识C++中的Trycatch语法,以及在反汇编中还原

C++反汇编第六讲,认识C++中的Trycatch语法,以及在反汇编中还原

C++反汇编第六讲,认识C++中的Trycatch语法,以及在反汇编中还原 C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原我们以前讲SEH异常处理的时候已经说过了,C++中的Try catch语法只不过是对SEH做了⼀个封装.如果不懂SEH异常处理,请点击博客链接熟悉⼀下,当然如果不想知道,也可以直接往下看.因为异常处理所以做了封装,但是不影响我们还原.这⾥有两种解决⽅法,第⼀种,⼩⽩也能明⽩了还原⽅式,不⽤懂原理第⼆种,了解其原理,并能在IDA中正确的还原.SEH异常处理博客链接:⼀⼂⼩⽩也懂的异常处理还原.第⼀种,不⽤懂任何原理,(反汇编要懂,最起码的汇编代码知道是什么,不然这个专题讲了你也看不懂)先看下⾼级代码:int main(int argc, char* argv[]){try{throw3;//抛出3整形的异常}catch (int){printf("%d\r\n",3);}return0;}OD调试,观看步骤.⾸先步骤分两步.1.找SEH异常处理回调.2.找参数多的call下断点3.最后⼀个是call 寄存器.则找到正确的cath位置.1.找到SEH的异常处理回调函数(如果不懂,看下SEH的筛选器异常.)观看栈参数,可以看到回调函数地址是00410CC0,此时反汇编窗⼝跟过去下断点.2.观看参数多了call,下断点,然后跟进.因为层级较多,这⾥⼤家动⼿实战即可.3.找到最后⼀层call⼀个寄存器.此时则找到了我们的cath处理块了.⼆⼂理解原理进⾏实战.上⾯说的,不懂原理你也可以做.⽐如以后⼯作了,原理不懂,起码做代码还原的时候还可以混⼝饭吃 ^_^下⾯讲解原理.⾸先,我们先寻找数据关系.⼀个函数可以有多个 try 这是没问题的,所以函数和try的关系是⼀对多的关系.⼀个try有多个catch也是没有问题的.那么对应关系也是⼀对多.所以操作系统为了管理这些.需要建表.(当然是未公开的),我们可以逆向得知.1.原理介绍,上半部分表观看表看到怎么多表是不是很晕.其实很简单,只是⾥⾯的某个字段对我们有⽤.1.观看函数信息表FuncInfo,重要字段就是dwTryCount,以及pTryBlockMap,我们说过⼀个函数可以有多个try,所以函数信息就记录了try的个数.以及每个try的try块结构,关于上⾯的成员,都是SEH的异常展开的.要做⾸尾动作的,对于我们还原没有任何阻碍,可以不⽤理解.2.TryBlockMap表(tyr块信息表),我们还说过,⼀个try可以有多个catch,显然,try块信息表也要记录catch的个数.⼀个catch信息块的结构.3.cathch信息块的结构体(msRTTIDsrc)msRTTIDsrc这个表是IDA识别的,我们可以⾃⼰说他是catch信息表,msRTTIDsrc的全名是 Microsoft Run Time TypeInfo ....微软运⾏时类型识别.表结构体中重要字段就是 dwCount,catch信息快的个数,⼀个cathch信息表.4.catch信息表(msRTTIDsrc),⾥⾯有4个成员, nflag ⼀个标志,表⽰你是常量,还是变量,还是引⽤. ptype,表⽰你的类型是什么类型.是int,还是float什么的,这个有个专门的表格存放着.下⾯重要字段则是catch的函数地址.编译器内部编译的时候,编译的catch是⼀个函数,也是有返回值的,返回值是catch_end的位置.到这⾥,我们的上半部分就看完了.总结⼀下: 对于上半部分.主要着重于函数信息表,try块表.catch表.以及catch块表.为什么说上半部分,是这样的,对于我们还原来说.有两种情况.1.完整的还原trycatch的异常信息结构2.找到关键的catch匹配函数.这两种是完全不⼀样的,如果我们还原就看上版本的表.如果我们要找那个catch捕获异常,则看下部分的表,(下⾯讲解.)实战演练.实战演练的时候,我们就要知道函数信息表在哪,其实我们已经找到了,只不过⼤家不知道,按照⼩⽩思路为什么⼀路跟就可以找到catch块表.只是我们没讲.1.找到注册异常回调的地⽅,进⼊回调函数内部.2.看到反汇编.这个地⽅,给eax赋值的地址就是函数信息表了.我们⽤IDA打开.根据表结构可以很⾃然的就能找到.catch块的位置.IDA实战.1.找到函数信息表点击进去可以看到MaicNumber等等⼀些列的东西.,通过最后⼀个成员,可以找到Try块信息表.2.找到Try块信息表可以看到Try块信息表中有⼏个Catch信息.3.找到Catch信息表.看到最后是catch的回调地址,⾥⾯则是catch的位置.我们也能看到类型是什么.⾃⼰点击进去看看即可.4.找到Catch函数地址,点击查看.正确找到了Catch块了.2.下半部分表格详解.上⾯的部分是针对我们还原做的.下半部分主要就是找我要快速定位这个异常是谁接受的,那个catch块处理的.第⼀个表异常抛出表,重要字段catch数组表第⼆个表是个数组表,⼀个throw对应多个catch,所以有个数组,这个数组是异常匹配表.表明了有多少个catch可以匹配.⼀个记录个数,第⼆个成员则是⼆级指针.其内容是⼀个地址,其地址是⼀个cathc信息块的结构.实战演练.关于异常抛出表要怎么寻找,还记得的我们的⾼级代码有⼀个 throw吗?,其位置就是抛出异常表的位置.关于抛出异常,我们需要了解的知识.当我们抛出⼀个常量或者变量的时候,不管怎么样,都会建⽴⼀个内存单元来接受我们抛出的值.所以别看你抛出的是常量,其实也成为内存单元了,也相当于⼀个变量了.1.找到异常信息表.最后⼀个成员是catch表.这个表保存了可以匹配的catch有多少个.2.找到catch异常信息匹配表.可以看出,值为1,下⾯是⼀个catch表,表明了有⼀个匹配的.3.找到catch信息表.通过异常信息表的第⼆个参数,也就是类型表,可以看是哪个匹配的.⽽且对类型表做⼀个引⽤,看谁引⽤类类型表,则能找到catch的函数地址.当然这⼀步是还原的动作.4.找到类型表.对类型表做⼀个交叉引⽤.如果引⽤显⽰的太少了,可以⾃⼰百度, IDA引⽤数量调整.看怎么设置的,或者当前位置按 x键查看.如果按x,跳过去的是try块表,你需要通过try块表可以直接查看.那个catch信息表.今天讲的主要是表结构.如果想对每⼀个成员都了解,并且想知道怎么跟出来了. 可以看⼀下书籍 <<C++反汇编与逆向分析揭秘>>也就是⼀本⼩黄书.。

反汇编c代码

反汇编c代码

反汇编c代码反汇编C代码是将C代码转换成相应的汇编代码的过程。

可以使用工具如IDA Pro、objdump等来进行反汇编操作。

以下是一个示例的C代码及其反汇编结果:```c#include <stdio.h>int main() {int a = 5;int b = 10;int c = a + b;printf("The sum of %d and %d is %d\n", a, b, c);return 0;}```反汇编结果:```Disassembly of section .text:0000000000401000 <main>:401000: 55 push %rbp401001: 48 89 e5 mov %rsp,%rbp401004: c7 45 fc 05 00 00 00 movl $0x5,-0x4(%rbp)40100b: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%rbp)401012: 8b 55 f8 mov -0x8(%rbp),%edx401015: 8b 45 fc mov -0x4(%rbp),%eax401018: 01 d0 a dd %edx,%eax40101a: 89 45 f4 mov %eax,-0xc(%rbp)40101d: 8b 45 f4 mov -0xc(%rbp),%eax401020: 89 c6 mov %eax,%esi401022: bf 00 00 00 00 mov $0x0,%edi401027: b8 00 00 00 00 mov $0x0,%eax40102c: e8 df ff ff ff callq 401010 <printf@plt>401031: b8 00 00 00 00 mov $0x0,%eax401036: c9 leaveq401037: c3 retq```这里展示了`main`函数的反汇编结果。

C语言反汇编-数据类型与常量

C语言反汇编-数据类型与常量

C语⾔反汇编-数据类型与常量反汇编(Disassembly) 即把⽬标⼆进制机器码转为汇编代码的过程,该技术常⽤于软件破解、外挂技术、病毒分析、逆向⼯程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解⾼级语⾔代码都有相当⼤的帮助,软件⼀切神秘的运⾏机制全在反汇编代码⾥⾯。

寻找OEP1.使⽤编译器 VS 2013 Express 版本,写代码,并关闭基地址随机化,编译采⽤debug版。

2.寻找OEP,x64dbg 载⼊,默认停在,回车跟进。

call <__tmainCRTStartup> 再次回车跟进。

下⽅看到⼀个安全cookie检测,主要防⽌在⽬标堆栈中瞎搞,⽽设计的安全机制。

编译器开启后,每次运⾏都会⽣成随机cookie,结束时会验证是否⼀致,防⽌瞎搞,⽼版本编译器中不存在这个选项,⼀开始开发⼈员也没想那么多,后来瞎搞的⼈多了,才加上的,主要是害怕⾃⼰的windows被玩坏。

继续向下看代码,看到以下代码就到家了,call 0x0041113B 跟进去就是main.数值类型变量:数值类型默认在32位编译器上是4字节存储的。

#include <stdio.h>int main(int argc, char* argv[]){int x = 10, y = 20, z = 0;printf("%d\n", x + y);return 0;}反汇编结果如下,如果在vc6下mov ecx,0xC 原因 int=4 3*4=0xC004113CC | 8DBD 1CFFFFFF | lea edi,dword ptr ss:[ebp-0xE4] | 取出内存地址004113D2 | B9 39000000 | mov ecx,0x39 | 指定要填充的字节数004113D7 | B8 CCCCCCCC | mov eax,0xCCCCCCCC | 将内存填充为0xcccccc004113DC | F3:AB | rep stosd | 调⽤rep指令,对内存进⾏初始化004113DE | C745 F8 0A000000 | mov dword ptr ss:[ebp-0x8],0xA | 对变量x初始化为10004113E5 | C745 EC 14000000 | mov dword ptr ss:[ebp-0x14],0x14 | 对变量y初始化为20004113EC | C745 E0 00000000 | mov dword ptr ss:[ebp-0x20],0x0 | 对变量c初始化为0004113F3 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] | 取出10并放⼊eax004113F6 | 0345 EC | add eax,dword ptr ss:[ebp-0x14] | 与20相加得到结果付给eax004113F9 | 8BF4 | mov esi,esp | 保存当前栈帧004113FB | 50 | push eax |004113FC | 68 58584100 | push consoleapplication1.415858 | 415858:"%d\n"00411401 | FF15 14914100 | call dword ptr ds:[<&printf>] |00411407 | 83C4 08 | add esp,0x8 |除了整数类型外,浮点数也是⼀种数值运算⽅式,将上⽅代码稍微修改后,编译并查看其反汇编代码.#include <stdio.h>int main(int argc, char* argv[]){double x = 1.05, y = 20.56, z = 0;z = x + y;printf("%f\n", z);return 0;}观察以下汇编代码,会发现前⾯的初始化部分完全⼀致,但后⾯则使⽤了xmm0寄存器,该寄存器是专门⽤于运算浮点数⽽设计的浮点运算模块默认⼤⼩就是64位,其将内存中的值取出来并放⼊xmm0中进⾏中转,然后复制到堆栈中,等待最后调⽤addsd命令完成对浮点数的加法运算,并将运算结果回写到缓冲区,最后调⽤打印函数实现输出.004113CC | 8DBD 10FFFFFF | lea edi,dword ptr ss:[ebp-0xF0] |004113D2 | B9 3C000000 | mov ecx,0x3C |004113D7 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |004113DC | F3:AB | rep stosd |004113DE | F2:0F1005 70584100 | movsd xmm0,qword ptr ds:[<__real@3ff0cccccccccccd>] | 将内存地址中的值取出并放⼊xmm0中004113E6 | F2:0F1145 F4 | movsd qword ptr ss:[ebp-0xC],xmm0 | 将数据放⼊堆栈中存储004113EB | F2:0F1005 80584100 | movsd xmm0,qword ptr ds:[<__real@40348f5c28f5c28f>] |004113F3 | F2:0F1145 E4 | movsd qword ptr ss:[ebp-0x1C],xmm0 |004113F8 | F2:0F1005 60584100 | movsd xmm0,qword ptr ds:[<__real@0000000000000000>] |00411400 | F2:0F1145 D4 | movsd qword ptr ss:[ebp-0x2C],xmm0 |00411405 | F2:0F1045 F4 | movsd xmm0,qword ptr ss:[ebp-0xC] | main.c:60041140A | F2:0F5845 E4 | addsd xmm0,qword ptr ss:[ebp-0x1C] | 对浮点数进⾏相加操作0041140F | F2:0F1145 D4 | movsd qword ptr ss:[ebp-0x2C],xmm0 | 结果放⼊堆栈等待被打印00411414 | 8BF4 | mov esi,esp |00411416 | 83EC 08 | sub esp,0x8 |00411419 | F2:0F1045 D4 | movsd xmm0,qword ptr ss:[ebp-0x2C] |0041141E | F2:0F110424 | movsd qword ptr ss:[esp],xmm0 |00411423 | 68 58584100 | push consoleapplication1.415858 | 415858:"%f\n"==L"春\n"00411428 | FF15 14914100 | call dword ptr ds:[<&printf>] |0041142E | 83C4 0C | add esp,0xC |字符与字符串变量:#include <stdio.h>int main(int argc, char* argv[]){char x = 'a', y = 'b',z = 'c';printf("x = %d --> y = %d --> z = %d", x,y,z);return 0;}反汇编结果如下,观察发现字符型的表现形式与整数类型基本⼀致,只是在数据位⼤⼩⽅⾯有所区别,如上int类型使⽤dword作为存储单位,⽽字符类型则默认使⽤byte形式存储。

C语言反汇编-函数与结构体

C语言反汇编-函数与结构体

C语⾔反汇编-函数与结构体反汇编(Disassembly) 即把⽬标⼆进制机器码转为汇编代码的过程,该技术常⽤于软件破解、外挂技术、病毒分析、逆向⼯程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解⾼级语⾔代码都有相当⼤的帮助,软件⼀切神秘的运⾏机制全在反汇编代码⾥⾯。

函数是任何⼀个⾼级语⾔中必须要存在的⼀个东西,使⽤函数式编程可以让程序可读性更⾼,充分发挥了模块化设计思想的精髓,今天我将带⼤家⼀起来探索函数的实现机理,探索编译器到底是如何对函数这个关键字进⾏实现的,从⽽更好地理解编译⾏为。

先来研究函数,函数是任何⼀门编程语⾔中都存在的关键字,使⽤函数式编程可以让程序可读性更⾼,充分发挥模块化设计思想的精髓,⽽函数传参的底层实现就是通过堆栈来实现的,⾸先我们来理解⼀下堆栈.当有参函数被执⾏时,通常会根据不同的调⽤约定来对参数进⾏压栈存储以STDcall约定为例,栈的调⽤原则是先进后出,最先被push到堆栈中的数据会被最后释放出来,⽽CPU中有两个寄存器专门⽤于维护堆栈的变化,ESP栈顶寄存器,EBP栈底寄存器(基址),这两个寄存器就像是好基友,两个寄存器相互配合,来让堆栈有条不乱.栈帧:就是ESP -> EBP 之间的空间,通常是调⽤函数时,函数的参数,从⼀个函数切换到另⼀个函数上,栈帧也会发⽣变化,当函数调⽤结束后,则需要平栈帧,不然会发⽣访问冲突,平栈帧的过程都是有编译器来解决的。

逆向分析函数实现机制函数与堆栈的基础: 下⾯⼀个简单的函数调⽤案例,我们来看看汇编格式是怎样的.#include <stdio.h>int VoidFunction(){printf("hello lyshark\n");return 0;}int main(int argc, char* argv[]){VoidFunction();return 0;}编译上⾯的这段代码,⾸先我们找到main函数的位置,然后会看到call 0x4110E1这条汇编指令就是在调⽤VoidFunction()函数,观察函数能发现函数下⽅并没有add esp,xxx这样的指令,则说明平栈操作是在函数的内部完成的,我们直接跟进去看看函数内部到底做了什么见不得⼈的事情.0041142C | 8DBD 40FFFFFF | lea edi,dword ptr ss:[ebp-0xC0] |00411432 | B9 30000000 | mov ecx,0x30 |00411437 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |0041143C | F3:AB | rep stosd |0041143E | E8 9EFCFFFF | call 0x4110E1 | 调⽤VoidFunction()00411443 | 33C0 | xor eax,eax | main.c:1300411445 | 5F | pop edi | main.c:14, edi:"閉\n"00411446 | 5E | pop esi | esi:"閉\n"00411447 | 5B | pop ebx |此时我们直接跟进call 0x4110E1这个函数中,分析函数内部是如何平栈的,进⼊函数以后⾸先使⽤push ebp保存当前EBP指针位置,然后调⽤mov ebp,esp这条指令来将当前的栈帧付给EBP也就是当基址使⽤,sub esp,0xC0则是分配局部变量,接着是push ebx,esi,edi则是因为我们需要⽤到这⼏个寄存器所以应该提前将原始值保存起来,最后⽤完了就需要pip edi,esi,ebx恢复这些寄存器的原始状态,并执⾏add esp,0xC0对局部变量进⾏恢复,最后mov esp,ebp还原到原始的栈顶指针位置,⾸尾呼应.004113C0 | 55 | push ebp | 保存栈底指针 ebp004113C1 | 8BEC | mov ebp,esp | 将当前栈指针给ebp004113C3 | 81EC C0000000 | sub esp,0xC0 | 抬⾼栈顶esp,开辟局部空间004113C9 | 53 | push ebx | 保存 ebx004113CA | 56 | push esi | 保存 esi004113CB | 57 | push edi | 保存 edi004113CC | 8DBD 40FFFFFF | lea edi,dword ptr ss:[ebp-0xC0] | 取出次函数可⽤栈空间⾸地址004113D2 | B9 30000000 | mov ecx,0x30 | ecx:"閉\n", 30:'0'004113D7 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |004113DC | F3:AB | rep stosd |004113DE | 8BF4 | mov esi,esp | main.c:5004113E0 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113E5 | FF15 14914100 | call dword ptr ds:[<&printf>] | 调⽤printf004113EB | 83C4 04 | add esp,0x4 | 降低栈顶esp,释放printf局部空间004113EE | 3BF4 | cmp esi,esp | 检测堆栈是否平衡,ebp!=esp则不平衡004113F0 | E8 46FDFFFF | call 0x41113B | 堆栈检测函数:检测平衡,不平衡则报错004113F5 | 33C0 | xor eax,eax | main.c:6004113F7 | 5F | pop edi | 还原寄存器edi004113F8 | 5E | pop esi | 还原寄存器esi004113F9 | 5B | pop ebx | 还原寄存器ebx004113FA | 81C4 C0000000 | add esp,0xC0 | 恢复esp,还原局部变量00411400 | 3BEC | cmp ebp,esp |00411402 | E8 34FDFFFF | call 0x41113B |00411407 | 8BE5 | mov esp,ebp | 还原原始的ebp指针00411409 | 5D | pop ebp |0041140A | C3 | ret |上⽅的代码其实默认⾛的是STDCALL的调⽤约定,⼀般情况下在Win32环境默认遵循的就是STDCALL,⽽在Win64环境下使⽤的则是FastCALL,在Linux系统上则遵循SystemV的约定,这⾥我整理了他们之间的异同点.这⾥我们来演⽰CDECL的调⽤约定,其实我们使⽤的Printf()函数就是在遵循__cdecl()约定,由于Printf函数可以有多个参数传递,所以只能使⽤__cdecl()约定来传递参数,该约定的典型特点就是平栈不在被调⽤函数内部完成,⽽是在外部通过使⽤⼀条add esp,0x4这种⽅式来平栈的.004113E0 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113E5 | FF15 14914100 | call dword ptr ds:[<&printf>] |004113EB | 83C4 04 | add esp,0x4 | 平栈004113EE | 3BF4 | cmp esi,esp |004113F0 | E8 46FDFFFF | call 0x41113B |004113F5 | 8BF4 | mov esi,esp | main.c:6004113F7 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113FC | FF15 14914100 | call dword ptr ds:[<&printf>] | 平栈00411402 | 83C4 04 | add esp,0x4 |在使⽤Release版对其进⾏优化的话,此段代码将会采取复写传播优化,将每次参数平衡的操作进⾏归并,⼀次性平衡栈顶指针esp,从⽽可以⼤⼤的提⾼程序的执⾏效率,汇编代码如下:004113E0 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113E5 | FF15 14914100 | call dword ptr ds:[<&printf>] |004113F7 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113FC | FF15 14914100 | call dword ptr ds:[<&printf>] |00411402 | 83C4 04 | add esp,0x8 | ⼀次性平栈加上0x8,平了前⾯的2个push通过以上分析发现_cdecl与_stdcall两者只在参数平衡上有所不同,其余部分都⼀样,但经过优化后_cdecl调⽤⽅式的函数在同⼀作⽤域内多次使⽤,会在效率上⽐_stdcall髙,这是因为_cdecl可以使⽤复写传播,⽽_stdcall的平栈都是在函数内部完成的,⽆法使⽤复写传播这种优化⽅式.除了前⾯的两种调⽤约定以外_fastcall调⽤⽅式的效率最髙,其他两种调⽤⽅式都是通过栈传递参数,唯独_fastcall可以利⽤寄存器传递参数,但由于寄存器数⽬很少,⽽参数相⽐可以很多,只能量⼒⽽⾏,故在Windows环境中_fastcall的调⽤⽅式只使⽤了ECX和EDX寄存器,分别传递第1个参数和第2个参数,其余参数传递则依然使⽤堆栈传递.#include <stdio.h>void _fastcall VoidFunction(int x,int y,int z,int a){printf("%d%d%d%d\n", x, y, z, a);}int main(int argc, char* argv[]){VoidFunction(1,2,3,4);return 0;}反汇编后观察代码发现call 0x4110E6就是在调⽤我们的VoidFunction()函数在调⽤之前分别将参数压⼊了不同的寄存器和堆栈中,接着我们继续跟进到call函数内部,看它是如何取出参数的.0041145E | 6A 04 | push 0x4 | 第四个参数使⽤堆栈传递00411460 | 6A 03 | push 0x3 | 第三个参数使⽤堆栈传递00411462 | BA 02000000 | mov edx,0x2 | 第⼆个参数使⽤edx传递00411467 | B9 01000000 | mov ecx,0x1 | 第⼀个参数使⽤ecx传递0041146C | E8 75FCFFFF | call 0x4110E6 |00411471 | 33C0 | xor eax,eax | main.c:11进⼊call 0x4110E6这个函数中,观察发现⾸先会通过mov指令将前两个参数提取出来,然后再从第四个参数开始依次将参数取出来并压栈,最后让Printf函数成功调⽤到.004113E0 | 8955 EC | mov dword ptr ss:[ebp-0x14],edx | edx => 提取出第⼆个参数004113E3 | 894D F8 | mov dword ptr ss:[ebp-0x8],ecx | ecx => 提取出第⼀个参数004113E6 | 8BF4 | mov esi,esp | main.c:5004113E8 | 8B45 0C | mov eax,dword ptr ss:[ebp+0xC] | 保存第四个参数004113EB | 50 | push eax |004113EC | 8B4D 08 | mov ecx,dword ptr ss:[ebp+0x8] | 保存第三个参数004113EF | 51 | push ecx |004113F0 | 8B55 EC | mov edx,dword ptr ss:[ebp-0x14] | 保存第⼆个参数004113F3 | 52 | push edx |004113F4 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] | 保存第⼀个参数004113F7 | 50 | push eax |004113F8 | 68 58584100 | push consoleapplication1.415858 | 415858:"%d%d%d%d\n"004113FD | FF15 14914100 | call dword ptr ds:[<&printf>] |00411403 | 83C4 14 | add esp,0x14 | 平栈定义并使⽤有参函数: 我们给函数传递些参数,然后分析其反汇编代码,观察代码的展⽰形式.#include <stdio.h>int Function(int x,float y,double z){if (x = 100){x = x + 100;y = y + 100;z = z + 100;}return (x);}int main(int argc, char* argv[]){int ret = 0;ret = Function(100, 2.5, 10.245);printf("返回值: %d\n", ret);return 0;}下⽅的反汇编代码就是调⽤函数ret = Function()的过程,该过程中可看出压栈顺序遵循的是从后向前压⼊的.0041145E | C745 F8 00000000 | mov dword ptr ss:[ebp-0x8],0x0 | main.c:1700411465 | 83EC 08 | sub esp,0x8 | main.c:1800411468 | F2:0F1005 70584100 | movsd xmm0,qword ptr ds:[<__real@40247d70a3d70a3d>] | 将10.245放⼊XMM0寄存器00411470 | F2:0F110424 | movsd qword ptr ss:[esp],xmm0 | 取出XMM0中内容,并放⼊堆栈00411475 | 51 | push ecx |00411476 | F3:0F1005 68584100 | movss xmm0,dword ptr ds:[<__real@40200000>] | 将2.5放⼊XMM00041147E | F3:0F110424 | movss dword ptr ss:[esp],xmm0 | 同理00411483 | 6A 64 | push 0x64 | 最后⼀个参数10000411485 | E8 51FDFFFF | call 0x4111DB | 调⽤Function函数0041148A | 83C4 10 | add esp,0x10 |0041148D | 8945 F8 | mov dword ptr ss:[ebp-0x8],eax | 将返回值压栈00411490 | 8BF4 | mov esi,esp | main.c:1900411492 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] |00411495 | 50 | push eax |00411496 | 68 58584100 | push consoleapplication1.415858 | 415858:"返回值: %d\n"0041149B | FF15 14914100 | call dword ptr ds:[<&printf>] | 输出结果004114A1 | 83C4 08 | add esp,0x8 |压栈完成以后我们可以继续跟进call 0x4111DB这个关键CALL,此处就是运算数据的关键函数,跟进去以后,可发现其对浮点数的运算,完全是依靠XMM寄存器实现的.004113F1 | 8945 08 | mov dword ptr ss:[ebp+0x8],eax |004113F4 | F3:0F1045 0C | movss xmm0,dword ptr ss:[ebp+0xC] | main.c:8004113F9 | F3:0F5805 8C584100 | addss xmm0,dword ptr ds:[<__real@42c80000>] |00411401 | F3:0F1145 0C | movss dword ptr ss:[ebp+0xC],xmm0 |00411406 | F2:0F1045 10 | movsd xmm0,qword ptr ss:[ebp+0x10] | main.c:90041140B | F2:0F5805 80584100 | addsd xmm0,qword ptr ds:[<__real@4059000000000000>] |00411413 | F2:0F1145 10 | movsd qword ptr ss:[ebp+0x10],xmm0 |00411418 | 8B45 08 | mov eax,dword ptr ss:[ebp+0x8] | main.c:11向函数传递数组/指针: 这⾥我们以⼀维数组为例,⼆维数组的传递其实和⼀维数组是相通的,只不过在寻址⽅式上要使⽤⼆维数组的寻址公式,此外传递数组其实本质上就是传递指针,所以数组与指针的传递⽅式也是相通的.#include <stdio.h>void Function(int Array[], int size){for (int i = 0; i<size; ++i){printf("输出元素: %d \n", Array[i]);}}int main(int argc, char* argv[]){int ary[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };Function(ary, 10);return 0;}以下代码就是Function(ary,10)函数的调⽤代码,⾸先压栈传递0A也就是10,接着传递ary⾸地址,最后调⽤call指令.004114B4 | 6A 0A | push 0xA | 10004114B6 | 8D45 D4 | lea eax,dword ptr ss:[ebp-0x2C] | ary ⾸地址004114B9 | 50 | push eax | push eax004114BA | E8 63FCFFFF | call 0x411122 | 调⽤Function()004114BF | 83C4 08 | add esp,0x8 | 堆栈修复函数中返回指针,其实就是返回⼀个内存地址,我们可以打印出这个内存地址具体的值,如下是⼀段测试代码,这⾥的原理于上⽅都是相通的,此处就不在浪费篇幅了.#include <stdio.h>int GetAddr(int number){int nAddr;nAddr = *(int*)(&number-1);return nAddr;}int main(int argc, char* argv[]){int address = 0;address = GetAddr(100);printf("%x\n",address);return 0;}函数的参数传递就到此结束了,其实其他的参数传递⽆外乎就是上⾯的这⼏种传递形式,只是在某些实现细节上略有差异,但⼤体上也就是这些东西,在真正的逆向过程中还需要考虑编译器的版本等具体细节,每⼀个编译器在实现参数传递上都略微不同,这也就是编译特性所影响的,我们应该灵活运⽤这些知识,才能更好地分析这些字节码.变量作⽤域解析接着我们来研究⼀下变量的作⽤域,在C语⾔中作⽤域可分为局部变量与全局变量,两种变量⼜分为静态变量和动态变量,接下来我们将通过反汇编学习研究他们之间的异同点.探索全局变量的奥秘: 全局变量与常量有很多相似的地⽅,两者都是在程序执⾏前就存在的,这是因为编译器在编译时就将其写⼊到的程序⽂件⾥,但是在PE⽂件中的只读数据节⾥,常量的节属性被修饰为不可写⼊,⽽全局变量和静态变量的属性为可读可写,PE⽂件加载器在加载可执⾏⽂件时,会率先装载这些常量与全局变量,然后才会运⾏程序⼊⼝代码,因此这些全局变量可以不受作⽤域的影响,在程序中的任何位置都可以被访问和使⽤,来看⼀段C代码:#include <stdio.h>int number1 = 1;int number2 = 2;int main(int argc, char* argv[]){scanf("%d", &number1);printf("您输⼊的数字: %d\n", number1);number2 = 100;return 0;}如下反汇编代码可以看出,全局变量的访问是直接通过⽴即数push consoleapplication1.415858访问的,此⽴即数是通过编译器编译时就写⼊到了程序中的,所以也就可以直接进⾏访问了.004113E0 | 68 00804100 | push <consoleapplication1._number1> | 此处的压栈参数就是全局变量004113E5 | 68 58584100 | push consoleapplication1.415858 | 415858:"%d"004113EA | FF15 10914100 | call dword ptr ds:[<&scanf>] |004113F0 | 83C4 08 | add esp,0x8 | 保存第⼆个参数004113F3 | 3BF4 | cmp esi,esp |004113F5 | E8 41FDFFFF | call 0x41113B |004113FA | 8BF4 | mov esi,esp | main.c:9004113FC | A1 00804100 | mov eax,dword ptr ds:[<_number1>] |00411401 | 50 | push eax |00411402 | 68 5C584100 | push consoleapplication1.41585C | 41585C:"您输⼊的数字: %d\n"00411407 | FF15 18914100 | call dword ptr ds:[<&printf>] |0041140D | 83C4 08 | add esp,0x8 |00411410 | 3BF4 | cmp esi,esp |00411412 | E8 24FDFFFF | call 0x41113B |00411417 | C705 04804100 64000000 | mov dword ptr ds:[<_number2>],0x64 | main.c:11, 64:'d'00411421 | 33C0 | xor eax,eax | main.c:12探索局部变量的奥秘: 局部变量的访问是通过栈指针相对间接访问,也就是说局部变量是程序动态创建的,通常是调⽤某个函数或过程时动态⽣成的,局部变量作⽤域也仅限于函数内部,且其地址也是⼀个未知数,编译器⽆法预先计算.#include <stdio.h>int main(int argc, char* argv[]){int num1 = 0;int num2 = 1;scanf("%d", &num1);printf("%d", num1);num2 = 10;return 0;}反汇编代码,局部变量就是通过mov dword ptr ss:[ebp-0x8],0x0动态开辟的空间,其作⽤域就是在本函数退出时消亡.004113DE | C745 F8 00000000 | mov dword ptr ss:[ebp-0x8],0x0 | 申请局部变量004113E5 | C745 EC 01000000 | mov dword ptr ss:[ebp-0x14],0x1 | main.c:6004113EC | 8BF4 | mov esi,esp | main.c:8004113EE | 8D45 F8 | lea eax,dword ptr ss:[ebp-0x8] |004113F1 | 50 | push eax |004113F2 | 68 58584100 | push consoleapplication1.415858 | 415858:"%d"004113F7 | FF15 10914100 | call dword ptr ds:[<&scanf>] |说到局部变量,不得不提起局部静态变量,局部静态变量的声明只需要使⽤static关键字声明,该变量⽐较特殊,他不会随作⽤域的结束⽽消亡,并且也是在未进⼊作⽤域之前就已经存在了,其实局部静态变量也是全局变量,只不过它的作⽤域被限制在了某⼀个函数内部⽽已,所以它本质上还是全局变量,来⼀段代码验证⼀下:#include <stdio.h>int main(int argc, char* argv[]){static int g_number = 0;for (int x = 0; x <= 10; x++){g_number = x;printf("输出: %d\n", g_number);}return 0;}观察这段反汇编代码,你能够清晰的看出,同样是使⽤mov eax,dword ptr ds:[<g_number>]从全局数据区取数据的,这说明局部变量声明为静态属性以后,就和全局变量变成了⼀家⼈了.004113DE | C745 F8 00000000 | mov dword ptr ss:[ebp-0x8],0x0 | main.c:7004113E5 | EB 09 | jmp 0x4113F0 |004113E7 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] |004113EA | 83C0 01 | add eax,0x1 |004113ED | 8945 F8 | mov dword ptr ss:[ebp-0x8],eax |004113F0 | 837D F8 0A | cmp dword ptr ss:[ebp-0x8],0xA | A:'\n'004113F4 | 7F 27 | jg 0x41141D |004113F6 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] | main.c:9004113F9 | A3 30814100 | mov dword ptr ds:[<g_number>],eax |004113FE | 8BF4 | mov esi,esp | main.c:1000411400 | A1 30814100 | mov eax,dword ptr ds:[<g_number>] | 与全局变量是⼀家⼈00411405 | 50 | push eax |00411406 | 68 58584100 | push consoleapplication1.415858 | 415858:"输出: %d\n"0041140B | FF15 14914100 | call dword ptr ds:[<&printf>] |00411411 | 83C4 08 | add esp,0x8 |00411414 | 3BF4 | cmp esi,esp |00411416 | E8 1BFDFFFF | call 0x411136 |0041141B | EB CA | jmp 0x4113E7 | main.c:110041141D | 33C0 | xor eax,eax | main.c:12探索堆变量的奥秘: 堆变量是最容易识别的⼀种变量类型,因为分配堆区的函数就⼏个calloc/malloc/new等,所以这类变量往往能被调试器直接补货到,这种变量同样属于局部变量的范畴,因为它也是通过函数动态申请的⼀段内存空间,这⾥只给出⼀个案例吧,反编译⼤家可以⾃⼰研究,这⼀个是很简单的了.#include <stdlib.h>#include <stdio.h>int main(int argc, char* argv[]){int *pMalloc = (int*)malloc(10);printf("变量地址: %x", pMalloc);free(pMalloc);return 0;}结构体与共⽤体针对C语⾔的反汇编,就剩⼀个结构体与共⽤体了,这⾥的内容⽐较少,我就不再新的⽂章⾥写了,直接在这⾥把它给写完,C语⾔的反汇编就到此结束。

用Visual studio 2013 写C程序及反汇编

用Visual studio 2013 写C程序及反汇编

用Visual studio 2013 写C程序及反汇编1.创建一个新的C++项目
创建一个win32控制台程序,如下图
之后,修改项目名称、位置等信息后,点ok
在application settings中设置:Empty project,点击finish。

2.创建C程序
在解决方案管理器的源上右击,选择“add>new item”,在弹出的窗口中选择C++file,命名后,点击添加。

选择“调试>开始调试”,进行调试
需要注意的是:当选择“sart without debugging”时,system函数就不需要了
3.反编译
在调试开始的情况下下,选择“debug> break all”进入中断模式,再选择“debug>windows>
disassembly”,打开反编译窗口
即可在反编译窗口中查看C程序的机器语言与汇编语言。

CC++反汇编-各种表达式(加减乘)

CC++反汇编-各种表达式(加减乘)

CC++反汇编-各种表达式(加减乘)C/C++反汇编-各种表达式(加减乘)基于vs2019的反汇编优化对于vs来说⼀般的优化有两种⽅案:O1:⽣成可执⾏⽂件空间⼩O2:执⾏效率⾼在vs2019中的release默认是采⽤的O2⽅案来处理也就是执⾏效率优先,⽽在debug版本中为了调试⽐较⽅便可能就会优化⽐较少。

接下来的反汇编会在release和debug中两个分开呈现常量传播编译期间会将可计算结果的变量转化为常量来处理⽐如int c = 1;printf("%d\n",c);这⾥会直接⽤1来替代c⽽不是在内存中进⾏读取常量折叠当⼏个常量进⾏计算的时候,编译器会直接将计算的结果来处理int c=1+2-3;会直接把c赋值为-1加法直接上代码DEBUG下//加法反汇编//变量赋值int Number1 = 0;00C11DE8 mov dword ptr [Number1],0int Number2 = 0;00C11DEF mov dword ptr [Number2],0//常量加常量Number1 = 1 + 2;00C11DF6 mov dword ptr [Number1],3//常量加变量Number1 = Number2 + 1;00C11DFD mov eax,dword ptr [Number2]00C11E00 add eax,100C11E03 mov dword ptr [Number1],eax//变量加变量Number1 = Number1 + Number2;00C11E06 mov eax,dword ptr [Number1]00C11E09 add eax,dword ptr [Number2]00C11E0C mov dword ptr [Number1],eax这⾥可以看到当两个常量相加的时候,编译器会直接计算值来处理⽽不是⼀个⼀个寄存器来读取,这样会直接减少运⾏量Release下//加法反汇编//变量赋值int Number1 = 0;int Number2 = 0;//常量加常量Number1 = 1 + 2;//常量加变量Number1 = Number2 + 1;Number1 = Number1 + Number2;这⾥直接没有了那么为什么呢?因为这⾥采⽤了各种优化,在编译过程中,编译器通常会采⽤两种优化⽅式:常量传播和常量折叠来处理这⾥可以采⽤对变量进⾏不是常量的赋值⽐如:int Number = argcargc是main函数中的第⼀个参数,需要输⼊才能识别,所以编译器不会将其折叠减法减法和加法类似,只是对于减法的处理是利⽤对补码的加法来处理。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
首先是winmain :
_text segment
_wmain proc
push ebp ;保存旧的ebp
mov ebp, esp ; ebp保存当前栈的位置
push -1 ;建立seh(structured exception handler链
; -1表示表头,没有prev
push __ehhandler$_wmain ; seh异常处理程序的地址
call __rtc_checkesp ;检查esp值,这个时候esp应该和ebp匹配,否则说明出现了栈不平衡的情况,这种情况下调用子程序报错
mov esp, ebp ;恢复ebp到esp
pop ebp ;恢复原来的ebp值
ret 0
_wmain endp
专门用于seh的子程序。__unwindfunclet$_wmain$0当异常发生的时候被调,负责进行栈展开,主要是调用析构函数。__ehhandler$_wmain则是在exception被抛出的时候调用。text$x segment
mov edx, dword ptr [esp+8] ; esp =当前的fs:0, [esp + 8] =之前的seh结构,也就是main中建立的
一个简单的C++程序反汇编解析
本系列主要从汇编角度研究c++语言机制和汇编的对应关系。第一篇自然应该从最简单的开始。c++的源代码如下:
class my_class
{
public :
my_class(
{
m_member = 1;
}
void method(int n
{
m_member = n;
}
~my_class(
mov eax, cccccccch
rep stosd
mov eax, dword ptr ___security_cookie
xor eax, ebp
push eax ; ebp ^ __security_cookie压栈保存lea eax, dword ptr [ebp-0ch] ; ebp-0ch是新的seh链的结构地址(刚压入栈中的栈地址
__unwindfunclet$_wmain$0: ;当seh发生的时候会调用该函数,析购a_class
lea ecx, dword ptr [ebp-14h] ; ecx = [ebp – 14h],也就是a_class的地址
jmp ??1my_class@@qae@xz ;调用my_class::~my_class __ehhandler$_wmain:
old ebp
高地址
main接着后面调用my_class的构造函数
lea ecx, dword ptr [ebp-14h]
call ??0my_class@@qae@xz ;调用my_class::my_class, ??my_class@@qae@xz是经过name mangling后的名字
mov dword ptr [ebp-4], 0 ;进入__try块,ept块
mov dword ptr fs:0, eax ;设置到teb中作为当前active的seh链表末尾
到此为止栈的内容是这样的:
低地址
security cookie after xor
edi
esi
ebx
local stack: d8h
old fs:0
__ehhandler$_wmain
ffffffffh
接着调用my_class::method
push 10 ;参数入栈
lea ecx, dword ptr [ebp-14h] ;遵循thiscall调用协定, ecx存放的是this指针
call ?method@my_class@@qaexh@z ;调用子程序my_class:method(10
之后是析构:
mov dword ptr [ebp-e0h], 0 ;用来放置返回值
mov dword ptr [ebp-4], -1 ;标记try的正常结束
lea ecx, dword ptr [ebp-14h] ; a_class的地址作为this存入ecx
call ??1my_class@@qae@xz ; my_class::~my_class
mov eax, dword ptr fs:0 ; fs:0指向teb的内容,头4个字节是当前seh链的地址
push eax ;保存起来
sub esp, d8h ;分配d8h字节的空间
push ebx
push esi
push edi
lea edi, dword ptr [ebp-e4h] ; e4h = d8h + 4 * 3,跳过中间ebx, esi, edi mov ecx, 36h ; 36h*4h=d8h,也就是用36h个cccccccch填满刚才分配的d8h字节空间
mov eax, dword ptr [ebp-e0h] ;返回值按照约定放入eax中
main函数退出代码如下:
push edx
mov ecx, ebp
push eax
lea edx, dword ptr $ln7@wmain
call @_rtc_checkstackvars@8 ;检查栈
pop eax
pop edx
mov ecx, dword ptr [ebp-0ch] ;取出之前保存的旧的fs:0,并恢复mov dword ptr fs:0, ecx
pop ecx
pop edi
pop esi
pop ebx
add esp, e4h ;退掉分配的d8h +建立seh链所需的0ch字节
cmp ebp, esp
{
m_member = 0;
}
private :
int m_member;
};
int _tmain(int argc, _tchar* argv[]
{
my_class a_class;
a_class.method(10;
return 0;
}
可以直接debug的时候看到assembly代码,不过这样获得的代码注释比较少。比较理想的方法是利用vc编译器的一个选项/fas来生成对应的汇编代码。/fas还会在汇编代码中加入注释注明和c++代码的对应关系,十分有助于分析。build代码便可以在输出目录下发现对应的.asm文件。本文将逐句分析汇编代码和c++的对应关系。
相关文档
最新文档