共计 3129 个字符,预计需要花费 8 分钟才能阅读完成。
简介: 本故事依据 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
成员变量:
struct perf_event {
// ...
struct perf_event_attr attr;
// ...
};
接着是 perf_event_attr
中的 config
成员变量:
struct perf_event_attr {
// ...
__u64 config;
// ...
};
看到最初,部长和小王都倒吸了一口凉气,这 config
居然是个 64 位无符号整数,把它赋值给一个 int
型变量不出问题就怪了!
见大家都不谈话,小黑挠了挠头,弱弱的问到:“怎么了,你们怎么都不谈话,这有什么问题吗?”
小王把小黑拉到一边,“问题大了,你看我要是把一个值为 0xFFFFFFFF 的config
赋值给 event_id
,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- 第四步:批改后的中断处理函数指向了用户空间,提前在此安顿恶意代码
- 第五步:应用层执行
int 4
汇编指令,触发 4 号中断,线程将进入内核空间,以至高权限执行提前安顿的恶意代码。
事件总算是上不着天; 下不着地,安全部长回去之后便上报帝国总部,修复了此破绽,将 event_id
的类型从 int
修改为 u64
。
即便如此,部长的情绪却并没有轻松多少,未知的敌人曾经闯入帝国,它们是谁?做了什么?当初藏在哪里?一个又一个的问题还在一直在脑中闪现······
未完待续······
彩蛋
一个闷热的下午,风扇飞速的旋转,热得人喘不过气。
部长的办公室呈现了一个相熟的身影,走近一看原来是小马哥。
“部长,nginx 公司又出事了”
预知后事如何,请关系后续精彩······起源 | 编程技术宇宙
作者 | 轩辕之风