Técnicas AntiCracking – Parte IV – Técnicas Ativas de AntiDebuggin
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__
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+0×30]
-
cmp byte ptr [%eax+0×02], 0
-
je _RunProgram
-
);
-
}
-
-
void RunProgram(){
-
// start you program here
-
}
-
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], 0×100; "
-
"popfd;"
-
"nop;");
-
} catch (Exception e) {
-
trapFlag = false; // uma exceção foi gerada, então não existe debugger
-
}
-
-
if(trapFlag){
-
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();
-
}
-
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.
Você pode ler as eventuais respostas desta entrada
através do RSS 2.0 feed.
As respostas estão encerradas, mas você pode
trackback
a partir do seu próprio site.