Jump to content
  • Construindo seu debugger - Parte 3: ptrace

       (2 reviews)

    Olá, já faz um bom tempo desde do ultimo artigo sobre a construção de debuggers mas, sem mais delongas, vamos dar continuidade a esta série! ? 

    Neste artigo iremos falar um pouco sobre uma chamada de sistema que é capaz de controlar quase todos os aspectos de um processo: a syscall PTRACE (process trace). Antes de continuarmos, vale ressaltar que todo o código utilizado neste artigo está disponível no repositório do Github.

    De acordo com o manual do Linux (man ptrace), a syscall ptrace é definida assim:

    "A syscall ptrace provê meios para que um processo (denominado "tracer") possa observar, controlar a execução de um outro processo (denominado "tracee"), examinar e modificar a memória e registradores do "tracee". É primariamente utilizado para a implementação de 'breakpoint debugging' e para rastreamento de syscalls".

    Em outras palavras, podemos utilizar a ptrace para controlar um outro processo sobre o qual termos permissões sobre!

    Por exemplo, execute:

    strace /bin/ls

    O comando "strace" acima, é utilizado para que se possa rastrear todas as syscalls que um programa realiza. Vale lembrar que toda a técnica utilizada para o rastreamento de syscalls envolve o conteúdo abordado nos artigos anteriores, então é de suma importância que você tenha lido (ou saiba) o primeiro artigo sobre Sinais e o segundo sobre Forks.

    Antes de começar a rastrear um dado comando, o strace precisa ter controle total sobre a execução do processo alvo, para isso é feito um fork do processo em questão e o mesmo é "traceado". Voltaremos neste assunto em breve.

    A wrapper da ptrace é definida em <sys/ptrace.h> e tem o seguinte protótipo:

    #include <sys/ptrace.h>
    
      long ptrace(enum __ptrace_request request, pid_t pid,
                  void *addr, void *data);

    Onde o primeiro argumento request é um enum onde cada valor define uma ação em cima do "tracee", tais como TRACEME, GETEREGS, SETREGS e etc. O segundo argumento, pid, é o PID (Process Identification) do processo que queremos "tracear", o terceiro argumento addr é um endereço para alguma interação que a ser realizada da memória do processo "traceado" e o quarto e último argumento data é algum tipo de dado passado para o processo.

    Agora que você ja conhece o formato desta syscall, vamos fazer um pequeno breakdown do comando "strace".

    Execute:

    strace strace /bin/ls 2>&1 | grep -A2 clone

    Por mais bizarro que o comando acima pareça, o que vamos fazer aqui é rastrear todas as syscalls que o strace faz usando o próprio strace! Como a saída padrão do strace não é o stdout (dê uma lida em standart streams, caso esteja confuso) então é primeiro redirecionar a saída de erro para a saída padrão, para que seja possível rodar o grep no que queremos.

    Estamos buscando aqui, alguma chamada a syscall clone, que é sempre chamada quando é feito um fork. A chamada à ptrace vem logo em seguida:

    clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c4aa8ea10) = 16203
    ptrace(PTRACE_SEIZE, 16203, NULL, 0)    = 0

    Nesse caso, o strace cria um processo filho e em seguida usa o ptrace com o argumento SEIZE para iniciar o rastreamento (tracing) de um processo sem interrompê-lo, como analisaremos em seguida. Dessa maneira o strace é capaz de interceptar cada chamada de sistema feita pelo processo!

    Dê uma olhada no comando ltrace, que diferente do strace, rastreia todas as chamadas à bibliotecas (libraries trace) e tente fazer o mesmo que fizemos acima!

    Algumas ações notáveis que podemos fazer com a ptrace:

    • PTRACE_PEEKTEXT, PTRACE_PEEKDATA
      • Ler uma word em um dado endereço.
    • PTRACE_POKETEXT, PTRACE_POKEDATA
      • Copiar uma word para um determinado endereço (injete dados na memória).
    • PTRACE_GETREGS
      • Ler os registradores de um processo, que será guardado na struct user_regs_struct em <sys/user.h>.
    • PTRACE_SETREGS
      • Escrever nos registradores de um processo (também no formato da struct acima).

    Execute "man ptrace" para uma abordagem mais detalhadas de todos os valores disponíveis. ?

     

    Implementando um simples tracer

    Agora que já temos uma base de forks e uma ideia de como o ptrace funciona, podemos unificar os dois e tenho certeza que o ptrace irá ficar mais claro. A partir de agora ele é fundamental para a implementação do nosso debugger.

    O primeiro passo é definir o escopo de como será feito o nosso "tracer": vamos rastrear um processo que já esta sendo executado ou vamos criar um novo? Para o nosso debugger, iremos apenas criar um fork e trocar sua imagem de execução para a do programa que queremos debugar, usando uma das funções da família exec.

    Primeiro vamos usar a função execl, que faz parte do leque de funções exec (man 3 exec) que trocam a imagem do nosso processo por outra, ou seja, o nosso programa é realmente trocado por outro em uma execução.

    A função execl é definida como:

    #include <unistd.h>
      
    int execl(const char *pathname, const char *arg, ...
                           /* (char  *) NULL */);

    Onde o primeiro argumento pathname é caminho completo do nosso executável alvo e os demais argumentos, que podem ser vários, são os argumentos para o programa que será executado.

    Para seguir um padrão, o primeiro argumento que geralmente colocamos é o caminho do programa em questão (lembrem que no array argv a posição 0 guarda o nome do programa em si), o resto dos argumentos são opcionais e seguem no modelo de lista de argumentos que são delimitados por um argumento NULL, que geralmente usamos para finalizar a lista.

    Agora considere o seguinte exemplo:

    #include <unistd.h>
    #include <stdio.h>
    
    
    int main(int argc, char* const* argv)
    {
    	if (argc < 3) {
    		printf("Usage: %s <command> <args>\n", argv[0]);
    		return 1;
    	}
    	
    	const char* command = argv[1];	
    	char* const* args = &argv[1];
    
    	printf("First arg => %s\n", args[0]);
    	execv(command,  args);
    	puts("Continua?\n");
    
    	return 0;
    }

    Compile com

    $ gcc -o exec exec.c
    $ ./exec /bin/ls -lah

    Este programa bem simples demonstra como a exec funciona.

    O que acabamos de criar aqui foi uma espécie de wrapper para qualquer comando: ele irá pegar o nome do comando e os seus respectivos argumentos e trocar sua execução atual pela a que você especificou.

    Note também a string "Continue?" que deveria ser impressa na tela. Esta nunca será impressa pois o nosso programa virou de fato, outro.

    Interessante, não? Usando um pouco de criatividade, podemos criar novos processos filhos combinando forks + exec, ou seja, criamos um fork do nosso processo e trocamos sua imagem por outra! Dessa maneira, por exemplo, temos total controle sobre o comando ls.

    Modificando um pouco o código acima e seguindo a ideia de forks, temos:

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ptrace.h>
    #include <unistd.h>
    
    int main(int argc, char* const* argv)
    {
    	if (argc < 3) {
    		printf("Usage: %s <command> <args>\n", argv[0]);
    		return 1;
    	}
    	
    	const char* command = argv[1];	
    	char* const* args = &argv[1];
    	pid_t child_pid = fork();
    
    	// Neste ponto, todas as variaveis sao copiadas para o nosso fork
    	// o fork NAO recebe as mesmas variaveis, apenas uma cópia ;)	
    	if (!child_pid) {
    		// Hora de transformar nosso fork em outro programa
    		ptrace(PTRACE_TRACEME, NULL, NULL, NULL);
    		execv(command, args);
    	}	
    	
    	char in;
    	do {
    		puts("Iniciar processo ? [y/n]: ");
    		in = getchar();
    	} while (in != 'y');
    
    	ptrace(PTRACE_CONT, child_pid, NULL, NULL);
    	
    	return 0;
    }

    Compile

    $ gcc -o fork_exec fork_exec.
    $ ./fork_exec /bin/ls

    O programa acima realiza os primeiros passos do nosso tracer: é passado o caminho de um programa e os argumentos para o mesmo. Com isso criamos um fork e usamos o ptrace no própio fork com o argumento TRACEME. Este parâmetro indica que o este processo será "traced" pelo seu processo pai. Em seguida trocamos a nossa execução para o nosso programa alvo. Neste momento temos total controle sobre a execução, no exemplo acima, do comando ls.

    Quando um processo inicia sua execução com TRACEME + exec, o mesmo recebe um sinal de interrupção (SIGTRAP) até que o seu processo pai indique que ele deve continuar sua execução. Por isso, o nosso processo pai, que retém o PID do processo filho, usa o ptrace com o argumento CONT para que seja enviado o signal para dar continuidade de execução.

    E depois?

    Agora toda a comunicação entre os processos pai e o filho se dará via sinais e usaremos a syscall wait constantemente.

    Lembra que definimos acima algumas funções que podemos usar em conjunto com a ptrace? Para já irmos adiantando alguns artigos, vamos fazer um programa que mostra o estado dos registradores para um processo, passo a passo. Vamos usar dois parâmetros para a ptrace: GETREGS e STEP. Segue o código:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #include <unistd.h>
    
    #include <sys/types.h>
    #include <sys/ptrace.h>
    #include <sys/user.h>
    #include <sys/wait.h>
    
    
    void display_regs(struct user_regs_struct* regs)
    {
        printf("RIP: 0x%x\n", regs->rip);
        printf("RBP: 0x%x\n", regs->rbp);
        printf("RSP: 0x%x\n", regs->rsp);
    }
    
    int main(int argc, char* const* argv)
    {
    
        if (argc < 2) {
            fprintf(stderr, "Usage: %s <program_path>\n", argv[0]);
            return 1;
        }
    
        const char* progName = argv[1];
        
        pid_t child = fork();
    
        if (!child) {
            ptrace(PTRACE_TRACEME, NULL, NULL, NULL);
            execl(progName, progName, NULL);
        }
        
        int status;
        int options = 0;
        int signal;
    
        // Estrutura que mantem os registradores
        struct user_regs_struct regs;
    
        /// Capta primeiro sinal de parada do filho
        waitpid(child, &status, 0);
        signal = WSTOPSIG(status);
    
        if (signal == SIGTRAP) {
            printf("Processo alvo %s esperando pronto para iniciar\n\n", progName);
        }
        
        printf("Executando 10 instruções\n");
        for (int i = 0; i < 10; ++i) {
            printf("Passo: %d\n", i+1);
            // Executa uma instrução
            ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
            // Espera sinal do filho
            waitpid(child, &status, 0);
            // Copia o estado atual dos registradores
            ptrace(PTRACE_GETREGS, child, NULL, &regs);
    
            // Função local para imprimir os principais registradores
            display_regs(&regs);
            puts("\n\n");
        }
    
        puts("Continuando...\n");
    
        /// Continua execução
        ptrace(PTRACE_CONT, child, NULL, NULL);
        waitpid(child, &status, 0);
    
        printf("Filho saiu com %d\n", WIFEXITED(status));
        return 0;
    }

     

    Compile:

    $ gcc -o tracer tracer.c
    $ ./tracer /bin/ls

    O código acima, além de criar e rastrear o processo, executa as primeiras 10 instruções e copia os estados dos registradores em cada passo. Logo após, continua a execução do programa normalmente.

    A estrutura user_reg_struct, definida em <sys/user.h>, contém todos os registradores que estão disponíveis na sua arquitetura. O código foi escrito considerando um ambiente x86-64.

    Com o estudo da ptrace, fechamos toda a introdução para construirmos o nosso debugger de fato, que vamos começar a desenvolver no próximo artigo, incialmente com capacidade de por breakpoints, imprimir o atual estado dos registrados e executar instrução por instrução do processo.

    Qualquer dúvida ou correção sinta-se livre de por nos comentários!  ?

    Links úteis:

    Até a próxima!

    Edited by Fernando Mercês


    User Feedback

    Join the conversation

    You can post now and register later. If you have an account, sign in now to post with your account.
    Note: Your post will require moderator approval before it will be visible.

    Guest

    • This will not be shown to other users.
    • Add a review...

      ×   Pasted as rich text.   Paste as plain text instead

        Only 75 emoji are allowed.

      ×   Your link has been automatically embedded.   Display as a link instead

      ×   Your previous content has been restored.   Clear editor

      ×   You cannot paste images directly. Upload or insert images from URL.


    Guest seijinz

       1 of 1 member found this review helpful 1 / 1 member

    sensacional!

    antigamente era difícil achar artigos assim em português, só tinha nas revistas phrack e 29A

     

    parabéns

    Link to comment

  • Similar Content

    • By Felipe.Silva
      A heap é uma estrutura especial de memória usada pelo processo. O que tem de especial nela é o fato de seu tamanho ser variável, já que sua memória pode ser alocada ou desalocada dinamicamente pelo processo. Isso pode ser feito usando syscalls do sistema operacional e o mesmo é responsável por alocar mais páginas de memória para a seção caso seja necessário.
      No Linux o segmento de dados pode ser aumentado ou diminuído usando a syscall brk, e é esse espaço de memória que os programas normalmente usam para a heap. Em C nós normalmente não usamos a heap diretamente e, ao invés disso, usamos a função malloc() e semelhantes para lidar com a alocação dinâmica de memória. O que alguns não sabem é que na verdade chamadas para a função malloc() ou free() não necessariamente irão resultar na alocação ou desalocação de memória para o processo. Isso se dá porque é a libc quem de fato gerencia a heap e nós não estamos diretamente solicitando ou liberando memória para o sistema operacional.
      *libc é a biblioteca padrão da linguagem C que contém funções essenciais para tarefas básicas como: Manipulação de strings e arquivos, entrada e saída de dados, funções básicas de matemática etc.
      A função malloc(), de acordo com a implementação da glibc, usa o segmento de dados, o dividindo em uma ou mais regiões de memória que eles chamam de “arenas”. A arena principal corresponde à heap original do processo, porém outras arenas também podem ser alocadas para o processo. Inicialmente cada thread criada no processo tem uma arena individual até atingir um certo limite pré-definido de arenas que podem ser alocadas no processo. Atingindo esse limite, as threads posteriores passam a compartilhar a mesma arena. Em cada arena de memória existem divisões da memória que são chamadas de maneira homônima de heap, e são nessas heaps que a função malloc() de fato aloca memória para o usuário da libc.
      Cada bloco de memória que malloc() aloca na heap é chamado de chunk, e cada chunk contém metadados usados pelo sistema interno de malloc para organizar a heap como uma lista duplamente encadeada. Para fins de ilustração, abaixo está a estrutura de um chunk, usada na glibc:
      struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; INTERNAL_SIZE_T mchunk_size; struct malloc_chunk* fd; struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; struct malloc_chunk* bk_nextsize; O valor mchunk_prev_size seria o tamanho do chunk anterior e mchunk_size o tamanho do chunk atual. Os ponteiros *fd e *bk são usados somente quando o chunk está livre, e seriam ponteiros usados para a lista circular duplamente encadeada de chunks que apontam para o chunk posterior e anterior, respectivamente. No entanto, essa estrutura não representa muito claramente como o chunk é de fato usado pelo sistema de malloc, na figura abaixo isso é ilustrado com mais precisão.

      O ponteiro que malloc() retorna não aponta para o início do chunk mas sim para o início do espaço de memória que pode ser usado pelo usuário. O tamanho do espaço de memória de um chunk é alinhado pelo tamanho de um double word na arquitetura. Caso malloc() seja chamado passando um tamanho desalinhado como argumento, um espaço extra é alocado para manter o alinhamento. Por exemplo, se o alinhamento está sendo feito para 8 bytes e malloc é chamada com 9 como argumento, malloc irá te devolver um chunk com 16 bytes de espaço de memória usável. Além do alinhamento no tamanho do chunk, também existe um alinhamento no endereço de memória retornado por malloc() que é sempre alinhado para o tamanho de uma word. Isso é feito porque em algumas arquiteturas esse alinhamento de memória é necessário para se evitar uma exceção. Em outras arquiteturas (x86, por exemplo) o alinhamento melhora a performance do processador no acesso à memória.
      Como existe esse alinhamento no tamanho de um chunk isso garante que os três bits menos significativos de mchunk_size não sejam necessários para definir o tamanho do chunk. Se aproveitando disso, os três últimos bits são usados como flags para determinar alguns metadados usados pelo sistema de chunks.

      O bit M indica que o chunk não pertence a nenhuma arena e, ao invés disso, foi alocado dinamicamente em uma memória mapeada. Caso este bit esteja ligado, os outros dois são ignorados. No contexto de um chunk livre este bit está sempre desligado, tendo em vista que a lista encadeada de chunks livres somente se aplica a chunks que estão em alguma arena.
      Os chunks diretamente mapeados na memória (com bit M ligado) são criados para chunks muito grandes. Esses chunks quando liberados com a função free() são imediatamente liberados da memória. Por outro lado, usar free() em um chunk comum não necessariamente irá liberar memória para o sistema operacional. O que free() faz nesse caso é marcar o chunk como livre o adicionando de volta à lista de chunks livres. Assim como é indicado nesse trecho da glibc:
      /* Mark the chunk as belonging to the library again. */ (void)tag_region (chunk2mem (p), memsize (p)); Repare como o comentário descreve a ação como “marcar o chunk como pertencente à biblioteca novamente”, e é efetivamente isso que a função free() faz, não sendo necessariamente uma liberação de memória para o sistema operacional. Inclusive um recurso de otimização que a glibc usa é o que eles chamam de tcache (Thread Local Cache), que se trata de uma lista de chunks existente em cada thread individualmente. Quando você aloca um novo chunk na thread e posteriormente o libera, ele é adicionado ao tcache daquela thread e pode ser reutilizado em uma nova alocação posterior.
      Um adendo que a função free() pode efetivamente liberar memória para o sistema operacional se houver vários chunks livres no topo do segmento de dados (o que raramente acontece). Ela faz isso chamando a função interna systrim(), que por sua vez (no Linux) usa a syscall brk para diminuir novamente o segmento de dados.
      Um detalhe interessante que vale citar aqui é que na glibc (no Linux) existem as funções brk e sbrk que servem como wrappers para aumentar/diminuir o segmento de dados. O sistema de liberação de memória do systrim() espera que essas funções não sejam utilizadas diretamente para poder fazer a liberação de memória apropriadamente. Se você usá-las em seu código por algum motivo, irá “quebrar” o sistema de liberação de memória automático do free(), o fazendo não mais liberar memória quando é usado em chunks de arenas. Logo, não é recomendável que você use essas funções diretamente a menos que você esteja implementando seu próprio sistema de gerenciamento de memória dinâmica.
      O código abaixo é um experimento a fim de vermos na prática os metadados do chunk no Linux:
      // gcc test.c -o test #include <stdio.h> #include <stdlib.h> int main(void) { size_t *content = malloc(8); size_t chunk_size = content[-1] & ~0b111; size_t chunk_flags = content[-1] & 0b111; printf("size: %zu\nflags: %zu\n", chunk_size, chunk_flags); return 0; } No meu Linux é retornado 32 como tamanho do chunk e 1 como flag, indicando que somente o bit P está ligado. Sugiro ao leitor variar o tamanho passado para malloc a fim de comprovar que o alinhamento do tamanho do chunk de fato ocorre. Também sugiro passar um número grande para malloc() a fim de ver a partir de qual tamanho malloc() irá deixar de usar uma arena e irá alocar o chunk com mmap(). Caso isso ocorra o bit M será ligado e o número 2 (decimal) será indicado como flags.
      Nota: Esse código propositalmente não utiliza free() antes de finalizar o programa. É redundante e desnecessário usá-la quando o programa é finalizado, tendo em vista que todas as páginas de memória usadas pelo processo serão liberadas pelo sistema operacional.
      Referências
      https://man7.org/linux/man-pages/man2/brk.2.html https://sourceware.org/glibc/wiki/MallocInternals https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=e2d7b1b58396906375ba0e953a20ac57f0904378;hb=refs/heads/master http://c-faq.com/malloc/freeb4exit.html
    • By Julliana Bauer
      A presença de Security Champions nas equipes de desenvolvimento pode trazer uma visão mais estruturada acerca da segurança de aplicações. Além disso, ele pode ser um grande influenciador da cultura de segurança dentro da empresa.
      Mas você sabe qual é o papel deste profissional?
      Conversamos com o Rodrigo Maués, Tech Lead na Conviso Application Security, para entender melhor quem é e qual o papel do Security Champion dentro de um time de segurança.

      O que é o Security Champion
      De acordo com Maués, dentro de empresas que produzem softwares, é comum existir um atrito ocasional entre duas áreas.
      Para a área de desenvolvimento, as equipes de segurança são pontos de gargalo dentro de um processo já bem pressionado e com prazos apertados. Do outro lado, temos as equipes de segurança que, por vezes, entram em conflito ao buscar introduzir mais segurança nos produtos entregues pelos desenvolvedores.
       “Este conflito nem sempre é facilmente solucionado, pois vai contra algo que, do ponto de vista comercial, é bem mais forte que a validação de um código: as demandas de um mercado cada vez mais inovador e ágil”, contextualiza o Tech Lead. 
      É aí que entra o papel do Security Champion. No mundo de Segurança de Aplicações, entendemos os  Security Champions como sendo os membros das equipes de desenvolvimento que receberam treinamento específico para atuar como ponto focal de segurança de aplicações dentro do time. 
      Quando um membro do time de desenvolvimento se torna um Security Champion, ele estabelece uma melhor comunicação com seus pares e muda a cultura do desenvolvimento de dentro para fora, já que acessa a mesma linguagem dos membros envolvidos na produção do software.
      “Desta forma, o time de Desenvolvimento consegue compreender muito melhor a informação passada, uma vez que recebe o conhecimento de um dos seus”, esclarece Maués. 
      Ou seja: o Security Champion trabalha como uma ponte entre as duas áreas, de forma conciliadora, para garantir que a cultura da segurança seja mantida sem desgastar as equipes.

      Quais as responsabilidades de um Security Champion?
      Entre as principais responsabilidades dos Security Champions está a mudança cultural dos desenvolvedores, que devem passar a ter um olhar mais cuidadoso no trabalho de codificação, aplicando as melhores práticas de desenvolvimento seguro e buscando ter um olhar cada vez mais focado em segurança desde o início da criação de seus produtos.
      Um Security Champion, de forma geral, é um transformador cultural para as equipes de desenvolvimento, e atua também como uma ponte de ligação entre as áreas de desenvolvimento e de segurança. “É ele quem consegue manter um entendimento dos dois mundos, amenizando os conflitos e disputas”, esclarece Maués. 
      Algumas atividades comuns do dia a dia de um Security Champion são:
      Ajudar na realização de revisões de segurança;  Ajudar com a observação de melhores práticas de segurança; Desenvolver Modelagem de Ameaças para aplicativos novos e em evolução; Participar de movimentos de P&D – Pesquisa e Desenvolvimento; Orientar na identificação de requisitos de segurança; Avaliar e estudar bugs em código; Servir de elo de contato entre as demais equipes da área de segurança. No entanto, é muito importante ressaltar que o Security Champion não realiza essas tarefas sozinho. Tudo é feito em colaboração com o time! Afinal, o papel do Security Champion não é o de centralizar o conhecimento - e sim, de disseminá-lo nas equipes. 

      Como se tornar um Security Champion? Existe um perfil específico?
      É comum que Security Champions sejam desenvolvedores treinados e devidamente capacitados para suportar as iniciativas de segurança. 
      No entanto, isso não é regra - é possível que profissionais egressos de outras áreas, mas com algum conhecimento em desenvolvimento, recebam treinamento para atuar como Security Champions caso cumpram outros requisitos.
      De todo modo, é preciso ressaltar que Security Champions não são profissionais de segurança de aplicações que são focados exclusivamente em segurança. Essa confusão é muito comum, mas é uma concepção errada. 
      A escolha dos Security Champions dentro de cada time gera uma noção de pertencimento e ajuda no trabalho com os desenvolvedores. É imprescindível um trabalho cauteloso, que começa por um mapeamento de times. 
      Para isso, é preciso identificar e capacitar membros dos times de desenvolvimento que tenham esse perfil, para que atuem como facilitadores de segurança. E este papel exige características comportamentais, como iniciativa e autogestão.  Mas caso você sinta afinidade com essa carreira, esse já é um ótimo indício!

      Vagas na Conviso
      A Conviso, mantenedora aqui do Mente Binária, está com muitas vagas abertas. São vagas para áreas variadas dentro da empresa - de Desenvolvedor a Analista de Segurança da Informação.
      Caso você sinta afinidade com a especialidade da Conviso - Segurança de Aplicações - não deixe de se inscrever, ou mesmo de se cadastrar em nosso banco de talentos. É só clicar no botão abaixo:

    • By Fernando Mercês
      A Hex-Rays surpreendeu todo mundo agora. Liberou a versão mais recente do IDA, a 7.6, no modelo freeware. Só isso já seria muito bom, mas eles foram além: O IDA 7.6 freeware inclui um descompilador online (cloud-based) gratuito! Pois é, pessoas... Parece que a concorrência exercida pelo Ghidra realmente está sendo saudável para a comunidade. Ao disparar o plugin de descompilação (F5), o programa deixa claro que este recurso na versão gratuita suporta somente binários de 64-bits, dentre outros avisos:

       
      Ao continuar, a descompilação é concluída e códiogo em pseudo-C é exibido ao lado. Veja o exemplo descompilando o notead.exe nativo do Windows:

      Os recursos de interação como renomear variáveis e funções no descompilador estão habilitados, uma notícia muito boa para a comunidade!
      Além do descompilador, a versão 7.6 do IDA freeware inclui:
      Modo escuro
      Tá na moda né? E o IDA não ficou de fora. Se você for em Options -> Colors e mudar o Theme para dark, vai ter um visual assim:

       
      Organização de dados em pastas
      Sendo um disassembler interativo, a versão 7.6 oferece a opção de organizar os dados em pastas. Isso vale para vários locais. Um deles é a janela de funções. É possível agora criar pastas e organizar as funções dentro delas. Para isso, basta clicar com o botão direito do mouse na janela de funções e escolher Show folders. Depois é só selecionar as funções desejadas e escolher Create folder with items.

      E mais:
      Suporte a binários compilados em Go. Suporte ao novo Apple M1. Rebasing mais rápido. Sincronização entre o descompilador online e o disassembly. Lembando que o disassembler em si suporta binários de 32-bits também (PE, ELF e Mach-O). Só o descompilador que não.
       

    • By Bruna Chieco
      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!
    • By Bruna Chieco
      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.
×
×
  • Create New...