Jump to content
Injeção de código é uma técnica que consiste em adicionar instruções extras em um executável. Essas instruções podem ser adicionadas em vários lugares do programa, inclusive executar antes do entry point original.
O uso mais comum para injeção de código é para fins maliciosos, onde um shellcode poderia ser inserido no executável e dar acesso remoto para um atacante. Mas um exemplo de uso "justo" para essa técnica é para fins de patching no executável quando você quer que algo seja alterado em tempo de execução no binário.
Se você já tentou injetar código em um executável manualmente deve saber que não é uma tarefa tão divertida. Pensando neste tipo de impasse, imaginei que seria interessante ter uma ferramenta para automatizar esse tipo de manipulação de um executável. Por esse motivo criei o pei, uma ferramenta para automatizar injeção de código e outros tipos de manipulações em executáveis PE de 32-bit e 64-bit.
O pei foi programado pensando na automação e por isso é fácil usar a ferramenta a partir de um script. Com ela você pode obter e modificar valores no executável, e é claro, injetar código.
Antes de qualquer coisa você pode instalar o pei em seu Linux rodando os comandos abaixo:
git clone https://github.com/Silva97/pei cd pei make sudo make install Nota: Caso use Windows e não esteja usando WSL ou um MSYS2 da vida, você pode compilar o projeto instalando o make e o MinGW (recomendo usar o Chocolatey). No entanto, o “sudo make install” não vai funcionar no Windows, você vai ter que adicionar o executável ao PATH manualmente.
Se você não estiver a fim de compilar o executável, fiz o favor de adicionar o binário da ferramenta compilado para Windows nos releases dela. Você pode baixá-lo no link https://github.com/Silva97/pei/releases/latest.
O uso básico da ferramenta segue o seguinte formato:
pei [opções] <operação> <executável> [argumento] Se você quiser ver mais detalhes de como usar a ferramenta você pode rodar “pei -h”.
Operações
As operações são o que determinam o que a ferramenta irá fazer com o executável, indo desde exibir informações sobre ele até modificar campos dos cabeçalhos.
show
A operação show serve para exibir informações sobre o executável e campos dos cabeçalhos. Se você não passar argumentos para a operação por padrão ela irá exibir informações básicas do executável:

Você também pode especificar uma combinação de letras para escolher quais campos serão exibidos, dentre elas: c (COFF header), o (optional header), d (data directories) e s (section). Exemplo:
$ pei show test.exe co Esse comando, por exemplo, exibe o COFF header e optional header do executável.
get
A operação get pega o valor de um campo individual de um dos cabeçalhos (coff, optional ou section) do executável.
Seguindo uma notação com pontos, semelhante à acessar campos de estruturas em C, você pode especificar o cabeçalho e o nome do campo para ser obtido. Por exemplo, para obter o entry point do executável o comando ficaria:
$ pei get executavel.exe optional.entry_point '%x' 14f0 Dica: Veja o nome dos campos dos cabeçalhos usando a operação show.
O argumento após o nome do campo é uma string de formatação idêntica a da função printf() da libc, que aceita quaisquer flags de formatação disponíveis para a função.
Para acessar os campos de uma seção é necessário especificar o número da seção também, como demonstrado no print abaixo:

edit
A operação edit serve para modificar o valor de campos, onde o nome do campo é especificado de maneira idêntica à operação get.
Você pode utilizar os operadores `=`, `|=` e `&=` que fazem exatamente a mesma coisa que na linguagem C. Exemplos:
$ pei edit executavel.exe section.0.name = .code $ pei edit executavel.exe optional.entry_point = 0xabcd1234 Esta operação aceita números em decimal, hexadecimal ou octal na hora de definir o valor de campos numéricos.
zeros
Esta operação simplesmente exibe uma lista das maiores sequências de bytes nulo em cada seção do executável. É este espaço que é utilizado para injetar o código, tornando a operação útil para você poder escolher em qual seção injetar o código.
$ pei zeros executavel.exe Section #0 '.text': 0x00000000000022fb of 13 bytes Section #1 '.data': 0x000000000000242c of 1012 bytes Section #2 '.rdata': 0x0000000000002a5b of 37 bytes Section #6 '.idata': 0x0000000000003a26 of 22 bytes Section #7 '.CRT': 0x000000000000420b of 21 bytes Section #9 '/4': 0x0000000000004649 of 23 bytes Section #10 '/19': 0x0000000000004cbe of 10 bytes Section #12 '/45': 0x000000000003e2fc of 5 bytes Section #13 '/57': 0x0000000000041019 of 8 bytes Section #15 '/81': 0x0000000000043c33 of 44 bytes Section #16 '/92': 0x0000000000045509 of 23 bytes inject - A cereja do bolo ?
Esta é a operação que injeta o código. Você pode usar a opção -f para especificar o arquivo contendo o código a ser injetado, que seria um raw binary. Onde esse arquivo deve conter apenas as instruções de código de máquina a ser injetado, como um shellcode por exemplo.
Opcionalmente, você pode usar a opção -s para especificar o número da seção que você quer injetar o código. Se a opção não for especificada, por padrão o pei vai injetar onde houver mais espaço disponível.
$ pei -f my_code.bin inject executavel.exe Writed code of 12 bytes on offset 0x0000000000043924 of section #15 '/81' Após o código injetado, o pei insere um jump absoluto para o entry point original do executável, fazendo com que após o código injetado ser executado o fluxo de execução do programa continue normalmente.
Outra modificação que o pei faz é desabilitar o dynamic base do executável, para evitar que o jump aponte para o endereço errado.
Dica: Injetando Código Muito Grande
Se você precisar injetar um código maior do que o espaço disponível apontado pela operação zeros, você pode dividir o código a ser injetado em várias partes e injetar cada parte por vez, seguindo a ordem da última até a primeira parte. Isso funciona porque o pei irá adicionar um jump no final do código para o endereço que está sendo indicado como entry point. Se você já injetou código antes, esse endereço é o endereço do código anteriormente injetado. ?
Dessa forma você pode fazer um chain de saltos da primeira parte até a última e então saltar para o entry point original. Exemplo:
$ pei inject -f parte-3.bin executavel.exe Writed code of 87 bytes on offset 0x0000000000043833 of section #15 '/81' $ pei inject -f parte-2.bin executavel.exe Writed code of 80 bytes on offset 0x0000000000044924 of section #11 '/23' $ pei inject -f parte-1.bin executavel.exe Writed code of 32 bytes on offset 0x0000000000401a15 of section #1 '.text' Isso irá criar a seguinte ordem de execução: parte-1.bin -> parte-2.bin -> parte-3.bin -> entry point. Onde as setas “->” representam os saltos.
diff
Esta operação exibe diferenças entre dois executáveis. Ela compara cada campo dos cabeçalhos do executável e o conteúdo das seções, e depois exibe estas diferenças no terminal. Você pode usar a opção -c (ou --color) para que a saída da operação seja colorida:

Em vermelho são os valores no executável original que foram modificados e em verde são os valores novos no executável modificado.
patch
Essa operação lê um texto de entrada no mesmo formato que a saída da operação diff e replica as mesmas modificações no executável. Exemplo:
$ pei patch executavel2.exe diff-output.txt Caso você não especifique um patch file, o pei irá ler de stdin. Assim, é possível que você faça um pipe entre uma operação de diff e patch:
$ pei diff original.exe mod.exe | pei patch outro.exe A diferença de fazer isto e simplesmente fazer uma cópia do executável modificado, é que a ideia é replicar somente as diferenças entre os executáveis. Quaisquer outros campos não são tocados pela operação patch, o que te permite salvar alterações e replicá-las por meio de um script. ?
Se você quiser, também é possível escrever um patch file manualmente, bastando imitar a saída de diff. Uma dica é que os valores dos campos originais (em vermelho) não são necessários para a operação patch, então você pode inserir somente os valores novos. Seguindo o print da saída do diff que utilizamos agora pouco como exemplo:
optional.entry_point xxx 0x4ca33 section.0.name xxx .code section.15.characteristics xxx 0x62100040 // @O texto não faz diferença, só importa o @ no início da linha // Daqui para baixo qualquer linha que não inicie com + será ignorada. +0x43830 00 30 9f 61 62 63 0a b8 f0 14 40 00 ff e0 00 00 // O formato é: +0xoffset bytes em hexadecimal Nota: Onde há o “xxx” seriam os campos dos valores originais (em vermelho) que não são lidos pelo pei porém são necessários para imitar a saída da operação diff. Você pode colocar qualquer valor aí que não dará erro já que são ignorados.
Você pode usar a operação patch em scripts para automatizar várias modificações em um executável ao mesmo tempo, inclusive em bytes de um determinado offset.
  • 2,903 views
Neste artigo analisaremos alguns tópicos sobre o tema tanto sob a ótica do desenvolvedor, quanto do hacker, comparando os desafios e técnicas referentes ao desenvolvimento de cheats para jogos online e offline.
Jogos single player
Do ponto de vista do desenvolvedor a integridade de um jogo single player raramente apresenta um problema para o produto e a comunidade. Devido à ausência de interação entre os jogadores, hacks pouco podem fazer além de obter feitos incríveis como concluir o jogo em tempos recordes ou desbloquear todas as conquistas para que o jogador possa exibir-se para seus amigos. Às vezes é até mesmo desejável pelo desenvolvedor que os jogadores comprometam a integridade do jogo através da criação de Mods,  por exemplo, pois eles expandem e renovam o jogo, criando formas completamente diferentes de jogá-lo.
Do ponto de vista do profissional de segurança, jogos são uma maneira incrível de testar o seu potencial de engenharia reversa e até mesmo programação, quando você reescreve alguma seção crítica do game, por exemplo.
Scanners de memória
Sem dúvida jogos são programas complexos. Além de toda a lógica de gameplay o programa deve conter as instruções necessárias para renderizar itens na tela, gerenciar dispositivos de som, receber input do usuário, além de diversas outras funções comuns presentes nas engines como pathfinding, sistema de partículas, gerenciamento de animações, etc. Fazer a engenharia reversa de um jogo tentando compreender cada etapa do programa até encontrar as seções relacionadas à lógica de jogo seria como procurar uma agulha no palheiro.
Por este motivo desenvolveram-se ferramentas para inverter esse processo. Com a ajuda de Scanners de memória, a partir de elementos visuais do jogo como vida ou dinheiro, é possível descobrir o valor da variável desejada. Isso permite ao hacker alterar aquele valor ou usar aquela variável (quem acessa esse endereço?) para identificar seções de código relacionadas, criando exploits ainda mais complexos.
O processo de scan por valor funciona em três etapas:
Escaneia-se um valor conhecido, como vida, dinheiro, pontuação, etc; Altera-se essa variável dentro do jogo para que ela mude de valor; Repete-se as etapas 1 e 2 até obter um número pequeno de possíveis endereços. Ao fim do processo, é possível trocar o valor da variável dentro do jogo, obtendo vantagens como grandes quantias de vida ou dinheiro.
O scan de memória funciona buscando todos os endereços de memória do processo que contém aquele exato valor. Quando o segundo scan é feito, ele utiliza a primeira lista de resultado e remove os endereços que não se adequam ao valor atual. Ao fim do processo, sobram bons palpites para o real endereço da variável:
Exemplo de Scan de memória usando Cheat engine
Scan por proximidade
Esta técnica se baseia a partir da relação de proximidade espacial entre as variáveis. Por exemplo, se em um jogo o personagem possuir uma barra de vida cujo valor exato é incerto, uma estratégia possível é buscar por algum valor conhecido associado e após encontrar aquele valor olhar em suas proximidades para checar se a variável vida está lá. Isso ocorre porque na maioria das vezes os programadores armazenam os dados de cada elemento do jogo em objetos ou structs, que são contínuos na memória:
Struct player{ Char nome[50]; Int dinheiro; float vida ; float vidaMax ; Imagem *sprite; }  
Exemplo de Scan por proximidade usando Cheat engine
Fuzzing
As vezes vale a pena alterar uma região de memória próxima a variáveis importantes e observar se algo mudou dentro do jogo. Apesar de diversas vezes resultar em crashes, às vezes pode render resultados interessantes,descobrindo listas ou IDs de itens:
Exemplo de Fuzzing obtendo os endereços da lista de parceiros e desbloqueando personagens não jogáveis
Quem acessa esse endereço? e Injeção de código
Na maioria dos debuggers é possível adicionar um breakpoint de dado, que trava a execução do programa quando uma instrução faz uma leitura ou uma escrita naquele endereço. Isso pode ser muito útil para encontrar funções relacionadas à aquela variável.
Em alguns casos além de apenas alterar o valor de variáveis é interessante alterar também trechos de código para que o programa se comporte diferente. Para isso, uma técnica usada para alterar dinamicamente o código em execução é o code hooking. Esta técnica consiste em substituir uma instrução por um jump para uma região de memória não utilizada ou alocada pelo debugger. Nessa região, além do código removido dar espaço ao jump, podemos adicionar novas instruções. Ao fim do hook ele retorna para o endereço subsequente ao pulo:

Esquema de mapa de memória e execução durante um hook
Em um dos casos que encontrei tratava-se de um jogo onde a pontuação mudava dinamicamente a cada nível, com isto, apesar do valor da variável da pontuação ser conhecida, trocar manualmente seu valor era desconfortável.
Portanto, adicionando um breakpoint de dado no valor da pontuação encontrei a função que checa se a pontuação foi atingida a cada quadro:
1.mov eax,[ecx+000000A0] ;Pontuação atual lida 2.mov ecx,[ecx+000000A4] ;Pontuação meta lida 3.cmp eax,ecx ; compara os pontos atuais com a meta 4.<--je 05AE94C6 5.mov eax,[ebp+08] 6.mov eax,[eax+000000A8] 7.test eax,eax 8.jne 05AE9C7F ;Todo este trecho é pulado se não atingirmos a meta 9.mov eax,[ebp+08] 10.movzx eax,byte ptr [eax+7C] 11.test eax,eax 12.jne 05AE9C7F 13.-->mov eax,[ebp+08] 14.mov eax,[eax+28] ;resto da função 15.mov ecx,eax ... Apesar de ser muito difícil interpretar o que toda a função faz, apenas as 4 primeiras instruções são importantes.
Se a pontuação atual for diferente do objetivo, o trecho do meio responsável por passar de fase não roda. Eu tentei remover apenas o jump e forçar sua execução mas o jogo trava, provavelmente devido a uma comparação futura com a pontuação. Portanto, a ideia é roubar o fluxo de execução logo antes da comparação da instrução 3 ser executada.
Para descobrir o endereço de onde deve ser injetado o pulo é possível fazer um scan por Array of bytes. No caso buscamos o padrão das instruções acima. Essa é uma maneira consistente de manter o cheat funcionando mesmo após atualizações que modifiquem o binário, pois mesmo que o endereço da função mude, dificilmente suas instruções serão alteradas
O Cheat Engine em especial permite escrever um script que realiza essa busca e aplica o hook. Quando o script está ativo ele aplica os patches necessários, enquanto está desativado ele reverte as alterações para o código original. Dessa forma foi simples criar um botão de auto win para qualquer estágio do jogo:
aobscan(INJECT,8B 89 A4 00 00 00 3B C1 74) ;sequência de instruções a ser buscada alloc(newmem,$1000) [ENABLE] code: mov eax,[ecx+000000A4] ;Instrução original de leitura - lê a meta mov [ecx+000000A0],eax ;copia a meta para a pontuação atual mov ecx,eax ;e também para o registrador onde está a pontuação atual jmp return ;volta para a execução normal de código  
Exemplo de injeção de código criando um botão de auto win
Exemplo de injeção de código alterando o comportamento dos inimigos para curar o jogador
Online multiplayer
Enquanto nos jogos single player o uso de cheats traz pouco ou nenhum problema para os demais jogadores, quando se trata de multiplayer online, sobretudo dos jogos mais competitivos, cheats podem se tornar um pesadelo quando jogadores passam a obter vantagens absurdas sobre os demais, criando um ambiente injusto e frustrante dentro da partida.
Felizmente as engines que suportam multiplayer já são pensadas com a segurança em mente, mitigando a maioria das técnicas discutidas na seção sobre single player.
Modelo de cliente servidor
O modelo de cliente servidor assume que existe uma máquina na rede que possui autoridade sobre as demais máquinas, máquina esta chamada de servidor. O servidor também contém um único estado do jogo que é replicado para os demais clientes. Toda a interação entre jogadores deve passar pelo servidor, já que os clientes não possuem nenhuma conexão entre si. Com isto, é tarefa do servidor validar todos os inputs recebidos antes de replicá-los. Existem duas formas de interação servidor-cliente, são elas Remote Procedure Calls (RPCs) e Replicação de Variáveis.
RPCs são um pedido feito para outra máquina rodar um código específico e estes pedidos podem ser feitos pelo servidor ou pelo cliente. Por exemplo, se o jogador 1 de um jogo tiro deseja atirar, os outros clientes devem ser capazes de observar que o disparo foi realizado. Para isso ele deve solicitar ao servidor que realize um disparo usando uma RPC. Em seguida, o servidor deve solicitar aos demais jogadores que repliquem esse disparo, partindo da cópia que cada um deles tem do jogador 1:

Exemplo de código multiplayer para FPS implementado na Unreal Engine 4
A segunda forma de comunicação é a replicação de variáveis. Diferente de RPCs, a replicação ocorre apenas do servidor para o cliente. No exemplo acima a função dar dano altera (server side) a variável "vidaDoPersonagem". Quando isso acontece, o servidor envia um pacote atualizando o seu valor para todos os clientes. Portanto, nenhum cliente possui de fato a variável vida mas uma cópia dela. Por isso as técnicas de alteração de variáveis discutidas na seção de jogos offline raramente funcionam em multiplayers. Embora seja possível alterar essa cópia, o estado do jogo no servidor permanece intacto, então mesmo que você tenha 1000 de vida em sua cópia, quando você morrer no servidor, ainda ocorrerá uma RPC para a sua máquina e para os outros jogadores avisando que você morreu.
Tradeoff entre jogabilidade e segurança - O problema do input
Sempre que o servidor recebe uma atualização do cliente ele deve checar se a ação feita pelo jogador é válida, como por exemplo se o jogador ainda possui munição antes de atirar, se a arma não está em recarga, etc. Todavia, algumas ações são praticamente impossíveis de serem verificadas. Um servidor poderia verificar por exemplo todo o input feito pelo jogador, enviando cada botão pressionado para que o servidor calcule coisas como posição ou ângulo da câmera, porém devido à latência de rede e o tempo de processamento isso significaria uma jogabilidade travada e lenta. A menos que o jogo seja lento como uma partida de xadrez, o servidor precisa confiar parcialmente no input dado pelo usuário. Por isso, cheats como speedhacks e aimbots são tão comuns. Posição e orientação de onde o jogador está olhando são basicamente controladas pelo cliente.
Algumas engines como a Unreal conseguem mitigar os efeitos de possíveis speed hacks calculando em server side a posição que o personagem deveria estar e aplicando as devidas correções no cliente. Essas correções também servem para sincronizar o cliente com o servidor em situações de dessincronia como durante perdas de pacotes:
Exemplo de correção de posicionamento pelo servidor na Unreal Engine 4
Padrões inseguros de programação para multiplayer
Não validar corretamente a entrada de RPCs; No caso acima um cliente não íntegro poderia passar um valor elevado como parâmetro, dando danos absurdos aos adversários; Não validar o tempo entre RPCs; O jogador não deve atirar milhares de vezes por segundo, mas não tem nada no servidor que impeça isso; Não replicar as variáveis necessárias; O desenvolvedor não deve confiar ao cliente variáveis importantes como vida ou munição.
Exemplo de código multiplayer vulnerável
RCEs e ataques a outros jogadores
Quando um desenvolvedor publica um jogo ele está distribuindo software que vai rodar no computador de diversos usuários. Como qualquer outro programa, ele é suscetível à ataques clássicos de corrupção de memória como buffer overflow, Use After Free, etc, que podem permitir execução remota de código para outros atacantes que possam interagir com o programa. Esse foi o caso do CVE-2019-15943, onde no Counter-Strike: Global Offensive (CSGO) um jogador poderia convidar outro player para uma partida utilizando um mapa customizado. O mapa customizado era criado especificamente para fazer o jogo crashar, corrompendo a cadeia SHE e potencialmente permitindo a execução de código na máquina do usuário que entrou na partida.
Acessibilidade ao código, Unity e Among Us
Há alguns meses atrás eu estava curioso para ver como o game Among Us implementa algumas coisas. Uma breve pesquisa no google revela que o jogo foi desenvolvido usando a Unity Engine. Saber em qual engine o programa é produzido é útil na hora de olhar pelos arquivos do game durante uma análise estática. A Unity utiliza C# como linguagem de programação, gerando arquivos .NET (quando no Windows) que são facilmente decompilados e editáveis por ferramentas como dnSpy, tornando o processo de modificação e engenharia reversa bem simples. No entando, atualmente a Unity suporta também o uso do IL2cpp, um conversor de linguagem intermediária (IL) para C++ antes de gerar o pacote do projeto. Isso gera benefícios como performance para o jogo e também dificulta o acesso ao código.
Entretanto, o processo de engenharia reversa não é impossível. Utilizando a ferramenta IL2cppDumper é possível extrair a partir dos arquivos de metadados os offsets de cada função do GameAssembly.dll, bem como seus respectivos nomes. Vasculhando pelas funções foi possível encontrar a rotina que gerencia o cooldown do botão de matar quando o jogador é o impostor:

Método responsável pelo cooldown do botão, decompilado pelo ghidra

Código assembly referente ao método SetTimeKiller
Aqui vemos que a função recebe o tempo como parâmetro e seu primeiro uso é escrevê-lo em um dos atributos do objeto. Um hack interessante e bem crítico seria transformar qualquer tempo de cooldown em 0.

Código assembly após o patch
Troquei a instrução xmm0,[ebp +0xc] que lia o argumento da pilha por um simples xor eax,eax, zerando o registrador.
No lugar de movss [esi+0x44],xmm0 basta mover o valor de eax, como se todo o argumento fosse 0. Com apenas duas instruções alteradas, o exploit funcionou bem mesmo em sessões online.
O servidor não checava o tempo entre as RPCs para matar o jogador, permitindo assim abusos por clientes modificados e eventualmente frustrando os demais jogadores depois de morrerem em partidas de 10 segundos ou menos.


 
Método após o patch, decompilado pelo Ghidra
Referências
https://www.youtube.com/watch?v=GxthbWfSak8 https://docs.unrealengine.com/en-US/InteractiveExperiences/Networking/Server/index.html https://docs.unrealengine.com/en-US/InteractiveExperiences/Networking/CharacterMovementComponent/index.html
  • 4,450 views
As 3 vulnerabilidades que serão explicadas neste artigo foram reportadas para o MSFT, para o pessoal interno do Xbox Brasil e Microsoft Games, porém mesmo depois de mais de 6 meses lutando pela correção das vulnerabilidades não houve interesse da Microsoft em corrigi-las. Por esse motivo decidi publicá-las.
Nota: Existem mais vulnerabilidades como essas que o MSFT informou não ter impacto que também serão publicadas em breve.
Depois de bypassar os meios de pagamento e de assinar os serviços de assinatura do Gamepass através da Sandbox dos desenvolvedores da Microsoft, encontrei outras 3 falhas de lógica nos processos de pagamento da Microsoft.
Vulnerabilidade 1 - Entendendo a vulnerabilidade que permite gerar notas fiscais dos jogos de Xbox sem comprá-los e a possibilidade de lucrar através do Nota Fiscal Paulista:
Este bug permite que um agente malicioso gere Notas Fiscais de jogos do Xbox mesmo que a compra não seja realizada. Essa vulnerabilidade pode ser usada por um golpista para ganhar dinheiro através de perdas financeiras da Microsoft, pois existe a possibilidade do consumidor resgatar parte do valor pago em imposto através do programa chamado “Nota Fiscal Paulista”.
Como a Microsoft Store gera NFs (Notas Fiscais) sem nenhum tipo de controle, é possível tentar realizar a compra de jogos em pré-venda sem pagar por eles e mesmo quando as licenças dos jogos são revogadas por falta de pagamento, o golpista receberá a fatura do jogo como se tivesse realizado a compra. Com isso, o Xbox pagará o valor do imposto do jogo vendido e o golpista poderá lucrar até 30% do valor do ICMS (Imposto sobre Circulação de Mercadorias e Serviços).
Reproduzindo a falha
Primeiro precisamos lembrar de uma das primeiras vulnerabilidades que reportei a Microsoft, na qual era possível comprar os jogos da Microsoft Store sem pagar por eles. Reproduzindo a primeira vulnerabilidade:
Registramos um cartão válido na nossa conta do Xbox Live ou MSA. Cancelamos e bloqueamos o cartão de crédito no banco emissor. Também podemos utilizar uma conta do paypal sem nenhum cartão de crédito cadastrado ou um cartão de crédito pré-pago. Agora com cartão inválido, conta do paypal ou cartão pré-pago podemos comprar os jogos em pré-venda. Como a cobrança também pode ser realizada 10 dias antes do lançamento, conseguimos baixar os jogos sem termos o valor do jogo debitado. A tentativa de débito do cartão registrado será realizada 10 dias antes do lançamento do jogo e você poderá receber várias notificações informando sobre a falha no pagamento. Nesse momento é que era encontrada a primeira falha, pois a licença do produto não era revogada. Agora com a correção da vulnerabilidade e com a licença revogada, não teremos acesso ao produto, porém o restante dos processos financeiros continuarão independente do pagamento ser realizado ou não. Com isto, quando a Microsoft revogar a licença do jogo, podemos ir até a nossa conta MSA e notar que receberemos a nota fiscal do produto sem pagar por ele.
Entendendo como ganhar dinheiro com a falha
O agente malicioso usa o bug para obter várias faturas sem pagar pelos jogos. Exemplo: o invasor compra R$ 10.000 em jogos usando várias contas MSA. A Microsoft vai gerar a Nota Fiscal e pagar R$ 1.700 para a cidade de São Paulo (a taxa é de mais ou menos 17%.). Mesmo que o pagamento dos jogos não seja realizado, o agente malicioso recebe as Notas fiscais após ter as licenças dos jogos revogadas pela Microsoft. O golpista entra no site https://www.nfp.fazenda.sp.gov.br/login.aspx e resgata R$ 510 do seu CPF (ou CPFs de laranja). Com isso em mente, podemos criar várias contas e dessa maneira comprar o mesmo jogo várias vezes: 1 conta MSA comprando R$ 10.000 irá resgatar R$ 510 com o Nota Fiscal Paulista; 10 conta MSA comprando R$ 10.000 irá resgatar R$ 5,100 com o Nota Fiscal Paulista; Obs.: o salário mínimo no Brasil é de R $ 1.100. O golpista pode atingir quase 5 vezes essa quantidade aplicando golpes na Microsoft.
PoC



Vulnerabilidade 2 - Entendendo a vulnerabilidade que permite adicionar dinheiro infinito na carteira da Microsoft Store:
Continuando a mesma reprodução do bug anterior: quando recebemos as Notas Fiscais dos jogos comprados, mas não recebemos o jogo comprado.
Existe uma janela que você poderá jogar os jogos comprados com o bug de 1-3 dias depois do seu lançamento. Depois disso a licença do jogo será revogada e você não poderá jogá-lo mais.
Com isso, há algum processo que faz a validação para identificar se o usuário que recebeu a nota fiscal também recebeu o jogo. Caso o usuário mesmo depois de receber a Nota Fiscal ainda não tenha recebido o jogo, o valor da compra será estornado para o cartão de crédito cadastrado. No entanto, como não temos cartão válido para o estorno, não recebemos o valor de volta.
Para amenizar toda essa confusão com a compra e para agradar seu cliente, a Microsoft envia um “mimo” de R$ 27,00, mas o que acontece se reproduzirmos esse mesmo bug várias vezes seguidas? Bom, eis a resposta:
PoC





Sendo assim é possível pacientemente executar várias vezes esse processo para receber dinheiro de reembolso.
Um agente malicioso poderia criar várias contas, adicionar dinheiro na carteira dessas contas e revender os jogos da loja. Como existe a opção de comprar o jogo como presente, os jogos poderiam ser enviados a qualquer usuário do Xbox Live.
Outro ponto importante é que não existe segregação nas lojas da Microsoft Store e Xbox. Sendo assim, caso seja adicionado um bom valor à conta do usuário, além dos jogos este usuário poderá comprar licenças do Windows, Office 365, mouses, notebooks, etc.
Vulnerabilidade 3 - Entendendo a vulnerabilidade que permite comprar jogos versão Ultimate/Deluxe/Definitiva e pagar apenas pelo jogo Standard:
Ainda seguindo a mesma lógica dos bugs reportados anteriormente... Além das duas vulnerabilidades acima, reportei a vulnerabilidade que permite que eu possa comprar jogos standard e usufruir da versão Ultimate. Como seria isso?
Reproduzindo a falha
Siga os mesmos passos que usamos para gerar as notas fiscais sem pagar pelo jogo. A diferença é que nesse caso devemos escolher a versão mais cara do jogo (lembrando que esse bug irá funcionar para jogos em pré order). Realizando o teste no jogo Watch Dogs Legion no qual a edição Standard custa R$ 279,95 e a edição Ultimate custa R$ 459.95. Compramos o jogo edição Ultimate com um cartão inválido e quando este jogo for lançado teremos a licença revogada. Se tentarmos jogá-lo depois do seu lançamento receberemos a mensagem que o jogo deve ser comprado novamente (até aí tudo bem). O problema é que a Xbox revoga apenas a licença do jogo e não a licença dos conteúdos premium, Season Pass, DLCs, etc. Nesse caso, apesar de não termos mais a licença do jogo base, ainda temos a licença de todo o resto do conteúdo premium.
Agora para usufruir do jogo edição Ultimate (valor R$ 459,95), precisamos apenas comprar a versão standard do jogo pagando os R$ 279,95. Isso nos dá uma economia e prejuízo para a Microsoft/Ubisoft de R$ 180,00.
Referências
https://github.com/smarlonfabiano/xbox_xpl https://twitter.com/astrounder
https://www.linkedin.com/in/marlonfabiano/
https://portal.fazenda.sp.gov.br/servicos/nfp https://portal.fazenda.sp.gov.br/servicos/nfp/Paginas/Como-é-feito-o-cálculo-do-crédito.aspx https://epocanegocios.globo.com/colunas/Financas-de-Bolso/noticia/2017/01/como-ganhar-dinheiro-com-nota-fiscal-paulista.html
  • 2,998 views
91% dos crimes virtuais tem como vetor de ataque inicial o e-mail. Os anos vão passando e os atacantes aprimoram suas técnicas, fazendo o máximo possível para que um e-mail com conteúdo malicioso chegue à caixa de entrada de sua vítima.
No final do ano passado, recebi uma mensagem suspeita para análise e compartilho com vocês através deste artigo o desfecho dessa história.
Introdução
A análise foi iniciada após o recebimento de um e-mail que foi reportado por um grupo de usuários como suspeito. A mensagem, em português,  tem conteúdo que faz referência a uma suposta nota fiscal que estaria acessível através de um anexo.
No campo From, observo uma tentativa clara de simular uma troca interna de mensagens já que onde deveria ser exibida o nome do remetente está um endereço de e-mail, com o mesmo domínio do destinatário, e ao lado podemos visualizar o e-mail que de fato foi utilizado para o envio da mensagem.

Neste momento, já existiam evidências suficientes para contestar a veracidade da mensagem. Segui com a análise pois notei algo que me chamou atenção.
Submeti os hashes dos arquivos, tanto o compactado quanto o que foi extraído, para uma consulta no VirusTotal (VT) e fiquei surpreso ao perceber que não existiam registros de análises anteriores.
Pois bem, isso foi o suficiente para estimular a minha curiosidade e tentar entender se neste caso tratava-se de um ataque direcionado ou se eram apenas arquivos circulando por aí.
Análise Estática
Segui então com a análise do e-mail e capturei para consulta o IP e domínio do servidor de e-mail que  foi utilizado como relay para o envio da mensagem e também o suposto IP de origem da mensagem:


Fiz uma breve pesquisa sobre estes indicadores e notei que o domínio inepaca[.]net é bem antigo e com ótima reputação, já o IP de origem é de um serviço de internet doméstica na Itália o que levanta a suspeita de que esta mensagem veio de uma máquina previamente comprometida e parte da botnet:

Esses fatores contribuem para que a mensagem passe pelos filtros de segurança e chegue até a caixa de entrada do usuário, cenário que fortalece a importância de um processo de conscientização em segurança da informação.
Após descompactar o arquivo anexo, tive acesso a outro arquivo com a extensão .docx que quando executado com o Microsoft Word automaticamente já era exibido em Modo Protegido. É comum a utilização de documentos do Microsoft Office como mecanismo para download da ameaça principal já que estes apoiam a estratégia de engenharia social utilizada pelos atacantes, simulando arquivos legítimos que normalmente circulam nas empresas, e possibilitam a execução de códigos VBA (Visual Basic for Applications).
Analisando o documento observei a presença de macros AutoOpen. Esse tipo de macro visa a execução de diversas instruções em background quando o documento for  aberto.
Por padrão, as versões mais recentes do Microsoft Word já desabilita a execução de macros junto a inicialização dos documentos, solicitando ao usuário que habilite a execução deste tipo de conteúdo alertando sobre o risco em potencial.
Para tentar contornar esta proteção, os atacantes geralmente incluem uma imagem no corpo documento solicitando a habilitação do conteúdo e neste caso não foi diferente:

Ao abrir os recursos de desenvolvedor, que deve ser habilitado customizando a guia de opções dentro do Microsoft Word, tive acesso ao editor de Visual Basic e localizei dois módulos e um deles era bem extenso e com várias funções e nomes que mais pareciam palavras geradas aleatoriamente. Neste momento fiquei na dúvida se aquele código era válido ou se estava ali somente para gerar confusão, dificultando a análise.
Passei alguns dias analisando o código e ainda não havia chegado a um entendimento completo sobre o resultado de sua execução. Neste momento o leitor pode se perguntar por que o binário não foi submetido para análise em uma sandbox. Naquele momento eu ainda não tinha indícios suficientes de que não se tratava de um ataque direcionado.
Olhando as propriedades do arquivo notei que o arquivo estava protegido para edição, uma restrição simples, sem senha, que após um clique o arquivo estava liberado. O que poderia ter no conteúdo do arquivo ou na imagem que eu não pude notar?
Luz! Pressionei CTRL + T para selecionar  o conteúdo do arquivo  e notei que a seleção destacou algumas linhas de texto logo abaixo da imagem:

Como as letras estavam brancas, coloquei a cor preta e aumentei a fonte. Pronto! O “segredo” foi revelado:

Naveguei até o final do conteúdo e desconfiei de que provavelmente o texto, na verdade, era uma string base64 ofuscada com conteúdo irrelevante inserido de forma intencional:

As coisas começaram a fazer sentido. Nas imagens é possível perceber que os caracteres qq)(s2) se repetem por todo o texto. Apenas removendo os caracteres que se repetem já percebemos que se trata de um script em Powershell:

Continuei o tratamento da string utilizando a ferramenta Cyberchef, extraindo algumas URLs do código. A string foi ofuscada em pelo menos três camadas e ao final codificada em base64:

Fiz uma pesquisa em cima das URL’s e, com base nas informações obtidas, pude ter uma noção melhor da infraestrutura utilizada. Nesse momento percebi que poderia iniciar uma análise dinâmica, já que a probabilidade de um ataque direcionado foi bastante reduzida.
Análise dinâmica
O binário foi submetido à plataforma Any.Run e de cara já percebemos que a ferramenta detecta a presença da ameaça EMOTET:

Com a execução do binário pude complementar a análise estática do arquivo, verificando que a macro executa o código em PowerShell de forma indireta, através do Windows Management Instrumentation (WMI).
Utilizar recursos nativos do sistema para execução de código é uma técnica que tem sido bastante explorada pelos atacantes na tentativa de evadir mecanismos de detecção que estejam em execução no sistema da vítima.
Continuando a análise, fechei o quebra-cabeça analisando o arquivo PCAP gerado e localizando a URL utilizada para a comunicação e download de um arquivo malicioso:

O arquivo baixado é da extensão  .DLL, submeti o hash deste binário à uma pesquisa e diferente dos arquivos anteriores, encontrei uma análise no VT com um índice de detecção considerável.
O termo Emotet se refere a um trojan bancário que foi utilizado por uma quantidade significativa de mecanismos durante o processo de classificação.
Conclusão
Até o início deste ano podíamos considerar o Emotet como uma das ameaças mais complexas em atividade. No final de janeiro, toda a sua infraestrutura foi desmantelada em uma ação conjunta de empresas privadas e forças policiais de diversos países.
Através de um processo automatizado, o Emotet se propagava em campanhas de phishing ( como a analisada neste artigo) que de várias maneiras tentam induzir as vítimas a executarem os anexos maliciosos.
Após surgir como um banking trojan em meados de 2014 e expandir suas atividades ao longo dos anos, acabou se tornando uma das principais portas de entrada para outras ameaças e ataques de escala global. No ano passado, as campanhas ganharam atenção novamente já que além da própria botnet o malware passou a ser mecanismo de distribuição de outras ameaças.
As técnicas utilizadas pelo atacante e apresentadas neste artigo são observadas em uma série de campanhas que ainda estão em curso. Sendo assim, considerar uma campanha de conscientização para complementar os controles tecnológicos continua sendo uma ótima estratégia, afinal, 91% dos crimes virtuais se iniciam com um e-mail.
Você já conhece o curso de Análise de Malware Online (AMO)? Se não conhece, cola lá no canal do Mente Binária no Youtube e aproveite  a oportunidade de aprender gratuitamente como fazer análises semelhantes às realizadas neste artigo.
Outras referências
https://threats.kaspersky.com/br/threat/Trojan-Banker.Win32.Emotet/ https://www.malwarebytes.com/emotet/
  • 5,535 views
O que é criptografia?
É uma área de estudo que tem foco em transferir dados entre um grupo sem que alguém de fora desse grupo consiga recuperar os dados. Para isso é necessário alguma convenção do grupo, todos eles devem usar um mesmo mecanismo que geralmente envolve algum passo a passo, o que chamamos de algoritmo, e uma chave que apenas as pessoas do grupo podem ter acesso.
Criptografia clássica
Desde muito tempo temos essa necessidade de trocar informações de forma secreta, então antes mesmo de existir computadores já existia a criptografia. Antigamente eram bem comuns esquemas de cifra de substituição, ou seja, um grupo criava um mapa de representação de caracteres, por exemplo o “A” vira “C”, “B” vira “K”, e por aí vai.
Um bom exemplo é a cifra de césar, também conhecida como ROT13, é uma cifra de substituição bem simples que cada caractere é mapeado para uma posição, por exemplo “A” vira “0”, “B” vira “1”, etc. Depois disso era escolhida uma chave, no caso o césar escolheu 13, e todas as posições eram somadas 13, então onde tinha “A” vira a letra na posição 13 que é a letra “N”, “B” vira a da posição 14 ou seja a letra “O”, etc.

Figura 1 - Uma ilustração do mapa e a palavra CESAR ambas com a chave 13
Além da cifra de César, outra bem comum, porém menos antiga, é a cifra de Vigenère que na verdade é uma generalização da cifra de César. Basicamente invés de somar uma chave fixa vamos somar o deslocamento da chave mais do texto simples. Por exemplo, vamos ter uma chave “ACB” e um texto simples “DFG”, então o “D” vira a posição 4 do alfabeto que começa com a letra “A”, o “F” vira a posição 6 do alfabeto que começa com C e por aí vai.
Criptografia moderna
Com a popularização dos computadores a criptografia clássica foi ficando obsoleta porque a capacidade de cálculo do computador é muito alta. Logo, por exemplo,  se uma pessoa interceptar uma cifra feita com o esquema de César fica muito fácil para ela recuperar a informação sem saber a chave, apenas testando todas as chaves possíveis.
A partir dessa fraqueza dos esquemas clássicos surgiu a criptografia moderna que engloba esquemas de chave simétrica, assimétrica e funções hash. A criptografia de chave simétrica funciona com o princípio de que existe uma chave secreta e alguns exemplos de primitivas são AES e Salsa20. Em geral, essas primitivas se baseiam em uma matemática pesada, extensa e difícil que acaba se reduzindo a operações de bit a bit, como XOR e AND. Embora as primitivas já tragam bastante segurança, não é recomendado usar apenas a primitiva e sim uma implementação dela, como por exemplo AES-256 que usa a primitiva do AES porém com uma chave de 256 bits. Além disso, no caso de cifras em blocos, também é interessante utilizar algum modo de operação, que é essencialmente como cada bloco vai ser processado a cada passo da encriptação.

Figura 2 - Criptografia com chave simétrica
Além da criptografia de chave simétrica, também existe a de chave assimétrica que se baseia em uma chave pública, que pode ser conhecida por qualquer um, e uma privada, que é secreta. As primitivas desse tipo são baseadas em uma função armadilha, que é uma função na qual é muito fácil realizar uma operação de transformação mas é muito difícil realizar o inverso. Um bom exemplo para esse tipo é o RSA, as funções armadilhas do RSA são a de logaritmo discreto e a fatoração de um número.

Figura 3 - Criptografia com chave assimétrica
Além desses esquemas também vieram as funções hash, que são funções que transformam um dado em um conjunto de bits de forma que aquele dado seja único, para garantir unicidade na verdade se parte do princípio de que com uma probabilidade muito baixa dois dados vão ter o mesmo conjunto de bits, então sempre pode acontecer de ter a chamada colisão que é quando dois dados geram a mesma hash. Exemplos de funções hash são MD5 e SHA1, que  estão depreciadas pois já foram encontradas formas de gerar colisão, e SHA2 que até agora está bem.
Criptografia pós quântica
Na década de 90, mais precisamente entre 1995 e 1996, surgiram dois algoritmos, um deles, o algoritmo de Shor, diz que é possível inverter uma função armadilha na qual grande parte dos esquemas assimétricos são estruturados e o outro, algoritmo de Grover, descreve uma forma de diminuir o tempo da busca pela chave secreta de esquemas simétricos. Porém ambos algoritmos precisam de um computador quântico que ainda não foi desenvolvido, com a chegada desses algoritmos a comunidade científica que pesquisa criptografia viu que era necessário criar esquemas baseados em problemas que nem computadores quânticos conseguissem quebrar, e aí surgiu a criptografia pós quântica.
Só para sabermos a proporção, ter esses algoritmos significa que qualquer um com acesso a um computador quântico forte o suficiente vai conseguir ver qualquer dado que uma pessoa troca com a maioria dos sites, seja cartão de crédito, endereço, senhas, isso porque a maioria dos sites usa um esquema junto do protocolo TLS que é “quebrável” com o algoritmo de Shor.
Embora os algoritmos existam há bastante tempo, somente em 2016 começaram a planejar uma padronização assim como fizeram com AES e RSA. O National Institute of Standards and Technology ou NIST organizou alguns rounds em que pesquisadores pudessem compartilhar esquemas que pudessem ser usados como primitivas para uma troca segura de informações. Os esquemas nesses rounds são divididos em algumas funções de armadilha que possuem muitos casos de borda e acabam sendo bem fáceis de resolver nesses casos, então para criar um esquema bom é preciso bastante conhecimento no problema matemático que ele é baseado, além de conhecimento de ataques.
Como transferir/armazenar dados de forma segura?
Transferir dados de forma segura parece uma tarefa difícil depois de ler um pouco sobre criptografia pós quântica, porém ainda não sabemos quando um computador quântico bom o suficiente vai ser criado.
Para transferir dados encriptados em um protocolo de texto usamos algum tipo de encoding. Encoding é só uma tradução do dado de uma convenção para outra, os mais comuns que consigo lembrar são codificação base64 e hexadecimal. Lembrando que encoding não é considerado criptografia pois não envolve nenhuma chave e nem funções de uma via, como nos hashes.
Seguem algumas boas práticas para troca e armazenamento de dados:
Nunca implemente um esquema criptográfico. Sempre use bibliotecas de preferência com código aberto, a não ser que você saiba realmente o que está fazendo.
Nunca armazene senhas em texto simples. É recomendado armazenar hashes de senhas.
Sempre verifique a integridade do arquivo que foi transferido, seja pela hash ou verificando alguma assinatura.
Sempre troque chaves secretas com algum mecanismo seguro de troca de chaves.
Para se manter atualizado sobre o estado da arte da criptografia é interessante ver blogs de pesquisadores e publicações científicas. Muitos pesquisadores publicam no ePrint da iacr onde também tem várias palestras gratuitas. Outras apresentações muito boas são da conferência Chaos Computer Club, onde grandes pesquisadores montam palestras com conteúdo muito bom e didático. Se você curte uns desafios, também tem o site CryptoHack, que tem vários desafios bem interessantes de RSA, criptografia de curvas elípticas, AES, desafios matemáticos e até alguns de hashing.
  • 2,599 views
Muito se fala sobre um vazamento recente de dados que expôs as informações pessoais de mais de 220 milhões de brasileiros, incluindo nomes completos, datas de nascimento, CPF, diversos dados financeiros e de crédito, além de dados de 104 milhões de veículos e de 40 milhões de empresas. Os dados de 223.739.215 CPFs incluem até 37 tipos de registros (informações distintas) associados à eles.
Rapidamente, já surgiram alguns sites para pesquisar se um determinado CPF está na base vazada (spoiler alert: é óbvio que está!), como o FuiVazado.com.br e uma página criada pelo pessoal do Rio Hacker Maker Space: https://news.riohms.com.br/vazou/ - mas aparentemente estes sites foram retirados do ar.

Vale lembrar que esse não é o primeiro mega-vazamento de dados de Brasileiros, e infelizmente não será o último. Veja alguns casos bem recentes que foram divulgados na imprensa:
Setembro/2020 - Detran PR expõe cópia de seu banco de dados com credenciais de administradores; Novembro/2020 - Vazamento de senha [do Ministério da Saúde] expõe dados de [16 milhões de pacientes] casos suspeitos confirmados de Covid-19; Dezembro/2020 - Nova falha do Ministério da Saúde expõe dados pessoais de mais de 200 milhões de brasileiros; Janeiro/2021 - Falha no site do Detran-RS expôs RG e CNH de 5,1 milhões de motoristas; Janeiro/2021 - Detran expõe dados de milhares de motoristas multados; Fevereiro/2021 - Hacker põe à venda na dark web 270 milhões de registros da Dataprev; Além desses casos recentes de vazamentos ou exposição de dados online já sabemos que há décadas os criminosos vendem CDs com bases de dados vazados - em São Paulo isso pode ser encontrado a venda nos camelôs da Rua Santa Ifigênia, tradicional ponto comercial das lojas de eletrônicos no centro da cidade.
Ou seja, engana-se quem tinha alguma expectativa de proteção e privacidade de nossos dados pessoais. Apenas como comparação, os 430 maiores incidentes de vazamento de dados em 2019 somaram mais de 22 bilhões de credenciais vazadas - mais do que 3 vezes a população da Terra.

Se você desconfia que suas informações pessoais foram roubadas ou vazadas, prepare-se para uma potencial dor de cabeça caso comecem a usar seus dados para fraudes. Na prática, infelizmente, há pouco o que possa ser feito para prevenir que isso aconteça. Mesmo assim, veja abaixo oito dicas:
Descubra que seus dados foram vazados e se estão sendo utilizados por fraudadores: Talvez você só descubra isso da pior forma, quando chegar uma cobrança de uma compra ou financiamento que nunca fez. Mas existem alguns ações e serviços gratuitos e pagos que podem te ajudar a descobrir se seus dados vazaram, como por exemplo:
Monitore seu extrato bancário e suas transações de cartão de crédito com frequência e fique atento a cobranças, compras ou saques estranhos. Nesses casos, entre em contato com o seu banco imediatamente. Melhor ainda: alguns bancos permitem que você receba um alerta por SMS toda vez que realizar uma transação;
Have I Been Pwned: esse site, gratuito, te avisa se o seu e-mail e senha constam em algum dos principais vazamentos de dados conhecidos;
Registrato: este serviço do Banco Central permite consultar um relatório de dívidas, operações de câmbio e informações sobre contas em bancos, inclusive dados sobre chaves do Pix. É útil para identificar se fraudadores criaram contas de laranja em seu nome;
Cadastro Pré: nesse site é possível verificar se as operadoras de celular tem alguma linha telefônica pré-paga ativa em nome do seu CPF;
Serasa: possuem alguns serviços para pesquisar sobre seus dados, como o "Limpa Nome" (gratuito, mas precisa de cadastro) que permite consulta se tem alguma dívida associada ao seu CPF e o AntiFraude (pago), que promete monitorar o seu CPF, e-mail, celular e passaporte em sites da Dark Web.
Determine quais informações foram comprometidas: O segredo é saber de onde seus dados foram roubados e exatamente o que aconteceu com a empresa que sofreu o vazamento. Se você souber qual empresa ou serviço foi a origem do vazamento de dados, o próximo passo é avaliar o seu potencial risco, baseado nas informações que vazaram (ex: CPF, telefone, endereço, dados de familiares, etc) - mas se esses detalhes não forem fornecidos, faça sua própria avaliação de tudo que você compartilhou com essa empresa e, por segurança, suponha que todos os seus dados naquela empresa ou site foram comprometidos. Se a origem do vazamento não for conhecido, tente identificar pelo menos quais informações foram expostas;
Revise e proteja todas as senhas: Aproveite a oportunidade e o susto para revisar as suas senhas. Veja algumas dicas importantes:
Crie senhas novas e fortes e evite reutilizar qualquer uma delas;
Use uma senha única para cada conta de login e faça com que ela seja a mais segura possível (pelo menos nos sites e serviços que você usa com mais frequência);
Use um software ou aplicativo gerenciador de senhas para facilitar o uso e armazenamento delas, pois esse tipo de app permite criar senhas complexas e armazenar de forma segura as senhas dos sites que utiliza;
Nunca use dados pessoais como senhas ou como parte de suas senhas. Não use datas de nascimento nem datas comemorativas, nem nomes e apelidos, e nem informações sobre seus hobbies e gostos pessoais;
O mais importante: use autenticação de 2 fatores sempre que possível, e lembre-se de ativar a senha do seu WhatsApp (a "Verificação em Duas Etapas") - veja aqui no FAQ do WhatsApp como fazer;
Cuidado com links em e-mails ou mensagens de texto: Os cibercriminosos usam dados pessoais vazados para criar mensagens de phishing direcionadas e mais convincentes, por isso, tenha sempre atenção redobrada para qualquer mensagem inesperada ou suspeita. Tome cuidado também se você receber algum e-mail ou mensagem de texto que diz estar relacionado a algum vazamento de dados, e tome mais cuidado ainda se a mensagem fornecer um link para clicar! Ou melhor: Não clique! Entre em contato direto com a empresa, por telefone ou e-mail, para obter informações sobre o vazamento de seus dados;
Em caso de roubo de dados de cartão de crédito ou débito, entre em contato imediatamente com o banco: Se há a suspeita de que seus números de cartão de crédito ou débito foram vazados, entre em contato com seu banco imediatamente para cancelar o cartão e solicitar um novo. Alguns aplicativos bancários permitem que você bloqueie temporariamente o seu cartão, então faça isso antes mesmo de ligar para o banco! Está sem sinal telefônico, ou sem celular? Vá até um caixa eletrônico e veja se consegue bloquear seu cartão, pois muitos caixas permitem fazer isso mesmo sem a posse do cartão do banco. Aproveite e troque também a senha do seu cartão;
Em caso de vazamento do seu documento de identidade ou carteira de habilitação, faça um boletim de ocorrência: Caso o ataque tenha atingido um banco de dados em que você cadastrou a foto de algum documento pessoal, faça um boletim de ocorrência (B.O.) na delegacia mais próxima. Em alguns estados podemos fazer o B.O. online, pela Internet;
Revise as suas configurações de privacidade nas redes sociais, para limitar as informações que expõe publicamente;
Desconfie sempre e seja paranoico: Como seus dados pessoais foram vazados, fraudadores podem tentar entrar em contato com você por telefone ou pelas redes sociais para obter mais informações ou conseguir acesso a sua conta no WhatsApp ou em bancos. Por isso, sempre desconfie se receber ligações de pessoas pedindo seus dados ou informações sobre suas contas e senhas, mesmo que elas se identifiquem como funcionários de seu banco ou de uma empresa que você conheça. Nunca informe seu CPF, nome completo nem suas senhas por telefone. Se tiver dúvidas, desligue o telefone e ligue diretamente para a central de atendimento da empresa que supostamente está solicitando suas informações.
 
Lembre-se que todo cuidado é pouco! Afinal, esses vazamentos podem ser utilizados pelos criminosos para roubar a identidade de suas vítimas e realizar vários tipos de golpes, como abrir conta corrente em nome de outras pessoas, pegar empréstimos e financiamentos bancários, etc. De fato, segundo uma pesquisa realizada em 2020 pela PSafe, 1 em cada 5 brasileiros já foi vítima de roubo de identidade na Internet ?.
Em tempo: se você teve oportunidade de acessar a uma base de dados pessoais vazada, resista a tentação, pois você pode estar cometendo um crime, previsto em lei. O uso dessas informações para fins ilícitos pode representar o crime de falsidade ideológica (artigo 299 do Código Penal), e a simples posse dessa base pode ferir a Lei Geral de Proteção de Dados Pessoais (LGPD), pois não houve o consentimento dos titulares de dados para o seu uso e compartilhamento.
  • 3,333 views
Olá, pessoal!
Primeiramente, gostaria de agradecer a parceria do Mente Binária com nosso time, o ELT, visando trazer conteúdo de qualidade pra comunidade, principalmente pra quem está começando no mundo dos CTFs e na área de segurança da informação em geral. Nosso objetivo é ajudar a fortalecer as bases, pois com uma base sólida, o player consegue ir longe tanto nas competições quanto na carreira profissional.
Neste primeiro artigo, vamos falar um pouco sobre o que são CTFs, por que acreditamos que as pessoas deveriam jogar e qual a melhor maneira de começar.
 
O que são CTFs?
CTF significa Capture The Flag, e nessas competições o objetivo do player é resolver desafios de computação, normalmente ligados diretamente à Segurança da Informação, e obter flags, que são códigos que confirmam que a pessoa resolveu um desafio, e permite que ela pontue no Dashboard (placar). A pontuação varia de acordo com a dificuldade de cada desafio.
Atualmente existem três grandes formatos de CTF: os Jeopardy-style, Attack&Defense e os que são Pentest-based. Nesse artigo focaremos nos Jeopardy, que são a especialidade do nosso time.
O formato da flag varia de evento pra evento, mas em geral elas possuem um prefixo escolhido pelo organizador. Veja alguns exemplos: 
Genérico: flag{alguma_string_aqui} Pwn2Win CTF: CTF-BR{alguma_string_aqui} DEF CON CTF: OOO{alguma_string_aqui}  
CTFs Jeopardy-style
Nas competições Jeopardy, são apresentadas diversas categorias de desafios aos participantes, que variam de evento pra evento, ficando a cargo da organização decidir quais categorias serão exploradas. Essa escolha é baseada nas habilidades dos membros que criarão os desafios, chamados de challenges. Esses eventos normalmente ocorrem aos finais de semana, e têm duração de 24, 36 ou 48 horas consecutivas, sem intervalo. Os times que participam dessas competições podem ter quantos membros quiserem, a não ser que a competição limite o número de participantes (o que é bem raro).
O CTFTime.org é o site que contém o ranking internacional dos CTFs Jeopardy e Attack&Defense, o perfil dos times e players, e os eventos que estão por vir (Upcoming). Ele também centraliza os write-ups das equipes, que são os tutoriais feitos após as competições. É uma forma de ver as diversas abordagens que levaram à solução de cada equipe, e aprender bastante com isso.
Atualmente, as categorias de desafios que são praticamente obrigatórias pra qualquer evento, são:
 
Pwning / Binary Exploitation
É entregue um binário (um programa executável) com alguma vulnerabilidade aos times, e eles tem que fazer engenharia reversa, achar a vulnerabilidade, e criar um exploit pra esse programa, que tem uma cópia rodando como serviço (daemon) em um servidor remoto,. O objetivo é explorar este serviço, visando conseguir ler a flag. As mais diversas técnicas e falhas envolvendo corrupção de memória são exploradas, bem como o bypass de todas as mitigações existentes.
 
Cryptography
Desafios que exploram ataques criptográficos, como explorar algoritmos deficientes (com bugs) ou implementados incorretamente, além de técnicas diversas contra algoritmos e implementações existentes que não são seguras ou podem de alguma forma ser subvertidos.
 
Miscellaneous
Aqui, os criadores podem usar toda sua criatividade e colocar desafios de computação que não se encaixam necessariamente nas outras categorias. Alguns eventos colocam aqui desafios de programação, também chamados de PPC (Professional Programming and Coding), de Análise Forense, Redes, etc, apesar dessas categorias também poderem ser apresentadas separadamente.
 
Reverse Engineering
O objetivo é fazer engenharia reversa de um binário, que pode ter sido escrito em qualquer linguagem e compilado pra qualquer arquitetura, entender seu funcionamento e extrair a flag. Às vezes podem envolver análise de malwares.
 
Web Hacking
Nessa categoria, os players exploram aplicações web com as mais diversas vulnerabilidades. O código-fonte pode ou não ser disponibilizado.
 
Também existem outras categorias, que são mais raras, como Hardware Hacking e Eletrônica (sempre presentes no Pwn2Win - evento internacional que nosso time organiza). Há também a possibilidade de um desafio englobar mais de uma categoria, como Reverse Engineering + Cryptography, Web Hacking + Cryptography, etc.
 
Por que jogar?
Apesar da participação nesse tipo de competição poder ser realizada de forma individual, é difícil ser competitivo jogando sozinho, já que as competições exigem habilidades multidisciplinares, devido à variedade de categorias. Seguem alguns aspectos que talvez motive você a começar a jogar:
É uma ótima maneira pra aprender técnicas novas e aplicar em programas de Bug Bounty ou na realização de Pentests. Fortalece muito o trabalho em equipe, característica que é essencial para o mercado de trabalho atual. Faz as pessoas pensarem fora da caixa pra tentar bypassar as restrições impostas pelos criadores dos desafios. Eventualmente, sua equipe pode encontrar 0days, já que o brainstorm pra resolução dos desafios entre os membros é grande. Times competitivos podem conhecer outros países/culturas e ganhar boas premiações em dinheiro. Por exemplo, o ELT já foi pro Japão duas vezes: em 2018 e 2019. CTFs abrem diversas portas para oportunidades de emprego e criam um networking muito bom. Recrutadores bem instruídos sabem o valor de alguém que é um bom CTF Player. A rivalidade saudável entre os times é algo que pode impulsionar seu aprendizado e a sua evolução intelectual e técnica.  
Como começar?
Podemos dividir o início em três fases:
 
Fase 1 - Base teórica e pré-requisitos
Você precisa dar uma olhada nas categorias, talvez ler alguns write-ups, e decidir com qual categoria tem mais afinidade. Foco é importante pra conseguir evoluir. Não atire em várias categorias ao mesmo tempo!
Escolhida a categoria, leia bastante e adquira os conhecimentos fundamentais sobre a área, pra não chegar cru nos desafios. Além de leitura, outra opção é ver vídeos de boas fontes no YouTube. O próprio canal Papo Binário (aqui do Mente Binária) tem vários cursos e vídeos avulsos que podem ajudá-lo a adquirir uma boa base, principalmente se você está mirando em Reversing ou Pwning.
Algo que é válido para praticamente todas as categorias, é aprender alguma linguagem de programação versátil, como o Python. Além disso, conhecimentos básicos em Redes e Sistemas Operacionais farão toda a diferença, independente do caminho que você decida seguir!
A grande máxima aqui é que jogar CTFs é uma ferramenta para lapidar o conhecimento existente e elevar você a outros níveis. Aprender uma categoria do zero apenas jogando não é um caminho muito interessante, e que com certeza vai deixar várias lacunas no aprendizado.
 
Fase 2 - Começando a brincar
A melhor maneira de colocar a mão na massa, quando decidir que está pronto, é através de sites de Wargames, que são "CTFs" que funcionam 24/7. Algumas indicações de sites com desafios bons:
ringzer0team.com - Desafios de todas as categorias. root-me.org - Desafios de todas as categorias. Possui material de apoio. cryptohack.org - Desafios específicos de criptografia. pwnable.xyz - Desafios específicos de pwning.  
Fase 3 - Hora da ação
Após ter adquirido a base e treinado nos sites propostos, convide alguns amigos, veja a lista de CTFs que estão por vir no CTFTime.org, e comece a praticar nas competições que não tem rating alto, que são normalmente mais fáceis. Um ponto negativo é que elas podem ter desafios que envolvem adivinhação (guessing), caso os organizadores sejam inexperientes. Nesse caso, apenas ignore esses.
 
Links Úteis
Links úteis que podem ajudá-lo na sua jornada:
CTFTime Write-ups - Repositório de Write-ups do CTFTime.
CTF-BR Docs - Compilação de links interessantes, como palestras, artigos e outros recursos.
ELT Write-ups: CTFTime Profile Page (links para os write-ups na parte inferior) e GitHub.
Canal LiverOverflow - Vários vídeos muito bem produzidos relacionados à conteúdo técnico de CTF e infosec em geral. Em inglês.

A Comunidade Brasileira
No Brasil, desde 2014, o CTF-BR tem tentado unir e fortalecer os times, bem como ajudar na formação de novas equipes e no crescimento dos players. Para ficar mais próximo e interagir com o pessoal, veja as redes sociais do projeto.
 
É isso pessoal, espero que tenham gostado e se interessado pelo maravilhoso mundo dos CTFs, e qualquer dúvida pode ser tirada pelo Telegram, via IRC (gnx @ freenode), comentando aqui no artigo ou nos grupos do CTF-BR que podem ser vistos no link acima. Vale também assistir o seguinte vídeo, onde complemento este artigo:
Até a próxima! \o/
  • 7,709 views
Os compiladores são ferramentas muito úteis e importantes no mundo da programação e desenvolvimento. A função básica dos compiladores é pegar uma linguagem de "alto nível" (linguagem com maior nível de abstração do hardware) e produzir um código semanticamente equivalente em "baixo nível". A velocidade de execução do código compilado é uma vantagem que se destaca, tendo em vista que o compilador faz otimizações no processo de compilação. Verificações de erros sintáticos e semânticos são outras funcionalidades também executadas pelo compilador.
Por que criar um compilador?
Além dos motivos mencionados anteriormente, a forma mais simples e rápida de entender como os compiladores funcionam é criando um. Neste tutorial iremos criar um compilador simples, porém abordando os principais conceitos da compilação de forma teórica e prática.
Para seguir esse tutorial será necessário o conhecimento de algoritmo e no mínimo uma linguagem de programação. Neste artigo estarei utilizando a linguagem C.
Antes de começarmos a criação do projeto, vamos organizar o nosso projeto:
Criaremos uma linguagem que trabalha com números inteiros e reais; Utilizaremos condições (if, else, etc); Utilizaremos expressões aritméticas e relacionais; Etapas da compilação
As etapas que um compilador executa são: Análise léxica, Análise sintática, análise semântica, otimizador de código e gerador de código objeto. Alguns compiladores tem uma estrutura bem mais complexa, dependendo da linguagem a ser compilada:

Nosso projeto terá as seguintes etapas: análise léxica, análise sintática, análise semântica e gerador de código. O gerador de código vai gerar um bytecode para uma máquina virtual que também vamos implementar. Bytecodes são instruções para uma máquina virtual, como mover um valor para a memória ou para um registrador, por exemplo. Abaixo podemos ver um trecho de código em Python e seus respectivos bytecodes:
def soma(): print(10 + 10)  
0 LOAD_GLOBAL 0 (print) 2 LOAD_CONST 1 (20) 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 0 (None) 10 RETURN_VALUE No final desta série estaremos executando o seguinte código:
INIT VAR max := 10 VAR num INPUT num IF (num < max) INIT PRINT 0 END ELSE INIT PRINT 1 END END Análise Léxica
A análise léxica consiste em pegar cada caractere de uma linguagem e identificar os padrões da linguagem. Exemplo:
int a = 10 Aqui podemos identificar os seguintes padrões:
int é uma palavra reservada do compilador; a é um identificador/variável; = é um sinal de atribuição; 10 é um número inteiro; Ao realizar esse processo estamos identificando os lexemas, que são pedaços de uma string (texto), reconhecidos pelo analisador léxico. Os tokens são um par constituído de um nome e um valor de atributo, sendo este último opcional:
<tipo, valor> Onde:
tipo como o nome já diz seria o tipo do token. valor é o valor de um token. Alguns tokens não utilizam este campo. Representação da análise léxica:

Para uma entrada como VAR num := 100 + 10 obtemos os seguintes tokens:
<PC_VAR> <ID, num> <OP_ATR> <T_INT, 100> <OP_MAIS> <T_INT, 10> Onde:
<PC_VAR> representa a palavra chave VAR; <ID, num> representa um identificador (variável ou função) tendo o valor num; <OP_ART> representa o operador de atribuição =; <OP_MAIS> representa o operador aritmético mais (+); <T_INT, 100>, <T_INT, 10> representa um inteiro com o valor 100 e 10 respectivamente; Não se esqueça que os tipos de token são definidos por você!
Usarei o gcc como compilador C e o vscode como editor. Iremos começar de uma forma simples,  melhorando tudo aos poucos, vamos nessa!
Essa é a estrutura de pastas do nosso projeto. Temos uma pasta para os headers, uma pasta src para o código fonte e a pasta exe, que terá o executável:

Escreva o texto seguinte no arquivo teste.txt:
INIT PRINT 1 + 2 * 3 END
include/lex.h - Aqui simplesmente criamos um módulo para tratar da análise léxica e definimos a função que retorna um token:
#ifndef art_lex_h #define art_lex_h void proximo_token(); #endif src/lex.c: Esta é nossa função inicial que lê cada caractere e mostra na console. Se o caractere for EOF, significa que não há mais caracteres no arquivo (fim de arquivo) e então paramos o loop:
#include <string.h> #include <ctype.h> #include "glob.h" #include "lex.h" // variável que passará por cada caractere do arquivo static int c; void proximo_token() { while (1) { c = fgetc(file); if (c == EOF) break; else printf("%c", c); } } includes/glob.h: Este outro arquivo serve para algumas definições globais (que vamos usar em mais de um arquivo). Definimos os tipos dos tokens, um enum para representar o token e uma struct com os campos tipo e val:
#ifndef art_glob_h #define art_glob_h #include <stdio.h> #include <stdlib.h> FILE *file; // linha atual static int linha = 1; // tipos de tokens enum { // palavras chave PC_INIT, PC_END, PC_PRINT, PC_INPUT, PC_VAR, PC_IF, PC_ELSE, // numeros T_INT, // operadores OP_MAIS, OP_MENOS, OP_MULT, OP_DIVI, // ( ) := < > <= >= = T_LPARENT, T_RPARENT, T_ATRIB, T_MENOR, T_MAIOR, T_MENOR_I, T_MAIOR_I, T_IGUAL, // identificador ID }; typedef struct { int tipo; int val; } Token; Token tok; #endif src/main.c: Na função main iremos tentar abrir um arquivo. Caso haja algum erro o programa sairá mostrando a mensagem de erro. Caso contrário, leremos todos os caracteres do arquivo teste.txt. Vamos ver se funciona:
#include <stdlib.h> #include "lex.h" #include "glob.h" int main(int argc, char *argv[]) { // abrir o arquivo file = fopen(argv[1], "r"); if (file == NULL) { printf("Erro ao abrir o arquivo"); exit(EXIT_FAILURE); } proximo_token(); fclose(file); return EXIT_SUCCESS; // ou return 0 } Para facilitar o processo de compilação usaremos o seguinte Makefile:
all: gcc -c src/lex.c -I includes -o exe/lex.o gcc src/main.c exe/*.o -I includes -o exe/main rm -r exe/*.o *Se você estiver em um ambiente Windows saiba que o comando rm -r exe/*.o  não funcionará.
Ao executar o Makefile teremos na pasta exe o arquivo compilado. Ao executarmos teremos a seguinte saída:
INIT PRINT 1 + 2 * 3 END Perfeito! Por agora vamos ignorar espaços em branco, tabulação e quebra de linha.
Criaremos agora uma função que vai criar um token. Por enquanto ela irá apenas mostrar na saída algo como <’+’, 0> <’INIT’, 0>, mas depois vamos mudar isso.
lex.c: Aqui estamos somando 1 na variável linha para uso posterior em caso de nosso compilador ache um caractere que não existe em nossa linguagem (como um “$”, por exemplo):
void makeToken(char *nome, int val) // mostrar o token { printf("<%s, %d>", nome, val); } void voltaPonteiro() // volta um caracter se necessário { if (c != EOF) fseek(file, ftell(file)-1, SEEK_SET); } void proximo_token() { // após o if else if (c == ' ' || c == '\t') continue; else if (c == '\n') { linha++; continue; } } No código acima temos uma função voltaPonteiro, que é responsável por voltar um caractere no arquivo. Em alguns casos vamos ter que ver o caractere a frente e depois voltar o caractere quando estivermos analisando uma palavra chave. Enquanto o caractere for alfanumérico o ponteiro avança.
Para facilitar o entendimento vamos utilizar a imagem abaixo como exemplo. Aqui reconhecemos a palavra num e paramos no caractere =, ou seja, reconhecemos o token <ID, num>. Quando vamos continuar o processo iniciamos do =, isto é, o próximo caractere é o espaço, seguido do número 1 e assim por diante. Tendo em vista que = é um caractere diferente do que estaríamos esperando iremos esquece-lo e então voltaremos um caractere parando assim no m.

lex.c: vamos reconhecer operadores aritméticos como mais (+), menos (-), multiplicação (*) e divisão (/):
void proximo_token() { // codigo anterior else if (c == '+') makeToken("+", 0); else if (c == '-') makeToken("-", 0); else if (c == '*') makeToken("*", 0); else if (c == '/') makeToken("/", 0); // codigo else Ao compilar o código e executar teremos algo como:
$ ./exe/main.exe teste.txt INITPRINT1<+, 0>2<*, 0>3END lex.c: Agora vamos reconhecer os demais números, palavras, parênteses, etc:
else if (c == '+') { makeToken("+", 0); } else if (c == '-') { makeToken("-", 0); } else if (c == '*'){ makeToken("*", 0); } else if (c == '/') { makeToken("/", 0); } else if (c == '(') { makeToken("(", 0); } else if (c == ')') { makeToken(")", 0); } else if (c == ':') { c = fgetc(file); // pega o próximo caractere if (c == '=') // se for '=' sabemos que é o token ':=' makeToken(":=", 0); } else if (c == '<') { c = fgetc(file); // pega o próximo caractere if (c == '=') // se for '=' sabemos que é o token '<=' makeToken("<=", 0); else makeToken("<", 0); } else if (c == '>') { c = fgetc(file); if (c == '=') makeToken(">=", 0); else makeToken(">", 0); } else if (c == '=') { makeToken("=", 0); } else if (isdigit(c)) { numero(); } else if (isalpha(c)) { palavra(); } else { printf("O caracter '%c' na linha %d nao reconhecido.\n", c, linha); exit(EXIT_FAILURE); } lex.c: Temos duas novas funções, são elas palavra e numero:
void palavra() { char palavra[100] = ""; int pos = 0; while (isalnum(c)) { palavra[pos++] = c; c = fgetc(file); } voltaPonteiro(); if (strcmp(palavra, "INIT") == 0) makeToken("INIT", 0); else if (strcmp(palavra, "PRINT") == 0) makeToken("PRINT", 0); else if (strcmp(palavra, "INPUT") == 0) makeToken("INPUT", 0); else if (strcmp(palavra, "VAR") == 0) makeToken("VAR", 0); else if (strcmp(palavra, "IF") == 0) makeToken("IF", 0); else if (strcmp(palavra, "ELSE") == 0) makeToken("ELSE", 0); else if (strcmp(palavra, "END") == 0) makeToken("END", 0); else makeToken("ID", 0); } Não é a função mais otimizada que você já viu, mas funciona:
void numero() { int k = 0; while (isdigit(c)) { k = k * 10 + c - '0'; c = fgetc(file); } voltaPonteiro(); makeToken("T_INT", k); } Testamos o código agora:
$ ./exe/main teste.txt <INIT, 0><PRINT, 0><T_INT, 1><+, 0><T_INT, 2><*, 0><T_INT, 3><END, 0> Olha só, reconhecemos a maior parte dos tokens de nossa linguagem! Agora que tal mais um teste utilizando outro teste.txt?
INIT VAR max := 10 VAR num INPUT num IF (num < max) INIT PRINT 0 END ELSE INIT PRINT 1 END END  
$ ./exe/main teste.txt <INIT, 0><VAR, 0><END, 0><:=, 0><=, 0><T_INT, 10><VAR, 0><END, 0><INPUT, 0><END, 0><IF, 0> <(, 0><END, 0><<, 0><END, 0><), 0><INIT, 0><PRINT, 0><T_INT, 0><END, 0><ELSE, 0><INIT, 0> <PRINT, 0><T_INT, 1><END, 0><END, 0> Na próxima parte vamos fazer algumas alterações no analisador léxico e depois daremos início ao analisador sintático. Até lá. ?
  • 14,918 views
Passei um tempo usando o Windows 10 depois de anos em Linux e macOS. Logo percebi dificuldades específicas com shell, instalação de programas, etc. Mas também percebi que é possível deixar o Windows muito próximo, no quesito de usabilidade, de um Linux com shell completo ou macOS. Neste artigo vou mostrar as ferramentas e configurações que mais me ajudaram. Boa leitura! ?
 
Gerenciador de pacotes
Vou começar falando dele por um motivo muito simples: outros programas podem ser instalados com ele. Pois é, parece que a Microsoft aprendeu com as distros Linux e projetos como o Chocolatey, já que finalmente anunciou seu gerenciador de pacotes, o winget. Para instalar, baixe o arquivo com extensão .appxbundle na área de releases do repositório do projeto no Github. Após isso você pode abrir qualquer terminal (Command Prompt / cmd.exe ou Powershell) e digitar winget pra ver seu novo gerenciador de pacotes:

Vamos buscar por exemplo os programas que que tenham a palavra "security" em seu nome ou descrição, para testar:

Qualquer um desses programas pode ser instalado com um simples "winget install". Faça o teste com o Notepad++ por exemplo:
winget install notepad++ Os comandos list (listar os programas instalados) e upgrade (atualizar os programas instalados) ainda são experimentais, mas você pode habilitá-los da seguinte maneira:
Digite winget settings no terminal. Um arquivo settings.json se abrirá. Nele, adicione: "experimentalFeatures": { "list" : true, "upgrade" : true }, O meu aqui tá assim:

A lista de programas disponíveis está aumentando cada vez mais, já que o winget é de código aberto. ?
 
Terminal decente
Mas na real, a primeira coisa de que senti falta foi um terminal decente. Graças a Deus encontrei o Windows Terminal. Para instalar com o winget (não se assuste - dá pra usar a tecla Tab para autocompletar):
winget install Microsoft.WindowsTerminalPreview Aí olha só quem chega na festa:

Este terminal concentra tudo em um: Prompt de Comando, Powershell e Linux (rodando via WSL), e é multi-abas. Claro que tá longe de um Terminator ou iTerm2, mas também tá longe de um cmd.exe né? Deus me livre e guarde! kkkk
O novo Windows Terminal também tem uma configuração baseada em JSON e uns recursos interessantes como a divisão automática da janela do terminal. Ao apertar Alt+Shitft+D, olha o que acontece:

terminal_split.mp4 A divisão da janela (split) funciona para janelas com qualquer shell.
 
Hashes
Eu sempre curti um software pra Windows chamado TotalHash, que adiciona os hashes de arquivos em suas propriedades, mas este infelizmente tem dois problemas: não é livre nem suporta plugins (logo, não consigo extender para suportar os hashes que preciso), é pago e não é scriptável. Resolvi isso com o cmdlet Get-FieHash do Powershell. Por padrão ele exibe o SHA-256 de um arquivo, mas há várias opções, como se pode observar na imagem:

 
Sniffer
Por essa nem o futuro esperava! O Windows 10 agora tem um sniffer nativo: o pktmon. Se liga:

E sim, ele suporta filtros. Por exemplo, para logar somente pacotes IPv4 na porta 80/tcp:
pktmon filter add HttpPuro -d ipv4 -t tcp -p 80 Você pode adicionar mais de um filtro se quiser e com qualquer nome (aqui chamei o meu de HttpPuro). Depois, é só iniciar a captura pedindo para logar os eventos:
pktmon start --etw Para parar a captura, o comando é o stop:
pktmon stop Por padrão o pktmon cria um arquivo chamado PktMon.etl num formato próprio. Este pode ser convertido para texto:
pktmon format PktMon.etl -o log.txt Ou para o formato PCAP-NG, suportado pelo Wireshark e outros analisadores de pacotes:
pktmon pcapng PktMon.etl -o capture.pcap Há várias outras opções que valem serem exploradas. É de fato um sniffer completo.
Em breve vou falar mais sobre as seguintes ferramentas:
PowerToys Sysinternals O novo diskusage.exe. WSL 2.0. Por hora é isso. Conforme eu encontre coisas novas, vou adicionando aqui. Se souber de alguma novidade ou algo que ache que precise ser citado aqui, por favor comente aí embaixo!
Espero que seja útil para quem precisa usar o Windows por algum tempo, por qualquer motivo. ?
  • 8,168 views
Se você é da área de Segurança da Informação ou simplesmente tem interesse pelo assunto já deve ter notado que todo dia temos notícias de novos malwares surgindo, sejam eles malwares completamente novos ou variantes de um malware já conhecido. Com isto em mente, faz algum tempo que as empresas de segurança, inteligência e até mesmo pesquisadores independentes passaram a buscar métodos de automatizar não só a análise destes malwares, mas também a administração e armazenamento do arquivo em si, suas características e relacionamentos com outros arquivos demais entidades (domínios, campanhas, endereços IP, etc). Obviamente a análise automatizada não substitui a análise humana, mas já é uma ajuda e tanto considerando o número de malwares surgindo diariamente.
Para cada uma destas necessidades descritas anteriormente existe uma ou mais ferramentas/plataformas que podem ser utilizadas para cumprir estes objetivos. Dentre elas estão plataformas de sandboxing  como Hybrid-Analysis e AnyRun, ferramentas de análise estática de arquivos como o DIE (Detect It Easy), pev, yara, capa, e também repositórios de malware como o VirusShare e o Malware Bazaar.
Não podemos negar que todas estas ferramentas/plataformas ajudam e muito no nosso dia a dia, mas ainda assim não conseguiríamos organizar nossas informações e centralizá-las em um único lugar de forma automática, tendo em vista que as as soluções descritas acima são isoladas e não conectam umas com as outras de forma nativa. A plataforma que chegou mais próximo de atingir as quatro exigências (isto é: análise automatizada, administração, armazenamento, relacionamento com demais entidades) foi uma plataforma chamada Virus Total, também conhecido como VT, atualmente administrado pelo Google.
Virus Total
O Virus Total trouxe para a comunidade uma forma simples e rápida de análise de IoCs (Indicator of Compromise) e também uma API bem simples de se utilizar para fins de automação. Dentre as diversas funcionalidades da plataforma estão inclusas análise estática, checagem de assinatura utilizando uma lista gigantesca de Anti-Virus, descrição das características gerais do IoC e comentários da comunidade. Além disso, ele também possui uma versão paga (bem cara por sinal) onde você pode fazer hunting de malwares utilizando regras de Yara, download de arquivos, buscas baseadas em histórico, visualização gráfica e uma API bem mais robusta e permissiva.
É importante deixar claro que o termo IoC não se refere apenas à arquivos e seus hash, mas também à URL, domínios e IP. Ou seja, o VT realmente acaba se tornando uma opção super viável para começar qualquer tipo de investigação.
O cenário atual de Segurança da Informação
Com o passar do tempo não só a comunidade, mas também o mercado de Segurança da Informação no geral passou a notar que a única forma de se posicionar contra os ataques atuais é através de contribuição. Pelo mesmo motivo que gerou a necessidade de se criar formas automatizadas de análise, a contribuição se mostra cada dia mais que necessária pois ela não impõe limites, muito pelo contrário, ela dá liberdade o suficiente para você contribuir da forma que quiser.
Um ótimo exemplo que mostra o exercício da contribuição e o quão valioso isto pode ser é o próprio Linux, que desde sua primeira versão foi liberado para receber contribuições e hoje é um dos maiores projetos existentes na área de tecnologia, com milhares de contribuidores ao redor do mundo.
Com isto em mente, podemos notar uma desvantagem no VT: o espaço para contribuição é limitado.
Desafios
Como já comentado anteriormente, as principais funcionalidades são suportadas apenas na versão paga e infelizmente não são todos que podem pagar pelo valor do serviço.
Um dos principais motivos dessa limitação é fato do código não ser aberto, isto é, estamos presos às funcionalidades que o time do VT disponibiliza. Se o código fosse disponível para a comunidade, resolveríamos tanto o problema monetário quanto a limitação de funcionalidades disponíveis.
Uma outra porta que seria aberta no cenário descrito acima é a seguinte: Imagine que você, sua empresa, seu time ou um grupo de amigos estão com um projeto em mãos que envolve análise, classificação, categorização ou qualquer tipo de manipulação de malware. Com o código em mãos você teria liberdade de fazer a instalação da plataforma localmente ou em um servidor do qual você controla, limitando o acesso à quem você quiser e como quiser.
A comunidade
Tendo estes desafios em mente, a comunidade começou a criar alternativas para resolver alguns problemas encontrados no cenário atual. A ideia do artigo não é de forma alguma dizer que uma plataforma é melhor que outra ou que o Virus Total está errado em trabalhar no modelo que trabalha, muito pelo contrário, o objetivo aqui é mostrar as várias formas que temos de se chegar no mesmo objetivo. Uns mais flexíveis, outros com mais conteúdo disponível, mas todos conseguem te ajudar a chegar no mesmo lugar:
Saferwall: Este é o projeto mais maduro que temos atualmente quando o assunto é análise automatizada e contribuição da comunidade. Robusto e flexível para ser instalado em  diversos ambientes, o Saferwall consegue entregar informações estáticas de arquivos, detecções baseadas em assinaturas de alguns antivírus, identificações de packers e download dos arquivos submetidos anteriormente. Além disso, o Saferwall possui uma plataforma aberta e que aceita colaboração, além de disponibilizar o código para você instalar onde e como bem entender. Dentre as formas de instalação estão inclusas o minikube (indicado para ambientes de testes), em nuvem utilizando AWS e On-Premise.


Freki: O projeto Freki foi criado por uma única pessoa, mas não deixa a desejar quando o assunto é funcionalidade e fácil instalação. Com possibilidade de ser instalado utilizando Docker, este projeto possui não só análise estática dos arquivos PE submetidos, mas também disponibiliza sua própria API e puxa informações do VT para garantir que não falte nada.


Aleph: focando bastante na parte de inteligência, o projeto Aleph entrega para você não só informações estáticas dos arquivos submetidos, mas também análise dinâmica utilizando sandbox, visualização gráfica dos resultados e uma saída em JSON formatada para ser utilizada em backends como Elasticsearch, por exemplo. Além disso, o Aleph também consegue mapear as técnicas utilizadas pelo malware utilizando o MITRE ATT&CK Framework. Eu realmente aconselho você dar uma olhada na palestra da MBConf v3 sobre o Aleph para saber mais sobre o projeto.
 


A tabela à seguir foi criada para facilitar a visualização das funcionalidades descritas acima. É importante deixar claro que a versão do VT utilizada para a criação da tabela é a gratuita:
 
 
VirusTotal
Saferwall
Freki
Aleph
Análise Estática
✔️
✔️
✔️
✔️
Análise Dinâmica
 
X
 
✔️
 
X
 
✔️
 
Suporte à múltiplos SO
✔️
 
✔️
 
X
 
✔️
 
Análise de IoC de rede
✔️
 
X
 
X
 
X
 
Código Aberto
X
 
✔️
 
✔️
 
✔️
 
Download de arquivos
 
X
 
✔️
 
✔️
 
✔️
 
Instalação local
X
 
✔️
 
✔️
 
✔️
 
Controle total do backend
X
 
✔️
 
✔️
 
✔️
 
API
 
✔️
 
✔️
 
✔️
 
X
Como podemos ver, todos estes projetos são de código aberto, o que permite a seus usuários livre contribuição. Caso você tenha interesse em contribuir para alguns desses projetos, aqui vai uma dica: nenhum deles possui ainda análise de URL/IP/domínio de forma isolada, isto é, independente do arquivo. Tenho certeza que uma contribuição deste tipo seria bem vinda. ?
Conclusão
Ajudando estes projetos nós não só melhoramos a ferramenta/plataforma em si, mas ajudamos todos que a utilizam e também construímos um sistema livre e aberto de análise, inteligência e investigação.
Se você é da área ou simplesmente curte contribuir, não deixe de dar uma olhada em cada um destes projetos e, se possível, contribuir com eles. Lembrando que quando falamos de contribuição, não há limites. Pode ser um commit, uma ideia, ajuda monetária ou um simples OBRIGADO aos desenvolvedores e contribuidores por disponibilizarem projetos tão úteis para a comunidade.
  • 5,468 views
A Microsoft possui um programa extenso de bug bounty (programa de pagamento de recompensas a pesquisadores que encontram e reportam falhas de segurança dos produtos da empresa). Já participei algumas vezes e recebi alguns reconhecimentos no portal do MSRC (Microsoft Security Response Center), mas meu primeiro bounty no MSRC derivou de uma falha que identifiquei no método de pagamento da Microsoft. Tal falha permitiu que eu realizasse compra de produtos da loja e não pagasse nada por isso.
Quando reportei a falha ao MSRC, ela não foi aceita de primeira. O time de triagem desacreditou, mesmo com as PoCs (Proof Of Concept - Provas de Conceito) de alguém que dizia: "Ei Microsoft, eu consigo assinar o Xbox Live de graça". ?‍♂️
 
Tive o meu ticket no MSRC encerrado, porém continuei pesquisando e aprimorando a exploração da vulnerabilidade e depois de um tempo abri um novo ticket dizendo: “Ei Microsoft, além de assinar seus serviços, agora eu consigo comprar seus produtos sem pagar nada”.

Prova de conceito da compra dos jogos sem pagar
Depois de muita descrença por parte do MSRC e de muitas compras (cerca de R$ 15.000,00 em produtos) eles responderam: “Ei, fique conosco, pois daremos continuidade à reprodução de seu bug”.
Entendendo a falha
O fluxo de compra da Microsoft é o seguinte:
O cliente, estando logado no Xbox ou na Microsoft Store, procura e seleciona os produtos nos quais tem interesse e ao final da compra solicita o fechamento do carrinho, iniciando processo de pagamento. Microsoft recebe a solicitação da compra dos itens selecionados e os dados para pagamento. O Host de pagamento da Microsoft faz uma requisição para o Gateway de pagamento responsável. O Gateway de pagamento consulta o Adquirente para que ele faça a liquidação da transação. O Adquirente consulta a instituição financeira para saber se o cliente possui crédito suficiente para a transação. O Banco submete a transação às suas regras de validação que, dentre outras coisas, verifica se o cliente possui crédito para concluir a transação e devolve a resposta para o Adquirente. O processo inverso acontece para que a aprovação da compra seja concluída. Entendendo o fluxo de compra, também precisamos saber que a Microsoft possui duas categorias de produtos:
Produtos em release (jogos que já foram lançados) Produtos em pre-order (jogos que serão lançados) A lógica da cobrança dos produtos é a seguinte:
Para produtos já lançados, a validação (checkout realizado pelo gateway) ocorre no momento do cadastro do cartão de crédito e no momento da compra.
Para produtos em pre-order, a validação também ocorre no momento do cadastro e da compra, porém há uma pequena diferença:  
A cobrança de um produto em pre-order ocorre no momento da compra, mas caso não haja limite disponível, haverá nova tentativa em outro momento. No caso, se a primeira cobrança falhar, a segunda cobrança acontece 10 dias antes do produto ser lançado. Sendo assim os passos 2 ao 7 do fluxograma mostrado anteriormente serão executados exatamente 10 dias antes do produto mudar de categoria, de pre-order para  release.
Nesse caso temos a seguinte linha do tempo:
Cadastro do cartão de crédito - Uma consulta ao gateway de pagamento é realizada para confirmação de que os dados inseridos se tratam de um cartão de crédito válido. Compra do produto - A segunda consulta é realizada ao gateway de pagamento, porém como se trata de um produto em pre-order, não existe a obrigatoriedade de pagarmos no ato da compra e temos uma opção implícita de sermos "cobrados" 10 dias antes do release. Entrega do produto - O produto é enviado para o comprador.  Nesse caso, a tentativa da quebra da lógica do pagamento é conseguir acesso ao produto e não pagar antes dos 10 dias do seu lançamento.
Para contornar o pagamento temos que fazer com que nosso cartão de crédito seja um cartão válido no momento do cadastro do método de pagamento e que seja um cartão inválido no momento do débito do valor do produto.
Reproduzindo a falha
Registramos um cartão válido na nossa conta do Xbox Live (primeira consulta ao gateway de pagamento é realizada). Cancelamos e bloqueamos o cartão de crédito no banco emissor. Agora com cartão inválido, podemos comprar os produtos em pré-venda.A segunda consulta ao gateway é realizada, porém como a cobrança pode ser realizada 10 dias antes do lançamento, conseguimos acesso aos produtos sem sermos debitados.  A tentativa de débito do cartão registrado será feita 10 dias antes do lançamento do produto e você poderá receber várias notificações informando sobre a falha no pagamento Keep calm. :). Nesse momento é que encontramos a falha e ela está no estorno da compra, pois a licença do produto não é revogada! Quando o produto for lançado você continuará com a licença normalmente e poderá usufruir dos jogos sem problemas. O vídeo abaixo mostra os jogos que obtive reproduzindo o esquema acima:
Depois de um tempo eu percebi que em alguns casos não é necessário nem mesmo cancelar o cartão de crédito válido.
Outra opção é utilizar um um cartão de crédito pré-pago ou conta do PayPal sem fundos suficientes.
Interessante é que quando comecei a escrever este artigo, não existia nenhum programa de bug bounty ativo para o Xbox One. No entanto, após a aceitação da falha por parte do MSRC, criaram um programa privado para o qual eu fui convidado e, mais recentemente, lançaram um programa público.
Essa falha já foi corrigida, porém existem outras plataformas de e-Commerce e de jogos que podem estar vulneráveis a ataques similares.
Após contactar a Microsoft, eles corrigiram a vulnerabilidade me deram uma ótima recompensa. ??
 
  • 5,556 views
Em todos os sistemas operacionais existem arquivos estruturados. Imagine um bloco segmentado em diversas partes e cada uma sendo uma área que armazena um tipo de dado específico (ex.: cabeçalho, área de código, área de dado inicializado, área de dado estático, área de dado não inicializado, área de referência de definições externas/outros objetos) servindo de referência para resguardar determinada classe de dado do respectivo arquivo binário para serem usados durante a execução do software ou até mesmo para fornecer informações que ajudam no processo de debugging. O conceito dessa formatação do arquivo (file format) é presente em todos os sistemas operacionais populares como Windows e Unix-like – isso inclui o macOS.
Sabendo que o macOS é um sistema operacional do Unix é de se esperar que seus arquivos binários também tenham um “formato”, e estes são conhecidos como “arquivos de objeto do Mac” ou simplesmente Mach-O. Com esse entendimento o propósito deste artigo é dar uma visão técnica geral sobre a estrutura de arquivos construídos com este formato.
 
Por que é importante conhecer o formato Mach-O?
Algumas pessoas acreditam que o sistema operacional macOS (atualmente na versão denominada Catalina) é mais seguro que outros sistemas operacionais existentes pelo fato de não ser afetado por malware. Grande engano! Atualmente, vemos muitas publicações de vulnerabilidades relacionadas ao macOS, o que demonstra que este sistema operacional é, sim, um alvo em potencial.
A grande pergunta que sempre faço é: “O que é mais interessante para um criminoso?”. Neste contexto, por “criminoso” me refiro à qualquer indivíduo que se utiliza dos meios eletrônicos para cometer fraudes. Deixando dispositivos móveis de lado, minha opinião é que hajam duas alternativas principais:
Infectar o maior número de hosts possível (Windows ou Linux); Infectar um número mais restrito de hosts, porém algo mais direcionado a usuários, em geral, de cargos executivos, por exemplo: Diretores, CSO, etc. ou usuários domésticos, que muitas vezes permitem que softwares de fonte desconhecida sejam executados livremente em seu sistema operacional, ao desativar mecanismos de segurança como o gatekeeper; Se eu fosse um criminoso, optaria pela segunda opção; pois, atualmente o MacBook está se tornando cada vez mais popular.
A imagem abaixo nos mostra a grande quantidade de arquivos Mach-O que foram analisados no VirusTotal nos últimos 7 dias desde a escrita deste artigo:

Estes são os tipos de arquivos submetidos ao VirusTotal nos últimos 7 dias, obtidos em 25/julho/2020.
Repare que a imagem não reporta arquivos infectados, mas sim os binários de cada tipo analisados. Bom, é perceptível que Mach-O está ganhando uma certa predominância hoje em dia, embora ainda seja bem inferior ao número do arquivo executável do Windows (Win32.exe).
Apenas a título de curiosidade, o Mach-O tem um formato multi arquitetura, também conhecido como “fat binary” (conforme podemos ver na imagem abaixo)  aonde ele suporta 3 tipos de arquiteturas diferentes: x86_64, i386 e ppc7400:


Aqui temos uma tabela com todos os “Magic Number” (valor numérico de texto usado para identificar um formato de arquivo) referentes à binários do tipo Mach-O:

Ainda nesta linha de pesquisa, a técnica utilizada para gerar um binário suportado com várias plataformas (cross-compiling) é demonstrada na imagem  abaixo utilizando o compilador gcc:

Usando o comando file do macOS vemos o tipo do arquivo e a arquitetura da plataforma que é suportado:

O formato Mach-O de 64-bits
Conforme observado anteriormente os binários Mach-O tem três regiões principais: Cabeçalho (Header); Comandos de carregamento (Load Commands); e, Dados (Data). A imagem abaixo representa a estrutura básica dos arquivos Mach-O 64-bit:

No Header, encontram-se especificações gerais do binário, como seu magic number e a arquitetura alvo. Podemos encontrar este header em /usr/include/mach-o/loader.h:

Conhecendo um pouco mais a estrutura do mach header podemos notar que ela é composta por 8 membros, cada um possuindo 4 bytes, ou seja: 4 * 8 = 32. Podemos ver os primeiros 32 bytes do binário, isto é, os valores do header abaixo:


A região Load Commands especifica a estrutura lógica do arquivo e informações para que o binário possa ser carregado em memória e utilizado pelo sistema. Ela é composta por uma sequência de diversos modelos de commands numa tupla, por exemplo: “[load_command, specific_command_headers]” -- definindo as diferentes “seções lógicas” (commands) do binário. Cada command necessita de um ou mais cabeçalhos específicos, por isso, o segundo membro da tupla (specific_command_headers) pode variar de acordo com o tipo de command da mesma em questão:

A título de exemplo, podemos ver também o command LC_SEGMENT_64  do cabeçalho do binário Mach-O:

Neste mesmo contexto, podemos ver que as bibliotecas dinâmicas (dylib) "libncurses" e "libSystem" foram carregadas nos commands 12 e 13, que pertencem ao cabeçalho LC_LOAD_DYLIB.
Deste jeito, o kernel consegue mapear as informações do executável para um espaço de memória que pode ser acessado simultaneamente por múltiplos programas na finalidade de prover comunicação entre eles ou para evitar compartilhamento de dados supérfluos – tal conceito é conhecido como memória compartilhada:

Podemos ver também que a section __text contém o segmento __TEXT:

E por fim temos a Data, onde temos instruções armazenadas logo após a região LOAD_Commands. Na região Data é que são definidas as permissões de leitura e gravação. Dependendo do tipo de Mach-O a maneira como essa região é usada varia.
Quando analisamos um binário um dos primeiros pontos para o início dos testes é a inspeção do binário em um debugger a partir de seu entrypoint. No caso do deste Mach-O que estamos analisando percebemos que o código é colocado na seção __TEXT, as bibliotecas são carregadas no cabeçalho LC_LOAD_DYLIB e o LC_MAIN é o cabeçalho que aponta para o ponto de entrada (entrypoint) :


Por enquanto já temos uma noção básica da estrutura dos binários Mach-O. Em um próximo artigo, iremos detalhar melhor este binário com foco em engenharia reversa para identificar ações de software malicioso.

Para ajudar, recomendo a você artigos da H2HC Magazine sobre pilhas, registradores etc., dos colegas Fernando Mercês, Ygor da Rocha Parreira, Gabriel Negreiros, Filipe Balestra e Raphael Campos nas edições 7, 8, 9, 10 e 11. Outra referência para auxiliar nesta análise é o artigo "Montando sua máquina virtual para engenharia reversa em macOS"[11].

Até lá!

Referências
Palestra H2HC University Vídeo Demo Malware Keranger Mach-O Vídeo Demo Crackme Mach-O Calling Conventions OS X ABI Mach-O File Format Revista H2HC ed7 Revista H2HC ed8 Revista H2HC ed9 Revista H2HC ed10 Revista H2HC ed11 Montando sua máquina virtual para engenharia reversa em macOS
  • 4,614 views
Já faz um bom tempo (quase 1 ano!) desde o último artigo da série de desenvolvimento de debuggers. Este é o último artigo da série e iremos finalmente criar nosso primeiro prototipo de debugger.
A ideia aqui, é compilar tudo que foi ensinado nos artigos anteriores sobre Sinais, Forks e ptrace . Com isso, criaremos um simples tracer em C que irá receber um endereço como argumento e colocar um breakpoint no mesmo.
Diagrama
Antes vamos definir um pouco o escopo do nosso software:
 

O nosso tracer irá criar um fork e nesse fork será feita a chamada para a execv, que por sua vez irá trocar a imagem do atual processo (seu conteúdo) pela de outro processo, fazendo com que de fato vire outro processo. Já o nosso debugger, dentro de um loop, irá se comunicar via sinais com o processo filho.
Breakpoints
Existem dois tipos de breakpoints: software breakpoints e hardware breakpoints. Ambos servem para interromper a execução do nosso software em determinada instrução. Para que isso seja possível é necessário que a execução do processo seja interrompida na nossa CPU.
Interrupções
Quando ocorre algum evento no computador que precisa de um tratamento imediato, a CPU invoca uma interrupção. Cada evento desse contém uma ação especifica que nosso kernel irá lidar de alguma maneira e a estrutura responsável por salvar os valores e significados das mesmas é a Interrupt Descriptor Table.
 

A imagem acima representa visualmente uma implementação desse vetor, onde cada posição (offset) contém uma struct associada e nela os valores necessários para lidar com isso. Você pode ter uma explicação mais detalhada aqui.
Mas por que eu estou falando de tudo isso? Porque breakpoints nada mais são do que uma interrupção em um dado endereço que faz com que o processador pare a execução do seu programa.
O valor que interrompe a CPU para um breakpoint é o 0x03. Vamos testar isto nesse pequeno bloco de código:
main() { int x = 4; // Iniciando qualquer coisa __asm__( "int $0x03" ); } A macro __asm__ permite que seja colocado o código direto em assembly, nesse caso, foi colocado o mnémonico INT, que cuida das interrupções com o valor 3 (offset comentado acima na IDT). Se você compilar e executar esse programa:
~ ./code zsh: trace trap (core dumped) ./code Nesse momento o trabalho de fazer o handle dessa interrupção é do nosso software. O que fizemos aqui foi implementar um software breakpoint. Agora vamos executar esse programa no gdb e não por breakpoint algum (dentro do gdb) e só executar:
(gdb) r Starting program: /home/zlad/code Program received signal SIGTRAP, Trace/breakpoint trap. 0x000055555555515f in main () (gdb) disas Dump of assembler code for function main: 0x0000555555555139 <+0>: push %rbp 0x000055555555513a <+1>: mov %rsp,%rbp 0x000055555555513d <+4>: sub $0x10,%rsp 0x0000555555555141 <+8>: movl $0x2,-0x4(%rbp) 0x0000555555555148 <+15>: mov -0x4(%rbp),%eax 0x000055555555514b <+18>: mov %eax,%esi 0x000055555555514d <+20>: lea 0xeb0(%rip),%rdi 0x0000555555555154 <+27>: mov $0x0,%eax 0x0000555555555159 <+32>: callq 0x555555555030 <printf@plt> 0x000055555555515e <+37>: int3 => 0x000055555555515f <+38>: mov $0x0,%eax 0x0000555555555164 <+43>: leaveq 0x0000555555555165 <+44>: retq End of assembler dump. (gdb) Veja que a nossa interrupção foi capturada pelo GDB, pois ele detectou um breakpoint trap e é exatamente isso que iremos fazer. Nosso tracer será capaz de detectar quando irá ocorrer um SIGTRAP, ou seja, um sinal que deve ser tratado por nosso sistema operacional.
Finalmente implementando
Vamos finalmente começar o nosso pequeno tracer, que será capaz colocar breakpoints, executar instrução por instrução e imprimir os registradores na tela!
Para inserir a interrupção de breakpoint (int 3) não precisamos de muito, pois já existe um mnemónico para isso que é o int3 e que tem como valor 0xCC. Para inserir breakpoints precisamos de um endereço (que vá ser executado) e uma maneira de escrever nesse local na memória virtual do nosso processo.
Já vimos anteriormente o ptracer e nele sabemos que temos alguns enums que podem ser passados como seu primeiro argumento. São eles o PEEK_DATA e o POKE_DATA, que servem para buscar algo na memória de um processo e escrever algo na memória de um processo, respectivamente. Segue a função que vamos usar para adicionar breakpoints no nosso tracer:
uint64_t add_breakpoint(pid_t pid, uint64_t address) { uint64_t break_opcode = 0xCC; uint64_t code_at = ptrace(PTRACE_PEEKDATA, pid, address, NULL); uint64_t breakpoint_code = (code_at & ~0xFF) | break_opcode; ptrace(PTRACE_POKEDATA, pid, address, breakpoint_code); return code_at; } Respire fundo e vamos em partes, a ideia aqui é a seguinte:
Dado o pid do nosso processo filho e um endereço de memória, vamos buscar o código que estava naquele local (code_at), salvar esse código (não só queremos adicionar um novo opcode, mas podemos futuramente querer executá-lo) e então vamos adicionar nossa instrução nos bytes menos significativos, ou seja, vamos executar ela primeiro.
Usamos aqui uma variável de 64 bits por conta da arquitetura do meu sistema. Se você quiser tornar isto portável, é possível criar uma variável genérica baseada na arquitetura:
#ifdef __i386__ #define myvar uint32_t #else #define myvar uint64_t #endif Isso é opcional, mas caso você queira criar algo mais genérico, esse é o caminho.
A operação bitwise que fizemos aqui também pode ser “nebulosa” para alguns, mas segue o equivalente de maneira mais “verbosa” e em python:
>>> hex(0xdeadbeef & ~0xFF) # Mascarando byte menos significativo '0xdeadbe00' >>> hex(0xdeadbeef & ~0xFF | 0xCC) # Mascarando byte e adicionado opcode int3(0xCC) '0xdeadbecc' O que é feito aqui é uma jogada lógica. Vamos quebrar isso em passos:
Fazemos um AND com algo negado (0xFFFFFF00); Fazemos um OR com o resultado que irá "preencher" o espaço vazio, visto que um valor OR 0 será sempre o valor com conteúdo; No final mascaramos o último byte e colocamos nosso opcode; O nosso loop precisa executar enquanto nosso processo filho estiver sendo debugado. Em termos de estrutura de códigos vamos usar um laço que irá receber uma flag para sua execução:
while (!WIFEXITED(status)) { // Our code } Caso você esteja perdido nessa função WIFEXITED, vale a pena dar uma olhada no artigo desta série sobre Forks. Agora é puramente uma questão de jogar com sinais e estruturar nosso código da maneira mais coesa possível, resumindo, pura programação ?
Após nosso breakpoint ser definido em memória precisamos fazer o handling disso. Para isso usamos a função WSTOPSIG, que irá receber o status do nosso processo (que é atribuído na função wait) e irá nos dizer qual tipo de interrupção ocorreu:
while (!WIFEXITED(status)) { wait(&status); signal = WSTPOPSIG(status); switch(signal) { case SIGTRAP: puts("We just hit a breakpoint!\n"); display_process_info(pid); break; } } No momento que uma sigtrap for enviada para a gente podemos considerar que caímos no nosso breakpoint. Nesse momento, nosso processo filho está block (pois sofreu uma interrupção), esperando algum tipo de ação para continuar.
A função display_process_info(pid) irá mostrar o atual estado dos nossos registrados, usando o enum PTRACE_GETREGS que recebe a struct regs (também já visto no artigo passado):
void display_process_info(pid_t pid) { struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, NULL, &regs); printf("Child %d Registers:\n", pid); printf("R15: 0x%x\n", regs.r15); printf("R14: 0x%x\n", regs.r14); printf("R12: 0x%x\n", regs.r12); printf("R11: 0x%x\n", regs.r11); printf("R10: 0x%x\n", regs.r10); printf("RBP: 0x%x\n", regs.rbp); printf("RAX: 0x%x\n", regs.rax); printf("RCX: 0x%x\n", regs.rcx); printf("RDX: 0x%x\n", regs.rdx); printf("RSI: 0x%x\n", regs.rsi); printf("RDI: 0x%x\n", regs.rdi); printf("RIP: 0x%x\n", regs.rip); printf("CS: 0x%x\n", regs.cs); printf("EFGLAS: 0x%x\n", regs.eflags); } O código do nosso loop final fica da seguinte forma:
while (!WIFEXITED(status)) { signal = WSTOPSIG(status); switch(signal) { case SIGTRAP: puts("We just hit a breakpoint!\n"); break; } printf("> "); fgets(input, 100, stdin); if (!strcmp(input, "infor\n")) { display_process_info(pid); } else if (!strcmp(input, "continue\n")) { ptrace(PTRACE_CONT, pid, NULL, NULL); wait(&status); } } printf("Child %d finished...\n", pid); return 0; } Não iremos focar em implementação pela parte da interação do úsuario pois não é o foco dessa série de artigos. Tentei ser o mais “verboso” possível no quesito UX ?. No projeto original usei a lib linenoise para criar uma shell interativa, mas isso fica para sua imaginação.
Vamos executar:
~/.../mentebinaria/artigos >>> ./tracer hello 0x401122 #<== Endereco da main [130] Forking... Adding breakpoint on 0x401122 We just hit a breakpoint! > infor Child 705594 Registers: R15: 0x0 R14: 0x0 R12: 0x401050 R11: 0x2 R10: 0x7 RBP: 0x0 RAX: 0x401122 RCX: 0x225d7578 RDX: 0x19a402c8 RSI: 0x19a402b8 RDI: 0x1 RIP: 0x401123 CS: 0x33 EFGLAS: 0x246 We just hit a breakpoint! > continue Hello world Child 705594 finished... A ideia aqui não é criar tudo para você. A partir de agora, com o conhecimento básico dessa série de artigos, é possível criar o seu próprio debugger ou ferramenta semelhante. Deixo aqui o meu projeto, sdebugger, que foi fruto do meu estudo sobre este tema. Todo conhecimento base que eu passei aqui foi o necessário para criar este projetinho.
Agradeço a toda turma do Mente Binária pelo apoio e desculpa à todos pela demora para finalizar essa série de artigos. Tenho várias ideias para artigos futuros, então vamos nos ver em breve!
Links úteis:
ELF Interruptions Breakpoints Interrupt Descriptor Table Qualquer problema/erro por favor me chame ?
  • 3,610 views
Faz algum tempo que ando botando mais a mão na massa na parte prática da análise de malware e nos conceitos que a envolvem. Este fato somado com minha paixão por tomar notas nos meus estudos acaba resultando na criação de alguns relatórios. Com isto em mente, decidi colocar aqui a análise do último sample no qual trabalhei, de um ransomware chamado Nephilin.   Overview   O Nephilin é uma variante do Nefilim, um Ransomware que acabou ficando bem conhecido no mês de fevereiro/março devido ao fato de ser uma variante do conhecido Nemty, que costumava trabalhar com operações de RaaS (Ransomware as a Service - um modelo de negócio utilizado por criminosos que facilita muito a distribuição de Ransomwares. Basicamente o criador do malware recruta pessoas para usarem o Ransomware e recebe uma parte de seus lucros, facilitando assim a distribuição e a entrada de pessoas não experientes no crime). Por mais que as formas de operação sejam diferentes, há similaridades de código entre essas três famílias de malware.   Análise Estática   O sample analisado foi compilado para x86 e não possui nenhuma técnica que possa dificultar nossa análise como por exemplo packers/protectors e o uso de ASLR. No entanto, este binário é assinado com um certificado válido:     Olhando os imports podemos notar que a Import Directory Table possui apenas uma entrada, a da kernel32.dll, levantando a suspeita da utilização de runtime linking, ou seja, o loader não resolve o endereço das funções em tempo de carregamento, pois eles são resolvidos em tempo de execução:     Podemos suspeitar ainda mais pelo fato do nome algumas DLLs estarem na lista de strings do binário, assim como o nome de algumas funções que não são exportadas pela kernel32.dll:     Não vou me preocupar muito com esta parte estática da análise, tendo em vista que a ideia deste artigo é cair de cabeça em cada funcionalidade do malware, isto é, executá-lo e ir analisando seu comportamento.   Análise Dinâmica   A primeira função que o malware chama é a que vai criar todo o contexto de criptografia para gerar uma chave que será a chave utilizada para descriptografar a Ransom Note:       Dentro desta função existem várias funções que permitem trabalhar com criptografia no Windows. A primeira função criptográfica chamada é a CryptAcquireContext, responsável por criar um handle para um key container dentro de um Cryptographic Service Provider (CSP). Após pegar o handle, o tamanho de uma string é calculado para posteriormente se criar e retornar um Hash Object dentro do CSP criado anteriormente. Esta string tem 66 bytes de tamanho (0x42 em hexa) e é uma string lotada de "a".    A função CryptCreateHash cria o Hash Object especificando o tipo como SHA1 e, logo depois, a função CryptHashData tira o hash do que está dentro de um buffer de tamanho 66 bytes sendo passado como parâmetro:         Por fim, o SHA1 gerado é derivado, para a geração de uma chave RC4. Como podemos ver quase todas as funções estão sendo importadas dinamicamente. ?   O que acontece após a função que gera esta chave RC4 é a criação de um Mutex com o nome "sofos delaet sosos":     Em seguida, uma string em base64 aparece e é decodada com a função CryptStringToBinary, resultando em uma chave pública RSA:   "BgIAAACkAABSU0ExAAgAAAEAAQDNFw18bUF1x32DZaZt4gnQtAnv5XH60d9B6UgIbVfRdHPeyEljZLKlGBKFPTsh+8xsDHe/9vynuOlnuPt91grReMAwcTDVkxBh/PDkf3Jq0bnFgZAWbgMvGX6lApXTDcTArf4US63VI3z8YPyDNJwEvBEWI13ywob8ECLsrD/C6BPkYG0mBU1ccixzOgkgad0iDvwS/C8iyW1Mi0PCoBa+3TCTVwt0Zpy/HceV5U7SevG7RRN5HrErv54Ihg6kTPPhdxkYdO+CUND19aLqh8MAVLRuP5hR6b6r7cjBNAW2+USaaMyT/llNXdPdySbatLlH6Mau4z1eqzYc7hMB2f+6"       Há depois uma tentativa de pegar um handle para um CSP onde o key container tem o nome  "rsa session" e, sem sucesso, o binário cria um novo chamado "skr skr skr":     Agora a chave pública decodada anteriormente será importada para o contexto "skr skr skr":       Ações padrão da maioria dos ransomwares incluem desabilitar a checagem de erros durante o boot, desabilitar o modo de recuperação, deletar backups, etc. O Nephlin faz isso através de uma chamada à função ShellExecuteA() passando uma linha com o cmd.exe como parâmetro:   "C:\\asdfgsdgasd\\..\\Windows\\asdgagsahsfahfhasahfsd\\..\\System32\\cmd.exe" "/c bcdedit /set {default} bootstatuspolicy ignoreallfailures & bcdedit /set {default} recoveryenabled no & wbadmin delete catalog -quiet & wmic shadowcopy delete"     Aqui foi utilizada uma abordagem um tanto curiosa, tendo em vista que o malware considera diretórios que provavelmente não existirão no sistema de arquivos da vítima ("asdgagsahsfahfhasahfsd", por exemplo) e sobe um nível no filesystem utilizando ".." para acessar de fato o que importa, ou seja, o caminho real seria simplesmente "C:\Windows\System32\cmd.exe" ?   Neste momento acontece uma checagem em relação à como o malware foi executado na linha de comando. Se foi passado algum parâmetro, ele checa pra ver se é um arquivo ou diretório. Se for um arquivo, ele chama direto a função que encripta. Caso seja um diretório, ele chama a função que checa uma lista de exclusão dos tipos de arquivos que ele não quer encriptar e esta função chama então a função que encripta.   É interessante notar que com essas checagens o ransomware pode ser usado em diversos cenários e não simplesmente para encriptar o sistema completamente. Ex: para manualmente testar se ele está funcionando (antes da possível invasão), ser executado manualmente após a invasão visando diretórios/arquivos específicos, etc:     Se nenhum parâmetro for passado via linha de comando, o binário começa a mapear os drives que estão no sistema e pegar seus tipos, buscando especificamente por drives fixos, removíveis (pen drives, etc) e mapeamentos de rede:     Após pegar o drive o seu nome é concatenado com a string "NEPHILIN-DECRYPT.txt", à fim de criar a Ransom Note na raiz do Drive em questão:     Após a chamada à CreateFile, podemos ver a Ransom Note sendo criada, mas vazia por enquanto:     Antes do conteúdo ser de fato escrito no arquivo, ele precisa ser decodado, tendo em vista que é uma string em base64 (sim, outra string em base64):     Abaixo está o buffer que contém o conteúdo da Ransom Note em base64:     Após decodar o base64 o buffer aparenta estar encriptado, e de fato está:     A função CryptDecrypt utiliza a chave RC4 gerada anteriormente para decriptar o conteúdo do buffer em questão. Podemos ver o conteúdo em clear text após a execução da função:     Por fim podemos ver a função WriteFile, que irá escrever o conteúdo no arquivo "NEPHILIN-DECRYPT.txt" criado anteriormente:       Agora que a Ransom Note foi criada, o processo de criptografia começa. O meio utilizado é através da criação de outra thread, isto é, para cada drive encontrado, uma nova thread é criada. A função CreateThread recebe como parâmetro para indicar seu início o endereço de uma função, que por sua vez chama a função que checa a lista de exclusão e depois começa a criptografia. Além disso, o nome do drive escolhido no momento é passado como parâmetro para esta função.

Esta lista de exclusão é basicamente um lista que contém nomes de arquivos, diretórios e extensões das quais o malware não irá encriptar. Para cada arquivo encontrado o malware irá comparar com as especificações desta lista e, caso não bata, a função de criptografia será chamada:           Criptografia   A criptografia pode começar de 3 formas diferentes, como mencionado anteriormente: passando um arquivo como parâmetro pela linha de comando, passando um diretório ou mapeando os drives e criando threads.   Um trecho da função que faz as devidas checagens pode ser observada abaixo:     Se o arquivo checado não estiver na lista de exclusão, a função de criptografia é chamada:     O processo de criptografia se inicia com a abertura do arquivo em questão e a obtenção do seu tamanho. Depois disso, há duas chamadas para a função SystemFunction036 para gerar números aleatórios. Basicamente esta função é um alias para a função RtlGenRandom, que recebe como parâmetro um buffer e o tamanho do número aleatório que você quer gerar. O tamanho escolhido são 16 bytes (0x10):       Tendo 2 buffers de 10 bytes de tamanho cada, com os devidos números aleatórios gerados anteriormente, há duas chamadas à CryptEncrypt, uma para cada buffer. Aqui a chave pública RSA é utilizada para encriptar o buffer em questão, resultando em outros dois buffers de 256 bytes cada.       O conjunto de funções a seguir faz a mesma operação, mas apontando para lugares diferentes. A função SetFilePointerEx é utilizada para apontar para o fim do arquivo (baseando-se no tamanho obtido anteriormente) e depois a função WriteFile é utilizada para escrever os 256 bytes encriptados lá. A próxima chamada à SetFilePointerEx agora aponta para o fim do arquivo + 256 bytes e então escreve o segundo buffer encriptado onde o ponteiro está apontando.       Neste momento as checagens de tamanho de arquivo começam, assim como as chamadas de função e loops que envolvem a criptografia.   A primeira checagem feita é se o arquivo é maior que 64MB e, caso seja, as funções que criptografam o arquivo começam a ser chamadas de 125KB em 125KB. Caso o arquivo seja menor há uma outra checagem para ver se ele é menor que 1.2MB e caso ele não seja as funções de criptografia rodam em cima de 600KB apenas e finalizam. Caso o arquivo seja menor que 1.2MB ele é encriptado "de uma vez" e depois finaliza.         Para cada arquivo é gerada uma chave randômica com a função SystemFunction036 e depois esta é encriptada com a chave RSA pública. Esta abordagem dificulta bastante a criação de um decryptor, tendo em vista que a chave será sempre aleatória. Por outro lado, se tivemos a chave RSA privada em mãos a aleatoriedade não teria efeito nenhum pois para cada arquivo teríamos a chave responsável pela sua criptografia.   Por fim a extensão ".NEPHILIN" é adicionada ao arquivo aberto:             Uma coisa importante a se notar é que se a criptografia foi executada para um arquivo ou diretório específico tanto a Ransom Note quanto o wallpaper do Ransomware não são criados. Podemos observar que as funções de mapeamento de drives (que contém a criação da Ransom Note) e criação do papel de parede são ignoradas devido ao salto incondicional JMP:     E por fim...   Considerando ainda que não foram especificados arquivos e diretórios, a função responsável por criar a imagem do papel de parede é chamada. Há várias funções aqui e estas utilizam funções gráficas do Windows para editar a imagem em questão:     Uma das funções chamadas nesta função responsável por criar a imagem é justamente a função de decoda o base64 da Ransom Note, pois o que é escrito no papel de parede é a mesma coisa da Ransom Note. Após várias funções gráficas para preparar a imagem o arquivo é finalmente criado em %TEMP%, com nome god.jpg e seu conteúdo é escrito no arquivo:           Após configurar a imagem como papel de parede, o malware chama sua última função, que é responsável por fechar todos os handles e contextos de criptografia ainda pendentes:     Depois disso, o processo simplesmente sai retornando 0.   Lista de exclusão:   NEPHILIN-DECRYPT.txt $RECYCLE.BIN NTDETECT.COM MSDOS.SYS IO.SYS boot.ini AUTOEXEC.BAT ntuser.dat desktop.ini CONFIG.SYS BOOTSECT.BAK program files program files (x86) windows ntldr RECYCLER bootmgr programdata appdata .dll .NEPHILIM .exe .log .cab .cmd .com .cpl .ini .url .ttf .mp3 .pif .mp4 .msi .lnk   Espero que o estudo desta análise seja proveitoso assim como foi para mim e qualquer dúvida/feedback estou à disposição!   Abraços!
  • 4,907 views
Cá estava eu programando com o nasm, tentando (apenas tentando mesmo) reproduzir os wrappers de systemcall que existem na glibc, quando me deparei com o tamanho de um bináriozinho em assembly que só retorna um valor, um "hello world" no nasm, ali no canto do diretório. O binário tinha 4.2K, nada realmente muito pesado, mas para um programa que não utiliza nenhuma biblioteca e só retorna um valor me pareceu muito estranho.
Código do programa:
BITS 32 global _start _start: mov eax, 1 mov ebx, 10 int 0x80 Para compilar e testar:
[mario@zrmt rivendell]$ nasm -f elf32 elrond.asm [mario@zrmt rivendell]$ ld -m elf_i386 -s elrond.o -o elrond [mario@zrmt rivendell]$ ./elrond [mario@zrmt rivendell]$ echo $? 10 Aqui vai o hexdump do binário:
[mario@zrmt rivendell]$ hexdump -C elrond 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 03 00 01 00 00 00 00 90 04 08 34 00 00 00 |............4...| 00000020 20 10 00 00 00 00 00 00 34 00 20 00 02 00 28 00 | .......4. ...(.| 00000030 03 00 02 00 01 00 00 00 00 00 00 00 00 80 04 08 |................| 00000040 00 80 04 08 74 00 00 00 74 00 00 00 04 00 00 00 |....t...t.......| 00000050 00 10 00 00 01 00 00 00 00 10 00 00 00 90 04 08 |................| 00000060 00 90 04 08 0c 00 00 00 0c 00 00 00 05 00 00 00 |................| 00000070 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00001000 b8 01 00 00 00 bb 2a 00 00 00 cd 80 00 2e 73 68 |......*.......sh| 00001010 73 74 72 74 61 62 00 2e 74 65 78 74 00 00 00 00 |strtab..text....| 00001020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00001040 00 00 00 00 00 00 00 00 0b 00 00 00 01 00 00 00 |................| 00001050 06 00 00 00 00 90 04 08 00 10 00 00 0c 00 00 00 |................| 00001060 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 |................| 00001070 01 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................| 00001080 0c 10 00 00 11 00 00 00 00 00 00 00 00 00 00 00 |................| 00001090 01 00 00 00 00 00 00 00 |........| 00001098 Da pra perceber que de 0x72 à 0xfff todos os bytes são 0. Humm... suspeito. Não sou especialista e posso estar terrívelmente errado, mas não lembro dessa quantidade de zeros no manual do formato ELF. Se abrirmos o binário com o readelf veremos o seguinte:
[mario@zrmt rivendell]$ readelf elrond -h ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8049000 Start of program headers: 52 (bytes into file) Start of section headers: 4128 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 2 Size of section headers: 40 (bytes) Number of section headers: 3 Section header string table index: 2 Três Section Headers, dois Program Headers e mais um bando de coisa. Como não precisamos das seções para executar o programa irei ignorá-las por agora. Não precisamos das seções para executar o programa devido ao fato de que elas são feitas para auxiliar o linker no momento de construção do binário. Como o binário já está construído e nenhuma das seções representa objetos dinâmicos, elas podem ser ignoradas.
Então vamos diminuir esse programa aí. Primeiramente, devemos descobrir o endereço base do programa, para isto, basta pegar o entrypoint (0x8049000) e diminuir o offset do Program Header que tem a flag de executável (que vai conter o devido código do programa). Lembrando que o entrypoint é composto pelo endereço base do programa (para ser mapeado em memória) + “endereço” (no arquivo) do primeiro byte que corresponde ao código executável. O que vamos fazer aqui é achar esse primeiro byte, que pode ser encontrado no Program Header, onde se tem a flag de executável que recebe o nome de p_offset. Vejamos o readelf -l:
[mario@zrmt rivendell]$ readelf -l elrond Elf file type is EXEC (Executable file) Entry point 0x8049000 There are 2 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0x00074 0x00074 R 0x1000 LOAD 0x001000 0x08049000 0x08049000 0x0000c 0x0000c R E 0x1000 Section to Segment mapping: Segment Sections... 00 01 .text Para ajudar: de acordo com o manual o campo p_offset é “O offset do início do arquivo onde o primeiro byte do segmento se encontra”. Como estamos lidando com um segmento executável esse primeiro byte vai ser o início do nosso código.
Então dá para ver que o segundo Program Header (que possui a flag de executável) tem offset 0x001000! Então o endereço base é 0x08048000 (0x08049000 - 0x00001000) ! Já que temos o endereço base podemos excluir os zeros (caso contrário o programa ficaria quebrado e não iríamos conseguir analisá-lo com o readelf), alto lá! Apenas os inúteis! Mas quais são os inúteis ? Todos os que os Program Headers apontam, pois esses serão os  bytes do programa mapeados em memória, então vamos deixar eles lá. Vou usar o hyx como editor hexa, mas o hte também funciona.
Após excluirmos todos os zeros entre 0x74 e 0x1000:
[mario@zrmt rivendell]$ hyx elrond 0000> 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 0010: 02 00 03 00 01 00 00 00 00 90 04 08 34 00 00 00 |............4...| 0020: 20 10 00 00 00 00 00 00 34 00 20 00 02 00 28 00 | .......4. ...(.| 0030: 03 00 02 00 01 00 00 00 00 00 00 00 00 80 04 08 |................| 0040: 00 80 04 08 74 00 00 00 74 00 00 00 04 00 00 00 |....t...t.......| 0050: 00 10 00 00 01 00 00 00 00 10 00 00 00 90 04 08 |................| 0060: 00 90 04 08 0c 00 00 00 0c 00 00 00 05 00 00 00 |................| 0070: 00 10 00 00 00 b8 01 00 00 00 bb 2a 00 00 00 cd |...........*....| 0080: 80 00 2e 73 68 73 74 72 74 61 62 00 2e 74 65 78 |...shstrtab..tex| 0090: 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |t...............| 00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 0b 00 00 |................| 00c0: 00 01 00 00 00 06 00 00 00 00 90 04 08 00 10 00 |................| 00d0: 00 0c 00 00 00 00 00 00 00 00 00 00 00 10 00 00 |................| 00e0: 00 00 00 00 00 01 00 00 00 03 00 00 00 00 00 00 |................| 00f0: 00 00 00 00 00 0c 10 00 00 11 00 00 00 00 00 00 |................| 0100: 00 00 00 00 00 01 00 00 00 00 00 00 00 |.............| Ahh muito mais enxuto! Porém o bicho tá todo quebrado. Se executarmos:
[mario@zrmt rivendell]$ ./elrond Bus error (core dumped) Um “Bus error” não é nada mais que uma tentativa de read ou write em um espaço de memória desalinhado. Como citado no manual os mapeamentos tem que ser alinhados com as páginas de memória, ou seja, 4KB.
Vamos consertá-lo! Vamos ter que consertar: o entrypoint e o mapeamento do segundo Program Header, ou seja, seu endereço virtual, físico e seu offset. Como estamos alterando as posições dos segmentos (isto é, o nome oficial para o que um Program Header mapeia)  teremos que alterar seu mapeamento no arquivo junto com o entrypoint (que aponta para o primeiro byte de um segmento executável). Na verdade, o endereço físico pode ser ignorado, o manual cita que os “System V” ignoram endereços físicos de aplicações, mas iremos adicioná-los em prol da completude.
Revisando... o entrypoint vai ser o endereço base mais o offset do segundo Program Header, e esse offset vai ser 0x75 (lembre-se que era 0x1000, mas com a retirada dos zeros entre 0x74 e 0x1000 efetivamente reduzimos o entrypoint em 0xFFF - 0x74 = 0xF8B,  logo, o entrypoint vai ser 0x1000 - 0xF8B = 0x75) então nosso entrypoint vai ser 0x08048075. Esse também vai ser o endereço virtual e o endereço físico do header.
Então troquemos:
O entrypoint no Header ELF por 0x08048075 O offset do section header por 0x00000075 Os endereços virtuais e físicos do segundo Program Header por 0x08048075 Agora mais do que nunca teremos que ter atenção. Saque seu editor de hexa preferido e lembre-se que estamos lidando com little endian. Vou usar o hyx, que é um editor hexa um pouco parecido com o vi:

No terminal de cima temos o arquivo original sem os zeros, já no de baixo temos o arquivo já alterado.
Para ajudar:
Vermelho: Entrypoint Amarelo: Offset do Header Verde: Endereço Virtual do Header Azul: Endereço Físico do Header Agora se executarmos:
[mario@zrmt rivendell]$ ./elrond [mario@zrmt rivendell]$ echo $? 10 Como disse lá em cima, não alterei as seções e nesse caso (binário já linkado e sem bibliotecas dinâmicas) elas não são importantes. Tente ler elas pra ver o que acontece.
No fim passamos de 4.2k para ...
[mario@zrmt rivendell]$ ls -lh elrond -rwxr-xr-x 1 mario mario 269 --- -- --:-- elrond 269!
Achei que a galera poderia gostar dessa pequena aventura, acho bem interessante principalmente para aprender bem sobre o formato. Se gostarem tenho planos pra parte dois!
  • 6,592 views