Товары из Китая

Ломаем квадрик: вандализация с элементами реверс-инжиниринга


Ломаем квадрик: вандализация с элементами реверс-инжиниринга

В последнее время авторы взялись ранее купленные ненужные вещи раздербанивать и смотреть, что там внутрях. Подержу-ка и я эту инициативу — у меня много лет назад купленный баксов за 10 квадрик завалялся, и он мне, как есть, явно абсолютно не нужен. Но может для чего и сгодится — ниже вы найдете что-то наподобие реверс-инжиниринга с кучей библиотек.

Для чего я это все делал — в статью не вошло, и так слишком много получается, не у каждого хватит терпения прочитать.

Вот в таком виде квадрик сохранился — и коробка есть, и запасная батарейка, и запасные винты. Помнится, я его купил, когда еще здоров был, и даже несколько раз игрался. Буквально через месяц после покупки все началось — и первым поехало зрение. Картинка раздвоилась и что к чему — уже было непонятно. И каким из двух мультикоптеров управлять, и как далеко он находится — оставалось только гадать.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Берем картину мироздания и тупо смотрим, что к чему.

При раскурочивании игрушки целью было все-таки понять все протоколы, чтобы потом плату можно было использовать в мирных целях.

Раскручиваем пульт управления. В качестве приемо-передатчика используется микросхема XN297, регистры управления похожи на Nordic Semiconductor nRF24L01 с некоторыми дополнениями, но по формату посылки не совсем совместима. С микроконтроллера название сошлифовано, но мы же не лыком шиты, и чувствуем, что это TG54528.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

К сожалению, от этого знания толку нуль. Что она из себя представляет и как ее программировать гугл не знает или не захотел мне рассказать. Придется использовать пульт как есть, протокол мы раскопаем позднее.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Дисплейчик используется некий BAOMEI BM-8025A. К сожалению, издевательств не выдержал и после сборки уже не работал.

Собираем пульт и пытаемся разобраться с управлением квадрика.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Обнаруживаем LDO XC6206P282MR на 2.8 Вольта.

Микроконтроллер STM32F031K4 (ARM®32-bit Cortex®-M0 up to 48 MHz, 4 Kbytes of SRAM, 16 Kbytes Flash), гироскоп с акселерометром MPU6881 (оказался вполне совместим с MPU6150, наверняка у DMP прошивка другая, но так как ее описания все равно нет, то и не заботит) и приемопередатчик — тот же XN297, что и в пульте управления.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Рисуем схему соединений — получается что-то типа такого, остальное нас мало волнует:

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Подключаем контрольные точки — потенциальным критикам пайки могу только заметить — попробуйте сделать так с жуткой атаксией.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Точки подпайки — используем провод от дохлых наушников-вкладышей.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Вид снизу:

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Для начала подключаемся к SPI и смотрим инициализацию XN297

Инициализация


160us
-----
0x200F/0xE00 *
-----
160us
-----
0x2009/0xE00 *
0x2A26/0xE00 0xA867/0x00 0x35CC/0x00 *
0xAFF/0xE26 *
0x3026/0xE00 0xA867/0x00 0x35CC/0x00 *
0x10FF/0xE26 *
0x390B/0xE00 0xDFC4/0x00 0xA703/0x00 *
0x3EC9/0xE00 0x9AB0/0x00 0x61BB/0x00 0xAB9C/0x00 *
0x1EFF/0xEC9 *
0x3F4C/0xE00 0x846F/0x00 0x9C20/0x00 *
0x1FFF/0xE4C *
0x2607/0xE00 *
0x310F/0xE00 *

А теперь, как пульт управления биндится с квадриком — для этого пульт нужно включить с ручкой trottle внизу, поднять ее до упора вверх и снова опустить. В это время нужно посмотреть, что происходит с SPI.

Bind


0x7FF/0x4040
0x2009/0x4000
0x61FF/0x40AA 0xFFFF/0xE62 0xFFFF/0x00 0xFFFF/0xBC 0xFFFF/0x7F7F 0xFFFF/0x201E 0xFFFF/0x4040 0xFFFF/0x00
0x2740/0x4E00
0xE200/0xE00
0x200F/0xE00
-------
0x2009/0xE00
0x2511/0xE00
0x200F/0xE00
0x7FF/0xE0E

После этого переключаемся на I2C и смотрим инициализацию MPU6881 — собственно, чтобы убедиться в совместимости с MPU6150.

Инициализация MPU6881


write to 0x68 ack data: 0x6B 0x02
write to 0x68 ack data: 0x19 0x01
write to 0x68 ack data: 0x1A 0x01
write to 0x68 ack data: 0x1B 0x18
write to 0x68 ack data: 0x1C 0x18
write to 0x68 ack data: 0x1D 0x00
write to 0x68 ack data: 0x1E 0x0A
write to 0x68 ack data: 0x3B
read to 0x68 ack data: 0xFF 0xD3 0xFF 0x8A 0x08 0xF9 0x09 0xF9 0xFF 0x7F 0x00 0x24 0xFF 0xFE
write to 0x68 ack data: 0x3B
read to 0x68 ack data: 0xFF 0xD3 0xFF 0x8A 0x08 0xF9 0x09 0xF9 0xFF 0x7F 0x00 0x24 0xFF 0xFE
write to 0x68 ack data: 0x3B
read to 0x68 ack data: 0xFF 0xD3 0xFF 0x8A 0x08 0xF9 0x09 0xF9 0xFF 0x7F 0x00 0x24 0xFF 0xFE

Важно ничего не упустить — микропроцессор залочен, прошивку считать малой кровью не получится. Чтобы разблокировать доступ — нужно стереть содержимое флеш-памяти. Соответственно, потом уже ничего не посмотришь.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Теперь как все это будем программировать? Самое простое — использовать Microsoft Visual Studio Code — бесплатная среда, которая прекрасно работает с Linux, если вам это важно. Ставим плагин PlatformIO и создаем любой проект на базе любого контроллера STM32. Во время создания этого проекта PlatformIO сам натащит из интернета все нужные вам инструменты и библиотеки. К сожалению, STM32F031K4 там отсутствует. Но где наша не пропадала?

В каталоге .platformio/platforms/ststm32/boards создаем файл genericSTM32F031K4.json

со следующим содержимым:

genericSTM32F031K4.json


{
"build": {
"cpu": "cortex-m0",
"extra_flags": "-DSTM32F031x4",
"f_cpu": "48000000L",
"mcu": "stm32f031k4",
"product_line": "STM32F031x4",
"variant": "STM32F0xx/F031K4"
},
"debug": {
"jlink_device": "STM32F031K4",
"openocd_target": "stm32f0x",
"svd_path": "STM32F031.svd"
},
"frameworks": [
"arduino",
"cmsis",
"stm32cube",
"libopencm3"
],
"name": "generic STM32F031K4",
"upload": {
"maximum_ram_size": 4096,
"maximum_size": 16384,
"protocol": "stlink",
"protocols": [
"jlink",
"cmsis-dap",
"stlink",
"blackmagic",
"serial"
]
},
"url": "https://www.hotmcu.com/stm32f030f4p6-minimum-systerm-boardcortexm0-p-208.html",
"vendor": "Generic"
}

Теперь можно даже с помощью ардуино программировать этот контроллер, но не рекомендую. Сама по себе не особо эффективная среда ардуино будет работать поверх HAL. Не буду высказывать свое мнение, кубологи будут недовольны — но у такой связки простейшая моргалка светодиодом съест больше 50% доступной памяти. Поэтому я дальше буду пользоваться старой, как дерьмо мамонта, библиотекой libopencm3 или вообще напрямую в регистры писать.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Как всегда, начинаем с самого тупого — поморгаем светодиодами. Для этого нужно написать подпрограммы инициализации и выводы в порты ввода-вывода. Лиха беда начало, пишем:

system.cpp


#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/cm3/systick.h>
#include <system.h>
volatile uint32_t TimeCounter;
void system_init(void)
{
rcc_clock_setup_in_hsi_out_48mhz(); // set STM32 to clock by 48MHz from HSI oscillator
TimeCounter=0;
systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
STK_CVR = 0; // clear counter
systick_set_reload(rcc_ahb_frequency / 1000); // Set up timer interrupt
systick_counter_enable();
systick_interrupt_enable();
}
void delay_us(uint16_t del_us)
{
uint32_t cnt = del_us << 2;
do
{
asm volatile("nop");
asm volatile("nop");
asm volatile("nop");
asm volatile("nop");
} while (--cnt);
}
void delay(uint32_t time)
{
TimeCounter = time;
while(TimeCounter != 0);
}
void sys_tick_handler(void)
{
if (TimeCounter |= 0) TimeCounter--;
}
void init_LEDs(void)
{
// Enable clocks to the GPIO subsystems
rcc_periph_clock_enable(RCC_GPIOB);
rcc_periph_clock_enable(RCC_GPIOA);
// LED OUTPUTS
gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO2 | GPIO4 | GPIO12);
gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO0 | GPIO2);
gpio_clear(GPIOA, GPIO2 | GPIO4 | GPIO12);
gpio_clear(GPIOB, GPIO0 | GPIO2);
}
void LedMask(uint8_t mask)
{
if (mask & 0x01) gpio_set(GPIOA, GPIO2); else gpio_clear(GPIOA, GPIO2);
if (mask & 0x02) gpio_set(GPIOA, GPIO4); else gpio_clear(GPIOA, GPIO4);
if (mask & 0x04) gpio_set(GPIOB, GPIO0); else gpio_clear(GPIOB, GPIO0);
if (mask & 0x08) gpio_set(GPIOA, GPIO12); else gpio_clear(GPIOA, GPIO12);
if (mask & 0x10) gpio_set(GPIOB, GPIO2); else gpio_clear(GPIOB, GPIO2);
}
void LedSet(uint8_t mask)
{
if (mask & 0x01) gpio_set(GPIOA, GPIO2);
if (mask & 0x02) gpio_set(GPIOA, GPIO4);
if (mask & 0x04) gpio_set(GPIOB, GPIO0);
if (mask & 0x08) gpio_set(GPIOA, GPIO12);
if (mask & 0x10) gpio_set(GPIOB, GPIO2);
}
void LedClear(uint8_t mask)
{
if (mask & 0x01) gpio_clear(GPIOA, GPIO2);
if (mask & 0x02) gpio_clear(GPIOA, GPIO4);
if (mask & 0x04) gpio_clear(GPIOB, GPIO0);
if (mask & 0x08) gpio_clear(GPIOA, GPIO12);
if (mask & 0x10) gpio_clear(GPIOB, GPIO2);
}
void LedToggle(uint8_t mask)
{
if (mask & 0x01) gpio_toggle(GPIOA, GPIO2);
if (mask & 0x02) gpio_toggle(GPIOA, GPIO4);
if (mask & 0x04) gpio_toggle(GPIOB, GPIO0);
if (mask & 0x08) gpio_toggle(GPIOA, GPIO12);
if (mask & 0x10) gpio_toggle(GPIOB, GPIO2);
}

Используем часть этих подпрограмм в основном цикле:

Blink


#include <system.h>
main(void)
{
system_init();
init_LEDs();
while (1)
{
uint8_t mask = 1;
for(uint8_t i=0; i<5; i++)
{
LedMask(mask);
mask <<=1;
delay(500);
}
}
}

Моргает!

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Для дальнейшей отладки нам не помешает последовательный вывод, одним светодиодиком придется пожертвовать, вместо него подключим вывод UART — это будет вывод PA2. Когда все будет готово, эту библиотеку нужно будет прибить насмерть, чтобы место не занимала и не мешала могралкам.

UART.cpp


#include <UART.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include <math.h>
void init_USART(void)
{
// only tx at PA2
rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_USART1);
// Setup GPIO pins for USART transmit.
gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2);
gpio_set_af(GPIOA, GPIO_AF1, GPIO2);
// Setup USART parameters.
usart_set_baudrate(USART1, 115200);
usart_set_databits(USART1, 8);
usart_set_parity(USART1, USART_PARITY_NONE);
usart_set_stopbits(USART1, USART_CR2_STOPBITS_1);
usart_set_mode(USART1, USART_MODE_TX_RX);
usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
usart_enable(USART1);
}

uint8_t num2sym(uint8_t symb)
{
symb &= 0x0F;
if (symb<10) symb+='0';
else symb+='A'-10;
return symb;
}
void tx_byte_UART(uint8_t c)
{
tx_UART('0');
tx_UART('x');
tx_UART(num2sym(c>>4));
tx_UART(num2sym©);
}
void tx_word_UART(uint16_t c)
{
tx_UART('0');
tx_UART('x');
tx_UART(num2sym(c>>12));
tx_UART(num2sym(c>>8));
tx_UART(num2sym(c>>4));
tx_UART(num2sym©);
}
void tx_UART(uint8_t c)
{
while ((USART_ISR(USART1) & USART_ISR_TXE)==0); // TX empty
usart_send(USART1, c);
}
void buff_UART(uint8_t *pointer, uint8_t length)
{
do
{
uint8_t data= *pointer;
tx_UART(num2sym(data>>4));
tx_UART(num2sym(data));
tx_UART(' ');
pointer++;
} while(--length);
tx_UART(0xa);
tx_UART(0xd);
}

void int2s(uint8_t *String, int16_t Data)
{
ldiv_t DivNum;
uint8_t * Pointer;
bool sign = false;
if(Data<0)
{
sign = true;
Data = -Data;
}

Pointer = String;
for(uint8_t i=0; i<5; i++) *Pointer++ = ' ';
*Pointer = 0;
Pointer = String+4;
for(uint8_t i=0; i<4; i++)
{
if (Data<10)
{
*Pointer--= (char)Data+'0';
break;
}
else
{
DivNum = ldiv(Data, 10);
*Pointer-- = (char)DivNum.rem+'0';
Data = DivNum.quot;
}
}
if (sign) *Pointer = '-';
}
void str2UART(uint8_t *pointer)
{
uint8_t maxl=16;
do
{
uint8_t data= *pointer;
if (data==0) break;
tx_UART(data);
pointer++;
} while(--maxl);
}
void int2UART(uint16_t Number)
{
uint8_t tx_buff[16];
int2s(tx_buff, Number);
str2UART(tx_buff);
}

Больше большие куски кода в текст вставлять не буду, все можно загрузить отсюда. Так как неизвестно, что когда отключат и запретят, лежит на майлрушном облаке, его вряд ли тронут. Вроде как гитхаб уже грозился пакость с отключением устроить. Хотя если бы Муське была бы возможность сохранять небольшие файлы — это было бы намного удобно.

Теперь пробуем поиграться с пультом управления, заодно разберемся со структурой пакетов, которые пульт отправляет. Библиотеку, написанную для XN297 найдете в архиве, ссылка выше. Сам тест выглядит так:

Тест XN297


#include <XN297.h>
#include <SPI.h>
#include <UART.h>
#include <system.h>
void CheckVoltage(void)
{
adc_read();
delay(100);
adc_read();
if (adc_read()<CODE_MIN)
{
for(uint8_t j=0; j<6; j++)
{
uint8_t mask = 1;
for(uint8_t i=0; i<5; i++)
{
LedMask(mask);
mask <<=1;
delay(100);
}
}
standby();
}
}
void TestRadio(void)
{
static bool binded = false;
uint16_t out16;
static uint8_t blink_cnt=0;
out16 = transfer_word_SPI(0x07, 0xFF);
if (out16 & 0x40)
{
if (binded) rx_pack();
else
{
bind();
binded = true;
LedClear(0x10);
}
buff_UART(rx_buff,16);
}
else
{
if (!binded)
{
blink_cnt++;
if (blink_cnt==50)
{
blink_cnt=0;
LedToggle(0x10);
}
}
}
delay(2);
}

int main(void)
{
system_init();
init_LEDs();
adc_init();
init_USART();
init_SPI();
init_XN297();
CheckVoltage();
uint8_t mask = 1;
for(uint8_t i=0; i<5; i++)
{
LedMask(mask);
mask <<=1;
delay(500);
}
while (1)
{
TestRadio();
}
}

Теперь можно очень просто разобраться с посылкой пульта управления, у меня получилось вот так:

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Управлять можно моторчиками с помощью PWM или сервами с PPM — в библиотеках все есть.

Крутим моторчики и включаем светодиодики с пульта


uint8_t u8map(int16_t x, int16_t in_min, int16_t in_max, int16_t out_min, int16_t out_max)
{
return (x - in_min) *(out_max - out_min) / (in_max - in_min) + out_min;
}

int main(void)
{
system_init();
init_LEDs();
init_PWM();
init_SPI();
init_USART();
init_XN297();
bool binded = false;
while (1)
{
uint16_t out16;
out16 = transfer_word_SPI(0x07, 0xFF);
if (out16 & 0x40)
{
if (binded)
{
rx_pack();
buff_UART(rx_buff,16);
uint8_t status;
status = rx_buff[14];
if(status &0x04) gpio_set(GPIOA, GPIO2); else gpio_clear(GPIOA, GPIO2);
if(status &0x08) gpio_set(GPIOA, GPIO4); else gpio_clear(GPIOA, GPIO4);
if(status &0x10) gpio_set(GPIOA, GPIO12); else gpio_clear(GPIOA, GPIO12);
if(status &0x20) gpio_set(GPIOB, GPIO0); else gpio_clear(GPIOB, GPIO0);
if(status &0x40) gpio_set(GPIOB, GPIO2); else gpio_clear(GPIOB, GPIO2);
status = rx_buff[6];
status = u8map(status, 0, 0xff, 0, 100);
timer_set_oc_value(TIM1, TIM_OC1, status);
status = rx_buff[7];
status = u8map(status, 0x43, 0xbb, 0, 100);
timer_set_oc_value(TIM1, TIM_OC2, status);
status = rx_buff[8];
status = u8map(status, 0x43, 0xbb, 0, 100);
timer_set_oc_value(TIM2, TIM_OC1, status);
status = rx_buff[9];
status = u8map(status, 0x43, 0xbb, 0, 100);
timer_set_oc_value(TIM2, TIM_OC2, status);
}
else
{
bind();
binded = true;
}
}
delay(100);
}
}

Для полноты картины добавим в библиотеки функции для работы с MPU6881. Вообще-то он мне не нужен, но уж раз есть — надо иметь возможность пользоваться. Общеизвестно, что определить положение движущегося объекта при помощи только акселерометра достоверно нельзя, нужен еще гироскоп и, если совсем по-хорошему, и магнетометр. И арифметику вспомнить придется. Вы еще помните, что такое кватернионы? Я — нет.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Придется вспомнинать, ну а в библиотеках все приложено, в том числе и фильтр Махони.

Вот простейший метод протестировать эту библиотеку. Магнетометра нет, и поэтому у этого фильтра курс слегка плывет. Проще всего, наверно, это устранить в процессе калибровки.

Тест IMU


cImu IMU;
uint8_t cout;
void TestImu(void)
{
IMU.readAccelData(); // Read the x/y/z adc values
IMU.readGyroData(); // Read the x/y/z adc values
IMU.Mahony_no_mag_Update();
delay(20);
cout++;
if (cout==50)
{
IMU.quater2euler();
cout=0;
int16_t Angles[3];
Angles[0] = (int16_t)(IMU.roll);
Angles[1] = (int16_t)(IMU.pitch);
Angles[2] = (int16_t)(IMU.yaw);

for(uint i=0; i<3; i++)
{
int2UART(Angles[i]);
tx_UART(' ');
}
tx_UART(0xa);
tx_UART(0xd);
}
}

int main(void)
{
system_init();
init_LEDs();
init_USART();
init_i2c();


IMU.resetMPU9250(); // Reset registers to default in preparation for device calibration
IMU.calibrateMPU9250(IMU.gyroBias, IMU.accelBias); // Calibrate gyro and accelerometers, load biases in bias registers
IMU.initMPU9250();
IMU.deltat = 0.02;
cout=0;
while(1) TestImu();
}

В этой библиотеке вы также найдете общеизвестный трюк по извлечению обратного квадратного корня из Quake III Arena.

Ломаем квадрик: вандализация с элементами реверс-инжиниринга

Для чего я этот квадрик расковырял — может, в другой раз расскажу. А пока позвольте на этом откланяться — может, кто-то чего-нибудь полезного найдет в моем рассказике.


СМОТРИ ТАКЖЕ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *