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!
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!