Blog: Hardware Hacking
Breaking Samsung firmware, or turning your S8/S9/S10 into a DIY “Proxmark”
This post is a companion to the DEF CON 28 video available here https://www.youtube.com/watch?v=aLe-xW-Ws4c
Breaking the Firmware of Samsung’s NFC Chips
Recently I have been looking into how to push the capabilities of my old smartphones beyond what you could traditionally do just by rooting it. Smartphones contain huge amounts of hardware which are well used for their standard purposes, but are often locked down to the point where you can’t modify their behaviour more than superficially. However, when they can be pushed, phones can be used as effective attack tools.
Wi-Fi Monitor Mode
Monitor mode is often used with standard wireless dongles to sniff Wi-Fi traffic and crack WPA keys. By changing the mode of a wireless dongle to receive all traffic at a lower level than what is traditionally provided by the interface, it is possible to gain access to a huge amount of previously inaccessible information.
On smartphones, there are a few ways this can be implemented. Many Qualcomm Snapdragon CPUs support this outright, and with root access on a phone one can use the following command to put their device in this mode:
echo 4 > /sys/module/wlan/parameters/con_mode
On Broadcom chipsets, this can be slightly more complicated, requiring kernel patches and custom firmware blobs, however there is publicly available information about how to pull this off.
Many Android-based security tools support this functionality outright, however it is also possible to create your own setup using chrooted environments to utilise these features.
On all of my rooted phones I place a Debian-based chroot into the core data partition. This can easily be generated in Debian-based Operating Systems using the “qemu-debootstrap” command, which create a Debian root filesystem of your selected CPU architecture and version. I use this in conjunction with the following commands, which I usually place in the “/data/local/userinit.sh” file on my phones (a script which often runs on startup on Android devices) in order to provide this chrooted environment with access to the phone’s hardware without affecting the core Android Operating System, as well as starting an SSH server which takes you straight into this environment.
mount -o remount,rw /data mount --bind /proc /data/debian_arm64/proc mount --bind /sys /data/debian_arm64/sys mount --bind /dev /data/debian_arm64/dev mount devpts /data/debian_arm64/dev/pts -t devpts chroot /data/debian_arm64/ /bin/bash --login -c /usr/sbin/sshd &
USB Device Emulation
Linux, especially on embedded devices, is capable of acting as a USB device. On Android, the kernel is usually compiled to support very specific features which would be of use to the user, such as MTP, PTP, tethering, and for more advanced users, ADB. This is usually compiled as a static feature of the kernel, with limited modification based on device files in the device’s /sys/ directory. By downloading the phone’s kernel’s source code, which can usually be found online, one can alter the configuration to enable a large number of modules which all perform different features.
My favourite of these is the Gadget Filesystem, a module which allows the userspace of the OS to control the USB functionality. This allows embedded Linux devices to behave as any USB device, as long as it is configured correctly. All that is required to do so is the ability to access files in your preferred language. I often do this in C. You can use this to emulate devices in full, or to exploit weaknesses in USB stacks.
While all of these features are interesting and readily available, I found that there was a lack of research in the low-level functionality of NFC chips used in Android devices. While a standard Android device is capable of acting as a reader, having some limited tag emulation functionality, and is capable of being used as a relay tool for some high level data, it is limited in functionality that one would usually find in an NFC attack tool.
Proxmarks, Chameleons, and rudimentary tools are capable of communicating over NFC at a raw level, and are capable of performing attacks that simply wouldn’t be possible on a phone, even with root access. The reason for this is that phones communicate via NFC by communicating with a secondary chip, built specifically for this purpose. I decided to select a target phone, and see whether I could modify the firmware of its NFC chip in order to turn a standard smartphone into an NFC attack tool.
Samsung S6 – SM-G920F
My first target was my Samsung S6. This was an older smartphone I had available, and had set up for this purpose. I had played around with its capabilities before, with kernel modifications and replacement of the Android Operating System with a basic Debian setup, and thought it would yield some interesting results.
Looking into the filesystem of the phone, and some logs, I found that it was using a chip developed by Samsung Semiconductor, and that this was rarely found in teardowns online as the US version of this phone used an entirely different chipset. All non-US versions used this chip however.
NFC Controller – S3FWRN5
The chip was found to be the S3FWRN5, a chip developed in 2014 and found in both the Galaxy S6 and Note 4 phones. Research on this chip and its capabilities online showed that a key feature of it was securely updating the firmware, meaning that there was likely to be a firmware binary somewhere in the filesystem of the phone.
I managed to purchase the chips themselves online, however I decided I wanted to do all of my reverse engineering from the phone, and did not end up using them for the project.
When you are looking at hardware communication on an Android device, it is pretty much the same as looking at it on any embedded Linux device. Via ADB, it was possible to navigate to the “/dev/” folder, and view the available device files. Looking at the kernel source for Samsung phones gave me quick insight into how the chip was communicated with from the phone, and noted that it used I2C for communication, and GPIO pins for setting power modes. These are easily accessed in the filesystem via “/dev/i2c-*” and “/dev/gpio*” files.
However, the kernel driver for the chip was found to have abstracted this to a single device file: “/dev/sec-nfc”. This file utilised IOCTLs for setting the power and mode, and could be written to and read from to send and receive data.
As standard NFC chips communicate using a standard protocol called NCI. This protocol is constructed of basic command headers and is used to both extract and restrict functionality in ways that are intended to reduce the complexity of the interaction. Each NCI command is made up of the following elements:
- GID – Byte containing identifier of functionality group (Core, RF, Vendor Specific)
- OID – Byte containing identifier of specific operation
- Length – Byte containing the length of parameters
- Payload – Up to 0xFC bytes of data related to the operation
The capabilities of this protocol are vast but do reduce the complexity of NFC operations, relegating the intricacies of communication to the chip itself.
Non-standard NCI Functionality
NCI has built into the protocol several elements which allow manufacturers to expand its capabilities beyond what is required as standard. There are various purposes for this, such as configuration information which is specific to the chip, or adding hidden functionality to the chip.
The most key of these is the Group ID 0xF. This group is in place specifically for vendor-specific commands, and allows for adding any non-standard functionality. One can brute-force their way through these commands, even if they’re not documented, by sending commands with incrementing Operation IDs and checking error responses. In NCI, these functions are most likely to contain interesting or exploitable features, as they have no documented standards.
A good example of these are the following commands, utilised by Samsung’s S3FWRN5 to set frequency values for communication.
Firmware Updates on the S3FWRN5
Firmware updates for all NFC chips I have looked at all use their own protocols. While these still use the same endpoint as the core NCI communication, in the case of the S3FWRN5 this was I2C, the protocols themselves were different, and often required being placed into a special mode to perform these operations. In the S3FWRN5 the chip was set into bootloader mode via an IOCTL, allowing for firmware updates to occur.
I found that the firmware update files were easy to find in the “/vendor/firmware/” partition of my phone, in the file “sec_s3fwrn5p_firmware.bin”
I wanted to trace a full firmware update, and hopefully log how it was performed. This was done to speed up analysis as, while the source code for updates were online, I felt it would be quicker to analyse the actual communication, and give me a more intricate view of how updates were performed. To this end, I modified the “.rc” configuration files on my phone, specifically the ones related to the NFC chip. I found a file that contained the firmware directory, as well as configurations to increase the data tracing level and even whether the phone always performed firmware updates on startup. I modified this to suit my needs.
Firmware Update Protocol Analysis
With these features modified I found that I could trace firmware updates via Logcat whenever I enabled NFC on my phone. Filtering this data allowed me to gain a full view of each aspect of the update process.
It was easy to note from this log how updates were performed. A four byte header was used, followed by subsequent payload data. The following addresses were noted for each command.
- 0x00: Command type
- 0x01: Command
- 0x02-0x03: Payload size
- 0x04-0x100: Payload data
It was also noted that every alternating command set the upper bit of the first byte of the transmission.
S3FWRN5 Firmware File Analysis
I took a further look at the update file I had after assessing the details of these updates. This allowed me to note which aspects were metadata and which parts were worth looking into for emulating updates.
An obvious datestamp (highlighted in red) was noted, followed by what was likely a version number, then some address information, which was likely to be metadata about specific aspects of the file.
In green I noticed a large number of high entropy bytes, this was very likely to be a cryptographic signature, and its start address was noted in the metadata. The existence of a signature meant that this would not just be a reverse engineering project, but require a signature bypass exploit.
Finally, in blue I noticed data which was much more uniform, and was likely the start of the firmware. This was corroborated by looking at the commands sent in updates. The low entropy of this firmware meant that it was also unencrypted.
Identifying CPU Architecture
A quick win would be to know what architecture the firmware was in. As this was a raw binary file this would not provide any specifics on architecture or any other information about the chip. The chip was likely to either utilise the 8051 architecture, often found in embedded NFC chips, or ARM Thumb, which I have found to be the most commonly used architecture in embedded chipsets in recent years.
I decided to check for Thumb code first, as there’s a quick mnemonic that would allow me to instantly know whether this was the case. A common operation in Thumb is “BX LR”, an operation used to return in functions where no registers have been pushed or popped by branching to the Link Register. Due to the nature of this operation, it’ll be found in Thumb firmware in high quantities. The opcode for “BX LR” is 0x70 0x47, which translates in ASCII to “pG”. By running the strings command on a binary and grepping for this value, you can easily tell whether a chip is using Thumb code. In this case, luckily, it was indeed utilising Thumb code.
This was excellent for a few reasons. Firstly, it meant that the chip was likely going to utilise a Cortex-M style or Securcore architecture. These are ARM based architectures with well defined standards. Additionally, Thumb code is much easier to analyse, reverse engineer, and patch than other common embedded architectures.
Implementing Firmware Updates
With these details in mind, I decided to write a tool to implement the firmware updates. If I could do this, I could then modify them to see if I could attack the chip at a bootloader-level. If I could do so, it would allow for custom firmware to be deployed, as well as make any vulnerabilities much more difficult to patch.
I wrote a C application which I could deploy to my phone in order to interact with the chip hardware. By replaying the commands I found in my traced firmware update and checking the responses, a tool could be written quickly. IOCTLs for setting mode were identified from the original kernel source.
This allowed for assessment of specific functionality including the fact that some command sent subsequent data payloads beyond the original command, as noted with the signature and SHA-1 hashed sent at the start of updates.
Firmware Update Protocol
Firmware updates were found to have a specific sequence, using incrementing numbered commands:
- 0: Reset
- 1: Boot Info
- 2: Begin Update
- 4: Update 4096 Byte Sector
- 5: Complete Update
These were again confirmed by looking at the kernel source.
The construction of this protocol and the sequence used meant that the SHA-1 hash of the firmware was verified at the end of the update process. This meant that one could write modified firmware to the chip even if they couldn’t execute it.
It was noted that no command “0x03” was present in this sequence. This heavily implied there was a hidden command there.
I surmised that if there were hidden commands, it was possible that they existed from 0x06 to 0xFF as well. I brute-forced through each command at each point of the update sequence: before updates occurred, during updates, and after updates. Error responses were used to denote how these were used: response 2 meant the command was not valid, and command 9 meant that the payload size was too small.
Hidden command 3 was found to perform the same functions as command 4, with the only exception being that command 3 utilised 512 byte payload sizes. This was not exploitable in anyway, however an additional hidden command was noted: command 6.
Hidden Command 6
Using the error responses I managed to work out that this command took eight bytes of payload data, and only functioned before firmware updates were occurring. Initially, I sent eight empty bytes, and received a different error value, but no data. Due to this, I started setting incrementing single bits through out the eight bytes.
Eventually, when this hit the first bit of the fifth byte, the command returned four bytes of data: 00 20 00 20, or 0x20002000. This was of interest because this could easily have been a stack pointer, found at the very start of most Cortex-M and Securcore chipsets as part of the Vector table.
Incrementing to the next bit found that an additional four bytes were returned as well as the original four bytes: BD 02 00 00 (0x000002BD). This could easily be the Reset Vector, found at the next address of the Vector table. At this point, it was obvious that hidden command 6 allowed for reading of arbitrary memory from the chip, with the first four bytes being the 32-bit address, and the second four being the response size. This was proven, by reading incrementing addresses in memory.
Using this command, one could dump the bootloader, firmware, RAM and hardware registers.
Dumping the Bootloader
Stitching memory from address 0x00000000 together incrementally allowed for generating of a binary file containing the bootloader in full, and showed that it was 8KB in size, and followed the same structure as any standard Cortex-M chipset. I could now perform static analysis of the firmware, however as the embedded firmware has no strings or function references, this was likely to be a long-winded task.
I initially loaded the Bootloader into IDA, and found that it disassembled nicely.
This allowed me to assess important information about how updates were performed.
The above code was found near the start of the bootloader, and was determined to be used to check the GPIO pin used for booting either the core firmware or the bootloader. This showed that a magic value (0x5AF00FA5) was checked for at address 0x3000, which was at the start of the deployed firmware and was set to 0xFFFFFFFF in the update file. If this was not present, the bootloader would not boot the updated firmware. Initially I attempted to place this value directly in the firmware being sent, however this was not effective.
It was easy to stop the code which receives I2C commands, as shown above with commands 0, 1, 2 and 6 being available before firmware updates have been performed.
Lastly, the RSA public key was noted, which is easily spotted as a large number of high entropy bytes follows by 0x00010001 (65537) the exponent traditionally used for RSA keys.
I wanted to find a memory corruption exploit in this bootloader, so that I could deploy custom firmware. There were a few limitations to this, however.
Firstly, I only had one phone, and if I bricked the NFC chip via fuzzing, I would need to buy a new one in order to continue the project. I didn’t want to do this and wanted to attack it as safely as possible.
Secondly, all communication was performed over I2C, and debugging via this method would be difficult.
I decided the best approach would be to emulate the bootloader as closely as possible to identify weaknesses.
Unicorn Engine is an excellent tool for emulating specific architectures of firmware in code. Unlike standard QEMU, which runs as an application, Unicorn allows for emulating via libraries which can be used in C or Python, and allow for hooking each step of the emulation process.
Initial setup of Unicorn Engine for my firmware was simple: I mapped all valid memory, loaded my bootloader at address 0x00000000 and set the program counter to the reset vector.
The simplicity of this was aided by the fact that the bootloader did not use threads and used an infinite loop for checking I2C commands.
There were a few complications however. Embedded chips always start with setting up hardware peripherals to standard configurations and verifying that this had occurred correctly. As these were not emulated, the firmware would constantly restart, believing that initialisation was invalid. I remedied this by NOPing out the initial setup functions.
With this, the firmware was found to run until it started constantly reading from address 0x40022030. This is a hardware address used to read information about a specific element of the chip’s peripherals. Looking at why it was reading this address found that it was checking for specific bits, and infinite-looping until it found the right one. I believed that this was likely to be the status register for the I2C interface, which meant we were on the right track. Using Unicorn’s hooking functions, I made this address always return random values, so that the specific bit was set.
Next, the chip was found to constantly read in bytes from address 0x40022038, close to the original address. As this was reading in data continuously, this was assumed to be the chip’s I2C FIFO buffer. I made my code start sending bootloader commands via this address, to see what would happen.
Once I had done this, I found the firmware would start writing to address 0x40022034, between both previous addresses. By printing these written values, I found that it was sending responses similar to the original bootloader. This meant that I had enough superficial emulation of the chip to start fuzzing.
Memory Corruption Opportunities
I could now start randomised fuzzing without worrying about bricking the chip, and with debugging capabilities. There were a few opportunities in this, including the usage of 16-bit values for payload sizes, which was larger than the 8KB of RAM available on the chip, and the usage of chunked additional data after commands during updates.
The most likely area where I believed there would be the possibility of memory corruption was in the firmware update initialisation function. This function worked by sending the size of the SHA-1 hash of the firmware (0x14), and the size of the signature (0x80). After this, chunked data was sent containing this hash and signature.
Initially, attempts were made to shorten these values in an attempt to manipulate how much data was compared between the hash and the signature, however this was an ineffective approach.
With my emulated bootloader, I increased the size of the hash, and then made the chunked data match that size. I found that this allowed increasingly large amounts of data to be sent using this command until eventually the emulated bootloader hit an exception due to memory being out of bounds, accessing memory outside the 8KB of RAM on the chip.
This likely meant that I had found a buffer overflow vulnerability, which would allow me to override the stack and manipulate the Link Register values currently stored in it. This was a usable memory corruption vulnerability.
I wanted to write shellcode for this vulnerability in order to aid the running of custom firmware. As this was an embedded chip there was not going to be any ASLR or complex mitigations against buffer overflows, however there was an unexpected problem.
Most Cortex-M style chipsets are capable of executing code from both the flash and RAM. This means that if you find a buffer overflow you can easily write shellcode and jump into it, however investigation into marketing materials related to this chip found that it was based on the SC000 SecurCore architecture, a standard similar to Cortex-M, but with some security limitations that are not publicly defined. As my shellcode did not work on the real chip, I assumed one of the limitations was the inability to execute from RAM, which meant I would have to take a different approach.
ROP exploits are often usable in circumstances like this. By manipulation of the stack to adjust registers and jump to the ends of functions, it is possible to perform a specific calculations using only the available codebase. However, it was noted that the stack at this point in the bootloader process was extremely small, made up of only 4 32-bit words at the point of the buffer overflow taking effect.
As these two approaches were not possible, I decided to keep it simple. Initially, I wanted to overwrite the magic number at the start of firmware using the exploit in order to allow it to display itself as a valid firmware, however I found that it would be just as effective to jump into this firmware from the bootloader. I performed this by overwriting the stored Link Register with a pointer to address 0x016d. It should be noted that all Thumb code always has the lowest bit set to differentiate itself from standard 32-bit ARM code, and this is why the pointer is off-by-one.
This code loaded the address of the firmware in memory (0x3000), got its reset vector, stored at 0x300c, and then jumped to this code. This simple approach bypassed all of the signature checks and meant that I had jumped straight into my unsigned firmware.
I performed this attack on the physical chip, with a basic modification of the version number, used because it was simple to retrieve via NCI.
I could now run any custom firmware via this approach. I disclosed this vulnerability to Samsung, as it compromised all of the integrity mechanisms on the chip.
The way I saw it, there were two approaches which could be taken to remediate this vulnerability:
Patch the bootloader from the main firmware, removing the buffer overflow
This could brick the chip, as the core bootloader would be overwritten
Patch the Kernel to disallow large hashes and signatures
Trivially bypassed by kernel modification or direct I2C access
Due to these mitigations being ineffectual, it was unlikely that the vulnerability would be patched.
As I had found this vulnerability on an older chip, I thought that it would likely be on a new one too. I navigated to Samsung Semiconductor’s website and found that they had a list of Mobile NFC chips they were currently advertising.
Searching for these chips online did not provide a huge amount of usable information, but I assumed that they were likely to be more modern Samsung smartphones. I downloaded ROMs for the non-US versions of all of Samsung’s phones newer than the S6 using a ROM archive, and started extracting them.
The goal of this extraction was to identify the firmware binaries stored in the “/vendor/” partition on the phone, as these would likely be named similarly to those in the S6. Occasionally, the vendor partition is not stored in ROM and can only be found on the phone itself. Due to this, sometimes finding firmware files can be difficult.
I settled on purchasing a Samsung S9, which I found used the S3NRN82 NFC Controller, the latest chip available in smartphones. I found that this was likely also present in the Samsung S8 and S10, however I decided the S9 would be a good choice.
As the phone allowed for OEM unlocking and Custom ROMs, root access was attained.
Looking at the firmware file found that it was formatted similarly to the S3FWRN5 firmware, which meant that it was likely using a similar general architecture. However, I noted that the stack pointer had changed from 0x20002000 to 0x20003000, meaning that there was more RAM available. The Reset Vector was also lower, moved from 0x3021 to 0x2021, meaning that the bootloader had likely been changed too. The firmware size was 32KB larger, implying that more flash was available.
Replicating the Vulnerability
I wanted to replicate the original vulnerability as closely as possible, to prove it was still present. As such, I took the same approach.
I found that hidden commands 3 and 6 were now no longer present, so I was no longer able to arbitrarily read memory from the chip, and that a new command 7 was available, which did nothing but restart the chip.
As I had no memory readout, any exploitation I performed would have to be done blind. This would be difficult as the bootloader had likely been modified in unexpected ways.
Lastly, I found that my firmware update tool no longer worked, seemingly due to the SHA-1 hashes no longer being valid with the signature.
Modifying the Exploit
I2C communication was found to no longer be printed via Logcat, and this option was fully removed from the phone’s configurations. Due to this, it was difficult to trace any changes to the update process. Eventually, I found a file called “/proc/nfclog” which contained data about when IOCTLs were performed and the sizes of payloads being sent to the chip.
With this, I could trace the new firmware update process, which allowed me to note that at the start of firmware updates, the hash chunk being sent was of size 0x24. This was the 4 byte command header and a 32 byte hash. I assumed this meant that the chip used SHA-256 instead of SHA-1, and luckily this was the case.
As there was no code to work with, all exploitation was performed blind, using assumptions about how the chip worked and knowledge garnered from the original chip.
Firstly, I wanted to prove that the buffer overflow was still present. To this end, I increased the hash sizes until the chip did not respond after receiving too much data. I could find the exact size of the stack this way by noting whether the chip provided no response after the last command was sent, or if it responded then crashed. If it was the latter, it was still with in the bounds of the chip’s RAM.
Now that I knew the size of the stack, I flooded it with 32-bit pointers. This means that I would likely hit any Link Register values as soon as possible, and any modified registers were unlikely to cause issues.
As I didn’t know where I needed to jump to in the bootloader, because I hadn’t seen it yet, I decided to take a brute-force approach.
Starting from 0x0001, the lowest address on the chip, I flooded the stack, and then attempted to execute the firmware as I had in the S3FWRN5. If the chip did not respond to NCI requests at this point, the correct place had not been hit, and I power-cycled the chip, incremented the address by 2, and tried again. Eventually this led to the exploit working at address 0x0165.
As I had replicated this vulnerability, I disclosed it to Samsung, as it was likely they would want to remediate it. Remediation was performed by them on all newly manufactured chips, as well as chips currently in development, as of April 2020. Despite this, it would still be possible to exploit this vulnerability and run custom firmware on currently available chips.
Creating Custom Firmware
While modifying version numbers is a good proof of concept, it does not exploit the full capabilities of an embedded chip of this type. By modifying the firmware, I could potentially emulate different 13.56MHz NFC tags, sniff NFC communication, or perform reader-based attacks.
I decided to make my initial goal simple: dumping the bootloader. I wanted to do this via the I2C interface, as it would then facilitate later debugging.
I wanted to modify an existing firmware and add my own features, so to start with I decided to find the firmware with the most blank space. I downloaded an old Samsung S8 ROM and extracted its S3NRN82 firmware binary. Looking at this I found around 0x1800 bytes of FF values. This denoted blank memory in flash where I could dump my own code as needed without the potential of breaking existing code by overwriting it.
I wanted to write my custom code in C, as it’s much easier than writing raw assembly. To do this I used the ARM gcc compiler, and use the “-c” flag. This flag tells the compiler not to do any linking or relocation, and just compile a basic object file. With this, you cannot access standard libraries or use a heap, but stack handling is accounted for. I started with a basic C function.
In C, function calls are performed as Branch and Link instructions, which store the current Program Counter in the Link Register and then jump relatively to the current position. This essentially works to allow for returning from the called function. As my C functions compiled with appropriate returns and stack handling, I could simply jump to these as needed by overwriting existing Branch and Link instructions in the real firmware.
Branch and Link instructions use Two’s compliment relative addresses, which allows for jumping to code before and after the current position. It can be a bit complex to work out over and over again, so as I was writing my build tool, I wrote a function which performed these calculations as needed.
I expanded on this build application so that I could generate appropriate function relocations. This meant that I could call functions from within functions in my custom C code, allowing me to implement as much functionality as needed, but also more easily gain addresses in the code relative to my custom functions.
At its most basic form, this tool compiled my C functions, dumped them into the firmware payload, and overwrote my desired functions.
I decided to overwrite a vendor-specific NCI function “2F 24” as I found I could send arbitrary-length parameters to it, and that it didn’t serve any meaningful function on the device that I could see.
I searched for where the NCI response may be set up, which started with “4F 24”, I searched using regex in IDA for “MOVS.*#0x24” which took me to one function call, which was definitely the function I needed to modify.
Brief analysis showed me that “sub_119BE” sent I2C responses, so I elected to overwrite “sub_11A76”, as this would allow me to modify the response data and disallow anything else from doing so.
I wanted to be able to receive parameters, such as addresses for memory reads, but at this stage I had no idea where they would be stored in RAM. To find this, I crafted an NCI request: “2F 24 04 FA CE FA CE” and then searched for this arbitrary, yet unique value. I then made the code dump this address so I could read from it in the function as needed.
With this all in place, I could write code which dumped arbitrary memory. I used this to dump the bootloader.
I disassembled the new bootloader to see what code I was hitting for my exploit.
I found that the code at address 0x0165 was reading from an unknown address stored in R0, and then the exploit worked. I decided to modify it to match the S3FWRN5’s exploit.
As I knew I could write custom firmware, I decided to use arbitrary emulation of NFC tags as an initial example of what could be done.
The chips support common 13.56MHz protocols, such as ISO14443a and ISO14443b, and in reader mode supported a large number of tag types based on these standards. I wanted to emulate a Mifare Classic tag in full, so started looking at how ISO14443a was utilised. I used a proxmark to debug this process.
To get the phone set up to perform as a tag in its default state, I replayed the NCI commands to start the chip in order. I then went through them in sequence and removed ones that did not affect the NFC communication.
I found that the last command sent was the RF discover command, and that I could modify this to only act as an ISO14443a tag and nothing else.
I’d need to learn a lot about the intricacies of the hardware capability of the chip, so started to perform analysis of the firmware I was working with. I started by searching for comparisons on the ISO14443a SELECT command (0x93), to see where it was used. The first result provided immediate information about the hardware.
Addresses starting with 0x4xxxxxxx are generally hardware peripherals in Cortex-M chips, meaning that the 0x93 value was being compared from something in that memory space. Using my previous arbitrary read command, I dumped all of the memory in 0x40020000, as this was likely to handle NFC communication on the chip, and each hardware peripheral is addressed individually.
Looking at these addresses while the phone was on an NFC reader showed the NFC WUPA (0x52) command, used in enumeration, meaning that this was the right hardware address for communication. This also showed some configuration data at lower addresses.
The first thing that needed to be changed in order to emulate different NFC tags was enumeration information. Communication with an NFC tag always begins with getting information about its type and its unique identifier. This is made up of the following values:
- ATQA – defined by NCI
- SAK – defined by NCI
- UID – randomised on phones, with the first byte always 0x08
The ATQA and SAK values are used to defined tag types and UID sizes, and while these could be set via NCI, specific bits were set to prevent spoofing of specific tag types.
As these were used for enumeration, which has some time constraints over normal communication, it was likely it used specific hardware registers to provide swift responses.
I wrote easily identifiable ATQA and SAK values via NCI then searched RAM. This allowed me to know what static memory was accessed and led me to the function which set up these values in the firmware.
I overrode this function with my own C function, called it from this newly created function to perform all other hardware setup, and then modified to the SAK, ATQA and UID values as I wanted them.
I used the proxmark to read the phone using this custom firmware, and noted that this was successful.
Next, I wanted to override the full communication of the chip. I started by searching for the HALT (0x50) and RATS (0xE0) commands in the firmware, as these were known to be supported by the firmware in its current form. Searching for the RATS value as a comparison yielded four results, one of which was the state machine for NFC communication in the firmware.
Following this function provided me information about how responses were performed, and allowed me to write C code to perform this for myself.
I overrode the state machine so that I could add my own functionality.
I implemented a basic read command, done on most Mifare tags using 0x30 as the command value, with a block and then a CRC. I implemented this to provide basic functionality to the tag which could be expanded on later.
I fully implemented Mifare Classic communication at this point, as I knew the protocol well and had code ready to patch in. I also added some debugging functions for NFC.
Of note, however, was the HALT command, which was found to perform some modifications to the hardware state machine. In order to not have to bother with working out how this worked, I called the function call which handled halts in the core firmware.
With this full communication, I could authenticate and read blocks using a proxmark.
While this worked on a proxmark, it would not work on a legitimate reader. This was due to how communication with Mifare Classic works when it is encrypted.
Expanding Hardware Functionality
In ISO14443a, every 8 bits communicated has an additional bit used as parity for error checking. This is set to a 1 or a 0 depending on whether the number of set bits is odd or even. As standard, the chip generated this extra bit itself and would take 8-bit buffers in responses. This was problematic, as this bit is encrypted in legitimate Mifare communication. I felt that there was likely to be an unknown configuration in the chip’s registers which would support this, and I set out to find it.
I set and unset bits throughout the first 64 bytes of the hardware registers and assessed what each one did to responses. This had some interesting outcomes, however at address 0x40020004, the parity type was found to be modifiable by setting bit 0x4000.
What this did was allow me to set the ninth bit, however this meant I had to fit 9-bits into 8-bits. I did this by shifting data to compensate for this in the responses.
By doing this, Mifare Classic could be fully implemented.
A last addition to this firmware was dumping of writes. As I was uploading tag binary files via I2C, I wanted to them to also be modified when they were modified by a reader. To do this I simply sent I2C responses whenever a write was performed, containing the new data.
I created a basic demo for this, reading the S9 with a different phone, and showing how this emulation worked.
Tag emulation allows for spoofing of 13.56MHz access control cards, as well as more esoteric purposes that the technology used for. All other NFC functionality worked as normal, despite this patching after I reenabled it.
I felt that this approach was more subtle than a dedicated attack tool, and could have its uses if the technology was expanded, to features such as offline cracking attacks which are known for multiple NFC protocols.
I found that this emulation would work for any of the support protocols, and that by modifying hardware registers in specific ways I could also perform some rudimentary passive sniffing, and with the framework in place it is likely I could flesh out this feature.
- All outlined vulnerabilities were patched by Samsung as of April 2020
- The vulnerability required root access, but fully compromised the chip
- Phones are exploitable embedded devices, and should be treated as such
- Bootloader vulnerabilities are more common than you think, especially in phones
- Developing custom firmware for proprietary chips is challenging, but rewarding
- If an undisclosed vulnerability is found in an old chip, it’ll likely be in the new one
All code for this project can be found on GitHub: https://github.com/Iskuri/Samsung-NFC-Controller-Reverse-Engineering