1-- #!/usr/bin/env lua -> shebang line seems to be breaking tshark :-(
2
3-- Usage: tshark -q -Xlua_script:network2john.lua -r selected.pcap
4--
5-- Wireshark in Fedora 25 doesn't have Lua enabled. Use Fedora 27 / Ubuntu or
6-- something else which has Wireshark with Lua support or compile Wireshark
7-- with Lua support.
8--
9-- tshark -r selected.pcap -T pdml > data.pdml # use this for development!
10
11
12-- Extract RADIUS CHAP hashes from .pcap files.
13-- https://tools.ietf.org/html/rfc2865 -> The CHAP challenge value is found in
14-- the CHAP-Challenge Attribute (60) if present in the packet, otherwise in the
15-- Request Authenticator field. NOTE: We don't handle the former case yet.
16
17if not Listener then
18	print("Usage: tshark -q -Xlua_script:network2john.lua -r target.pcap")
19	os.exit(0)
20end
21tap_radius = Listener.new(nil, "radius")
22
23-- Extract RADIUS CHAP hashes from .pcap files.
24-- We can also parse the "radius.avp" entries for more flexibility?
25local f_code = Field.new("radius.code")
26local f_authenticator = Field.new("radius.authenticator")
27local f_username = Field.new("radius.User_Name")
28local f_ident = Field.new("radius.CHAP_Ident")
29local f_hash = Field.new("radius.CHAP_String")
30
31function tap_radius.packet(pinfo,tvb,tapdata)
32	local code = f_code()
33
34	if code.value == 1 then  -- Access-Request
35		local canary = f_ident()
36		if canary then
37			local challenge = tostring(f_authenticator().value):lower()
38			local id = tostring(f_ident().value):lower()
39			local response =  tostring(f_hash().value):lower()
40			local username = tostring(f_username().value)
41			local hash = string.format("%s:$chap$%s*%s*%s", username, id, challenge, response)
42			print(hash)
43			canary = nil
44		end
45	end
46end
47
48function tap_radius.draw()
49end
50
51-- Extract RADIUS authentication hashes from .pcap files.
52-- http://www.untruth.org/~josh/security/radius/radius-auth.html
53-- (An Analysis of the RADIUS Authentication Protocol)
54local f_code = Field.new("radius.code")
55local f_authenticator = Field.new("radius.authenticator")
56local f_username = Field.new("radius.User_Name")
57local f_upe = Field.new("radius.User_Password_encrypted")
58
59-- put the passed-in args into a table
60local args = {...}
61count = 0
62for i,v in ipairs(args) do
63	count = count + 1
64end
65
66function tap_radius.packet(pinfo,tvb,tapdata)
67	local code = f_code()
68
69	if count < 2 then
70		print("Usage: tshark -q -Xlua_script:network2john.lua -X lua_script1:<secret-or-password-value-here> -X lua_script1:0 -r target.pcap\n")
71		print("Note: -X lua_script1:<secret-or-password-value-here> -> this is used as either the user password or the shared secrert\n")
72		print("Note: Use -X lua_script1:0 to attack RADIUS shared secret\n")
73		print("Note: Use -X lua_script1:1 to attack user password")
74		os.exit(0)
75	end
76
77	-- print them out
78	if code.value == 1 then  -- Access-Request
79		local canary = f_upe()
80		if canary then
81			local challenge = tostring(f_authenticator().value):lower()
82			local upe =  tostring(f_upe().value):lower()
83			local username = tostring(f_username().value)
84			local secret = args[1]
85			local mode = args[2]
86			local hash = string.format("%s:$radius$1*%s*%s*%s*%s", username, mode, secret, challenge, upe)
87			print(hash)
88			canary = nil
89		end
90	end
91end
92
93function tap_radius.draw()
94end
95
96
97-- Extract EAP-MD5 hashes from .pcap files.
98tap_eap = Listener.new(nil, "eap")
99
100local f_code = Field.new("eap.code")
101local f_id = Field.new("eap.id")
102local f_etype = Field.new("eap.type")
103local f_identity = Field.new("eap.identity")
104local f_challenge = Field.new("eap.md5.value")
105local f_response = Field.new("eap.md5.value")
106
107local username = nil
108local challenge = nil
109local response = nil
110local id = nil
111
112function tap_eap.packet(pinfo,tvb,tapdata)
113	local code = f_code()
114	local etype = f_etype()
115
116	if code.value == 2 and etype.value == 1 then -- Response, Identity (extract username)
117		username = tostring(f_identity())
118	end
119
120	if code.value == 1 and etype.value == 4 then -- Request, MD5-Challenge EAP
121		challenge = tostring(f_challenge().value):lower()
122	end
123
124	if code.value == 2 and etype.value == 4 then -- Response, MD5-Challenge EAP
125		response = tostring(f_response().value):lower()
126		id = tostring(f_id().value)
127	end
128
129	if username and challenge and response then
130		local hash = string.format("%s:$chap$%s*%s*%s", username, id, challenge, response)
131		print(hash)
132		username = nil
133		challenge = nil
134		response = nil
135		id = nil
136	end
137end
138
139function tap_eap.draw()
140end
141
142
143-- Extract SNMPv3 USM hashes from .pcap files.
144--
145-- Special thanks goes to Peter Wu for making this script work!
146-- require "socket"
147
148-- function sleep(sec)
149-- 	socket.select(nil, nil, sec)
150-- end
151
152tap_snmp = Listener.new(nil, "snmp")
153
154local f_msgVersion = Field.new("snmp.msgVersion")
155local f_msgSecurityModel = Field.new("snmp.msgSecurityModel")
156local f_msgAuthoritativeEngineID = Field.new("snmp.msgAuthoritativeEngineID")
157local f_msgAuthenticationParameters = Field.new("snmp.msgAuthenticationParameters")
158local f_msgUserName = Field.new("snmp.msgUserName")
159local f_snmp = Field.new("snmp")
160local snmp_tip_printed = false
161
162
163function tap_snmp.packet(pinfo,tvb,tapdata)
164	if not snmp_tip_printed then
165		print("Set the SNMP_ALGORITHM environment variable for a speed boost, if you already know the algorithm being used. Read this script to know more.")
166		-- sleep(1)  -- doesn't work reliably across distributions
167		snmp_tip_printed = true
168	end
169	local msgVersion = f_msgVersion()
170	local msgSecurityModel = f_msgSecurityModel()
171	local msgAuthoritativeEngineID = f_msgAuthoritativeEngineID()
172	local msgAuthenticationParameters = f_msgAuthenticationParameters()
173	local msgUserName = f_msgUserName()
174
175	local snmp_algorithm = os.getenv("SNMP_ALGORITHM")
176	local algorithm = 0 -- try both HMAC-MD5-96 and HMAC-SHA-96 (authProtocol)
177	if snmp_algorithm == "MD5" then
178		algorithm = 1
179	elseif snmp_algorithm == "SHA1" then
180		algorithm = 2
181	end
182
183	if msgSecurityModel then
184		if msgVersion.value ~= 3 then
185			return
186		end
187		if msgSecurityModel.value ~= 3 then
188			return
189		end
190		if msgAuthoritativeEngineID.len == 0 then
191			return
192		end
193		if msgAuthenticationParameters.len == 0 then
194			return
195		end
196		if msgAuthenticationParameters.len ~= 12 then -- this is known to be 96-bits
197			return
198		end
199		if msgUserName.len == 0 then
200			return
201		end
202
203		local snmp_field = f_snmp()
204		local snmp_payload = snmp_field.range()
205		local wholeMsg = snmp_payload:bytes():tohex():lower()
206		local AuthoritativeEngineID = tostring(msgAuthoritativeEngineID.value):lower()
207		local AuthenticationParameters = tostring(msgAuthenticationParameters.value):lower()
208		local UserName = tostring(msgUserName)
209		-- zero out the hash (is there a safer/better way to do this?)
210		local wholeMsgProper = wholeMsg:gsub(AuthenticationParameters, "000000000000000000000000")
211		local hash = string.format("%s:$SNMPv3$%s$%s$%s$%s$%s", UserName, algorithm, pinfo.number,
212			wholeMsgProper, AuthoritativeEngineID, AuthenticationParameters)
213		print(hash)
214	end
215end
216
217function tap_snmp.draw()
218end
219
220
221
222-- Extract DHCPv6 authentication hashes from .pcap files.
223-- NOTE: This requires Wireshark 2.9.0 (from git master) as of June, 2018.
224
225tap_dhcpv6 = Listener.new(nil, "dhcpv6")
226
227local f_algorithm = Field.new("dhcpv6.auth.algorithm")
228local f_hash= Field.new("dhcpv6.auth.md5_data")
229local f_dhcpv6 = Field.new("dhcpv6")
230
231function tap_dhcpv6.packet(pinfo,tvb,tapdata)
232	local canary = f_hash()
233	if not canary then
234		return
235	end
236
237	local algorithm = f_algorithm()
238	if algorithm.value ~= 1 then
239		return
240	end
241
242	local hash = tostring(canary.value):lower()
243	local dhcpv6_field = f_dhcpv6()
244	local dhcpv6_payload= dhcpv6_field.range()
245	local wholeMsg = dhcpv6_payload:bytes():tohex():lower()
246	local wholeMsgProper = wholeMsg:gsub(hash, "00000000000000000000000000000000")
247	local hash = string.format("%s:$rsvp$1$%s$%s", pinfo.number, wholeMsgProper, hash)
248	print(hash)
249end
250
251function tap_dhcpv6.draw()
252end
253
254
255
256-- Extract DHCPv4 authentication hashes from .pcap files.
257-- NOTE: This requires Wireshark 2.9.0 (from git master) as of June, 2018.
258
259tap_dhcpv4 = Listener.new(nil, "bootp")
260
261local f_algorithm = Field.new("bootp.option.dhcp_authentication.alg_delay")
262local f_hash= Field.new("bootp.option.dhcp_authentication.hmac_md5_hash")
263local f_dhcpv4 = Field.new("bootp")
264
265function tap_dhcpv4.packet(pinfo,tvb,tapdata)
266	local canary = f_hash()
267	if not canary then
268		return
269	end
270
271	local algorithm = f_algorithm()
272	if algorithm.value ~= 1 then
273		return
274	end
275
276	local hash = tostring(canary.value):lower()
277	local dhcpv4_field = f_dhcpv4()
278	local dhcpv4_payload= dhcpv4_field.range()
279	local wholeMsg = dhcpv4_payload:bytes():tohex():lower()
280	wholeMsg = string.sub(wholeMsg, 1, 6) .. "00" .. string.sub(wholeMsg, 9, 48) .. "00000000" .. string.sub(wholeMsg, 57)  -- zero'ize hops and giaddr
281	local wholeMsgProper = wholeMsg:gsub(hash, "00000000000000000000000000000000")  -- zero'ize the mac
282	local hash = string.format("%s:$rsvp$1$%s$%s", pinfo.number, wholeMsgProper, hash)
283	print(hash)
284end
285
286function tap_dhcpv4.draw()
287end
288
289
290
291-- Extract iSCSI CHAP hashes from .pcap files.
292--
293-- WARNING: This code is unlikely to handle parallel login sessions well!
294
295tap_iscsi = Listener.new(nil, "iscsi")
296
297local f_opcode = Field.new("iscsi.opcode")
298local f_kv = Field.new("iscsi.keyvalue")
299
300local username = nil
301local challenge = nil
302local response = nil
303local id = nil
304
305function tap_iscsi.packet(pinfo,tvb,tapdata)
306	local opcode = f_opcode()
307
308	if opcode.value == 0x23 then  -- extract CHAP_C, and CHAP_I
309		items = {f_kv()}
310		for index in pairs(items) do
311			item = tostring(items[index])
312			if string.find(item, 'CHAP_C') then
313				challenge = item:gsub("CHAP_C=0x", "")  -- robust?
314			end
315			if string.find(item, 'CHAP_I') then
316				id = item:gsub("CHAP_I=", "")  -- robust?
317			end
318		end
319	end
320
321	if opcode.value == 0x3 then  -- extract CHAP_N, and CHAP_R
322		items = {f_kv()}
323		for index in pairs(items) do
324			item = tostring(items[index])
325			if string.find(item, 'CHAP_R') then
326				response = item:gsub("CHAP_R=0x", "")
327			end
328			if string.find(item, 'CHAP_N') then
329				username = item:gsub("CHAP_N=", "")
330			end
331		end
332	end
333
334	if username and challenge and response then
335		local hash = string.format("%s:$chap$%s*%s*%s", username, id, challenge, response)
336		print(hash)
337		username = nil
338		challenge = nil
339		response = nil
340		id = nil
341	end
342end
343
344function tap_iscsi.draw()
345end
346
347
348-- Extract DHCP OMAPI hashes from .pcap files. Tested with omshell, and pypureomapi.
349tap_omapi = Listener.new(nil, "omapi")
350
351local f_authid = Field.new("omapi.authid")
352local f_authlen = Field.new("omapi.authlength")
353local f_omapi = Field.new("omapi")
354local omapi_tip_printed = false
355
356function tap_omapi.packet(pinfo,tvb,tapdata)
357	if not omapi_tip_printed then
358		print("[WARNING] The DHCP OMAPI secret value is likely to be uncrackable under normal circumstances!")
359		omapi_tip_printed = true
360	end
361
362	local authid = f_authid()
363	if not authid then
364		return
365	end
366	if authid.value ~= 1 then
367		return
368	end
369
370	local authlen = f_authlen()
371	if authlen.value ~= 16 then
372		print("[DEBUG] omapi.authlength is not 16, please report this to us!")
373		return
374	end
375
376	local omapi_field = f_omapi()
377	local omapi_payload = omapi_field.range()
378	local wholeMsg = tostring(omapi_payload:bytes():tohex():lower())
379	local payload = string.sub(wholeMsg, 8+1, -32-1)
380	local signature = string.sub(wholeMsg, -32)
381	local hash = string.format("%s:$rsvp$1$%s$%s", pinfo.number, payload, signature)
382	print(hash)
383end
384
385function tap_omapi.draw()
386end
387