Construindo seu debugger - Parte 2: Forks - Articles - Mente Binária
Neste artigo será explicado o que são forks e seu uso em desenvolvimento de aplicações em sistemas UNIX. Sem mais delongas, vamos prosseguir!!!?
Dessa maneira você tem uma representação bem visual de como está dividida a sua estrutura de processos ?. Note que todos os processos são filhos do seu Init system (seja ele SystemV, Systemd, etc). Aconselho você explorar o comando pstree para uma visão bem mais detalhada do seu sistema! Outra abordagem é usar o própio comando ps:
Rode o comando acima (dependendo da quantidade de processos use um pipe para o less ?) e com ele teremos uma visão mais detalhada. A coluna PID representa o ID do processo em si e a coluna PPID representa o "Parent Process ID", que nada mais é que o ID do processo pai. Note que o PID 1 é o seu Init System e os seus processos rodam como filho dele!
Precisamos incluir a lib <sys/types.h> para que seja possivel acessar o tipo pid_t. A função fork não espera nenhum parâmetro para a sua construção e o código abaixo demonstra o quão simples é cria um fork.
// Acesso a syscall
// Acesso ao tipo variavel pid_t
printf("Processo normal...\n");
printf("Eu sou o processo filho meu PID: %d\n", pid);
printf("Eu sou o processo pai de %d\n", pid);
Compile o código acima da seguinte forma:
$ gcc -o fork fork.c
Note que o código se "divide" a partir da chamada fork e um if é usado para saber se estamos executando no pai ou no filho, note também que o pai sabe o PID e o filho não.
$ watch -n1 pstree
O comando acima vai executar o pstree a cada 1 segundo, desta forma você verá o exato momento da criação do fork.
A syscall wait espera que ao menos 1 de seus processos filho troque de estado, já a waitpid espera por um processo específico. Como sabemos exatamente qual processo queremos rastrear iremos usar esta call ?:
printf("Spliting work...\n");
for(int i = 0; i < 100000000; i++ ) {
a += i*2 + 10 *i;
printf("Waiting child finish work...\n");
signal = WEXITSTATUS(status);
printf("Child exited, status = %s\n", strsignal(signal));
Compile o código acima e execute:
$ gcc -o work work.c
Spliting work...
Waiting child finish work...
Child exited, status = Killed
Veja que após a chamada de fork nosso processo filho executa várias iterações e realiza um cálculo (um cálculo totalmente randômico) e após isso retorna 9. Este retorno em questão é apenas por motivos educativos (no artigo anterior falamos de sinais e como eles funcionam). O processo pai usa a syscall waitpid para esperar que qualquer signal seja enviada do pid especificado. Após receber um status é verificado se o fork saiu (WIFEXITED) e se sim, pegamos o signal enviado usando WEXITSTATUS(status da saída) e usamos a chamada strsignal(provida pela string.h) para recuperar uma versão em texto do signal. Nesse caso iremos recuperar o signal "KILLED", pois colocamos 9 apenas por razões educativas.
$./work && echo "Filho saiu com 0, tudo certo..."  || echo "Filho saiu com 1, algo errado..."
No caso acima a nossa shell irá criar um fork do nosso work, executar o nosso programa (que por sua vez também executa um fork mas não entra em questão aqui) e se o signal retornado pelo fork for 0 ele imprime uma mensagem, caso contrario ele imprime uma mensagem de erro, dessa maneira você pode orquestrar um shell scripting usando o própio retorno do processo ?
Comentarios são todos bem vindos e todos os códigos usados estão disponíveis no github! ?
Muito bom excelente postagem parabens?
Livro aborda programação funcional: entenda a importância desse tema
O professor, analista de sistemas e economista José Augusto N. G. Manzano lançou o livro "Algoritmos Funcionais - Introdução minimalista à lógica de programação funcional pura aplicada à teoria dos conjuntos".
O livro busca fundamentar, dentro de seu escopo, diversas ações matemáticas e de programação importantes dentro do universo funcional. "Este livro foi escrito exclusivamente para uso em sala de aula. É um material voltado à apresentação e estudo da 'Lógica de Programação Funcional', que vem tomando grandes posições dentro do universo comercial no desenvolvimento de software", escreveu o autor em publicação no seu LinkedIn.
Paradigma funcional – O paradigma funcional tem um jeito de programar específico, diferenciado do tradicional, com algumas vantagens para certas aplicações. Uma das áreas em que o paradigma funcional se destaca é a ciência de dados, que tem sido uma grande aposta das empresas, especialmente startups, na busca de profissionais.
Dentro do paradigma funcional, há várias linguagens de programação. O Nubank, por exemplo, utiliza bastante uma linguagem chamada Clojure, e tem uma alta demanda por profissionais que programem nessa linguagem (saiba mais).
Este livro, portanto, é uma introdução para quem quer adentrar nesse mundo das linguagens funcionais. Qualquer futuro programador em uma dessas linguagens funcionais vai se beneficiar deste conteúdo.
Clique e veja como comprar
O professor Manzano se destaca também por produzir muitos materiais da área, se mantendo sempre atualizado sobre o que o mercado está precisando no momento. Já divulgamos outros trabalhos de sua autoria aqui no Mente Binária, entre eles o livro 'Fundamentos em Programação Assembly':
Inclusive, o canal do professor Manzano no YouTube possui mini aulas sobre programação de computadores e outros assuntos relacionados à área da computação e tecnologia da informação. Vale conferir!
Vulnerabilidade no Linux permite que atacantes acessem pilha de memória do kernel
O Cisco Talos, grupo global de inteligência de ameaças de cibersegurança da Cisco, descobriu uma vulnerabilidade de divulgação de informações no kernel do Linux.
A vulnerabilidade, rastreada como CVE-2020-28588, pode permitir que um invasor visualize a pilha de memória do kernel, o que significa que dados ou informações que não deveriam ser vistas possam ser acessadas. O problema foi visto pela primeira vez pelo Cisco Talos em um dispositivo Azure Sphere (versão 20.10), um dispositivo ARM de 32 bits que executa um kernel do Linux corrigido.
O kernel do Linux é o núcleo livre e de código aberto dos sistemas operacionais do tipo Unix. A vulnerabilidade existe especificamente na funcionalidade /proc/pid/syscall de dispositivos ARM de 32 bits executando Linux.
Um invasor pode explorá-la lendo /proc/<pid>/syscall, um arquivo legítimo do sistema operacional Linux, podendo aproveitar esse vazamento de informações para explorar com êxito vulnerabilidades adicionais não corrigidas.
O Cisco Talos trabalhou com o Linux para garantir que esse problema seja resolvido e uma atualização já está disponível para os clientes afetados. Os usuários são incentivados a atualizar esses produtos afetados o mais rápido possível para o Kernel Linux versões 5.10-rc4, 5.4.66 e 5.9.8.
Injeção e manipulação de executáveis com o pei
By Felipe.Silva
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".
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.
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.
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:
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.
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.
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.
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.
Vulnerabilidades encontradas no kernel do Linux existem há 15 anos
Três vulnerabilidades foram encontradas no subsistema iSCSI do kernel do Linux, permitindo que invasores locais com privilégios básicos de usuário obtenham privilégios de root em sistemas Linux sem patch. Segundo o BleepingComputer, os bugs de segurança só podem ser explorados localmente, o que significa que invasores em potencial terão que obter acesso a dispositivos vulneráveis explorando outra vulnerabilidade ou usando um vetor de ataque alternativo.
O mais impressionante é que essas vulnerabilidades já existem há 15 anos. A descoberta foi feita por pesquisadores do GRIMM. "Ao contrário da maioria das coisas que encontramos acumulando poeira, esses bugs revelaram-se ainda bons, e um acabou sendo utilizável como um escalonamento de privilégio local (LPE) em vários ambientes Linux", diz publicação feita no blog do GRIMM.
De acordo com o pesquisador de segurança do GRIMM, Adam Nichols, as falhas afetam todas as distribuições Linux, mas felizmente o módulo de kernel scsi_transport_iscsi vulnerável não é carregado por padrão. No entanto, dependendo da distribuição do Linux que os atacantes estejam focando, o módulo pode ser carregado e explorado para escalonamento de privilégios.
Saiba mais sobre as vulnerabilidades:
CVE-2021-27363: vazamento do ponteiro do kernel (vazamento de informações) CVE-2021-27364: leitura fora dos limites (vazamento de informações, negação de serviço) CVE-2021-27365: estouro de buffer de heap (escalonamento de privilégio local, vazamento de informações, negação de serviço)