# Antidebugging using the DebugPort

2008-08-01 tag_coding ^

When a debugger starts a process to be debugged or, the article case, connects to a already created process, the communication between these processes is made through an internal resource inside Windows called LPC (Local Procedure Call). The system creates a "magic" communication port for debugging and the debugging events pass throw it.

Among these events we can tell the most frequent:

  • Activated breakpoints
  • Thrown exceptions
  • Threads creation/termination
  • DLLs load/unload
  • Process exit

In the case of connecting into a existent process, the API DebugActiveProcess is called. Since this call, if successful, the caller program is free now to call the API DebugActiveProcess, looking for debugging events. The main loop for a debugger is, so, pretty simple:

void DebugLoop()
{
	bool exitLoop = false;
	while( ! exitLoop )
	{
		DEBUG_EVENT debugEvt;
		// Wait for some debug event.
		WaitForDebugEvent(&debugEvt, INFINITE);
		// Let us see what it is about.
		switch( debugEvt.dwDebugEventCode )
		{
			// This one...
			// That one...
			// Process is going out. We get out the loop and go away.
			case EXIT_PROCESS_DEBUG_EVENT:
			exitLoop = true;
			break;
		}
		// We need to unfreeze the thread who sent the debug event.
		// Otherwise, it stays frozen forever!
		ContinueDebugEvent(debugEvt.dwProcessId, debugEvt.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
	}
} 

The interesting detail about this communication process is that a program can be debugged actively only for ONE debugger. In other words, while there's a process A debugging process B, no one besides A can debug and break B.Using this principle, we can imagine a debugging protection based on this exclusivity, creating a protector process that connects to the protected process and "debugs" it:

/** @brief Antidebug protection based on DebugPort aquisition.
* @author Wanderley Caloni (wanderley@caloni.com.br)
* @date 2007-08
*/
#include <windows.h>
/* Every debugger needs a debugging loop. In this loop it catches
debugging events sent by the operating system.
*/
DWORD DebugLoop()
{
	DWORD ret = ERROR_SUCCESS;
	bool exitLoop = false;
	while( ! exitLoop )
	{
		DEBUG_EVENT debugEvt;
		WaitForDebugEvent(&debugEvt, INFINITE);
		switch( debugEvt.dwDebugEventCode )
		{
			// Process going out. We get out the loop and leave.
			case EXIT_PROCESS_DEBUG_EVENT:
			exitLoop = true;
			break;
		}
		// Necessary, since the current thread is frozen.
		ContinueDebugEvent(debugEvt.dwProcessId, debugEvt.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
	}
	return ret;
}
/* Attachs to the protected process againt debugging. Actually, we protect it
againt debugging being its debugger.
*/
DWORD AntiAttach(DWORD pid)
{
	DWORD ret = ERROR_SUCCESS;
	if( pid )
	{
		BOOL dbgActProc;
		dbgActProc = DebugActiveProcess(pid);
		if( dbgActProc )
			DebugLoop();
		else
			ret = GetLastError();
	}
	else
		ret = ERROR_INVALID_HANDLE;
	return ret;
}
/* In the beginning, God said: 'int main!'
*/
int main(int argc, char* argv[])
{
	DWORD ret = ERROR_SUCCESS;
	if( argc > 1 )
	{
		DWORD pid = atoi(argv[1]);
		ret = AntiAttach(pid);
	}
	return (int) ret;
} 

The needed steps to test the code above are:

1. Compile the code

2. Run notepad (or another victim)

3. Get its PID (Process ID)

4. Run the protector process passing the notepad PID as the argument

5. Try to attach to the notepad using a debugger (e.g. Visual C++)

After the attach process, the debug port is occupied, and the communication between the debugger and debuggee is made throug LPC. Bellow we can see a little illustration of how things work:

Basically the process stay receiving debugging events (through the LPC message queue) until the final event, the process exit. Notice that if someone try to terminate the protector process the debuggee process will be terminated, too.

Flawless? OK...

The strength in this protection is that it doesn't affect the code understanding and readability. In fact the code that protects is in another process. The weakness, I would say, it is your visibility. Everyone that will try to attack the solution will se two processes being created, what gives him/her something to think about...

That's why thinking about the implementation is vital. Particularly the main point to be thought is the debugger/debuggee union. As much as better these two pieces were packed, harder to the attacker will be to separate them. An additional idea is to use the same technique in the opposite way, in other words, the debuggee process to attach into the debugger.

This time I'm not going to say that there's a easy solution. Maybe because I haven't though enough about the problem. Ideas?


# Antidebugging during the process attach

2008-08-05 tag_coding ^

Today was a great day for reverse engineering and protection analysis. I've found two great programs to to these things: a API call monitor (update: does not exist anymore) and a COM call monitor (update: either). Besides that, in the first program site - from a enthusiastic of the good for all Win32 Assembly - I've found the source code for one more antidebugging technique, what bring us back to our series of antidebugging techniques.

The antiattaching technique

The purpose of this protection is to detect if some debugger tries to attach into our running process. The attach to process operation is pretty common in all known debugger, as WinDbg and Visual Studio. Different from the DebugPort protection, this solution avoids the attach action from the debuggee program. In this case the protection can make choices about what to do on the event of attach (terminate the process, send an e-mail, etc).

The code I've found does nothing more than to make use of the attach process function that's always called: the ntdll!DbgUiRemoteBreakin. Being always called, we can just to put our code there, what is relatively easy to do:

#include <windows.h>
#include <iostream>
#include <assert.h>
using namespace std;
/** This function is triggered when a debugger try to attach into our process.
*/
void AntiAttachAbort()
{
	// this is a test application, remember?
	MessageBox(NULL, "Espertinho, hein?", "AntiAttachDetector", MB_OK | MB_ICONERROR);
	// this is the end
	TerminateProcess(GetCurrentProcess(), -1);
}
/** This function installs  a trigger that is activated when a debugger try to attach.
@see AntiAttachAbort.
*/
void InstallAntiAttach()
{
	PVOID attachBreak = GetProcAddress(
		GetModuleHandle("ntdll"), // this dll is ALWAYS loaded
		"DbgUiRemoteBreakin"); // this function is ALWAYS called on the attach event
	assert(attachBreak); // attachBreak NEVER can be null
	// opcodes to run a jump to the function AntiAttachAbort
	BYTE jmpToAntiAttachAbort[] =
	{ 0xB8, 0xCC, 0xCC, 0xCC, 0xCC,   // mov eax, 0xCCCCCCCC
	0xFF, 0xE0 };                     // jmp eax
	// we change 0xCCCCCCCC using a more useful address
	*reinterpret_cast<PVOID*>(&jmpToAntiAttachAbort[1]) = AntiAttachAbort;
	DWORD oldProtect = 0;
	if( VirtualProtect(attachBreak, sizeof(jmpToAntiAttachAbort), 
		PAGE_EXECUTE_READWRITE, &oldProtect) )
	{
		// if we can change the code page protection we put a jump to our code
		CopyMemory(attachBreak, 
			jmpToAntiAttachAbort, sizeof(jmpToAntiAttachAbort));
		// restore old protection
		VirtualProtect(attachBreak, sizeof(jmpToAntiAttachAbort), 
			oldProtect, &oldProtect);
	}
}
/** In the beginning, God said: 'int main!'
*/
int main()
{
	InstallAntiAttach();
	cout << "Try to attach, if you can...";
	cin.get();
} 

To compile the code above, just call the compiler and linker normally. Obs.: We need the user32.lib in order to call MessageBox API:

cl /c antiattach.cpp

link antiattach.obj user32.lib

antiattach.exe

Try to attach, if you can...

After the program has been running, every try to attach will show a detection message and program termination.

windbg -pn antiattach.exe

Code peculiarities

Yes, I know. Sometimes we have to use "brute force codding" and make obscure codes, like this:

// opcodes to run a jump to the function AntiAttachAbort
BYTE jmpToAntiAttachAbort[] =
{ 0xB8, 0xCC, 0xCC, 0xCC, 0xCC,   // mov eax, 0xCCCCCCCC
0xFF, 0xE0 };                     // jmp eax
// we change 0xCCCCCCCC using a more useful address
*reinterpret_cast<PVOID*>(&jmpToAntiAttachAbort[1]) = AntiAttachAbort; 

There are a lot of ways to do the same thing. The example above is what is normally called in the crackers community as a shellcode, what is a pretty name for "byte array that is really the assembly code that does interesting things". Shellcode for short =).

Alternative ways to do this are:

1. To declare a naked function in Visual Studio, to create an empty function just after, do some math to calculate the size of the function to be copied into another place (aware of Edit and Continue option).

2. To create a structure whose members are masked opcodes. This way, is possible in the constructor to receive the values and use it as a "mobile function".

Both have pros and cons. The cons are related with the environment dependency. In the first alternative is necessary to configure the project to disable "Edit and Continue" option, whilst in the second one is necessary to align 1 byte the structure.

Anyway, given the implementation, the main advantage is to isolate the code in only two functions - AntiAttachAbort and InstallAntiAttach - an API local hook (in the same process) that should never be called in production code. Besides, there are C++ ways to do such thing like "live assembly". But this is matter for other future and exciting articles.


# Aprendizado em kernel mode

2008-08-07 ^

Hoje terminei minha primeira leitura de Memory Dump Analysis Vol. 1, e qual não foi a minha surpresa ao encontrar entre os últimos posts justamente o que eu estava precisando: um guia de livros que se deve ler para começar a programar em kernel mode.

O melhor de tudo nem é a lista de livros, cujos títulos já estão batidos na minha cabeça de tanto meu amigo Ferdinando comentar a respeito. A grande sacada foi ele ter feito um roteiro no estilo "leia esse livro primeiro, depois comece com esse e ao mesmo tempo acompanhe aquele, sempre atento ao Windows Internals". As coisas não ficam mais fáceis (ler 8 livros, todos com média de 700 páginas), mas pelo menos ficam mais organizadas, tem começo, meio e fim (será?).

Claro, esse é o método Dmitry Vostokov, o que não quer dizer que funciona com qualquer um. No entanto, gosto de suas buscas de padrão, analogias de dumps com o mundo real, abstrações filosóficas e, principalmente, as explicações das telas azuis em UML. Se entendo facilmente essa forma de explicar, é possível que esse método facilite um poucos as coisas não-tão-fáceis de fazer para mim.

Agora só falta começar =).


# Guia para iniciantes no DriverEntry

2008-08-11 tag_coding ^

A mensagem anterior deixou bem claro que tenho um roteiro de leituras bem hardcore a fazer nos próximos 20 anos. Pretendo, enquanto isso, programar alguma coisinha rodando em ring0, porque nem só de teoria vive o programador-escovador-de-bits. Pensando nisso, esse fim-de-semana comecei a me aventurar nos ótimos exemplos e explicações do www.driverentry.com.br, nossa referência kernel mode tupiniquim.

A exemplo do que Dmitry fez com os livros de drivers, acredito que a mesma coisa pode ser feita com os blogues. A maneira de esmiuçá-los vai depender, principalmente, da quantidade de material a ser estudado e das práticas necessárias para que o conhecimento entre na cabeça de uma vez por todas.

No momento, minha prática se resume a isso:

  • Debug or not debug. Aqui resolvi dar uma olhada de perto nas macros e funções usadas para tracing no DDK, e descobri que, assim como a runtime do C, podemos ter mensagens formatadas no estilo do printf e vprintf, o que economiza uma porção de código repetitivo. Dessa forma pude usar minha estratégia de ter a macro LOG usada para mandar linhas de depuração na saída padrão. Ainda tenho que estudar, contudo, o uso da variável va_list em kernel.
  • ExAllocatePool (WithoutTag). Precisei fazer alguns testes no Dependency Walker e anexar o fonte que faz a vez do GetProcAddress para drivers em meu miniprojeto do Bazaar para aprendizado de programação em kernel.
  • Getting Started. Esse foi o artigo mais interessante de todos, pois foi a base de todo o código que ando repetindo em meus exercícios. Além desse, é vital o uso do Visual Studio no processo de desenvolvimento, pois muitas (quase todas) das funções do DDK são alienígenas para mim, assim como os seus 497 parâmetros cada.
  • Driver plus plus. Tive que perder algum tempo codificando uma segunda versão do Useless e baixando o framework da Hollis para testar as peculiaridades do C++ em kernel mode. Não que eu vá usar alguma coisa avançada nesse estágio, mas preciso conhecer algumas limitações e alguns macetes que farão uma grande diferença no futuro, quando as linhas de código ultrapassarem 10.000.
  • Pulei alguns tópicos que pretendo explorar quando estiver mais à vontade com alguns conceitos básicos, como a explicação de como obter o processo dono de uma IRP, a explicação do que é uma IRP (apesar de eu ter baixado e brincado com o monitor da OSR) e a aparentemente simples explanação sobre como funcionam as listas ligadas do DDK. Tudo isso virá com o tempo, e algumas coisas estarão sempre martelando na cabeça. É só dar tempo ao tempo e codificar.
  • Nós queremos exemplos. Esse foi o artigo que mais me deu trabalho, mas que mais valeu a pena. Codifiquei tudo do zero, olhando aos poucos no código do Fernando para pegar o jeito de usar funções com nomes enormes e auto-explicativas e parâmetros com os nomes a, b, c. Também dediquei um tempinho considerável com a aplicação de user mode, para (re)aprender a depurar dos dois lados da moeda.

Próximos passos?

Pelo que eu vi, no geral, acredito que aos poucos irei voltar para os tópicos que pulei, além de olhar em outros artigos que chamaram minha atenção:

  • Como criar um driver de boot
  • Usando o DSF para interagir com dispositivos USB de mentirinha
  • A continuação emocionante de nosso driver que recebe reads e writes
  • Usar o que existe de bom e melhor para garantir a qualidade de um driver
  • Mais alguns detalhes que começam a fazer sentido em nosso KernelEcho
  • Criando e usando IOCTLs. Essa vai ser ótima!
  • A necessidade inevitável de mexer com o registro do sistema

Tudo isso aliado aos exemplos e à teoria latente do Windows 2000 Device Driver Book (minha primeira leitura) irá dar um upgrade forçado aos meus neurônios. Espero sobreviver para contar o final da história.


# Quando o navegador não quer largar um arquivo

2008-08-13 ^

De vez em quando gosto muito de um vídeo que estou assistindo. Gosto tanto que faço questão de guardar para assistir mais vezes depois. O problema é que o meu Firefox ou, para ser mais técnico, o plugin de vídeo que roda em cima do meu navegador, não permite isso. Ele simplesmente cria um arquivo temporário para exibir o vídeo e logo depois o apaga, utilizando uma técnica muito útil da função CreateFile, que bloqueia o acesso do arquivo temporário e apaga-o logo após o uso:

HANDLE WINAPI CreateFile(

__in LPCTSTR lpFileName,

__in DWORD dwDesiredAccess,

__in DWORD dwShareMode,

__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,

__in DWORD dwCreationDisposition,

__in DWORD dwFlagsAndAttributes,

__in_opt HANDLE hTemplateFile

);

dwShareMode

Value Meaning

0 Disables subsequent open operations on a file or device

0x00000000 to request any type of access to that file or device.

dwFlagsAndAttributes

Value Meaning

FILE_FLAG_DELETE_ON_CLOSE The file is to be deleted immediately after all of its

handles are closed, which includes the specified handle

and any other open or duplicated handles.

Muito bem. Isso quer dizer que é possível abrir um arquivo que mais ninguém pode abrir (nem para copiar para outro arquivo), e ao mesmo tempo garante que quando ele for fechado será apagado. Isso parece uma ótima proteção de cópia não-autorizada para a maioria das pessoas.

Infelizmente, tudo isso roda sob limites muito restritos: um navegador, rodando em user mode, usando APIs bem definidas e facilmente depuráveis.

De volta ao WinDbg

Antes de iniciar a reprodução do vídeo, e conseqüentemente a criação do arquivo temporário, podemos atachar uma instância do nosso depurador do coração e colocar um breakpoint onde interessa:

windbg -pn firefox.exe

Microsoft (R) Windows Debugger Version 6.8.0004.0 X86

Copyright (c) Microsoft Corporation. All rights reserved.

*** wait with pending attach

Symbol search path is: SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols;K:\Docs\Projects

Executable search path is:

ModLoad: 00400000 00b64000 L:\FirefoxPortable\App\firefox\firefox.exe

ModLoad: 7c900000 7c9b4000 C:\WINDOWS\system32\ntdll.dll

ModLoad: 7c800000 7c8ff000 C:\WINDOWS\system32\kernel32.dll

...

ModLoad: 77a00000 77a55000 C:\WINDOWS\System32\cscui.dll

ModLoad: 765d0000 765ed000 C:\WINDOWS\System32\CSCDLL.dll

(b58.ba8): Break instruction exception - code 80000003 (first chance)

eax=7ffdb000 ebx=00000001 ecx=00000002 edx=00000003 esi=00000004 edi=00000005

eip=7c901230 esp=021bffcc ebp=021bfff4 iopl=0 nv up ei pl zr na pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246

ntdll!DbgBreakPoint:

7c901230 cc int 3

0:017> bp kernel32!CreateFileA

0:017> bp kernel32!CreateFileW

0:017> g

Breakpoint 2 hit

eax=00000001 ebx=00000000 ecx=05432c10 edx=0000003e esi=0532ea00 edi=00000000

eip=7c831f31 esp=0317fdc4 ebp=0317fde8 iopl=0 nv up ei pl nz na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202

kernel32!CreateFileW:

7c810760 8bff mov edi,edi

Nesse momento podemos dar uma boa olhada nos parâmetros 4 e 6 da função para ver se trata-se realmente da proteção prevista (na verdade, prevista, nada; esse é um artigo baseado em uma experiência passada; vamos imaginar, contudo, que estamos descobrindo essas coisas como na primeira vez).

0:000> dd esp

0012f30c 300afc06 03f91920 c0000000 00000000

0012f31c 00000000 00000002 14000000 00000000

Como podemos ver, o modo de compartilhamento do arquivo é nenhum. Entre os flags definidos no sexto parâmetro, está o de apagar o arquivo ao fechar o handle, como pude constatar no header do SDK.

Nesse caso, a solução mais óbvia e simples foi deixar esse bit desabilitado, não importando se o modo de compartilhamento está desativado. Tudo que temos que fazer é assistir o vídeo mais uma vez e fechar a aba do navegador. O arquivo será fechado, o compartilhamento aberto, e o arquivo, não apagado.

0:012> bp kernel32!CreateFileW "ed @esp+4*6 poi(@esp+4*6) & 0xfbffffff"

breakpoint 1 redefined

0:012> bp kernel32!CreateFileA "ed @esp+4*6 poi(@esp+4*6) & 0xfbffffff"

breakpoint 0 redefined

0:012> g

E agora posso voltar a armazenar meus vídeos favoritos.


# Duas pequenas dicas para programar no caos

2008-08-15 ^

Ultimamente não tenho acertado muito bem meus cronogramas, com erros que variam de um dia a uma semana. A causa desse problema, pelo que eu tenho conseguido detectar, está em dois problemas que acredito acontecer de maneira muito freqüente em um ambiente de desenvolvimento que ainda está no caos:

  • Mudança constante de prioridade
  • Falta de testes básicos no software antes de mexer

Portanto, aí vão algumas dicas empíricas para lidar com esses detalhezinhos que são "faceizinhos de serem esquecidinhos" (by Rafael).

Simples de dizer, não? No entanto, se o que você está fazendo é tão pequeno quanto duas horas passa a ficar um pouco mais fácil. E isso é possível se você souber fazer direito um cronograma, dividindo suas tarefas em tarefas menores, mais paupáveis e "pausáveis".

Um exemplo real serial o de uma mudança em um projeto que envolva três componentes: uma LIB estática, um componente COM e um driver. No caso de ser necessário parar no meio do projeto, é importante que essas três partes estejam bem separadas em tarefas que alteram o código-fonte um a um, sendo a última tarefa a integração entre todos. É interessante notar que, se for bem estruturado o projeto, é possível fazer testes individuais por componente antes da integração de fato, o que torna as coisa bem menos dolorosas. A divisão seria algo incremental e possivelmente paralelizável:

Você tem certeza que o programa está rodando como deveria, que não existem problemas paralelos e relacionados que podem prejudicar seu desempenho cronogrametal? A última versão funciona realmente como deveria funcionar? Não? Nesse caso, esqueça sua estimativa inicial: ela foi pro espaço. Quer dizer, do ponto de vista otimista, adiada para depois de serem resolvidos os problemas atuais.

Mais uma vez, os testes individuais (chamados de unit tests) são importantes para a consistência do projeto no decorrer de sua vida. Isso aliado a um processo de build automatizado que detecte erros de funcionamento e compilação pode economizar um tempo enorme na hora de fazer uma "pequena modificaçãozinha" naquele fonte escroto.

Em empresas onde a qualidade de software é piada, essas duas atitudes podem salvar algumas vidas e projetos no meio do caminho, apesar de parar no meio das tarefas não ser uma das melhores práticas de um desenvolvimento sério.


# Os processos-fantasma

2008-08-20 tag_coding ^

Estava eu outro belo dia tentando achar um problema em um driver que controla criação de processos quando, por acaso, listo os processos na máquina pelo depurador de kernel, após ter dado alguns logons e logoffs, quando me vem a seguinte lista de processos do Windows Explorer:

PROCESS 815f0da0 SessionId: 0 Cid: 0694 Peb: 7ffd8000 ParentCid: 0100

DirBase: 0d6e9000 ObjectTable: 00000000 HandleCount: 0.

Image: explorer.exe

PROCESS 8164bda0 SessionId: 0 Cid: 03b0 Peb: 7ffdf000 ParentCid: 0100

DirBase: 02673000 ObjectTable: 00000000 HandleCount: 0.

Image: explorer.exe

PROCESS 815f7d50 SessionId: 0 Cid: 020c Peb: 7ffd9000 ParentCid: 0100

DirBase: 0bc7f000 ObjectTable: 00000000 HandleCount: 0.

Image: explorer.exe

PROCESS 8164c698 SessionId: 0 Cid: 0794 Peb: 7ffde000 ParentCid: 0100

DirBase: 0cb08000 ObjectTable: e1a40f20 HandleCount: 279.

Image: explorer.exe

Analisando pelo Gerenciador de Tarefas, podemos detectar que o único processo de pé possui o PID (Process ID) do último elemento de nossa lista, curiosamente o único com um contador de handles diferente de zero.

Lembrando que 1940 em hexadecimal é 0x794, exatamente o valor deixado em destaque na lista acima, e reproduzido abaixo:

PROCESS 8164c698 SessionId: 0 Cid: 0794 Peb: 7ffde000 ParentCid: 0100

DirBase: 0cb08000 ObjectTable: e1a40f20 HandleCount: 279.

Image: explorer.exe

Sendo ele o único processo a rodar, a única explicação válida para as outras instâncias do explorer.exe estarem de pé seria o fato de haver algum outro processo (inclusive o sistema operacional) com um handle aberto para ele. Felizmente isso pode ser facilmente verificado pelo uso do comando !object do WinDbg, no caso abaixo com o primeiro explorer.exe da lista, utilizando-se a sua estrutura EPROCESS (em vermelho na lista acima).

kd> !object 815f0da0

Object: 815f0da0 Type: (817cce70) Process

ObjectHeader: 815f0d88 (old version)

HandleCount: 2 PointerCount: 3

Muito bem. Temos dois handles e dois ponteiros ainda abertos para o objeto processo-fantasma explorer.exe. O fato de haver um handle aberto indica que é muito provável que se trate de um outro processo rodando em user mode, já que normalmente as referências para objetos dentro do kernel são feitas com o uso de ponteiros.

Para descobrirmos quem detém esse handle, existe o comando !handle, que pode exibir informações sobre todos os handles de um determinado tipo no processo atual. Como queremos procurar por todos os handles do tipo Process em todos os processos existentes, é necessário usá-lo em conjunto com o comando mais esperto for_each_process, que pode fazer coisas incríveis para o programador de user/kernel:

kd> !for_each_process "!handle 0 1 @#Process Process"

processor number 0, process 817cc830

Searching for handles of type Process

PROCESS 817cc830 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000

DirBase: 00039000 ObjectTable: e1000cc0 HandleCount: 286.

Image: System

Handle table at e1002000 with 286 Entries in use

0004: Object: 817cc830 GrantedAccess: 001f0fff

0298: Object: 8169a958 GrantedAccess: 001f03ff

0308: Object: 8156c880 GrantedAccess: 00000438

067c: Object: 816744e8 GrantedAccess: 001f03ff

processor number 0, process 81589020

Searching for handles of type Process

PROCESS 81589020 SessionId: none Cid: 016c Peb: 7ffd7000 ParentCid: 0004

DirBase: 06978000 ObjectTable: e130d688 HandleCount: 21.

Image: smss.exe

Handle table at e12a5000 with 21 Entries in use

0038: Object: 81561128 GrantedAccess: 001f0fff

003c: Object: 81561128 GrantedAccess: 00000400

0050: Object: 815b2128 GrantedAccess: 001f0fff

0054: Object: 81668020 GrantedAccess: 00000400

processor number 0, process 81561128

Searching for handles of type Process

PROCESS 81561128 SessionId: 0 Cid: 0234 Peb: 7ffde000 ParentCid: 016c

DirBase: 0742d000 ObjectTable: e13e0418 HandleCount: 342.

Image: csrss.exe

Handle table at e14f3000 with 342 Entries in use

0014: Object: 815b2128 GrantedAccess: 001f0fff

00ec: Object: 8154e880 GrantedAccess: 001f0fff

0100: Object: 8156c880 GrantedAccess: 001f0fff

0130: Object: 815dc798 GrantedAccess: 001f0fff

processor number 0, process 815b2128

Searching for handles of type Process

PROCESS 815b2128 SessionId: 0 Cid: 024c Peb: 7ffde000 ParentCid: 016c

DirBase: 075b2000 ObjectTable: e13d5790 HandleCount: 448.

Image: winlogon.exe

Handle table at e102b000 with 448 Entries in use

018c: Object: 8154e880 GrantedAccess: 001f0fff

019c: Object: 8156c880 GrantedAccess: 001f0fff

processor number 0, process 8154e880

Searching for handles of type Process

PROCESS 8154e880 SessionId: 0 Cid: 0290 Peb: 7ffda000 ParentCid: 024c

DirBase: 07908000 ObjectTable: e15fea78 HandleCount: 261.

Image: services.exe

Handle table at e15cf000 with 261 Entries in use

029c: Object: 81668020 GrantedAccess: 001f0fff

0330: Object: 815dc798 GrantedAccess: 001f0fff

... continua por muuuuuuuito mais tempo

Uma simples busca pelo EPROCESS do processo-fantasma nos retorna dois processos que o estão referenciando: um svchost.exe e um outro processo com um nome muito suspeito, provavelmente feito sob encomenda para a confecção desse artigo:

Handle table at e167b000 with 247 Entries in use

processor number 0, process 8169a958

Searching for handles of type Process

PROCESS 8169a958 SessionId: 0 Cid: 03f4 Peb: 7ffdb000 ParentCid: 0290

DirBase: 08437000 ObjectTable: e156bc38 HandleCount: 1302.

Image: svchost.exe

Handle table at e19fe000 with 1302 Entries in use

0108: Object: 815b2128 GrantedAccess: 00000478

0128: Object: 815b2128 GrantedAccess: 00000478

012c: Object: 815b2128 GrantedAccess: 00100000

015c: Object: 815b2128 GrantedAccess: 0000047a

01f4: Object: 81615928 GrantedAccess: 00000478

02f0: Object: 815f7d50 GrantedAccess: 00100068

035c: Object: 8169a958 GrantedAccess: 001f0fff

0dbc: Object: 8156c880 GrantedAccess: 00100000

0f44: Object: 8169a958 GrantedAccess: 00000068

1020: Object: 815f0da0 GrantedAccess: 00100068

10dc: Object: 8169a958 GrantedAccess: 00100000

1118: Object: 815f42f0 GrantedAccess: 00100068

...

processor number 0, process 8164c220

Searching for handles of type Process

PROCESS 8164c220 SessionId: 0 Cid: 044c Peb: 7ffdf000 ParentCid: 02a4

DirBase: 0db16000 ObjectTable: e15c66b8 HandleCount: 12.

Image: ProcessLeaker.exe

Handle table at e103a000 with 12 Entries in use

0010: Object: 815f0da0 GrantedAccess: 00100000

001c: Object: 815f42f0 GrantedAccess: 00100000

0028: Object: 8164bda0 GrantedAccess: 00100000

002c: Object: 815f7d50 GrantedAccess: 00100000

0030: Object: 8164c698 GrantedAccess: 00100000

Se lembrarmos o ponteiro dos outros processos, podemos notar que ele está bloqueando todas as outras instâncias dos antigos explorer.exe, executados em outras sessões do usuário:

PROCESS 815f0da0 SessionId: 0 Cid: 0694 Peb: 7ffd8000 ParentCid: 0100

DirBase: 0d6e9000 ObjectTable: 00000000 HandleCount: 0.

Image: explorer.exe

PROCESS 8164bda0 SessionId: 0 Cid: 03b0 Peb: 7ffdf000 ParentCid: 0100

DirBase: 02673000 ObjectTable: 00000000 HandleCount: 0.

Image: explorer.exe

PROCESS 815f7d50 SessionId: 0 Cid: 020c Peb: 7ffd9000 ParentCid: 0100

DirBase: 0bc7f000 ObjectTable: 00000000 HandleCount: 0.

Image: explorer.exe

PROCESS 8164c698 SessionId: 0 Cid: 0794 Peb: 7ffde000 ParentCid: 0100

DirBase: 0cb08000 ObjectTable: e1a40f20 HandleCount: 279.

Image: explorer.exe

Esse ProcessLeaker se tratava de um serviço do mesmo produto que contém de fato um leak de recurso: em um dado momento ele abre um handle para o processo explorer.exe, só que por alguns motivos obscuros ele não é fechado nunca, gerando uma lista interminável de processos-fantasma. E é lógico que ele originalmente não chama ProcessLeaker.exe =)

Essa análise mostra duas coisas: que com um pouco de conhecimento e atitude é possível encontrar bugs em outras partes do programa, mesmo quando resolvendo outros problemas e que, nem sempre o problema está onde parece estar, que seria no nosso querido driver de controle de processos do começo da história.


# ProcessLeaker

2008-08-21 tag_coding ^

O artigo anterior mostrava como detectar o leak de um processo gerado pela retenção e não-liberação de handles para o Windows Explorer. O problema fora causado por um serviço malcriado. No entanto, a título de demonstração, criei um pequeno programinha sem-vergonha para fazer as coisas parecerem difíceis. No entanto o programa é bem fácil:

#include <windows.h>
#include <stdio.h>
int main()
{
	DWORD pid;
	while( scanf("%d", &pid) == 1 )
	{
		HANDLE proc = OpenProcess(SYNCHRONIZE, FALSE, pid);
	}
}

Para usá-lo, basta abrir um Gerenciador de Tarefas com opção de exibir o PID dos processos.

A partir daí, é só criar e matar várias instâncias do explorer.exe. Antes de matar um, digite o PID do novo processo no ProcessLeaker.

Para listar os processos perdidos, basta usar o comando "!process 0 0" no WinDbg depurando em kernel. O resto você já sabe.


<< >>