flat assembler
Message board for the users of flat assembler.
Index
> Tutorials and Examples > [Linux32] vDSO, auxv, vsyscall, int 0x80, calling the kernel |
Author |
|
revolution 23 Jan 2019, 10:08
[Linux32] vDSO, auxv, vsyscall, int 0x80, calling the kernel
In 32-bit Linux calling the kernel can be done using int 0x80. There are also other, more efficient ways to call kernel services and functions. Linux maps into every process a kernel interface page called the virtual dynamic shared object (vDSO). http://man7.org/linux/man-pages/man7/vdso.7.html What this gives us is a function to call the kernel using either the syscall or the sysenter instruction. This is called the vsyscall entry. So depending upon which instruction the CPU supports Linux figures it out and makes it available to us in the vDSO. There are at least three ways to find the entry point of vsyscall The first and easiest is the AT_SYSINFO value available from the auxv table. Code: format ELF executable 0 at 1 shl 16 entry main AT_NULL = 0 AT_SYSINFO = 32 SYS_EXIT = 1 SYS_WRITE = 4 STD_OUTPUT = 1 struc Elf32_auxv_t { .a_type rd 1 .a_val rd 1 } virtual at 0 Elf32_auxv_t Elf32_auxv_t sizeof.Elf32_auxv_t = $ end virtual segment executable default_sys_call: ;if the search fails we use this to call the kernel int 0x80 retn main: mov eax,[esp] ;argument count (argc) lea ebx,[esp + 4 + (eax + 1) * 4] ;skip the args and the final null .skip_environment: cmp dword[ebx],0 ;last entry in environment? lea ebx,[ebx + 4] ;next entry in environment jnz .skip_environment .scan_auxv: mov eax,[ebx + Elf32_auxv_t.a_type] add ebx,sizeof.Elf32_auxv_t ;next auxv entry cmp eax,AT_NULL ;end of auxv table? jz .auxv_done cmp eax,AT_SYSINFO jnz .scan_auxv mov eax,[ebx - sizeof.Elf32_auxv_t + Elf32_auxv_t.a_val] mov [sys_call],eax .auxv_done: mov ecx,vdso_at mov edx,[sys_call] call write_hex mov eax,SYS_WRITE mov ebx,STD_OUTPUT mov ecx,hello_world mov edx,hello_world_len call [sys_call] mov eax,SYS_EXIT xor ebx,ebx call [sys_call] write_hex: ;ecx = address ;edx = value .next_nibble: mov eax,edx shr eax,28 cmp al,10 sbb al,0x69 das mov [ecx],al inc ecx shl edx,4 jnz .next_nibble retn segment readable writeable align 4 sys_call dd default_sys_call hello_world: db 'vdso at 0x' vdso_at db '00000000',10 hello_world_len = $ - hello_world Code: vdso at 0xF7772C80 vdso at 0xF77C3C80 vdso at 0xF77F9C80 vdso at 0xF771DC80 vdso at 0xF775FC80 vdso at 0xF77A2C80 vdso at 0xF77D4C80 vdso at 0xF77BAC80 vdso at 0xF7790C80 vdso at 0xF7799C80 However there is this note Quote: For some architectures, there is also an AT_SYSINFO tag. This is used only for locating the vsyscall entry point and is frequently omitted or set to 0 (meaning it's not available). This tag is a throwback to the initial vDSO work (see History below) and its use should be avoided. Code: format ELF executable 0 at 1 shl 16 entry main AT_NULL = 0 AT_SYSINFO_EHDR = 33 Elf32_Ehdr.e_entry = 0x18 SYS_EXIT = 1 SYS_WRITE = 4 STD_OUTPUT = 1 struc Elf32_auxv_t { .a_type rd 1 .a_val rd 1 } virtual at 0 Elf32_auxv_t Elf32_auxv_t sizeof.Elf32_auxv_t = $ end virtual segment executable default_sys_call: ;if the search fails we use this to call the kernel int 0x80 retn main: mov eax,[esp] ;argument count (argc) lea ebx,[esp + 4 + (eax + 1) * 4] ;skip the args and the final null .skip_environment: cmp dword[ebx],0 ;last entry in environment? lea ebx,[ebx + 4] ;next entry in environment jnz .skip_environment .scan_auxv: mov eax,[ebx + Elf32_auxv_t.a_type] add ebx,sizeof.Elf32_auxv_t ;next auxv entry cmp eax,AT_NULL ;end of auxv table? jz .auxv_done cmp eax,AT_SYSINFO_EHDR jnz .scan_auxv mov ecx,[ebx - sizeof.Elf32_auxv_t + Elf32_auxv_t.a_val] mov eax,[ecx + Elf32_Ehdr.e_entry] ;get program entry add eax,ecx mov [sys_call],eax .auxv_done: mov ecx,vdso_at mov edx,[sys_call] call write_hex mov eax,SYS_WRITE mov ebx,STD_OUTPUT mov ecx,hello_world mov edx,hello_world_len call [sys_call] mov eax,SYS_EXIT xor ebx,ebx call [sys_call] write_hex: ;ecx = address ;edx = value .next_nibble: mov eax,edx shr eax,28 cmp al,10 sbb al,0x69 das mov [ecx],al inc ecx shl edx,4 jnz .next_nibble retn segment readable writeable align 4 sys_call dd default_sys_call hello_world: db 'vdso at 0x' vdso_at db '00000000',10 hello_world_len = $ - hello_world So there is a third method. Do a lookup of the ELF symbol table and find the entry for __kernel_vsyscall. Full code for this is below. Using this method is more flexible and gives us access to other functions also. For example my system provides a ccall function for __vdso_clock_gettime which allows faster access to the high resolution timer. If you need high precision timing then this might be useful. Code: ;stack pointer->argc dword = number of args (n) ; argv 0 dword = program name ; argv 1 dword ; argv ... dwords ; argv n - 1 dword ; argv NULL dword = end of args ; envp 0 dword ; envp 1 dword ; envp ... dwords ; envp NULL dword = end of environment ; auxv 0 Elf32_auxv_t ; auxv 1 Elf32_auxv_t ; auxv ... Elf32_auxv_t ; auxv NULL Elf32_auxv_t = AT_NULL vector format ELF executable 0 at 1 shl 16 entry main AT_NULL = 0 AT_SYSINFO = 32 AT_SYSINFO_EHDR = 33 EI_NIDENT = 16 SHT_STRTAB = 3 SHT_DYNSYM = 11 SYS_EXIT = 1 SYS_WRITE = 4 SYS_TIME = 13 SYS_CLOCK_GETTIME = 265 STD_OUTPUT = 1 CLOCK_MONOTONIC = 1 TIMESPEC_SCALE = 1000000000 TEST_ITERATIONS = 10000000 DOWN_SCALE = 1000 struc Elf32_auxv_t { .a_type rd 1 .a_val rd 1 } struc e_ident { .ei_mag0 rb 1 .ei_mag1 rb 1 .ei_mag2 rb 1 .ei_mag3 rb 1 .ei_class rb 1 .ei_data rb 1 .ei_version rb 1 .ei_osabi rb 1 .ei_abiversion rb 1 .ei_pad rb EI_NIDENT - $ + .ei_mag0 } struc Elf32_Ehdr { .e_ident e_ident .e_type rw 1 .e_machine rw 1 .e_version rd 1 .e_entry rd 1 .e_phoff rd 1 .e_shoff rd 1 .e_flags rd 1 .e_ehsize rw 1 .e_phentsize rw 1 .e_phnum rw 1 .e_shentsize rw 1 .e_shnum rw 1 .e_shstrndx rw 1 } struc Elf32_Shdr { .sh_name rd 1 .sh_type rd 1 .sh_flags rd 1 .sh_addr rd 1 .sh_offset rd 1 .sh_size rd 1 .sh_link rd 1 .sh_info rd 1 .sh_addralign rd 1 .sh_entsize rd 1 } struc Elf32_Sym { .st_name rd 1 .st_value rd 1 .st_size rd 1 .st_info rb 1 .st_other rb 1 .st_shndx rw 1 } struc timespec { .tv_sec rd 1 .tv_nsec rd 1 } struc symbol_scan name { .address rd 1 .name dd name + 0 } struc table_info { .address rd 1 .size rd 1 } struc string_header value { .length db value + 0 ;if you need strings longer than 255 bytes then set this to "dw" } irp structure, e_ident, Elf32_auxv_t, Elf32_Ehdr, Elf32_Shdr, Elf32_Sym, timespec, symbol_scan, table_info { virtual at 0 structure structure sizeof.#structure = $ end virtual } virtual at -sizeof.string_header string_header string_header sizeof.string_header = $ - $$ end virtual segment executable default_sys_call: ;if the search fails we use this to call the kernel int 0x80 retn main: mov eax,[esp] ;argument count (argc) lea ebx,[esp + 4 + (eax + 1) * 4] ;skip the args and the final null .skip_environment: cmp dword[ebx],0 ;last entry in environment? lea ebx,[ebx + 4] ;next entry in environment jnz .skip_environment .scan_auxv: mov eax,[ebx + Elf32_auxv_t.a_type] add ebx,sizeof.Elf32_auxv_t ;next auxv entry cmp eax,AT_NULL ;end of auxv table? jz .auxv_done cmp eax,AT_SYSINFO_EHDR jz .examine_ELF cmp eax,AT_SYSINFO jnz .scan_auxv mov eax,[ebx - sizeof.Elf32_auxv_t + Elf32_auxv_t.a_val] mov [sys_call_AT_SYSINFO],eax push eax message_found0 call print_format jmp .scan_auxv .examine_ELF: mov edi,[ebx - sizeof.Elf32_auxv_t + Elf32_auxv_t.a_val] push edi message_elf_header call print_format mov eax,[edi + Elf32_Ehdr.e_entry] ;get program entry add eax,edi mov [sys_call_Elf32_Ehdr.e_entry],eax ;vsyscall address from program entry push eax message_found1 call print_format push edi call find_dynsym ;search the dynamic symbol table for the named symbols push edi symbol_scan_table call scan_symbols jmp .scan_auxv .auxv_done: mov eax,[vsyscall] ;trusted if present mov ebx,[sys_call_AT_SYSINFO] ;trusted if present mov ecx,[sys_call_Elf32_Ehdr.e_entry];untrusted and not documented test eax,eax jnz .first_entry_address_okay mov eax,ebx .first_entry_address_okay: test eax,eax jz .no_trusted_entry_found mov [sys_call],eax ;set the new vsyscall address cmp eax,ecx jnz .e_entry_fail mov ecx,message_success xor ebx,ebx ;result code .done: push ecx call print_format call tests mov eax,SYS_EXIT call [sys_call] .e_entry_fail: mov ecx,message_e_entry_failed mov ebx,1 ;result code jmp .done .no_trusted_entry_found: test ecx,ecx jz .no_address_found mov [sys_call],ecx ;try this untrusted value and see what happens mov ecx,message_untrusted_only mov ebx,2 ;result code jmp .done .no_address_found: mov ecx,message_none_found mov ebx,3 ;result code jmp .done tests: call time_stamp mov esi,TEST_ITERATIONS .loop_int_0x80: mov eax,SYS_TIME xor ebx,ebx int 0x80 dec esi jnz .loop_int_0x80 call time_stamp mov ecx,DOWN_SCALE div ecx push eax message_int_0x80 call print_format ; cmp [vsyscall.address],0 jz .done_vsyscall call time_stamp mov esi,TEST_ITERATIONS .loop_vsyscall: mov eax,SYS_TIME xor ebx,ebx call [vsyscall.address] ;system call function dec esi jnz .loop_vsyscall call time_stamp mov ecx,DOWN_SCALE div ecx push eax message_vsyscall call print_format .done_vsyscall: ; cmp [time.address],0 jz .done_vdso call time_stamp mov esi,TEST_ITERATIONS .loop_vdso: push 0 call [time.address] ;ccall function add esp,4 dec esi jnz .loop_vdso call time_stamp mov ecx,DOWN_SCALE div ecx push eax message_vdso call print_format .done_vdso: retn time_stamp: ;return edx:eax = nanoseconds since last call to time_stamp mov ecx,timer mov ebx,CLOCK_MONOTONIC mov eax,SYS_CLOCK_GETTIME call [sys_call] mov eax,TIMESPEC_SCALE mul [timer.tv_sec] add eax,[timer.tv_nsec] adc edx,0 mov ecx,dword[prev_time_stamp + 0] mov dword[prev_time_stamp + 0],eax sub eax,ecx mov ecx,dword[prev_time_stamp + 4] mov dword[prev_time_stamp + 4],edx sbb edx,ecx retn find_dynsym: ;arg1 = ELF_header ;find the dynamic symbol table and its associated string table mov edx,[esp + 4] ;edx = ELF_header xor ecx,ecx ;ecx = section number ;find the SHT_DYNSYM section .next_section: inc ecx cmp cx,[edx + Elf32_Ehdr.e_shnum] jae .not_found call .compute_header_pointer cmp [eax + Elf32_Shdr.sh_type],SHT_DYNSYM jnz .next_section ;save the details mov edx,DYNSYM call .initialise_table_info ;check the linked symbol table is valid and of type SHT_STRTAB mov edx,[esp + 4] ;edx = ELF_header mov ecx,[eax + Elf32_Shdr.sh_link] movzx eax,[edx + Elf32_Ehdr.e_shnum] cmp ecx,eax jae .not_found call .compute_header_pointer cmp [eax + Elf32_Shdr.sh_type],SHT_STRTAB jnz .not_found mov edx,STRTAB call .initialise_table_info .done: retn 4 .not_found: xor eax,eax mov [DYNSYM.address],eax jmp .done .compute_header_pointer: ;ecx = section number ;edx = ELF_header movzx eax,[edx + Elf32_Ehdr.e_shentsize] imul eax,ecx add eax,[edx + Elf32_Ehdr.e_shoff] ;eax = section header offset add eax,edx ;eax = section header pointer retn .initialise_table_info: ;eax = section header ;edx = table info mov ecx,[eax + Elf32_Shdr.sh_addr] add ecx,[esp + 4 * 2] ;add in the ELF_header mov [edx + table_info.address],ecx mov ecx,[eax + Elf32_Shdr.sh_size] mov [edx + table_info.size],ecx retn scan_symbols: ;arg1 = scan table, arg2 = VDSO offset ;given a list of symbol names, find their corresponding addresses in the VDSO push esi mov esi,[esp + 4 * 2] ;esi = symbol scan table .loop_symbol: mov eax,[esi + symbol_scan.name] test eax,eax jz .done_scan push eax call find_symbol test eax,eax jz .next_symbol add eax,[esp + 4 * 3] ;add the VDSO offset mov [esi + symbol_scan.address],eax ;store the addresss push [esi + symbol_scan.name] eax message_symbol_found call print_format .next_symbol: add esi,sizeof.symbol_scan jmp .loop_symbol .done_scan: pop esi retn 8 find_symbol: ;arg1 = symbol name ;return eax = offset of symbol if found, zero if not push esi edi mov edx,[DYNSYM.address] ;edx = DYNSYM table test edx,edx jz .not_found xor eax,eax ;eax = current symbol offset .loop_symbols: mov esi,[edx + eax + Elf32_Sym.st_name] cmp esi,[STRTAB.size] jae .next_symbol add esi,[STRTAB.address] mov edi,[esp + 4 * 3] ;edi = name to find movzx ecx,[edi + string_header.length] cld repz cmpsb jnz .next_symbol cmp byte[esi],0 jz .found_symbol .next_symbol: add eax,sizeof.Elf32_Sym cmp eax,[DYNSYM.size] jb .loop_symbols .not_found: xor eax,eax jmp .done .found_symbol: mov eax,[edx + eax + Elf32_Sym.st_value] .done: pop edi esi retn 4 print_format: ;arg1 = string, arg2 ... argN = values ; % expands to an unsigned decimal number ; # expands to an unsigned hexadecimal number ; $ expands to a string push esi ebx edi ebp mov esi,[esp + 4 * 5] ;esi = string lea ebx,[esp + 4 * 6] ;ebx = values movzx edi,[esi + string_header.length];edi = length .loop_char: test edi,edi jz .done mov al,[esi] mov ebp,16 ;ebp = base 16 cmp al,'#' jz .number mov ebp,10 ;ebp = base 10 cmp al,'%' jz .number cmp al,'$' jz .string mov ecx,esi mov edx,1 .write: call .write_bytes .next_char: dec edi inc esi jmp .loop_char .string: mov ecx,[ebx] ;ecx = address movzx edx,[ecx + string_header.length] add ebx,4 jmp .write .number: .max_number_length = 12 mov edx,[ebx] ;edx = number mov ecx,esp sub esp,.max_number_length .next_digit: mov eax,edx xor edx,edx div ebp xchg edx,eax cmp al,10 sbb al,0x69 das dec ecx mov [ecx],al test edx,edx jnz .next_digit lea edx,[esp + .max_number_length] sub edx,ecx ;edx = length call .write_bytes add esp,.max_number_length add ebx,4 jmp .next_char .done: mov ecx,ebx pop ebp edi ebx esi eax ;eax = return address mov esp,ecx push eax retn .write_bytes: ;ecx = address ;edx = length test edx,edx jz .write_bytes_done push ebx mov eax,SYS_WRITE mov ebx,STD_OUTPUT call [sys_call] pop ebx .write_bytes_done: retn segment readable writeable align 4 sys_call dd default_sys_call ;all kernel calls pass through this symbol_scan_table: vsyscall symbol_scan vsyscall_name sigreturn symbol_scan sigreturn_name rt_sigreturn symbol_scan rt_sigreturn_name gettimeofday symbol_scan gettimeofday_name time symbol_scan time_name clock_gettime symbol_scan clock_gettime_name dummy symbol_scan 0 DYNSYM table_info STRTAB table_info timer timespec sys_call_AT_SYSINFO rd 1 sys_call_Elf32_Ehdr.e_entry rd 1 prev_time_stamp rq 1 segment readable struc string [chars] { common local ..length, ..dummy ..dummy string_header ..length . db chars ..length = $ - . } vsyscall_name string '__kernel_vsyscall' sigreturn_name string '__kernel_sigreturn' rt_sigreturn_name string '__kernel_rt_sigreturn' gettimeofday_name string '__vdso_gettimeofday' time_name string '__vdso_time' clock_gettime_name string '__vdso_clock_gettime' message_elf_header string '0x# VDSO ELF header',10 message_found0 string '0x# vsyscall from AT_SYSINFO',10 message_found1 string '0x# vsyscall from Elf32_Ehdr.e_entry',10 message_symbol_found string '0x# $',10 message_success string 'Address match for Elf32_Ehdr.e_entry okay',10 message_e_entry_failed string 'Elf32_Ehdr.e_entry not matched',10 message_untrusted_only string 'Only untrusted vsyscall address found',10 message_none_found string 'No vsyscall address found',10 message_int_0x80 string 'int_0x80 time %',10 message_vsyscall string 'vsyscall time %',10 message_vdso string 'vdso time %',10 Code: 0xF7711C80 vsyscall from AT_SYSINFO 0xF7711000 VDSO ELF header 0xF7711C80 vsyscall from Elf32_Ehdr.e_entry 0xF7711C80 __kernel_vsyscall 0xF7711CA0 __kernel_sigreturn 0xF7711CB0 __kernel_rt_sigreturn 0xF7711AD0 __vdso_gettimeofday 0xF7711C40 __vdso_time 0xF7711820 __vdso_clock_gettime Address match for Elf32_Ehdr.e_entry okay int_0x80 time 4694864 vsyscall time 3032215 vdso time 117856 |
|||
23 Jan 2019, 10:08 |
|
revolution 28 Jan 2019, 07:16
I can't imagine a case where I need to detect and call the Linux kernel from a Windows exe. It is a neat trick though.
What is a reliable method to detect Linux anyhow? Execute int 0x80 and catch the exception? |
|||
28 Jan 2019, 07:16 |
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.