Jump to content

Fernando Mercês

Administradores
  • Content Count

    618
  • Joined

  • Last visited

  • Country

    Brazil

Everything posted by Fernando Mercês

  1. Fantástico, @Wanderley Caloni! A equipe toda se emocionou com suas palavras. Eu jamais imaginei que passaríamos uma imagem de bem organizados, ainda mais no primeiro evento. Mas conseguimos! Obrigado pela sensibilidade, pontualidade, pela apresentação incrível e pelo conteúdo! Abraço!
  2. Desculpa, gente. O site tá com muitos acessos e levou uns tombinhos. hehe
  3. Version 3.0.0.1

    10 downloads

    Editor de recursos (seção .rsrc), tipo o ResHack. Coloquei aqui porque tá sumindo da internet e não podemos deixar isso acontecer. 🙂
  4. Ano passado eu assisti à uma palestra sobre esse novo utilitário da suíte GNU chamado poke. Ele é um editor de dados binários de linha de comando bem diferente dos que costumo usar (HT Editor, Hiew, etc). Hoje decidi testá-lo e curti bastante. Tá em mega beta, então não tá nem perto de ter pacote disponível nos repositórios oficiais das distros Linux, mas consegui compilar e neste artigo vou dar as instruções, que podem variar em cada ambiente, até porque o poke está em constante desenvolvimento. Usei um ambiente Debian testing aqui. Instalando as dependências A dependência mais chatinha de instalar foi a gettext, porque o pacote pronto dela não foi suficiente. Então tive que clonar e compilar: $ sudo apt install perf fp-compiler fp-units-fcl groff build-essential git $ git clone https://git.savannah.gnu.org/git/gettext.git $ cd gettext $ ./gitsub.sh pull $ ./autogen.sh $ ./configure $ make $ sudo make install Com a gettext instalada, agora podemos partir para as demais dependências do poke: $ sudo apt install build-essential libgc-dev libreadline-dev flex libnbd-dev help2man texinfo Só então podemos seguir para a compilação do poke. Compilando o poke $ git clone git://git.savannah.gnu.org/poke.git $ cd poke $ ./bootstrap $ ./configure $ make $ sudo make install Criando links para as bibliotecas Como instalei as bibliotecas do poke em /usr/local e o meu sistema não tinha este diretório configurado para que o loader busque as bibliotecas, precisei criar dois links para elas em /usr/lib: $ sudo ln -s /usr/local/lib/libpoke.so.0 /usr/lib/libpoke.so.0 $ sudo ln -s /usr/local/lib/libtextstyle.so.0 /usr/lib/libtextstyle.so.0 Sei que há outras maneiras de resolver isso, mas fiz assim pra acelerar, afinal eu queria mexer no poke logo! 🤪 Abrindo um binário PE no poke Baixei o executável do PuTTY para brincar um pouco e abri assim: $ poke putty.exe _____ ---' __\_______ ______) GNU poke 0.1-beta __) __) ---._______) Copyright (C) 2019, 2020 Jose E. Marchesi. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Powered by Jitter 0.9.212. Perpetrated by Jose E. Marchesi. hserver listening in port 47209. For help, type ".help". Type ".exit" to leave the program. (poke) Gerenciando os arquivos abertos O poke permite trabalhar com múltiplos arquivos de uma vez. Você pode ver a lista de arquivos abertos com o seguinte comando: (poke) .info ios Id Mode Size Name * #0 rw 0x0010b990#B ./putty.exe ios signifca "IO Spaces". Não tem nada a ver com o SO da Cisco ou com o da Apple. hehe Se quiser abrir outro arquivo, pode usar o comando .file <arquivo> e aí pode selecionar em qual você quer trabalhar com o comando .ios #n onde n é o número que identifica o arquivo, mas vou seguir o artigo com somente um arquivo aberto mesmo, então só teremos a tag #0. Dumpando dados Um dos principais comandos do poke é o dump (perceba este não começa com um ponto) que basicamente visualiza o conteúdo do arquivo, mas este tem várias opções. Vamos à mais básica: A primeira linha na saída acima é só uma régua pra te ajudar a encontrar os bytes. Fiz questão de colar uma captura de tela aí acima pra você ver que o poke colore a saída, mas nos exemplos seguintes vou colar a saída em texto pelo bem da sua largura de banda. 🙂 Por padrão, o dump exibe 128 bytes do arquivo, começando do seu primeiro byte. O número de bytes pode ser alterado na própria linha de comando: (poke) dump :size 64#B 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 4d5a 7800 0100 0000 0400 0000 0000 0000 MZx............. 00000010: 0000 0000 0000 0000 4000 0000 0000 0000 ........@....... 00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... A sintaxe pode parecer um pouco estranha no início, mas você acostuma rápido. O sufixo #B diz que a unidade usada é bytes. Você pode testar outros valores como 2#KB ou 1#MB por exemplo. 😉 Dumpando a partir de posições específicas Para dumpar a partir de uma posição específica, podemos usar a opção :from do comando dump: (poke) dump :from 0x30#B :size 32#B 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... 00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 7468 ........!..L.!th No comando acima eu pedi para o poke me mostrar 32 bytes a partir da posição 0x30. Seria o equivalente a fazer hd -n 32 -s 0x30 <arquivo>. O poke mantém um ponteiro de leitura no arquivo, por isso se você comandar somente dump novamente, o dump ocorrerá a partir da última posição lida (no caso, 0x30). Se quiser voltar o ponteiro para a posição zero, é a mesma sintaxe: dump :from 0#B. Interpretando dados O dump sempre te entrega uma saída em hexadecimal, mas e se quisermos interpretar os dados e exibi-los de maneiras diferentes? Para isso a gente larga de mão o comando dump e começa a operar com o jeito do poke de ler e interpretar especificamente, assim: (poke) byte @ 0#B 77UB O sufixo UB significa Unsigned Byte. Se eu quiser a saída em hexa por exemplo, basta eu setar a variável obase (output base): (poke) .set obase 16 (poke) byte @ 0#B 0x4dUB Eu poderia querer ler 2 bytes. Tranquilo: (poke) byte[2] @ 0#B [0x4dUB,0x5aUB] Posso interpretar o conteúdo como número também: (poke) uint16 @ 0#B 0x4d5aUH O prefixo UH significa Unsigned Half (Integer). Perceba que o poke sabe que um uint16 tem 2 bytes e por isso os lê sem a necessidade que especifiquemos o número de bytes a serem lidos. À essa altura você já sacou que equivalentes aos tipos padrão da linguagem C (da inttypes.h na real) estão disponíveis para uso né? Fique à vontade pra testar off64, int64, int32, etc. Lendo strings Além dos tipos numéricos, o poke tem o tipo string, onde ele lê até encontrar um nullbyte: (poke) dump 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 4d5a 7800 0100 0000 0400 0000 0000 0000 MZx............. 00000010: 0000 0000 0000 0000 4000 0000 0000 0000 ........@....... 00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000030: 0000 0000 0000 0000 0000 0000 7800 0000 ............x... 00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468 ........!..L.!Th 00000050: 6973 2070 726f 6772 616d 2063 616e 6e6f is program canno 00000060: 7420 6265 2072 756e 2069 6e20 444f 5320 t be run in DOS 00000070: 6d6f 6465 2e24 0000 5045 0000 4c01 0700 mode.$..PE..L... (poke) string @ 0x4d#B "!This program cannot be run in DOS mode.$" Patch simples Vamos fazer um patch simples: alterar o "T" desta string acima de maiúsculo para minúsculo. Basicamente é só colocar à esquerda o jeito que acessamos uma determinada posição do arquivo e igualar ao que a gente quer. Sabendo que para converter maiúsculo para minúsculo na tabela ASCII basta somar 32 (0x20), podemos fazer: (poke) byte @ 0x4e#B = 0x74 Perceba que fui na posição 0x4e, porque na 0x4d temos o '!' e não o 'T'. Só pra checar se funcionou: (poke) string @ 0x4d#B "!this program cannot be run in DOS mode.$" (poke) Legal né? Mas dá pra ficar melhor. O poke suporta char, então podemos meter direto: (poke) char @ 0x4e#B = 't' (poke) string @ 0x4d#B "!this program cannot be run in DOS mode.$" Por hora é só. Fica ligado aí que postarei a parte 2 em breve, onde vou mostrar mais recursos do poke que tô achando bem úteis para engenharia reversa. Até lá! 😎
  5. Ah, isso é a sintaxe do IDA. É exatamente a mesma coisa: jmp dword ptr ds:[edi*4+100011] // OllyDbg, x64dbg, etc jmp dword_100011a[edi * 4] // IDA Ambas são interpretações de um salto indireto FF 24. Pode checar com o radare2 ó: $ rasm2 -ax86 -d ff24bd11001000 jmp dword [edi*4 + 0x100011] FF 24 é o jump indireto. BD é o EDI*4 e o resto é o endereço do array. Percebe que o radare já não usa o "ptr" que o Olly e o x64dbg usam. Pode ser confuso mesmo, mas é tudo a mesma coisa. É que assim, no geral as pessoas simplificam em sintaxes Intel e AT&T, mas na prática os disassemblers fazem "pequenos patches" nelas. Só pra você ver o quanto varia: IDEAL (Borland): JMP [DWORD DS:EDI*4+0x100011] HLA (High Level Assembly): JMP ([TYPE DWORD DS:EDI*4+0x100011]) AT&T: jmp *%ds: 100011(,%edi,4) Sem contar as opções que os disassemblers possuem como omitir o segmento (aí não vai ter mais o ds:), disassemblar em maiúsculo ou minúsculo, com ou sem o "0x", etc. E tem mais: quando estamos escrevendo código-fonte em Assembly, mudam detalhes também. No MASM por exemplo se o seu array se chama "arr", vai ser arr[edi * 4] (como o IDA mostra). Mas aí tem que ver como é no TASM, YASM, NASM, BASM... Enfim, no final, sabendo que as diferenças de sintaxe existem, compare sempre os opcodes, assim você vai ter certeza do que tá rolando. 😉 Abraço!
  6. Ótima pergunta. Acho realmente incrível estudar os pormenores! Muitos livros passam batido e acho que uma das coisas mais difíceis de se fazer é explicar as bases com qualidade. Vou tentar. 🙂 Você já deve ter ouvido falar que utilizar variáveis numéricas em construções switch/case é "melhor" ou "mais rápido", certo? Fato que a linguagem C nem suporta com outros tipos, mas em PHP por exemplo permite o uso de strings: switch ($var) { case "mente": // código break; case "binária": // código break; default: // código break; } Já no C, tem que ser números mesmo. E aí tem várias maneiras de otimizar isso, em vários níveis. Como a instrução CMP é meio "cara" para o processador, os compiladores tentam evitá-la, assim como os saltos. Num teste rápido aqui compilei o seguinte código no macOS ó: #include <stdio.h> int main(int argc, char *argv[]) { int var; scanf(" %d", &var); switch (var) { case 0xaa: printf("http://menteb.in"); break; case 0xbb: printf("mentebinaria.com.br"); break; case 0xcc: printf("papobinario.com.br"); break; default: printf("menteb.in/conf"); } putchar('\n'); return 0; } O compilador (no meu caso, o clang) evitou instruções CMP usando SUB do valor que pus no case e depois pulando se for zero (JE/JZ): 0000000100000e72 subl $0xaa, %r8d 0000000100000e79 movl %eax, -0x18(%rbp) 0000000100000e7c movl %edx, -0x1c(%rbp) 0000000100000e7f movl %r8d, -0x20(%rbp) 0000000100000e83 je 0x100000eba 0000000100000e89 jmp 0x100000e8e 0000000100000e8e movl -0x1c(%rbp), %eax 0000000100000e91 subl $0xbb, %eax 0000000100000e96 movl %eax, -0x24(%rbp) 0000000100000e99 je 0x100000ed0 0000000100000e9f jmp 0x100000ea4 0000000100000ea4 movl -0x1c(%rbp), %eax 0000000100000ea7 subl $0xcc, %eax 0000000100000eac movl %eax, -0x28(%rbp) 0000000100000eaf je 0x100000ee6 Mas ainda tem os saltos condicionais JE/JZ que fazer o processador ter que checar a Z flag. Basicamente tô falando tudo isso pra te dizer que o código nem sempre vai ser o que você acha. O compilador pode otimizar de muitas formas. No clang aqui tem várias opções no manual ó: -O0, -O1, -O2, -O3, -Ofast, -Os, -Oz, -Og, -O, -O4 Specify which optimization level to use: -O0 Means "no optimization": this level compiles the fastest and generates the most debuggable code. -O1 Somewhere between -O0 and -O2. -O2 Moderate level of optimization which enables most optimizations. -O3 Like -O2, except that it enables optimizations that take longer to perform or that may generate larger code (in an attempt to make the program run faster). -Ofast Enables all the optimizations from -O3 along with other aggressive optimizations that may violate strict compliance with language standards. -Os Like -O2 with extra optimizations to reduce code size. -Oz Like -Os (and thus -O2), but reduces code size further. -Og Like -O1. In future versions, this option might disable different optimizations in order to improve debuggability. -O Equivalent to -O2. -O4 and higher Currently equivalent to -O3 O gcc tem uma flag específica pra criar ou não as jump tables que você tá com dúvida (a opções são fjump-tables e fno-jump-tables) e você pode fazer um teste aí. Falando especificamente sobre as jump tables agora... Basicamente, ao invés de fazer várias comparações, o compilador deu um jeito de usar o valor da variável submetida ao switch (no caso, chamada de edi no seu exemplo) para fazer uma conta e pular para o local certo. Como? Bem, como mostrado na linha 19, no endereço 0x100011a4 o compilador criou a jump table, ou seja, um array (portanto, possui seus elementos armazenados em sequência) com 6 elementos, que contêm o código de cada caso diferente (perceba que existem 6 casos, excluindo o default, mas só 4 são únicos). Este endereços são endereços de 32-bits, logo, 4 bytes cada, então ficaria assim: 0x100011a4 <+0> == 0x10001125 0x100011a4 <+4> == 0x10001125 0x100011a4 <+8> == 0x1000113A 0x100011a4 <+12> == 0x1000112C 0x100011a4 <+16> == 0x10001133 0x100011a4 <+20> == 0x1000113A Certo? Tô somando de 4 em 4 bytes. Também poderia escrever os endereços na forma 0x100011a4, 0x100011a8, 0x100011ac, etc, ao invés de usar a notação 0x100011a4 <+offset em decimal>, sacou? Legal. Cada um desses endereços é um ponteiro e o valor é o endereço do código de um caso único. Por exemplo, o primeiro elemento do array contém o valor 0x10001125, que contém o código dos casos 0 e 1. Por isso o segundo elemento do array tem o mesmo valor do primeiro. 😉 Já o terceiro elemento tem o valor 0x1000113A, que é o endereço do código para os casos 2 e 5. E por aí vai... Agora como fazer a comparação pra usar a jump table? Analise de novo da linha 1 em diante do teu código... Tem uma comparação logo de cara, com 5. Se for maior, cai no caso default. O compilador inteligentemente utilizou a instrução JA e não JG. A diferença é que a primeira interpreta os valores como sem sinal, portanto se a variável por igual a -1 por exemplo, ela vai pular pro caso default de qualquer jeito. Foda né? 😎 Na linha 3 vem a mágica: jmp ds:0x100011a4[edi * 4] O compilador usou a própria variável submetida ao switch (no caso, o valor dela está no registrador EDI) pra direcionar o salto incondicional JMP. Em 0x100011a4 temos a nossa jump table e EDI está sendo usado como índice do elemento do array a ser lido. Então: Se EDI é 0, 0*4==0, então o primeiro elemento do array será lido e o salto vai para 0x10001125. Se EDI é 1, 1*4==4, então o segundo elemento do array será lido e o salto vai para 0x10001125. Se EDI é 2, 2*4==8, então o terceiro elemento do array será lido e o salto vai para 0x1000113A. Se EDI é 3, 3*4==12, então o terceiro elemento do array será lido e o salto vai para 0x1000112C. Se EDI é 4, 4*4==16, então o terceiro elemento do array será lido e o salto vai para 0x10001133. Se EDI é 5, 5*4==20, então o terceiro elemento do array será lido e o salto vai para 0x1000113A. É como na aritmética de ponteiros em C - uma soma por exemplo considerará o tamanho do tipo. Então, no código abaixo, o primeiro elemento vai estar num endereço X e o segundo em X+4, o terceiro em X+8 etc. Testa aí só: #include <stdio.h> int main(int argc, char *argv[]) { int array[] = { 0x10001125, 0x10001125, 0x1000113a, 0x1000112c, 0x10001133, 0x1000113a }; for (int i=0; i<6; i++) printf("%p: %#.2x\n", array+i, *(array+i)); return 0; } No meu caso aqui deu isso: $ gcc -o jmp jmp.c && ./jmp 0x7ffee12a48c0: 0x10001125 0x7ffee12a48c4: 0x10001125 0x7ffee12a48c8: 0x1000113a 0x7ffee12a48cc: 0x1000112c 0x7ffee12a48d0: 0x10001133 0x7ffee12a48d4: 0x1000113a Perceba que na primeira iteração, o printf imprime o endereço do array, que no meu caso é 0x7ffee12a48c0. Na segunda iteração, o printf já imprime o endereço do segundo elemento, que é 0x7ffee12a48c4 e não 0x7ffee12a48c1, pois cada elemento possui 4 bytes. Por isso o [edi * 4] no teu código. 😉 Acho que entendendo isso você mesmo consegue responder as perguntas que fez, mas avisa aí se faltar algo. Abraço!
  7. Pára com isso, você é muito inteligente em dedicar tempo estudando programação em C. Admirável também postar seus avanços aqui. Isso não só é bom pra ti, mas ajuda e inspira outras pessoas, ou seja, é um sinal de no-ego muito forte. Parabéns e muito obrigado! 👍
  8. Fernando Mercês

    Cursos

    É, acho que seria muito legal ter um curso de Assembly no canal, mas não sei se sou a melhor pessoa pra isso viu. Tô aberto a propostas. kkkkk Por hora, conheço esse em Português ó: Abraço!
  9. Comecei a estudar a linguagem Go há alguns dias e fiquei muito impressionado com seus recursos. A facilidade para programação paralela, o fato de ter ponteiros, funções que retornam mais de um valor, código enxuto (se você declarar uma variável e não usar, o programa nem compila!) e outros realmente me encantaram. Recentemente precisei disassemblar um trecho de código de um binário PE para um projeto que está escrito em Go. Vi que existem algumas bibliotecas prontas para serem usadas, como gapstone (bindings da Capstone) e go-zydis (bindings da Zydis) mas não encontrei uma nativa. No entanto, vi que existe uma ferramenta nativa no toolset da linguagem similar ao objdump do GNU binutils: $ go doc cmd/objdump Objdump disassembles executable files. Usage: go tool objdump [-s symregexp] binary Objdump prints a disassembly of all text symbols (code) in the binary. If the -s option is present, objdump only disassembles symbols with names matching the regular expression. Compilei um "hello, world" em Go só pra ver: ~/hello $ cat main.go package main import "fmt" func main() { fmt.Println("menteb.in") } ~/hello $ go build E de fato o objdump da Go funciona: ~/hello $ go tool objdump hello | head TEXT go.buildid(SB) :-134217728 0x1001000 ff20 JMP 0(AX) :-134217728 0x1001002 476f OUTSD DS:0(SI), DX :-134217728 0x1001004 206275 ANDB AH, 0x75(DX) :-134217728 0x1001007 696c642049443a20 IMULL $0x203a4449, 0x20(SP), BP :-1 0x100100f 226d35 ANDB 0x35(BP), CH :-1 0x1001012 4c6f OUTSD DS:0(SI), DX :-1 0x1001014 6a52 PUSHL $0x52 :-1 0x1001016 436e OUTSB DS:0(SI), DX :-1 0x1001018 4a31794f XORQ DI, 0x4f(CX) Mas ao tentar com o um PE compilado pra 64-bits, descobri que só funciona com binários feito em Go. 😩 $ go tool objdump putty.exe objdump: disassemble putty.exe: no runtime.pclntab symbol found De qualquer forma, resolvi olhar o código-fonte deste objdump interno da linguagem pra ver qual é dessa mandinga. Na linha 43 do main.go do objdump tem um import pra uma biblioteca chamada objfile. Pensei: Wow, deve ser uma biblioteca de disassembly, talvez eu possa alterar ! E na hora já criei um projeto tentando usá-la mas fui surpreendido com um errão! kkkk ~hello $ cat main.go package main import "fmt" import "cmd/internal/objfile" func main() { fmt.Println("menteb.in") } ~hello $ go build main.go:4:8: use of internal package cmd/internal/objfile not allowed Não pesquisei muito sobre essa história sobre eu não poder usar um pacote interno (por quê o objdump pode e eu não posso?!), mas fui olhar esta objfile e terminei encontrando seu fonte. Para minha alegria, neste arquivos disasm.go vi os seguintes imports: "golang.org/x/arch/arm/armasm" "golang.org/x/arch/arm64/arm64asm" "golang.org/x/arch/ppc64/ppc64asm" "golang.org/x/arch/x86/x86asm" Agora sim, carái! É tudo público e posso usar. Desculpe o desabafo.. hehe o artigo na verdade começa aqui mas quis contar como cheguei porque né. 😁 Cada uma dessas bibliotecas possui uma função Decode() justamente pra decodificar uma instrução (tipo Inst). Testei com um NOP em 64-bits, só pra ver: package main import ( "fmt" "log" "golang.org/x/arch/x86/x86asm" ) func main() { dados := []byte{0x90} ins, err := x86asm.Decode(dados, 64) if err != nil { log.Fatalln(err) } fmt.Println(ins) } A saída foi exatamente a esperada: $ ./hello NOP Show. Agora é abrir um PE, ler de onde quero e daí disassemblar usado essa x86asm.Decode() num loop, mas vou deixar esse exercício aí pra quem quiser treinar Go. Ou se acharem útil posso postar um aqui mais tarde. Aqui já funcionou mas precisa de uma polida. 🙂 Perceba também que há bibliotecas para ARM e PowerPC. Achei bem maneiro. Talvez em breve o time da Go adicione suporte a mais arquiteturas. Amém! 🙏
  10. @fredericopissarra, pode dar uma luz aqui? 🙂
  11. Eita, não sabia disso. Você tem razão: a função é preparada para limpar buffers de saída, não de entrada. Tenho a sensação de já ter usado com buffer de entrada, então deve ter bastante código bugado meu por aí! kkkk Existe a fpurge também, mas não sei se é padrão. Nunca usei. Obrigado por alertar, @Marioh! 👍 Abraço!
  12. Yep, como o @Marioh falou, é preciso consumir o buffer (no caso, o stdin, que é o buffer que você tá usando). Acontece que ao digitar 1 e pressionar [enter], você insere dois bytes no buffer: o '1' e '\n'. Aí você tá consumindo só o primeiro com o seu scanf(), deixando o '\n' lá. Como a fgets() lê até encontrar um \n, ela o encontra logo de cara não copia mais nada pra char nome[]. Ou seja, o comportamento é o esperado. Tem outras soluções além da do Marioh. No final vão meio que dar no mesmo: 1. Usando a fflush(): scanf("%c", &value_menu); fflush(stdin); 2. Lendo o \n (que é o segundo byte) pra outra variável (meio feio): char tmp; scanf("%c%c", &value_menu, &tmp); No caso acima você não usaria o char tmp nunca mais, mas se olhar a documentação da scanf, vai ver que tem isso ó: CONVERSIONS Following the % character introducing a conversion, there may be a number of flag characters, as follows: * Suppresses assignment. The conversion that follows occurs as usual, but no pointer is used; the result of the conversion is simply discarded. 3. Ou seja, podes usar o *: scanf("%c%*c", &value_menu); Assim não vai precisar da variável auxiliar. Independente do que escolher (e provavelmente há soluções além dessas), o importante é você entender o que se passa. 🙂 Abraço!
  13. @dudsdev não creio que seja o menor PE possível do ponto de vista do tamanho do arquivo (porque ainda teria alguns cabeçalhos que daria pra remover e tal) mas de funcionalidade o código compila sim. Chutaria uns 2KB de tamanho... Abraço.
  14. Oi @kassane. Eu não tenho usado o Telegram mas se puder compartilhar lá, agradecemos! 😉
  15. O que é Análise de Malware? É o processo de entender como um malware funciona, o que faz e qual seu impacto no ambiente alvo. Pode envolver análise de toda a campanha ou infraestrutura do malware e até mesmo chegar em atribuição. Como funciona? Com conhecimentos de engenharia reversa, análise estática e dinâmica, além de algum conhecimento específico da comunidade de investigação, é possível mapear as rotinas maliciosas de um arquivo, entender seu funcionamento e até mesmo gerar assinaturas para deteção, prevenção e mitigação de ataques de malware. O treinamento O AMO (Análise de Malware Online) é um treinamento básico gratuito publicado no nosso canal no YouTube Papo Binário, graças ao suporte dos nossos apoiadores. A ideia é formar um analista de malware júnior, que possa atuar com análise em empresas ocupando cargos na área de análise de malware, resposta à incidentes, SOC, analista forense, entre outros. As aulas publicadas estão abaixo. Pré-requisitos Conhecimento em criação de máquinas virtuais. Ter assistido o Curso de Engenharia Reversa Online (CERO). Ter assistido o Programação Moderna em C. Instrutor @Fernando Mercês Aulas publicadas Aula 0+1 - Preparando o ambiente e analise inicial do primeiro malware! Aula 02 - Conhecendo o DIE e o IDA Aula 03 - Monitoração de chamadas com o API Monitor Aula 04 - Análise dinâmica de criptografia (Parte 1) Aula 05 - Scriptando o x64dbg Aula 06 - Análise dinâmica de criptografia (Parte 2)
  16. Saudações, gnoo. É, verifiquei aqui no sistema e não tem essa opção, não sei o motivo, já que é possível editar os posts no fórum e as avaliações feitas, por exemplo. O jeito é comentar de novo com a ratificação. Não é a solução mais adequada, entendo, mas não vejo outra alternativa rápida. Abraço.
  17. É nóis! Obrigado pelas perguntas também! 😉 Sim, os endereços nela são resolvidos em loading time e o loader a preenche. Eu não lembro se falo especificamente desta seção, mas já viu esta aula do CERO? De qualquer forma, tem o livro "Learning Linux Binary Analysis" do elfmaster que tem mais informações. Eu sei pouco sobre este processo, só entendo o básico mesmo. Sinceramente, eu encontrei esse arquivo por minha conta quando tava tentando dumpar a memória de um processo no contexto de forense. Nunca vi documentação sobre ele, mas acho que o comando pmap o utiliza, então pode ser outra forma de buscar também, além do fonte do kernel em si claro. Abraço!
  18. Olá, bom dia! Estamos buscando um Desenvolvedor com conhecimento de C++ que tenha experiência com framework Qt, Git e MySQL para atuar em uma empresa de tecnologia especializada em Engenharia Submarina localizada na Barra da Tijuca. Inglês de intermediário a avançado é importante. Você tem interesse? Pode encaminhar seu CV com atual salário? Caso não, posso contar com a sua indicação de bons profissionais? Aguardo seu retorno. Obrigada, isabelle.teixeira @ beexecutive.com . br
  19. Olha, eu não sou um grande especialista e toda vez que olho no Linux parece que tá tudo diferente mas acho que só tá te faltando o conceito de mapeamento (mapping). No caso do ELF os segmentos (no arquivo) são mapeados para a memória, ou seja, têm seu conteúdo copiado e ganham endereços lá. Só que cada segmento pode possuir várias seções. Por exemplo, vou compilar um programa que imprime seu próprio PID e também tem uma string (pra forçar a criação da seção .data no binário): #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { char s[] = "Meu PID:"; printf("%s %d\n", s, getpid()); while (1); return 0; } Podemos compilar e analisar como ficaram os segmentos e as seções no binário (no arquivo, antes de ser carregado, ou seja, antes de ter suas seções mapeadas para a memória): $ gcc -no-pie -o hello hello.c $ readelf -l hello Elf file type is EXEC (Executable file) Entry point 0x401050 There are 11 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x0000000000000268 0x0000000000000268 R 0x8 INTERP 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000470 0x0000000000000470 R 0x1000 LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000 0x00000000000001dd 0x00000000000001dd R E 0x1000 LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000 0x0000000000000148 0x0000000000000148 R 0x1000 LOAD 0x0000000000002e10 0x0000000000403e10 0x0000000000403e10 0x0000000000000228 0x0000000000000230 RW 0x1000 DYNAMIC 0x0000000000002e20 0x0000000000403e20 0x0000000000403e20 0x00000000000001d0 0x00000000000001d0 RW 0x8 NOTE 0x00000000000002c4 0x00000000004002c4 0x00000000004002c4 0x0000000000000044 0x0000000000000044 R 0x4 GNU_EH_FRAME 0x000000000000200c 0x000000000040200c 0x000000000040200c 0x000000000000003c 0x000000000000003c R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000002e10 0x0000000000403e10 0x0000000000403e10 0x00000000000001f0 0x00000000000001f0 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .got.plt .data .bss 06 .dynamic 07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09 10 .init_array .fini_array .dynamic .got Perceba que os segmentos de 02 a 05 são do tipo LOAD. Isto significa que eles serão mapeados/carregados/copiados para a memória. Na sequência você qual seção pertence a cada segmento. Daria pra dizer o seguinte então: A .text tá no segmento 03, vai ser mapeada em 401000, que tem permissão de leitura e execução (R E). A .rodata tá no segmento 04, vai ser mapeada em 402000, que tem permissão de leitura somente (R). A .data e .bss estão no segmento 05, vão ser mapeadas em 403e10, que tem permissão de leitura e escrita (R W). Só que seções diferentes vão para regiões diferentes da memória do processo, como a imagem que você postou ilustra, então esse mapeamento não é um pra um. Pra checar, executa o programa e pega o PID: $ ./hello Meu PID: 2186 Noutra aba: $ cat /proc/2186/maps 00400000-00401000 r--p 00000000 08:01 131608 /tmp/hello 00401000-00402000 r-xp 00001000 08:01 131608 /tmp/hello 00402000-00403000 r--p 00002000 08:01 131608 /tmp/hello 00403000-00404000 r--p 00002000 08:01 131608 /tmp/hello 00404000-00405000 rw-p 00003000 08:01 131608 /tmp/hello 01620000-01641000 rw-p 00000000 00:00 0 [heap] 7f9efaa89000-7f9efaaab000 r--p 00000000 08:01 664113 /usr/lib/x86_64-linux-gnu/libc-2.28.so 7f9efaaab000-7f9efabf3000 r-xp 00022000 08:01 664113 /usr/lib/x86_64-linux-gnu/libc-2.28.so 7f9efabf3000-7f9efac3f000 r--p 0016a000 08:01 664113 /usr/lib/x86_64-linux-gnu/libc-2.28.so 7f9efac3f000-7f9efac40000 ---p 001b6000 08:01 664113 /usr/lib/x86_64-linux-gnu/libc-2.28.so 7f9efac40000-7f9efac44000 r--p 001b6000 08:01 664113 /usr/lib/x86_64-linux-gnu/libc-2.28.so 7f9efac44000-7f9efac46000 rw-p 001ba000 08:01 664113 /usr/lib/x86_64-linux-gnu/libc-2.28.so 7f9efac46000-7f9efac4c000 rw-p 00000000 00:00 0 7f9efac71000-7f9efac72000 r--p 00000000 08:01 664105 /usr/lib/x86_64-linux-gnu/ld-2.28.so 7f9efac72000-7f9efac90000 r-xp 00001000 08:01 664105 /usr/lib/x86_64-linux-gnu/ld-2.28.so 7f9efac90000-7f9efac98000 r--p 0001f000 08:01 664105 /usr/lib/x86_64-linux-gnu/ld-2.28.so 7f9efac98000-7f9efac99000 r--p 00026000 08:01 664105 /usr/lib/x86_64-linux-gnu/ld-2.28.so 7f9efac99000-7f9efac9a000 rw-p 00027000 08:01 664105 /usr/lib/x86_64-linux-gnu/ld-2.28.so 7f9efac9a000-7f9efac9b000 rw-p 00000000 00:00 0 7ffcd5fbf000-7ffcd5fe0000 rw-p 00000000 00:00 0 [stack] 7ffcd5feb000-7ffcd5fee000 r--p 00000000 00:00 0 [vvar] 7ffcd5fee000-7ffcd5ff0000 r-xp 00000000 00:00 0 [vdso] Veja que temos 5 segmentos no hello, além de heap e stack. Mas eram 4 segmentos que seriam mapeados né? É, mas algumas seções deles foram mapeadas com permissões diferentes, exigindo outros segmentos. Para ver quais, pode usar o gdb, attachando no processo: $ gdb -q -p 2186 Attaching to process 2186 Reading symbols from /tmp/hello...(no debugging symbols found)...done. Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...(no debugging symbols found)...done. Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done. 0x000000000040116b in main () (gdb) info file Symbols from "/tmp/hello". Native process: Using the running image of attached process 2186. While running this, GDB does not access memory from... Local exec file: `/tmp/hello', file type elf64-x86-64. Entry point: 0x401050 0x00000000004002a8 - 0x00000000004002c4 is .interp 0x00000000004002c4 - 0x00000000004002e4 is .note.ABI-tag 0x00000000004002e4 - 0x0000000000400308 is .note.gnu.build-id 0x0000000000400308 - 0x0000000000400324 is .gnu.hash 0x0000000000400328 - 0x00000000004003a0 is .dynsym 0x00000000004003a0 - 0x00000000004003e6 is .dynstr 0x00000000004003e6 - 0x00000000004003f0 is .gnu.version 0x00000000004003f0 - 0x0000000000400410 is .gnu.version_r 0x0000000000400410 - 0x0000000000400440 is .rela.dyn 0x0000000000400440 - 0x0000000000400470 is .rela.plt 0x0000000000401000 - 0x0000000000401017 is .init 0x0000000000401020 - 0x0000000000401050 is .plt 0x0000000000401050 - 0x00000000004011d1 is .text 0x00000000004011d4 - 0x00000000004011dd is .fini 0x0000000000402000 - 0x000000000040200b is .rodata 0x000000000040200c - 0x0000000000402048 is .eh_frame_hdr 0x0000000000402048 - 0x0000000000402148 is .eh_frame 0x0000000000403e10 - 0x0000000000403e18 is .init_array 0x0000000000403e18 - 0x0000000000403e20 is .fini_array 0x0000000000403e20 - 0x0000000000403ff0 is .dynamic 0x0000000000403ff0 - 0x0000000000404000 is .got 0x0000000000404000 - 0x0000000000404028 is .got.plt 0x0000000000404028 - 0x0000000000404038 is .data 0x0000000000404038 - 0x0000000000404040 is .bss Taí, todas as seç˜ões daqueles 4 segmentos do tipo LOAD (PT_LOAD o nome certo hehe) e seus devidos endereços de início e sim. Você pode fazer um paralelo com o conteúdo do /proc/<pid>/maps e ver que faz sentido, por exemplo, a .data estar em 404028 já que a .got.plt também precisa ficar numa região com leitura e escrita e esta é listada antes da data no segmento 05. Já a .got, originalmente (no arquivo), num segmento com rw, foi para uma região somente com leitura. Resumindo, na prática o número de regiões de memória (segmentos na imagem que você postou) pode ser outro. Pode ser confuso mesmo, porque tem segmento e seção no arquivo, depois segmento em memória e seção na memória, ou seja, 4 coisas, mas o mapeamento não é um pra um. Espero que ajude. Brincando mais com gdb, readelf, etc aí e fazendo mais testes tenho certeza que vai ficar mais claro. 👍 Abraço!
  20. Opa, tem várias coisas incorretas aí. O certo seria imprimir a. O tamanho do array é 5, mas você tá passando 10. Essa linha diz "enquanto 10 for menor que 10", então nem nada neste bloco será executado... A sua função sort também tá meio confusa. Não entendi bem como você quis fazer. O bubble sort consiste simplesmente em percorrer o array n-1 vezes, onde n é o tamanho do array (ou seja, seu número de elementos) testando se o elemento é menor que o anterior. Se sim, trocam-se. Recomendo estudar mais o bubble sort. Tem muito material na web sobre, além de vídeos no YouTube, etc. Só não pegue nada pronto, do contrário você não aprende e daí seu tempo de estudo é jogado no lixo e a grana e o tempo que você tá investindo neste curso/faculdade também. 😉 Abraço!
  21. Fernando Mercês

    MBConf@Home

    until
    Nossa primeira conferência, totalmente online e gratuita! Todas as informações sobre a MBConf estão disponíveis em http://menteb.in/conf . Se você já viu tudo lá e quer se registrar, basta confirmar sua presença aqui. Pouco antes do horário de início da conferência você receberá um link para ingressar nela. Agenda 10h00 - Abertura - Fernando Mercês 10h15 - Análise do malware Guildma - Renato Marinho 11h05 - Sistemas embarcados - Sergio Prado 11h55 - Debugging tricks - Thiago Queiroz 12h45 - Bluetooth shell - Noilson Caio 13h35 - Debugging com WinDbg - Wanderley Caloni 14h25 - Engenharia Reversa em Android - Maycon Vitali 15h10 - Encerramento - Anchises Moraes Lembre-se de compartilhar a conferência nas suas redes sociais. 😎
  22. Opa! Na verdade a função compara o argumento recebido (char param_1) com os elementos de um array (eu prefiro chamar assim, pra não confundir com os tipos vector em algumas lingaugens). Só pra ficar mais preciso, pois não é "um valor de um vetor". Mas eu entendi o que você quis dizer. 😉 O array tem que estar pronto quando essa função é chamada. Logo, basta colocar um breakpoint logo no início dessa função e olhar o conteúdo do array. Não lembro se o Ghidra já tem debugger, mas você pode usar o gdb. Na real, podemos assumir que não existem muitos "tipos" em baixo nível. É tudo número mesmo e o tamanho é que importa. Se o Ghidra usou (int) e o tamanho do elemento é 4, é isso, estamos diante de um array de elementos do tipo int32 (4 bytes). É uma boa dedução, mas dá pra ver isso rápido porque o Ghidra já nomeou o vetor como DAT_. Este prefixo é para variáveis na seção .data (ou numa seção com as mesmas propriedades, já que o nome da seção em si não vale de muita coisa). A distância do endereço dele (301020) para a função (1007fa), que tá numa seção de código - normalmente a .text), também sugere que ele está em outra seção e se você for no "memory map" do Ghidra (entre aspas porque não lembro como o Ghidra chama), vai ver que 301020 está no range da seção .data (entre seu início e fim). Se você desconfiar das suposições do Ghidra, pode sempre checar o Assembly. De novo, o que vai definir o tipo é como o conteúdo é lido. Se são 4 bytes, é int32. Se as instruções de comparação são as que consideram sinal, então é signed. Do contrário, unsigned. E por aí vai. Ótimas perguntas! Abraço!
  23. Hackers To Hackers Conference (H2HC) é uma conferência organizada por pessoas que trabalham ou que estão diretamente envolvidas com pesquisas e desenvolvimento na área de segurança da informação, cujo principal objetivo é permitir a disseminação, discussão e a troca de conhecimento sobre segurança da informação entre os participantes e também entre as empresas envolvidas no evento. Com treinamentos e palestras apresentadas por membros respeitados do mundo corporativo, grupos de pesquisas e comunidade underground, neste ano a conferência promete demonstrar técnicas que nunca foram vistas ou discutidas com o público anteriormente. E por que realizar uma conferência onde podem ser demonstradas novas técnicas de ataque, novas ferramentas e pontos de inseguranças de sistemas? Porque queremos mostrar esse tipo de informação para o público, principalmente para pessoas cujo trabalho é proteger e aumentar a segurança dos sistemas e fazer com que elas entendam melhor como os outros atacam os seus computadores. As pessoas que atacam normalmente conhecem diversas técnicas e é importante que analistas de segurança, auditores de sistemas entre outras pessoas responsáveis pela segurança também saibam como se defender. Como pode ser percebido ao acessar os melhores fóruns, sites e listas de e-mail sobre o assunto, encontrar falhas de segurança não é uma tarefa muito difícil. Para cada falha descoberta por um pesquisador e enviada a um fabricante, provavelmente existem outras que já são conhecidas por pesquisadores que não notificam o fabricante. E nós acreditamos que a melhor maneira para se proteger contra essas falhas desconhecidas é entender profundamente como os problemas acontecem e criar mecanismos de segurança para impedir uma classe de falhas, e não apenas aplicar as correções publicadas pelo fabricante e esperar que ninguém ataque seu sistema com 0day. Mais informações: https://www.h2hc.com.br/h2hc/pt/
×
×
  • Create New...