xfrm学习笔记


背景知识

XFRM(transform)是Linux中实现 IPsec协议的模块

xfrm_sec_ctx

结构体定义位置:https://elixir.bootlin.com/linux/v5.15/source/include/uapi/linux/xfrm.h#L31

关键断点分析:xfrm_add_sa,在该函数中构建xfrm_state结构体,而该结构体中有个成员就是我们所关注的xfrm_sec_ctx:

进入到xfrm_state_construct函数中构建xfrm_state结构体,首先会分配空间:

之后会根据netlink的消息体中是否有XFRMA_SEC_CTX来决定是否分配security:

XFRMA_SEC_CTX的值如下:

我们直接从头来看这个netlink消息应该怎么构造,自己动手,丰衣足食!

还是先放一下netlink消息的结构图:

还是要将family设置成AF_NETLINK就是netlink的消息了,xfrm_user_rcv_msg负责接收netlink消息,这可能是一个内核线程?🤔

然后设置nlhdr->nlmsg_type 为 XFRM_MSG_NEWSA,就可以走到关键函数 xfrm_add_sa:

https://elixir.bootlin.com/linux/v5.15/source/net/xfrm/xfrm_user.c#L671

第一步要分析newsa_info:

这里的p就是消息主体,相当于去掉0x10长度的nfgenmsg;

可以这样构造:

这个attr的

分配xfrm_sec_ctx

xfrm_user.c - net/xfrm/xfrm_user.c - Linux source code v5.15 - Bootlin Elixir Cross Referencer

#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/xfrm.h>
#include <linux/rtnetlink.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <netinet/in.h>

#define NLMSG_TAIL(nmsg) \
((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))

#define BUFSIZE 4096

void add_attr(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) {
int len = RTA_LENGTH(alen);
struct rtattr *rta = NLMSG_TAIL(n);
if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
fprintf(stderr, "add_attr: message exceeded bound\n");
exit(1);
}
rta->rta_type = type;
rta->rta_len = len;
memcpy(RTA_DATA(rta), data, alen);
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
}

void trigger_xfrm_sec_ctx() {
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);
if (sock < 0) {
perror("socket NETLINK_XFRM");
exit(1);
}

char buffer[BUFSIZE];
memset(buffer, 0, sizeof(buffer));

struct nlmsghdr *nlh = (struct nlmsghdr *)buffer;
struct xfrm_usersa_info *sa = (struct xfrm_usersa_info *)(nlh + 1);
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*sa));
nlh->nlmsg_type = XFRM_MSG_NEWSA;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;

// 填充 SA 基本信息
sa->family = AF_INET;
sa->id.proto = IPPROTO_AH;
sa->id.spi = htonl(0x1000);
sa->mode = XFRM_MODE_TRANSPORT;
sa->reqid = 42;
sa->saddr.a4 = inet_addr("10.0.0.1");
sa->lft.soft_byte_limit = sa->lft.hard_byte_limit = XFRM_INF;

// 添加认证算法信息:hmac(sha256)
struct {
struct xfrm_algo_auth auth;
uint8_t key[16];
} __attribute__((packed)) alg_auth;

memset(&alg_auth, 0, sizeof(alg_auth));
strncpy(alg_auth.auth.alg_name, "hmac(sha256)", sizeof(alg_auth.auth.alg_name));
alg_auth.auth.alg_key_len = 128; // in bits
alg_auth.auth.alg_trunc_len = 96; // in bits
memset(alg_auth.key, 0x42, sizeof(alg_auth.key));
add_attr(nlh, BUFSIZE, XFRMA_ALG_AUTH, &alg_auth, sizeof(alg_auth));

// 构造 secctx
struct {
struct xfrm_user_sec_ctx ctx;
char ctx_str[64];
} __attribute__((packed)) sec_ctx;

memset(&sec_ctx, 0, sizeof(sec_ctx));
const char *label = "user_u:object_r:ipsec_spd_t:s0";
int ctx_len = strlen(label);

sec_ctx.ctx.len = sizeof(sec_ctx.ctx) + ctx_len;
sec_ctx.ctx.exttype = XFRMA_SEC_CTX;
sec_ctx.ctx.ctx_alg = 1;
sec_ctx.ctx.ctx_doi = 1;
sec_ctx.ctx.ctx_len = ctx_len;
memcpy(sec_ctx.ctx_str, label, ctx_len);

add_attr(nlh, BUFSIZE, XFRMA_SEC_CTX, &sec_ctx, sizeof(sec_ctx.ctx) + ctx_len);

// 发送消息
struct sockaddr_nl addr = { .nl_family = AF_NETLINK };
struct iovec iov = { .iov_base = buffer, .iov_len = nlh->nlmsg_len };
struct msghdr msg = { .msg_name = &addr, .msg_namelen = sizeof(addr),
.msg_iov = &iov, .msg_iovlen = 1 };

if (sendmsg(sock, &msg, 0) < 0) {
perror("sendmsg");
} else {
printf("[+] XFRM_MSG_NEWSA with AUTH + secctx sent.\n");
}

close(sock);
}

int main() {
// 创建 user + net namespace
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) != 0) {
perror("unshare");
return 1;
}

// UID/GID 映射
int uid = getuid();
int gid = getgid();
FILE *f;

f = fopen("/proc/self/setgroups", "w");
if (f) {
fprintf(f, "deny\n");
fclose(f);
}

f = fopen("/proc/self/uid_map", "w");
fprintf(f, "0 %d 1\n", uid);
fclose(f);

f = fopen("/proc/self/gid_map", "w");
fprintf(f, "0 %d 1\n", gid);
fclose(f);

printf("[*] Namespaces created, triggering xfrm_sec_ctx allocation...\n");
trigger_xfrm_sec_ctx();
return 0;
}

xfrm_sec_ctx的前8字节为元数据,其中偏移为2的位置存放了其长度字段,后边跟上数据:

https://elixir.bootlin.com/linux/v5.15/source/net/xfrm/xfrm_state.c#L1482

要先运行下面这个函数:

参考

https://a1ex.online/2021/04/08/CVE-2017-7184-Linux-xfrm%E6%A8%A1%E5%9D%97%E8%B6%8A%E7%95%8C%E8%AF%BB%E5%86%99%E6%8F%90%E6%9D%83%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录