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