聊聊那些黑客小说

系统宕,
资料泄,
挂马黑站何时了?
安全知多少!
告警昨夜又响起,
往事不堪回首月明中。
漏洞应犹在,
只是域名改。
问君能有几多愁?
恰似行行代码错误留。

黑客小说:培养对信息安全的兴趣

之前在《漏洞战争》的前言里面有提到,鄙人初入象牙塔之时,曾看过多本黑客小说,包括《黑客传说》、《地狱黑客》(后改名为《禁区之门》,参考凯文·米特尼克事迹写的,后来又出了第2部,但好像没写完)、《指间的黑客》等等,后来也因此对安全产生兴趣,入了这行道。

但,道归道,兴趣归兴趣!

因为曾有多少人,来了兴趣多年,却未曾自学过。

“我对安全很感兴趣,求师傅教教我!”这种老套路的提问,大家见得还少吗?

正如许多人宁愿被动受苦,也不愿主动吃苦学习一样,这个问题在知乎上也曾被热烈讨论过。被动受苦大多不用多动脑,久而久之,身体也会慢慢地适应,也就逐渐被生活、被制度所驯化。

不过,此处想说的重点是,黑客小说可以培养人们对信息安全的兴趣,而学习的过程本身就是孤独单调的,如有兴趣相伴,则更易独行久远!

行业人写小说:有始无终,多半夭折

安全圈里面其实也有一些人写过黑客小说,不乏某些知名人士,但基本上都是开了个头,却没多久就夭折收场。反正,我是没见过有人写完出版的。

于是,我上Google搜索了下起点网,通过一些安全专业名词进行搜索,找到几部相关小说:

  “第一节操作系统原理、第二节系统及命令详解、第三节溢出漏洞原理、第四节web原理、第五节http协议、tcp/ip协议第六节sql注入原理、上传漏洞、XSS、CSRF……”

​ ——《别说我是黑客》

  过了十多分钟,jsky的界面左侧刷出来许多asp文件和几个目录。右栏则是标注着绿色和红色叹号的几列英文。
  看到扫描结果出来,杨风面带微笑进行下一步操作。他的运气不错,得到了xss与sqlinjection漏洞,这是网站暴露出来的跨站脚本漏洞和sql注入漏洞。
  杨风转到育民高中主页,随手选取了一个注入地址,提交上引号。
  返回错误。
  接着提交and1=1语句。
  返回正常。
  继续提交and1=2语句。
  返回错误。
  杨风做这两步,主要是测试网站程序设计者是否在其中过滤了关键字,如果过滤就不能被注入。

​ ——《黑客记事本》

​ “尊敬的孙诚先生,请允许我对你和你的团队–塞伯坦工作室致以敬意。我们的检测人员已经验证了你所寄过来的数据,并证实了ios漏洞的存在,在对你们表示感谢的同时,Zerodium会按照约定,向你的团队支付共计五万四千美元的报酬,并希望你们能尽快将后续数据补完。在邮件中,你们提到找到了ios的两处极为隐秘的高危漏洞,Zerodium上下都对此非常感兴趣。希望能够尽快收到你的回复,报酬方面请不用担心,Zerodium是一个非常有信誉的平台!”

​ ——《从变形金刚开始》

“我明白了,灵根这个电源,就相当于是有着固定编码的反编译器,每个灵根都有一个固定编码,就是所谓的灵根属性。灵根可以把跟自己编码一样的灵气团,进行逆向反编译,把编译好的灵气团分解成单纯由0和1构成的信息流,就是所谓的灵力!”

​ ——《程序员修真之路》

从教你用jsky黑站(PS:为啥不推荐wvs呢),到黑客穿越、程序员修仙等,各种奇思妙想,在网络小说领域也算是另类的存在。

但是从这些专业名词看,作者即使不是安全圈的,至少也是IT技术行业的,对一些常见的安全事件、技术名词都比较了解。

文学与IT技术本身就是两个不同的领域,要同时兼顾就有一定难度。如果算程序员里面写的小说,比较火的,应该是那本《疯狂的程序员》吧!

懵懂之美:似懂非懂才是最大的乐趣

我已经很久没看黑客小说,尤其是入了行之后,就更不看了。

如果要推荐的话,还是开头提到的那3本小说:《黑客传说》、《禁区之门》、《指间的黑客》,不过那是我大学时的口味了,现在重新看也不一定就如当初那般喜欢。

因为当你对安全行业熟悉后,再去看这些小说的时候,可能就容易较真,少了些许乐趣。

我当初看上面的小说的时候,其实也还没入门安全,所以看得特别起劲。

所以,如果要看这类小说,就把那些行业知识都抛诸脑后,享受那当初的懵懂之美,才是读小说的乐趣所在。

后话

也许是性格使然,在文字世界里,鄙人老喜欢委婉地批判人和事(俗称:骂人)。不过文学世界里,一千个读者就有一千个哈姆雷特,应该支持下这种多元文化。不然就像娱乐圈里,谁演孙悟空,都要被六小龄童骂一般,少了胸怀,甚至阻碍了行业发展。

安全研究者的自我修养(续)

接上篇继续聊安全研究者的自我修养,上篇重点讲技术修炼,本篇聊聊行业现象、谈谈沉淀、情怀等等。

11、工具与方法论沉淀

虽说代码审计是项必备技能,但终究是项体力活。

有些漏洞(比如逻辑漏洞)可能就需要人工审计,但也有不少漏洞是可以自动化Fuzzing,一些能自动化或半自动化实现的,尽量写程序自动化。

因为,纯人工审计终究熬不过年纪,熬不过团队人员的离散变迁,熬不过互联网的快速发展……

比如,2012年刚开始写《漏洞战争》时,单身一人,从早上8点多起床吃饭,然后开始调代码、看代码,一直奋战到晚上12点,身体无压力。近7年过去了,现在要是这么折腾,身体就要散架了……

比如,团队里的人分工做不同领域的代码审计,若无工具和方法论沉淀,那么有人走的话,此人对应的领域可能就无法持续产出;若有新人加入,代码审计的技能又不好传承,很多得自己重头来。所以,一直觉得,好的团队应该是,即使人员离散变迁,依然能够独立运作、持续产出的。

比如,Linux内核在2018年净增87万行代码,很多类似复杂庞大的项目,看代码有时看都看不过来,一般都是针对性地挑模块作代码审计。

比如,Fuzzer开发里面就有很多共用功能是可以直接做成框架沉淀下来,文件变异、崩溃监控、样本去重精简等等,很多时候有个新的攻击面需要测试,就可以直接在框架的基础上写fuzzer,将会高效很多。下文提到的一个IE漏洞挖掘案例就是基于这思路挖到的。

我曾经想开发两个漏洞挖掘系统,一个二进制,一个Web,名字都想好了,合称”冰弓玄箭“,但业余一直都没什么时间开发,仅写了个界面,希望2019年能够完成:

image-20190112143451515

”冰弓“的Logo直接用的是“破甲弓”,感觉很酷……

再说说方法论,这词虽有点虚,但其实本质上就是一种技术方法的总结而已。

比如,渗透测试的时候,总有些人每次都能搞到RCE,无论啥网站,完全摆脱“随机挖洞”的命运。多数情况下,他们都会有一套自己测试方法,或者将一些经验转换成工具,测试时就拿自己的工具和以往总结的方法论开搞。

比如,STRIDE威胁建模本身就是一套方法论,一套简单的风险助记符,当然我这里不是说安全研究要用它,只是举个方法论的例子,它也没有那么万能。

写这么多,总结起来就一句话:多总结,多沉淀!

12、漏洞研究风向标:安全公告

如果大家有关注四大厂商(Google、Microsoft、Apple、Adobe)的安全公告的话,会发现有段时间会出现很多类似漏洞的公告,出现一个新的攻击面之后,一帮研究人员就蜂捅而上狂刷一波。

这种情况一向是先下手为强,而上文提到的工具和方法论就更显得尤为重要了,否则最后都只能捡剩的。

比如本周 Microsoft 安全公告出来后,我仔细分析了下,然后下班回家写了个Fuzzer,挂着跑了一天,出来个Crash,再用几分钟成功构造出PoC,实现IE浏览器的远程代码执行,可见也是个品相极佳的神洞:

image-20190112153428430

但不幸的是,我打了1月的补丁后,发现修复了,成功“撞洞”,真的是欲哭无泪……

但至少证明,通过安全公告寻找新的攻击面,然后挖掘一些类似漏洞,一直是一种高效的漏洞研究方式。

13、老一辈研究者都去哪儿了?

img

最近腾讯AILab张潼离职的事传得很火,还有之前各大厂聘请的AI科学家陆续辞职,回归学术界,很多人因此唱起科学家之于科技公司的无用论,主要有以下几点原因:

  1. 研究成果无法落地为产品:做安全研究也是如此,很多事情是无法落地的,圈内很多研究团队都是拿漏洞来打比赛赚影响力,真正能实现为公司营利的(打比赛赚奖金的忽略不计,因为那些都不够给研究者们的工资),我只知道有1个研究团队/实验室今年营利了。
  2. 长期无产出,KPI压力大:研究了很长时间,最后仍一无所获,那KPI咋办、PPT怎么写、晋级怎么答辩。安全行业有句老话来形容安全研究工作,叫“三年不开锅,开锅吃三年”,但多数个人和企业都等不到三年。之前同事说王小云为何能破解出MD5,是因为她在学校里很长时间没搞出东西的时候,领导没找她麻烦,没有KPI压力,以致能够长期专注于此。具体原因我不确定,但学术界自然是没有企业有这般KPI压力。
  3. 业务数据不共享:业务部门的产品数据基本不太可能共享给实验室作研究的,一般都是实验室以SDK的形式提供给业务用,数据由业务自主控制。这种情况对于安全研究的影响相对较少一些。

头两点是多数安全研究者的困境,也跟圈内同行讨论过,下面聊聊这帮老一代“知青”最后都去哪儿了?这里我主要总结一些圈内人的应对方法(其实多数都是转型),具体不作点评,总结为主,也欢迎私信讨论(新注册的公众号已不允许留言)。

  1. 坚持研究:这帮人主要还是那些研究能力较强的,且有一定研究成果的人,围观下各大实验室就知道个大概,不多说;
  2. 转型安全产品开发与运营:有产品就能解决落地问题,帮助企业解决实际问题,有不少人走这条道,去做威胁情报系统、漏洞扫描器、WAF、云安全产品等等;
  3. 转型业务安全:跟研究工作差异较大,因为业务安全的主要问题很多时候并非漏洞,而是跟业务产品相关的黑灰产对抗等等;
  4. 自由研究者:国外很多此类研究者,靠拿漏洞赏金过活,俗称“赏金猎人”,国内相对少一些,也有一些国内自由研究者后来又进企业做研究的,这里讲的几种转型都可以来回转换,有些人就干过。
  5. 创业:这里包括安全行业内的创业,也包括那些开淘宝店、奶茶店、服装生意、卖水果的……

14、个人终究干不过团队

有时想搞的研究太多了,但发现一个人根本搞不过来,需要多人协作才可能完成。但需要多人在研究领域上有交集,否则拉在一块也是各搞各的。

前篇第7点讲到“进入研究者团队或社区,互相学习”,也是一大影响因素,互相学习也是一种提高效率和产出的方式。

算了,不多说了!

后话

这次真的结束了,没有续篇了。

思考了很多,总结了很多,有些也是写了删,删了写。

安全研究领域一直也没人写过这些,出来唠叨几句,也欢迎大家私信讨论。

最后奉一首酒桌上的《苦行僧》结束本话题,听过这首歌很多个版本,包括原唱,但终究还是觉得视频里这位老哥唱得更具江湖气、更具情感、更具感染力……旁边一老哥听着听着都偷偷抹泪了!

之所以点这首歌,是因为:每一个研究者都是独立自行的苦行僧!

安全研究者的自我修养

在上篇文章《推荐今年C3黑客大会上的几个议题》中提到”Attacking Chrome IPC“这个议题,我觉得该议题最大的亮点是在前半场,作者nedwill是之前在hack2win大赛上因攻破Chrome浏览器而一战成名,他讲了如何训练漏洞研究能力的过程,讲述自己这几年在漏洞研究上的历程和心得,很励志,其建议也非常具有可操作性,值得效仿学习。我反复看了多遍,对其作了一些总结和补充。

1、刻意练习10000小时

这份“鸡汤”道理,想必大家都懂,就不解释了,不懂的自行百度,或者去读读《异类》这本经典书籍。

作者建议以月为单位来制定研究目标,他曾连续花了6个月的时间来研究Chrome Sandbox,但最终一无所获。

所以,有时坚持了不一定能达到目标,但不坚持,就更没戏了。

2、训练挖洞的双技能

(1)看洞:哪里看?历史漏洞的git log、bug报告、代码质量报告等等

(2)识洞:就是肉眼看代码找漏洞,即代码审计,难点也就是在这上面,训练方法继续往下看

3、代码审计训练

(1)根据自己目标定位,寻找相应的历史漏洞案例进行学习,比如要搞chrome就找chrome的历史漏洞

(2)掌握漏洞所在的模块或子系统,但不看完整的漏洞细节描述,尝试在漏洞版本中找出对应的漏洞

(3)如果(2)中未能找出漏洞,就去看漏洞细节描述,对比自己的审计过程,看遗漏了哪一步骤

(4)不断重复上述训练,直至相信:挖洞只是体力消耗,而非能力问题

这第4点说得,非常励志,因为挖洞挖久了,有时真的容易怀疑自己的能力,目标难度越大,越容易打击人。

作者第一次训练的漏洞是j00ru(Project Zero成员)的IDA漏洞:https://j00ru.vexillium.org/2014/10/secure-2014-slide-deck-and-hex-rays-ida-pro-advisories-published/,2014年的文章了

4、3~5年的训练计划

1~2年:做做 CTF 或 WarGames 题目,网上有很多CTF writeup可以参考学习

2~3年:简单点的目标,就是找相对容易挖的产品

3~5年:困难点的目标

目标的难易程度可以直接参考相应的产品的漏洞奖励计划或私有市场的价格,挑选出一份目标清单,按难易程度排序,逐一去实现它。

5、Fuzzing训练

作者代码审计2年后,才开始尝试Fuzzer开发。

(1)拿已公开的历史漏洞问自己:如何写fuzzer挖掘到此漏洞?

(2)如果自己不知道此漏洞,那又能够挖掘到呢?

(3)不断重复训练并改进fuzzer,相信会有更多漏洞被意外发现

6、努力往往比运气和天赋更重要

虽然挖洞也需要一定运气和天赋,但多数你认为的挖洞天才,其实只不过是花了比你多100倍,甚至更多的时间在这项技术研究上而已

7、进入研究者团队或社区,互相学习

国外的交流氛围会比国内的更好一些,也更愿意分享。

很多时候自己的交流圈,大多是一些熟识的同行,或者同事,一般可交流的人还是比较少的。

经常在网上看到不少人会问,如何认识xx大牛、黑客,但其实很多时候却是:

努力提高自己的专业能力,圈子最终会吸纳你进去认识更多圈内人。

8、建立自己的漏洞信息来源

RSS订阅无疑是自己最好的方式,这个需要依赖平时自己去不断收集订阅。

很多漏洞相关的博文,往往曝露出某些软件新的攻击面,抢占先机就显得尤为重要,比如当年Android stagefirght mp4漏洞、word公式编辑器、adobe图片转换器等等,如果能及时关注并尝试去挖掘,往往可以收获不少漏洞的。

9、收集和学习开源的漏洞挖掘工具

比如afl、honggfuzz、libfuzzer等很多优秀的漏洞挖掘工具,都是值得好好阅读代码,学习其中的fuzzing思路,可以更好地应用到未来的漏洞挖掘研究上。

10、很多不愿搞研究工作的挖洞人,只不过是为了权衡利弊

在《从0到1:开启商业与未来的秘密》一书中有一章叫做“秘密”,漏洞研究可以当作挖掘秘密,为什么人们不探索秘密呢?书中提到4种原因,我觉得同样适用于漏洞研究领域:

(1)渐进主义:把目标定得低一些,更容易取得好成绩;

(2)风险规避:人们害怕秘密是因为怕犯错,除此之外,可能也担心KPI没法完成,又或者挖洞拿到的奖金又该如何跟公司“分赃”呢?

(3)自满:很多时候,某些人可以坐享其成,又何必自己去挖掘秘密;国内研究氛围又喜欢搞营销吹牛逼,牛逼吹多了吹大了,有时连自己都信了;

(4)扁平化:任何一个拥有雄心壮志的人,在涉及某一研究领域之前都会问自己一个问题:如果有可能挖掘到漏洞,难道全球人才库中更加聪明、更加有技术能力的人还没有发现吗?这种怀疑的声音阻止了不少人去探索秘密,从事研究工作,因为身处的世界似乎大到任何个人都无法做出独特的贡献。

结语

今年因个人原因,已从安全研究转向业务安全,深知研究的不易。

相信安全领域有秘密的存在,虽会导致黑产的诞生,但肯定也会因此诞生一些优秀的研究者。

最后以白桦的《船》致谢所有仍在安全研究道路上前进的人:

我有过多次这样的奇遇,
从天堂到地狱只在瞬息之间:
每一朵可爱、温柔的浪花
都成了突然崛起、随即倾倒的高山。

每一滴海水都变脸色,
刚刚还是那样的美丽、蔚蓝;
旋涡纠缠着旋涡,
我被抛向高空又投进深渊……

当时我甚至想到过轻生,
眼前一片苦海无边;
放弃了希望就像放弃了舵柄,
在暴力之下只能沉默和哀叹。

今天我才有资格嘲笑昨天的自己,
为昨天落叶似的惶恐感到羞惭;
虚度了多少年华,
船身多次被礁石撞穿……

千万次在大洋里撒网,
才捕获到一点点生活的经验,
才恍然大悟,
啊!道理原是如此浅显;

你要航行吗
必然会有千妖百怪出来阻拦;
暴虐的欺凌是它们的游戏,
制造灭亡是它们唯一的才干。

命中注定我要常常和它们相逢,
因为我的名字叫做船;
面对强大于自身千万倍的对手,
能援救自己的只有清醒和勇敢。

恐惧只能使自己盲目,
盲目只能夸大魔鬼的狰狞嘴脸;
也许我的样子比它们更可怕,
当我以命相拼,一往无前!

只要我还有一根完整的龙骨,
绝不驶进避风的港湾;
把生命放在征途上,
让勇敢来决定道路的宽窄、长短。

我完完全全的自由了,
船头成为埋葬它们的铁铲;
我在波浪中有节奏地跳跃,
就像荡着一个巨大的秋千。

即使它们终于把我撕碎,
变成一些残破的木片,
我不会沉沦,决不!
我还会在浪尖上飞旋。

后来者还会在残片上认出我,
未来的诗人会唱然长叹:
“这里有一个幸福的灵魂,
它曾经是一艘前进着的航船……”

推荐今年C3黑客大会上的几个议题

最近几天在德国举办的 The 35th Chaos Communication Congress (35C3) 黑客大会,在Twitter上传得火热,在国内却无人问津。

从这可以看出同为微博的安全圈氛围是完全不同的,新浪微博还是偏娱乐些的,而且国外的圈子自然比天朝的要大得多,很多国家的人在上面交流。

所以,我现在经常混Twitter,当作获取安全资讯的途径,新浪微博就真的当作看新闻的了……

德国C3混沌黑客大会

Chaos Communication Congress(C3)大会是每年在德国举办的黑客大会,直译过来就是”混沌通信大会“,圈内通常叫”C3“,今年是第35届,所以叫35c3,今年还有CTF比赛,一些打过pwn2own的人出了一些浏览器实际漏洞的题目,也蛮具有实战价值的。

每年这大会都会以演讲视频的方式分享出各个议题,以前大多是聚焦在无线电安全,所以一些什么2G\3G\4G短信、电话窃听经常出自该会议。今年也有一些不错的软件安全相关的议题,下面推荐几个鄙人自认为不错的几个议题。

From Zero to Zero Day

image-20181230193447667

大会上有个议题叫 “From Zero to Zero Day”,演讲者是位高中生,讲述他自己如何在一年之内,从无任何安全基础到挖掘到第一枚Edge浏览器远程执行代码漏洞的经历。

总结来讲就是:

1、学习编程语言(C/C++、asm等等)

2、学习操作系统原理

3、学习常见二进制漏洞原理

4、打打CTF,写写write-up

5、学习并实践去分析真实的漏洞案例,就是直接看代码,调代码

6、不断重复上述练习

我直接帖几张关键截图,也推荐大家去听听(视频链接:https://media.ccc.de/v/35c3-9657-from_zero_to_zero_day):

image-20181230192405638

image-20181230192425929

image-20181230192131880

image-20181230192320182

image-20181230192254473

Attack Chrome IPC

image-20181230193950157

这个议题之前作者在韩国PoC大会上讲过,并在网上公布过pdf(https://data.hackinn.com/ppt/2018韩国POC安全大会/ned.pdf),主要讲他之前在Hack2Win bevx黑客大赛上用于黑掉Chrome浏览器的IPC漏洞,此次C3大会的演讲视频参见:https://media.ccc.de/v/35c3-9579-attacking_chrome_ipc

这议题里面讲了作者一些研究二进制漏洞的一些学习和研究的方法,比如做代码审计、打CTF,也介绍了他这几年的安全研究历程,比国内流行晒crash+CVE的方式更接地气。

最后介绍如何使用 libfuzzer+libprotobuf-mutator去fuzz chrome IPC,并开源fuzzer代码合并到chroium项目中。

image-20181230194648886

Jailbreaking iOS From past to present

image-20181230202552499

讲iOS越狱发展史的,可以学习到iOS上各种安全机制的原理以及绕过方法,画了不少原理图,通俗易懂不少。对于想了解整个iOS越狱技术发展历程的同学,这确实是份不错的资料。

作者这次也在推持上放出了pdf和视频:

pdf: https://api.tihmstar.net/35c3slides.pdf

video: https://media.ccc.de/v/35c3-9618-jailbreaking_ios

整个议题主要围绕以下几点展开:

  • 越狱类型(非完美越狱、完美越狱……)
  • Exploit mitigations (ASLR, iBoot-level AES, KPP, KTRR, PAC)
  • Kernel patches (h3lix)
  • Kppless jailbreaks
  • 越狱的未来趋势

The Layman’s Guide to Zero-Day Engineering

image-20181230211151217

Ret2公司分享如何寻找webkit攻击面并fuzz的方法,是基于MozillaSecurity的dharma语法生成框架写的js fuzzer,以及如何借助IDA+Lighthouse开源插件(正是Ret2团队开发的,曾获得过IDA插件比赛的二等奖)来分析代码覆盖率问题。

除此之外,也介绍如何利用frida去hook mach_msg,用来fuzz WindowServer,最后用WindowServer的漏洞来实现root提权。

他们在其博客(https://blog.ret2.io)上也分享过不少漏洞研究方面的干货,这次分享的内容也大部分就是来自博客上的一些文章。

里面还提到长亭的real world ctf比赛了。

这里有张比较有意思的图,发出来给大伙看下:

image-20181230213304029

视频链接:https://media.ccc.de/v/35c3-9979-the_layman_s_guide_to_zero-day_engineering

Modern Windows Userspace Exploitation

image-20181230214817405

视频链接:https://media.ccc.de/v/35c3-9660-modern_windows_userspace_exploitation

微软MSRC的人过来分享下windows平台下的一些二进制漏洞利用方法,包括ROP绕过DEP、信息泄露绕过ASLR、绕过CFG、ACG、CIG等等,并现场给了一些演示,不过看起来像是一些CTF赛题,演示的相关代码已放在作者的github上:https://github.com/saaramar

都是一些利用系统漏洞防御技术的原理与绕过技术的总结,连各种漏洞缓解机制绕过的微软奖励都给大家标注上了,其实有点类似上面Jailbreaking iOS议题的Windows版本。

最后来张Exploit Mitigations清单:

image-20181230215938958

结语

后面官方可能还会继续更新演讲视频,可以关注官方twitter(@c3voc_releases)获取消息。

另外,对于英语不好的同学,不妨下个”腾讯翻译“同声翻译试下,虽然中文翻译没那么准确,但看看显示的英文单词也能知道个大意:

image-20181230195201468

2018年读过的书

从2018年开始一直坚持每月读2本书以上,庆幸自己坚持下来了,共读了38本书,也希望明年能够继续坚持。

有些是实体书,有些是在微信读书上看的电子版,在手机app上看书确实会高效很多,今年有一半的书是在微信读书上看的,非常适合空闲时间阅读,以及像坐地铁、等地铁这种零碎的时间。

下面是我今年读过的书单,分别都打个分数,8分及以上的代表推荐,6分以下的别看:

  1. 《人性的弱点》(6分)
  2. 《代码整洁之道》(8分)
  3. 《如何阅读一本书》(8分)
  4. 《两晋南北朝那些事》(7分)
  5. 《软技能:代码之外的生存指南》(8分)
  6. 《秋叶:如何高效读懂一本书》(6分)
  7. 《横向领导力》(7分)
  8. 《程序员成长课》(8分)
  9. 《英语写作手册》(8分)
  10. 《爆款文案》(7分)
  11. 《运营之光》(8分)
  12. 《威胁建模》(7分)
  13. 《程序员的英语》(7分)
  14. 《冰鉴全鉴》(2分)
  15. 《内向者沟通圣经》(4分)
  16. 《灰度决策》(4分)
  17. 《启功行书技法》(8分)
  18. 《见识》(9分)
  19. 《半小时漫画中国史1、2》(7分)
  20. 《Web安全之机器学习入门》(6分)
  21. 《秦崩》(8分)
  22. 《启功给你讲书法》(8分)
  23. 《思考,快与慢》(10分)
  24. 《三国志》(8分)
  25. 《季羡林谈写作》(6分)
  26. 《Android应用安全防护与逆向分析》(7分)
  27. 《精进:如何成为一个很厉害的人》(8分)
  28. 《非暴力沟通》(9分)
  29. 《系统架构设计》(7分)
  30. 《人人都是产品经理》(8分)
  31. 《漏洞》(7分)
  32. 《风格感觉:21世纪写作指南》(8分)
  33. 《态度》(8分)
  34. 《高效阅读》(5分)
  35. 《写给大家看的设计书》(10分)
  36. 《硅谷钢铁侠:埃隆·马斯克的冒险人生》(8分)
  37. 《八卦医学史2》(6分)
  38. 《从0到1:开启商业与未来的秘密》(7分)

所以8分以上的推荐书籍有:

《代码整洁之道》(8分)

《如何阅读一本书》(8分)

《软技能:代码之外的生存指南》(8分)

《程序员成长课》(8分)

《英语写作手册》(8分)

《运营之光》(8分)

《启功行书技法》(8分)

《见识》(9分)

《秦崩》(8分)

《启功给你讲书法》(8分)

《思考,快与慢》(10分)

《三国志》(8分)

《精进:如何成为一个很厉害的人》(8分)

《非暴力沟通》(9分)

《人人都是产品经理》(8分)

《风格感觉:21世纪写作指南》(8分)

《态度》(8分)

《写给大家看的设计书》(10分)

《硅谷钢铁侠:埃隆·马斯克的冒险人生》(8分)

Bochspwn漏洞挖掘技术深究(2):内核未初始化漏洞检测

本文主要介绍Bochspwn Reloaded内核未初始化漏洞检测技术,它采用污点追踪对内核层向用户层泄露数据的行为进行检测。

关于bochs插桩技术参考《Bochspwn漏洞挖掘技术深究(1):Double Fetches 检测》,此处不再赘述。

直接先看下instrument.h中实现插桩函数有哪些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Bochs初始化CPU对象时的回调函数
void bx_instr_initialize(unsigned cpu);
// Bochs析构CPU对象时的回调函数
void bx_instr_exit(unsigned cpu);
//Bochs每次执行中断操作(软件中断、硬件中断或异常)时的回调函数
void bx_instr_interrupt(unsigned cpu, unsigned vector);
// Bochs执行指令前的回调函数
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i);
// Bochs执行指令后的回调函数
void bx_instr_after_execution(unsigned cpu, bxInstruction_c *i);
// Bochs访问线性内存时的回调函数
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,
unsigned len, unsigned memtype, unsigned rw);
// WRMSR指令(写模式定义寄存器)被执行时的回调函数,MSR寄存器数与值作为参数传递给回调函数
void bx_instr_wrmsr(unsigned cpu, unsigned addr, Bit64u value);

初始化工作

第一篇中讲过bx_instr_initialize主要用来加载配置信息,针对不同的系统环境设置不同的数据结构偏移地址,用来提供需要的进程/线程等重要信息。在这里它另外增加污点追踪功能的初始化工作:

1
2
3
4
5
6
7
8
9
// Initialize the taint subsystem.
taint::initialize();

// Initialize helper taint allocations.
globals::pool_taint_alloc = (uint8_t *)malloc(kTaintHelperAllocSize);
memset(globals::pool_taint_alloc, kPoolTaintByte, kTaintHelperAllocSize);

globals::stack_taint_alloc = (uint8_t *)malloc(kTaintHelperAllocSize);
memset(globals::stack_taint_alloc, kStackTaintByte, kTaintHelperAllocSize);

主要作一些用于污点信息记录的内存结构分配与VEH异常处理回调设置:

1
2
3
4
5
6
7
8
void initialize() {
// Reserve a memory region for the taint data.
taint_area = (uint8_t *)VirtualAlloc(NULL, kTaintAreaSize, MEM_RESERVE, PAGE_READWRITE);

// Register a VEH handler to commit taint memory touched in other taint
// functions.
AddVectoredExceptionHandler(/*FirstHandler=*/1, OvercommitHandler);
}

VEH回调函数实现如下,当发生访问违例时,若异常地址不在污点内存区域,则将其设置为可读写内存,然后继续执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static LONG CALLBACK OvercommitHandler(
_In_ PEXCEPTION_POINTERS ExceptionInfo
) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
const uint8_t *excp_address = (uint8_t *)ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
if (excp_address >= taint_area && excp_address < &taint_area[kTaintAreaSize]) {
if (VirtualAlloc((void *)((uint64_t)excp_address & (~0xffff)), 0x10000, MEM_COMMIT, PAGE_READWRITE)) {
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}

return EXCEPTION_CONTINUE_SEARCH;
}

中断响应

再看下bx_instr_interrupt函数实现,主要是发生中断时,检测该中断地址是否可写,并设置全局标志:

1
2
3
4
5
6
7
8
void bx_instr_interrupt(unsigned cpu, unsigned vector) {
if (globals::bp_active && vector == 3) {
BX_CPU_C *pcpu = BX_CPU(cpu);
write_lin_mem(pcpu, globals::bp_address, 1, &globals::bp_orig_byte);

globals::bp_active = false;
}
}

污点标记与追踪

bochspwn-reloaded会对内核分配的stack/heap/pools作污点标记:

image-20181222092741926

1、栈污点标记

检测修改ESP寄存器的指令,比如:ADD ESP, ... SUB ESP, ... AND ESP, …,若在执行后(bx_instr_after_execution)ESP发生递减,则调用taint::set_taint(new_rsp, length, /*tainted=*/true)标记为污点

1
2
3
4
5
6
7
8
9
10
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i) {
...
const unsigned int opcode = i->getIaOpcode();
switch (opcode) {
case BX_IA_SUB_EqId:
case BX_IA_SUB_GqEq: /* Stack allocation handling */
...
case BX_IA_PUSH_Eq: /* Allocator prologue handling. */
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void bx_instr_after_execution(unsigned cpu, bxInstruction_c *i) {
globals::rep_movs = false;

if (globals::rsp_change) {
BX_CPU_C *pcpu = BX_CPU(cpu);
const uint64_t new_rsp = pcpu->gen_reg[BX_64BIT_REG_RSP].rrx;

if (new_rsp < globals::rsp_value) {
uint64_t length = globals::rsp_value - new_rsp;

if (length <= kTaintHelperAllocSize) {
taint::set_taint(new_rsp, length, /*tainted=*/true);
write_lin_mem(pcpu, new_rsp, length, (void *)globals::stack_taint_alloc);

if (globals::config.track_origins) {
taint::set_origin(new_rsp, length, pcpu->prev_rip);
}
}
}

globals::rsp_change = false;
globals::rsp_value = 0;
}
}

2、堆/Pools污点标记

检测内核内存分配操作的指令,则调用taint::set_taint(address, size, /*tainted=*/true)进行污点标记,主要通过bx_instr_wrmsr函数来实现,当写入的地址是MSR_LSTAR寄存器时,它代表着syscall调用:

1
#define MSR_LSTAR	0xc0000082 /* long mode SYSCALL target */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void bx_instr_wrmsr(unsigned cpu, unsigned addr, Bit64u value) {
if (addr == MSR_LSTAR) {
globals::nt_base = value - globals::config.KiSystemCall64_offset; // ntoskrnl.exe中nt!KiSystemCall64偏移地址,用于获取内核基址

for (size_t i = 0; i < globals::config.pool_alloc_prologues.size(); i++) {
globals::config.pool_alloc_prologues[i] += globals::nt_base;
}
set_breakpoints_bulk(globals::config.pool_alloc_prologues, BP_POOL_ALLOC_PROLOGUE);

for (size_t i = 0; i < globals::config.pool_alloc_epilogues.size(); i++) {
globals::config.pool_alloc_epilogues[i] += globals::nt_base;
}
set_breakpoints_bulk(globals::config.pool_alloc_epilogues, BP_POOL_ALLOC_EPILOGUE);
}
}

其中pool_alloc_prologuespool_alloc_epilogues分别代表alloc函数的前序与后序函数,以下是windows-x64系统配置下的地址:

1
2
pool_alloc_prologues  = 0x1E0590
pool_alloc_epilogues = 0x1E07AD

3、污点清除
当栈顶弹出或者堆块调用free函数前序指令(Linux下配置地址),以及内存拷贝的目标地址是内核地址时,均将其污点标记清除,如果是win平台则主要依靠bx_instr_lin_access来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,
unsigned len, unsigned memtype, unsigned rw) {
BX_CPU_C *pcpu = BX_CPU(cpu);
const uint64_t pc = pcpu->prev_rip;

if (rw != BX_WRITE && rw != BX_RW) {
return;
}

if (!pcpu->long_mode() || !windows::check_kernel_addr(pc) || !windows::check_kernel_addr(lin)) {
return;
}

if (globals::rep_movs) {
return;
}

const uint64_t rsp = pcpu->gen_reg[BX_64BIT_REG_RSP].rrx;
if (globals::rsp_locked.find(rsp) != globals::rsp_locked.end()) {
return;
}

taint::set_taint(lin, len, /*tainted=*/false);
}

4、污点传播
bx_instr_before_execution中主要对以下操作指令作检测,指令形式主要为 <REP> MOVS{B,D},用于污点传播追踪:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const unsigned int opcode = i->getIaOpcode();
switch (opcode) {
case BX_IA_MOV_GqEq: /* Standard library memcpy() prologue handling. */
...
case BX_IA_REP_MOVSB_YbXb:
case BX_IA_REP_MOVSW_YwXw:
case BX_IA_REP_MOVSD_YdXd:
case BX_IA_REP_MOVSQ_YqXq: /* Inline memcpy handling */
...
switch (opcode) {
case BX_IA_REP_MOVSB_YbXb: mult = 1; break;
case BX_IA_REP_MOVSW_YwXw: mult = 2; break;
case BX_IA_REP_MOVSD_YdXd: mult = 4; break;
case BX_IA_REP_MOVSQ_YqXq: mult = 8; break;
}
...

case BX_IA_RET_Op64: /* Allocator and memcpy() epilogue handling. */
...

对于非<REP> MOVS{B,D}指令的内存访问:

  • 写操作:清除内存污点标记,标记为已初始化;
  • 读操作:检测污点标记,如果shadow memory中标记为未初始化读取,则在guest memory中验证:标记不匹配则清除污点,否则若真为未初始化读取就当漏洞报告出来
1
2
3
4
5
6
7
8
9
10
/* src_in_kernel */ {
uint64_t tainted_offset = 0;
taint::access_type type = taint::check_taint(pcpu, src, size, &tainted_offset);

if (type == taint::METADATA_MARKER_MISMATCH) {
taint::set_taint(src, size, /*tainted=*/false);
} else if (type == taint::ACCESS_INVALID) {
process_bug_candidate(
pcpu, i, pcpu->prev_rip, src, size, dst, taint::get_origin(src + tainted_offset));
}

总结起来,是否为漏洞主要基于以下几点:

1、<REP> MOVS{B,D}中 源地址为内核,目标地址为用户地址,从内核输出数据到用户

2、源地址被标记为污点

Bochspwn漏洞挖掘技术深究(1):Double Fetches 检测

虽然现在技术文章很少人看,大家都喜欢聊安全八卦,但技术文章输出是一种很好的学习方式。更重要的是,专业的文章是给专业的人看的,并非为了取悦所有人。

对于应用程序的代码插桩,有现成的Pin和DynamoRIO插桩框架,在Fuzzing中可以用来实现代码覆盖率的反馈驱动,这已经被应用到winafl,效果很好。除了挖洞,在逆向工程领域应用也很广泛。

上面都是针对应用层的,内核层的,上面的Pin和DynamoRIO就派不上用场了,对于这种系统内核级的指令插桩,有时就会采用虚拟化技术为实现,比如通过Qemu或Bochs虚拟机。

ProjectZero的j00ru大神就用bochs的插桩API为实现针对内核double fetches的监测,项目称为bochspwn,后来又采用污点追踪方式检测未初始化漏洞导致的内核信息泄露,叫bochspwn-reloaded。

Bochs Instrument API 文档参考:http://bochs.sourceforge.net/cgi-bin/lxr/source/instrument/instrumentation.txt ,在编译bochs时指定插桩代码目录:

1
./configure [...] --enable-instrumentation="instrument/myinstrument"

下面是bochspwn中用到的API:

1
2
3
4
5
6
7
8
// Bochs初始化CPU对象时的回调函数
void bx_instr_initialize(unsigned cpu);
// Bochs析构CPU对象时的回调函数
void bx_instr_exit(unsigned cpu);
// Bochs访问线性内存时的回调函数
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,unsigned len, unsigned memtype, unsigned rw);
// Bochs执行指令前的回调函数
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i);

bx_instr_initialize用来加载配置信息,针对不同的系统环境设置不同的数据结构偏移地址,用来提供需要的进程/线程等重要信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[general]
trace_log_path = memlog.bin
modules_list_path = modules.bin

os = windows
bitness = 32
version = win10_32

min_read_size = 1
max_read_size = 16
min_write_size = 1
max_write_size = 16

callstack_length = 48
write_as_text = 0

symbolize = 0
symbol_path = <symbols path>

[win7_32]
kprcb = 0x120
current_thread = 0x04
tcb = 0x0
process = 0x150
client_id = 0x22c
process_id = 0
thread_id = 4
create_time = 0x200
image_filename = 0x16c
kdversionblock = 0x34
psloadedmodulelist = 0x18
loadorder_flink = 0x0
basedllname = 0x2c
baseaddress = 0x18
sizeofimage = 0x20
us_len = 0x0
us_buffer = 0x4
teb_cid = 0x20
irql = 0x24
previous_mode = 0x13a
exception_list = 0x0
next_exception = 0x0
try_level = 0xc
......

Bochspwn的核心功能实现就在于bx_instr_lin_accessbx_instr_before_execution两个函数。先看下bx_instr_before_execution的实现逻辑:

  1. 忽略实模式real mode
  2. 忽略无关的系统调用中断指令,仅允许int 0x2eint 0x80
  3. 获取当前进程/线程ID相关的信息,当发现漏洞时方便重现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i) {
static client_id thread;
BX_CPU_C *pcpu = BX_CPU(cpu);
unsigned opcode;

// We're not interested in instructions executed in real mode.
if (!pcpu->protected_mode() && !pcpu->long64_mode()) {
return;
}

// If the system needs an additional invokement from here, call it now.
if (globals::has_instr_before_execution_handler) {
invoke_system_handler(BX_OS_EVENT_INSTR_BEFORE_EXECUTION, pcpu, i);
}

// Any system-call invoking instruction is interesting - this
// is mostly due to 64-bit Linux which allows various ways
// to be used for system-call invocation.
// Note: We're not checking for int1, int3 nor into instructions.
opcode = i->getIaOpcode();
if (opcode != BX_IA_SYSCALL && opcode != BX_IA_SYSENTER && opcode != BX_IA_INT_Ib) {
return;
}

// The only two allowed interrupts are int 0x2e and int 0x80, which are legacy
// ways to invoke system calls on Windows and linux, respectively.
if (opcode == BX_IA_INT_Ib && i->Ib() != 0x2e && i->Ib() != 0x80) {
return;
}

// Obtain information about the current process/thread IDs.
if (!invoke_system_handler(BX_OS_EVENT_FILL_CID, pcpu, &thread)) {
return;
}

// Process information about a new syscall depending on the current mode.
if (!events::event_new_syscall(pcpu, &thread)) {
return;
}
}

再看下bx_instr_lin_access实现逻辑:

  1. 忽略仅读写指令
  2. 检测CPU类型(32位或64位)
  3. 判断当前指令地址pc是否为内核地址,判断访问的线性内存地址是否为用户层地址
  4. 检测读取的内存长度是否处于0~16字节之间,长度大小范围在config.txt中配置,仅处理此范围内的指令操作
  5. 通过上述条件之后,就代表可能存在内核漏洞,然后反汇编指令,然后填充日志记录信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,
unsigned len, unsigned memtype, unsigned rw) {

BX_CPU_C *pcpu = BX_CPU(cpu);
// Not going to use physical memory address.
(void)phy;

// Read-write instructions are currently not interesting.
if (rw == BX_RW)
return;

// Is the CPU in protected or long mode?
unsigned mode = 0;

// Note: DO NOT change order of these ifs. long64_mode must be called
// before protected_mode, since it will also return "true" on protected_mode
// query (well, long mode is technically protected mode).

if (pcpu->long64_mode()) {
#if BX_SUPPORT_X86_64
mode = 64;
#else
return;
#endif // BX_SUPPORT_X86_64
} else if (pcpu->protected_mode()) {
// This is either protected 32-bit mode or 32-bit compat. long mode.
mode = 32;
} else {
// Nothing interesting.
// TODO(gynvael): Well actually there is the smm_mode(), which
// might be a little interesting, even if it's just the bochs BIOS
// SMM code.
return;
}

// Is pc in kernel memory area?
// Is lin in user memory area?
bx_address pc = pcpu->prev_rip;
if (!invoke_system_handler(BX_OS_EVENT_CHECK_KERNEL_ADDR, &pc, NULL) ||
!invoke_system_handler(BX_OS_EVENT_CHECK_USER_ADDR, &lin, NULL)) {
return; /* pc not in ring-0 or lin not in ring-3 */
}

// Check if the access meets specified operand length criteria.
if (rw == BX_READ) {
if (len < globals::config.min_read_size || len > globals::config.max_read_size) {
return;
}
} else {
if (len < globals::config.min_write_size || len > globals::config.max_write_size) {
return;
}
}

// Save basic information about the access.
log_data_st::mem_access_type access_type;
switch (rw) {
case BX_READ:
access_type = log_data_st::MEM_READ;
break;
case BX_WRITE:
access_type = log_data_st::MEM_WRITE;
break;
case BX_EXECUTE:
access_type = log_data_st::MEM_EXEC;
break;
case BX_RW:
access_type = log_data_st::MEM_RW;
break;
default: abort();
}

// Disassemble current instruction.
static Bit8u ibuf[32] = {0};
static char pc_disasm[64];
if (read_lin_mem(pcpu, pc, sizeof(ibuf), ibuf)) {
disassembler bx_disassemble;
bx_disassemble.disasm(mode == 32, mode == 64, 0, pc, ibuf, pc_disasm);
}

// With basic information filled in, process the access further.
process_mem_access(pcpu, lin, len, pc, access_type, pc_disasm);
}

信息记录方式都是通过invoke_system_handler函数去处理自定义系统事件,目前主要支持4种操作系统(windows\linux\freebsd\openbsd),macOS还没搞过,原作者是说想继续实现macOS,这个值得尝试开发下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const struct tag_kSystemEventHandlers {
const char *system;
s_event_handler_func handlers[BX_OS_EVENT_MAX];
} kSystemEventHandlers[] = {
{"windows",
{(s_event_handler_func)windows::init,
(s_event_handler_func)windows::check_kernel_addr,
(s_event_handler_func)windows::check_user_addr,
(s_event_handler_func)windows::fill_cid, // 获取线程环境块TEB,读取进程/线程ID
(s_event_handler_func)windows::fill_info, // 基于config.txt中配置的进线程结构offset去读取进线程信息,包括进程文件名、创建时间、栈回溯等信息
(s_event_handler_func)NULL}
},
{"linux",
{(s_event_handler_func)linux::init,
(s_event_handler_func)linux::check_kernel_addr,
(s_event_handler_func)linux::check_user_addr,
(s_event_handler_func)linux::fill_cid,
(s_event_handler_func)linux::fill_info,
(s_event_handler_func)NULL}
},
{"freebsd",
{(s_event_handler_func)freebsd::init,
(s_event_handler_func)freebsd::check_kernel_addr,
(s_event_handler_func)freebsd::check_user_addr,
(s_event_handler_func)freebsd::fill_cid,
(s_event_handler_func)freebsd::fill_info,
(s_event_handler_func)freebsd::instr_before_execution}
},
{"openbsd",
{(s_event_handler_func)openbsd::init,
(s_event_handler_func)openbsd::check_kernel_addr,
(s_event_handler_func)openbsd::check_user_addr,
(s_event_handler_func)openbsd::fill_cid,
(s_event_handler_func)openbsd::fill_info,
(s_event_handler_func)openbsd::instr_before_execution}
},
{NULL, {NULL, NULL, NULL, NULL, NULL}}
};

最后就是输出记录的信息,比如作者发现的CVE-2018-0894漏洞信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
------------------------------ found uninit-copy of address fffff8a000a63010

[pid/tid: 000001a0/000001a4] { wininit.exe}
COPY of fffff8a000a63010 ---> 1afab8 (64 bytes), pc = fffff80002698600
[ mov r11, rcx ]
Allocation origin: 0xfffff80002a11101
(ntoskrnl.exe!IopQueryNameInternal+00000071)
--- Shadow memory:
00000000: 00 00 00 00 ff ff ff ff 00 00 00 00 00 00 00 00 ................
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
--- Actual memory:
00000000: 2e 00 30 00 aa aa aa aa 20 30 a6 00 a0 f8 ff ff ..0..... 0......
00000010: 5c 00 44 00 65 00 76 00 69 00 63 00 65 00 5c 00 \.D.e.v.i.c.e.\.
00000020: 48 00 61 00 72 00 64 00 64 00 69 00 73 00 6b 00 H.a.r.d.d.i.s.k.
00000030: 56 00 6f 00 6c 00 75 00 6d 00 65 00 32 00 00 00 V.o.l.u.m.e.2...
--- Stack trace:
#0 0xfffff80002698600 (ntoskrnl.exe!memmove+00000000)
#1 0xfffff80002a11319 (ntoskrnl.exe!IopQueryNameInternal+00000289)
#2 0xfffff800028d4426 (ntoskrnl.exe!IopQueryName+00000026)
#3 0xfffff800028e8fa8 (ntoskrnl.exe!ObpQueryNameString+000000b0)
#4 0xfffff8000291313b (ntoskrnl.exe!NtQueryVirtualMemory+000005fb)
#5 0xfffff800026b9283 (ntoskrnl.exe!KiSystemServiceCopyEnd+00000013)

从杀软之殇谈产品

百度

前几天,百度杀毒、百度卫士宣布停止服务,祭出“百度杀毒感谢一路有你”的殇曲,就此终结!

PC杀毒软件的历史已经有35年,但在这移动互联网的时代,问津者能有几人呢?

今天就此事聊聊安全产品和安全从业人员。

面向系统的产品

杀毒软件的出现就是弥补系统自身安全能力的不足,算是面向系统的安全产品,不仅是PC,Android上也是如此。

当年Android病毒那么严重,第一款Android主动防御软件LBE诞生时也是风光无限,甚至后面还利用系统漏洞作防御。

再看看现在,随着Windows系统自主安全能力以及自家杀软Windows Defender的不断完善,并且提供给杀软的接口越来越少,权限越来越集中管理,使得PC杀软越来越无用武之地,甚至曾经的头牌杀软已沦为流氓软件。

而这几年Google在Android安全上的投入也是非常大,整个系统的安全性相比前几年提升很大,他们家的chrome也曾在pwn2own黑客大赛幸存过几年,虽然今年被破了。

以前iOS截图后是不能直接分享的,然后就有一些软件直接提供截图后的分享功能,但是现在苹果自己在iOS上做了,如果软件还保留原分享功能,就成为一种干扰,有些软件就又不得不去掉:

image-20181124123301131

所以,凡是面向系统的产品,自系统厂商打算自己做时,就没你啥事了,无论是现今的PC,还是未来的移动端,均是如此。

面向业务的产品

以前,很多企业为了维护业务安全,都是从安全公司买服务或产品。

后来,随着企业的壮大,很多安全工作都企业自己做,自己开发扫描器、防火墙等等,然后各安全公司就开始被收购,乙方安全人员开始向甲方流动。

再后来,企业内部的业务部门也逐渐壮大,很多业务也开始自己做安全,他们也怕被安全部门捅漏洞丢面子,一些容易做的安全产品会被优先替代掉,一些需要长期投入并维护的安全产品,就得看业务部门是否有此人力,有些安全产品没有多年的积累一时也是无法替代的。与此同时,一些安全部门人员开始向业务部门流动。

所以,凡是面向业务的产品,自业务打算自己做时,就没你啥事了

面向人的产品

人,可以分为商户(2B)与用户(2C),比如阿里就是典型的2B基因,腾讯就是典型的2C基因,虽然他们各自都不信这邪。正如当年阿里做来往,腾讯做拍拍一般,结局都是相当地凄惨。

杀软2C的道路基本已经凉凉的,但是2B之路还有点残羹剩饭,尤其是天朝机构,出于国家安全考虑,在抵制国外安全产品的同时,就需要国产安全产品替代。所以现在很多国内杀软都是逐步转向做2B业务,但肯定都是不如从前了。

人总有各种各样的需求,面对人的产品就是为了解决这些需求。比如人与人的沟通,以前飞鸽传书、快马加急,到现在的电话短信、微信视频等方式,需求亘古不变,只是产品在进化,在被另一种更高效、更低成本的产品替代。

所以,面向人的产品,产品要解决的需求形态可能长期存在,但产品终会被更高效、更低成本的产品替代

安全人员的生存之道

最近一年,在微信群里,仍然还有一些做Windows病毒分析的同学,还在聊Windows病毒,看着都觉得无力。如果是结合漏洞的病毒样本分析,还可以高谈APT混口饭吃,而其它类型的病毒都已经没什么市场了。

从一方面讲,移动时代不求思变转型的安全人员,有时也挺惨的,杀软产品不做了,这帮开发和分析人员又该何去何从。

就连招二进制安全的,很多人只会windows平台,不懂移动端,经常无法满足业务安全需求。当然,如果是深入Windows系统漏洞攻防的,那现在还是相当有市场的。

杀软这事也只是系统自身进化导致的,如果是系统颠覆的,如Android灭掉了Symbian,一身超神的塞班技能也无济于事。

我现在特别希望Android被新系统颠覆,所有安全人员又打回同一起跑线,重新开始研究新系统的漏洞攻防。当年虽赶上Android安全,但只搞了应用安全,没有深入系统安全,后来上车晚了,还是觉悟不够。

所以,安全人员还是得居安思危,不然哪天就真没饭吃了。

读《态度》

image-20181118184524117

《态度》是吴军在今年新出的书籍,是他写给女儿的40封家书。第一次读吴军的书是《见识》,读完觉得不错,因此这次“双十一”买了好几本他写的书,包括《浪潮之颠》、《大学之路》。

在新书中,主要分6部分来讲,分别讲了一些做人做事的原则、对待金钱和人际关系的态度、还有学习和人生哲学的一些观点。

关于教育

作者说”教育改变命运“,其实多数读书人应该都认同这一观点,他在书中讲述了3个观点:

  1. ”教育改变命运“已成为全世界大部分国家的共识

    欧洲很早就意识到教育对人一生的帮助,所以很早就开始兴办免费教育,让交不起学费的贫家子弟都可以到”官办“学校读书,牛顿就是靠这种免费的公立教育完成中学学业,进入剑桥大学的。很多来自中国农村的贫穷人家,为了摆脱贫穷,让自己子女接受良好的教育,通过一代人的努力摆脱贫穷,改变命运。

  2. 衣食无忧的富家子弟也有必要接受教育

    中国著名作家吴晓波,就是那个写了《腾讯传》的人,调查了早期在股市上发财的几十人,发现除了一两个之外,其他人的结局无一例外都很惨,有破产的,有坐牢的,有被仇家杀的,有正在被追杀的。这些人都有两个共同点:第一,敢于冒险;第二,受教育水平低,最高中学学历。因此,他们在有了钱之后,没有更高的理想和追求。

    【PS】:在中国,尤其是农村,一些没接受过多少教育的暴发户,有时会对年轻人或读书人说:“读书没有用的,你读那么多书赚的还不是没我多”!如果此时你对他说:“让你的孩子退学吧!“,估计他就没话了。还有一些在外工作多年的人,包括腾讯人,有时过年同学聚会,发现一些曾经读书不好或者辍学的老同学现在混得相当不错,比自己好太多的时候,通常都会反问自己:“读那么多书真的有用吗“?这种情况都是”幸存者偏差“的认识而已(顺便给大家推荐另一本书《思考,快与慢》,里面就讲了很多这种思考或认识的陷阱)。记得,腾讯内部论坛就有人发过这样感慨。

  3. 对退学创业的误解

    互联网行业的退学创业代表主要有5人:比尔盖茨、乔布斯、佩奇、布林,以及扎克伯格。佩奇和布林是进入斯坦福读博士之前退学的,盖茨和扎克伯格情况类似,人家都是上过哈佛的,同样比绝大部分年轻人都出色,并接受过良好的教育,而且他们都是创业成功之后才退学的。乔布斯是因为不忍花父母的钱才没有读大学的,但至少是在斯坦福大学接受过教育的,如果他来自一个相对富有的家庭,或许会读完大学。

####关于格局

决策时格局要大,做事时境界要高。用作者通俗一点的话来讲,就是要长远考虑,尽可能往最好的目标努力。

书中举例几个MIT和哈佛毕业生组成的创业团队,他们打算做高频交易(在股市中通过快速操作以赚取低买高卖时差价)的创业项目,作者认为他们都是非常聪明的人,但做事的格局不大,因为这赚不到大钱,属于小打小闹,而且这事仅是提高股市交易量,并无其它意义,对世界更不可能产生任何重大影响,而且这件事的公司已经很多了。

这种看似只赚不赔的高频交易公司为何做不到像Google、Apple那样赚更多钱呢?因为前者以改变世界为目的,后者以赚小钱、小富即安为目的。

很多亚裔家长一直在纠结孩子上名校是否有用?作者对此的观点是,如果格局提升不上去,上了也没有用,还达不到谷歌员工的平均水平。

关于贫穷

一个生于富有家庭的孩子不怕别人说他穷的,而一个贫家子弟通常反而怕别人说他穷,看不起他。卖肾买iPhone的人,一定是贫家子弟,怕别人说他穷。于是,很多穷人最后还是选择与穷人,甚至比自己穷的人为伍,成绩差的孩子还是选择扎堆一块玩。久而久之,那些人就无法摆脱原属阶层了。

大家常说:”物以类聚,人以群分“,因为这样对人来说,交际成本是最低的,也是最舒适的方式。一般人都不太愿意跳出自己的舒适区,去尝试更有难度且有助提升自我的挑战。

不管贫穷与否,如果事情做不好,那么你所处的地位可能与贫穷无异。通常情况下,人的心理能否接受自己不如别人,在可能会被别人嘲笑的情况下,是否还能努力往前走,直到改变自己的状态,这就是一种考验,与走出贫困差不多。

其实多数人都知道,要多向学习好的、能力强的人为伍,这些道理小学老师都经常说。但有时要去向比自己牛逼的人学习请教,也是需要一点勇气,以及改变自我的努力和决心。

关于投资

作者在书中挺推荐买标准普尔500指数,还让他女儿拿1/3的钱直接购买标普500指数ETF,每过两三个月就继续定投,不管涨跌。说实话,我也没关注这指数,今天赶紧加个关注先。

作者对投资给出了4条建议:

  1. 永远不要觉得自己能够打败市场

    我觉得下面这张图已经足够说明一切了,最近在微信和朋友圈上传得挺火的。

    image-20181118191352193

  2. 对市场要有信心

作者说,股市有涨有跌,但要相信股市在较长的时间里是往上走的。

我曾经对腾讯700也是迷之自信,但你看看现在,这一年都跌成啥样了,当然如果你说再等个一两年,那应该还是涨的,看谁命长了。还好作者后面补了个第3点。

  1. 虽然股市在下跌后总会涨回来,但是单一股票未必。

在2000年股价到达顶点的英特尔和思科,今天的股价不足当年的1/4,而且可能永远没有机会回到当时的峰值了。也就是说,投资单一的股票,即使遇到明星公司,也未必能长期赚钱。

  1. 时间是你的朋友,而时机不是。

投资要有耐心,不要急于求成。聪明的投资人永远在股市上投资,而不是试图投机挑选最低点和最高点。因此,走出坏运气的关键是耐心,让时间成为我们的朋友。

除以上建议之外,作者还给出了3条禁忌:

  1. 不要进行过于冒险,会导致来顶之灾的投资

    比如做空股票和使用杠杆投资,你们再回头看看“格雷厄姆的微笑”吧!

  2. 不要进行自己不懂的投资

    拿自己的短处和别人的长处比,胜算微乎其微。同时,你看不懂的投资里面常常有很多陷阱。

  3. 不要被那些所谓的失去了的投机机会乱了方寸

    经常有人说:”如果我当初买了比特币,今天能赚100倍“。这种话是没有意义的,如同中彩票一般,是运气,遇不上,也不必在意,因为人生的机会还有很多。

关于友情

作者在书中讲到一个在腾讯的经历,大家感受下,其它就不啰嗦了:

IMG_6440

关于交际

与我们接触的人当中,可以分为4种:

  1. 与自己关系好,能力强;
  2. 与自己关系好,能力有限;
  3. 并非自己朋友,但能力强;
  4. 与自己关系不好,能力不强。

第1种好办,第4种交集不多,可以忽略,所以为人处世方面,我们需要比较留意的第2和第3种人。

对于这些人,我们必须理性对待,避免依据个人喜好来判断人和事:符合自己喜好的人,无论他们做什么都觉得好;不符合自己喜好的人,无论他们做什么都要挑毛病。

比如特朗普,很多人讨厌他,其实我也讨厌他,然后就有很多人对他做的任何事都持反对意见,无论对错,这就有点失去理想了。

在社会上,个人生活和事业有时是需要他人的支持和帮助,需要我们能够团结大多数人,把事情做好。

关于拒绝

作者举例说曾有朋友找他帮忙,就是帮个孩子联系谷歌或者腾讯的实习机会,但看了孩子材料之后就直接回绝了。那朋友也表示理解,就没有再提此事。

这种事太常见了,尤其是在腾讯这类公司,很多人会找过来帮忙找个工作之类,现在基本每次回家都有人找我。以前有人让我帮忙投简历,我一般都直接帮投了,即使有些简历或者学历问题,基本都拿不到面试机会的,我也帮忙投了,所以在公司那个”伯乐“平台上,结果全是拒绝的。后来,我学聪明了,不再当”老好人“。一些简历有问题,有明显水平不行的,我都委婉地回绝掉。一方面,投了也是白投,另一方面HR又不傻,我老帮人投这类简历,别人还会觉得我看人的水平太次太有问题了。

对于别人请帮忙的事,作者总结出4点,按不同情况采取不同的方法处理:

  1. 能力不及,不能帮上忙,直接在第一时间委婉拒绝。
  2. 能帮上忙,但是自己代价太大,不想帮的,就不要勉强自己,但也要及早通知对方。
  3. 不论多困难都愿意帮,而且极有可能办成,这时就答应对方,然后全力去做。
  4. 虽然愿意帮,但有可能帮上,也可能帮不上,这时要将实际情况说明,千万不要轻易许诺。

结语

这书整体上我觉得还不错,虽然有些是对中学生或大学生说的道理,很多事情自己经历过也都明白,但也有一些适合不同年龄段的人学习的知识点,所以还是推荐阅读。

喜欢吴军写的书,并非崇拜对方,而他写的书确实很好,也很用心。在这种浮躁的社会,出书也不容易,写好书更不容易,自己出过书的人应该更有体会。

读《风格感觉:21世纪写作指南》

1、节俭使用元话语(语句中标示话语结构的标记语言,即用来提醒读者应该注意什么),可以用提问代替元话语,比如:

【改前】:这一章讨论引起名字流行程度上升和下降的因素。

【改后】:一个名字流行或不流行的原因是什么?

2、放弃专家腔、更自然地对话

【改前】:近年来,越来越多的心理学家和语言学家将注意力转向儿童语言习得的问题。本文将评述这一过程近年来的研究。

【改后】:小孩子不用专门上课,就能获得说一门语言的能力。他们怎么做到的?

3、写作清晰有力,少用模糊词汇

【改前】:杰克是个诚实的人。

【改后】:杰克是个特别诚实的人。

加了“特别”反而让人产生困惑,甚至以为说的是反话,所以少用“非常”、“十分”、“特别”这种过犹不及的强调词。

4、少用抽象词,比如“xx性”、“xx观”、“xx力”,这种特别容易出现在工作中,俗称“官僚体”,腾讯内部就有一堆此类名词(闭环、打法、自控力、大局观、专注度……)

5、去掉僵尸名词:比如“做出确认”变成“确认”、“做出辞职的决定”变成“决定辞职”。

6、采用主动和互动风格,即站在读者角度或者自己主动的角度。

【改前】:我们很高兴地宣布,本实验室的新设施将对外开放,随时准备承接各类脑科学实验。

【改后】:你将有机会使用本实验室,来做你的脑科学实验。

7、【有序地称呼反复出现的事物】避免读者过多思考,免得他们需要思考是否为新事物

一个英国人、一个法国人和一个犹太人坐在一起,这个英国人说……,这个法国人说……,这个犹太人说……

少用他/她/它去代替前面说过的人物事等,减少读者疑惑和思考,避免产生歧义,比如:

愧疚、复仇和苦痛会从感情上毁掉你和你的孩子。必须摆脱他们。

上面的是摆脱你的孩子,还是摆脱愧疚、复仇和苦痛?

8、避免使用过多的连接词,否则文章就会变得臃肿不堪,比如下句中的因为就是多余的,因为前面的“原因”已经暗示我们正在做出解释:

太多人生活在黑暗之中的原因是因为他们想那样生活。

9、【谨慎地运用否定】正如克林顿辩解道“我没有和那个女人发生过性关系”的时候,并没有使各种传言平息下来,有时甚至起到“此地无银三百两“的反作用。

10、正确的写作用法只是促成良好写作最小的因素,其重要性远远比不过保持连贯性、使用古典风格、克服知识的诅咒,更不用说在智力上维护勤恳了。假如真的希望提高写作质量,或者怒斥他人的文章,最需要关注的不是那些语法规则,而是那些支配批判性思考和发现事实的规则:

​ a. 查资料

​ b.确保论证有理有据

​ c. 不要把轶事或个人经历当作世界的常态,避免把特殊个例当作显著现象

​ d. 谨防虚假的二分法:把复杂问题化为两种思想之间的战争,几乎无法帮助我们增进理解

​ e.论证应当基于理性,而非个人。即“不要证明自己对,而要弄清什么是对的”

最后说一句最重要的话:

热文或畅销书之所以流行,关键在于其内容,而非华丽的辞藻文法。