Skip to content

MASTG-TECH-0076: Revisão de Código Desmontado em Objective-C e Swift

Nesta seção, exploraremos manualmente o código binário de aplicativos iOS e realizaremos análise estática nele. A análise manual pode ser um processo lento e requer muita paciência. Uma boa análise manual pode tornar a análise dinâmica mais bem-sucedida.

Não existem regras rígidas para realizar análise estática, mas há algumas diretrizes que podem ser usadas para ter uma abordagem sistemática na análise manual:

  • Compreender o funcionamento do aplicativo em avaliação — o objetivo do aplicativo e como ele se comporta em caso de entrada incorreta.
  • Explorar as várias strings presentes no binário do aplicativo, o que pode ser muito útil, por exemplo, para identificar funcionalidades interessantes e possíveis lógicas de tratamento de erro no aplicativo.
  • Procurar por funções e classes com nomes relevantes para nosso objetivo.
  • Por último, encontrar os vários pontos de entrada no aplicativo e seguir a partir deles para explorar o aplicativo.

As técnicas discutidas nesta seção são genéricas e aplicáveis independentemente das ferramentas usadas para análise.

Objective-C

Para revisar efetivamente o código nativo desmontado, é importante ter um entendimento básico do Objective-C runtime. Funções como _objc_msgSend e _objc_release são particularmente significativas no Objective-C runtime.

Além do que você aprendeu em Disassembling Native Code, aplicaremos esses conceitos usando iOS UnCrackable Nível 1. O objetivo deste aplicativo é encontrar uma string secreta oculta em seu binário.

O aplicativo apresenta uma tela inicial simples, permitindo interação do usuário ao inserir strings personalizadas no campo de texto fornecido. Nosso objetivo é fazer engenharia reversa do aplicativo para descobrir a string secreta oculta.

Quando o usuário insere a string errada, o aplicativo exibe um pop-up com a mensagem "Verification Failed".

Você pode anotar as strings exibidas no pop-up, pois isso pode ser útil ao buscar o código onde a entrada é processada e uma decisão está sendo tomada. Felizmente, a complexidade e a interação com este aplicativo são diretas, o que é bom para nossos esforços de engenharia reversa.

Para análise estática nesta seção, usaremos o Ghidra 9.0.4. A autoanálise do Ghidra 9.1_beta tem um bug e não mostra as classes Objective-C.

Podemos começar verificando as strings presentes no binário, abrindo-o no Ghidra. A lista de strings pode ser avassaladora no início, mas com alguma experiência em reversão de código Objective-C, você aprenderá a filtrar e descartar as strings que não são realmente úteis ou relevantes. Por exemplo, as mostradas na captura de tela abaixo, que são geradas para o Objective-C runtime. Outras strings podem ser úteis em alguns casos, como aquelas que contêm símbolos (nomes de funções, nomes de classes, etc.), e as usaremos ao realizar análise estática para verificar se alguma função específica está sendo usada.

Se continuarmos nossa análise cuidadosa, podemos identificar a string "Verification Failed", que é usada para o pop-up quando uma entrada incorreta é fornecida. Se você seguir as referências cruzadas (Xrefs) dessa string, chegará à função buttonClick da classe ViewController. Analisaremos a função buttonClick mais adiante nesta seção. Ao verificar outras strings no aplicativo, apenas algumas parecem candidatas prováveis para um flag oculta. Você pode testá-las e verificar também.

Seguindo em frente, temos dois caminhos a tomar. Podemos começar analisando a função buttonClick identificada na etapa acima ou começar analisando o aplicativo a partir dos vários pontos de entrada. Em situações do mundo real, na maioria das vezes você seguirá o primeiro caminho, mas, sob uma perspectiva de aprendizado, nesta seção tomaremos o último caminho.

Um aplicativo iOS chama diferentes funções predefinidas fornecidas pelo runtime do iOS dependendo de seu estado dentro do ciclo de vida do aplicativo. Essas funções são conhecidas como pontos de entrada do aplicativo. Por exemplo:

  • [AppDelegate application:didFinishLaunchingWithOptions:] é chamado quando o aplicativo é iniciado pela primeira vez.
  • [AppDelegate applicationDidBecomeActive:] é chamado quando o aplicativo está passando do estado inativo para o ativo.

Muitos aplicativos executam código crítico nessas seções e, portanto, normalmente são um bom ponto de partida para seguir o código sistematicamente.

Uma vez concluída a análise de todas as funções na classe AppDelegate, podemos concluir que não há código relevante presente. A falta de qualquer código nas funções acima levanta a questão: de onde está sendo chamado o código de inicialização do aplicativo?

Felizmente, o aplicativo atual tem uma base de código pequena, e podemos encontrar outra classe ViewController na visualização Symbol Tree. Nesta classe, a função viewDidLoad parece interessante. Se você verificar a documentação de viewDidLoad, poderá ver que ela também pode ser usada para realizar inicialização adicional em views.

Se verificarmos a descompilação desta função, há algumas coisas interessantes acontecendo. Por exemplo, há uma chamada para uma função nativa na linha 31 e um label é inicializado com um flag setHidden definido como 1 nas linhas 27-29. Você pode anotar essas observações e continuar explorando as outras funções nesta classe. Por brevidade, explorar outras partes da função é deixado como exercício para os leitores.

Em nossa primeira etapa, observamos que o aplicativo verifica a string de entrada apenas quando o botão da interface do usuário é pressionado. Assim, analisar a função buttonClick é um alvo óbvio. Como mencionado anteriormente, esta função também contém a string que vemos nos pop-ups. Na linha 29, uma decisão está sendo tomada, com base no resultado de isEqualString (saída salva em uVar1 na linha 23). A entrada para a comparação vem do campo de entrada de texto (do usuário) e do valor do label. Portanto, podemos assumir que a flag oculta está armazenada nesse label.

Agora seguimos o fluxo completo e temos todas as informações sobre o fluxo do aplicativo. Também concluímos que a flag oculta está presente em um label de texto e, para determinar o valor do label, precisamos revisitar a função viewDidLoad e entender o que está acontecendo na função nativa identificada. A análise da função nativa é discutida em Análise de Código Native Desmontado.