我做了一个实验,其中一个新线程使用这个简单的无限循环执行shellcode:
NOP
JMP REL8 0xFE (-0x2)
这会生成以下shellcode:
0x90, 0xEB, 0xFE
在此无限循环之后,还有其他指令以将目标字节覆盖回到-0x2结束,使其再次成为无限循环,并且绝对跳转将线程发送回此无限循环。
现在我问自己,当目标的单个字节被仅被其他线程部分覆盖时,是否可能执行了跳转指令。 例如,假设另一个线程将跳转的目标(0xFE或二进制的11111110)覆盖为0x0(00000000)以释放此无限循环的线程。 可能会发生跳转让我们说0x1E(00011110),因为目标字节在那纳秒时没有被完全覆盖? 在这里提出这个问题之前,我已经在C ++程序中完成了自己的实验,并且我已经让它运行了几个小时而没有错过任何一次跳转。 如果您想查看我为此实验制作的代码I have uploaded it to GitHub
根据该实验,似乎不可能在仅部分覆盖的同时执行指令。 但是,我对汇编和处理器知之甚少,因此我在这里问这个问题: 有人可以确认我的观察吗?是否确实不可能在被另一个线程部分覆盖的情况下执行指令?有谁知道为什么肯定?
非常感谢你的帮助和知识,我不知道在哪里寻找这样的信息。
答案 0 :(得分:3)
不,字节存储总是atomic on x86,即使对于交叉修改代码也是如此。
有关交叉修改代码的英特尔手册的一些链接,请参阅Observing stale instruction fetching on x86 with self-modifying code。也许Reproducing Unexpected Behavior w/Cross-Modifying Code on x86-64 CPUs
当然,编写高效交叉修改代码(以及刚刚JIT编译的运行代码)的所有建议都涉及避免将存储放入其他线程当前正在执行的页面中。
为什么你用“shellcode”这样做呢?这应该是漏洞利用的一部分吗?如果没有,为什么不像普通人一样在asm中编写代码,在jmp
指令上添加标签,这样你就可以通过分配给extern char jmp_bytes[2]
来从C存储?
如果这应该是一个有效的跨线程通知机制......它不是。在数据加载和具有pause
循环的条件分支上旋转将允许从循环中退出的延迟低于自我修改的代码机器核,当您希望它最终执行某些有用的操作时,它会刷新整个管道而不是浪费CPU时间。 至少是简单分支遗漏延迟的几倍。
更好的是,使用操作系统支持的条件变量,这样线程就可以睡眠而不是加热你的CPU(当有工作要做的时候,将CPU的热量余量减少到高于其额定时钟速度的涡轮增压)。
当前CPU使用的机制是,如果检测到EIP / RIP附近的存储或管道中的任何飞行指令,它将清除机器。 (perf计数器machine_clears.smc
,又称机器核武器。)它甚至没有尝试“有效”地处理它,但是如果你做了一个非原子商店(例如实际上是两个独立的商店,或者一个商店分裂在缓存行边界上)目标CPU核心可以在不同的部分看到它,并可能用一些更新的字节和其他字节来解码它。但是单个字节总是以原子方式更新,因此不可能在一个字节内撕裂。
然而,x86 on paper doesn't guarantee that,但正如Andy Glew(英特尔P6微体系结构系列的架构师之一)所说,implementing stronger behaviour比纸质规范实际上是满足所有必需保证的最有效方式跑得快(和/或避免破坏广泛使用的软件中的现有代码!)