Англофикация корейского видеорегистратора

от автора

Всем привет!

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

Итак, имеем видеорегистратор FineVu LX5000 power. В поисках русской или английской прошивки перерыл некоторое количество сайтов, но все безуспешно.

На сайте производителя мне попадалась информация что для продуктов для рынка Кореи, прошивки с другими языками не предоставляются.

Скачал с офф сайта Корейскую прошивку. Потом заметил что у офф сайта есть US версия, подумал что там тоже могут быть подобные видеорегистраторы. Нашел видеорегистратор который выглядит примерно так же как мой, это был GX5000. Скачал прошивку от него и начал изучать.

Прошивка с расширением файла .bin легко открылась архиватором 7-zip, внутри была структура папок Linux системы.

Структура папок в прошивке

Структура папок в прошивке

Начал я искать папку с программой, которая рулит видеорегистратором. Нашел в /mnt/blackbox. В этой директории мне попался файл git-revision в котором был указан автор коммитов, попытался написать ему, но ответа нет по сей день. Ладно, продолжаем, в этой же директории нашел картинки, очень много картинок, прошивка занимает 80Мб, и львиная часть этого объема это картинки и звуки. Я так понял что инженеры компании производителя вообще не парились, и весь интерфейс нарезали картинками, даже информационные сообщения в картинках, вот пример:

Как вы помните, до этого я же скачал английскую прошивку от видеорегистратора GX5000, в этой прошивке оказались почти такие же картинки, но уже с англ. надписями. Вытащил картинки и звуки, из каждого файла, и начал сравнивать, оказалось что большая часть картинок переведена, однако были и особенные пункты меню и кнопки, которые остались не переведенные, с этим помог переводчик. С помощью других сервисов определил что шрифт на картинках очень поход на Roboto Medium. Благодаря этому удалось сделать наиболее похожие к родным картинки с кнопками и пр.

Дальше встал вопрос, как собрать прошивку с обновленными файлами, пробовал разные варианты с форматом iso, но все безуспешно. Решил что обратно все соберу просто заменой файлов в исходном файле. Попросил ChatGPT написать мне простенький скрипт на Python, который ищет последовательность байт в исходном файле, и заменяет нужной последовательностью байт. Если проще, то ищет в.bin файле картинку и заменяет ее английской версией картинки. Скрипт работал плохо, и пришлось его немного допилить. Тут же я увидел что некоторые картинки получились больше весом, поэтому они перетирают байты следующих файлов. Сначала сделал так: если файл с англ. текстом в байтах больше, чем файл с родным, корейским текстом, то просто сдвину байты в.bin файле. Но потом отказался от этой идеи, из за того что в прошивке есть симлинки, и даже если для симлинков это не страшно, есть еще причины почему лучше было по-максимуму сохранить структуру файла, но об этом позже. Как обходной вариант, придумал что могу ужать png и wav а разницу заменить нулями. Так и сделал. Пришлось повозиться конечно чтобы все заменяемые файлы были меньше, сложнее всего было с wav, пришлось немного порезать продолжительность дорожки.

import os  def replace_and_adjust_bytes(file1_path, file2_path, file3_path):     try:         # Read bytes from file1         with open(file1_path, 'rb') as file1:             file1_bytes = file1.read()          # Read bytes from file2         with open(file2_path, 'rb') as file2:             file2_bytes = file2.read()          # Read bytes from file3         with open(file3_path, 'rb') as file3:             file3_bytes = bytearray(file3.read())          # Find the starting index of the file1 bytes in file3         start_index = file3_bytes.find(file1_bytes)          if start_index == -1:             print(f"Error: File 1 bytes not found in {file3_path}.")             return          # If file2 is larger than file1, we need to shift the bytes in file3         if len(file2_bytes) > len(file1_bytes):             extra_bytes = len(file2_bytes) - len(file1_bytes)             # Shift bytes in file3 after the found sequence to accommodate extra bytes from file2             file3_bytes = file3_bytes[:start_index] + file2_bytes + file3_bytes[start_index + len(file1_bytes):]             print(f"WARNING! file2 {file2_path} more than file1 {file1_path} make shift!")         else:             # If file2 is smaller, replace and pad the difference with zeros             padding = len(file1_bytes) - len(file2_bytes)             file3_bytes[start_index:start_index + len(file1_bytes)] = file2_bytes + b'\x00' * padding             #print(f"File2 {file2_path} less than file1 {file1_path} make zeroes!")          # Write the modified content back to file3         with open(file3_path, 'wb') as file3:             file3.write(file3_bytes)          #print(f"Successfully replaced bytes of {file1_path} with {file2_path} in {file3_path}")      except Exception as e:         print(f"An error occurred while processing {file1_path}: {e}")  def recursive_file_process(folder1, folder2, file3):     for dirpath1, _, filenames1 in os.walk(folder1):         # Generate corresponding folder2 path         relative_path = os.path.relpath(dirpath1, folder1)         dirpath2 = os.path.join(folder2, relative_path)          for filename in filenames1:             # Full paths for file1 and file2             file1_path = os.path.join(dirpath1, filename)             file2_path = os.path.join(dirpath2, filename)              # Only proceed if the file exists in both folder1 and folder2             if os.path.exists(file2_path):                 replace_and_adjust_bytes(file1_path, file2_path, file3)             else:                 print(f"Skipping {filename}: File not found in {dirpath2}.")  # Example usage folder1 = 'C:\\temp\\blackbox lx5000 orig' #Директория с родными Корейскими картинками folder2 = 'C:\\temp\\blackbox lx5000' #Директория с англ. картинками file3 = 'LX5000PWR_FW.bin'  # This is the single file in the folder with the script  recursive_file_process(folder1, folder2, file3)

Дальше нужно было понять что в файле с прошивкой отвечает за версию и есть ли подписи, CRC или что-то такое. Открыл .bin файл в hex-editor и обнаружил в первых байтах название регистратора, и версию прошивки, сразу поднял версию прошивки.

Начало файла оригинальной прошивки

Начало файла оригинальной прошивки

Попробовал закинуть прошивку в видеорегистратор, он отказался принимать прошивку. Предположил что скорее всего какая то контрольная сумма поменялась, или я в процессе замены картинок что то еще зацепил. Продолжил изучение дальше, ушел в конец файла, там были 10 байт чем то заняты, возможно подпись или CRC подумал я. Как оно вычисляется я тогда не знал. Но файлы внутри прошивки то мне видны, начал искать что-то вроде updater или upgrader. Нашел несколько файлов sh и исполняемых, по sh вышел на файл bbupgrade, закинул его в ida, декомпилировал в псевдокод, и начал смотреть.

Методы которые представляли интерес

Методы которые представляли интерес

Увидел там и проверку подписи и проверку файла.

Благодаря выводам в консоль, намного проще разобраться что происходит на каждом этапе

Благодаря выводам в консоль, намного проще разобраться что происходит на каждом этапе

В очередной раз обратился к ChatGPT, попросил переписать этот псевдокод в c++, он переписал, но программа не работала. Как и сказал сам ChatGPT, псевдокод был для POSIX систем — там были методы которые есть только в linux. Не стал я искать чем заменить эти вызовы, просто перешел в linux, запустил код и подсунул ему оригинальный файл с прошивкой, код не сработал, потому что ChatGPT не учел некоторые касты, ну и вообще перемудрил. В общем немного посидел с напильником, код успешно запустился и выдал сообщение, что файл валидный.

#include <cstdio> #include <cstring> #include <cstdlib> #include <cerrno> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <regex.h> #include <string.h> using namespace std;  int FWUpgradeFileAnalyze(const char *a1, const char *a2) {     int fd;     ssize_t bytesRead;     struct stat buf;     unsigned char *ptr = nullptr;     int versionNew;     int s2[] = {-953237502, 1437226410, 44717482};     __int16_t v22 = 11968;     char s[128] = {0};     char name[128] = {0};     int currentVersion = 100000;     unsigned char checksum;      // Create the file path      // Check if the file exists     if (stat(a1, &buf) >= 0)     {         printf("[Firmware Check] %s - %ld\n", a1, buf.st_size);          // Allocate memory for the file content         ptr = (unsigned char *)malloc(buf.st_size);         if (ptr)         {             memset(ptr, 0, buf.st_size);              // Open the file             fd = open(a1, O_RDONLY);             if (fd >= 0)             {                 // Read the file                 bytesRead = read(fd, ptr, buf.st_size);                 if (bytesRead == buf.st_size)                 {                     close(fd);                      // Validate the file content                     if (strlen((const char *)ptr) == 9 && strcmp((const char *)ptr, "LX5000PWR") == 0)                     {                         checksum = 255;                         for (int i = 0; i < buf.st_size - 1; ++i)                         {                             checksum += ptr[i];                         }                          printf("[Firmware Check] checksum=%d checkF=%d\n", (unsigned char)~checksum, ptr[buf.st_size - 1]);                          if ((unsigned char)~checksum == ptr[buf.st_size - 1])                         {                             // Calculate the new version from the file content                             versionNew = 10 * (10 * (10 * (10 * (10 * (ptr[16] - '0') + ptr[18] - '0') + ptr[19] - '0') + ptr[21] - '0') + ptr[22] - '0') + ptr[23] - '0';                              printf("[Firmware Check] versionNew=%02x\n", versionNew);                              if (versionNew >= currentVersion)                             {                                 printf("[Firmware Check] OK(%s, %s(new:%d, curr:%d), %02x)\n",                                        (const char *)ptr, (const char *)ptr + 16, versionNew, currentVersion,                                        ptr[buf.st_blksize - 1]);                                 free(ptr);                                 return 1;                             }                             else                             {                                 printf("[Firmware Check] Fail(%s, %s(new:%d, curr:%d), %02x)\n",                                        (const char *)ptr, (const char *)ptr + 16, versionNew, currentVersion,                                        ptr[buf.st_blksize - 1]);                             }                         }                         else                         {                             printf("[Firmware Check] Checksum Fail(%s, %s, %02x)\n",                                    (const char *)ptr, (const char *)ptr + 16, ptr[buf.st_blksize - 1]);                         }                     }                     else                     {                         printf("[Firmware Check] Sig Fail:");                         for (int j = 0; j <= 14; ++j)                             printf("%02x:%02x ", ((unsigned char *)s2)[j], ptr[buf.st_size - 16 + j]);                         putchar('\n');                     }                     free(ptr);                      puts("[Firmware Check] Fail.");                     sync();                     return 0;                 }                 else                 {                     int err = errno;                     printf("[Firmware Check] read fail:%s, error:%d\n", s, err);                     free(ptr);                     close(fd);                     return 0;                 }             }             else             {                 printf("[Firmware Check] open fail:%s\n", s);                 free(ptr);                 return 0;             }         }         else         {             int err = errno;             printf("[Firmware Check] malloc fail:%d\n", err);             return 0;         }     }     else     {         int err = errno;         printf("[Firmware Check] stat fail:%s, error:%d\n", s, err);         return 0;     } }  int main(int argc, char *argv[]) {      FWUpgradeFileAnalyze("/opt/fw/LX5000PWR_FW_my.bin", "");     return 1; }  int FWUpgradeFileCheck(int a1) {     return -1; }  int sub_10FA4(char *a1, char *a2) {     size_t v3;     char *s;     char *pattern;     size_t v7;     regmatch_t pmatch;     regex_t preg;      pattern = a1;     s = a2;     v7 = strlen(a2);      if (regcomp(&preg, pattern, REG_EXTENDED) != 0) // Using REG_EXTENDED for flag '3' in regcomp         return 0;      v3 = strlen(s);      if (regexec(&preg, s, 1, &pmatch, 0) != 0) // Match single occurrence     {         regfree(&preg);         return 0;     }     else     {         regfree(&preg);         return 1;     } } 

Отлично, подумал я, уже финиш в одном метре от меня. Подсунул модифицированный файл с прошивкой, внутри которого были английские картинки, программа выдала мне что файл не валидный, и отобразила какая контрольная сумма должна быть в последнем байте, поменял в прошивке байт на указанный, запустил снова с++ код, программа выдала сообщение, что файл валидный. Супер.

Довольный я принялся к установке прошивки в видеорегистратор. Закинул файл на флешку, запустил регистратор, и начал ждать… Первым появилось сообщение о наличии новой прошивки(если не совпадает сумма, подпись, да даже название файла, регистратор просто удаляет прошивку, и никаких сообщений не появляется), это был хороший знак, через несколько секунд начинается процесс установки прошивки. 2%…4%…6%…8% и за секунду счетчик с 8% перепрыгивает на 100% и видеорегистратор перезагружается…

Процесс обновления прошивки

Процесс обновления прошивки

Через несколько секунд он включается, и…. Все осталось по-корейски. Не получилось.

Предупреждение при запуске видеорегистратора

Предупреждение при запуске видеорегистратора

Я расстроился, и на несколько дней закинул идею. Но периодически я вспоминал и думал а как же так, ведь я все сделал чтобы прошивка принялась, что я мог не учесть.

Решил сравнить файл с родной прошивкой с модифицированным файлом, и заметил что по всему файлу идет сдвиг на один байт. Этот сдвиг идет сразу после версии прошивки, т.е. я не аккуратно отредактировал версию.

Разница в оригинальной и модифицированной прошивке

Разница в оригинальной и модифицированной прошивке

Тут я уже понимал что 99% что проблема в этом сдвиге. Исправляю эту нелепую ошибку, закидываю прошивку на флэшку и включаю видеорегистратор. Начался процесс прошивки 2%…4%…6%…8%…10%…12%…. доходит до 100%, видеорегистратор перезагружается, иии…

Предупреждение при запуске видеорегистратора на англ. языке

Предупреждение при запуске видеорегистратора на англ. языке

Все получилось, теперь у видеорегистратора интерфейс англофицирован.

Меню на английском языке

Меню на английском языке

Спасибо за внимание!

P.S. Никого не призываю к реверс инжинирингу, и проделыванию подобных манипуляций с техникой. Все что вы делаете, вы делаете на свой страх и риск.


ссылка на оригинал статьи https://habr.com/ru/articles/855334/


Комментарии

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

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