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
décembre 13th, 2007
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
décembre 3rd, 2007