Olá e bem-vindos ao primeiro vídeo de nossa série.
Conforme prometido, no primeiro vídeo, vamos nos concentrar nas funções nativas de engenharia reversa com o
Ghidra.
Portanto, isso pode ser, por exemplo, uma biblioteca que vem com um aplicativo Android e que contém código nativo
que faz algumas coisas sofisticadas.
E queremos descobrir o que é esse material sofisticado.
Portanto, vamos ver se há algum aplicativo que cubra esse tópico.
E se você abrir essas telas de crack.
Sim, é isso mesmo.
Já é o próximo.
Portanto, infelizmente, o código abaixo não tem uma biblioteca nativa incluída.
Por isso, simplesmente escolhi o próximo, que é o validador de licenças do Android.
Portanto, o validador de licença do Android é um crack me que implementa uma função de validação de chave em código nativo,
empacotado como um executável Elf autônomo para dispositivos Android.
Isso significa que temos um binário que é executado no dispositivo Android, e esse binário é uma chave ou contém uma função de
validação de chave, que precisa passar por engenharia reversa.
O autor, também mencionado posteriormente por esse simples aplicativo de amostra, não fez mapas.
Embora esse aplicativo de amostra possa não representar um cenário do mundo real, e esse é, eu diria,
o caso frequente dos Ctfs do Android que foram terceirizados para o componente nativo.
Portanto, se analisarmos esses desafios de Captcha, é mais parecido com a engenharia reversa de um binário ARM.
No entanto, essa habilidade ainda é necessária e bastante útil porque, se um aplicativo vier com uma
biblioteca, esse código estará sendo usado no aplicativo Android.
Portanto, precisamos analisar ou precisamos ser capazes de analisar esse código.
O que ele está fazendo?
E então, sim, veremos se conseguimos encontrar falhas.
Portanto, mesmo que não represente uma aplicação no mundo real, ainda assim vamos dar uma olhada
nela.
Está bem.
Portanto, ele serve como uma valiosa ferramenta de aprendizado para compreender o básico da execução simbólica.
Tudo bem.
Não falamos sobre execução simbólica, mas esse também é um tópico que não está realmente relacionado ao Android.
A execução simbólica é muito boa para resolver problemas, sim.
Desafios de capturar a bandeira ou coisas como, uh, número de série, validador de licença e coisas do gênero.
Portanto, a execução simbólica é mais ou menos assim.
Temos um ponto inicial em nosso código e um ponto final em nosso código.
E queremos descobrir quais parâmetros temos de fornecer para percorrer o gráfico de fluxo completo
desse código e chegar lá.
Portanto, a execução simbólica é diferente da emulação porque a execução simbólica está fazendo anotações.
Por exemplo, nesse ramo, o valor de x precisa ser maior que dez para ir para o lado esquerdo.
E se for menor que dez, ele vai para o lado direito.
Sobre a parte da emulação.
Estamos adotando apenas um caminho porque, nesse momento, nosso valor não tem um valor concreto, mas
a execução simbólica é capaz de perceber todos esses diferentes, digamos, um.
Condições.
A palavra é condições.
Essas diferentes condições permitem criar uma carga útil posteriormente.
Não é um tópico por si só.
Já usei, por exemplo, um pouco de raiva no passado.
Não sou especialista nesse assunto, mas já o usei para resolver alguns captcha, refletir desafios e você
pode passar um ano inteiro e fazer um curso inteiro sobre execução simbólica.
Portanto, talvez eu inclua uma breve introdução a esse tópico.
Mas acho que, sim, digamos que, se você lida com binários ofuscados, ele pode ser útil, mas não o estou usando
com frequência para resolver essas tarefas.
Mas, mesmo assim, vamos ver se você está interessado, me avise.
E, sim, se eu receber o feedback de que vocês querem ter um vídeo separado sobre esse assunto,
nós o cobriremos.
Tudo bem.
Isso pode ser aplicado em situações práticas ao lidar com aplicativos Android, eu sugeriria.
Hmm.
Está bem.
Bibliotecas nativas ofuscadas.
Sim.
Esse é um caso em que ele pode ser útil.
Isso é verdade.
De fato.
E assim por diante.
Muito bem, então o autor deste é Bernhard Mueller, eu acho.
Espero que a pronúncia esteja correta.
Parece alemão para mim.
E vamos ver.
E este está disponível aqui.
Então, abrimos esta.
E aqui temos o validador de licenças do Android, que desperta interesse, é claro, e assim por diante.
Não há keygen disponível.
Gere uma chave serial válida que seja aceita pelo aplicativo.
Certo, então.
Com relação à descrição, parece que pode haver várias soluções disponíveis.
Precisamos gerar uma chave serial válida, o que significa que pode haver mais.
Portanto, pode haver algum padrão nesse aplicativo.
E, sim, esse padrão pode se aplicar a várias entradas diferentes.
Instalação.
Então, baixamos esse desafio e ele parece ser um binário validado.
Colocamos isso em dados temporários locais.
Tornamos isso executável.
Começamos com isso.
Recebemos essa dica pelo uso.
Portanto, temos que fornecer o serial como parâmetro.
Temos esse parâmetro.
E, se estiver errado, receberemos um formato errado de série incorreto.
E se fornecermos esse parâmetro, obteremos.
Inserção da base 32 decode out length check license product activation pass.
Parabéns.
Muito bem, vou instalar isso em meu dispositivo e executá-lo.
Se você não tiver um dispositivo real, não poderá executar esta etapa.
Mas isso não é necessário para resolver esse desafio.
Portanto, trata-se de um desafio inverso.
E tudo isso pode ser feito apenas com a análise do código.
No entanto, gostaria de mostrar como o aplicativo é executado e, em seguida, daremos uma olhada na solução desse
desafio e também na aplicação de algumas técnicas de reversão.
Hum, o que estou fazendo pode ser útil também para você, caso analise códigos.
Tudo bem, ok.
Então, eu sugeriria que fizéssemos isso.
Então, clicamos em download, ok.
E vamos ver.
É válido.
Tudo bem.
ADB adb push validate into data local tmp.
Está bem.
Shell do ADB.
Portanto, isso precisa ser uh ou precisa ser executado em um smartphone.
Também não tenho certeza se podemos emular esse binário com o qemu porque ele pode ter sido escrito especialmente para a arquitetura
Android, obtendo todas as coisas, essas bibliotecas e coisas assim.
Talvez seja possível, mas, como já mencionado, não é necessário ter isso em execução.
Então, vou lhe mostrar como é.
Você também pode ver isso aqui.
E depois faremos tudo isso em Ghidra.
Está bem.
Então, começamos esse programa.
Em seguida, recebemos essa instrução de uso, ok.
Testamos o 1234.
Formato incorreto de série incorreta.
Está bem.
Vamos tentar a outra parte aqui.
Tudo bem.
Parabéns.
O desafio foi resolvido.
Não foi tão difícil assim, acho que sim.
Espero que você tenha aprendido alguma coisa.
E eu sugeriria.
Não.
É claro que sim.
Hum, sim.
É uma brincadeira.
Vamos ver como esse binário se parece.
Então, primeiro realizamos um arquivo para ver do que se trata.
Podemos ver que se trata de um binário Elf, portanto, é um executável.
32 bits, mas também é executado em uma arquitetura de 64 bits.
Este, hum, este aqui, está em pixel, que é de 64 bits.
Portanto, isso não deve ser um problema.
É a arquitetura ARM.
Ele é vinculado dinamicamente e é despojado, o que significa que não há nenhum símbolo nele, o que torna a reversão
um pouco mais difícil.
Mas é assim que você obtém essas bibliotecas ou binários.
Hum, vamos chamá-lo de.
Não, vamos dizer que não é de hoje, porque isso está muito, muito fora de moda por um tempo.
Você não obtém símbolos de depuração na maioria dos casos.
Portanto, temos que lidar com esse problema.
Então, vamos começar com as crianças e depois dar uma olhada nesta.
Ferramentas Chitra ok.
Vamos encerrar o assunto.
Portanto, aqui está a visão geral.
Entramos no arquivo do novo projeto Non-shared project porque estamos trabalhando sozinhos no Ghidra um validator Android
CTF.
Fornecemos o nome do projeto.
E então temos várias opções diferentes.
Temos o navegador de código, temos o depurador, mas o depurador é apenas um invólucro para o gdb.
Portanto, o ghidra não vem com seu próprio depurador.
Um.
Sim, acho que podemos dizer isso dessa forma.
Há opções para emular e depurar, mas é um invólucro.
Nós temos essa.
Esse é o emulador.
Esse é um recurso incrível que foi adicionado no passado.
Portanto, talvez consigamos imitar isso, mas isso ficaria fora de controle se nos aprofundássemos demais
no assunto.
E aqui está o rastreamento de versão, o que significa que podemos aplicar um determinado estado em um aplicativo.
E se o aplicativo for atualizado, podemos realizar o rastreamento de versão para ver o que foi alterado.
Isso é usado com bastante frequência para encontrar vulnerabilidades.
Por exemplo, se você tiver um binário com uma vulnerabilidade e o fornecedor corrigir
essa vulnerabilidade, você poderá identificar o que o fornecedor alterou e ver onde a vulnerabilidade poderia estar.
É claro que, na maioria das vezes, o fornecedor está realizando mais do que apenas uma alteração.
Portanto, ainda está um pouco bagunçado.
Mas essa é uma ferramenta útil.
Ok, até agora isso é tudo para Ghidra.
E vamos iniciar o navegador de código.
Vamos também copiar esse binário em um novo estado.
Portanto, criamos um diretório CTF e, em seguida, validamos os downloads.
E colocamos isso no CTF.
Portanto, esse é o nosso novo ponto de partida agora, ok, estou abrindo o Nautilus nesse diretório.
Então, temos isso aqui.
E agora eu simplesmente arrasto e solto no Ghidra.
A Chitra é inteligente o suficiente para reconhecer que se trata de um executável ARM.
Um VI V8 de 32 bits little endian.
Está bem.
Então ela simplesmente passou.
E agora, a Chitra está nos pedindo para analisar esse binário.
Gostaríamos de analisá-lo porque Chitra está fazendo todas essas referências, hum, a compilar e decodificar
e coisas assim.
Portanto.
Sim.
E aqui eu gostaria de acrescentar duas opções.
O primeiro é o ID do parâmetro de compilação que torna os parâmetros um, sim.
Ou melhora um pouco a descompilação desses parâmetros, o que significa que podemos obter tipos
melhores e coisas do gênero.
E também o localizador de instruções agressivas da ARM.
Portanto, a arquitetura ARM pode ser executada no modo de 32 bits, que é o modo ARM, e no modo thump,
que é o modo de 16 bits.
E Chitra está tentando identificar se há algumas instruções de 16 bits que seriam perdidas de outra
forma.
Portanto, há oportunidades de alternar entre esses dois modos na arquitetura ARM.
E, ao aplicar isso, Chitra está realizando todas essas verificações.
É claro que isso tem um lado negativo em binários pequenos.
Absolutamente nenhum problema.
Se você tiver um binário grande e estivermos falando de, digamos, dez MB.
Então, dez megabytes podem levar um bom tempo.
Então, digamos, 40 minutos, hum, para realizar essas verificações, o que eu gosto de fazer é, se eu tiver binários enormes, iniciar
o Chitra com opções básicas, talvez o parâmetro de compilação id, algo que eu ainda queira incluir.
Caso contrário, excluo esses dois.
Iniciar a análise de que eu, hum, obtive esse resultado mais rapidamente.
Em seguida, já comece a analisar um pouco e faça isso em uma verificação separada de que, sim, a Chitra está fazendo isso mais uma vez com todas
essas opções definidas e, se isso tiver sido concluído, poderei mudar para esse projeto
se for realmente crítico em termos de tempo.
No momento, é um binário pequeno.
Portanto, mantemos essas duas opções verificadas.
Está bem.
Analisar.
Sim.
E aqui está uma janela importante.
Podemos ver que o binário validate um tem dependências.
Portanto, essas são bibliotecas que não foram incluídas porque não temos essas bibliotecas.
Isso significa que se tivermos funções exportadas, por exemplo, em printf e coisas do gênero, elas não poderão
ser resolvidas.
Às vezes é bom ter isso resolvido.
Mas isso significa que precisamos obter essas bibliotecas.
Podemos tentar buscá-las no smartphone, pesquisar on-line e coisas do gênero.
Mas se você não tiver 100% de certeza de que essa é a biblioteca nessa versão, as coisas também podem ficar confusas.
A menos que você não descubra e funcione em uma biblioteca como essa, que é muito importante para
nós.
Gosto de manter esse projeto como está, porque assim não começamos cometendo erros.
Portanto, obtivemos uma nova configuração com poucas informações.
Talvez no início, sim, mas pelo menos está correto nesse ponto.
Está bem, está bem.
Portanto.
E sim, aqui está.
Reduzi um pouco a resolução porque obtive uma tela branca e isso atrapalharia o vídeo para
vocês.
Portanto, está um pouco confuso em relação à fonte.
E não estamos tendo muito espaço, mas acho que é possível.
Está bem.
Se estamos analisando isso agora, temos que pensar no que queremos alcançar e no que queremos alcançar para resolver
esse desafio.
E esse desafio está sendo resolvido se fornecermos o parâmetro correto.
Portanto, portanto.
Se voltarmos, não estará aqui.
É aqui.
Portanto, podemos ver que esse aplicativo já nos fornece muitas informações úteis sobre as
saídas.
Podemos ver que houve uso, número de série incorreto, formato errado.
Portanto, essa é uma condição que não queremos alcançar.
Temos um código D de base 32 inserido.
Portanto, esse aplicativo parece fazer algo com a base 32, o que também já é interessante.
Temos um comprimento de saída.
Obtivemos a licença de verificação e depois isso.
Parabéns.
Portanto, esse é o ramo ou o código de programa que queremos alcançar.
Portanto, vamos tentar encontrar isso em nosso binário.
Portanto, estamos copiando isso.
Parabéns.
E estamos procurando por cadeias de caracteres.
Então, vamos salvar isso primeiro, ok.
Em seguida, entramos na janela.
E não, vamos pesquisar e depois procurar por strings, ok.
Buscamos pelo menos três.
E nós pegamos todos os blocos carregados.
Agora temos todas essas cadeias de caracteres e podemos colocar isso aqui para ver.
Sim, a ativação do produto foi aprovada.
Parabéns.
Portanto, se agora clicarmos duas vezes neste.
Em seguida, Chitra abriu a referência da corda no plano de fundo.
Portanto, se minimizarmos isso, poderemos ver.
Sim, aqui está.
Portanto, aqui podemos ver a ativação do produto aprovada.
Parabéns.
E este está sendo referenciado nesta função aqui.
Esse é o endereço dessa função.
E esses são os endereços em que essa string está sendo usada.
Isso significa que se clicarmos duas vezes nessa referência aqui.
Acabamos na função que está usando essa parte.
E isso já parece um pouco bagunçado, o que é bom, eu acho.
Assim, podemos ver a ativação do produto aprovada.
Parabéns.
Também podemos ver que temos duas referências, mas no código descompilado há apenas uma.
Vamos ver se voltamos para a janela de cadeias de caracteres.
Desculpe, mas não as cordas.
Temos que voltar.
Mas não quero estragar tudo agora.
Portanto, se usarmos a tecla Control e depois a tecla de seta para a esquerda, voltaremos à janela anterior.
Portanto, aqui podemos ver que o endereço é 844 e o 848 estava sendo referenciado.
Assim, podemos ver que temos duas referências aqui, mas apenas uma em nosso código descompilado.
E isso agora é muito importante.
A Chitra está fazendo um trabalho incrível em relação à descompilação de um código de montagem e recupera essa representação em
C aqui.
Mas, às vezes, é claro que pode haver erros nesse código aqui e, portanto, não podemos confiar totalmente
nele.
Também precisamos dar uma olhada no código de montagem do braço se quisermos analisar isso.
Portanto, se clicarmos neste, chegaremos a este ponto e a esta parte aqui.
E se você clicar em "congratulations" (parabéns), verá que ela foi mencionada duas vezes aqui.
Portanto, isso está correto no código de montagem.
E também pode estar correto aqui, porque este está apenas adicionando um valor ao registro R3.
E este está copiando esse valor para outro registro.
Portanto, não há problema algum.
No entanto, não podemos confiar totalmente nessa parte aqui.
Portanto, às vezes também precisamos verificar o código de montagem.
Tudo bem.
Então, o que eu gosto de fazer aqui é identificar a função que queremos alcançar, e eu gosto
de dar uma olhada nelas.
Hum, sim.
Saídas.
O que essa função está fazendo.
Temos esse nome de função aqui, e este é um pouco confuso porque tem apenas o nome fun.
E, em seguida, o endereço dessa função, se você, por exemplo, não fizer isso agora,
só quero mostrar se você clicar em algum lugar.
Isso já é interessante, mas não é o que estou procurando aqui.
Aqui temos apenas esse nome de função.
Temos isto, nome da função, isto, isto e assim por diante.
Não temos a menor ideia do que essas funções fazem, e precisamos investigar cada uma delas.
Portanto, ao aplicar determinados nomes ao que eles podem fazer, fica um pouco mais legível.
Esse código está correto.
Portanto, se voltarmos ao controle e depois à seta para a esquerda, tudo bem.
Portanto, não tenho a menor ideia do que essa função está fazendo.
Mas parece lidar com parabéns e com uma teoria incorreta.
Portanto, eu entro nessa e gosto de usar atalhos, mas é difícil de seguir.
Portanto, eu faço isso na GUI.
Renomeamos essa função.
Então, clique com o botão direito do mouse para renomear a função e eu a nomeio assim.
Agora, por exemplo, o sucesso da decisão falhou.
Agora, se encontrarmos isso mais tarde em nossa decisão de código de sucesso ou falha, então saberemos.
Sim, esta é a função que controla se passamos na verificação de série
ou se falhamos aqui.
Ok, então identificamos a função que queremos alcançar e agora precisamos obter o caminho para essa
função.
E há outra janela muito útil que talvez não esteja disponível para você no momento.
Portanto, vou fechar isso aqui e depois mostrar como você pode ativar isso.
Este está dentro da árvore de chamadas de função da janela.
E este mostra agora todas as funções de entrada ou todas as chamadas de entrada relacionadas a essa função.
Então, qual função está chamando esta aqui?
E também mostra quais funções estão sendo chamadas por esta aqui.
Por exemplo, no lado direito, podemos ver todas essas funções.
Eles estão sendo chamados.
Mas não é isso que está nos interessando no momento.
Queremos saber como podemos alcançá-lo.
Portanto, damos uma olhada nas chamadas recebidas, não nas chamadas efetuadas.
Portanto, podemos ver que essa é a nossa função.
E este está sendo chamado por este aqui, ok.
Vamos clicar duas vezes.
E aqui já podemos ver que essa função está sendo chamada pela função de entrada.
Portanto, essa é a nossa cadeia de chamadas completa no momento, o que é bastante impressionante, pois não é muito.
Temos nossa função de entrada, que é esta aqui.
Essa função de entrada chama essa.
E este chama a atenção para o sucesso e o fracasso de nossas decisões.
Portanto, se chegarmos a esse ponto na função de sucesso, então, nossa entrada foi válida.
Está bem.
Portanto, esta aqui parece ser a nossa função principal.
Por quê?
Porque temos a função de entrada aqui.
Aqui está um pouco do que está sendo preparado.
E aqui temos esse primeiro ponteiro de função que termina aqui.
Portanto, essa é a primeira função que está sendo chamada em nosso código de usuário.
E também podemos ver um pouco de uma estrutura.
Temos os parâmetros e obtivemos o valor de retorno.
Portanto, nosso valor de retorno aqui é indefinido.
Mas estamos retornando aqui um valor inteiro.
E se pensarmos na estrutura de uma função principal, ela normalmente se parece com isso.
Portanto, temos um int main argc char argv.
Está bem.
Portanto, esse é o valor de retorno, um valor de retorno e um número inteiro como um número inteiro.
Argc é o primeiro parâmetro e Accaee é o uh, digamos, o número de.
Argumentos que fornecemos ao nosso binário, estamos chamando de yes.
Portanto, se abrirmos esse binário sem um parâmetro, teremos um argumento, que é o caminho para
o binário.
Se fornecermos um parâmetro, teremos dois parâmetros.
Assim, por exemplo, se você o abrir dessa forma, valide, então argc seria um, porque esse é o caminho
referente a esse binário.
E se dissermos que validamos nosso número de série, então xy seria dois porque temos o primeiro, que
é o caminho, mais nosso parâmetro ou argumento.
Não importa como você queira chamá-lo.
Está bem.
Em seguida, temos nosso argv, que é este aqui.
E esse é apenas um array de strings.
Portanto, esses são nossos parâmetros.
Por exemplo, este aqui.
Tudo bem.
Então vamos ver.
Vamos manter isso em aberto aqui.
Está bem.
O que eu gostaria de fazer é reatribuir essa assinatura de função porque ela parece errada.
Então, por que isso parece errado?
Porque se dermos uma olhada nesse parâmetro dois.
E, a propósito, essa é uma opção que eu configurei, mas não está configurada para você.
Se você clicar no parâmetro, talvez seja necessário clicar com o botão do meio do mouse para que eles sejam destacados.
E essa é uma opção que você pode executar aqui com uma ferramenta de edição.
Opções.
Você pode procurar o mouse e, em seguida, o destaque de texto do cursor, e pode alternar do meio para a esquerda.
Então, basta clicar neste e ele destacará todas essas ocorrências.
Está bem.
Portanto, este aqui está sendo convertido em um ponteiro de caractere duplo.
E também temos uma referência de índice aqui.
Portanto, se virmos algo assim, o que eu gosto de fazer é simplesmente aplicar isso como um parâmetro aqui
para ver se o código fica mais legível.
É simplesmente uma tentativa e erro para ver se faz mais sentido.
Dessa forma, podemos ver que temos um ponteiro duplo inteiro aqui.
Hum, mas presumo que essa seja a função principal.
Portanto, vamos tentar desta forma.
Clicamos com o botão direito do mouse e digitamos novamente o ponteiro de caractere duplo variável.
Por que essa sobreposição.
Isso é muito irritante.
Ok, vamos selecionar este.
Então, eu clico em 3 para 1, e temos que verificar se este se torna um pouco mais legível.
3 para 1.
Sim, isso parece muito melhor agora.
Está bem.
Agora vamos aplicar também o restante.
Então, clicamos com o botão direito do mouse.
E também podemos dizer editar a assinatura da função.
Portanto, podemos dizer que esse é um tipo de retorno.
E renomeamos isso para main ou provavelmente main, caso não tenha certeza.
Mas, neste momento, temos certeza de que ela está sendo chamada pela função de entrada.
Está bem.
Então, também podemos fornecer, quero dizer, este é RC e este é o nome argv, por exemplo.
Sim.
Está bem.
E agora o código ficou um pouco mais legível.
Tudo bem.
Agora vamos ver.
Hum, o que eu gosto de fazer é dar uma olhada rápida em algumas funções printf,
porque essas funções printf já dão uma ideia e destacam quais são essas variáveis.
Portanto, podemos ver que estamos usando um local c como um número inteiro.
E este parece ser um comprimento fora do comum porque está sendo usado como um.
Sim, como um valor decimal nessa instrução printf aqui.
Portanto, este aqui é provavelmente um comprimento externo.
Não temos a menor ideia do que significa out length e como ele está sendo usado, mas vamos chamá-lo de out length.
Está bem.
Até o momento, não consigo ver nenhum outro parâmetro.
Portanto, não podemos fazer mais.
Então, vamos começar do início.
Se nossos arcos não forem iguais a, o que significa que fornecemos mais ou menos de um parâmetro, então
entramos nessa função.
Clicamos duas vezes para validar o uso e assim por diante.
Portanto, essa é apenas a saída de uso.
Portanto, clique com o botão direito do mouse.
Ou você pode simplesmente clicar nesse local e pressionar L.
Mas os atalhos e o Shedra podem ficar fora de controle.
Portanto, há pessoas que passam por esse navegador e, em seguida, realizam todos esses ajustes.
Tento tornar isso um pouco mais fácil de acompanhar.
Então, vamos para renomear essa função, e esta é apenas uma ajuda de impressão.
Ok, então eu uso o controle e a tecla de seta para a esquerda.
Duas vezes.
Agora estamos de volta.
Isso significa que temos que fornecer um parâmetro aqui.
Ok, então obtivemos o comprimento da string em nosso parâmetro que fornecemos.
Portanto, argv, que é o primeiro, arg0 é o primeiro.
Mas isso está relacionado ao binário um.
Portanto, nosso argv é o primeiro parâmetro que fornecemos.
E aqui temos uma string. length, o que significa que esse agora contém o comprimento de nossa entrada.
Portanto, esse é o nosso péssimo hábito.
Clique com o botão direito do mouse em renomear.
Esse é o nosso comprimento de entrada.
Está bem.
Em seguida, podemos ver se o comprimento da entrada não é igual a zero x dez.
Podemos passar o mouse sobre isso e, em seguida, ele será exibido.
São 16.
Mas também podemos clicar com o botão direito do mouse nessa opção.
E então podemos ver a representação decimal.
Se não for 16, então estamos chamando essa função.
Então, vamos clicar duas vezes.
Série incorreta.
Formato incorreto.
Está bem.
Portanto, clique com o botão direito do mouse em renomear.
Incorreto.
Talvez impresso.
Incorreto.
Formato incorreto.
Ok, controle e dois tempos restantes.
Isso significa que, se não estivermos ou se estivermos inserindo um número com 16 caracteres, passaremos por essa verificação
e deverá haver uma saída de decodificação da base 32 aqui.
Então, vamos tentar fazer isso rapidamente.
Ainda temos o site aberto em algum lugar?
Não.
Vamos fazer isso rapidamente.
Portanto, o shell do ADB valida os dados temporários locais.
Ok, vamos fornecer 16 caracteres.
Perfeito.
Podemos ver 16 caracteres.
Podemos ver a base 32 decodificando o comprimento, o que quer que isso signifique.
Verifique a licença.
Incorreto.
Serial com um caractere a menos ou a mais.
Formato serial incorreto.
Certo, perfeito.
Até o momento, entendemos o programa, o que é bom.
Em seguida, podemos ver a decodificação da base 32.
E isso já é importante, pois significa que nossa entrada precisa ser codificada na base 32, suponho, a partir
deste ponto.
Mas vamos ter isso em mente.
Então, temos essa função.
E isso agora tem muitos parâmetros.
E o que eu gosto de fazer é clicar neste ícone de câmera aqui.
Isso tira um instantâneo.
Portanto, temos isso agora em nosso lado direito.
E se agora clicarmos duas vezes neste.
Temos esses parâmetros aqui.
Portanto, agora podemos ver o que é cada parâmetro.
Por exemplo, o primeiro é zero zero e este aqui é o nosso argv1.
Aqui estamos com 16.
Isso é algo relacionado à pilha e é o nosso comprimento de saída.
Portanto, já podemos fornecer esse nome aqui.
Podemos dizer, por exemplo, o seguinte.
Renomear.
Esse é o nosso comprimento de saída.
Está bem.
No entanto, vamos dar uma olhada rápida nessa função.
Tudo bem se eu vir coisas como essa?
Hum, eu sou.
Normalmente, não gosto de reverter isso porque.
Pode ser uma função básica em C ou, por exemplo, um MD5 ou um Sha qualquer coisa ou
coisas desse tipo.
Portanto, não quero perder tempo invertendo funções que não são tão relevantes ou que são apenas funções
básicas.
Se não tivermos símbolos no printf, quero dizer, aqui temos no printf porque ele precisa ser exportado.
Mas se esta for agora, hum, não sei, digamos uma função MD5.
Então, podemos tentar procurar certos valores codificados.
Portanto, md5, funções Sha one e coisas do gênero.
Eles têm valores hexadecimais codificados que podem ser pesquisados on-line.
E então, nós já sabemos, hum, agora eu gostaria de pular essa parte e ver se ela se torna importante mais tarde.
Portanto, ele pode fazer algo muito importante.
Mas, a menos que não seja algo que ainda não tenhamos descoberto, gosto de ignorar essas funções porque são muito complexas.
E, no final, estamos revertendo algo que talvez não seja realmente relevante para nós.
Hum, sim.
Então, podemos clicar nisso porque, sim, ele entra na toca do coelho para que possamos ver.
Hmm.
Isso se torna sim.
Se esse for o desafio, então sei por que deveria ser.
Sim.
Resolvido por execução simbólica.
Mas, neste momento, vamos pular essa parte aqui.
Ok, então vamos encerrar esse caso.
Portanto, este é um desempenho com o comprimento da saída.
Ok, isso pode ser relevante mais tarde.
Não sabemos isso no momento.
Então, vamos ver como entrar na licença de verificação.
E podemos ver que agora temos esse parâmetro de pilha.
O parâmetro da pilha também tem 16 bytes.
E esses 16 bytes são iguais ao comprimento da entrada.
E o que presumo neste momento é que estamos entrando em nossa função aqui, que é muito complexa.
Então, talvez devêssemos renomear isso.
Função complexa.
Estamos entrando nessa função complexa com nosso argumento e vários outros parâmetros.
E, no final, podemos escrever isso nesta parte aqui.
E essa parte está sendo usada agora nessa última função.
Sucesso ou fracasso da decisão.
Portanto, essa é apenas a assimilação.
Ou seja, podemos dar uma olhada rápida nessa função complexa em relação a esse parâmetro,
que é este aqui.
Está bem.
Este está sendo transferido para o local 38.
E este está sendo preenchido nesta e nesta função.
Sim.
Portanto, estamos definitivamente preenchendo esse espaço de memória com algum tipo de dado.
Está bem.
Podemos ver se estamos retornando para fora dessa?
Nesse caso, temos que verificar o código de montagem para ele.
Um.
Onde retornamos.
Está bem.
Muitas instruções de ramificação.
Sim.
É complexo.
Vamos ignorar isso por enquanto.
Ok, então, se essa decisão for bem-sucedida ou não, vamos entrar nessa.
E digamos que isso seja algo que esteja sendo executado com a nossa e com a nossa contribuição, provavelmente.
Entrada modificada.
Está bem.
Decisão, sucesso ou fracasso.
Então, aqui temos o nosso, desculpe-me, clique com o botão direito do mouse para renomear e, provavelmente, o modo de entrada.
Portanto, aqui temos nossa função de sucesso e fracasso.
Vejamos.
Temos um monte de variáveis locais, mas elas não estão sendo usadas em instruções de impressão.
Portanto, não sabemos quais são esses.
Nós temos essa.
Também não é conhecido.
Ok, temos que começar do início.
Nossa provável entrada agora está sendo transferida para esse registro local.
Portanto, esse é agora o nosso mod de entrada local.
Está bem.
E não gosto de fornecer nomes grandes porque o código fica muito ilegível.
Mas, mesmo assim, acho que não há problema.
Assim, podemos ver que obtivemos o for loop.
E esse loop for tem isso.
Ok, este parece ser um encontro.
Assim, nosso um típico para I é igual a zero I é menor que cinco ou menor que cinco.
Desculpe.
Ele é aumentado.
Portanto, vamos mudar o nome para I.
Renomear a variável.
Ele também está sendo usado apenas nesta parte aqui.
Portanto, podemos renomeá-lo para I.
Se estiver sendo reutilizado mais tarde nesse código, talvez eu não seja tão útil.
Mas, nesse caso, não há problema.
Está bem.
Portanto, o código já se tornou bastante legível.
O que temos aqui é.
Conseguimos um loop, que passa por isso cinco vezes.
012.
345.
Tempos de por um.
Em seguida, ele pega nossa entrada e faz XOR com o próximo valor de nossa entrada.
Armazena isso no local 20, portanto, esse parece ser o nosso resultado.
Resultado.
E então ele aumenta.
Nosso índice em dois.
Está bem.
Portanto, ter a referência aqui significa que estamos tendo a primeira entrada da nossa matriz de bytes, o que significa que este
é o índice zero.
Portanto, estamos usando o índice zero com o índice um.
E então estamos aumentando esse índice em dois.
E vamos escrever isso.
Portanto, temos o resultado I para nosso primeiro valor.
Parece um mod de entrada local.
Portanto, temos aqui o nosso primeiro índice, que está sendo combinado com o próximo.
E então aumentamos esse índice em dois.
Ou seja, nosso próximo resultado.
Deve se parecer com isso.
Sim, ele pega esses dois valores, faz o XOR e os armazena de volta no resultado.
E, em seguida, ele pega os próximos dois valores.
Assim, por exemplo, se tivermos a, b, c d, então a e b estão sendo XOR.
E na próxima instrução, c e d seriam XOR.
Acho que estou bastante confiante de que esse é o caso aqui, a menos que.
Vamos ver se é realmente com uh aumentou o AD em dois.
Sim.
Está bem.
Às vezes, se tivermos, por exemplo, um array de números inteiros, ele conta até uh quatro, o que significa o próximo elemento.
E, ao verificar se este agora está sendo adicionado por dois, estamos nos certificando de que este não
é uma descompilação.
Vamos chamá-lo de erro, que simplesmente pula para um próximo elemento A ou algo assim.
Portanto, isso está realmente sendo incrementado em dois.
Não tenho certeza se isso está claro neste momento.
Acho que não está muito bem explicado, mas espero que você entenda o que quero dizer.
Portanto, se tivermos uma matriz de números inteiros e quisermos obter o próximo índice, teremos que adicionar quatro, pois um número
inteiro tem quatro bytes.
E, portanto, obter a próxima entrada significa mais quatro.
E, portanto, verificamos se este é apenas um incremento de dois, o que significa que não estamos
pulando.
Por exemplo, um índice curto, que é de dois bytes.
Portanto, vamos pular para o próximo índice.
E isso agora está realmente agregando o valor dois.
Com relação à matriz de bytes, que tem um byte de tamanho, significa que estamos pulando dois elementos acima.
Muito bem.
Então, sim, acho que o vídeo já está ficando um pouco longo, mas vamos ver.
Estamos, acho que estamos, perto do final.
Já temos ou acabamos de ter essas funções aqui.
Vejamos.
Espero que não estejam explodindo.
Portanto, dê um clique duplo nesse vídeo.
Está bem.
Este é um 0X4C de retorno e este é o caractere L.
Portanto, também podemos clicar com o botão direito do mouse e fazer isso desta forma.
Então, renomeamos essa função um para return L porque ela sempre retorna L, certo?
Não sou um grande fã dessa representação.
Portanto, vamos manter as coisas assim.
Portanto, esse é um L de retorno, ok.
Portanto, isso agora está armazenando o valor L, mas este está sendo reatribuído.
Portanto, se renomeássemos isso para L local, seria errado, porque aqui ele está sendo substituído.
Portanto, controle Z.
Não estamos renomeando isso agora, ok.
Próxima função.
0X4F.
Isso é um O.
Renomear.
Devolver tudo.
Aqui está o al mais uma vez perfeito.
Podemos reconhecer isso porque o renomeamos aqui.
Essa é a mesma função.
Então vamos ver o que está acontecendo aqui.
0X5A.
Isso é um conjunto.
Ok, então temos essa função à esquerda 21.
Ponto de exclamação.
Está bem.
Vamos ver o que está acontecendo aqui.
Portanto, essa variável ou vamos ver o que está acontecendo aqui.
Esse é o nosso loop for.
Repetimos isso cinco vezes.
Em seguida, armazenamos o valor de nosso primeiro e segundo elemento XOR juntos nessa parte.
Então aumentamos isso.
Então, chegamos a essa parte.
Esse é armazenado no resultado.
E para zero, e esse é armazenado no resultado um.
Ops.
Está bem.
E então temos isso aqui.
E aqui estamos nós retornando.
L e, em seguida, o primeiro elemento de C do nosso resultado é comparado se for um L.
Então, aqui está.
Em seguida, se o resultado null sorry zero for igual a L, tudo bem.
Uh.
Vejamos.
Então, temos outra comparação.
Se retornar.
Oh.
Ah, isso parece um pouco confuso, mas aqui estamos atribuindo essa variável com retorno.
Oh.
Portanto, isso está sendo atribuído agora.
Oh.
E então ele é comparado.
Se o resultado for tão bom.
Estamos tendo menos do que estamos tendo.
Oh.
Então, mais uma vez, L é atribuído a U1 e está sendo comparado com o resultado dois.
Portanto, o terceiro elemento é um L.
Em seguida, atribuímos um conjunto a essa variável e comparamos.
Se o quarto elemento estiver em um conjunto.
Em seguida, retornamos o ponto de exclamação e verificamos se o quinto elemento é esse ponto de exclamação.
Portanto, temos a nossa entrada e essa entrada agora está se xorando.
Assim.
Em seguida, ele compara esses elementos.
Uh, fica L. O. L.
Coloque o ponto de exclamação para que o lolz.
Tudo bem.
O que não sabemos no momento é essa parte aqui no meio.
Portanto, se você voltar atrás.
Agora, com essa função complexa, podemos fazer uma suposição ousada de que estamos inserindo a decodificação Base32, o que
significa que nossa entrada precisa ser codificada em 32 bits.
Então, vamos escrever isso aqui.
Podemos definir e comentar com o botão direito do mouse.
Então, temos várias opções diferentes.
Postar comentário significa que estamos adicionando esse comentário após essa instrução.
Acho que o comentário sobre a promessa são esses asteriscos que estão apenas na visualização da montagem.
Comentário repetível Não tenho certeza sobre esse comentário no momento.
O comentário de final de linha o colocaria no final da linha, mas é difícil de ler no código de montagem.
Por isso, gosto de usar comentários prévios, o que significa que eles estão sendo adicionados antes na visualização da montagem e também na visualização
descompilada.
Portanto, nossa entrada provavelmente precisa ser codificada em base32, a menos que o desenvolvedor nos engane aqui.
Ok, agora podemos ver que colocamos esse comentário aqui, mas gostaríamos de tê-lo aqui.
Esse é o caso porque, hum.
As instruções de montagem estão um pouco erradas em relação ao código descompilado.
Ou digamos que o código descompilado esteja fora das instruções de montagem, porque no lado esquerdo estamos corretos.
No lado direito, pode haver erros, portanto, podemos clicar com o botão direito do mouse no comando.
Então, podemos usar esse controle X para cortá-lo.
E então entramos nessa colocação e dizemos aqui, comente.
Defina o pré-comentário e não haverá problema algum em tê-lo aqui.
Está bem.
Portanto, o que o código está fazendo é pegar nosso argumento, que precisa ser, ou provavelmente é, basicamente
uma base 32 codificada.
Em seguida, estamos verificando se temos um parâmetro.
Se não, nós recebemos a ajuda.
Ele obtém o comprimento da string.
Ele verifica se foram fornecidos 16 caracteres.
Codificado em Base32.
Isso é importante neste momento.
Se não, está imprimindo no formato errado, então é mais provável.
Vamos renomear isso.
Provavelmente decodificação base32.
Provavelmente, ele se baseia em 32 decodificações.
Escreve-o neste aqui.
Aqui estão.
É por isso que o comprimento de saída pode ter sido zero antes, porque não era possível usar a base 32.
Um.
Decodifique isso.
Vamos experimentar isso.
Isso é algo, hum.
Podemos verificar.
Portanto, aqui podemos ver que o comprimento de saída é zero porque esse não é um formato de codificação de base 32 válido.
Vamos ver se conseguimos.
Oof!
Um.
Como conseguimos isso exatamente?
16.
Mas, mesmo assim, vamos fazer isso da seguinte maneira: a, b, c, d.
Temos a base 32?
Sim, nós a temos.
Está bem.
Agora não tenho certeza.
12345678 ok.
Vamos fazer isso duas vezes.
Então, temos 16 bytes e vamos ver se agora obtemos um comprimento.
Um.
Aqui no comprimento externo.
Isso significa que, muito provavelmente, ele está realizando essa codificação, decodificação e decodificação.
Sim.
Portanto, 3 para 1.
Sim.
Perfeito.
Podemos ver que temos um comprimento de saída de dez, ok.
Portanto, isso é codificado em base32, ok.
Isso significa que chegamos facilmente a esse ponto aqui.
Ao fornecer uma entrada codificada em 32 base32.
Hum, o que é complicado agora é que nossa entrada de base 32 precisa ser e xored com ela mesma.
Desculpe.
Não, não, nós estamos bagunçando ou eu estou bagunçando as coisas agora.
Desculpe.
Vamos voltar.
Base 32 decodificada.
Portanto, aqui temos uma matriz de bytes depois.
Portanto, o que estamos fazendo aqui é colocar o nosso.
Nossa página 64.
Desculpe.
Base 32.
Sou eu.
Não estou acostumado com a base 32.
Então, uh.
Sim, estamos colocando nosso.
Esqueça o que você viu.
Estamos lançando nossa base 32.
Uh, entrada em uma função de decodificação, e estamos recebendo de volta os bytes brutos.
Então, hum.
Sim, aqui estamos tendo bytes ao entrar nessa função.
Então, vamos renomear isso.
Essa é a base 32 decodificada, o que significa bytes.
Ok, vamos entrar nessa.
E vamos renomear esse nome para provavelmente bytes de entrada.
Está bem.
Isso é muito importante.
Então, aqui estamos nós com bytes.
E isso significa.
Agora temos que fazê-lo.
Agora precisamos encontrar uma maneira de fazer X ou fazer com que nossos bytes façam X juntos.
O que resulta em moradores locais.
Também podemos entrar nesse gráfico de função de exibição.
Então, acho que pode ser um pouco mais fácil de ver.
Sim.
Está bem.
Podemos simplesmente aumentar o zoom.
Portanto, aqui temos o início de nossa função de sucesso da decisão.
Não é realmente importante que você leia todas as linhas.
Trata-se apenas do panorama geral.
Está bem.
Então, temos algumas coisas aqui.
Em seguida, este é o nosso loop while, com o qual estamos xorando nossas coisas.
E então podemos ver que sim, isso parece perfeito.
Podemos ver que ele se compara.
Se a nossa saída nos der um resultado de "l o l", coloque um ponto de exclamação.
Se esse for o caso, a ativação do produto será bem-sucedida e, se não for, acabaremos aqui.
Também podemos marcar isso com a cor truz.
Portanto, essa é a parte que não queremos alcançar, e essa é a parte que queremos alcançar.
Agora, se pudermos salvar isso e voltar.
Agora também temos isso destacado aqui.
Está bem.
Portanto, este vídeo já está um pouco longo.
E eu gostaria de dividi-lo em duas partes.
Vamos recapitular isso rapidamente, porque é um pouco difícil ou, no momento, isso não está realmente relacionado
ao hacking de aplicativos Android.
Está mais relacionado à engenharia reversa, que pode ou pode ser necessária para invadir aplicativos Android.
Portanto, vamos fazer uma recapitulação e, depois, você pode tentar resolver isso por conta própria, caso se sinta completamente
sobrecarregado, sem problema algum.
Hum, faremos isso no próximo vídeo juntos.
Então vamos ver.
Está bem.
Portanto, vamos tomar essas medidas.
As etapas consistem em fornecer uma entrada codificada em base32.
Esse é então decodificado com base32.
Hum, e então talvez, talvez façamos desta forma.
E, na próxima etapa, nosso resultado está sendo xored.
Assim.
E depois vamos ter esse mais dois, hum, aqui depois.
Esse resultado está sendo verificado.
Se o resultado zero tiver se tornado o valor L.
Se, uh, o resultado um se tornou o valor.
Ah, e assim por diante.
Então, é como se estivesse tudo bem.
No próximo vídeo, apresentaremos a solução para esse caso.
Portanto, você pode tentar resolver isso.
Não estou esperando isso.
Não porque você não seja capaz de fazê-lo, porque se você não tiver feito essa engenharia reversa com frequência, todas
essas coisas, como xoring de valores entre si e coisas assim, esses padrões são novos para você e você
precisa aprender isso primeiro.
Eu também passei por essa fase.
Portanto, sim, agora reconheço esses padrões, mas é um processo de aprendizado, portanto, não se desmotive
com isso, certo?
Tudo bem.
Portanto, estou ansioso para vê-lo no próximo vídeo, onde apresentaremos a solução para esse
caso.
Vejo você lá.