用一个奇怪的技巧猜测整个x86-64指令以秒为单位

2021-03-26 20:06:49

Home x86内部结构猜测整个x86-64指令在几秒钟内使用这个奇怪的技巧

作为俗气的标题声音,我保证无法击败技术的俗气,我会在这篇文章中告诉你。早上我看到Mark Ermolov的推文关于无证指导从/写入CRBUS,我手里有一点空闲时间,我知道我不得不找出OPCode,所以我立即开始理论制作。经过几个小时的数字盯着数字后,我最终提出了一种使用侧面(?) - 频道在处理器中发现每个指令的方法。这是一个有趣的方法,涉及处理器的更有趣的组件,所以我认为我也可以写它,所以它会出现。

这些天,现代处理器采用疯狂的微体系结构复杂性建立,并且由于人们希望良好的旧指令解码器不再直接解码执行单元。它们最终根据处理器的微码将它们解码为微型指令,以派遣到处理器的执行端口。有两个单位在现代英特尔处理器中执行此转换:

微指令转换发动机(螨虫)。负责翻译到四个或多种微指令的简单遗留指令的单位。

微码定序器(MS)。该单位负责翻译更复杂的指令,这些指令推动英特尔架构的CISC疯狂我们所有人都保持良好。

调度这些微指令的另一个单元是解码的流缓冲区(DSB)更常见的是ICACHE,但它与我们要做的实验并不相关。现在,为什么我告诉你这一切?主要是因为我们可以通过优雅地提供英特尔的性能计数器来简化这些极低的单位;主要是这两个坏男孩:

利用这些事件与桑过期的方法相比利用这些事件的优点是,即使指令的微代码抛出#ud(例如,如果密码不匹配或不符合条件),它不能欺骗我们'D仍然必须被解码。

现在问题在那里得到这些指示。有一百万种方法可以通过执行随机指令在脚下射击。如果我们触及改变中断状态的指令,会导致系统重置,并使用内部缓存或我们使用的非常性能计数器,我们正在执行的堆栈,探查器的代码,代码段的堆栈我们正在执行......

简单的解决方案是简单地,至少在我们的可见宇宙中执行,这将我们带来了我们的第二酷微架构细节:推测和秩序的执行。评估分支处的分支条件的处理器显然已经脱离了时尚,所以当你做一个分支的时候,真的发生了什么,首先,分支预测引擎试图猜测你将要降低逆转的成本来降低逆转的成本在指令管道中,给定两个分支的可能性,在可能的情况下,在可能的情况下,没有猜测围栏,其中一个恢复了他们的净变化......这几乎是我们想要的不是它吗?现在虽然探测分支重置分支预测状态,但我之前尝试过它,它有点困扰,所以更简单的方式是简单地拨打电话。考虑这段赛段:

如果可能,这将导致执行推测代码和调用的子程序超出订单。与堆栈的更简单的解决方案相比,Xchg似乎有点矫枉过正,但就我的实验而言,如果例程是非返回,则处理器太智能以拆分执行,因此我们需要馈送分支目标缓冲区想要。我在这里使用Xchg而不是一个mov,因为它意味着锁定导致处理器在给定的情况下,它必须结束它必须结束可见的可见光,所以我至少可以理解。

我们还需要删除我之前提到的解码流缓冲区,以便我们没有得到从缓存中喂养的任何微指令,但是解决了一个非常简单的解决方案。指令高速缓存还必须处理自我重写码,因此执行内存对任何内存写入非常敏感,因为在每次测量之前添加下面的简单片段,请在处理此问题之前添加此信息。

自动IP = IA32 :: GET_IP()& 〜63ull; std :: copy_n((volatile uint8_t *)IP,512,(volatile uint8_t *)IP); Ia32 :: sfence(); for(size_t n = 0; n!= 512; n + = 64)Ia32 :: Clflush(IP + N);

最后,我们已经击中了实验的令人兴奋的部分,试验。我们希望精确的结果意味着不中断。您可能会被欺骗思考在内核模式中,并做CLI解决了这个问题,但这并不是真的在现实中起作用。担心我的第一件事是一个#smi正在交付,虽然我一直听到它冻结了PMC,但就我试验而真的没有做得很好,但即使它仍然是我们的杂质必须消除所以我会重复实验,直到强大的IA32_MSR_SMI_Count在执行期间保持不变。 #nmi是另一个bugger,所以设置中断处理程序,使其发出信号,以解决这个问题(因为我太懒了,无法放松)。 #MC也将在此类别中考虑,但此时也可能让它全部燃烧。

多次重复实验并挑选mod(x),并以严格的方式写入代码,消除我们离开的其他所有问题。下一步只是写入代码并实际收集数据。推测代码将是NOP的15个副本和最后一个0xce,以导致实际的#ud并停止推测执行。我们将在0x00-0xff的范围内尝试每个操作码,并且它们将可选地占用0x0f的前缀,并且选择{0x66,0xf2,0xF3}中的另一个前缀,因为英特尔喜欢使用它们来创建新的操作码薄空气(例如最近的FRED指令ERETS与其F2 0F 01 CA编码)。我们还需要添加一个用于发现MODR / M变体的后缀。此过程仅在几秒钟内完成,这将标题为此帖子。

首先,我们将获得两个基线测量,Nops留下的NOP,以及0xce作为第一个操作码,它将显示完整执行和for-Real-#UD案例的计数器值(我测试了其他Opcodes,0xce真的不是NSA后门,这是#ud的Go。)。)。

只需删除符合for-real- #ud案例测量的所有测量都会摆脱大多数垃圾,现在我们需要摆脱冗余前缀,看看下面的数据,你可以看到一个模式emerge:

┌─────────┬──────────────────────────────────┬──── ───────(指数)│解码│mits│ms│├────────────────────────── ───────────────────── - = 90│' nop' │54│80│/ *基线* /│6690│' data16 nop' │53│67││f290│' nop' │53│80││f20f90│' seto字节ptr [rax-0x6f6f6f70]' │48│80│└─────────┴────────────────────────────────── ┴───────────

每个NOP都转换为单一的微指令,将由MITS处理,这意味着如果前缀是冗余的,则应始终关闭一个并且MS应该保持不变,另外,我们可以通过声明滤除一些冗余的检查0x0f从不冗余。组合我们以简单的方式摆脱大多数冗余,甚至可能能够计算指令长度,整洁!下面的代码消除了54954个条目。

const propertymate =(i1,i2)=> {return i2.ms == i1.ms&& i2.outoforder == i1.outoforder&& i2.iclass == i1.iclass;}; //清除冗余前缀.// for(const of object.keys.keys(指令)){//跳过如果已经删除了。 // const i1 =指令[k1];如果(!i1){继续; } //迭代每个前缀(除了0f):// for for for(for prefixlist的const pfx){//如果存在指令:// const k2 = pfx + k1; if(k2指示){//如果指令具有与父级派生的匹配属性,则删除该条目。 // const i2 =指令[k2]; if(propertyMatch(i1,i2)){// mits#1 == mits#2如果指令停止,可以指示相同的指令。 //否则,Mits#1必须是一个比mits#2更多,因为它应该再执行一个NOP。 // if(i1.mits!= i2.mits){if(i1.mits!= i2.mits + 1){继续; }}否则if(i1.mits> affactbaseline.mits){继续;删除指令[k2]; }}}}}

基于后缀的净化也是或多或少相同的逻辑,摆脱了72869个指令,让我们有1699个条目,这足以开始分析!

//清除冗余后缀.//for(object.key.keys.keys.keys(指令)){//跳过(如果已删除或不相关)。 // const i1 =指令[k1]; if(!i1 || k1.length< = 2){继续; } //查找maching条目:// for(object.key.keys的const k2){//如果它是匹配的,则除了最后一个字节:// if(k2.startswith(k1.substr(0,k1.length - 2))&&& k2!= k1){//如果它具有匹配的属性忽略长度,请删除它// const i2 =指令[k2]; if(propertyMatch(i1,i2)){删除指令[k2]; }}}}}

Warning: Can only detect less than 5000 characters

......