Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
обновлён 2 декабря!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

Исследование NVRAM раздела EMMC на процессорах MTK MT65XX

Обсудить статью на форуме

Очень удобно, когда все крэкерские инструменты, книги и статьи в одном месте. Используйте сборник от EXELAB - вот тут.

Автор: punxer <punxer@yandex.ru>

Здравствуйте, форумчане.

Давненько я не занимался каким либо реверсом да и программированием под настольные операционные системы, возможно пришло время это исправить.

Тема статьи весьма узко специализирована, тем не менее информации как таковой практически нет, возможно потому, что и интереса особого к теме нет, но он таки возник, я постараюсь частично пролить свет.

Чего я не буду преследовать в данной статье:
- готовое решение;
- изменение IMEI,MAC,SN на какой либо другой, так как это действие не законно;

Что я постараюсь донести (из своего понимания, термины и реалии могут отличаться от описанных мной, это лишь мои рабочие домыслы):
- Что такое NVRAM;
- Как устроена NVRAM MTK при работе на низком уровне;
- Данные и структуры NVRAM и их представление в бинарных данных EMMC;
- Шифрование NVRAM и что с ним делать;
Что я использую при исследовании:
- IDA & HexRays;
- Hex Editor;
- VC++ v6.0;
- Google и MTK sources;

Итак, преступим.

NVRAM в широком понимании слова это - см. википедию.
Тут все понятно, просто энергонезависимая память для хранения данных. Реализации могут быть разные в текущих реалиях:
- Батарейка и память;
- Отдельная EEPROM;
- Раздел NAND/EMMC;
- Файлы NAND/EMMC;
- и так далее, кто на что горазд.
По сути это блок данных, переживающих отсутствие основного питания. На телеонах в NVRAM хранятся заводские/пользователские калибровки, IMEI, WIFI MAC, BT MAC, SerialNumber и тд.
Особого смысла уделять даному пункту какое то время нет. Вроде понятно все.
В аппаратах на базе процессоров MEDIATEK NVRAM располагается в NVDATA в разделе /data/nvram, к которому можно получить доступ с рут правами, в нем мы имеем дерево каталогов и файлы в корне AllFile и AllMap, которые являются частями бинарного региона в EMMC nvram. Дамп NVRAM из NVDATA назовем nvram.img, ибо это раздел имеющий файловую систему. Дамп NVRAM из EMMC назовем nvram.bin - это двоичный образ, не имеющий какой либо доступно открытой структуры, хранящий в себе все данные и файлы nvram.img.
Работать будем с nvram.bin и попробуем частично понять его структуру и восстановить оттуда данные об IMEI, WIFI,BT.
В телефоне, если погуглить, вся работа с NVRAM идет через пару основных библиотек без исходного кода: libnvram.so, libfile_op.so. Данные библиотеки предоставляют доступ к NVRAM, доступ к сохранению NVRAM в файлы или регион EMMC, то есть могут дать нам представлениее о данных в NVRAM.bin.
В именах библиотек nvram.bin - это binregion. Что ж, преступим к листингу IDA( да я нашел libfile_op с отладочной информаией) и просмотру дампа в шестнадцатиричном редакторе.
В редакторе сразу можно увидеть что то похожее на заголовки файлов с путями в nvdata почти всего, что нам нужно:

/data/nvram/APCFG/APRDEB/BT_Addr.....@..X..A.................z.@...@<..A.....(.@[|.@...........A.......@....<..A.......@.......@.... ......./data/nvram/APCFG/APRDEB/WIFI.dr.....@..X..A.................z.@...@<..A.....(.@[|.@...........A.......@....<..A.......@.......@...."......./data/nvram/APCFG/APRDEB/WIFI_CUSTOM.

Если присмотреться пристальнее то можно увидеть, что расстояния между началами имен всегда одно и то же = 0x8C.
Это по всей видимости размер заголовка файла, такое представление я понял гуляя по исходникам МТК прошивок. Так же статистически выяснено что начало имени первого идет всегда по смещению 0x40.
Откроем libfile_op в IDA и посморим декомпилированные функции.

v17 = open("/data/AllMap", 4098);
v32 = (LID *)malloc(0x8Cu);
            v33 = v32;
            if ( !v32 )
            {
              v21 = "FileOp_MakeFile malloc memory for FileInfo failed!!\n";
              goto LABEL_38;
            }
            memset(v32, 0, 0x8Cu);
            v33->reserved007 = 7;
            v33->recsize = fsize;
            ++v44;
            pthread_mutex_lock(&gFileStartAddrlock);
            v33->offset = gFileStartAddr;
            memcpy(v33->name, acSrcPathName, 0x80u);
            write(v25, v33, 0x8Cu);
            _android_log_print(
              3,
              "NVRAM",
              "FileInfo: Filenum %u (addr - %d / size - %d) \n",
              v44,
              v33->offset,
              v33->recsize);
            gFileStartAddr += v33->recsize;
            v34 = 1;
В итоге получаем из этой с схожих функций структуру лежащуюю по смещению  0x40-0x12 от начала дампа, к коду она уже применена к переменно v33 :
typedef struct
{
	unsigned int reserved007;
	unsigned int offset;
	unsigned int recsize;
	unsigned char name[128];// дополнение 

}LID,*PLID;

Размерность массива взята из исходников так что бы подходил размер всей структуры = 0x8C.
В начале файла до начала первого заголовка мы майдем по нулевому смещению:

BackupFileInfo  struc ; (sizeof=0xC, align=0x4, copyof_29)
00000000                                         ; XREF: .bss:stBackupFileInfo/r
00000000 ulCheckSum      DCD ?                   ; XREF: FileOp_CheckBackUpResult+30/r
00000000                                         ; FileOp_RestoreData_All+30/r
00000004 iCommonFileNum  DCD ?                   ; XREF: FileOp_RestoreAll_NvRam+2A/r
00000008 iCustomFileNum  DCD ?                   ; XREF: FileOp_RestoreAll_NvRam+2C/r
0000000C BackupFileInfo  ends

В каких еденицах эти размеры и конкретно чего(как взаимосвязаны файлы AllFile, AllData размеры частей в дампе) я пока не понял
и не было целью пока что.
По смещению 12(DEC):

00000000 File_Title_Header struc ; (sizeof=0x18, align=0x4, copyof_20)
00000000                                         ; XREF: FileOp_BackupDataToFiles/r
00000000                                         ; FileOp_RestoreFromFiles/r
00000000 iApBootNum      DCW ?
00000002 iApCleanNum     DCW ?
00000004 iMdBootNum      DCW ?                   ; XREF: FileOp_BackupDataToFiles+4E/o
00000004                                         ; FileOp_BackupDataToFiles+502/o ...
00000006 iMdCleanNum     DCW ?
00000008 iMdImpntNum     DCW ?
0000000A iMdCoreNum      DCW ?
0000000C iMdDataNum      DCW ?
0000000E                 DCB ? ; undefined
0000000F                 DCB ? ; undefined
00000010 iFileBufLen     DCD ?
00000014 BackupFlag      DCD ?
00000018 File_Title_Header ends

И далее по смещению (64-12)(dec) вышеупомянутый список подряд лежащих структур LID( мое название).
Получаем такую структуру файла:
0x00 - BackupFileInfo(0x0C)
0x0С - File_Title_Header (0x1C - максимально встечаемый размер но судя по всему с выравниванием структура идет до 0x34 )
0x34 -Смещение первого LID и так до последнего (искать удобно по паттерну строки /data/, эти пути прямые для восстановления файлов в nvdata телефона)
Уже этого достаточно для составления списка заголовков файлов, далее они читаются начиная от смещения 0x00 прибавляя при каждой итерации размер файла из структуры заголовка. Так как мы знаем, что сами тела файлов присутствуют в теле дампа, весьма осмысленно будет предположить что файл делен на части, и смещение в структуре не что инеое, как смещение + оффсет данных.
Далее пройдясь по исходникам ядер и SN_Writer_Tool мы видим что MAC адреса WIFI лежат прямым текстом. Поиском в HEX- редакторе мы легко найдем смещение файла данных WIFI. В исходниках MTK можно найти такую структуру:

typedef struct _MT6620_CFG_PARAM_STRUCT {
	/* 256 bytes of MP data */
	UINT_16 u2Part1OwnVersion;
	UINT_16 u2Part1PeerVersion;
	UINT_8 aucMacAddress[6];
	UINT_8 aucCountryCode[2];
	TX_PWR_PARAM_T rTxPwr;
	UINT_8 aucEFUSE[144];
	UINT_8 ucTxPwrValid;
	UINT_8 ucSupport5GBand;
	UINT_8 fg2G4BandEdgePwrUsed;
	INT_8 cBandEdgeMaxPwrCCK;
	INT_8 cBandEdgeMaxPwrOFDM20;
	INT_8 cBandEdgeMaxPwrOFDM40;
	UINT_8 ucRegChannelListMap;
	UINT_8 ucRegChannelListIndex;
	UINT_8 aucRegSubbandInfo[36];
	UINT_8 aucReserved2[256 - 240];
	/* 256 bytes of function data */
	UINT_16 u2Part2OwnVersion;
	UINT_16 u2Part2PeerVersion;
	UINT_8 uc2G4BwFixed20M;
	UINT_8 uc5GBwFixed20M;
	UINT_8 ucEnable5GBand;
	UINT_8 aucPreTailReserved;
	UINT_8 aucTailReserved[256 - 8];
} MT6620_CFG_PARAM_STRUCT, *P_MT6620_CFG_PARAM_STRUCT, WIFI_CFG_PARAM_STRUCT, *P_WIFI_CFG_PARAM_STRUCT;

Посмотрев на нее, видим, что начало файла WIFI параметров начинается со смещения mac_offset - 4 и все укладывается в рамки нашего понимания.
Так же найдем смещение bluetooth, к слову, - это первый элемент заголовка, и смещение в нем 0x00, что наталкивает на мысли, что смещение данного файла и будет смещеним нужного нам региона в бинарнике.
Опытным путем, описанным выше, устанавливаем, что смещение файлов, лежащих подряд в соответствии с заголовками = 0x20000.
Уже на данном этапе мы можем частично восстановить дерево каталогов и файлов nvdata, включая файлы с MAC адреами WIFI, BT и файлы с IMEI.
Данные коментарии приведены из исходников SnWriterTool
* UI input = "1234567890AC" storage in AP nvram will be:
* wifiAddr[0] = 0x12
* wifiAddr[1] = 0x34
* wifiAddr[2] = 0x56
* wifiAddr[3] = 0x78
* wifiAddr[4] = 0x90
* wifiAddr[5] = 0xAC

/*
*** Feature phone ***
* UI input = "1234567890AC" storage in Modem nvram will be:
* btAddr[0] = 0xAC
* btAddr[1] = 0x90
* btAddr[2] = 0x78
* btAddr[3] = 0x56
* btAddr[4] = 0x34
* btAddr[5] = 0x12
---------------------------------------------------------
*** Smart Phone ***
* UI input = "1234567890AC" storage in AP nvram will be:
* btAddr[0] = 0x12
* btAddr[1] = 0x34
* btAddr[2] = 0x56
* btAddr[3] = 0x78
* btAddr[4] = 0x90
* btAddr[5] = 0xAC
Попытки так же найти IMEI никаким успехом не увенчались, хотя известно из весьма широкодоступных документов MTK, что IMEI занимает 8 байт - тетрада на цифру от 0 до 9.
И так же из исходников SN Writer tool, что тетрады поменяны местами:
//UI input = "123459876543210" storage in nvram will be:
//imei[0] = 0x21
//imei[1] = 0x43
//imei[2] = 0x95
//imei[3] = 0x78
//imei[4] = 0x56
//imei[5] = 0x34
//imei[6] = 0x12
//imei[7] = 0xf0
В процессе понимания и восстановления структуры, я не принимал это как заведомую правду, но проверяя предположения, остановлился на этом.
Из вышесказанного следует, что данные об IMEI, SN лежат в зашифрованном виде и, казалось бы на этом все, но гуляя по исходникам и программам, работающим каким либо образом было найдено упоминание алгоритма RC4, потом был найден исходник imei.c, который по вводимым данным собирает файл с imei - mp0b_001.
В исходнике виден ксор "ключем" и та же перестановка тетрад, что и выше. Ключ в исходнике не является ни чем иным, как кейстримом от какого то ключа по алгоритму RC4, а так же смещения двух имеев занимающие по 12байт, что поначалу смутило, но в иходнике виден явный подсчет контрольной суммы имея в байты 11-12 и заполнение 9-10 байт константными значениями, то есть было предположено, что имей таки занимает 10 байт, и подтвержение сего предположения не заставило себя ждать:

typedef struct
{
    unsigned char   imei[8];
    unsigned char   svn;
    unsigned char   pad;
} nvram_ef_imei_imeisv_struct;

Итак, имеем кейстрим, алгоритм RC4, известную структуру и можем прочитать не более 8 байт с шифрованного места, так как имеи начинаются одинаково, а в зашифрованном виде они разные, то очевидным будет что шифруются только 8 байт по смещению 0 и по смещению 12 от файла mp0b о чем собственно нам и говорит рабочий вариант с keystream длиной в 8 байт. К слову, на данный момент я уже знал этот 4 байтный ключ, найденный не мной и давно и широко используемый в узких кругах. Вот код из IMEI.c использующий данный кейстрим и кодирующий имей 15 цифр в 12 байтную часть mp0b:

int calc_imei(char inp_imei[15], char out_imei[12])
{
    char out_mask[12] = {0xAB, 0xA0, 0x6F, 0x2F, 0x1F, 0x1E, 0x9A, 0x45, 0x0, 0x0, 0x0, 0x0};
    int i=0, j=0;

    for (i=0, j=0; i < 15; i++, j++)
    {

        if (inp_imei[i] < '0' || inp_imei[i] > '9')
        {
            return 1;
        }
        out_imei[j] = (inp_imei[i] - '0');

        if (i >= 14)
            break;

        if (inp_imei[i+1] < '0' || inp_imei[i+1] > '9')
        {
             return 1;
        }
        out_imei[j] += ((inp_imei[i+1] - '0') << 4);

        out_imei[j] = out_imei[j] ^ out_mask[j];
        i++;
    }

    out_imei[j] = out_imei[j] ^ out_mask[j];

    out_imei[8] = 0x57; //&#208;&#339;&#208;&#190;&#208;¶&#208;µ&#209;‚ &#208;±&#209;‹&#209;‚&#209;&#338; &#208;&#184; 0x0, &#208;&#189;&#208;&#184; &#208;&#189;&#208;° &#209;‡&#209;‚&#208;&#190; &#208;&#189;&#208;µ &#208;&#178;&#208;»&#208;&#184;&#209;&#143;&#208;µ&#209;‚
    out_imei[9] = 0xDB; //&#208;&#339;&#208;&#190;&#208;¶&#208;µ&#209;‚ &#208;±&#209;‹&#209;‚&#209;&#338; &#208;&#184; 0x0, &#208;&#189;&#208;&#184; &#208;&#189;&#208;° &#209;‡&#209;‚&#208;&#190; &#208;&#189;&#208;µ &#208;&#178;&#208;»&#208;&#184;&#209;&#143;&#208;µ&#209;‚

    out_imei[10] = out_imei[11] = 0;

    for (i = 0; i < 10; i++)
    {
        if (i & 0x1)
        {
            out_imei[11] += out_imei[i];
        }
        else
        {
            out_imei[10] += out_imei[i];
        }
    }

    return 0;
}

Произвести обратные действия, думаю не составит труда.
Приведу небольшие куски весьма топорного кода, который никогда не претендовал на какое - то готовое решение и в основном использовался для автоматизации рутины и проверки догадок (перебор в лоб из первых вариантов, когда я еще не имел представления о структуре и размерах записей):

while (!is.eof())
	{
		is.seekg(i);
		is.get(temp);
		if (temp=='/')
		{
			is.get(temp);
			if (temp=='d')
			{
				is.get(temp);
				if (temp=='a')
				{
					cout<<"Start : "<<"0x"<<std::hex<<i<<endl;
					is.seekg(i);
					is.read(fn,FILENAMELENGTH);
					cout <<" "<< fn <<endl;
					is.seekg(i-12);
					is.read((char*)&lid,sizeof(lid));
					lid.base=0x20000;
					//raw record dump
					is.read((char*)filebuff,lid.recsize);

Тут из полного имени файла легко получаем короткое имя и делаем вывод на экран, сохраняем файл по относительному пути каталогов заменяя слеши на правлильные и добавляя путь к нашему EXE.
Вывод на экран WIFI MAC адреса(в fname имя в lid )


if (!fname->find("WIFI",0) /*&& fname->find("WIFI_CUSTOM",0)*/)
{
	memcpy(mac,((char*)filebuff+4),6);
	cout<<"Found WiFi Addr: 0x"<<std::hex<<(int)mac[0]<<std::hex<<(int)mac[1]<<std::hex<<(int)mac[2]
	<<std::hex<<(int)mac[3]<<std::hex<<(int)mac[4]<<std::hex<<(int)mac[5]<<endl;

}

Вывод IMEI:

if (!fname->find("MP0B_001",0))
{

	CryptInit((unsigned char *)key,sizeof(key));
	CryptProcess((char *)filebuff,lid.recsize);			
	cout << "IMEI1 = ";
	cout <<"Raw Bytes: "<<std::hex<<(unsigned int)(((unsigned char *)filebuff)[0]) <<(unsigned int)(((unsigned char *)filebuff)[1])<<endl;
	for (i=0;i<8;i++)
	{
		imei[i] = (char)(((((unsigned char *)filebuff)[i] & 0xF0) >> 4) |
		((((unsigned char *)filebuff)[i] & 0x0F) << 4));			
		cout<<internal<<setfill('0'); // fill with 0s
		cout<<setw(2);
		os<<internal<<setfill('0'); // fill with 0s
		os<<setw(2);
		if (i==7)
		{
			imei[i] = imei[i] >> 4;
			cout<<internal<<setfill('0'); // fill with 0s
			cout<<setw(1);
			os<<internal<<setfill('0'); // fill with 0s
			os<<setw(1);
		}

		cout<<std::hex<<(unsigned int)(imei[i]);
	}
			


Шифрование RC4 реализовано:

void CryptInit(unsigned char* key, unsigned int keylen)
{
	ii = jj = kk = tt =  xx = 0;
	rctemp = 0;

    //always initialize the arrays with zero
    memset(Sbox,0, sizeof(Sbox));
    memset(Sbox2,0, sizeof(Sbox2));

    //initialize sbox i
    for(ii = 0; ii < 256U; ii++)
    {
        Sbox[ii] = (char)ii;
    }

    jj = 0;

    //initialize the sbox2 with user key
    for(ii = 0; ii < 256U ; ii++)
    {
        if(jj == keylen)
        {
            jj = 0;
        }
        Sbox2[ii] = key[jj++];
    }


    jj = 0 ; //Initialize j
    //scramble sbox1 with sbox2
    for(ii = 0; ii < 256; ii++)
    {
        jj = (jj + (unsigned long) Sbox[ii] + (unsigned long) Sbox2[ii]) % 256U ;
        rctemp =  Sbox[ii];
        Sbox[ii] = Sbox[jj];
        Sbox[jj] =  rctemp;
    }

	ii = jj = 0;
}

void CryptProcess(char *inp, unsigned int inplen)
{
    for(xx = 0; xx < inplen; xx++)
    {
       
        ii = (ii + 1U) % 256U;
        
        jj = (jj + (unsigned long) Sbox[ii]) % 256U;
        rctemp = Sbox[ii];
        Sbox[ii] = Sbox[jj] ;
        Sbox[jj] = rctemp;

        
        tt = ((unsigned long) Sbox[ii] + (unsigned long) Sbox[jj]) %  256U ;

		if (inp)
		{
			kk = Sbox[tt];
			inp[xx] = (inp[xx] ^ kk);
               }
	}
}


Собственно все, что я хотел, я получил: вывод данных с сырого дампа, частичное понимание его структуры.
Ключи и данный кейстрим подходят разве что для MT65XX, написать брутфорс 4х байтного ключа особых проблем составить не должно, как и 8 байтного
(какова вероятность что расшифрованный кусок будет иметь только цифры в тетрадах и сойдется контрольная сумма?!)), не считая времени прямого перебора - порядка 1 миллиона дней.
Надеюсь данная информация будет кому то полезна. Интересующимся прошу на почту с идеями и предложениями.
Ключ 4байтный известный заранее я сбрутил потом тоже успешно.
При комментировании вспомните пожалуйста начало статьи=).

Дамп, библиотеки на которых все отработано вышлю при нужде.








Обсуждение статьи: Исследование NVRAM раздела EMMC на процессорах MTK MT65XX >>>


Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS