Skip to content

MASTG-TECH-0024: Análise de Código Nativo Desmontado

Seguindo o exemplo de "Desmontando Código Nativo", usaremos diferentes desmontadores para revisar o código nativo desmontado.

radare2

Depois de abrir seu arquivo no radare2, você deve primeiro obter o endereço da função que está procurando. Você pode fazer isso listando ou obtendo informações i sobre os símbolos s (is) e filtrando (~ grep interno do radare2) por alguma palavra-chave. No nosso caso, estamos procurando por símbolos relacionados ao JNI, então digitamos "Java":

$ r2 -A HelloWord-JNI/lib/armeabi-v7a/libnative-lib.so
...
[0x00000e3c]> is~Java
003 0x00000e78 0x00000e78 GLOBAL   FUNC   16 Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI

O método pode ser encontrado no endereço 0x00000e78. Para exibir sua desmontagem, basta executar os seguintes comandos:

[0x00000e3c]> e emu.str=true;
[0x00000e3c]> s 0x00000e78
[0x00000e78]> af
[0x00000e78]> pdf
╭ (fcn) sym.Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI 12   sym.Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI (int32_t arg1);           ; arg int32_t arg1 @ r0
│           0x00000e78  ~   0268           ldr r2, [r0]                ; arg1
│           ;-- aav.0x00000e79:
│           ; UNKNOWN XREF from aav.0x00000189 (+0x3)           0x00000e79                    unaligned
│           0x00000e7a      0249           ldr r1, aav.0x00000f3c      ; [0xe84:4]=0xf3c aav.0x00000f3c
│           0x00000e7c      d2f89c22       ldr.w r2, [r2, 0x29c]           0x00000e80      7944           add r1, pc                  ; "Hello from C++" section..rodata
╰           0x00000e82      1047           bx r2

Vamos explicar os comandos anteriores:

  • e emu.str=true; habilita a emulação de string do radare2. Graças a isso, podemos ver a string que estamos procurando ("Hello from C++").
  • s 0x00000e78 é um seek para o endereço s 0x00000e78, onde nossa função alvo está localizada. Fazemos isso para que os comandos seguintes se apliquem a este endereço.
  • pdf significa print disassembly of function (imprimir desmontagem da função).

Usando o radare2, você pode executar comandos rapidamente e sair usando os flags -qc '<comandos>'. Das etapas anteriores já sabemos o que fazer, então vamos simplesmente juntar tudo:

$ r2 -qc 'e emu.str=true; s 0x00000e78; af; pdf' HelloWord-JNI/lib/armeabi-v7a/libnative-lib.so

╭ (fcn) sym.Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI 12   sym.Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI (int32_t arg1);           ; arg int32_t arg1 @ r0
│           0x00000e78      0268           ldr r2, [r0]                ; arg1
│           0x00000e7a      0249           ldr r1, [0x00000e84]        ; [0xe84:4]=0xf3c
│           0x00000e7c      d2f89c22       ldr.w r2, [r2, 0x29c]           0x00000e80      7944           add r1, pc                  ; "Hello from C++" section..rodata
╰           0x00000e82      1047           bx r2

Observe que neste caso não estamos iniciando com o flag -A nem executando aaa. Em vez disso, apenas dizemos ao radare2 para analisar aquela única função usando o comando analyze function af. Este é um daqueles casos em que podemos acelerar nosso fluxo de trabalho porque você está focando em alguma parte específica de um aplicativo.

O fluxo de trabalho pode ser ainda mais aprimorado usando r2ghidra, uma integração profunda do decompilador Ghidra para radare2. O r2ghidra gera código C decompilado, o que pode auxiliar na análise rápida do binário.

IDA Pro

Assumimos que você abriu com sucesso lib/armeabi-v7a/libnative-lib.so no IDA Pro. Uma vez que o arquivo é carregado, clique na janela "Functions" à esquerda e pressione Alt+t para abrir a caixa de diálogo de pesquisa. Digite "java" e pressione Enter. Isso deve destacar a função Java_sg_vantagepoint_helloworld_ MainActivity_stringFromJNI. Clique duas vezes na função para pular para seu endereço na janela de desmontagem. A "Ida View-A" deve agora mostrar a desmontagem da função.

Não há muito código ali, mas você deve analisá-lo. A primeira coisa que você precisa saber é que o primeiro argumento passado para cada função JNI é um ponteiro de interface JNI. Um ponteiro de interface é um ponteiro para um ponteiro. Este ponteiro aponta para uma tabela de funções: um array de ainda mais ponteiros, cada um dos quais aponta para uma função de interface JNI (já ficou tonto?). A tabela de funções é inicializada pela Java VM e permite que a função nativa interaja com o ambiente Java.

Com isso em mente, vamos dar uma olhada em cada linha do código assembly.

LDR  R2, [R0]

Lembre-se: o primeiro argumento (em R0) é um ponteiro para o ponteiro da tabela de funções JNI. A instrução LDR carrega este ponteiro de tabela de funções em R2.

LDR  R1, =aHelloFromC

Esta instrução carrega em R1 o offset relativo ao PC da string "Hello from C++". Note que esta string vem diretamente após o fim do bloco de função no offset 0xe84. O endereçamento relativo ao contador de programa permite que o código execute independentemente de sua posição na memória.

LDR.W  R2, [R2, #0x29C]

Esta instrução carrega o ponteiro de função do offset 0x29C na tabela de ponteiros de função JNI apontada por R2. Esta é a função NewStringUTF. Você pode olhar a lista de ponteiros de função em jni.h, que está incluído no Android NDK. O protótipo da função é assim:

jstring     (*NewStringUTF)(JNIEnv*, const char*);

A função recebe dois argumentos: o ponteiro JNIEnv (já em R0) e um ponteiro String. Em seguida, o valor atual do PC é adicionado a R1, resultando no endereço absoluto da string estática "Hello from C++" (PC + offset).

ADD  R1, PC

Finalmente, o programa executa uma instrução de branch para o ponteiro de função NewStringUTF carregado em R2:

BX   R2

Quando esta função retorna, R0 contém um ponteiro para a string UTF recém-construída. Este é o valor de retorno final, então R0 é deixado inalterado e a função retorna.

Ghidra

Depois de abrir a biblioteca no Ghidra, podemos ver todas as funções definidas no painel Symbol Tree em Functions. A biblioteca nativa para o aplicativo atual é relativamente muito pequena. Há três funções definidas pelo usuário: FUN_001004d0, FUN_0010051c e Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI. Os outros símbolos não são definidos pelo usuário e são gerados para o funcionamento adequado da biblioteca compartilhada. As instruções na função Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI já foram discutidas em detalhes nas seções anteriores. Nesta seção, podemos observar a descompilação da função.

Dentro da função atual, há uma chamada para outra função, cujo endereço é obtido acessando um offset no ponteiro JNIEnv (encontrado como plParm1). Esta lógica também foi demonstrada diagramaticamente acima. O código C correspondente para a função desmontada é mostrado na janela Decompiler. Este código C descompilado torna muito mais fácil entender a chamada de função que está sendo feita. Como esta função é pequena e extremamente simples, a saída de descompilação é muito precisa, o que pode mudar drasticamente ao lidar com funções complexas.