Rise的自留地

记录生活中的点滴,分享编程技术和游戏开发经验。

0%

李国栋床上堆着书,每天晚上睡在榻榻米上,读书读到凌晨一两点,读到两眼充血,像针扎一样痛苦,才把书放开。蜷缩到榻榻米上,用绳子把左腿跟一只桌脚绑在一起,熄了灯睡觉。
    “这样一来,我一翻身,扯不动腿,就会醒过来。醒过来就马上爬起来继续看书——今年是第三年了,再考不上,就要当兵去了!”
    高考前,李国栋很平静地这样解释他的生活方式。他消瘦的脸颊上浮着一层暗暗的青气,眼白里满是一条条细细的血丝。讲话的时候,眼神涣散,不知道他在看哪里。
    “为什么不换个读书方法?这种煎熬式读书不是效果很差吗?”
    他摇摇头:“我不知道还有什么别的方法。”
    “为什么不找其他出路?不上大学,去读职校或学技术?”
    他开始咬指甲,每一片指甲都咬得烂烂毛毛的:“不行,我非读大学不可。”
    李国栋后来仍旧落了榜,但是也没去当兵。他在精神病院里住了两个星期之后,有个晚上,偷偷吞了五枚大铁钉,从七楼的阳台上跳下来,刚好掉在垃圾车旁边。
    麦尔教授对老鼠很有兴趣,曾经做过这样的实验。
    他把老鼠聚集在一个平台上,让它们一个个往下面两个门上跳。跳向左门,它会碰得鼻青脸肿;跳向右门,门就会打开,门后是甜美的乳酪。小老鼠当然不笨,训练几次后,就快快乐乐地往右门跳去,不再摔得一鼻子灰。
    可是,就在小老鼠的选择方式固定了的时候,麦尔把乳酪从右门移到左门。本来以为可以饱食一顿的老鼠现在又碰得鼻青脸肿,它不知道客观情势已经改变了。幸好,摔了几次后,它又渐渐熟悉了新的情况,原来乳酪在左边!
    问题是,麦尔又有了新花样。他把门的颜色重新漆过,把乳酪一会儿放左,一会儿放右。老鼠在新的习惯形成之后,发觉原来的方式又行不通了,它必须不断地适应新情况,不断地修正自己的习惯行为……
    终于,老鼠变不过来了,它的下一个反应就是“以不变应万变”。麦尔发觉,在应变不过来的时候,老鼠“拧”了,开始固执起来,根本就拒绝改变方式。譬如说,如果它已经习惯于跳向左门,你就是把乳酪明明白白地放在右门上,让它看见,它仍旧狠狠地往左门去碰肿鼻子,愈碰就愈紧张。如果实验者在这个关口继续强迫它去作跳左或跳右的抉择,老鼠就往往会抽筋、狂奔、东撞西跌或咬伤自己,然后全身颤抖直到昏迷为止。换句话说,这只老鼠已经“精神崩溃”了。
    于是,麦尔教授归纳出导致老鼠“精神崩溃”的五个阶段:
    首先,对某一个难题(左门或右门),让老鼠逐渐培养出一种应对的习惯来(选择右门:右门有乳酪)。
    第二个阶段,客观环境改变,老鼠发觉惯有的方式已经不能解决问题,因此感到恐惧。
    第三个阶段,不断的焦虑与挫折、失败之后,它就固执地以旧有的方式面对新的情况,不计后果(就是看见乳酪出现在右边,仍旧往左边闯)。
    第四个阶段,根本放弃努力(乳酪也不吃了,干脆饿死)。
    最后,如果外力迫使它非解决问题不可,它就又回到它所习惯的旧方式(左门就是左门,非左门不可)。当然又碰得鼻青脸肿,饿得头昏眼花。明明只要换个途径就可解决一切,它却固执地在习惯行为中饱受挫折与失败的煎熬,最后以崩溃结束。
    在垃圾车边被清洁工人发现的李国栋是一只弄“拧”了的老鼠,我们的社会环境与教育制度是控制乳酪、制造难题的科学家。从前,大学之门是通往乳酪的门,所有的人都往那个门上跳。“士大夫”观念深深地植入人们心中,因为我们发觉成了“士大夫”之后就有甜美的乳酪可吃。但是,在大家都习惯了这个方式之后,客观情况却变了,乳酪换了门。往“士大夫”那个门撞去,就会撞个鼻青脸肿,而且得不到乳酪。
    可是孩子们继续去撞那一扇门。做父母的也继续鼓励孩子们去撞那扇没有乳酪的门。他们说“有志者,事竟成”;说“精诚所至,金石为开”;说“老天不负苦心人”。门的颜色变了,乳酪的位置换了,可是弄“拧”了的人固执地守着旧有的方式“以不变应万变”。
    一个人,也只不过是只有可能精神崩溃的老鼠。人生的每个阶段里都有看似不可解的难题时时强迫他作出抉择:考试失败了、爱人变心了、婚姻破裂了、工作失去了,每一个难题都需要一个解决的办法。究竟乳酪在左边还是右边?不管左右,当一个人不再能以“新”的方式来应付“新”的情况,当他不计后果,根本拒绝改变自己的时候,他就是一只弄“拧”了的老鼠,精神的解体只是自然的结局。如何能不受制于旧习惯、旧观念、旧方法,如何不因搞“拧”了老去撞一扇没有乳酪的门,需要的是弹性与智慧。
    智慧,不正是人之所以为人,鼠之所以为鼠的差别吗?

                           ==Ph4nt0m Security Team==
                       Issue 0x01, Phile #0x04 of 0x06

|=---------------------------------------------------------------------------=|
|=----------------------=[       安全幻想曲2008       ]=---------------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=--------------------=[           By axis             ]=--------------------=|
|=--------------------=[   <axis_at_ph4nt0m_dot_org>   ]=--------------------=|
|=---------------------------------------------------------------------------=|

    我见过的大多数安全人员,都对技术有着一种狂热,甚至是一种偏执。这种情绪在做安
全研究员的时候是非常有好处的,因为作为研究员,可能要偏执考虑到一些极端的情况。这
种钻研精神,是光靠勤奋所无法达到的。但是在甲方做安全的话,可能更多时候需要的就不
是狂热,而是掌握平衡的艺术。在商业利益与安全性发生冲突时,如何处理好这个平衡,是一
个关键。

    举一个简单的例子来说,眼下最流行的XSS攻击,其修补方案从总体上来说,大致可以分
为escape output和filter input两种。对于狂热的安全人员来说,当然是恨不得把网站全
部弄成静态的,输出都采用escape output,全部输出纯文本,就天下太平了。然而现实与理
想总是有差别的,首道难关就是网站肯定会有些富文本的需求。

    当安全和需求相抵触时,一定是安全给商业需求让路。这里要避免一个误区,就是安全
应该是为需求而服务的,而不是成为需求的障碍。其实这个观点大多数人都心知肚明,但是
在实际操作起来的时候往往会事与愿违。

    再回到富文本上来,当需求决定需要有富文本输出的时候,狂热的安全人员(下称为狂战
士吧)就只好退而求其次,要求对富文本做filter input,对其他没有富文本的地方做escape
output。接下来问题来了,对于程序员来说,富文本往往采用了一些第三方的,或者是基于第
三方的富文本编辑器,还有的是自己实现了一个。而这些富文本编辑器,往往在考虑
xss defense的时候有所欠缺。这时候采用什么样的策略来做filter input,就成为了新的
问题。

    第一个难关就是程序员会拉上商业,一起来和狂战士PK,说filter input很容易误杀客
户的正常操作,还会影响到性能。当然这小小的难关还难不倒狂战士。狂战士往往会轻蔑的
一笑,然后把风险推到商业上,说出了问题让他背黑锅之类。这种狠话一放出来,商业往往就
会退缩了,毕竟狂战士这么个狠角色是摆在那里的。所以最后会决定让程序员去整filter。

    于是程序员简单写了个基于正则的blacklist,并且禁用了部分标签,比如script。狂战
士这时候又蹦了出来,对程序员指手画脚,要求禁用style,因为这玩意太难控制了,黑客有几
百种利用style的方式;狂战士还说,基于正则的匹配这个魔法等级太低了,要换个高级魔法,
比如个语法分析器,类似html purify这种,还要有log analysis和realtime monitor功能。

    一般到了这个时候,程序员对狂战士的忍耐已经差不多到极限了,因为甲方网站很少以
安全为主要考核因素,没人会认为一个视频网站或者是交友网站的安全需要做的比FBI更好,
因为没那么大的成本投入。于是程序员说要释放这么个高级魔法需要一个团的程序员配合,
还需要召唤很长时间才能放出来,所以狂战士的这个非常牛B的魔法无法完成。而一般在这
个时候,程序员往往会用啥性能和稳定性之类的因素来忽悠狂战士,说这种魔法一般有一定
概率会反噬,没整好就把自己整残了。

    狂战士无奈之下,只好同意程序员实现一部分的魔法,filter部分过滤完整有效就行了。
做好这个之后,狂战士还让程序员去对没有富文本需求的地方使用escape output。程序员
这时候对狂战士已经忍无可忍了,因为由于以前从来没有注意过xss这方面的问题,所以需要
escape的地方是以“千”或者是“万”为单位的,多如牛毛。于是程序员开始消极怠工,并且开
始诉苦。这条路走不通了,狂战士只好开始寻求更好的方案。

    后来狂战士回家睡了一觉,在梦中有仙人传授武艺,于是马上想到了新的办法。第一招
是filter output,不过这个扯淡的方法根本属于yy,因为对服务器压力太大。第二招是使用
WAF,就是web application firewall,开个虚拟补丁,这样程序员不补也能搞定web漏洞。不
过这样就依赖于WAF的规则了,而且治标不治本。看来昨晚那个仙人估计是灶君一类低级的
小神,尽出馊点子。看来狂战士还得继续和程序员PK下去了。

    可以见到,那些牛圈里的狂战士常认为是“奇技淫巧”的XSS问题里,有这么多头疼的问题。
简单的问题变得越来越复杂。

    安全是一个持续的过程(process)。既然是过程,就会有第一步、第二步 ... 第N步,有
一个持续的概念在里面,不能今天整了,明天就不管了。今天的安全并不代表明天的安全,新
的技术和应用在不断发展,就会不断带来新的问题。经常看到一个升级反而把漏洞升级出来
的例子。所以安全是一个持续的对抗过程,hacking与anti-hacking的过程,广义来说,更是
一个弱化风险的过程。

    很多BOSS往往都会这么问狂战士:我上了这个720安全卫士是不是桌面安全就不用管了?
我上了这个卖红茶IPS是不是就能挡住所有刺客入侵了? 狂战士这时候很无奈的说:不行,还
是有很多trojan和rootkit可以bypass主动防御,很多shellcode和0day可以anti IPS。 于
是BOSS很生气的说: 那我花这么多钱买这个做啥? 狂战士一般会忽悠他说:上了这个可以解
决90%的攻击。 于是BOSS会很不满意,让狂战士出技术分析报告,一定要有充分的理由才行,
狂战士往往要面对这种烦恼。

    其实BOSS的这种观点是一种急功近利的想法,没有认识到安全是一个过程,并且是一个
持续改进的过程。不是买个box就能解决问题的。没有100%的安全,有漏洞的地方太多了。
经常有魔法师用木桶原理来阐述安全问题,但其实很多时候,连木板在哪里,到底那块木板才
是短板,都没有一个很清晰的认识,因为很多时候根本无法量化,所以狂战士的工作经常陷入
误区。板子太多了,系统、网络、用户、应用、数据、桌面......

    放眼看去,全是短板,每块板子都能让刺客或盗贼轻松的进来,偷走核心数据或者弄摊网
站然后扬长而去。或者各种短板互相组合,让问题变得更加扑朔迷离。

    前面说的WAF就是一种比较功利的做法,虽然厂商经常会蹦出来说这玩意是需要有专人
维护的,也是一个持续的过程。但实际上很多购买WAF的用户都没有好好的去做这个过程。
其实WAF、IPS最大的软肋不是在没人跟进上,而是在于其是串联的网络上的,特别是开了虚
拟补丁的阻断模式的时候。这对于高可用性的应用来说,绝对是无法忍受的。没人敢背这个
误杀的黑锅。要是因此导致了PV下降,可能老板就要喊到办公室去喝茶了。不过WAF也不是
完全没用,如果能够用好的话,对于网站还是还是很有帮助的,至少在monitor和攻击流量分
析上起着积极的意义。不过前提是用好。

    刚才说了安全是一个过程(Process),其实有人跟进这个过程还不够,下面还要重点说说
深度防御的思想。经常看到YY小说的作者在写到黑客攻防的时候,说到XXX在xx分钟内就突
破了N道防火墙,N大于100;变形金刚里也这么有这种场景。其实这纯粹是扯淡,没事整那么
多防火墙做什么,无端影响了可用性。不过YY作者深度防御的理念还是正确的,只是他不知
道那玩意不应该单纯叫防火墙,要想表达这个思想,可以整个专业名词,比如:多层防御体系。
这样装B就可以装的比较像样了。举例来说,可以在应用层校验用户输入数据,DB层面检查每
条sql,操作系统上细分权限,服务最少化,网络上防御arp spoof,加密传输通道,做好ACL…类
似措施还有很多,防御的方案交叉层叠起来,就能起来一个比较好的保护效果。

    不过偏偏还有不识趣的,比如前面的很多程序员都会说,我都已经做了filter input,还
要escape output做啥。狂战士一般听到后会有想要狂化的冲动。按耐住狂化,告诉程序员,
说filter input可能会做不干净,会被bypass,毕竟如果遇到一个手执绝世0day(bypass
filter)的9级刺客,什么牛B的防御魔法都挡不住,所以能escape output的地方,最好escape
掉,这样最干净。可是即便是这样做好了,还是有些会有很难处理和发现的地方,比如在DOM
里的XSS,比如在JS里面一些写的很BT的地方,等。这些只能靠肉眼去看了。PK还得进行下去。

    但是程序员还是不能很好的理解,他们跑出来说:我这里做了完善的access control,只
有管理员才看的到,这里就算有注射有跨站就随他去了,不需要修复。想偷这种懒的人其实
不在少数。这种想法违背了深度防御的思想。先姑且不论如果管理员密码泄露,或者管理员
是个内鬼的情况。如果刺客通过注射拿到了管理员密码,或者是直接通过XSS和CSRF来对后
台进行注射,那么前面的access control就完全没作用了。

    在一定程度上,是可以容忍风险的存在的,但是从长期来看,这种做法是非常不可取的。
比如有的管理员会说防火墙只允许80端口,那么RPC漏洞或开其他端口的应用漏洞是否就可
以不补了。也许一时来说是没什么问题,但是如果放置不管将导致没有人来维护漏洞,也许
哪天的防火墙策略变更,或者来自内部系统的威胁,都有可能导致当时看起来无害的漏洞被
利用。而这种做法的一个后果往往是难以检查原因,就是说咋死的都不知道。所以这又回到
了开始的话题:安全是一个持续的过程。

    在灌输完深度防御的思想给程序员以后,狂战士又被另外一种程序员打击到崩溃了。面
对满目都是红色的扫描报告,他们说:我这个xxx ftp没漏洞,除非狂战士可以证明黑客能搞
进来拿到shell。一般狂战士听到这种要求,狂化的概率在80%以上。首先,不是只有能拿到
shell的才叫漏洞。一个dos可能会造成业务的中断,一个infomation leak可能会为后续攻
击带来便利,等等。

    面对scan report以及CVE查询出来的漏洞,大部分都是没有现成的exp能够利用的,而且
要利用漏洞可能有各种苛刻的条件,比如要求本地交互shell啊,或者要求有帐户之类。而更
多的时候,漏洞根本连细节的都没有,只有一个漏洞公告里一个简单的划分critical,标红。
就算有exp,可能还要考虑到exp的稳定性和成功率、语言版本啥的,打过去也不一定能成功。
更何况狂战士无法处心积虑了为了POC给程序员看,而花费大量的精力来追求一个可能没有
结果的漏洞。

    但是无法POC不代表就没有风险了。我们的目标是要保证一个系统长期的在任何情况下
都能安全运行,机密数据不会外泄,业务不会中断。所以这种程序员犯的错误就是偷换了概
念,把威胁范围缩小了,用个体来代替全局。很多时候威胁可能来自内部,可能来自误操作,
可能来自其他的风险。要说服这种程序员很辛苦,只能够靠长期的“忽悠”,来慢慢感化他们,
要是运气好还能做出一两个POC来震撼下他们,刘震撼(ZhenHan.Liu)就是为此而生的。佛曰:
我不下地狱谁下地狱。

    作为一个优秀的狂战士,往往要有相当程度的mission impossible的修为。很多时候,
需要为浏览器漏洞、操作系统漏洞擦屁股,不然最后吃亏的还是自己的用户。面对钓鱼和诈
骗,很多时候那些认为web安全是“奇技淫巧”狂战士们认为解决方案是impossible的,认为
no patch for stupid。比如phishing,诚然,如果有一个一劳永逸的方案,那么这种完美魔
法要是放出来了绝对可以获得圣阶魔导师的称号。但是YY归YY,现实归现实。狂战士们很头
疼这种问题,但是却不得不去面对它。

    魔法最终还是放出来了,可惜不完美。目前anti-phishing的魔法,有整到浏览器里内置
对抗的(IE7/8),也有浏览器toolbar、扩展的,有在IM里做过滤的,还有穷举malicious sites
的,更有发动人民战争来维护一个blacklist的,其难度和成本从低到高什么都有,不过基本
都无法一次性解决问题。比较有创意的魔法属于yahoo发明的sign seal,基于认证机器的原
理来识别真实网站,不过这个方法的缺陷在于需要长期教育用户,实际使用效果不一定好。
yahoo还整了个domainkey技术来在邮件里对抗phishing,不过这个缺陷更明显,需要邮件服
务商支持。yahoo的狂战士挺有想法的,就是太理想化了一点。

    说到安全世界的另外一股强大力量不能不提教廷,这个宗教从精神上统治了安全世界,
一群群红衣主教们整出来了一堆标准、规范比如BS7799之类来帮助狂战士们更好的忽悠他
们的BOSS。其实标准是死的,主教们的出发点是好的,不过这些标准啥的就和秘籍差不多,狂
战士们以为他们读明白了,其实很少人真正读懂了。那玩意如果拿来忽悠BOSS们确实是一套
套的,但用在实处则有一个本地化的过程。必须要把标准之类的东西和实际情况结合起来,
不然就只能停留在忽悠的层面上。

    最能体现问题的出在编码规范上。可能有N个权威的机构都出了他们自己的code规范,
或者某些狂战士佣兵团(安全公司)也自己整了套。不过在具体使用的时候,很多狂战士都是
拿了一套去用在所有的公司身上,其实这样的结果就是到最后没有程序员遵守用那玩意,因
为在实际情况中往往不好用。每个公司都有自己的体系、环境和编码习惯。系统的designer
和architect只要不是小白一般都或多或少的会考虑点安全风险,规范只有本地化以后才能
很好的用起来,不然绝对会水土不服。所以要是再遇到什么安全公司拿标准、规范来忽悠的
时候,狂战士们就要睁亮了眼睛了!

    胡侃瞎吹了这么多其实也没说到重点,不过重点已经不是本文要讲的事情了,想要讲的
东西还有很多,也许以后会陆续写出来。狂战士是份很好的职业,希望有更多的狂战士甚至
是半兽人朋友能够加入我所在的狂战士佣兵团!

-EOF-

渲染到纹理是D3D中的一项高级技术。一方面,它很简单,另一方面它很强大并能产生很多特殊效果。 比如说发光效果,环境映射,阴影映射,都可以通过它来实现。渲染到纹理只是渲染到表面的一个延伸。我们只需再加些东西就可以了。首先,我们要创造一个纹理,并且做好一些防范措施。第二步我们就可以把适当的场景渲染到我们创建的纹理上了。然后,我们把这个纹理用在最后的渲染上。
  ?main.cpp
  首先我们得声明所需要的对象。当然我们需要一张用来渲染的纹理。此外,我们还需要两个Surface对象。一个是用来存储后台缓冲区,一个用来当纹理的渲染对象。后面我再详细介绍它们。另外我们还需要两个矩阵,一个是用来当纹理的投影矩阵,另一个是存储原来的矩阵。
  LPDIRECT3DTEXTURE9 pRenderTexture = NULL;
  LPDIRECT3DSURFACE9 pRenderSurface = NULL,pBackBuffer = NULL;
  D3DXMATRIX matProjection,matOldProjection;
  现在我们来创建纹理。前两个参数是纹理的宽度和高度,第三个参数是纹理的多级渐进纹理序列参数,在这里是设为1,第四个参数非常重要而且必须设为D3DUSAGE_RENDERTARGET,表明我们所创建的纹理是用来渲染的。剩下的参数就是指纹理格式,顶点缓冲区的内存位置,和一个指向纹理的指针。当纹理是用来当渲染对象时,顶点缓冲区的内存位置必须设为D3D_DEFAILT。
  g_App.GetDevice()->CreateTexture(256,256,1,D3DUSAGE_RENDERTARGET,D3DFMT_R5G6B5,D3DPOOL_DEFAULT,&pRenderTexture,NULL);
  为了访问纹理内存对象,我们需要一个Surface对象,因为D3D中的纹理是用这样的一个Surface来存储纹理数据的。为了得到纹理表面的Surface,我们需要调用方法GetSurfaceLevel() 。第一个参数我们设为0,第二个参数为一个指向surface对象的指针。
  pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);
  下一步就是创建一个适合纹理维数的投影矩阵,因为纹理的横纵比和后台缓冲区的不一样。
  D3DXMatrixPerspectiveFovLH(&matProjection,D3DX_PI / 4.0f,1,1,100);
  在我们的循环渲染之前,我们必须保存后台缓冲区和它的投影矩阵。
  g_App.GetDevice()->GetTransform(D3DTS_PROJECTION,&matOldProjection);
  g_App.GetDevice()->GetRenderTarget(0,&pBackBuffer);
  渲染循环函数可以分为两个部分。第一部分是渲染到纹理的过程。因此,渲染对象必须设为纹理表面。然后我们就可以把东西渲染到这个对象上了。渲染到另一个表面上和正常地渲染到后台缓冲区差不多。只有一点不同,那就是先不调用Prensent()函数,因为纹理上的内容并不需要显示在屏幕上。象平时一样,我们先要重置表面颜色缓冲区,并且调用BeginSence()和EndSence()方法。为了能够适当的渲染,我们必须设置和纹理表面相符的投影矩阵。否则最后的图象可能被扭曲
  //render-to-texture
  g_App.GetDevice()->SetRenderTarget(0,pRenderSurface); //set new render target
  g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(100,100,100),1.0f,0); //clear texture
  g_App.GetDevice()->BeginScene();
  g_App.GetDevice()->SetTexture(0,pPyramideTexture);
  D3DXMatrixRotationY(&matRotationY,fRotation);
  D3DXMatrixTranslation(&matTranslation,0.0f,0.0f,5.0f);
  g_App.GetDevice()->SetTransform(D3DTS_WORLD,&(matRotationY * matTranslation));
  g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matProjection); //set projection matrix
  g_App.GetDevice()->SetStreamSource(0,pTriangleVB,0,sizeof(D3DVERTEX));
  g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLELIST,0,4);
  g_App.GetDevice()->EndScene();
  渲染循环的第二部分就是渲染最后场景的过程(也就是显示到屏幕上的过程)。渲染对象重新设为后台缓冲区,投影矩阵重新设为原来的投影矩阵。由于纹理已经准备好了,所以它和纹理层0相关联。
  //render scene with texture
  g_App.GetDevice()->SetRenderTarget(0,pBackBuffer); //set back buffer
  g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);
  g_App.GetDevice()->BeginScene();
  g_App.GetDevice()->SetTexture(0,pRenderTexture); //set rendered texture
  g_App.GetDevice()->SetTransform(D3DTS_WORLD,&matTranslation);
  g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matOldProjection); //restore projection matrix
  g_App.GetDevice()->SetStreamSource(0,pQuadVB,0,sizeof(D3DVERTEX));
  g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2);
  g_App.GetDevice()->EndScene();
  g_App.GetDevice()->Present(NULL,NULL,NULL,NULL);
  最后我们通过调用Release()方法释放Surface对象。
  pRenderSurface->Release();
  pRenderSurface = NULL;
  pBackBuffer->Release();
  pBackBuffer = NULL;
  渲染到纹理能让你做很多事情,但是你必须注意一些限制。首先深度缓冲区必须总是大于或等于渲染对象的大小。此外,渲染对象和深度缓冲区的格式必须一致。

                      ==Ph4nt0m Security Team==
                   Issue 0x01, Phile #0x03 of 0x06  

|=—————————————————————————=| |=———————=[ 做一个优秀的木匠 ]=———————=| |=—————————————————————————=| |=—————————————————————————=| |=——————–=[ By F.Zh ]=——————–=| |=—————————————————————————=| |=—————————————————————————=|
[本文内容可能会伤及到部分名人粉丝感情,作者表示仅为插科打诨之用,并无恶意] 有副图描述了从发现漏洞到最后盈利的过程,大概意思是研究人员发现了房子的漏洞,木 匠针对漏洞造了一个梯子,最后脚本小子进屋偷东西。国内的圈子里面,玩票性质的安全爱好 者大多不愿意做脚本小子,同时也不见得有足够的时间去找房子的漏洞,所以闲暇时候基本上 做做木匠活当消遣。但木匠也是有三六九等的,有朱由校,有鲁班,也有就只能给地主老财家 做楠木棺材的。作为一个有职业道德的木匠,显然应该努力向前面两个靠拢,因为只能做做楠 木棺材的,未免也太失面子了。
这篇文章就从国内某著名破解论坛搞的科普竞赛开始,由一个楠木棺材级别的木匠挣扎 着介绍一下放眼能够看到的技巧。在切入正题前,有必要介绍一下科普竞赛的背景和结果: 大约是看到windows漏洞太值钱,破解组织也开始搞起了逆向和exploit,而且还以竞赛的方 式来引起非木匠的关注。科普竞赛的题目是两道,如Sowhat所说 ( http://hi.baidu.com/secway/blog/item/cb121863a6af72640c33facf.html ),第二道题是 可以Google到的,而第一道题显然是个送分题,因此科普竞赛实际上是个比手快的过程。最 后结果是nop拿了第一,这个名字让人不禁联想到了五一国际劳动节和革命先烈鲜血的颜色, 当然,我们依然怀着无比的敬仰和美好的期望,希望这个nop不是职业运动员参加了业余比赛。 先看看存在问题的程序。逆向很简单,但是为了方便,还是直接给出官方公布的源代码。 具有严重自虐倾向的木匠请编译后用ida逆向一下,并自备低温蜡烛和爱心小皮鞭。 ========================和谐的分割线=================================

在游戏开发和维护过程中,客户端都是不断更新的,伴随着每一次的更新,都会发布
一个更新补丁包来对旧的客户端进行更新,来使其变成新的客户端,补丁包应该包含
更新成新客户端的最少量资源(最大量资源就是整个新的客户端覆盖旧的)
更新程序通过读一个更新脚本,对旧的客户端进行文件添加,文件覆盖,文件删除等
操作来更新旧的客户端,当更新量比较少比较简单的情况下,更新脚本可以资源整理
人员自己写,但当更新量太大,资源多而杂的情况下,手写更新脚本就变得极容易出
错了,所以有必要开发一个工具自动查找两个版本的差异,自动生成更新脚本。
比较的方法:
假设有两个文件夹A,和文件夹B,A是旧的客户端,B是新的客户端,需要通过算法来
找出两个文件夹的差异,并生成脚本,此脚本即明确的表明一些操作能将A变成B的过
程。
1.遍历A文件夹中的所有文件名,包括子目录,储存到O中去
2.遍历B文件夹中的所有文件名,包括子目录,储存到N中去
3.找出需要添加到旧版本中的文件:
通过遍历N中每个文件,查询是否在O中存在,如果不存在的文件则为需要添加的文
,储存到FA中去。
4.找出旧版本中需要删除的文件:
通过遍历O中每个文件,查询是否在N中存在,如果不存在则表示此文件需要删除,
并储存到FD中去。
5.找出旧版本中需要覆盖的文件:
通过遍历N中每个文件,找出在O中也同样存在此文件,这里把游戏用的资源包排除掉,
它们将在后面的操作中检测并更新,如果找到的两个相同的文件,比较文件内容只要
一个字节的内容不相同,则说明旧的版本中的此文件需要被新的版本中的文件覆盖掉
6.游戏资源包的更新操作:
通过遍历N中的每个为游戏资源包类型的文件,并查找O中是否
同样存在此文件,如果存在再比较两个内容是否相同,如果有一个字节不相同就表示内
容不同,则跳入下面包的更新操作中去。
a. 遍历旧包内的所有文件,储存到PO中
b. 遍历新包内的所有文件,储存到PN中
c. 找出旧包内需要添加的文件:
   通过遍历PN中每个文件,找出在PO中没有的,储存到PA中去。
d. 找出旧包内需要删除的文件:
   通过遍历PO中每个文件,找出在PN中没有的,储存到PD中去。
e. 找出旧包内需要覆盖的文件:
   通过遍历PO中每个文件,如果PN中也存在此文件,如果他们之间有一个字节不相同
   则表示旧包内的此文件需要覆盖掉,储存到PR中去。
通过上面的过程,两个文件夹的差异已经找出来了,这时就可以根据差异信息生成更新脚本,
同时把旧版本需要添加,覆盖,包内需要添加,覆盖的文件抽取出来,生成资源包。
下面截图是我写的一个版本比较工具的截图:

uploads/200803/21_153511_vctools.jpg

最后需要添加和替换的资源全部复制到resource目录下去
生成的更新脚本类似如下:

<VersionCompare>
  <Version Old="1.0" New="1.1" />
  <Resource Path="./resource/" />
  <UpdateActions>
    <FileActions>
      <Add From="0.dat" To="SkyBox/NewPictures/anc_elephantear1.PNG" />
      <Add From="1.dat" To="SkyBox/NewTexts/Apple.txt" />
      <Add From="2.dat" To="SkyBox/NewTexts/Pear.txt" />
      <Add From="3.dat" To="SkyBox/NewTexts/Orange.Txt" />
      <Add From="4.dat" To="TerrainMaterial/GoodLcuk.doc" />
      <Add From="5.dat" To="WaterColour/半兽人.mp3" />
      <Add From="6.dat" To="ABc1.sgp" />
      <Delete Where="SkyBox/bm00500SkyBox_BK.jpg" />
      <Delete Where="ShadowLayer/TerrainBlock16.tga" />
      <Delete Where="ShadowLayer/TerrainBlock36.tga" />
      <Delete Where="ShadowLayer/TerrainBlock44.tga" />
      <Delete Where="ShadowLayer/TerrainBlock63.tga" />
      <Replace From="7.dat" ToReplace="00500.xml" />
      <Replace From="8.dat" ToReplace="ShadowLayer/TerrainBlock46.tga" />
      <Replace From="9.dat" ToReplace="SkyBox/SkyBox.material" />
      <Replace From="10.dat" ToReplace="TerrainMaterial/TerrainMaterials.material" />
      <Replace From="11.dat" ToReplace="WaterColour/WaterColour_bm00500.tga" />
    </FileActions>
    <PackageActions Package="ShadowLayer/abc1.sgp">
      <Add From="12.dat" To="复件 skybox/terrainblock7.tga" />
      <Delete Where="00002.dat" />
      <Delete Where="00002.dat.addons" />
      <Delete Where="00002.xml.bak" />
      <Delete Where="bf00002.xml.bak" />
      <Replace From="13.dat" ToReplace="aaa.xml" />
      <Replace From="14.dat" ToReplace="skybox/lava_01.tga" />
      <Replace From="15.dat" ToReplace="skybox/skybox.material" />
      <Replace From="16.dat" ToReplace="复件 skybox/skybox.material" />
    </PackageActions>
    <PackageActions Package="SkyBox/abc1.sgp">
      <Add From="17.dat" To="shadowlayer/bm00500skybox_bk.jpg" />
      <Delete Where="skybox/bf00002skybox_bk.dds" />
      <Delete Where="skybox/bf00002skybox_dn.dds" />
      <Delete Where="skybox/bf00002skybox_fr.dds" />
      <Delete Where="skybox/bf00002skybox_lf.dds" />
      <Delete Where="skybox/bf00002skybox_rt.dds" />
      <Delete Where="skybox/bf00002skybox_up.dds" />
      <Delete Where="skybox/lava_01.tga" />
      <Delete Where="skybox/skybox.material" />
      <Delete Where="skybox/thumbs.db" />
      <Replace From="18.dat" ToReplace="aaa.xml" />
      <Replace From="19.dat" ToReplace="复件 skybox/skybox.material" />
    </PackageActions>
    <PackageActions Package="TerrainMaterial/abc1.sgp">
      <Add From="20.dat" To="新建文件夹/bm00500terrain.jpg" />
      <Delete Where="watercolour/bf00002wateredge.dds" />
      <Replace From="21.dat" ToReplace="aaa.xml" />
      <Replace From="22.dat" ToReplace="skybox/lava_01.tga" />
      <Replace From="23.dat" ToReplace="skybox/skybox.material" />
      <Replace From="24.dat" ToReplace="复件 skybox/skybox.material" />
    </PackageActions>
    <PackageActions Package="WaterColour/abc1.sgp">
      <Add From="25.dat" To="skybox/watercolour_bm00500.tga" />
      <Delete Where="shadowlayer/shadowlayer.rar" />
      <Delete Where="shadowlayer/terrainblock0.tga" />
      <Delete Where="shadowlayer/terrainblock1.tga" />
      <Delete Where="shadowlayer/terrainblock10.tga" />
      <Delete Where="shadowlayer/terrainblock11.tga" />
      <Delete Where="shadowlayer/terrainblock12.tga" />
      <Delete Where="shadowlayer/terrainblock13.tga" />
      <Delete Where="shadowlayer/terrainblock14.tga" />
      <Delete Where="shadowlayer/terrainblock15.tga" />
      <Delete Where="shadowlayer/terrainblock16.tga" />
      <Delete Where="shadowlayer/terrainblock17.tga" />
      <Delete Where="shadowlayer/terrainblock18.tga" />
      <Delete Where="shadowlayer/terrainblock19.tga" />
      <Delete Where="shadowlayer/terrainblock2.tga" />
      <Delete Where="shadowlayer/terrainblock20.tga" />
      <Delete Where="shadowlayer/terrainblock21.tga" />
      <Delete Where="shadowlayer/terrainblock22.tga" />
      <Delete Where="shadowlayer/terrainblock23.tga" />
      <Delete Where="shadowlayer/terrainblock24.tga" />
      <Delete Where="shadowlayer/terrainblock25.tga" />
      <Delete Where="shadowlayer/terrainblock26.tga" />
      <Delete Where="shadowlayer/terrainblock27.tga" />
      <Delete Where="shadowlayer/terrainblock28.tga" />
      <Delete Where="shadowlayer/terrainblock29.tga" />
      <Delete Where="shadowlayer/terrainblock3.tga" />
      <Delete Where="shadowlayer/terrainblock30.tga" />
      <Delete Where="shadowlayer/terrainblock31.tga" />
      <Delete Where="shadowlayer/terrainblock32.tga" />
      <Delete Where="shadowlayer/terrainblock33.tga" />
      <Delete Where="shadowlayer/terrainblock34.tga" />
      <Delete Where="shadowlayer/terrainblock35.tga" />
      <Delete Where="shadowlayer/terrainblock36.tga" />
      <Delete Where="shadowlayer/terrainblock37.tga" />
      <Delete Where="shadowlayer/terrainblock38.tga" />
      <Delete Where="shadowlayer/terrainblock39.tga" />
      <Delete Where="shadowlayer/terrainblock4.tga" />
      <Delete Where="shadowlayer/terrainblock40.tga" />
      <Delete Where="shadowlayer/terrainblock41.tga" />
      <Delete Where="shadowlayer/terrainblock42.tga" />
      <Delete Where="shadowlayer/terrainblock43.tga" />
      <Delete Where="shadowlayer/terrainblock44.tga" />
      <Delete Where="shadowlayer/terrainblock45.tga" />
      <Delete Where="shadowlayer/terrainblock46.tga" />
      <Delete Where="shadowlayer/terrainblock47.tga" />
      <Delete Where="shadowlayer/terrainblock48.tga" />
      <Delete Where="shadowlayer/terrainblock49.tga" />
      <Delete Where="shadowlayer/terrainblock5.tga" />
      <Delete Where="shadowlayer/terrainblock50.tga" />
      <Delete Where="shadowlayer/terrainblock51.tga" />
      <Delete Where="shadowlayer/terrainblock52.tga" />
      <Delete Where="shadowlayer/terrainblock53.tga" />
      <Delete Where="shadowlayer/terrainblock54.tga" />
      <Delete Where="shadowlayer/terrainblock55.tga" />
      <Delete Where="shadowlayer/terrainblock56.tga" />
      <Delete Where="shadowlayer/terrainblock57.tga" />
      <Delete Where="shadowlayer/terrainblock58.tga" />
      <Delete Where="shadowlayer/terrainblock59.tga" />
      <Delete Where="shadowlayer/terrainblock6.tga" />
      <Delete Where="shadowlayer/terrainblock60.tga" />
      <Delete Where="shadowlayer/terrainblock61.tga" />
      <Delete Where="shadowlayer/terrainblock62.tga" />
      <Delete Where="shadowlayer/terrainblock63.tga" />
      <Delete Where="shadowlayer/terrainblock7.tga" />
      <Delete Where="shadowlayer/terrainblock8.tga" />
      <Delete Where="shadowlayer/terrainblock9.tga" />
      <Replace From="26.dat" ToReplace="aaa.xml" />
      <Replace From="27.dat" ToReplace="skybox/lava_01.tga" />
      <Replace From="28.dat" ToReplace="skybox/skybox.material" />
      <Replace From="29.dat" ToReplace="复件 skybox/skybox.material" />
    </PackageActions>
  </UpdateActions>
</VersionCompare>

www.azure.com.cn

今天餐馆有两伙人打架,其他无关的人都跑掉了,只有我没有离开座位,微笑的看着他们。我觉得自己非常酷。
突然有一个人指着我说:打他们丫老大!我刚要说我不是,一个酒瓶子就把我头打开了花。然后几个人过来揣我。另一伙看他们在打不认识的人竟然也不帮忙。
我快被打半死时pol.ice来了,还把我当成主犯拉回去审讯。刚才才被家长领回家。我现在悟出了一个非常深刻的道理,就是:
没实力,千万别装B!

五、加载类的源代码。(编译环境vc6,win98)

typedef   BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD,  LPVOID );

class CMemLoadDll
{
public:
CMemLoadDll();
~CMemLoadDll();
BOOL    MemLoadLibrary( void* lpFileData , int DataLength);  // Dll file data buffer
FARPROC MemGetProcAddress(LPCSTR lpProcName);
private:
BOOL isLoadOk;
BOOL CheckDataValide(void* lpFileData, int DataLength);
int  CalcTotalImageSize();
void CopyDllDatas(void* pDest, void* pSrc);
BOOL FillRavAddress(void* pBase);
void DoRelocation(void* pNewBase);
int  GetAlignedSize(int Origin, int Alignment);
private:
ProcDllMain pDllMain;

private:
DWORD  pImageBase;
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_SECTION_HEADER pSectionHeader;
};

CMemLoadDll::CMemLoadDll()
{
isLoadOk = FALSE;
pImageBase = NULL;
pDllMain = NULL;
}
CMemLoadDll::~CMemLoadDll()
{
if(isLoadOk)
{
  ASSERT(pImageBase != NULL);
  ASSERT(pDllMain   != NULL);
  //脱钩,准备卸载dll
  pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);
  VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);
}
}

//MemLoadLibrary函数从内存缓冲区数据中加载一个dll到当前进程的地址空间,缺省位置0x10000000
//返回值: 成功返回TRUE , 失败返回FALSE
//lpFileData: 存放dll文件数据的缓冲区
//DataLength: 缓冲区中数据的总长度
BOOL CMemLoadDll::MemLoadLibrary(void* lpFileData, int DataLength)
{
if(pImageBase != NULL)
{
  return FALSE;  //已经加载一个dll,还没有释放,不能加载新的dll
}
//检查数据有效性,并初始化
if(!CheckDataValide(lpFileData, DataLength))return FALSE;
//计算所需的加载空间
int ImageSize = CalcTotalImageSize();
if(ImageSize == 0) return FALSE;

// 分配虚拟内存
void *pMemoryAddress = VirtualAlloc((LPVOID)0x10000000, ImageSize,
     MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(pMemoryAddress == NULL) return FALSE;
else
{
  CopyDllDatas(pMemoryAddress, lpFileData); //复制dll数据,并对齐每个段
  //重定位信息
  if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress >0
   && pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size>0)
  {
   DoRelocation(pMemoryAddress);
  }
  //填充引入地址表
  if(!FillRavAddress(pMemoryAddress)) //修正引入地址表失败
  {
   VirtualFree(pMemoryAddress,0,MEM_RELEASE);
   return FALSE;
  }
  //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。
  //统一设置成一个属性PAGE_EXECUTE_READWRITE
  unsigned long old;
  VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&old);
}
//修正基地址
pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress;

//接下来要调用一下dll的入口函数,做初始化工作。
pDllMain = (ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress);
BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);
if(!InitResult) //初始化失败
{
  pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);
  VirtualFree(pMemoryAddress,0,MEM_RELEASE);
  pDllMain = NULL;
  return FALSE;
}

isLoadOk = TRUE;
pImageBase = (DWORD)pMemoryAddress;
return TRUE;
}

//MemGetProcAddress函数从dll中获取指定函数的地址
//返回值: 成功返回函数地址 , 失败返回NULL
//lpProcName: 要查找函数的名字或者序号
FARPROC  CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName)
{
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||
  pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)
  return NULL;
if(!isLoadOk) return NULL;

DWORD OffsetStart = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
DWORD Size = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
int iBase = pExport->Base;
int iNumberOfFunctions = pExport->NumberOfFunctions;
int iNumberOfNames = pExport->NumberOfNames; //<= iNumberOfFunctions
LPDWORD pAddressOfFunctions = (LPDWORD)(pExport->AddressOfFunctions + pImageBase);
LPWORD  pAddressOfOrdinals = (LPWORD)(pExport->AddressOfNameOrdinals + pImageBase);
LPDWORD pAddressOfNames  = (LPDWORD)(pExport->AddressOfNames + pImageBase);

int iOrdinal = -1;

if(((DWORD)lpProcName & 0xFFFF0000) == 0) //IT IS A ORDINAL!
{
  iOrdinal = (DWORD)lpProcName & 0x0000FFFF - iBase;
}
else  //use name
{
  int iFound = -1;

  for(int i=0;i<iNumberOfNames;i++)
  {
   char* pName= (char* )(pAddressOfNames[i] + pImageBase);
   if(strcmp(pName, lpProcName) == 0)
   {
    iFound = i; break;
   }
  }
  if(iFound >= 0)
  {
   iOrdinal = (int)(pAddressOfOrdinals[iFound]);
  }
}

if(iOrdinal < 0 || iOrdinal >= iNumberOfFunctions ) return NULL;
else
{
  DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];
  if(pFunctionOffset > OffsetStart && pFunctionOffset < (OffsetStart+Size))//maybe Export Forwarding
   return NULL;
  else return (FARPROC)(pFunctionOffset + pImageBase);
}

}

// 重定向PE用到的地址
void CMemLoadDll::DoRelocation( void *NewBase)
{
/* 重定位表的结构:
// DWORD sectionAddress, DWORD size (包括本节需要重定位的数据)
// 例如 1000节需要修正5个重定位数据的话,重定位表的数据是
// 00 10 00 00   14 00 00 00      xxxx xxxx xxxx xxxx xxxx 0000
// -----------   -----------      ----
// 给出节的偏移  总尺寸=8+6*2     需要修正的地址           用于对齐4字节
// 重定位表是若干个相连,如果address 和 size都是0 表示结束
// 需要修正的地址是12位的,高4位是形态字,intel cpu下是3
*/
//假设NewBase是0x600000,而文件中设置的缺省ImageBase是0x400000,则修正偏移量就是0x200000
DWORD Delta = (DWORD)NewBase - pNTHeader->OptionalHeader.ImageBase;

//注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase
  + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //开始扫描重定位表
{
  WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));
  //计算本节需要修正的重定位项(地址)的数目
  int NumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);
  for( int i=0 ; i < NumberOfReloc; i++)
  {
   if( (DWORD)(pLocData[i] & 0xF000) == 0x00003000) //这是一个需要修正的地址
   {
    // 举例:
    // pLoc->VirtualAddress = 0x1000;
    // pLocData[i] = 0x313E; 表示本节偏移地址0x13E处需要修正
    // 因此 pAddress = 基地址 + 0x113E
    // 里面的内容是 A1 ( 0c d4 02 10)  汇编代码是: mov eax , [1002d40c]
    // 需要修正1002d40c这个地址
    DWORD * pAddress = (DWORD *)((unsigned long)NewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
    *pAddress += Delta;
   }
  }
  //转移到下一个节进行处理
  pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock);
}
}

//填充引入地址表
BOOL CMemLoadDll::FillRavAddress(void *pImageBase)
{
// 引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组,全部是0表示结束
// 数组定义如下:
//
    // DWORD   OriginalFirstThunk;         // 0表示结束,否则指向未绑定的IAT结构数组
    // DWORD   TimeDateStamp;
    // DWORD   ForwarderChain;             // -1 if no forwarders
    // DWORD   Name;                       // 给出dll的名字
    // DWORD   FirstThunk;                 // 指向IAT结构数组的地址(绑定后,这些IAT里面就是实际的函数地址)
unsigned long Offset = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;
if(Offset == 0) return TRUE; //No Import Table
PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);
while(pID->Characteristics != 0 )
{
  PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->FirstThunk);
  PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->OriginalFirstThunk);
  //获取dll的名字
  char buf[256]; //dll name;
  BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID->Name);
  for(int i=0;i<256;i++)
  {
   if(pName[i] == 0)break;
   buf[i] = pName[i];
  }
  if(i>=256) return FALSE;  // bad dll name
  else buf[i] = 0;
  HMODULE hDll = GetModuleHandle(buf);
  if(hDll == NULL)return FALSE; //NOT FOUND DLL
  //获取DLL中每个导出函数的地址,填入IAT
  //每个IAT结构是 :
  // union { PBYTE  ForwarderString;
        //   PDWORD Function;
        //   DWORD Ordinal;
        //   PIMAGE_IMPORT_BY_NAME  AddressOfData;
  // } u1;
  // 长度是一个DWORD ,正好容纳一个地址。
  for(i=0; ;i++)
  {
   if(pOriginalIAT[i].u1.Function == 0)break;
   FARPROC lpFunction = NULL;
   if(pOriginalIAT[i].u1.Ordinal & IMAGE_ORDINAL_FLAG) //这里的值给出的是导出序号
   {
    lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal & 0x0000FFFF));
   }
   else //按照名字导入
   {
    //获取此IAT项所描述的函数名称
    PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)
     ((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData));
//    if(pByName->Hint !=0)
//     lpFunction = GetProcAddress(hDll, (LPCSTR)pByName->Hint);
//    else
     lpFunction = GetProcAddress(hDll, (char *)pByName->Name);
   }
   if(lpFunction != NULL)   //找到了!
   {
    pRealIAT[i].u1.Function = (PDWORD) lpFunction;
   }
   else return FALSE;
  }

  //move to next
  pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));
}
return TRUE;
}

//CheckDataValide函数用于检查缓冲区中的数据是否有效的dll文件
//返回值: 是一个可执行的dll则返回TRUE,否则返回FALSE。
//lpFileData: 存放dll数据的内存缓冲区
//DataLength: dll文件的长度
BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength)
{
//检查长度
if(DataLength < sizeof(IMAGE_DOS_HEADER)) return FALSE;
pDosHeader = (PIMAGE_DOS_HEADER)lpFileData;  // DOS头
//检查dos头的标记
if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;  //0x5A4D : MZ

//检查长度
if((DWORD)DataLength < (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)) ) return FALSE;
//取得pe头
pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader->e_lfanew); // PE头
//检查pe头的合法性
if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return FALSE;  //0x00004550 : PE00
if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0x2000  : File is a DLL
  return FALSE; 
if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以运行
  return FALSE;
if(pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) return FALSE;

//取得节表(段表)
pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));
//验证每个节表的空间
for(int i=0; i< pNTHeader->FileHeader.NumberOfSections; i++)
{
  if((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) > (DWORD)DataLength)return FALSE;
}
return TRUE;
}

//计算对齐边界
int CMemLoadDll::GetAlignedSize(int Origin, int Alignment)
{
return (Origin + Alignment - 1) / Alignment * Alignment;
}
//计算整个dll映像文件的尺寸
int CMemLoadDll::CalcTotalImageSize()
{
int Size;
if(pNTHeader == NULL)return 0;
int nAlign = pNTHeader->OptionalHeader.SectionAlignment; //段对齐字节数

// 计算所有头的尺寸。包括dos, coff, pe头 和 段表的大小
Size = GetAlignedSize(pNTHeader->OptionalHeader.SizeOfHeaders, nAlign);
// 计算所有节的大小
for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i)
{
  //得到该节的大小
  int CodeSize = pSectionHeader[i].Misc.VirtualSize ;
  int LoadSize = pSectionHeader[i].SizeOfRawData;
  int MaxSize = (LoadSize > CodeSize)?(LoadSize):(CodeSize);

  int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);
  if(Size < SectionSize)
   Size = SectionSize;  //Use the Max;
}
return Size;
}
//CopyDllDatas函数将dll数据复制到指定内存区域,并对齐所有节
//pSrc: 存放dll数据的原始缓冲区
//pDest:目标内存地址
void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc)
{
// 计算需要复制的PE头+段表字节数
int  HeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders;
int  SectionSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
int  MoveSize = HeaderSize + SectionSize;
//复制头和段信息
memmove(pDest, pSrc, MoveSize);

//复制每个节
for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i)
{
  if(pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)continue;
  // 定位该节在内存中的位置
  void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);
  // 复制段数据到虚拟内存
  memmove((void *)pSectionAddress,
       (void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),
    pSectionHeader[i].SizeOfRawData);
}

//修正指针,指向新分配的内存
//新的dos头
pDosHeader = (PIMAGE_DOS_HEADER)pDest;
//新的pe头地址
pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader->e_lfanew));
//新的节表地址
pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));
return ;
}

程序使用动态库DLL一般分为隐式加载和显式加载两种,分别对应两种链接情况。本文主要讨论显式加载的技术问题。我们知道,要显式加载一个DLL,并取得其中导出的函数地址一般是通过如下步骤:
    (1) 用LoadLibrary加载dll文件,获得该dll的模块句柄;
    (2) 定义一个函数指针类型,并声明一个变量;
    (3) 用GetProcAddress取得该dll中目标函数的地址,赋值给函数指针变量;
    (4) 调用函数指针变量。

    这个方法要求dll文件位于硬盘上面。现在假设我们的dll已经位于内存中,比如通过脱壳、解密或者解压缩得到,能不能不把它写入硬盘文件,而直接从内存加载呢?答案是肯定的。经过多天的研究,非法操作了N次,修改了M个BUG,死亡了若干脑细胞后,终于有了初步的结果,下面做个总结与大家共享。

一、加载的步骤

    由于没有相关的资料说明,只能凭借感觉来写。首先LoadLibrary是把dll的代码映射到exe进程的虚拟地址空间中,我们要实现的也是这个。所以先要弄清楚dll的文件结构。好在这个比较简单,它和exe一样也是PE文件结构,关于PE文件的资料很多,阅读一番后,基本上知道了必须做的几个工作:
    (1)判断内存数据是否是一个有效的DLL。这个功能通过函数CheckDataValide完成。原型是:
        BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength);
    (2)计算加载该DLL所需的虚拟内存大小。这个功能通过函数CalcTotalImageSize完成。原型是:
        int CMemLoadDll::CalcTotalImageSize();
    (3)将DLL数据复制到所分配的虚拟内存块中。该功能通过函数CopyDllDatas完成。要注意段对齐。
        void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc);
    (4)修正基地重定位数据。这个功能通过函数DoRelocation完成。原型是:
        void CMemLoadDll::DoRelocation( void *NewBase);
    (5)填充该DLL的引入地址表。这个功能由函数FillRavAddress完成。原型是:
        BOOL CMemLoadDll::FillRavAddress(void *pImageBase);
    (6)根据DLL每个节的属性设置其对应内存页的读写属性。我这里做了简化,所有内存区域都设置成一样的读写属性。
    (7)调用入口函数DllMain,完成初始化工作。这一步我一开始忽略了,所以总是发现自己加载的dll和LoadLibrary加载的dll有些不同(我把整块内存区域保存到两个文件中进行比较,够晕的)。只是最近猜想到还需要这一步。
    (8)保存dll的基地址(即分配的内存块起始地址),用于查找dll的导出函数。从现在开始这个dll已经完全映射到了进程的虚拟地址空间,可以使用它了。
    (9)不需要dll的时候,释放所分配的虚拟内存。

二、要说明的几个问题

   (1)目前CMemLoadDll仅仅针对win32 动态库,没有考虑mfc常规和扩展dll。
   (2)只考虑使用dll中的函数,对于导出类的dll,由于通常都是隐式链接,所以也没有考虑。导出变量的dll虽然也是隐式链接,但是通过查找函数的方法也可以找到该变量,不过在取值的时候一定要符合dll中对变量的定义,比如dll中导出的是一个int变量,则得到该变量在dll中的地址后,需要强制转换成int*指针,然后取值。
   (3)查找函数的功能通过函数
       FARPROC  CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName);
实现,参数是dll导出的函数(或者变量)的名字。这里必须注意函数名修饰,通常不加extern"C"的函数,编译以后在dll中导出的都是修饰名,比如:
    在dll头文件中: extern __declspec(dllexport) int nTestDll;
    在.dll中的导出符号变成 ?nTestDll@@3HA
   所以,为了能够找到我们需要的函数,必须在.h中添加extern "C"修饰。最好是给dll加一个def文件,里面明确给出每个函数的导出名字。
   (4)PE中的内容比较多,有些细节没有考虑。比如CheckDataValide函数中没有考虑dll对操作系统版本的要求。
   (5)PE文件中的节有很多种。可以从节表(或者叫做区块表)中一一找到。而且每个节的属性都不同。例如:.text, .data, .rsrc, .crt等等。由于这个代码基于手头已有的pe文件资料,对于不熟悉的节,在映射dll数据的时候没有考虑是否需要处理。
   (6)一开始把dll映射到进程的地址空间以后,我试图直接使用GetProcAddress查找函数。最初我认为LoadLibrary返回的HINSTANCE值是0x10000000,把它传递给GetProcAddress可以找到目标函数,而我也把dll映射到0x10000000这个地址,但是当我把这个值传递给GetProcAddress的时候,发现无法找到函数,用GetLastError得到错误码一看是无效句柄的错误,这才明白原来LoadLibrary在加载dll的时候,同时创建了一个句柄放入进程的句柄表,而我们要做这个工作是比较麻烦的,所以只能自己写一个查找函数。
   (7)释放dll所占据的虚拟内存,原来我使用
      VirtualFree((LPVOID)pImageBase, 0,MEM_FREE);
后来发现有问题,应该使用 VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);
   (8)MemGetProcAddress不仅支持通过函数名查找,还支持通过导出序号查找函数。例如下面的用法:
DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress((LPCTSTR)1);

三、创建测试用的DLL,工程的名字取"TestDll"

    用VC向导创建一个WIN32 DLL工程,里面选择“导出一些符号”,为了测试需要,对源代码进行如下修改:
(1)头文件
    // This class is exported from the TestDll.dll
    class TESTDLL_API CTestDll {
        public:
CTestDll(void);
    };
    extern TESTDLL_API int nTestDll;
    //要修改的地方,添加了extern "C" 和 char *参数:
    extern "C"  TESTDLL_API int fnTestDll(char *);
  (2)cpp文件
      a. 添加 #include "stdlib.h"
      b. DllMain中
  case DLL_PROCESS_DETACH:
   nTestDll = 12345;
   break;
      c. 初始化变量
         TESTDLL_API int nTestDll=654321;
      d. 修改函数
         TESTDLL_API int fnTestDll(char *p)
         {
          if(p == NULL)
      return nTestDll;
          else
      return atoi(p);
          }

四、创建测试工程。使用一个dlg工程,测试代码如下:

    假设 DllNameBuffer里面保存有dll文件的路径
CFile f;
if(f.Open(DllNameBuffer,CFile::modeRead))
{
  int FileLength = f.GetLength();
  void *lpBuf = new char[FileLength];
  f.Read(lpBuf, FileLength);
  f.Close();

  CMemLoadDll a;
  if(a.MemLoadLibrary(lpBuf, FileLength)) //加载dll到当前进程的地址空间
  {
   typedef  int (*DLLFUNCTION)(char *);
   DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress("fnTestDll");
   if(fDll != NULL)
   {
    MessageBox("找到函数!!");
    CString str;
    str.Format("Result is: %d & %d",fDll(NULL), fDll("100"));
    MessageBox(str);
   }
   else
   {
    DWORD err = GetLastError();
    CString str;
    str.Format("Error: %d",err);
    MessageBox(str);
   }
  }

  delete[] lpBuf;
}

五、加载类源代码。(在后续贴子里面给出)

每个DOTA PLAYER都希望自己能拥有绝世神功,体验那种局局超神的快感和被别人认可的延伸!你是否苦于自己的基本功不够扎实?因为FARM速度问题不能成为一个 合格的DPS?你是否苦与天天拼命练习却看不到自己进步?你是否苦与想找到一个迅速提高自己DOTA水平的捷径?如果你有这些问题,那么我将给大家带来的 是一部DOTA高手速成宝典! 一 基本功 重中之重

2008.3.31
》什么是骄傲?牛呗!什么是爱情?骗呗!什么是温柔?贱呗!什么是艺术?脱呗!
什么是仗义?傻呗!什么是聪明?吹呗!什么是勤俭?抠呗!什么是朋友?你呗!
什么是可爱?我呗!什么是谦虚?装呗!什么是勇敢?二呗!什么是幽默?贫呗!
》看了神雕侠侣知道年龄不是问题;
看了断背山知道性别不是问题;
看了金刚发现原来物种也不是问题;
想起人鬼情未了才真正懂得...死活都不是问题!
2008.4.1
》我终究没能飚得过那辆宝马,只能眼看着它在夕阳中绝尘而去,不是我的引擎不
好,而是我的车链子掉了。
》人生没有彩排,每天都是直播,不仅收视率低,而且工资不高。
》天天吃稀饭,不甘心,昨天去菜市场绕了一圈,我想我还是继续吃稀饭吧。
》每当我错过一个女孩,我就在地上放一块砖,于是便有了长城。
2008.4.2
》吃自助餐最高境界:扶墙进,扶墙出。
》不怕虎一样的敌人,就怕猪一样的队友。
》夏天就是不好,穷的时候我连西北风都没得喝...
》怀才就像怀孕,时间久了才能让人看出来。
》你以为我会眼睁睁地看着你去送死?我会闭上!
2008.4.3
》留了青山在,还是没柴烧。
》纯,属虚构,乱,是佳人。
》红杏不出墙,坚决拽出来。
》圣诞快乐,说明你平时不快乐。
》天没降大任于我,照样苦我心智,劳我筋骨。
2008.4.4
》我在女友手机里的名字是“他”,分手后,我就变成了“它”。
》众里寻她千百度,蓦然回首,那人依旧对我不屑一顾。
》听说女人如衣服,兄弟如手足。回想起来,咱这尊千手观音竟然裸奔了20多年!
》念了十几年书,想起来还是幼儿园比较好混。
》什么是幸福?幸福就是猫吃鱼,狗吃肉,奥特曼打小怪兽。
》生活中一大半麻烦是由于你说Yes说的太快,说No说的太慢。
2008.4.5
》你可以跑不过刘翔,但你必须要跑过CPI!
》地铁上的广告:挤吗?买辆车吧!出租车上的广告:堵吗?坐地铁吧!
》谎言与誓言的区别在于,一个是听的人当真了,一个是说的人当真了。
》工资真的要涨了,能给孩子奖赏了,见到老婆敢嚷了,敢尝到海鲜鸭掌了,闲时能
逛商场了,遇见美女心痒了,结果物价又涨了,一切都是白想了。
2008.4.6
》一等女人家里称霸,二等女人在家吵闹,三等女人家中挨打,四等女人煤气自杀。
》一等男人家外有家,二等男人家外有花,三等男人准时回家,四等男人准时回家遇
见她的他。
》各位女同事,请不要对我放电,我老婆有来电显示。
》强烈抗议广告时间插播电视剧!
2008.4.7
》我昨天参加了一个减肥班,教练让我穿宽松的衣服训练。要是有宽松的衣服,我来
参加减肥班干什么?
》我举着丘比特的箭追呀追,你穿着防弹背心飞呀飞。
》唾沫是用来数钞票的,不是用来讲道理的。
》我喝酒是想把痛苦溺死,但这该死的痛苦却学会了游泳。
》80后的重要任务就是制造08后。
2008.4.8
》你想发财吗?你想交桃花运吗?你想当官吗?你想一夜成名吗?你想永葆青春吗?
----不要瞎想了,好好工作吧!
》女人,口才常有而身材不常有;男人,身材常有而钱财不常有。
》孤单是一个人的狂欢,狂欢是一群人的孤单。
》女友不当尼姑的原因是她英语四级没过,庵里不收。
2008.4.9
》爱情就像海滩上的贝壳--不要拣最大的,也不要拣最漂亮的,就拣自己喜欢的,拣
到了就永远不再去海滩。
》爱人是路,朋友是树,人生只有一条路,一条路上多棵树,有钱的时候别迷路,缺
钱的时候靠靠树,幸福的时候莫忘路,休息的时候浇浇树。
》四草法则:兔子不吃窝边草,好马不吃回头草,老牛时兴吃嫩草,天涯何处无芳
草。
》每天早上起床后我都要看一遍《福布斯》富翁排行榜,如果上面没有我的名字,我
就去上班。
2008.4.10
》站在天堂看地狱,人生就像情景剧;站在地狱看天堂,为谁辛苦为谁忙!
》尘世间最痛苦的事莫过于--女人跟了别人走,四级考了五十九。
》新式morning call--生前何必久睡,死后自会长眠。
》左青龙,右白虎,肩膀纹个米老鼠。
》没钱的时候,在家里吃野菜;有钱的时候,在酒店吃野菜。
2008.4.11
》我的原则是:人不犯我,我不犯人;人若犯我,我就生气!
》我慢慢发现,人才是妖精!有些妖精吃人,但人什么都吃,逮着一只妖精没准也能
烧烤了!
》长得真有创意,活得真有勇气!
》人总要犯错误的,否则正确之路人满为患。
》偶尔幽生活一默你会觉得很爽,但生活幽你一默就惨了...
2008.4.12
》代沟就是--我问老爸:“你觉得《菊花台》怎么样?”老爸想想说:“没喝过!”
》猛的一看你不怎么样,仔细一看还不如猛的一看。
》一口不能吃个胖子,但胖子却是一口一口吃出来的!
》对男人一知半解的女人最后成了男人的妻子,对男人什么都了解的女人最后成了老
女人。
》考试要到了,借给我点钱当补考费吧...
2008.4.13
》上天在赐予我们青春的同时也赐予了我们青春痘。
》出问题先从自己身上找原因,别一便秘就怪地球没引力。
》虽然我长的不是很帅,但小时候也有人夸我左边鼻孔很偶像派。
》老妈的规劝:闺女,要适当吃一点才有劲减肥啊。
》春天是感冒和感情高发的季节。有人不小心感冒了,有人不小心恋爱了,我属于前
者。
2008.4.14
》我当年也是个痴情的种子,结果下了场雨...淹死了。
》钞票不是万能的,有时候还需要信用卡。
》我允许你走进我的世界,但绝不允许你在我的世界里走来走去。
》我希望有一天能用鼠标双击我的钱包,然后选中一张百元大钞,按下“ctrl+c”,
接着不停地“ctrl+v”。
》我是一棵孤独的树,千百年来矗立在路旁,寂寞地等待,只为有一天你从我身边走
过时为你倾倒,砸不扁你就算我白活了。
2008.4.15
》爱我的请举手,不爱我的请倒立!
》人怕出名猪怕壮,男怕没钱女怕胖。
》如果有钱也是一种错,我情愿一错再错。
》如果婚姻是爱情的坟墓,那么我期待有一个人把我埋了。
》千万别在一棵树上吊死,可以到周围的树上多试几次。
》不要把银行卡密码设成女友的生日,不然总要换多麻烦。
2008.4.16
》最幸福的事:睡觉睡到自然醒,数钱数到手抽筋。最悲哀的事:睡觉睡到手抽筋,
数钱数到自然醒。
》钱可以买房子但买不到家,可以买婚姻但买不到爱情,可以买钟表但买不到时间,
钱不是一切,反而是痛苦的根源。把你的钱给我,让我一个人承担痛苦吧!
》男孩穷着养,不然不晓得奋斗:女孩富着养,不然人家一块蛋糕就哄走了。
2008.4.17
》士为知己者装死,女为悦己者整容。
》命运负责洗牌,但是玩牌的是我们自己。
》年轻的时候,我们常常冲着镜子做鬼脸;年老的时候,镜子算是扯平了。
》我以后生个儿子名字要叫"好帅",那别人看到我就会说:"好帅的爸爸!"
2008.4.18
》爱人是种无奈,被爱是种姿态,等爱是种期待,无爱是种能耐。
》女人之美,在于蠢得无怨无悔;男人之美,在于说谎说的白日见鬼。
》明月几时有,把酒问室友,不知隔壁帅哥,可有女朋友?
》武功再高,也怕菜刀。
2008.4.19
》每个人至少拥有一个梦想,有一个理由去坚强。
》成熟的人不问过去,聪明的人不问现在,豁达的人不问未来。
》爱情就像两个拉着橡皮筋的人,受伤的总是不愿放手的那一个!
》心若没有栖息的地方,到哪里都是在流浪!
2008.4.20
》大脑是最高贵的器官--因为是大脑告诉你的。
》珍惜生活--上帝还让你活着,就肯定有他的安排。
》工作,退一步海阔天空;爱情,退一步人去楼空。
》我们产生一点小分歧:她希望我把粪土变黄金,我希望她视黄金如粪土。
》工作的最高境界就是看着别人上班,领着别人的工资。
2008.4.21
》见到我以后你会突然发现:原来帅也可以这样具体!
》小时候不学习,妈妈说:“长大后让你嫁给卖猪肉的王老五。”现在我教育女儿:
好好学习,长大后才能嫁给卖猪肉的王老五。”
》如果天上落下一颗水,那是我想你而流的泪;如果天上落下两滴水,那是我爱你而
心碎;如果天上落下无数滴水,那是...别瞎想了,下雨了。
》中午在食堂叫了两个菜,吃第一个我震撼了:世上还有比这更难吃的菜吗?吃第二个
我哭了:还真有啊!
2008.4.22
》最近很多人跳楼,大家小心别被砸到了。
》看着我的眼睛,除了眼屎,你还会看到坚毅和真诚。
》下辈子我一定投胎做一个女人,然后嫁一个我这样的男人。
》法官:你为什么要印Jia钞?被告无辜地说:因为我不会印真钞。
》亲爱的,我怀孕了,三个月了,不过你放心,不是你的,不用你负责...
2008.4.23
》妈妈说人生最好不要错过两样东西:最后一班回家的车和一个深爱你的人。
》男人没本事就别说女人太现实,女人没实力就别说男人太花心。
》就算我是一只癞蛤蟆,我也坚决不娶母癞蛤蟆。
》某女的一篇博客日记:某月某日,大醉而归,伸手一摸----手机和贞操都在,放心
睡觉!
2008.4.24
》睡眠是门艺术,谁也无法阻挡我追求艺术的脚步!
》本人不但有车,还是自行的。
》男人膝下有黄金,我把整个腿都切下来了,连块铜也没找着。
》如果看到面前的阴影,别怕,那是因为背后有阳光!
》问:你喜欢我哪一点?答:喜欢你离我远一点!
2008.4.25
》“帅”有什么用!到头来还不是被“卒”吃点!
》人生四大悲:久旱逢甘霖,一滴;他乡遇故知,债主;洞房花烛夜,隔壁;金榜
提名时,做梦。
》爱情就像打篮球,有进攻有防守,有时还会有假动作!
》早晨赖床,遂从口袋里掏出6枚硬币:如果抛出去六个都是正面,我就去上课。踌躇
了半天,还是算了,别冒这个险。
2008.4.26
》别人花前月下,我却花钱日下。》很久很久以前,谎言和真实在河边洗澡,谎言先
洗好,穿了真实的衣服离开,真实却不肯穿谎言的衣服。后来,在人们的眼里,只有
穿着真实衣服的谎言,却很难接受赤裸裸的真实。
》鸟在笼中,恨关羽不能张飞;人处世上,要八戒更须悟空。
》玫瑰,你的;巧克力,你的;钻石,你的;你,我的。
2008.4.27
》吾表兄,年四十余.始从文,连考三年而不中.遂习武,练武场上发一矢,中鼓吏,逐之
出.改学医,自撰一良方,服之卒.
》一杯下肚,轻言细语;两杯下肚,甜言蜜语;三杯下肚,豪言壮语;四杯下肚,胡言乱
语;五杯下肚,无言无语.
》幸福就是痒的时候挠一下,不幸就是痒了但挠不着,更不幸的是,很久以来灵魂和肉体
都感觉不到那种蠢蠢欲动的痒了.
2008.4.28
》成功的丈夫是钱多到妻子花不完,成功的妻子是找到这样的丈夫。
》记得毕业后不久的一天,女友给我发了条短信:“我们还是分手吧!”我还没来得及
伤心呢,女友又发来一条:“对不起,发错了!”这下可以彻底伤心了。
》猪有猪的思想,人有人的思想。如果猪有人的思想,那它就不是猪了----是八戒!
》动物园有只猴子奇丑无比,人见人吐。有一天我去了,我吐了;有一天你去了,猴
子吐了。
2008.4.29
》三人行,必有我师;三剑客,必有一强;三角恋,必有一伤。
》男人没钱时恨女人俗气,有钱时恨不得女人都俗气。
》我想早恋,但是已经晚了… …
》也曾伤心流泪,也曾黯然心碎,这是“二”的代价。
2008.4.30
》朋友们都说我是著名的音乐人,因为每次出去K歌,他们唱的都是别人的歌,而我却
总是自己谱曲。
》别和我谈理想,戒了!
》世界上难以自拔的,除了牙齿,还有爱情。
》什么是压力,老婆孩子是压力;什么是动力,老婆孩子是动力。
》说金钱是罪恶,都在捞;说美女是祸水,都想要;说高处不胜寒,都在爬;说烟酒
伤身体,都不戒;说天堂最美好,都不去!
2008.5.1
》五一体验家庭一日游:活动包括清洗我家地板和厨房油污、刷洗锅碗瓢盆、洗涤衣
物和床上用品,公车往返,自备午餐,二百一人,报名从速。
》过节了,我好想请你去体验一下KTV!知道什么是KTV吗?就是K你一顿,再T你一
脚,然后做个V字的手势!耶!
》平时工作很忙碌,趁着五一狂购物。两手不空满载归,慰劳自己绝不误。
》五一祝你:追求一段真情,寻找一个挚友,实现一个梦想,呵护一个家庭,请我大
餐一顿!
2008.5.2
》什么叫乐观派?这个......就象茶壶一样,屁股被烧得红红的,还有心情吹口哨!
》你永远也看不见我最爱你的时候,因为我只有在看不见你的时候,才最爱你。同样
你永远也看不见我最寂寞的时候因为我只有在你看不见我的时候,我才最寂寞。
》使你疲劳的不是远方的高山,而是你鞋子里面的一粒沙子!
》开车无难事,只怕有新人