黑客与宕机

简介: 造成系统异常宕机(无响应、异常重启)的原因有很多种,最常见的是操作系统内部缺陷和设备驱动缺陷。本文作者将和大家分享内存转储分析的底层逻辑和方法论,并通过一个线上真实案例来展示从分析到得出结论的整个过程,希望对同学们处理此类问题和对系统的理解上有所帮助。

image.png
相信凡是与计算机高频亲密接触的人,都遇到过系统无响应,或突然重启的情况。这样的情况如果发生在客户端设备,如手机,或者笔记本电脑上,且不是频繁出现,基本上我们的解法就是鸵鸟算法,即默默重启设备,然后继续使用,当作什么都没发生过。

但是,如果这样的问题发生在服务端,比如运行微信、微博后台程序的虚拟机或者物理机上,那往往会产生相当严重的影响。轻则导致业务中断,重则导致业务长时间无法工作。

大家都知道,驱动这些计算机的是运行在其上的操作系统,如 Windows 或者 Linux 等。系统异常宕机(无响应、异常重启)的原因有很多种,但总体来看,操作系统内部缺陷,或者设备驱动缺陷是最常见的两类原因。

从根本上解决这类问题“唯一正确”的方法,是操作系统内存转储分析(Memory Dump Analysis)。内存转储分析属于高阶的软件调试能力,需要工程师有丰富且全面的系统级别理论知识和大量的疑案破解似的上手实践经验。

内存转储分析的方法论

内存转储分析是对专业能力要求极高的一个工作,也是非常不容易的一件事情。在以往案例分享后,得到比较有趣的反馈,如“耳边想起了柯南的配音”,或者“真黑猫警长!做的是 IT 工程师,却整天搞刑侦工作”。

内存转储分析需要用到的基础能力,包括但不限于反汇编、汇编分析、各种语言的代码分析,系统层面各种结构的理解,如堆,栈,虚表等,甚至深入到 bit 级别。

试想,一个系统运行了很长一段时间。在这段时间里,系统积累了大量正常、甚至不正常的状态。这时如果系统突然出现了一个问题,那这个问题十有八九跟长时间积累下来的状态有关系。

分析内存转储,就是分析发生问题时,系统产生的“快照”。实际上需要工程师以这个快照为出发点,追溯历史,找出问题发生源头。这有点像是从案发现场,推理案发经过一样。

死锁分析方法

内存转储分析方法,可以从所要解决问题的角度,简单分成两类,分别是死锁分析方法,和异常分析方法。这两种方法的区别在于,死锁分析方法以系统全局为出发点,而异常分析则从具体异常点开始。

死锁问题表现出来,就是系统不响应问题。死锁分析方法着眼于全局。这里的全局,就是整个操作系统,包括所有进程在内的系统全貌。我们从教科书里学到的知识,一个运行中的程序,包括了代码段,数据段和堆栈段。用这个方法去看一个系统也同样适合。系统的全貌,其实就包括正在被执行的代码(线程),和保存状态的数据(数据、堆栈)。

死锁的本质,是系统中部分或者全部线程,进入了互相等待且互相依赖的状态,使得进程所承载的任务无法被继续执行了。所以我们分析这类问题的中心思想,就是分析系统中所有的线程的状态和它们之间的依赖关系,正如如图 1 所示。

image.png
图 1

线程的状态相对来说是比较确定的信息。我们可以通过读取内存转储中线程的状态标志位,来获取这类的信息。而依赖关系分析则需要很多的技巧和实践经验。最常用分析方法有对象的持有等待关系分析,时序分析等。

异常分析方法

相对死锁分析,异常分析方法的核心是异常。我们经常遇到的异常有除零操作,非法指令执行,错误地址访问,甚至包括软件层面自定义的非法操作等。这些异常反应到操作系统层面,就是异常重启类宕机问题。

异常问题归根结底是处理器执行了具体的指令而触发的。换句话说,我们看到的现象,肯定是处理器踩到了异常点。所以分析异常类问题,我们需要从异常点出发,逐步地推导出代码执行到这一点的完整逻辑。

以经验来看,懂得做内存转储异常分析的工程师不多,而理解以上一点的人更是少之又少。很多工程师分析异常重启问题,基本上只停留在异常本身,根本没有推导出问题背后的整个逻辑。

相比死锁分析方法,异常分析的方法没有那么多固定的章法,甚至很多时候,因为问题逻辑复杂,我们没有办法找出根本原因。

总体看来,异常分析的底层逻辑,是不断地对比预期和非预期的状况,然后找出背后的原因。比如处理其执行了错误指令而触发的异常,那我们需要从回答正常被执行的指令应该是什么,为什么处理器拿到了这个错误指令这两个问题开始,不断深入,追根究底。

用死锁分析方法处理异常问题,用异常分析方法处理死锁问题

以上两种内存转储分析方法,是基于问题分析的起点和一般性分析手段来分类的。在实际问题处理过程中,我们经常需要从系统全局状态中,找到进一步处理异常问题的思路,也会用具体细节分析手段,来给全局类问题最后一击。

黑客与宕机

问题背景

宕机问题有一种比较少见的问题模式,就是看起来完全不相关的机器同时出现宕机。处理这个模式的问题,我们需要找到在这些机器上能同时触发问题的条件。

通常,这些机器要么几乎在同一时间点出现问题,要么从某一个时间点开始,相继出现问题。对于前一种情况,比较常见的情形是,物理设备故障导致运行在其上的所有虚拟机宕机,或者一个远程管理软件同时杀死了多个系统的关键进程;对于后一种情况,可能的一个原因是,用户在所有实例上部署了同一个有问题的模块(软件、驱动)。

而实例被大范围地攻击,则是另一个常见的原因。比如在 WannaCry 勒索病毒肆虐的时候,经常出现一些公司,或者一些部门的机器全部蓝屏的情形。

在这个案例中,用户安装了阿里云的云监控产品之后,出现了大范围云服务器连续宕机的情况。为了自证清白,我们耗费了不少体力脑力来深入分析这个问题。通过此案例分享,希望能给读者以启发。

坏掉的内核栈

我们处理操作系统宕机类问题的唯一正确方法是内存转储。不管是 Linux 或 Windows,在系统宕机之后,都能够通过自动,或者人工的方式,产生内存转储。

分析 Linux 内存转储的第一步,我们使用 crash 工具打开内存转储,并用 sys 命令观察系统的基本信息和宕机的直接原因。对于这个问题来说,宕机的直接原因是"Kernel panic - not syncing: stack-protector: Kernel stack is corrupted in: ffffxxxxxxxx87eb",如图 2 所示。

image.png
图2

关于这条信息,我们必须逐字解读。"Kernel panic - not syncing:" 这部分内容在内核函数 panic 里输出,凡是调用到 panic 函数,必然会有这一部分输出,所以这一部分内容和问题没有直接关系。而"stack-protector: Kernel stack is corrupted in:" 这部分内容,在内核函数 __stack_chk_fail,这个函数是一个堆栈检查函数,它会检查堆栈,同时在发现问题的时候调用 panic 函数产生内存转储报告问题。

而它报告的问题是堆栈损坏。关于这个函数,后续我们会进一步分析。

而 ffffxxxxxxxx87eb 这个地址,是函数 __builtin_return_address(0) 的返回值。当这个函数的参数是 0 的时候,这个函数的输出值是调用它的函数的返回地址。这句话现在有点绕,但是后续分析完调用栈,问题就会变得很清楚。

函数调用栈

分析宕机问题的核心,就是分析 panic 的调用栈。图 3 中的调用栈,乍看起来是 system_call_fastpath 调用了 __stack_chk_fail,然后 __stack_chk_fail 调用了 panic,报告了堆栈损坏的问题。但是稍微和类似的堆栈作一点比较的话,就会发现,事实并非这么简单。

image.png

图3

图 4 是一个类似的,以 system_call_fastpath 函数开始的调用栈。不知道大家有没有看出来这个调用栈和上边调用栈的不同。实际上,以 system_call_fastpath 函数开始的调用栈,表示这是一次系统调用(system call)的内核调用栈。

image.png

图4

图 4 的调用栈,表示用户模式的进程,有一次 epoll 的系统调用,然后这个调用进入了内核模式。而图 3 中的调用栈显然是有问题的,因为我们就算查遍所有的文档,也不会找到一个系统调用,会对应于内核 __stack_chk_fail 函数。

这里需要提醒的是,这边引出另外一个,在分析内存转储的时候需要注意的问题,就是用 bt 打印出来的调用栈有的时候是错误的。

所谓的调用栈,其实不是一种数据结构。用 bt 打印出来的调用栈,其实是从真正的数据结构,线程内核堆栈中,根据一定的算法重构出来的。而这个重构过程,其实是函数调用过程的一个逆向工程。

相信大家都知道堆栈的特性,即先进后出。关于函数调用,以及堆栈的使用,可以参考图 5。可以看到,每个函数调用,都会在堆栈上分配到一定的空间。而 CPU 执行每个函数调用指令 call,都会顺便把这条 call 指令的下一条指令压栈。这些“下一条指令”,就是所谓的函数返回地址。

image.png

图5

这个时候,我们再回头看 Panic 的直接原因那一部分,即函数 __builtin_return_address(0) 的返回值。

这个返回值,其实就是调用 __stack_chk_fail 的 call 指令的下一条指令,这条指令属于调用者函数。这条指令地址被记录为 ffffxxxxxxxx87eb。

如图 6 所示,我们用 sym 命令查看这个地址临近的函数名,显然这个地址不属于函数 system_call_fastpath,也不属于内核任何函数。这也再次验证了,panic 调用栈是错误的这个结论。

image.png

图6

关于 raw stack,如图7所示,我们可以用 bt -r 命令来查看。因为 raw stack 往往有几个页面,这里只截图和 __stack_chk_fail 相关的这一部分内容。

image.png

图7

这部分内容,有三个重点数据需要注意,panic 调用 __crash_kexec 函数的返回值,这个值是 panic 函数的一条指令的地址;__stack_chk_fail 调用 panic 函数的返回值,同样的,它是 __stack_chk_fail 函数的一条指令的地址;ffffxxxxxxxx87eb 这个指令地址,属于另外一个未知函数,这个函数调用了 __stack_chk_fail。

Syscall number 和 Syscall table

因为带有 system_call_fastpath 函数的调用栈,对应着一次系统调用,而 panic 的调用栈是坏的,所以这个时候我们自然而然会疑问,到底这个调用栈对应的是什么系统调用。

在 linux 操作系统实现中,系统调用被实现为异常。而操作系统通过这次异常,把系统调用相关的参数,通过寄存器传递到内核。在我们使用 bt 命令打印出调用栈的时候,我们同时会输出,发生在这个调用栈上的异常上下文,也就是保存下来的,异常发生的时候,寄存器的值。

对于系统调用(异常),关键的寄存器是 RAX,如图 8 所示。它保存的是系统调用号。我们先找一个正常的调用栈验证一下这个结论。0xe8 是十进制的 232。

image.png

图8

使用 crash 工具,sys -c 命令可以查看内核系统调用表。我们可以看到,232 对应的系统调用号,就是 epoll,如图 9 所示。

image.png

图9

这个时候我们再回头看“函数调用栈”这节的图 3,我们会发现异常上下文中 RAX 是 0。正常情况下这个系统调用号对应 read 函数,如图 10 所示。

image.png

图10

从图 11 中,我们可以看出,有问题的系统调用表显然是被修改过的。修改系统调用表(system call table)这种事情,常见的有两种代码会做,这个相当辩证。一种是杀毒软件,而另外一种是病毒或木马程序。当然还有另外一种情况,就是某个蹩脚的内核驱动,无意识地改写了系统调用表。

另外我们可以看到,被改写过的函数的地址,显然和最初被 __stack_chk_fail 函数报出来的地址,是非常邻近的。这也可以证明,系统调用确实是走进了错误的 read 函数,最终踩到了 __stack_chk_fail 函数。

image.png

图11

Raw data

基于上边的数据,来完全说服客户,总归还是有点经验主义。更何况,我们甚至不能区分,问题是由杀毒软件导致的,还是木马导致的。这个时候我们花费了比较多的时间,尝试从内存转储里挖掘出 ffffxxxxxxxx87eb 这个地址更多的信息。

有一些最基本的尝试,比如尝试找出这个地址对应的内核模块等等,但是都无功而返。这个地址既不属于任何内核模块,也不被已知的内核函数所引用。这个时候,我们做了一件事情,就是把这个地址前后连续的,所有已经落实(到物理页面)的页面,用 rd 命令打印出来,然后看看有没有什么奇怪的字符串可以用来作为 signature 定位问题。

就这样,我们在邻近地址发现了下边这些字符串,如图 12 所示。很明显这些字符串应该是函数名。我们可以看到 hack_open 和 hack_read 这两个函数,对应被 hacked 的 0 和 2 号系统调用。还有函数像 disable_write_protection 等等。这些函数名,显然说明这是一段“不平凡”的代码。

image.png

图12

后记

宕机问题的内存转储分析,需要我们足够的耐心。我个人的一条经验是:every bit matters,就是不要放过任何一个 bit 的信息。内存转储因为机制本身的原因,和生成过程中一些随机的因素,必然会有数据不一致的情况,所以很多时候,一个小的结论,需要从不同的角度去验证。

目录
相关文章
|
1月前
|
存储 监控 安全
服务器死机,数据丢失怎么办?
【10月更文挑战第27天】当服务器死机且数据丢失时,应先尝试重启服务器并检查硬件问题。随后,利用备份数据、数据恢复软件或专业服务恢复数据。为预防未来数据丢失,需定期备份数据,使用热备份和RAID技术,定期维护服务器,强化安全性,并建立监控和日志记录机制。
107 8
|
7月前
|
SQL 监控 安全
如何发现服务器被入侵了,服务器被入侵了该如何处理?
如何发现服务器被入侵了,服务器被入侵了该如何处理?
|
监控 安全 数据安全/隐私保护
服务器数据恢复—如何预防服务器故障?发生故障后如何恢复服务器数据?
服务器常见故障: 硬件故障:磁盘、板卡、电源故障等。 软件故障:操作系统崩溃、程序运行错误等。 入侵破坏:加密、删除服务数据等。 不可控力:浸水、火烧、倒塌等。 误操作:格式化、删除、覆盖等。
|
安全 JavaScript
怎么防止网站不被挂木马篡改
因为说这些脚本文件它都是一些静态的文件连接,那么既然是静态的,那就是说我们存放这个脚本的这个服务器,它只能放静态文件,不支持任何的脚本语言,那它也就不可能存在任何的后门,也不可能被篡改挂木马,上传脚本后门文件等等这些问题,所以说只要替换成静态的连接文件,那么说你网站从此以后没人能修改这些静态的脚本文件,他如果想给网站挂木马的话,那只有其他的办法,那就是说给你网页插入一个新的脚本文件,那么他给你插一个新的脚本文件的话,我想普通的站长一眼就能看到,所以不需要到处去找,直接就可以把它移除掉,或者是我们可以配合一些网站的检测一些程序,检测你网站是否存在未知的文件,配合这样一些东西去方便快速的锁定,这个
276 0
怎么防止网站不被挂木马篡改
|
安全
黑客与宕机
宕机问题有一种比较少见的pattern,就是看起来完全不相关的机器同时出现宕机。处理这个pattern的问题,我们需要找到,在这些机器上能同时触发问题的条件。 通常,这些机器要么几乎在同一时间点出现问题,要么从某一个时间点开始,相继出现问题。
3294 0
|
安全
网站被主机攻击断网 871
网络攻击871是怎么回事?
2809 0
|
安全
警惕“搜房网”“凯业房园”等网站被挂马
据瑞星“云安全”系统统计, 上周末期间,共有5,556,431人次的网民遭到网页挂马攻击,瑞星共截获了748,911个挂马网址。 周末最流行木马病毒是 Worm.Win32.Autorun.gbn(U盘蠕虫下载器)“云安全”系统共收到100,754次用户上报。
1815 0
|
安全
愚人节黑客以身试法 人民法院被挂马
金山毒霸云安全中心监测,4月1日共截获国内挂马网站1619个。其中政府网站受害居多。 金山毒霸云安全中心监测到成都市武侯区人民法院被挂马,当日被挂马的其他重点网站: 中共青岛市委宣传部宣网 http://www.
1326 0
|
监控
当造血干细胞遭遇青春期
科学家们对出生后骨髓造血干细胞的数量进行了严密的“监控”发现,出生后,骨髓内的造血干细胞通过细胞分裂的方式进入高速扩增。但是,当青春期开始了以后,这一发育进程突然就驶入了“刹车制动”阶段,造血干细胞的数量开始逐步达到稳定。
1273 0
下一篇
DataWorks