帝国危机夜幕降临,喧嚣褪去,忙碌的Linux帝国逐渐平静下来。谁也没想到,一场危机正在悄然逼近……“咚!”,帝国安全大臣办公室的敲门声打破了夜晚的宁静。“部长,我刚刚发现有程序在修改passwd文件。”原来是文件系统部的小黑来了。安全部长皱了皱眉。这个密码文件不寻常。记录了系统所有用户的信息,不过0.1ms后,皱着的眉头才松了下来。“有什么好大惊小怪的?只要有root权限就可以!”安全部长没有抬头,继续看着每天的系统日志。“部长,重点是这个程序不是从系统调用进入内核的,而是从中断入口进入的。”安全部长愣了一下,大约0.2ms后,他放下了手中的日志,站了起来。“你是说,他是通过中断描述符表(IDT)进来的?”小黑点点头。“小王,你赶紧和他一起去IDT看看,查清楚了就第一时间报告我。”部长对身边的助理说道。助理点点头,正要走,刚走到门口,又被部长拦住了。“等等!这件事非同小可,我还是亲自过去一趟吧。”IDT修正案的安全部长立即出发,来到了IDT所在的地方。这里的一切都保持不变,没有任何区别。臣指着记号表的中段问道:“他从哪扇门进来的?”“4号”,这时候,守在IDT门口的白发老者闻讯过来应了一声。“奇怪了,IDT表中的函数项都是我们操作系统安排的,按理说,它们都不会修改passwd文件。”大臣看着这些条目,对自己低下了头。“部长,这件事得跟您汇报一下,这小子进来之前,把第四项的入口地址高32位改成了0x00000000,进来之后又恢复成0xFFFFFFFF。”老爷子说完,拿出了IDT表项的结构图展开:大臣闻言猛地抬起头,“这个表项地址是64位的,分为三部分存放在IDT表项,高32位通常是0xFFFFFFFF,指向我们内核空间的中断处理函数,现在变成了0x00000000,所以整个函数的入口地址指向用户态地址空间?小黑和助理都不敢说话,后果有多严重谁都知道,天知道那家伙以内核权限在用户空间执行了什么代码。“不对,在他进来之前,用户空间的程序怎么可能改变IDT的内容?他没有访问权限,你是不是看错了?”“我没有看错,他改得恰到好处,我特别注意他的调用栈,不是在用户空间,而是从内核空间的函数——perf_swevent_init。”老头说。悲惨的整数+1的大臣什么也没说,直接带领大家来到perf_swevent_init函数,“老头,你还记得具体位置吗?”大臣问道,“来自第19行的static_key_slow_inc函数。”“让我看看。”助理往前一挤,想向部长炫耀一下。但它在与IDT不兼容的perf_swevent_enabled数组上运行。怎么修改成IDT?”,助理摸了摸他的头,后退了两步,也没看出有什么问题。使用数字event_id作为下标来访问数组元素。如果event_id出错,越界访问,指向IDT也不是没有可能。啊!”助理迅速看了一眼event_id,然后露出失望的表情,“不行,第9行有校验,你看,超过8就校验不通过。”线索到这里就断了.在perf_swevent_init函数中寻找IDT被修改的玄机,貌似会白回来,不知不觉已经很晚了,部长一行决定先回去,再做长远打算”部长走了几步,见助理没有跟上,转身喊道:“部长,你先停下来,我好像觉得不对劲。“发现了什么?”部长和小黑往回走。有符号数?”小黑忍不住问道。“如果……”“如果event_id变成负数,就可以访问rrayoutofbounds,passsizecheckonline9!”助理的话还没说完,部长就解开了谜底!大家又一次把注意力集中在了这个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分配一个值为0xFFFFFFFF的config,event_id会变成什么?”“负、负、负1?””是的,一个有符号数的最高位是用来标示正负的。如果这个config的最高位是1,后面的位是经过精心设计的,不仅是为了骗过那里第9行的验证,还可以对某个位置的数字执行原子+1操作。”助理继续说道。“不错,小王,你进步了!”不知什么时候部长也过来了,被部长表扬,助理有些不好意思。“听了半天,不就是越界了,给某个地方的数字加1吗?有什么大不了的?”小黑一脸不屑。助理连连摇头,“不要小看加1这个行为,如果加在一些敏感的地方,会造成大麻烦的!”小黑有些疑惑,“比如说?”》比如记录中断异常处理函数的IDT,记录系统调用的sys_call_table,这些表中的函数地址都位于英制内核空间,这个加1就不是其他的了但是这些表中的函数地址。这很麻烦。”助理继续说道。“我明白了,不过就算加1,应该问题不大吧?”助理叹了口气,“看来你还是没明白,我就以这次修改的IDT表为例,给大家看看下表中的条目——中断描述符的格式”“IDT中中断/异常处理函数的地址并不是一个完整的64位,而是拆分成几个部分,其中高32位我给大家标红是的,在64位的Linux帝国中,高内核空间地址的32位是0xFFFFFFFF,如果……”“如果用前面的event_id数组下标越界访问,给这个地方原子加1,那么就变成0了吧?”小黑终于明白了。真相大白了。安全部长为助理的精彩分析点赞:“不错,大家都很聪明!现在,我们来复习一下!”第一步:精心设计一个config值,从应用层传入内核空间perf_swevent_init函数的第二步:利用内核漏洞将一个64位无符号数赋值给一个int变量,导致变量溢出为负数。第三步:利用溢出event_id越界访问perf_swevent_enabled,指向IDT表项,对第四个中断处理函数的高32位进行原子+1第四步:修改后的中断处理函数指向用户空间第五步布置恶意代码:应用层执行int4汇编指令,触发4号中断,线程会进入内核空间以高权限执行事先布置好的恶意代码。事情终于水落石出。安全部长回国后反馈问题,修复了漏洞,将event_id的类型由int改为u64。这一次终于化解了危机。
