[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: p0dalirius\npatreon: Podalirius"
  },
  {
    "path": "DumpSMBShare.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# File name          : DumpSMBShare.py\n# Author             : Podalirius (@podalirius_)\n# Date created       : 6 Jul 2022\n\nimport argparse\nimport os\nimport sys\nimport traceback\nfrom impacket import version\nfrom impacket.examples import logger, utils\nfrom impacket.smbconnection import SMBConnection, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB_DIALECT, SessionError\n\n\nclass BFSDumpShare(object):\n    \"\"\"docstring for BFSDumpShare.\"\"\"\n\n    def __init__(self, smb, share, base_dir=\"\", dump_dir=\".\", quiet=False, debug=False, only_list_files=False):\n        super(BFSDumpShare, self).__init__()\n        self.quiet = quiet\n        self.debug = debug\n\n        self.smb = smb\n        self.share = share\n        self.dump_dir = dump_dir\n        self.base_dir = base_dir\n        self.only_list_files = only_list_files\n        if not os.path.exists(self.dump_dir):\n            os.makedirs(self.dump_dir, exist_ok=True)\n\n    def list_shares(self):\n        print(\"[>] Listing shares ...\")\n        resp = self.smb.listShares()\n        shares = []\n        for k in range(len(resp)):\n            shares.append(resp[k][\"shi1_netname\"][:-1])\n        return shares\n\n    def dump_share(self, targetfile=\"\", extensions=[], base_dir=None):\n        if base_dir is not None:\n            self.base_dir = base_dir\n        print(\"[>] Dumping files with extensions %s ... \" % extensions)\n        # Breadth-first search algorithm to recursively find .extension files\n        files = []\n        searchdirs = [self.base_dir + \"/\"]\n        while len(searchdirs) != 0:\n            next_dirs = []\n            for sdir in searchdirs:\n                if self.debug:\n                    print(\"[>] Searching in %s \" % sdir)\n                try:\n                    for sharedfile in self.smb.listPath(self.share, sdir + \"*\", password=None):\n                        if sharedfile.get_longname() not in [\".\", \"..\"]:\n                            if sharedfile.is_directory():\n                                if self.debug:\n                                    print(\"[>] Found directory %s/\" % sharedfile.get_longname())\n                                next_dirs.append(sdir + sharedfile.get_longname() + \"/\")\n                            else:\n                                if len(extensions) == 0 or any([sharedfile.get_longname().endswith(\".\" + e) for e in extensions]) or sharedfile.get_longname() == targetfile:\n                                    if self.debug or not self.quiet or self.only_list_files:\n                                        print(\"[>] Found matching file %s\" % (sdir + sharedfile.get_longname()))\n                                    full_path = sdir + sharedfile.get_longname()\n                                    files.append(full_path)\n                                    if not self.only_list_files:\n                                        self.dump_file(full_path)\n                                else:\n                                    if self.debug:\n                                        print(\"[>] Found file %s\" % sharedfile.get_longname())\n                except SessionError as e:\n                    if self.debug:\n                        print(\"[error] %s \" % e)\n            searchdirs = next_dirs\n            if self.debug:\n                print(\"[>] Next iteration with %d folders.\" % len(next_dirs))\n        return files\n\n    def dump_file(self, filepath, only_file=False):\n        # Sanitize dir\n        filepath = filepath.replace(\"\\\\\", \"/\")\n\n        _dir, _file = os.path.dirname(filepath), os.path.basename(filepath)\n        if _dir.startswith(\"//\"):\n            _dir = _dir[2:]\n        try:\n            if only_file:\n                if not os.path.exists(self.dump_dir):\n                    os.makedirs(self.dump_dir, exist_ok=True)\n                path = self.dump_dir + \"/\" + _file\n            else:\n                # Create directory\n                if _dir.startswith(self.base_dir.rstrip('/')):\n                    _dir = _dir[len(self.base_dir.rstrip('/')):].lstrip('/')\n                if not os.path.exists(self.dump_dir + \"/\" + _dir + \"/\"):\n                    os.makedirs(self.dump_dir + \"/\" + _dir + \"/\", exist_ok=True)\n                path = self.dump_dir + \"/\" + _dir + \"/\" + _file\n            # Write file\n            f = open(path, \"wb\")\n            self.smb.getFile(self.share, filepath, f.write)\n            f.close()\n            return True\n        except SessionError as e:\n            if self.debug:\n                print(\"[error] %s\" % e)\n            return False\n        except Exception as e:\n            raise\n\n\ndef parse_args():\n    print(\"DumpSMBShare v1.3 - by Remi GASCOU (Podalirius)\\n\")\n\n    parser = argparse.ArgumentParser(add_help=True, description=\"A script to dump files and folders remotely from a Windows SMB share.\")\n\n    parser.add_argument(\"target\", action=\"store\", help=\"[[domain/]username[:password]@]<targetName or address>\")\n\n    group = parser.add_mutually_exclusive_group(required=True)\n    group.add_argument(\"-s\", \"--share\", type=str, default=None, help=\"SMB Share to dump\")\n    group.add_argument(\"-l\", \"--list-shares\", default=False, action=\"store_true\", help=\"Lists SMB shares on the remote machine.\")\n\n    parser.add_argument(\"-L\", \"--list-files\", default=False, action=\"store_true\", help=\"Lists all the files present in the SMB share.\")\n\n    parser.add_argument(\"-e\", \"--extensions\", type=str, required=False, default=\"\", help=\"Extensions\")\n    parser.add_argument(\"-D\", \"--dump-dir\", type=str, required=False, default=None, help=\"Dump directory\")\n    parser.add_argument(\"-f\", \"--file\", type=str, default=None, help=\"SMB file to dump\")\n\n    parser.add_argument(\"-B\", \"--base-dir\", type=str, required=False, default=\"\", help=\"Directory to search in (Default: /)\")\n    parser.add_argument(\"--debug\", action=\"store_true\", help=\"Turn on debug output. (Default: False)\")\n    parser.add_argument(\"-q\", \"--quiet\", action=\"store_true\", default=False, help=\"Turn DEBUG output ON\")\n\n    group = parser.add_argument_group(\"authentication\")\n    group.add_argument(\"-H\", \"--hashes\", action=\"store\", metavar=\"LMHASH:NTHASH\", help=\"NTLM hashes, format is LMHASH:NTHASH\")\n    group.add_argument(\"--no-pass\", action=\"store_true\", help=\"Don't ask for password (useful for -k)\")\n    group.add_argument(\"-k\", \"--kerberos\", action=\"store_true\", help=\"Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line\")\n    group.add_argument(\"-A\", \"--aesKey\", action=\"store\", metavar=\"hex key\", help=\"AES key to use for Kerberos Authentication (128 or 256 bits)\")\n\n    group = parser.add_argument_group(\"connection\")\n\n    group.add_argument(\"--dc-ip\", action=\"store\", metavar=\"ip address\", help=\"IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter\")\n    group.add_argument(\"-I\", \"--target-ip\", action=\"store\", metavar=\"ip address\", help=\"IP Address of the target machine. If omitted it will use whatever was specified as target. This is useful when target is the NetBIOS name and you cannot resolve it\")\n    group.add_argument(\"-P\", \"--port\", choices=[\"139\", \"445\"], nargs=\"?\", default=\"445\", metavar=\"destination port\", help=\"Destination port to connect to SMB Server\")\n\n    if len(sys.argv) == 1:\n        parser.print_help()\n        sys.exit(1)\n\n    return parser.parse_args()\n\n\ndef parse_target(args):\n    domain, username, password, address = utils.parse_target(args.target)\n    if args.target_ip is None:\n        args.target_ip = address\n    if domain is None:\n        domain = \"\"\n    if password == \"\" and username != \"\" and args.hashes is None and args.no_pass is False and args.aesKey is None:\n        from getpass import getpass\n        password = getpass(\"Password:\")\n    if args.aesKey is not None:\n        args.k = True\n    if args.hashes is not None:\n        lmhash, nthash = args.hashes.split(\":\")\n    else:\n        lmhash = \"\"\n        nthash = \"\"\n    return domain, username, password, address, lmhash, nthash\n\n\ndef init_smb_session(args, domain, username, password, address, lmhash, nthash):\n    smbClient = SMBConnection(address, args.target_ip, sess_port=int(args.port))\n    dialect = smbClient.getDialect()\n    if dialect == SMB_DIALECT:\n        if args.debug:\n            print(\"[>] SMBv1 dialect used\")\n    elif dialect == SMB2_DIALECT_002:\n        if args.debug:\n            print(\"[>] SMBv2.0 dialect used\")\n    elif dialect == SMB2_DIALECT_21:\n        if args.debug:\n            print(\"[>] SMBv2.1 dialect used\")\n    else:\n        if args.debug:\n            print(\"[>] SMBv3.0 dialect used\")\n    if args.kerberos is True:\n        smbClient.kerberosLogin(username, password, domain, lmhash, nthash, args.aesKey, args.dc_ip)\n    else:\n        smbClient.login(username, password, domain, lmhash, nthash)\n    if smbClient.isGuestSession() > 0:\n        if args.debug:\n            print(\"[>] GUEST Session Granted\")\n    else:\n        if args.debug:\n            print(\"[>] USER Session Granted\")\n    return smbClient\n\n\nif __name__ == \"__main__\":\n    args = parse_args()\n    args.extensions = [e.strip() for e in args.extensions.strip().split(\",\") if len(e.strip()) != 0]\n\n    domain, username, password, address, lmhash, nthash = parse_target(args)\n\n    try:\n        smbClient = init_smb_session(args, domain, username, password, address, lmhash, nthash)\n        if args.list_shares:\n            if args.dump_dir is None:\n                g = BFSDumpShare(smbClient, args.share)\n            else:\n                g = BFSDumpShare(smbClient, args.share, dump_dir=args.dump_dir)\n            shares = g.list_shares()\n            for s in shares:\n                print(\"  - %s\" % s)\n            print()\n        else:\n            if args.dump_dir is None:\n                args.dump_dir = \"./%s/%s/\" % (address, args.share)\n            g = BFSDumpShare(smbClient, args.share, base_dir=args.base_dir, dump_dir=args.dump_dir, quiet=args.quiet, debug=args.debug, only_list_files=args.list_files)\n            if args.share in g.list_shares():\n                if args.file is not None:\n                    print(\"[+] Dumping file '%s' from share '%s'\" % (args.file, args.share))\n                    g.base_dir = os.path.basename(args.base_dir)\n                    g.dump_file(args.file, only_file=True)\n                else:\n                    dumped_files = g.dump_share(extensions=args.extensions)\n                    if not args.list_files:\n                        print(\"[+] Dumped %d files from share '%s'\" % (len(dumped_files), args.share))\n            else:\n                print(\"[>] Cannot find share '%s'\" % args.share)\n    except Exception as e:\n        raise e\n"
  },
  {
    "path": "README.md",
    "content": "![](./.github/banner.png)\n\n<p align=\"center\">\n  A python script to dump files and folders remotely from a Windows SMB share.\n  <br>\n  <img alt=\"GitHub release (latest by date)\" src=\"https://img.shields.io/github/v/release/p0dalirius/DumpSMBShare\">\n  <a href=\"https://twitter.com/intent/follow?screen_name=podalirius_\" title=\"Follow\"><img src=\"https://img.shields.io/twitter/follow/podalirius_?label=Podalirius&style=social\"></a>\n  <a href=\"https://www.youtube.com/c/Podalirius_?sub_confirmation=1\" title=\"Subscribe\"><img alt=\"YouTube Channel Subscribers\" src=\"https://img.shields.io/youtube/channel/subscribers/UCF_x5O7CSfr82AfNVTKOv_A?style=social\"></a>\n  <br>\n</p>\n\n## Features\n\n - [x] Only list shares with `--list-shares`.\n - [x] Select only files with given extensions (with `--extensions`) or all files.\n - [x] Choose the local folder to dump to with `--dump-dir`.\n - [x] Select base folder to search from in the share with `--base-dir`.\n\n## Usage\n\n```\n$ ./DumpSMBShare.py -h\nDumpSMBShare v1.3 - by Remi GASCOU (Podalirius)\n\nusage: Dump.py [-h] (-s SHARE | -l) [-e EXTENSIONS] [-D DUMP_DIR] [-f FILE] [-B BASE_DIR] [--debug] [-q] [-H LMHASH:NTHASH] [--no-pass] [-k] [-A hex key]\n               [--dc-ip ip address] [-I ip address] [-P [destination port]]\n               target\n\npositional arguments:\n  target                [[domain/]username[:password]@]<targetName or address>\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -s SHARE, --share SHARE\n                        SMB Share to dump\n  -l, --list-shares     Lists SMB shares on the remote machine.\n  -L, --list-files     Lists all the files present in the SMB share.\n  -e EXTENSIONS, --extensions EXTENSIONS\n                        Extensions\n  -D DUMP_DIR, --dump-dir DUMP_DIR\n                        Dump directory\n  -f FILE, --file FILE  SMB file to dump\n  -B BASE_DIR, --base-dir BASE_DIR\n                        Directory to search in (Default: /)\n  --debug               Turn on debug output. (Default: False)\n  -q, --quiet           Turn DEBUG output ON\n\nauthentication:\n  -H LMHASH:NTHASH, --hashes LMHASH:NTHASH\n                        NTLM hashes, format is LMHASH:NTHASH\n  --no-pass             Don't ask for password (useful for -k)\n  -k, --kerberos        Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot\n                        be found, it will use the ones specified in the command line\n  -A hex key, --aesKey hex key\n                        AES key to use for Kerberos Authentication (128 or 256 bits)\n\nconnection:\n  --dc-ip ip address    IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter\n  -I ip address, --target-ip ip address\n                        IP Address of the target machine. If omitted it will use whatever was specified as target. This is useful when target is the NetBIOS\n                        name and you cannot resolve it\n  -P [destination port], --port [destination port]\n                        Destination port to connect to SMB Server\n```\n\n## Example\n\n + Dump all files from the `SYSVOL` share:\n\n    ```\n    ./DumpSMBShare.py 'LAB.local/user2:Admin123@192.168.2.1' --debug\n    ```\n\n![](./.github/example_verbose.png)\n\n![](./.github/example.png)\n\n## Contributing\n\nPull requests are welcome. Feel free to open an issue if you want to add other features.\n"
  },
  {
    "path": "requirements.txt",
    "content": "impacket"
  }
]