Silence Will Fall (Or How It Can Take 2 Years to Get Your Vuln Registered)

Photo credit:


In 2018, we at BI.ZONE were preparing CTFZone Finals (held offline) that were meant to coincide with the OFFZONE conference. We really wanted to spice up the competition, so we decided to host the PWN challenge on actual hardware. Since this was an Attack-Defense CTF (that’s where teams are given identical services to host and protect, and all the teams try to steal flags from each other), we could give each team a custom circuit board with the CTFZone logo. The circuit board had dual purpose: a somewhat unusual task and a piece of memorabilia. At the start of the project, when the deadlines were still far ahead, we imagined a design, where the board would host two chips: one containing the core logic and one just for authentication. The attackers would be able to upload their malicious code to the core chip’s memory by leveraging the embedded vulnerabilities, but it was impossible to break into the authentication chip. This way we could prevent the attacking team from simply cloning the defending team’s device once and being able to emulate it later. Instead they would have to interact with the authentication chip on the target device in order to get the flag for each round. Such mechanisms require cryptographic solutions. Thankfully (or so I thought), ST (we were going to use STM chips for both microcontrollers) provided a precompiled cryptographic library for its devices that is called STM32 cryptographic firmware library software expansion for STM32Cube (X-CUBE-CRYPTOLIB).


PKCS#1 is the RSA Cryptography Standard. It defines the primitives and schemes for using the RSA algorithm for public-key cryptography. Its current version is 2.2, but the version used in the X-CUBE-CRYPTOLIB is 1.5. This version of the specification is infamous for introducing a critical vulnerability into the encryption / decryption process. However, to understand it in full we must first cover the inner workings of RSA and what is inside PKCS#1.

Basic RSA

The RSA primitive relies on the hard problem of factorizing a product of two large prime numbers. Imagine Bob wants to send Alice a message that only she would be able to read. They decide on the following algorithm (all the formulas will follow the list, since medium can’t handle Latex):

  1. Alice randomly picks two prime numbers (p and q) of predefined length (for example, 1024 bits).
  2. Alice computes their product N (the modulus).
  3. Alice takes the predefined public exponent e. The most common value is 65537, because it has just 2 bits set. In the past 3 and 17 have also been used, but they are not guaranteed to wrap the modulus during exponentiation.
  4. Alice computes private exponent d. Now (d, N) is Alice’s private key and (e, N) is Alice’s public key.
  5. Alice sends her public key to Bob.
  6. Bob converts the message M which he wants to send to Alice into a number m less than N. He exponentiates the number to e modulo N and sends the result to Alice.
  7. Alice takes the result, exponentiates it to d modulo N and gets the initial m as a result, which she then converts back into M
    ! On a side note, since Medium does not support Latex, some exponents are expressed in brackets using the ^ symbol.
    =pq, where p, q are prime
    e∗d = 1(mod (p-1)(q-1))
    ∀m ≠ 0: m ^(e∗ d) (mod N)=m^(1+k∗(p−1)∗(q−1)) (mod N)=m (mod N),
    where k∈ N

RSA teething problems

RSA has several problems. For example, you can’t just pick any two prime numbers to compute the modulus, they need to fulfill certain requirements. Otherwise, the primitive becomes unsafe. And even if you’ve picked the perfect p and q, you’d still be left with some issues. We’ll use the following notation from now on: m stands for plaintext (message) and c stands for ciphertext.


To address these issues, RSA (as in the company) created a specification that became PKCS#1 v1.5 (Public Key Cryptography Standards). The specification introduces formatting for encrypted (or signed) blocks (effectively, a special padding). This padding is used before the whole block is encrypted. Let’s say the length of modulus N in octets (bytes) is equal to k. The data we want to encrypt/sign is D. Then the encryption block is:

  1. BT=00, all octets in PS are set to 00
  2. BT=01, all octets in PS are set to FF
  3. BT=02, all octets in PS are pseudorandomly generated and not equal to 00
  1. With BT=01, D can be unpadded regardless of its contents, with BT=00 the first byte of D, which is equal to 00, will also be unpadded, thereby creating a problem.
  2. Both BT=01 and BT=02 create large integers for encryption, so all attacks requiring a small (short) D don’t work

Bleichenbacher’s Padding Oracle Attack

Unfortunately, not all is well and good with PKCS#1v1.5, or I would not be sitting here writing this article. The following attack is 22 years old.

  1. Alice decrypts the ciphertext with the RSA primitive.
  2. Alice unpads the message.
  3. Alice parses the message and performs actions based on the message.
  1. Blinding the ciphertext, creating cₒ, that corresponds to unknown mₒ
  2. Finding small sᵢ, such that mₒsᵢ mod N is PKCS-conforming. For every such sᵢ the attacker computes intervals that must contain mₒ using previously known information
  3. Starts when only one interval remains. The attacker has sufficient information about mₒ to choose sᵢ such that mₒsᵢ mod N is more likely to be PKCS conforming than a randomly chosen message. The size of sᵢ is increased gradually, narrowing the possible range of mₒ until only one possible value remains.
  1. Intercept some message from Bob to Alice.
  2. Use Alice as a padding oracle to decrypt the message.

Back to disclosure

Since I found this vulnerability in one software library for microcontrollers, I decided to check if there are any other libraries that provide this specification as the default method for RSA encryption/decryption. Turns out, ST is not the only company. Microchip also provides a library that contains cryptographic primitives and lo and behold — the only encryption scheme for RSA is once again PKCS#1v1.5. Here is an excerpt from the RSA decryption function:

  • They didn’t want to register a CVE, because they just followed the specification and the library itself didn’t return any padding errors (this is just partially true)
  • They were developing PKCS#1v2.2 (a newer specification with safer padding) and were going to publish it with a new version of X-CUBE-CRYPTOLIB in the spring.

Proof of Concepts

General information

Since these libraries are aimed at embedded devices, using them on a regular PC with amd64 architecture comes with certain limits. The worst of them is the speed of computation.

  • The full attack (will take a long time on MLA, an absurdly long time on ST)
  • The attack with precalculated successful steps

Preliminary steps

MLA library is readily available here:, but you have to request X-CUBE-CRYPTOLIB in advance here (you’ll have to register): Or if you’ve already registered and are logged in:

  • requirements.txt (file with python requirements for “” and python attack clients)
  • (python script which parses a pem file, creates headers and imports for vulnerable servers and clients, also encrypts one message, which is to be decrypted by the attacking clients and creates a trace to show the attack without taking too much time)
  • (runs openssl to create a 2048-bit RSA key, then runs “”)
sudo apt install openssl python3 python3-pip python3-gmpy2 && python3 -m pip install -r requirements.txt

Microchip PoC

Install Microchip Libraries for Applications (MLA). The latest version at the time of writing is “v2018_11_26”. It has to be installed in a non-virtualized environment. For some reason they’ve implemented this check in the installer.

  • bigint_helper_16bit_intel.S (rewritten “bigint_helper_16bit.S” from the bigint library inside MLA. It was written for PIC architecture, making the library unusable on a PC. I rewrote all functions in intel (x86_64) assembly, to use the same bigint library that crypto_sw in MLA uses)
  • bleichenbacher_oracle_mla.c (the main file containing high-level server functions)
  • crypto_support_params.h (enums and function definitions for cryptographic functions)
  • crypto_sw.patch (a patchfile that comments out one line in crypto_sw library to stop type collision when building on a PC)
  • crypto.c (contains all functions which initialize and deinitialize MLA’s RSA implementation and functions that wrap around encryption and decryption routines)
  • (a simple bash script to apply the patch)
  • makefile
  • oracle_params.h (some server parameters)
  • support.c (decrypted message checking function)
  • support.h (check_message function and return values’ definitions and seed length definition)
cd Microchip/vulnerable_server
cp -r ~/microchip/mla/v2018_11_26/framework/bigint .
cp -r ~/microchip/mla/v2018_11_26/framework/crypto_sw .
cp ../../Preparation/microchip_files/key_params.h .
cp ../Preparation/attacker/ .
cp ../Preparation/speedup/trace.txt .
  1. Run the full attack (you will have to wait a long time, however)
python3 -t trace.txt -s
python3 -t trace.txt


Download and unzip qemu_stm32.

cd qemu_stm32-stm32_v0.1.2
sudo apt install arm-none-eabi-gcc-arm-non-eabi-newlib gcc
./configure --extra-cflags="-w" --enable-debug --target-list="arm-softmmu"
#include <sys/sysmacros.h>
cd qemu_stm32-stm32_v0.1.2
cp ../stm32_qemu.patch .
cp ../ .
cp -r STM32CubeExpansion_Crypto_V3.1.0/Fw_Crypto/STM32F1/Middlewares/ST/STM32_Cryptographic stm32_p103_demos-0.3.0/
cd stm32_p103_demos-0.3.0
cp ../ .
cp ../stm32_p103_demos.patch .
  • main.c — the main file containing high-level functionality of the server
  • stm32f10x_conf.h — configuration file
  • support.c — message parsing and fixing
  • support.h — definitions for the use of support.c
cp ../../../../Preparation/st_files/key_params.h demos/bleich/
make bleich_ALL
  • Server QEMU terminal
  • Server GDB terminal
  • Attacker terminal
#Server QEMU terminal
./qemu-system-arm -M stm32-p103 -kernel ../../stm32_p103_demos-0.3.0/demos/bleich/main.bin -gdb tcp::3333
  • Go to “<some_prefix>/ST/vulnerable_server/”
  • install gdb-multiarch if it’s not installed
  • run gdb-multiarch
  • load file “binary/stm32_p103_demos-0.3.0/demos/bleich/main.elf” (this is necessary for the script to work)
  • connect to qemu gdb server
  • load python script “<some_prefix>/ST/vulnerable_server/gdb_server/” (gdb has to use python3)
#Server GDB terminal
file binary/stm32_p103_demos-0.3.0/demos/bleich/main.elf
target remote localhost:3333
source gdb_server/
pkill -9 gdb

Back to disclosure… Again

So, I submitted these PoCs to HackerOne in October of 2019. However, since Bleichenbacher’s Padding Oracle Attack has only Medium severity, the impact wasn’t high enough for the report to be eligible for the Internet program. Sigh… Ok, the only thing that was left was to submit the vulnerabilities directly to MITRE. On the 21st of October I submitted the request and got the automatic reply. A few days after that I replied to the letter, asking if they required any more information, but there was silence. I got preoccupied with work and CTF preparations (a year has passed) and completey forgot about that letter.

  1. CVE-2020–20949 (Vulnerability in ST X-CUBE-CRYPTOLIB 3.1.0, 3.1.2)
  2. CVE-2020–20950 (Vulnerability in Microchip Libraries for Applications 2018–11–26)



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


BI.ZONE: an expert in digital risks management. We help organizations around the world to develop their businesses safely in the digital age