百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

可怕!CPU竟然暗藏了这么多未公开的指令

liebian365 2024-10-18 09:33 28 浏览 0 评论

我们知道,我们平时编程写的高级语言,是经过编译器编译以后,变成了CPU可以执行的机器指令:

而CPU能支持的指令,都在它的指令集里面了。

很久以来,我都在思考一个问题:

CPU有没有未公开的指令?

或者说:

CPU有没有隐藏的指令?

为什么会有这个问题?

平常我们谈论网络安全问题的时候,大多数时候都是在软件层面。谈应用程序的漏洞、后端服务的漏洞、第三方开源组件的漏洞乃至操作系统的漏洞。

但很少有机会去触及硬件,前几年爆发的熔断幽灵系列漏洞,就告诉我们,CPU也不是可信任的。

要是CPU隐藏有某些不为人知的指令,这是一件非常可怕的事情。

如果某一天,某些国家或者某些团体组织出于某种需要,利用这些隐藏的指令来发动攻击,后果不堪设想。

虽然想到过这个问题,但我一直没有付诸实践去认真的研究。

直到前段时间,极客时间的一位老师分享了一份PDF给我,解答了我的疑惑。

这份PDF内容是2017年顶级黑客大会Black Hat上的一篇报告:《us-17-Domas-Breaking-The-x86-ISA》,作者是大神:@xoreaxeaxeax,熟悉汇编的同学知道这名字是什么意思吗?

这份PDF深度研究了x86架构CPU中隐藏的指令,原报告因为是英文,看起来有些晦涩,这篇文章,我尝试用大家易懂的语言来给大家分享一下这篇非常有意思的干货。

有些人会问:真的会有隐藏指令的存在吗,CPU的指令集不是都写在指令手册里了吗?

我们以单字节指令为例,单字节的范围是0x00-0XFF,总共256种组合,Intel的指令手册中是这样介绍单字节指令的:

横向为单字节的高四位,纵向为单字节的低四位,顺着表格定位,可以找到每一个单字节指令的定义。比如我们常见的nop指令的机器码是0x90,就是行为9,列为0的那一格。

但是不知道你发现没有,这张表格中还有些单元格是空的,比如0xF1,那CPU拿到一个为0xF1的指令,会怎么执行呢?

指令手册没告诉你。

这篇报告的主要内容就是告诉你,如何去寻找这些隐藏的指令。

指令集的搜索空间

想要找到隐藏的指令,得先明确一个问题:一条指令到底有多长,换句话说,有几个字节,我们应该在什么样的一个范围内去寻找隐藏指令。

如果指令长度是固定的,比如JVM那样的虚拟机,那问题好办,直接遍历就行了。

但问题难就难在,x86架构CPU的指令集属于复杂指令集CISC,它的指令不是固定长度的。

有单字节指令,比如:

90 nop

CC int 3

C3 ret

也有双字节指令,比如:

8B C8 mov ecx,eax

6A 20 push 20h

还有三四节、四字节、五字节···最长能有十几个字节,比如这条指令:

指令:lock add qword cs:[eax + 4 * eax + 07e06df23h], 0efcdab89h

机器码:2e 67 f0 48 818480 23df067e 89abcdef

一个字节、两个字节,甚至三个四个遍历都还能接受,4个字节最多也就42亿多种组合,对于计算机来说,也还能接受。

但越往后,容量是呈指数型增长,这种情况再去遍历,显然是不现实的。

指令搜索算法

这份报告中提出了一种深度优先的搜索算法:

该算法的指导思想在于:快速跳过指令中无关紧要的字节

怎么理解这句话?

比如压栈的指令push,下面几条虽然字节序列不同,但变化的只是数据,其实都是压栈指令,对于这类指令,就没必要花费时间去遍历:

68 6F 72 6C 64 push 646C726Fh

68 6F 2C 20 77 push 77202C6Fh

68 68 65 6C 6C push 6C6C6568h

第一个字节68就是关键字节,后面的四个字节都是压入栈中的数据,就属于无关紧要的字节。

如果能识别出这类,快速跳过,将能够大面积减少需要遍历的搜索空间。

(PS:本文来自公众号:编程技术宇宙)

上面只是一个例子,如何能够系统化的过滤掉这类指令呢?报告中提出了一个方案:

观察指令中的有意义的字节,它们对指令的长度和异常表现会产生冲击。

又该怎么理解这句话?

还是上面那个例子,当尝试修改第一个字节68的时候,这一段二进制序列可能就完全变成了别的指令,甚至指令长度都会发生变化(比如把68改成90,那就变成了一个字节的nop指令),那么就认为这第一个字节是一个有意义的字节,修改了它会对指令的长度产生重要影响。

反之,如果修改后面字节的数据,会发现这仍然是一条5个字节的压栈指令,长度没变化,也没有其他异常行为表现与之前不同,那么就认为后面几个字节是无关紧要的字节。

在这个指导思想下,我们来看一个例子:

从下面这一段数据开始出发:

我们从两个字节的指令开始遍历:

把最后那个字节的内容+1,尝试去执行它:

发现指令长度没有变化(具体怎么判断指令长度变没变,下一节会重点讨论),那就继续+1,再次尝试执行它:

一直这样加下去,直到发现加到4的时候,指令长度发生了变化,长度超过了2(但具体是多少还不知道,后文会解释):

那么在这个基础上,长度增加1位,以指令长度为3的指令来继续上面的探索过程:从最后一位开始+1做起。

随着分析的深入,梳理一下指令搜索的路径图:

当某一条的最后一个字节遍历至FF时,开始往回走(就像递归,不能一直往下,总有回去的时候):

往回走一个字节,将其+1,继续再来:

按照这个思路,整个要搜索的指令空间压缩到可以接受遍历的程度:

如何判定指令长度

现在来解答前面遗留的一个问题。

上面这个算法能够工作的一个重要前提是:

我们得知道,给末尾字节+1后,有没有影响指令的长度。

要判断某个字节是不是关键字节,就得知道这个字节的内容变化,会不会影响到指令长度,所以如果无法判断长度有没有变化,那上面的算法就无从谈起了。

所以如何知道长度有没有变化呢?报告中用到了一个非常巧妙的方法。

假设我们要评估下面这一串数据,前面开头到底多少个字节是一条完整指令。

可能第一个字节0F就是一条指令。

也可能前面两个字节0F 6A是一条指令。

还可能前面五个字节0F 6A 60 6A 79 6D是一条指令。

到底是什么情况,我们不知道,让我们用程序来尝试推导出来。

准备两个连续的内存页面,前面一个拥有可执行的权限,后面一个不能执行。

记住:当CPU发现指令位于不可执行的页面中时,它会抛异常!

现在,在内存中这样放置上面的数据流:第一个字节放在第一个页面的末尾位置,后面在字节放在第二个不可执行的页面上。

然后JMP到这条指令的地址,尝试去执行它,CPU中的译码器开始译码:

译码器译码发现是0F,不是单字节指令,还需要继续分析后面的字节,继续取第二个字节:

但注意,第二个字节是位于不可执行的页面,CPU检查发现后会抛出页错误异常:

如果我们发现CPU抛了异常,并且异常的地址指向了第二个页面的地址,那么我们可以断定:这条指令的长度肯定不止一个字节。

既然不止一个字节,那就往前挪一下,放两个字节在可执行页面,从第三个字节开始放在不可执行页面,继续这个过程。

继续上面这个过程,放三个字节在可执行页面:

四个:

当放了四个字节在可执行页面之后,事情发生了变化:

指令可以执行了!虽然也抛了异常(因为天知道这是个什么指令,会抛什么异常),但页错误的地址不再是第二个页面的地址了!

有了这个信号,我们就知道,前面4个字节是一条完整的指令:

挖掘隐藏指令

现在核心算法和判断指令长度的方法都介绍完了,可以正式来开挖,挖出那些隐藏的指令了!

以一台Intel Core i7的CPU为目标,来挖一挖:

挖掘成果,收获颇丰:

这些都是Intel指令集手册中未交待,但CPU却能执行的指令。

然后是AMD Athon的CPU:

挖掘成果:

那这些隐藏的指令是做什么的呢?

有些已经被逆向工程分析了。

还有的就是毫无记录,只有Intel/AMD自己人知道了,谁知道它们用这些指令是来干嘛的?

软件即便是开源都能爆出各种各样的问题,何况是黑盒一样的硬件。

CPU作为计算机中的基石,它要是出了问题,那可是大问题。

我不是阴谋论,害人之心不可有,但防人之心不可无。

看完这些,我对国产、安全、自主可控这几个字的理解又加深了一层。

各位朋友,你对这些隐藏指令怎么看?欢迎评论区分享你的观点。


原作者:公众号—— 轩辕之风

相关推荐

go语言也可以做gui,go-fltk让你做出c++级别的桌面应用

大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...

旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗

这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...

codeblocks和VS2019下的fltk使用中文

在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...

FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库

FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...

中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux

IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...

Linux 5.13内核有望合并对苹果M1处理器支持的初步代码

预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...

Ubuntu系统下COM口测试教程(ubuntu port)

1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...

湖北嵌入式软件工程师培训怎么选,让自己脱颖而出

很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...

新阁上位机开发---10年工程师的Modbus总结

前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...

创建你的第一个可运行的嵌入式Linux系统-5

@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...

如何在Linux下给zigbee CC2530实现上位机

0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...

Python实现串口助手 - 03串口功能实现

 串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...

为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口

UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...

同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理

串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...

嵌入式linux为什么可以通过PC上的串口去执行命令?

1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...

取消回复欢迎 发表评论: