引发的一场灾难简介:这个故事改编自Linux内核的一个真实漏洞。原本林克帝国的危机之夜降临,喧嚣褪去,忙碌的Linux帝国渐渐平静下来。没有人想到,一场改变帝国命运的风暴正在悄然逼近……“轰!”,帝国安全部长办公室的敲门声打破了夜晚的宁静。“部长,我刚刚发现有线程在修改passwd文件。”原来是文件系统部的小黑来了。“有什么好大惊小怪的?只要有root权限就可以!”安全部长没有抬头,继续看着每天的系统日志。“部长,重点是这个线程不是从系统调用进入内核的,而是从中断入口进入的。”安全部长愣了一下,大约0.2ms后,他放下了手中的日志,站了起来。“你是说,他是通过中断描述符表(IDT)进来的?”小黑点点头。“小王,你赶紧和他一起去IDT看看,查清楚了就第一时间报告我。”部长对身边的助理说道。小王点了点头,正要出发,刚走到门口,又被大臣拦住了。“等等!这件事非同小可,我还是亲自过去一趟吧。”IDT修正案的安全部长立即出发,来到了IDT所在的地方。这里的一切都保持不变,没有任何区别。臣指着那排城门问道:他从哪一扇城门进来的?“4号”,这时候,守在IDT门口的白发老者走了过来,应了一声。“奇怪了,IDT表里的函数入口都是帝国安排的,按理说,没有一个会修改passwd文件。”大臣看着这些条目,对自己低下了头。“部长,这件事得跟您汇报一下,这小子进来之前,把第四项的入口地址高32位改成了0x00000000,进来之后又恢复成0xFFFFFFFF。”老爷子说完,拿出了IDT入口的结构图展开:大臣闻言猛地抬起头,“高32位变成了0x00000000,所以整个函数入口地址都指向了用户态地址空间?”小黑和小王都不敢说话,后果有多严重谁都知道。天知道那个家伙在用户空间用内核权限执行了什么代码。“不行,在他进来之前,一个用户空间的线程怎么可能改变IDT的内容?他没有访问权限,我不信!”调用栈不在用户空间,而是来自内核空间的函数——perf_swevent_init。”老头说。一个整数+1的悲剧大臣什么也没说,直接带领大家来到了perf_swevent_init函数。“老头子,你还记得具体位置吗?”部长问道。“它来自第19行的static_key_slow_inc函数。”“让我看一看。”小王推上前去,想向大臣炫耀一下。“嗯,这个static_key_slow_inc是做一个整数的原子+1操作。但是它操作的是perf_swevent_enabled数组,不兼容IDT,怎么修改成IDT呢?”,小望墨摸了摸头又后退两步,也没看出有什么问题。“不一定!”,大臣还是皱着眉头,说道,“你看,它是用数字event_id作为下标访问数组元素的,如果event_id出错,访问越界,指向数组元素也不是不可以IDT。啊!小王快速看了一眼event_id,随后露出失望的表情,“不行,第9行有校验,你看,超过8就校验不通过。”线索到这里就断了,本来指望perf_swevent_init函数找到IDT被修改的玄机,看来要白回来了。不知不觉已经很晚了,部长一行决定先回去,再做长远打算。大臣走了几步,见小王没有跟来,转身叫住他。“臣,请住手,我好像觉得有些不对劲。”小王此刻也皱起了眉头。“发现什么了?”大臣和小黑往回走。“部长,你看第3行,这个event_id是一个int型变量,也就是说是一个带符号的数字。”小王说。“带符号的数字有什么问题?”小黑忍不住问道。“如果……”“_如果event_id变成负数,就可以越界访问数组,通过第9行的大小检查!_”,小王话音未落,大臣就解谜了!大家又一次把目光放在了event_id上,打算看看第三行赋给它的event->attr.config是什么。首先是perf_event中的attr成员变量:structperf_event{//...structperf_event_attrattr;//...};然后是perf_event_attr中的config成员变量:structperf_event_attr{//...__u64config;//...};看到最后,部长和小王都倒吸一口凉气。配置原来是一个64位无符号整数。难怪将其分配给int变量可以毫无问题地工作!小黑见大家都不说话,挠了挠头,弱弱的问道:“怎么了,怎么都不说话了?有什么问题吗?”小王把小何拉到一边,“问题大了,你看我给event_id赋一个config值0xFFFFFFFF,event_id会变成什么?”“负、负、负1?””是的,一个有符号数的最高位是用来标示正负的。如果这个config的最高位是1,后面的位经过精心设计,不仅可以骗过那里第九行的验证,还要对某个位置的数字执行原子+1操作。”小王继续说道。“不错,小王,你进步了!”不知什么时候,大臣也走了过来,被大臣表扬,小王有些不好意思。“听了半天,不就是越界了,给某个地方的数字加1吗?有什么大不了的?”小黑一脸不屑。小王连连摇头,“别小看加1的行为,如果加在一些敏感的地方,会造成大麻烦的!”小黑有些疑惑,“比如说?”“比如记录中断和异常处理函数的IDT,比如记录系统调用的sys_call_table,这些表中的函数地址都位于英制内核空间,很麻烦。”小王继续说道。“我明白了,不过加1应该问题不大吧?”小王叹了口气,“看来你还是不明白,看表中的条目——中断描述符的格式”“IDT中中断/异常处理函数的地址并不是一个完整的64位,但是拆分成几个部分,其中我把高32位用红色给大家标出来,在64位Linux帝国中,内核空间地址的高32位是0xFFFFFFFF,变成了0,对吧?”小黑终于明白了。真相大白。安全部长为小王的精彩分析点赞:“不错,大家都很聪明!现在,我们来复习一下!”第一步:精心设计一个config值,从应用层传入内核空间的perf_swevent_init函数第二步:利用empire内核漏洞将一个64位无符号数赋值给一个int变量,导致该变量溢出到负数。第三步:利用溢出event_id越界访问perf_swevent_enabled,指向IDT表项,对第四个中断处理函数的高32位进行原子+1第四步:修改后的中断处理函数指向用户空间第五步布置恶意代码:应用层执行int4汇编指令,触发4号中断,线程会进入内核空间以高权限执行事先布置好的恶意代码。事情终于水落石出。安全大臣回来后,向帝国本部汇报,修复了漏洞,将event_id的类型从int改为u64。即便如此,大臣的心情也好不到哪里去,不知名的敌人闯入了帝国,他们是谁?你做了什么?你现在躲在哪里?一个个问题还在脑海里闪过……未完待续……复活节彩蛋一个闷热的下午,电风扇急速旋转,热得让人喘不过气来。一个熟悉的身影出现在部长办公室,仔细一看,竟然是小马哥。《大臣,nginx公司又出事了》预示着接下来会发生什么,敬请期待精彩的后续……来源|编程技术宇宙作者|轩辕之锋
