2010-01-07:

DR6 może, ale nie musi, być przydatne do detekcji bochs/VirtualPC

medium:x86:assembler:bochs:virtualpc
Tematyka niniejszego postu będzie bardzo podobna do poprzedniego, czyli nadal pozostaje przy opisywaniu niewielkich, ale imho ciekawych, detali architektury x86. Takie detale często bywają przeoczone przez twórców emulatorów czy wirtualizerów, a więc mogą posłużyć do odróżnienia środowiska wirtualnego, od prawdziwej maszyny. Bohaterem dzisiejszego postu jest rejestr DR6, albo, będąc bardziej precyzyjnym, jego cztery pierwsze bity (0-3) czyli B0 do B3, które mówią o spełnieniu bądź nie spełnieniu warunków breakpointu. Prosiłbym o przeczytanie całego postu przed wyciągnięciem wniosków :)

Na początek, rzućmy okiem na manual Intela (tom 3B):
B0 through B3 (breakpoint condition detected) flags (bits 0 through 3) — Indicates (when set) that its associated breakpoint condition was met when a debug exception was generated. These flags are set if the condition described for each breakpoint by the LENn, and R/Wn flags in debug control register DR7 is true. They are set even if the breakpoint is not enabled by the Ln and Gn flags in register DR7.
Powyższe należy interpretować w następujący sposób:
1. Generowane jest przerwanie debuggera (int 1)
2. Dla każdego sprzętowego breakpointu (0-3) sprawdzany jest warunek, i flagi są ustawiane bądź zerowane

Należy zauważyć, że nic powyżej nie jest napisane o tym że, przed ustawieniem flag B0-B3, trzeba sprawdzić czy sprzętowy breakpoint (dalej będę pisał hwbp) jest włączony czy nie (rejestr DR7, bity Ln i Gn, gdzie n to numer hwbp).

OK, więc jak myślicie, czy wszyscy autorzy vm/emu poprawnie zinterpretowali i zaimplementowali powyższe? A może ktoś zinterpretował to jako "kiedy dany hwbp 'zaskoczy', ustaw odpowiednią flagę DR6, i wygeneruj int1"?
Sprawdźmy!

Zacznijmy od bochs'a. Po pierwsze, bochs zazwyczaj (w różnych distro) jest skompilowany z wyłączoną funkcjonalnością hwbp (czyżby detekcja bochs via niedziałające hwbp?), więc aby przetestować powyższe założenie, należy skompilować bochs'a z opcją --enable-x86-debugger.
W międzyczasie rzućmy okiem na kod obsługujący hwbp:

#if BX_X86_DEBUGGER
 else {
   // only bother comparing if any breakpoints enabled and
   // debug events are not inhibited on this boundary.
   if (! (BX_CPU_THIS_PTR inhibit_mask & BX_INHIBIT_DEBUG_SHADOW) && ! BX_CPU_THIS_PTR in_repeat) {
     if (BX_CPU_THIS_PTR dr7 & 0x000000ff) {
       bx_address iaddr = get_laddr(BX_SEG_REG_CS, BX_CPU_THIS_PTR prev_rip);
       Bit32u dr6_bits = hwdebug_compare(iaddr, 1, BX_HWDebugInstruction, BX_HWDebugInstruction);
       if (dr6_bits) {
         // Add to the list of debug events thus far.
         BX_CPU_THIS_PTR debug_trap |= dr6_bits;
         BX_ERROR(("#DB: x86 code breakpoint catched"));
         exception(BX_DB_EXCEPTION, 0, 0); // no error, not interrupt
       }
     }
   }
 }
#endif

Wygląda na to, że większość pracy wykonuje funkcja hwdebug_compare:

 for (unsigned n=0;n<4;n++) {
   bx_address dr_start = BX_CPU_THIS_PTR dr[n] & ~alignment_mask[dr_len[n]];
   bx_address dr_end = dr_start + alignment_mask[dr_len[n]];
   ibpoint_found_n[n] = 0;

   // See if this instruction address matches any breakpoints
   if (dr7 & (3 << n*2)) {
     if ((dr_op[n]==opa || dr_op[n]==opb) &&
          (laddr_0 <= dr_end) &&
          (laddr_n >= dr_start)) {
       ibpoint_found_n[n] = 1;
       ibpoint_found = 1;
     }
   }
 }

 // If *any* enabled breakpoints matched, then we need to
 // set status bits for *all* breakpoints, even disabled ones,
 // as long as they meet the other breakpoint criteria.
 // dr6_mask is the return value.  These bits represent the bits
 // to be OR'd into DR6 as a result of the debug event.
 Bit32u dr6_mask = 0;

 if (ibpoint_found) {
   if (ibpoint_found_n[0]) dr6_mask |= 0x1;
   if (ibpoint_found_n[1]) dr6_mask |= 0x2;
   if (ibpoint_found_n[2]) dr6_mask |= 0x4;
   if (ibpoint_found_n[3]) dr6_mask |= 0x8;
 }

Jak widzicie, w komentarzu jest zaznaczone, iż autorzy są świadomi jak to powinno być poprawnie oprogramowane. Natomiast kod nad komentarzem wydaje się temu przeczyć - przed sprawdzeniem warunku, sprawdzane jest czy dany hwbp jest włączony. W skrócie: komentarz jest OK, kod już nie.

Poniżej zamieściłem screenshot (kliknij by powiększyć) z PoC na bochs:
bochs is detected


Jak widać, bochs może być wykryty w ten sposób. A teraz trochę screenów z innych vm/emu (nie zwracajcie uwagi na info o int 8):
Microsoft VirtualPC (detected)
VirtualPC is detected

QEMU (not detected)
QEMU is not detected

Sun VirtualBox (not detected)
VirtualBox is not detected


Dla porównania, foto ekranów prawdziwego sprzętu (lapto ASUS z Core 2 Duo, oraz netbook ASUS EeePC z Intel Atom na pokładzie):
Laptop (not detected)
Laptop is not detected

Netbook (not detected)
Netbook is not detected


Yaay! Możemy wykorzystać tą sztuczkę do detekcji Bochs i VirtualPC!!!

Niestety, ale nie do końca.

Manual który cytowałem pochodzi z października roku 2006. Niecały miesiąc temu nowy zestaw manuali został wydany (Grudzień 2009), i wygląda na to że coś się zmieniło:
B0 through B3 (breakpoint condition detected) flags (bits 0 through 3) — Indicates (when set) that its associated breakpoint condition was met when a debug exception was generated. These flags are set if the condition described for each breakpoint by the LENn, and R/Wn flags in debug control register DR7 is true. They may or may not be set if the breakpoint is not enabled by the Ln or the Gn flags in register DR7. Therefore on a #DB, a debug handler should check only those B0-B3 bits which correspond to an enabled breakpoint.
W luźnym tłumaczeniu, flagi B0-B3 mogą, ale nie muszą zostać zapalone, jeżeli breakpoint jest wyłączony. W związku z czym, niniejszy post może, ale nie musi być przydatny.

Pytanie brzmi - w których procesorach zmiana nastąpiła. Nowy zestaw manuali zawiera zaktualizowane informacje, dotyczące kilku nowych (niżej wymienionych) procesorów. Można więc założyć, iż wszystkie, starsze od poniższych, procesory nadal zachowują się tak jak na początku postu, czyli ustawiają flagi. Wygląda więc na to, że do detekcji wystarczy dołożyć sprawdzanie modelu (cpuid), i detekcja nadal będzie działać... przynajmniej dla starszych komputerów :)

Intel® Core™2 Quad processor Q6000 series
Intel® Xeon® processor 3000, 3200 series
Intel® Xeon® processor 5000 series
Intel® Xeon® processor 5300 series
Intel® Core™2 Extreme processor X7000 and X6800 series
Intel® Core™2 Extreme QX6000 series
Intel® Xeon® processor 7100 series
Intel® Pentium® Dual-Core processor
Intel® Xeon® processor 7200, 7300 series
Intel® Core™2 Extreme QX9000 series
Intel® Xeon® processor 5200, 5400, 7400 series
Intel® CoreTM2 Extreme processor QX9000 and X9000 series
Intel® CoreTM2 Quad processor Q9000 series
Intel® CoreTM2 Duo processor E8000, T9000 series
Intel® AtomTM processor family
Intel® CoreTM i7 processor
Intel® CoreTM i5 processor


Podsumowując, ciekawostka interesująca, ale nie tak przydatna jak na to liczyłem :)

P.S. polecam pierwszy komentarz po angielskiej stronie lustra :)

Comments:

2010-01-09 20:02:52 = lolcat
{
Ciekawe ilu osobom ta wiedza się przyda hehe ;P, nie mówię, jest ciekawa, ale kto tu chce wykrywać emulowane środowiska, autorzy malware'u? Target audience 4-10 w całej Polsce?
}
2010-01-09 21:50:48 = Gynvael Coldwind
{
@lolcat
A tam "przydać" od razu ;p
To ciekawostka dla ludzi siedzących na niższych poziomach architektury, nic mniej, i niedużo więcej ;>
Co do targetu, to dobre pytanie ;> Sam jestem ciekaw ;>
}
2010-01-10 10:26:46 = _krzywy
{
A ja widzę zastosowanie antydebugingowe, ale mogłeś jeszcze na VMWare sprawdzić to bym się upewnił.
Jeśli VM -> GTFO. Brakuje 64bitowych ollków i innych r-0 wieć crackerzy instalują VM z 32bitowym sys. chcąc pozostać przy dx11, i pełnym adresowaniem więcej niż 3,2gb pamięci.
}
2010-01-11 09:46:21 = Jozin z Bazin
{
@_krzywy:
Antydebug bazujący na detekcji wirtualizacji to raczej dosyć karkołomny pomysł. VM'ki teraz są dosyć powszechnie używane chocby do testowania softu...

Inna sprawa, że olly na 64-bitowym systemie śmiga i można nim na luzie debugować aplikacje x86 (jedynie nie załadujemy sobie pluginów bazujących na r0)

@Gyn, lolcat:
Art docelowo przydatny chyba faktycznie głównie virii wirterom ;) w Polsce bedzie tego raczej więcej niż 4-10 :> A w skali całego globu lepiej nawet nie liczyć (post wisi też na angielskojęzycznej stronie bloga i openrce). Drugą grupą docelową będą ludzie odpowiedzialni za implementacje środowisk emulowanych (czyli głównie branża AV).
}
2010-01-11 15:01:07 = Krzywy
{
Jozin z Bazin: Stealth64 nie jest jeszcze tak stabilny jak by chcieli tego autorzy, ollek na 64 dziala zdecydowanie wolniej i nawet standardowe pluginy sie wysypuja jak olly advanced.

Chodziło mi raczej o dodatek do antydebuga. To + pare innych warunków.
}
2010-01-16 02:06:23 = Gynvael Coldwind
{
@_krzywy
Anti-debug w tym wypadku wymagałby skorzystania z WinAPI. Jestem w sumie prawię że pewien, że da się to zrobić na poziomie WinAPI, natomiast nie testowałem tego, więc nie wiem na ile jest to użyteczne. No chyba że mowa o anti-debug w bootkitach lub pod inne OSy ofc ;>
Co do VMWare, Parallels, etc - niestety nie dysponuje takim softem zainstalowanym na kompach chwilowo. Ale thx za przypomnienie ;> Muszę wypełnić te luki w mojej kolekcji ;>

@Jozin z Bazin
Jeśli art przydatny virii writerom, to przydatny i reverserom którzy to później analizują - więc liczba osób którym się to przyda się jest nieznacznie większa niż podane przez Ciebie 4-10 osób ;>
Niemniej jednak może ktoś nowy się zainteresuje tego typu badaniami (szczerzę to głównie na to liczę, bo użyteczność tej detekcji oceniam dość nisko).
}

Add a comment:

Nick:
URL (optional):
Math captcha: 3 ∗ 5 + 3 =