# Antidebugging using the DebugPort
Caloni, 2008-08-01 computer projects english [up] [copy]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:
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.
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
Caloni, 2008-08-05 computer projects english [up] [copy]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 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
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
Caloni, 2008-08-07 [up] [copy]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
Caloni, 2008-08-11 computer [up] [copy]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:
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:
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.
# Duas pequenas dicas para programar no caos
Caloni, 2008-08-15 [up] [copy]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:
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
Caloni, 2008-08-20 computer [up] [copy]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
Caloni, 2008-08-21 computer [up] [copy]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.