Conozca Linux vDSO a través de un pequeño experimento

Aquí ya no se explica el concepto de vDSO, sino que se habla directamente de su significado:

  • vDSO es similar a un tablero de anuncios de información, los usuarios pueden obtener lo que necesitan sin pasar por ningún trámite.
  • vDSO es equivalente a una biblioteca C expuesta directamente por el kernel como complemento de GLIBC.
  • ...

Las llamadas como gettimeofday están atrapadas en el kernel para obtener una marca de tiempo cada vez. Parece un poco caro. Es mejor para el kernel colocar la marca de tiempo en un lugar público que pueda ser expuesto a cualquier usuario, y el usuario puede leerlo por sí mismo. Casos de uso típicos de vDSO.

Para simplificar la descripción, desactivamos ASLR:

[root@localhost ~]# sysctl -w kernel.randomize_va_space=0

Simplemente abra un programa de ping y obtenga el intervalo de mapa de vdso en / proc / pid / smap:

7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0                          [vdso]
Size:                  8 kB
...

Lo sacamos:

[root@localhost ~]# dd if=/proc/3688/mem of=./vsdo.dd obs=1 bs=1 skip=140737354113024 count=8192

Entonces vemos lo que es:

[root@localhost ~]# file ./vdso.dd
./vdso.dd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=09be88363f7ca8b05e2cb54a82d16bec2e840186, stripped

Luego, puede objdump, al igual que una biblioteca de enlaces dinámicos normal:

[root@localhost ~]# objdump -T vdso.dd

vdso.dd:     文件格式 elf64-x86-64

DYNAMIC SYMBOL TABLE:
ffffffffff700354 l    d  .eh_frame_hdr	0000000000000000              .eh_frame_hdr
ffffffffff700700  w   DF .text	000000000000059d  LINUX_2.6   clock_gettime
0000000000000000 g    DO *ABS*	0000000000000000  LINUX_2.6   LINUX_2.6
ffffffffff700ca0 g    DF .text	00000000000002d5  LINUX_2.6   __vdso_gettimeofday
ffffffffff700fa0 g    DF .text	000000000000003d  LINUX_2.6   __vdso_getcpu
ffffffffff700ca0  w   DF .text	00000000000002d5  LINUX_2.6   gettimeofday
ffffffffff700f80  w   DF .text	0000000000000016  LINUX_2.6   time
ffffffffff700fa0  w   DF .text	000000000000003d  LINUX_2.6   getcpu
ffffffffff700700 g    DF .text	000000000000059d  LINUX_2.6   __vdso_clock_gettime
ffffffffff700f80 g    DF .text	0000000000000016  LINUX_2.6   __vdso_time

Eche un vistazo, eche un vistazo, qué hay realmente, hay algunas funciones de anuncio de tiempo, lo que significa que si desea obtener el tiempo, simplemente ajuste la función aquí, veamos cómo es la llamada al sistema de tiempo más simple Para obtener la hora, el siguiente es el resultado de objdump -D para el archivo vdso.dd:

ffffffffff700f80 <__vdso_time@@LINUX_2.6>:
ffffffffff700f80:   55                      push   %rbp
ffffffffff700f81:   48 85 ff                test   %rdi,%rdi
ffffffffff700f84:   48 8b 04 25 a8 f0 5f    mov    0xffffffffff5ff0a8,%rax
ffffffffff700f8b:   ff
ffffffffff700f8c:   48 89 e5                mov    %rsp,%rbp
ffffffffff700f8f:   74 03                   je     ffffffffff700f94 <__vdso_time@@LINUX_2.6+0x14>
ffffffffff700f91:   48 89 07                mov    %rax,(%rdi)
ffffffffff700f94:   5d                      pop    %rbp
ffffffffff700f95:   c3                      retq

Obviamente, no se llamó al sistema, pero la hora se obtuvo directamente de la dirección 0xffffffffff5ff0a8, entonces la dirección 0xffffffffff5ff0a8 debe ser la ubicación del tablero de anuncios de la hora mapeado desde el kernel al modo de usuario.

Recuerda la dirección 0xffffffffff5ff0a8, el análisis del modo de usuario está aquí, ingresamos al kernel para echar un vistazo.

Primero verifique la ubicación de vdso desde / proc / kallsyms:

ffffffff81941000 D vdso_start
ffffffff819424b0 D vdso_end

A continuación, encontramos la ubicación del tablero de anuncios de tiempo del kernel vsyscall_gtod_data:

ffffffff81a75080 D vsyscall_gtod_data

Veamos el valor del tablero de anuncios:

crash> struct vsyscall_gtod_data.wall_time_sec ffffffff81a75080
  wall_time_sec = 1600912854
crash> struct vsyscall_gtod_data.wall_time_sec ffffffff81a75080
  wall_time_sec = 1600912856
crash> struct vsyscall_gtod_data.wall_time_sec ffffffff81a75080
  wall_time_sec = 1600912857

Obviamente, el campo wall_time_sec del tablón de anuncios es el valor devuelto a tiempo. A continuación encontramos su dirección:

crash> struct vsyscall_gtod_data ffffffff81a75080 -o
struct vsyscall_gtod_data {
    
    
  [ffffffff81a75080] seqcount_t seq;
        struct {
    
    
            int vclock_mode;
            cycle_t cycle_last;
            cycle_t mask;
            u32 mult;
            u32 shift;
  [ffffffff81a75088] } clock;
  [ffffffff81a750a8] time_t wall_time_sec;
  [ffffffff81a750b0] u64 wall_time_snsec;
  [ffffffff81a750b8] u64 monotonic_time_snsec;
  [ffffffff81a750c0] time_t monotonic_time_sec;
  [ffffffff81a750c8] struct timezone sys_tz;
  [ffffffff81a750d0] struct timespec wall_time_coarse;
  [ffffffff81a750e0] struct timespec monotonic_time_coarse;
}

Bueno, es 0xffffffff81a750a8. Se asigna a la dirección expuesta al modo de usuario en 0xffffffffff5ff0a8.

A continuación, confirmamos esto:

  • Modifique la dirección de mapeo y regrese a la llamada de tiempo con 0.

Veamos el tablero de anuncios nuevamente:

crash> struct vsyscall_gtod_data ffffffff81a75080
...
  sys_tz = {
    
    
    tz_minuteswest = 0,
    tz_dsttime = 0
  },

¿Cómo mapeamos sys_tz? Este valor siempre es 0, esperamos que el tiempo devuelva 0.

Para esto, primero obtenemos el desplazamiento entre sys_tz y wall_time_sec:

crash> eval ffffffff81a750c8-ffffffff81a750a8
hexadecimal: 20
    decimal: 32
      octal: 40

Por lo tanto, solo necesitamos cambiar el código de función de tiempo de vdso:

ffffffffff700f84:   48 8b 04 25 a8 f0 5f    mov    0xffffffffff5ff0a8,%rax

A:

ffffffffff700f84:   48 8b 04 25 c8 f0 5f    mov    0xffffffffff5ff0c8,%rax

Es decir, el octavo byte de la función de tiempo, 0xa8 se puede cambiar a 0xc8:

A través de la coincidencia de patrones, puede obtener el desplazamiento de la función de tiempo en la página vdso:

     f80:   55                      push   rbp
     f81:   48 85 ff                test   rdi,rdi
     f84:   48 8b 04 25 a8 f0 5f    mov    rax,QWORD PTR ds:0xffffffffff5ff0a8
     f8b:   ff
     f8c:   48 89 e5                mov    rbp,rsp
     f8f:   74 03                   je     0xf94
     f91:   48 89 07                mov    QWORD PTR [rdi],rax
     f94:   5d                      pop    rbp
     f95:   c3                      ret

Eso es 0xf80.

Entonces 0xffffffff81941f80 es la dirección de la función de tiempo:

unsigned char *addr = (unsigned char *)0xffffffff81941f80;
addr[8] = 0xc8;

Antes de la modificación, primero programamos la verificación:

#include <time.h>
#include <stdio.h>

typedef time_t  (*time_func)(time_t *);
int main(int argc, char *argv[])
{
    
    
    time_t tloc;
    // 直接从地址拿值
	unsigned long *p = (unsigned long *)0xffffffffff5ff0a8;
	// 通过函数拿值
    time_func func = (time_func)0x7ffff7ffaf80;

    func(&tloc);
    printf("%ld\n", tloc);
	printf("%lu\n", *p);
}

El resultado esperado debe ser el mismo valor obtenido de dos formas:

[root@localhost ~]# ./a.out
1600923922
1600923922
[root@localhost ~]# ./a.out
1600923923
1600923923
[root@localhost ~]#

Modifique las instrucciones correspondientes a la página del kernel de la siguiente manera:

[root@localhost ~]# cat modtime.stp
#!/usr/local/bin/stap -g

function modtime(val:long)
%{
    
    
	unsigned char *addr = (unsigned char *)0xffffffff81941f80;
	unsigned char c = (unsigned char)STAP_ARG_val;

	addr[8] = c;
%}

probe begin
{
    
    
	modtime($1)
	exit()
}

Ejecutalo:

[root@localhost ~]# ./modtime.stp 0xc8
[root@localhost ~]# ./a.out
0
1600924228
[root@localhost ~]# ./a.out
0
1600924229
[root@localhost ~]# ./modtime.stp 0xa8
[root@localhost ~]# ./a.out
1600924238
1600924238
[root@localhost ~]#

Cuando se modifican las instrucciones de la página vdso, todos los procesos que llaman al tiempo serán anormales, lo cual es muy obvio:

top - 08:00:00 up 42 min,  3 users,  load average: 0.00, 0.00, 0.00
Tasks: 114 total,   1 running, 113 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :        0 total,        0 free,        0 used,        0 buff/cache
KiB Swap:        0 total,        0 free,        0 used.        0 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 root      20   0   51696   3808   2492 S  0.0  inf   0:01.29 systemd
    2 root      20   0       0      0      0 S  0.0 -nan   0:00.00 kthreadd
    3 root      20   0       0      0      0 S  0.0 -nan   0:00.00 ksoftirqd/0
    7 root      rt   0       0      0      0 S  0.0 -nan   0:00.01 migration/0
    8 root      20   0       0      0      0 S  0.0 -nan   0:00.00 rcu_bh
    9 root      20   0       0      0      0 S  0.0 -nan   0:00.00 rcuob/0
   10 root      20   0       0      0      0 S  0.0 -nan   0:00.00 rcuob/1

Vale la pena mencionar que antes de vdso, el mecanismo de vsyscall es similar, excepto que solo proporciona un mapa sin abstraer el significado del enlace dinámico, por lo que no puede disfrutar de la protección de seguridad que brinda ASLR.


Los zapatos de cuero en Wenzhou, Zhejiang están mojados, por lo que no engordan con la lluvia.

Supongo que te gusta

Origin blog.csdn.net/dog250/article/details/108807183
Recomendado
Clasificación