Skip to content

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:

EIP = 0x1c1c0053   ; lands at off+1 of a sprayed ADD -> NOP sled -> payload

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