Linux LSM(Linux Security Modules) Hook Technology


目錄

0. 引言
1. Linux Security Module Framework Introduction
2. LSM Sourcecode Analysis
3. LSMs Hook Engine:基於LSM Hook進行元數據的監控獲取
4. LSM編程示例
5. Linux LSM stacking

 

0. 引言

從最佳實踐的角度來說,在Linux Kernel中進行安全審計、Access Control(訪問控制)最好的方式就是使用Linux提供的原生的框架機制,例如

1. Kprobe: Linux提供的原生的調試機制(Debug),允許向任意的內核函數注冊Hook回調通知點,當執行到Hooked函數的時候,會觸發我們注冊的kprobe_handle
2. LSM(Linux Security Modules): Linux提供的原生的Hook框架,本質上來說,LSM是直接串行的插入到了Linux Kernel的代碼路徑中,是Linux本身對外提供了一套審計機制(Hook框架)

LSM框架的設計初衷是為了在Linux Kernel中實現一個MAC(Mandatory Access Control) Hook框架,LSM已經作為Linux Kernel的一部分隨着內核一起發布了,使用框架的好處就在於,安全人員可以直接基於LSM框架進行上層審計模塊的開發,專注於業務邏輯,而不需要關注底層的內核差異兼 容性和穩定性,這是相對於sys_call hook方式最大的優勢

 

1. Linux Security Module Framework Introduction

The Linux Security Module (LSM) framework provides a mechanism for various security checks to be hooked by new kernel extensions. The name "module" is a bit of a misnomer since these extensions are not actually loadable kernel modules. Instead, they are selectable at build-time via CONFIG_DEFAULT_SECURITY and can be overridden at boot-time via the "security=..." kernel command line argument, in the case where multiple LSMs were built into a given kernel.

The primary users of the LSM interface are Mandatory Access Control (MAC) extensions which provide a comprehensive security policy. Examples include

1. SELinux
2. Smack
3. Tomoyo
4. AppArmor
5. other extensions can be built using the LSM to provide specific changes to system operation 
//Linux LSM是Linux下的一個框架標准,並不是一個特定的技術,類似於WINDOWS中的NDIS(Network Driver Interface Specification)概念,而SELINUX是在遵循針這一框架的基礎上實現的具體Hook技術,SELINUX和LSM的關系是具體實現和框架標准的關系

0x1: Design

LSM inserts "hooks" (upcalls to the module) at every point in the kernel where a user-level system call is about to result in access to an important internal kernel object such as inodes and task control blocks.

The project is narrowly scoped to solve the problem of access control to avoid imposing a large and complex change patch on the mainstream kernel. It is not intended as a general "hook" or "upcall" mechanism, nor does it support Operating system-level virtualization.

0x2: LSM Principle

The Linux Security Modules (LSM) project has developed a lightweight, general purpose, access control framework for the mainstream Linux kernel that enables many different access control models to be implemented as loadable kernel modules.

A number of existing enhanced access control implementations, including

1. Linux Capabilities Model 
2. Security-Enhanced Linux (SELinux) Model
3. Domain and Type Enforcement (DTE) Model

使用LSM Hook框架進行內核安全審計、元數據捕獲,安全人員只需要按照既定的調用規范編寫LKM模塊,並加載進Linux內核,而不需要對system call lookup表進行任何修改

LSM allows modules to mediate access to kernel objects by placing hooks in the kernel code just ahead of the access, as shown in picture above

0x3: LSM Implementation Principle

The LSM kernel patch modifies the kernel in five primary ways

1. it adds opaque security fields to certain kernel data structures
    1) task_struct: Task(Process)
    2) linux_binprm: Program    
    3) super_block: Filesystem
    4) inode: Pipe、File,、or Socket
    5) file: Open File
    6) sk_buff: Network Buffer(Packet)
    7) net_device: Network Device
    8) kern_ipc_perm: Semaphore、Shared Memory Segment、Message Queue
    9) msg_msg: Individual Message
/*
The setting of these security fields and the management of the associated security data is handled by the security modules. LSM merely provides the fields and a set of calls to security hooks that can be implemented by the module to manage the security fields as desired
LSM以串行地方式插入到Linux Kernel中,在進行串行Hook的同時,同時自身也需要維護一套數據結構來保存自身的Hook信息,這也是Hook Engine的通用思路
*/

2. the patch inserts calls to security hook functions at various points within the kernel code 
LSM在Linux Kernel中插入了兩種"security hooks function call"
    1) LSM安全策略判斷
    2) lSM自身數據結構維護
這些hook function 指針都保存在一個全局結構體: "security-ops"中(struct security_operations)


3. the patch adds a generic security system call
LSM provides a general security system call that allows security modules to implement new calls for security-aware applications

4. the patch provides functions to allow kernel modules to register and unregister themselves as security modules
When a security module is loaded, it must register itself with the LSM framework by calling the "register-security"
function. This function sets the global "security_ops table" to refer to the module’s hook function pointers(即初始狀態下,"security-ops"是指向NULL的指針), causing the kernel to call into the security module for access control decisions. The "register-security" function will not overwrite a previously loaded module.
Once a security module is loaded, it becomes a policy decision whether it will allow itself to be unloaded.

5. the patch moves most of the capabilities logic into an optional security module,
The Linux kernel currently provides support for a subset of POSIX.1e capabilities.

0x4: LSM Hook Method

1. Task Hooks
LSM provides a set of task hooks that enable security modules to manage process security information and to control process operations

2. Program Loading Hooks
LSM provides a set of programloading hooks that are called at critical points during the processing of an execve operation.
"linux_binprm"
3. IPC Hooks Security modules can manage security information and perform access control for System V IPC using the LSM IPC hooks. LSM inserts a hook into the existing ipcperms function so that a security module can perform a check for each existing Linux IPC permission check 4. Filesystem Hooks For file operations, three sets of hooks were defined: 1) filesystem hooks 2) inode hooks 3) file hooks 5. Network Hooks Application layer access to networking is mediated using a set of socket hooks. These hooks, which include interposition of all socket system calls, provide coarse mediation coverage of all socket-based protocols. Since active user sockets have an associated inode structure, a separate security field was not added to the socket structure or to the lower-level sock structure. As the socket hooks allow general mediation of network traffic in relation to processes, LSM significantly expands the kernel’s network access control framework (which is already handled at the network layer by Netfilter)(LSM對網絡的訪問控制和Netfilter保持兼容). For example, the sock rcv skb hook allows an inbound packet to be mediated in terms of its destination application, prior to being queued at the associated userspace socket. 6. Other Hooks LSM provides two additional sets of hooks: 1) module hooks Module hooks can be used to control the kernel operations that create, initialize, and delete kernel modules. 2) a set of top-level system hooks System hooks can be used to control system operations, such as setting the system hostname, accessing I/O ports, and configuring process accounting.

可以看出,LSM在向上層提供了一個統一的Hook的接口的同時,在下層進行了大量的兼容,對於Linux系統的各個子系統來說,要實現對它們的Hook,差異性是不言而喻的,LSM的Hook Point就像觸手一樣遍布在Linux Kernel的各個角落

0x5: 基於LSM框架的具體實現

LSM provides only the mechanism to enforce enhanced access control policies. Thus, it is the LSM modules that implement a specific policy and are critical in proving the functionality of the framework. 

1. SELinux
http://en.wikipedia.org/wiki/Security-Enhanced_Linux
http://selinuxproject.org/page/NB_LSM

A Linux implementation of the Flask flexible access control architecture and an example security server that supports Type Enforcement, Role-Based Access Control, and optionally MultiLevel Security.
SELinux was originally implemented as a kernel patch  and was then reimplemented as a security module that uses LSM.
SELinux can be used to confine processes to least privilege, to protect the integrity and confidentiality of processes and data, and to support application security needs.  

2. DTE Linux 
An implementation of Domain and Type Enforcement developed for Linux 
Like SELinux, DTE Linux was originally implemented as a kernel patch and was then adapted to LSM. With this module loaded, types can be assigned to objects and domains to processes. The DTE policy restricts access between domains and from domains to types. The DTE Linux project also provided useful input into the design and implementation of LSM.

3. LSM port of Openwall kernel patch

4. POSIX.1e capabilities

Relevant Link:

http://lxr.free-electrons.com/source/Documentation/security/LSM.txt
https://www.usenix.org/legacy/event/sec02/wright.html
http://en.wikipedia.org/wiki/Linux_Security_Modules
https://www.usenix.org/legacy/event/sec02/wright.html
https://www.usenix.org/legacy/event/sec02/full_papers/wright/wright.pdf
https://www.kernel.org/doc/ols/2002/ols2002-pages-604-617.pdf
http://se7so.blogspot.com/2012/04/linux-security-modules-framework-lsm.html
http://www.kroah.com/linux/talks/ols_2002_lsm_paper/lsm.pdf

 

2. LSM Sourcecode Analysis

0x1: LSM的數據結構定義

/source/security/security.c

...
static struct security_operations *security_ops;
static struct security_operations default_security_ops = {
    .name   = "default",
};
...
/**
* security_init - initializes the security framework
*
* This should be called early in the kernel initialization sequence.
*/
int __init security_init(void)
{
    printk(KERN_INFO "Security Framework initialized\n");

    security_fixup_ops(&default_security_ops);
    security_ops = &default_security_ops;
    do_security_initcalls();

    return 0;
}

..
static void __init do_security_initcalls(void)
{
    initcall_t *call;
    call = __security_initcall_start;
    while (call < __security_initcall_end) 
    {
        (*call) ();
        call++;
    }
}

內核代碼聲明了全局靜態結構體變量: security_ops,並將結構體初始化為默認Hook結構體(default_security_ops)

LSM中最重要的數據結構,保存所有LSM Hook Point Function的: struct security_operations的結構定義如下

/source/include/linux/security.h

struct security_operations 
{
    //A string that acts as a unique identifier for the LSM
    char name[SECURITY_NAME_MAX + 1];

    int (*ptrace_access_check) (struct task_struct *child, unsigned int mode);
    int (*ptrace_traceme) (struct task_struct *parent);
    int (*capget) (struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted);
    int (*capset) (struct cred *new, const struct cred *old, const kernel_cap_t *effective, const kernel_cap_t *inheritable, const kernel_cap_t *permitted);
    int (*capable) (const struct cred *cred, struct user_namespace *ns, int cap, int audit);
    int (*quotactl) (int cmds, int type, int id, struct super_block *sb);
    int (*quota_on) (struct dentry *dentry);
    int (*syslog) (int type);
    int (*settime) (const struct timespec *ts, const struct timezone *tz);
    int (*vm_enough_memory) (struct mm_struct *mm, long pages);

    /*
    1. Security hooks for program execution operations
    程序裝載鈎子,LSM(Linux Security Modules)提供了一系列程序裝載鈎子,用在一個execve()操作執行過程的關鍵點上
        1) "linux_binprm"結構中的安全域允許安全模塊維護程序裝載過程中的安全信息
        2) LSM提供了鈎子用於允許安全模塊在裝載程序前初始化安全信息和執行訪問控制
        3) LSM提供了鈎子允許模塊在新程序成功裝載后更新任務的安全信息
        4) LSM提供了鈎子用來控制程序執行過程中的狀態繼承,例如確認打開的文件描述符(不是可信啟動)
    */
    //Save security information in the bprm->security field, typically based on information about the bprm->file, for later use by the apply_creds hook
    int (*bprm_set_creds) (struct linux_binprm *bprm);
    //This hook mediates the point when a search for a binary handler will begin. This hook may be called multiple times during a single execve; and in each pass set_creds is called first.
    int (*bprm_check_security) (struct linux_binprm *bprm);
    int (*bprm_secureexec) (struct linux_binprm *bprm);
    //Prepare to install the new security attributes of a process being transformed by an execve operation, based on the old credentials pointed to by @current->cred and the information set in @bprm->cred by the bprm_set_creds hook
    void (*bprm_committing_creds) (struct linux_binprm *bprm);
    //Tidy up after the installation of the new security attributes of a process being transformed by an execve operation.
    void (*bprm_committed_creds) (struct linux_binprm *bprm);

    /*
    2. Security hooks for filesystem operations.
    */
    //Allocate and attach a security structure to the sb->s_security field.
    int (*sb_alloc_security) (struct super_block *sb);
    //Deallocate and clear the sb->s_security field.
    void (*sb_free_security) (struct super_block *sb);
    //Allow mount option data to be copied prior to parsing by the filesystem, so that the security module can extract security-specific mount options cleanly (a filesystem may modify the data e.g. with strsep()).
    int (*sb_copy_data) (char *orig, char *copy);
    //Extracts security system specific mount options and verifies no changes are being made to those options.
    int (*sb_remount) (struct super_block *sb, void *data);
    int (*sb_kern_mount) (struct super_block *sb, int flags, void *data);
    int (*sb_show_options) (struct seq_file *m, struct super_block *sb);
    //Check permission before obtaining filesystem statistics for the @mnt mountpoint.
    int (*sb_statfs) (struct dentry *dentry);
    //Check permission before an object specified by @dev_name is mounted on the mount point named by @nd.
    int (*sb_mount) (const char *dev_name, struct path *path, const char *type, unsigned long flags, void *data);
    //Check permission before the @mnt file system is unmounted.
    int (*sb_umount) (struct vfsmount *mnt, int flags);
    //Check permission before pivoting the root filesystem.
    int (*sb_pivotroot) (struct path *old_path, struct path *new_path);
    //Set the security relevant mount options used for a superblock
    int (*sb_set_mnt_opts) (struct super_block *sb, struct security_mnt_opts *opts, unsigned long kern_flags, unsigned long *set_kern_flags);
    //Copy all security options from a given superblock to another
    int (*sb_clone_mnt_opts) (const struct super_block *oldsb, struct super_block *newsb);
    //Parse a string of security data filling in the opts structure
    int (*sb_parse_opts_str) (char *options, struct security_mnt_opts *opts);
    //Compute a context for a dentry as the inode is not yet available since NFSv4 has no label backed by an EA anyway.
    int (*dentry_init_security) (struct dentry *dentry, int mode, struct qstr *name, void **ctx, u32 *ctxlen);


#ifdef CONFIG_SECURITY_PATH
    int (*path_unlink) (struct path *dir, struct dentry *dentry);
    int (*path_mkdir) (struct path *dir, struct dentry *dentry, umode_t mode);
    int (*path_rmdir) (struct path *dir, struct dentry *dentry);
    int (*path_mknod) (struct path *dir, struct dentry *dentry, umode_t mode,
               unsigned int dev);
    int (*path_truncate) (struct path *path);
    int (*path_symlink) (struct path *dir, struct dentry *dentry,
                 const char *old_name);
    int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
              struct dentry *new_dentry);
    int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
                struct path *new_dir, struct dentry *new_dentry);
    int (*path_chmod) (struct path *path, umode_t mode);
    int (*path_chown) (struct path *path, kuid_t uid, kgid_t gid);
    int (*path_chroot) (struct path *path);
#endif

    /*
    3. Security hooks for inode operations.
    */
    //Allocate and attach a security structure to @inode->i_security.
    int (*inode_alloc_security) (struct inode *inode);
    void (*inode_free_security) (struct inode *inode);
    //Obtain the security attribute name suffix and value to set on a newly created inode and set up the incore security field for the new inode.
    int (*inode_init_security) (struct inode *inode, struct inode *dir, const struct qstr *qstr, const char **name, void **value, size_t *len);
    int (*inode_create) (struct inode *dir, struct dentry *dentry, umode_t mode);
    int (*inode_link) (struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry);
    int (*inode_unlink) (struct inode *dir, struct dentry *dentry);
    int (*inode_symlink) (struct inode *dir, struct dentry *dentry, const char *old_name);
    int (*inode_mkdir) (struct inode *dir, struct dentry *dentry, umode_t mode);
    int (*inode_rmdir) (struct inode *dir, struct dentry *dentry);
    int (*inode_mknod) (struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev);
    int (*inode_rename) (struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry);
    int (*inode_readlink) (struct dentry *dentry);
    int (*inode_follow_link) (struct dentry *dentry, struct nameidata *nd);
    int (*inode_permission) (struct inode *inode, int mask);
    int (*inode_setattr)    (struct dentry *dentry, struct iattr *attr);
    int (*inode_getattr) (struct vfsmount *mnt, struct dentry *dentry);
    int (*inode_setxattr) (struct dentry *dentry, const char *name, const void *value, size_t size, int flags);
    void (*inode_post_setxattr) (struct dentry *dentry, const char *name, const void *value, size_t size, int flags);
    int (*inode_getxattr) (struct dentry *dentry, const char *name);
    int (*inode_listxattr) (struct dentry *dentry);
    int (*inode_removexattr) (struct dentry *dentry, const char *name);
    int (*inode_need_killpriv) (struct dentry *dentry);
    int (*inode_killpriv) (struct dentry *dentry);
    int (*inode_getsecurity) (const struct inode *inode, const char *name, void **buffer, bool alloc);
    int (*inode_setsecurity) (struct inode *inode, const char *name, const void *value, size_t size, int flags);
    int (*inode_listsecurity) (struct inode *inode, char *buffer, size_t buffer_size);
    void (*inode_getsecid) (const struct inode *inode, u32 *secid);


    /*
    4. Security hooks for file operations
    */
    //Check file permissions before accessing an open file.  This hook is called by various operations that read or write files.
    int (*file_permission) (struct file *file, int mask);
    //Allocate and attach a security structure to the file->f_security field
    int (*file_alloc_security) (struct file *file);
    //Deallocate and free any security structures stored in file->f_security.
    void (*file_free_security) (struct file *file);
    //Check permission for an ioctl operation on @file
    int (*file_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);
    //Check permissions for a mmap operation at @addr.
    int (*mmap_addr) (unsigned long addr);
    //Check permissions for a mmap operation.
    int (*mmap_file) (struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags);
    //Check permissions before changing memory access permissions.
    int (*file_mprotect) (struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot);
    //Check permission before performing file locking operations.
    int (*file_lock) (struct file *file, unsigned int cmd);
    //Check permission before allowing the file operation specified by @cmd from being performed on the file @file. 
    int (*file_fcntl) (struct file *file, unsigned int cmd, unsigned long arg);
    int (*file_set_fowner) (struct file *file);
    int (*file_send_sigiotask) (struct task_struct *tsk, struct fown_struct *fown, int sig);
    int (*file_receive) (struct file *file);
    //Save open-time permission checking state for later use upon file_permission, and recheck access if anything has changed since inode_permission
    int (*file_open) (struct file *file, const struct cred *cred);

    /*
    5. Security hooks for task operations.
    LSM(Linux Security Modules)提供了一系列的任務鈎子使得安全模塊可以管理進程的安全信息並且控制進程的操作
        1) LSM可以使用"task_struct"結構中的安全域來維護進程安全信息
        2) 任務鈎子提供了控制進程間通信的鈎子,例如kill()
        3) LSM提供了控制對當前進程進行特權操作的鈎子,例如setuid()
        4) LSM提供了對資源管理操作進行細粒度控制的鈎子,例如setrlimit()和nice()
    */
    //Check permission before creating a child process
    int (*task_create) (unsigned long clone_flags);
    //@task task being freed Handle release of task-related resources. (Note that this can be called from interrupt context.)
    void (*task_free) (struct task_struct *task);
    //Only allocate sufficient memory and attach to @cred such that cred_transfer() will not get ENOMEM.
    int (*cred_alloc_blank) (struct cred *cred, gfp_t gfp);
    //eallocate and clear the cred->security field in a set of credentials.
    void (*cred_free) (struct cred *cred);
    //Prepare a new set of credentials by copying the data from the old set.
    int (*cred_prepare)(struct cred *new, const struct cred *old, gfp_t gfp);
    //Transfer data from original creds to new creds
    void (*cred_transfer)(struct cred *new, const struct cred *old);
    //Set the credentials for a kernel service to act as (subjective context).
    int (*kernel_act_as)(struct cred *new, u32 secid);
    int (*kernel_create_files_as)(struct cred *new, struct inode *inode);
    int (*kernel_module_request)(char *kmod_name);
    int (*kernel_module_from_file)(struct file *file);
    //Update the module's state after setting one or more of the user identity attributes of the current process.
    int (*task_fix_setuid) (struct cred *new, const struct cred *old, int flags);
    int (*task_setpgid) (struct task_struct *p, pid_t pgid);
    int (*task_getpgid) (struct task_struct *p);
    int (*task_getsid) (struct task_struct *p);
    void (*task_getsecid) (struct task_struct *p, u32 *secid);
    int (*task_setnice) (struct task_struct *p, int nice);
    int (*task_setioprio) (struct task_struct *p, int ioprio);
    int (*task_getioprio) (struct task_struct *p);
    int (*task_setrlimit) (struct task_struct *p, unsigned int resource, struct rlimit *new_rlim);
    int (*task_setscheduler) (struct task_struct *p);
    int (*task_getscheduler) (struct task_struct *p);
    int (*task_movememory) (struct task_struct *p);
    int (*task_kill) (struct task_struct *p, struct siginfo *info, int sig, u32 secid);
    int (*task_wait) (struct task_struct *p);
    //Check permission before performing a process control operation on the current process.
    int (*task_prctl) (int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
    void (*task_to_inode) (struct task_struct *p, struct inode *inode);

    int (*ipc_permission) (struct kern_ipc_perm *ipcp, short flag);
    void (*ipc_getsecid) (struct kern_ipc_perm *ipcp, u32 *secid);

    int (*msg_msg_alloc_security) (struct msg_msg *msg);
    void (*msg_msg_free_security) (struct msg_msg *msg);

    int (*msg_queue_alloc_security) (struct msg_queue *msq);
    void (*msg_queue_free_security) (struct msg_queue *msq);
    int (*msg_queue_associate) (struct msg_queue *msq, int msqflg);
    int (*msg_queue_msgctl) (struct msg_queue *msq, int cmd);
    int (*msg_queue_msgsnd) (struct msg_queue *msq,
                 struct msg_msg *msg, int msqflg);
    int (*msg_queue_msgrcv) (struct msg_queue *msq,
                 struct msg_msg *msg,
                 struct task_struct *target,
                 long type, int mode);

    int (*shm_alloc_security) (struct shmid_kernel *shp);
    void (*shm_free_security) (struct shmid_kernel *shp);
    int (*shm_associate) (struct shmid_kernel *shp, int shmflg);
    int (*shm_shmctl) (struct shmid_kernel *shp, int cmd);
    int (*shm_shmat) (struct shmid_kernel *shp,
              char __user *shmaddr, int shmflg);

    int (*sem_alloc_security) (struct sem_array *sma);
    void (*sem_free_security) (struct sem_array *sma);
    int (*sem_associate) (struct sem_array *sma, int semflg);
    int (*sem_semctl) (struct sem_array *sma, int cmd);
    int (*sem_semop) (struct sem_array *sma,
              struct sembuf *sops, unsigned nsops, int alter); 

    /*
    6. Security hooks for Netlink messaging.
    */
    //Save security information for a netlink message so that permission checking can be performed when the message is processed.
    int (*netlink_send) (struct sock *sk, struct sk_buff *skb);

    void (*d_instantiate) (struct dentry *dentry, struct inode *inode);

    int (*getprocattr) (struct task_struct *p, char *name, char **value);
    int (*setprocattr) (struct task_struct *p, char *name, void *value, size_t size);
    int (*ismaclabel) (const char *name);
    int (*secid_to_secctx) (u32 secid, char **secdata, u32 *seclen);
    int (*secctx_to_secid) (const char *secdata, u32 seclen, u32 *secid);
    void (*release_secctx) (char *secdata, u32 seclen);

    int (*inode_notifysecctx)(struct inode *inode, void *ctx, u32 ctxlen);
    int (*inode_setsecctx)(struct dentry *dentry, void *ctx, u32 ctxlen);
    int (*inode_getsecctx)(struct inode *inode, void **ctx, u32 *ctxlen);

#ifdef CONFIG_SECURITY_NETWORK
    /*
    7. Security hooks for Unix domain networking.
    */
    //Check permissions before establishing a Unix domain stream connection between @sock and @other.
    int (*unix_stream_connect) (struct sock *sock, struct sock *other, struct sock *newsk);
    //Check permissions before connecting or sending datagrams from @sock to @other.
    int (*unix_may_send) (struct socket *sock, struct socket *other);

    int (*socket_create) (int family, int type, int protocol, int kern);
    int (*socket_post_create) (struct socket *sock, int family, int type, int protocol, int kern);
    int (*socket_bind) (struct socket *sock, struct sockaddr *address, int addrlen);
    //Check permission before socket protocol layer connect operation attempts to connect socket @sock to a remote address,
    int (*socket_connect) (struct socket *sock, struct sockaddr *address, int addrlen);
    int (*socket_listen) (struct socket *sock, int backlog);
    int (*socket_accept) (struct socket *sock, struct socket *newsock);
    int (*socket_sendmsg) (struct socket *sock, struct msghdr *msg, int size);
    int (*socket_recvmsg) (struct socket *sock, struct msghdr *msg, int size, int flags);
    int (*socket_getsockname) (struct socket *sock);
    int (*socket_getpeername) (struct socket *sock);
    int (*socket_getsockopt) (struct socket *sock, int level, int optname);
    int (*socket_setsockopt) (struct socket *sock, int level, int optname);
    int (*socket_shutdown) (struct socket *sock, int how);
    int (*socket_sock_rcv_skb) (struct sock *sk, struct sk_buff *skb);
    int (*socket_getpeersec_stream) (struct socket *sock, char __user *optval, int __user *optlen, unsigned len);
    int (*socket_getpeersec_dgram) (struct socket *sock, struct sk_buff *skb, u32 *secid);
    int (*sk_alloc_security) (struct sock *sk, int family, gfp_t priority);
    void (*sk_free_security) (struct sock *sk);
    void (*sk_clone_security) (const struct sock *sk, struct sock *newsk);
    void (*sk_getsecid) (struct sock *sk, u32 *secid);
    void (*sock_graft) (struct sock *sk, struct socket *parent);
    int (*inet_conn_request) (struct sock *sk, struct sk_buff *skb, struct request_sock *req);
    void (*inet_csk_clone) (struct sock *newsk, const struct request_sock *req);
    void (*inet_conn_established) (struct sock *sk, struct sk_buff *skb);
    int (*secmark_relabel_packet) (u32 secid);
    void (*secmark_refcount_inc) (void);
    void (*secmark_refcount_dec) (void);
    void (*req_classify_flow) (const struct request_sock *req, struct flowi *fl);
    int (*tun_dev_alloc_security) (void **security);
    void (*tun_dev_free_security) (void *security);
    int (*tun_dev_create) (void);
    int (*tun_dev_attach_queue) (void *security);
    int (*tun_dev_attach) (struct sock *sk, void *security);
    int (*tun_dev_open) (void *security);
    void (*skb_owned_by) (struct sk_buff *skb, struct sock *sk);
#endif    /* CONFIG_SECURITY_NETWORK */

#ifdef CONFIG_SECURITY_NETWORK_XFRM
    /*
    8. Security hooks for XFRM operations.
    */
    int (*xfrm_policy_alloc_security) (struct xfrm_sec_ctx **ctxp, struct xfrm_user_sec_ctx *sec_ctx, gfp_t gfp);
    int (*xfrm_policy_clone_security) (struct xfrm_sec_ctx *old_ctx, struct xfrm_sec_ctx **new_ctx);
    void (*xfrm_policy_free_security) (struct xfrm_sec_ctx *ctx);
    int (*xfrm_policy_delete_security) (struct xfrm_sec_ctx *ctx);
    int (*xfrm_state_alloc) (struct xfrm_state *x, struct xfrm_user_sec_ctx *sec_ctx);
    int (*xfrm_state_alloc_acquire) (struct xfrm_state *x, struct xfrm_sec_ctx *polsec, u32 secid);
    void (*xfrm_state_free_security) (struct xfrm_state *x);
    int (*xfrm_state_delete_security) (struct xfrm_state *x);
    int (*xfrm_policy_lookup) (struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir);
    int (*xfrm_state_pol_flow_match) (struct xfrm_state *x, struct xfrm_policy *xp, const struct flowi *fl);
    int (*xfrm_decode_session) (struct sk_buff *skb, u32 *secid, int ckall);
#endif    /* CONFIG_SECURITY_NETWORK_XFRM */

    /* key management security hooks */
#ifdef CONFIG_KEYS
    /*
    9. Security hooks affecting all Key Management operations
    */
    int (*key_alloc) (struct key *key, const struct cred *cred, unsigned long flags);
    void (*key_free) (struct key *key);
    int (*key_permission) (key_ref_t key_ref, const struct cred *cred, key_perm_t perm);
    int (*key_getsecurity)(struct key *key, char **_buffer);
#endif    /* CONFIG_KEYS */

#ifdef CONFIG_AUDIT
    int (*audit_rule_init) (u32 field, u32 op, char *rulestr, void **lsmrule);
    int (*audit_rule_known) (struct audit_krule *krule);
    int (*audit_rule_match) (u32 secid, u32 field, u32 op, void *lsmrule,
                 struct audit_context *actx);
    void (*audit_rule_free) (void *lsmrule);
#endif /* CONFIG_AUDIT */
};

0x2: LSM在Linux Kernel函數的Hook點:security_file_mmap

LSM以串行插入的方式集成到了Linux Kernel Function中的正常函數中,以"security_file_mmap"為例

source/security/security.c

int security_file_mmap(
    struct file *file, 
    unsigned long reqprot, 
    unsigned long prot, 
    unsigned long flags, 
    unsigned long addr, 
    unsigned long addr_only)
{
    int ret;

    //LSM Hook Function嵌入到了原始的內核函數的執行流程中
    ret = security_ops->file_mmap(file, reqprot, prot, flags, addr, addr_only);
    if (ret)
        return ret;
    return ima_file_mmap(file, prot);
}

Relevant Link:

http://lxr.free-electrons.com/source/security/security.c
http://wenku.baidu.com/view/df89fe235901020207409c49.html

 

3. LSMs Hook Engine:基於LSM Hook進行元數據的監控獲取

1. sys_fork
2. sys_execvesys_connect
4. sys_init_module

0x1: sys_fork

Linux下進程創建的第一步就是執行fork,用該系統調用時,子進程復制父進程的全部資源。由於要復制父進程進程描述符給子進程(進程描述的結構很大),這一過程會消耗很大的性能開銷。linux采用了"寫時復制技術"(copy on write,COW),使子進程先共享父進程的物理頁,只有子進程進行寫操作時,再復制對應的物理頁,避免了無用的復制開銷,提高了系統的性能

source/arch/x86/kernel/process.c

int sys_fork(struct pt_regs *regs)
{
    return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
}

在do_fork中的LSM Hook Point如下

1. retval = security_task_create(clone_flags);
在copy_process中被調用
int security_task_create(unsigned long clone_flags)
{
    return security_ops->task_create(clone_flags);
}

在這個LSM Hook中,當前新創建的進程的命令行參數並沒有傳入進來

0x2: sys_execve hook

當用戶態發起一個sys_execve()的系統調用,在內核態需要經過一系列的調用流程,我們逐一學習它的LSM Hook點

1. do_execve

/source/fs/exec.c 

/*
 * sys_execve() executes a new program.
 */
int do_execve(char * filename, char __user *__user *argv, char __user *__user *envp, struct pt_regs * regs)
{
    //將運行可執行文件時所需的信息組織到一起
    struct linux_binprm *bprm;
    struct file *file;
    struct files_struct *displaced;
    bool clear_in_exec;
    int retval;

    retval = unshare_files(&displaced);
    if (retval)
        goto out_ret;

    retval = -ENOMEM;
    bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
    if (!bprm)
        goto out_files;

    /*
    1. LSM Hook Point 1: 
    retval = prepare_bprm_creds(bprm);
    prepare_exec_creds->security_prepare_creds
    int security_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp)
    {
        return security_ops->cred_prepare(new, old, gfp);
    }
    */
    retval = prepare_bprm_creds(bprm);
    if (retval)
        goto out_free;

    retval = check_unsafe_exec(bprm);
    if (retval < 0)
        goto out_free;
    clear_in_exec = retval;
    current->in_execve = 1;
    //找到並打開給定的可執行程序文件,open_exec()返回file結構指針,代表着讀入可執行文件的上下文
    file = open_exec(filename);
    //強制轉換
    retval = PTR_ERR(file);
    //判斷open_exec()返回的是否是無效指針
    if (IS_ERR(file))
        goto out_unmark;
     
    sched_exec();

    //要執行的文件
    bprm->file = file;
    //要執行的文件的名字
    bprm->filename = filename;
    bprm->interp = filename;

    retval = bprm_mm_init(bprm);
    if (retval)
        goto out_file;

    //統計命令汗參數的個數
    bprm->argc = count(argv, MAX_ARG_STRINGS);
    if ((retval = bprm->argc) < 0)
        goto out;
    //統計環境變量參數的個數
    bprm->envc = count(envp, MAX_ARG_STRINGS);
    if ((retval = bprm->envc) < 0)
        goto out;
    
    //可執行文件中讀入開頭的128個字節到linux_binprm結構brmp中的緩沖區,用於之后內核根據這頭128字節判斷應該調用哪個解析引擎來處理當前文件
    retval = prepare_binprm(bprm);
    if (retval < 0)
        goto out;

    //文件名拷貝到新分配的頁面中
    retval = copy_strings_kernel(1, &bprm->filename, bprm);
    if (retval < 0)
        goto out;

    bprm->exec = bprm->p;
    //將環境變量拷貝到新分配的頁面中
    retval = copy_strings(bprm->envc, envp, bprm);
    if (retval < 0)
        goto out;

    //將命令行參數拷貝到新分配的頁面中
    retval = copy_strings(bprm->argc, argv, bprm);
    if (retval < 0)
        goto out;

    //所有准備工作已經完成,所有必要的信息都已經搜集到了linux_binprm結構中的bprm中
    current->flags &= ~PF_KTHREAD;
    
    //調用search_binary_handler()裝入並運行目標程序,根據讀入數據結構linux_binprm內的二進制文件128字節頭中的關鍵字,決定調用哪種加載函數
    /*
    2. LSM Hook Point 2: 
    retval = security_bprm_check(bprm); 
    int security_bprm_check(struct linux_binprm *bprm) 
    { 
        return security_ops->bprm_check_security(bprm); 
    } 
    */
    retval = search_binary_handler(bprm, regs);
    if (retval < 0)
        goto out;

    /* execve succeeded */
    current->fs->in_exec = 0;
    current->in_execve = 0;
    acct_update_integrals(current);    

    free_bprm(bprm);
    if (displaced)
        put_files_struct(displaced);
    return retval;

out:
    if (bprm->mm) {
        acct_arg_size(bprm, 0);
        mmput(bprm->mm);
    }

out_file:
    //發生錯誤,返回inode,並釋放資源
    if (bprm->file) 
    {
        //調用allow_write_access()防止其他進程在讀入可執行文件期間通過內存映射改變它的內容
        allow_write_access(bprm->file);
        //遞減file文件中的共享計數
        fput(bprm->file);
    }

out_unmark:
    if (clear_in_exec)
        current->fs->in_exec = 0;
    current->in_execve = 0;

out_free:
    free_bprm(bprm);

out_files:
    if (displaced)
        reset_files_struct(displaced);
out_ret:
    return retval;
}

在do_execve中(search_binary_handler也包含在了do_execve中),包含了2個LSM Hook Point

1. prepare_bprm_creds:cred初始化過程的LSM Hook點
2. search_binary_handler:根據待執行文件的頭128字節判斷對應的解析處理引擎

對於這兩個點,我們對比一下我們需要獲取的元數據

1. current
2. argv

current(即task_struct)這個時候還未完全初始化完畢,例如"current->cred"還保存的是父進程的信息(因為linux下的所有進程都是通過父進程去"復制"出來的),代碼在"execve_lsm_hook.c"

所以在這個LSM Hook Point上,不能直接獲取current進行使用,是不准確的

另一方面,argv:retval = copy_strings(bprm->envc, envp, bprm);完成了將當前啟動進程的命令行參數復制到內核棧的內存頁中,將頁的偏移地址保存在了"linux_binprm->p"指針中,這個p指針保存的是這個內存頁的偏移,需要將參數從這塊虛擬頁內存中copy出來,如果直接打印printk會導致kernel panic(可以將那行注釋去掉測試)

綜上所述,這兩個Hook點都不能滿足我們進行execve進程執行元數據收集的目的,我們繼續往下尋找

2. load_elf_binary

在do_execve()函數的准備階段,已經從可執行文件頭部讀入128字節存放在bprm的緩沖區中,而且運行所需的參數和環境變量也已收集在bprm中
search_binary_handler()函數就是逐個掃描formats隊列,直到找到一個匹配的可執行文件格式,運行的事就交給它 

1. 如果在這個隊列中沒有找到相應的可執行文件格式,就要根據文件頭部的信息來查找是否有為此種格式設計的可動態安裝的模塊
2. 如果找到對應的可執行文件格式,就把這個模塊安裝進內核,並掛入formats隊列,然后再重新掃描 

在linux_binfmt數據結構中,有三個函數指針

1. load_binary
load_binary就是具體的ELF程序裝載程序,不同的可執行文件其裝載函數也不同
    1) a.out格式的裝載函數為: load_aout_binary()
    2) elf的裝載函數為: load_elf_binary()
2. load_shlib
3. core_dump

我們的Hook對象是ELF可執行程序的執行,因此,我們繼續深入研究load_elf_binary這個函數

...
#ifdef CONFIG_MODULES
        } else {
#define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e))
            if (printable(bprm->buf[0]) &&
                printable(bprm->buf[1]) &&
                printable(bprm->buf[2]) &&
                printable(bprm->buf[3]))
                break; /* -ENOEXEC */
            request_module("binfmt-%04x", *(unsigned short *)(&bprm->buf[2]));
#endif
...

內核會根據bprm結構體中判斷得到的文件格式類型,加載對應的解析函數,對於ELF文件來說就是binfmt_elf.c

source/fs/binfmt_elf.c

在load_elf_binary()函數中,有兩個LSM Hook Point

1. file_permission(interpreter, MAY_READ)
int file_permission(struct file *file, int mask)
{
    return inode_permission(file->f_path.dentry->d_inode, mask);
}

2. install_exec_creds(bprm);
void install_exec_creds(struct linux_binprm *bprm)
{
    security_bprm_committing_creds(bprm);

    commit_creds(bprm->cred);
    bprm->cred = NULL;
    /*
     * cred_guard_mutex must be held at least to this point to prevent
     * ptrace_attach() from altering our determination of the task's
     * credentials; any time after this it may be unlocked.
     */
    security_bprm_committed_creds(bprm);
    mutex_unlock(&current->cred_guard_mutex);
}
..
void security_bprm_committed_creds(struct linux_binprm *bprm)
{
    security_ops->bprm_committed_creds(bprm);
}

在load_elf_binary()函數中,有一行代碼需要重點關注

retval = create_elf_tables(bprm, &loc->elf_ex, load_addr, interp_load_addr);

跟進函數進行分析

.....
p = current->mm->arg_end = current->mm->arg_start;
    while (argc-- > 0) 
    {
        size_t len;
        if (__put_user((elf_addr_t)p, argv++))
        {
            return -EFAULT;
        } 
        len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
        if (!len || len > MAX_ARG_STRLEN)
        {
            return -EINVAL;
        }     
        p += len;
    }
    if (__put_user(0, argv))
    {
        return -EFAULT;
    }     
....

內核代碼在這里將當前進程的參數保存在current->mm->arg_start和current->mm->arg_end之間的內存空間中,這也意味着在調用了create_elf_tables之后,我們就可以在current當中獲取到當前進程的命令行參數了

3. do_mmap

從源代碼中可以看到,在load_elf_binary中的do_mmap之前,調用了create_elf_tables,所以我們一定可以在do_mmap中尋找到一個hook點,在這個hook點當中可以通過current->mm->arg_start和current->mm->arg_end可以拿到進程的參數

\linux-2.6.32.63\include\linux\mm.h

/*
file:表示要映射的文件
addr:虛擬空間中的一個地址,表示從這個地址開始查找一個空閑的虛擬區 
len:要映射的文件部分的長度
prot:這個參數指定對這個虛擬區所包含頁的存取權限。可能的標志有
    1) PROT_READ
    2) PROT_WRITE
    3) PROT_EXEC
    4) PROT_NONE
前三個標志與標志VM_READ、VM_WRITE 及VM_EXEC的意義一樣。PROT_NONE表示進程沒有以上三個存取權限中的任意一個 
Flag:指定映射對象的類型,映射選項和映射頁是否可以共享。它的值可以是一個或者多個以下位的組合體
    1) MAP_FIXED: 使用指定的映射起始地址,如果由start和len參數指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上 
    2) MAP_SHARED: 與其它所有映射這個對象的進程共享映射空間。對共享區的寫入,相當於輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新 
    3) MAP_PRIVATE: 建立一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。這個標志和以上標志是互斥的,只能使用其中一個 
    4) MAP_DENYWRITE: 這個標志被忽略 
    5) MAP_EXECUTABLE: 這個標志被忽略 
    6) MAP_NORESERVE: 不要為這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會得到保證。當交換空間不被保留,同時內存不足,對映射區的修改會引起段違例信號 
    7) MAP_LOCKED: 鎖定映射區的頁面,從而防止頁面被交換出內存 
    8) MAP_GROWSDOWN: 用於堆棧,告訴內核VM系統,映射區可以向下擴展
    9) MAP_ANONYMOUS: 匿名映射,映射區不與任何文件關聯 
    10) MAP_ANON: MAP_ANONYMOUS的別稱,不再被使用。
    11) MAP_FILE: 兼容標志,被忽略
    12) MAP_32BIT: 將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標志只在x86-64平台上得到支持 
    13) MAP_POPULATE: 為文件映射通過預讀的方式准備好頁表。隨后對映射區的訪問不會被頁違例阻塞 
    14) MAP_NONBLOCK: 僅和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在於內存中的頁面建立頁表入口。

off:文件內的偏移量,因為我們並不是一下子全部映射一個文件,可能只是映射文件的一部分,off就表示那部分的起始位置 
*/
static inline unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long offset)
{
    unsigned long ret = -EINVAL;
    if ((offset + PAGE_ALIGN(len)) < offset)
        goto out;
    if (!(offset & ~PAGE_MASK))
        //do_mmap()函數對參數offset的合法性檢查后,就調用do_mmap_pgoff()函數,該函數才是內存映射的主要函數 
        ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);
out:1
    return ret;
}

\linux-2.6.32.63\mm\mmap.c

在do_mmap_pgoff中可以找到的LSM Hook Point如下

1. error = security_file_mmap(NULL, 0, 0, 0, addr, 1);
int security_file_mmap(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags, unsigned long addr, unsigned long addr_only)
{
    return security_ops->file_mmap(file, reqprot, prot, flags, addr, addr_only);
}

在這個LSM Hook點:security_ops->file_mmap中,代碼截獲到的是當前系統所有的文件映射請求,如果需要專門針對ELF可執行文件鏡像的file_mmap,需要針對ELF鏡像頭作判斷,針對ELF可執行文件的file_mmap進行,數據量很大,會對性能產生影響,

Relevant Link:

http://blog.csdn.net/edwardlulinux/article/details/7998433

4. 在通用的LSM Hook點都無法直接獲取參數的情況下如何通過Hacking方式獲取進程當前命令行參數

LSM Hook框架是一種訪問控制的審計型框架,它的主要目的是對進程執行的身份權限、文件名等信息進行權限審計,對進程命令行參數這些元數據的捕獲並不是那么方便,目前遇到的難點情況如下

1. 直接通過最靠近do_execve的LSM Hook接口:bprm_check_security進行Hook,這個時候拿到的arg和current都是不准確的
2. 將Hook點下移,通過do_mmap中的LSM Hook接口:file_mmap進行Hook,參數arg和current是可以取到且也是准確的,但是這個函數的上層調用頻率太高,需要ELF鏡像頭部進行區分,對系統運行性能影響較大

解決這個問題的思路可以朝這個方向思考

1. 將LSM Hook點盡量上移,在執行流支的盡量上游進行Hook,減少冗余調用頻率,提高Hook命中率
2. 因為Hook上移,不可避免帶來的問題就是,原始Linux內核代碼的執行在這個點還未進行,很多變量還沒有賦值、或者設置好
3. 我們的代碼需要針對我們所需的變量,將從當前代碼邏輯位置后面的和這些變量相關的內核代碼拷貝過來,我們在代碼中進行模擬執行,即將原本內核做的事情,我們提前做了
4. 但是需要注意的是,我們模擬執行的代碼不能對正常的內核函數的執行流支產生任何影響,從程序的角度來說,就是只能拷貝模擬執行那種只獲取(get),而不設置(set)的代碼邏輯

基於以上思考,我們有了下面的Hacking思路

1. 選取"security_ops->bprm_committed_creds"作為LSM Hook點
2. "security_ops->bprm_committed_creds"在load_elf_binary的install_exec_creds中被調用,而install_exec_creds在create_elf_tables的前面,這個時候current->mm->arg_end還未被設置成命令行參數的結束位置、current->mm->arg_start已經是命令行參數的起始位置了,我們需要仿照create_elf_tables中對current->mm->arg_end的計算代碼進行模擬執行,得到arg_end,要注意的是,為了保持系統狀態一致性,不能去修改current->mm->arg_end
3. 計算好arg_start、arg_end之后,我們繼續模擬proc_pid_cmdline這個函數從arg_start、arg_end中獲取到當前進程的命令行參數

按照這個思路,我們進行代碼DEMO實現

我們來關注load_elf_binary這個函數中,看到這行代碼:

retval = create_elf_tables(bprm, &loc->elf_ex, load_addr, interp_load_addr);

source/fs/binfmt_elf.c

....
p = current->mm->arg_end = current->mm->arg_start;
while (argc-- > 0) {
    size_t len;
    if (__put_user((elf_addr_t)p, argv++))
        return -EFAULT;
    len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
    if (!len || len > MAX_ARG_STRLEN)
        return -EINVAL;
    p += len;
}
if (__put_user(0, argv))
    return -EFAULT; 

/*
current->mm->arg_start指向的已經是當前進程命令行參數的起始地址
但是未將current->mm->arg_end設置到命令行參數的終止位置(即arg_start + arg_len)
*/
current->mm->arg_end = current->mm->env_start = p;
.....

看這段代碼,我們要明白的是,Linux將當前進程的命令行參數和環境變量參數都統一放在了一整塊內核內存空間中了,而使用arg_start + offset、arg_len來進行區分它們,所以,如果我們想從中取出命令行參數,就需要仿照Linux內核的代碼,對arg_start、arg_end進行計算,並得到len,這樣才能從current->mm指向的內存中拷貝出我們想要的

int fake_cmdline(int argc)
{
    char buffer[2048] = {0};
    unsigned long start ,end, p;

    start = current->mm->arg_start;
    end = current->mm->arg_end;
    p = current->mm->arg_start;

    printk("argc = %d, start =%p, end=%p, p =%p\n", argc, start, end, p);
    
    while (argc-- > 0) 
    {
        size_t len;
        len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
        if (!len || len > MAX_ARG_STRLEN)
            return -EINVAL;
        p += len;
    }

    end = p;
    printk("argc = %d, start =%p, end=%p, p =%p\n", argc, start, end, p);
    proc_cmdline(start, end , buffer);

    return 0;
}

拷貝出來之后,我們需要根據arg_start、arg_end從內核內存地址空間中直接復制出參數字符串,模擬proc_pid_cmdline這個函數的代碼邏輯

\linux-2.6.32.63\fs\proc\base.c

static int proc_pid_cmdline(struct task_struct *task, char * buffer)
{
    int res = 0;
    unsigned int len;
    struct mm_struct *mm = get_task_mm(task);
    if (!mm)
        goto out;
    if (!mm->arg_end)
        goto out_mm;    /* Shh! No looking before we're done */

     len = mm->arg_end - mm->arg_start;
 
    if (len > PAGE_SIZE)
        len = PAGE_SIZE;
 
    res = access_process_vm(task, mm->arg_start, buffer, len, 0);

    // If the nul at the end of args has been overwritten, then
    // assume application is using setproctitle(3).
    if (res > 0 && buffer[res-1] != '\0' && len < PAGE_SIZE) {
        len = strnlen(buffer, res);
        if (len < res) {
            res = len;
        } else {
            len = mm->env_end - mm->env_start;
            if (len > PAGE_SIZE - res)
                len = PAGE_SIZE - res;
            res += access_process_vm(task, mm->env_start, buffer+res, len, 0);
            res = strnlen(buffer, res);
        }
    }
out_mm:
    mmput(mm);
out:
    return res;
}

我們只要完成以上步驟,理論上應該是可以拿到進程的命令行參數的,在進行實際編碼的時候,從最佳實踐的角度來說,我們的代碼中應該注意以下幾點

1. 盡量少的使用拷貝的源代碼,而是采用系統原生提供的函數去實現功能,但是要注意的是,如果這個使用到的系統原生函數是會"改變當前系統運行狀態"(例如設置全局變量、改變全局指針),就絕對不能使用,而只能采用拷貝源代碼自己實現的方式,將其中會對當前系統狀態產生影響的代碼去掉,而只保留那些"取值型""臨時變量計算型"的非核心代碼
2. 我們拷貝的執行代碼一定要特別注意對全局變量、全局指針的操作,不能因為我們的模擬執行,而在當前LSM Hook點影響到了Linux內核的狀態,否則這樣往下執行系統就要處於不一致狀態了
3. Linux下進程都是通過父進程復制出來的,在bash下執行指令是通過bash復制出來的,而current值(即新進程的task_struct)是在整個sys_execve過程中不斷完成初始化填充的,我們需要確認我們的hook點取到的current值已經是新進程的current值

代碼示例見: "4. LSM編程示例->0x3: 針對sys_execve使用LSM Hook方式獲取進程命令行參數"

到目前為止,我們已經解決了針對sys_execve進程執行的LSM Hook,並獲取到了進程啟動時的命令行參數,接下來繼續研究一下如何Hook監控當前系統的網絡連接狀態,即對socket的LSM Hook

5. 借助remove_arg_zero直接從內核棧的內存頁中拷貝獲得進程參數

/source/fs/exec.c

/*
Arguments are '\0' separated strings found at the location bprm->p points to; 
chop off the first by relocating brpm->p to right after the first '\0' encountered.
 */
int remove_arg_zero(struct linux_binprm *bprm)
{
    int ret = 0;
    unsigned long offset;
    char *kaddr;
    struct page *page;

    if (!bprm->argc)
        return 0;

    do 
    {
        offset = bprm->p & ~PAGE_MASK;
        page = get_arg_page(bprm, bprm->p, 0);
        if (!page) 
        {
            ret = -EFAULT;
            goto out;
        }
        kaddr = kmap_atomic(page, KM_USER0);

        for (; offset < PAGE_SIZE && kaddr[offset]; offset++, bprm->p++)
            ;

        kunmap_atomic(kaddr, KM_USER0);
        put_arg_page(page);

        if (offset == PAGE_SIZE)
            free_arg_page(bprm, (bprm->p >> PAGE_SHIFT) - 1);
    } while (offset == PAGE_SIZE);

    bprm->p++;
    bprm->argc--;
    ret = 0;

out:
    return ret;
}
EXPORT_SYMBOL(remove_arg_zero);

remove_arg_zero(bprm)是把argv[0]中的'\0'從bprm->page中清除,我們在bprm_check_security進行Hook的時候,通過調用這個內核函數之后,可以直接從bprm->page中獲取到進程的啟動參數,從而調用copy函數進行拷貝

0x3: sys_connect

使用LSM(Linux Security Modules)框架的一個最大的好處就是Linux Kernel幫助屏蔽了底層的差異性,在Linux 32bit/64bit和socket系統調用的差異性在LSM Hook這一層都不存在了,而取而代之的是統一的Hook接口

....
int (*socket_create) (int family, int type, int protocol, int kern);
int (*socket_post_create) (struct socket *sock, int family, int type, int protocol, int kern);
int (*socket_bind) (struct socket *sock, struct sockaddr *address, int addrlen);
int (*socket_connect) (struct socket *sock, struct sockaddr *address, int addrlen);
int (*socket_listen) (struct socket *sock, int backlog);
int (*socket_accept) (struct socket *sock, struct socket *newsock);
int (*socket_sendmsg) (struct socket *sock, struct msghdr *msg, int size);
int (*socket_recvmsg) (struct socket *sock, struct msghdr *msg, int size, int flags);
....

我們以socket_connect作為研究對象

關於和socket相關的數據結構定義,請參閱另一篇文章

http://www.cnblogs.com/LittleHann/p/3865490.html
//搜索:6. 系統網絡狀態相關的數據結構

在我們這個LSM Hook Engine的業務場景下,我們如果需要針對socket_connect動作進行Hook監控,我們要解決的問題有

1. struct socket、inet_sk((struct socket)->(struc sock))、struct sockaddr這3中結構體中,要從中篩選出准確保存當前socket連接動作的IP、PORT等信息的字段
2. LSM的security_socket_connect()函數會在一次socket連接過程中被多次調用,包括從初始化到最后建立完整連接,我們需要選擇相關type字段,對當前的狀態進行過濾,過濾出TCP Connect完整建立的那次調用

1. LSM Hook點被多次調用的問題

\linux-2.6.32.63\net\socket.c

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen)
{
  ...
/* 調用該BSD socket對應的connect調用 這里的sock是在前面的socket調用的時候就初始化的,對應於TCP的BSD socket的操作符集是inet_stream_ops 要注意的是: 網絡子模塊在內核初始化的時候(linux-2.6.32.63\net\ipv4\af_inet.c->inet_init()中)就注冊了TCP,UDP和RAW3中協議 linux-2.6.32.63\net\ipv4\af_inet.c const struct proto_ops inet_stream_ops = { .family = PF_INET, .owner = THIS_MODULE, .release = inet_release, .bind = inet_bind, .connect = inet_stream_connect, .socketpair = sock_no_socketpair, .accept = inet_accept, .getname = inet_getname, .poll = tcp_poll, .ioctl = inet_ioctl, .listen = inet_listen, .shutdown = inet_shutdown, .setsockopt = sock_common_setsockopt, .getsockopt = sock_common_getsockopt, .sendmsg = tcp_sendmsg, .recvmsg = sock_common_recvmsg, .mmap = sock_no_mmap, .sendpage = tcp_sendpage, .splice_read = tcp_splice_read, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_sock_common_setsockopt, .compat_getsockopt = compat_sock_common_getsockopt, #endif }; 從上面的結構中可以看到connect對應的函數是inet_stream_connect,我們繼續分析inet_stream_connect函數 */ err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, sock->file->f_flags); out_put: ... }

\linux-2.6.32.63\net\ipv4\af_inet.c

int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
    struct sock *sk = sock->sk; 
    ...
    switch (sock->state) {
    default:
        err = -EINVAL;
        goto out;
    /* 
    該BSD socket已連接
    */  
    case SS_CONNECTED:
        err = -EISCONN;
        goto out;
    /* 
    該BSD socket正在連接
    */  
    case SS_CONNECTING:
        err = -EALREADY;
        /* Fall out of switch with err, set for this state */
        break;
    case SS_UNCONNECTED:
        err = -EISCONN; 
        if (sk->sk_state != TCP_CLOSE)
            goto out; 
        err = sk->sk_prot->connect(sk, uaddr, addr_len);
        if (err < 0)
            goto out;
        /* 
        上面的調用完成后,連接並沒有完成
        */  
        sock->state = SS_CONNECTING;

        /* Just entered SS_CONNECTING state; the only
         * difference is that return value in non-blocking
         * case is EINPROGRESS, rather than EALREADY.
         */
        err = -EINPROGRESS;
        break;
    }
    ...
}
EXPORT_SYMBOL(inet_stream_connect);

從源代碼里可以看到,security_socket_connect()在每次sys_connect被調用時都會被調用,而我們的目的只是針對TCP Connect這個動作捕獲一次包含實際數據的socket動作即可,因此需要對security_socket_connect進行過濾,可以用於過濾的字段有

1. struct inet_sock->is_icsk
2. struct socket->state
3. struct socket->type
4. struct sockaddr->sa_family

除此之外,針對socket中的IP、Port等信息的獲取也存在問題,代碼實現請參閱"0x4: 針對sys_connect使用LSM Hook方式獲取socke TCP Connect連接的IP、Port參數"

參考一下TOMOYO Linux的做法,相關知識請參閱另一篇文章

http://www.cnblogs.com/LittleHann/p/4149509.html

我們可以模仿TOMOYO從SOCKET相關數據結構中獲取IP、PORT的做法,在編程中,我們需要注意以下幾點

1. socket_connect的被調用頻率是很高的,需要針對TCP Connect(IPv4、IPv6)進行過濾
    1) struct socket->type
        1.1) case SOCK_STREAM: stream (connection) socket
        1.2) case SOCK_SEQPACKET: sequential packet socket
    //從所有sys_connect動作中過濾出TCP Socket之后,繼續下一層過濾
    2) struct socket->state
        2.1) case SS_UNCONNECTED: 只有在這個狀態下,內核的SOCKET代碼才會調用inet_stream_connect發起TCP連接
    //過濾出TCP connect連接之后,繼續下一層過濾
    3) struct sockaddr->sa_family
        3.1) AF_INET6
        3.2) AF_INET

2. 從網絡的角度理解,SOCKET的建立本質上一次網絡狀態的建立過程,它有很多次的握手交互子過程組成,每一次獲取和設置的信息都似乎不同的
    1) 對於kretprobe來說,它是在整個sys_socketcall調用結束之后才獲得回調觸發的,此時SOCKET的相關信息已經全部填充完成,是可以獲取到參數的
    2) 而對於LSM Hook框架來說,針對sys_connect的LSM Hook點是在函數的入口就被觸發調用的,這個時候,TCP Connect的建立還未完成,有很多的元數據是拿不到的

翻閱sys_connect的內核代碼可以得出以下結論

1. 目的(對端)SOCKET信息(IP、PORT)
在sys_connect傳入的"struct sockaddr *uaddr"中保存,這個變量中保存的是最原始的數據,內核代碼中其他的變量都是從這里開始獲取的
/*
...
struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
daddr = usin->sin_addr.s_addr;
dport = usin->sin_port;
...
*/

2. 源(本端)SOCKET信息(IP、PORT)
嚴格來說,源SOCKET信息應該是在sys_bind,即socket初始化建立的時候由用戶傳入配置的,這些源SOCKET信息在整個socket變量生命周期中都一直有效,所以,我們獲取源SOCKET信息的來源應該是"struct sock *sk"變量
/*
...
struct inet_sock *inet = inet_sk(sk);
inet->sport
inet->saddr
*/

所以,我們的LSMs代碼中對源、目的SOCKET信息的獲取應該分別從"struct sock *sk"、"struct sockaddr *uaddr"中獲取

相關的代碼,請參閱"0x4: 針對sys_connect使用LSM Hook方式獲取socke TCP Connect連接的IP、Port參數"

對於使用LSMs Hook方式獲取SOCKET相關信息,存在一點局限性,即sport無法獲取到

\linux-2.6.32.63\net\ipv4\tcp_ipv4.c

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
    ...
    /* Socket identity is still unknown (sport may be zero).
     * However we set state to SYN-SENT and not releasing socket
     * lock select source port, enter ourselves into the hash tables and
     * complete initialization after this.
    */
    tcp_set_state(sk, TCP_SYN_SENT);
    err = inet_hash_connect(&tcp_death_row, sk);
    if (err)
        goto failure;

    err = ip_route_newports(&rt, IPPROTO_TCP, inet->sport, inet->dport, sk);
    if (err)
        goto failure;

    /* OK, now commit destination to socket.  */
    sk->sk_gso_type = SKB_GSO_TCPV4;
    sk_setup_caps(sk, &rt->u.dst);

    if (!tp->write_seq)
        tp->write_seq = secure_tcp_sequence_number(inet->saddr,
                               inet->daddr,
                               inet->sport,
                               usin->sin_port);
    ...

在內核代碼的注釋中對這一現象作出了解釋,因為TCP連接中的"源端口"是一個系統稀缺資源,內核在收到TCP Connect動作的時候,需要從本地"端口池(/proc/sys/net/ipv4/ip_local_port_range 中配置)"中選取一個可用的端口,並分配給當前的socket,並填充路由表,然后才能發起真正的連接

關於內核中SOCKET端口選取的相關知識,請參閱另一篇文章

http://www.cnblogs.com/LittleHann/p/3875451.html
//搜索:2. connect() API原理

那現在問題來了,我們能不能像之前獲取sys_execve的命令行參數一樣,通過拷貝內核源代碼,模擬這個端口選取的過程,在我們的LSMs代碼中進行模擬執行,從而確定這個sport呢?答案是不行,原理還是一樣的,我們要拷貝模擬執行的操作會對當前Linux內核的狀態進行"改變",會破壞當前Kernel的一致性

1. 會改變當前socket的state狀態
2. 會在hash表中增加entry
3. 這兩個操作涉及到的內核代碼很逗,且如果模擬執行后無法進行逆向回滾

暫時沒有解決方案

Relevant Link:

0x4: sys_init_module

\linux-2.6.32.63\security\security.c

int security_kernel_create_files_as(struct cred *new, struct inode *inode)
{
    return security_ops->kernel_create_files_as(new, inode);
}

int security_kernel_module_request(void)
{
    return security_ops->kernel_module_request();
}

待研究

 

4. LSM編程示例

0x1: 獲取當前執行進程的絕對路徑

execve_lsm_hook.c

#include <linux/security.h>
#include <linux/sysctl.h>
#include <linux/ptrace.h>
#include <linux/prctl.h>
#include <linux/ratelimit.h>
#include <linux/workqueue.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/dcache.h>
#include <linux/path.h>
#include <linux/kprobes.h>
#include <linux/module.h> 

static int (*register_security_p)(struct security_operations *ops);
static int (*unregister_security_p)(struct security_operations * ops);

// do nothing
int kprobe_pre(struct kprobe *p, struct pt_regs *regs)
{
        return 0;
}

static void* acquire_func_by_kprobe(char* func_name)
{
    void *symbol_addr=NULL;
    struct kprobe kp;

    do
    {
       memset(&kp, 0, sizeof(kp));
       kp.symbol_name = func_name;
       kp.pre_handler = kprobe_pre;
       if(register_kprobe(&kp) != 0)
       {
            symbol_addr=(void*)kp.addr;
            break;
       }
       //this is the address of  "symbol_name"
       symbol_addr = (void*)kp.addr;

       //now kprobe is not used any more,so unregister it
       unregister_kprobe(&kp);

    }while(false);

    return symbol_addr;
}

int execve_lsm_hook(struct linux_binprm *bprm)
{
    struct cred *currentCred;

    if (bprm->filename)
    {
        printk("file: %s\n", bprm->filename);
        //printk("file argument: %s\n",bprm->p);
    }
    else
    {
        printk("file exec\n");
    } 
    
    currentCred = current->cred;    
    printk(KERN_INFO "uid = %d\n", currentCred->uid);
    printk(KERN_INFO "gid = %d\n", currentCred->gid);
    printk(KERN_INFO "suid = %d\n", currentCred->suid);
    printk(KERN_INFO "sgid = %d\n", currentCred->sgid);
    printk(KERN_INFO "euid = %d\n", currentCred->euid);
    printk(KERN_INFO "egid = %d\n", currentCred->egid);  

    printk("comm: %s\n", current->comm);

    return 0;
} 

static struct security_operations test_security_ops = 
{
        .name = "test",
        .bprm_check_security = execve_lsm_hook,
};

static __init int test_init(void)
{
    int ret_val = -1;

    //獲取register_security的導出地址
    register_security_p = acquire_func_by_kprobe("register_security");
    printk("register_security:%p\n", register_security_p);   
    if (!register_security_p)
    {
        printk("get register_security error\n");
    }  

    //獲取unregister_security的導出地址
    unregister_security_p = acquire_func_by_kprobe("unregister_security");
    printk("unregister_security:%p\n", unregister_security_p);
    if (!unregister_security_p)
    {
        printk("get unregister_security error\n"); 
    }
     
    //注冊LSMsbas
     //if (register_security_p && unregister_security_p)
     if (register_security_p)
    {
        ret_val = register_security_p(&test_security_ops); 
        printk("register_security:%d\n", ret_val);  
    }  

       return 0;
} 

static void test_cleanup(void)
{
    int ret_val=-1;

    //if (register_security_p && unregister_security_p)
    if (unregister_security_p)
    {
        ret_val = register_security_p(&test_security_ops); 
        printk("register_security:%d\n", ret_val);  
    }

    return;
} 

module_init(test_init);
module_exit(test_cleanup);

//一定要有這個聲明,否則會有unknown module symbol error
MODULE_LICENSE("GPL");

Makefile

obj-m := execve_lsm_hook.o
PWD       := $(shell pwd)

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    rm -rf *.o *~ core .*.cmd *.mod.c ./tmp_version *.ko modules.order  Module.symvers

clean_omit:
    rm -rf *.o *~ core .*.cmd *.mod.c ./tmp_version modules.order  Module.symvers

Relevant Link:

http://hi.baidu.com/wzt85/item/275a9651277866aaacc857d5

0x2: 通過arg_start、arg_end對應的內存獲取進程參數

get_arc_from_current.c

#include <linux/module.h>    // included for all kernel modules
#include <linux/kernel.h>    // included for KERN_INFO
#include <linux/init.h>        // included for __init and __exit macros
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/mm_types.h>
#include <linux//vmalloc.h>
#include <linux/cache.h>
#include <asm/cacheflush.h>
#include <linux/pagemap.h>

 
static void util_get_task_name(struct task_struct* arg_task)
{
    struct task_struct *task = arg_task;
    char *name = NULL;
    char *argv = NULL;
        
    if(task != NULL)
    {
        name = kmalloc(PATH_MAX, GFP_ATOMIC);
        argv = kmalloc(PATH_MAX, GFP_ATOMIC);
        char *buffer = NULL;
        struct mm_struct *mm = get_task_mm(task);
        int len = 0;
        unsigned long address = 0;
  
        if(!name || !mm)
            goto out;

        down_read(&mm->mmap_sem);

        len = mm->arg_end - mm->arg_start;
        address = mm->arg_start;
        buffer = name;

        while(len)
        {
            void *maddr = NULL;
            struct page *page = NULL;
            struct vm_area_struct *vma = NULL;
            int bytes = 0, offset = 0;
                        
            int ret = get_user_pages(task, mm, address, 1, 0, 1, &page, &vma);

            if(ret <= 0)
            {
                bytes = ret;
                goto next;
            }
                                
            bytes = len;
            offset = address & (PAGE_SIZE-1);       

            if (bytes > PAGE_SIZE-offset)
            {
                bytes = PAGE_SIZE - offset;
            } 
            
            maddr = kmap(page);
            copy_from_user_page(vma, page, address, buffer, maddr + offset, bytes);
            kunmap(page);

            page_cache_release(page);

next:
            len -= bytes;
            address += bytes;
            buffer += bytes;
        }
                
        up_read(&mm->mmap_sem);
        mmput(mm);

        printk("name = %s, len = %d\n", name, len);
        //這里需要調整一下參數,因為進程的參數是以空格作為分隔的,需要將以空格分隔的參數串分離出來分別打印
        name += 6;
        printk("arg = %s, len = %d\n", name, len);
    }

out:
    if(name) 
    {
        kfree(name);
    }
    return;
} 

static int __init hello_init(void)
{ 
    struct cred *currentCred;
    currentCred = current->cred;    
    printk(KERN_INFO "uid = %d\n", currentCred->uid);
    printk(KERN_INFO "gid = %d\n", currentCred->gid);
    printk(KERN_INFO "suid = %d\n", currentCred->suid);
    printk(KERN_INFO "sgid = %d\n", currentCred->sgid);
    printk(KERN_INFO "euid = %d\n", currentCred->euid);
    printk(KERN_INFO "egid = %d\n", currentCred->egid);  

    util_get_task_name(current);

    printk(KERN_INFO "Hello world!\n"); 
    return 0;    // Non-zero return means that the module couldn't be loaded.
}
 
static void __exit hello_cleanup(void)
{
    printk(KERN_INFO "Cleaning up module.\n");
}
 
module_init(hello_init);
module_exit(hello_cleanup);
MODULE_LICENSE("GPL");

Makefile

obj-m := get_arc_from_current.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
 
all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules
 
clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

Relevant Link:

http://laokaddk.blog.51cto.com/368606/694037/

0x3: 針對sys_execve使用LSM Hook方式獲取進程命令行參數

get_arc_by_hacking.c

#include <linux/module.h>    // included for all kernel modules
#include <linux/kernel.h>    // included for KERN_INFO
#include <linux/init.h>        // included for __init and __exit macros
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/mm_types.h>
#include <linux//vmalloc.h>
#include <linux/cache.h>
#include <asm/cacheflush.h>
#include <linux/pagemap.h> 

#ifndef MODULE
    #define MODULE
#endif
#ifndef __KERNEL__
    #define __KERNEL__
#endif   
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/kallsyms.h>
#include <linux/syscalls.h>
#include <asm/unistd.h> 
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/kprobes.h>
#include <linux/security.h>
#include <linux/binfmts.h>
#include <asm/current.h>
#include <linux/threads.h> 
#include <linux/mm.h>
#include <asm/page.h>  


static struct security_operations sec_ops; 
static int (*register_security_p)(struct security_operations *ops);
static int (*unregister_security_p)(struct security_operations * ops);
static int (*access_process_vm_p)(struct task_struct *tsk, unsigned long addr, void *buf, int len, int write);

// do nothing
int kprobe_pre(struct kprobe *p, struct pt_regs *regs)
{
    return 0;
}
 
//使用kprobe獲取內核中任意函數的地址
static void* acquire_func_by_kprobe(char* func_name)
{
    void *symbol_addr = NULL;
    struct kprobe kp;

    do
    {
        memset(&kp,0,sizeof(kp));
        kp.symbol_name=func_name;
        kp.pre_handler=kprobe_pre;
        if(register_kprobe(&kp)!=0)
        {
            symbol_addr=(void*)kp.addr; 
            break;
        } 
        symbol_addr=(void*)kp.addr; 
        //now kprobe is not used any more,so unregister it
        unregister_kprobe(&kp); 

    }while(false);

    return symbol_addr;
}  

//根據arg_start、arg_end從內核內存地址空間中直接復制出參數字符串,模擬proc_pid_cmdline這個函數的代碼邏輯
static int proc_cmdline(unsigned long start, unsigned long end, char * buffer)
{
    int res = 0, i;
    unsigned int len;

    struct task_struct * task = current;
    struct mm_struct *mm = get_task_mm(task);
    //struct mm_struct *mm = bprm->mm;
    printk("proc_cmdline, mm = %p, arg_start = %d , arg_end=%d\n", mm, mm->arg_start, mm->arg_end);
    if (!mm)
        goto out;

    if (!mm->arg_end)
    {
        if(end <= start)
            goto out_mm;    
    } 

    len = end - start;
    printk("args len = %d\n", len);

    if (len > PAGE_SIZE)
        len = PAGE_SIZE;
 
    if (access_process_vm_p)
    {
        res = access_process_vm_p(task, mm->arg_start, buffer, len, 0);  
    }
    //res = access_vm(task, mm->arg_start, buffer, len, 0);

    // If the nul at the end of args has been overwritten, then
    // assume application is using setproctitle(3).
    if (res > 0 && buffer[res-1] != '\0' && len < PAGE_SIZE) 
    {
        len = strnlen(buffer, res);
        if (len < res) 
        {
            res = len;
        } 
        else 
        {
            len = mm->env_end - mm->env_start;
            if (len > PAGE_SIZE - res)
            {
                len = PAGE_SIZE - res;
            } 
            if (access_process_vm_p)
            {
                res += access_process_vm_p(task, mm->env_start, buffer+res, len, 0);
            }
            //res += access_vm(task, mm->env_start, buffer+res, len, 0);
            res = strnlen(buffer, res);
        } 
    }

    printk("arg 1 :%s\n", buffer); 
    //命令行參數是以空格結尾的,這里臨時跳過這段空格,真正編碼的時候需要針對空格作"replace"處理,才能正常打印出來
    printk("arg 2 : %s\n", buffer+6);

out_mm:
    mmput(mm);
    printk("out_mm\n");
out:
    printk("out\n");
    return res;
} 


void print_current()
{
    struct cred *currentCred;
    currentCred = current->cred;    
    printk(KERN_INFO "uid = %d\n", currentCred->uid);
    printk(KERN_INFO "gid = %d\n", currentCred->gid);
    //printk(KERN_INFO "suid = %d\n", currentCred->suid);
    //printk(KERN_INFO "sgid = %d\n", currentCred->sgid);
    printk(KERN_INFO "euid = %d\n", currentCred->euid);
    printk(KERN_INFO "egid = %d\n", currentCred->egid);  
    printk(KERN_INFO "tid = %d\n", task_tgid_vnr(current));  
    printk(KERN_INFO "sid = %d\n", task_session_vnr(current));
    printk(KERN_INFO "pid = %d\n", current->pid); 
    printk(KERN_INFO "tgid = %d\n", current->tgid); 

    printk(KERN_INFO "tty name: %s\n", current->signal->tty); 
    printk(KERN_INFO "programe name: %s\n", current->comm); 
    return;   
}

//模擬內核代碼對arg_start、arg_end的計算
int fake_cmdline(int argc)
{
    char buffer[2048] = {0};
    unsigned long start ,end, p;

    start = current->mm->arg_start;
    end = current->mm->arg_end;
    p = current->mm->arg_start;

    printk("argc = %d, start =%p, end=%p, p =%p\n", argc, start, end, p);

    while (argc-- > 0) 
    {
        size_t len;
        len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
        if (!len || len > MAX_ARG_STRLEN)
            return -EINVAL;
        //將p指針指向當前進程參數的結束地址內存區
        p += len;
    }

    end = p;
    printk("argc = %d, start =%p, end=%p, p =%p\n", argc, start, end, p);
    //根據arg_start、arg_end從內核內存地址空間中直接復制出參數字符串,模擬proc_pid_cmdline這個函數的代碼邏輯
    proc_cmdline(start, end , buffer);

    //打印current信息,驗證當前LSM Hook點是否已經完成了current的賦值,因為Linux中所有的進程都是父進程"復制"出來的,在新進程創建的過程中,新進程的current是逐步填充設置為新進程的相關信息
    print_current();
    return 0;
} 

void security_bprm_committed_creds(struct linux_binprm *bprm)
{  
    printk("security_bprm_committed_creds\n"); 

    do 
    { 
        if (bprm->filename)
        {
            printk("in committed cred pid = %d, mm:%p,  file:%s\n",current->pid, current->mm, bprm->filename);
        }else
        {
            printk("file exec\n");
        } 
        
        //模擬內核代碼對arg_start、arg_end的計算
        fake_cmdline(bprm->argc);
    } while (false); 

    return;
}


static int __init hello_init(void)
{ 
    int ret_val = -1;

    do 
    {
        memset(&sec_ops, 0, sizeof(sec_ops)); 
        sprintf(sec_ops.name,"aegis_lsm");

        sec_ops.bprm_committed_creds = security_bprm_committed_creds;


        //獲取access_process_vm函數的內核地址
        access_process_vm_p = acquire_func_by_kprobe("access_process_vm"); 
        printk("access_process_vm:%p\n", access_process_vm_p); 
        if (!access_process_vm_p)
        {
            printk("get access_process_vm error\n");
        }

        //獲取LSM注冊函數register_security的內核地址
        register_security_p = acquire_func_by_kprobe("register_security"); 
        printk("register_security:%p\n", register_security_p); 
        if (!register_security_p)
        {
            printk("get register_security error\n");
        }

        //獲取LSM注銷函數unregister_security的內核地址
        unregister_security_p = acquire_func_by_kprobe("unregister_security"); 
        printk("unregister_security:%p\n", unregister_security_p); 
        if (!unregister_security_p)
        {
            printk("get unregister_security error\n"); 
        }

        //注冊LSM Hook模塊
        if (register_security_p)
        {
            ret_val = register_security_p(&sec_ops);
            printk("register_security successfully: %d\n", ret_val);
            break;
        }
    }
    while(false);

    printk(KERN_INFO "Hello world!\n"); 
    return ret_val;         
}
 
static void __exit hello_cleanup(void)
{
    if (unregister_security_p)
    {
        unregister_security_p(&sec_ops); 
    }
    printk(KERN_INFO "Cleaning up module.\n");
}
 
module_init(hello_init);
module_exit(hello_cleanup);
MODULE_LICENSE("GPL");

0x4: 針對sys_connect使用LSM Hook方式獲取socke TCP Connect連接的IP、Port參數

get_ip_by_lsm.c

#ifndef MODULE
#define MODULE
#endif
#ifndef __KERNEL__
#define __KERNEL__
#endif 
#include <linux/kernel.h>
#include <linux/init.h> 
#include <linux/kallsyms.h>
#include <linux/syscalls.h>
#include <asm/unistd.h> 
#include <linux/time.h>
#include <linux/timer.h> 
#include <linux/security.h> 
#include <asm/current.h>
#include <linux/threads.h>
#include <linux/sched.h> 
#include <asm/page.h>
#include <asm/cacheflush.h>
#include <linux/pagemap.h>

#include <linux/inet.h>
#include <linux/ipv6.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/string.h> 
#include <linux/version.h>
     

#include <linux/module.h>
#include <linux/binfmts.h>
#include <linux/kprobes.h>
#include <linux/slab.h>
#include <linux/tty.h>

//thread header 
#include <linux/kthread.h> 
#include <linux/mm.h>

//modinfo header
#include <linux/vmalloc.h>  

 
#include <net/ipv6.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 25)
        #define SADDR saddr
        #define DADDR daddr
        #define SPORT sport
        #define DPORT dport
#else
        #define SADDR inet_saddr
        #define DADDR inet_daddr
        #define SPORT inet_sport
        #define DPORT inet_dport
#endif
 
typedef void* (*look_up_symbol_t)(char*);

static void* (*look_up_symbol)(char*);

static struct security_operations org_sec_ops_back_up;
static struct security_operations *org_sec_ops_p = NULL;

static struct security_operations sec_ops; 
static int (*register_security_p)(struct security_operations *ops);
static int (*unregister_security_p)(struct security_operations * ops); 

 
static unsigned int wpoff_cr0_32(void)
{
    unsigned int cr0 = 0;
    unsigned int ret=0;
    asm volatile ("movl %%cr0, %%eax":"=a"(cr0)); //匯編代碼,用於取出CR0寄存器的值
    ret = cr0;
    cr0 &= 0xfffeffff;
    asm volatile ("movl %%eax, %%cr0": :"a"(cr0));//匯編代碼,將修改后的CR0值寫入CR0寄存器
    return ret;
}

static unsigned long int wpoff_cr0_64(void)
{
    unsigned long int cr0 = 0;
    unsigned long int ret=0;
    asm volatile ("movq %%cr0, %%rax":"=a"(cr0)); //匯編代碼,用於取出CR0寄存器的值
    ret = cr0;
    cr0 &= 0xfffffffffffeffff;
    asm volatile ("movq %%rax, %%cr0": :"a"(cr0));//匯編代碼,將修改后的CR0值寫入CR0寄存器

    return ret;
}

static unsigned long int wpoff_cr0(void)
{
    if(sizeof(void*)==4)
    {
    return wpoff_cr0_32();
    }else
    {
    return wpoff_cr0_64();
    }
}

static void set_cr0_32(unsigned int val)
{
    asm volatile ("movl %%eax, %%cr0": :"a"(val));
    return;
}

static void set_cr0_64(unsigned long int val)
{
    asm volatile ("movq %%rax, %%cr0": :"a"(val));
    return;
}

static void set_cr0(unsigned long int val)
{
    if(sizeof(void*)==4)
    {
    set_cr0_32((unsigned int)val);
    }else
    {
    set_cr0_64(val);
    }
} 


static void SLEEP_MILLI_SEC(int nMilliSec)
{

    long timeout = (nMilliSec) * HZ / 1000;

    while(timeout > 0)
    {
    timeout = schedule_timeout(timeout);
    }
}


// do nothing
int kprobe_pre(struct kprobe *p, struct pt_regs *regs)
{
    return 0;
}
  
//使用kprobe獲取內核中任意函數的地址
static void* acquire_func_by_kprobe(char* func_name)
{
    void *symbol_addr = NULL;
    struct kprobe kp;

    do
    {
        memset(&kp,0,sizeof(kp));
        kp.symbol_name=func_name;
        kp.pre_handler=kprobe_pre;
        if(register_kprobe(&kp)!=0)
        {
            symbol_addr=(void*)kp.addr; 
            break;
        } 
        symbol_addr=(void*)kp.addr; 
        //now kprobe is not used any more,so unregister it
        unregister_kprobe(&kp); 

    }while(false);

    return symbol_addr;
}  
 

static look_up_symbol_t init_lookup_func(void)
{
    if (!look_up_symbol)
    {
        look_up_symbol = (look_up_symbol_t)acquire_func_by_kprobe("kallsyms_lookup_name");
    }

    return look_up_symbol;
}


//判斷當前socket連接是否為"SS_UNCONNECTED"狀態,只有在這個狀態下,才是TCP Connect最初發起連接的那一次  
int is_inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
    int ret = 0;
    int err = 0;
    struct sock *sk = sock->sk; 
    const unsigned int type = sock->type;

    //1. struct socket->type
    switch (type) 
    {
        case SOCK_DGRAM:
        case SOCK_RAW:
            ret = 0;
            break;
        case SOCK_STREAM:
        case SOCK_SEQPACKET:
            ret = 1;
            //2. struct socket->state 
            if (uaddr->sa_family == AF_UNSPEC) 
            { 
                goto out;
            } 
            if (sk->sk_state == TCP_CLOSE)
            {
                goto sock_error;
            }
            switch (sock->state) 
            {  
                case SS_CONNECTED:
                    ret = 0;
                    break; 
                case SS_CONNECTING:
                    ret = 0;
                    break; 
                //我們真正需要捕獲的TCP Conncet動作
                case SS_UNCONNECTED:
                    ret = 1;
                    //3. struct sockaddr->sa_family
                    switch (uaddr->sa_family) 
                    {
                        case AF_INET6:
                        case AF_INET:
                            ret = 1;
                            break; 
                        default:
                            ret = 0;
                    }
                    break; 
                default:
                    ret = 0;
            }  
            break;
        default:
            ret = 0;
    } 
 
out: 
    return ret;

sock_error: 
    goto out;
}


void socket_connect_log(struct socket *sock, struct sockaddr *addr, int addr_len)
{   
    int ret = 0;
    const __be32 *daddr = NULL; 
    __be16 dport = 0;
    __be16 sport = 0;
    const __be32 *saddr = NULL;

    pid_t pid = current->pid;

    do 
    {  
        ret = 0;
        //過濾冗余事件
        ret = is_inet_stream_connect(sock, (struct sockaddr *)&addr, addr_len, sock->file->f_flags);
        if (ret != 1)
        {
            goto skip;  
        } 

        //獲取待連接的對端的IP、PORT信息
        switch (addr->sa_family) 
        {
            case AF_INET6:
                if (addr_len < SIN6_LEN_RFC2133)
                {
                    goto skip;   
                }  
                daddr = (__be32 *) ((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr;
                dport = ((struct sockaddr_in6 *) addr)->sin6_port;                
                saddr = &inet_sk(sock->sk)->SADDR;
                //sport = ntohs(inet_sk(sock->sk)->SPORT);  
                sport = inet_sk(sock->sk)->SPORT; 
                break;
            case AF_INET:
                if (addr_len < sizeof(struct sockaddr_in))
                {
                    goto skip;
                } 
                daddr = (__be32 *) &((struct sockaddr_in *) addr)->sin_addr;
                dport = ((struct sockaddr_in *) addr)->sin_port;
                saddr = &inet_sk(sock->sk)->SADDR;
                //sport = ntohs(inet_sk(sock->sk)->SPORT);  
                sport = inet_sk(sock->sk)->SPORT; 
                break;
            default:
                daddr = NULL;
                dport = 0;
                saddr = NULL;
                sport = 0;
                goto skip;
        } 

        //printk("process: %d,  daddr: %s,  dport: %d\n", pid, daddr, dport);
        printk("process: %d, saddr: %d:%d, daddr: %d:%d\n", pid, saddr, sport, daddr, dport);

    } while (false); 

skip:
    return; 
}


static int __init hello_init(void)
{ 
    int ret_val = -1;
    unsigned long int cr0 = 0;

    do 
    {
        memset(&sec_ops, 0, sizeof(sec_ops)); 
        //sprintf(sec_ops.name,"aegis_lsm");

        //針對TCP CONNECT連接動作進行Hook
        sec_ops.socket_connect = socket_connect_log; 

        if (!init_lookup_func())
        {
            break;
        }


        //獲取LSM注冊函數register_security的內核地址
        register_security_p = acquire_func_by_kprobe("register_security"); 
        printk("register_security:%p\n", register_security_p); 
        if (!register_security_p)
        {
            printk("get register_security error\n");
        }

        //獲取LSM注銷函數unregister_security的內核地址
        unregister_security_p = acquire_func_by_kprobe("unregister_security"); 
        printk("unregister_security:%p\n", unregister_security_p); 
        if (!unregister_security_p)
        {
            printk("get unregister_security error\n"); 
        }

        //注冊LSM Hook模塊
        if (register_security_p)
        {
            ret_val = register_security_p(&sec_ops);
            printk("register_security successfully: %d\n", ret_val);
            break;
        } 
 
        memset(&org_sec_ops_back_up, 0, sizeof(org_sec_ops_back_up));
        //獲取內核中security_ops(LSM Table)的地址
        org_sec_ops_p = look_up_symbol("security_ops");
        if (!org_sec_ops_p)
        {
            break;
        } 
        org_sec_ops_p = *(void**)org_sec_ops_p; 
        printk("org_sec_ops:%p\n", org_sec_ops_p);
        //保存內核中的"security_ops"
        memcpy(&org_sec_ops_back_up, org_sec_ops_p, sizeof(org_sec_ops_back_up));

        cr0 = wpoff_cr0(); 
        org_sec_ops_p->socket_connect = socket_connect_log; 
        set_cr0(cr0); 

        ret_val = 0;
    }
    while(false);

    printk(KERN_INFO "Hello world!\n"); 
    return ret_val;         
}
 
static void __exit hello_cleanup(void)
{
    unsigned long int cr0 = 0;

    if (unregister_security_p)
    {
        unregister_security_p(&sec_ops); 
    } 
    else if(org_sec_ops_p)
    {
        cr0 = wpoff_cr0();  
        org_sec_ops_p->socket_connect = org_sec_ops_back_up.socket_connect; 
        set_cr0(cr0); 

        SLEEP_MILLI_SEC(100);
    } 
    printk(KERN_INFO "Cleaning up module.\n");
}
 
module_init(hello_init);
module_exit(hello_cleanup);
MODULE_LICENSE("GPL");

 

5. Linux LSM stacking

我們知道,LSM Hook Function被保存在內核內存的一個全局數組(security_ops)中,在正常流程中,調用register_security()向系統注冊一個LSM回調函數,如果當前已經開啟了SELINUX,即當前掛載點已經注冊了一個LSM回調函數,則后來的register_security()會失敗,即默認情況下,每個LSM掛載點最多只能注冊一個回調函數
LSM的一個Patch Project可以使得LSM支持"模塊棧式函數掛載"
Linux kernel patches to support LSM (security module) stacking, plus a module which actively stacks LSMs.

LSM Stacking Patch使用了一個內核標准雙鏈表將Stacking鏈式的LSM模塊進行存儲,采用FIFO的形式進行鏈式存儲,這樣,多個LSM模塊可以堆疊式地注冊到系統中,

Relevant Link:

http://lsm-stacker.sourceforge.net/
http://sourceforge.net/projects/lsm-stacker/files/
http://t75040.security-selinux.securetalk.info/current-future-plans-to-support-stacking-lsm-modules-t75040-20.html 

 

Copyright (c) 2014 LittleHann All rights reserved 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM