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ços 0x00000e78, onde nossa função alvo está localizada. Fazemos isso para que os comandos seguintes se apliquem a este endereço.pdfsignifica 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.
