Fala galera, hoje vou falar um pouco sobre Assembly e como criar um pequeno
programinha.
Primeiramente seria um tanto enfadonho falar sobre fazer um programa sem
falar sobre compiladores, mesmo não havendo a necessidade de compilar um
codigo em assembly,o compilador pode traduzir o codigo para assembly
e depois usar um assembler para criar as instructions code.
Na hora de compilar um programa acontece o seguinte:
(Isso aqui nas linguagens de alto-nivel)
Resumindo:
1- o compilador pega o codigo do programa e transforma em
instruction code que são os codigos que podem ser "entendidos" pelo
processador.
2-A primeira etapa gera um arquivo .o que eh um Object code File que contem
as instructions. Seria mais ou menos assim:
//Codigo em C
int main()
{
int i = 1;
exit(0)
}
//Instruction code
55
89 E5
83 EC 08
C7 45 FC 01 00 00 00
83 EC 0C
6A 00
E8 D1 FE FF FF
Voce não precisa saber a funcao de cada numero ate porque cada processador
tem suas proprias instruction codes. Mas preste atenção na 4 linha
01 00 00 00-> Esta carregandoo valor 1.
Continuando
O object code em si nao pode ser executado os sistemas operacionais
requerem um tipo especifico de executavel, e é ai que o Linker entra
Linker:
O linker pega o object file e adiciona bibliotecas e outros object codes
necessarios e cria o executavel.
Geralmente você não vê isso porque as IDE`s ja compilam e linkam tudo de uma
vez.
Mnemonicos:
Como visto acima decorar todos instructions code, cada numero, cada opcode,etc
seria quase impossivel, para isso criaram os Mnemonicos.
Ta, mas pra que servem?
Eles servem para, ao inves de decorar aqueles numeros infernais voce pode usar
palavras faceis de lembrar, como por exemplo:movl $1, %eax. Aqui movemos o
valor 1 para o registrador eax. Voce deve estar percebendo que essa declaracao
que fiz acima pode ser um pouco diferente do que outros codigo como: usar o
sinal de porcentagem antes do registrador.Mas jaja eu explico o porque disso.
Vamos falar um pouco sobre dados em Assembly:
Um dos modos de armazenar um dado em assembly eh atraves de labels:
(As barras sao so para deixar mais arrumado,elas nao sao usadas de fato)
//variavel:
.ascii "Guia do hacker"
//contadeluz:
.long 130
E obvio olhando isto que, criamos uma string e um valor int.
Ta mas como eu acesso isso no eu programa?
Simples, basta colocar o nome do label(variavel ou contadeluz)
Jaja isso sera posto em prática.
Esses labels sao colocados na memoria da seguinte maneira:
Peço desculpa pela imagem pois nao sei mexer com photoshop.
O primeiro elemento declarado e "insertado" primeiro
Nesse caso apos o ultimo byte do label variavel o label contadeluz ja seria
setado ali.(vou colocar as primeiras letras da string)
De fato como eh uma string ela vai ser colocada na memoria de acordo com o
valor de cada caractere da tabela ascii .
Sections:
As sections em assembly sao uma das coisas mais importantes na hora de fazer
codigo as principais sao 3:
-data section // variavel predefinidas
-bss section // funcao parecida com a .data porem as informacoes nao sao
inicializadas imediatamente, ela contem buffers para declaracao de dados
no decorrer do programa
-section .text // Se você já viu os videos do Nick certamente viu que o entry
point deve estar na section .text pois eh la que o codigo em si fica.
Assemblers:
Os assembler são programas que vao traduzir o codigo assembly para o cpu.
Ele vai gerar um objet file tambem.
"AFF entao vai estar compilando?"
NAO! Ele vai apenas "traduzir" uma vez que estamos falando de linguagem de
baixo nivel.
Existem varios assemblers aqui vai o nome de alguns:
-MASM(Microsotf)
-NASM(Netwide assembler)
-GAS(GNU)//uso este
-HLA(High level Assembler)
Apos voce "assemblar" surgira um object code, mas oque devemos fazer?
Vamos usar um linker e criar o executavel.
Eu vou abordar toda a teoria primeiro depois a pratica ok?
Vamos para mais uma ferramenta o Debugger:
Como você ja deve ter usado o Ollydbg ja sabe mais ou menos como
funciona.O debugger eh muito util para encontrar erros em programas, ou
para analise.
Ele executa o programa numa "sandbox" então voce pode acessar suas funções,
ver o funcionamento, parar a execução,ver locais de memória, etc.
O debugger que usarei vai ser o gdb(GNU).
O compilador permite voçê ver o codigo depois de "assemblado"(ex:executavel)
Mais e os object files?
Existem varias formas mais eu vou usar um programa chamado objdump.
Agora chegando no fim da teoria =)
Vou falar sobre as sintaxes que usarei para criar nosso programa.
Uma das coisas mais CHATAS é que os assemblers usam sintaxes diferentes
para o codigo assembly, como eu disse vou usar o gas(GNU) ele usa uma sintaxe
chama AT&T que vem de AT&T Bell Labs que foi onde o UNIX e a linguagem C
surgiram.
Vou citar algumas diferncas sobre esta sintaxe e a Intel vamos la:
--A sintaxe AT&T usa o '$' quando se fala em valores.Exemplo:$4->AT&T, 4->Intel
--A AT&T usa o '%' quando se refere a um nome como um registrador.
Exemplo: AT&T->%eax. Intel->eax
--Essa é a mais importante: AT&T usa o sentido oposto.Como assim?
Suponhamos que eu queira mover o valor 4 para o registrador eax:
AT&T-> movl $4, %eax
Intel-> mov eax, 4
--Em jumps as coisas tambem podem mudar:
AT&T->ljmp $lugar %eax
Intel->jmp lugarffset
Bom sem demora vamos fazer o programa:
A finalidade deste programa é mostrar qual o fornecedor do seu processador:
#cpuid guaidohacker.com.br extract Vendor Id
.section .data
output:
.ascii "The processor Vendor id is 'xxxxxxxxxxxx'\n"
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
movl $output, %edi
movl %ebx, 28(%edi)
movl %edx, 32(%edi)
movl %ecx, 36(%edi)
movl $4, %eax
movl $1, %ebx
movl $output, %ecx
movl $42, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80
Vou enumerar cada linha do codigo ok?
Criamos um label chamado output que contera uma string, note que ha varios
"x" eles serão substituidos pelo ID do processador.
Note que ele esta na section data pois é uma "variavel"
apos isso temos a section .text e a .globl _start.
A .globl significa que pode ter um uso externo tal como a ultilização do codigo
em um programa em C.
_start é onde o programa vai começar(start point)
Linha-1-Depois nos movemos o valor 0 para eax.
Linha-2-Chamamos cpuid.Mas oque e isso?
//CPUID é uma instrução que vai fazer alguma coisa especifica de acordo
com o valor de eax, aqui vai algumas tarefas que podem ser feita com cpuid:
EAX = 0 -> Mostra o Fornecedor do CPU
EAX = 1 -> Mostra o tipo,modelo,familia do CPU.
EAX = 2 -> Cache de configuração
EAX = 3 -> Numero serial do CPU.
NOTA IMPORTANTE: no nosso caso como EAX = 0 o processador vai retornar
os valores os registradores:
EBX -> 4 primeiros bytes da string
EDX -> 4 bytes do meio da string
ECX -> 4 ultimos bytes da string
Sao os principais usos.
Continuando:
3-Movemos nosso label output parao registrador EDI(data pointer for destination string)
4-Criamos/Movemos um ponteiro começando no local 28 baseado em EDI(que e o nosso label) para %ebx
5-Criamos/Movemos um ponteiro começando no local 32 da string em EDI para %edx
6-Criamos/Movemos um ponteiro comecando no local 36 da string EDI para %ecx
NOTA:Voce nao precisa saber oque cada instruçao faz de imediato eu farei mais
posts detalhando cada função.
Linha 7,8,9,10,11-São responsaveis para mostrar a informacao obtida e
chama uma interrupção de software(int $0x80)
as 3 ultimas linhas:
Depois de mostrar o ID do 'produtor' é hora de sairmos do programa(exit)
Vamos usar uma system call(1) o valor 1 diz que o programa acabou.
E o registrador EBX contem um valor retornado pelo programa o zero indica
que deu tudo certo.
Feito isso salve o programa com x.asm ou x.s
É hora de utilizar o assembler(GAS)
para usar o GAS basta digitar 'as'[opcoes].
Vamos la:
as -o assemblygh.asm assemblygh.o //criando o object file
Agora vamos utilizar o linker para criar o executavel:
ld -o assemblygh assemblygh.o
apos isso é so executar:
./assemblygh
Repare que os valores contidos naqueles ponteiros são relativos a string
se voce quiser trocar para "O meu ID eh 'xxxxxxxxxxxx'\n", voçê tem que mudar
os valores no caso serio algo assim:
...codigo
movl %ebx, 12(%edi)
movl %edx, 16(%edi)
movl %ecx, 20(%edi)
Nao entendi a contagem...
Eh basicamente a contagem dos caracteres:
O meu ID eh 'xxxxxxxxxxxx'\n
A letra O seria 0 o espaço seria 1 o M 2 e assim por diante.
Bom esta ai o nosso programa,Agora vamos "debugar" ele.
Para isso eu criei outra pasta e movi somente o arquivo .asm para ela.
Eu vou usar o gdb(GNU debugger).
Para rodar ele corretamente no programa e preciso reassemblar o programa.asm
de uma maneira diferente:
as -gstabs -o programa.o programa.asm
ld -o programa programa.o
E criamos o executavel, repare que este é maior que o criado anteriormente
sem o parametro -gstabs.
Sem o parâmetro
Com o gstabs
Isso ocorre porque este parâmetro é necessário para auxiliar o debugger a
percorrer o executável.
Vamos la:
gdb programa, depois disso ele vai esperar os comandos
//vamos setar um breakpoint
break *_start
//isso faz um breakpoint logo apos a section start
A partit dai, basta digitar 'next' para percorrer o programa.
OBJDUMP
Este carinha vai nos permitir ver object codes, olhe só
objdump -s programaobj.o
Você deve estar pensando
-Já vi isso em um shellcode!
De fato sim, oque ocorre é que a segunda coluna que contém os objets são usados
num array do tipo char para executar um shellcode.Eu farei um proxímo post sobre isso. Mas só pra ilustrar iria ficar mais ou menos assim
char shellcode[] = "\xb8\x00\x00\x00...."
Depois é necessario cria uma função para executar isso, mas como eu disse farei
um post sobre shellcode e como escrever um.
Espero que tenha gostado e peço desculpas pelas imagens, é meu primeiro post,estou aberto a críticas , valeu!
programinha.
Primeiramente seria um tanto enfadonho falar sobre fazer um programa sem
falar sobre compiladores, mesmo não havendo a necessidade de compilar um
codigo em assembly,o compilador pode traduzir o codigo para assembly
e depois usar um assembler para criar as instructions code.
Na hora de compilar um programa acontece o seguinte:
(Isso aqui nas linguagens de alto-nivel)
Resumindo:
1- o compilador pega o codigo do programa e transforma em
instruction code que são os codigos que podem ser "entendidos" pelo
processador.
2-A primeira etapa gera um arquivo .o que eh um Object code File que contem
as instructions. Seria mais ou menos assim:
//Codigo em C
int main()
{
int i = 1;
exit(0)
}
//Instruction code
55
89 E5
83 EC 08
C7 45 FC 01 00 00 00
83 EC 0C
6A 00
E8 D1 FE FF FF
Voce não precisa saber a funcao de cada numero ate porque cada processador
tem suas proprias instruction codes. Mas preste atenção na 4 linha
01 00 00 00-> Esta carregandoo valor 1.
Continuando
O object code em si nao pode ser executado os sistemas operacionais
requerem um tipo especifico de executavel, e é ai que o Linker entra
Linker:
O linker pega o object file e adiciona bibliotecas e outros object codes
necessarios e cria o executavel.
Geralmente você não vê isso porque as IDE`s ja compilam e linkam tudo de uma
vez.
Mnemonicos:
Como visto acima decorar todos instructions code, cada numero, cada opcode,etc
seria quase impossivel, para isso criaram os Mnemonicos.
Ta, mas pra que servem?
Eles servem para, ao inves de decorar aqueles numeros infernais voce pode usar
palavras faceis de lembrar, como por exemplo:movl $1, %eax. Aqui movemos o
valor 1 para o registrador eax. Voce deve estar percebendo que essa declaracao
que fiz acima pode ser um pouco diferente do que outros codigo como: usar o
sinal de porcentagem antes do registrador.Mas jaja eu explico o porque disso.
Vamos falar um pouco sobre dados em Assembly:
Um dos modos de armazenar um dado em assembly eh atraves de labels:
(As barras sao so para deixar mais arrumado,elas nao sao usadas de fato)
//variavel:
.ascii "Guia do hacker"
//contadeluz:
.long 130
E obvio olhando isto que, criamos uma string e um valor int.
Ta mas como eu acesso isso no eu programa?
Simples, basta colocar o nome do label(variavel ou contadeluz)
Jaja isso sera posto em prática.
Esses labels sao colocados na memoria da seguinte maneira:
Peço desculpa pela imagem pois nao sei mexer com photoshop.
O primeiro elemento declarado e "insertado" primeiro
Nesse caso apos o ultimo byte do label variavel o label contadeluz ja seria
setado ali.(vou colocar as primeiras letras da string)
De fato como eh uma string ela vai ser colocada na memoria de acordo com o
valor de cada caractere da tabela ascii .
Sections:
As sections em assembly sao uma das coisas mais importantes na hora de fazer
codigo as principais sao 3:
-data section // variavel predefinidas
-bss section // funcao parecida com a .data porem as informacoes nao sao
inicializadas imediatamente, ela contem buffers para declaracao de dados
no decorrer do programa
-section .text // Se você já viu os videos do Nick certamente viu que o entry
point deve estar na section .text pois eh la que o codigo em si fica.
Assemblers:
Os assembler são programas que vao traduzir o codigo assembly para o cpu.
Ele vai gerar um objet file tambem.
"AFF entao vai estar compilando?"
NAO! Ele vai apenas "traduzir" uma vez que estamos falando de linguagem de
baixo nivel.
Existem varios assemblers aqui vai o nome de alguns:
-MASM(Microsotf)
-NASM(Netwide assembler)
-GAS(GNU)//uso este
-HLA(High level Assembler)
Apos voce "assemblar" surgira um object code, mas oque devemos fazer?
Vamos usar um linker e criar o executavel.
Eu vou abordar toda a teoria primeiro depois a pratica ok?
Vamos para mais uma ferramenta o Debugger:
Como você ja deve ter usado o Ollydbg ja sabe mais ou menos como
funciona.O debugger eh muito util para encontrar erros em programas, ou
para analise.
Ele executa o programa numa "sandbox" então voce pode acessar suas funções,
ver o funcionamento, parar a execução,ver locais de memória, etc.
O debugger que usarei vai ser o gdb(GNU).
O compilador permite voçê ver o codigo depois de "assemblado"(ex:executavel)
Mais e os object files?
Existem varias formas mais eu vou usar um programa chamado objdump.
Agora chegando no fim da teoria =)
Vou falar sobre as sintaxes que usarei para criar nosso programa.
Uma das coisas mais CHATAS é que os assemblers usam sintaxes diferentes
para o codigo assembly, como eu disse vou usar o gas(GNU) ele usa uma sintaxe
chama AT&T que vem de AT&T Bell Labs que foi onde o UNIX e a linguagem C
surgiram.
Vou citar algumas diferncas sobre esta sintaxe e a Intel vamos la:
--A sintaxe AT&T usa o '$' quando se fala em valores.Exemplo:$4->AT&T, 4->Intel
--A AT&T usa o '%' quando se refere a um nome como um registrador.
Exemplo: AT&T->%eax. Intel->eax
--Essa é a mais importante: AT&T usa o sentido oposto.Como assim?
Suponhamos que eu queira mover o valor 4 para o registrador eax:
AT&T-> movl $4, %eax
Intel-> mov eax, 4
--Em jumps as coisas tambem podem mudar:
AT&T->ljmp $lugar %eax
Intel->jmp lugarffset
Bom sem demora vamos fazer o programa:
A finalidade deste programa é mostrar qual o fornecedor do seu processador:
#cpuid guaidohacker.com.br extract Vendor Id
.section .data
output:
.ascii "The processor Vendor id is 'xxxxxxxxxxxx'\n"
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
movl $output, %edi
movl %ebx, 28(%edi)
movl %edx, 32(%edi)
movl %ecx, 36(%edi)
movl $4, %eax
movl $1, %ebx
movl $output, %ecx
movl $42, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80
Vou enumerar cada linha do codigo ok?
Criamos um label chamado output que contera uma string, note que ha varios
"x" eles serão substituidos pelo ID do processador.
Note que ele esta na section data pois é uma "variavel"
apos isso temos a section .text e a .globl _start.
A .globl significa que pode ter um uso externo tal como a ultilização do codigo
em um programa em C.
_start é onde o programa vai começar(start point)
Linha-1-Depois nos movemos o valor 0 para eax.
Linha-2-Chamamos cpuid.Mas oque e isso?
//CPUID é uma instrução que vai fazer alguma coisa especifica de acordo
com o valor de eax, aqui vai algumas tarefas que podem ser feita com cpuid:
EAX = 0 -> Mostra o Fornecedor do CPU
EAX = 1 -> Mostra o tipo,modelo,familia do CPU.
EAX = 2 -> Cache de configuração
EAX = 3 -> Numero serial do CPU.
NOTA IMPORTANTE: no nosso caso como EAX = 0 o processador vai retornar
os valores os registradores:
EBX -> 4 primeiros bytes da string
EDX -> 4 bytes do meio da string
ECX -> 4 ultimos bytes da string
Sao os principais usos.
Continuando:
3-Movemos nosso label output parao registrador EDI(data pointer for destination string)
4-Criamos/Movemos um ponteiro começando no local 28 baseado em EDI(que e o nosso label) para %ebx
5-Criamos/Movemos um ponteiro começando no local 32 da string em EDI para %edx
6-Criamos/Movemos um ponteiro comecando no local 36 da string EDI para %ecx
NOTA:Voce nao precisa saber oque cada instruçao faz de imediato eu farei mais
posts detalhando cada função.
Linha 7,8,9,10,11-São responsaveis para mostrar a informacao obtida e
chama uma interrupção de software(int $0x80)
as 3 ultimas linhas:
Depois de mostrar o ID do 'produtor' é hora de sairmos do programa(exit)
Vamos usar uma system call(1) o valor 1 diz que o programa acabou.
E o registrador EBX contem um valor retornado pelo programa o zero indica
que deu tudo certo.
Feito isso salve o programa com x.asm ou x.s
É hora de utilizar o assembler(GAS)
para usar o GAS basta digitar 'as'[opcoes].
Vamos la:
as -o assemblygh.asm assemblygh.o //criando o object file
Agora vamos utilizar o linker para criar o executavel:
ld -o assemblygh assemblygh.o
apos isso é so executar:
./assemblygh
Repare que os valores contidos naqueles ponteiros são relativos a string
se voce quiser trocar para "O meu ID eh 'xxxxxxxxxxxx'\n", voçê tem que mudar
os valores no caso serio algo assim:
...codigo
movl %ebx, 12(%edi)
movl %edx, 16(%edi)
movl %ecx, 20(%edi)
Nao entendi a contagem...
Eh basicamente a contagem dos caracteres:
O meu ID eh 'xxxxxxxxxxxx'\n
A letra O seria 0 o espaço seria 1 o M 2 e assim por diante.
Bom esta ai o nosso programa,Agora vamos "debugar" ele.
Para isso eu criei outra pasta e movi somente o arquivo .asm para ela.
Eu vou usar o gdb(GNU debugger).
Para rodar ele corretamente no programa e preciso reassemblar o programa.asm
de uma maneira diferente:
as -gstabs -o programa.o programa.asm
ld -o programa programa.o
E criamos o executavel, repare que este é maior que o criado anteriormente
sem o parametro -gstabs.
Sem o parâmetro
Com o gstabs
Isso ocorre porque este parâmetro é necessário para auxiliar o debugger a
percorrer o executável.
Vamos la:
gdb programa, depois disso ele vai esperar os comandos
//vamos setar um breakpoint
break *_start
//isso faz um breakpoint logo apos a section start
A partit dai, basta digitar 'next' para percorrer o programa.
OBJDUMP
Este carinha vai nos permitir ver object codes, olhe só
objdump -s programaobj.o
Você deve estar pensando
-Já vi isso em um shellcode!
De fato sim, oque ocorre é que a segunda coluna que contém os objets são usados
num array do tipo char para executar um shellcode.Eu farei um proxímo post sobre isso. Mas só pra ilustrar iria ficar mais ou menos assim
char shellcode[] = "\xb8\x00\x00\x00...."
Depois é necessario cria uma função para executar isso, mas como eu disse farei
um post sobre shellcode e como escrever um.
Espero que tenha gostado e peço desculpas pelas imagens, é meu primeiro post,estou aberto a críticas , valeu!
Comment