Sh*Load Exploits (Episode V: Return of the Error)

Firmware Developers Need To Know

Our first post in the Firmware Developers Need To Know blog series, Episode I: The Last Error, pointed out the benefits of adopting clean error codes. And then two weeks later, TLStorm, bam. Armis’ research engineers announced the discovery of three vulnerabilities in APC devices –the key problem – ignoring error codes! Unfortunately, little attention or thought is paid to error codes within firmware code (and many critical open source projects).

TL;DRStorm

Quick summary, TLStorm vulnerabilities are due to Schneider Electric’s APC firmware glue code ignoring errors returned from the venerable Mocana NanoSSL library (now DigiCert). The errors are directly related to an active man-in-the-middle (MiTM) attack during a TLS handshake. According to Armis’ blog, the Mocana NanoSSL documentation did warn to close the TLS connection if an error occurs.

Armis found method to trigger buffer overflow, if an error-connection was forced to remain open. This bug is difficult to anticipate since it is an implementation specific defect. Adding a state error for previously ignored errors is a good change to detect forcing open an error connection, but the cake was already lost when the errors had been ignored. Pseudo code to demonstration the key problem:

				
					//pseudo glue code
char resumeSecretKey[MASTER_SECRET_SIZE] = { 0x00 };

// first connect – no resumption yet – false indicates no resume
pSSL = SSL_connect(“dellfer.com”, false, NULL);    // ignores the error

// attempt to collect resumeSecret from failed tls handshake
SSL_getSessionResumeInfo(pSSL, resumeSecretKey);      // ignores the error

// no resume secret found – resumeSecretKey contains the init value (0x00s)
pSSL = SSL_connect(“dellfer.com”, true, resumeSecret);  // finally, no error but bad guy wins

				
			

Better Call SHA

In retrospect, SSL libraries should consider a possible enhancement for developers that willfully ignore their library errors, a hash value. Append the hash value of resumeSecretKey[] to ensure the resumption secret buffer is correctly populated by our example fictitious SSL_getSessionResumeInfo() function. I recommend including the common name as hash input to ensure the resumption key is paired correctly. SSL_connect() should validate resumeSecretKey[] before proceeding with a session resumption handshake. I did briefly consider recommending using a random value for resumeSecretKey[] as a safety line, however, as we saw in the previous blog, Episode III: Entropy of the Clones, we may end up with a predictable, non-zero resume secret.

				
					static STATUS
TLS_wrapResumeSecret(ubyte *pSecretBuf, ubyte4 lenSecretBuf,
                     ubyte *pCommonName, ubyte4 lenCommmonName,
                     ubyte *pProtectSecretBuf, ubyte4 lenProtectSecretBuf)
{
    hashCtx*    pHashCtx = NULL;
    STATUS      status;

    if (NULL == pProtectSecretBuf)
        return ERR_GEN_NULL_PTR;

    memset(pProtectSecretBuf, 0x00, lenProtectSecretBuf);

    if ((lenProtectSecretBuf <= lenSecretBuf) ||
        (lenSecretBuf + SHA2HashSize256 != lenProtectSecretBuf))
    {
        return ERR_GEN_BAD_LENGTH;
    }

    if (OK > (status = SHA2_init256(&pHashCtx)))
        goto exit;

    /* add a little salt */
    if (OK > (status = SHA2_update256(pHashCtx, m_pLaunchSecretSalt, LAUNCH_SECRET_SALT_LEN)))
        goto exit;

    if (OK > (status = SHA2_update256(pHashCtx, pSecretBuf, lenSecretBuf)))
        goto exit;

    if (OK > (status = SHA2_update256(pHashCtx, pCommonName, lenCommmonName)))
        goto exit;

    // pProtectSecretBuf = pSecretBuf[lenSecretBuf] | hashResult[SHA2HashSize256]
    if (OK > (status = SHA2_final256(pHashCtx, pProtectSecretBuf + lenSecretBuf)))
        goto exit;

    memcpy(pProtectSecretBuf, pSecretBuf, lenSecretBuf);

exit:
    SHA2_cleanup(&pHashCtx);

    return status;

} /* TLS_wrapResumeSecret */

				
			

SH’ALL Good, Man

While working on the code for this blog, I made an obvious discovery – SHA2 implementations are susceptible to a “preimage attack,” if error codes are ignored. On an error state, the IETF’s SHA2 reference implementation (RFC 6234) returns immediately – the message digest result is untouched; a highly predictable value.

				
					// RFC 6234 Reference Code:
int SHA256Input(SHA256Context *context, const uint8_t *message_array,
    unsigned int length)
{
  if (!length)
    return shaSuccess;

  if (!context || !message_array)
    return shaNull;

  if (context->Computed) {
    context->Corrupted = shaStateError;
    return shaStateError;
  }

  if (context->Corrupted)
     return context->Corrupted;

  while (length-- && !context->Corrupted) {
    context->Message_Block[context->Message_Block_Index++] =
            (*message_array & 0xFF);

    if (!SHA224_256AddLength(context, 8) &&
      (context->Message_Block_Index == SHA256_Message_Block_Size))
      SHA224_256ProcessMessageBlock(context);

    message_array++;
  }

  return shaSuccess;

} 

int SHA224Input(SHA224Context *context, const uint8_t *message_array,
    unsigned int length)
{
  return SHA256Input(context, message_array, length);
}

static int SHA224_256ResultN(SHA256Context *context,
    uint8_t Message_Digest[ ], int HashSize)
{
  int i;

  if (!context) return shaNull;
  if (!Message_Digest) return shaNull;
  if (context->Corrupted) return context->Corrupted;  \\ Message_Digest[] untouched

  if (!context->Computed)
    SHA224_256Finalize(context, 0x80);

  for (i = 0; i < HashSize; ++i)
    Message_Digest[i] = (uint8_t)
      (context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) ));

  return shaSuccess;
}

int SHA224Result(SHA224Context *context,
    uint8_t Message_Digest[SHA224HashSize])
{
  return SHA224_256ResultN(context, Message_Digest, SHA224HashSize);
}

vuln()
{
    SHA224Context context;
    unint8_t result[SHA224HashSize] = { 0 };
    
    SHA224Reset(&context);
    SHA224Input(&context, “hi”, 3);
    SHA384Input(&context, “guy”, 4); // mixing algo size trigger corruption
    SHA224Result(&context, result);  // ignored errors lead to 0x00 hash result
}

				
			

But If You Do, Call SHA

I began looking for situations where hash algorithms error codes might be ignored and the potential impact. And sure enough there are many instances and many libraries are not heeding SHA2 error codes! Including Edward Curve algorithms, for example, EdDSA (ed25519).

EdDSA is a widely accepted alternative algorithm that is easier to implement than ECDSA and very fast – bonus EdDSA doesn’t have the ECDSA entropy signing weakness. Similar to ECDSA, EdDSA uses a signature {R,s} are provided by peer for authentication. ECDSA explicitly states to ensure {R,s} are non-zero, EdDSA doesn’t have that same restriction.

EdDSA signature {R,s} verification algorithm:

h = (SHA2(R + publicKey + msg)) mod q
P1 = s * G
P2 = R + h * publicKey

Signature is good, if P1 equals P2. If R and s are zero, P1 would be zero, and P2 would not match P1, since P2 is h * publicKey. The question: can h become zero? Yes – it is possible for hash result to be zero, but in-reality, this is known as a preimage attack; it would take a tremendous amount time to break.

OpenSSL’s implementation of the above algorithm:

				
					bool ed25519_verify(const uint8_t* m, size_t mlen,
                    const uint8_t sig[64],
                    const uint8_t* pk,
                    const uint8_t domain_sep[], size_t domain_sep_len)
   {
   uint8_t h[64];
   uint8_t rcheck[32];
   ge_p3 A;
   SHA_512 sha;

   if(sig[63] & 224)
      {
      return false;
      }
   if(ge_frombytes_negate_vartime(&A, pk) != 0)
      {
      return false;
      }

   sha.update(domain_sep, domain_sep_len);
   sha.update(sig, 32);
   sha.update(pk, 32);
   sha.update(m, mlen);
   sha.final(h);
   sc_reduce(h);

   ge_double_scalarmult_vartime(rcheck, h, &A, sig + 32);

   return constant_time_compare(rcheck, sig, 32);
   }

				
			

Sh*Load Exploits

During the process of surveying code, I noticed a few things. OpenSSL inconsistently checks for errors, however OpenSSL’s SHA2 source code doesn’t generate errors. There are no length checks or checks to prevent mixing various hash algorithms with the same context – you can’t ignore errors that aren’t available. It doesn’t mean OpenSSL is safe.

The issue is that OpenSSL is often spliced with other tech such as FIPS-140 cryptographic modules, or cryptographic offload modules, especially in devices – if any of those implementations throws an error, OpenSSL will have an issue. The cryptographic modules in embedded devices will often require a mutex to prevent race condition when addressing the hardware, if one was to tamper with mutex identifier, it would trigger an ignored error that needs to be checked.

codepen
Example of a real-world mutex to prevent a hardware offload race condition.
A FIPS-140 module can potentially trigger an error state too. The error stated depends on the implementation of the FIPS cryptographic module. Typically, a FIPS software module error state or mutex will be placed in a module variable, which then is located at a predictable memory address. WolfSSL and BoringSSL are both very diligent at checking error codes better than the other implementations that don’t bother to check for errors. I did find a Sh*Load-like bug within WolfSSL when using Freescale hardware offload. The code in question would result in a predictable value on errors (0x00). After notification WolfSSL responded immediately, and followed up quickly with a patch:
codepen2
Return status check patch in EdDSA 25519 signature verification.
codepen3
The previous hardware signature verification code following a failure-sled to effectively a memset(0x00).

Sh*Load Chaos

There are two ways cryptographic hardware offload algorithms are addressed: inline mnemonic instructions and memory mapped I/O. To be continued….

Invention, it must be humbly admitted, does not consist in creating out of void,
but out of chaos.
– Mary Shelley

The Tips

Don’t get it twisted. The proper fix is to check for errors. Error codes are amazing. During my search, a significant vulnerability was disclosed publicly by security researcher Neil Madden that Java’s ECDSA signature verification always succeeded if R or S are zero – oops! Attackers may already be exploiting Sh*Load bugs in the wild – it would be nearly impossible to detect.

Dellfer is focused on the ability to detect and mitigate device firmware vulnerabilities that will never be present in a log report. Bugs like this are pervasive, even in third party code you rely on, it’s impossible to track and prevent it all. Unfortunately, the exploitation footprint will always remain high at any given time. The insightful developers that recognize the need for built-in incident detection and response are opening up new value for their customers.

Tip 1: Don’t ignore errors.
Tip 2: Add an error state for tracking ignored error conditions within your state machines.
Tip 3: Data-like resumption keys should add a data-wrapper sealer to prevent glue code damage.
Tip 4: When exiting on error condition within a function – don’t leave things nice, tidy, and predictable for a hacker — the code looks super sus’.

Code to help maintain your state of mind.

References:

Share

Table of Contents

Subscribe to
The Dellfer Brief

The latest industry insights and company news delivered to your inbox.

See Our Blog Posts

Enter Your Information to Access This White Paper

Enter Your Information to Access This White Paper

Enter Your Information to Access This White Paper

Enter Your Information to Access This White Paper

Enter Your Information to Access This Datasheet

Enter Your Information to Access This Datasheet