Técnicas AntiCracking – Parte IV – Técnicas Ativas de Antidebug
As técnicas mencionadas abaixo são utilizadas para dificultar a análise dinâmica do código, aquela quando usamos um debugger.
No post “A Fórmula do Crack” discutimos alguma coisa sobre debuggers. Como é o processo de debuggin em user-mode e kernel-mode, como cada um deles trabalha quando um usuário anexa um processo à eles, quando fixa um breakpoints em uma instrução etc.
O que não foi discutido é a forma como o debugger atua: quando o usuário escolhe pontos de parada para conferir o andamento da execução do programa é comum o debugger substituir a(s) instrução(ões) por uma instrução própria, que só ele entenda. Esta instrução é conhecida como “interrupção de breakpoint” (breakpoint interrupt) e não é executada pelo programa, mas notifica ao debugger que uma interrupção foi encontrada naquele ponto. Quando a execução chega nesta instrução, o debugger congela a execução do programa e podemos inspecionar seu estado.
Sabendo que estas instruções geram comportamentos específicos – como levantamento de exceções – utilizamo-as como chaves para detecção dos debuggers.
1. hadUserModeDebugger__</p>
hadUserModeDebbuger, também conhecido como UserDebuggerInformation ou isDebuggerPresentAPI é uma API (Aplication Program Interface) usada na detecção de debuggers que trabalham em user-mode, tais como o OllyDbg.
Quando implementada em um aplicativo acessa o “Process Environment Block” (PEB) para determinar se um debugger foi encontrado. A implementação do código é a seguinte:
/*
* da mesma forma que fizemos como exemplo no tópico “Detectando Máquinas Virtuais”,
* antes de termos uma função para inicializar o programa, sempre colocamos a função
* para teste das condicoes (onde testamos licença, presença de vm, de debugger, etc).
*
* no java, seria equivalente ao bloco static{ } da primeira classe a ser carregada.
*/
void prestarter(){
__asm(
mov %eax, %fs: [00000018]
mov %eax, [%eax+0x30]
cmp byte ptr [%eax+0x02], 0
je _RunProgram
);
}
void RunProgram(){
// start you program here
}
</p>
2. The Trick
Esta técnica consiste em utilizar uma “flag” (Trap Flag) para detectar se uma exceção específica é gerada ou não. É uma abordagem semelhante à alguns algoritmos de detecção de máquinas virtuais, como já comentei antes (“The Jerry Code”, http://www.trapkit.de/research/vmm/jerry/index.html , que utiliza instruções que não são da arquitetura física para gera exceções específicas para detecção).
Um valor booleano é criado para determinar se o sistema caiu na armadilha ou não. A armadilha é a seguinte: se a exceção não é gerada, então é porque um debugger retirou a exceção para que o programa possa ser analisado. Ou seja, a única possibilidade da “flag” não mudar de estado é que “algo” impeça a exceção. E este “algo” é o próprio debugger.
A vantagem desta abordagem é que ela pode detectar de forma simples qualquer debugger que esteja monitorando o programa que desejamos proteger, seja em modo usuário ou kernel.
Também é uma forma relativamente segura, pois não depende de uma API externa do sistema operacional e mantém todo o controle dentro da própria aplicação.
Uma implementação da Armadilha é a seguinte:
void prestarter(){
bool trapFlag = true;
try {
__asm__("pushfd; "
"or dword ptr[%esp], 0x100; "
"popfd;"
"nop;");
} catch (Exception e) {
trapFlag = false; // uma exceção foi gerada, então não existe debugger
}
if(trapFlag){
printf(“Debugger esta presente.”);
die(); // programa morre aqui, não executa se tiver debugger
}else{
RunProgram(); // as opções, funcionalidades, etc, são abertas aqui
}
}
void RunProgram(){
// start you program here
}
void main(){
prestarter();
}
</p>
Conclusão
Estas implementações tendem a ser bem eficientes, mas deve-se ficar claro que é restrita à família NT. Embora as AADTs sejam as que oferecem menos perda de performance (quase nula, como observa-se nos códigos acima), são as que oferecem mais riscos, pois qualquer incompatibilidade com o ambiente de execução acarreta em um erro fatal, impedindo execução do programa.
Outra desvantagem das AADTs é que elas são dependentes de arquitetura. O que torna o sistema não-portável.</p>