Хардуерно хакване на хард диск – втора част

Оригиналът е на Jeroen Domburg

17
2216

В първата част започнахме началото на хардуерен хак за хард диск, който може незабележимо да компрометира цялата компютърна система. Спряхме се на платката, която се намира в хард дисковете и на чиповете, които се използват в нея, както и на адресното пространство на контролера. Да продължим с инжектирането на собствен външен код.

Инжектиране на външен код

Разбира се, ако исках да променям нещо в кеша, не бих сканирал всичките тези 64 MB оперативна памет. В реалния живот няма време за такива неща и явно трябва да разбера как работи този кеш. За тази цел трябваше да създам дъмп на фърмуера на този хард диск, да го дизасемблирам и да разбера как работят функциите за кеширане.

Дизасемблирането на подобен фърмуер не е тривиална задача. Първо, в кода са смесени ARM инструкции и инструкции в стил thumb. Това дразни, а и аз нямам дизасемблер, който може автоматично да превключва между тези два типа. Освен това, в кода липсва характерната особеност, която силно опростява дизасемблирането на софтуера: обикновено функциите са написани по такъв начин, че да извеждат съобщения от типа ‘Couldn’t open logfile!’, когато нещо не върви както трябва. Тези съобщения оказват много голяма помощ, когато се опитваш да разбереш по какъв начин се използва дадена функция. Но в този фърмуер няма подобни редове: наложи се да се разбере предназначението на всяка процедура само по нейния код. И още, кодовата база е доста остаряла и понякога дизасемблираният код изглежда така, сякаш част от функциите са добавени по-късно, което усложни анализа.

Но има и неща, които опростяват дизасемблирането. Първо, изглежда че Western Digital не използвала маскиране и скриване на кода – няма никакви трикове от рода на условен или безусловен преход в средата на процесорна инструкция. Освен това, благодарение на JTAG интерфейса е възможно да се експериментира с кода, като се поставят контролни точки и да се извършват промени в този код в реално време. Това значително опрости определянето ролята на всяка функция.

След продължително изучаване на кода и многократното използване на дебъгера за проверка на моите догадки, успях да се добера до системата за кеширане: таблицата в оперативната памет, която аз нарекох ‘Таблица на дескрипторите на кеша’:

Всеки елемент от таблицата на дескрипторите на кеша описва един блок от кеша на хард диска. Той съдържа началния LBA на секторите на диска, които са вече кеширани или трябва да бъдат кеширани, показва до каква степен е запълнен кешът, има флагове за описване състоянието на някои елементи на кеша и число-адрес, указващо къде в паметта се намират кешираните данни.

Сега вече, когато разкрих тайната на дескрипторите на кеша, започнах да се питам, дали ще мога да прихвана данните, които трябва да бъдат записани в хард диска, след като са подадени от компютъра към SATA порта? За да направя това, трябва да намеря начин HDD контролера да изпълни мой собствен код. Нещо повече, трябва да накарам този код да се изпълни в точно определено време – ако променя кеша прекалено рано, то тогава данните от плочите на хард диска няма да са се записали в него, а ако го променя твърде късно, то те вече ще са отишли в компютъра.

Реализирах това като разширение на вече съществуваща функция. Моят хак ще се изпълнява от процесорното ядро Feroceon 2, за което както знаем от първата част, че извършва обмена на данни чрез SATA интерфейса. Очевидно е, че именно за него трябва да има някаква функция, която извършва настройките на SATA за получаването и изпращането на данните към кеша. Ако намеря тази функция, навярно ще мога да изпълнявам свой собствен външен код.

След дълги изследвания, поставяне на контролни точки и много проби и грешки, аз най-после намерих съответстващата на моите изисквания функция. Модифицирах я по такъв начин, че в началото да изпълнява моя код. Ето как изглежда оригиналният код:

000167BE ; r0 - slot in sata_req
000167BE sub_0_167BE:
000167BE                 PUSH    {R4-R7,LR}
000167C0                 MOVS    R7, R0
000167C2                 LSLS    R1, R0, #4
000167C4                 LDR     R0, =sata_req
000167C6                 SUB     SP, SP, #0x14
000167C8                 ADDS    R6, R1, R0
000167CA                 LDRB    R1, [R6,#0xD]
000167CC                 LDR     R2, =stru_0_40028DC
000167CE                 STR     R1, [SP,#0x28+var_1C]
000167D0                 LDRB    R0, [R6,#(off_0_FFE3F108+2 - 0xFFE3F0FC)]
000167D2                 LDRB    R5, [R6,#(off_0_FFE3F108 - 0xFFE3F0FC)]

А ето как изглежда същото място, когато в началото на кода е добавено извикване към фрагмента с моя код:

000167BE ; r0 - slot in sata_req
000167BE sub_0_167BE:
000167BE                 PUSH    {R4-R7,LR}
000167C0                 MOVS    R7, R0
000167C2                 LD      R6, =hookedAddr
000167C4                 BX      R6
000167C6                 .dw     checksumFix
000167C8                 .dd     hookedAddr
000167CC                 LDR     R2, =stru_0_40028DC
000167CE                 STR     R1, [SP,#0x28+var_1C]
000167D0                 LDRB    R0, [R6,#(off_0_FFE3F108+2 - 0xFFE3F0FC)]
000167D2                 LDRB    R5, [R6,#(off_0_FFE3F108 - 0xFFE3F0FC)]
000167D4                 LSLS    R0, R0, #4
...
FFE3F000                 PUSH    {R0-R12, LR}
FFE3F004                 BX      changeThingsInCache
FFE3F008                 POP     {R0-R12, LR}
FFE3F00C                 LSLS    R1, R0, #4
FFE3F010                 LDR     R0, =sata_req
FFE3F014                 SUB     SP, SP, #0x14
FFE3F018                 ADDS    R6, R1, R0
FFE3F01C                 LDRB    R1, [R6,#0xD]
FFE3F020                 BX      0x167CC

Вижда се, че някои от началните инструкции са заменени с преход към моя код в неизползваната памет с адрес 0xFFE3F000, като са добавени допълнителни байтове, за да може контролната сума за оригиналната област на кода да е вярна. Ако не бях направил това, то хард дискът щеше да зареди резервно копие на същия код от своите плочи, а на нас това не ни е необходимо. Кодът, към който се осъществява прехода, изпълнява моя собствена функция с име changeThingsInCache, а след това прави същото, което трябваше да извършат заменените от мен инструкции в началото на оригиналния код. След това се извършва завръщане към оригиналната функция, сякаш нищо не се е случило.

Сега вече е достатъчно да напиша своята функция за промяна на кешираните данни. Като за първи тест реших да напиша процедура, която в псевдо код изглежда по следния начин:

void hook() {
  foreach (cache_struct in cache_struct_table) {
    if (is_valid(cache_struct)) {
      foreach (sector in cache_struct.sectors) {
        sector[0]=0x12345678;
      }
    }
  }
}

Този неголям фрагмент от инжектиран код при всяко негово извикване заменя първите 4 байта на всеки сектор с 0x12345678. Ясно е, че като я вмъкна във фърмуера на хард диска, това число трябва да се запише в началото на всеки прочетен сектор. Заредих съответните фрагменти чрез JTAG…

И ето какъв е резултатът:

Реалността

Естествено, бих могъл да превърна постигнатото в един напълно завършен хак, но заради необходимостта от включването на JTAG за запис в оперативната памет при всяко стартиране на хард диска, то този хак е съвършено безполезен. Трябва да направя така, че той да бъде постоянно записан някъде – в някое място, откъдето автоматично да се взема след всяко включване на HDD.

За тази цел е подходяща флаш паметта на хард диска. По принцип, всичко може да се събере в някой от свободните служебни сектори върху плочите, но ако нещо се обърка, няма да има начин да възстановя данните от този диск. По принцип, чипът на флаш паметта е просто стандартен компонент с осем крачета и може да се разпои от платката, да се презапише и да се запои отново. Точно така и направих – разпоих този чип и го поставих върху една опростена експериментална платка по такъв начин, че лесно да мога лесно да превключвам тази памет – към програматора или към хард диска:

И какво да запишем в тази флаш памет? За щастие, използваният в този чип формат бе вече известен: той се състои множество блокове данни, а в самото начало се намира таблица, която ги описва. Тази таблица указва местоположението на всеки блок във флаш паметта, каква компресия се използва (ако е компресиран), мястото където трябва да се запише в оперативната памет и накрая, адресът на изпълнение, откъдето трябва да се изпълни програмата, записана в съответния блок (ако е програма).

За съжаление, не можах да модифицирам кода, записан в тази флаш памет, понеже той бе компресиран с неизвестен алгоритъм. Но за сметка на това успях да добавя допълнителен блок и да му задам такъв адрес, че той да се изпълнява преди другите. Това силно опрости моята работа: когато започне изпълнението на ‘моя’ блок, мога да направя така, че той да вмъкне преходи във вече разкомпресирания код.

Разбира се, за тази цел трябва да изтегля, да разделя на части и след това отново да сглобя двоичния файл на флаш паметта. За тази цел написах специален софтуерен инструмент, на който дадох скучното име fwtool. Този инструмент може да прави дъмпове на различните блокове флаш памет и да преобразува техните заглавия в текстови файлове за по-лесно редактиране. След това може да да се модифицира, премахва и добавя блок и накрая всичкото да се събере в един файл, който да бъде записан във флаш паметта. По този начин добавих в този образ собствения фрагмент с код, записах всичкото обратно в чипа и го запоих на неговото място върху платката на хард диска. Стартирах HDD и получих следното:

Точно това исках да получа. Номерът е, че вече не се налагат модификации през JTAG.

Другият начин

Въпреки че модификацията на флаш паметта бе една голяма стъпка напред, както преди, не можех да реализирам своя хакерски сценарий: не мисля, че на света има сървърна компания, която ще вземе подарения от мене хард диск и ще започне да го използва след като види, че флаш чипът е бил отпоен и отново запоен. Трябва да намеря начин да презапиша тази флаш памет както си е там – директно върху платката на хард диска и желателно, когато този HDD си е включен към компютъра.

Софтуерните инструменти за презаписване на фърмуерите на Western Digital ясно показват, че това е възможно. На практика това си е просто една програма, която се стартира под DOS и записва новия фърмуер във флаш паметта на хард диска и в сервизната област на този HDD. Според информацията в уеб пространството, в програмите от този тип се използват така наречените Vendor Specific Commands. Има и други софтуерни инструменти, които могат да модифицират фърмуерите – известен е експериментален код, който може да използва незаетите запазени сектори за скриване на данни. Освен това, съществува и комплекта инструменти с име idle3-tools, които могат да бъдат използвани за модификацията на един байт във фърмуера.


Край на втора част. В третата част се описва окончателния хак и различните методи и похвати за неговото използване.

5 2 гласа
Оценете статията
Абонирай се
Извести ме за
guest
17 Коментара
стари
нови оценка
Отзиви
Всички коментари