지난 강좌에서 Buffer Overflows 공격의 원리에 대해서 간단한 샘플코드를 이용하여 살펴보았다. 이번 강좌에서는 주어진 크기의 데이터 공간에 위치하지 못한 데이터가 메모리에 쓰여져 시스템의 임의의 명령을 실행시키도록 하는 코드 즉, 쉘코드(shellcode)를 작성하는 방법을 살펴보도록 한다.
Shellcode란? Shellcode란 시스템의 특정 명령을 실행하도록 하는 기계어 코드로 기본적으로 어셈블리(Assembly) 언어로 작성된 프로그램을 이진 덤프(binary dump)함으로서 얻을 수 있으며, Buffer Overflow 공격에서 shellcode는 시스템에서 할당한 기억공간(Buffer)보다 큰 데티어로 메모리의 특정 위치에 저장되어 공격자에 의도하는 결과를 이끌어낸다.
Shellcode와 레지스터(Register), 어셈블리(Assembly) Buffer Overflow 공격 시 공격자가 취약한 시스템으로 전송한 shellcode는 CPU의 레지스터 영역에 쓰여지게(Overwrite) 된다. 시스템의 레지스터는 여러 가지가 있으나 Shellcode는 이중 EAX, EBX, ECX, EDX 레지스터 영역에 쓰여져 시스템의 정상적인 실행 명령대신 shellcode가 의미하는 명령을 실행하게 한다. 이렇게 시스템의 레지스터에 실행명령을 overwrite되는 shellcode는 사람이 이해할 수 없는 기계어로 작성되는데 이를 위해서 Assembly 언어로 작성된 프로그램을 이진 덤프(Binary dump)하여 얻을 수 있다.
여기서 간단하게 Assembly 언어를 이용하여 임의의 명령을 실행하는 방법을 알아보자면, 임의의 명령어가 실행하기 전에 명령어(정확하게 표현하자면 '시스템 호출')를 식별할 수 있는 코드가 eax 레지스터에 기억되고 나머지 abx, ecs, edx 레지스터에는 시스템 호출 시 넘겨지는 매개변수 값이 저장된다. eax ~ edx 레지스터에 시스템 호출에 필요한 값들을 저장하였으면, 그 다음으로는 0x80 인터럽트를 발생시켜 cpu가 레지스터에 저장된 값들을 가지고 시스템 호출을 실행한다.
Shellcode 예제 1 - exitshell Shellcode 작성의 첫번째 예제는 프로그램을 종료하는 exit() 함수를 shellcode로 작성하여 shellcode 작성법을 살펴보도록 본다.
Shellcode도 다른 일반 프로그램과 마찬가지로 함수들의 조합으로 작성되는 프로그램으로 함수를 호출하기 위해서는 아래의 두 가지 사항을 알아야 한다.
● 호출하려는 함수의 시스템 호출 방법 √ Unix Like : 시스템 호출 번호 √ Windows : 시스템 호출의 메모리 주소 ● 시스템 호출의 매개변수 값
먼저 시스템 호출 방법은 Unix 시스템과 Windows 시스템과 차이가 있는데, Linux 등과 같은 Unix like 시스템 호출을 정의한 헤더파일에서 시스템 호출번호를 이용하여, Windows 계열의 운영체제에서는 kernel23.dll을 별도의 도구를 이용하여 호출하려는 시스템의 주소를 이용한다.
[그림 1 : /usr/include/asm/unistd.h에 정의된 시스템 호출]
[그림 2 : dumpbin 프로그램을 이용한 kernel32.dll의 메모리 정보]
[그림 3 : dumpbin 프로그램을 이용한 kernel32.dll내 시스템 호출 메모리 정보]
첫 번째 예제로 작성할 exitshell을 Linux 시스템에서 C언어로 작성한다면 다음과 같은 함수가 된다.
위 C언어로 작성된 프로그램과 동일하게 실행하는 프로그램을 어셈블리 언어로 작성하기 위해서는 아래의 사항에 대한 확인이 필요하다.
● exit() 함수의 시스템 호출번호 ● exit() 함수 호출시 매개변변수 값
위 두 가지 중 exit() 함수에 대한 호출번호는 /usr/include/asm/unistd.h 파일에서 확인하면 '1'로 정의되어 있으며, 매개변수 값은 오류 없이 실행이 종료되었음을 의미하도록 '0'으로 설정한다.
이렇게 확인한 정보들을 이제는 레지스터에 저장하는데 각 사항을 저장하는 위치는 아래와 같다.
● eax 레지스터 : exit() 함수의 시스템 호출번호 ● ebx 레지스터 : exit() 함수 호출시 매개변변수 값
이제까지의 내용을 어셈블리 언어로 작성하여 완성된 코드는 아래와 같다.
exitshell.s
● Line 03 ~ 04: eax 레지스터에 exit() 시스템 호출 번호 '1' 저장 ● Line 05 ~ 06 : ebx exit() 시스템 호출 매개변수 값 '0' 저장 ● Line 07 : exit(0) 실행을 위한 인터럽트 발생
작성된 코드를 컴파일 및 링크시키고 정상적인 실행을 확인한 후 생성된 실행 파일을 덤프하면 최종적인 shellcode를 얻을 수 있다.
[그림 4 : exitshell.s의 실행 및 이진덤프를 통한 shellcode 작성]
이번 강좌에서는 간단한 함수 호출을 통해서 어셈블리 언어 프로그래밍과 이를 통하여 shellcode를 생성하는 단계까지 소개하였다.
다음 강좌에서는 shellcode 작성 시 유의하여 할 사항 및 shellcode 작성에 대해서 좀더 구체적으로 살펴보는 시간을 갖도록 하자.
- 끝 - |