Jump to content

fredericopissarra

Members
  • Posts

    428
  • Joined

  • Last visited

  • Days Won

    164

Posts posted by fredericopissarra

  1. Qual é a mensagem de erro que dá? (PS: Use formatação, o botão <> aqui em cima.)

    Anyway... Recomendo que você não use arquivos de cabeçalho (header files) para definir funções, apenas protótipos, macros e declarações de externs. Por exemplo:
     

    /* main.c */
    #include "functions.h"
    
    int main( void )
    { imprimeOi(); }

    Eis o header functions.h:

    /* functions.h */
    #ifndef FUNCTIONS_H_
    #define FUNCTIONS_H_
    
    void imprimeOi(void);
    
    #endif

    E a função é definida em functions.c:

    /* functions.c */
    #include <stdio.h>
    
    // Incluído para "bater" o protótipo com a definição abaixo.
    #include "functions.h"
    
    void imprimeOi( void )
    {
      puts( "Oi" );
    }

    Agora é só compilar:

    $ cc -o test main.c functions.c
    $ ./test
    Oi

    PS: Incluir arquivos com "" significa procurar apenas no diretório corrente. Incluir com <> significa procurar por todos os diretórios "padrão" do compilador, inclusive os informados pelas opções -I. No caso acima poderíamos substituir o #include "functions.h" por #include <functions.h> mas teríamos que incluir a opção -I. (repare o ponto) na linha de comando do cc.

    • Curtir 1
  2. O que acontece se o preço de venda for informado como 0 (zero)?
    O que acontece se inserir mais que 4 "produtos"?
    O que acontece se tiver um produto que custe R$ 16777217,00?
    O que acontece se, ao invés do usuário informar um valor numérico, informar uma string inválida (tipo `abc' para venda)?
    Por que você deixou o '\n' na string do nome do produto (fgets() deixa o '\n' final lá!)?
    E se o nome do produto tiver mais que 60 chars?

    PS: int main() não é C, mas C++. fflush(stdin) está errado (fflush() deve ser usado apenas em streams de saída).

    • Curtir 1
  3. Para tentar ajudar, eis um método de implementar lista encadeada dupla circular... começamos com uma estrutura do nó "cabeça":

    struct list_head {
      struct list_head *prev_, *next_;
    };

    Uma lista vazia tem sempre esse nó e os links prev e next apontando para ele mesmo. Podemos criar um macro para isso:

    #define LIST_HEAD_INIT(list__) { &(list_), &(list__) }

    Podemos ter, também, uma inicialização em runtime:

    static inline void list_init( struct list_head *list )
    { list->prev_ = list->next_ = list; }

    Uma lista vazia poderia ser declarada (em tempo de compilação) como:

    struct list_head mylist = LIST_HEAD_INIT(mylist);

    Para facilitar a criação de nós "polimórficos" usarei o seguinte macro:

    #define LIST_HEAD_PTRS struct list_head *prev_, *next_

    Ou, em runtime, como:

    {
      struct list_head list;
    
      list_init( &list );
      ...
    }

    O nosso nó pode ser declarado como:

    struct music_entry {
      LIST_HEAD_PTRS;
      
      char *name;  // Nome da música (usarei como "chave").
      // ... outros campos aqui.
    };

    Com isso struct music_entry tem exatamente a mesma estrutura que struct list_head, nos dois primeiros membros (os links). De fato, qualquer estrutura que declare LIST_HEAD_PTRS como seu primeiro membro pode ser usada como nó para uma lista. Assim, todas as nossas rotinas para manipulação de lista podem, simplesmente, usar struct list_head como nó base.

    Primeira rotina: Adicionar um nó na lista... De posse do ponteiro de um nó qualquer, podemos adicionar um nó depois ou antes desse:
     

    // Rotina "privada" (repare o underscore no nome).
    // Passamos o elemento a inserir na lista e os ponteiros prev e next
    // que serão usados.
    static inline void list_add_(struct list_head *element,
                                 struct list_head *prev, struct list_head *next )
    {
      element->prev_ = prev;
      element->next_ = next;
      prev->next_ = element;
      next->prev_ = element;
    }
    
    // Adiciona um elemento depois de um nó:
    static inline void list_add_after( struct list_head *element,
                                       struct list_head *node )
    { list_add_( element, node, node->next_ ); }
    
    // Adiciona um elemento antes de um nó:
    static inline void list_add_before( struct list_head *element,
                                        struct list_head *node )
    { list_add_( element, node->prev, node ); }	

    Segunda rotina: Apagar um nó.
     

    // Rotina "privada". Dados os links prev e next, retira o elemento do meio.
    static inline void list_del_( struct list_head *prev,
                                  struct list_head *next )
    { prev->next_ = next; next->prev_ = prev; }
    
    // Retira um elemento da lista.
    static inline void list_del( struct list_head *element )
    { list_del_( element->prev, element->next ); }

    Note que, se a lista estiver vazia e passarmos o ponteiro da "cabeça" nada é, de fato, feito (essa é a vantagem de usar esse nó "sentinela").

    Uma outra rotina útil é a determinação se uma lista está vazia ou não... para isso precisamos usar SEMPRE o nó "cabeça". Algo semelhante é a determinação se estamos ou não na cabeça:

    static inline int list_is_empty( struct list_head *head )
    { return head == head->next_; }

    Para percorrermos todos os elementos da lista podemos usar um macro assim:

    // iterção "para frente"
    #define list_for_each( iter__, head__ ) \
      for ( iter__ = (head__)->next_; iter__ != (head__); iter__ = iter__->next_ )
    
    // iteração "para trás"
    #define list_for_earch_prev( iter__, head__ ) \
      for ( iter__ = (head__)->prev_; iter__ != (head__); iter__ = iter__->prev_ )
    
    // iterção segura "para frente" (para o caso de queremos modificar
    // um elemento na iterção). Usa um ponteiro "temporário" extra.
    #define list_for_each_safe( iter__, tmp__, head__ ) \
      for ( iter__ = (head__)->next_, tmp__ = (iter__)->next_; \
            iter__ != (head__); \
            iter__ = tmp__, tmp__ = (iter__)->next_ )
    
    // iterção segura "para trás" (para o caso de queremos modificar
    // um elemento na iterção). Usa um ponteiro "temporário" extra.
    #define list_for_each_prev_safe( iter__, tmp__, head__ ) \
      for ( iter__ = (head__)->prev_, tmp__ = (iter__)->prev_; \
            iter__ != (head__); \
            iter__ = tmp__, tmp__= (iter__)->prev_ )

    Assim, considere se tivermos uma lista list e queremos procurar pela música "Owner of a lonely heart":

    struct list_head *i;
    struct music_entry *p;
    
    list_for_each( i, &list )
    {
      p = (struct music_entry *)i;
    
      if ( ! strcasecmp( p->name, "Owner of a lonely heart" ) )
        break;
    }
    
    // Não estamos na cabeça, entao achamos.
    if ( i != &list )
      printf( "found '%s'.", p->name );

    Acredito que, com isso, a implementação do programinha fique mais simples, huh?

    PS: Alguns perceberão que essa implementação é praticamente idêntica a do Linux, mas, na verdade, esse método é o de Robert Sedgewick.

    • l33t 1
  4. Existem diversas maneiras de implementar listas, especialmente circulares... Eu recomendo a implementação de lista circular com encadeamento duplo e com um nó sentinela "cabeça" (head) vazio. Isso facilita um bocado a implementação e permite a implementação via polimorfismo (mesmo em C). Assim, uma lista vazia sempre terá um nó cabeça onde os links apontam para si mesmo... adicionar nós é questão de mudar os links apenas.

    Isso também facilita a alocação, já que num novo nó só tem que obedecer a ordem dos links do nó "cabeça", com membros adicionados.

    A decisão de como a lista será implementada é o primeiro passo (deixo para sua pesquisa e tentativa de implementação -- não mostrarei aqui).

    Buscar por um nó da lista é simplesmente percorrer todos os elementos "depois" do nó "cabeça" até voltar a ele, comparando as "chaves" com a desejada...

    Contar quantos nós existem é simplesmente percorrer toda lista à partir do próximo nó depois da "cabeça" até retornar a ela.

    Apagar um nó é só modificar os links e dealocar a memória do nó.

    Depois que tiver algum trabalho feito, poste aqui... Não acho que alguém vá fazer o trabalho por você...

    []s
    Fred

  5. Se fosse para dar uma nota, a minha seria ZERO.

    1º - Isso ai não é C (é C++)
    2º - A apresentação do código está horrível;
    3º - A apresentação do programa, funcionando, também está ruim;
    3º - Existem vários erros (não de "compilação", mas de como o troço supostamente deveria funcionar).

  6. Não faz parte de sua dúvida, mas eis um código que não é recursivo e usa aritmética de múltipla precisão e te permite calcular o fatorial de qualquer valor inteiro, positivo, dentro da faixa de um unsigned long int.
     

    // Calcula fatorial usando aritmética de multipla precisão.
    //
    // Compilar com:
    //    cc -O2 -o factmp factmp.c -lgmp
    //
    // Não esquecer de instalar libgmp-dev.
    //
    #include <stdio.h>
    #include <stdlib.h>
    #include <gmp.h>
    #include <errno.h>
    #include <time.h>
    
    int main(int argc, char *argv[])
    {
      char *p;
      mpz_t r;
      unsigned long n, c, total;
      time_t t1, t2;
    
      if ( argc != 2 )
      {
        fprintf( stderr, "Usage: %s <value>\n", argv[0] );
        return EXIT_FAILURE;
      }
    
      // O valor deve ser inteiro e dentro da faixa de unsigned long int.
      errno = 0;
      n = strtoul( *++argv, &p, 10 );
      if ( errno == ERANGE || *p )
      {
        fputs( "ERROR: Invalid value.\n", stderr );
        return EXIT_FAILURE;
      }
    
      // Inicializa r e seta-o com 1.
      mpz_init_set_ui(r, 1);
    
      t1 = time( NULL );
    
      c = 0;
      total = n;
      while ( n >= 1 )
      {
        mpz_mul_ui( r, r, n-- );
        printf( "%.2f%% complete... \r", 100.0 * ++c / total );
      }
      putchar( '\n' );
    
      t2 = time( NULL );
    
      mpz_out_str( stdout, 10, r );
      putchar('\n');
    
      // Joga r no lixo.
      mpz_clear(r);
    
      printf( "Total time: %lu seconds.\n", t2 - t1 );
    
      return EXIT_SUCCESS;
    }

    Mas, atenção: Se você usar valores muito grandes (maiores que 100000, por exemplo), o tempo de calculo será bem grande (tente!).

    []s
    Fred

  7. Considere:
     

    int fact( int x )
    {
      if ( x < 0 ) return 1;
    
      return x * fact( x - 1 );
    }

    Se x = 0, inicialmente, temos um return 0 * fact( -1 );
    E essa chamada, recursiva, retornará 1 (já que o novo x < 0)... Dai, 0 * 1 = 0.

    Note que, por definição, n! = 1 se n <= 1. E fatorial só é definido para valores positivos... A rotina DEVERIA ser:
     

    unsigned int fact( unsigned int x )
    {
      if ( x <= 1 ) return 1;
    
      return x * fact( x - 1 );
    }

    Sendo que a função causará overflow se x > 12.

    • Agradecer 1
  8. 1 hora atrás, geyslan disse:

    Em verdade, qualquer chamada a execve() pode falhar a depender de como ela é construída. Não considero isso um erro, uma vez que a chamada dos shellcodes foram explicitamente construídas.

    Sobre o jmp e/ou call, não sei a que shellcode se refere. Poderia referenciar?

    A essa aqui, no seu texto:

    ; "\x31\xC9\xF7\xE1\xB0\x0B\x51\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\xCD\x80"

    31 c9           xor    ecx,ecx
    f7 e1           mul    ecx
    b0 0b           mov    al,0xb
    51              push   ecx
    68 2f 2f 73 68  push   0x68732f2f
    68 2f 62 69 6e  push   0x6e69622f
    89 e3           mov    ebx,esp
    cd 80           int    0x80

    Obviamente isso assume que `execve` não falhará (cadê o `ret` depois do `int 0x80`?) e você dá um exemplo de uso:

      __asm__ ("movl $0xffffffff, %eax\n\t"
         "movl %eax, %ebx\n\t"
         "movl %eax, %ecx\n\t"
         "movl %eax, %edx\n\t"
         "movl %eax, %esi\n\t"
         "movl %eax, %edi\n\t"
         "movl %eax, %ebp\n\t"

         // Calling the shellcode
         "call shellcode");

    É desse call que estou falando... E há outro aspecto a ser considerado... Na SysV ABI, EBX (ou RBX no modo x86-64) tem que ser preservado entre chamadas... Esse mov ebx,esp altera EBX sem preservá-lo, o que levará (possivelmente, se execve falhar e se tivéssemos um ret ali no final) o código chamador a se comportar de forma errática... Seu shellcode deveria preservar EBX.

  9. Uma abordagem interessante, mas há alguns "erros"...

    O primeiro é que a syscall execve() espera que o segundo argumento seja a lista argv incluindo argv[0]... Passar NULL para args pode funcionar, mas está fora do padrão.

    Segundo, no modo x86-64 esse macete dos dois pushes com literais não funcionará... E note que você está empurrando "\bin\\sh" (com essas duas barras no meio", quando poderia empurrar "\bin\sh\0" (8 bytes), embora esteja empurrando um zero antes para garantir o fim da string. No caso, se tentar fazer:

      push `\sh\0`
      push `/bin`

     
    No modo x86 isso gerará a string, na pilha: "\bin",0,0,0,0,"\sh\0",0,0,0,0. PUSH com um imediato de 64 bits não existe na ISA.

    Terceiro, execve() pode falhar... dependendo de como a chamada será feita, o programa crasha (aliás, você não quis dizer jmp shellcode ao invés de um call, acima? Se execve() falhar seu processo provavelmente crasha (não tem um ret)...).

    O meu desafio para vocês é fazer um código executável menor que esses 33 bytes (de código, não de imagem binária) para o modo x86-64:

    ; test.asm (NASM)
    ;
    ;   nasm -felf64 -o test.o test.asm
    ;   ld -s -o test test.o
    ;
      bits  64
      default rel   ; Usando endereços relativos ao RIP.
                    ; código fica "position independent".
                    ; ESSE é o default da SysV ABI.

      global _start
    _start:
      lea   rdi,[sh]         ; Calculo do endereço efetivo para deixar
                             ; o código "PIC" (ou PIE).
                             ; PUSH sh/POP rdi usa 1 byte a menos, mas
                             ; perderíamos o PIC/PIE.

      xor   edx,edx          ; envp = NULL;
      push  rdx              ; argv[1] = NULL;
      push  rdi              ; argv[0] = "/bin/sh";

      ; mov rsi,rsp
      push  rsp
      pop   rsi              ; rsi = &argv.

      ; sys_execve espera, como argumentos:
      ;   int execve( const char *path, char **argv, char **envp );
      ; Por padrão argv[0] deve ser o mesmo ponteiro path.

      ; mov eax,59
      push  byte 59          ; mov eax,sys_execve (5 bytes) - push/pop (2 bytes)
      pop   rax

      syscall

      ; SE execve falhar, sai do processo
      mov   edi,eax

      ; mov eax,60
      push  byte 60          ; mov eax,sys_exit
      pop   rax              ;

      syscall
    sh:
      db    `/bin/sh\0`

    []s
    Fred

  10. 3 horas atrás, Scania_13 disse:

    Estou tentando descobrir os problemas de alguns códigos em assemble, porém não estou conseguindo resolver alguns que são: mov ax, [si][di];mov ds, cs; e mov ax, [si+di+0].

    Os erros que aparecem são:

    illegal index mode (mov ax, [si][di]) illegal use of segment register (mov ds, cs | mov ax, [si+di+0])

    Ao usar um endereço efetivo com "registradores" de 16 bits você tem que seguir as regras do 8086. O endereço "base" é sempre BX ou BP, nenhum outro e o índice é sempre SI ou DI, nenhum outro.

    Assim: mov ax,[si][di] não existe, nem mesmo mov ax,[si+di+0].

    • Curtir 1
  11. A segunda forma é... confusa. E potencialmente não portável. Ao converter um ponteiro para uint64_t você tem o potencial de bagunçar o endereço (para isso existe o tipo uintptr_t ou intptr_t... mas, mesmo assim, não recomando).

    Com sua estrutura eu faria:

    for ( person_t *q = p, c = 0; q->name; q++, c++ );
    // Aqui c conterá a contagem (declare c como unsigned int, por exemplo).
    // Declará-lo como uint16_t te dará, no máximo 65535 elementos e, pior,
    // códigos que lidam com WORDs costumam ser maiores que com DWORDs...
    // Prefira 'int' e derivados, se puder.

    Com relação à segunda alegação, veja isso:

    ; Opcodes  Instruções
    66 FF C1   INC CX
    FF C1      INC ECX
    48 FF C1   INC RCX
    

    Note que para lidar com CX o compilador tem que adicionar o prefixo 0x66 nos opcodes... É sempre bom evitar tipos de tamanho diferente de 'int', se puder (instruções que lidam com registradores de 64 bits adicionam o prefixo REX [0x4?]).

    Ainda... podemos fazer, também, o seguinte:

    person_t *q = p;
    for ( ; q->name; q++ )
      ;
    c = q - p;

    Já que a subtração de dois ponteiros do mesmo tipo nos dá a quantidade de elementos. Seria melhor definir, nesse caso, c como ptrdiff_t.

    PS: Tanto as especificações POSIX, quanto a documentação da glibc, proíbem a nomeação de identificadores com o sufixo _t (minúsculo), reservando-o para bibliotecas nativas. Eu evito isso nomeando meus tipos com o sufixo _T (maiúsculo), como em person_T.

    • Curtir 2
  12. Para sua avaliação:
     

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int saque ( unsigned int *restrict notas,
                const unsigned int *restrict valores,
                unsigned int nvalores,
                unsigned int valor )
    {
      // Zera os contadores.
      memset ( notas, 0, nvalores * sizeof ( int ) );
    
      for ( int i = 0; i < nvalores; i++ )
      {
        // casos especiais
        // Problemas com R$ 6,00 e R$ 8,00 restantes...
        if ( valores[i] == 5 )
        {
          // Assume que o último elemento equivale às
          // notas de R$ 2,00... revise isso se não for o caso.
          if ( valor == 6 )
          {
            notas[nvalores - 1] = 3;
            valor = 0;
            break;
          }
          else if ( valor == 8 )
          {
            notas[nvalores - 1] = 4;
            valor = 0;
            break;
          }
        }
    
        while ( valor >= valores[i] )
        {
          notas[i]++;
          valor -= valores[i];
        }
      }
    
      return valor == 0;
    }
    
    int main ( void )
    {
      static const unsigned int valores[7] = { 200, 100, 50, 20, 10, 5, 2 };
      unsigned int notas[7];
      unsigned int valor;
    
      fputs ( "Quantidade desejada: ", stdout );
      fflush ( stdout );
    
      if ( scanf ( "%u", &valor ) == 1 )
      {
        if ( ! saque ( notas, valores, 7, valor ) )
        {
        error:
          fputs ( "Valor inválido.\n", stdout );
          return EXIT_FAILURE;
        }
    
        // Mostrar notas.
        for ( int i = 0; i < 7; i++ )
          printf ( "R$ %3u,00 -> %u notas.\n", valores[i], notas[i] );
      }
      else
        goto error;
    
      return EXIT_SUCCESS;
    }

    []s
    Fred

×
×
  • Create New...