-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathCVE-2009-3608-explained.txt
86 lines (71 loc) · 4.73 KB
/
CVE-2009-3608-explained.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
(Some details from this writeup taken from the Matasano blog http://chargen.matasano.com)
Poppler contains an integer overflow vulnerability in version 0.11.0 that allows fewer class instances to be allocated then expected.
This results in memory corruption when a constructor for each requested class instance is called.
In poppler/XRef.cc on line 132 in the function ObjectStream::ObjectStream() the variable nObjects is an attacker controlled signed
integer pulled directly from a PDF document.
[132] if (nObjects >= INT_MAX / (int)sizeof(int)) {
[133] error(-1, "Invalid 'nObjects'");
[134] goto err1;
[135] }
[136]
[137] objs = new Object[nObjects];
On line 137 we see an array of Object classes being allocated using this attacker controlled value. If we look back up on line 132
there was an attempt to detect too large of a value, and bail accordingly. The size of our class is 12 bytes (its definition is in
poppler/Object.h). This means we can supply a value that will wrap around and cause only a few classes to be allocated. The value
we need to supply should be less than (INT_MAX / (int)sizeof(int)) and large enough to overflow when we reach the new operator.
357913943 should do the trick and will cause new to allocate only 8 class instances. This can be easily triggered by a PDF with
a compressed stream:
...
/Type/ObjStm/N 357913943/First 2/Filter/FlateDecode/Length 1
...
Our class has a constructor, and that constructor is shown below:
...
class Object {
public:
// clear the anonymous union as best we can -- clear at least a pointer
void zeroUnion() { this->name = NULL; }
// Default constructor.
Object():
type(objNone) { zeroUnion(); }
...
Like most constructors it initializes some member variables of our class instance(s). It first sets the object type to objNone (0xd)
and then it calls a small function, zeroUnion(), which initializes a union member within the class to NULL. But heres the tricky
part, our Object constructor is called for all 357913943 class instances, not the 8 we tricked it into allocating. You wouldn't know
this from looking at source. This is one of those times were binary analysis is needed, regardless of whether you have the code or
not. And of course this is going to vary between different compilers. This little detail presents a major problem for our chances of
successful exploitation. The entire constructor is small enough to be inlined by our compiler. Lets disassemble and reverse the
relevant vulnerable code. This disassembly is from the ObjectStream::ObjectStream() function and occurs just after the call to the
new operator:
...
ee067: call 50904 <new> ; call the new operator to allocate our class instances
ee06c: test %esi,%esi ; %esi holds the # of Objects requested, check if its 0
ee06e: je ee08f ; if we requested 0 objects theres no need to call any constructors, jump ahead
ee070: mov %eax,%edx ; get the return value of new, our first class instance and move to %edx
ee072: xor %ecx,%ecx ; clear %ecx
ee074: lea 0x0(%esi,%eiz,1),%esi
ee078: add $0x1,%ecx ; increment %ecx by 1
ee07b: movl $0xd,(%edx) ; set this class instances object type to objNone 0xd
; (%edx points into a class instance on the heap)
ee081: movl $0x0,0x4(%edx) ; move our 0x0 (NULL) value 4 bytes from %edx (this->name)
; (%edx points at a class instance on the heap)
ee088: add $0xc,%edx ; add 12 to our class instance pointer (12 bytes is the size of our Object class)
ee08b: cmp %ecx,%esi ; compare %ecx (number of class instances we initialized)
; and %esi (the number we need to call a constructor for)
ee08d: jne ee078 ; if we aren't done yet, jump back and initialize the next class instance
...
And our registers from the crash at runtime (tested using evince on Ubuntu, which uses libpoppler):
eax 0x914cfe8 152358888
ecx 0x2c03 11267 ; number of constructors we called
edx 0x916e000 152494080 ; location of current class instance we are calling a constructor for (top of heap)
ebx 0xb719bff4 -1223049228
esp 0xb67a5ed0 0xb67a5ed0
ebp 0xb67a5f68 0xb67a5f68
esi 0x15555556 357913942 ; number of class instances we requested from new
edi 0x914cc88 152358024
eip 0xb70e88c3 0xb70e88c3
The location of the heap in our evince process:
$ cat /proc/`pidof evince`/maps | grep heap
08edf000-0916e000 rw-p 08edf000 00:00 0 [heap]
The disassembly comments above are pretty self explanitory. As you can see we call 11267 constructors before we crash. Our constructor
was called 11267 times basically just ripping through the heap writing 0xd and NULL bytes at every 12th dword.
chris . rohlf at gmail.com (2009)