【问题标题】:Kernel crashes when accessing socket buffer structure访问套接字缓冲区结构时内核崩溃
【发布时间】:2015-12-07 08:09:37
【问题描述】:

我一直在尝试拦截 connect 系统调用,试图重写 tcp 请求的目标 IP 地址。我想我成功地从符号表中检索了连接系统调用的地址并拦截了它。我的结论是基于我的函数被内核调用的事实。但是,我不太确定我的代码是否实际上拦截了连接系统调用。为了验证我是否尝试打印出源或目标的 IP 地址。但是每当我尝试访问套接字缓冲区结构时,内核就会崩溃。您可以在 hijackConnect 函数中看到该部分代码,它们已被注释掉。我想知道我做错了什么以及如何确保我真的在拦截 tcp 请求。如果可能的话,我想知道如何重写目标 IP 地址,以便我可以将 http 请求重定向到特定的网站。即使您不知道如何去做,我仍然很欣赏良好的指针和学习资源。

免责声明:我并不想做任何险恶的事情。这是针对内核黑客学校项目的,它必须在内核空间中完成。我的内核是3.16版本,以下代码是根据自己的需要改编的在线教程。

#define DISABLE_WRITE_PROTECTION (write_cr0(read_cr0() & (~ 0x10000)))
#define ENABLE_WRITE_PROTECTION (write_cr0(read_cr0() | 0x10000))

static unsigned long **find_sys_call_table(void); 
asmlinkage int hijackConnect(struct sock *sk, struct sockaddr *uaddr,int addr_len);

asmlinkage int (*original_sys_connect)(struct sock *, struct sockaddr *, int);
asmlinkage unsigned long **sys_call_table;

/* Initialisation routine */
int init_module(void)
{
    sys_call_table = find_sys_call_table();

    if(!sys_call_table) {
       printk(KERN_ERR "Couldn't find sys_call_table.\n");
       return -EPERM;  /* operation not permitted; couldn't find general error */
    }

    DISABLE_WRITE_PROTECTION;
    original_sys_connect = (void *) sys_call_table[SYS_CONNECT];
    sys_call_table[SYS_CONNECT] = (unsigned long *) hijackConnect;
    ENABLE_WRITE_PROTECTION;

    printk(KERN_INFO "Connect system call is hijacked!\n");

    return 0;
 }

/* Cleanup routine */
void cleanup_module(void)
{
    printk(KERN_INFO "Unhook hijacking\n");

    /* Restore the original sys_open in the table */
    DISABLE_WRITE_PROTECTION;
    sys_call_table[SYS_CONNECT] = (unsigned long *) original_sys_connect;
    ENABLE_WRITE_PROTECTION;
}

static unsigned long **find_sys_call_table() {
   unsigned long offset;
   unsigned long **sct;

    for(offset = PAGE_OFFSET; offset < ULLONG_MAX; offset += sizeof(void *)) {
       sct = (unsigned long **) offset;

       if(sct[__NR_close] == (unsigned long *) sys_close)
          return sct;
    }

    /*
     * Given the loop limit, it's somewhat unlikely we'll get here. I don't
     * even know if we can attempt to fetch such high addresses from memory,
     * and even if you can, it will take a while!
     */
   return NULL;
 }

 asmlinkage int hijackConnect(struct sock *sk, struct sockaddr *uaddr,int     addr_len)
 {
    printk(KERN_INFO "Connect is called!\n");

    /*  struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
    struct inet_sock *inet = inet_sk(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    __be16 orig_sport, orig_dport;
    __be32 daddr, nexthop, test;

    orig_sport = inet->inet_sport;
    orig_dport = usin->sin_port;
    test = inet->inet_saddr;
    //daddr = usin->sin_addr.s_addr;

    printk(KERN_INFO "ADDRESS - %d", ntohl(test));*/
    return (*original_sys_connect)(sk,uaddr,addr_len); 
}

【问题讨论】:

  • 系统调用函数的正确定义是SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen)。请注意,它的第二个参数uservaddr 具有其类型的__user 修饰符。这意味着给定的指针指向用户地址空间,并且应该通过特殊函数(如copy_from_user)访问。顺便说一句,探索原始系统调用可能会发现,它使用move_addr_to_kernel 来处理该指针。

标签: linux-kernel kernel-module


【解决方案1】:

代码显然是错误的,方法也是如此。 这里的真正目标是什么?

你有:

asmlinkage int hijackConnect(struct sock *sk, struct sockaddr *uaddr,int     addr_len)

并使用它来替换 connect() 的系统调用条目。很明显这是不正确的——用户空间应该如何将指针传递给套接字?更不用说 connect 系统调用接受不同的参数:

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
                int, addrlen)

第一个参数是一个文件描述符,而不是一个指向套接字的指针,所以不出所料会导致崩溃。

但是,该方法本身存在根本缺陷。由于您稍后尝试调用实际的 connect(),内核从用户空间重新获取数据,攻击者可以简单地修改它以修复您的代码所做的任何事情。详情请见http://www.watson.org/~robert/2007woot/

【讨论】:

  • 谢谢,但是你能解释一下 SYSCALL_DEFINE3 的函数签名吗?因为我很难理解为什么它没有返回类型,而函数体却返回 int 值。
【解决方案2】:

如果可能的话,我想知道如何重写目标 IP 地址,以便我可以将 http 请求重定向到特定的网站。

这是最适合 Linux netfilter( iptables)包过滤框架的任务。

劫持系统调用不是一种受支持的技术,会导致系统不稳定。在这种特定情况下,它会在 32 位 x86 内核上严重失败,因为connect() 没有自己的系统调用号;相反,socketcall() 系统调用与SYS_CONNECT 作为其第一个参数一起使用。您在系统调用表中覆盖的条目完全不相关。 (我相信它实际上是exit()的条目!)

【讨论】:

  • 感谢您的回复。我实际上尝试过使用 Netfilter,并成功阻止了对静态 IP 网站的访问。(动态 IP 是另一个问题)。但是,尝试通过更改 tcp 标头的目标 IP 将网站重定向到另一个站点失败。即使在重新计算校验和之后。客户端(firefox、wget)不断向原始站点发出请求,就好像它直到超时才确认我所做的更改。那时我认为我的级别可能比我需要达到的级别低得多,并决定尝试在更高级别更改 IP,即连接系统调用。
猜你喜欢
  • 2018-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-19
  • 2019-09-25
相关资源
最近更新 更多