Ir para conteúdo

fredericopissarra

Membros
  • Postagens

    420
  • Registro em

  • Última visita

  • Dias Ganhos

    160

Posts postados por fredericopissarra

  1. 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

  2. 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).

  3. 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

  4. 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
  5. 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.

  6. 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

  7. 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
  8. 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
  9. 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

  10. sizeof p te dará a quantidade de bytes do objeto p (o identificador do array).
    sizeof p[0] te dará a quantidade de bytes de um elemento (o primeiro) do objeto p[0] (o elemento do array).

    Assim, a quantidade de elementos do array pode ser calculada como sizeof p / sizeof p[0].

    A vantagem de usar essa aproximação é que sizeof p / sizeof p[0] será avaliada em tempo de compilação para uma constante.

    Note que, com isso, não é necessário usar o elemento final NULL:

    person_t p[] = {
      { "Fulano", 32 },
      { "Ciclano", 41 }
    };

    for ( int i = 0; i < sizeof p / sizeof p[0]; i++ )
      printf( "\"%s\", %d anos.\n", p[i].name, p[i].age );

    Note também que a sua segunda técnica (com o último item com NULL) assume que o ponteiro p+c, ao ser derreferenciado, te dará o ponteiro name. A técnica é válida (mas pode ser problemática -- especialmente com um casting para `uint64_t`, que será válido apenas no modo x86-64), mas seria mais "correto" fazer ( desde que o primeiro elemento apontado por q seja um char * ).

    // q aponta para um elemento de p... *(char **)q é o ponteiro `name`...
    for ( person_t *q = p; *(char **)q; q++ )
      printf( "\"%s\", %d anos.\n", q->name, q->age );

    Mas, isso parece mais complicado do que usar sizeof, não é?

×
×
  • Criar Novo...