Administradores Fernando Mercês Postado Agosto 10, 2017 em 01:35 Administradores Compartilhar Postado Agosto 10, 2017 em 01:35 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 para o comentário Compartilhar em outros sites More sharing options...
Ygor Da Rocha Parreira Postado Agosto 15, 2017 em 13:24 Compartilhar Postado Agosto 15, 2017 em 13:24 Puta merda... nasm e Intel syntax =/ Link para o comentário Compartilhar em outros sites More sharing options...
Administradores Fernando Mercês Postado Agosto 15, 2017 em 16:19 Autor Administradores Compartilhar Postado Agosto 15, 2017 em 16:19 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 para o comentário Compartilhar em outros sites More sharing options...
Ygor Da Rocha Parreira Postado Agosto 21, 2017 em 15:07 Compartilhar Postado Agosto 21, 2017 em 15:07 db "\xcd\x80" =P Link para o comentário Compartilhar em outros sites More sharing options...
celacantus Postado Setembro 28, 2017 em 23:13 Compartilhar Postado Setembro 28, 2017 em 23:13 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 para o comentário Compartilhar em outros sites More sharing options...
Rick Santos Postado Novembro 9, 2017 em 14:40 Compartilhar Postado Novembro 9, 2017 em 14:40 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 para o comentário Compartilhar em outros sites More sharing options...
Rick Santos Postado Novembro 9, 2017 em 14:46 Compartilhar Postado Novembro 9, 2017 em 14:46 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 para o comentário Compartilhar em outros sites More sharing options...
Administradores Fernando Mercês Postado Novembro 9, 2017 em 17:35 Autor Administradores Compartilhar Postado Novembro 9, 2017 em 17:35 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 para o comentário Compartilhar em outros sites More sharing options...
Rick Santos Postado Novembro 10, 2017 em 01:59 Compartilhar Postado Novembro 10, 2017 em 01:59 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 para o comentário Compartilhar em outros sites More sharing options...
fredericopissarra Postado Dezembro 5, 2017 em 00:07 Compartilhar Postado Dezembro 5, 2017 em 00:07 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 para o comentário Compartilhar em outros sites More sharing options...
Rick Santos Postado Dezembro 5, 2017 em 16:13 Compartilhar Postado Dezembro 5, 2017 em 16:13 Muito obrigado pela correção Frederico, com certeza terei mais cuidado com estes aspetos. Link para o comentário Compartilhar em outros sites More sharing options...
Administradores Fernando Mercês Postado Dezembro 6, 2017 em 02:54 Autor Administradores Compartilhar Postado Dezembro 6, 2017 em 02:54 @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 para o comentário Compartilhar em outros sites More sharing options...
fredericopissarra Postado Dezembro 22, 2017 em 00:34 Compartilhar Postado Dezembro 22, 2017 em 00:34 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 para o comentário Compartilhar em outros sites More sharing options...
fransalles Postado Maio 20, 2018 em 01:27 Compartilhar Postado Maio 20, 2018 em 01:27 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 para o comentário Compartilhar em outros sites More sharing options...
fredericopissarra Postado Maio 21, 2018 em 11:16 Compartilhar Postado Maio 21, 2018 em 11:16 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 para o comentário Compartilhar em outros sites More sharing options...
fransalles Postado Maio 22, 2018 em 01:33 Compartilhar Postado Maio 22, 2018 em 01:33 fredericopissarra Obrigado pelo excelente comentário e sim você tem toda a razão, principalmente quando disse que deveria informar o modelo usado. Link para o comentário Compartilhar em outros sites More sharing options...
Rick Santos Postado Maio 22, 2018 em 21:04 Compartilhar Postado Maio 22, 2018 em 21:04 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 para o comentário Compartilhar em outros sites More sharing options...
Lincoln Arantes Postado Setembro 8, 2019 em 13:07 Compartilhar Postado Setembro 8, 2019 em 13:07 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 para o comentário Compartilhar em outros sites More sharing options...
Posts Recomendados
Arquivado
Este tópico foi arquivado e está fechado para novas respostas.