Unconfigured Ad Widget

Collapse

Anúncio

Collapse
No announcement yet.

Cpuid #2

Collapse
X
 
  • Filter
  • Tempo
  • Show
Clear All
new posts

  • Font Size
    #1

    Cpuid #2

    Mazel Tov!

    Hoje vou mostrar uma outra maneira de obter o Vendor ID do seu processador
    anteriormente havia mostrado como faze-lo por meio de SYSCALLS.
    Hoje irei focar em fazer o mesmo "trabalho" porém com funções em C.
    Isso mesmo! Iremos usar funções em C em nosso código em Assembly.

    Primeiramente irei dar uma breve explicação sobre a instrução CPUID
    Essa instrução vai nos mostrar uma informação específica do processador
    Existem várias informações que podemos obter com diferentes valores da CPUID.
    Como Assim?
    A cpuid vai retornar alguma informação da cpu, oque vai definir qual informação
    será mostrada é o valor contido em EAX ou RAX(64bits).
    Se ainda não ficou claro você pode olhar a tabela que mostra oque será
    mostrado de acordo com o valor em EAX:

    Apenas usuários registrados e ativados podem ver os links., Clique aqui para se cadastrar...

    A sintaxe básica para a instrução CPUID é:
    mov [número_desejado], eax
    cpuid

    Como eu disse iremos pegar o Vendor ID(valor 0), hoje irei colocar
    o código em 64bits, e usarei os respectivos registradores.Lembrando que estou
    usando linux com o assembler GAS, se for fazer no NASM deverá trocar a sintaxe.
    O Vendor ID é retornado pela CPUID nos registradores ebx,ecx e edx(rbx, rcx, rdx)
    na seguinte ordem:

    -ebx(rbx): Contém os 4 primeiros bytes da string
    -edx(rdx): Contém os 4 bytes do meio da string
    -ecx(rcx): Contém os ultimos 4 bytes da string

    Vamos ao código:

    .section .data
    string1:
    .asciz "%s"
    string2:
    .asciz "Guiadohacker.com.br: Olhando o Vendor ID\n"
    string3:
    .asciz "Coded: p0w3ll\n"
    .section .bss
    .lcomm vendoridaqui, 12

    .section .text
    .globl _start
    _start:

    movq $4, %rax
    movq $1, %rbx
    movq $string2, %rcx
    movq $41, %rdx
    int $0x80

    movq $4, %rax
    movq $1, %rbx
    movq $string3, %rcx
    movq $14, %rdx
    int $0x80

    parte_principal:
    movq $0, %rax
    cpuid
    movq $vendoridaqui, %rdi
    movq %rbx, (%rdi)
    movq %rdx, 4(%rdi)
    movq %rcx, 8(%rdi)
    pushq $string1
    pushq $vendoridaqui
    callq printf
    addq $16, %rsp


    pushq $0
    callq exit

    Como estamos fazendo uso de funções da biblioteca C temos que linkar ela no nosso código, caso contrário as funções PRINTF e EXIT não serão reconhecidas.

    Existem duas formas de linkar as bibliotecas C com o nosso código, são elas:

    -Static linking
    -Dynamic linking

    Irei futuramente em outros posts destacar a fundo a diferença entre os dois métodos, mas por hora vou dar uma breve explicação:

    Static linking: Este tipo de linkagem vai incluir toda a biblioteca e suas respectivas funções, uma grande vantagem é que, com isso, o programa não vai depender da biblioteca em si caso ela seja deletada pois ela ja vai estar "embutida" no executável.Porém este método aumenta muito o tamanho do código.

    Dynamic linking:Este tipo de linkagem não vai incluir a biblioteca no programa, as funções
    serão chamadas em run-time, este método não vai interferir muito no tamanho do executável oque ja é uma grande vantagem, além disso estas bibliotecas podem ser compartilhadas por vários programas.Um exemplo muito comum de bibliotecas compartilhadas(shared libraries) são as DLL's ou dynamic-link library.Uma dll danificada pode causar erro em diversos programas ja que todos vão acessar ela para obter suas funções.A outra vantagem das dll's é que se for necessário mudar alguma função(vamos supor que temos uma função X que usa 'gets', e você queira trocar para FGETS) só será preciso alterar a biblioteca compartilhada e todos os programas que a usam serão automaticamente 'atualizados' de uma vez só, enquanto que os programas que usam o Static Linking teriam de ser linkados e/ou recompilados um por um.


    Vamos "Assemblar" nosso código:
    as -o nomedoobject.o nomedoprograma.s

    Agora vamos a parte da linkagem:
    Nesse caso temos que usar a biblioteca de 64-bits e ela está em /lib64/ld-linux-x86-64.so.2
    Entretanto se linkarmos com esta biblioteca teremos um erro, mas por que?
    Simples!Na verdade este é um atalho para outro lugar que contém a verdadeira biblioteca é possivel ver isso com o comando:

    file ld-linux-x86-64.so.2:
    ld-linux-x86-64.so.2: symbolic link to /lib/x86_64-linux-gnu/ld-2.19.so

    Hmm.. Veja que ele nos mostrou o lugar exato da biblioteca a qual o atalho está se referenciando.A sintaxe para a linkagem dinamica é:

    ld -dynamic-linker /lib/x86_64-linux-gnu/ld-2.19.so -o nomedoexecutavel -lc nomedoobject.o

    Este -lc usado na sintaxe especifica o arquivo a ser linkado com nossa biblioteca.

    Rode o executável e veja o resultado:
    ./nomedoexecutavel
    Guiadohacker.com.br: Olhando o Vendor ID
    Coded: p0w3ll
    GenuineIntel


    Dependendo do seu processador a saída pode ser diferente, o link que deixei anteriormente mostra as possiveis strings.

    Agora vou explicar o código para maior clareza:

    Na section data(que contem dados já definidos e inicializados) criei algumas strings a mais importante é a string1:

    .asciz "%s" --> a diretiva .asciz diferente da .ascii já adiciona o caractere nulo que é requerido pela função PRINTF, a string %s será "obtida" pela
    instrução CPUID

    A section .BSS contém dados não definidos neste caso um buffer de 12bytes(o tamanho da string do Vendor ID), será nesse buffer que a string
    será armazenada.Um ponto importante desta section e que os dados aqui não são incluídos no executável na hora de assemblar, ou seja, uma das vantagens de se definir dados na BSS é que esses dados não vão interferir(por hora) no tamanho do arquivo. O buffer nesse caso sera preenchido em run-time quando a instrução CPUID for executada.(Mas se porventura o buffer fosse de 16bytes, só seriam acrescentados os bytes usados nele ou seja 12)

    Antes de mostrar o Vendor ID em fiz uso da syscall WRITE para mostrar algumas strings.
    As syscalls podem receber vários argumentos que podem ou não serem armazenados em registradores, a WRITE possui 4 argumentos:
    ssize_t write(int fd, const void *buf, size_t count);

    As syscalls recebem os parâmetros nos registradores em ordem "alfabetica":
    -RAX(numero da syscall)
    -RBX -> primeiro parâmetro(input)
    -RCX -> segundo parâmetro(input)
    -RDX -> terceiro parâmetro(input)

    Os parâmetros da syscall WRITE são:
    File Descriptor(RBX)
    Endereço da String(RCX)
    Tamanho da String(RDX)


    O File Descriptor é um número que vai definir como o arquivo(no caso, a string) será manipulada, existem 3 valores possíveis.

    0 -Entrada padrão (stdin)
    1 -Saída padrão (stdout)
    2 -Erro padrão (stderr)


    Logo no trecho:
    movq $4, %rax ->Número da SYSCALL
    movq $1, %rbx -> File Descriptor(STDOUT)
    movq $string2, %rcx (Endereço da String)
    movq $41, %rdx (Tamanho da String)
    int $0x80(interrupção, chama/executa a syscall)


    Ou seja tudo isso foi feito para imprimir uma string.


    Vamos agora ao código principal, o intuito do programa é mostrar uma informação da CPU:

    movq $0, %rax -->Número 0 faz a cpuid mostrar o Vendor ID
    cpuid
    movq $vendoridaqui, %rdi ->Movendo o endereço do buffer para o registrador RDI
    movq %rbx, (%rdi) -> 4 primeiro bytes do VendorID que estão em RBX movidos para (RDI)
    movq %rdx, 4(%rdi) -> 4 bytes do meio do VendorID que estão em RDX para os proximos 4 bytes de (RDI)
    movq %rcx, 8(%rdi) -> ultimos 4 bytes do VendorID que estão em RCX nos ultimos 4 bytes de RDI(nesse caso RDI representa nosso buffer de 12 bytes)
    --------------------------------------------------------------------------------------------------------------------------------
    Agora vem uma parte interesssante todavia requer um pouco de estudo sobre a STACK.
    O estilo dos programas em C quando executam funções c, é passar os parâmetros da função pela STACK, não irei abordar todo o funcionamento
    da STACK aqui para nao extender o tamanho do post.
    O GNU Assembler incluiu alguns caracteres especias nos mnemônicos para indicar o tamanho dos dados:

    -b(byte)
    -w(word, 16bits)
    -l(double word, 32bits)
    -q(quad word , 64bits)

    Como estou fazendo o codigo na sintaxe de um processador de 64bits tratei quase todos os dados com o caractere Q.
    A função PRINTF(print format) requer um caractere para a formatação no caso o '%s' se refere a uma string.
    Tecnicamente falando seria assim: printf("%s", variavelstring);
    Continuando...
    ---------------------------------------------------------------------------------------------------------------------------------------

    pushq $string1 -> joga o caractere de formatação %s na stack
    pushq $vendoridaqui -> Joga a string em si na stack
    callq printf -> Com os argumentos nos devidos lugares a função PRINTF ja pode ser chamada

    ---------------------------------------------------
    addq $16, %rsp --> Outro ponto interessante sobre a stack,que darei uma pequena explicação sobre o porque coloquei esta instrução

    Como foram pushados dos tipos de dados(o caractere '$' no GNU Assembler, indica um endereço de memória, é muito útil mover endereços
    de strings para registradores porque, ja que eles possuem apenas 8bytes só caberiam 8 caracteres, movendo o endereço podemos acessar a string inteira), estes 2 PUSH'S puxam os dois endereços das strings para stack e como cada endereço tem o tamanho de 64bits eles somam 128 bits no total ou 16bytes segundo a matematica avançada . Os push's mudam o lugar do stack-pointer e depois da função ser feita é recomendável resetar o stack-pointer para o local original que estava antes dos elementos serem colocados na stack, isso se faz adicionando um certo numero ao stack-pointer, este número vai depender do número de push's ou das variáveis locais na stack, nesse caso como puxamos 2 valores devemos somar os 128bits e para isso convertermos para bytes.


    A parte final do código vai executar a função exit() do C.

    pushq $0 --> argumento da função exit
    callq exit --> chamando a função


    Esse trecho seria o equivalente a: exit(0);


    Se tiver alguma dúvida basta perguntar, Valeu!

X
Working...
X