很久以前接触到日系手机内核里面各式各样的LSM的时候,就对LSM背后到底是什么,产生了一些奇怪的兴趣。
最近有些得空,读了一些关于LSM,钩子,内核相关的东西,再搭上以前对FJSEC这些OEM LSM的逆向的经验。
尝试胡乱弄个LSM出来看看?也许有用也也许没用,又成为了一个慢慢挖坑再填坑的作品。
为啥叫NGLSM?NG是我很早用的一个网名,全称是Nango,不懂当初为什么会想到这个名字,但是既然想到了,那么就这样叫吧。

// 2023.06.08-00.00.01/FIRST RELEASE
// 2023.06.11-00.01.01/ADD PROTECT OF SETSOCKOPT/GETSOCKOPT
// 2025.03.16-00.02.01/ADD PROTECT OF /DATA/LOCAL/TMP FOLDER
#include <linux/lsm_hooks.h>
#include <linux/binfmts.h>
#include <linux/path.h>
#include <linux/cred.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/mount.h>
#include <linux/bprm.h>
#include <linux/socket.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/string.h>

#define STATUS_FILE          "/data/security/lsm/status"
#define STATUS_PERMISSIVE    "PERMISSIVE"
#define STATUS_ENFORCING     "ENFORCING"
#define STATUS_MAX           100
#define TASK_FLAG_RESTRICTED (1 << 30)

static inline bool is_restricted_task(struct task_struct *task)
{
    return (task->flags & TASK_FLAG_RESTRICTED) != 0;
}


static int nglsm_status(void)
{
    struct file *f;
    char buf[STATUS_MAX];
    loff_t pos = 0;
    int status = -1;
    ssize_t ret;

    f = filp_open(STATUS_FILE, O_RDONLY, 0);
    if (IS_ERR(f)) {
        pr_err("[NGLSM] STATUS FILE CAN'T ACCESSABLE: %s\n", STATUS_FILE);
        return status;
    }

    ret = kernel_read(f, buf, STATUS_MAX - 1, &pos);
    if (ret < 0) {
        pr_err("[NGLSM] STATUS FILE CAN'T ACCESSABLE: %s\n", STATUS_FILE);
        filp_close(f, NULL);
        return status;
    }
    buf[ret] = '\0';

    if (strncmp(buf, STATUS_PERMISSIVE, strlen(STATUS_PERMISSIVE)) == 0)
        status = 0;
    else if (strncmp(buf, STATUS_ENFORCING, strlen(STATUS_ENFORCING)) == 0)
        status = 1;

    filp_close(f, NULL);
    return status;
}

static int nglsm_bprm_check(struct linux_binprm *bprm)
{
    const char *path = bprm->filename;
    size_t len = strlen("/data/local/tmp/");
    if (path && (strncmp(path, "/data/local/tmp/", len) == 0)) {
        pr_info("[NGLSM] TEMPORARY EXECUTABLE FILE RESTRICTED: %s\n", path);
        current->flags |= TASK_FLAG_RESTRICTED;
    }
    return 0;
}

static int nglsm_check(void)
{
    int global_status = nglsm_status();
    if (global_status == 0 && !is_restricted_task(current))
        return 0;
    return -EPERM;
}

static int nglsm_sb_mount(const char *dev_name, struct path *path,
                            const char *type, unsigned long flags, void *data)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] MOUNT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int nglsm_sb_umount(struct vfsmount *mnt, int flags)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] UMOUNT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int nglsm_sb_pivotroot(struct path *old_path, struct path *new_path)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] PIVOTROOT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int nglsm_socket_getsockopt(struct socket *sock, int level, int optname,
                                     char __user *optval, int __user *optlen)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] GETSOCKOPT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int nglsm_socket_setsockopt(struct socket *sock, int level, int optname,
                                     char __user *optval, unsigned int optlen)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] SETSOCKOPT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int lsm_status_proc_show(struct seq_file *m, void *v)
{
    int status = nglsm_status();
    switch (status) {
    case 0:
        seq_printf(m, "PERMISSIVE\n");
        break;
    case 1:
        seq_printf(m, "ENFORCING\n");
        break;
    default:
        seq_printf(m, "UNKNOWN\n");
    }
    return 0;
}

static int lsm_status_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, lsm_status_proc_show, NULL);
}

static const struct file_operations lsm_status_proc_fops = {
    .owner   = THIS_MODULE,
    .open    = lsm_status_proc_open,
    .read    = seq_read,
    .llseek  = seq_lseek,
    .release = single_release,
};

static struct security_hook_list nglsm_hooks[] __lsm_ro_after_init = {
    LSM_HOOK_INIT(bprm_check, nglsm_bprm_check),
    LSM_HOOK_INIT(sb_mount, nglsm_sb_mount),
    LSM_HOOK_INIT(sb_umount, nglsm_sb_umount),
    LSM_HOOK_INIT(sb_pivotroot, nglsm_sb_pivotroot),
    LSM_HOOK_INIT(socket_getsockopt, nglsm_socket_getsockopt),
    LSM_HOOK_INIT(socket_setsockopt, nglsm_socket_setsockopt),
};

static int __init nglsm_init(void)
{
    if (!proc_create("lsm_status", 0444, NULL, &lsm_status_proc_fops)) {
        pr_err("[NGLSM] PROC ACCESS FAILED\n");
        return -ENOMEM;
    }

    security_add_hooks(nglsm_hooks, ARRAY_SIZE(nglsm_hooks), "nglsm");
    pr_info("[NGLSM] NGLSM HOOKED\n");
    return 0;
}

security_initcall(nglsm_init);

本文及其附件均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

添加新评论