免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: Godbach
打印 上一主题 下一主题

Linux下实现劫持系统调用的总结 [复制链接]

论坛徽章:
0
1 [报告]
发表于 2009-12-05 00:15 |显示全部楼层
本帖最后由 Godbach 于 2010-08-30 11:45 编辑

unsigned long
get_syscall_table_x86_64(void)
{
#define OFFSET_SYSCALL 256

    unsigned long     syscall_entry;
    char         sc_asm[OFFSET_SYSCALL];
    char        *p;

#ifndef CONFIG_XEN
    syscall_entry = get_syscall_from_hw(ARCH_X86_64);
#else
    if (!is_running_on_xen())
        syscall_entry = get_syscall_from_hw(ARCH_X86_64);
    else if ((is_initial_xendomain()))
        syscall_entry = get_syscall_from_xen(ARCH_X86_64);
    else
        syscall_entry = get_syscall_from_kallsyms(ARCH_X86_64);
#endif

    if (unlikely(syscall_entry == 0)) {
        printk("[%s] Could not find system_call entry\n",
               __FUNCTION__);
        return -ENOSYS;
    }
   
    memcpy(sc_asm, (void*)syscall_entry, OFFSET_SYSCALL);

    p = (char *)memmem(sc_asm, OFFSET_SYSCALL, "\xff\x14\xc5", 3);
    if (unlikely(!p)) {
        printk("[%s] Could not to find system calls table\n",
               __FUNCTION__);
        return -ENOSYS;
    }
   
    return *(unsigned long*)(p + 3);

#undef OFFSET_SYSCALL
}

评分

参与人数 1可用积分 +30 收起 理由
Godbach + 30 感谢分享

查看全部评分

论坛徽章:
0
2 [报告]
发表于 2009-12-05 00:16 |显示全部楼层
本帖最后由 Godbach 于 2010-08-30 11:45 编辑

unsigned long
get_syscall_from_hw(arch_t arch)
{
    unsigned long    syscall_entry = 0;
    struct idtr    idtr;
    struct idt    idt;

    switch(arch) {
    case ARCH_X86_64:
        rdmsrl(MSR_LSTAR, syscall_entry);
        break;
    case ARCH_X86:
        asm("sidt %0"
            :"=m"(idtr)
            );
        memcpy(&idt, (char *) idtr.base + 16 * 0x80, sizeof(idt));
        syscall_entry =
            (((unsigned long)idt.offset_high ) << 32) |
            (((idt.offset_middle << 16) |
              idt.offset_low) &
             0x00000000ffffffff );
        break;
    }
    return syscall_entry;
}

论坛徽章:
0
3 [报告]
发表于 2009-12-05 00:17 |显示全部楼层
本帖最后由 Godbach 于 2010-08-30 11:45 编辑

unsigned long
get_syscall_from_xen(arch_t arch)
{
    unsigned long        syscall_entry = 0;
    xen_domctl_t        dmctl;
    vcpu_guest_context_t    ctxt;

    dmctl.cmd        = XEN_DOMCTL_getvcpucontext;
    dmctl.domain        = 0;
    dmctl.interface_version = XEN_DOMCTL_INTERFACE_VERSION;

    dmctl.u.vcpucontext.vcpu   = 0;
    dmctl.u.vcpucontext.ctxt.p = &ctxt;

    if (unlikely(HYPERVISOR_domctl(&dmctl) != 0)) {
        printk("[%s] hypercall failed\n", __FUNCTION__);
        return 0;
    }

    switch(arch) {
    case ARCH_X86_64:
        syscall_entry = ctxt.syscall_callback_eip;
        break;
    case ARCH_X86:
        syscall_entry = ctxt.trap_ctxt[0x80].address;
        break;
    }
   
    return syscall_entry;
}

论坛徽章:
0
4 [报告]
发表于 2009-12-05 00:18 |显示全部楼层
本帖最后由 Godbach 于 2010-08-30 11:45 编辑

unsigned long
get_syscall_from_kallsyms(arch_t arch)
{
    unsigned long          syscall_entry;
    struct nameidata     nd;
    const  char        *path_str = "/proc/kallsyms";
    const  char        *symbol_str;
    struct vfsmount        *old_mnt;
    struct dentry        *old_dentry;
    int             err = 0;
    void            *p;
    struct seq_file        *m;
    struct kallsym_iter    *iter;
    struct file        *kallsyms_file;
    loff_t             index = 0;
    void            *old_buf;
            
    switch(arch) {
    case ARCH_X86_64:
        symbol_str = "system_call";
        break;
    case ARCH_X86:
        symbol_str = "ia32_syscall";
        break;
    default:
        return 0;
    }
   
    old_mnt         = current->fs->rootmnt;
    old_dentry   = current->fs->root;
   
    nd.mnt         = mntget(current->fs->rootmnt);
    nd.dentry    = dget(current->fs->root);
    nd.last_type = LAST_ROOT;
    nd.flags     = LOOKUP_FOLLOW | LOOKUP_NOALT;
    nd.depth     = 0;

    if ((err = path_walk(path_str, &nd)) == 0) {
        kallsyms_file = dentry_open(nd.dentry, nd.mnt, O_RDWR);
        if (kallsyms_file == NULL) {
            printk("[%s] can not open kallsyms file\n",
                   __FUNCTION__);
            syscall_entry = -EIO;
            goto out;
        }
        
        m = kallsyms_file->private_data;
        m->version = kallsyms_file->f_version;
            
        mutex_lock(&m->lock);
            
        old_buf = m->buf;

        m->buf = kmalloc(m->size = KSYM_SYMBOL_LEN, GFP_KERNEL);
        iter = m->private;
            
        while (1) {
            p = m->op->start(m, &index);
            err = PTR_ERR(p);
            if (!p || IS_ERR(p)) {
                syscall_entry = err;
                break;
            }
            m->count = 0;
            m->op->show(m, p);

            if (!strncmp(m->buf + sizeof("%s+%#lx/%#lx [%s]") + 1,
                     symbol_str,
                     strlen(symbol_str))) {
                m->buf[2 * sizeof(void *)] = 0;
                sscanf(m->buf, "%lx", &syscall_entry);
                break;
            }
            m->op->stop(m, p);
            index++;
        }
        
        m->op->stop(m, p);
        m->count = 0;
        m->size = 0;
        kfree(m->buf);
        m->buf = old_buf;
            
        mutex_unlock(&m->lock);
        filp_close(kallsyms_file, NULL);
    } else {
        printk("[%s] path_walk failed %d\n", __FUNCTION__, err);
    }

out:
    dput(old_dentry);
    mntput(old_mnt);

    return syscall_entry;
}

论坛徽章:
0
5 [报告]
发表于 2009-12-05 00:19 |显示全部楼层
本帖最后由 Godbach 于 2010-08-30 11:46 编辑

typedef enum {
    ARCH_X86 = 0,
    ARCH_X86_64,
} arch_t;

论坛徽章:
0
6 [报告]
发表于 2009-12-05 00:19 |显示全部楼层
本帖最后由 Godbach 于 2010-08-30 11:46 编辑

.macro LOAD_SYSCALL_ARGS
    movq 8(%rsp),  %r11
    movq 16(%rsp), %r10
    movq 24(%rsp), %r9
    movq 32(%rsp), %r8
    movq 56(%rsp), %rdx
    movq 64(%rsp), %rsi
    movq 72(%rsp), %rdi
    movq %r10,     %rcx
.endm

论坛徽章:
0
7 [报告]
发表于 2009-12-05 00:24 |显示全部楼层
以上是64位系统调用劫持代码,支持 Xen dom0,如果是 domU,那就看运气了,目前流行的发行版缺省都采用了 kallsyms。kvm/vmware和物理机没区别。

如果在调用原始的系统调用例程之前做了很多事情会改变寄存器值,需要执行一下那一小段汇编代码。

P.S 这坨东西应该是 CU 首发,要转载请注明转载自 CU。

[ 本帖最后由 vupiggy 于 2009-12-4 17:27 编辑 ]

论坛徽章:
0
8 [报告]
发表于 2009-12-08 09:40 |显示全部楼层
原帖由 W.Z.T 于 2009-12-6 09:59 发表
截获系统调用的东西都是n久之前的技术了, 楼主加油,继续搞点inline hook的总结出来

看到你写的关于inline hook的文章, 忘了是米国哪个大学若干年前(32位时代)某门课的一份作业是劫持 page fault 处理就用的是同一思路. 那个老师给的参考代码和你文章里出现的一段代码一样, 我认为有问题, 至少不美.

p[1] = (offset & 0x000000ff);
p[2] = (offset & 0x0000ff00) >> 8;
p[3] = (offset & 0x00ff0000) >> 16;
p[4] = (offset & 0xff000000) >> 24;

建议改成 xchg, 好处嘛...

[ 本帖最后由 vupiggy 于 2009-12-8 02:42 编辑 ]

论坛徽章:
0
9 [报告]
发表于 2010-01-21 01:42 |显示全部楼层
个人认为武断地清除和设置 cr0 的权限位也不合理,更合理的是,保存原来的 cr0,干完坏事之后再恢复回去。

多嘴说点题内话,用 kprobe 这类``官方''提供的方法来插入自己的逻辑很不美,受制于人。要做到真正意义的劫持, fork/vfork/clone/execve 这一系列的系统调用,唯一的方法是:RTFC,搞懂了 entry.S,解决方案就呼之欲出。

首先要明确,fork/vfork/clone/execve 这一系列系统调用比较特殊,系统调用表里的对应项不像其它系统调用那样是 C 函数地址,而是汇编代码的地址 (stub_XXXX),在这些 stub_XXXX 前头一段注释这么说:

/*
 * Certain special system calls that need to save a complete full stack frame.
 */


对 execve,看内核怎么玩的:

ENTRY(stub_execve)
    CFI_STARTPROC
    popq %r11
    CFI_ADJUST_CFA_OFFSET -8
    CFI_REGISTER rip, r11
    SAVE_REST
    FIXUP_TOP_OF_STACK %r11
    movq %rsp, %rcx
    call sys_execve
    RESTORE_TOP_OF_STACK %r11
    movq %rax,RAX(%rsp)
    RESTORE_REST
    jmp int_ret_from_sys_call
    CFI_ENDPROC
END(stub_execve)


如果像劫持其它系统调用那样把系统调用表对应 __NR_execve 的那项改成了自己实现的 C 函数地址,并且在该 C 函数内妄图调用原始的系统调用例程 (oops, 是 stub_execve),必死无疑,因为寄存器全乱了 (所以所谓的栈要调平衡一说并不准确,而是内核汇编代码将要放入栈的寄存器值被破坏了)。要么:
1. 整明白内核那段汇编是干什么的,要保存/恢复那些寄存器,自己照样做,多挂几次机,就想明白了 有些宏在 calling.h 中定义。要么:
2. 要是实在不想和底层机制玩命,有一个投机取巧的方法,看到那句可爱的:``call sys_execve'' 了没有,找到它 (它的 opcode 是 0xe9 .. .. .. .. ),把后面的操作数,也就是一个 C 函数, sys_execve 地址的偏移,换成自己实现的一个 asmlinkage 的 C 函数的偏移值即可,这样在自己的 C 函数里就可以在调用 sys_execve 之前之后为所欲为,因为 sys_execve 是 asmlinkage 的,编译器替我们保证参数正确。

要劫持 fork/vfork/clone 也只需要那么一点点技巧,看内核代码:
.macro PTREGSCALL label,func,arg
        .globl \label
\label:
        leaq        \func(%rip),%rax
        leaq    -ARGOFFSET+8(%rsp),\arg /* 8 for return address */
        jmp        ptregscall_common
END(\label)
        .endm
...
PTREGSCALL stub_clone, sys_clone, %r8
...
ENTRY(ptregscall_common)
        popq %r11
        CFI_ADJUST_CFA_OFFSET -8
        CFI_REGISTER rip, r11
        SAVE_REST
        movq %r11, %r15
        CFI_REGISTER rip, r15
        FIXUP_TOP_OF_STACK %r11
        call *%rax
        RESTORE_TOP_OF_STACK %r11
        movq %r15, %r11
        CFI_REGISTER rip, r11
        RESTORE_REST
        pushq %r11
        CFI_ADJUST_CFA_OFFSET 8
        CFI_REL_OFFSET rip, 0
        ret
        CFI_ENDPROC
END(ptregscall_common)

看看,又是 jmp 又是 call 的,应该清楚要做什么了。

注意:2.6.29开始,PTREGSCALL 宏的定义变了,于是 hack 的方式要跟着变。

[ 本帖最后由 vupiggy 于 2010-1-20 18:50 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP