Archive for décembre, 2007

Nolife :(

Brrr fait froid, tient en parlant de ca, le nouvel opus du zine venu du québec vient de sortir. Mindkind, hé oui ils récidivent. On se retrouve avec un zine plus orienté « journal et enquête » avec des morceaux de scène under info, de l’humour et du clash, d’ailleurs je prend encore cher dans le mag, extrait : « Assaye pas de nous convaincre que t’as une vie Ivan… ça fit pas :-P », excusez les, ils ne savent pas ce qu’ils disent, je me demande si les fautes sont faites exprès ou si elles sont dues à l’accent :p. Sinon l’article de Wyzeman « The circle of lost 3 hacker », la suite de son enquête sur la team devhell et de son rapprochement avec le phrack staff, le fameux « Circle of Lost Hackers », j’adore :). Bon je ne vais pas troller plus dessus sinon je vais encore me faire défacer …

Pendant que j’y suis, j’aimerais pour faire découvrir une série de scripts pour WinDbg fait par le grand et discret Yolejedi. Labo Windbg Script est une bibliothèque permettant d’éffectuer des opérations sur des composants noyau sans se prendre la tête avec la doc de WinDbg. On peut dumper l’IDT, la GDT, les MSR, les syscalls à la fois de ntsokrnl et de win32k, retrouver des process cachés avec une simple petite commande ! Exemple :

kd> ad /q *; $$><script\\@@init_cmd.wdbg;

Labo Windbg Script : Ok :)
('al' for display all commands)
kd> al
  Alias            Value
 -------          -------
 !!display_all_gdt               $$><script\display_all_gdt.wdbg;>
 !!display_all_idt               $$><script\display_all_idt.wdbg;>
 !!display_all_msrs              $$><script\display_all_msrs.wdbg;>
 !!display_current_gdt           $$><script\display_current_gdt.wdbg;>
 !!display_current_idt           $$><script\display_current_idt.wdbg;>
 !!display_current_msrs          $$><script\display_current_msrs.wdbg;>
 !!display_system_call           $$><script\display_system_call.wdbg;>
 !!hide_current_process          $$><script\hide_current_process.wdbg;>
 !!save_all_reports              $$><script\save_all_reports.wdbg;>
 !!search_hidden_process         $$><script\search_hidden_process.wdbg;>
 !@check_msr_and_printf_name     $$><script\check_msr_and_printf_name.wdbg;>
 !@display_gdt                   $$><script\display_gdt.wdbg;>
 !@display_idt                   $$><script\display_idt.wdbg;>
 !@display_msrs                  $$><script\display_msrs.wdbg;>
 !@display_thread_process_info   $$><script\display_thread_process_info.wdbg;>
 !@get_debug_mode                $$><script\get_debug_mode.wdbg;>
 !@get_original_ntcall           $$><script\get_original_ntcall.wdbg;>
 !@get_original_win32kcall       $$><script\get_original_win32kcall.wdbg;>
 !@get_system_version            $$><script\get_system_version.wdbg;>
 !@hide_process                  $$><script\hide_process.wdbg;>
 !@is_hidden_process             $$><script\is_hidden_process.wdbg;>
 !@printf_state_name_of_thread   $$><script\printf_state_name_of_thread.wdbg;>
 rep_root         script
kd> !!display_all_gdt

#################################
# Global Descriptor Table (GDT) #
#################################

Processor 00
Base : 8003F000    Limit : 03FF

Off.  Sel.  Type      Sel.:Base      Limit     Present  DPL  AVL  Informations
----  ----  --------  -------------  --------  -------  ---  ---  ------------

0000  0000  NullDesc  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
0008  0008  Code32         00000000  FFFFFFFF  YES      0    0    Execute/Read, accessed  (Ring 0)CS=0008
0010  0010  Data32         00000000  FFFFFFFF  YES      0    0    Read/Write, accessed  (Ring 0)DS/SS/ES=0010
0018  001B  Code32         00000000  FFFFFFFF  YES      3    0    Execute/Read, accessed  (Ring 3)CS=001B
0020  0023  Data32         00000000  FFFFFFFF  YES      3    0    Read/Write, accessed  (Ring 3)DS/SS/ES=0023
0028  0028  TSS32          80042000  000020AB  YES      0    0    (Busy) Eip = 0c4d8b51
0030  0030  Data32         FFDFF000  00001FFF  YES      0    0    Read/Write, accessed  (Ring 0)FS=0030  FS:0->(KPCR*)FFDFF000
0038  003B  Data32         00000000  00000FFF  YES      3    0    Read/Write, accessed  (Ring 3)FS=003B  FS:0->(TEB*) 00000000
0040  0043  Data16         00000400  0000FFFF  YES      3    0    Read/Write
0048  0048  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
0050  0050  TSS32          80550880  00000068  YES      0    0    (Available) Eip = nt!KiTrap08 (804e069d)
0058  0058  TSS32          805508E8  00000068  YES      0    0    (Available) Eip = nt!KiTrap02 (804df5b6)
0060  0060  Data16         00022F30  0000FFFF  YES      0    0    Read/Write, accessed
0068  0068  Data16         000B8000  00003FFF  YES      0    0    Read/Write
0070  0070  Data16         FFFF7000  000003FF  YES      0    0    Read/Write
0078  0078  Code16         80400000  0000FFFF  YES      0    0    Execute/Read
0080  0080  Data16         80400000  0000FFFF  YES      0    0    Read/Write
0088  0088  Data16         00000000  00000000  YES      0    0    Read/Write
0090  0090  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
0098  0098  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00A0  00A0  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00A8  00A8  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00B0  00B0  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00B8  00B8  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00C0  00C0  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00C8  00C8  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00D0  00D0  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00D8  00D8  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 00000000 00000000
00E0  00E0  Code16         FA7B5000  0000FFFF  YES      0    0    Execute/Read, accessed
00E8  00E8  Data16         00000000  0000FFFF  YES      0    0    Read/Write
00F0  00F0  Code16         804D8B28  0003B337  YES      0    0    Execute-Only
00F8  00F8  Data16         00000000  0000FFFF  YES      0    0    Read/Write
0100  0100  Data32         FA7C5000  0000FFFF  YES      0    0    Read/Write, accessed
0108  0108  Data32         FA7C5000  0000FFFF  YES      0    0    Read/Write, accessed
0110  0110  Data32         FA7C5000  0000FFFF  YES      0    0    Read/Write, accessed
0118  0118  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 8003F120 00000000
0120  0120  Reserved  ....:........  ........  NO       0    0    Raw Descriptor : 8003F128 00000000
[...]

C’est beau ! La doc est bien faite en plus, je vous laisse donc vous amusez avec le travail du mâitre :
http://ivanlef0u.fr/repo/labo_windbg_script.zip

Yolejedi si tu lis ça, revient :(

5 comments décembre 13th, 2007

OutputDebugString

J’étais tranquillement en train de debugger avec Olly un de mes soft qui utilisait l’API OutputDegugString lorsque en écoutant du scremo Jap (c’est du bon !) lorsque que je suis tombé sur un paradoxe que seul Chuck Norris aurait pu comprendre au premier coup d’oeil. Le message de debug apparaissait bien sous Olly mais pas dans le tool DebugView qui est censé afficher les messages provenant de cette API. A partir de ce moment un WTF a germé dans mon esprit comme si je me retrouvais face un PABX datant des années 10 avant JR.

Beaucoup de gens connaissent l’API OutputDebugString comme anti-Olly-v1.10 utilisant le fait que OllyDbg plante lorsqu’il reçoit une string mal formaté. Dans mon cas j’ai affaire a un bug qui permettrait de détecter la présence de OllyDbg. Dans ce post je vais essayer de vous présenter le fonctionnement de l’API OutputDebugString puis comment concevoir un anti-olly basé dessus.

OutputDebugString n’est pas très difficile à utiliser, elle demande en paramètre juste un pointeur sur une string (AINSI ou Unicode, au choix). Je m’en sers surtout pour debug des programmes, style DLL, pour obtenir des messages d’erreurs que je peux lire avec DebugView. Bon finit de jouer, sortons IDA (5.2 is out #@!) sur kernel32 et désassemblons OutputDebugStringA.

; Exported entry 636. OutputDebugStringA
; Attributes: bp-based frame

; void __stdcall OutputDebugStringA(LPCSTR lpOutputDebugString)
public _OutputDebugStringA@4
_OutputDebugStringA@4 proc near

Arguments= dword ptr -244h
var_240= dword ptr -240h
var_23C= dword ptr -23Ch
var_238= dword ptr -238h
var_234= dword ptr -234h
var_230= dword ptr -230h
hObject= dword ptr -22Ch
var_228= dword ptr -228h
lpBaseAddress= dword ptr -224h
hEvent= dword ptr -220h
var_21C= byte ptr -21Ch
var_1C= dword ptr -1Ch
ms_exc= CPPEH_RECORD ptr -18h
arg_0= dword ptr  8

push    234h
push    offset dword_7C85A0A0
call    __SEH_prolog    ; hHandle
mov     eax, ___security_cookie
mov     [ebp+var_1C], eax
mov     ecx, [ebp+arg_0]
mov     [ebp+var_23C], ecx
and     [ebp+ms_exc.disabled], 0
mov     eax, ecx
lea     esi, [eax+1]

loc_7C859DA1:
mov     dl, [eax]
inc     eax
test    dl, dl
jnz     short loc_7C859DA

sub     eax, esi
inc     eax
mov     [ebp+Arguments], eax
mov     [ebp+var_240], ecx
lea     eax, [ebp+Arguments]
push    eax             ; lpArguments
push    2               ; nNumberOfArguments
push    0               ; dwExceptionFlags
push    40010006h       ; dwExceptionCode ; DBG_PRINTEXCEPTION_C
call    _RaiseException@16 ; RaiseException(x,x,x,x)
jmp     loc_7C85A023

loc_7C85A023:
or      [ebp+ms_exc.disabled], 0FFFFFFFFh
mov     ecx, [ebp+var_1C]
call    @__security_check_cookie@4 ; __security_check_cookie(x)
call    __SEH_epilog
retn    4
_OutputDebugStringA@4 endp

Ok, comme on peut le voir, la fonction en fait appel qu’a l’api RaiseException qui comme son joli nom l’indique va nous générer une belle exception. Le prototype de RaiseException est le suivant :

void RaiseException(
  DWORD dwExceptionCode,
  DWORD dwExceptionFlags,
  DWORD nNumberOfArguments,
  const ULONG_PTR* lpArguments
);

Dans le cas présent on a un dwExceptionCode qui vaut 0×40010006 (DBG_PRINTEXCEPTION_C). Le param lpArguments pointe sur 2 valeurs, le premier arg correspond à la taille de la string+1, le second à un pointeur dessus, par exemple :

0012FCD8   40010006  |ExceptionCode = 40010006
0012FCDC   00000000  |ExceptionFlags = EXCEPTION_CONTINUABLE
0012FCE0   00000002  |nArguments = 2
0012FCE4   0012FCF4  \pArguments = 0012FCF4

0012FCF4  06 00 00 00 70 03 40 00                           ...p @.

String en 0x400370
00400370  42 6F 75 68 0A 00 00 00                          Bouh....

Après on se retrouve dans 2 cas, soit il n’y pas de debugger attaché au programme et l’exception est géré par le SEH du thread, soit il y a un debugger, style OllyDbg , qui catch les exceptions et décide de leurs sorts. Commençons avec le cas « normal », lorsqu’un thread génère une exception le SEH (Structured Exception Handler) va voir ce qu’il peut faire pour éviter d’appeler le méchant UnhandledExceptionFilter qui vous dit que votre programme a foiré comme une daube car l’exception n’a pu être gérée.

En toute logique, après l’appel à NtRaiseException par RtlRaiseException, on se retrouve dans le dernier SEH installé dans la stack du thread et pointé par fs:[0].

kd> kv
ChildEBP RetAddr  Args to Child
0012f8a4 7c9137bf 0012f990 0012ff24 0012f9ac kernel32!_except_handler3 (FPO: [Uses EBP] [3,0,7])
0012f8c8 7c91378b 0012f990 0012ff24 0012f9ac ntdll!ExecuteHandler2+0x26
0012f978 7c91eafa 00000000 0012f9ac 0012f990 ntdll!ExecuteHandler+0x24
0012f978 7c812a5b 00000000 0012f9ac 0012f990 ntdll!KiUserExceptionDispatcher+0xe (FPO: [2,0,0]) (CONTEXT @ 0012f9ac)
0012fccc 7c859dcc 40010006 00000000 00000002 kernel32!RaiseException+0x53 (FPO: [Non-Fpo])
0012ff34 004002a2 00400270 00370031 002d0031 kernel32!OutputDebugStringA+0x54 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ff4c 0040038c 00000001 00323778 00322978 OutputDbgStr+0x2a2
0012ffc0 7c816fd7 00370031 002d0031 7ffdf000 OutputDbgStr+0x38c
0012fff0 00000000 004002a9 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])

Remarqué que le dernier SEH a été installé par l’api OutputDebugString elle même avec le « call __SEH_prolog ». On se retrouve donc, dans le SEH, _except_handler3 de kernel32. C’est là qu’il faut savoir lire de la doc, j’ai relu le fameux « A Crash Course on the Depths of Win32 Structured Exception Handling » de Matt Pietrek pour comprendre plusieurs choses. Normalement lorsqu’on décide d’installer un SEH sur la stack on réalise l’opération suivante :

PUSH ADDR HANDLER
FS PUSH [0] ;address of next ERR structure
FS MOV [0],ESP ;give FS:[0] the ERR address just made 

_EXCEPTION_REGISTRATION struc
     prev    dd      ?
     handler dd      ?
 _EXCEPTION_REGISTRATION ends

On push sur la stack une structure EXCEPTION_REGISTRATION qui contient un pointeur sur le SEH précédant puis un pointeur sur la routine qui devra gérer l’exception. C’est le cas le plus courant.

Dans le cas ou le binaire (ici Kernel32) se retrouve compiler avec un outil comme Visual C++, la structure EXCEPTION_REGISTRATION change un peu. On tombe sur une struct plus étendue :

struct _EXCEPTION_REGISTRATION{
     struct _EXCEPTION_REGISTRATION *prev
     void (*handler)(PEXCEPTION_RECORD,
                     PEXCEPTION_REGISTRATION,
                     PCONTEXT,
                     PEXCEPTION_RECORD)
     struct scopetable_entry *scopetable
     int trylevel
     int _ebp
     PEXCEPTION_POINTERS xpointers
}

Il suffit donc de regardé le SEH placé, juste avant l’appel à RaiseException pour voir le contenu de la structure EXCEPTION_REGISTRATION

0012FF24   0012FFB0  Pointer to next SEH record
0012FF28   7C839AA8  SE handler
0012FF2C   7C85A0A0  kernel32.7C85A0A0
0012FF30   00000000

kd> ln 7C839AA8
(7c839aa8)   kernel32!_except_handler3 

kd> dd  7c85a0a0 l 3
7c85a0a0  ffffffff 7c859dd6 7c859ddf

Le champ de plus intéressant est le pointeur sur la structure SCOPETABLE.

typedef struct _SCOPETABLE
{
 DWORD       previousTryLevel;
 DWORD       lpfnFilter
 DWORD       lpfnHandler
} SCOPETABLE, *PSCOPETABLE;

Dans mon cas, j’ai commme lpfnFilter la fonction suivante :

lpfnFilter :
7C859DD6  XOR EAX,EAX
7C859DD8  INC EAX
7C859DD9  RET

lpfnFilter est juste une simple fonction qui renvoie true. Le lpfnHandler est quand à lui beaucoup plus intéressant. En fait la fonction except_handler3 va appeler lpfnFilter et regarder son résultat. Si jamais celui-ci vaut 1 (« #define EXCEPTION_EXECUTE_HANDLER 1″ dans except.h) alors elle execute le handler pointé par lpfnHandler de la structure SCOPETABLE.

Et qu’est qu’on trouve dans le jolie lpfnHandler ? Un beau disass !

lpfnHandler :
7C859DDF  MOV ESP,DWORD PTR SS:[EBP-18]
7C859DE2  XOR EDI,EDI
7C859DE4  MOV DWORD PTR SS:[EBP-234],EDI
7C859DEA  MOV DWORD PTR SS:[EBP-224],EDI
7C859DF0  MOV DWORD PTR SS:[EBP-22C],EDI
7C859DF6  MOV DWORD PTR SS:[EBP-220],EDI
7C859DFC  MOV EAX,DWORD PTR DS:[7C884040]
7C859E01  CMP EAX,EDI
7C859E03  JNZ SHORT kernel32.7C859E27
7C859E05  CMP BYTE PTR DS:[7C8863F8],0
7C859E0C  JNZ SHORT kernel32.7C859E23
7C859E0E  CALL kernel32.7C859B7C ; CreateDBWinMutex
7C859E13  MOV DWORD PTR DS:[7C884040],EAX
7C859E18  CMP EAX,EDI
7C859E1A  JNZ SHORT kernel32.7C859E27
7C859E1C  MOV BYTE PTR DS:[7C8863F8],1
7C859E23  CMP EAX,EDI
7C859E25  JE SHORT kernel32.7C859E9B
7C859E27  PUSH -1                                  ; /Timeout = INFINITE
7C859E29  PUSH EAX                                 ; |hObject
7C859E2A  CALL kernel32.WaitForSingleObject        ; \WaitForSingleObject
7C859E2F  PUSH kernel32.7C85A090                   ; /MappingName = "DBWIN_BUFFER"
7C859E34  PUSH EDI                                 ; |InheritHandle
7C859E35  PUSH 2                                   ; |Access = FILE_MAP_WRITE
7C859E37  CALL kernel32.OpenFileMappingA           ; \OpenFileMappingA
7C859E3C  MOV DWORD PTR SS:[EBP-234],EAX
7C859E42  CMP EAX,EDI
7C859E44  JE SHORT kernel32.7C859E88
7C859E46  PUSH EDI                                 ; /MapSize
7C859E47  PUSH EDI                                 ; |OffsetLow
7C859E48  PUSH EDI                                 ; |OffsetHigh
7C859E49  PUSH 6                                   ; |AccessMode = 6 ; FILE_MAP_WRITE|FILE_MAP_READ
7C859E4B  PUSH EAX                                 ; |hMapObject
7C859E4C  CALL kernel32.MapViewOfFile              ; \MapViewOfFile
7C859E51  MOV DWORD PTR SS:[EBP-224],EAX
7C859E57  CMP EAX,EDI
7C859E59  JE SHORT kernel32.7C859E88
7C859E5B  PUSH kernel32.7C85A07C                   ; /EventName = "DBWIN_BUFFER_READY"
7C859E60  PUSH EDI                                 ; |Inheritable
7C859E61  PUSH 100000                              ; |Access = 100000
7C859E66  CALL kernel32.OpenEventA                 ; \OpenEventA
7C859E6B  MOV DWORD PTR SS:[EBP-22C],EAX
7C859E71  CMP EAX,EDI
7C859E73  JE SHORT kernel32.7C859E88
7C859E75  PUSH kernel32.7C85A068                   ; /EventName = "DBWIN_DATA_READY"
7C859E7A  PUSH EDI                                 ; |Inheritable
7C859E7B  PUSH 2                                   ; |Access = 2
7C859E7D  CALL kernel32.OpenEventA                 ; \OpenEventA
7C859E82  MOV DWORD PTR SS:[EBP-220],EAX
7C859E88  CMP DWORD PTR SS:[EBP-220],EDI
7C859E8E  JNZ SHORT kernel32.7C859E9B
7C859E90  PUSH DWORD PTR DS:[7C884040]             ; /hMutex = NULL
7C859E96  CALL kernel32.ReleaseMutex               ; \ReleaseMutex
7C859E9B  MOV DWORD PTR SS:[EBP-4],1
7C859EA2  MOV EAX,DWORD PTR SS:[EBP-23C]
7C859EA8  MOV DWORD PTR SS:[EBP-228],EAX
7C859EAE  LEA ECX,DWORD PTR DS:[EAX+1]
7C859EB1  MOV DL,BYTE PTR DS:[EAX]
7C859EB3  INC EAX
7C859EB4  TEST DL,DL
7C859EB6  JNZ SHORT kernel32.7C859EB1
7C859EB8  SUB EAX,ECX
7C859EBA  MOV DWORD PTR SS:[EBP-230],EAX
7C859EC0  MOV ESI,DWORD PTR SS:[EBP-230]
7C859EC6  CMP ESI,EDI
7C859EC8  JBE kernel32.7C859F94
7C859ECE  CMP DWORD PTR SS:[EBP-220],EDI
7C859ED4  JE SHORT kernel32.7C859F39
7C859ED6  PUSH 2710                                ; /Timeout = 10000. ms
7C859EDB  PUSH DWORD PTR SS:[EBP-22C]              ; |hObject
7C859EE1  CALL kernel32.WaitForSingleObject        ; \WaitForSingleObject
7C859EE6  TEST EAX,EAX
7C859EE8  JNZ SHORT kernel32.7C859F39

7C859EEA  CALL kernel32.GetCurrentProcessId        ; [GetCurrentProcessId
7C859EEF  MOV EDX,DWORD PTR SS:[EBP-224]
7C859EF5  MOV DWORD PTR DS:[EDX],EAX ; copie le PID dans le buff
7C859EF7  MOV EAX,0FFB
7C859EFC  CMP ESI,EAX
7C859EFE  JNB SHORT kernel32.7C859F02
7C859F00  MOV EAX,ESI
7C859F02  MOV EBX,EAX
7C859F04  MOV DWORD PTR SS:[EBP-238],EBX
7C859F0A  MOV ECX,EBX
7C859F0C  MOV ESI,DWORD PTR SS:[EBP-228]
7C859F12  LEA EDI,DWORD PTR DS:[EDX+4]
7C859F15  SHR ECX,2
7C859F18  REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> ; copie la string dans la mapped section
7C859F1A  MOV ECX,EAX
7C859F1C  AND ECX,3
7C859F1F  REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>

7C859F21  MOV EAX,DWORD PTR SS:[EBP-224]
7C859F27  MOV BYTE PTR DS:[EBX+EAX+4],0
7C859F2C  PUSH DWORD PTR SS:[EBP-220]              ; /hEvent
7C859F32  CALL kernel32.SetEvent                   ; \SetEvent
7C859F37  JMP SHORT kernel32.7C859F81
7C859F39  MOV EAX,1FF
7C859F3E  CMP ESI,EAX
7C859F40  JNB SHORT kernel32.7C859F44
7C859F42  MOV EAX,ESI
7C859F44  MOV EBX,EAX
7C859F46  MOV DWORD PTR SS:[EBP-238],EBX
7C859F4C  MOV ECX,EBX
7C859F4E  MOV ESI,DWORD PTR SS:[EBP-228]
7C859F54  LEA EDI,DWORD PTR SS:[EBP-21C]
7C859F5A  SHR ECX,2
7C859F5D  REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
7C859F5F  MOV ECX,EAX
7C859F61  AND ECX,3
7C859F64  REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
7C859F66  MOV BYTE PTR SS:[EBP+EBX-21C],0
7C859F6E  LEA EAX,DWORD PTR SS:[EBP-21C]
7C859F74  PUSH EAX
7C859F75  PUSH kernel32.7C85A064                   ;  ASCII "%s"
7C859F7A  CALL 
7C859F7F  POP ECX
7C859F80  POP ECX
7C859F81  ADD DWORD PTR SS:[EBP-228],EBX
7C859F87  SUB DWORD PTR SS:[EBP-230],EBX
7C859F8D  XOR EDI,EDI
7C859F8F  JMP kernel32.7C859EC0
7C859F94  OR DWORD PTR SS:[EBP-4],FFFFFFFF
7C859F98  JMP SHORT kernel32.7C859FCC

Hooooo c’est beau. Ce disass correspond au vrai fonctionnement de OutputDebugString. La fonction commence d’abord par ouvrir un handle sur la section nommée DWIN_BUFFER avec l’API OpenFileMapping. La section étant globale on la retrouve dans l’ObjectDirectory \BaseNamedObjects. Remarquez que OutputDebugString ne crée pas la section DWIN_BUFFER, cette section doit être crée par l’utilisateur himself, ce n’est en aucun cas un binaire de Windows qui la crée (comme je le pensais au départ …). Ce qui veut dire que l’appel à OpenFileMapping peut très bien foirer mais en étant attentif on voit que la fonction va utiliser OpenEvent pour obtenir un handle sur l’event DBWIN_BUFFER_READY et attendre 10 sec dessus avec WaitForSingleObject. Si jamais durant ces 10sec l’event n’est pas signaled par un SetEvent alors l’api ne copiera pas les données dans la mapped section.

En gros, c’est au programme qui a crée la section de signalé toutes les 10 sec au moins que la section est diponible et que OutputDebugString peut y écrire dedans. Justement, après avoir écrit dans la section (si cela est possible), l’api va le signaler avec l’event DBWIN_DATA_READY pour dire aux autres process qui seraient en train de lire la section que des données sont dispo.

Je vous invite à regarder la pseudo implémentation de OutputDebugString.

Bon sachant tout cela, revenons à notre OllyDbg. Ce qui est se passe est très simple, Olly va handler l’exception 0×40010006 de son coté et affiché le param de OutputDebugString pour lui tout seul. Ce qui fait que le lpfnHandler de la SCOPETABLE ne sera pas exécuté, c’est pourquoi on ne retrouve pas notre message sous DebugView.
L’idée est donc très simple pour détecter Olly, dans notre programme on lance un thread qui va crée une section nommé DWIN_BUFFER puis on appel OutputDebugString en parallèle. On dit au thread d’attendre l’event DBWIN_DATA_READY pendant un temps assez court. Après plusieurs cas possible, soit le wait se termine et aucun message n’a été écrit, alors un debugger à empêcher OutputDebugString de fonctionner normalement. Soit l’event est signaled, dans ce cas sachant que le message est précédé du PID du process qui a écrit dans la section DWIN_BUFFER, on compare ce PID à celui de notre process et on vérif si il correspond. Si oui, alors c’est bon, il n’y pas de debugger, sinon on pose la bombe (HEQDSH0T !).

On se retrouve avec le bout de code suivant pour notre thread :

//Wait for the DBWIN_BUFFER_READY event to be signaled: this says that the memory buffer is no longer in use.
//Most of the time, this event will be signaled immediately when it's examined, but it won't wait longer
//than 10 seconds for the buffer to become ready (a timeout abandons the request).
hEventBuff=CreateEvent(NULL, false, false, "DBWIN_BUFFER_READY");
if(hEventBuff==INVALID_HANDLE_VALUE)
{
	printf("Error with CreateEvent : %d\n", GetLastError());
	return 0;
}

//Event servant a OutputDebugString pour dire qu'un message a ete inscrit dans le buffer
hEventData=CreateEvent(NULL, false, false, "DBWIN_DATA_READY");
if(hEventData==INVALID_HANDLE_VALUE)
{
	printf("Error with CreateEvent : %d\n", GetLastError());
	return 0;
}

//cree la section pourtant le nom DBWIN_BUFFER, on la retrouve dans l'ObjectDirectory \BaseNamedObjects
hMap=CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, "DBWIN_BUFFER");
if(hMap==NULL)
{
	printf("Error with OpenFileMapping : %d\n", GetLastError());
	CloseHandle(hEventData);
	CloseHandle(hEventBuff);
	return 0;
}

pDbgBuff=MapViewOfFile(hMap,
            		FILE_MAP_READ,
            		0,
            		0,
           		 	512);
if(pDbgBuff==NULL)
{
	printf("Error with MapViewOfFile : %d\n", GetLastError());
	CloseHandle(hMap);
	CloseHandle(hEventData);
	CloseHandle(hEventBuff);
	return 0;
}             		 	

SetEvent(hEventBuff);

//2 sec de wait
WaitForSingleObject(hEventData, 2000);

printf("PID : %d -> %s\n", *(PDWORD)pDbgBuff, (PCHAR)pDbgBuff+4);

//dans le msg on a une struct du type
//DWORD PID;
//CHAR Msg;

//on check le PID, si il est diff de notre process alors c'est qu'il y a interception du msg par un debugger
if((*(PDWORD)pDbgBuff)!=(DWORD)GetCurrentProcessId())
	printf("FU ! OllyDbg detected, GetCurrentProcessId() : %d\n", GetCurrentProcessId()); 

SetEvent(hEventBuff);

CloseHandle(hEventData);
CloseHandle(hEventBuff);

Et voilà, un joli anti-olly tout mimi.

Juste un mot sur la suite de OutputDebugString. Celle-vi va appeler DbgPrint qui ensuite va faire les appels suivants :
DbgPrint -> vDbgPrintExWithPrefix -> DebugPrint -> DebugService -> int 2Dh (KiDebugService)
On se retrouve dans le kernel sur la fonction KdpPrint

f744e8f0 8067e398 ffffffff 00000000 0012faa8 nt!KdpPrint (FPO: [Non-Fpo])
f744e928 80506452 f744ed64 00000000 f744ed10 nt!KdpTrap+0xd9 (FPO: [Non-Fpo])
f744ecf4 804df235 f744ed10 00000000 f744ed64 nt!KiDispatchException+0x1bb (FPO: [Non-Fpo])
f744ed5c 804df947 0012fa04 7c94058f badb0d00 nt!CommonDispatchException+0x4d (FPO: [0,20,0])
f744ed5c 7c940590 0012fa04 7c94058f badb0d00 nt!KiTrap03+0xad (FPO: [0,0] TrapFrame @ f744ed64)
0012fa04 7c94056b 00000001 0012faa8 00000005 ntdll!DebugService+0x1c (FPO: [Non-Fpo])
0012fa20 7c940528 0012faa0 ffffffff 00000000 ntdll!DebugPrint+0x1c (FPO: [Non-Fpo])
0012fcc4 7c94040a 7c94040c ffffffff 00000000 ntdll!vDbgPrintExWithPrefix+0x1af (FPO: [Non-Fpo])
0012fce0 7c859f7f 7c85a064 0012fd24 00370031 ntdll!DbgPrint+0x1a (FPO: [Non-Fpo])
*** ERROR: Module load completed but symbols could not be loaded for OutputDbgStr.exe
0012ff40 0040029e 00400270 0012ffc0 00400383 kernel32!OutputDebugStringA+0x1fd (FPO: [Non-Fpo])

KdpPrint va se charger d’envoyer le message au kernel debugger (si il est présent) attaché au système.

Hop le binaire+code :
http://ivanlef0u.fr/repo/Output.rar

Sinon, c’est encore chaud, les papers de la PacSec sont là :
http://dragos.com/PacSec2007/

ref :
Understanding Win32 « OutputDebugString »
http://ocliteracy.com/techtips/outputdebugstring.html

NT Debug message support
http://alter.org.ua/en/docs/nt_kernel/kdprint/

A DBWin32 Debugger for Windows
http://www.ddj.com/cpp/184403245

11 comments décembre 3rd, 2007


Calendar

décembre 2007
L Ma Me J V S D
« nov   jan »
 12
3456789
10111213141516
17181920212223
24252627282930
31  

Posts by Month

Posts by Category