Всем привет!
Попал ко мне в руки на постоянное использование корейский видеорегистратор, язык интерфейса в нем — Корейский, и никак поменять его нельзя. Не то что там нужно постоянно что то читать и нажимать, но хотелось чтобы интерфейс стал понятен.
Итак, имеем видеорегистратор 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. Никого не призываю к реверс инжинирингу, и проделыванию подобных манипуляций с техникой. Все что вы делаете, вы делаете на свой страх и риск.