Статья является продолжением Реверс черного тессеракта. , без прочтения которой, единственный вопрос, на который именно тут дается ответ - "почему на КДПВ этого цикла статей везде картинки авторства Евгения Тихоновича Мигунова?".
Отвечаю. Потому, что по странному совпадению, всеми книгами, которые он иллюстрировал, в детстве я зачитывался. Как художник, на мой взгляд, он великолепен, и выбранные цитаты его творчества, на мой взгляд, очень хорошо ложатся на темы конкретных статей. Единственное "но", его подходящие иллюстрации книг не всегда соответствуют строгому альбомному видению КДПВ Хабра. Посему, необрезанный оригинал я всё же приведу и под катом.
Блок типа 0x0D
В первой части файл carindb нарезали на части, следующий шаг - выбрать какой-то из типов блоков и посмотреть его попристальнее.
Пока нет структуры данных, не выйдет построить семантику. Первичная задача: получить понимание, какие структуры образуют данные, какие типы у них, как соотносятся данные внутри между собой, угадать стандартные структуры, разметить, выбрав хоть какой то тип блока.
А потом - наверняка будет хорошо (с).
Напомню карту блоков из прошлой статьи, надо чем-то руководствоваться при выборе препарируемого типа.
0x00000000 12 1 0x00 15329
0x00000800 13 1 0x01 5279
0x00001000 07 1 0x02 5809
0x00001800 0B 1 0x03 5867
0x00002000 0A 1 0x04 --
0x00003000 0D 3 <--- 0x05 --
0x00010800 0C 692 0x06 5198
0x011F6000 0F 31 0x07 1
0x012EA000 0E 1114 0x08 26
0x02401800 11 145 0x09 7712
0x02763800 10 1524 0x0A 1
0x048A2000 08 1 0x0B 1
0x048A2800 09 9 0x0C 692
0x048BC800 06 5198 ---> 0x0D 3
0x055E0000 08 17 0x0E 1114
0x05620800 09 7192 0x0F 31
0x0643F800 00 15329 0x10 1524
0x127BF800 08 1 0x11 145
0x127C1000 09 169 0x12 1
0x1281C000 03 5867 0x13 1
0x13C2F000 08 1 0x14 324
0x13C30800 09 169 0x15 4097
0x13C8A800 02 5809 0x16 15817
0x15070800 08 1 0x17 --
0x15071000 09 54 0x18 --
0x1509C800 01 5279 0x19 --
0x16335000 08 1 0x1A --
0x16335800 09 86 0x1B --
0x16365800 16 15817 0x1C 1095
0x1C36A000 08 1 0x1D 114
0x1C36A800 09 26 0x1E --
0x1C37A000 15 4097 0x1F --
0x1E00A800 08 1
0x1E00B000 09 3
0x1E010000 1C 1095
0x1E6E5800 08 1
0x1E6E6000 09 3
0x1E6EA000 14 324
0x1E90D800 08 1
0x1E90E000 09 1
0x1E910800 1D 114
Например, возьму тип 0x0D - и от начала недалёк (смещение 0x3000), и всего три по количеству - не сильно много, но есть рядом разные представители того же типа для проверки гипотез разметки.
Поехали. В темплейте прыгаю на адрес 0x3000, и объявляю переменную block_0x0D типа структуры BT_0x0D, объявленной выше.
PTR и LIST
После указанного стрелочкой DWORD, заметьте, каждые 3 DWORD начинается явное повторение сходных значений. На картинке выше синим выделил второе из повторений, перед ним и после - явно что-то очень похожее.
Добавляю в структуру BT_0x0D переменную UINT unknown с зеленым фоном, и после неё массив из 2042 структур BT_0x0D__triple, которая состоит из 3-х UINT разных цветов - удобно на глаз анализировать соответствие соседей. Количество 2024 подобрал, визуально контролируя окончание блока. (Во вкладке Variables раскрываю struct BT_0x0D block_0x0D, раскрываю дочернюю struct BL_HEAD head, кликаю на строку uchar here_last_byte - и фокус в окне hex перемещается на самый конец данного блока. Меняю число элементов массива, нажимаю F5, применить темплейт, если перебор - закрашиваются hex после конца, уменьшить число, и пока не закрасятся значащие байты.)
Структуре BT_0x0D добавляю локальную переменную size - расчетный размер блока в байтах.
Значение 2042 в шестнадцатеричной форме выглядит, как 0x7FA. А следующий сразу за head UINT unknown с зеленым фоном = 0x0c07fa.
Причем, и массив array[0x7FA]
начинается со смещения 0x0c от начала блока. Пара значений: указатель на массив и количество элементов массива за указателем явно неспроста рядом.
Новый тип данных в "inc_common.bt" PTR - указатель на данные внутри блока. Структура ищет у родительской переменную с размером структурыexists(parentof(this).size)
, и если её значение больше size, цвет фона - красный и запись в лог о несоответствии значения возможному. Хоть какая-то проверка.
Объявляет ushort ptr <format=hex>
- смещение относительно начала блока.
Если ptr ненулевой, и, если у родителя есть переменная с именем offset, по рассчитанному абсолютному адресу оставляет метку - байтик here на голубеньком фоне, кликая на него в Variables - в окне hex отобразится это место. Read_PTR - отображается во вкладке Variables в колонке Values.
typedef struct{
if( exists(parentof(this).size) ){
if ( ReadUShort(FTell()) > parentof(this).size){
SetBackColor(cRed); // wrong pointer when bigger block size
Printf("ERR! hex off=%X PTR=0x%04X > block.size 0x%X\n",
FTell(),
ReadUShort(FTell()),
parentof(this).size); // Log situation
}
}
ushort ptr <format=hex>; // relative offset pointer value
if(!ptr){ // do if ptr != 0
if( exists(parentof(this).offset) ){ // absolute block begin offset
local DWORD curr_position <hidden=true> = FTell();
FSeek(parentof(this).offset + ptr); // jump to calc addr
byte here <bgcolor=cAqua>; // make bookark
FSeek(curr_position); // return
}
}
}PTR <read = Read_PTR, fgcolor=cBlue, bgcolor=cAqua >;
string Read_PTR(PTR &a){
local string s;
SPrintf(s, "0x%04X (0x%04X)",
a.ptr, // value, relative offset
parentof(a).offset + a.ptr); // absolute address
return s;
}
Новый тип данных в "inc_common.bt" LIST - список указатель на начало массива и количество элементов. Локальные offset, size транзитом идут для следующей PTR ptr, но потом offset этой структуры
typedef struct{
local uint offset, size ; // block offset and size for next PTR ptr
if( exists(parentof(this).offset) ){
offset = parentof(this).offset; // start block offset
}
if( exists(parentof(this).size) ){
size = parentof(this).size; // parent block size
}
PTR ptr<bgcolor=cLtAqua>; // ptr to first list item
offset = ptr.ptr + offset; // absolute addr of list begin
ushort cnt <fgcolor=cDkGreen>; // items quantity
}LIST <read=Read_LIST, bgcolor=cLtAqua>;
string Read_LIST(LIST &a){
local string s;
SPrintf(s, "0x%04X (0x%04X) :cnt:%i(%Xh)",
a.ptr.ptr, a.offset, a.cnt, a.cnt );
return s;
}
И вот теперь в структуре BT_0x0D переменную UINT unknown с зеленым фоном заменяю на LIST p_data, правя количество элементов BT_0x0D__triple main_array[cnt]
на p_data.cnt, массиву добавлю атрибут optimize=false - иначе 010Editor схитрит, всем расчетным элементам присваивая значения первого. И в структуре парсинга блока BT_0x0D последним добавлю яркий byte after_parsed_block_info - и визуально в окне hex видно, где окончился парсинг, и прыгнуть на неё выбором в Variables можно.
В самой структуре BT_0x0D__triple тоже изменения, при сохраненном размере:
Первый UINT unknown1 в структуре - BL_ADDR, как адреса блоков они все валидны.
Сразу за адресом, похоже, char ch, его значения лежат в промежутке, что и ASCII кода символов от '0' до 'z'.
За ним еще один char seems_always_eq_1 - вставлю проверку, а правда ли всегда seems_always_eq_1 == 1
UINT unknown3 остается по-прежнему
Завершает внутренности структуры 16битный aligment: явно данные выравнивают по границе 32бита.
typedef struct{
BL_ADDR bl_type_0c;
char ch; // chars
ubyte seems_always_eq_1; //
if(seems_always_eq_1 != 1) Printf("%X i!=1 %i( %X )\n",
FTell(), // offset where happened
seems_always_eq_1, seems_always_eq_1);
uint unknown3 <bgcolor=cLtYellow>;
ushort always_zero <fgcolor=cLtGray>; // 16bit = 0
if(always_zero) // тут ноль не ноль
Printf("%X always_zero = %i( %X )\n",
FTell(), // offset where happened
always_zero, always_zero);
}BT_0x0D__triple <read = Read_triple>;
string Read_triple(BT_0x0D__triple &a){
local string s;
SPrintf(s, "%c %i %08X",
a.ch, a.seems_always_eq_1,
a.unknown3
);
return s;
}
Как видите, в консоли, во вкладке Output, - множество намеков, что seems_always_eq_1 - вовсе не всегда равна 1, бывает и 0.
Поправляю условие проверки if((seems_always_eq_1 != 1)&&(seems_always_eq_1 != 0) )
, после чего парсинг проходит без замечаний в логе. Значение always_zero, похоже, действительно предназначена для выравнивания данных по границе 32 бита. Добавляю проверку в BT_0x0D__triple: BL_ADDR bl_type_0c действительно ссылается всегда на тип 0x0C?
Нет, не всегда, говорит консоль. Чтобы визуально выделить те элементы, где тип блока bl_type_0c не 0xC, а 0xD , выделяю ярким нераскрашенный seems_always_eq_1.
if(bl_type_0c.type == 0xC)
ubyte seems_always_eq_1 <bgcolor=cLtYellow>; // type 0xC
else
ubyte seems_always_eq_1 <bgcolor=cPurple>; // type==0xD
if((seems_always_eq_1 != 1)&&(seems_always_eq_1 != 0) )
Printf("%X i!=1 %i( %X )\n",
FTell(), // offset where shit happened
seems_always_eq_1, seems_always_eq_1);
Результат неожиданнен: в блоке типа 0xD в BT_0x0D__triple seems_always_eq_1 всегда ==1, если bl_type_0c ведёт "наружу", является типом 0xC, и всегда ==0, если bl_type_0c.type == 0xD, то есть на тот же тип. Является bool - ссылка "наружу", или "внутрь", самое имя ей is_ptr_out.
Но, если часть ссылок ведёт внутрь, то на какие именно значения они указывают? Переписываю функцию, для отображения в Variables значения BT_0x0D__triple. Если is_ptr_out ==0, то в цикле "собираются" far_away.cnt символов ch из BT_0x0D__triple, начиная с адреса far_away.offset. Дерево?
string Read_triple(BT_0x0D__triple &a){
local string s;
if(a.is_ptr_out){
SPrintf(s, "%c -> %08X (%02X) 0x%0X:%i(%X)",
a.ch, a.bl_type_0c.raw, a.bl_type_0c.type,
a.far_away.offset, a.far_away.cnt, a.far_away.cnt
);
}else{
SPrintf(s,"---%c -> %08X (%02X) ->",
a.ch, a.bl_type_0c.raw, a.bl_type_0c.type);
local uint i; // Собираем все char, на которые указывает LIST
for(i=a.far_away.offset + i; // ch absolute addr
i<(a.far_away.offset + 4 +a.far_away.cnt * 12); // LIST end
i+= 12){ // 12 = sizeof(BT_0x0D__triple)
SPrintf(s,"%s %c", s, ReadByte(i+4));
}
}
return s;
}
Блоки типа 0x0B, 0x0D, 0x0F и 0x11
Все данные блока типа 0x0D размечены, но когда в руки попадает ухватистый молоток, то трудно остановиться и не продолжать этим молотком забивать всё, что попадает под руку и хоть чуть-чуть похоже на гвоздь.
С минимальными изменениями, ранее сохраненный темплейт "util_vdo_stat.bt" выводит все варианты типов блоков в виде вызовов функции block().
block(0x00000000); // 12 1
block(0x00000800); // 13 1
block(0x00001000); // 07 1
block(0x00001800); // 0B 1
block(0x00002000); // 0A 1
block(0x00003000); // 0D 3
block(0x00010800); // 0C 692
block(0x011F6000); // 0F 31
block(0x012EA000); // 0E 1114
block(0x02401800); // 11 145
block(0x02763800); // 10 1524
block(0x048A2000); // 08 1
block(0x048A2800); // 09 9
block(0x048BC800); // 06 5198
block(0x055E0000); // 08 17
block(0x05620800); // 09 7192
block(0x0643F800); // 00 15329
block(0x127BF800); // 08 1
block(0x127C1000); // 09 169
block(0x1281C000); // 03 5867
block(0x13C2F000); // 08 1
block(0x13C30800); // 09 169
block(0x13C8A800); // 02 5809
block(0x15070800); // 08 1
block(0x15071000); // 09 54
block(0x1509C800); // 01 5279
block(0x16335000); // 08 1
block(0x16335800); // 09 86
block(0x16365800); // 16 15817
block(0x1C36A000); // 08 1
block(0x1C36A800); // 09 26
block(0x1C37A000); // 15 4097
block(0x1E00A800); // 08 1
block(0x1E00B000); // 09 3
block(0x1E010000); // 1C 1095
block(0x1E6E5800); // 08 1
block(0x1E6E6000); // 09 3
block(0x1E6EA000); // 14 324
block(0x1E90D800); // 08 1
block(0x1E90E000); // 09 1
block(0x1E910800); // 1D 114
Копипащу в основной темплейт, применяю, получаю в Variables представителей всех типов блоков. Визуально пытаюсь найти сходство сырых данных разных типов с данными в блоке 0x0D, похожие пытаюсь распарсить структурой BT_0x0D.
Понимаю, что структуру блока BT_0x0D правильно называть BT_0x0B_0x0D_0x0F_0x11: для этих типов блоков подходит один и тот же темплейт (его надо только подправить, чтобы лог был не такой грязный от сообщений, что тип не 0xD и не 0xC).
Перед тем, как понять рекурсию, надо понять рекурсию
Из блока типа 0xB "внешние" "ссылки" ведут в 0xA. Из блока 0xD ссылки ведут в 0xC. Из 0x0F в 0x0E. Из 0x11 в 0x10. Везде получается ссылки ведут или в тот же тип блока, из которого вызываются, (по сути ссылаясь на массивы того же типа данных BT_0x0D__triple), или в тип блока на единицу меньший.
Редактирую BT_0x0D__triple.
Добавляю перед описанием структуры BT_0x0D__triple её forward declaration для рекурсивного описания.
Переименовываю BL_ADDR bl_type_0c -> bl_more_info
Добавляю local en_BL_TYPE en_curr_bl_type = head.type; текущий блок
Проверка раскараски фона is_ptr_out меняется if(bl_more_info.type == (en_curr_bl_type-1) )1)
Если !is_ptr_out, т.е. ссылка идет "внутрь" - создаётся массив дочерних структур BT_0x0D__triple (рекурсия, у дочерних так же создадутся дочерние и т.д. и т.п.)
struct BT_0x0D__triple; // forward declaration for recursive use
typedef struct{
BL_ADDR bl_more_info;
char ch <bgcolor=cBlue, fgcolor=cYellow>; // char
local en_BL_TYPE en_curr_bl_type = head.type; // current bl type
if(bl_more_info.type == (en_curr_bl_type-1) ){
ubyte is_ptr_out <bgcolor=cYellow>; // outer link
}else{
ubyte is_ptr_out <bgcolor=cGreen>; // innler link
}
// next for LIST far_away, have use size and offset - from bl_type_0c
local uint size = bl_more_info.size *0x800; // size in blocks, * 0x800
local uint offset = bl_more_info.offset;
LIST far_away <optimize=false>;
ushort always_zero <hidden=true, fgcolor=cLtGray>; // 16bit value = 0
if(always_zero) // тут ноль не ноль
Printf("%X always_zero = %i( %X )\n",
FTell(), // offset where happened
always_zero, always_zero);
if(!is_ptr_out){
// jmp and recursive declare children struct
local uint return_addr <hidden=true> = FTell();
FSeek(far_away.offset);
BT_0x0D__triple childs[far_away.cnt]<optimize=false>;
FSeek(return_addr);
}
}BT_0x0D__triple <read = Read_triple>;
string Read_triple(BT_0x0D__triple &a){
local string s;
if(a.is_ptr_out){
SPrintf(s, "%c -> %08X (%02X) cnt:%i",
a.ch, a.bl_more_info.raw, a.bl_more_info.type,
a.far_away.cnt
);
}else{
SPrintf(s,"---%c -> %08X (%02X) cnt:%i ->",
a.ch, a.bl_more_info.raw, a.bl_more_info.type, a.far_away.cnt);
local uint i; // Собираем все char, на которые указывает LIST
for(i=a.far_away.offset + i; // ch absolute addr
i<(a.far_away.offset + 4 +a.far_away.cnt * 12); // LIST end
i+= 12){ // 12 = sizeof(BT_0x0D__triple)
SPrintf(s,"%s %c", s, ReadByte(i+4));
}
}
return s;
}
Устройства ввода-вывода автонавигации.
А ведь я видел нечто похожее в автонавигаторе.
Для ввода адреса пункта назначения необходимо последовательно ввести в четыре поля - страна, город, улица, дом (на экране они называются Country, City, Road, Number) строковые значения. Буквы для ввода последовательно выбираются на всплывающей при редактировании поля экранной клавиатуре энкодером, причем на этой QWERTY-клавиатуре доступны к выбору не все буквы, а лишь некоторые.
Перемещение фокуса по буквам клавиатуры - вращением энкодера, выбор буквы - нажатием того же энкодера.
Вначале доступно к заполнению только поле страна, на остальные перейти нельзя. Для официального диска Восточной Европы при заполнении страны на клавиатуре активны и доступны буквы B, C, E, L, M, P, R, S, остальные неактивны. При выборе L (энкодер с нажатием) появляется меню выбора, LATVIJA или LIETUVA. А если выбрать P - поле страна сразу заполняется значением POLAND и фокус переходит на поле город, заполнение которого происходит аналогичным способом: последовательным вводом букв из предложенных на клавиатуре. Набрать можно только те города, которые расположены в Польше. После курсор уходит на поле улица, которая заполняется так же, невозможно для Poland, Olsztyn набрать название улицы, которой в Ольштыне нет.
Нагляднее описано в мануале на VDO VDO DAYTON PN 6000 на 18 странице, модель, картинка другие, но логика и данные те самые.
block(0x00001800); // 0B 1 BT_0x0B_0x0D_0x0F_0x11
block(0x00002000); // 0A 1
block(0x00003000); // 0D 3 BT_0x0B_0x0D_0x0F_0x11
block(0x00010800); // 0C 692
block(0x011F6000); // 0F 31 BT_0x0B_0x0D_0x0F_0x11
block(0x012EA000); // 0E 1114
block(0x02401800); // 11 145 BT_0x0B_0x0D_0x0F_0x11
block(0x02763800); // 10 1524
Предварительная гипотеза - а не функционал ли ввода адреса и обеспечивают рассматриваемые типы?
По количеству блоков, по последовательности расположения в carindb можно тогда предположить, что 0xB - выбор букв для страны, тогда 0xA - страна. Тогда следующая пара 0xD и 0xC - город, 0xF-0xE - улица, 0x11-0x10 - дом.
Итоги
Результаты статьи суммарно:
Структура PTR, псевдо-интеллектуальный указатель внутри блока
Структура LIST - указатель на массив, с количеством элементов
Рекурсивная структура с неэстетичным временным названием BT_0x0D__triple, описывающая единицу информации в блоках 0x0B, 0x0D, 0x0F, 0x11
Найдены блоки, которые описываются одной и той же структурой.
Структура BT_0x0B_0x0D_0x0F_0x11 полностью размечает всё содержимое блоков с типами 0x0B, 0x0D, 0x0F, 0x11
Рекурсивные структуры данных 010Editor
Гипотеза о ~
существовании обитаемых миров~ функциональном назначении распарсенных данныхДанные не только BigEndian, но еще выравниваются по границе 32 бита.
Темплейт работает и на официальных картах carindb_ee carindb_bnl (с учетом, что там блоки 0x0B, 0x0D, 0x0F, 0x11 находятся по другим адресам)
Чтобы уйти от привязки к "волшебным цифрам" отступа при вызове функции block(), напишу в инклюд "inc_block.bt" функцию FindBlockByType(en_BL_TYPE type), возвращающую адрес типа BL_ADDR первого попавшегося блока с подходящим типом. Нельзя возвращать смещение, потому что при ненахождении функция возвращает 0.
// Return finded first block addreses equal argument type
uint FindBlockByType(en_BL_TYPE type){
local UINT ptr, data;
for(ptr=0; ptr<FileSize(); ptr+=0x800){
FSeek(ptr); // set cursor =
data = ReadUInt(FTell()); // read 32bit as uint
// Смещение /8 == первым трем байтам значения под курсором
if( ptr >>11 == data >>8){
// О, тут начало блока, data - BL_ADDR
//ReadUByte(FTell()+5); // read type
if( ReadUByte(FTell()+5) == type){ // finded ?
return data; // And now my watch is ended
}
}
}
return 0; // return NO FINDED
}
В основном темплейте теперь для объявления блока необходимого типа на любых исходных carindb достаточно такой конструкции: block(FindBlockByType(0x0B)); // 0B 1 BT_0x0B_0x0D_0x0F_0x11
Дальнейший анализ - в следующей статье.