MASTG-TECH-0023: Revisão de Código Java Descompilado
Seguindo o exemplo de Descompilação de Código Java, assumimos que você decompilou e abriu com sucesso o aplicativo Android UnCrackable L1 no jadx. Quando o jadx terminar de decompilar, você pode navegar pelas classes decompiladas selecionando-as na árvore de arquivos. Observe que muitos dos pacotes, classes e métodos decompilados possuem nomes estranhos de uma letra; isso ocorre porque o bytecode foi "minificado" com o ProGuard durante o build. Este é um tipo básico de ofuscação)) que torna o bytecode um pouco mais difícil de ler, mas com um aplicativo simples como este, não causará muitos problemas. No entanto, ao analisar um aplicativo mais complexo, isso pode se tornar bastante incômodo.
Ao analisar código ofuscado, é uma boa prática anotar nomes de classes, métodos e outros identificadores à medida que avança. Abra a classe MainActivity no pacote sg.vantagepoint.uncrackable1. O método verify é chamado quando você toca no botão "verify". Este método passa a entrada do usuário para um método estático chamado a.a, que retorna um valor booleano. Parece plausível que a.a verifique a entrada do usuário, então refatoraremos o código para refletir isso.
Clique com o botão direito do mouse no nome da classe (o primeiro a em a.a) e selecione Rename no menu suspenso (ou pressione N). Altere o nome da classe para algo que faça mais sentido com base no que você sabe sobre a classe até agora. Por exemplo, você pode chamá-la de "Validator" (você sempre pode revisar o nome depois). a.a agora se torna Validator.a. Siga o mesmo procedimento para renomear o método estático a para check_input.
Parabéns, você acabou de aprender os fundamentos da análise estática! Tudo se resume a teorizar, anotar e revisar gradualmente as teorias sobre o programa analisado até entendê-lo completamente ou, pelo menos, o suficiente para o que você deseja alcançar.
Em seguida, pressione Ctrl+clique (ou Command+clique no Mac) no método check_input. Isso leva você à definição do método. O método decompilado se parece com isto:
public static boolean check_input(String str) {
byte[] bArrA;
byte[] bArr = new byte[0];
try {
bArrA = sg.vantagepoint.a.a.a(b("8d127684cbc37c17616d806cf50473cc"), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));
} catch (Exception e) {
Log.d("CodeCheck", "AES error:" + e.getMessage());
bArrA = bArr;
}
return str.equals(new String(bArrA));
}
Então, temos uma String codificada em Base64 que é passada para a função a no pacote sg.vantagepoint.a.a (novamente, tudo é chamado de a) junto com algo que parece suspeitosamente com uma chave de criptografia codificada em hex (16 bytes hex = 128 bits, um comprimento de chave comum). O que exatamente esse a específico faz? Ctrl+clique nele para descobrir.
public class a {
public static byte[] a(byte[] bArr, byte[] bArr2) {
SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(2, secretKeySpec);
return cipher.doFinal(bArr2);
}
}
Agora você está chegando a algum lugar: é simplesmente AES-ECB padrão. Parece que a string Base64 armazenada em arrby1 em check_input é um ciphertext. Ela é descriptografada com AES de 128 bits e depois comparada com a entrada do usuário. Como tarefa bônus, tente descriptografar o ciphertext extraído e encontre o valor secreto!
Uma maneira mais rápida de obter a string descriptografada é adicionar análise dinâmica, conforme explicado em Enganchamento de Métodos.