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

Tutorial A5-Bonus Breaking AES-256 Bootloader

From ChipWhisperer Wiki
Revision as of 06:05, 23 June 2016 by Gdeon (Talk | contribs) (Moved source code)

Jump to: navigation, search

This tutorial is an add-on to Tutorial A5 Breaking AES-256 Bootloader. It continues working on the same firmware, showing how to obtain the hidden IV and signature in the bootloader. It is not possible to do this bonus tutorial without first completing the regular tutorial, so please finish Tutorial A5 first.

This tutorial is under construction! Check back in a few days.

Background

AES in CBC Mode

  • Repeat of theory from tutorial

The IV

  • Suggest some ideas

The Signature

  • Timing attack
  • Show firmware

Exploring the Bootloader

In this tutorial, we have the luxury of seeing the source code of the bootloader. This is generally not something we would have access to in the real world, so we'll try not to use it to cheat. (Peeking at supersecret.h counts as cheating.) Instead, we'll use the source to help us identify important parts of the power traces.

Bootloader Source Code

Inside the bootloader's main loop, it does three tasks that we're interested in:

  • it decrypts the incoming ciphertext;
  • it applies the IV to the decryption's result; and
  • it checks for the signature in the resulting plaintext.

This snippet from bootloader.c shows all three of these tasks:

// Continue with decryption
trigger_high();                
aes256_decrypt_ecb(&ctx, tmp32);
trigger_low();
             
// Apply IV (first 16 bytes)
for (i = 0; i < 16; i++){
    tmp32[i] ^= iv[i];
}

//Save IV for next time from original ciphertext                
for (i = 0; i < 16; i++){
    iv[i] = tmp32[i+16];
}

// Tell the user that the CRC check was okay
putch(COMM_OK);
putch(COMM_OK);

//Check the signature
if ((tmp32[0] == SIGNATURE1) &&
   (tmp32[1] == SIGNATURE2) &&
   (tmp32[2] == SIGNATURE3) &&
   (tmp32[3] == SIGNATURE4)){
   
   // Delay to emulate a write to flash memory
   _delay_ms(1);
}   

This gives us a pretty good idea of how the microcontroller is going to do its job. However, we can go one step further and find the exact assembly code that the target will execute. If you have Atmel Studio and its toolchain on your computer, you can get the assembly file from the command line with

avr-objdump -m avr -D bootloader.hex > disassembly.txt

This will convert the hex file into assembly code, making it more human-readable. The important part of this assembly code is:

 344:	d3 01       	movw	r26, r6
 346:	93 01       	movw	r18, r6
 348:	f6 01       	movw	r30, r12
 34a:	80 81       	ld	r24, Z
 34c:	f9 01       	movw	r30, r18
 34e:	91 91       	ld	r25, Z+
 350:	9f 01       	movw	r18, r30
 352:	89 27       	eor	r24, r25
 354:	f6 01       	movw	r30, r12
 356:	81 93       	st	Z+, r24
 358:	6f 01       	movw	r12, r30
 35a:	ee 15       	cp	r30, r14
 35c:	ff 05       	cpc	r31, r15
 35e:	a1 f7       	brne	.-24     	;  0x348
 
 360:	fe 01       	movw	r30, r28
 362:	b1 96       	adiw	r30, 0x21	; 33
 364:	81 91       	ld	r24, Z+
 366:	8d 93       	st	X+, r24
 368:	e4 15       	cp	r30, r4
 36a:	f5 05       	cpc	r31, r5
 36c:	d9 f7       	brne	.-10     	;  0x364

 36e:	84 ea       	ldi	r24, 0xA4	; 164
 370:	0e 94 16 02 	call	0x42c	;  0x42c
 374:	84 ea       	ldi	r24, 0xA4	; 164
 376:	0e 94 16 02 	call	0x42c	;  0x42c

 37a:	89 89       	ldd	r24, Y+17	; 0x11
 37c:	88 23       	and	r24, r24
 37e:	09 f0       	breq	.+2      	;  0x382
 380:	98 cf       	rjmp	.-208    	;  0x2b2

 382:	8a 89       	ldd	r24, Y+18	; 0x12
 384:	8b 3e       	cpi	r24, 0xEB	; 235
 386:	09 f0       	breq	.+2      	;  0x38a
 388:	94 cf       	rjmp	.-216    	;  0x2b2

 38a:	8b 89       	ldd	r24, Y+19	; 0x13
 38c:	82 30       	cpi	r24, 0x02	; 2
 38e:	09 f0       	breq	.+2      	;  0x392
 390:	90 cf       	rjmp	.-224    	;  0x2b2

 392:	8c 89       	ldd	r24, Y+20	; 0x14
 394:	8d 31       	cpi	r24, 0x1D	; 29
 396:	09 f0       	breq	.+2      	;  0x39a
 398:	8c cf       	rjmp	.-232    	;  0x2b2

 39a:	83 e3       	ldi	r24, 0x33	; 51
 39c:	97 e0       	ldi	r25, 0x07	; 7
 39e:	01 97       	sbiw	r24, 0x01	; 1
 3a0:	f1 f7       	brne	.-4      	;  0x39e
 3a2:	87 cf       	rjmp	.-242    	;  0x2b2

We'll use both of the source files throughout the tutorial.

Power Traces

Matching Power to Code

Attacking the IV

Steps:

  • Investigation
    • Look at bootloader code
    • Move trigger
    • Record 1
    • Show different instructions in trace
  • Making the attack feasible
    • Capture a bunch (500?)
    • Apply decryption
    • Look at one bit
    • Find means + plot
    • Find differences + plot
  • Automating the attack
    • Finding the attack points
    • Getting a single bit
    • Building the IV bytes
  • Full script in appendix

Example:

#Imports for IV Attack
from Crypto.Cipher import AES

def initPreprocessing(self):
    self.preProcessingResyncSAD0 = preprocessing.ResyncSAD.ResyncSAD(self.parent)
    self.preProcessingResyncSAD0.setEnabled(True)
    self.preProcessingResyncSAD0.setReference(rtraceno=0, refpoints=(6300,6800), inputwindow=(6000,7200))
    self.preProcessingResyncSAD1 = preprocessing.ResyncSAD.ResyncSAD(self.parent)
    self.preProcessingResyncSAD1.setEnabled(True)
    self.preProcessingResyncSAD1.setReference(rtraceno=0, refpoints=(4800,5100), inputwindow=(4700,5200))
    self.preProcessingList = [self.preProcessingResyncSAD0,self.preProcessingResyncSAD1,]
    return self.preProcessingList

class AESIVAttack(object):
   numSubKeys = 16

   @staticmethod
   def leakage(textin, textout, guess, bnum, setting, state):
       knownkey = [0x94, 0x28, 0x5D, 0x4D, 0x6D, 0xCF, 0xEC, 0x08, 0xD8, 0xAC, 0xDD, 0xF6, 0xBE, 0x25, 0xA4, 0x99,
                   0xC4, 0xD9, 0xD0, 0x1E, 0xC3, 0x40, 0x7E, 0xD7, 0xD5, 0x28, 0xD4, 0x09, 0xE9, 0xF0, 0x88, 0xA1]
       knownkey = str(bytearray(knownkey))
       ct = str(bytearray(textin))

       aes = AES.new(knownkey, AES.MODE_ECB)
       pt = aes.decrypt(ct)
       return getHW(bytearray(pt)[bnum] ^ guess)

Appendix D AES-256 IV Attack Script

NB: This script works for 0.10 release or later, see local copy in doc/html directory of chipwhisperer release if you need earlier versions

Full attack script, copy/paste into a file then add as active attack script:

#IV Attack Script
from chipwhisperer.common.autoscript import AutoScriptBase
#Imports from Preprocessing
import chipwhisperer.analyzer.preprocessing as preprocessing
#Imports from Capture
from chipwhisperer.analyzer.attacks.CPA import CPA
from chipwhisperer.analyzer.attacks.CPAProgressive import CPAProgressive
import chipwhisperer.analyzer.attacks.models.AES128_8bit
# Imports from utilList

# Imports for AES256 Attack
from chipwhisperer.analyzer.attacks.models.AES128_8bit import getHW

#Imports for IV Attack
from Crypto.Cipher import AES

class AESIVAttack(object):
   numSubKeys = 16

   @staticmethod
   def leakage(textin, textout, guess, bnum, setting, state):
       knownkey = [0x94, 0x28, 0x5D, 0x4D, 0x6D, 0xCF, 0xEC, 0x08, 0xD8, 0xAC, 0xDD, 0xF6, 0xBE, 0x25, 0xA4, 0x99,
                   0xC4, 0xD9, 0xD0, 0x1E, 0xC3, 0x40, 0x7E, 0xD7, 0xD5, 0x28, 0xD4, 0x09, 0xE9, 0xF0, 0x88, 0xA1]
       knownkey = str(bytearray(knownkey))
       ct = str(bytearray(textin))

       aes = AES.new(knownkey, AES.MODE_ECB)
       pt = aes.decrypt(ct)
       return getHW(bytearray(pt)[bnum] ^ guess)

class userScript(AutoScriptBase):
    preProcessingList = []
    def initProject(self):
        pass

    def initPreprocessing(self):
        self.preProcessingResyncSAD0 = preprocessing.ResyncSAD.ResyncSAD(self.parent)
        self.preProcessingResyncSAD0.setEnabled(True)
        self.preProcessingResyncSAD0.setReference(rtraceno=0, refpoints=(6300,6800), inputwindow=(6000,7200))
        self.preProcessingResyncSAD1 = preprocessing.ResyncSAD.ResyncSAD(self.parent)
        self.preProcessingResyncSAD1.setEnabled(True)
        self.preProcessingResyncSAD1.setReference(rtraceno=0, refpoints=(4800,5100), inputwindow=(4700,5200))
        self.preProcessingList = [self.preProcessingResyncSAD0,self.preProcessingResyncSAD1,]
        return self.preProcessingList

    def initAnalysis(self):
        self.attack = CPA(self.parent, console=self.console, showScriptParameter=self.showScriptParameter)
        self.attack.setAnalysisAlgorithm(CPAProgressive, AESIVAttack, None)
        self.attack.setTraceStart(0)
        self.attack.setTracesPerAttack(100)
        self.attack.setIterations(1)
        self.attack.setReportingInterval(25)
        self.attack.setTargetBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
        self.attack.setTraceManager(self.traceManager())
        self.attack.setProject(self.project())
        self.attack.setPointRange((4800,6500))
        return self.attack

    def initReporting(self, results):
        results.setAttack(self.attack)
        results.setTraceManager(self.traceManager())
        self.results = results

    def doAnalysis(self):
        self.attack.doAttack()

Attacking the Signature