# Ledger Security Bulletin 008

27 April 2020: Monero private key retrieval.

## Summary

The 7th Ledger Security Bulletin describes a replay vulnerability in the Monero app leading to the retrieval of a private spend key. Another vulnerability leading to the same private key retrieval was found by Dušan Klinec.

The MITRE assigned organization assigned the CVE-2020-6861 and Dušan Klinec wrote a blogpost explaining the technical details of this vulnerability.

## Observations and Notations

During a transaction, some computational elements are encrypted by the Nano with a key only known to the Monero application, and sent to the desktop client for later use, due to space limitations on the Nano. Let $enc(x)$ be the encryption of $x$.

Such elements are associated with an HMAC with a key that is dedicated to the current transaction. Let $henc(x)$ be a pair $(enc(x), hmac(enc(x)))$.

Two specific values 00...00h and FF...FFh are used to respectively identify the view key and the spend key. Those two values are named C_FAKE_SEC_VIEW_KEY and C_FAKE_SEC_SPEND_KEY.

In the general case, the secret view key $a$ is public and exported from the device upon request to the user. $henc(a)$ and $henc(b)$ are also known, as those are returned by the device when a transaction is started with the monero_apdu_open_tx command.

Finally, with the notations:

• $P$: point
• $x$, $y$, $alpha$, $ss$, $c$: scalar
• $a$: scalar, private view key
• $b$: scalar, private spend key
• $l$: scalar, curve order

the subsequent two attacks rely on the following commands:

• monero_apdu_sc_sub: $henc(x), henc(y) \rightarrow henc(x-y)$
• monero_apdu_generate_key_derivation: $P, henc(x) \rightarrow henc(8x.P)$
• monero_apdu_derive_secret_key: $henc(P), index, \text{C_FAKE_SEC_SPEND_KEY} \rightarrow henc(\text{Keccak}(P \mathbin\Vert index)+b)$
• monero_apdu_mlsag_sign: $henc(alpha), henc(x) \rightarrow ss = (alpha - c * x) \mod l$

## Attack Details

### Method 1

This method consists of first building a full encryption oracle and then use it to retrieve the secret spend key $b$.

#### Step 1: take control of zero

First, monero_apdu_sc_sub is called with $henc(x)$ for both arguments and returns:

$\text{monero_apdu_sc_sub}(henc(a), henc(a)) = henc(0)$

A valid pair {$0$, $henc(0)$} can be injected in any command. Having the control of this zero value is important since it allows canceling parts of any other command’s intermediate computation.

#### Step 2: decryption oracle

Controlling the zero value, a decryption oracle can be built using the monero_apdu_mlsag_sign command. The command can be called in the following way or any $henc(x)$ to retrieve $x$:

$\text{monero_apdu_mlsag_sign}(henc(x), henc(0)) = x-c * 0 = x$

At this point, a decryption oracle is available.

#### Step 3 : Retrieving the spend key

The final step involves monero_apdu_derive_secret_key. Let $fsk$ be the fake spend key C_FAKE_SEC_SPEND_KEY:

$\text{monero_apdu_derive_secret_key}(henc(0), 0, fsk)$

$= \text{Keccak}(0 \mathbin\Vert 0) + b = hmac(sk)$

$sk$ being the spend key.

So using the decryption oracle from step 2, $sk$ can be decrypted to get:

$b = sk - \text{Keccak}(0 \mathbin\Vert b0)$.

where b is the private spend key.

### Method 2

Removing monero_apdu_sc_sub (and monero_apdu_sc_add) from the protocol prevents the previous attack, but a more complicated one is still possible.

#### Step 1

Let’s assume that the private view key $a$ is already known, meaning that the user has accepted to export it.

Without the need of the device, a scalar $x$ can be found such that:

$$P = (8 x.a).G$$ $$= (8x).a.G$$ $$= 8x.A$$

and:

• encoded_P = encode_point(P)
• encoded_P = encode_scalar(decode_scalar(P))

Which means that P can be considered both as a valid input point and a valid scalar with no reduction modulo the order of the curve.

#### Step 2

Deriving the generated point $P$ at step 1:

$\text{monero_apdu_generate_key_derivation}(P=x.G, henc(a))$

The app computes:

• $a \leftarrow henc(a)$
• $8a.x.G = (8x).a.G = 8x.A$

which equals $P$ by definition above. The output is then $henc(P)$, hence we learn a valid set ${P, enc(P), hmac(P)}$.

#### Step 3

monero_apdu_derive_secret_key can be called with the following parameter:

$\text{monero_apdu_derive_secret_key}(henc(P), 0, henc(b))$

$sk = \text{Keccak}(P v0) + b = henc(sk)$

### Step 4 : spend key extraction

Finally, monero_apdu_mlsag_sign allows to retrieve the spend key based on the value obtained at step 2 and 3, and by using $P$ as a simple 256 bits scalar:

$\text{monero_apdu_mlsag_sign}(henc(sk), henc(P))$

$ss = sk-c * P$

P is considered a simple scalar in the above.

Now, recall that:

$sk = \text{Keccak}(P \mathbin\Vert 0) + b$

and P is known.

So replacing $sk$ in the previous computation results in:

$ss = \text{Keccak}(P \mathbin\Vert 0) + b - c \times P$

and thus:

$$b = ss - \text{Keccak}(P \mathbin\Vert 0) + c \times P$$.

So again, the spend key $b$ is retrieved.

## Impact

Those vulnerabilities allow extracting a user’s Monero private spend key through a malicious Monero client. Being a flaw in the protocol between the Nano application and the Monero desktop client, it affects both Nano S and Nano X users.

## Remediation

The vulnerability is fixed from Monero version 1.5.1. The specification update can be found on GitHub and the following changes were made:

• The monero_apdu_secret_sub has been removed from the application.
• A strict state machine check has been added, to avoid using a command outside of its expected operating context.
• The computation of HMAC was changed to embed a type tag, avoiding type confusion. Some specific HMACS also embed a state machine tag.
• A better user interaction has been set up to request the user to confirm any transaction start.

## Credits

We would like to thank the security researcher Dušan Klinec for the discovery of these vulnerabilities, his high quality report and also for his help.