Excuse the ads! We need some help to keep our site up.
Shellcode를 개발하기 전에 다음과 같은 이해가 필요합니다.
우리가 사용중인 많은 프로그램들은 C 언어와 같은 고수준의 언어를 컴파일 과정에 의해 Assembly, Machine code 같은 저수준 언어로 변경된 파일입니다.
C code는 해당 시스템에 맞는 Assembly code로 변환 → 변환된 Assembly code를 Machine code로 표현
이렇게 Machine code 는 메모리에 로드되어 코드를 실행하게 됩니다.
즉, 공격자는 취약성을 이용하여 Shell을 획득하기 위해서는 주수준 언어로 개발 된 코드가 필요합니다.
Shellcode는 Assembly, Machine code 같은 저수준 언어 개발 되어야 합니다.
|
|
|
lazenca0x0@ubuntu:~/$ cat /usr/include/x86_64-linux-gnu/asm/unistd_32.h #ifndef _ASM_X86_UNISTD_32_H #define _ASM_X86_UNISTD_32_H 1 #define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16 #define __NR_break 17 #define __NR_oldstat 18 #define __NR_lseek 19 #define __NR_getpid 20 ... |
lazenca0x0@ubuntu:~/$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h #ifndef _ASM_X86_UNISTD_64_H #define _ASM_X86_UNISTD_64_H 1 #define __NR_read 0 #define __NR_write 1 #define __NR_open 2 #define __NR_close 3 #define __NR_stat 4 #define __NR_fstat 5 #define __NR_lstat 6 #define __NR_poll 7 #define __NR_lseek 8 #define __NR_mmap 9 #define __NR_mprotect 10 #define __NR_munmap 11 #define __NR_brk 12 #define __NR_rt_sigaction 13 #define __NR_rt_sigprocmask 14 #define __NR_rt_sigreturn 15 #define __NR_ioctl 16 #define __NR_pread64 17 #define __NR_pwrite64 18 #define __NR_readv 19 #define __NR_writev 20 ... |
|
|
section .data ; 데이터 세그먼트 msg db "Hello, world!",0x0a, 0x0d ; 문자열과 새 줄 문자, 개행 문자 바이트 section .text ; 텍스트 세그먼트 global _start ; ELF 링킹을 위한 초기 엔트리 포인트 _start: ; SYSCALL: write(1,msg,14) mov eax, 4 ; 쓰기 시스템 콜의 번호 '4' 를 eax 에 저장합니다. mov ebx, 1 ; 표준 출력를 나타내는 번호 '1'을 ebx에 저장합니다. mov ecx, msg ; 문자열 주소를 ecx에 저장니다. mov edx, 14 ; 문자열의 길이 '14'를 edx에 저장합니다. int 0x80 ; 시스템 콜을 합니다. ; SYSCALL: exit(0) mov eax, 1 ; exit 시스템 콜의 번호 '1'을 eax 에 저장합니다. mov ebx, 0 ; 정상 종료를 의미하는 '0'을 ebx에 저장 합니다. int 0x80 ; 시스템 콜을 합니다. |
lazenca0x0@ubuntu:~/ASM$ nasm -f elf ASM32.asm lazenca0x0@ubuntu:~/ASM$ ld -m elf_i386 -o hello ASM32.o lazenca0x0@ubuntu:~/ASM$ ./hello Hello, world! lazenca0x0@ubuntu:~/ASM$ file ./hello ./hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped lazenca0x0@ubuntu:~/ASM$ |
sudo apt-get install nasm |
section .data ; 데이터 세그먼트 msg db "hello, world!",0x0a, 0x0d ; 문자열과 새 줄 문자, 개행 문자 바이트 section .text ; 텍스트 세그먼트 global _start ; ELF 링킹을 위한 초기 엔트리 포인트 _start: ; SYSCALL: write(1,msg,14) mov rax, 1 ; 쓰기 시스템 콜의 번호 '1' 를 rax 에 저장합니다. mov rdi, 1 ; 표준 출력를 나타내는 번호 '1'을 rdi에 저장합니다. mov rsi, msg ; 문자열 주소를 rsi에 저장니다. mov rdx, 14 ; 문자열의 길이 '14'를 rdx에 저장합니다. syscall ; 시스템 콜을 합니다. ; SYSCALL: exit(0) mov rax, 60 ; exit 시스템 콜의 번호 '60'을 eax 에 저장합니다. mov rdi, 0 ; 정상 종료를 의미하는 '0'을 ebx에 저장 합니다. syscall ; 시스템 콜을 합니다. |
lazenca0x0@ubuntu:~/ASM$ nasm -f elf64 ASM64.asm lazenca0x0@ubuntu:~/ASM$ ld -o hello64 ASM64.o lazenca0x0@ubuntu:~/ASM$ ./hello64 hello, world! lazenca0x0@ubuntu:~/ASM$ file ./hello64 ./hello64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped lazenca0x0@ubuntu:~/ASM$ |
BITS 32 ; nasm에게 32비트 코드임을 알린다 call helloworld ; 아래 mark_below의 명령을 call한다. db "Hello, world!", 0x0a, 0x0d ; 새 줄 바이트와 개행 문자 바이트 helloworld: ; ssize_t write(int fd, const void *buf, size_t count); pop ecx ; 리턴 주소를 팝해서 exc에 저장합니다. mov eax, 4 ; 시스템 콜 번호를 씁니다. mov ebx, 1 ; STDOUT 파일 서술자 mov edx, 15 ; 문자열 길이 int 0x80 ; 시스템 콜: write(1,string, 14) ; void _exit(int status); mov eax,1 ;exit 시스템 콜 번호 mov ebx,0 ;Status = 0 int 0x80 ;시스템 콜: exit(0) |
lazenca0x0@ubuntu:~/ASM$ nasm ASM32.s lazenca0x0@ubuntu:~/ASM$ ndisasm -b32 ASM32 00000000 E80F000000 call dword 0x14 00000005 48 dec eax 00000006 656C gs insb 00000008 6C insb 00000009 6F outsd 0000000A 2C20 sub al,0x20 0000000C 776F ja 0x7d 0000000E 726C jc 0x7c 00000010 64210A and [fs:edx],ecx 00000013 0D59B80400 or eax,0x4b859 00000018 0000 add [eax],al 0000001A BB01000000 mov ebx,0x1 0000001F BA0F000000 mov edx,0xf 00000024 CD80 int 0x80 00000026 B801000000 mov eax,0x1 0000002B BB00000000 mov ebx,0x0 00000030 CD80 int 0x80 lazenca0x0@ubuntu:~/ASM$ hexdump -C ASM32 00000000 e8 0f 00 00 00 48 65 6c 6c 6f 2c 20 77 6f 72 6c |.....Hello, worl| 00000010 64 21 0a 0d 59 b8 04 00 00 00 bb 01 00 00 00 ba |d!..Y...........| 00000020 0f 00 00 00 cd 80 b8 01 00 00 00 bb 00 00 00 00 |................| 00000030 cd 80 |..| 00000032 lazenca0x0@ubuntu:~/ASM$ |
lazenca0x0@ubuntu:~/ASM$ python Python 2.7.12 (default, Nov 20 2017, 18:23:56) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> f = open('ASM32','r') >>> data = f.read() >>> data '\xe8\x0f\x00\x00\x00Hello, world!\n\rY\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\xba\x0f\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80' >>> |
#include<stdio.h> #include<string.h> unsigned char shellcode [] = "\xe8\x0f\x00\x00\x00Hello, world!\n\rY\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\xba\x0f\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80"; unsigned char code[]; void main(){ int len = strlen(shellcode); printf("Shellcode len : %d\n",len); strcpy(code,shellcode); (*(void(*)()) code)(); } |
lazenca0x0@ubuntu:~/ASM$ gcc -o shellcode -fno-stack-protector -z execstack --no-pie -m32 shellcode.c test.c:5:15: warning: array 'code' assumed to have one element unsigned char code[]; ^ lazenca0x0@ubuntu:~/ASM$ ./shellcode Shellcode len : 2 Segmentation fault (core dumped) lazenca0x0@ubuntu:~/ASM$ |
|
strcpy()함수가 호출되기 전이기 때문에 code 변수 영역(0x804a074)에는 아무런 값이 저장되어 있지 않습니다.
lazenca0x0@ubuntu:~/ASM$ gdb -q ./shellcode Reading symbols from ./shellcode...(no debugging symbols found)...done. gdb-peda$ disassemble main Dump of assembler code for function main: 0x0804846b <+0>: lea ecx,[esp+0x4] 0x0804846f <+4>: and esp,0xfffffff0 0x08048472 <+7>: push DWORD PTR [ecx-0x4] 0x08048475 <+10>: push ebp 0x08048476 <+11>: mov ebp,esp 0x08048478 <+13>: push ecx 0x08048479 <+14>: sub esp,0x14 0x0804847c <+17>: sub esp,0xc 0x0804847f <+20>: push 0x804a040 0x08048484 <+25>: call 0x8048340 <strlen@plt> 0x08048489 <+30>: add esp,0x10 0x0804848c <+33>: mov DWORD PTR [ebp-0xc],eax 0x0804848f <+36>: sub esp,0x8 0x08048492 <+39>: push DWORD PTR [ebp-0xc] 0x08048495 <+42>: push 0x8048550 0x0804849a <+47>: call 0x8048320 <printf@plt> 0x0804849f <+52>: add esp,0x10 0x080484a2 <+55>: sub esp,0x8 0x080484a5 <+58>: push 0x804a040 0x080484aa <+63>: push 0x804a074 0x080484af <+68>: call 0x8048330 <strcpy@plt> 0x080484b4 <+73>: add esp,0x10 0x080484b7 <+76>: mov DWORD PTR [ebp-0x10],0x804a074 0x080484be <+83>: mov eax,DWORD PTR [ebp-0x10] 0x080484c1 <+86>: call eax 0x080484c3 <+88>: nop 0x080484c4 <+89>: mov ecx,DWORD PTR [ebp-0x4] 0x080484c7 <+92>: leave 0x080484c8 <+93>: lea esp,[ecx-0x4] 0x080484cb <+96>: ret End of assembler dump. gdb-peda$ b *0x080484af Breakpoint 1 at 0x80484af gdb-peda$ r Starting program: /home/lazenca0x0/ASM/shell Shellcode len : 2 Breakpoint 1, 0x080484af in main () gdb-peda$ x/64bx 0x804a074 0x804a074 <code>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a07c: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a084: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a08c: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a094: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a09c: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a0a4: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a0ac: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 gdb-peda$ gdb-peda$ x/64bx 0x804a040 0x804a040 <shellcode>: 0xe8 0x0f 0x00 0x00 0x00 0x48 0x65 0x6c 0x804a048 <shellcode+8>: 0x6c 0x6f 0x2c 0x20 0x77 0x6f 0x72 0x6c 0x804a050 <shellcode+16>: 0x64 0x21 0x0a 0x0d 0x59 0xb8 0x04 0x00 0x804a058 <shellcode+24>: 0x00 0x00 0xbb 0x01 0x00 0x00 0x00 0xba 0x804a060 <shellcode+32>: 0x0f 0x00 0x00 0x00 0xcd 0x80 0xb8 0x01 0x804a068 <shellcode+40>: 0x00 0x00 0x00 0xbb 0x00 0x00 0x00 0x00 0x804a070 <shellcode+48>: 0xcd 0x80 0x00 0x00 0xe8 0x0f 0x00 0x00 0x804a078: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 gdb-peda$ |
gdb-peda$ ni 0x080484b4 in main () gdb-peda$ x/32bx 0x804a074 0x804a074 <code>: 0xe8 0x0f 0x00 0x00 0x00 0x00 0x00 0x00 0x804a07c: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a084: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a08c: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 gdb-peda$ |
0xE80F000000 : call dword 0x14
lazenca0x0@ubuntu:~/ASM$ ndisasm -b32 ASM32 00000000 E80F000000 call dword 0x14 00000005 48 dec eax 00000006 656C gs insb 00000008 6C insb 00000009 6F outsd 0000000A 2C20 sub al,0x20 0000000C 776F ja 0x7d 0000000E 726C jc 0x7c 00000010 64210A and [fs:edx],ecx 00000013 0D59B80400 or eax,0x4b859 00000018 0000 add [eax],al 0000001A BB01000000 mov ebx,0x1 0000001F BA0F000000 mov edx,0xf 00000024 CD80 int 0x80 00000026 B801000000 mov eax,0x1 0000002B BB00000000 mov ebx,0x0 00000030 CD80 int 0x80 lazenca0x0@ubuntu:~/ASM$ |
BITS 32 ; nasm에게 32비트 코드임을 알린다 jmp short last ; 맨 끝으로 점프한다. helloworld: ; ssize_t write(int fd, const void *buf, size_t count); pop ecx ; 리턴 주소를 팝해서 exc에 저장합니다. mov eax, 4 ; 시스템 콜 번호를 씁니다. mov ebx, 1 ; STDOUT 파일 서술자 mov edx, 15 ; 문자열 길지 int 0x80 ; 시스템 콜: write(1,string, 14) ; void _exit(int status); mov eax,1 ;exit 시스템 콜 번호 mov ebx,0 ;Status = 0 int 0x80 ;시스템 콜: exit(0) last: call helloworld ; 널 바이트를 해결하기 위해 위로 돌아간다. db "Hello, world!", 0x0a, 0x0d ; 새 줄 바이트와 개행 문자 바이트 |
lazenca0x0@ubuntu:~/ASM$ nasm ASM32-2.s lazenca0x0@ubuntu:~/ASM$ ndisasm -b32 ASM32-2 00000000 EB1E jmp short 0x20 00000002 59 pop ecx 00000003 B804000000 mov eax,0x4 00000008 BB01000000 mov ebx,0x1 0000000D BA0F000000 mov edx,0xf 00000012 CD80 int 0x80 00000014 B801000000 mov eax,0x1 00000019 BB00000000 mov ebx,0x0 0000001E CD80 int 0x80 00000020 E8DDFFFFFF call dword 0x2 00000025 48 dec eax 00000026 656C gs insb 00000028 6C insb 00000029 6F outsd 0000002A 2C20 sub al,0x20 0000002C 776F ja 0x9d 0000002E 726C jc 0x9c 00000030 64210A and [fs:edx],ecx 00000033 0D db 0x0d lazenca0x0@ubuntu:~/ASM$ |
64 bit, 32bit, 16 bit 레지스터에 표현 가능한 값보다 작은 값을 저장하게 되면 나머지 공간은 null byte로 채워지게 됩니다.
|
|
|
|
BITS 32 ; nasm에게 32비트 코드임을 알린다 jmp short last ; 맨 끝으로 점프한다. helloworld: ; ssize_t write(int fd, const void *buf, size_t count); pop ecx ; 리턴 주소를 팝해서 exc에 저장합니다. xor eax,eax ; eax 레지스터의 값을 0으로 초기화합니다. mov al, 4 ; 시스템 콜 번호를 씁니다. xor ebx,ebx ; ebx 레지스터의 값을 0으로 초기화합니다. mov bl, 1 ; STDOUT 파일 서술자 xor edx,edx ; edx 레지스터의 값을 0으로 초기화합니다. mov dl, 15 ; 문자열 길지 int 0x80 ; 시스템 콜: write(1,string, 14) ; void _exit(int status); mov al,1 ;exit 시스템 콜 번호 xor ebx,ebx ;Status = 0 int 0x80 ;시스템 콜: exit(0) last: call helloworld ; 널 바이트를 해결하기 위해 위로 돌아간다. db "Hello, world!", 0x0a, 0x0d ; 새 줄 바이트와 개행 문자 바이트 |
lazenca0x0@ubuntu:~/ASM$ nasm RemoveNullbyte.s lazenca0x0@ubuntu:~/ASM$ ndisasm RemoveNullbyte 00000000 EB15 jmp short 0x17 00000002 59 pop cx 00000003 31C0 xor ax,ax 00000005 B004 mov al,0x4 00000007 31DB xor bx,bx 00000009 B301 mov bl,0x1 0000000B 31D2 xor dx,dx 0000000D B20F mov dl,0xf 0000000F CD80 int 0x80 00000011 B001 mov al,0x1 00000013 31DB xor bx,bx 00000015 CD80 int 0x80 00000017 E8E6FF call word 0x0 0000001A FF db 0xff 0000001B FF4865 dec word [bx+si+0x65] 0000001E 6C insb 0000001F 6C insb 00000020 6F outsw 00000021 2C20 sub al,0x20 00000023 776F ja 0x94 00000025 726C jc 0x93 00000027 64210A and [fs:bp+si],cx 0000002A 0D db 0x0d lazenca0x0@ubuntu:~/ASM$ |
#include<stdio.h> #include<string.h> unsigned char shellcode [] = "\xeb\x15\x59\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x31\xd2\xb2\x0f\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xe6\xff\xff\xffHello, world!\n\r"; unsigned char code[] = ""; void main() { int len = strlen(shellcode); printf("Shellcode len : %d\n",len); strcpy(code,shellcode); (*(void(*)()) code)(); } |
lazenca0x0@ubuntu:~/ASM$ gcc -o shellcode2 -fno-stack-protector -z execstack --no-pie -m32 shellcode2.c lazenca0x0@ubuntu:~/ASM$ ./shellcode2 Shellcode len : 43 Hello, world! lazenca0x0@ubuntu:~/ASM$ |