当前位置:首页 > 网站源码 > 正文内容

腾讯云服务器怎么登录linux(腾讯云服务器怎么登录亚马逊账号)

网站源码1年前 (2023-09-19)301

本文作者为团队小伙伴阿松,在Linux文件监控领域实战经验丰富。本次引入eBPF在文件监控上应用,提升文件变更的关联进程信息等。在实现过程中,分享了eBPF kbproe时,被插桩函数超多参数获取的解决方案。

本文作者为团队小伙伴阿松,在Linux文件监控领域实战经验丰富。本次引入eBPF在文件监控上应用,提升文件变更的关联进程信息等。在实现过程中,分享了eBPF kbproe时,被插桩函数超多参数获取的解决方案。

本文内容 ,如非特殊说明,均基于 4.18 内核,x86-64 CPU 架构。

插桩的程序类型选择

说起 eBPF 大家都不陌生,就内核而言,hook 会尽可能选在 tracepoint,如果没有 tracepoint,会考虑使用 kprobe。

tracepoint 的范围有限,而内核函数又太多,基于各种需求场景,kprobe 的出场机会较多;但需要注意的,并不是所有的内核函数都可以选作 hook 点,inline 函数无法被 hook,static 函数也有可能被优化掉;如果想知道究竟有哪些函数可以选做 hook 点,在 Linux 机器上,可以通过 less /proc/kallsyms 查看。

使用 eBPF 时,内核代码 kprobe 的书写范例如下:

SEC( "kprobe/vfs_write")

intkprobe_vfs_write(struct pt_regs *regs)

{

structfile* file

file= ( structfile*) PT_REGS_PARM1( regs);

// ...

}

其中 pt_regs 的结构体如下:

structpt_regs{

/*

* C ABI says these regs are callee-preserved. They aren't saved on kernel entry

* unless syscall needs a complete, fully filled "struct pt_regs".

展开全文

*/

unsignedlongr15;

unsignedlongr14;

unsignedlongr13;

unsignedlongr12;

unsignedlongbp;

unsignedlongbx;

/* These regs are callee-clobbered. Always saved on kernel entry. */

unsignedlongr11;

unsignedlongr10;

unsignedlongr9;

unsignedlongr8;

unsignedlongax;

unsignedlongcx;

unsignedlongdx;

unsignedlongsi;

unsignedlongdi;

/*

* On syscall entry, this is syscall#. On CPU exception, this is error code.

* On hw interrupt, it's IRQ number:

*/

unsignedlongorig_ax;

/* Return frame for iretq */

unsignedlongip;

unsignedlongcs;

unsignedlongflags;

unsignedlongsp;

unsignedlongss;

/* top of stack page */

};

通常来说,我们要获取的参数,均可通过诸如 PT_REGS_PARM1 这样的宏来拿到,宏定义如下:

# definePT_REGS_PARM1(x) ((x)->di)

# definePT_REGS_PARM2(x) ((x)->si)

# definePT_REGS_PARM3(x) ((x)->dx)

# definePT_REGS_PARM4(x) ((x)->cx)

# definePT_REGS_PARM5(x) ((x)->r8)

可以看到,上述的宏只能获取 5 个参数;但是在最近的一个项目中,就遇到了如何获取超过 5 个参数的难题,这也是本文的由来,如果你也有类似的困惑,本文也许是为你准备的。

如何获取插桩函数中第 6 个参数

上述的 5 个宏已经可以覆盖大多数的获取小于 5 个参数的需求,不知道大家有没有想过,使用 eBPF 时如果获取的参数个数大于 5 个怎么办呢?

如下的内核函数 __get_user_pages (幸运的是,该 static 函数并未被优化掉):

staticlong__get_user_pages(struct task_struct *tsk, struct mm_struct *mm,

unsignedlongstart, unsignedlongnr_pages,

unsignedintgup_flags, struct page **pages,

struct vm_area_struct **vmas, int*nonblocking)

在希望对这个函数进行 hook 的时候犯了难,该函数总共有 8 个参数,如果想拿到最后 3 个参数,该如何操作呢?

且看 BCC 是如何操作的。

BCC 代码中明确表明:只支持寄存器参数。那什么是寄存器参数呢?其实就是内核函数调用约定中的前 6 个参数要通过寄存器传递,只支持这前六个寄存器参数。

constexprintMAX_CALLING_CONV_REGS = 6;

constchar*calling_conv_regs_x86[] = {

"di", "si", "dx", "cx", "r8", "r9"

};

boolBTypeVisitor::VisitFunctionDecl(FunctionDecl *D){

if(D->param_size > MAX_CALLING_CONV_REGS + 1) {

error(GET_BEGINLOC(D->getParamDecl(MAX_CALLING_CONV_REGS + 1)),

"too many arguments, bcc only supports in-register parameters");

returnfalse;

}

}

BCC 中使用如下的代码对用户写的 BPF text 进行 rewrite ,覆盖的参数刚好是前 6 个参数,分别保存于 di, si, dx, cx, r8, r9 寄存器:

constchar*calling_conv_regs_x86[] = {

"di", "si", "dx", "cx", "r8", "r9"

};

voidBTypeVisitor::genParamDirectAssign(FunctionDecl *D, string& preamble,

constchar**calling_conv_regs) {

for( size_tidx = 0; idx < fn_args_.size; idx++) {

ParmVarDecl *arg = fn_args_[idx];

if(idx >= 1) {

// Move the args into a preamble section where the same params are

// declared and initialized from pt_regs.

// Todo: this init should be done only when the program requests it.

stringtext = rewriter_.getRewrittenText(expansionRange(arg->getSourceRange));

arg->addAttr(UnavailableAttr::CreateImplicit(C, "ptregs"));

size_td = idx - 1;

constchar*reg = calling_conv_regs[d];

preamble += " "+ text + " = ("+ arg->getType.getAsString + ")"+

fn_args_[ 0]->getName.str + "->"+ string(reg) + ";";

}

}

}

看到这里,大家应该明白,之所以能使用 BCC 提供的如此简便的 python 接口( 内核函数前面加上前缀 kprobe__,第一个参数永远是 struct pt_regs * ,然后需要使用几个内核参数就填写几个 )来做一些监控工作,是因为 BCC 在幕后做了大量的 rewirte 工作,respect!

int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk) {

[...]

}

之前总是由于 eBPF 给的限制(按照 eBPF 的 calling convention,只有 5 个参数可以传递),以为更多的参数是无法获取的。实际上可以回忆下,实际上按照 amd64 的调用约定,最多是可以通过寄存器传递 6 个参数的。

这么看下来,获取第 6 个参数的方案其实也是很简单,手动添加如下的宏即可:

#define PT_REGS_PARM6(x) ((x)->r9)

插桩函数超过 6 个参数怎么办

amd64 的调用约定同样规定了,超过 6 个的参数,都会在栈上传递,具体可以参考 regs_get_kernel_argument

那么如果参数超过 6 个,处理方案呼之欲出:从栈上获取。

regs_get_kernel_argument 该函数在新版本的内核中才有,实现如下:

staticinlineunsignedlongregs_get_kernel_argument(struct pt_regs *regs,

unsignedintn)

{

staticconstunsignedintargument_offs[] = {

#ifdef __i386__

offsetof(struct pt_regs, ax),

offsetof(struct pt_regs, dx),

offsetof(struct pt_regs, cx),

#define NR_REG_ARGUMENTS 3

# else

offsetof(struct pt_regs, di),

offsetof(struct pt_regs, si),

offsetof(struct pt_regs, dx),

offsetof(struct pt_regs, cx),

offsetof(struct pt_regs, r8),

offsetof(struct pt_regs, r9),

#define NR_REG_ARGUMENTS 6

#endif

};

if(n >= NR_REG_ARGUMENTS) {

n -= NR_REG_ARGUMENTS - 1;

returnregs_get_kernel_stack_nth(regs, n);

} else

returnregs_get_register(regs, argument_offs[n]);

}

从上述的代码可以看到,常用的前 6 个参数,确实是在寄存器中获取,分别是 di, si, dx, cx, r8, r9 ,这也印证了我们之前的想法,且和 BCC 中的行为是一致的。

从 regs_get_kernel_argument 中也可以看到,从第 7 个参数开始,便开始从栈上获取了,关键函数为: regs_get_kernel_stack_nth ,这个函数在 4.18 内核中也有,如下:

staticinlineunsignedlongregs_get_kernel_stack_nth(struct pt_regs *regs, unsignedintn)

{

unsignedlong*addr = ( unsignedlong*)kernel_stack_pointer(regs);

addr += n;

if(regs_within_kernel_stack(regs, ( unsignedlong)addr))

return*addr;

else

return0;

}

// 等价于bpf提供的帮助宏 #define PT_REGS_SP(x) ((x)->sp)

staticinlineunsignedlongkernel_stack_pointer(struct pt_regs *regs)

{

returnregs->sp;

}

regs_get_kernel_stack_nth 是标准的栈上操作获取,只不过内核提供了一些地址合法性的检查,不考虑这些的话,在 eBPF 中其实可以一步到位;使用如下函数,便能返回栈上的第 n 个参数(从 1 开始)。

static__always_inline unsignedlongregs_get_kernel_stack_nth(struct pt_regs *regs,

unsignedintn)

{

unsignedlong*addr;

unsignedlongval;

addr = ( unsignedlong*)PT_REGS_SP(x) + n;

if(addr) {

bpf_probe_read(&val, sizeof(val), addr);

returnval;

}

return0;

}

捎带提一句,在 amd64 中,eBPF calling ABI 使用了 R1-R5 来传递参数,且做了如下的寄存器映射约定,方便 jit 转换为 native code,提高效率。

R0 – rax returnvalue from function

R1 – rdi 1st argument

R2 – rsi 2nd argument

R3 – rdx 3rd argument

R4 – rcx 4th argument

R5 – r8 5th argument

R6 – rbx callee saved

R7 - r13 callee saved

R8 - r14 callee saved

R9 - r15 callee saved

R10 – rbp frame pointer

而 R0 - R10,是 bpf 虚拟机的内部的特殊标识符(函数调用等地方使用),如果 jit 可用,bpf code 会被翻译为 native code 。

Linux Amd64 调用约定demo 验证

那 Amd64 的 ABI 是如何操作的呢?可以使用如下的代码进行验证:

腾讯云服务器怎么登录linux(腾讯云服务器怎么登录亚马逊账号)

# cat myfunc.c

intutilfunc( inta, intb, intc)

{

intxx = a + 2;

intyy = b + 3;

intzz = c + 4;

intsum = xx + yy + zz;

returnxx * yy * zz + sum;

}

intmyfunc( inta, intb, intc, intd,

inte, intf, intg, inth)

{

intxx = (a + b) * c * d * e * (f + (g * h));

intzz = utilfunc(xx, 2, xx % 2);

returnzz + 20;

}

intmain{

myfunc( 1, 2, 3, 4, 5, 6, 7, 8);

return0;

}

gcc -c -g myfunc.c 进行编译汇编得到 myfunc.o

eBPF 字节码反汇编

objdump -S myfunc.o 反汇编,查看调用约定是不是和我们从教科书上看到的一致

先看 main 函数,可以简单地得出如下结论:

超过 6 个参数的函数调用,需要用到栈传递

前 6 个参数,分别使用 di、si、dx、cx、r8、r9

使用栈传递的参数,是从右向左压栈,此例中先压入 8,再压入 7

int main {

c4: f3 0f 1e fa endbr64

c8: 55 push %rbp

c9: 48 89 e5 mov %rsp,%rbp

myfunc(1, 2, 3, 4, 5, 6, 7, 8);

cc: 6a 08 push $0x8 #栈上传递参数

ce: 6a 07 push $0x7 #栈上传递参数

d0: 41 b9 06 00 00 00 mov $0x6,%r9d #如下是寄存器传递参数

d6: 41 b8 05 00 00 00 mov $0x5,%r8d

dc: b9 04 00 00 00 mov $0x4,%ecx

e1: ba 03 00 00 00 mov $0x3,%edx

e6: be 02 00 00 00 mov $0x2,%esi

eb: bf 01 00 00 00 mov $0x1,%edi #第1个参数,寄存器传递

f0: e8 00 00 00 00 call f5 <main+0x31>

f5: 48 83 c4 10 add $0x10,%rsp

return 0;

f9: b8 00 00 00 00 mov $0x0,%eax

}

fe: c9 leave

ff: c3 ret

用户空间程序调用

再看被 main 调用的 myfunc 函数的反汇编:

和 main 函数的调用参数排列一致,参数 1-6 是寄存器传递,参数 7-8 是栈上传递

int myfunc(int a, int b, int c, int d,

int e, int f, int g, int h)

{

50: f3 0f 1e fa endbr64

54: 55 push %rbp

55: 48 89 e5 mov %rsp,%rbp

58: 48 83 ec 28 sub $0x28,%rsp

5c: 89 7d ec mov %edi,-0x14(%rbp) #第1个参数,从edi中复制到栈上

5f: 89 75 e8 mov %esi,-0x18(%rbp)

62: 89 55 e4 mov %edx,-0x1c(%rbp)

65: 89 4d e0 mov %ecx,-0x20(%rbp)

68: 44 89 45 dc mov %r8d,-0x24(%rbp)

6c: 44 89 4d d8 mov %r9d,-0x28(%rbp) #第6个参数

int xx = (a + b) * c * d * e * (f + (g * h));

70: 8b 55 ec mov -0x14(%rbp),%edx

73: 8b 45 e8 mov -0x18(%rbp),%eax

76: 01 d0 add %edx,%eax # a+b

78: 0f af 45 e4 imul -0x1c(%rbp),%eax #(a+b) * c

7c: 0f af 45 e0 imul -0x20(%rbp),%eax #(a+b) * c * d

80: 0f af 45 dc imul -0x24(%rbp),%eax #(a+b) * c * d * e

84: 89 c2 mov %eax,%edx

86: 8b 45 10 mov 0x10(%rbp),%eax #栈上第1个参数 g

89: 0f af 45 18 imul 0x18(%rbp),%eax # g*h

8d: 89 c1 mov %eax,%ecx

8f: 8b 45 d8 mov -0x28(%rbp),%eax # 参数f

92: 01 c8 add %ecx,%eax # (g*h) + f

94: 0f af c2 imul %edx,%eax # ((g*h) + f) * (a+b) * c * d * e

97: 89 45 f8 mov %eax,-0x8(%rbp)

寄存器堆栈状态

main 函数调用 myfunc,做完 prolog 操作后,栈和寄存器的状态如下:

main-myfunc 实战 kprobe 获取 6 个以上参数

说了那么多,到底是不是符合预期呢?尝试使用 BCC 验证下,为了方便验证,换了一个比较容易从用户态验证的 hook 点: inotify_handle_event

如果在 BCC 中使用了超过 6 个的参数,则会报错,比如函数 kprobe__inotify_handle_event 的原型如下:

intkprobe__inotify_handle_event(struct pt_regs *ctx, struct fsnotify_group *group,

struct inode *inode,

u32 mask, constvoid*data, intdata_type,

constunsignedchar*file_name, u32 cookie,

struct fsnotify_iter_info *iter_info)

当在 BCC 中做超过 6 个参数的获取时,得到如下错误:

error: too many arguments, bcc only supports in-register parameters

如果只使用前 6 个寄存器的参数,如下代码即可:

#!/usr/bin/python

frombcc importBPF

# load BPF program

b = BPF(text= """

#include <uapi/linux/ptrace.h>

int kprobe__inotify_handle_event(struct pt_regs *ctx, struct fsnotify_group *group,

struct inode *inode,

u32 mask, const void *data, int data_type,

const unsigned char *file_name)

{

char comm[128];

int pid = bpf_get_current_pid_tgid >> 32;

bpf_get_current_comm(comm, sizeof(comm));

bpf_trace_printk("pid is:%d, comm is: %s\\n", pid, comm);

bpf_trace_printk("file is: %s\\n", file_name);

return 0;

}

""" )

b.trace_print

但是我们可以使用如下的方式,拿到剩下的参数(以 cookie 为例):

unsignedlongcookie;

bpf_probe_read(&cookie, 8, ( unsignedlong*)PT_REGS_SP(ctx) + 1);

完整代码如下:

#!/usr/bin/python

frombcc importBPF

# load BPF program

b = BPF(text= """

#include <uapi/linux/ptrace.h>

int kprobe__inotify_handle_event(struct pt_regs *ctx, struct fsnotify_group *group,

struct inode *inode,

u32 mask, const void *data, int data_type,

const unsigned char *file_name)

{

char comm[128];

unsigned long cookie;

int pid = bpf_get_current_pid_tgid >> 32;

bpf_probe_read(&cookie, 8, (unsigned long*)PT_REGS_SP(ctx) + 1);

bpf_get_current_comm(comm, sizeof(comm));

bpf_trace_printk("pid is:%d, comm is: %s\\n", pid, comm);

bpf_trace_printk("cookie is %d, file is: %s\\n", cookie, file_name);

return 0;

}

""" )

b.trace_print

shell 1 运行 BCC 代码

./get-stack-arg.py

shell 2 使用 inotify-tools 验证

[root@rmed ~]# inotifywait -m ./

shell 3 做如下的操作

[root@rmed ~]# mv testFileA testFileB

shell 1 如下输出

shell 1 output

shell 2 如下输出

shell 2 output

为了保持严谨性,可以使用 https://man7.org/linux/man-pages/man7/inotify.7.html[1] 中的代码进行验证,

主要是做了如下改动,增加对 IN_MOVED_FROM | IN_MOVED_TO 的监控:

diff --git a/inotify.c b/inotify.c

index 08fa55a..7116a9a 100644

--- a/inotify.c

+++ b/inotify.c

@@ -61,6 +61,10 @@

if (event->mask & IN_CLOSE_WRITE)

printf("IN_CLOSE_WRITE: ");

+ if (event->mask & IN_MOVED_FROM)

+ printf("IN_MOVED_FROM: ");

+ if (event->mask & IN_MOVED_TO)

+ printf("IN_MOVED_TO: ");

/* Print the name of the watched directory. */

for (int i = 1; i < argc; ++i) {

@@ -75,6 +79,8 @@

if (event->len)

printf("%s", event->name);

+ if (event->cookie)

+ printf("cookie: %d", event->cookie);

/* Print type of filesystem object. */

if (event->mask & IN_ISDIR)

@@ -123,7 +129,7 @@

for (i = 1; i < argc; i++) {

wd[i] = inotify_add_watch(fd, argv[i],

- IN_OPEN | IN_CLOSE);

+ IN_OPEN | IN_CLOSE | IN_MOVED_FROM | IN_MOVED_TO);

if (wd[i] == -1) {

fprintf(stderr, "Cannot watch '%s': %s\n",

argv[i], strerror(errno));

@@ -182,3 +188,4 @@

同样的,使用 BCC 和自己编译的 inotify 工具验证。

BCC 输出:

ebpf-bcc-inotify

inotify 输出:

ebpf-inotify

输出符合预期,剩下的第 8 个参数,大家可自行修改代码验证。

祝大家玩得开心。

参考文献:

https://eyakubovich.github.io/2022-04-19-ebpf-kprobe-params/

https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64

https://man7.org/linux/man-pages/man7/inotify.7.html

https://github.com/iovisor/bcc/blob/master/src/cc/frontends/clang/b_frontend_action.cc

https://elixir.bootlin.com/linux/latest/source/arch/x86/include/asm/ptrace.h#L346

扫描二维码推送至手机访问。

版权声明:本文由我的模板布,如需转载请注明出处。


本文链接:http://390c.top/post/31459.html

分享给朋友:

“腾讯云服务器怎么登录linux(腾讯云服务器怎么登录亚马逊账号)” 的相关文章

制作小程序的软件排名(设计小程序的软件)

制作小程序的软件排名(设计小程序的软件)

本篇文章给大家谈谈制作小程序的软件排名,以及设计小程序的软件对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 本文目录一览: 1、深圳微信小程序制作哪家好 2、杭州小程序开发公司哪家好...

新手唱歌直播选择哪个平台好(唱歌直播在哪个平台好)

新手唱歌直播选择哪个平台好(唱歌直播在哪个平台好)

今天给各位分享新手唱歌直播选择哪个平台好的知识,其中也会对唱歌直播在哪个平台好进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!本文目录一览: 1、想做唱歌类型的主播,应该去哪个平...

直播app免费下载安卓(直播app安卓最新版下载大全)

直播app免费下载安卓(直播app安卓最新版下载大全)

今天给各位分享直播app免费下载安卓的知识,其中也会对直播app安卓最新版下载大全进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!本文目录一览: 1、电视直播的软件有哪些?...

淘手游网络游戏交易平台账号被找回了怎么办(淘手游我把账号找回了)

淘手游网络游戏交易平台账号被找回了怎么办(淘手游我把账号找回了)

本篇文章给大家谈谈淘手游网络游戏交易平台账号被找回了怎么办,以及淘手游我把账号找回了对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 本文目录一览: 1、买的游戏账号被找回怎么办 2、淘手游...

使命召唤手游账号怎么换绑(使命召唤手游怎么换绑手机号)

使命召唤手游账号怎么换绑(使命召唤手游怎么换绑手机号)

今天给各位分享使命召唤手游账号怎么换绑的知识,其中也会对使命召唤手游怎么换绑手机号进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!本文目录一览: 1、使命召唤账号怎么解绑微信...

cctv5在线直播(无插件)(cctv5在线直播无插件360)

cctv5在线直播(无插件)(cctv5在线直播无插件360)

本篇文章给大家谈谈cctv5在线直播(无插件),以及cctv5在线直播无插件360对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 本文目录一览: 1、cctv5在线直播无插件 2、cctv...