首页
论坛
课程
招聘
[原创]从两道0解题看Linux内核堆上msg_msg对象扩展利用
2021-9-12 17:47 10343

[原创]从两道0解题看Linux内核堆上msg_msg对象扩展利用

2021-9-12 17:47
10343

目录

 

在前些日子结束的 corCTF 2021 国际赛中,出现了两个有意思的0解Linux kernel题,赛后我的队友向我推荐了这两道题,我浏览了一下官方题解,做了复现。

 

这两到题的主要目的是介绍了 <在Linux Kernel中,当我们控制了 struct msg_msg 之后,如何构造任意(越界)读、任意写、以及任意释放的原语。进而如何配合userfaultfd实现对于当前进程的task_struct,以及cred的进攻利用,实现权限提升> 的这样一种技术。

 

这是出题人的想法与官方WP:

 

Fire of Salvation

 

Wall Of Perdition

 

这个仓库里收集了题目文件:

 

Github

Netfiler Hook简介

Linux Kernel Communication — Netfilter Hooks

netfilter是一个用于数据包处理的框架,在正常的套接字接口之外。

它有四个部分。首先,每个协议都定义了 "钩子"(IPv4定义了5个),这些钩子是数据包穿越该协议栈过程中的明确定义的hook point。在每一个点上,协议都根据数据包和hook number调用netfilter框架。

 

Netfilter给了我们一种在固定的point对packet进行回调,解析,修改,过滤的可能。

 

Netfilter提供了一种叫做netfilter hooks的东西,这是一种使用回调的方式,以便在内核内过滤数据包。

 

有5种不同的netfilter hook分别位于如下位置 1~5。

1
2
3
4
5
6
7
8
9
10
A Packet Traversing the Netfilter System:
   --->[1]--->[ROUTE]--->[3]--->[4]--->
                 |            ^
                 |            |
                 |         [ROUTE]
                 v            |
                [2]          [5]
                 |            ^
                 |            |
                 v            |

他们对应的是:

  1. NF_INET_PER_ROUNTING

  2. NF_INET_LOCAL_IN

  3. NF_INET_FORWARD

  4. NF_INET_POST_ROUTING

  5. NF_INET_LOCAL_OUT

具体的,我们需要使用 nf_register_net_hook 针对hook进行注册(结构体nf_hook_ops)

Fire of Salvation

题目描述

1
2
3
4
5
6
7
8
9
10
11
12
Elastic objects in kernel have more power than you think. A kernel config file is provided as well, but some of the important options include:
 
CONFIG_SLAB=y
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDEN=y
CONFIG_STATIC_USERMODEHELPER=y
CONFIG_STATIC_USERMODEHELPER_PATH=""
CONFIG_FG_KASLR=y
 
SMEP, SMAP, and KPTI are of course on. Note that this is an easier variation of the Wall of Perdition challenge.
 
hint: Using the correct elastic object you can achieve powerful primitives such as arb read and arb write. While arb read for this object has been documented, arb write has not to the extent of our knowledge (it is not a 0 day tho so don't worry).

可以看到开启了一堆保护和额外的加固。

 

注意以下选项:

  • FG-KASLR (Function Granular Kernel Address Space Layout Randomization):细粒度的kaslr,函数级别上的KASLR优化。
  • STATIC_USERMODE_HELPER 禁掉了对于modprobe_path和core_pattern的利用(只读区域)

值得注意的是的使用了SLAB分配器而非SLUB。

 

题目源码在:

 

https://paste.ubuntu.com/p/2xzRxyVjqy/

 

题目本身实现了一个 内核态的防火墙驱动,定义了针对ipv4数据包的出入站规则 。

 

init_firewall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
初始化两个全局的list 
firewall_rules_in:存储指向入站规则的指针
firewall_rules_out:存储指向出站规则的指针
*/
firewall_rules_in = kzalloc(sizeof(void *) * MAX_RULES, GFP_KERNEL);
firewall_rules_out = kzalloc(sizeof(void *) * MAX_RULES, GFP_KERNEL);
 
 
/*
注册hook函数
*/
 if (nf_register_net_hook(&init_net, &in_hook) < 0)
    {
        printk(KERN_INFO "[Firewall::Error] Cannot register nf hook!\n");
        return ERROR;
    }
if (nf_register_net_hook(&init_net, &out_hook) < 0)
    {
        printk(KERN_INFO "[Firewall::Error] Cannot register nf hook!\n");
        return ERROR;
    }

对应的结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
static struct nf_hook_ops in_hook = {
  .hook        = firewall_inbound_hook,/* 钩子函数 */
  .hooknum     = NF_INET_PRE_ROUTING,    /* 钩子点,NF_INET_PRE_ROUTING代表当包到达时被调用。     */
  .pf          = PF_INET,                  /* 协议族 */
  .priority    = NF_IP_PRI_FIRST    /* 优先级 */
};
 
static struct nf_hook_ops out_hook = {
  .hook        = firewall_outbound_hook,
  .hooknum     = NF_INET_POST_ROUTING,
  .pf          = PF_INET,
  .priority    = NF_IP_PRI_FIRST
};

firewall_inbound_hook && firewall_outbound_hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*本函数会在包进站时被调用*/
static uint32_t firewall_inbound_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
    int i;
    uint32_t ret;
 
    for (i = 0; i < MAX_RULES; i++)
    {       
          //扫描存在的过滤规则
        if (firewall_rules_in[i])
        {
              // 调用process_rule处理对应的数据包
            ret = process_rule(skb, firewall_rules_in[i], INBOUND, i);
            if (ret != SKIP)
                return ret;
        }
    }
 
    return NF_ACCEPT;
}
 
 
/*本函数会在包出站时被调用*/
static uint32_t firewall_outbound_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
    int i;
    uint32_t ret;
 
    for (i = 0; i < MAX_RULES; i++)
    {
        if (firewall_rules_out[i])
        {
            ret = process_rule(skb, firewall_rules_out[i], OUTBOUND, i);
 
            if (ret != SKIP)
                return ret;
        }
    }
 
    return NF_ACCEPT;
}

process_rule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
static uint32_t process_rule(struct sk_buff *skb, rule_t *rule, uint8_t type, int i)
{
    struct iphdr *iph;
    struct tcphdr *tcph;
    struct udphdr *udph;
 
    printk(KERN_INFO "[Firewall::Info] rule->iface: %s...\n", rule->iface);
    printk(KERN_INFO "[Firewall::Info] skb->dev->name: %s...\n", skb->dev->name);
 
      /* 比较interface是否匹配 */
    if (strncmp(rule->iface, skb->dev->name, 16) != 0)
    {
        printk(KERN_INFO "[Firewall::Error] Rule[%d], inferface doesn't match, skipping!\n", i);
        return SKIP;
    }
 
      /* 取当前的ip头 */
    iph = ip_hdr(skb);
        /* 如果是INBOUND过滤 */
    if (type == INBOUND)
    {
          /* 判断是否在一个子网内? */
        if ((rule->ip & rule->netmask) != (iph->saddr & rule->netmask))
        {
            printk(KERN_INFO "[Firewall::Error] Rule[%d], ip->saddr doesn't belong to the provided subnet, skipping!\n", i);
              /* 如果不在则返回SKIP跳过 */
            return SKIP;
        }
    }
  /* 如果是OUTBOUND过滤 */
    else
    {   
          /* 判断子网合法性 */
        if ((rule->ip & rule->netmask) != (iph->daddr & rule->netmask))
        {
            printk(KERN_INFO "[Firewall::Error] Rule[%d], ip->daddr doesn't belong to the provided subnet, skipping!\n", i);
            return SKIP;
        }
    }
        /* 如果是TCP协议 */
    if ((rule->proto == IPPROTO_TCP) && (iph->protocol == IPPROTO_TCP))
    {
        printk(KERN_INFO "[Firewall::Info] Rule[%d], protocol is TCP\n", i);
                /* 取tcp头 */
        tcph = tcp_hdr(skb);
                /* 检查端口合法性 */
        if ((rule->port != 0) && (rule->port != tcph->dest))
        {
            printk(KERN_INFO "[Firewall::Error] Rule[%d], rule->port (%d) != tcph->dest (%d), skipping!\n", i, ntohs(rule->port), ntohs(tcph->dest));
            return SKIP;
        }
                /* 判断action是否合法,只允许NF_DROP 、NF_ACCEPT  */
        if ((rule->action != NF_DROP) && (rule->action != NF_ACCEPT))
        {
            printk(KERN_INFO "[Firewall::Error] Rule[%d], invalid action (%d), skipping!\n", i, rule->action);
            return SKIP;
        }
 
        printk(KERN_INFO "[Firewall::Info] %s Rule[%d], action %d\n", (type == INBOUND) ? "Inbound" : "Outbound", i, rule->action);
 
        return rule->action;
    }
 
        /* 如果是UDP协议 */
    else if ((rule->proto == IPPROTO_UDP) && (iph->protocol == IPPROTO_UDP))
    {
        printk(KERN_INFO "[Firewall::Info] Rule[%d], protocol is UDP\n", i);
 
        udph = udp_hdr(skb);
 
        if ((rule->port != 0) && (rule->port != udph->dest))
        {
            printk(KERN_INFO "[Firewall::Error] Rule[%d], rule->port (%d) != udph->dest (%d), skipping!\n", i, ntohs(rule->port), ntohs(udph->dest));
            return SKIP;
        }
 
        if ((rule->action != NF_DROP) && (rule->action != NF_ACCEPT))
        {
            printk(KERN_INFO "[Firewall::Error] Rule[%d], invalid action (%d), skipping!\n", i, rule->action);
            return SKIP;
        }
 
        printk(KERN_INFO "[Firewall::Info] %s Rule[%d], action %d\n", (type == INBOUND) ? "Inbound" : "Outbound", i, rule->action);
 
        return rule->action;
    }
 
    return SKIP;
}

firewall_add_rule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
static long firewall_add_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
    printk(KERN_INFO "[Firewall::Info] firewall_add_rule() adding new rule!\n");
 
    if (firewall_rules[idx] != NULL)
    {
        printk(KERN_INFO "[Firewall::Error] firewall_add_rule() invalid rule slot!\n");
        return ERROR;