从底层分析c和类c语言

来源:互联网 发布:什么叫it行业 编辑:程序博客网 时间:2024/05/17 22:15

 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下Linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类C语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为c语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。


 后话:本人虽然非常热爱计算机技术,但是由于有很多琐事缠身且学习时间不算长(因为现在大三了,而且本专业不是计算机是电子,又要把期末给混过去,又要抽时间自学),故难免见识有些局限性。那些玩内核的大牛见了不要见笑。


http://blog.csdn.net/jggyyhh/article/details/50429886?locationNum=5&fps=1

 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类c语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为C语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。


 后话:本人虽然非常热爱计算机技术,但是由于有很多琐事缠身且学习时间不算长(因为现在大三了,而且本专业不是计算机是电子,又要把期末给混过去,又要抽时间自学),故难免见识有些局限性。那些玩内核的大牛见了不要见笑。


http://blog.csdn.net/jggyyhh/article/details/50429886?locationNum=5&fps=1

   写这篇文章的目的是对近期底层学习的总结,也算是勉励自己吧,毕竟是光靠兴趣苦逼自学不是自己专业的东西要承受很多压力。


 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类c语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为C语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。


 后话:本人虽然非常热爱计算机技术,但是由于有很多琐事缠身且学习时间不算长(因为现在大三了,而且本专业不是计算机是电子,又要把期末给混过去,又要抽时间自学),故难免见识有些局限性。那些玩内核的大牛见了不要见笑。


http://blog.csdn.net/jggyyhh/article/details/50429886?locationNum=5&fps=1


 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类c语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为C语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。


 后话:本人虽然非常热爱计算机技术,但是由于有很多琐事缠身且学习时间不算长(因为现在大三了,而且本专业不是计算机是电子,又要把期末给混过去,又要抽时间自学),故难免见识有些局限性。那些玩内核的大牛见了不要见笑。


http://blog.csdn.net/jggyyhh/article/details/50429886?locationNum=5&fps=1

 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类c语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为C语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。


 后话:本人虽然非常热爱计算机技术,但是由于有很多琐事缠身且学习时间不算长(因为现在大三了,而且本专业不是计算机是电子,又要把期末给混过去,又要抽时间自学),故难免见识有些局限性。那些玩内核的大牛见了不要见笑。


http://blog.csdn.net/jggyyhh/article/details/50429886?locationNum=5&fps=1

   


 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类c语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为C语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。


 后话:本人虽然非常热爱计算机技术,但是由于有很多琐事缠身且学习时间不算长(因为现在大三了,而且本专业不是计算机是电子,又要把期末给混过去,又要抽时间自学),故难免见识有些局限性。那些玩内核的大牛见了不要见笑。


http://blog.csdn.net/jggyyhh/article/details/50429886?locationNum=5&fps=1

   写这篇文章的目的是对近期底层学习的总结,也算是勉励自己吧,毕竟是光靠兴趣苦逼自学不是自己专业的东西要承受很多压力。


 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类c语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为C语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。


 后话:本人虽然非常热爱计算机技术,但是由于有很多琐事缠身且学习时间不算长(因为现在大三了,而且本专业不是计算机是电子,又要把期末给混过去,又要抽时间自学),故难免见识有些局限性。那些玩内核的大牛见了不要见笑。


http://blog.csdn.net/jggyyhh/article/details/50429886?locationNum=5&fps=1



   写这篇文章的目的是对近期底层学习的总结,也算是勉励自己吧,毕竟是光靠兴趣苦逼自学不是自己专业的东西要承受很多压力。


 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类c语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为C语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。


 后话:本人虽然非常热爱计算机技术,但是由于有很多琐事缠身且学习时间不算长(因为现在大三了,而且本专业不是计算机是电子,又要把期末给混过去,又要抽时间自学),故难免见识有些局限性。那些玩内核的大牛见了不要见笑。


   写这篇文章的目的是对近期底层学习的总结,也算是勉励自己吧,毕竟是光靠兴趣苦逼自学不是自己专业的东西要承受很多压力。


 要想深入理解C语言就不得不要知道几个知识点:

1.众所周知用任意一高级语言(不是脚本语言)写的代码都要经过类似:预处理->编译成汇编代码(compilation)->汇编(assembly)->连接(linking)这样的阶段。其中预处理产生.i文件,compilation产生.s文件,assembly产生.o文件,最后连接才会产生可执行文件,.o文件中不同机器上是不同的,而Java的能够“一次编译,到处运行“是因为Java不会像c那样在不同机器上产生不同的.o文件,而是用jvm虚拟机屏蔽了不同机器上的不同之处,于是只有不同的机子上都要Java的插件,一次编译后的文件就能到处运行。(可以想象的是为啥Android机的硬件配置往往要比iphone好,因为android机正用了java的技术故中间多了一次转换过程当然效率要比用object-c编的iOS程序低,不过据说最近jvm采用了一些技术将效率提高了不小,不过这个我还没研究过就不说了)。

2.当你的代码被编译器编译成可执行文件(不一定是exe,这是个误区,以PE文件为例,这些格式其实是在PE文件头偏移量为0016h处的Characteristics字段表明的,如果是exe这一字段为0x0f01),不同的操作系统下的可执行文件是不同的,Linux下为ELF,windows下为PE。由于我比较熟悉PE文件的格式,我就拿PE文件做个例子,你反汇编任意一个Windows的可执行文件你就会发现每个文件都被分成了很多个块,大致分成了.text,.idata,.rdata,.data,.rsrc块,这是为啥呢?这其实是为了方便程序映射到进程内存空间,因为为了方便管理和实现各种机制,进程的内存空间是分段的,在linux下一个进程的内存空间大致是这样:

其中进程的用户态的线性地址空间是从0x00000000到0xbfffffff,也就是一般的应用程序跑的线性地址空间(内存中每一个字节的数据被赋予一个地址),注意这里是线性地址空间, 你反汇编左侧的地址空间是逻辑地址,

如上图左侧的是逻辑地址,(这些地址都是16进制),逻辑地址要经过分段机制才能指向线性地址,而线性地址要经过分页才能指向物理地址(物理地址才是内存条上),(有些操作系统没有分段机制,逻辑地址等于线性地址)。这其中的细节展开是一章的内容,我就不多说,有兴趣的可以看下linux内核方面的书籍,你要清楚你的程序要跑起来必定cpu要为你的程序分配内存(其实还有很多东西),跑起来后看情况你的程序会以一个进程或者线程的状态出现在操作系统上(进程的描述可不是简单的pid就能标识的,而是task_struct这个被称为进程描述符的东西同样这些东西要参考内核方面的书籍)。下图是windows可执行文件的映射(比较懒啊,直接把笔记弄上去了尴尬):

我想经过我这样一番描述你大致模糊的清楚一个程序在你电脑的存在和运行是啥情况了,下面我将分析语句了微笑


静态作用域:

没错,这就是《编译原理》里的那部分内容,不过我加上了我的从底层上的一些见解即解释,什么是静态作用域呢?通俗的说就是你通过源代码就能判断一个声明的作用范围,在

这个范围内所有对该声明变量的使用都指向那个声明。c语言的(类c语言)作用域规则是基于程序结构的(块),也就是和你的“{ }”符号的使用有关,如下图:


最后一个cout<<a<<b打印的a的值为1,因为在一个块(也是一个作用域)内的语句会首先使用该块内的声明,如B3域内cout<<a<<b打印的a的值是3,如果该块内没有这个声明,则

找到其父块,如B3域内cout<<a<<b打印的b的值是2。其实,int a是一种声明,int a=1也是声明,而定义是在你声明过后类似于  a=1这种语句,其实定义可以看成是定值,而a这个东西

只是一个名字,名字和变量(内存位置也即不同的内存地址)的关系如图:


    在不同域名字可以一样,但是因为其环境(作用域)不同其实它指向的内存位置是不同的,且你在定义之前必须声明,要不然它不清楚是对哪个内存位置进行赋值操作,如:B2域和B3域都有个int a =*的声明,其实它们分别指向不同的内存位置所以可以存不同的值。故定义(定值)所指向的变量(实际上是内存位置)是取决于作用域的声明的,即使是相同语句(名字)也会随环境变化而赋予不同变量值。又因为C语言在作用域内是按顺序执行语句的,也就有了这个例子网上找的例子,其中int max(int,int);声明了个函数变量(有了一个相应的内存地址),它的作用域为整个程序,但是这个变量在这个作用域内还没有值,int main()函数下是另一个作用域,在这个作用域内并没有max函数的声明故它调用其父作用域的声明,在其父作用域内有个函数声明(因为在执行main函数前就执行了int max(int,int);),故函数成功调用,此例中你将int max(int,int);放在main()函数之下就不行了,这是因为C语言是顺序执行的,其实此例的最后7行既可以说是定义也可以说是声明,就像int a=1一样。

    而java,c++与c语言的不同在于,它多了public,private,protected等等这些限制作用域的关键字,而不像c语言那样仅仅靠“{ }”程序员自己限制作用域范围或者函数(不同函数也是个不同作用域),与是乎java就有了许多个不同类型的被封装的作用域,比如说public声明的方法能被所有定义的类的对象调用。。。于是乎产生了对象这种东西我认为c语言不是面向对象的语言的根本原因是它没有对作用域进行自定义化的封装,没有产生有独特性质方法(在c语言是函数)的“对象”,通俗上说是没有类似public这样的关键字。(只是个人见解,大牛见了不要见笑)



接下是重点了:  上面说了那么多其实都没深入到汇编层次也没用上之前我叙述的进程内存的知识,也没有从底层给出不同作用域的实现机制,接下来才是关键之处。

     这是我简化后的linux进程内存存储方式(类似于第一张图,其实第一张图也是简略后的,.text段和.data段中有很多其它的东西(segement),毕竟你一个比较大的程序要有动态链接库还有一些则与linux中ANSI C的函数库libc的函数有关,这些东西要不涉及内核调用要不和库函数有关有的甚至与gcc编译器有关),线性地址从左到右依次变大,其中.text段中存有只读的二进制文件, .data 存有全局初始化变量如:static Int a=0 .bss段存了全局未初始化变量如:static Int a。你会纳闷那我函数内存储的变量 如在B2中的int b=2,b所指向的变量存在哪呢?其实它们都存在stack这里面,stack也就是栈的意思,只要没有全局化声明的变量都存在stack里面。声明在static内的变量是固定的(地址固定),也即一旦你在程序里面改变了这个名字的值那么它会在你程序运行周期内永久改变,无论你改变的语句所在的作用域是啥。接下来我将通过反汇编一些程序为你揭示那些普通函数的变量是怎样在栈内存在的:(没学过汇编的朋友接下来的内容你可能会看不懂,但是没法,我的主题是深入理解c语言,不过这之上的内容我认为也是很有意义的)

     首先在linux用 objdump -S  test1.o  命令反汇编我之前编好的test.o的还未链接成可执行文件的.o文件(可用 gcc -c -o test1.c 命令产生,-S是将汇编代码和从语言同时显示),因为.o文件不是可执行文件没链接,故当在一个函数内调用另一个函数时没有call语句,.o文件链接后会产生很多不是源代码的段,这是系统自带的调用或者库链接甚至有些段是用来传递用户态进程的寄存器数据给内核的,这些机制的存在我认为不仅仅出于系统功能的作用,还有很大部分出于安全性,最早之前的栈分配方式是非常容易被缓冲区攻击的。之前的C语言程序反汇编的代码大致是这样的点此处查看,栈的保护方式多种多样,有的在返回地址处加垫片,有的用ASLR技术也就是所谓的地址空间随机化技术,在我分析完代码后会简单地演示这种技术,这些技术随着linux不断发展而更新,使得操作系统越来越安全,这就是开源的魅力,相当于全世界的高手都在参与操作系统的更新,这也是linux的魅力与活力。我接触过shellcode的编写,虽然依旧很菜,但是我还是大致知道常见的几种缓冲区攻击和一些过时的漏洞尴尬。好了,言归正传,由于我对现在版本内核的保护机制不了解,故有些语句作用我不太清楚,只能瞎猜测一番,如有大牛看到不要见笑,前面说到.o文件,我认为.o文件不太适合演示,故我演示的是反汇编可执行文件test1,用 gcc -o test test1.c命令编译,然后用objdump -d -M i386 test1 反汇编,这里的-M命令选项是指定汇编语言的格式,用objdump -i 可以看到格式选项,从语句形式的角度看一共有两种格式,intel和AT&T,默认的是AT&T,这两种格式差别不大,然后每种格式下分了32位和64位两种,其实是寄存器改变了,不过64位寄存器是兼容32位的,为了方便我将统一使用AT&T的32位(i386)指令集,你甚至可以加两个-M选项如objdump -d -M i386 -M intel test1 这使用的是 intel的32位指令集。64位寄存器和32位的兼容如下图:


源代码test1.c:

[objc] view plain copy
  1. #include<stdio.h>  
  2.   
  3. int sum(int temp1,int temp2);  
  4. int main()  
  5.   
  6. {  
  7.  int i;  
  8.   i=sum(2,3);  
  9. return 0;  
  10. }  
  11. int sum(int temp1,int temp2)  
  12. int c=temp1;  
  13. int b=temp2;  
  14. int a= b+c;  
  15. return a;  
  16. }                

    在这我要纠正一个很多人都会犯的错误,就是写void main()这种形式的主函数,main()函数的返回值必须是int,linux进程退出分为正常退出和异常退出两种,正常退出中有一种是在main()函数里执行return操作(其它的是调用内核函数exit()和_exit(),其中exit()会将内存缓冲区数据回写给文件)main函数的返回值由  __libc_start_main接收,并传递给exit,return+非零值 表示非正常退出(另外进程中断时会调用about()函数表示非正常退出),其实c语言的return机制非常像Java中的try(),catch()的异常抛出,然而我们大多数人把return当做返回值用,其实从另一角度这也可以看做是“异常”处理吧,如果在main()函数(或者其它函数)里调用其它函数,当被调用函数内的return执行完后会将控制权(看后面就知道是cpu指令寄存器eip(rip))交给调用函数,如果main()函数中执行完return则将控制权交给操作系统,在早期的编译器版本中void main()会报错,新版本的编译器会在void main()中自动加入return 0。这个错误看似没啥,但是搞不好会被技艺高超的黑客所利用。

好回归正题:反汇编代码:我主要关心源代码的函数main和sum,因为其它段是和源代码内容基本无关的(其实是有些东西太复杂不懂害羞)

[plain] view plain copy
  1. test1:     file format elf64-x86-64  
  2.   
  3.   
  4. Disassembly of section .init:  
  5.   
  6. 0000000000400370 <_init>:  /这个区和最后的_fini和gcc编译器在链接时加载一般init与内核调用有关,这个我们  
  7. 不太关心(其实我也没深入研究过<img alt="害羞" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/shy.gif" />不懂,大概有个模糊概念)  
  8.   
  9.   400370:  48                      dec    %eax  
  10.   400371:   83 ec 08                sub    $0x8,%esp  
  11.   400374:   48                      dec    %eax  
  12.   400375:   8b 05 45 05 20 00       mov    0x200545,%eax  
  13.   40037b:   48                      dec    %eax  
  14.   40037c:   85 c0                   test   %eax,%eax  
  15.   40037e:   74 05                   je     400385 <_init+0x15>  
  16.   400380:   e8 2b 00 00 00          call   4003b0 <__gmon_start__@plt>  
  17.   400385:   48                      dec    %eax  
  18.   400386:   83 c4 08                add    $0x8,%esp  
  19.   400389:   c3                      ret      
  20.   
  21. Disassembly of section .plt:  
  22.   
  23. 0000000000400390 <__libc_start_main@plt-0x10>:  
  24.   400390:   ff 35 3a 05 20 00       pushl  0x20053a  
  25.   400396:   ff 25 3c 05 20 00       jmp    *0x20053c  
  26.   40039c:   0f 1f 40 00             nopl   0x0(%eax)  
  27.   
  28. 00000000004003a0 <__libc_start_main@plt>:  
  29.   4003a0:   ff 25 3a 05 20 00       jmp    *0x20053a  
  30.   4003a6:   68 00 00 00 00          push   $0x0  
  31.   4003ab:   e9 e0 ff ff ff          jmp    400390 <_init+0x20>  
  32.   
  33. 00000000004003b0 <__gmon_start__@plt>:  
  34.   4003b0:   ff 25 32 05 20 00       jmp    *0x200532  
  35.   4003b6:   68 01 00 00 00          push   $0x1  
  36.   4003bb:   e9 d0 ff ff ff          jmp    400390 <_init+0x20>  
  37.   
  38. Disassembly of section .text:  
  39.   
  40. 00000000004003c0 <_start>:    /这是真正的程序入口处  
  41.   4003c0:   31 ed                   xor    %ebp,%ebp  
  42.   4003c2:   49                      dec    %ecx  
  43.   4003c3:   89 d1                   mov    %edx,%ecx  
  44.   4003c5:   5e                      pop    %esi  
  45.   4003c6:   48                      dec    %eax  
  46.   4003c7:   89 e2                   mov    %esp,%edx  
  47.   4003c9:   48                      dec    %eax  
  48.   4003ca:   83 e4 f0                and    $0xfffffff0,%esp/看到这个语句我想到了垫片保护栈的技术  
  49.   但是好像有点不太一样,这处语句附近一定保存了argc 和argv[].  
  50.   4003cd:  50                      push   %eax  
  51.   4003ce:   54                      push   %esp  
  52.   4003cf:   49                      dec    %ecx  
  53.   4003d0:   c7 c0 70 05 40 00       mov    $0x400570,%eax  
  54.   4003d6:   48                      dec    %eax  
  55.   4003d7:   c7 c1 00 05 40 00       mov    $0x400500,%ecx  
  56.   4003dd:   48                      dec    %eax  
  57.   4003de:   c7 c7 b6 04 40 00       mov    $0x4004b6,%edi  
  58.   4003e4:   e8 b7 ff ff ff          call   4003a0 <__libc_start_main@plt>  
  59.   4003e9:   f4                      hlt      
  60.   4003ea:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  61.   
  62. 00000000004003f0 <deregister_tm_clones>:  
  63.   4003f0:   b8 07 09 60 00          mov    $0x600907,%eax  
  64.   4003f5:   55                      push   %ebp  
  65.   4003f6:   48                      dec    %eax  
  66.   4003f7:   2d 00 09 60 00          sub    $0x600900,%eax  
  67.   4003fc:   48                      dec    %eax  
  68.   4003fd:   83 f8 0e                cmp    $0xe,%eax  
  69.   400400:   48                      dec    %eax  
  70.   400401:   89 e5                   mov    %esp,%ebp  
  71.   400403:   76 1b                   jbe    400420 <deregister_tm_clones+0x30>  
  72.   400405:   b8 00 00 00 00          mov    $0x0,%eax  
  73.   40040a:   48                      dec    %eax  
  74.   40040b:   85 c0                   test   %eax,%eax  
  75.   40040d:   74 11                   je     400420 <deregister_tm_clones+0x30>  
  76.   40040f:   5d                      pop    %ebp  
  77.   400410:   bf 00 09 60 00          mov    $0x600900,%edi  
  78.   400415:   ff e0                   jmp    *%eax  
  79.   400417:   66 0f 1f 84 00 00 00    nopw   0x0(%eax,%eax,1)  
  80.   40041e:   00 00   
  81.   400420:   5d                      pop    %ebp  
  82.   400421:   c3                      ret      
  83.   400422:   66 66 66 66 66 2e 0f    data16 data16 data16 data16 nopw %cs:0x0(%eax,%eax,1)  
  84.   400429:   1f 84 00 00 00 00 00   
  85.   
  86. 0000000000400430 <register_tm_clones>:   /从字面上看这与将寄存器复制到内核有关  
  87.   400430:   be 00 09 60 00          mov    $0x600900,%esi  
  88.   400435:   55                      push   %ebp  
  89.   400436:   48                      dec    %eax  
  90.   400437:   81 ee 00 09 60 00       sub    $0x600900,%esi  
  91.   40043d:   48                      dec    %eax  
  92.   40043e:   c1 fe 03                sar    $0x3,%esi  
  93.   400441:   48                      dec    %eax  
  94.   400442:   89 e5                   mov    %esp,%ebp  
  95.   400444:   48                      dec    %eax  
  96.   400445:   89 f0                   mov    %esi,%eax  
  97.   400447:   48                      dec    %eax  
  98.   400448:   c1 e8 3f                shr    $0x3f,%eax  
  99.   40044b:   48                      dec    %eax  
  100.   40044c:   01 c6                   add    %eax,%esi  
  101.   40044e:   48                      dec    %eax  
  102.   40044f:   d1 fe                   sar    %esi  
  103.   400451:   74 15                   je     400468 <register_tm_clones+0x38>  
  104.   400453:   b8 00 00 00 00          mov    $0x0,%eax  
  105.   400458:   48                      dec    %eax  
  106.   400459:   85 c0                   test   %eax,%eax  
  107.   40045b:   74 0b                   je     400468 <register_tm_clones+0x38>  
  108.   40045d:   5d                      pop    %ebp  
  109.   40045e:   bf 00 09 60 00          mov    $0x600900,%edi  
  110.   400463:   ff e0                   jmp    *%eax  
  111.   400465:   0f 1f 00                nopl   (%eax)  
  112.   400468:   5d                      pop    %ebp  
  113.   400469:   c3                      ret      
  114.   40046a:   66 0f 1f 44 00 00       nopw   0x0(%eax,%eax,1)  
  115.   
  116. 0000000000400470 <__do_global_dtors_aux>:  
  117.   400470:   80 3d 89 04 20 00 00    cmpb   $0x0,0x200489  
  118.   400477:   75 11                   jne    40048a <__do_global_dtors_aux+0x1a>  
  119.   400479:   55                      push   %ebp  
  120.   40047a:   48                      dec    %eax  
  121.   40047b:   89 e5                   mov    %esp,%ebp  
  122.   40047d:   e8 6e ff ff ff          call   4003f0 <deregister_tm_clones>  
  123.   400482:   5d                      pop    %ebp  
  124.   400483:   c6 05 76 04 20 00 01    movb   $0x1,0x200476  
  125.   40048a:   f3 c3                   repz ret   
  126.   40048c:   0f 1f 40 00             nopl   0x0(%eax)  
  127.   
  128. 0000000000400490 <frame_dummy>:  
  129.   400490:   bf e8 06 60 00          mov    $0x6006e8,%edi  
  130.   400495:   48                      dec    %eax  
  131.   400496:   83 3f 00                cmpl   $0x0,(%edi)  
  132.   400499:   75 05                   jne    4004a0 <frame_dummy+0x10>  
  133.   40049b:   eb 93                   jmp    400430 <register_tm_clones>  
  134.   40049d:   0f 1f 00                nopl   (%eax)  
  135.   4004a0:   b8 00 00 00 00          mov    $0x0,%eax  
  136.   4004a5:   48                      dec    %eax  
  137.   4004a6:   85 c0                   test   %eax,%eax  
  138.   4004a8:   74 f1                   je     40049b <frame_dummy+0xb>  
  139.   4004aa:   55                      push   %ebp  
  140.   4004ab:   48                      dec    %eax  
  141.   4004ac:   89 e5                   mov    %esp,%ebp  
  142.   4004ae:   ff d0                   call   *%eax  
  143.   4004b0:   5d                      pop    %ebp  
  144.   4004b1:   e9 7a ff ff ff          jmp    400430 <register_tm_clones>  
  145.   
  146. 00000000004004b6 <main>:  
  147.   4004b6:   55                      push   %ebp  //保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  148. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  149.   
  150.   4004b7:  48                      dec    %eax   //这句的eax自减1,和下下句我完全不知道是啥意思,我  
  151. 猜测是某种计数用的。  
  152.   
  153.   4004b8:  89 e5                   mov    %esp,%ebp//创建新栈底  
  154.   4004ba:   48                      dec    %eax  
  155.   4004bb:   83 ec 10                sub    $0x10,%esp//esp向上偏移16位,因为$0x10是16进制,开辟了个  
  156. 能存4个整型(int)数据的区域或者16个char数据类型的区域  
  157.  4004be:   be 03 00 00 00          mov    $0x3,%esi//将第二个参数值3存入esi  
  158.   4004c3:   bf 02 00 00 00          mov    $0x2,%edi//将第一个参数值2存入esi  
  159.   4004c8:   e8 0a 00 00 00          call   4004d7 <sum>/将jmp 4004d7即跳到sum函数作用域,然后将下一  
  160. 条语句的地址作为返回地址压栈  
  161.   4004cd:  89 45 fc                mov    %eax,-0x4(%ebp)//将从sum那计算出的a值存到ebp的上面第一个  
  162. 整型区域(偏移量4).这也就是i的内存空间  
  163.   4004d0:   b8 00 00 00 00          mov    $0x0,%eax//清空eax  
  164.   4004d5:   c9                      leave  //相当于 mov %esp,%ebp pop %ebp两条语句,目的是还原原栈底  
  165.   4004d6:   c3                      ret    //相当于 pop eip,作用是返回调用该函数的函数的空间,作用  
  166.  域被改变  
  167.   00000000004004d7 <sum>:  
  168.   4004d7:   55                      push   %ebp//保存原栈底,然后栈顶指针(esp)向上移动4位,因为  
  169. ebp是32位寄存器(32bit)即4个字节(8bit),内存给每个字节分配一个地址。  
  170.   
  171.   4004d8:   48                      dec    %eax  
  172.   4004d9:   89 e5                   mov    %esp,%ebp//同main  
  173.   4004db:   89 7d ec                mov    %edi,-0x14(%ebp)//将第一个参数值2从esi存入ebp上方偏移量为  
  174. 14的内存空间处  
  175.   4004de:  89 75 e8                mov    %esi,-0x18(%ebp)//将第2个参数值3从esi存入ebp上方偏移量为  
  176. 14的内存空间处  
  177.   4004e1:   8b 45 ec                mov    -0x14(%ebp),%eax//将第一个参数值2从存入eax  
  178.   
  179.   4004e4:   89 45 fc                mov    %eax,-0x4(%ebp)//将eax的值2从存入ebp上方偏移量为  
  180. 4的内存空间处,相当于sum函数内的c指向的空间位置  
  181.   4004e7:   8b 45 e8                mov    -0x18(%ebp),%eax//将第二个参数值3从存入eax  
  182.   4004ea:   89 45 f8                mov    %eax,-0x8(%ebp)//将eax的值3从存入ebp上方偏移量为  
  183. 8的内存空间处,相当于sum函数内的b指向的空间位置  
  184.   4004ed:   8b 55 f8                mov    -0x8(%ebp),%edx  
  185.   4004f0:   8b 45 fc                mov    -0x4(%ebp),%eax  
  186.   4004f3:   01 d0                   add    %edx,%eax//这上面3句相当于函数内的a=b+c,且a的值存入eax中  
  187.   4004f5:   89 45 f4                mov    %eax,-0xc(%ebp)//将eax的值a(5)从存入ebp上方偏移量为  
  188. 12的内存空间处,相当于sum函数内的a指向的空间位置  
  189.   4004f8:   8b 45 f4                mov    -0xc(%ebp),%eax//将a的值存入eax,为之后main函数传值做铺垫  
  190.   4004fb:   5d                      pop    %ebp//恢复原栈底  
  191.   4004fc:   c3                      ret    /同main  
  192.   4004fd:   0f 1f 00                nopl   (%eax)   //占位用,无实际意义  
  193.   
  194. 0000000000400500 <__libc_csu_init>:  
  195.   400500:   41                      inc    %ecx  
  196.   400501:   57                      push   %edi  
  197.   400502:   41                      inc    %ecx  
  198.   400503:   89 ff                   mov    %edi,%edi  
  199.   400505:   41                      inc    %ecx  
  200.   400506:   56                      push   %esi  
  201.   400507:   49                      dec    %ecx  
  202.   400508:   89 f6                   mov    %esi,%esi  
  203.   40050a:   41                      inc    %ecx  
  204.   40050b:   55                      push   %ebp  
  205.   40050c:   49                      dec    %ecx  
  206.   40050d:   89 d5                   mov    %edx,%ebp  
  207.   40050f:   41                      inc    %ecx  
  208.   400510:   54                      push   %esp  
  209.   400511:   4c                      dec    %esp  
  210.   400512:   8d 25 c0 01 20 00       lea    0x2001c0,%esp  
  211.   400518:   55                      push   %ebp  
  212.   400519:   48                      dec    %eax  
  213.   40051a:   8d 2d c0 01 20 00       lea    0x2001c0,%ebp  
  214.   400520:   53                      push   %ebx  
  215.   400521:   4c                      dec    %esp  
  216.   400522:   29 e5                   sub    %esp,%ebp  
  217.   400524:   31 db                   xor    %ebx,%ebx  
  218.   400526:   48                      dec    %eax  
  219.   400527:   c1 fd 03                sar    $0x3,%ebp  
  220.   40052a:   48                      dec    %eax  
  221.   40052b:   83 ec 08                sub    $0x8,%esp  
  222.   40052e:   e8 3d fe ff ff          call   400370 <_init>  
  223.   400533:   48                      dec    %eax  
  224.   400534:   85 ed                   test   %ebp,%ebp  
  225.   400536:   74 1e                   je     400556 <__libc_csu_init+0x56>  
  226.   400538:   0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)  
  227.   40053f:   00   
  228.   400540:   4c                      dec    %esp  
  229.   400541:   89 ea                   mov    %ebp,%edx  
  230.   400543:   4c                      dec    %esp  
  231.   400544:   89 f6                   mov    %esi,%esi  
  232.   400546:   44                      inc    %esp  
  233.   400547:   89 ff                   mov    %edi,%edi  
  234.   400549:   41                      inc    %ecx  
  235.   40054a:   ff 14 dc                call   *(%esp,%ebx,8)  
  236.   40054d:   48                      dec    %eax  
  237.   40054e:   83 c3 01                add    $0x1,%ebx  
  238.   400551:   48                      dec    %eax  
  239.   400552:   39 eb                   cmp    %ebp,%ebx  
  240.   400554:   75 ea                   jne    400540 <__libc_csu_init+0x40>  
  241.   400556:   48                      dec    %eax  
  242.   400557:   83 c4 08                add    $0x8,%esp  
  243.   40055a:   5b                      pop    %ebx  
  244.   40055b:   5d                      pop    %ebp  
  245.   40055c:   41                      inc    %ecx  
  246.   40055d:   5c                      pop    %esp  
  247.   40055e:   41                      inc    %ecx  
  248.   40055f:   5d                      pop    %ebp  
  249.   400560:   41                      inc    %ecx  
  250.   400561:   5e                      pop    %esi  
  251.   400562:   41                      inc    %ecx  
  252.   400563:   5f                      pop    %edi  
  253.   400564:   c3                      ret      
  254.   400565:   66 66 2e 0f 1f 84 00    data16 nopw %cs:0x0(%eax,%eax,1)  
  255.   40056c:   00 00 00 00   
  256.   
  257. 0000000000400570 <__libc_csu_fini>:  
  258.   400570:   f3 c3                   repz ret   
  259.   
  260. Disassembly of section .fini:  
  261.   
  262. 0000000000400574 <_fini>:  
  263.   400574:   48                      dec    %eax  
  264.   400575:   83 ec 08                sub    $0x8,%esp  
  265.   400578:   48                      dec    %eax  
  266.   400579:   83 c4 08                add    $0x8,%esp  
  267.   40057c:   c3                      ret   

   从源代码的分析可知,每一个函数的作用域相当于一个创建的栈,其内部的变量存入各自的栈中,调用一个函数只是将eip即cpu指令寄存器的值指向被调用函数的内存空间的开头然后将该调用语句的下一句地址压栈,且调用函数的栈顶是被调用函数的栈底,图类似于我上面给的超链接处的内容的图,只不过现在的内核为了安全舍弃了将参数压栈的这种调用方式而是通过寄存器esi,edi传递,想想也是,这样做防止了以前用类似strcpy()函数的缺陷而造成的缓冲区溢出。

    下面我将演示ASLR技术也就是所谓的地址空间随机化技术,这个技术能干扰黑客编写shellcode,因为很多shellcode的编写要准确计算出esp到ebp之间的长度,然后用恶意代码填充,这我就不说方法了,找一本国外的黑客书籍都有介绍,但是基本上是没有的,就如我之前所说现在的linux内核版本加入了很多保护机制,你必须要绕开它你的shellcode才有用。

   

   回到正题:这是我编的一段小代码,作用是打印esp寄存器的内容即栈顶地址

test.c源代码:

[plain] view plain copy
  1. #include<stdio.h>  
  2. unsigned int get_esp(){  
  3.         __asm__("movl %esp, %eax");  
  4. }  
  5. int main(){  
  6.         printf("STACK ESP:0x%x\n", get_esp());  
  7. }  
运行结果如图:


 你会发现每运行一次,esp的值都有变化。

使用命令 echo "0" > /proc/sys/kernel/randomize_va_space #on slackware systems

将这个保护选项关闭后再运行,结果如下:



现在栈顶地址固定了,一般我研究shellcode时会把栈顶值固定,这样才容易出效果,毕竟比较菜。

原创粉丝点击