There are new articles available, click to refresh the page.
Before yesterdayInterrupt Labs

How to Speak your Hardware’s Language

By: Joel C
18 February 2022 at 17:48

Note: This article was created to sit alongside training that was run at a conference recently. The article can be used in it’s own right, but you may need to acquire some components and tools to follow along.

The Hardware Workshop at Home edition

Ever wondered where to begin on a target that no one has ever looked at before? Once you get down to the hardware almost every target looks the same! Follow along with our “Hardware Workshop from Home” and Joel will show you how to speak the hardware language, and gain access to debugging features that are common to almost all kinds of device.


Software List

Hardware List

Wiring Guide

How to wire your hardware

This lab is aimed at people who want to be able to attack devices at the hardware level but may not have much experience with electronics or low-level communication protocols. It is intended to be an introduction to those who are just starting their journey into cyber security or who may have some experience attacking software and wish to start applying those skills to physical hardware but may not know where to start. When attacking hardware, our goals are usually a little different to when we are attacking something like a web application or piece of software. Usually, our main reason for attacking the hardware is to find a way to extract the firmware so that it can be reverse engineered. Another reason may be to try and extract secrets from the device, such as firmware signing keys allowing us to run our own custom firmware later.

If you do not have the challenge code flashed to your device, you can download the project files at the bottom of the page.

Reading PCBs

We will start out with what I normally refer to as “reading a PCB”. PCB stands for “Printed Circuit Board” and it’s the internal part of the device which you will be presented with when you first take a device apart. It is the main component to which all the electronics are attached.

The first step is a visual inspection of the PCB. With this step, we will usually be able to infer a high level, mental, flow diagram giving us a basic understanding of what the main components of the device are, and what is likely to be interesting to attack. One of our main goals at this point is also to determine which parts of the PCB are not interesting to us as attackers so we do not waste too much time reverse engineering or attacking a part of the device which is not going to yield anything useful.

Back of the PCB showing the power handling components
Front of the PCB with the processing components highlighted in yellow and interfaces highlighted in green

Power Management

The images above show a PCB taken from a device with several sections highlighted. Usually, the first area we want to identify is the power handling section of the device. The device will need power from somewhere, whether it be from a 12V power jack or a battery, and the area of the PCB near where the power is connected will contain the components used to step the voltage down to something which is more suitable for the main CPU or microcontroller and will usually include some filtering to ensure that the main processing components receive stable, clean power. This filtering can usually be easily identified by a group of filtering capacitors (usually large cylindrical components) near where the power enters the device.

Once the power handling section of the PCB has been identified, we need to find what voltage the main processing devices use since this will determine what voltage our tools need to be. The majority of electronic devices on the market today will use 3.3V or 5V with 3.3V being the most common. The easiest way to determine what voltage we need to use is to identify the voltage regulators which are used to step down (or in the case of some battery powered devices, step up) the voltage from the input voltage and regulate it at a steady 3.3 or 5 Volts. The most common voltage regulator you will come across are usually small rectangular devices with 3 pins on one side and a large tab on the other side. If you identify a component that looks like this then a simple google search for it’s datasheet should tell you whether it is a 5V or 3.3V regulator. In the case of adjustable/variable output regulators, the datasheet will give you the pinout of the device and you can use a multimeter to measure the voltage between the output and GND (ground) pins.

Once we have determined what voltage the device operates at, we can largely ignore the power handling section of the PCB.

I/O Filtering

Most devices are going to have some kind of input and output connectivity, for example, a Wi-Fi router will usually have ethernet connections and possibly a connection for an ADSL line. Connections like this, where it is expected that end users will be attaching and detaching cables and other devices, are likely to have input protection and input filtering circuitry near to the physical port. For our purposes, we can ignore these components as well since they won’t provide us with anything useful to attack.

Main Processing

The next section we want to identify, which will be useful to us as attackers, are the main processing components, these are highlighted in yellow above. Usually, there will only be one and it is likely to be the largest chip, most likely with no pins visible. The reason there will likely not be any pins visible is because many modern processors or System-on-Chips (SoCs) use a Ball Grid Array (BGA) package which means the connections to the PCB is a grid of solder joints on the underside of the device. Once you have identified potential candidate devices for the main processing devices it is worth searching the internet to try and find their datasheets. This can be done by simply entering the part number into google but be aware that some manufacturers only release the datasheets for certain devices to developers under NDA so they may not be public.

Once you’ve identified the main processing components and read through their datasheets, then the next thing we want to know is where its firmware is stored. In your search for datasheets, it’s possible that you identified some eMMC flash devices or other flash chips which are likely to store the firmware, if not, the firmware may be stored in the device itself (usually this is only the case if the firmware is fairly simple since the devices themselves don’t usually have much storage). Flash chips themselves are usually BGA packages as well and will be located near to the main CPU. On some simpler devices they may just use a small 8 pin flash chip, these can usually only store about 8MB but they’re cheap making them ideal for situations where the firmware doesn’t need to be very complicated.

Connectivity Headers

At this point, you probably have a bit of an understanding about how the pieces fit together and what each section of the PCB does, and what parts of it you’ll be wanting to attack, usually, the main processor and/or the flash. What we need is a way to communicate with the device, the next section will focus on common communication protocols but for now we have one more thing left to identify which is the physical connection headers which we will use to connect our tools.

Looking around the PCB, you will want to find any rows of holes or in some cases, if you’re lucky, rows of pins. You will be looking for a single row of 4 pins and possibly 2 rows of 3, 5, or 10 pins. This is not, however, a hard and fast rule so look for any populated or unpopulated headers. If you are really unlucky there may not be any headers at all, instead there may only be small circular pads in which case you will need to solder wires on to these pads and try to reverse engineer what they are connected to. This process is beyond the scope of this blog post but if you do end up soldering wires to the pads, I can recommend using thin silicon wire because it is much less likely to tear the pad from the PCB when you are moving things around.

If you find a single row of 4 (or maybe just 3) pins, it is likely this is a Serial connection and could give you a console on the device. If you find 2 rows of pins (usually 3 or 5) then it is possible that you have found a JTAG header and may be able to debug the device, read the contents of Flash or otherwise interact with the hardware at a very low level.

Communication Protocols

Universal (Synchronous) Asynchronous Receive/Transmit

U(S)ART, more commonly known as Serial, is simple communication protocol used mainly to provide a human interface to a hardware device, i.e., to provide technicians or end users with a console on the device which can be used for configuration when there is no screen, or the developers want to allow for extra debugging capabilities not intended for normal end users.

The physical connections required for serial communications are fairly simple. Each device has a transmit and receive (TX and RX) and the connection is made from one device’s TX to the other device’s RX. You will also need to connect the two device’s grounds together, usually there is a pin on the header connected to GND so make sure these are connected together as well otherwise you will get garbage data.

Serial communications are almost always asynchronous because it makes the connections simpler. However, this means that the two devices need to agree on some parameters beforehand so that the communication will work. The parameters which need to be configured are:

  • Baud rate
  • Number of data bits
  • Number of parity bits
  • Number of stop bits

The baud rate in any communication system refers to the symbol rate of the protocol, in a binary system such as this, the baud rate is equal to the bit rate. The lowest baud rate you will likely come across is 9600 baud but this is usually only when following hobbyist tutorials on the internet, for example if you were following an Arduino tutorial. Most devices with a Serial connection will use 115200 as the baud rate. There are few others which are possibly in use but it’s extremely unlikely that you will find anything other than 115200 so when you are configuring the serial port and you are not sure of the baud rate, start with that.

The number of data bits can be set somewhat arbitrarily although it’s most common to see 8 bits of data since this is what most computers will use as their byte width. In some rare cases you may find an ascii terminal which only uses seven data bits but this is not very likely on a modern system.

The number of parity bits can be either none, even or odd and is used for error detection/correction on the wire. Most systems (all of the ones I’ve ever seen) use skip this and don’t use any parity bits. This makes each byte slightly more efficient and doesn’t sacrifice much on a modern system.

Finally, the protocol can use either one or two stop bits. This refers to the length of time the line must stay idle before the next byte can be sent. Pretty much all systems you will come across will only use one stop bit, again, this makes the transmission slightly more efficient.

The configuration you will most likely see is referred to as 8N1 which means 8 data bits, no parity, and 1 stop bits. Different configurations will have different notation, for example 7 data bits, even parity and 1 stop bit would be 7E1.

The following image is a timing diagram showing how the voltage on the wire changes to transmit a byte using an 8N1 configuration, the diagram is read left to right. The start of the transmission is denoted by a high to low transition on the data line, this remains low for the duration of one bit (as determined by the baud rate) which is the start bit. Then, eight bits of data are transmitted, they are denoted as hexagons because they can be either one or zero depending on the byte being transmitted. There is no parity bit in this configuration, so the communication ends with the line being held high for the duration of one bit, this is the stop bit. Once this completed, the next byte can be transmitted, instantiated again with a high to low transition on the line.

UART timing diagram showing the 8N1 configuration

Now you have a bit of background on how the protocol works, you can connect to your target device. To do this you will need a USB-Serial adapter. These can be bought fairly cheaply online but if you are using the challenge hardware from the conference then you will not need one because it’s built into the device itself. Plug the device into a USB port on your computer and it should be recognised as a serial comms device. If you are using Linux, run dmesg to see what the device has been named, it’s likely to be called /dev/ttyUSB0 or /dev/ttyACM0 so look out for those. On Windows the device will show up as the next available COM port, look in device manager to see what it has been named.

In order to get a serial console on the device you will need a terminal application. On Windows, the best option is Putty which you may already have for SSH connections. On Linux, my preferred tool is minicom, but there are several alternatives including screen, miniterm and, if you like, manually configuring a tty.

Connect to the device using your preferred tool and then press the button on the end of the board to reset it. You should be presented with a screen asking for a password. You can try a few guesses but we’re probably going to need to find some other way of obtaining the password or bypassing it altogether.

A note on real world Serial usages:

Many Linux based devices like routers and switches will send their boot up output to a Serial port. It can be a good idea to connect to one of these devices with serial and send the output to a file while the device boots up. Then you can go through the output “offline” and look for anything interesting. The way these devices tend to work is that they will load a bootloader from internal flash (usually something like U-Boot) which is then used to load the main firmware from external flash memory into RAM. By carefully looking through the start up log, you may be able to find a way to interrupt the boot process before it loads the external flash. If this is possible, you may be able to modify the flash before it boots. While these changes are probably not permanent, they might be able to get you a root console on the device quite quickly. The other thing you may be able to do from a U-Boot console is read out the flash byte by byte. However, this probably isn’t the most efficient method as we will see. For example, think about the following questions:

  • At 115200 baud, how long would it take to transfer 8MB of data?
  • How much of each byte is “wasted” (start/stop bits)?
  • When you read out the data, it isn’t the raw bytes, it’s the ascii representation of the bytes in memory, along with addresses. How much data would you actually need to read out in order to dump the entire flash?
  • How much data needs to be sent to the device in order to send the commands for reading?
  • What is a realistic time to dump the whole of memory over Serial?

Serial Peripheral Interface (SPI)

Obviously, when the firmware is loaded into memory by the bootloader, it isn’t done using UART otherwise it would take at least 30 minutes to boot the device each time which would not be acceptable. UART is fine for human readable interfaces but for device-to-device communication we need something much faster.

There are several protocol options used when reading from flash devices and if you read the datasheet for the flash device you have in front of you, it will tell you what protocols it supports, and you can probably use a logic analyser to inspect the communication and get an understanding of how it works. The protocol we will focus on here is called Serial Peripheral Interface or simply, SPI.

SPI works differently to UART in that it has one master device which is responsible for orchestrating the communication and one or more peripheral devices which only read in or write out data. SPI requires four physical connections between the master and peripheral devices as shown in the diagram below.

SPI Wiring Diagram

The master is in control of the chip select (often denoted as #CS, /CS, or sometimes SS) line and the clock line which is denoted as SCK. When a peripheral’s chip select line is pulled low, it will listen for and respond to commands from the master device, in the case of flash memory, these may be read or write commands along with addresses and the data to be written. The master can only communicate with one device at a time otherwise there is a risk of bus contention as multiple devices try to communicate at once.

The timing diagram below shows a typical sequence of sending data to a peripheral device. The communication is initiated by a high to low transition on the #CS line (this line must also stay low for the duration of the transmission). Commands are sent to the peripheral on the MOSI (Master Out, Slave In) line, each bit is transmitted with a transition on the SCK line. Data is read out from the peripheral on the MISO (Master In, Slave Out) line, again data is read at each transition of the SCK line.

SPI can be run in one of 4 modes determined by the value of two bits: the CPOL or Clock Polarity bit and the CPHA or Clock Phase bit. The mode must be determined beforehand and agreed by both devices and not all peripheral devices are capable of operating in all the modes. Most common is mode zero or mode three, where both bits are one or both bits are zero.

Capturing The Password

If you have the challenge device from the conference, the way it works is to read the password from flash memory upon boot. What this means is we can use a logic analyser to capture the communication as the device boots up and determine the password as it is read from flash. Once we understand how it obtains the password, we could write our own password to the flash chip and have the device read that upon boot.

To capture the communication, you will need to hook up the logic analyser pins to the pins of the flash chip (as well as a GND connection). When I do this, I tend to use channel 0 for #CS, channel 1 for SCK and then channel 2 and 3 for MOSI and MISO. Look at the datasheet for the flash chip to determine the pinout. If you are unsure of the pinout of a particular target, you can simply connect to all the pins and capture some data. From there you will probably be able to reverse engineer what the pins are doing. The #CS pin is fairly obvious as is the clock pin. It should be clear which pins are the data, but you may not know which is MISO and which is MOSI, however, you’ve now narrowed it down enough that it’s simple to just try either configuration and see what works.

Once you have connected the logic analyser wires to the target, fire up Sigrok Pulseview. It should find your Bitmagic probe and show you eight channels. If there is a problem connecting to the device, you may need to disconnect the Nucleo board from the computer and connect the Bitmagic probe again. Once Pulseview has identified the probe, it is usually fine to plug the target device back into USB (you may find its COM port has changed though).

In order to capture the data efficiently, we need to perform a little bit of setup in Pulseview. The first thing I do (and this step is optional) is to remove the unused channels to make the display a bit cleaner. Click on “channels” and deselect the ones we don’t need (everything from channel 4-8).

Channels button highlighted here in yellow

Next, set the trigger to falling edge (the line which goes down) on the #CS line.

Select “Trigger on falling edge”

This will mean we can start a capture, but it won’t actually capture any data until it sees a transition on the chip select line. Finally, make sure that the speed is fast enough, I normally use about 1-4MHz and set the number of samples to ~100K. If you mouse over the number of samples, it will tell you how long it will capture for, the communication will happen quite quickly, but you will probably want to capture a minimum of ~10mS. When determining the capture rate, you will need to set it to something at least twice as fast as the data rate, ideally you want it to be a minimum of four times the data rate. This will prevent aliasing of the signal and losing data. Usually, it is fine to just set it as fast as possible but you may get limited by the USB bus speed at some point so you don’t want to push it too far or it may fail to capture.

Once you have everything setup, start a capture, by clicking “run” and reset the device by pushing the button. Pulseview should have captured some data but at the moment we don’t know what it is.

Most logic analyser software (Pulseview included) will have protocol decoders built in which will be able to decode the data you capture. Click on “add protocol decoder” on the top bar and search for flash (you can add a generic SPI decoder, but the flash decoder will tell you more about the commands being executed). Double click “SPI flash/EEPROM” and on the new channel which has appeared, click on the name on the left. This will bring up the configuration where we can tell the decoder which channel corresponds to which line of the SPI protocol. Set the CS, SCK, MOSI and MISO as per your connections. The software should now decode the data and will tell that a “read” command was issued and will show what data was returned. If it shows the data as hex, you can set it to display ascii in the dropdown where you configured the channels.

Captured SPI data with appropriate decoder

At this point, you should have the password. Enter it into the Serial console and see if it unlocks!


First, a quick history lesson as to what JTAG is and why it came about. As electronic devices began to get more complicated and the components became more miniaturised, designers were able to fit more components onto a single PCB and those components were becoming more difficult to program and debug. Bad solder joints were common issues on these devices and could be difficult to track down.

The solution was to create a standardised way of debugging and programming devices from different vendors which may all be on the same PCB using a single interface. The Joint Test Action Group (JTAG) was created as a collaboration between several large vendors who determined the standard.

In order for a device to be compliant, it must implement the BYPASS and IDCODE commands. This ensures that devices in the chain can be identified and can be put into a mode where they do not respond to JTAG commands. This allows other devices in the chain to be debugged without interference.

As alluded to, JTAG devices are joined together in a chain, where the data output of one goes into the data input of the next device in the chain. The data out of the last device in the chain goes back to the programmer. The point where the programmer is connected to the chain is known as the Test Action Port or TAP and is technically what people mean when they say “JTAG Port”.

Connecting To Your Target

The STM Nucleo board (The white PCB on the breadboard) has a built in JTAG debugger called an ST-Link (Specifically an ST-Link V2) and this can be used to debug the processor which is running the challenge. Luckily for us, this is all connected correctly already so all we need to do is connect the board via USB and run some software to interface with the ST-Link.

The software we will use to interface with the ST-Link is called OpenOCD, this is essentially our bridge between the physical ST-Link debugger, and GDB. Open a terminal and move to the OpenOCD directory. Then, run the following command to start OpenOCD:

OPENOCD_DIR> .\bin\openocd.exe \

-f .\share\openocd\scripts\interface\stlink.cfg \

-f .\share\openocd\scripts\target\stm32g0x.cfg

OpenOCD needs two configuration files in order to successfully connect to and control the target. One describing the interface, in this case, an ST-Link V2, and one describing the target, in our case, an STM32G0. The contents of these file could be concatenated together and provided as a single file but it’s common to simply provide two config files on the command line.

If OpenOCD successfully connects to the target, you should see something like the following output:

Open On-Chip Debugger 0.11.0 (2021-03-07-12:52)
Licensed under GNU GPL v2
For bug reports, read
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select '.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 2000 kHz
Info : STLINK V2J39M27 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.238270
Info : stm32g0x.cpu: hardware has 4 breakpoints, 2 watchpoints
Info : starting gdb server for stm32g0x.cpu on 3333
Info : Listening on port 3333 for gdb connections

The last line of this output is the most important, if everything went well you should see that OpenOCD is listening for connections from GDB on port 3333.

Leave this terminal open (OpenOCD needs to remain running) but you can minimise it if you like. Open another terminal and navigate to the folder containing arm-none-eabi-gdb.exe.

Run arm-none-eabi-gdb.exe from the terminal and at the (gdb) prompt you should be able to connect to OpenOCD by running target remote :3333

Once connected, gdb will halt the target wherever it was executing which will likely be an unknown address. At this point, gdb has no idea what it is debugging so we need to tell it. Normally, at this point in the process you will have already dumped the firmware and performed some reverse engineering. So once you get to the point of debugging over JTAG, you know where the interesting functions are and you have some idea about how you want to proceed. In our case, I have provided an .elf file which is a binary file containing the code running on the device along with symbols as well as source code embedded into the file. This will make debugging much easier for the purposes of this workshop because we can skip the part where we need to reverse engineer the application.

To perform the next part of the workshop you will need to download the ELF file which contains the application code and symbols. The link is found here Hardware_workshop.elf.

To tell gdb about the code running on the device, we load it with the file command. At the (gdb) prompt run the command file Hardware_workshop.elf (provide the full path as necessary). Once gdb has loaded the symbols we can start to explore the application. Once you are at the point where gdb has symbols loaded and the target is halted, you can start to look for ways to bypass the password. There are a few ways to do this and I’ll leave it up to you to find them. However, here are a few gdb commands which you may find useful:

i(nfo) functions – list all function symbols

i(nfo) registers – get current register values

i function_name  – show the source for function_name

b(reakpoint) main  – set breakpoint at main (or any function)

p(cast type) function(param1, param2) – call a function

set $reg = $reg + value – set a register to some value (Shown here setting it to a value stored in a register plus a fixed value)

Hopefully this has given you a bit of an introduction to the various ways you might go about attacking a piece of hardware, as with all vulnerability research, the key is usually to gain as much of an understanding about the target device as possible. This gives you the best opportunity for finding ways to subvert its operation.

Project Files

If you do not have the challenge programmed to the nucleo board, I have provided the STM32CubeIDE project below. This is the complete source code so you can make changes and play around to your heart’s content. If you do have the challenge already programmed on your device (i.e. you were at one of the workshops on the day) then this version is slightly updated compared to the version running on your device. I would recommend getting the STM Cube IDE because it includes (almost) all the tools you need to build and flash the device. You may find that the reset button does not work correctly though and this can be fixed by downloading the ST-Link Utility (sometimes called CubeProgrammer). Using this utility, you can change the “option bytes” (Target->Option bytes or alternatively ctrl-B) on the Nucleo and change “NRST_MODE” from “GPIO” to “Reset input only”.

The Project file can be found here: Hardware

If you want more of a challenge, here is the binary file (as well as the ELF file) – you can flash this directly to the board using the ST-Link Utility: Challenge

The post How to Speak your Hardware’s Language appeared first on Interrupt Labs.

So you want to work in Vulnerability Research?

By: Arron S
21 March 2022 at 09:04

Curious about what it’s like to work as a Vulnerability Researcher at Interrupt Labs? Keep reading.

What is vulnerability research?

The core of vulnerability research (VR) is finding and exploiting bugs, but that’s an oversimplification of the work we do at Interrupt Labs. Finding and exploiting vulnerabilities in software and hardware targets is an involved, time consuming and often difficult process.

When asked how they’d describe vulnerability research, one of our researchers said the following:

"Taking a new device or piece of software, figuring out how it's supposed to work (down to the very lowest level) and then figuring out if it can be made to do things it's not supposed to."

We can distil this process into 3 main stages:

  • Reverse Engineering – Figuring out how the target works
  • Vulnerability Discovery – Seeing if we can make the target do something it’s not intended to do
  • Exploit Development – Turning that knowledge into a reliable proof of concept exploit

For us, this is what vulnerability research is all about. It’s not just about finding bugs, we also want to gain a thorough understanding of how something works. The more you know about a target, whether that’s a specific piece of hardware, software or an operating system, the easier it is to identify things that may be interesting. This in turn makes it easier to identify where vulnerabilities may be present.

What does an average week look like?

Each of our researchers work on projects within a dedicated team. The individual projects and goals may vary, but within each team everyone is looking at the same broad topic, such as Android, iOS or embedded devices to name a few. This allows our researchers to become experts within their field, while still allowing for variety in the individual projects that are ongoing.

Project work is where our vulnerability researchers will spend the bulk of their time, typically on tasks which support one of the three stages of reverse engineering, vulnerability discovery or exploit development. That could involve examining a target in a disassembler (such as IDA or Ghidra), reading source code, writing tooling such as fuzzers, crafting an exploit or just learning about a new piece of technology.

Alongside working on technical projects, there are many ways our researchers can develop both personally and professionally. We encourage everyone at Interrupt Labs to identify and complete training to improve their skills, providing both the time and resources as required. We also encourage our researchers to share what they learn, for example by developing training which can be delivered both internally and externally. Within the wider VR community we attend talks, workshops and conferences, and researchers can support recruitment efforts by attending careers fairs and university outreach events.

That’s all well and good, but what does an average week actually look like? Steph, Sam, Luke and Mike had the following to say:

Steph E - "My week starts with a team stand-up, where we discuss the research we're doing and outline our goals for the week. Monday to Wednesday I spend most of my time doing research, typically reverse engineering, developing tools to help us understand how something works and looking for security weaknesses. Towards the end of the week I spend more time working collaboratively with our customers."

Sam J - "The average week revolves around unpacking and analysing different malware, developing small tools to help my workflow and working towards goals and OKRs."

Luke G - "In an average week, I am engaged with our technical teams, understanding their projects, the progress they've made and any challenges they're facing. Externally I'm engaging with our customers to make sure they are happy and that we're doing all we can to provide them with impactful value. I spend a lot of time planning and delivering ways in which we can improve the business, and especially how we can build more strategic partnerships with our customers."

Mike A - "I'd say a standard week would likely include studying source code, reading assembly, writing scripts/running experiments to test theories, learning a new protocol that the subject uses, writing up findings and mulling ideas over with others."

A key theme here is that alongside doing technical work, we’re all very focused on engaging with our customers and exploring how we can deliver the best work possible.

So clearly we’re all very busy people. But what’s the appeal of vulnerability research over any other job?

What do you enjoy about working in vulnerability research?

Everyone at Interrupt Labs is here because we enjoy the work we do. For me, it’s the challenging but varied nature of vulnerability research that appeals and it’s also pretty cool when your job revolves around working out how to break things.

Here are some of the other things our vulnerability researchers enjoy about working in this industry.

Luke G - "I enjoy that I get to work with loads of smart and driven people, and that we're providing a function to society that is demonstrably impactful."

Mike S - "I love diving into the unknown and knowing that something you come across will be a unique challenge."

Sam J - "I enjoy that the research is often quite open-ended meaning you have the opportunity to drill into the areas you find the most interesting or excel at."

Steph E - "Constantly being able to learn something new, that there is no shortage of challenging problems to solve and we have the freedom to be creative in solving those problems."

It’s pretty clear that the main things drawing people to the VR industry are how varied the work is and it’s unique challenging nature. As Luke says, vulnerability research can have significant real world impact, which makes solving those difficult challenges all the more rewarding.

How do I break into the industry?

One of the most common things we hear at Interrupt Labs is that people aren’t aware that vulnerability research can be a full time career choice. I was at university before joining Interrupt Labs, and when looking for a graduate job I noticed many organisations gave their staff time to do research but very few focused entirely on vulnerability research.

Once people realise full time VR is a career choice, the next question we tend to get is “What do I need to do to get a job?” Vulnerability research can be quite broad, so there’s no one size fits all answer. But some generic skills include:

  • Reverse engineering and experience with associated tools such as debuggers (e.g. GDB) and disassemblers (e.g. IDA Pro or Ghidra). It’s also good if you’re inquisitive and want to find out how things work.
  • An understanding of techniques for vulnerability discovery and exploitation, as well as an understanding of different types of vulnerabilities and how they may be mitigated. Example topics include buffer overflows, DEP, ASLR and fuzzing.
  • Programming, both to gain an understanding of what code is doing and so you can write your own tools and exploits.

This might seem like VR is an industry exclusively for people with a Computer Science or Cyber Security background, but that isn’t the case. While technical skills like the ones listed above are important, they typically represent much broader underlying skillsets. For example, IDA Pro is one of many tools you might use for reverse engineering, but at its core reverse engineering is all about problem solving and discovery. Likewise, vulnerability discovery and exploitation techniques are about identifying weaknesses and working out how to abuse those weaknesses to make the software or hardware do something unintended. Problem solving and lateral thinking are not exclusive to Computer Science, Cyber Security or even STEM, and at Interrupt Labs we welcome people from diverse backgrounds.

Our vulnerability researchers come from a wide range of education, training, and experience. Some of us entered the industry recently, beginning our journeys as university graduates and career changers. Others have been working in the industry for over a decade. I asked our researchers what would you do differently if you were just starting your vulnerability research journey today and these were some of the answers.

Sam J - "If I were to start learning VR today, I would learn one tool/concept at a time instead of trying to learn multiple at once."

Luke G - "I would probably be more confident at demonstrating my passion and skills - get involved more. And I'd definitely ask more questions."

Rob H - "I would get as many different electronic devices as I could get my hands on and start taking them apart. Doesn't matter what it is, it could be a router, switch, bitcoin wallet, smart meter, smart toaster, anything. The more diverse the better. It's important to build up experience not just in specific technical concepts, but also how you approach problems, ones that you may never have come across before."

James S - "I came into VR from software engineering and so I mostly learnt in my spare time. If I was going through it again, I'd tackle it from two directions. First I'd identify what exactly interests me about VR and what I find fun, then I'd make sure to have time set aside to do those things. Secondly I'd make sure to find support in the community or from mentors, find someone who is around 2 or more years ahead of where you want to be in your career and ask them for support - they'll probably say yes."

Steph E - "I think I would look to develop more formal software engineering skills (particularly C or C++) instead of picking them up along the way. Being able to understand how something is built is a really helpful skill for reverse engineering."

I want to do this!

If you’ve read this far and think vulnerability research is an industry you’d like to get involved in, the good news is we are recruiting for researchers at all levels.

If you’re new to vulnerability research, perhaps you’re a university graduate or looking for a career change, then our Vulnerability Researcher Development Programme (VRDP) might be a good fit. The programme runs yearly and at the time of writing applications are open for a September 2022 start. Successful candidates will complete a 6 month training programme which combines tutor led and independent training across a range of topics, followed by hands on experience working on real projects alongside our experienced researchers. For more information and to apply, head over to our careers page. If you’ve missed the deadline for this year, don’t worry, we’ll be opening up applications for 2023 very soon.

If you’re an experienced researcher, we also want to hear from you. Maybe you’ve got extensive experience reverse engineering a specific target, or maybe you’ve got a CVE or two under your belt. Whether you’ve got 1 or 10 years experience, we’re keen to talk to you.

The post So you want to work in Vulnerability Research? appeared first on Interrupt Labs.

Dissection of a Payment Terminal

By: Rob H
10 May 2022 at 12:26

Part 1: The hardware

Have you purchased something from a shop today? How about yesterday? Been to a restaurant lately? How about the cinema? In todays (nearly) cashless society, payment terminals (also known as card machines or card readers) can be found everywhere! From supermarkets and bars to market stalls and petrol stations; anywhere that monetary transactions can be made, you’ll usually find a payment terminal somewhere nearby.

Despite these devices being found everywhere in day to day life, not much is known about how they work, and for good reason (I’m looking at you exploit-craftin’ money-stealin’ ski-mask-wearin’ scallywags). But these mysterious black boxes piqued my interest, and I had to know more.

This series of blog posts will document my research into Ingenico payment terminals, including topics such as:

  • How the device works
  • How some of the device’s physical tamper protection mechanisms work
  • The files and network protocols I’ve deconstructed and how I deconstructed them
  • The tooling I’ve created to communicate with the device
  • How I dumped the firmware off of the flash chip

This first post will cover:

  • The device’s external features and internal components
  • How the terminal protects itself using physical tamper protection mechanisms


One of the most common payment terminals you can find in the UK is the Ingenico iPP350. You will probably recognize it.

Fig. 1 – The main character of the story

The iPP350 (an abbreviation of ‘Ingenico Pin Pad’) is part of a family of payment terminals manufactured by Ingenico that run on the Telium 2 operating system.

In terms of external physical features, the iPP350 is fairly simple:

Fig. 2 – Sunny side up Fig. 3 – Sunny side down

The function keys are used to navigate the terminal’s settings and open various menus (depending on what applications are installed). This terminal supports three methods of payment: Insert, contactless, and magstripe. The contactless method is supported by an optional module that can be installed under the removable cover on the underside of the device. Also included is a MicroSD port to expand the device’s memory, and SAM modules (Secure Access Modules).

Under the hood

Opening the terminal’s case reveals a single PCB:

Fig. 4 – Take note of the white covering… Fig. 5 – The wonky sticker bugs me
more than it should

On the underside of the PCB (Fig. 5) is an Electro Magnetic Interference (EMI) shield, likely there to protect the components inside against interference from the contactless antennae. Removing this shield reveals the primary microcontroller, flash chip and ram chip.

Here’s a detailed diagram explaining all of the components:

Fig. 6 – Might be TOO detailed Fig. 7 – To PCB? Or not to PCB?

There is very little information online about the microcontroller (nicknamed “Thunder”), except that it contains an ARM9 architecture. Thunder comes in a Ball Grid Array (BGA) package and is manufactured by a company called MONEFT3X. The flash chip is a 128MB H27U1G8F2B in a 48 pin TSOP1 package and the RAM chip is an 8MB HY57V281620F in a 54 pin TSOP2 package. Both the flash and RAM chips are manufactured by a company called Hynix. Other important components present on this side of the PCB are an internal coin battery and a security enclosure for the credit card slot (we’ll talk more about this later). An assortment of test pads are scattered across the PCB, which I plan to explore in a future blog post.

Peeling back the white cover on the PCB reveals a number of additional components, the most important of which is the secondary microcontroller (nicknamed “Booster”). This microcontroller runs on an ARM7 architecture and is responsible for handling all cryptographic functions that the terminal needs to carry out (such as verifying the signatures of installed applications). Booster also holds all sensitive information found on the device such as private keys and passwords.

Fig. 8 – Booster

Now that we’ve had an overview of the device’s external and internal components, lets take a closer look at some of the more interesting parts of the device hardware…

Tamper? I hardly know ‘er!

When someone pays for something with a credit or debit card, they are trusting that the system they are using will protect them against dangers such as credit card and bank account theft. Therefore, it is important that payment terminals operate in exactly the way they are expected to once they’ve left the factory. But what if an attacker opens up one of these devices, changes something, then closes it back up again? How would you know something was different about it?

The first screen to appear when you plug in an iPP350 (after the obnoxiously loud startup beep) is blue and displays a smiley face, or in my case a sad face.

Fig. 9 – 🙁

No, I haven’t accidentally purchased a Tamagotchi, and no, I haven’t forgotten to feed him. This face indicates whether or not the device has been tampered with. The device still boots into the Telium 2 operating system, but will constantly flash one of two warnings depending on the current physical state of the device: one stating that the device is Unauthorized, and the other stating Alert irruption !!! Sounds angry right? Maybe I have forgotten to feed him…

Fig. 10 – Scary Fig. 11 – Scarier


noun /ɪˈrʌpʃn/ /ɪˈrʌpʃn/

1. the act of entering or appearing somewhere suddenly and with a lot of force

The assassination still feels like a primal catastrophe—an irruption of inexplicable evil as horrifying as any supernatural bogeyman.

Ross Douthat

​Tamper protection mechanisms are mechanisms for protecting a device against tampering (the clue is in the name). If someone attempts to gain access to the terminal’s internal components and interrupts one or more of these protection mechanisms, the device detects this as an ongoing irruption event. It then proceeds to wipe any sensitive areas containing data such as private keys from the device, and displays the Alert irruption!!! warning message. The terminal is able to detect irruption events, even if the device is not powered, thanks to the internal coin battery.

Once an irruption event is detected and the sensitive data on the terminal has been wiped, the terminal can no longer be used to process transactions. The only way that the terminal can be put back in working order is to return it to Ingenico, at which point they will reset the device and replace the secret keys.

If an irruption event occurs, but the tamper protection mechanisms are then restored, the terminal no longer registers that an irruption event is occurring. However, the secret areas of the device are still wiped, and the device is unable to operate fully without this. In this scenario the device displays the Unauthorized warning message.

So what sort of tamper protection mechanisms does the iPP350 have?

Tamper Protection Mechanism 1: Keypad

If we look at where the keypad sits on the PCB, we can see that the contacts underneath each key come in the form of two concentric rings. By bridging the connection between the inner and outer rings, the device registers a button press. Remember that white sticker covering the PCB we saw earlier? The underside of that is covered in round metallic discs which are used to bridge the connections.

Fig. 12 – These rubber keys… Fig. 13 – …press down on
these rings…
Fig. 14 – …by using these discs

However, you’ll notice that there are eight other contacts that aren’t covered by a key (see Fig. 15 below). These contacts are continuously pushed down by rubber points on the keypad all the while the PCB is contained inside the device casing. When all tamper contacts are pressed down, there are two bridges made: one between contacts 1, 2, and 8, and the other between contacts 3, 7, and 6 (see Fig 16 below).

Fig. 15 – The third outer rings are ground contacts. Fig. 16 – Right foot green

As soon as an attempt is made to open the device casing by removing the screws, the pressure that is applied to these tamper contacts is decreased, severing the connection between the inner and outer tamper contact rings.

Contacts 4 and 5 are not linked to the other tamper contacts, but are instead connected to each other through a connection across the white covering.

If you look closely at the underside of the white covering (and scratch away at the black part like a lottery ticket) you will see that the entire sticker is covered in a single trace that weaves in and out like a maze. This is so that if an attacker attempts to access the internals of the device by drilling through or cutting away at this covering, the terminal will detect that the connection between contacts 4 and 5 has been severed and will wipe the sensitive data, even if pressure to the tamper contacts continues to be applied.

Fig. 17 – Notice the LABYRINTHIAN trace, it looks like the high score on Snake

When I received the iPP350 from eBay, the Unauthorized warning was already present, meaning that an irruption event had previously occurred on the device (or that someone had wiped the secret areas). I removed the PCB from the case, after which the Alert irruption!!! message then appeared. After soldering a bridge between the inner and outer rings of all eight tamper protection contacts, the device no longer recognised an ongoing irruption event, and returned to displaying the Unauthorized message.

Fig. 18 – The PCB outside of the case but only showing the Unauthorized message

Tamper Protection Mechanism 2: Card slot

The second tamper protection mechanism is designed to protect the credit card slot. It stands to reason that this part of the terminal should get it’s own tamper protection mechanism, as it’s the component that directly communicates with a user’s credit card. If a malicious actor was able to access this part of the device without consequence, they may be able to intercept signals between the credit card and the terminal. To combat this, the internal card reader mechanism is enclosed by a thin green plastic shroud (see Fig. 19).

Fig. 19 – The lean green card-protecting enclosure

This shroud has 6 tabs that are passed through holes to the underside of the PCB and soldered down (Fig. 20). Contact points 3b and 4b are grounds, as are the thin lines around each of the other contact points. But what’s so protective about a thin piece of plastic I hear you ask? The fact that it uses the same mechanism as the white cover from earlier: a long winding trace that zig-zags all over the entire underside of the enclosure (see Fig. 21). If the enclosure gets cut or punctured and the trace is disrupted, then an irruption event is triggered and the secrets get wiped.

Fig. 20 – The six contact points Fig. 21 – If you look really closely you can see the minotaur

With the enclosure removed from the PCB, contact points 1 and 2 share continuity, as do points 5 and 6. Points 3a and 4a do not share continuity with each other, or any of the other points (see Fig. 22).

On the enclosure, contacts 1 and 6 share continuity, as do 2 and 3a, and 4a and 5. This means that when the enclosure is soldered to the PCB, the circuit is complete.

Fig. 22 – Soldering the enclosure to the PCB completes the circuit

Therefore, by wiring points 3a and 4a together, the irruption event can be stopped even when the enclosure is completely removed from the PCB.

Fig. 23 – Enclosure removed, device angry Fig. 24 – Points 3a and 4a connected with
a wire, no irruption

So there you have it folks, a quick intro to the Ingenico iPP350 payment terminal hardware, as well as a rundown of how the device is protected from physical tampering against malicious ne’er-do-wells. We’ve also learnt a new word today: irruption. Tell your friends.

Stay tuned for the sequel to this post, where we’ll look at the different modes that the iPP350 can run in, how they work, and how to interact with them (this isn’t going to be one of those things where the sequel is not as good as the first one, this will be like The Dark Knight compared to Batman Begins).

The post Dissection of a Payment Terminal appeared first on Interrupt Labs.

Converting IDA DB to VxWorks .sym

By: Joseph B
20 July 2022 at 10:10


Interrupt Labs is often asked to undertake dynamic analysis of stripped closed-source binaries. Even after recovering a lot of information through static analysis (often with IDA), the lack of symbols makes dynamic analysis challenging (using IDA’s debugging features isn’t always possible). In one particular case, we were presented with a stripped VxWorks image and managed to recover many symbols statically but were unable to use them to debug outside of IDA. I was aware that GDB could load symbols from .sym files and wondered if it was possible to extract the information from an IDA Database (.idb or .i64) into a .sym file. This post details my journey into the internals of these two file formats and will cover:

  • What a .sym file is.
  • What information needed to create a .sym file.
  • What challenges arise in extracting that information from an IDA Database.
  • What challenges arise in creating a .sym file with the extracted information.
  • How the resultant .sym file was tested.
  • A detailed appendix with information about the IDA Database format.

The tool created as a result of this research is available here.

What’s a .sym?

My first challenge was working out what a VxWorks .sym file is. I wasn’t provided with any examples and could find no decisive documentation. After some searching I discovered the following from the “VxWorks Kernel Programmer Guide 6.2”:

  • A vxWorks.sym file is created during image building if INCLUDE_STANDALONE_SYM_TBL is not set (otherwise the symbols are bundled with the image).
  • objcopy somehow has the ability to create .sym files.

With this knowledge, I began searching GitHub and eventually found a repository with VxWorks build tools. This enabled me to find the link between INCLUDE_STANDALONE_SYM_TBL and objcopy:

ifneq   ($(findstring INCLUDE_NET_SYM_TBL, $(COMPONENTS)),)
define MAKE_SYM
	$(BINXSYM) [email protected] [email protected]
	$(LDOUT_SYMS) [email protected]
EXTRACT_SYM_FLAG= --extract-symbol

Here is the relevant section from objcopy‘s man page:

    Keep the file's section flags and symbols but remove all section data.
    Specifically, the option: 
    *<removes the contents of all sections;> 
    *<sets the size of every section to zero; and> 
    *<sets the file's start address to zero.> 
    This option is used to build a .sym file for a VxWorks kernel.

This suggested that a .sym file was really just an ELF file with the non-debugging stuff removed. Now I just needed to find some examples for confirmation and reference.

I set out searching GitHub again and eventually wrote a Python script to download all files named vxworks.sym.

from itertools import count
from os import environ
from pathlib import Path
from time import sleep
from urllib.request import urlretrieve

import requests

    PAT = environ["GITHUB_PAT"]
except IndexError:
    print("Set the GITHUB_PAT environment variable to a personal access token from:")

DOWNLOAD_FOLDER = (Path(__file__).parent.parent / "vxworks" / "syms").resolve()
DOWNLOAD_FOLDER.mkdir(parents=True, exist_ok=True)

downloaded_hashes = set()

for page in count(1):
    results = requests.get(
        headers={"Authorization": f"Token {PAT}"},
        params={"q": "filename:vxworks.sym", "per_page": "100", "page": str(page)},

    if len(results) == 0:

    for result in results:
        if (
            result["name"].lower() == "vxworks.sym"
            and result["sha"] not in downloaded_hashes

                result["html_url"].replace("/blob/", "/raw/"),
                DOWNLOAD_FOLDER / f"""{result["sha"]}.sym""",



After deduplication, I ended up with 64 examples. Here is the readelf -S output for one of them:

  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 0000c0 000000 00  AX  0   0 64
  [ 2] .wrs_build_vars   PROGBITS        00000000 0000c0 000000 00  WA  0   0  1
  [ 3] .sdata2           PROGBITS        00000000 0000c0 000000 00  AX  0   0  8
  [ 4] .data             PROGBITS        00000000 0000c0 000000 00  WA  0   0  8
  [ 5] .tls_data         PROGBITS        00000000 0000c0 000000 00  WA  0   0  1
  [ 6] .tls_vars         PROGBITS        00000000 0000c0 000000 00  WA  0   0  1
  [ 7] .sdata            PROGBITS        00000000 0000c0 000000 00  WA  0   0  8
  [ 8] .sbss             NOBITS          00000000 0000c0 000000 00  WA  0   0  8
  [ 9] .bss              NOBITS          00000000 0000c0 000000 00  WA  0   0 16
  [10] .boot             PROGBITS        00000000 0000c0 000000 00      0   0  1
  [11] .reset            PROGBITS        00000000 0000c0 000000 00      0   0  1
  [12] .debug_frame      PROGBITS        00000000 0000c0 000000 00      0   0  1
  [13] .PPC.EMB.apuinfo  PROGBITS        00000000 0000c0 000000 00      0   0  1
  [14] .shstrtab         STRTAB          00000000 0000c0 000090 00      0   0  1
  [15] .symtab           SYMTAB          00000000 0003f8 01e460 10     16 3271  4
  [16] .strtab           STRTAB          00000000 01e858 01fa38 00      0   0  1

As expected, all the sections are empty except for:

  • .shstrtab – Contains strings for section names.
  • .symtab – Contains the symbol table.
  • .strtab – Contains strings for symbol names.

What to Extract?

My next task was working out what I needed to extract from the IDA Database file to create the .sym file. Luckily the ELF format is very well documented. Every symbol should have:

  • A name (stored in .strtab).
  • A value (address).
  • A type (either STT_OBJECT for a variable or STT_FUNC for a function).
  • A section reference (an index into the ELF’s section header table).

This meant that I needed to extract section information to create the section header table, along with function and variable information for the symbols themselves.

IDA Database Parsing

The next step was to parse the IDA Database and extract the necessary information. This wasn’t easy since I didn’t have access to the IDA SDK, but luckily there were some open-source tools that I could use for reference (see here).

There were two sections of the IDA Database that interested me:

  • NAM – Contains the addresses of names. Has a simple array structure.
  • ID0 – Contains almost all other useful information. Has a complex B-tree structure.

Whilst parsing NAM was simple, parsing ID0 required many steps:

  • Extracting the B-tree structures (see here for more details).
  • Converting the B-tree structures to a more Python-friendly format.
  • Writing a ranged B-tree search algorithm.
  • Implementing IDA’s proprietary “NetNode” system (see here for more details).
  • Writing the correct “NetNode” queries to extract the required information.

A detailed reference for the IDA Database format can be found here.

.sym Creation

The final problem was the creation of the .sym file. Building a program to read and write ELF files was fairly easy thanks to extensive documentation and copious examples. The challenge came from the difference between IDA section metadata and ELF section metadata.

IDA sections have:

  • A name.
  • A start address.
  • A size.
  • Three flags: read, write and execute (having none of these indicates unknown permissions).

ELF sections have:

  • A name.
  • A start address.
  • A size.
  • Over 10 flags.
  • A type.

To resolve this difference I needed to make a few educated guesses:

  • For flags:
    • Start with any that can be inferred from the section name.
    • Add SHF_ALLOC if the section name is not recognised.
    • Add SHF_WRITE if the section’s write flag is set (or it has no flags set).
    • Add SHF_EXECINSTR if the section’s execute flag is set (or it has no flags set).
  • For type:
    • If it can be inferred from the section name, use that.
    • Otherwise use SHF_PROGBITS.


To test I started by writing a simple C program:

#include <stdio.h>

int number = 0;

void or() {
    number |= 1;
    printf("Number: %d\n", number);

void shift() {
    number <<= 1;
    printf("Number: %d\n", number);

int main() {
    return 0;

And compiled it (stripping symbols):

gcc test.c -no-pie -s -o test

Next, I had an IDA Database created for the program with the global variable number and function or labelled. I then used the tool to convert the database to a .sym file:

python -m sc -i test.i64 -s test.sym -e little --type EXEC --machine X86_64

After loading the program into GDB I ran the following:

(gdb) symbol-file test.sym 
Reading symbols from test.sym...
(No debugging symbols found in test.sym)
(gdb) b or
Breakpoint 1 at 0x401136
(gdb) r
Starting program: /mnt/hgfs/symbols-converter/vxworks/test 

Breakpoint 1, 0x0000000000401136 in ?? ()
(gdb) p (int) number
$1 = 0
(gdb) si 10
0x000000000040115f in ?? ()
(gdb) p (int) number
$2 = 1

Which shows that the .sym file can be loaded and allows debugging as expected.


I ended up extending the tool by adding a few more features:

  • An option to include automatic (sub_) function names.
  • An option to input from a Ghidra XML export rather than an IDA Database.
  • An option to export to JSON or text rather than a .sym file.

It is available here.

Appendix – IDB


This appendix will talk about the structure of an IDA Database (.idb or .i64) file. IDA databases can contain up to six sections:

  • ID0 – Contains most useful information.
  • ID1 – Contains flags for each byte in the binary.
  • NAM – Contains addresses of names.
  • SEG – Unknown.
  • TIL – Contains information about data types.
  • ID2 – Unknown.

Only ID0 and NAM will be covered here.

Most of this information was obtained through reviewing the code of the following amazing projects:

This appendix was created with reference to databases created in IDA Pro 7.7. Integers are unsigned and little-endian unless otherwise specified.


Offset Size Field Purpose
0x00 4 magic Should be either IDA0, IDA1 or IDA2. IDA0 and IDA1 imply that the file has a 32-bit word size, IDA2 implies that it has a 64-bit word size.
0x06 8 id0_offset Offset to the ID0 section from the start of the file.
0x0d 8 id1_offset Offset to the ID1 section from the start of the file.
0x1a 4 signature Should be 0xaabbccdd.
0x1e 2 version Should be 6.
0x20 8 nam_offset Offset to the NAM section from the start of the file.
0x28 8 seg_offset Offset to the SEG section from the start of the file.
0x30 8 til_offset Offset to the TIL section from the start of the file.
0x38 4 id0_checksum CRC32 checksum of the ID0 section.
0x3c 4 id1_checksum CRC32 checksum of the ID1 section.
0x40 4 nam_checksum CRC32 checksum of the NAM section.
0x44 4 seg_checksum CRC32 checksum of the SEG section.
0x48 4 til_checksum CRC32 checksum of the TIL section.
0x4c 8 id2_offset Offset to the ID2 section from the start of the file.
0x50 4 id2_checksum CRC32 checksum of the ID2 section.

Section Header

All sections have the following header. The section contents start immediately after.

Offset Size Field Purpose
0x00 1 compression_method 0 for no compression. 2 for Zlib compression.
0x01 8 section_length The length of the section (before decompression).



Offset Size Field Purpose
0x00 4 next_free_index The index of the next free page.
0x04 2 page_size The number of bytes occupied by a single page.
0x06 4 root_page_index The index of the root page.
0x0a 4 record_count The number of non-dead records.
0x0e 4 page_count The number of non-dead pages.
0x13 9 magic Should be B-tree v2.

page_size - 0x1c bytes of padding follow the header.



The contents of ID0 are laid out as a B-tree. A B-tree is similar to a binary search tree except that each page (collection of records) may have more than two children. Each record has a key (shown in the diagram below) and a value.


Every page starts with the following header:

Offset Size Field Purpose
0x00 4 first_page_index The index of the first (left-most) child page. If this is 0 then the page is a leaf page, otherwise, it is an index page.
0x04 2 count The number of records in the page.

After this, there is a count length array of record meta structures.

Index Record Meta
Offset Size Field Purpose
0x00 4 page_index The index of the child page to the right of the record.
0x04 2 record_offset The offset from the start of the page to the record.
Leaf Record Meta
Offset Size Field Purpose
0x00 4 indent The number of bytes to prepend to this record’s key from the start of the last (next-left) record’s key.
0x04 2 record_offset The offset from the start of the page to the record.

Each field follows immediately from the last.

Size Field Purpose
2 key_length The length of the record’s key (without indent).
key_length key The record’s key.
2 value_length The length of the record’s value.
value_length value The record’s value.

Net Nodes


Net nodes are IDA’s method of grouping records related to something (often an address). Each net node has an integer node ID. It may also have a string node ID which can be resolved to the integer node ID.

The records inside a net node are identified by a tag (single byte value) and index (4 or 8-byte value). The B-tree structure makes it efficient to find all records with a given tag.

String Node ID

String node IDs can be resolved to integer node IDs by searching the B-tree for a record with the key:


The record’s value gives the integer node ID.


Some nodes have a string name. This can be found by searching the B-tree for a record with the key:


node_id is big-endian with a size matching file’s word size (see Header). The record’s value gives the name.


You can find a record with a specific index and tag by searching for the key:


Both node_id and index are big-endian with sizes matching the file’s word size.

All records with a given tag can be found by performing a ranged search on the B-tree.

Variable Length Integers

Record values often use IDA’s proprietary variable-length integer formats.

  • Up to two bytes (T):
    • If the first byte begins with 0b11, the value is stored in the following two bytes.
    • Else if the first byte begins with 0b1, the value is stored in the remainder of the first byte and the following byte.
    • Else the value is stored in the first byte.
  • Up four bytes (U):
    • If the first byte begins with 0b111, the value is stored in the following four bytes.
    • Else if the first byte begins with 0b11, the value is stored in the remainder of the first byte and the following four bytes.
    • Else if the first byte begins with 0b1, the value is stored in the remainder of the first byte and the following byte.
    • Else the value is stored in the first byte.
  • Up to eight bytes (V) – Stored as two consecutive Us. The fist U is the lower four bytes, and the second is the upper four bytes.
  • Up to word size (W) – U if the word size if the file’s word size is 32-bits and V if it is 64-bits.

All are big-endian.


Types use the letters from Variable Length Integers. Upper-case means unsigned and lower-case means signed.

Segments (Sections)

Segment information is found in the $ funcs net node. Every record with the tag S has the format:

Type Field Description
W start The start address of the segment.
W length The length of the segment.
W name_index The index of the segment’s name in $ segstrings (covered later).
W class_index The index of the segment’s class in $ segstrings (covered later).
W org_base Dependant on the processor.
U flags Detailed here.
U alignment_codes Unknown.
U combination_codes Unknown.
U permissions Flags. 1 is read, 2 is write, 4 is execute. 0 means unknown flags.
U bitness The number of bits used for segment addressing. 0 is 16-bits, 1 is 32-bits, 2 is 64-bits.
U type Determines how the kernel deals with the segment.
U selector A unique value used to identify the segment.
U colour The segment’s colour. Subtract one for the RGBA value.

The $ segstrings net node has a record with tag S at index 0. This is an array of:


Where the string length is a single byte.


Function information is found in the $ funcs net node. Every record with the tag S begins with:

Type Field Description
W start The start address of the function chunk.
W length The length of the function chunk.
T flags Flags. 0x8000 means this is a tail chunk (it is a head chunk otherwise).

Head chunks then have the following:

Type Field Description
W frame The node ID of the frame net node.
W locals_size The size of the local variables (bytes).
T registers_size The size of the saved registers (bytes).
W arguments_size The size of the stack arguments (bytes).

And tail chunks have:

Type Field Description
w parent_offset The offset to the head chunk. Subtract this from start to get the address of the head chunk.
U referer_count The number of referrers referencing this chunk.

Every function has one head chunk and zero or more tail chunks. The name of the head chunk is the function name.

The information documented here is only part of the function information that is available.



Offset 32 Offset 64 Size Field Purpose
0x00 0x00 4 magic Should be VA* followed by a null byte.
0x08 0x08 4 non_empty 0 if the section is empty, 1 otherwise.
0x10 0x10 4 page_count The number of pages (size 0x2000) occupied by the section.
0x18 0x1c 4 name_count The number of name addresses in the section. If the file’s word size is 64-bits then this number needs to be halved.

If the file’s word size is 32-bits then 0x1fe4 bytes of padding follow the header and if it is 64-bits then 0x1fe0 bytes of padding follow it.

Name Addresses

A name_count array of integers matching the file’s word size. To resolve the integers to strings see Name.

The post Converting IDA DB to VxWorks .sym appeared first on Interrupt Labs.

Game Hacking with Binary Ninja

By: Ben R
20 July 2022 at 10:10

Most searches for game hacking will return with a plethora of articles about using CheatEngine to make alterations in memory. While this is a valid option, I wanted to challenge myself to use a different method to implement some common game cheat functionality. I decided to use a reverse engineering framework to implement patches to the game files. Not only does it avoid using the clunky CheatEngine UI, it’s also much more portable & repeatable to make permanent changes to the game file, rather than in memory.

As the never ending battle between IDA and Ghidra rages, Binary Ninja has slowly been improving in reliability and performance. Behind the aesthetic UI and intuitive keybindings is a powerful scripting API. Gone are the days of writing Java or Jython (looking at you Ghidra) – Binary Ninja supports Python and C++ by default, with Rust binding available too. This article is essentially going to be a demo of how I use the scripting API to write patch automation.

The target for the article is going to be the PwnAdventure3 Windows Client. PwnAdventure is a deliberately vulnerable video game, written for the Ghost in a Shell 2015 event, so feel free to follow along, or dive in and create some cheats for yourself.

PwnAdventure can be hosted on a server or played entirely from the game client. For the purposes of this article the game will be played entirely from the game client (offline mode).

The three most interesting files are PwnAdventure3.exe, GameLogic.dll, GameLogic.pdb

A Basic Patch

The first goal I set was to implement a speed hack, to make navigating the map less painful. This can be done in PwnAdventure by patching the GameLogic.dll file. The DLL comes with a PowerBasic Library (PBL) file too (a file containing symbols and data structures inside the DLL). Since speed is related to the player, looking in the Player class, there is a method GetSprintMultiplier

The method is simple enough, it retrieves a floating point number from a memory address and returns.

float __convention("thiscall") Player::GetSprintMultiplier(class RubicksCube* const this)
fld     st0, dword [0x10078b34] # 0x70dbca0a
retn     {__return_addr}

By increasing the value at address 0x10078b34, we can increase the rate the player increases speed, and also their maximum speed. We could automate this by writing a bigger number to the address 0x10078b34. This could be done in Binary Ninja by simply entering the following command into the Python interpreter:

bv.write(0x10078b37, b"\x7f")

This sets the highest byte of the 4 bit (little endian) signed integer to the max possible positive value. Saving the newly patched DLL and running the PwnAdventure.exe file, we now observe our speed increases rapidly until we can cross the entire map in just a few seconds.

Improving the Reliability

This works fine, but if a game update was released, the memory address that holds this value could have changed so if we ran the patch command it would just corrupt the file, which pretty much defeats the point of automating it in the first place. By dynamically identifying the memory address through scanning Player::GetSprintMultiplier for a pointer we eliminate this risk.

# get the address of the Player::GetSprintMultiplier function 
address_of_multi_func = bv.get_symbols_by_name("Player::GetSprintMultiplier")[0].address
# get the Player::GetSprintMultiplier function object
multi_func = bv.get_function_at(address_of_multi_func)
# list of all the instructions in the function, store the first one
multi_func_instr = [i for i in multi_func.llil_instructions][0]
# find the address constant in the list of tokens
for token in multi_func_instr.tokens:
    if type(token.value) == int and (token.value != 0):
        pointer_to_speed_multiplier = token.value
# write to the address
bv.write(pointer_to_speed_multiplier + 3, b"\x7f")

So that’s a fair way to increase the reliability. We could also increase the portability by removing the reliance on the PBL file.

Improving the Portability

In PwnAdventure, a player can collect spells as the progress through the game. The first spell a player gets is called Great Balls of Fire, and it costs 4 mana to perform.

Being able to perform the spell without worrying about mana levels would be pretty cool, let’s patch it out. This can be done without the PBL file by dynamically locating interesting data that exists directly inside the DLL, rather than symbols imported from the PBL file. Strings are a useful place to start here.

With the PBL file imported, the GreatBallsOfFire::GetManaCost pseudocode for the function looks like this:

enum ItemRarity __convention("thiscall") GreatBallsOfFire::GetManaCost(class Flag* const this)
    return 4;

The GetManaCost method belongs to the GreatBallsOfFire class, if we find the class vtable, we should find the method. We could locate the class by finding another method belonging to the same class, that handles a unique string. The method GreatBallsOfFire::GetFlavorText uses the string Many balls. Very fire. Ow. The process for finding GreatBallsOfFire::GetManaCost becomes:

  1. Find references to the unique string by scanning memory and finding xrefs to it. Through this, the GreatBallsOfFire::GetFlavorText can be located
  2. Parse the GreatBallsOfFire class vtable to find the GetManaCost method
  3. Patch GreatBallsOfFire::GetManaCost

So to find references to the string, the find_all_data method can be used. This takes a bytestring and address range as arguments, and searches for consecutive bytes that match the specified bytestring. This returns a generator object that can be parsed into a list.

fire_string: bytes = b"Many balls. Very fire. Ow."
fire_refs: list = list(bv.find_all_data(bv.start, bv.end, fire_string))

A Binary Ninja plugin for something like this could look like the following. The code has been commented heavily to describe the functionality of the Binary Ninja specific functions:

from binaryninja import *
import struct
def mana_hack(bv) -> None:
    Patch the GreatBallsOfFire::GetManaCost function to return 0, providing unlimited mana
    fire_string: bytes = b"Many balls. Very fire. Ow."
    # find_all_data returns a generator object containing tuples of (address, binaryninja.databuffer.DataBuffer object)
    fire_refs: list = list(bv.find_all_data(bv.start, bv.end, fire_string))
    if len(fire_refs) != 1:
        log_error(f"Invalid number of strings ({len(fire_refs)}) found for string {fire_string}. Was expecting to be unique. Exiting")
    # Use get_code_refs to find code xrefs. The address of the referencing instruction is stored in field 0
    # returns a generator of binaryninja.binaryview.ReferenceSource objects, which have an address attribute
    refs_to_string: list = [i.address for i in bv.get_code_refs(fire_refs[0][0])]
    if len(refs_to_string) != 1:
        log_error(f"Invalid number of references found for string {fire_string}. Expected 1. Exiting")
        log_info(f"Got the following reference to '{fire_string}': {hex(refs_to_string[0])}")
    # pass an address, returns a list of binaryninja.function.Function objects
    GetFlavorText_func = bv.get_functions_containing(refs_to_string[0])[0]
    # get xrefs to the start address of the function from the data segment.
    # Returns a generator of BinaryView.get_data_refs objects
    GreatBallsOfFire_ref: list = list(bv.get_data_refs(GetFlavorText_func.start)) # inside the GreatBallsOfFire class vftable
    if len(GreatBallsOfFire_ref) != 1:
        log_error(f"Invalid number of references found to GreatBallsOfFire::GetFlavorText. Expected 1. Exiting")
        start_addr: int = GreatBallsOfFire_ref[0]
        log_info(f"Got the following reference to GreatBallsOfFire::GetFlavorText: {hex(start_addr)}")
    function_found: bool = False
    # iterate through the vtable looking for a function that matches our expected function
    while function_found is not True:
        # read 4 bytes of data from the start_addr
        pointer: int = struct.unpack("I",, 4))[0]
        # returns a binaryninja.function.Function object that the provided address belongs to.
        function_ptr = bv.get_function_at(pointer)
        # create a list of hlil instructions. This will make it easier to identify the interesting
        # function without comparing individual assembly code instructions
        instruction: list = list(function_ptr.hlil.instructions)[0]
        if str(instruction) == "return 4":
            function_found = True
        elif function_ptr is None:
            # if its none, we've gone past the end of the vtable so should just give up
            log_warn("Parsed GreatBallsOfFire class but could not find GetManaCost. Exiting")
            start_addr += 4
    # read the vtable pointer to get address of GreatBallsOfFire::GetManaCost
    GetManaCost_func: int = struct.unpack("I",, 4))[0]
    log_info(f"Patching at location: {hex(GetManaCost_func)}")
    # the first instruction of the function, so we can just write to the addr directly
    # bv.write returns the number of bytes written, so if 0 then write failed
    if bv.write(GetManaCost_func, function_ptr.arch.assemble("mov eax, 0")) == 0:
        log_info(f"Writing patch to location {GetFlavorText_func} failed.")
    # Check save file operation returns True ,to alert the user if there is a save issue
    if is True:
        log_info("Patch complete. Spell 'Great Balls Of Fire' should now cost 0 Mana ")
        log_info("Could not save automatically. Attempt to save manually")
PluginCommand.register("Hack Mana", "Implement a Patch for Unlimited Mana", mana_hack)

The spell now costs no Mana. This works on dll files independently of the PBL file too making the script more portable. This could be extended further by reworking the script to work independently of the Binary Ninja, as a headless script.

For anyone following along, you may notice that the fireballs now also cause zero damage. This is because the calculation for spell damage in PwnAdventure is based on Mana cost. So, my challenge to you is to write a new Binary Ninja plugin to provide unlimited (or a huge amount of) Mana by patching the Player::UseMana or Player::GetMana functions. The snippets plugin can be useful for writing these kinds of UI scripts if you don’t want to make them full plugins. Feel free to tweet @InterruptLabs to let us know how you solved it!

The post Game Hacking with Binary Ninja appeared first on Interrupt Labs.

pipe_buffer arbitrary read write

By: Jayden R
8 November 2022 at 09:10


In this post we will look at an arbitrary read/write technique that can be used to achieve privilege escalation in a variety of Linux kernel builds. This has been used practically against Ubuntu builds, but the technique is amenable to other targets such as Android. It is particularly useful in cases where ARM64 User Access Override mitigates the common technique of setting addr_limit to ULONG_MAX.  

The pipe_buffer technique was discovered independently by the author, but a recent Blackhat talk suggested the technique is being used in the wild. The technique provides an intuitive way to gain arbitrary read/write, so we suspect that it’s been used widely for a long time. 

The technique

The technique targets the page pointer of a pipe buffer. In ordinary pipe operations, this page stores data which was written through a pipe. The data may then be loaded from the page into userspace through a read operation.  By overwriting said page pointer we’re able to read from and write to arbitrary locations in the physical address space. This includes the kernel heap, targeting sensitive objects such as task descriptors and credentials, as well the writeable pages of the kernel image itself.  

struct pipe_buffer

The pipe_buffer array is a common heap-allocated structure targeted in Linux kernel exploitation. By leaking a pipe_buffer element, we are able to deduce the kernel (virtual) base address. In overwriting the pipe_buffer we’re typically able to gain code execution. 

Each pipe is managed by the pipe_inode_info data structure. The pipe_buffer array comes automatically with every pipe. It is pointed to by the field pipe_inode_info::bufs and is treated as a ring through the pipe_inode_info::tail and pipe_inode_info::head indices.  

The array is allocated from the memcg slab caches. It may be a variety of sizes. In particular we can have our pipe_buffer array be of size n * sizeof(struct pipe_buffer) where n is a power of 2. With fcntl(pipe_fd, F_SETPIPE_SZ, PAGE_SIZE * n) we can alter the pipe ring size.  

A single element in the pipe_buffer array is structured as follows: 

struct pipe_buffer { 
       struct page *page; 
       unsigned int offset, len; 
       const struct pipe_buf_operations *ops; 
       unsigned int flags; 
       unsigned long private; 

The typical leak and overwrite target is the pipe_buf_operations pointer. This is great if a ROP chain is sufficient to gain full privileges, however on some platforms such as Android this is not sufficient. For arbitrary read/write we will use the page pointer instead. 

struct page

The kernel represents physical memory using struct page objects. These are all stored in a single array which we will call vmemmap_base (its symbol on some common platforms).  

When we write to a pipe, the kernel reserves a new page to store our data. This page can then be spliced onto other pipes or have its contents copied back out from the read-side of the pipe. 

static ssize_t pipe_write(struct kiocb *iocb, struct iov_iter *from) 
. . . 
       for (;;) { 
. . . 
              if (!pipe_full(head, pipe->tail, pipe->max_usage)) { 
. . . 
                     struct page *page = pipe->tmp_page; 
. . . 
                     if (!page) { 
                            page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT); 
                            if (unlikely(!page)) { 
                                   ret = ret ? : -ENOMEM; 
                            pipe->tmp_page = page; 
. . . 
/* Insert it into the buffer array */ 
buf = &pipe->bufs[head & mask]; 
buf->page = page; 
buf->ops = &anon_pipe_buf_ops; 
buf->offset = 0; 
buf->len = 0; 
. . . 
copied = copy_page_from_iter(page, 0, PAGE_SIZE, from); 

As we can see here, alloc_page() returns a new page from the page allocator. A pipe_buffer is then initialised to encapsulate the page. The user-supplied contents are copied into it. The exact mechanics of the page allocator is outside the scope of this post, but just think of it as a free-list of available physical pages.  

The central question for this technique is: assuming we can corrupt a pipe_buffer, are we able to set pipe_buffer::page to the struct page representing a sensitive region of memory? We will look at two applications. The first targets heap memory and involves straightforward arithmetic. The second targets the kernel image itself and may require some additional brute-forcing. 

Read and write into the kernel heap 

We will further split this into two cases. In the first case, we assume that we know where a target object is in the heap. In the second case, we assume that we don’t know where a target object is in memory and we need to find it.

With only a leaked struct page pointer we can:

  • Deduce the vmemmap_base address
  • Calculate the physical page loadpoint of the heap base
  • Repeatedly increment, scale, and rewrite the page pointer to seek across the heap

Suppose we’re targeting the object with virtual address 0xffff98784d431d00 and we’ve leaked the struct page address 0xffffebea044d9f00. Both are randomized with KASLR. 

Through the mask 0xfffffffff0000000 & 0xffffebea044d9f00 we get 0xffffebea00000000 for vmemmap_base

First, we ask the question: how can we choose the struct page which corresponds to the target object in the heap? Clearly, this target struct page will be vmemmap + offset. But what offset? Since the vmemmap array corresponds directly to physical memory and since the heap base is not typically (physically) randomized, we can use the simple formula:

+ ((0x100000000 >> 12) * 0x40)
+ (((target_object & 0xffffffff) >> 12) * 0x40) 

Indexing the target object’s page

The result of this formula gives the virtual address for the struct page element of the vmemmap array corresponding to the physical page which underlies our target object. A few questions remain: what is that 0x100000000? Why shift by 12? Why scale by 0x40? 

Luckily for us, the physical heap base is not randomized. It starts at physical address 0x100000000.  The 12-shift returns the “index” of the page in memory. For example, the address 0x100000000 corresponds to the 0x100000000 >> 12 page of memory. Finally, the 0x40-scale corrects the bytes offset in the vmemmap_base array according to the size of the elements. In other words, 0x40 is the size of a single struct page. The analogous operation is:
int x[N]; int y = x[3]; which retrieves the value at &x + (3 * sizeof(int))

So in plain words, the formula says: 
Take the vmmemap base address and displace up to the struct page of the first physical page of heap memory. Then displace by the number of pages between the bottom of the heap and the target object. 

If we set the pipe_buffer::page to the result then we will be able to read/write to the page of the target object. Note that objects can lie over multiple pages. So it’s important to determine the page relative to the target field(s) of an object rather than just from the beginning of the object.  

This can be used to set the pipe_buffer fields as follows: 

uint64_t cred_page = virt_to_page(target_obj, vbase); 
uint64_t cred_off = (target_obj & 0xfff); 
pbuf->page = (long *)cred_page; 
pbuf->offset = cred_off + 0x4; 
pbuf->len = 0; 
pbuf->ops = (long *)FAKE_OPS; 
pbuf->flags = PIPE_BUF_FLAG_CAN_MERGE; 
pbuf->private = 0; 
write(dest_pipe.wr, zeroes, 0x20); 

where virt_to_page() is the implementation of the formula. As the reader can see, we targeted the cred object of our task, overwriting the *id fields with zeroes to escalate privileges. This assumes we already know the virtual address of our task credentials.

Manipulating the page pointer of a pipe buffer

On the other hand, we might not yet know the address. In this case we would need to seek through the heap to identify our task_struct and then our struct cred. One way to do this is to use prctl() to change our task’s name just before searching for it. Since prctl() changes the task_struct::comm field to the new name we can use this, as well as some other determinants, to confirm that we’ve found the task_struct

To do this, we loop with i over (vmemmap_base + ((0x100000000 >> 12) * 0x40)) + (0x40 * i), writing it back as the pipe_buffer::page. We can then repeatedly leak heap memory, halting when we find our task_struct. Once we’ve read out this final leak we’ll have our cred virtual address. From here, we are in the first case again as shown above. This likely means we need to retrigger the vulnerability a substantial number of times.  

Avoiding reallocation

One possible scenario is when we know the address of our object replacement in memory but not the address of our target object. For example, we know the address of a msg_msgseg which has overlaid a pipe_buffer array but not the address of the credentials which we ultimately need to overwrite.  

If this is the case, then we can repeatedly overwrite the seeker pipe_buffer by setting the page pointer of an overwriter pipe to the seeker pipe_buffer page. This works as follows:

  1. Calculate the struct page address of the seeker pipe_buffer page.
  2. Create another pipe – our overwriter.
  3. Trigger the use-after-free and write to the seeker pipe_buffer::page its own page.
  4. Call tee() with the seeker pipe as source and the overwrite pipe as destination.

Now we have a reliable way to overwrite the seeker pipe_buffer without reallocating it. We can use this in the following way:

void set_new_pipe_bufs_overwrite(char *buf, struct pipe_struct *overwrite, 
                                 char *obj_in_page, struct pipe_buffer *pbuf, 
                                 uint64_t new_page, uint32_t len, int *tail) 
    if(read(overwrite->read, buf, PAGE_SIZE) != PAGE_SIZE) 
        error_out("read overwriter_pipe[0]"); 
    struct pipe_buffer *setpbuf = (struct pipe_buffer *) obj_in_page; 
    setpbuf += (*tail) % 8; 

    *setpbuf = *pbuf;
    setpbuf->page = (void *) new_page; 
    setpbuf->len = len; 
    if (write(overwrite->write, buf, PAGE_SIZE) != PAGE_SIZE) 
        error_out("write overwriter_pipe[1]"); 

The read() sets the overwriter’s pipe_inode_info::tmp_page to the seeker pipe_buffer object’s underlying page. This temporary page can then we written to directly. After this, we construct the new pipe_buffer in our buffer. Finally, we write out the new seeker pipe_buffer with the overwriter pipe. Behind the scenes, the kernel copies the given contents into the physical page represented by the overwriter pipe’s pipe_inode_info::tmp_page. This circumvents repeated reallocation of the use-after-free object.

Reading and writing to the kernel image

Let’s suppose that we need to target a variable in the kernel image itself. Or that we don’t want to seek through the whole heap, instead opting to traverse a list such as via init_task to find our task and then our cred. Can we just leak a kernel image virtual address (e.g. pipe_buffer::ops) and then use the virt_to_page() formula as before?  

On x86 systems, with KASLR enabled, this is not possible. The option CONFIG_RANDOMIZE_BASE for this architecture randomizes the physical load address and the virtual base address separately. That is, one cannot be used to derive the other.  

To discover the kernel image base we need to have leaked a struct page pointer (or else have a partial overwrite primitive of the first qword of a pipe_buffer). We also need to know a byte pattern in the kernel image, at some offset, to confirm when we’ve found our target page.  

Let’s take the first qword of Ubuntu 22.04: 0x4801e03f51258d48 which introduces the startup_64 function. We’ll seek from some offset in vmemmap until we find this byte pattern at the first leaked qword read from our corrupted pipe_buffer.  But doesn’t this mean we need to seek across every single page a hard slog in 0x40 byte increments?

Luckily, the kernel can’t be loaded at any arbitrary physical address. It’s constrained by CONFIG_PHYSICAL_ALIGN. It will also be randomized above CONFIG_PHYSICAL_START. So we need only check in CONFIG_PHYSICAL_ALIGN increments.

Further, for ARM64 systems Kconfig has something different to say:

CONFIG_RANDOMIZE_BASE randomizes the virtual address at which the kernel image is loaded, as a security feature that deters exploit attempts relying on knowledge of the location of kernel internals.

This is most interesting for Android and means that physical base randomization needs to be implemented by third-party vendors.  

Regardless, it’s demonstrably feasible to brute-force the randomized physical base without any optimization. However, one method to speed things up is to search by increments of
(N * CONFIG_PHYSICAL_ALIGN) and in a single leak check that any of the qwords of the N offsets of the kernel image, are present.

For example, in Ubuntu’s case we have the alignment 0x200000. But we don’t want to check every 0x200000th physical address for the startup_64 qword. So we seek by (0x200000 * 8) at a time and check for any of 8 known qword at offsets (0x200000 * (0 < n < 9)) in the kernel image. Once we find one, we displace backwards by the right offset and we’ve got the physical base. 

bool search_phys_base(const char *buf, int64_t x, 
                          uint64_t *scroll_page) 
#define QWORD2 0x95e8e58948f63155 
#define QWORD3 0x6548478b4c48ec83 
#define QWORD4 0x000004c8908b0000 
#define QWORD5 0xb0458948ffffff60 
#define QWORD6 0x04ba550000441f0f 
#define QWORD7 0x4500000233840f40 
#define QWORD8 0x2524894865f8010f 
#define scale_offset(i) (((((0x200000) * i) >> 12) * 0x40)) 
    uint64_t first_qword = ((uint64_t *)buf)[0]; 
    switch (first_qword) { 
        case STARTUP_QWORD_5_15: 
        case QWORD2: 
            *scroll_page -= scale_offset(1); 
        case QWORD3: 
            *scroll_page -= scale_offset(2); 
. . .

Once we’ve got the struct page representing the physical base, we can easily derive the struct page for the target object in the kernel image as the kernel image pages are ordered to be physically contiguous. For example, 
uint64_t init_task_page = kbase_page + ((INIT_TASK_OFF >> 12) * 0x40); where INIT_TASK_OFF is the known offset of the init_task in the kernel image. 

Additional considerations

Limiting factors 

As outlined above, we need to leak (or partially overwrite) a struct page pointer. We may also need to brute-force up to the physical base page for the kernel image. This latter factor can increase the running time of an exploit which uses this technique.  

It’s also not possible to write to read-only kernel memory through this method. We can’t just alter some system call’s implementation to run our own shellcode. An extension of the technique might, however, target page tables directly in order to switch permission bits to then write out to read-only memory.  

Grace factors 

The Linux memory model sees physical page frames as a substratum of raw memory – ready to be linked with virtual addresses, or used directly, when storing and loading data. This allows us to use kernel pages in pipes where there really ought to only be user pages. Further, we may be able to target other process’ user pages to leak secrets or corrupt internal data anyway. So armed with knowledge of kernel page dynamics, as well as with a pipe_buffer corruption primitive, it is possible to do very interesting things in the physical address space. 

The post pipe_buffer arbitrary read write appeared first on Interrupt Labs.