#!/usr/bin/env python2
#
# Modified version of the CORE's script "samrdump.py" adapted for Inguma
#
# Copyright (c) 2007 Joxean Koret <joxeankoret@yahoo.es>
# Copyright (c) 2003 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# $Id: samrdump.py,v 1.2 2003/11/14 21:26:07 jkohen Exp $
#
# Description: DCE/RPC SAMR dumper.
#
# Author:
#  Javier Kohen <jkohen@coresecurity.com>
#
# Reference for:
#  DCE/RPC.

import socket
import string
import sys
import types

from impacket import uuid
from impacket.dcerpc import dcerpc_v4, dcerpc, transport, samr
from lib.libexploit import CIngumaModule

name = "samrdump"
brief_description = "Dump the SAM database"
type = "gather"

class ListUsersException(Exception):
    pass

class SAMRDump(CIngumaModule):
    KNOWN_PROTOCOLS = {
        '139/SMB': (r'ncacn_np:%s[\pipe\samr]', 139),
        '445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445),
        }

    entries = []
    debug = False
    
    dict = {}

    def __init__(self, protocols = None,
                 username = '', password = ''):
        if not protocols:
            protocols = SAMRDump.KNOWN_PROTOCOLS.keys()

        self.__username = username
        self.__password = password
        self.__protocols = protocols

    def debugPrint(self, *params):
        print params

    def dump(self, addr):
        """Dumps the list of users and shares registered present at
        addr. Addr is a valid host name or IP address.
        """

        encoding = sys.getdefaultencoding()

        print '[+] Retrieving endpoint list from %s' % addr

        # Try all requested protocols until one works.
        entries = []
        for protocol in self.__protocols:
            protodef = SAMRDump.KNOWN_PROTOCOLS[protocol]
            port = protodef[1]

            print "[+] Trying protocol %s..." % protocol
            rpctransport = transport.SMBTransport(addr, port, r'\samr', self.__username, self.__password)

            try:
                entries = self.__fetchList(rpctransport)
            except Exception, e:
                print '[!] Protocol failed: %s' % e
                raise
            else:
                # Got a response. No need for further iterations.
                break

        # Display results.
        for entry in entries:
            (username, uid, user) = entry

            if user.is_enabled():
                self.addToDict(addr + "_users", username.replace("\x00", ""))

            buf = "User %s" % username
            print buf
            print "-"*len(buf)
            print ""
            
            base = "%s (%d)" % (username, uid)
            print base + 'Enabled:' + str( ('false', 'true')[user.is_enabled()] )
            
            try:
            	print base + 'Last Logon:' + str(user.get_logon_time())
            except:
            	print sys.exc_info()[1]
            
            try:
            	print base + 'Last Logoff:' + str(user.get_logoff_time())
            except:
            	print sys.exc_info()[1]
            
            try:
            	print base + 'Kickoff:' + str(user.get_kickoff_time())
            except:
            	print sys.exc_info()[1]
            
            try:
            	print base + 'Last PWD Set:' + str(user.get_pwd_last_set())
            except:
            	print sys.exc_info()[1]
            
            try:
            	print base + 'PWD Can Change:' + str(user.get_pwd_can_change())
            except:
            	print sys.exc_info()[1]
            
            try:
            	print base + 'PWD Must Change:' + str(user.get_pwd_must_change())
            except:
            	print sys.exc_info()[1]
            
            try:
            	print base + 'Group id: %d' % user.get_group_id()
            except:
            	print sys.exc_info()[1]
            	
            try:
            	print base + 'Bad pwd count: %d' % user.get_bad_pwd_count()
            except:
            	print sys.exc_info()[1]
            
            try:
            	print base + 'Logon count: %d' % user.get_logon_count()
            except:
            	print sys.exc_info()[1]
            	
            try:
            	items = user.get_items()
            except:
            	print sys.exc_info()[1]

            for i in samr.MSRPCUserInfo.ITEMS.keys():
                name = items[samr.MSRPCUserInfo.ITEMS[i]].get_name()
                name = name.encode(encoding, 'replace')
                print base + ' ' + i + ':', name

            print

        if entries:
            num = len(entries)
            if 1 == num:
                print 'Received one entry.'
            else:
                print 'Received %d entries.' % num
        else:
            print 'No entries received.'

        self.entries = entries

    def __fetchList(self, rpctransport):
        dce = dcerpc.DCERPC_v5(rpctransport)

        encoding = sys.getdefaultencoding()
        entries = []

        dce.connect()
        dce.bind(samr.MSRPC_UUID_SAMR)
        rpcsamr = samr.DCERPCSamr(dce)

        try:
            resp = rpcsamr.connect()
            if resp.get_return_code() != 0:
                raise ListUsersException, 'Connect error'

            _context_handle = resp.get_context_handle()
            resp = rpcsamr.enumdomains(_context_handle)
            if resp.get_return_code() != 0:
                raise ListUsersException, 'EnumDomain error'

            domains = resp.get_domains().elements()

            print 'Found domain(s):'
            print ''
            for i in range(0, resp.get_entries_num()):
                print " . %s" % domains[i].get_name()

            print ''
            print "Looking up users in domain %s ... " % domains[0].get_name()
            print ''
                            
            resp = rpcsamr.lookupdomain(_context_handle, domains[0])
            if resp.get_return_code() != 0:
                raise ListUsersException, 'LookupDomain error'

            resp = rpcsamr.opendomain(_context_handle, resp.get_domain_sid())
            if resp.get_return_code() != 0:
                raise ListUsersException, 'OpenDomain error'

            domain_context_handle = resp.get_context_handle()
            resp = rpcsamr.enumusers(domain_context_handle)
            if resp.get_return_code() != 0:
                raise ListUsersException, 'OpenDomainUsers error'

            for user in resp.get_users().elements():
                uname = user.get_name().encode(encoding, 'replace')
                uid = user.get_id()

                r = rpcsamr.openuser(domain_context_handle, uid)
                print "Found user: %s, uid = %d" % (uname, uid)

                if r.get_return_code() == 0:
                    info = rpcsamr.queryuserinfo(r.get_context_handle()).get_user_info()
                    entry = (uname, uid, info)
                    entries.append(entry)
                    c = rpcsamr.closerequest(r.get_context_handle())
            print ''
        except ListUsersException, e:
            print "Error listing users: %s" % e

        dce.disconnect()

        return entries

class CSamrDump:

    target = ""
    port = 0
    waitTime = 0
    timeout = 1
    exploitType = 1
    services = {}
    results = {}
    _dumper = None
    interactive = True
    user = ""
    password = ""

    def help(self):
        print "target = <target host or network>"
        print "port = <target port>"
        print
        print "Optional:"
        print "username = <username>"
        print "password = <password>"

    def run(self):
        print "[+] Trying an anonymous connection ... "
        if self.port in (139, 445):
            proto = "%d/SMB" % self.port
            print "[+] Using protocol %s" % proto
            self._dumper = SAMRDump(proto, self.user, self.password)
        else:
            self._dumper = SAMRDump(username=self.user, password=self.password)

        self._dumper.dict = self.dict
        self._dumper.dump(self.target)
        self.dict = self._dumper.dict

        return True

    def printSummary(self):
        pass

# Process command-line arguments.
if __name__ == '__main__':
    if len(sys.argv) <= 1:
        print "Usage: %s [username[:password]@]<address> [protocol list...]" % sys.argv[0]
        print "Available protocols: %s" % SAMRDump.KNOWN_PROTOCOLS.keys()
        print "Username and password are only required for certain transports, eg. SMB."
        sys.exit(1)

    import re

    username, password, address = re.compile('(?:([^@:]*)(?::([^@]*))?@)?(.*)').match(sys.argv[1]).groups('')

    if len(sys.argv) > 2:
        dumper = SAMRDump(sys.argv[2:], username, password)
    else:
        dumper = SAMRDump(username = username, password = password)
    dumper.dump(address)
