Técnicas AntiCracking – Parte V – Burlando Disassemblers

Confusing Disassemblers (CD) são técnicas aplicadas em programas voltados à plataformas que utilizam processadores de instruções com tamanho variável. Nestas plataformas – normalmente CISCs, como a IA32 – é possível criar uma armadilha para os disassamblers de forma que eles não consigam interpretar a instrução como um comando ASM. Ou seja, consiste em dessincronizar a instrução real (formada por zeros-e-uns) com a forma abstrata dela (código legível, asm).

Antes de discutirmos sobre como as técnicas são aplicadas é importante entender que a diferença entre elas se dá devido à forma como cada disassembler analisa o código.

1. Conversão Linear (CLin)

Conversão linear é uma abordagem utilizada por alguns disassemblers que simplesmente desassembla a instrução seqüencialmente, sem levar em conta como as instruções variam ao longo do fluxo de execução. Ou seja, o disassembler simplesmente faz a conversão em paridade: a seqüência X equivale à instrução Y, e isso a cada instrução, uma após outra, não importando os desvios que ocorrem pelo código.

Desta forma, quando empregada a conversão linear, o disassembler irá converter instrução por instrução, o que significa que uma seqüência dados no meio do código poderia confundir o disassembler fazendo com que ele interpretasse aquilo como comando.

Os disassemblers que conheço, e mencionei no artigo “A Fórmula de um Crack”, que aplicam a técnica de conversão linear são: Numega SoftIce ( veja: http://www.caloni.com.br/blog/archives/introducao-ao-softice ), Microsoft WinDbg ( http://www.microsoft.com/whdc/DevTools/Debugging/default.mspx ) e o Syster (Baseado no SoftIce ( http://www.sysersoft.com/ ) .

Veja o código:

	__asm(“	Algum código para disfarçar;”
			“jmp Desvio;”
			“_emit 0x0f”
			“Desvio: ”
			“mov %eax, [AlgumaVariavelQualquer] ;”
			“push %eax ;”
			“call FuncaoQualquer”
	);

Este código irá confundir os desassembladores que utilizam CLin, gerando uma outra seqüência totalmente diferente da original, afetando até mesmo o entendimento do código que o cracker está buscando.

Contudo, se utilizássemos um disassembler como o IDA PRO este código acima não traria muitos efeitos pois desviaria para a próxima instrução junto com “Desvio”, sem misturar dados com instruções.

2. Conversão com Ligação Recursiva (CLR)

Enquanto na conversão linear as instruções são transcritas uma-a-uma, sem levar em conta como o programa é executado, na Ligação Recursiva as instruções são analisadas de acordo com suas ligações através do fluxo de execução. Ou seja, quando aparece um comando de desvio para outra instrução, que aponta para certo endereço, o disassembler também converte este endereço, seguindo o fluxo de execução. Assim, Ligação Recursiva acaba sendo mais tolerante às técnicas anti-disassembly.

Os principais disassemblers que implementam a CLR são: o fabuloso OllyDbg e o IDA Pro (a qual cria até fluxogramas para visualizarmos exatamente os desvios no código).

3. Burlando

A função ofuscar abaixo insere uma seqüência de instruções em assembly que pode confundir alguns disassemblers.

	#define paste(a,b) a##b
 
	inline ofuscar(){
		__asm(
			“mov %eax, __LINE__ * 0x66699902;”
			“cmp %eax, __LINE__ * 0x9cb16d48;”
			“je paste(Junk, __LINE__);”
			“mov %eax, paste(After, __LINE__);”
			“jmp %eax;”
			“paste(Junk, __LINE__);”
			“emit(0xd8+__LINE__ % 8); ”
			“paste(After, __LINE__););
	}

Note que é possível criar uma dezena de variações destas função. Se distribuirmos esta função (ou suas combinações) através do código alvo podemos tornar o processo de cracking extremamente cansativo.

Contudo, – como sempre digo – anticracking sempre acarreta uma perda de eficiência no programa-alvo. O uso excessivo desta função ofuscar() poderia causar um aumento considerável no conjunto de instruções. Como conseqüência consumiria mais memória e disco.

Outra medida interessante, mas que comprometeria muito a eficiência, é fazer um programa que gere diferentes combinações da função ofuscar() e as espalhe pelo código. Isso porque utilizar a mesma função gera padrões repetitivos no objeto final, o que tornaria sua proteção extremamente vulnerável para ferramentas que buscam trechos repetitivos.

Particularmente não penso que seja a solução mais interessante. A remoção de informação simbólica é uma boa, combinada com ofuscação e Transformações no Controle do Fluxo acabam sempre sendo as abordagens mais eficientes.