巧用 c 库 preload hook 技术定位 systemd 服务启动异常问题

问题描述

在系统启动的时候,/var/run、/run 目录的创建顺序影响某 systemd 服务的正常启动,需要确定是谁创建了这两个目录以定位问题。

由于此问题在 systemd 服务启动过程中触发,不能使用 auditd 等工具监测,于是想到通过 hook c 库中目录创建与软链接创建相关函数来记录日志以找到犯罪服务,在本文中记录一下。

hook demo 源码

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h> /* Definition of AT_* constants */
#include <execinfo.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>

#define MAXPATH 1024

int apply_filter(const char* func, const char *pathname, int ret)
{
    char buf[MAXPATH];
    int pid = getpid();

    memset(buf, 0x0, sizeof(buf));
    sprintf(buf, "/filter_path.sh %s %s %d %d", func, pathname, pid, ret);
    system(buf);
}

int mkdir(const char *pathname, mode_t mode)
{
    int (*old_mkdir)(const char *, mode_t);
    int ret;

    old_mkdir = dlsym(RTLD_NEXT, "mkdir");
    ret = old_mkdir(pathname, mode);
    
    apply_filter(__FUNCTION__, pathname, ret);
    return ret;
}

int mkdirat(int dirfd, const char *pathname, mode_t mode)
{
    int (*old_mkdirat)(int, const char *, mode_t);
    int ret;
    
    old_mkdirat = dlsym(RTLD_NEXT, "mkdirat");
    ret = old_mkdirat(dirfd, pathname, mode);
    apply_filter(__FUNCTION__, pathname, ret);
    
    return ret;
}

int symlink(const char *target, const char *linkpath) {
        int (*old_symlink)(const char*, const char*);
	int ret;
	
        old_symlink = dlsym(RTLD_NEXT, "symlink");
		
	ret = old_symlink(target, linkpath);
	apply_filter(__FUNCTION__, target, ret);
	return ret;
}

int symlinkat(const char *target, int newdirfd, const char *linkpath)
{
	int (*old_symlinkat)(const char*, int, const char*);
	int ret;

	old_symlinkat = dlsym(RTLD_NEXT, "symlinkat");
	ret = old_symlinkat(target, newdirfd, linkpath);
	apply_filter(__FUNCTION__, target, ret);
	return ret;
}

上述代码 hook 了 mkdir 与 symlink 相关 c 库函数,主要逻辑如下:

  1. 在 hook 函数中先调用 dlsym 获取目标函数的地址
  2. 调用目标函数并获取返回值
  3. 插入一个 apply_filter 函数调用,此函数中调用用户编写的脚本并传入必要的参数信息
  4. 函数正常返回

在 c 语言中调用一个脚本,避免对源码不断的修改、编译以及二进制文件替换操作。

编译命令:

gcc -fPIC -shared hook_mkdir.c -o mkdir.so -ldl

部署方法:

拷贝到 /lib64 中,然后执行如下命令:echo /lib64/mkdir.so > /etc/ld.so.preload

过滤脚本内容示例

#!/bin/bash

FUNC=$1
PATHNAME=$2
pid=$3
ret=$4

program=$(readlink /proc/$pid/exe 2>/dev/null)
cmdline=$(cat /proc/$pid/cmdline 2>/dev/null | sed 's/\x0/ /g')
TIME=$(date)

echo "$TIME:pid:$pid $program execute $FUNC with argument $cmdline to create $PATHNAME with return value $ret" >> /tmp/mkdir.log

对于从 cmdline 文件中获取的字符串,由于其使用 ‘\0’ 分割,需要替换为空格以正常显示,sed 替换命令中使用的字符为 ‘\x0’ 而非 ‘\0’。

将上述脚本保存为 /filter_path.sh,使用前执行 chmod +x 给脚本添加可执行权限。

注意事项

在更新 preload hook so 时,先从 /etc/ld.so.preload 中移除项目,然后更新,直接更新可能存在问题。

如何判段是否生效?

使用 LD_DEBUG=”symbols” 环境变量执行 mkdir,有如下输出信息:

# LD_DEBUG="symbols" mkdir test 2>&1 | grep -A 2 'symbol=mkdir'
    179361:	symbol=mkdir;  lookup in file=mkdir [0]
    179361:	symbol=mkdir;  lookup in file=/lib64/mkdir.so [0]
    179361:	symbol=dlsym;  lookup in file=mkdir [0]
    179361:	symbol=dlsym;  lookup in file=/lib64/mkdir.so [0]
--
    179361:	symbol=mkdir;  lookup in file=/lib/x86_64-linux-gnu/libselinux.so.1 [0]
    179361:	symbol=mkdir;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
    179361:	symbol=apply_filter;  lookup in file=mkdir [0]
    179361:	symbol=apply_filter;  lookup in file=/lib64/mkdir.so [0]

第一次从 mkdir.so 中找到了 mkdir 符号,然后调用 dlsym 获取真实的 mkdir 符号位置,在 libc.so.6 中找到,有类似输出表明配置正常。

示例 log

/tmp# touch mkdir.log
/tmp# mkdir -p ./var/run; ln -s var/run run
/tmp# cat ./mkdir.log 
Sun 16 Oct 2022 04:42:19 PM CST:pid:179414 /usr/bin/mkdir execute mkdir with argument mkdir -p ./var run  to create var with return value -1
Sun 16 Oct 2022 04:42:19 PM CST:pid:179414 /usr/bin/mkdir execute mkdir with argument mkdir -p ./var/run  to create run with return value 0
Sun 16 Oct 2022 04:42:19 PM CST:pid:179431 /usr/bin/ln execute symlinkat with argument ln -s var/run run  to create var/run with return value 0

日志记录正常,测试通过。

其它案例

以 hook main 函数为例探讨 PRELOAD 的原理
关闭 stdout 引发的灾难

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值