Как стать автором
Обновить

01. Сшей красное с красным, желтое с желтым, белое с белым. Наверняка будет хорошо

Время на прочтение11 мин
Количество просмотров3.2K

Статья является продолжением Реверс черного тессеракта. , без прочтения которой, единственный вопрос, на который именно тут дается ответ - "почему на КДПВ этого цикла статей везде картинки авторства Евгения Тихоновича Мигунова?".
Отвечаю. Потому, что по странному совпадению, всеми книгами, которые он иллюстрировал, в детстве я зачитывался. Как художник, на мой взгляд, он великолепен, и выбранные цитаты его творчества, на мой взгляд, очень хорошо ложатся на темы конкретных статей. Единственное "но", его подходящие иллюстрации книг не всегда соответствуют строгому альбомному видению КДПВ Хабра. Посему, необрезанный оригинал я всё же приведу и под катом.

Блок типа 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;
}
02-03
02-03

Как видите, в консоли, во вкладке 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

Дальнейший анализ - в следующей статье.

Теги:
Хабы:
Всего голосов 13: ↑12 и ↓1+11
Комментарии0

Публикации