seccomp

oogu ㅣ 2023. 2. 13. 20:29

// gcc -o seccomp seccomp.cq
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/unistd.h>
#include <linux/audit.h>
#include <sys/mman.h>

int mode = SECCOMP_MODE_STRICT;

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

int syscall_filter() {
    #define syscall_nr (offsetof(struct seccomp_data, nr))
    #define arch_nr (offsetof(struct seccomp_data, arch))
    
    /* architecture x86_64 */
    #define REG_SYSCALL REG_RAX
    #define ARCH_NR AUDIT_ARCH_X86_64
    struct sock_filter filter[] = {
        /* Validate architecture. */
        BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr),
        BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0),
        BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
        /* Get system call number. */
        BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
        };
    
    struct sock_fprog prog = {
    .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
    .filter = filter,
        };
    if ( prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1 ) {
        perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
        return -1;
        }
    
    if ( prctl(PR_SET_SECCOMP, mode, &prog) == -1 ) {
        perror("Seccomp filter error\n");
        return -1;
        }
    return 0;
}


int main(int argc, char* argv[])
{
    void (*sc)();
    unsigned char *shellcode;
    int cnt = 0;
    int idx;
    long addr;
    long value;

    initialize();

    shellcode = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    while(1) {
        printf("1. Read shellcode\n");
        printf("2. Execute shellcode\n");
        printf("3. Write address\n");
        printf("> ");

        scanf("%d", &idx);

        switch(idx) {
            case 1:
                if(cnt != 0) {
                    exit(0);
                }

                syscall_filter();
                printf("shellcode: ");
                read(0, shellcode, 1024);
                cnt++;
                break;
            case 2:
                sc = (void *)shellcode;
                sc();
                break;
            case 3:
                printf("addr: ");
                scanf("%ld", &addr);
                printf("value: ");
                scanf("%ld", addr);
                break;
            default:
                break;
        }
    }
    return 0;
}

[그림 1-1]

ROP overwrite 가능, code, data 영역 고정이다. 

코드를 보면 seccomp가 걸려있어 어떤 모드인지 알기 위해 seccomp-tool 돌렸는데 이상함..

[그림 1-2]

prctl 함수 인자값에 원래 seccomp mode가 있어야하는 mode라는 변수가 자리함

[그림 1-3]

변수 mode는 전역 변수로 선언되어있고 STRICT 모드이다. 따라서 read, write, exit, sigreturn 시스템 콜만 호출이 가능하다.

익스를 생각해보면 타 시스템 콜은 STRICT 모드라서 사용할 수가 없다. ABI도 STRICT 모드라서 안된다.

 

엄청 삽질하다가 mode change 라는 힌트를 보고 깨달았다. 변수 mode가 전역변수로 있는 이유를... 변수 mode의 값을 바꿔서 seccomp mode를 바꾸는 것이다. 

[그림 1-4]

변수 mode는 전역변수 위치에 있고 PIE가 없어서 특정 위치에 고정된다.

[그림 1-5]

0x602090에 값을 1에서 0 또는 2로 바꾸고 syscall_filter() 실행하면 다른 모드로 실행되어서 STRICT 모드가 우회가 가능하다.

FILTER MODE가 되는 이유는 아무 필터도 걸어놓지 않았기 때문이다.

 

익스 시나리오는 CASE3에서 임의의 주소에 쓰기가 가능하다. 이를 이용해 0x602090 주소에 0 또는 2로 값을 바꾸고 CASE1에서 shellcode를 넣고 CASE2를 실행하면 쉘이 실행될 것이다.

 

[그림 1-6]

 

[그림 1-7]

[그림 1-6]를 실행하면 변수 mode의 값이 1에서 2로 바뀌었다.

[그림 1-8]
[그림 1-9]

성공!

 

알게 된 점

1. STRICT MODE 일 때 익스는 mode를 바꾸거나 prctl got 값을 의미 있는 주소로 바꾸면 된다.

2.  코드를 의심스럽게 보자..? ex) mode 변수는 왜 전역변수일까..?

'Dreamhack - pwnable' 카테고리의 다른 글

environ  (0) 2023.03.06
master_canary  (1) 2023.02.15
cmd_center  (0) 2023.02.03
sint  (0) 2023.02.03
tcache_dup2  (0) 2023.02.01