Fernando Mercês Posted August 10, 2017 at 01:35 AM Share Posted August 10, 2017 at 01:35 AM Esta é uma maneira antiga de usar as chamas de sistemas do Linux, mas ainda funciona e tem fins didáticos :-) ; # apt install nasm ; $ nasm -f elf32 hello.asm ; $ ld -m elf_i386 -o hello hello.o ; $ ./hello section .rodata ; seção .rodata do ELF, onde ficam os dados somente-leitura msg: db "Mente Binária", 10 ; nossa string que será impressa, seguida de um \n len: equ $-msg ; "$" significa "aqui" -> posição atual menos posição do texto. len terá o tamanho da string. section .text ; seção .text do ELF, onde fica o código global _start ; faz o label "_start" visível ao linker (ld) _start: mov edx,len ; arg3 da syscall write(), quantidade de bytes para imprimir (tamanho) mov ecx,msg ; arg2, pointeiro para o endereço da string mov ebx,1 ; arg1, em qual file descriptor (fd) escrever. 1 é stdout mov eax,4 ; 4 é o código da syscall write() int 0x80 ; interrupção 0x80 do kernel (executa a syscall apontada em eax) mov ebx,0 ; arg1 da syscall exit(). 0 significa execução com sucesso mov eax,1 ; 1 é o código da syscall exit() int 0x80 ; executa a syscall apontada em eax, que vai sair do programa Link to comment Share on other sites More sharing options...
Ygor Da Rocha Parreira Posted August 15, 2017 at 01:24 PM Share Posted August 15, 2017 at 01:24 PM Puta merda... nasm e Intel syntax =/ Link to comment Share on other sites More sharing options...
Fernando Mercês Posted August 15, 2017 at 04:19 PM Author Share Posted August 15, 2017 at 04:19 PM Ah, não me venha com gas e AT&T syntax :-D Acho que o pior é usar a int 0x80. Queria fazer uma versão nova. =) Link to comment Share on other sites More sharing options...
Ygor Da Rocha Parreira Posted August 21, 2017 at 03:07 PM Share Posted August 21, 2017 at 03:07 PM db "\xcd\x80" =P Link to comment Share on other sites More sharing options...
celacantus Posted September 28, 2017 at 11:13 PM Share Posted September 28, 2017 at 11:13 PM testei aqui e precisei colocar mais alguns parâmetros pro linker funcionar. Como estou numa máquina 64bits precisei settar a arquitetura também. Segue o comando: ld -m elf_i386 -s -o hello hello.o Link to comment Share on other sites More sharing options...
Rick Santos Posted November 9, 2017 at 02:40 PM Share Posted November 9, 2017 at 02:40 PM Em CPUs de 64 bits com sistemas Linux costumam suportar a instrução SYSCALL, assim não é necessário usar instruçoes emuladas como int 0x80. Um ponto importante é que no uso desta instrução, todos os parâmetros e números da system call devem ser passados por Registadores de 64 bits (Registadores - Assim chamado em Portugal xD), como nos diz o código do Kernel do Linux. Outro detalhe é que com o uso desta instrução o número da syscall das funções é diferente, para write em vez de ser mov eax, 4 será mov rax, 1. Link to comment Share on other sites More sharing options...
Rick Santos Posted November 9, 2017 at 02:46 PM Share Posted November 9, 2017 at 02:46 PM Outro detalhe a se considerar é que o retorno da syscall executada é acedido por RAX, que contém o endereço linear do ponteiro de retorno da função (syscall) executada. Link to comment Share on other sites More sharing options...
Fernando Mercês Posted November 9, 2017 at 05:35 PM Author Share Posted November 9, 2017 at 05:35 PM Então, @Rick Santos, foi isso que comentei com o @Ygor Da Rocha Parreira sobre atualizar este exemplo. A propósito, você não tem um "hello, world" desse aí pra compartilhar? Sei que tem alguns na Internet, mas um explicado em Português é coisa rara. Abraços, Fernando Link to comment Share on other sites More sharing options...
Rick Santos Posted November 10, 2017 at 01:59 AM Share Posted November 10, 2017 at 01:59 AM Bem, começando do início, uma system call basicamente será uma "interface" entre o User e Kernel Space. CPUs de 64 bits e com o uso de sistemas Linux costuma ser suportado a instrução SYSCALL, assim não sendo necessário usar instruçoes emuladas como int 0x80. (Todas as system calls estão disponíveis em funções da libc, pois elas raramente serão chamadas a partir de puro assembly após o código compilado). De uma maneira resumida, a instrução SYSCALL age como se fosse se fosse uma interrupção (no caso é uma interrupção de software, tal como a int 0x80, agindo diferentemente mas de uma forma bem mais performática), criando uma excepção que fará com que o controlo de execução da CPU seja transferido para um tratador de excepção (exception handler) residente no Kernel (mais concretamente Kernel Code... xD). A maneira com que o Kernel consegue passar o controle de execução para o tratador de excepção correto para a syscall em questão é a partir de uma tabela (syscall table), cujo é representada pela array "sys_call_table" presente no Kernel do Linux que é definida em "arch/x86/entry/syscall_64.c" a partir deste código: asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { [0 ... __NR_syscall_max] = &sys_ni_syscall, #include <asm/syscalls_64.h> }; Isto quer dizer que a array sys_call_table é uma array de "__NR_syscall_max + 1" de tamanho, onde "__NR_syscall_max", que é uma macro, representa o máximo número de system calls existentes na arquitetura em questão. Sem ter a certeza, penso que atualmente o valor de __NR_syscall_max é 322. Podemos ver a definição desta Macro no arquivo gerado durante a compilação do Kernel em "include/generated/asm-offsets.h": #define __NR_syscall_max 322. Outro tipo aprensentado no código de "/syscall_64.c" é "sys_call_ptr_t" que nada mais é do que um ponteiro para para a tabela de system calls (syscall table). Este é definido como um typedef para um ponteiro de uma função que nao retorna nada nem recebe parâmetros: typedef void (*sys_call_ptr_t)(void); Para não deixar muito longo a explicação sobre o código da syscall table vou apenas falar muito resumidamente sobre "sys_ni_syscall", que basicamente representa syscalls que nao foram implementadas diretamente. (OBS: Todos os elementos de sys_call_table apontam para estas syscalls não implementadas), o valor de retorno de "sys_ni_syscall" é "-errno" ou "-ENOSYS" em certos casos. asmlinkage long sys_ni_syscall(void) { return -ENOSYS; } O erro -ENOSYS diz nos que - Function not Implemented (POSIX.1) Mais uma nota: É possível iniciar a sys_call_table a partir de uma extensão do GCC chamada Designated Initializers, que permite a inicialização de elementos de forma não ordenada. Como foi visto no fim de "syscall_64.c" foi incluido o header asm/syscalls_64.h, que foi incialmente gerado pelo script arch"/x86/entry/syscalls/syscalltbl.sh", "syscalls_64.h", contém definidos macros que serão utilizados. Isto foi apenas uma curiosidade sobre syscalls em linux, é certo que é um assunto bastante extenso que seria praticamente impossível descreve-lo por completo aqui (Nem eu próprio o sei kkkk), mas já foi uma introdução, para os interessados sugiro a procura pelo assunto em documentação oficial, etc... --------------------------------------------------------------------------------------------------------------------------------------------- Para verificar a disponibilidade desta instrução na CPU em questão basta usar a instrução CPUID, a qual a descrição está fora do escopo. Mas o código para tal será este: mov rax,1 cpuid test edx,0x800 ; O bit 11 de EDX será "testado" e se ZF = 0 então a instrução SYSCALL é suportada. Segundo a documentação todos os parâmetros de uma syscall devem ser passados por Registadores e não pela Stack (Pilha). Segue a convenção no uso destes: RDI, RSI, RDX, R10, R8, R9 - Nestes 6 Registadores devem ser passados os parâmetros da syscall executada, notar que terão de ser passados nesta ordem específica de Registadores. RAX - Usado para indicar o número da syscall que será executada. (Levar em consideração que o valor que será colocado em RAX ou EAX para indicar a função derivará do uso da instrução SYSCALL para o uso de uma instrução emulada como int 0x80). (OBS: O Registador R11 não deve ser utilizado em syscalls pois o contexto de RFLAGS será guarda nele durante o retorno da chamada). O retorno da syscall será colocado em em RAX, como um endereço linear para o ponteiro dessa função (pelo menos no caso de algumas funções). Caso a execução da syscall gere algum erro, o próprio será colocado também em RAX como valor de retorno. Voltando atrás, à parte da passagem do número da syscall por RAX, pode ser obtida uma lista de syscalls no dirétorio e arquivos "arch/x86/entry/syscalls/syscalls_32.tbl" e "arch/x86/entry/syscalls/syscalls_64.tbl" (para arquiteturas 32 e 64 bits respetivamente (o número de syscalls contidas nestes arquivos depende do valor de "__NR_syscall_max", como visto anteriormente)). Nas manpages do linux também se encontram disponíveis listas e informaçoes sobre syscalls de uma forma mais aprofundada. Junto Envio: http://man7.org/linux/man-pages/man2/syscalls.2.html Algumas manpages sobre funções específicas contêm também informações sobre a respetiva syscall. -------------------------------------------------------------------------------------------------------------------------------------------- Um outro detalhe é que no Windows a instrução syscall também pode ser usada (não tao comum e viável, segundo documentação), caso não esteja dísponivel sempre pode ser usada a interrupção int 0x2e. ------------------------------------------------------------------------------------------------------------------------------------------------ Segue o exemplo da instrução SYSCALL, e como pedido pelo Fernando um Hello World : ------------------------------------------------------------------------------------------------------------------------------------------------ bits 64 ;informar que as instuções devem ser codificadas para a arquitetura 64 bits. section .data ; Secção do arquivo executável final que contém dados globais inicializados. msg db "Hello World", 0x0a ; mensagem que vai ser "printada" . len equ $ - msg ; tamanho da mensagem (Parâmetro que a função com que vamos printar a mensagem recebe). FILENO_STDOUT equ 1 ;Descritor de arquivos (File Descriptor de output de dados, também será passado como parâmetro à função). section .text ;Secção do arquivo executável final que contém as instruções do programa. global _start ; Faz com que o símbolo _start seja vísivel ao Linker (como mencionado pelo Fernando no tópico acima :D) _start: ;simbolo start (primeira função a ser executada no programa que irá executar funções construturas até chegar à famosa "main") Protótipo de write() - ssize_t write(int fd, void *buffer, size_t count); mov rax, 1 ; Número da função syscall que será executada, neste caso 1 = write() em syscall, com int 0x80, 4 = write(). mov rdi, FILENO_STDOUT ; Passar como parâmetro da função Write o descritor de ficheiros pelo Registador RDI mov rsi, msg ;Passar a mensagem a ser printada como parâmetro por RSI mov rdx, len ; Passar o tamanho da mensagem a ser printada como parâmetro por RDX syscall ; Finalmente executar a instrução SYSCALL que executará RAX = 4, ou seja write() com todos os parâmetros passados por registadores acima. mov rax, 60 ;Número da função da syscall que será executada, neste caso 60 = exit() mov rdi, 0x0 ; Passar o valor de retorno de exit como parâmetro por registador RDI syscall ;Executar a instrução syscall que executará RAX = 60, ou seja exit() como todos os parâmetros passados por Registadores acima. OBS: Deve ser mantida a ordem de registadores usados para passagem de parâmetros para funções, assim seguindo a convenção de chamada das syscalls Peço desculpa pela bagunça no código xD. Aqui fica uma introdução às System Calls. Se me lembrar de algo essencial que me tenha esquecido volto a editar, caso tenha cometido algum erro, agradeco a correção. Ricardo Santos. Link to comment Share on other sites More sharing options...
fredericopissarra Posted December 5, 2017 at 12:07 AM Share Posted December 5, 2017 at 12:07 AM Eu mudaria pouca coisa: Mover valores imediatos para registradores de 32 bits automaticamente zera os 32 bits superiores; Por motivoo de clareza, usar LEA ao invés de MOV para inicializar registradores com ponteiros; Offsets relativos a RIP são menores que os tradicionais; Valores constantes, incluindo strings, deveriam ser colocados em .rodata, não .data. bits 64 default rel section .rodata ; Se usar 'crase' como delimitador de strings, ; pode usar sequências de escape! Aspas, simples ; ou duplas, não fazem isso no NASM. msg: db `Hello!\n` msg_len equ $ - msg section .text global _start _start: mov eax,1 ; syscall 1: write mov edi,eax ; STDOUT_FILENO lea rsi,[msg] ; offsets relativos a RIP são menores. mov edx,msg_len syscall mov eax,60 ; syscall 60: exit xor edi,edi syscall Assim as instruções ficam pequenas (apenas LEA, acima, tem o prefixo REX). Link to comment Share on other sites More sharing options...
Rick Santos Posted December 5, 2017 at 04:13 PM Share Posted December 5, 2017 at 04:13 PM Muito obrigado pela correção Frederico, com certeza terei mais cuidado com estes aspetos. Link to comment Share on other sites More sharing options...
Fernando Mercês Posted December 6, 2017 at 02:54 AM Author Share Posted December 6, 2017 at 02:54 AM @Rick Santos sua explicação foi incrível. Muito obrigado! Compilei seu programa e funciona perfeitamente. Já que o @fredericopissarra falou da .rodata, andei dando uma olhada e achei bem legal o tratamento que o NASM dá dependendo do nome da seção/segmento: $ objdump -h hello.o hello.o: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .data 00000007 0000000000000000 0000000000000000 00000380 2**2 CONTENTS, ALLOC, LOAD, DATA 1 .rodata 00000007 0000000000000000 0000000000000000 00000390 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .rodata666 00000007 0000000000000000 0000000000000000 000003a0 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .mentebin 00000007 0000000000000000 0000000000000000 000003b0 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 rodata 00000007 0000000000000000 0000000000000000 000003c0 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .comment 00000005 0000000000000000 0000000000000000 000003d0 2**0 CONTENTS, READONLY 6 .bss 00000041 0000000000000000 0000000000000000 000003e0 2**2 ALLOC 7 .text 0000001e 0000000000000000 0000000000000000 000003e0 2**4 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE Conforme visto, para qualquer nome fora do padrão do formato (seções 2, 3 e 4), o comportamento padrão é uma seção de dados somente leitura. No entanto, ao usar um nome suportado, o tratamento é outro. Usar .rodata faz a string ficar numa seção somente leitura (a .data tem permissão de escrita), mas tem mais uma coisa: o alinhamento tá em 2**2 (contra 2**0 das outras seções "fora do padrão"). Casa exatamente com o que a documentação diz sobre o alinhamento das seções, onde 2**2 = 4 bytes. Em tempo, esta thread tá excelente. Muito obrigado por compartilharem conhecimento! Abraços, Fernando Link to comment Share on other sites More sharing options...
fredericopissarra Posted December 22, 2017 at 12:34 AM Share Posted December 22, 2017 at 12:34 AM Notem que modifiquei o código original que postei, mostrando as chamadas SYSCALL... O NASM aceita o uso de delimitadores de string com "crases", que permite o uso de sequências de escape como \n, \r, \0, \t, ... Outra coisa... ao usar a diretiva "default rel", não precisa se preocupar com o uso do tipo de endereçamento relativo ao RIP, este torna-se o default. Link to comment Share on other sites More sharing options...
fransalles Posted May 20, 2018 at 01:27 AM Share Posted May 20, 2018 at 01:27 AM Boa noite a todos, No meu caso para funcionar, estou utilizando o kali linux instalado dentro do windows 10 (Microsoft Store tem para baixar e instalar). A plataforma que utilizo é de 64 bits e utilizei a seguinte variação para funcionar: hello.asm section .data msg db "Mente Binária Rocks!" section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, msg mov rdx, 13 syscall mov rax, 60 mov rdi, 0 syscall nasm -f elf64 -o hello.o hello.asm ld -o hello hello.o ./hello Link to comment Share on other sites More sharing options...
fredericopissarra Posted May 21, 2018 at 11:16 AM Share Posted May 21, 2018 at 11:16 AM Em 09/11/2017 em 12:46, Rick Santos disse: Outro detalhe a se considerar é que o retorno da syscall executada é acedido por RAX, que contém o endereço linear do ponteiro de retorno da função (syscall) executada. Existe outro detalhe com o uso de SYSCALL... Os Flags (quando retornados) são colocados em R11. Embora isso não seja problemático no Linux... Em 19/05/2018 em 22:27, fransalles disse: Boa noite a todos, No meu caso para funcionar, estou utilizando o kali linux instalado dentro do windows 10 (Microsoft Store tem para baixar e instalar). A plataforma que utilizo é de 64 bits e utilizei a seguinte variação para funcionar: hello.asm section .data msg db "Mente Binária Rocks!" section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, msg mov rdx, 13 syscall mov rax, 60 mov rdi, 0 syscall nasm -f elf64 -o hello.o hello.asm ld -o hello hello.o ./hello É sempre interessante informar o modelo usado com 'bits 64' ou 'bits 32' no código. Algumas instruções são codificadas de acordo com essas diretivas. O argymento -f elf64 diz ao NASM apenas qual é o formato do arquivo ELF. Link to comment Share on other sites More sharing options...
fransalles Posted May 22, 2018 at 01:33 AM Share Posted May 22, 2018 at 01:33 AM fredericopissarra Obrigado pelo excelente comentário e sim você tem toda a razão, principalmente quando disse que deveria informar o modelo usado. Link to comment Share on other sites More sharing options...
Rick Santos Posted May 22, 2018 at 09:04 PM Share Posted May 22, 2018 at 09:04 PM Em 21/05/2018 em 08:16, fredericopissarra disse: Existe outro detalhe com o uso de SYSCALL... Os Flags (quando retornados) são colocados em R11. Embora isso não seja problemático no Linux... É sempre interessante informar o modelo usado com 'bits 64' ou 'bits 32' no código. Algumas instruções são codificadas de acordo com essas diretivas. O argymento -f elf64 diz ao NASM apenas qual é o formato do arquivo ELF. Com certeza Frederico, eu cheguei a fazer uma observação sobre os flags: (OBS: O Registador R11 não deve ser utilizado em syscalls pois o contexto de EFLAGS será guarda nele durante o retorno da chamada). Obrigado pela informação. Link to comment Share on other sites More sharing options...
Lincoln Arantes Posted September 8, 2019 at 01:07 PM Share Posted September 8, 2019 at 01:07 PM Em 09/08/2017 em 21:35, Fernando Mercês disse: Esta é uma maneira antiga de usar as chamas de sistemas do Linux, mas ainda funciona e tem fins didáticos ? ; # apt install nasm ; $ nasm -f elf32 hello.asm ; $ ld -m elf_i386 -o hello hello.o ; $ ./hello section .rodata ; seção .rodata do ELF, onde ficam os dados somente-leitura msg: db "Mente Binária", 10 ; nossa string que será impressa, seguida de um \n len: equ $-msg ; "$" significa "aqui" -> posição atual menos posição do texto. len terá o tamanho da string. section .text ; seção .text do ELF, onde fica o código global _start ; faz o label "_start" visível ao linker (ld) _start: mov edx,len ; arg3 da syscall write(), quantidade de bytes para imprimir (tamanho) mov ecx,msg ; arg2, pointeiro para o endereço da string mov ebx,1 ; arg1, em qual file descriptor (fd) escrever. 1 é stdout mov eax,4 ; 4 é o código da syscall write() int 0x80 ; interrupção 0x80 do kernel (executa a syscall apontada em eax) mov ebx,0 ; arg1 da syscall exit(). 0 significa execução com sucesso mov eax,1 ; 1 é o código da syscall exit() int 0x80 ; executa a syscall apontada em eax, que vai sair do programa Parabéns! Código bastante útil... Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.