2017年之后所有linuxkernel的系统几乎都不能幸免。这个漏洞以一种非常复杂的方式,通过AF_ALG类型的socket发送aad验证头,巧妙利用内核数据之间的传输,用aad的成员seqno_lo覆盖普通用户的UID和GID从而获取root权限。linux用户的格式如下,一般在/etc/passwd,比如下面一个root用户和普通用户。root:x:0000:0000:root:/root:/bin/shtest:x:1000:1000:test:/home/test:/bin/sh
注意看它们两个不同点,除了路径就是000和1000的区别,root冒号前后的0000分别占据4个字节,它们分别是UID(userID)和GID(GroupID),0000代表当前用户是管理员权限,而1000则代表是普通用户权限。所以,只需要通过某种方式把普通用户test的1000改成0000即可让其成为管理员用户,从而进行提权。首先实例化一个AF_ALG类型的socket,把这个socket设置成加密型的socket,秘钥占据了56个字节CRYPTO_AUTHENC_KEYA_PARAM 内核子系统常量占4字节,表示门给authenc 算法使用的参数配置len(enckey) 对称加密数据的长度占4字节authkey 用户hash计算的,占32字节enckey 对称加密的数据,表示额外的加密,加密之后以确保无人能看懂数据 4+4+32+16==56字节
aad = b"\x00" * 4 + b"1111"
注意看aad数据four_bytes是你覆盖UID:GID的数据,可以传任意数据,但只有0000才是管理员权限。数据构造好了,需要告诉数据在内核里面怎么处理这个构造的数据。cmsg = [ (SOL_ALG, ALG_SET_OP, struct.pack("I", ALG_OP_DECRYPT)), (SOL_ALG, ALG_SET_IV, struct.pack("I", 16) + b"\x00" * 16), (SOL_ALG, ALG_SET_AEAD_ASSOCLEN, struct.pack("I", 8)), ]
ALG_OP_DECRYPT表示它是解密处理,ALG_SET_AEAD_ASSOCLEN表示前8个字节是关联数据,后面才是密文。当我们通过AF_ALG类型的socket发送了aad的时候,同时通过os.splice对page_cache进行缓存共享(即读写在同一块内存)的时候,add数据经过非常复杂的路径直接写入GID,注意这个地方UID并没有变。//scatterwalk_map_and_copystaticintcrypto_authenc_esn_decrypt(struct aead_request *req){ //此处省略 ,便于观看 scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); //此处省略 }
dst指向的就是aad,我们上面构造的aad如下(字符串1的ASSIC是0x31)从dst的0位置读8字节放入到tmp临时变量,从tmp临时变量4位置读取四字节放入到dst。这种过程做了什么?也就是把GID的四字节复制到UID,这样的话GID:UDI就会变成:1111:1111。如果上面传入的不是b"1111"而是b"0000"这不刚好是root的UID和GID嘛,是不是把当前普通用户变成了管理员用户呢?这里的walk_map_and_copy第三个参数表示读还是写,0为从dst读取放入tmp,1则为从tmp读放入dst。看似很简单,但是实际上很复杂,其中的如何从aad转换到struct page过程相当曲折。这里看下从struct page转换到虚拟地址。这个dst里面有个page_link,它是转换的目标[***Tyz***] p/x *dst$9 = {page_link = 0xffffea000014f0c0, offset = 0xbb0, length = 0x8, dma_address = 0x0, dma_length = 0x0, dma_flags = 0x0}
注意这个dst的offset是0xbb0,后面用到。在scatterwalk_map函数里面会调用page_address进行转换,具体转过程1.获取到page_link的vmemmap_base,也即是基地址0xffffea00000000002.x=把page_link-基地址3.y=x>>6<<0xC+page的基地址+dst->offset4.最终的这个y包含了aad传入的1也就是二进制的0x31
[***Tyz***] x/10i$pc=> 0xffffffff81700d1d <scatterwalk_map+13>: sub rax,QWORD PTR [rip+0x138f4ec] # 0xffffffff82a90210 <vmemmap_base> 0xffffffff81700d24 <scatterwalk_map+20>: sar rax,0x6 0xffffffff81700d28 <scatterwalk_map+24>: shl rax,0xc 0xffffffff81700d2c <scatterwalk_map+28>: add rax,QWORD PTR [rip+0x138f4ed] # 0xffffffff82a90220 <page_offset_base> 0xffffffff81700d33 <scatterwalk_map+35>: mov edx,DWORD PTR [rbx+0x10] 0xffffffff81700d36 <scatterwalk_map+38>: add rax,rdx 0xffffffff81700d39 <scatterwalk_map+41>: mov QWORD PTR [rbx],rax 0xffffffff81700d3c <scatterwalk_map+44>: pop rbx 0xffffffff81700d3d <scatterwalk_map+45>: ret 0xffffffff81700d3e <scatterwalk_map+46>: int3
以page_link==0xffffea000014f0c0为例0xffffea000014f0c0-0xffffea0000000000(vmemmap基地址)==0x14f0c013d600>>6<<0xC==0x53c3000x53c300+0xffff888000000000(page基地址)==0xffff8880053c30000xffff8880053c3000+oxbb0(dst->offset)==0xffff8880053c3bb0
然后我们来看下0xffff8880053c3bb0处内存[***Tyz***] x/8gx 0xffff8880053c3000+0xbb00xffff8880053c3bb0: 0x3131313100000000 0x00000000006a007c0xffff8880053c3bc0: 0x0000000000000000 0x017c0000000000000xffff8880053c3bd0: 0x000000000000006a 0x00000000000000000xffff8880053c3be0: 0x000002ab00000000 0x1a6b016400000000
00 00 00 00 31 31 31 310xffff8880053c3bb0内存对应的即是dst的page_link:0xffffea000014f0c0
我们看到GID全部为1,而UID为0。所以需要把UID也改成1。下面代码即是做这事scatterwalk_map_and_copy(tmp, dst, 0, 8, 0);scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);
如果传入的是b"0000"则就是root权限,这里仅为测试。以上大致CVE-2026-31431的原理,个人理解。