├── README.md ├── images └── moki.jpg ├── mirror └── digital-bond-codesys │ ├── README.md │ ├── codesys-shell.py │ ├── codesys-transfer.py │ └── codesys.nse └── setup.sh /README.md: -------------------------------------------------------------------------------- 1 | Moki Linux 2 | ==== 3 | 4 | Moki is a modification of Kali to encorporate various ICS/SCADA Tools scattered around the internet, to create a customized Kali Linux geared towards ICS/SCADA pentesting professionals. 5 | 6 | Some projects have been locally archived under `mirror`, as they are not maintained in a stable location externally. 7 | 8 | Install 9 | ------- 10 | To get and run the core bash script, from a fresh install of Kali, run: 11 | 12 | wget https://goo.gl/Sn7Cwi -O setup.sh 13 | sh setup.sh --help 14 | 15 | 16 | Current Tools 17 | ------------- 18 | 19 | - [Quickdraw] SCADA Snort Rules from Digital Bond 20 | - [CoDeSys exploit] from Digital Bond 21 | - [PLC Scan] from Dmitry Efanov 22 | - [Modscan] from Mark Bristow 23 | - [Siemens S7 metasploit] modules from Dillon Beresford 24 | - [Siemens S7 wireshark dissector] from Thomas Wiens 25 | 26 | 27 | 28 | 29 | 30 | [CoDeSys exploit]: http://www.digitalbond.com/tools/basecamp/3s-codesys/ 31 | [Quickdraw]: https://github.com/digitalbond/quickdraw 32 | [PLC Scan]: http://plcscan.googlecode.com 33 | [Modscan]: https://code.google.com/p/modscan/ 34 | [Siemens S7 metasploit]: https://github.com/moki-ics/s7-metasploit-modules 35 | [Siemens S7 wireshark dissector]: http://sourceforge.net/projects/s7commwireshark/ 36 | -------------------------------------------------------------------------------- /images/moki.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moki-ics/moki/b5bf6e03cb27d7116550ae142321a614f9b47192/images/moki.jpg -------------------------------------------------------------------------------- /mirror/digital-bond-codesys/README.md: -------------------------------------------------------------------------------- 1 | CoDeSys tools (Basecamp) 2 | ---------------------- 3 | This is an archived version of tools originally distributed by [Digital Bond]. 4 | 5 | 6 | [Digital Bond]: http://www.digitalbond.com/tools/basecamp/3s-codesys/ 7 | -------------------------------------------------------------------------------- /mirror/digital-bond-codesys/codesys-shell.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | import socket 4 | 5 | # Imported from Digital Bond's Basecamp Website 6 | # Codesys-transfer.py, Copyright 2012 Digital Bond, Inc 7 | # All praise to be attributed to Dale Peterson 8 | # All bugs and gripes to be attributed to K. Reid Wightman 9 | 10 | 11 | # Takes integer, returns endian-word 12 | def little_word(val): 13 | packed = struct.pack('h', val) 18 | return packed 19 | 20 | def connect(host, port): 21 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 22 | s.connect((host, port)) 23 | s.send("\xbb\xbb\x01\x00\x00\x00\x01") 24 | try: 25 | data = s.recv(1024) 26 | print "Debug: connected!" 27 | except: 28 | print "Debug: connection failed...:(" 29 | exit(1) 30 | return s 31 | 32 | def send_cmd(s, cmd): 33 | wrapcmd = "\x92\x00\x00\x00\x00" + cmd + "\x00" 34 | cmdlen = len(wrapcmd) 35 | data = "\xcc\xcc\x01\x00" + little_word(cmdlen) + ("\x00"*10) + "\x01" + "\x00" * 3 + "\x23" + little_word(cmdlen) + "\x00" + wrapcmd 36 | # data = "\xcc\xcc\x01" + big_word(cmdlen+1+4) + ("\x00"*11) + "\x01" + "\x00" * 3 + "\x23" + little_word(cmdlen) + "\x00" + wrapcmd 37 | 38 | s.send(data) 39 | responsefinished = False 40 | respdata = "" 41 | while responsefinished != True: 42 | try: 43 | receive = s.recv(1024) 44 | if len(receive) < 30: 45 | continue 46 | # If it is 0x04, then we received the last response packet to our request 47 | # Technically we shouldn't add this most recently received data to our payload, 48 | # Since it's equivalent to a dataless FIN 49 | if receive[27] == "\x04": 50 | responsefinished = True 51 | # Note that *sometimes* we have data in a 0x04 response packet! 52 | # continue 53 | # This is a hack, as with recv_file2 in the codesys-file transfer stuff 54 | #respdata += receive[30:] + "\n" 55 | print receive[31:] 56 | #print "Debug: Received response! -> ", receive 57 | # Acknowledge and request more data 58 | # Acknowledgement requires that we say which response packet we received, 59 | # Response packet number is at offset 28...it's really a little word, but I'm treating 60 | # it as a byte foolishly :). 61 | # First part of acknowledge is sidechannel comms using fc 6666 62 | ack1 = "\x66\x66\x01" + "\x00" * 13 + "\x01\x00\x00\x00\x06\x00\x00\x00" 63 | s.send(ack1) 64 | garbage = s.recv(1024) 65 | # the second part of acknowledge says we received the last cccc frame and we're ready for another 66 | # This hack doesn't work for big commands like dpt, ppt which have more than 256 responses! 67 | acknum = struct.unpack(' " 84 | exit(1) 85 | 86 | s = connect(sys.argv[1], int(sys.argv[2])) 87 | while True: 88 | print "> ", 89 | #try: 90 | myinput = raw_input() 91 | send_cmd(s, myinput) 92 | #except: 93 | # print "Debug: Caught signal, exiting..." 94 | # exit(0) 95 | 96 | -------------------------------------------------------------------------------- /mirror/digital-bond-codesys/codesys-transfer.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import struct 4 | 5 | # Imported from Digital Bond's Basecamp Website 6 | # Codesys-transfer.py, Copyright 2012 Digital Bond, Inc 7 | # All praise to be attributed to Dale Peterson 8 | # All bugs and gripes to be attributed to K. Reid Wightman 9 | 10 | # Takes integer, returns endian-word 11 | def little_word(val): 12 | packed = struct.pack(' Last block!" 61 | done = True 62 | blocknum += 1 63 | filedata += data[30:] # note: the data block does have two length fields at offsets 4 and 28 (little-endian words) but I ignore them 64 | print "Received block ", blocknum, " total bytes so far ", len(filedata) 65 | print "Debug: --> ", 66 | for byte in data[30:]: 67 | print hex(ord(byte)), 68 | print 69 | if done != True: 70 | # I found that sending these requests between blocks is a good idea. 71 | # It might not be necessary, though... 72 | # It's identical to the 'Resp' variable above. 73 | M5 = "\x66\x66\x01" + "\x00" * 13 + "\x01\x00\x00\x00\x06\00\x00\x00" 74 | s.send(M5) 75 | resp = s.recv(BUFFER_SIZE) 76 | # This is the actual request meaning 'the last block you sent was okay, send the next one' 77 | M6 = "\xcc\xcc\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x23\x01\x00\x00\x32" 78 | s.send(M6) 79 | rep = s.recv(BUFFER_SIZE) 80 | s.close() 81 | # Finally write the fruits of our labour out to local disk 82 | outfile = open(lfilename, 'rb') 83 | outfile.write(filedata) 84 | outfile.close() 85 | 86 | 87 | def send_file(host, port, lfilename, rfilename): 88 | BUFFER_SIZE = 1024 89 | BLOCK_SIZE = 1024 90 | # See recv_file2 for explanations of these first frames 91 | M1 = "\xbb\xbb\x01\x00\x00\x00\x01" 92 | M2 = "\xbb\xbb\x02\x00\x00\x00\x51\x10" 93 | # first block 94 | # May want to look for buffer overflows here in things like file length, packet length, etc. 95 | # Applying packet structure above: BBBB, Packet length, options, command (0x2f, 0x01), 0x00 is a null terminator? 96 | M3 = lambda x, y: "\xbb\xbb" + struct.pack('h', len(x) + len(y) + 2) + "\x00" + x + "\x00\xcd" + y 97 | # continuation block (block 2..n-1) 98 | # The last bytes before the data "\x00\x04" are actually the length of the continuation block 99 | # 1024 bytes in the normal case (little-endian) 100 | # Note \x00\x04 is the payload size (little-endian payload == 1024 byte blocks). 101 | # May want to look for buffer overflows here 102 | # M4 = lambda x: "\xbb\xbb\x04\x04\x00\x00\x30\x01\x00\x04" + x 103 | M4 = lambda x: "\xbb\xbb" + struct.pack('h', len(x) + len(y) + 2) + "\x00" + x + "\x00\xcd" + y 170 | # continuation block (block 2..n-1) 171 | # The last bytes before the data "\x00\x04" are actually the length of the continuation block 172 | # 1024 bytes in the normal case (little-endian) 173 | # Note \x00\x04 is the payload size (little-endian payload == 1024 byte blocks). 174 | # May want to look for buffer overflows here 175 | # M4 = lambda x: "\xbb\xbb\x04\x04\x00\x00\x30\x01\x00\x04" + x 176 | M4 = lambda x: "\xbb\xbb" + struct.pack(' " 254 | exit(1) 255 | 256 | if sys.argv[1] == "send": 257 | send_file(sys.argv[2], int(sys.argv[3]), sys.argv[4], sys.argv[5]) 258 | elif sys.argv[1] == "recv": 259 | recv_file2(sys.argv[2], int(sys.argv[3]), sys.argv[4], sys.argv[5]) 260 | elif sys.argv[1] == "sendlogic": 261 | if len(sys.argv) < 6: 262 | print "Usage: " + sys.argv[0] + " " 263 | exit(1) 264 | send_logic(sys.argv[2], int(sys.argv[3]), sys.argv[4], sys.argv[5], sys.argv[6]) 265 | else: 266 | print "Usage: " + sys.argv[0] + " " 267 | print " ::= send | recv | sendlogic (note sendlogic sends two files, please specify the checksum name)" 268 | exit(1) 269 | -------------------------------------------------------------------------------- /mirror/digital-bond-codesys/codesys.nse: -------------------------------------------------------------------------------- 1 | description = [[ development 2 | ]] 3 | 4 | 5 | author = "hdm" 6 | -- minor tweaks and bugfix by krw 7 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 8 | categories = {"discovery", "safe"} 9 | 10 | local nmap = require "nmap" 11 | local comm = require "comm" 12 | local stdnse = require "stdnse" 13 | local strbuf = require "strbuf" 14 | local nsedebug = require "nsedebug" 15 | 16 | --- 17 | -- Script is executed for any TCP port. 18 | portrule = function( host, port ) 19 | return port.protocol == "tcp" 20 | end 21 | 22 | --- 23 | -- Grabs a banner and outputs it nicely formatted. 24 | action = function( host, port ) 25 | local out = grab_banner(host, port, "\187\187\001\000\000\000\001") 26 | if out == "" then 27 | -- try a big-endian query 28 | out = grab_banner(host, port, "\187\187\000\001\000\000\001") 29 | end 30 | return output( out ) 31 | end 32 | 33 | --- 34 | -- Returns a number of milliseconds for use as a socket timeout value (defaults to 5 seconds). 35 | -- 36 | -- @return Number of milliseconds. 37 | function get_timeout() 38 | return 5000 39 | end 40 | 41 | 42 | --- 43 | -- Connects to the target on the given port and returns any data issued by a listening service. 44 | -- @param host Host Table. 45 | -- @param port Port Table. 46 | -- @return Socket descriptor and initial banner 47 | function grab_banner(host, port, query) 48 | 49 | local st, buff, banner 50 | local proto = "tcp" 51 | local socket = nmap.new_socket() 52 | socket:set_timeout(get_timeout()) 53 | banner = "" 54 | proto = "tcp" 55 | st = socket:connect(host, port, proto) 56 | if not st then 57 | socket:close() 58 | return nil 59 | end 60 | socket:send(query) 61 | -- Big endian version 62 | -- socket:send("\187\187\000\001\000\000\001") 63 | -- socket:send("\xbb\xbb\x01\x00\x00\x00\x01") 64 | st,banner = socket:receive() 65 | return banner 66 | end 67 | 68 | --- 69 | -- Formats the banner for printing to the port script result. 70 | -- 71 | -- Non-printable characters are hex encoded and the banner is 72 | -- then truncated to fit into the number of lines of output desired. 73 | -- @param out String banner issued by a listening service. 74 | -- @return String formatted for output. 75 | -- Ripped from banner.nse with line wrap disabled (corrupts output) 76 | function output( out ) 77 | 78 | if type(out) ~= "string" or out == "" then return nil end 79 | 80 | local filename = SCRIPT_NAME 81 | local line_len = 75 -- The character width of command/shell prompt window. 82 | local fline_offset = 5 -- number of chars excluding script id not available to the script on the first line 83 | 84 | -- number of chars available on the first line of output 85 | -- we'll skip the first line of output if the filename is looong 86 | local fline_len 87 | if filename:len() < (line_len-fline_offset) then 88 | fline_len = line_len -1 -filename:len() -fline_offset 89 | else 90 | fline_len = 0 91 | end 92 | 93 | -- number of chars allowed on subsequent lines 94 | local sline_len = line_len -1 -(fline_offset-2) 95 | 96 | -- replace non-printable ascii chars - no need to do the whole string 97 | out = replace_nonprint(out, (out:len() * 3) + 1) -- 1 extra char so we can truncate below. 98 | 99 | -- break into lines - this will look awful if line_len is more than the actual space available on a line... 100 | local ptr = fline_len 101 | local t = {} 102 | t[#t+1] = out 103 | 104 | return table.concat(t,"\n") 105 | 106 | end 107 | 108 | 109 | 110 | --- 111 | -- Replaces characters with ASCII values outside of the range of standard printable 112 | -- characters (decimal 32 to 126 inclusive) with hex encoded equivalents. 113 | -- 114 | -- The second parameter dictates the number of characters to return, however, if the 115 | -- last character before the number is reached is one that needs replacing then up to 116 | -- three characters more than this number may be returned. 117 | -- If the second parameter is nil, no limit is applied to the number of characters 118 | -- that may be returned. 119 | -- @param s String on which to perform substitutions. 120 | -- @param len Number of characters to return. 121 | -- @return String. 122 | -- Pulled from banner.nse and mangled to escape \r\t\n separately 123 | function replace_nonprint( s, len ) 124 | 125 | local t = {} 126 | local count = 0 127 | 128 | for c in s:gmatch(".") do 129 | if c:byte() == 9 then 130 | t[#t+1] = ("\\%s"):format("t") 131 | count = count+3 132 | elseif c:byte() == 10 then 133 | t[#t+1] = ("\\%s"):format("n") 134 | count = count+3 135 | elseif c:byte() == 13 then 136 | t[#t+1] = ("\\%s"):format("r") 137 | count = count+3 138 | elseif c:byte() < 32 or c:byte() > 126 then 139 | t[#t+1] = ("\\x%s"):format( ("0%s"):format( ( (stdnse.tohex( c:byte() )):upper() ) ):sub(-2,-1) ) -- capiche 140 | count = count+4 141 | else 142 | t[#t+1] = c 143 | count = count+1 144 | end 145 | if type(len) == "number" and count >= len then break end 146 | end 147 | 148 | return table.concat(t) 149 | 150 | end 151 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | url_moki_base="https://raw.githubusercontent.com/moki-ics/moki/master" 4 | url_background="$url_moki_base/images/moki.jpg" 5 | 6 | git_quickdraw="https://github.com/digitalbond/quickdraw" 7 | url_modscan="https://raw.githubusercontent.com/moki-ics/modscan/master/modscan.py" 8 | url_plcscan_base="https://raw.githubusercontent.com/moki-ics/plcscan/master" 9 | url_codesys_base="$url_moki_base/mirror/digital-bond-codesys" 10 | url_wireshark="https://1.na.dl.wireshark.org/src/wireshark-2.2.7.tar.bz2" 11 | git_s7metasploit="https://github.com/moki-ics/s7-metasploit-modules.git" 12 | git_s7wireshark="https://github.com/moki-ics/s7commwireshark.git" 13 | 14 | moki_data_dir="/usr/local/share/moki" 15 | meta_module_dir="/usr/share/metasploit-framework/modules/exploits" 16 | moki_directory_path="/usr/share/desktop-directories/Moki.directory" 17 | bin_directory="/usr/bin" 18 | desktop_apps="/usr/share/applications" 19 | nmap_scripts="/usr/share/nmap/scripts" 20 | 21 | wget="wget --no-check-certificate --quiet" 22 | add_to_moki="xdg-desktop-menu install --novendor --mode system $moki_directory_path" 23 | 24 | ################################################## 25 | # Parse Inputs 26 | ################################################## 27 | VERBOSE=false 28 | do_update=false 29 | codesys_install=false 30 | modscan_install=false 31 | plcscan_install=false 32 | s7metasploit_install=false 33 | snort_install=false 34 | snort_test=false # TODO: remove option, turn into shortcut 35 | s7wireshark_install=false 36 | 37 | ### Check Inputs ### 38 | while true; do 39 | case "$1" in 40 | -h | --help ) 41 | cat << "EOF" 42 | 43 | Usage: setup.sh [options] 44 | 45 | Installs extra SCADA/ICS tools under Kali 46 | Options: 47 | -h | --help This message 48 | -v | --verbose Script works in verbose mode 49 | --all Installs all tools 50 | --offensive Installs all offensive tools 51 | --defensive Installs all defensive tools 52 | --quickdraw Installs snort and Digital Bond's ICS snort Rules 53 | --plcsan Installs PLCscan script 54 | --codesys Installs CoDeSys Runtime exploit script 55 | --modscan Installs ModScan script 56 | --s7wireshark Installs the S7comm wireshark dissector 57 | EOF 58 | exit 0 59 | shift 60 | ;; 61 | -v | --verbose ) 62 | VERBOSE=true; 63 | shift 64 | ;; 65 | --all ) 66 | do_update=true 67 | codesys_install=true 68 | modscan_install=true 69 | plcscan_install=true 70 | s7metasploit_install=true 71 | snort_install=true 72 | s7wireshark_install=true 73 | shift 74 | ;; 75 | --offensive ) 76 | do_update=true 77 | codesys_install=true 78 | modscan_install=true 79 | plcscan_install=true 80 | s7metasploit_install=true 81 | s7wireshark_install=true 82 | shift 83 | ;; 84 | --defensive ) 85 | do_update=true 86 | snort_install=true 87 | shift 88 | ;; 89 | --codesys | --CoDeSyS ) 90 | codesys_install=true 91 | shift 92 | ;; 93 | --modscan | --ModScan ) 94 | modscan_install=true 95 | shift 96 | ;; 97 | --plcscan | --PLCscan | --PLCScan ) 98 | plcscan_install=true 99 | shift 100 | ;; 101 | --quickdraw ) 102 | do_update=true 103 | snort_install=true 104 | shift 105 | ;; 106 | --s7-metasploit ) 107 | s7metasploit_install=true 108 | shift 109 | ;; 110 | --s7wireshark ) 111 | do_update=true 112 | s7wireshark_install=true 113 | shift 114 | ;; 115 | --snort-test ) 116 | snort_test=true 117 | shift 118 | ;; 119 | * ) break 120 | ;; 121 | esac 122 | done 123 | 124 | 125 | #### test folder in ~/ #### 126 | dir="$HOME/.moki_tmp" 127 | rm -rf "$dir" 128 | mkdir "$dir" 129 | if ! cd "$dir" ; then 130 | echo "-> Error: could not cd to \"$dir\"" >&2 131 | exit 1 132 | fi 133 | 134 | ########## Run this to get sudo access ########### 135 | echo "# Checking for sudo access... " 136 | sudo ls >/dev/null 137 | 138 | ########## Make data dir ########### 139 | if [ ! -d "$moki_data_dir" ]; then 140 | echo "# Making $moki_data_dir ... " 141 | mkdir "$moki_data_dir" 142 | fi 143 | 144 | ########## Make Moki Desktop Directory File ####### 145 | cat > "$moki_directory_path" << "EOF" 146 | [Desktop Entry] 147 | Name=Moki ICS Tools 148 | Type=Directory 149 | Icon=k.png 150 | EOF 151 | 152 | ########## Update software repositories ########### 153 | if $do_update ; then 154 | echo "# Adding Official Kali Linux Repositories... " 155 | 156 | if ! grep "Moki" /etc/apt/sources.list; then 157 | cat >> /etc/apt/sources.list << "EOF" 158 | 159 | ## [Moki start] 160 | deb http://http.us.debian.org/debian testing main non-free contrib 161 | deb-src http://http.us.debian.org/debian testing main non-free contrib 162 | deb http://http.kali.org/kali kali main non-free contrib 163 | deb-src http://http.kali.org/kali main non-free contrib 164 | deb-src http://security.kali.org/kali-security kali/updates main contrib non-free 165 | ## [Moki end] 166 | EOF 167 | fi 168 | 169 | echo "# Updating apt-get & Upgrading all packages... " 170 | apt-get clean 2>/dev/null 1>/dev/null 171 | apt-get update -y --force-yes 172 | # apt-get upgrade -y --force-yes 173 | # apt-get dist-upgrade -y --force-yes 174 | fi 175 | 176 | 177 | ################################################## 178 | # Snort & Quickdraw SCADA Snort Rules 179 | ################################################## 180 | 181 | if $snort_install && [ ! `which snort` ]; then 182 | echo "# Installing snort..." 183 | apt-get install -y snort \ 184 | snort-common \ 185 | snort-common-libraries 186 | fi 187 | 188 | if $snort_install ; then 189 | snort_rules_dir="/etc/snort/rules" 190 | 191 | echo "# Downloading rules..." 192 | if ! git clone "$git_quickdraw"; then 193 | echo "-> Error: could not get $git_quickdraw" >&2 194 | exit 1 195 | fi 196 | quickdraw_dir="$dir/quickdraw" 197 | 198 | echo "# Copying Quickdraw SCADA rules to the rules directory..." 199 | cp $quickdraw_dir/all-quickdraw.rules $snort_rules_dir/all-quickdraw.rules 200 | 201 | snort_rules_file="/etc/snort/snort.conf" 202 | if ! grep "Moki" $snort_rules_file; then 203 | echo "# Editing the snort rules file..." 204 | cat >> $snort_rules_file << "EOF" 205 | 206 | ## [Moki start] 207 | #----------------------------- 208 | # Moki SCADA Variables 209 | #----------------------------- 210 | # for Snort 2.9.7.3 and Quickdraw v1.3, use these: 211 | ipvar MODICON_CLIENT $HOME_NET 212 | ipvar BACNET_CLIENT $HOME_NET 213 | ipvar FINS_CLIENT $HOME_NET 214 | ipvar FINS_SERVER $HOME_NET 215 | ipvar S7_SERVER $HOME_NET 216 | ipvar S7_CLIENT $HOME_NET 217 | ipvar MODBUS_CLIENT $HOME_NET 218 | ipvar MODBUS_SERVER $HOME_NET 219 | ipvar DNP3_CLIENT $HOME_NET 220 | ipvar DNP3_SERVER $HOME_NET 221 | portvar DNP3_PORTS 20000 222 | # for Quickdraw prior to v1.3, use these too: 223 | #ipvar ENIP_CLIENT $HOME_NET 224 | #ipvar ENIP_SERVER $HOME_NET 225 | 226 | #----------------------------- 227 | # Moki SCADA Rules 228 | #----------------------------- 229 | include $RULE_PATH/all-quickdraw.rules 230 | ## [Moki end] 231 | EOF 232 | fi 233 | 234 | # Change "ipvar HOME_NET any" to "ipvar HOME_NET 10.0.0.0/8" in /etc/snort/snort.conf 235 | # Future work: identify if this can be a variable; 10.0.0.0/8 came from Snort error log 236 | sed -i "s|ipvar HOME_NET any|ipvar HOME_NET 10.0.0.0/8|" /etc/snort/snort.conf 237 | 238 | if [ ! -d $moki_data_dir/pcap ]; then 239 | echo "# Installing SCADA pcap samples from Digital Bond to $moki_data_dir/pcap" 240 | mkdir $moki_data_dir/pcap 241 | cp $quickdraw_dir/*.pcap $moki_data_dir/pcap 242 | fi 243 | fi 244 | 245 | if $snort_test ; then 246 | snort_rules_file="/etc/snort/snort.conf" 247 | 248 | if ! which snort; then 249 | echo "-> Error: snort not installed" >&2 250 | exit 1 251 | fi 252 | 253 | if [ ! -d $moki_data_dir/pcap ]; then 254 | echo "-> Error: pcap files not found" >&2 255 | exit 1 256 | fi 257 | 258 | echo "# Testing snort configuration file..." 259 | if ! snort -T -c "$snort_rules_file" 2>/dev/null 1>/dev/null; then 260 | echo -n "-> Error: snort doesn't like the config or active rules." >&2 261 | echo -n " Maybe \$HOME_NET is 'any' in $snort_rules_file?" >&2 262 | echo " The PCAP files require monitoring 10.0.0.0/8." >&2 263 | exit 1 264 | fi 265 | 266 | echo "# Running tests..." 267 | logdir="/tmp/moki" 268 | rm -rf "$logdir" 269 | mkdir "$logdir" 270 | 271 | for pcapfile in $moki_data_dir/pcap/*.pcap 272 | do 273 | pcapname=`basename "$pcapfile"` 274 | logfile="$logdir"/"$pcapname".out 275 | snort -c "$snort_rules_file" -l "$logdir" --pcap-single "$pcapfile" 2>"$logfile" 1>"$logfile" 276 | done 277 | 278 | echo "# Checking alerts..." 279 | testlog="$logdir"/bacnet_test.pcap.out 280 | if [ ! -f "$testlog" ]; then 281 | echo "-> Error: $testlog missing" >&2 282 | else 283 | if ! grep "Snort processed 26 packets" "$testlog"; then 284 | echo "-> Error: packets missing from $testlog" >&2 285 | fi 286 | if ! grep "Alerts: * 13 " "$testlog"; then 287 | echo "-> Error: alerts missing from $testlog" >&2 288 | fi 289 | if ! grep "Logged: * 13 " "$testlog"; then 290 | echo "-> Error: logged events missing from $testlog" >&2 291 | fi 292 | fi 293 | 294 | testlog="$logdir"/dnp3_test_data_part1.pcap.out 295 | if [ ! -f "$testlog" ]; then 296 | echo "-> Error: $testlog missing" >&2 297 | else 298 | if ! grep "Snort processed 181 packets" "$testlog"; then 299 | echo "-> Error: packets missing from $testlog" >&2 300 | fi 301 | if ! grep "Alerts: * 116 " "$testlog"; then 302 | echo "-> Error: alerts missing from $testlog" >&2 303 | fi 304 | if ! grep "Logged: * 116 " "$testlog"; then 305 | echo "-> Error: logged events missing from $testlog" >&2 306 | fi 307 | fi 308 | 309 | testlog="$logdir"/dnp3_test_data_part2.pcap.out 310 | if [ ! -f "$testlog" ]; then 311 | echo "-> Error: $testlog missing" >&2 312 | else 313 | if ! grep "Snort processed 33 packets" "$testlog"; then 314 | echo "-> Error: packets missing from $testlog" >&2 315 | fi 316 | if ! grep "Alerts: * 0 " "$testlog"; then 317 | echo "-> Error: alerts missing from $testlog" >&2 318 | fi 319 | if ! grep "Logged: * 0 " "$testlog"; then 320 | echo "-> Error: logged events missing from $testlog" >&2 321 | fi 322 | fi 323 | 324 | testlog="$logdir"/enip_test.pcap.out 325 | if [ ! -f "$testlog" ]; then 326 | echo "-> Error: $testlog missing" >&2 327 | else 328 | if ! grep "Snort processed 11 packets" "$testlog"; then 329 | echo "-> Error: packets missing from $testlog" >&2 330 | fi 331 | if ! grep "Alerts: * 0 " "$testlog"; then 332 | echo "-> Error: alerts missing from $testlog" >&2 333 | fi 334 | if ! grep "Logged: * 0 " "$testlog"; then 335 | echo "-> Error: logged events missing from $testlog" >&2 336 | fi 337 | fi 338 | 339 | testlog="$logdir"/fox_info.pcap.out 340 | if [ ! -f "$testlog" ]; then 341 | echo "-> Error: $testlog missing" >&2 342 | else 343 | if ! grep "Snort processed 10 packets" "$testlog"; then 344 | echo "-> Error: packets missing from $testlog" >&2 345 | fi 346 | if ! grep "Alerts: * 1 " "$testlog"; then 347 | echo "-> Error: alerts missing from $testlog" >&2 348 | fi 349 | if ! grep "Logged: * 1 " "$testlog"; then 350 | echo "-> Error: logged events missing from $testlog" >&2 351 | fi 352 | fi 353 | 354 | testlog="$logdir"/modbus_test_data_part1.pcap.out 355 | if [ ! -f "$testlog" ]; then 356 | echo "-> Error: $testlog missing" >&2 357 | else 358 | if ! grep "Snort processed 118 packets" "$testlog"; then 359 | echo "-> Error: packets missing from $testlog" >&2 360 | fi 361 | if ! grep "Alerts: * 20 " "$testlog"; then 362 | echo "-> Error: alerts missing from $testlog" >&2 363 | fi 364 | if ! grep "Logged: * 20 " "$testlog"; then 365 | echo "-> Error: logged events missing from $testlog" >&2 366 | fi 367 | fi 368 | 369 | testlog="$logdir"/modbus_test_data_part2.pcap.out 370 | if [ ! -f "$testlog" ]; then 371 | echo "-> Error: $testlog missing" >&2 372 | else 373 | if ! grep "Snort processed 350 packets" "$testlog"; then 374 | echo "-> Error: packets missing from $testlog" >&2 375 | fi 376 | if ! grep "Alerts: * 0 " "$testlog"; then 377 | echo "-> Error: alerts missing from $testlog" >&2 378 | fi 379 | if ! grep "Logged: * 0 " "$testlog"; then 380 | echo "-> Error: logged events missing from $testlog" >&2 381 | fi 382 | fi 383 | 384 | testlog="$logdir"/modicon_test.pcap.out 385 | if [ ! -f "$testlog" ]; then 386 | echo "-> Error: $testlog missing" >&2 387 | else 388 | if ! grep "Snort processed 191 packets" "$testlog"; then 389 | echo "-> Error: packets missing from $testlog" >&2 390 | fi 391 | if ! grep "Alerts: * 0 " "$testlog"; then 392 | echo "-> Error: alerts missing from $testlog" >&2 393 | fi 394 | if ! grep "Logged: * 0 " "$testlog"; then 395 | echo "-> Error: logged events missing from $testlog" >&2 396 | fi 397 | fi 398 | 399 | testlog="$logdir"/omron_test.pcap.out 400 | if [ ! -f "$testlog" ]; then 401 | echo "-> Error: $testlog missing" >&2 402 | else 403 | if ! grep "Snort processed 18 packets" "$testlog"; then 404 | echo "-> Error: packets missing from $testlog" >&2 405 | fi 406 | if ! grep "Alerts: * 2 " "$testlog"; then 407 | echo "-> Error: alerts missing from $testlog" >&2 408 | fi 409 | if ! grep "Logged: * 2 " "$testlog"; then 410 | echo "-> Error: logged events missing from $testlog" >&2 411 | fi 412 | fi 413 | 414 | testlog="$logdir"/s7_test.pcap.out 415 | if [ ! -f "$testlog" ]; then 416 | echo "-> Error: $testlog missing" >&2 417 | else 418 | if ! grep "Snort processed 39 packets" "$testlog"; then 419 | echo "-> Error: packets missing from $testlog" >&2 420 | fi 421 | if ! grep "Alerts: * 0" "$testlog"; then 422 | echo "-> Error: alerts missing from $testlog" >&2 423 | fi 424 | if ! grep "Logged: * 0" "$testlog"; then 425 | echo "-> Error: logged events missing from $testlog" >&2 426 | fi 427 | fi 428 | 429 | echo "# If there are any errors above, see $logdir for details:" 430 | ls -al "$logdir" 431 | fi 432 | 433 | 434 | ################################################## 435 | # PLCscan tool 436 | ################################################## 437 | if $plcscan_install ; then 438 | echo "# Installing PLCScan... " 439 | if ! $wget $url_plcscan_base/plcscan.py -O $bin_directory/plcscan.py; then 440 | echo "-> Error: could not get $url_plcscan_base/plcscan.py" >&2 441 | exit 1 442 | fi 443 | if ! $wget $url_plcscan_base/modbus.py -O $bin_directory/modbus.py; then 444 | echo "-> Error: could not get $url_plcscan_base/modbus.py" >&2 445 | exit 1 446 | fi 447 | if ! $wget $url_plcscan_base/s7.py -O $bin_directory/s7.py; then 448 | echo "-> Error: could not get $url_plcscan_base/s7.py" >&2 449 | exit 1 450 | fi 451 | vim -c ':set ff=unix|wq' $bin_directory/plcscan.py 452 | sed -i '1s/^/#!\/usr\/bin\/env python\n/' $bin_directory/plcscan.py 453 | chmod +x $bin_directory/plcscan.py 454 | ln -s $bin_directory/plcscan.py $bin_directory/plcscan 455 | cat > "$desktop_apps/plcscan.desktop" << "EOF" 456 | [Desktop Entry] 457 | Name=plcscan 458 | Encoding=UTF-8 459 | Exec=sh -c "plcscan;${SHELL:-bash}" 460 | Icon=kali-menu.png 461 | StartupNotify=false 462 | Terminal=true 463 | Type=Application 464 | Categories=Moki;ICS; 465 | EOF 466 | $add_to_moki "$desktop_apps/plcscan.desktop" 467 | fi 468 | 469 | 470 | ################################################## 471 | # Digital Bond's CoDeSyS exploit tools 472 | ################################################## 473 | if $codesys_install ; then 474 | echo "# Installing Wago Exploit... " 475 | if ! $wget $url_codesys_base/codesys-shell.py -O $bin_directory/codesys-shell.py; then 476 | echo "-> Error: could not get $url_codesys_base/codesys-shell.py" >&2 477 | exit 1 478 | fi 479 | if ! $wget $url_codesys_base/codesys-transfer.py -O $bin_directory/codesys-transfer.py; then 480 | echo "-> Error: could not get $url_codesys_base/codesys-transfer.py" >&2 481 | exit 1 482 | fi 483 | if ! $wget $url_codesys_base/codesys.nse -O $nmap_scripts/codesys.nse; then 484 | echo "-> Error: could not get $url_codesys_base" >&2 485 | exit 1 486 | fi 487 | vim -c ':set ff=unix|wq' $bin_directory/codesys-shell.py 488 | vim -c ':set ff=unix|wq' $bin_directory/codesys-transfer.py 489 | sed -i '1s/^/#!\/usr\/bin\/env python\n/' $bin_directory/codesys-shell.py 490 | sed -i '1s/^/#!\/usr\/bin\/env python\n/' $bin_directory/codesys-transfer.py 491 | chmod +x $bin_directory/codesys-shell.py 492 | ln -s $bin_directory/codesys-shell.py $bin_directory/codesys-shell 493 | chmod +x $bin_directory/codesys-transfer.py 494 | ln -s $bin_directory/codesys-transfer.py $bin_directory/codesys-transfer 495 | cat > "$desktop_apps/codesys-shell.desktop" << "EOF" 496 | [Desktop Entry] 497 | Name=codesys-shell 498 | Encoding=UTF-8 499 | Exec=sh -c "codesys-shell;${SHELL:-bash}" 500 | Icon=kali-menu.png 501 | StartupNotify=false 502 | Terminal=true 503 | Type=Application 504 | Categories=Moki;ICS; 505 | EOF 506 | cat > "$desktop_apps/codesys-transfer.desktop" << "EOF" 507 | [Desktop Entry] 508 | Name=codesys-transfer 509 | Encoding=UTF-8 510 | Exec=sh -c "codesys-transfer;${SHELL:-bash}" 511 | Icon=kali-menu.png 512 | StartupNotify=false 513 | Terminal=true 514 | Type=Application 515 | Categories=Moki;ICS; 516 | EOF 517 | $add_to_moki "$desktop_apps/codesys-shell.desktop" 518 | $add_to_moki "$desktop_apps/codesys-transfer.desktop" 519 | fi 520 | 521 | 522 | ################################################## 523 | # modscan tool 524 | ################################################## 525 | if $modscan_install ; then 526 | echo "# Installing ModScan... " 527 | if ! $wget $url_modscan -O $bin_directory/modscan.py; then 528 | echo "-> Error: could not get $url_modscan" >&2 529 | exit 1 530 | fi 531 | chmod +x $bin_directory/modscan.py 532 | ln -s $bin_directory/modscan.py $bin_directory/modscan 533 | cat > "$desktop_apps/modscan.desktop" << "EOF" 534 | [Desktop Entry] 535 | Name=modscan 536 | Encoding=UTF-8 537 | Exec=sh -c "modscan;${SHELL:-bash}" 538 | Icon=kali-menu.png 539 | StartupNotify=false 540 | Terminal=true 541 | Type=Application 542 | Categories=Moki;ICS; 543 | EOF 544 | $add_to_moki "$desktop_apps/modscan.desktop" 545 | fi 546 | 547 | 548 | ################################################## 549 | # metasploit module: old S7-exploit 550 | ################################################## 551 | if $s7metasploit_install ; then 552 | echo "# Installing an old metasploit module for this S7 exploit:" 553 | echo "# http://www.exploit-db.com/exploits/19832/... " 554 | if ! git clone "$git_s7metasploit"; then 555 | echo "-> Error: could not get $git_s7metasploit" >&2 556 | exit 1 557 | fi 558 | mkdir -p "$meta_module_dir/simatic" 559 | if ! mv s7-metasploit-modules/*.rb "$meta_module_dir/simatic"; then 560 | echo "-> Error: could not put files into $meta_module_dir" >&2 561 | exit 1 562 | fi 563 | fi 564 | 565 | 566 | ################################################## 567 | # s7comm wireshark dissector 568 | ################################################## 569 | 570 | if $s7wireshark_install ; then 571 | echo "# Installing wireshark plugin dependencies..." 572 | apt-get install -y build-essential bison flex \ 573 | libtool libtool-bin autoconf \ 574 | libpcap-dev 575 | # libgtk-3-dev qt-sdk # we don't need these if we don't build the GUI 576 | fi 577 | 578 | if $s7wireshark_install ; then 579 | cwd=`pwd` 580 | echo "# Installing a wireshark plugin for the Siemens S7comm protocol:" 581 | if ! which wireshark; then 582 | echo "-> Error: wireshark not installed" >&2 583 | exit 1 584 | fi 585 | 586 | # grab the plugin code 587 | if ! git clone "$git_s7wireshark"; then 588 | echo "-> Error: could not get $git_s7wireshark" >&2 589 | exit 1 590 | fi 591 | 592 | # grab the wireshark code 593 | if ! $wget $url_wireshark; then 594 | echo "-> Error: could not get $url_wireshark" >&2 595 | exit 1 596 | fi 597 | tar xf wireshark-* 598 | wireshark_dir=`find . -type d -name "wireshark-*"` 599 | if [ x"$wireshark_dir" = x ]; then 600 | echo "-> Error: unpacking wireshark didn't seem to work " >&2 601 | exit 1 602 | fi 603 | 604 | # figure out the plugin destination 605 | plugins_dir=`find /usr/lib -type d -regex ".*/wireshark/plugins/.*"` 606 | if [ ! -d "$plugins_dir" ]; then 607 | plugins_dir="/usr/share/wireshark/plugins" 608 | mkdir -p $plugins_dir 609 | fi 610 | 611 | # copy plugin code to the wireshark source directory 612 | cp -R s7commwireshark/src/* $wireshark_dir/plugins 613 | 614 | # build wireshark (no GUI) just to be able to build the plugin libraries 615 | # then install the libraries to the plugin destination 616 | cd $wireshark_dir 617 | ./autogen.sh 618 | ./configure --enable-wireshark=No 619 | 620 | make -C plugins/s7comm all 621 | plugin="plugins/s7comm/.libs/s7comm.so" 622 | if [ ! -f "$plugin" ]; then 623 | echo "-> Error: $plugin missing" >&2 624 | exit 1 625 | fi 626 | cp $plugin $plugins_dir 627 | 628 | make -C plugins/s7comm_plus all 629 | plugin="plugins/s7comm_plus/.libs/s7comm_plus.so" 630 | if [ ! -f "$plugin" ]; then 631 | echo "-> Error: $plugin missing" >&2 632 | exit 1 633 | fi 634 | cp $plugin $plugins_dir 635 | cd $pwd 636 | 637 | # check that tshark detects the plugins are installed correctly 638 | echo "# Checking plugins are installed" 639 | s7plug1=`tshark -G plugins 2>/dev/null | grep s7comm.so` 640 | s7plug2=`tshark -G plugins 2>/dev/null | grep s7comm_plus.so` 641 | if [ x"$s7plug1" = x ] || [ x"$s7plug2" = x ]; then 642 | tshark -G plugins >&2 643 | echo "-> Error: wireshark plugins don't appear to have been installed properly" >&2 644 | exit 1 645 | fi 646 | fi 647 | 648 | 649 | ################################################## 650 | # custom background image 651 | ################################################## 652 | background_dest="/usr/share/backgrounds/gnome/moki.jpg" 653 | background_conf="/etc/dconf/db/local.d/01-moki-tweaks" 654 | if [ ! -f $background_conf ]; then 655 | echo "# Changing custom background image... " 656 | $wget $url_background -O $background_dest 657 | cat > $background_conf << "EOF" 658 | [org/gnome/desktop/background] 659 | picture-uri='file:///usr/share/backgrounds/gnome/moki.jpg' 660 | picture-options='scaled' 661 | primary-color='000000' 662 | secondary-color='FFFFFF' 663 | EOF 664 | dconf update 665 | fi 666 | 667 | ################################################## 668 | # cleanup 669 | ################################################## 670 | 671 | if true ; then 672 | # Clean up after the installs. 673 | echo "# Cleaning packages... " 674 | sudo apt-get -y --force-yes clean 2>/dev/null 1>/dev/null 675 | sudo apt-get -y --force-yes autoclean 2>/dev/null 1>/dev/null 676 | sudo apt-get -y --force-yes autoremove 2>/dev/null 1>/dev/null 677 | fi 678 | 679 | rm -rf "$dir" 680 | 681 | ################################################## 682 | # finished 683 | ################################################## 684 | echo "# " 685 | echo "# All Done!" 686 | echo "# " 687 | --------------------------------------------------------------------------------