转自: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. int sum(int temp1,int temp2);
  3. int main()
  4. {
  5. int i;
  6. i=sum(2,3);
  7. return 0;
  8. }
  9. int sum(int temp1,int temp2)
  10. { int c=temp1;
  11. int b=temp2;
  12. int a= b+c;
  13. return a;
  14. }

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

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

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

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

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

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

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

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

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

  1. String的底层分析 (学习笔记)

    StringTable底层分析 String的基本特性 StringPool String的内存分配 字符串的拼接操作 拼接效率的对比 intern()的理解 new String("&qu ...

  2. android 4.4 电池电量管理底层分析(C\C++层)

    参考文献:http://blog.csdn.net/wlwl0071986/article/details/38778897 简介: Linux电池驱动用于和PMIC交互.负责监听电池产生的相关事件, ...

  3. 为什么除了Go语言, 其他类C语言都是垃圾[翻译][转]

    2019独角兽企业重金招聘Python工程师标准>>> 英文原文: http://www.syntax-k.de/projekte/go-review 原始翻译: http://ww ...

  4. Android底层隐私数据,Android Intent传递数据底层分析详细介绍_Android_脚本之家

    Android  Intent传递数据底层分析详细介绍 我们知道在Activity切换时,如果需要向下一个ActivityB传递数据,可以借助Intent对象的putExtra方法. 但是不知各位有没 ...

  5. Java语言中提供了三个日期类_Java语言学习(5)-Java中基础封装类(日期、时间类)...

    日期和时间封装类 1. Data类 Java日期和时间采用Data类.Data类在java.util包中. Data类构造函数: 1)       Data()   采用当前时间初始化对象: 2)   ...

  6. android中intent放数据类型,Android Intent传递数据底层分析详细介绍

    Android  Intent传递数据底层分析详细介绍 我们知道在Activity切换时,如果需要向下一个ActivityB传递数据,可以借助Intent对象的putExtra方法. 但是不知各位有没 ...

  7. c语言解析sql语句_解析SQL语句比解析类C语言更麻烦?

    最近想做一个SQL语句解析器,换句话说想给自己的系统加上类似SQL语句的查询引擎.我之前做过一个解析类似C语言语法的解析器,可以解析 C/C++里的运算表达式,if-else-等基本语句.我以为做个S ...

  8. 基于Python实现的类Pascal语言的词法分析和语法分析器

    类Pascal语言的语法分析器 功能 使用Python实现的类Pascal语言的词法分析和语法分析器. 语法分析实现的功能有: 利用文法推导式构造LR(1)分析表 使用LR(1)分析表对输入的Toke ...

  9. 航空航天大类C语言程设第三次练习赛

    航空航天大类C语言程设第三次练习赛 第四期更新, 鸽子博主终于更新第三次练习赛了 (A题)求区间交并集 多行数据,每行有两个数,用空格分开,表示每个区间的下和上界,保证是合法的非空集,而且得到的并集是 ...

最新文章

  1. Android笔记(adb命令--reboot loader)
  2. np.asarray和np.array、np.nanmean和np.mean、np.diff、
  3. faster rcnn第二阶段loss出现nan_利用Faster_Rcnn训练模型时出现的问题
  4. WebApi中跨域解决办法
  5. 围成一圈的排列组合问题_分班必考知识点!小学奥数之排列组合问题
  6. Flex4_HttpService组件
  7. java矩阵连乘算法_使用java写的矩阵乘法实例(Strassen算法)
  8. python安卓版开发环境搭建_React Native Android 开发环境搭建(Windows 版)
  9. WORD如何设置第X页,共Y页页码设置?
  10. LightOj_1030 Discovering Gold
  11. ubuntu16.04 安装kicad5.1
  12. win8下IE10停止工作解决办法
  13. 编码器分类及原理和测速应用(含代码)
  14. 【2021最新版】Kafka面试题总结(25道题含答案解析)
  15. Lumion 11学会像真正的专业人士一样渲染
  16. Linux中cd会进入什么位置,linux命令中cd/和cd命令是什么意思
  17. 带你一起敲敲ES6的新特性,边解释边应用!
  18. 前后端分离,图片(资源)路径如何处理
  19. HoloLens忘记开机密码,并重新安装HoloLens系统
  20. 导出Excel—外部表不是预期的格式

热门文章

  1. [转] boost库的Singleton的实现以及static成员的初始化问题
  2. Strings_append_学习
  3. 远程桌面超出最大连接数问题
  4. 酷客多郝宪玮:不够小程序化的企业,将错失最近5年的流量红利
  5. Android进程间通信之socket通信
  6. asp.net mvc 前台使用后台变量
  7. 微软IT规划方法论解读
  8. Discuz! Database Error(2003) notconnect 问题解決
  9. 学习Machine Leaning In Action(四):逻辑回归
  10. 使用 varchar(max)、nvarchar(max) 和 varbinary(max) 数据类型代替text、ntext 和 image 数据类型...