Jump to content

Felipe.Silva

Membros
  • Posts

    78
  • Joined

  • Last visited

  • Country

    Brazil

Everything posted by Felipe.Silva

  1. 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:
  2. 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
  3. 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. 🙂
  4. 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;
  5. Veja sobre a syscall mremap. * https://man7.org/linux/man-pages/man2/mremap.2.html
  6. 276 downloads

    Livro em Português sobre Assembly em constante desenvolvimento. É de autoria do @Felipe.Silva, membro da comunidade aqui! 🤗
  7. 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". 🙂
  8. 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á.
  9. 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.
  10. "Nova" entre aspas. A linguagem existe desde 2005, mais velha que Go. 😁
  11. 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
  12. 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.
  13. 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... }
  14. opcode = (ir & 0xf000)>>12; Quando você fez isso daqui, você pegou os 4 bits mais significativos de ir. Para pegar só 1 é a mesma coisa, só vai mudar o número de bits setados no valor com que está sendo feito o AND. if ( ac & (1 << 15) ) pc = address; // Negativo! ac é menor que zero if ( ac && !(ac & (1 << 15)) ) pc = address; // ac é diferente de zero e positivo.
  15. Acontece que ac é unsigned short int, por isso a comparação do if do C não interpreta como um número sinalizado. Mas aí é só você fazer um casting para um número sinalizado que o código já vai estar correto. Exemplo: if((short int)ac < 0) pc = address; Mas se por algum motivo você quiser fazer a verificação manualmente, bastaria você pegar o bit mais significativo (MSB) do número e ver se ele está ligado ou não. Se estiver, significa que o número é negativo... Mas como eu disse, isso não é necessário.
  16. Você não está atualizando o registrador "pc" corretamente. Remova as linhas "pc = pc + 1;" e, ao invés disso, atualize o pc imediatamente após carregar a instrução da memória. Tu pode fazer assim: ir = Mem[pc++]; O pc precisa ser atualizado sempre que uma instrução for executada. Como você só estava atualizando em algumas instruções, o loop ficava rodando indefinidamente porque pc estava sempre apontando para a mesma instrução. Sobre saber se as instruções fazem o que era esperado... Para isso eu sugiro que você compile o código no seu PC, execute e veja se o resultado foi o esperado. Eu testei aqui as instruções load, store e add e funcionaram conforme o esperado... Esse foi o código que usei para testar: #include <stdio.h> unsigned short int Mem[] = { 0x1004, 0x3005, 0x2004, 0x0000, // Data 0x0005, 0x0002, }; void dorun() { // etc... } int main(void) { dorun(); for (int i = 0; i < sizeof Mem / sizeof *Mem; i++) { printf("%04x, ", Mem[i]); } putchar('\n'); return 0; } Não testei as outras instruções, mas com base nisso daí tu pode testar elas... Ou meter o louco e já submeter após corrigir a atualização do pc e ver no que dá. 🙂
  17. 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.
  18. Opa, não apenas é possível como é o hobby de muita gente kkk. O primeiro passo para fazer a engenharia reversa em um firmware é justamente fazer o dump (você usou o termo certo) do código do firmware, isto é chamado de extração de firmware. Sobre ter bloqueios, sim muitas vezes pode ter. Mas existem técnicas para tentar burlar alguns bloqueios. Eu não manjo do assunto mas vou te passar referências de quem manja: Embedded Hacking: Extração de Firmware usando SPI H2HC University 2018 - Internet of Sh!t - Hacking de Dispositivos Embarcados - Maycon Vitali Os dois conteúdos é do Maycon Vitali.
  19. Texto ocupa muito espaço visual, mas não tanto espaço em memória. Por exemplo, um artigo de 65536 caracteres é grande "pakas" para se ler, mas só ocupa 64 KiB. (considerando somente caracteres ASCII) O que é muita coisa para um embarcado com 32 KiB, mas para uma requisição HTTP com um servidor não é lá grande coisa a ponto de causar uma negação de serviço. Por exemplo no PHP, o tamanho máximo para upload de um arquivo é por padrão 2 MiB. Uma imagem em full HD pode alcançar fácil 4~5 MiB. Agora respondendo as perguntas: Depende da infraestrutura do site e como ele foi programado. Um site qualquer poderia cair só em tu ficar apertando F5 na página. Mas um site bem feito provavelmente iria limitar o número de requisições e começaria a responder seu script com 429 após um número N de requisições em um curto intervalo de tempo. O nome disto é rate limit. Limitar no lado do servidor, sim você pode fazer isto. Mas não vai fazer uma diferença significativa. Você pode pesquisar sobre técnicas para evitar que o seu serviço caia por qualquer besteira. Isto inclui desde configurar uma boa infraestrutura até configurar rate limit e, talvez o mais importante, seria otimizar sua aplicação. Porque não adianta de nada tu fazer tudo isto se existe uma determina página no teu site que roda um monte de queries e faz um monte de operações pesadas e nem sequer faz cache no lado do servidor. Tu tá criando uma bomba pronta para ser explodida, e nem precisa de script para explodir ela. No banco de dados existe um limite no tamanho de uma query. No MySQL a variável max_allowed_packet quem determina isto, e por padrão é 1 MiB. Se o site tá fazendo pesquisa usando queries no banco de dados (que não é a única, e nem a melhor forma), então ele já está englobado neste limite. Se isto é uma preocupação para você, uma solução não-gratuita seria pagar um serviço para proteção contra DDoS. Como o da Cloudflare. Dicas para melhorar a performance do site: Se seu servidor precisar fazer operações pesadas, tente otimizar isto o máximo possível e use cache no lado do servidor. Quando precisar usar o banco de dados, tente ao máximo fazer isto com uma só query. Aprenda a usar joins e evite queries dentro de loops. (pesquise sobre o N+1 query problem) Se você estiver escrevendo dados no banco de dados em loop, e não souber (ou não for possível) fazer isto com uma só query. Então adicione o loop dentro de uma transaction. Alterações dentro de transactions são primariamente feitas em memória, para só depois serem efetivamente escritas no disco. Portanto dentro do loop só existirá uma só escrita em disco, ao invés de várias em sequência. (ainda é melhor fazer tudo em uma única query) Só para servir de referência. Eu tinha um seeder aqui inserindo dados em loop e estava rodando em um pouco mais de 1 minuto. Adicionei a transaction e este tempo caiu para 5 segundos. Enfim, foi o que me lembrei agora. Mas tu pode pesquisar mais afundo para ver melhor estas coisas. o/
  20. 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.
  21. A questão é que usando um registrador de 64 bits, a instrução divide o valor contido em RDX:RAX pelo valor passado como operando. Considere que RDX são os 8 bytes mais significativos do valor, e RAX seriam os 8 bytes menos significativos. Simplesmente zerar RDX traria um resultado incorreto se o número em RAX fosse negativo, o certo seria fazer um sign-extend e a instrução CQO faz isso para você: mov rax, 2345 cqo ; RDX:RAX = sign_extend(RAX) idiv rbx
  22. Colega, pelo que pesquisei aqui isto é uma instrução intrísseca do compilador do Visual Studio (Microsoft). Tem muita coisa em C que não é padronização da linguagem mas sim de alguma implementação específica, esta é uma delas. https://docs.microsoft.com/pt-br/cpp/intrinsics/sidt?view=vs-2019 Se você usa o GCC, e suponho que sim, você pode usar Inline Assembly para isso. (outros compiladores também suportam Inline Assembly, mas com sintaxe diferente) Vou deixar um exemplo: #include <stdio.h> #include <inttypes.h> typedef struct idtr_s { void *base; uint16_t limit; } idtr_t; inline void sidt(idtr_t *idtr) { __asm__ ( "sidt %0" : "=m" (idtr) ); } int main(void) { idtr_t idt; sidt(&idt); printf("%p, %" PRIu16 "\n", idt.base, idt.limit); return 0; } Detalhe que o uso de uma inline function demanda que você ligue a otimização de código na hora de compilar. Tu pode por exemplo fazer assim: $ gcc -O2 tst.c -o tst Ah, uma pequena nota, sem querer atropelhar o autor. Mas o uso de DWORD para 'base' não está tecnicamente correto. Ele está assumindo que o tamanho do dado será necessariamente 4 bytes, mas isso só é verdade se o programa não estiver rodando em modo de 64-bit. Por isso usei um ponteiro void no lugar. EDIT: A instrução SIDT não carrega a IDT, carrega o IDTR que é um registrador que armazena estas informações sobre a IDT. "base" no caso é o endereço inicial de onde a IDT está.
×
×
  • Create New...