Ir para conteúdo

Felipe.Silva

Membros
  • Postagens

    88
  • Registro em

  • Última visita

  • Dias Ganhos

    29

Tudo que Felipe.Silva postou

  1. Quando você achar que deveria usar. Não existe "o momento certo" para usar, é você que decide isso. Quando você quiser valores de 2 bytes de tamanho, use `dw`. Quando quiser de 4 bytes, use `dd`. Quando quiser de 8 bytes, use `dq` e assim por diante. Ninguém pode tomar essa decisão além do próprio programador que está escrevendo o código. Portanto só você sabe quando usar. Quando você vai precisar de valores de 4 bytes de tamanho ao invés de 1 byte só? Ou de 8 bytes? Ou 2 bytes? Você que sabe. Veja o exemplo no print abaixo para entender o que essas pseudo-instruções fazem. Elas basicamente despejam os dados exatamente no lugar onde elas foram usadas. O que pode ser visto usando um visualizador hexadecimal como o `hd` (hexdump).
  2. E aí galera. Amanhã (sexta-feira) às 19:00 vou fazer uma apresentação ao vivo no canal Alquymia dando uma introdução sobre baixo nível. Na apresentação vou falar sobre: Arquitetura x86-64 Código de máquina Assembly Compiladores Quem tiver interesse de aprender como processadores funcionam não pode perder. Link para a live: Slides da apresentação: https://docs.google.com/presentation/d/1hPzG39uNl_tWji92vojtp0qZRabq12Mw4_y2GYq6YGc/edit?usp=sharing
  3. @Lincoln Arantes que tentativa infantil de se vingar só porque eu provei que o conteúdo de baixa qualidade que você estava compartilhando aqui no fórum foi gerado pelo ChatGPT. --------- Ah, e teve aquela vez também no Facebook que você afirmou que a linguagem C é código de máquina. As instruções em C estão dentro dos CI né? kkkkkkkkk Eu tenho os prints salvos então nem finja que nunca afirmou essa bobagem. Aliás, pior do que isso só se ainda acreditar nessa bobagem. Errar é humano, mas insistir no erro... kkkkkk De qualquer forma sua "solução" foi exatamente o que eu falei. No dia que eu escrevi isso daí eu fiz a conversão para ver se tinha mais alguma coisa. Eu verifiquei porque eu não ia afirmar que era "só isso" sem ter certeza que realmente era só isso. Só que eu não vou dar a solução de mão beijada, só dei a dica mesmo.
  4. Não tem o que ser decifrado, mano. Isso é uma sequência de bytes em hexadecimal, apenas converta de volta para bytes e veja no que deu.
  5. Desculpa a demora para responder. Bom, indo direto ao ponto: houve um erro meu e um seu aqui, rsrsrs. Repare que no seu arquivo 'assembly.asm' você definiu o rótulo como `main`, não é como está no código de exemplo passado no livro. Lá o nome do rótulo é `assembly` e não `main`. O nome do rótulo, não coincidentemente, é o mesmo da função invocada no código em C. O meu erro é que faltou um `section .text` no código em Assembly, por isso o erro de "undefined reference" no Windows (no Linux esse erro não acontece, sei lá porquê). Eu já atualizei as instruções no livro com a correção do código do 'assembly.asm' e adicionei os comandos de como compilar no Windows (só para garantir). O código, após a correção, ficou assim: bits 64 section .text global assembly assembly: mov eax, 777 ret Se você verificar o livro novamente e seguir as instruções atualizadas, deve funcionar dessa vez. Nota: Fiz os testes usando uma máquina virtual do Windows 7, MinGW-w64 versão 12.2.0-rt_v10-rev2 e NASM versão 2.16.01. Valeu por me marcar @Fernando Mercês.
  6. Sugiro ver os cursos da GoHacking (o "Ethical Hacking Modern Web Exploitation" por exemplo) e o HackTheBox. https://gohacking.com.br/cursos https://www.hackthebox.com/
  7. Essas diretivas CFI (Call Frame Information) geram informações que são usadas pelo depurador de código. A sigla CFA é de Canonical Frame Address que seria o endereço do stack pointer antes de entrar na função atual. A diretiva `.cfi_def_cfa` é usada para definir o valor do CFA. O primeiro parâmetro é um número que identifica um registrador e o segundo um valor numérico sinalizado (offset). O valor do CFA é definido como o valor desse registrador somado ao offset no segundo parâmetro. No caso o número 1 identifica o registrador EAX/RAX, o que não faz muito sentido ao meu ver. Essa diretiva foi tirada de um código real? Um exemplo real que peguei aqui foi: .cfi_def_cfa 7, 8 Onde o 7 é o registrador RSP. Então essa diretiva está gerando informação para dizer para o depurador: "Nesse exato momento o CFA é RSP+8" Você pode desabilitar essas diretivas com a flag -fno-asynchronous-unwind-tables, daí o código fica mais legível. Exemplo: $ gcc exemplo.c -o exemplo.s -S -fno-asynchronous-unwind-tables Referências 7.12 CFI directives - AS documentations CFI directives in assembly files - ImperialViolet
  8. Isso é só um rótulo (label) e não faz absolutamente nada. A sintaxe para declarar um rótulo é igual a sintaxe da linguagem C, colocando o nome do rótulo seguido de dois-pontos. Um rótulo é meramente um "nome" que pode ser usado para obter o endereço de memória do byte que está logo em seguida onde o rótulo foi declarado. Repare nesse código aí: .LC0: .string "ola" Logo após a declaração do rótulo há uma pseudo-instrução .string que recebe uma expressão de string como parâmetro. O que ela faz é simplesmente despejar os bytes da string aonde foi invocada. Portanto o rótulo .LC0 é um "nome" para o endereço da string "ola". Repare que no finalzinho do print esse rótulo é utilizado (na penúltima instrução): call __x86.get_pc_thunk.ax addl $_GLOBAL_OFFSET_TABLE, %eax subl $12, %esp leal .LC0@GOTOFF(%eax), %edx pushl %edx Esse call na primeira instrução é um "truque" que o GCC faz para obter endereços relativos. Para entender isso sugiro que leia isso aqui. A instrução leal (penúltima) é usada para obter o endereço daquela string e armazenar no registrador EDX, logo em seguida esse endereço é empilhado. --- O próprio Mente Binária tem um livro de Assembly que explica algumas coisas à respeito. O capítulo "Programando junto com C" será especialmente útil para você pois ele lida com o GAS e explica como código C funciona após compilado (em Assembly): https://mentebinaria.gitbook.io/assembly/programando-junto-com-c
  9. Você copiou e colou o código que te passei ou redigitou tudo? Se for o segundo caso, cole seu código aqui para eu ver (por completo). Ah, quando for exibir uma mensagem de erro é melhor mostrar ela por completo. Cada linha e cada letra. Pode até tirar um print do terminal também se quiser. Só com essa mensagem aí é impossível eu dizer o que está errado. Eu preciso das duas coisas: A mensagem de erro completa e o código completo que você escreveu. Uma dica quando for colar o código aqui, clique nesse botão com o símbolo "<>" que ele formata o texto como código:
  10. A linha `global_assembly:` deveria ser global seguido de assembly. Onde global é uma diretiva que será explicada posteriormente, e assembly seria o nome do rótulo logo abaixo (será explicado também). bits 64 global assembly assembly: mov eax, 777 ret
  11. Nunca usei a API deles mas confira na documentação na parte que fala sobre o POST de notificação. Segundo o que tá lá um PaymentId é enviado na requisição, o que te permite consultar o status da transação. O fluxo seria tipo: Recebi o POST com PaymentId. Uso o PaymentId para consultar na API o status da transação. Na documentação também diz que é possível cadastrar headers que serão enviados nessa requisição com um valor fixo. Isso pode servir como filtro para ter uma segurança mínima de que foi mesmo a CIELO que enviou o POST. Mas eu não confiaria tanto assim. ?
  12. Cara, você tem que primeiro obter a entrada do usuário e depois fazer o cálculo. Tu tá calculando o valor da variável `imc` antes, impossível obter o resultado correto. Mova a atribuição da variável imc para ficar após a leitura das variáveis peso e altura. Exemplo: imc = peso/(altura*altura); cout << "O seu IMC eh: " << imc << endl; return 0;
  13. Veja sobre a syscall mremap. * https://man7.org/linux/man-pages/man2/mremap.2.html
  14. 1.960 downloads

    Livro em Português sobre Assembly em constante desenvolvimento. É de autoria do @Felipe.Silva, membro da comunidade aqui! ?
  15. Você parou de atualizar head e tail em myMalloc()... Mas atualizar esses ponteiros é importante para o funcionamento desse sistema aí. Por exemplo: O que a função find_block() retorna se head for NULL? Ela só pode retornar NULL... Se você nunca inicializar head para apontar para um bloco, find_block() vai sempre retornar NULL. Você pode fazer algo mais ou menos assim: void *myMalloc(size_t size) { t_block b = find_block(size); if (b) { b->free = 0; } else { b = extend_heap(size); if (!b) return NULL; if (!head) head = b; tail = b; } return (b + 1); } Repare como eu inicializo head quando extend_heap() é chamado pela primeira vez. E repare que sempre é necessário chamá-lo eu atualizo tail. Entendeu a ideia? Assim head está sempre apontando para o primeiro bloco alocado e tail sempre apontando para o último. Não esquece disso também na myFree(). Ela precisa atualizar tail toda vez que liberar um bloco. E lembre-se que existe a possibilidade de myFree() ser usada no bloco apontado por head, neste caso o ponteiro head passaria a apontar para uma memória que já foi liberada. Talvez a solução mais simples neste caso seja redefinir head para NULL e "recomeçar". ?
  16. void *myMalloc(size_t size) { // TODO t_block b; if(head){ tail = head; // .... Esse "tail = head" não me parece certo. Pela lógica do código tail deveria ser atualizado para o novo bloco alocado na memória toda vez que a função extend_heap() precisar ser chamada... Do jeito que está tail volta a apontar para head a cada nova chamada de myMalloc() (exceto a primeira chamada onde head ainda será NULL). Sugiro atualizar o código para as vezes que precisou chamar "b = extend_heap(size);" atualizar tail para apontar para b. Ah, e qual é a lógica do else dentro da função myFree()? Também toma cuidado com a diferença entre = e ==. Dentro do if deveria ser == certo? Outra coisa que não entendi é isso: b = tail->next = NULL; Você quer modificar b para NULL? Mas logo em seguida você executa brk(b); o que não faria sentido passar NULL para esta função. E b também não é inicializada caso o valid_addr retorne false, pois você declarou a variável mas só a inicializa dentro do if. Essa estrutura s_block foi o professor que passou assim? Pois eu vejo um probleminha ao precisar desalocar memória e atualizar o valor de tail. Como você vai pegar o endereço do penúltimo bloco sem um ponteiro para o bloco anterior? Até daria para percorrer a lista para isso, mas seria mais eficiente ter um ponteiro t_block previous na estrutura. ? E uma coisa interessante é se sua myFree() liberasse todos os últimos blocos que estejam marcados como free, e não somente o último. Ou então deixar apenas N blocos no final e liberar o resto, assim poupa tempo pro myMalloc() não precisar de extend_heap() o tempo todo. Ah, uma dica: Antes de mais nada sugiro implementar logo a função debugBlockList(). Ela vai te ajudar a "visualizar" a lista e assim ficará mais fácil entender o que está funcionando conforme deveria e o que não está.
  17. Esse conceito de "unidade de armazenamento" associado à uma letra "só" existe no *Windows. No Linux (e outros UNIX-Like) os volumes são montados em diretórios. * "Só" porque MS-DOS e ReactOS também usam esse conceito. Se bobear pode haver algum sistema operacional desconhecido que faz isso também.
  18. "Nova" entre aspas. A linguagem existe desde 2005, mais velha que Go. ?
  19. 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
  20. O próprio Wine tem um debugger (winedbg) que você pode utilizar para depurar um .exe, dá para usar ele como gdb's server com a opção --gdb, caso você saiba usar o gdb... Porém o que o edb (e outros debuggers) faz é depurar processos e não arquivos executáveis. Se você pode iniciar um processo para o .exe então você pode depurá-lo. O passo-a-passo fica: Execute o .exe com o Wine. Abra o edb com privilégios root, menu "File" clique em "Attach". Procure pelo processo, dá para pesquisar pelo nome ou pelo PID. É necessário executar o edb como root porque é necessário privilégios root para poder fazer esse attach no processo em execução. Ah, um detalhe: É necessário que o processo se mantenha em execução para você poder fazer isso. Se ele finaliza imediatamente após iniciar você não vai conseguir fazer o attach.
  21. Na programação orientada a objetos existe o conceito de encapsulamento, que resumidamente é o conceito de não alterar uma variável diretamente mas sim usando uma função. Isso faz com que a variável fique "encapsulada" dentro do objeto e permite que qualquer lógica seja aplicada na alteração daquela variável. E é exatamente o que você quer fazer... Toda vez que as variáveis A e B são alteradas existe uma certa lógica ocorrendo com elas, não é? Então eu sugeriria você colocar tudo dentro de uma struct e implementar funções para modificar os valores de A e B. Tipo assim: struct var { bool mode; // A int value; // B int counter; }; void var_mode(struct var *variable, bool mode) { // etc... } void var_value(struct var *variable, int value) { // etc... }
  22. 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.
  23. Tenho uma ferramenta em Bash no meu GitHub chamada 'new' que pode ser útil para gerar executáveis assim, sem precisar de modificar "na mão". No repositório tu pode olhar a pasta "templates" que um deles é o bin-elf64. A sequência de comandos ficaria assim: $ nasm tst.asm -o tst.bin $ new bin-elf64 file=tst.bin out=tst $ chmod +x tst $ ./tst Um "Hello World!" fica com 171 bytes. Mas um detalhe: Menor não necessariamente significa melhor. Isso aí é só de brincadeira, o executável não vai ficar mais eficiente porque você "capou" ele. Mas como passa-tempo é válido.
×
×
  • Criar Novo...