2009-12-11:

Resume Flag a detekcja debuggera

re:asm:x86:easy
Na forum UW-Team.org pojawiło się pytanie o wykorzystanie flagi RF (Resume Flag) do detekcji debuggera. Ponieważ nie słyszałem wcześniej o wykorzystaniu tej flagi do anty-dbg, postanowiłem przyjrzeć się bliżej. Poniżej zamieszczam moją odpowiedź na zadane pytanie.

--quote--
Na 16 bicie jest flaga RF, Resume Flag, która jest używana do wyłączenia (ważne) hardware exceptions na czas jednej instrukcji (chodzi o zapobiegnięcie zapętleniu się handlera na breakpoint - gdyby tego nie było, to handler by musiał dezaktywować breakpoint, albo wpadłby w nieskończoną pętlę breakpoint->handler->breakpoint->handler->...).
Czyli, teoretycznie masz rację - można by wykryć czy w danym miejscu (mówimy tutaj o rozdzielczości typu jedna instrukcja, więc jest to mało użyteczne) został ztriggerowany hardware exception, a następnie debugger powrócił. Teoretycznie można by taką pułapkę przygotować instrukcją PUSHFD, która wrzuci wszystkie flagi na stos, a potem byśmy mogli sprawdzić czy na stosie jest zapalony bit 16.
Albo lepiej! Teoretycznie moglibyśmy w miarę często ustawiać za pomocą POPFD flagę RF, tak aby hardware breakpointy nie działały!

Ale niestety, jak to zwykle bywa, diabeł tkwi w szczegółach.
Cytat z manuala Intela (tom 2B):

When copying the entire EFLAGS register to the stack, the VM and RF flags (bits 16 and 17) are not copied; instead, the values for these flags are cleared in the EFLAGS
Czyli, instrukcji PUSHFD sobie nie użyjemy do zapisania RF na stosie, więc z pułapki numer jeden nici.

Drugi cytat z manuala Intela (tym razem tom 3B):

(Note that the POPF, POPFD, and IRET instructions do not transfer the RF image into the EFLAGS register.)
Uu, czyli nici i z ustawiania flagi RF.
Jedyna instrukcja na x86 która ustawia flagę RF to IRETD, która btw jest dostępna tylko w kernel-mode.

Tak na prawdę pozostaje jeszcze jedna opcja - exceptiony! Czy nie można by skorzystać z faktu, że przy rzucie wyjątkiem CPU zapisuje na stosie kontekst wątku, który następnie jest przekazywany do exception handlera user-mode? Jeżeli wyjątek będzie rzucony bezpośrednio po IRETD, to teoretycznie RF będzie zapalone jeśli był to hardware breakpoint!
No i kolejny cytat.

The processor sets the RF flag automatically prior to calling an exception handler for any fault-class exception except a debug exception that was generated in response to an instruction breakpoint.
Czyli, zakładając nawet że system grzecznie poda cały context exception handlerowi w user mode, to dla większości exceptionów sam CPU zapala flagę RF - czyli zaburza pomiar w stopniu uniemożliwiającym odczytanie wyniku.

Ale zostaje debug exception, czyli hardware breakpoint, który przecież chcemy sprawdzić właśnie czy był czy nie był wywołany i który i tak by najpierw debugger dostał w swoje ręce. Więc wracamy do punktu wyjścia. (zresztą, możemy sprawdzić zawartość rejestrów DR nie bawiąc się w ogóle we flagę RF)

Podsumowując, RF nam się do tego nie przyda.
--end of quote--

Zachęcam do poczytania o RF w manualach Intel'a, tom 1 i 3B, oraz o instrukcjach PUSHFD, PUSHF, POPFD, POPF, IRET i IRETD w maunalach tom 2a i 2b.

P.S. Ostatnio zostałem zapytany czym się różni POPF od POPFD (czyli co daje sufix D) - otóż w zasadzie niczym, tj. w real-mode ta instrukcja nazywa się POPF, a w protected-mode POPFD (D pewnie od double czy też double word). Niemniej jednak to tylko nazwa, opcode pozostaje taki sam. Większość assemblerów uznaje POPF i POPFD za alias. UPDATE: Zachęcam do przeczytania pierwszego komentarza by bw ;>

Comments:

2009-12-16 16:41:32 = bw
{
9C pushfd = esp - 4 | zapis flag jako DWORD
66 9C pushf = esp - 2 | zapis flag jako WORD

9D popfd = odczytaj flagi jako DWORD | esp + 4
66 9D popf = odczyt flag jako WORD | esp + 2

jak dla mnie jest roznica (wiec nie polecam stosowac ich wymiennie i w ogole omijac ta forme bez D) i dodatkowo niszczy 4-kowe wyalignowanie stosu, wiec jak ktos sie majtnie na Win to po ptokach i nawet zwykly MessageBox sie zle wyswietli, polecam testa:

.686
.mmx
.model flat,stdcall

include e:devmasmincludewindows.inc
include e:devmasmincludekernel32.inc
include e:devmasmincludeuser32.inc

includelib e:devmasmlibkernel32.lib
includelib e:devmasmlibuser32.lib

.data
szCaption db 'Hi',0
szHello db 'Hello world!',0

.code
_start:
pushf

push MB_ICONINFORMATION
push offset szCaption
push offset szHello
push 0
call MessageBox

popf

ret
end _start

mozna by to ciekawie wykorzystac w stalych fragmentach 32 bitowego kodu bez wywolan api, tzn. walnac pushf zeby blednie ustawic wyrownanie stosu i teraz jakiekolwiek proby podpiecia sie np. z jakims inline patchem (bez swiadomosci tego faktu) spowoduja jego wywalenie. Cheers.
}
2009-12-17 21:52:44 = Gynvael Coldwind
{
@bw
Thx za komentarz ;>
Przyznaje, że nie przyszło mi do głowy użycie prefixu 66 z tą instrukcją ;>
Cheers ;>
}
2009-12-19 18:06:03 = Fanael
{
Zależy od asemblera - np. taki FASM, jeśli powiemy mu, że kod ma być 32-bit, to pushf i pushfd wypluwa tak samo - 9C.
}
2009-12-23 20:37:45 = u
{
Z kolei GNU as na nie uznaje pushfd/popfd (co ciekawe, nie uznaje też postfixu l), ale za to "data16 pushf" albo "pushfw" emituje prefix 66 tak jak powyżej.
}

Add a comment:

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