https://dreamhack.io/lecture/roadmaps/2
System Hacking
시스템 해킹을 공부하기 위한 로드맵입니다.
dreamhack.io
<Shellcode>
익스플로잇(Exploit): 상대 시스템을 공격하는 것. '부당하게 이용하다'
셸코드(Shellcode):
- 익스플로잇을 위해 제작된 어셈블리 코드 조각. 일반적으로 셸을 획득하기 위한 목적으로 사용해서 "셸"이 붙음.
- 해커가 rip를 자신이 작성한 셸코드로 옮길 수 있으면 원하는 어셈블리 코드가 실행되게 할 수 있음.
- 셸코드는 어셈블리어로 구성, 공격을 수행할 대상 아키텍쳐와 운영체제에 따라, 셸코드의 목적에 따라 다름.
// 셸코드를 공유하는 사이트도 있지만 이는 범용적으로 작성된 것이므로 직접 작성해야 최적함.
orw 셸코드: 파일을 열고(o), 읽은 뒤(r) 화면에 출력(w)해주는 셸코드다.
- o, r, w의 syscall
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
read | 0x00 | unsigned int fd | char* buf | size_t count |
write | 0x01 | unsigned int fd | const char* buf | size_t count |
open | 0x02 | const chat* filename | int flags | umode_t mode |
파일 서술자(File Descriptor, fd):
- 유닉스 계열의 OS에서 파일에 접근하는 sw에 제공하는 가상의 접근 제어자.
- 프로세스마다 고유의 서술자 테이블을 가짐 => 그 안에 여러 파일 서술자 저장.
// 0번: 일반 입력(STDIN), 1번: 일반 출력(STDOUT), 2번: 일반 오류(STDERR)
- orw 셸코드 작성 => "/tmp/flags"를 읽는 셸코드를 작성
==============================
[open]
push 0x00
mov rax, 0x616c662f706d742f67
push rax
mov rdi, rsp // rdi = "/tmp/flags"
xor rsi, rsi // rsi = 0 -> flags = 0
xor rdx, rdx // mode = NULL
mov rax, 2 // rax = 2, syscall_open
syscall
/*
syscall의 반환 값이 rax에 저장
=> "/tmp/flags"의 fd는 rax에 저장
*/
[read]
mov rdi, rax
mov rsi, rsp
sub rsi, 0x30 // rsi = rsp - 0x30
mov rdx, 0x30 // len = 0x30
mov rax, 0x00 // rax = 0. syscall_read
syscall
[write]
mov rdi, 1 // fd = stdout
mov rax, 0x01 // rax = 1, syscall_write
syscall
==============================
- orw 셸코드 컴파일 및 실행
리눅스에서 실행 가능한 파일의 형식은 ELF // 윈도우의 경우 PE
ELF는 헤더 + 코드 + 기타 데이터로 구성됨. // 헤더: 실행에 필요한 여러 정보, 코드: CPU가 이해할 수 있는 기계어 코드
작성된 orw 셸코드는 ASCII로 작성된 어셈블리 코드.
=> 기계어로 치환하면 CPU 이해 가능
=> 리눅스에서 실행은 불가능. ELF가 아니니깐.
그래서 gcc 컴파일을 통해 orw 셸코드를 ELF 형식으로 변형해야 함.
어셈블리 코드 컴파일
1. 셸코드를 실행할 수 있는 스켈레톤 코드를 C언어로 작성
2. 거기에 셸코드를 탑재
// 스켈레톤 코드: 핵심 내용이 비어 있는, 기본 구조만 갖춘 코드
=> 스켈레톤 코드. 이대로 돌리면 컴파일은 안 된다. 문자열이랑 명령어랑 뒤섞여가지구... 이거 가지고 몇 시간 머리 싸매고 있었다 ㅠ.ㅠ 흑흑
실행을 위해 /tmp/flag 에 파일을 만들어 줄 거다.
=> 이렇게 event not found 에러가 뜨면, set +H를 해주면 된다.
orw.c 를 컴파일하고 실행해주면, /tmp/flag 파일 내용이 출력된다.
사실 원래 의도대로라면, 여기에서 memery leak이 나서 아랫줄에 뭐가 좀 더 출력되어야 하는데,
내 머신에는 뭐 저장된 게 없어서인지... 그냥 아무것도 안 뜬다.
아무튼 아랫줄에 뭔가가 의도되지 않은 것이 출력됐다고 치고
디버깅을 해보자.
- orw 셸코드 디버깅
gdb로 열어서 브레이크 포인트를 잡아준다.
orw 셸코드 내용이 있는 run_sh 함수에 중단점을 넣어준다.
run(r) 명령어를 넣어주면, context가 주욱 뜬다.
bp인 run_sh에서 딱 멈춘 게 보인다.
첫번째 syscall인 "0x5555555551a6 <run_sh+29> syscall" 까지 실행해준다.
ni(next into) 또는 si(step into)를 쓰면 진행된다.
// 그냥 엔터를 쳤는데도... 되네?...
첫번째 syscall이다.
open 이니까 rax에 0x02가 있고, rdi(fd)에 '/tmp/flag'가 있고, rsi(flags)에 0x0, rdx(count)에 0x0이 들어가 있다.
그리고 disassemble에 보면, <SYS_open>이라고 되어 있다! pwndbg 플러그인이 syscall 호출 시 분석해서 o, r, w를 판단해서 알려주는 듯...
그래서 open syscall이 끝나면, rax에 /tmp/flag의 fd(3)가 저장된다.
다음 syscall이다.
read니까 rax는 0x00, rdi(fd)는 0x03, rsi(buf)은 0x7fffffffdeb8, rdx(count)은 0x30이다.
read syscall 이 끝난 후 보니,
힝 난 왜 안 뜨지...
다음 write syscall.
rax는 0x01, rdi(fd)는 0x1, rsi(bf)는 0x7fffffffc278(flag), rdx(count)는 0x30이 저장되어 있다.
그래서, 데이터를 저장한 0x7fffffffc278에서 0x30바이트(=48바이트)를 출력한다.
근데 원래 플래그는 "flag{this_is_open_read_write_shellcode!}"로 40바이트다.
그 차이 8바이트만큼 얘가 더 쓰레기값을 출력하는 거다.
- 초기화 되지 않은 메모리 사용(Use of Uninitialized Memory)
스택은 여러 함수들이 공유하는 메모리. 스택을 해제한다는 게 진짜 스택을 쫙 날리는 게 아니고. rsp, rbp를 이동하는 거라... 그 값들은 스택에 남아있다. 그래서 얘가 가비지 데이터가 되는 건데, 얘 때문에 문제가 생길 수도 있다!
프로세스가 지멋대로 동작할 수도 있고, 중요한 정보가 노출될 수도 있고... 해서 그런 이유로 항상 초기화를 잘 해야 한다는 것. : Memory Leak
=> 매번 코딩할 때마다 초기화 하라고 alert 뜨면 짜증났는데 왜 귀찮게 시키는지 알겠다...
execve 셸코드:
- 셸(Shell, 껍질)이라는 건 OS에 명령을 내리기 위해 만든 사용자의 인터페이스 (<=> 커널(Kernel, OS의 핵심 기능)과 대비됨.)
- execve 셸코드는 임의의 프로그램을 실행하는 셸코드, 이를 이용해 서버의 셸을 획득할 수도... (보통 그냥 셸코드는 얘를 지칭하는 것.)
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
execve | 0x3b | const char* const* argv | const char* const* argv | const char* const* envp |
// argv: 실행파일에 넘겨줄 인자, envp: 환경변수
- execve 셸코드 컴파일 및 실행
orw 셸코드와 같이 스켈레톤 코드를 이용해서 execve 셸코드를 작성한다.
- execve 셸코드 디버깅
run_sh에 bp 걸어주고,
execve 셸코드니까
rax는 0x3b, rdi(filename)는 0x7fffffffdee0, rsi(argv)는 0x0, rdx(envp)는 0x0으로 저장되어 있다.
objdump를 이용한 shellcode 추출
작성한 셸코드를 byte code(opcode)의 형태로 추출하는 방법.
1. 먼저 어셈블 코드를 작성해 준다.
2.
sudo apt-get install nasm 으로 nasm 설치 후,
// nasm은 다른 OS에서 assembly code를 작성해서 수행하기 위한 assembler 중 하나다.
nasm -f [형식] [파일명]
objdump로 오브젝트 파일을 덤프한다.
-d 옵션으로 디어셈블 해준다.
nasm으로 ELF형식으로 바꾸고,
얘를 objdump로 디어셈블 한다.
objcopy는 기존 obj를 다른 obj로 복사해준다.
objcopy [op] in-file [out-file]
objcopy 옵션 중에
--dump-section [이름]=[파일] ...이 있다.
=> Dump the contents of section [이름] into [파일]이라는데
objcopy --dump-section .text=shellcode.bin shellcode.o
=> shellcode.o를 text 섹션을 shellcode.bin으로 덤프한다는 뜻...인 듯 싶다.
xxd [new filename]
바이너리 데이터를 16진수로 바꾸어주는 명령어
그래서 이렇게 해주면, 셸코드가 HEX 형식으로 나온다.
'System Hacking > DreamHack' 카테고리의 다른 글
DreamHack Wargame shell_basic (0) | 2022.07.28 |
---|---|
DreamHack System Hacking STAGE 3 Tool Installation (0) | 2022.07.11 |
DreamHack System Hacking STAGE 2 Background - Computer Science (0) | 2022.07.08 |