ASM.js / ASM.JS-targeted JIT Spray¶
Firefox'un asm.js JIT'ini istismar ederek attacker'ın seçtiği integer constant'ları executable page'lere aynen emit et; o constant'ların ortasına jump etmek gizli instruction byte'larını execute eder ve ASLR ile DEP'i bypass eder (CVE-2017-5400).
Mechanism¶
Neden çalışır
DEP, data page'lerini non-executable yapar; ASLR, code'un nerede olduğunu gizler. Bir JIT, ikisini de attacker için yener çünkü runtime'da yeni executable code yazar — ve attacker-controlled constant'ları doğrudan o code'un içine gömer.
Firefox'un asm.js JIT'inde, VAL = (VAL + 0xA8909090)|0; ifadesi
ADD EAX, 0xA8909090'a compile olur — 0xA8909090 constant'ı executable bir JIT
page'ine aynen emit edilir. x86'da instruction alignment yoktur, dolayısıyla o
instruction'ın içine bir byte jump etmek constant'ın byte'larını yeni
instruction'lar olarak yeniden yorumlar: 0x90 0x90 0x90, üç NOP olarak decode
olur ve 0xA8, sonraki byte'ı tüketerek TEST AL, imm8 olur. Bu tür birçok
constant'ın içine gadget byte'ları encode ederek ve asm.js modülünü binlerce kez
(~0x1000 region) instantiate ederek, attacker bir NOP-sled-artı-payload'ın
kopyalarını geniş, öngörülebilir bir adres aralığına spray eder.
Windows x86'da VirtualAlloc(), 64 KB-aligned page'ler (0xXXXX0000) dağıtır,
dolayısıyla 0x1c1c0053 gibi sabit bir landing address, spray edilen region'a
güvenilir biçimde isabet eder. Hem DEP'in hem de ASLR'ın dayandığı invariant —
attacker, bilinen executable code'u bilinen bir adrese yerleştiremez — yanlıştır,
çünkü JIT bunu talep üzerine tam olarak yapar. Hiçbir info leak ve hiçbir ROP chain
gerekmez.
Walkthrough¶
1. Instruction gizleyen constant'lar emit et. Crafted bir constant'ın her asm.js add'i payload byte'ları diker:
function spray(stdlib, ffi, heap) {
"use asm";
function g() {
var x = 0;
x = (x + 0xA8909090)|0; // -> ADD EAX, 0xA8909090 (bytes: 05 90 90 90 A8)
x = (x + 0xA8909090)|0; // chain many -> sled of NOPs at off+1
// ... continue with constants encoding the real shellcode ...
return x|0;
}
return g;
}
2. Birçok JIT region'ı spray et. Modülü tekrar tekrar instantiate et ki executable kopyalar öngörülebilir bir aralığı kaplasın:
var mods = [];
for (var i = 0; i < 0x1000; i++)
mods.push(spray(self, null, new ArrayBuffer(0x10000)).g);
3. Control'ü sled'in içine yönlendir. Bir memory-corruption primitive ile, EIP'yi 64 KB-aligned spray'in içinde sabit bir mid-instruction offset'ine set et:
Beklenen sonuç: execution, gömülü NOP'ların içinden kayarak constant-encoded shellcode'a girer — leak ya da ROP olmadan DEP ve ASLR bypass edilir.
Constant blinding ile mitige edilir
Düzeltme sınıfı constant blinding'tir (büyük immediate'ları rastgele bir cookie ile XOR'la ki emit edilen byte'lar öngörülemez olsun) artı randomized/guarded JIT page placement ve W^X JIT (önce yaz, sonra execute'a flip et). CVE-2017-5400, Firefox 52 / ESR 45.8'de düzeltildi.
Detection¶
Büyük-constant yüklü birçok executable page üreten bir dizi JIT compilation'ı ve bir JIT page'ine non-entry bir offset'te giren execution anormaldir — ama teknik, sıradan asm.js numeric code gibi görünmek üzere tasarlanmıştır.
Mitigation¶
(Onu durduran şey.) Constant blinding (gadget byte'larını immediate'lara gömmeyi yener), W^X / write-xor-execute JIT page'leri, randomized JIT region base'i ve NX-by-default. Ayrıca JIT spray ve constant-embedded gadget JIT spray'e bak.
References¶
- Mozilla. Bug 1334933 (CVE-2017-5400): targeted ASM.JS JIT-Spray allows bypassing ASLR and DEP. — https://bugzilla.mozilla.org/show_bug.cgi?id=1334933
- rh0dev. The Return of the JIT. — https://rh0dev.github.io/blog/2017/the-return-of-the-jit/