As of August 2020 the site you are on (wiki.newae.com) is deprecated, and content is now at rtfm.newae.com.

Changes

Jump to: navigation, search

AES-CCM Attack

10,876 bytes added, 21:45, 26 March 2017
no edit summary
{{InfoboxWarningbox|WARNING: This page under construction!}}
The following is an overview of the AES-CMM attack done by Eyal Ronenet al., detailed in his their draft/limited release paper [http://iotworm.eyalro.net/ IoT Goes Nuclear: Creating a ZigBee Chain Reaction(research paper website)], [http://eprint.iacr.org/2016/1047 IACR E-print submission]. If using this attack please '''do not cite this page''', instead cite the research paper only. The paper is currently a draft so there is no proceedings information etc as it has not yet been presented anywhere, but you can cite the E-Print version.
This page is presented as an example of using Python/ChipWhisperer to perform attacks against the AES-CCM cipher, without needing to do a more complex attack against AES-CTR mode.
== AES-CCM Overview ==
AES-CCM provides both encryption and authentication using the AES block cipher. This is a widely used mode since it requires only a single cryptographic primitive. That primitive is used in two different modes: CBC and CTR mode. The following shows how AES-CCM generally works: [[File:cbc_mac_source.png]] The "Calculated Tag" and "Expected Tag" are compared together, and only if they match is the decrypted data used. A change of any of the data blocks OR the header would change the calculated tag, resulting in an error. Some nice features of AES-CCM: * Can decrypt any data block, or decrypt blocks out of order due to AES-CTR usage.* Authentication Tag provides authentication that data has not been modified in transit.* Auth tag can include non-encrypted information, such as a header with address or length information.* Auth tag can be shortened (i.e., not full 16-byte length) for use with protocols with very sensitive length limitations. The difference between the two modes is explained below:
'''Cipher Block Chaining (CBC):''' The plaintext is XORed with the previous ciphertext before being encrypted. There is no ciphertext before the first plaintext, so a randomly chosen initialization vector (IV) is used instead:
In AES-CCM mode, the AES-CBC encryption is used to generate a nice "authentication tag". If a single byte changed anywhere in the data fed into the AES-CBC block, the final output will differ.
The AES-CTR mode is used for the actual data encryption. Note AES-CTR encryption and decryption is the same operation, as AES-CTR is basically generating a unique "pad" we XOR with the data. Additional usage information: * A nonce format is required for AES-CTR. This nonce can be based on information in the packet, such as source address, or be random.* An IV is required for the AES-CCM block. This I.V. can be sent (possibly encrypted) to the AES-CCM block, or be part of secret information stored in the bootloader. == Example Bootloader Details == To perform this attack, we will define a simple bootloader. We will be using this example since we cannot use AES-CCM "standalone" without having a little bit of structure (i.e., how we do the AES-ECB demos). This `bootloader' is really just a demo of something that downloads a block of encrypted data, since it doesn't perform any actual bootloading. The example bootloader has a simplified AES-CCM implementation (NB: do NOT use this as a reference for a good implementation!). It accomplishes the basic goals only of having: * Header data that is authenticated but not encrypted* An encrypted MAC tag* A bunch of encrypted firmware blocks Each block sent to the bootloader is 19 bytes long. The first byte indicates the type - header, auth tag, or data. If a new 'header' message is received it will abort any ongoing processing of existing data and restart the bootloader process. All messages share this feature:* CRC-16: A 16-bit checksum using the CRC-CCITT polynomial (0x1021). The LSB of the CRC is sent first, followed by the MSB. The bootloader will reply over the serial port, describing whether or not this CRC check was valid. '''Header Frame'''* <code>0x01</code>: 1 byte of fixed header* Header Info: 14 bytes of "header" data which could be version or other such stuff.* Length: Number of encrypted data frames (NOT including the auth-tag frame) that will follow. Note the 16 bytes of the header info + length are fed into the AES-CBC algorithm as part of the auth-tag generation. That is this data is authenticated but not encrypted. <pre>+------+------+------+------+ .... +------+------+------+------+| 0x01 | Header Info (14 bytes)| Length | CRC-16 |+------+------+------+------+ .... +------+------+------+------+</pre> '''Auth Tag Frame'''* <code>0x02</code>: 1 byte of fixed header* Auth-Tag: The expected output of the AES-CBC algorithm after processing the authenticated only data + decrypted data frames. This is then encrypted in AES-CTR mode with the CTR set to 0. <pre> |<----- Encrypted block (16 bytes) ------>| | AES-CTR Encryption with CTR=0 | | |+------+------+------+------+ .... +------+------+------+------+| 0x02 | Auth-Tag (encrypted MAC) | CRC-16 |+------+------+------+------+ .... +------+------+------+------+</pre> '''Data block frame'''* <code>0x03</code>: 1 byte of fixed header* Encrypted Data: Data encrypted in AES-CTR mode, with the CTR starting at 1 and incrementing. <pre> |<----- Encrypted block (16 bytes) ------>| | AES-CTR Encryption with CTR=1,2,3..N | | |+------+------+------+------+ .... +------+------+------+------+| 0x03 | Data (16 Bytes) | CRC-16 |+------+------+------+------+ .... +------+------+------+------+</pre>  The bootloader responds to each command with a single byte indicating if the CRC-16 was OK or not: <pre> +------+CRC-OK: | 0xA1 | +------+  +------+CRC Failed: | 0xA4 | +------+</pre> Once ALL messages are received, the bootloader will respond with a signature OK or not message: <pre> +------+Sig-OK: | 0xB1 | +------+  +------+Sig Failed: | 0xB4 | +------+</pre> Note details of the AES-CTR nonce, AES-CBC I.V., and key are stored in the firmware itself. In this example they are not downloaded as part of the encrypted firmware file.
== Background on Attack ==
The following will use the notation from [http://iotworm.eyalro.net/ IoT Goes Nuclear: Creating a ZigBee Chain Reaction]. This notation will be adopted throughout this wiki page. From the figure of AES-CCM, you can see that we don't directly control the input to any of the AES blocks. It could be possible to perform an AES-CTR attack (such as described by J. Jaffe in [https://www.iacr.org/archive/ches2007/47270001/47270001.pdf A First-Order DPA Attack Against AES in Counter Mode with Unknown Initial Counter]), but this will have issues if the bootloader limits the number of blocks we can send. But, as it turns out we can still break AES-CBC with a little additional computational effort (but not requiring any additional traces). === Breaking AES-CBC Encryption with unknown I.V. === If you've performed a standard CPA attack, you'll realize the problem with attacking AES-CBC is we don't directly control the input, which we call <math>PT</math>. Instead it's XORd with some unknown bytes (the AES-CBC ciphertext output). But if we are always attacking the same block (that is, we reset the AES state to initial by say resetting the device, and rerun the algorithm up to the first block), the unknown bytes are constant. As it turns out this is a pretty easy problem to solve. The first step is to perform a standard CPA attack. The only issue is we won't recover the actual encryption key used <math>k</math>, instead we recover <math>k \oplus CBC_{m-1}</math>, since we basically roll all the constant inputs into what we call a `modified key'. Note <math>CBC_{m-1}</math> is the output of the previous-block AES-CBC ciphertext. In what might seem like magic, we can use this modified key to directly determine the second-round key (the true key). This was originally presented by J. Jaffe in [https://www.iacr.org/archive/ches2007/47270001/47270001.pdf A First-Order DPA Attack Against AES in Counter Mode with Unknown Initial Counter]. The reason this works is if you remember we recovered <math>k' = k \oplus CBC_{m-1}</math>. In the AES algorithm the first thing we do is the AddRoundKey, which is: <math>AddRoundKey(a,b) = a \oplus b</math>.  In the true algorithm we have the case of:<math>AddRoundKey(k, CBC_{m-1} \oplus PT)</math> And when we use our modified key, we are feeding the PT directly into AddRoundKey:<math>AddRoundKey(k', PT)</math> Where the <math>k'</math> basically includes those additional constants, instead of them being added to the plaintext as part of the input processing. Ultimately it means the output of AddRoundKey, and thus processing of later rounds, is identical in both cases. So we can perform a CPA attack on the 2nd-round key, and directly recover the "true" first-round key by rolling back the key schedule. This is the solution now - we simply perform a CPA attack on the 2nd round of the AES algorithm, where we use the AES algorithm to determine the inputs to the second-round based on our modified key & the known plaintext.
== Performing Attack ==
=== Building Example ===
There is an example of a simple bootloader which uses AES-CCM in the firmware directory. A hex-file is also present for the Atmel XMEGA device on the ChipWhisperer-Lite board.
=== Collecting Traces ===
 
Collecting traces requires the following steps:
 
# Program into the target the aes-ccm bootloader. This bootloader can be found in the git repo, which also includes a .hex file.
# Set the target type to the special AES-CCM bootloader driver. This target module is detailed in the appendix on this page, you can copy that code into a new file in the `target' directory.
# Run a capture with ~500 traces. Use 1x CLKGEN for the ADC speed and the full point-range to be able to capture both software AES rounds. See capture script example for details.
=== Step #1: AES-CBC MAC Block #1 ===
==== 1-D: True Second-Round Key ====
In what might seem like magic, we can use this modified key to directly determine the second-round key (the true key). This was originally presented by J. Jaffe in [https://www.iacr.org/archive/ches2007/47270001/47270001.pdf A First-Order DPA Attack Against AES in Counter Mode with Unknown Initial Counter], and details were described earlier in this page. For our case we are using <math>PT_m = CT_m \oplus CTR_{m}</math>, that is we don't have <math>PT</math> directly, but we actually have the input to the AES-CTR decryption. Ultimately the AES-CTR output will become another unknown constant we will deal with later. The  To repeat the previous explanation: the reason this works is if you remember we recovered <math>k' = k \oplus CBC_{m-1} \oplus CTR_{m}</math>. In the AES algorithm the first thing we do is the AddRoundKey, which is:
<math>AddRoundKey(a,b) = a \oplus b</math>.
In the event the AES-CTR nonce input is unknown, additional work is required (detailed below).
=== Step #22A: AES-CBC MAC Block #2 ===
Repeating this for block #2 is exactly the same as before. Note you will need to perform a capture which triggers on the second block, which may require changes to the firmware source code.
Once you recover Block #1, you can calculate <math>CBC_{m}</math>. Recovering block #2 means you could use <math>CBC_{m}</math> to determine <math>CTR_{m+1}</math>. Then you can decrypt <math>CTR_{m+1}</math> to determine the AES-CTR nonce format.
== Example Bootloader ==
The example bootloader has a simplified === Step #2B: AES-CCM implementation (NB: do NOT use this as a reference for a good implementation!). It accomplishes the basic goals only of having:CTR Pad Output DPA ===
* Header data that is authenticated but not encrypted* An encrypted MAC tag* A bunch As an alternative to doing the CPA attack on a second block, we can use a DPA attack to figure out the AES-CTR output pad. The advantage of encrypted firmware blocksthe DPA attack method is it doesn't require any additional traces to be captured.
Each block sent to the bootloader is 19 bytes longTo begin with, we'll be using stand-alone Python scripts for this. The first byte indicates thing we'll do is simply display differences, where we assume the type - headerkey was "0x00", auth tag, or dataand an XOR leakage model. If a new 'header' message is received it This will abort any ongoing processing simply give us positive/negative spikes depending on the value of existing data and restart bits being XORd with the bootloader processknown input data.A simple script to do this is:
All messages share this feature:<syntaxhighlight lang="python">from chipwhisperer.common.api.CWCoreAPI import CWCoreAPIfrom matplotlib.pylab import * CRC-16: A 16-bit checksum using the CRC-CCITT polynomial (0x1021)from chipwhisperer. The LSB of the CRC is sent first, followed by the MSBanalyzer. The bootloader will reply over the serial portutils.populations import partition_traces, describing whether or not this CRC check was validdpaimport chipwhisperer.analyzer.utils.partitiontools as ptoolsimport chipwhisperer.analyzer.attacks.models.AES128_8bit as AES128_8bit
'''Header Frame'''* <code>0x01</code>class XOR_Test(AES128_8bit.AESLeakageHelper): 1 byte of fixed header* Header Info name = 'HW: 14 bytes of "header" data which could be version or other such stuff.XOR Test'* Length: Number of encrypted data frames def leakage(NOT including the auth-tag frameself, pt, ct, key, bnum) that will follow.: return pt[bnum] ^ key[bnum]
Note the 16 bytes of the header info + length are fed into the AES-CBC algorithm as part of the auth-tag generationcwapi = CWCoreAPI()cwapi. That is this data is authenticated but not encryptedopenProject(r'c:\users\colin\chipwhisperer_projects\tmp\aes_ccm_block1_500traces_clk1x.cwp')
<pre>+------+------+------+------+ tm = cwapi.project()... +------+------+------+------+| 0x01 | Header Info traceManager(14 bytes)| Length | CRC-16 |+------+------+------+------+ ntraces = tm.... +------+------+------+------+</pre>numTraces()
'''Auth Tag Frame'''* <code>0x02</code>: 1 byte of fixed header* Auth-Tag: The expected output of the AES-CBC algorithm after processing the authenticated only data + decrypted data frames. This is then encrypted in AES-CTR mode with the CTR set to 0.mod = XOR_Test()
<pre> |<----- Encrypted block (16 bytes) ------>| | AES-CTR Encryption with CTRcol =0 | | |+------+------+------+------+ .... +------+------+------+------+| 0x02 | Auth-Tag (encrypted MAC) | CRC-16 |+------+------+------+------+ .... +------+------+------+----['r', 'b', 'g', 'm', 'k', 'c', 'y', 'b--+</pre>']
'''Data block frame'''* <code>0x03</code>for bnum in [0]: 1 print "Working on byte of fixed header%d"%bnum* Encrypted Data: Data encrypted for bit in AES-CTR moderange(0, with the CTR starting at 8): print " Bit %d"%bit bmask = 1 and incrementing<<bit ptool = ptools.HWAES(tm, bnum=bnum, model=mod, bmask=bmask) groups = partition_traces(tm,ptool,0, key_guess=0x00) diff = dpa(groups) plot(diff, col[bit]) hold(True) show()</syntaxhighlight>
<pre> |<----- Encrypted block (16 bytes) ------>| | AES-CTR Encryption with CTR=1,2,3..N | | |+------+------+------+------+ .... +------+------+------+------+| 0x03 | Data (16 Bytes) | CRC-16 |+------+------+------+------+ .... +------+------+------+------+</pre>The results should look something like this:
[[File:dpa_total.png|800px]]
The bootloader responds to each command You might notice the 4 spikes will line up with a single byte indicating the spikes coming from the XOR correlation. Of interest if we zoom in on the CRC-16 was OK or notfirst spike, we should be able to detect multiple "paths" being taken. We'll set a threshold location somewhat arbitrarily as a first test:
<pre> +------+CRC-OK[[File: dpa_zoom.png| 0xA1 | +------+800px]]
+------+CRC Failed: | 0xA4 | +------+<Now we'll simply go through and read off each bit by deciding if it's above/pre>below zero. Note that (a) there is multiple potential threshold locations, and (b) you might get the inverse of the correct answer (each bit flipped) depending on your hardware. In practice we might need to test a few possible locations.
Once ALL messages are received, By doing the bootloader will respond same plotting operation with a signature OK or not messagebnum = 1, then bnum = 2, you should be able to figure out the "shift". This is to say how many points you need to move forward in time by, in this case it was 19 points. A final example attack that worked on my system (again you'll have to modify '''startingpoint''' and '''diffpoint'''):
<presyntaxhighlight lang="python"> +------+from chipwhisperer.common.api.CWCoreAPI import CWCoreAPISig-OK: | 0xB1 |from matplotlib.pylab import * +------+from chipwhisperer.analyzer.utils.populations import partition_traces, dpaimport chipwhisperer.analyzer.utils.partitiontools as ptoolsimport chipwhisperer.analyzer.attacks.models.AES128_8bit as AES128_8bit
+------+class XOR_Test(AES128_8bit.AESLeakageHelper):Sig Failed name = 'HW: | 0xB4 |XOR Test' +------+ def leakage(self, pt, ct, key, bnum): </pre> return pt[bnum] ^ key[bnum]
Note details of the AES-CTR nonce, AES-CBC I.V., and key are stored in the firmware itself. In this example they are not downloaded as part of the encrypted firmware file.
cwapi = CWCoreAPI()
cwapi.openProject(r'c:\users\colin\chipwhisperer_projects\tmp\aes_ccm_block1_500traces_clk1x.cwp')
 
tm = cwapi.project().traceManager()
ntraces = tm.numTraces()
 
mod = XOR_Test()
 
startingpoint = 17758
diffpoint = 19
 
best_guess = [0] * 16
 
for bnum in [0]: #Use this first to test on a single byte
#for bnum in range(0,16): #Uncomment this to break all bytes
print "Working on byte %d"%bnum
bguess = 0
for bit in range(0, 8):
print " Bit %d "%bit,
bmask = 1<<bit
ptool = ptools.HWAES(tm, bnum=bnum, model=mod, bmask=bmask)
groups = partition_traces(tm,ptool,0, key_guess=0x00)
diff = dpa(groups)
dp = diff[startingpoint + (diffpoint*bnum)]
print " (%+0.4f) = "%dp,
 
if dp > 0:
print "1"
bguess |= bmask
else:
print "0"
print " guess = %02X"%bguess
best_guess[bnum] = bguess
 
print("Best Guess: [" + ", ".join(["0x%02X"%x for x in best_guess]) + "]")
</syntaxhighlight>
 
Finally - we decrypt the best guess. If this was a valid AES-CTR mode output we'd expect to see the lower bytes as being '''00 01''' for the first data packet:
 
<syntaxhighlight lang="python">
from Crypto.Cipher import AES
key_from_cbcattack = [0x94, 0x28, 0x5D, 0x4D, 0x6D, 0xCF, 0xEC, 0x08, 0xD8, 0xAC, 0xDD, 0xF6, 0xBE, 0x25, 0xA4, 0x99]
aes = AES.new(str(bytearray(key_from_cbcattack)), AES.MODE_ECB)
best_guess = [0x54, 0x61, 0xEB, 0x14, 0xE7, 0x7A, 0xD5, 0xD9, 0xB8, 0x7A, 0xB9, 0x46, 0x57, 0xA4, 0x49, 0xAA]
ctr_test = aes.decrypt(str(bytearray(best_guess)))
print(" ".join(["%02x"%ord(x) for x in ctr_test]))
</syntaxhighlight>
 
This gives us the decrypted value of '''c1 25 68 df e7 d3 19 da 10 e2 41 71 33 b0 00 01'''. This happens to be the same value as saved in the bootloader supersecret.h file, with the expected counter values. So it looks like our attack was a success! We now know the AES-CTR nonce.
 
NB: The nonce in your firmware file (saved in supersecret.h) will probably be different from this.
== Bootloader Interface Code ==
<syntaxhighlightlang="python">
#!/usr/bin/python
# -*- coding: utf-8 -*-
raise IOError("Failed to communicate, no response")
</syntaxhighlight>
 
[[Category:Examples]]
Approved_users, bureaucrat, administrator
1,956
edits

Navigation menu