Содержание
Как вытащить из устройства то, что разработчики втащили
Вполне естественно, что начинать попытки достать что-то из микроконтроллера нужно с изучения механизмов, которые встроены разработчиком чипа для задач программирования памяти. В мануале указано, что производитель любезно разместил в памяти загрузчик, для нужд внутрисхемного программирования устройства.
Как видно из картинки выше, память разбита на 2 части: пользовательская область, и область загрузчика. Во второй как раз с завода залит загрузчик по умолчанию, который умеет писать, читать, стирать пользовательскую память и общается через асинхронный, синхронный, либо CAN-интерфейс.
Указано, что он может быть переписан на свой, а может быть и не переписан. В конце концов это легко проверяется попыткой постучаться к стандартному загрузчику хотя-бы через UART… Забегая вперед: производитель отопителя не стал заморачиваться своим загрузчиком, поэтом копать дальше можно в этом направлении.
Вход в режим работы загрузчика обеспечивается определенной комбинацией на входах CNVSS, P5_0, P5_5 во время аппаратного сброса. Дальше либо написать свою утилиту для копирования содержимого памяти, либо использовать готовую. Renesas предоставляет свою утилиту, которая называется «M16C Flash Starter», но функция чтения у нее урезана.
Она не сохраняет прочитанное на диск, а сравнивает его с файлом с диска. Т.е. по сути это не чтение, а верификация. Однако есть немецкая свободная утилитка с названием M16C-Flasher, которая вычитывать прошивку умеет. В общем начальный инструментарий подобрался.
150 000$ за пустой биткоин-кошелек!
Ascii шеллкод
Ответить на вопрос о том, что же такое шеллкод в полном обьеме, я, наверное не смогу. Но, в моей голове ответ выглядит так:
Это набор бинарных данных, который обычно используется в эксплоитах в качестве исполняемого кода. Если мы говорим об эксплоитах – их код можно логически разбить на две части:
Первая часть отвечает за абьюз уязвимости. То есть, это код, который использует специальные механики, такие как buffer overflow, race condition, use-after-free и тд. для того, чтоб заставить систему исполнить вторую часть эксплоита.
А вторая часть – это уже то, что должен исполнить процессор после того, как эксплоит получил контроль над уязвимой системой. Это и есть наш шеллкод. Он может быть очень разным. Это зависит от того, что мы хотим получить от взломанной системы. Мы можем использовать в качестве шеллкода код для скрытого майнинга крипты, запустить процесс bash и присоединить его дескрипторы через сокет к удаленному ПК (и таким образом получить шелл на взломанный комп). По сути, шеллкод это то, чем мы нагружаем процессор после взлома системы.
Стоит сказать, что такой код может быть только машинным. Потому что подсовывать высокоуровневый код, компилировать его на взломанном ПК, а потом исполнять – будет ну прям совсем изобретенным велосипедом. В процессе написания шеллкода нужно так же учитывать то, что он должен быть незаметным и маленьким по размеру.
Самая значимая часть статьи на phrack.org это ASCII. Дело в том, что очень много систем принимают в качестве пользовательского ввода как-раз таки данные в ASCII формате (наш диск не исключение – кроме текста, символов и цифр писать в консольник ничего нельзя).
В статье описано несколько механик о том, как писать шеллкод, используя только числа в диапазоне от 0x20 до 0x7E. И, мало того, каждый код операции процессора разбивается на биты, и рассказывается почему одна операция проходит ASCII “фильтр”, а другая нет. Статью писал истинный гений!
В этот раз, я покажу файл level_3.html целиком. Ведь он гораздо меньше, чем на предыдущем уровне.
level_3.html
001. ROM:00332D30
002. ROM:00332D30 ; Segment type: Pure code
003. ROM:00332D30 AREA ROM, CODE, READWRITE, ALIGN=0
004. ROM:00332D30 ; ORG 0x332D30
005. ROM:00332D30 CODE16
006. ROM:00332D30
007. ROM:00332D30 ; =============== S U B R O U T I N E =======================================
008. ROM:00332D30
009. ROM:00332D30 ; prototype: generate_key(key_part_num, integrity_validate_table, key_table)
010. ROM:00332D30 ; Function called when serial console input is 'R'. Generates key parts in R0-R3.
011. ROM:00332D30 ; The next level to reach, the key parts to print you must!
012. ROM:00332D30
013. ROM:00332D30 generate_key
014. ROM:00332D30
015. ROM:00332D30 var_A8 = -0xA8
016. ROM:00332D30
017. ROM:00332D30 PUSH {R4-R7,LR}
018. ROM:00332D32 SUB SP, SP, #0x90
019. ROM:00332D34 MOVS R7, R1
020. ROM:00332D36 MOVS R4, R2
021. ROM:00332D38 MOVS R5, R0
022. ROM:00332D3A MOV R1, SP
023. ROM:00332D3C LDR R0, =0x35A05C ; "SP: %x"
024. ROM:00332D3E LDR R3, =0x68B08D
025. ROM:00332D40 NOP
026. ROM:00332D42 LDR R1, =0x6213600 ; "R"...
027. ROM:00332D44 MOV R2, SP
028. ROM:00332D46
029. ROM:00332D46 loc_332D46 ; CODE XREF: generate_key 22j
030. ROM:00332D46 LDRB R6, [R1]
031. ROM:00332D48 ADDS R1, R1, #1
032. ROM:00332D4A CMP R6, #0xD
033. ROM:00332D4C BEQ loc_332D54
034. ROM:00332D4E STRB R6, [R2]
035. ROM:00332D50 ADDS R2, R2, #1
036. ROM:00332D52 B loc_332D46
037. ROM:00332D54 ; ---------------------------------------------------------------------------
038. ROM:00332D54
039. ROM:00332D54 loc_332D54 ; CODE XREF: generate_key 1Cj
040. ROM:00332D54 SUBS R6, #0xD
041. ROM:00332D56 STRB R6, [R2]
042. ROM:00332D58 SUBS R5, #0x49 ; 'I'
043. ROM:00332D5A CMP R5, #9
044. ROM:00332D5C BGT loc_332E14
045. ROM:00332D5E LSLS R5, R5, #1
046. ROM:00332D60 ADDS R5, R5, #6
047. ROM:00332D62 MOV R0, PC
048. ROM:00332D64 ADDS R5, R0, R5
049. ROM:00332D66 LDRH R0, [R5]
050. ROM:00332D68 ADDS R0, R0, R5
051. ROM:00332D6A BX R0
052. ROM:00332D6A ; ---------------------------------------------------------------------------
053. ROM:00332D6C DCW 0x15
054. ROM:00332D6E DCW 0xA6
055. ROM:00332D70 DCW 0xA4
056. ROM:00332D72 DCW 0xA2
057. ROM:00332D74 DCW 0xA0
058. ROM:00332D76 DCW 0x9E
059. ROM:00332D78 DCW 0x30
060. ROM:00332D7A DCW 0x52
061. ROM:00332D7C DCW 0x98
062. ROM:00332D7E DCW 0xE
063. ROM:00332D80 ; ---------------------------------------------------------------------------
064. ROM:00332D80
065. ROM:00332D80 key_part1
066. ROM:00332D80 LDR R0, [R4]
067. ROM:00332D82 MOVS R6, #1
068. ROM:00332D84 STR R6, [R7]
069. ROM:00332D86 BLX loc_332E28
070. ROM:00332D86 ; ---------------------------------------------------------------------------
071. ROM:00332D8A CODE32
072. ROM:00332D8A DCB 0
073. ROM:00332D8B DCB 0
074. ROM:00332D8C ; ---------------------------------------------------------------------------
075. ROM:00332D8C
076. ROM:00332D8C key_part2
077. ROM:00332D8C LDR R6, [R7]
078. ROM:00332D90 CMP R6, #1
079. ROM:00332D94 LDREQ R1, [R4,#4]
080. ROM:00332D98 EOREQ R1, R1, R0
081. ROM:00332D9C MOVEQ R6, #1
082. ROM:00332DA0 STREQ R6, [R7,#4]
083. ROM:00332DA4 B loc_332E28
084. ROM:00332DA8 ; ---------------------------------------------------------------------------
085. ROM:00332DA8
086. ROM:00332DA8 key_part3
087. ROM:00332DA8 LDR R6, [R7]
088. ROM:00332DAC CMP R6, #1
089. ROM:00332DB0 LDREQ R6, [R7,#4]
090. ROM:00332DB4 CMPEQ R6, #1
091. ROM:00332DB8 LDREQ R2, [R4,#8]
092. ROM:00332DBC EOREQ R2, R2, R1
093. ROM:00332DC0 MOVEQ R6, #1
094. ROM:00332DC4 STREQ R6, [R7,#8]
095. ROM:00332DC8 B loc_332E28
096. ROM:00332DCC ; ---------------------------------------------------------------------------
097. ROM:00332DCC
098. ROM:00332DCC key_part4
099. ROM:00332DCC LDR R6, [R7]
100. ROM:00332DD0 CMP R6, #1
101. ROM:00332DD4 LDREQ R6, [R7,#4]
102. ROM:00332DD8 CMPEQ R6, #1
103. ROM:00332DDC LDREQ R6, [R7,#8]
104. ROM:00332DE0 CMPEQ R6, #1
105. ROM:00332DE4 LDREQ R3, [R4,#0xC]
106. ROM:00332DE8 EOREQ R3, R3, R2
107. ROM:00332DEC MOVEQ R6, #1
108. ROM:00332DF0 STREQ R6, [R7,#8]
109. ROM:00332DF4 LDR R4, =0x35A036 ; "Key Generated: %s%s%s%s"
110. ROM:00332DF8 SUB SP, SP, #4
111. ROM:00332DFC STR R0, [SP,#0xA8 var_A8]
112. ROM:00332E00 MOVS R0, R4
113. ROM:00332E04 LDR R4, dword_332E40 4
114. ROM:00332E08 BLX R4
115. ROM:00332E0C ADD SP, SP, #4
116. ROM:00332E10
117. ROM:00332E10 loc_332E10 ; CODE XREF: generate_key:loc_332E10j
118. ROM:00332E10 B loc_332E10
119. ROM:00332E14 ; ---------------------------------------------------------------------------
120. ROM:00332E14 CODE16
121. ROM:00332E14
122. ROM:00332E14 loc_332E14 ; CODE XREF: generate_key 2Cj
123. ROM:00332E14 LDR R4, =0x35A020 ; "key not generated"
124. ROM:00332E16 SUB SP, SP, #4
125. ROM:00332E18 STR R0, [SP,#0xA8 var_A8]
126. ROM:00332E1A MOVS R0, R4
127. ROM:00332E1C LDR R4, =0x68B08D
128. ROM:00332E1E BLX R4
129. ROM:00332E20 ADD SP, SP, #4
130. ROM:00332E22 BLX loc_332E28
131. ROM:00332E26 MOVS R0, R0
132. ROM:00332E26 ; End of function generate_key
133. ROM:00332E26
134. ROM:00332E28 CODE32
135. ROM:00332E28
136. ROM:00332E28 loc_332E28 ; CODE XREF: generate_key 56p
137. ROM:00332E28 ; generate_key 74j ...
138. ROM:00332E28 ADD SP, SP, #0xA0
139. ROM:00332E2C LDR LR, [SP],#4
140. ROM:00332E30 BX LR
141. ROM:00332E30 ; ---------------------------------------------------------------------------
142. ROM:00332E34 dword_332E34 DCD 0x35A05C ; DATA XREF: generate_key Cr
143. ROM:00332E38 dword_332E38 DCD 0x68B08D ; DATA XREF: generate_key Er
144. ROM:00332E3C dword_332E3C DCD 0x6213600 ; DATA XREF: generate_key 12r
145. ROM:00332E40 dword_332E40 DCD 0x35A036, 0x68B08D ; DATA XREF: generate_key C4r
146. ROM:00332E40 ; generate_key D4r
147. ROM:00332E48 dword_332E48 DCD 0x35A020 ; DATA XREF: generate_key:loc_332E14r
148. ROM:00332E4C off_332E4C DCD 0x68B08D ; DATA XREF: generate_key ECr
149. ROM:00332E50 DCD 0
150. ROM:00332E50 ; ROM ends
151. ROM:00332E50
152. ROM:00332E50 END
Level3
По традиции, я начну с короткого содержания раздела диска:
Второй
Для всех дальнейших ключей нам надо сделать следующее. Здесь вы видите части из level_3.html, где ключи расставляются в регистры R1-R3:
...
079. ROM:00332D94 LDREQ R1, [R4,#4]
080. ROM:00332D98 EOREQ R1, R1, R0
...
091. ROM:00332DB8 LDREQ R2, [R4,#8]
092. ROM:00332DBC EOREQ R2, R2, R1
...
105. ROM:00332DE4 LDREQ R3, [R4,#0xC]
106. ROM:00332DE8 EOREQ R3, R3, R2
...
Как видим, каждый следующий ключ зависим от предыдущего через EOR. Из-за такой зависимости, для второго ключа, мы должны где-то хранить первый. Для третьего мы должны где-то хранить второй и тд. Инструкций с приставкой -EQ нету в Thumb. Они нам и не нужны.
Пробуем сделать второй ключ:
1. STR R3, [R7] = 0x3B 0x60 = ";`"
2. LDR R5, [R7] = 0x3D 0x68 = "=h"
3. LDR R1, [R4, #4] = 0x61 0x68 = "ah"
4. EORS R1, R0 = 0x41 0x40 = "A@"
5. STR R1, [R7] = 0x39 0x60 = "9`"
6. LDR R0, [R7] = 0x38 0x68 = "8h"
7. BX R5 = 0x28 0x47 = "(G"
Сохраняем адрес pfintf в память, куда указывает R7
Подгружаем адрес printf из памяти в R5
Грузим второй ключ по правилам из level_3.html в R1
Делаем EORS с первым ключом из R0 и сохраняем в R1. Второй ключ готов
Сохраняем его в память, куда указывает R7
Подгружаем его в R0
Триггерим printf
Все инструкции проходят фильтр. Пробуем и радуемся – вот наш второй ключ!
F3 T>R!;`[email protected]`8h(G!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! {,
DOWNLOAD_MICROCODE_FUTURE_USE_ONLY
LED:000000EE FAddr:002C7B5C
LED:000000EE FAddr:002C7B5C
Жалкие попытки
Как я и говорил в начале этой публикации, у меня ушло невероятно много времени, чтоб понять как взломать эту ерунду. Друзья, провальные попытки – тоже попытки. Поэтому, пройдемся по тем безуспешным шагам, которые я попробовал, но которые не увенчались успехом.
Как обновить прошивку китайских цифровых видеорегистраторов h.264?
Инструмент обновления цифрового видеорегистратора H.264 позволяет обновлять микропрограмму стандартного цифрового видеорегистратора с помощью компьютера или ноутбука.
Инструмент устанавливается на компьютер, а затем связывается с видеорегистратором через локальную сеть.
Итак, если у вас есть H.264 DVR или H.265 NVR (стандартные OEM) регистраторы и вам нужно обновить свое устройство до последней прошивки, используйте этот бесплатный инструмент, чтобы легко и безопасно обновить его.
Давайте сначала проясним, что такое DVR H.264. Эти типы видеорегистраторов производятся в Китае на базе чипсета HiSilicon и продаются по всему миру.
Обычно надпись на устройстве просто гласит «Цифровой видеорегистратор», «H.264 DVR», «Сетевой видеорегистратор», «H.265 NVR», или продавец размещает на нем свою собственную торговую марку.
В любом случае, все они производятся одной компанией и используют один и тот же тип чипсета, например, HiSilicon модели Hi3520D Hi3520A, Hi3521, Hi3521A, Hi3531, Hi3531A, Hi3535, Hi3736, Hi3537, Hi3538.
Существует три метода обновления прошивки: один — с помощью инструмента обновления на компьютере, другой — непосредственно на цифровом видеорегистраторе с помощью USB-накопителя или удаленно. Ниже мы объясним оба этих метода.
Обновление прошивки H.264 DVR
Инструмент обновления прошивки DVR — это небольшое программное обеспечение, которое позволяет вам установить прошивку на устройство через компьютер или ноутбук.
По сути, инструмент (официально названный Диспетчер устройств DVR ) будет сканировать сеть и обнаруживать в сети любые видеорегистраторы, камеры и видеорегистраторы.
Загрузите этот инструмент по этой ссылке с Google Диск и установите его на свой компьютер. Он работает только на компьютерах с Windows и версии ПО только на английском и китайском языках.
Далее вам нужно найти файл прошивки, который работает для вашей модели видеорегистратора H.264.
Ознакомьтесь со списком прошивок H.264 DVR и загрузите ту, которая соответствует номеру модели вашего DVR.
Также вы можете связаться с продавцом или дилером, у которого было приобретено устройство.
Это важный шаг, убедитесь, что у вас ПРАВИЛЬНАЯ прошивка. Если он неправильный, есть небольшой шанс испортить и повредить весь блок.
После этого нужно выполнить следующие шаги:
- Установите инструмент обновления ( Диспетчер устройств DVR ) на свой компьютер.
- Убедитесь, что видеорегистратор и компьютер находятся в одной сети. Таким образом, они оба должны получить доступ в Интернет от одного и того же маршрутизатора / Wi-Fi.
- Запустите инструмент обновления и нажмите «IP Search» . Инструмент выведет список всех устройств видеонаблюдения, обнаруженных в сети. Выберите свой DVR из списка.
- Нажмите «Обзор» и найдите прошивку на своем компьютере.
- Нажмите «Обновить» и дождитесь обновления прошивки.
- После завершения обновления вы должны услышать длинный звуковой сигнал, и DVR перезагрузится.
- Когда он снова появится в сети, войдите в меню и проверьте раздел «Версия», чтобы убедиться, что он обновлен. Многие агрегаты отказываются обновляться.
Обновление с помощью USB-накопителя
Как мы уже упоминали, неправильное обновление прошивки может привести к повреждению или неисправности цифрового видеорегистратора, поэтому внимательно следуйте инструкциям.
Прежде всего, загрузите нужную прошивку на свой компьютер. Распакуйте zip-файл и скопируйте все файлы, найденные в корневом каталоге USB-накопителя.
Подключите USB-накопитель к свободному USB-порту, расположенному на передней (или задней) DVR.
Войдите в DVR, используя свою учетную запись администратора.
Перейдите в раздел «Система», а затем перейдите в раздел « Обновить» . Выберите файл с USB-накопителя и нажмите «Отправить».
Позвольте DVR обработать обновление. Очень важно не перезапускать и не отключать питание DVR в это время.
После завершения обновления видеорегистратор перезагрузится и будет готов к использованию.
Если вы видите сообщение об ошибке «Вставьте USB», которое не исчезает через несколько секунд, проверьте подключение USB-устройства и убедитесь, что на USB-накопитель скопирована правильная прошивка. Кроме того, вы можете попробовать использовать другой USB-накопитель.
Обновление прошивки удаленно
Обновление прошивки цифрового видеорегистратора H.264 через Интернет может быть рискованным, если соединение прерывается или ненадежно; поэтому рекомендуется выполнять обновление микропрограмм локально с помощью USB-накопителя или инструмента обновления.
Если вы не смогли успешно настроить DVR для удаленного доступа, вы должны сделать это, прежде чем продолжить обновление прошивки.
Удаленное обновление также можно выполнить с помощью программного обеспечения CMS Video Viewer. Обратитесь к руководству по вашей CMS для получения дополнительной информации.
Шаги по удаленному обновлению DVR:
- Загрузите файл прошивки для вашего DVR (см. Пояснения выше).
- Распакуйте zip-файл и скопируйте все файлы, найденные в корневом каталоге USB-накопителя.
- В Internet Explorer войдите в свой DVR удаленно, используя учетную запись администратора. Если по какой-либо причине невозможно получить удаленный доступ к DVR, остановите этот процесс обновления прошивки и устраните проблему с подключением, прежде чем продолжить.
- После входа в систему перейдите на вкладку «Конфигурация» и щелкните ссылку «Настроить» в разделе « Общие». (Если вы не видите вкладку конфигурации, проверьте, имеет ли входящая в систему учетная запись права администратора, и убедитесь, что вы используете совместимый браузер, например Internet Explorer.)
- В разделе « Обновление прошивки» щелкните значок с красным знаком плюса ( ) и перейдите в папку, в которую вы ранее распаковали файлы прошивки.
- Добавьте ВСЕ файлы по одному в любом порядке. Когда все файлы, найденные в пакете прошивки, будут перечислены на странице «Обновить прошивку», нажмите кнопку «Обновить» . НЕ перезапускайте, не отключайте и не выключайте питание DVR в это время.
- DVR перезагрузится, а затем проверит информацию о версии, чтобы убедиться, что обновление прошло.
В этой статье мы показали три метода, которые можно использовать для обновления видеорегистраторов H.264, основанных на наборах микросхем HiSilicon.
Попробуйте выполнить обновление через USB-накопитель или с помощью инструмента обновления, мы не рекомендуем выполнять обновление удаленно, так как соединение может пропасть, а процесс обновления из-за этого прервется. Это, к сожалению, приведет к повреждению устройства.
Да, и не забудьте, что для обновления вам необходимо использовать пароль администратора.
Меняем прошивку
Первая мысль была загрузить файл level_3.lod в дизассемблер, найти место, которое будет похожим на то, что я вижу в level_3.html, отредактировать пару значений, и залить прошивку обратно на диск. Я воспользовался Hopper Disassembler, и все таки нашел это место!
Очень странно то, что каждая вторая строка кода была совсем не похожа на то, что я вижу в level_3.html. Возможно, это была какая-то контрольная сумма, или же логика прошивальщика seaflashlin_rbs работает специфичным образом. Так или иначе, чисто для тестов, я изменил парочку значений.
Но, когда я попытался прошить диск, утилита seaflashlin_rbs завершалась с ошибкой. И, после прошивки, мои изменения не применились.
Может потыкать железяку?
Немного погуглив, я понял что каждый чип (IC) имеет такую штуку как JTAG. Это своеобразный интерфейс для тестирования чипа. Через него можно отдать команду процессору остановить исполнение кода, и переключиться в debug-режим. С помощью openocd можно “транслировать” debug-режимы различных чипов, и вывести порт для gdb.
А уже с gdb можно попросить процессор показать определенные участки памяти, да и вообще слить всю память, которая находится в рабочем пространстве процессора. Если мы совершим подобное, мы отыщем функцию generate_key в огромном дампе памяти, и по референсам сможем найти все ключи!
Для подобной манипуляции есть парочка нюансов:
Немного о самом микроконтроллере
Камешек представляет собой 16-разрядный микроконтроллер в 100-выводном QFP корпусе. Ядро имеет 1 МБайт адресного пространства, тактовая частота 20МГц для автомобильного исполнения. Набор периферии так же весьма обширный: два 16-разрядных таймера и возможность генерации 3-фазного ШИМ для управления моторами, всякие UART, SPI, I2C естественно, 2 канала DMA, имеется встроенный CAN2.0B контроллер, а также PLL. На мой взгляд очень неплохо для старичка. Вот обзорная схемка из документации:
Так как моя задача выдрать ПО, то так же весьма интересует память. Данный МК выпускался в двух вариантах: масочном и Flash. Ко мне попал, как выше уже упоминалось, M306N5FCTFP. Про него в описании сказано следующее:
О защите от считывания
Все бы было совсем просто, если бы в загрузчике не была предусмотрена защита от несанкционированного доступа. Я просто приведу очень вольный перевод из мануала.
Функция проверки идентификатора
Используется в последовательном и CAN режимах обмена. Идентификатор, переданный программатором, сравнивается с идентификатором, записанным во flash памяти. Если идентификаторы не совпадают, команды, отправляемые программатором, не принимаются. Однако, если 4 байта вектора сброса равны FFFFFFFFh, идентификаторы не сравниваются, позволяя всем командам выполняться.
Таким образом, чтобы получить доступ к программе, нужно знать заветные 7 байт. Опять же, забегая вперед, я подключился к МК, используя тот же «M16C Flash Starter» и убедился, что комбинации из нулей и FF не проходят и этот вопрос придется как то решать.
Здесь сразу же всплыла мысль с атакой по сторонним каналам. Уже начал прикидывать в голове платку, позволяющую измерять ток в цепи питания, но решил, что интернет большой и большинство велосипедов уже изобретено. Вбив несколько поисковых запросов, довольно быстро нашел на hackaday.
io проект Serge ‘q3k’ Bazanski, с названием «Reverse engineering Toshiba R100 BIOS». И в рамках этого проекта автор решал по сути точно такую же задачу: добыча встроенного ПО из МК M306K9FCLR. Более того — на тот момент задача им была уже успешно решена.
В двух словах, q3k точно по такой же логике начал изучение с анализа потребляемого тока, в этом плане он был в гораздо более выгодных условиях, т.к. у него был ChipWhisperer, этой штукой я до сих пор не обзавелся. Но т.к. его первый зонд для снятия тока потребления оказался неподходящим и вычленить из шумов что-то полезное у него не получилось, он решил попробовать простенькую атаку на время отклика.
Дело в том, что загрузчик во время выполнения команды дергает вывод BUSY, чтобы проинформировать хост о том, занят он, или готов выполнять следующую команду. Вот, по предположению q3k, замер времени от передачи последнего бита идентификатора до снятия флага занятости мог послужить источником информации при переборе.
При проверке этого предположения перебором первого байта ключа действительно было обнаружено отклонение по времени только в одном случае — когда первый байт был равен FFh. Для удобства измерения времени автор даже замедлил МК, отключив кварцевый резонатор и подав на тактовый вход меандр 666кГц, для упрощения процедуры измерений. После чего идентификатор был успешно подобран и ПО было извлечено.
Отличия
Огооо! Первый блин комом. Здесь нету функции ahex2byte. Без конвертации вводимых символов в бинарные, мы уж точно не сможем прыгнуть по адресам каждого из ключа. То есть, у нас не получится взломать этот уровень так же, как и предыдущий. Оно и не дурно – задачка то новая!
Первое, что мне сразу бросилось в глаза – строка 18. Как мы помним из предыдущей статьи, это часть Function Prologue. Но, количество памяти, которое мы выделяем для стека огромное – аж целых 0х90 байт. Если бы эта функция имела много аргументов и дохрена переменных внутри, это бы хоть как-то оправдало на столько огромную цифру. Это, друзья, наш “первый звоночек”.
На строках 138-140 мы видим то же самое уменьшение стека, и прыжок на адрес, который был залинкован перед входом в функцию generate_key. Количество байт, на которое мы уменьшаем стек – 0xA0. Это на 16 байт больше того количества, на которое мы увеличивали стек сразу после входа в функцию.
На предыдущем уровне, мы имели ровно такую же разницу. В общем, этот кусок говорит нам о том, что здесь мы эксплуатируем ровно такую-же уязвимость, как и на предыдущем уровне – buffer overflow. Но, заставить программу отдать ключи нам придется другим, более изощренным способом.
На строке 24 мы видим, что адрес нашей функции printf грузится в регистр R3. Пока что не понятно, для каких целей мы это делаем, но это, уж поверьте, стоит держать в голове 🙂
Строки 30-36. Здесь у нас нету отличий от предыдущего уровня – все, что мы здесь делаем это копируем наши вводимые данные на стек, и продолжаем исполнение программы когда столкнемся с символом новой строки.
Строки 40-41. Опа! А здесь мы видим две замечательные инструкции. На строке 40 мы отнимаем 0x0D от последнего вводимого символа – новой строки (тот же 0x0D). Получаем ноль. И, на строке 41, мы сохраняем этот ноль на стек в качестве последнего символа нашего ввода.
Ну, вот и все. В остальном, программа почти идентична той, что мы имели на LEVEL2. Конечно, есть парочка непохожих вещей, но они выходят за рамки процесса взлома этого уровня.
Первый
1. STR R3, [R7] = 0x3B 0x60 = ";`"
2. LDR R5, [R7] = 0x3D 0x68 = "=h"
3. BX R5 = 0x28 0x47 = "(G"
Сохраняем адрес pfintf в память, куда указывает R7
Подгружаем адрес printf из памяти в R5
Триггерим printf
Вау! Все опкоды пройдут фильтр. Помним, что мы начинаем исполнять наш код начиная с третьего символа. Первый символ – обязательно будет “R”, второй – не важно какой. Конвертируем hex значения опкодов в ASCII, вводим что-то рандомное (соблюдаем наше количество в 163 символа), и в конце пишем адрес третьего символа на стеке – туда и вернется исполнение программы. Верхний байт адреса возврата 0x00 возьмется с символа новой строки.
F3 T>R!;`=h(G!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! {,
WRITE_READ_VERIFY_ENABLED
LED:000000EE FAddr:002C7BB4
LED:000000EE FAddr:002C7BB4
LED:000000EE FAddr:002C7BB4
В этот момент у меня прям реально пошли мурашки по коже! Мы получили что-то помимо ошибок. Это значит только одно – мы успешно триггернули printf. И, судя по тому, что в процессе программы, мы, как минимум прогоняем код по одному из ключей (скорее всего по первому), он должен лежать в R0. Ladies &
Gentleman, мы видим первый ключ! По поводу ошибок FAddr я писал в предыдущей статье, но здесь повторюсь – поскольку мы абьюзим адрес возврата, после выполнения printf процессор начинает исполнять неизвестный нам код. Он натыкается на невалидный код операции, и показывает адрес, где он с ним столкнулся. После такого – только ребут жесткого диска по питанию.
Первый блин — граблями
Ха! Подумал я… Сейчас я быстренько наклепаю программку к имевшейся у меня STM32VLDiscovery c STM32F100 на борту, которая будет отправлять код и измерять время отклика, а в терминал выплевывать результаты измерений. Т.к. макетная плата с целевым контроллером до этого подключалась к ПК через переходник USB-UART, то, дабы ничего не менять на макетке, работать будем в асинхронном режиме.
Когда при старте загрузчика вход CLK1 притянут к земле, он понимает, что от него хотят асинхронного общения. Собственно потому я его и использовал — подтяжка была уже припаяна и я просто соединил проводами две платы: Discovery и макетку с целевым M306.
Заметка по согласованию уровней:
Т.к. M16 имеет TTL-уровни на выводах, а STM32 — LVTTL (упрощенно, в даташите подробнее), то необходимо согласование уровней. Т.к. это не устройство, которое, как известная батарейка, должно работать, работать и работать, а по сути подключается разок на столе, то с трансляторами уровней я не заморачивался: выходные уровни от STM32 пятивольтовый МК переварил, в смысле 3 вольта как «1» воспринимает, выходы от М16 подаем на 5V tolerant входы STM32 дабы ему не поплохело, а ногу, которая дергает RESET M16 не забываем перевести в режим выхода с открытым стоком. Я вот забыл, и это еще 2ч в копилку упущенного времени.Этого минимума достаточно, чтобы железки друг друга поняли.
Логика атакующего ПО следующая:
- Устанавливаем соединение с контроллером. Для этого необходимо дождаться, пока завершится сброс, затем передать 16 нулевых символов с интервалом более, чем 20 мс. Это для того, чтобы отработал алгоритм автоопределения скорости обмена, т.к. интерфейс асинхронный, а МК о своей частоте ничего не знает. Стартовая скорость передатчика должна быть 9600 бод, именно на эту скорость рассчитывает загрузчик. После этого при желании можно запросить другую скорость обмена из пяти доступных в диапазоне 9600-115200 (правда в моем случае на 115200 загрузчик работать отказался). Мне скорость менять не нужно, поэтому я для контроля синхронизации просто запрашивал версию загрузчика. Передаем FBh, загрузчик отвечает строкой вроде «VER.1.01».
- Отправляем команду «unlock», которая содержит текущую итерацию ключа, и замеряем время до снятия флага занятости.
Команда состоит из кода F5h, трех байт адреса, где начинается область идентификатора (в моем случае, для ядра M16C, это 0FFFDFh), длина (07h), и сам идентификатор. - Измеряем время между передачей последнего бита идентификатора и снятием флага занятости.
- Увеличиваем перебираемый байт ключа (KEY1 на начальном этапе), возвращаемся к шагу 2 до тех пор, пока не переберем все 255 значений текущего байта.
- Сбрасываем статистику на терминал (ну или выполняем анализ «на борту»).
Для общения с целевым МК я использовал USART в STM32, для измерения времени — таймер в режиме Input Capture. Единственное, для простоты я измерял время не между последним битом ключа и снятием флага, а между началом передачи и флагом. Причиной было то, что последний бит может меняться, а в асинхронном режиме прицепить вход захвата особо не к чему. В то же время UART аппаратный и время передачи в принципе идентично и ощутимых погрешностей набегать не должно.
В итоге, для всех значений результаты были идентичны. Полностью идентичны. Тактовая частота таймера у меня была 24Мгц, соответственно разрешение по времени — 41,6 нс. Ну ок, попробовал замедлить целевой МК. Ничего не поменялось. Здесь в голове родился вопрос: что я делаю не так, как это делал q3k?
После сравнения разница нашлась: он использует синхронный интерфейс обмена (SPI), а я асинхронный (UART). И где-то вот здесь я обратил внимание на тот момент, который упустил вначале. Даже на схемах подключения для синхронного и асинхронного режимов загрузчика вывод готовности назван по-разному:
В синхронном это «BUSY», в асинхронном это «Monitor». Смотрим в таблицу «Функции выводов в режиме Standart Serial I/O»:
«Семён Семёныч…»
Упущенная вначале мелочь завела не туда. Собственно, если в синхронном режиме это именно флаг занятости загрузчика, то в асинхронном (тот, который serial I/O mode 2) — просто «мигалка» для индикации работы. Возможно вообще аппаратный сигнал готовности приемопередатчика, оттого и удивительная точность его поднятия.
В общем перепаиваем резистор на выводе SCLK с земли на VCC, припаиваем туда провод, цепляем все это к SPI и начинаем сначала…
Пишем
Теперь нам прийдется, довольно таки сильно, напрячь мозг. Самое важное, что должен уметь наш шеллкод – это триггерить printf. Без этого, мы не получим ни единого ключа. Как мы помним, в начале программы на строке 24, мы записывали адрес printf в R3, и этот регистр ни разу не менялся в процессе исполнения.
Мы уже пытались использовать инструкцию BX R3 – она не проходит ASCII фильтр. Но, мы можем попробовать переместить адрес из R3 в какой-то другой регистр и сделать Branch на него. Давайте глянем что такое MOV R5, R3 и BX R5 в виде опкодов. Детально расписывать что и как получаем я не буду. Надеюсь, с keystone все разобрались. Упрощу все до максимума:
MOV R5, R3 = 0x46 0x1D = "F "
BX R5 = 0x28 0x47 = "(G"
Блин, первая инструкция, как и все другие MOV, не пройдут фильтр. Хм, давайте подумаем. Может мы сможем сохранить содержимое R3 куда-то в память, а потом восстановим его в R5? Ведь, BX R5 прошла фильтр. Судя по программе, R7 указывает на таблицу целостности ключей – то есть, в этом регистре хранится адрес памяти, куда мы, наверное, можем писать. К черту таблицу целостности – когда мы пишем шеллкод, у нас полная свобода!
Сложность
Ребятушки, наконец! После довольно длительного перерыва ко мне, в конце концов, пришло вдохновение написать последнюю, финальную и завершающую часть этого чертовски долгого цикла о взломе диска от RedBalloonSecurity. В этой части вас ждет самое сложное из имеющихся заданий.
Сразу хочется сказать, что прочтение моей предыдущей статьи обязательно! Она расписана до мелочей. Текущая публикация пишется с учетом того, что вы прочли и поняли предыдущую.
Тестим шеллкод
Чтобы что-то протестировать, нужно это сначала написать. Здесь нам и нужен keystone assembler о котором шла речь на LEVEL2. Это не простой компилятор. Кроме самого компилятора он предоставляет несколько С-шных библиотек. Мы можем написать ассемблерную инструкцию, передать ее как текстовый параметр в keyston-овскую функцию, и получить 2х, или 4х (Thumb или ARM) байтовый код операции (опкод).
Третий
Для третьего ключа, делаем похожее:
1. STR R3, [R7] = 0x3B 0x60 = ";`"
2. LDR R5, [R7] = 0x3D 0x68 = "=h"
3. LDR R1, [R4, #4] = 0x61 0x68 = "ah"
4. EORS R1, R0 = 0x41 0x40 = "A@"
5. LDR R2, [R4, #8] = 0xA2 0x68 = "¢h"
6. EORS R2, R1 = 0x4A 0x40 = "J@"
7. STR R2, [R7] = 0x3a 0x60 = ":`"
8. LDR R0, [R7] = 0x38 0x68 = "8h"
9. BX R5 = 0x28 0x47 = "(G"
Опа! Инструкция на строке 5 не пройдет фильтр из-за символа “¢”. Он хоть и имеет текстовое представление, но не входит в рамки ASCII. Если я введу его в консоль, мне моментально отобразится сообщение, мол, символ не верный, и покажет чистую строку приглашения:
F3 T>
Input_Command_Error
F3 T>
Инструкция LDR R2, [R4, #8] делает оффсет от R4 на 8 байт, лезет по адресу в память, и сохраняет содержимое в R2. Хм, мы можем хитро выкрутиться, и прибавить к адресу в R4 4 байта, а потом лезть в память с таким же оффсетом как и для первого ключа (инструкция на строке 3 проходит ASCII фильтр как с R1, так и с R2).
ADDS R4, #4 = 0x04 0x34 = " 4"
Черт побери, из-за 0х04 мы не сможем использовать подобное. Включаем максимальную хитрость! Может прибавить 44, а потом отнять 40?
ADDS R4, #44 = 0x2c 0x34 = ",4"
SUBS R4, #40 = 0x28 0x3c = "(<"
Вау! Должно сработать. Делаем парочку изменений:
01. STR R3, [R7] = 0x3B 0x60 = ";`"
02. LDR R5, [R7] = 0x3D 0x68 = "=h"
03. LDR R1, [R4, #4] = 0x61 0x68 = "ah"
04. EORS R1, R0 = 0x41 0x40 = "A@"
05. ADDS R4, #44 = 0x2c 0x34 = ",4"
06. SUBS R4, #40 = 0x28 0x3c = "(<"
07. LDR R2, [R4, #4] = 0xA2 0x68 = "bh"
08. EORS R2, R1 = 0x4A 0x40 = "J@"
09. STR R2, [R7] = 0x3a 0x60 = ":`"
10. LDR R0, [R7] = 0x38 0x68 = "8h"
11. BX R5 = 0x28 0x47 = "(G"
F3 T>R!;`=hahA@,4(<bhJ@:`8h(G!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! {,
TraceBufferControlFlags1_37
LED:000000EE FAddr:002C7BB4
LED:000000EE FAddr:002C7BB4
Ну ничего себе! У нас получилось. Это наш третий ключик!
Успех!
В синхронном режиме все почти так же, только не требуется никакой предварительной процедуры установки соединения, упрощается синхронизация и захват времени можно выполнить точнее. Если бы сразу выбрал этот режим сохранил бы время… Я снова не стал усложнять и измерять время именно от последнего бита, а запускал таймер перед началом передачи последнего байта ключа, т.е. включаем таймер и отправляем в передатчик KEY7 (на скриншоте выше, из логического анализатора, видно расстояние между курсорами. Это и есть отсчитываемый отрезок времени).
Этого оказалось более чем достаточно для успешной идентификации. Вот так выглядит перебор одного байта:
По оси абсцисс у нас количество дискрет счетчика, по оси ординат, соответственно, передаваемое значение ключа. Отношение сигнал/шум такое, что даже никаких фильтров не требуется, прямо как в школе на уроке информатики: находим максимум в массиве и переходим в подбору следующего байта.
Первые 6 байт подбираются легко и быстро, чуть сложнее с последним: там просто наглый перебор не проходит, нужен сброс «жертвы» перед каждой попыткой. В итоге на каждую попытку уходит что-то около 400 мс, и перебор идет в худшем случае в районе полутора минут.
Но это в худшем. После каждой попытки запрашиваем статус и, как только угадали, останавливаемся. Я вначале вообще просто быстренько ручками перебрал идентификатор, вставляя в excel вывод консоли и строя график, тем более, что это была разовая задача, но уже для статьи решил дописать автоматический перебор, ради красивой консольки…
Конечно, если бы разработчик затер загрузчик (заменил своим), так просто выкрутиться не получилось бы, но в автомобильной электронике частенько МК вообще не закрыты. В частности в блоке управления с другого отопителя, в котором был установлен V850 того же Renesas все решилось подпайкой пары проводов и копированием прошивки штатной утилитой.
Ссылки:
Четвертый
Идем по тому же пути:
01. STR R3, [R7] = 0x3B 0x60 = ";`"
02. LDR R5, [R7] = 0x3D 0x68 = "=h"
03. LDR R1, [R4, #4] = 0x61 0x68 = "ah"
04. EORS R1, R0 = 0x41 0x40 = "A@"
05. ADDS R4, #44 = 0x2c 0x34 = ",4"
06. SUBS R4, #40 = 0x28 0x3c = "(<"
07. LDR R2, [R4, #4] = 0xA2 0x68 = "bh"
08. EORS R2, R1 = 0x4A 0x40 = "J@"
09. ADDS R4, #44 = 0x30 0x34 = ",4"
10. SUBS R4, #40 = 0x28 0x3c = "(<"
11. LDR R3, [R4, #4] = 0x63 0x68 = "ch"
12. EORS R3, R2 = 0x53 0x40 = "S@"
09. STR R3, [R7] = 0x3b 0x60 = ";`"
10. LDR R0, [R7] = 0x38 0x68 = "8h"
11. BX R5 = 0x28 0x47 = "(G"
Вводим:
F3 T>R!;`=hahA@,4(<bhJ@,4(<chS@;`8h(G!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! {,
${SORRY_HABR_DONT_WANT_TO_LEAK_KEY}
LED:000000EE FAddr:002C7BB4
LED:000000EE FAddr:002C7BB4
Ну вот и все. Мы сгенерировали все ключи! Совместив их в 1 строку я получил пароль к архиву. Когда пытался его ввести в 7z, я почему-то получил ошибку. Но, потыкав порядок ключей при совмещении строки, я все же добился желаемого. У нас 4 ключа, то есть – 16 возможных комбинаций. Такое брутфорсится в ручном режиме.
Эпилог
Есть еще одна вещь, которая выходила за рамки этих публикаций, но которая стоит внимания. На разделах диска было куча исследований от ребят из RedBalloonSecurity – pdf-ки и видосы с конференций. Как по мне, это отличный способ для кандидатов узнать чем занимается компания, и частью какого мира предстоит быть претенденту. Это очень круто!
Друзья, этот диск по правде занял очень теплое место в истории моей жизни. И я был чертовски рад поделиться этой историей с вами. Это был невероятно долгий путь. Как в процессе взлома, так и в процессе переноса моих мыслей и воспоминаний в виде этой серии публикаций.
Хочу снова упомянуть пользователя @raven19. Статья блещет грамматикой благодаря ему.