-
dreamhack stage 4 - Exploit Tech:Shellcode보안/SYSTEM HACKING 2024. 3. 15. 16:05
0. 서론
- 익스플로잇(Exploit)
:해킹 분야에서 상대 시스템을 공격하는 것/ 부당하게 이용하다 라는 의미
상대 시스템에 침투해 시스템을 악용
시스템해킹의 익스플로잇과 9가지 공격기법을 소개할 것이다.
1. 셸코드(Shellcode)
: 익스플로잇을 위해 제작된 어셈블리 코드 조각
- 시스템해킹에서의 셸 획득 중요성 - execve 셸코드
해커가 rip를 자신이 작성한 셸코드로 옮길 수 있으면 해커는 원하는 어셈블리 코드가 실행되게 할 수 있다.
- -> 어셈블리어는 모든 명령을 CPU에 내릴 수 있다.
- 셸코드의 구성과 목적
- 셸코드는 어셈블리어로 구성된다.
- 공격 수행 대상 아키텍처와 운영체제, 셸코드의 목적에 따라 다르게 작성된다.
- 공유되는 셸코드는 범용적으로 작성되어 시스템 환경을 완전히 반영할 수 없다
--> 최적의 셸코드: 직접 작성해야 한다.
2. orw 셸코드
: 파일을 열고 읽은 후 화면에 출력해주는 셸코드
- orw 셸코드를 작성하기 위해 알아야 하는 syscall
- 예제: /tmp/flag 를 읽는 셸코드 작성하기
구현하려는 셸코드의 동작을 C언어 형식의 의사코드로 표현한다.
예제 풀이 1. /tmp/flag 라는 문자열 메모리에 위치시키기
-- int fd = open("/tmp/flag", O_RDONLY, NULL) --
- 스택에 0x616c662f706d742f67(/tmp/flag) 를 push 한다.
- rdi가 가리키도록 rsp를 rdi로 옮긴다.
- rsi는 0으로 설정한다. O_RDONLY는 0이기 때문이다.
- rdx는 0으로 설정한다. 파일을 읽을 때 mode는 의미를 갖지 않기 때문이다.
- rax를 open의 syscall 값인 2로 설정한다.
구현모습)
예제 풀이 2. read(fd, buf, 0x30)
syscall의 반환값은 rax로 저장된다. open으로 획득한 /tmp/flag의 fd는 rax에 저장된다.
- rax를 rdi에 대입한다. read의 첫 번째 인자를 이 값으로 설정해야 하기 때문이다.
- rsi에 rsp-0x30을 대입한다. rsi는 파일에서 읽은 데이터를 저장할 주소를 가리키는데 0x30만큼 읽을 것이기
때문이다.
- rdx는 파일로부터 읽어낼 데이터의 길이인 0x30으로 설정한다.
- rax는 0으로 설정한다. read 시스템콜을 호출하기 위해서이다.
구현모습)
+ fd(File Descriptor, 파일서술자)
: 유닉스 계열의 운영체제에서 파일에 접근하는 소프트웨어에 제공하는 가상의 접근 제어자
- 프로세스마다 고유의 서술자 테이블을 가지고 있고 그 안에 여러 파일 서술자를 저장한다.
- 서술자는 번호로 구별되는데
0: 일반 입력
1: 일반 출력
2: 일반 오류 이다. 프로세스를 터미널과 연결된다.
예제 풀이 3. write(1, buf, 0x30)
- rdi 는 0x1로 설정한다. 출력은 stdout으로 할 예정이기 때문이다.
- rsi와 rdx는 read에서 사용한 값을 그대로 사용한다.
- rax는 1로 설정한다. write 시스템콜을 호출하기 위해서이다.
구현모습)
예제 풀이 요약)
- orw 셸코드 컴파일 및 실행
대부분의 운영체제는 실행 가능한 파일의 형식을 규정하고 있다.
리눅스의 ELF(Executable and Linkable Format)는
헤더 - 실행에 필요한 여러 정보가 적혀있음,
코드 - CPU가 이해할 수 있는 기계어 코드가 적혀있음,
기타데이터로 구성되어있다.
아까 작성한 셸코드는 어셈블리 코드라서 ELF 형식이 아니다 --> 리눅스에서 실행할 수 없다. gcc 컴파일을 통해 ELF형식으로 변형하자.
- 컴파일
방법: 스켈레톤 코드를 C언어로 작성 -> 셸코드 탑재
+ 스켈레톤 코드: 핵심이 비어있고 기본 구조만 갖춘 코드
- 실행
셸코드가 실제로 작동하도록 /tmp/flag 파일 생성
echo "flag{this_is_open_read_write_shellcode!}" > /tmp/flag
orw.c를 컴파일하고 실행한다.
저장한 문자열 출력
저장한 문자열 외에도 다른 문자열이 함께 출력되었다.
- orw 셸코드 디버깅
- 셸코드 동작을 분석해보자: orw를 gdb로 열고 run_sh)함수에 브레이크 포인트 설정
- run 명령어로 run_sh()함수의 시작부분까지 코드 실행: rip 위치한 것 확인 가능
1. int fd = open("/tmp/flag", O_RDONLY, NULL)
첫번째 syscall전까지 실행하고 syscall에 들어가는 인자를 확인한다.
pwndbg 플러그인은 syscall을 호출할 때 인자를 분석해준다.
open("/tmp/flag", O_RDONLY, NULL) 가 실행되는 것을 확인한다.
open 시스템 콜 수행 결과로 rax에 /tmp/flag의 fd(3)이 저장된다.
2. read(fd. buf, 0x30)
- 두번째 syscall 직전까지 실행 후 인자를 본다.
/tmp/flag의 fd(3)에서 데이터를 0x30바이트만큼 읽어 0x7fffffffc278에 저장한다.
실행 결과를 x/s로 확인해보면 문자열이 성공적으로 저장되었다.
3. write(1, buf, 0x30)
- 읽어낸 데이터를 출력하는 write 시스템 콜을 실행한다.
- 데이터를 저장한 0x7fffffffc278에서 48바이트를 출력한다.
아까 나온 예제처럼 다른 문자열이 포함되어 출력되었다. 초기화되지 않은 메모리 영역 사용 때문이다.
+ 초기화 되지 않은 메모리 사용(Use of Uninitialized Memory)
- 스택에서의 해제: rsp와 rbp를 호출한 함수의 것으로 이동한다. 어떤 함수를 해제한 이후 다른 함수가 스택 프레임을 위에 할당하면 이전 스택 프레임의 데이터는 여전히 새로 할당한 스택 프레임에 존재하고 이 값을 쓰레기 값이라고도 한다.
- Appendix. Uninitialized Memory
- 파일을 읽어서 스택에 저장했고, 스택의 영역을 다시 확인해보자.
48바이트 중 40바이트만 저장한 파일의 데이터이고 나머지 8바이트는 저장하지 않은 데이터이다. 나중에 write 시스템콜을 수행할 때 플래그와 함게 출력된다.
+ 메모리 릭(Memory Leak): 어셈블리 코드의 주소와 같은 중요한 값을 유출해내는 것
3. execve 셸코드
- 셸과 커널
- 셸(Shell, 껍질): 운영체제에 명령을 내리기 위해 사용되는 사용자의 인터페이스
<--> 커널(Kernel, 호두 속 내용물): 운영체제의 핵심 기능을 하는 프로그램
+ 셸 획득 == 시스템해킹의 성공
- execve 셸코드
: 임의의 프로그램을 실행하는 셸코드
execve 셸코드로 서버의 셸을 획득할 수 있다.
리눅스 계층
리눅스 대부분 sh, bash를 기본 셸 프로그램으로 탑재하고 있고 zsh, tsh 등을 설치할 수 있다. 우분투에서 execve 셸코드를 작성해보자.
- execve("/bin/sh", null, null) <- 실행 목표
execve 셸코드는 execve 시스템 콜만으로 구성된다.
- argv: 실행파일에 넘겨줄 인자
- envp: 환경변수
sh만 실행할 것이기 때문에 나머지는 null로 해도 된다.
리눅스 기본 실행 프로그램 (sh 포함)이 /bin/ 디렉토리에 저장되어 있다.
- execve 셸코드 컴파일과 실행
sh가 실행되는 것을 볼 수 있다.
- objdump를 이용한 셸코드 추출
셸코드를 byte code(opcode) 형태로 추출하는 방법의 예시이다.
shellcode.o (1단계)
shellcode.bin (2단계)
'보안 > SYSTEM HACKING' 카테고리의 다른 글
dreamhack stage 3 - Tool:pwntools (0) 2024.03.15 dreamhack stage 3 - Tool:gdb (0) 2024.03.15 dreamhack stage 2 - x86 Assembly (0) 2024.03.15 dreamhack stage 2 - Computer Architecture (0) 2024.03.15 dreamhack stage 2 - Linux Memory Layout (0) 2024.03.15