Бутлоадер з AES-128 і EAX на AVR Assembler в 1024 байта

    
 Або як я перестав боятися і полюбив асемблер
 
Одного разу влітку, я влаштувався в рідному університеті програмістом мікроконтролерів. У процесі спілкування з нашим головним інженером (здрастуйте, Олексій!), Я дізнався, що чіпи спилюють, проекти крадуть, замовники кидають і поява китайського клону наших программаторов для автомобільної електроніки — лише питання часу, задавити їх можна тільки високою якістю. При всьому цьому, в параною впадати не можна, користувачі навряд чи захочуть працювати з нашими залозками в ошийниках з вибухівкою.
 
Хороша міра захисту — оновлення програмного забезпечення. Китайські клони автоматично відмирають після кожної нової прошивки, а лояльні користувачі отримують нашу любов, турботу і нові можливості. Робін Гуди при такому розкладі, природно, дістануть свої логічні аналізатори, HEX-редактори і почнуть колупати процес прошивки з задоволення російсько-китайського співтовариства.
 
Хоч у нас і не було проектів, які вимагають подібних заходів захисту, було зрозуміло: зайнятися цим треба, колись це стане в нагоді. Погуглити — не знайдено, придумано — зроблено. У цій статті, я розповім, як вмістити повноцінне шифрування в 1 кілобайт і чому асемблер — це прекрасно. Багато тексту, коду і невеликий сюрприз для любителів старого заліза.
 
 

Платформа і мова

Всі проекти ми робили на ATmega просто тому що в СНД їх простіше і дешевше всього купити. Як я зрозумів з розмов зі старшими, це вельми глючне сімейство і потрібно виконувати багато дивних дій, щоб воно безвідмовно працювало. На асемблері ми писали, тому що завдання вкрай чутливі до швидкості роботи. Покладатися на компілятор було не можна і багато ділянок коду були прораховані по тактам. До того ж, тільки на асемблері можна робити неможливе.
 
Скажу чесно, спочатку я боявся вивчати асемблер, думаючи, що він викликає незворотні пошкодження мозку і програмістську інвалідність. Та й не просто так люди винайшли С — асемблер, має бути, дуже складна штука. Як виявилося, асемблер — найпростіший мова в світі. Нічого простіше асемблера не існує. Моргати светодіодик, входити в переривання і користуватися стеком я навчився за один день і приступив до роботи. Через пару місяців, досвіду було достатньо для написання проекту, про який піде мова в цій статті.
 
В асемблері немає ніяких складних абстракцій, що й забезпечує цю тупоголові простоту. Конкретно в AVR-версії немає навіть циклів. Щоб організувати цикл, потрібно взяти регістр, помістити в нього кількість ітерацій, зменшити регістр на одиничку в кінці тіла і, якщо там ще не 0 — стрибнути (якимось із братів goto) на початок тіла. До таких дивним конструкціям дуже швидко звикаєш і перестаєш боятися.
 
 

Що таке бутлоадер

Бутлоадер — програма, яка запускається відразу ж після пожвавлення мікроконтролера. Що бутлоадер робить — вирішує програміст. Як правило, бутлоадере відповідальні за деякі дуже системні функції, на зразок оновлення ПЗ, завантаження ОС або настройку оточення для роботи наступних програм. Наш бутлоадер виконуватиме одну-єдину функцію — оновлювати прошивку мікроконтролера.
 
В архітектурі AVR, бутлоадер може бути викликана двома способами — нацьковуванням на нього reset-вектора або установкою фьюз BOOTRST, який змусить мікроконтролер почати роботу не з 0 адреси, а з адреси початку бутлоадере, розмір якого задається, знову ж таки, в фьюз.
 
Які проблеми можуть виникнути на цьому етапі? Фьюз можна редагувати. Сильно розумний «користувач» може, наприклад, перепрограмувати BOOTRST і робота почнеться з reset-вектора, а не нашого буту. Він може змінити розмір бутлоадере, мікроконтролер почне виконувати якусь єресь з середини буту і це може скомпрометувати систему.
 
Значить, ми повинні перенаправити сам reset-вектор на наш бутлоадер, а в його тілі розставити безумовні переходи на початок, в тих місцях, які відповідають різним розмірам бутової області. Без проблем.
 
Бутлоадер визначатиме, виконувати йому користувача програму, або перейти в режим прошивки, по наявності або відсутності рівня на певному виведенні.
 
 

Криптографія

Перед початком роботи, я закінчив 1 частина курсу з криптографії від Дена Боні на Coursera. Професор Ден постійно говорив про те, що ні в якому разі не можна писати криптографічні системи самостійно, цим повинні займатися професіонали, а не студенти-першокурсники в свої канікули. Безумовно. На виправдання можу сказати, що цей бутлоадер і не позиціонується як невразлива до всіх атак штука. Його існування — не основна, а додаткова міра захисту інтелектуальної власності. Коротше кажучи, людина повинна поколупатися у файлі поновлення, потикатися осциллографом, помучитися недельку, подумати «а ну його до біса, заплачу 2000 $ китайцям, вони його спиляє» і зробити саме це. Вартість програмного злому повинна перевищувати вартість фізичного злому, не більше.
 
Мікроконтролер — відкрита система, всі інженери знають, як з ним працювати. Мікроконтролери можуть пропригиваєт команди при оверклоке, можуть «забувати» про захист при певному зниженій напрузі, можуть мати дуже дурні діри, начебто можливості запису і виконання коду в Flash з RAM, при неможливості читання (приклад — ST92F). Якщо є можливість змінити буквально пару байт прошивки — вона буде злита повністю. У прошивці, як правило, є області з очікуваною структурою — наприклад, невикористовувані вектора переривань, що полегшує зміна пари байт методом тику. Значить, простим блоковим шифром в режимі CBC / CTR не обійтися.
 
Якщо ніяк не можна допустити можливість зміни прошивки — доведеться використовувати код автентичності повідомлень. Приклади таких кодів — CCM, GCM, EAX. Чесно кажучи, я вже погано пам'ятаю, чому вибрав саме EAX. Швидше за все, його майбутня реалізація на асемблері здалася мені найбільш простий.
 
Кожна прошивка буде мати свій, випадково-згенерований ключ, що підключається у вигляді окремого файлу. Цим же файлом шифруються оновлення. Захист полягає в тому, що ми просто не будемо випускати нові прошивки для скомпрометованих ключів. Доведеться знати кожного користувача в обличчя, але безпека вимагає жертв. Також, під час генерації ключа, будуть обчислюватися деякі константи, щоб микроконтроллеру не довелося це робити самостійно.
 
 

EAX

Скільки документації потрібно інженеру, щоб написати шифрування? Дві картинки, взяті з наукової роботи .
 
 
 
Допоміжні алгоритми
Ми гарантуємо кратність обсягу даних розміром блоку на етапі шифрування прошивки, що зводить функцію pad до M ^ B (31) і змінна P (32) не буде використана ніколи.
На етапі генерації ключа, ми можемо обчислити константу L (40) і, як наслідок, B.
«Що виключає або зі стрілочкою» (31) означає, що B буде в-xor-ено тільки в кінець рядка M.
 
 
 
Процес шифрування
Де M — незашифрований текст, CT — шифрований текст, K — ключ, N — вектор ініціалізації, H — заголовок.
На асемблері необхідно буде написати тільки функцію дешифрування.
 
Нехай заголовок повідомлення H буде заданий, незмінний і відомий тільки нам. Це не впливає на крипостійкість, так як мається на увазі, що заголовок — публічна інформація. Тепер можна обчислити H '(23) на етапі генерації ключа.
Якщо подивитися на рядки 22, 24, укупі з 50 і 10, можна усвідомити, що циферка у верхньому індексі при OMAC перейде в останній символ рядка з довжиною, що дорівнює довжині блоку і буде використана в якості першого блоку в CBC тобто зашифрована. Шифрування цих рядків можна провести на етапі генерації ключа. Причому, у файл ключа будуть включені тільки зашифровані рядки з числами 0 (L0) і 2 (L2) — для обчислення N 'і C' відповідно.
Маючи на руках L0, B і N, обчислення N 'зводиться до E (L0 ^ B ^ N).
 
Отже, на етапі генерації ключа будуть обчислені B, L0, L2, H '.
Разом вони займуть 64 байта.
 
 

AES-128

AES — геніальний у своїй простоті алгоритм. До того ж, він має велику гнучкість, залежно від того, потрібна нам продуктивність або обіймав обсяг. У нашому випадку, критично важливий обіймав обсяг і простота. Про AES написано багато хороших статей, не буду вдаватися в подробиці його пристрою.
 
Особливістю AES є те, що дешифрування алгоритмічно складніше самого шифрування. У процесі дешифрування, потрібно множити на 14 в кінцевому полі. Оскільки зошитах таблиць множення в цьому жаху у нас немає, будемо користуватися AES нестандартно — для шифрування поновлення на потужному комп'ютері, будемо «дешифрувати» його, а при дешифрування на слабенькому мікроконтролері — «шифрувати». Різниці в криптостойкости немає.
 
Я не придумав жодного ефективного (і безпечного) способи розширення ключів. Підготовка всіх 11 примірників виконується на етапі генерації ключа. В принципі, з цієї причини можна зробити весь блок ключів повністю випадковим — це трохи збільшить захист від брутфорса, якщо якийсь бобер-збоченець вирішить їм зайнятися.
Розширений ключ займе 176 байт. Разом з підрахованими константами, він утворює файл ключів, який займе навчань 240 байт Flash. Залишилося 784 тобто 392 асемблерні команди.
 
Життєво важливою і величезною частиною AES є таблиця підстановки — байти, на які замінюються байти тексту. У байті аж 256 можливих комбінацій і таблиця займе стільки ж Flash-пам'яті. Неприпустимо! Значить, будемо її обчислювати.
 
Таблиця підстановки обчислюється таким чином: спочатку знаходиться число, зворотне номеру елемента таблиці, потім, це зворотне, піддається наступного афінних перетворень:
 
 
 
x0… x7 — зворотне число у вигляді вектора. Це потрібно написати на асемблері і вкластися в менше ніж 256 байт, щоб була вигода. Ми вкладемося в 88 байт. Приступимо.
 
 

Hello, world!

Програміст на асемблері завжди починає програму з спрощення власного життя. Воно полягає в перейменуванні регістрів, щоб, наприклад, замість r16 можна було писати temp_0. Я, за традицією, називаю 0 і 1 регістри OP0 і OP0, 2 і 3 — NULL і OxFF (заповнюючи їх відповідними значеннями), а решта — як доведеться.
 
 Регістри
.def	OP0	= r0
.def	OP1	= r1

.def	NULL	= r2
.def	OxFF	= r3

.def	b0 = r4
.def	b1 = r5
.def	b2 = r6
.def	b3 = r7
.def	b4 = r8
.def	b5 = r9
.def	b6 = r10
.def	b7 = r11
.def	b8 = r12
.def	b9 = r13
.def	bA = r14
.def	bB = r15

.def	bC = r16
.def	bD = r17
.def	bE = r18
.def	bF = r19
.def 	temp_0 = r20
.def 	temp_1 = r21
.def 	temp_2 = r22
.def 	temp_3 = r23
.def	temp_4 = r24
.def	temp_5 = r25

У нашому випадку, довелося використовувати майже всі регістри, з 4-го по 19-й, для зберігання розшифровували блоку даних. Я виніс його в регістри бо працювати з ними набагато простіше і швидше. Будь-яка діяльність з оперативною пам'яттю в мікроконтролерах все одно призводить до роботи з регістрами, навіщо платити більше.
 
Регістри з 20 по 25 використовуються для тимчасового зберігання обчислюваних даних і названі відповідно.
З 26 до 32 йдуть спеціальні 16-бітові регістри — X, Y, Z, використовувані для адресації пам'яті. Причому, тільки Z може використовуватися для адресації Flash. Як я чув, вони фізично мають технічні пристосування для інкремента, декремента і обчислення зміщення, тому, застосування їх в інших цілях, хоч і можливо, вважається поганою практикою і може призводити до неприпустимим глюків у відповідальних додатках.
 
Крім перейменування регістрів, порахуємо деякі корисні константи — кількість байт в сторінці Flash-пам'яті, розмір всього бутлоадере, розмір блоку шифру, кількість сторінок пам'яті, кількість блоків даних на сторінку і адресу, за якою потрібно розташувати файл ключів:
 
 Константи
.equ PAGE_BYTES 	 = PAGESIZE*2
.equ BOOT_SIZE		 = 1024
.equ BLOCK_SIZE		 = 16
.equ PAGES 		 = (FLASHEND+1)/PAGESIZE - BOOT_SIZE/PAGE_BYTES
.equ BLOCKS_PER_PAGE	 = PAGE_BYTES / BLOCK_SIZE
.equ KEY_ADDR 		 = (FLASHEND + 1) — (BLOCK_SIZE*(11+4))/2

Додамо майбутнього чуду гнучкості — дозволимо вибирати швидкість зв'язку по UART, порт, висновок і рівень, за яким ми будемо вирішувати, чи залишатися в бутлоадере або продовжити завантаження. Крім таких утилітарних налаштувань, можна задати адресу мітки, на яку буде здійснений стрибок, у разі, якщо прошивка не потрібно, а також, вирішити, чи повинен бутлоадер перешивати нульову сторінку пам'яті — там розташований вектор переривань і його перезапис зловмисником може скомпрометувати систему.
 
 Налаштування
;Reset address (Where to jump to if not asked to load boot)
.equ RESET_VECT		= 0
;Is 0th flash page used?
.equ USE_0th_PAGE	= 1

;////////////////////////PORT SETUP
// use port letter...
// A / B / C / D / E
.equ port_used = 'C'
// check status of pin number...
.equ pin   = 6
// load boot only if port is...
// (S)ET (1) / ©LEAR (0)
.equ level = 'C'

;////////////////////////BAUD RATE SETUP
.equ Fosc = 16000000	;clock frequency
.equ baud = 19200	;baud rate

.equ UBRR = Fosc / ( BLOCK_SIZE * baud ) - 1
.if high( UBRR ) != 0
	.error "Unsupported baud rate setting - high byte of UBRR is not 0!"
.endif

Тепер, розберемося з оперативною пам'яттю. AES вимагає 256 байт під таблицю заміни. Блок даних складається з вектора ініціалізації (16 байт), безпосередньо даних (розмір сторінки), мітки для перевірки цілісності (16 байт). Для розшифрування даних, нам потрібно згенерувати дешифрувального послідовність на основі вектора ініціалізації — ще один розмір сторінки.
 
 застовпити місце в пам'яті
.dseg
.org 0x60
	SBOX:	.byte 256		;rijndael substitution box
	;these three SHOULD be consecutive
	SAVED_IV:	.byte BLOCK_SIZE	;E(L0^N^B)
	RCVD_PAGE:	.byte PAGE_BYTES	;page to be written
	TAG:	.byte BLOCK_SIZE	;initially - precomputed header value

	ENC_IV:	.byte PAGE_BYTES	;IV's to xor with page to decrypt
.cseg

На ATmega16, з розміром сторінки 64 байта, обсяг використаної оперативної пам'яті — 544 байта з 1024. На ATmega8 — 416. Забагато. Існують мікроконтролери з великими сторінками Flash-пам'яті при малому обсязі оперативною. Можливо, можна щось придумати, але сумісність з усім сімейством мало кому знадобиться.
 
З директивами препроцесора познайомилися, перейдемо до ассемблеру. Програма традиційно починається з ініціалізації покажчика стека, вимикання переривань, установки регістрів NULL і OxFF, установки налаштувань UART.
 
 Ініціалізація
BOOT_START:
	ldi		temp_0,	low( RAMEND)
	out		SPL,	temp_0
	ldi		temp_0,	high(RAMEND)
	out		SPH,	temp_0

	cli

	clr		NULL
	mov		OxFF,	NULL
	com		OxFF

	ldi		temp_0,	low( UBRR )
	out		UBRRH,	NULL
	out		UBRRL,	temp_0
	ldi		temp_0,	( 1 << RXEN ) | ( 1 << TXEN )
	out		UCSRB,	temp_0
	ldi		temp_0,	( 1 << URSEL ) | ( 1 << UCSZ1 ) | ( 1 << UCSZ0 )
	out		UCSRC,	temp_0

На все це нам вистачило аж 6 різних асемблерних команд, які є абревіатурами або скороченнями. ldi — load into, mov — move, out — output to I \ O register, com — complement, cli — clear interrupt flag. Як я вже говорив, асемблер простий до неможливості. «Складні» частини, з усякими незрозумілими UBRRH (UART Baud Rate Register High-byte), детально описані в даташітах і є налаштуванням обладнання.
 
Вирішимо, залишатися в буті, чи ні. Вибір регістру порту, згідно налаштуванням користувача, виконується на етапі збирання:
 
 Усе ще нічого цікавого, можна не дивитися
.if port_used == 'A'
	cbi		DDRA,	pin
	sbi		PORTA,	pin
	nop
	.if level == 'S'
		sbis	PINA,	pin
	.elif level == 'C'
		sbic	PINA,	pin
	.endif

.elif port_used == 'B'
	cbi		DDRB,	pin
	sbi		PORTB,	pin
	nop
	.if level == 'S'
		sbis	PINB,	pin
	.elif level == 'C'
		sbic	PINB,	pin
	.endif

.elif port_used == 'C'
	cbi		DDRC,	pin
	sbi		PORTC,	pin
	nop
	.if level == 'S'
		sbis	PINC,	pin
	.elif level == 'C'
		sbic	PINC,	pin
	.endif

.elif port_used == 'D'
	cbi		DDRD,	pin
	sbi		PORTD,	pin
	nop
	.if level == 'S'
		sbis	PIND,	pin
	.elif level == 'C'
		sbic	PIND,	pin
	.endif

.elif port_used == 'E'
	cbi		DDRE,	pin
	sbi		PORTE,	pin
	nop
	.if level == 'S'
		sbis	PINE,	pin
	.elif level == 'C'
		sbic	PINE,	pin
	.endif
.endif
	;if user asked to load boot - this will be skipped
	jmp		RESET_VECT		

Трохи оригінальної буде процедура читання \ запису в послідовний порт. Так як у нас мало місця і невеликі швидкості, я вирішив поєднати підтвердження готовності до роботи з отриманням даних і розбити процедуру на кілька, мають різні можливості. На асемблері, це дуже просто зробити, викликаючи підпрограму за різними матюками:
 
 Готовність та отримання за 8 команд
;UART   <- 0xC0
;temp_0 <- UART
confirm_and_read:
	ldi		temp_0,	0xC0
;UART   <- temp_0
;temp_0 <- UART
UART_send:
	sbis	UCSRA,	UDRE		;skip next command if readiness bit is set
	rjmp	UART_send
	out		UDR,	temp_0
;temp_0 <- UART
UART_read:
	sbis	UCSRA,	RXC
	rjmp	UART_read
	in		temp_0,	UDR
	ret	

Сумовита частина завершена, світ для майбутньої роботи побудований. Почнемо працювати.
 
 

Таблиця підстановки за 88 байт

Для початку, потрібно знайти число, зворотне даному, в кінцевому полі. Таке, щоб при множенні цього числа на дане, вийшла 1. Алгоритм множення описаний у статті на вікі , не буду приводити його.
 
Знаходження зворотного в кінцевому полі — складне завдання. Тут потрібно застосувати розширений алгоритм Евкліда… але ми інженери. Приберіть випускників computer science від екранів. Ми шукаємо зворотний елемент повним перебором, за допомогою множення.
 
 Пошук зворотного елемента
ldi		XH,	high(SBOX)	;point X to SBOX memory location
	ldi		XL,	low( SBOX)
	ser		bF			;first inc will overflow to 0
next_box:
	inc		bF
	mov		temp_1,	bF	;save input in temp_1
	cp		temp_1,	NULL	;if it's null - return
	breq	sbox_byte_done	;return here
	mov		OP0,	OxFF	;so it overflows

look_more:
	inc		OP0		;try next candidate

;temp_0 <- OP0 * temp_1 (in a Galois field)
;branching is fine, function used in precomputation only
finite_multiplication:
	mov		b0,	OP0	;operand 0 (candidate)
	mov		b1,	temp_1	;operand 1 (current byte)

	ldi		temp_2,	0x1B	;0x1B holder
	clr		temp_0		;multiplication result

next_bit:
	lsr		b0		;operand 0 >> 1
	brcc	PC+2		;if lsb of operand 0 was 1
	eor		temp_0,	b1	;xor operand 1 into result

	lsl		b1		;operand 1 << 1
	brcc	PC+2		;if msb of operand 1 was 1
	eor		b1,	temp_2	;xor 0x1B into operand 1

	cp		b0,	NULL	;while there are bits in operand0
	brne	next_bit	;work on it

	cpi		temp_0,	1	;if multiplication result was not 1
	brne	look_more	;inverse is in OP0

Після чого, проводимо афінне перетворення і зберігаємо результат в пам'ять. Воно зібране з найпростіших кубиків. Програмування на асемблері — чудова вправа для мізків. Завжди можна знайти більш елегантне рішення, зберегти ще пару команд, вичавити ще пару тактів, причому, ця економія на сірниках часом — питання життя і смерті. Це паралельний чарівний світ програмування, в якому процес кодинга перетворений в збірку конструктора.
 
 Афінний перетворення на асемблері
clr		temp_1			;affine transform result
	ldi		temp_5,	0b11110001	;matrix producer
	ldi		temp_3,	0b00000001	;current bit mask

process_bit:
	mov		temp_4,	OP0		;multiplicative inverse
	and		temp_4,	temp_5		;and with matrix producer

pop_next_bit:
	lsl		temp_4			;inv&matrix << 1
	brcc	PC+2			;if it had msb
	eor		temp_1,	temp_3		;sum bit into result
	cp		temp_4,	NULL		;while operand has bits
	brne	pop_next_bit		;work on it
	
	lsl		temp_3			;move to next bit
	lsl		temp_5			;cyclically shift matrix producer
	brcc	PC+2			;if it had msb
	ori		temp_5,	1		;move msb to lsb
	cp		temp_3,	NULL		;while there are bits left
	brne	process_bit		;process next bit

sbox_byte_done:
	ldi		temp_2,	0b01100011	;0x63
	eor		temp_1,	temp_2		;xor it into result
	st		X+,	temp_1		;save to memory
	cpse	bF,	OxFF		;if we're at last byte
	rjmp	next_box		;we're done 

Місія виконана.
 
 
 
Як швидко все це працює? У симуляторі — 2203268 такту. 0.27 секунди на частоті 8 МГц. Я вважаю, це чудова швидкість.
 
Ми втратили 256 байт оперативної пам'яті і 0.27 секунд на старті, зберігши 168 байт Flash-пам'яті і вирішивши чудову головоломку.
 
Таблиця підміни готова, експансія ключів проведена на комп'ютері — є все необхідне для реалізації AES.
 
 

Assembled Encryption Standard

Почнемо з елементарних операцій. На кожному етапі дешифрування, ключ підсумовується з даними. Дані знаходяться у файлі регістрів, з 4-го, їх таких 16 штук, а на поточний ключ нехай завжди вказує регістр Z. Регістри-покажчики можуть вказувати не тільки на області SRAM або Flash, а й на файл регістрів, що дуже сильно спрощує нам життя і прискорює роботу системи.
 
 Add round key
add_round_key:
	clr		YH		;point to register file
	ldi		YL,	4
xor_Z_to_Y:
	lpm		temp_0,	Z+		;load key byte
	ld		temp_1,	Y		;load data byte
	eor		temp_1,	temp_0		;xor them
	st		Y+,	temp_1		;store back to data
	cpi		YL,	low( 4 + 16 )	;check if it was the last byte
	brne	xor_Z_to_Y		;if not - process next data byte
	ret

Ще одна проста операція — перемішування рядків. Кожен рядок даних циклічно зсувається вліво на свій порядковий номер. Потрібно просто подумати, який байт з яким поміняти і, нарешті, застосувати ці корисні навички обміну двох змінних місцями без використання додаткової змінної. Плюс рішення — можна перемішати всі ці операції між собою для додаткового захисту від атак сторонніми каналами.
 
 Shift rows
;cyclical shift: 0_row << 0; 1_row << 1; 2_row << 2; 3_row << 3
shift_rows:
	;1st row
	eor		b1,	bD
	eor		bD,	b1
	eor		b1,	bD

	eor		b1,	b5
	eor		b5,	b1
	eor		b1,	b5

	eor		b5,	b9
	eor		b9,	b5
	eor		b5,	b9

	;2nd row
	eor		b2,	bA
	eor		bA,	b2
	eor		b2,	bA

	eor		b6,	bE
	eor		bE,	b6
	eor		b6,	bE

	;3rd row
	eor		b3,	bF
	eor		bF,	b3
	eor		b3,	bF

	eor		b7,	bF
	eor		bF,	b7
	eor		b7,	bF

	eor		bB,	bF
	eor		bF,	bB
	eor		bB,	bF
	;done
	ret

Заміна всіх даних на відповідні їм підміни з таблиці — здавалося б, тривіальна задача. Простий підхід, в лоб, має фатальний недолік: він занадто линеен. Лінійність, незмінність і зрозумілість — потенційні вразливості для атаки сторонніми каналами. Грубо кажучи, нам нема чого міняти в реалізації від випадку до випадку, без незмінності результату, для додаткового рівня захисту. Зробимо інакше. Будемо обробляти один стовпець за раз. Міняємо порядок обходу — і атакуючий буде мучитися ще тиждень.
 
За підстановкою завжди слід зміщення рядків, тому, не будемо розділяти ці процедури.

Sub bytes
substitute_shift_rows:
	ldi		XH,	high(SBOX)
	ldi		XL,	low( SBOX)
	movw	OP0,	X

	;one column at a time
	clr		YH
	ldi		YL,	4
sub_next:
	movw	X,	OP0
	ldd		temp_0,	Y+0x08
	add		XL,	temp_0
	adc		XH,	NULL
	ld		temp_0,	X
	std		Y+0x08,	temp_0

	movw	X,	OP0
	ldd		temp_0,	Y+0x0C
	add		XL,	temp_0
	adc		XH,	NULL
	ld		temp_0,	X
	std		Y+0x0C,	temp_0

	movw	X,	OP0
	ldd		temp_0,	Y+0x04
	add		XL,	temp_0
	adc		XH,	NULL
	ld		temp_0,	X
	std		Y+0x04,	temp_0

	movw	X,	OP0
	ldd		temp_0,	Y+0x00
	add		XL,	temp_0
	adc		XH,	NULL
	ld		temp_0,	X
	st		Y+,	temp_0

	sbrs	YL,	3			;XL == 8
	rjmp	sub_next

Наближаємося до порталу в пекло. Перед входом туди, нам потрібно винайти множення на 2. З інженерної точки зору, множення на 2 в кінцевому полі відрізняється від простого тим, що після зсуву вліво, до результату треба додати 0x1B, якщо старший біт множника був одиничкою. Якщо біт був одиничкою, то… не можна використовувати переходи і умови в відповідальних ділянках криптографічних систем. Без проблем! Перед зсувом вліво, ми збережемо старший біт і потім будемо записувати його в потрібні місця порожнього регістра, поки не зберемо там 0x1B, якщо біт був одиничкою, або 0, якщо він був нулем.

Сюрприз. У моїй реалізації, процедура множення на 2 розмістилася на одному з розмірів бутлоадере. В точку попадання цього розміру, помістимо безумовний перехід на початок буту і, щоб він не заважав жити при кожному множенні на 2, перескочимо його.

Sub bytes
;temp_0 <- temp_0 * 2 (in a finite field)
;temp_0 = (temp_0 << 1) ^ (0x1B & MSB(temp_0))
;NO BRANCHING HERE
;uses NULL in a dirty way
mul_by_2:
	bst		temp_0,	7	;store 7th bit in T
	bld		NULL,	0	;we form 0x1B in NULL if T is set
	rjmp	cont_mul	
	rjmp	BOOT_START	;0x1F80. BOOTSZ can be here
cont_mul:
	bld		NULL,	4	
	lsl		temp_0		
	bld		NULL,	3	
	bld		NULL,	1
	eor		temp_0,	NULL
	clr		NULL
	ret

Залишився останній етап — змішання стовпців. Елементи кожного шпальти піддаються наступного перетворенню:



Множення на 2 ми написали вище. Додавання в кінцевому полі — виключає або. Для множення на 3, потрібно просто ще раз додати число до результату множення на 2. Складність у тому, що ми, раптово, пишемо на асемблері, обмежені в обсязі коду і доведеться багато рахувати. Оптимізації доведеться виконувати в розумі і коментарях. Потрібно дуже акуратно продумати хід обчислень і використовувати регістри з розумом.

Mix columns
mix_columns:
	;point to register file
	clr		YH
	ldi		YL,	4
next_column:
	ldd		temp_2, Y+0x00	;result0
	ldd		temp_3, Y+0x01	;r1
	ldd		temp_4, Y+0x02	;r2
	ldd		temp_5, Y+0x03	;r3
	mov		temp_0, temp_3	;r1 to operand
	rcall	mul_by_2	;r1 * 2

	mov		temp_1, temp_0	;save r1 * 2
	eor		temp_0, temp_2  ;r0 + r1 * 2 
	eor		temp_0, temp_5	;r0 + r1 * 2 + r3 (lacks r2 * 3)
	std		Y+0x01,	temp_0	;to r1
	mov		temp_0,	temp_2	;r0 to operand
	rcall	mul_by_2	;r0 * 2

	mov		OP0, temp_0		;OP0 <- r0 * 2
	eor		temp_0, temp_1	;r0 * 2 + r1 * 2
	eor		temp_0, temp_3	;r0 * 2 + r1 * 3
	eor		temp_0, temp_4	;r0 * 2 + r1 * 3 + r2
	eor		temp_0, temp_5	;r0 * 2 + r1 * 3 + r2 + r3 (done)
	std		Y+0x00, temp_0	;to r0
	mov		temp_1, OP0	;OP0 -> r0 * 2
	mov		temp_0,	temp_5	;r3 to operand
	rcall	mul_by_2	;r3 * 2

	mov		OP0,	temp_0	;OP0 <- r3 * 2
	eor		temp_0, temp_1	;r3 * 2 + r0 * 2
	eor		temp_0, temp_2	;r0 * 3 + r3 * 2
	eor		temp_0, temp_3	;r0 * 3 + r1 + r3 * 2
	eor		temp_0, temp_4	;r0 * 3 + r1 + r2 + r3 * 2 (done)
	std		Y+0x03,	temp_0	;to r3
	mov		temp_1, OP0	;OP0 -> r3 * 2
	mov		temp_0,	temp_4	;r2 to operand
	rcall	mul_by_2	;r2 * 2

	mov		OP0, 	temp_0	;OP0 <- r2 * 2
	eor		temp_0, temp_1	;r2 * 2 + r3 * 2
	eor		temp_0, temp_5	;r2 * 2 + r3 * 3
	eor		temp_0, temp_2	;r0 + r2 * 2 + r3 * 3
	eor		temp_0, temp_3	;r0 + r1 + r2 * 2 + r3 * 3 (done)
	std		Y+0x02, temp_0	;to r2

	mov		temp_1, OP0	;OP0 -> r2 * 2
	ldd		temp_0,	Y+0x01	;r0 + r1 * 2 + r3
	eor		temp_0, temp_1	;r0 + r1 * 2 + r2 * 2 + r3
	eor		temp_0, temp_4	;r0 + r1 * 2 + r2 * 3 + r3 (done)
	std		Y+0x01,	temp_0	;to r1

	adiw	Y,	4	;pointer to next column
	cpi		YL,	20	;if not done
	brne	next_column	;process next
	ret

Просто багато арифметики. У процесі обчислення, відбувається 6 звернень до пам'яті замість очікуваних 4, але, як мені здається, максимально можлива просторова оптимізація досягнута.

Отже, всі необхідні для AES етапи шифрування написані. Зберемо їх воєдино. Зберігати покажчики в стек перед початком роботи і відновлювати їх пізніше — хороша практика. Майже завжди вони використовуються десь ще й основна програма не очікує такої підстави, як зміна покажчиків на пам'ять в одній з процедур. Те ж саме відноситься і до статус-регістру. Якщо ви не обмежені в просторі — завжди зберігайте статус-регістр на початку процедури і відновлюйте його перед поверненням!

Само шифрування
;performs a round of encryption 
;using given expanded keys and s-box
Rijndael_encrypt:
	push	ZH
	push	ZL
	push	YH
	push	YL
	push	XH
	push	XL

	ldi		ZH,	high(KEYS*2)
	ldi		ZL,	low( KEYS*2)
	rcall	add_round_key
	ldi		temp_0,	9

encryption_cycle:
	push	temp_0	;store cycle counter

	rcall	substitute_shift_rows
	rcall	mix_columns
	rcall	add_round_key

	rjmp	continue_enc
	rjmp	BOOT_START		;0x1F00. BOOTSZ can be here
continue_enc:

	pop		temp_0	;restore cycle counter
	dec		temp_0
	brne	encryption_cycle

	rcall	substitute_shift_rows
	rcall	add_round_key

	pop		XL
	pop		XH
	pop		YL
	pop		YH
	pop		ZL
	pop		ZH
	ret

Порахуємо, у скільки байт ми вклалися. Додавання з ключем — 18 байт. Множення на 2 — 22 байта. Зрушення рядків — 50 байт. Підстановка — 62 байта. Перемішування стовпців — 94 байта. Зв'язати все це — 56 байт. Сумарно — 302 байта. Зрозуміти, що ти це зробив — безцінне. Трохи більше середнього розміру заголовка виконуваного файлу Windows.

Блочний шифр — добре, але без спеціального режиму шифрування, він практично даремний. Якщо блоковому шифру нічого робити — він теж марний, тому, займемося прийомом даних.

Протокол обміну інформацією

Блок зашифрованих даних в EAX, як і в практично будь-який інший схемою аутентифицированного шифрування, складається з вектора ініціалізації, самих даних і підпису. Вектор ініціалізації і підпис мають довжину блоку ніжележащего шифру, самі дані довільної довжини, в нашому випадку — розмір сторінки Flash. Згадайте етап виділення пам'яті — елементи блоку даних розташовані в SRAM послідовно, для полегшення процедури прийняття даних від комп'ютера.

З метою спрощення конструкції, що не будемо замислюватися про будь-химерному форматі даних — ми приймаємо прошивку, яка повинна заповнити собою весь обсяг Flash. Всі області пам'яті, які безпосередньо в прошивці не задіяні, зобов'язані бути заповнені випадковими байтами. Таким чином, не можна випустити маленький патч, що виправляє буквально пару команд — бутлоадер буде перешивати все. Це може бути довго, але безпека понад усе.

Протокол обміну даними наступний: відразу після генерації таблиці підміни, бутлоадер видає в порт байт 0xC0 (COnfirm) і чекає байт 0x60 (GO). Після сигналу готовності від комп'ютера, бут виставляє покажчик Z на початок записуваної області пам'яті, записує в temp_0 кількість сторінок, які потрібно буде прийняти, розшифрувати і записати, і переходить до прийому сторінки. Виглядає це таким чином:

Початок роботи
wait_for_start:
	rcall	confirm_and_read
	cpi		temp_0,	0x60
	brne	wait_for_start

;/////////////////////////////PAGE ADDR INIT
.if USE_0th_PAGE == 0
	ldi		ZH,	high(PAGE_BYTES)
	ldi		ZL,	low( PAGE_BYTES)
	ldi		temp_0,	PAGES - 1
.else 
	clr		ZH
	clr		ZL
	ldi		temp_0,	PAGES
.endif

next_page:
	;save page counter and address
	push	temp_0
	push	ZH
	push	ZL

;/////////////////////////////BLOCK RECEPTION
	;receive whole block
	ldi		XH,	high(SAVED_IV)
	ldi		XL,	low( SAVED_IV)
	ldi		temp_1,( BLOCK_SIZE /*nonce*/ + PAGE_BYTES /*page*/ + BLOCK_SIZE /*expected tag*/ )
get_more_block:
	rcall	confirm_and_read
	st		X+,	temp_0
	dec		temp_1
	brne	get_more_block

Як ми пам'ятаємо з лістингу функції confirm_and_read, вона спочатку відправляє 0xC0, потім чекає відповіді. Це забезпечує синхронізацію з комп'ютером в її найпростішому вигляді — програмне забезпечення повинне відправляти наступний байт тільки при повній готовності приймаючої сторони. Це, звичайно, повільно — на прийом-передачу даних йде більше часу, ніж на розшифрування, яким ми зараз займаємося.

EAX Assembled

Якщо ми будемо реалізовувати EAX саме так, як показано на Двох документуються Картинках — ми не вкладемося в об'єм. Тому, підкоригуємо хід подій.

Підпис — сума зашифрованого вектора ініціалізації (N, від Nonce), заголовка даних (його обробка виконується на етапі генерації ключа) та коду аутентифікації самих даних, обчисленого за допомогою алгоритму CMAC \ OMAC. Обчислена на місці підпис має зійтися з тією, яку нам прислали. Виключає або двох однакових чисел дорівнює 0. Значить, будемо підсумовувати всі обчислювані значення прямо в отриману підпис, а потім перевіримо, чи всі її значення звернулися в 0.

Нам вже відомий H '- заданий і оброблений заголовок, що знаходиться у файлі ключів. Відразу ж складемо його з отриманої підписом:

Початок роботи
;/////////////////////////////TAG INITIALIZATION
	;initialize precomputed header with tag
	;tag <- H ^ tag
header_to_tag:
	ldi		ZH,	high(PRECOMP_HEADER_TAG*2)
	ldi		ZL,	low( PRECOMP_HEADER_TAG*2)
	ldi		YH,	high(TAG)
	ldi		YL,	low( TAG)
next_header_byte:
	lpm		temp_0,	Z+	
	ld		temp_1,	Y
	eor		temp_0,	temp_1
	st		Y+,	temp_0
	cpi		YL,	low( TAG + BLOCK_SIZE )
	brne	next_header_byte

Наступний простий етап — обчислення N '. Всі необхідні дані для цього у нас є. Полегшимо собі життя, виділивши всі маніпуляції з блоками пам'яті в окремі процедури. Нам може знадобитися переміщення блоку даних у файл регістрів, складання двох блоків даних за вказівником і.т.п. У двох процедурах, залежно від мітки виклику, розмістилося аж 9. Чи не чув, щоб така оптимізація була можлива на мові більш високого рівня.

Допоміжні функції
;block <- block ^ Z
xor_Z_to_block_RAM:
	ldi		YH,	0
	ldi		YL,	4
;Y <- Y ^ Z
xor_Z_to_Y_RAM:
	ldi		temp_2,	BLOCK_SIZE
;Y <- Y ^ Z ( temp_2 times )
ram_xor_cycle:
	ld		temp_3,	Z+
	ld		temp_1,	Y
	eor		temp_1,	temp_3
	st		Y+,		temp_1
	dec		temp_2
	brne	ram_xor_cycle
	ret

;block -> SAVED_IV
save_IV:
	ldi		YH,	high(SAVED_IV)
	ldi		YL,	low( SAVED_IV)
;block -> Y
from_regs_to_Y:
	ldi		XH,	0
	ldi		XL,	4
	rjmp	move_from_X_to_Y
;SAVED_IV -> block
rest_IV:
	ldi		XH,	high(SAVED_IV)
	ldi		XL,	low( SAVED_IV)
;X -> block
from_X_to_regs:
	ldi		YH,	0
	ldi		YL,	4
;X -> Y
move_from_X_to_Y:
	ldi		temp_0,	0x10
;X -> Y ( temp_0 times )
ram_save_cycle:
	ld		temp_1,	X+
	st		Y+,	temp_1
	dec		temp_0
	brne	ram_save_cycle
	ret

Тепер, приступимо до обчислення N 'згідно документуються картинки. Тут використовується підготовлене на етапі генерації ключа B і L0. Все, що відбувається, традиційно, описується в коментарях. Наприкінці обчислення, отримане N 'підсумовується з підписом.

Обробка вектора ініціалізації
;/////////////////////////////NONCE
	;block <- N
	ldi		XH,	high(SAVED_IV)
	ldi		XL,	low( SAVED_IV)
	rcall	from_X_to_regs
	;block <- N ^ B
	ldi		ZH,	high(PRECOMP_B*2)
	ldi		ZL,	low( PRECOMP_B*2)
	rcall	add_round_key
	;block <- N ^ B ^ L0
	ldi		ZH,	high(PRECOMP_L0*2)
	ldi		ZL,	low( PRECOMP_L0*2)
	rcall	add_round_key
	;block <- E( N^B^L0 ) (nonce)
	rcall	Rijndael_encrypt
	;save calculated nonce
	rcall	save_IV
	;tag <- H ^ N ^ expected
	ldi		YH,	high(TAG)
	ldi		YL,	low( TAG)
	ldi		ZH,	high(SAVED_IV)
	ldi		ZL,	low( SAVED_IV)
	rcall	xor_Z_to_Y_RAM

Для шифрування даних, EAX використовує AES в режимі CTR — лічильника. Цей режим перетворює блочний AES в потоковий шифр, яким можна без особливих проблем шифрувати дані довільної довжини. Вектором ініціалізації виступає тільки що підготовлений N ', який, кожен блок, збільшується на одиничку і шифрується.

Збільшення на одиничку — проблема, коли у нас число з 16 випадкових байт, доведеться обробити всі перенесення до одного, хіба мало. Нічого складного.

Інкремент 16-ти регістрів одночасно
;block++
;all carrying is done properly
increment_regs:
	ldi		YH,	0
	ldi		YL,	20
	clr		temp_0
carry_next:
	ld		temp_0,	Y
	cpi		temp_0,	1
	ld		temp_0,	-Y
	adc		temp_0,	NULL
	st		Y,	temp_0
	cpi		YL,	5
	brsh	carry_next
	ret

Складаємо зашифрований вектор ініціалізації з даними — отримуємо розшифровані дані. Розшифровувати до перевірки підпису ми їх не будемо, вони будуть розшифровані безпосередньо перед записом у пам'ять. Але N 'вже готове, пам'ять під код розшифровки виділена — чому б його не згенерувати?

Режим CTR
;/////////////////////////////DECRYPTION IVs
	ldi		YH,	high(ENC_IV)
	ldi		YL,	low( ENC_IV)
IV_calc_cycle:
	;block <- E(IV)
	rcall	Rijndael_encrypt
	;ENC_IV <- E(IV)
	rcall	from_regs_to_Y
	push	YH
	push	YL
	;IV++
	rcall	rest_IV
	rcall	increment_regs
	rcall	save_IV
	pop		YL
	pop		YH
	cpi		YL,	low( ENC_IV + PAGE_BYTES )
	brne	IV_calc_cycle

Найскладніша частина — код аутентифікації повідомлення. Він заснований на CBC — не зручному для реалізації в асемблері режим шифрування. Чесно кажучи, не знаю, чому люди все ще використовують CBC замість CTR в звичайному житті. Він вимагає вирівнювання до розміру блоки, не параллелізіруется, має кілька кумедних вразливостей при неправильній реалізації і, в цілому, складніше. На щастя, про вирівнювання ми подбали на етапі шифрування прошивки.

Зверніть увагу, що B, згідно картинки, підсумовується тільки з останнім блоком — в кінець шіфруемий рядка. Як і у випадку з вектором ініціалізації, отриманий код аутентифікації відразу ж підсумовується з підписом.

Обчислення CMAC / OMAC підписи
;/////////////////////////////CMAC / OMAC TAG CALCULATION ( block <- C )
	;X contains 20 after last save_IV command
clear_registers:
	st		-X,	NULL
	cpi		XL,	4
	brne	clear_registers
	;block <- L2
	ldi		ZH,	high(PRECOMP_L2*2)
	ldi		ZL,	low( PRECOMP_L2*2)
	rcall	add_round_key
	;last block is processed individually
	ldi		temp_0,	BLOCKS_PER_PAGE
	ldi		ZH,	high(RCVD_PAGE)
	ldi		ZL,	low( RCVD_PAGE)
CBC_TAG:
	;block <- block ^ m(i)
	;temp_0 is fine
	rcall	xor_Z_to_block_RAM

	push	temp_0
	cpi		temp_0,	1
	brne	dont_add_B

	ldi		ZH,	high(PRECOMP_B*2)
	ldi		ZL,	low( PRECOMP_B*2)
	rcall	add_round_key
dont_add_B:
	;Z is saved properly
	rcall	Rijndael_encrypt

	pop		temp_0
	dec		temp_0
	brne	CBC_TAG

	;block <- H ^ N ^ C ^ expected
	ldi		ZH,	high(TAG)
	ldi		ZL,	low( TAG)
	rcall	xor_Z_to_block_RAM

Ми тільки що встановили останню деталь в пазл і можемо перевірити, зійшлася підпис, чи ні. Умова правильності підпису — всі її байти повинні звернутися в 0. Традиційна криптографічний перевірка — об'єднати всі значення за допомогою АБО в окремий регістр, ніяких умов. Після циклу вирішується, записувати дані в Flash, або повідомити про помилку підписи і померти.

Традиційна безпечна перевірка підпису
;/////////////////////////////TAG CHECK
	clr		temp_0
check_more:
	ld		temp_1,	-Y
	or		temp_0,	temp_1
	cpi		YL,	4
	brne	check_more
	cp		temp_0,	NULL
	breq	do_write
	rjmp	die

Мітка die відправляє нас у нескінченний цикл, що відправляє 0xFF на будь-який запит. Прошиваються програма повинна помітити неправильні байти підтвердження і оповістити користувача про те, що файл не підходить.

Вічний цикл помилки
;/////////////////////////////TAG FAILURE AND EXIT
die:
	ldi		temp_0,	0xFF
	rcall	UART_send
	rjmp	die

У разі, якщо підпис вірна — ми відновлюємо покажчик Z на поточну сторінку Flash і переходимо в процедуру запису. Якщо ця сторінка була останньою — переходимо на мітку upload_done, яка відправляє прошивають байт успіху — 0x0C і йде в цикл смерті.

Все добре — переходимо до запису
;/////////////////////////////TAG SUCCESS - CTR AND WRITE
do_write:
	;restore page pointers
	pop		ZL
	pop		ZH
	;decrypt and write page
	rcall	store_page
	;restore page counter
	pop		temp_0
	dec		temp_0
	;continue if not done, else - die
	breq	upload_done
	rjmp	next_page

Процедура розшифровки і записи в Flash нічим не примітна, я просто слідував документації по самопрограмування. Єдине цікаве місце — спроба зіпсувати записується у Flash байт вмістом регістра temp_0, в якому повинен бути результат упаковування підпису. Якщо підпис була правильною — в temp_0 знаходиться 0 і з даними нічого не станеться. Якщо з якоїсь причини мікроконтролер успішно «перелетів» всю перевірку і почав писати в Flash — принаймні, він писатиме туди сміття.

Процедура самопрограмування залізо-залежна, при портировании на інші мікроконтролери, може бути необхідно підкоригувати виклик команди store program memory.

Розшифровка даних і запис у Flash
;D( RCVD_PAGE ) -> flash
store_page:
	;erase current page
	ldi		temp_1, 0b00000011
	rcall	spm_it

	ldi		YH, high(RCVD_PAGE)
	ldi		YL, low( RCVD_PAGE)
	ldi		XH, high(ENC_IV)
	ldi		XL, low( ENC_IV)
write_next:
	ld		r0, Y+
	ld		temp_2, X+
	eor		r0, temp_2
	ld		r1, Y+
	ld		temp_2, X+
	eor		r1, temp_2
	;last countermeasure - if we jumped through tag check
	eor		r0, temp_0
	eor		r1, temp_0
	;store word to page buffer
	ldi		temp_1, 0b00000001
	rcall	spm_it
	adiw	Z, 2
	cpi		YL, low( RCVD_PAGE + PAGE_BYTES )
	brne	write_next
	;write page

	;back to page start
    subi	ZL, low( PAGE_BYTES)
    sbci	ZH, high(PAGE_BYTES)
	;write page
	ldi		temp_1, 0b00000101
	rcall	spm_it
	;to next page start
    subi	ZL, low( -PAGE_BYTES)
    sbci	ZH, high(-PAGE_BYTES)
	;re-enable flash
	ldi		temp_1, 0b00010001
	rcall	spm_it

	ret

spm_it:
	in		temp_2, SPMCSR
	sbrc	temp_2, SPMEN
	rjmp	spm_it
	out		SPMCSR, temp_1
	spm
	ret

Збираємо все воєдино. Рівне 1024 байта. Залишилося прошити мікроконтроллер, виставити фьюз і написати клієнтське ПЗ.


Тестування

Не будемо мучитися з клієнтським ПО, напишемо генератор ключів і шифрувальник прошивок на Qt, з використанням Crypto + +. Статичний заголовок для шифрування вписаний туди ж, намертво. Всі невживані області пам'яті заповнюються випадковими байтами.


Прошівальщік елементарна — чекаємо 0xC0, відправляємо 0x60 і, поки файл не закінчиться, відправляємо байт у відповідь на кожен 0xC0. Отримали 0x0C — все готово, отримали 0xFF — сталася помилка. Напишемо його на чистому С для Linux. COM-порту у мене на ноутбуці немає, тому… використовуємо Psion 5MX, який молодший за мене всього на 5 років.


Дістанемо із засіків батьківщини якусь плату з ATmega8A, прошу її бутом, з'єднаємо з шматком плати з MAX233, зберемо на псионной прошівальщік, перемотати все це випадковими проводочками, вкажемо прошівальщік порт і файл прошивки, перезапустити блок живлення… Процес пішов.

Remember, no Arduino
Щось явно відбувається
Прошу вибачення за стан столу і тестовий стенд — радіоелектронщик з мене не дуже.

Вся задача тестової програми — згасити світлодіод і повиснути назавжди. Після прошивки, їй це вдається. Успіх!

Старанний псионной передав 10кб в порт і чекає вказівок
Світлодіод погас
Таким нехитрим чином, можна зробити неможливе і залишити багато місця для користувацького коду. Тепер є спосіб викочувати патчі, абсолютно не турбуючись про те, що вихідні коди вкраде якийсь китайський шпигун.

Буду радий, якщо хтось знайде критичні помилки безпеки в моїй реалізації.

Всі вихідні коди і ця стаття поширюються під ліцензією CreativeCommons Attribution-NonCommercial-ShareAlike.

image

Репозиторій на GitHub: github.com / sirgal / AVR-EAX-AES-bootloader
(Весь код для ПК написаний похапцем і, місцями, рік тому, буду радий, якщо хтось побажає цей жах виправити, у мене поки немає часу)

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.