小さな実験を通してLinuxvDSOを知る

ここでは、vDSOの概念については説明していませんが、その意味について直接説明しています。

  • vDSOは情報掲示板に似ており、ユーザーは手続きを経ることなく必要なものを入手できます。
  • vDSOは、GLIBCの補足としてカーネルによって直接公開されるCライブラリと同等です。

gettimeofdayのような呼び出しは、毎回タイムスタンプを取得するためにカーネルにトラップされます。少しコストがかかるようです。カーネルは、タイムスタンプをすべてのユーザーに公開できる公共の場所に置き、ユーザーが自分で読み取ることができるようにすることをお勧めします。 vDSOの一般的な使用例。

説明を簡単にするために、ASLRをオフにします。

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

pingプログラムを開き、/ proc / pid / smapでvdsoのマップ間隔を取得するだけです。

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

私たちはそれをddします:

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

次に、それが何であるかを確認します。

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

次に、通常の動的リンクライブラリと同じように、objdumpを実行できます。

[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

見て、見て、実際には何がありますか、いくつかの時間アナウンス機能があります。つまり、時間を取得したい場合は、ここで機能を調整するだけで、最も単純な時間システム呼び出しがどのようになっているのかを見てみましょう。時間を取得するために、vdso.ddファイルのobjdump-Dの結果は次のとおりです。

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

明らかに、システム呼び出しは呼び出されませんでしたが、時刻はアドレス0xffffffffff5ff0a8から直接取得されたため、アドレス0xffffffffff5ff0a8は、カーネルからユーザーモードにマップされた時刻報ボードの場所である必要があります。

アドレス0xffffffffff5ff0a8を思い出してください。ユーザーモードの分析はここにあります。カーネルに入って確認します。

まず、/ proc / kallsymsからvdsoの場所を確認します。

ffffffff81941000 D vdso_start
ffffffff819424b0 D vdso_end

次に、カーネルタイムブレティンボードvsyscall_gtod_dataの場所を見つけます。

ffffffff81a75080 D vsyscall_gtod_data

掲示板の価値を見てみましょう。

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

明らかに、掲示板のwall_time_secフィールドはtimeに返される値です。以下にそのアドレスを示します。

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;
}

まあ、それは0xffffffff81a750a8です。これは、0xffffffffff5ff0a8でユーザーモードに公開されているアドレスにマップされます。

次にこれを確認します。

  • マッピングアドレスを変更し、0でタイムコールに戻ります。

掲示板をもう一度見てみましょう。

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

sys_tzをどのようにマッピングしますか。この値は常に0であり、0を返す時間が予想されます。

このために、最初にsys_tzとwall_time_secの間のオフセットを取得します。

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

したがって、vdsoの時間関数コードを変更するだけで済みます。

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

に:

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

つまり、時間関数の8番目のバイトである0xa8を0xc8に変更できます。

パターンマッチングにより、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

それは0xf80です。

その場合、0xffffffff81941f80は時間関数のアドレスです。

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

変更する前に、まず検証をプログラムします。

#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);
}

期待される結果は、次の2つの方法で得られた値と同じである必要があります。

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

カーネルページに対応する命令を次のように変更します。

[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()
}

それを実行します:

[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 ~]#

vdsoページの指示が変更されると、時間を呼び出すすべてのプロセスが異常になります。これは非常に明白です。

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

vdsoの前は、vsyscallメカニズムは似ていますが、動的リンクの意味を抽象化せずにマップを提供するだけであるため、ASLRによってもたらされるセキュリティ保護を享受できないことは言及する価値があります。


浙江文州の革靴は濡れているので、雨でも太りません。

おすすめ

転載: blog.csdn.net/dog250/article/details/108807183