-
[Windows] ALMind 1day AnalysisSystem/Windows 2019. 3. 8. 01:13
BoB Windows Software Fuzzing&Exploit 수업에서
ESTSoft사의 ALmind를 타겟으로 잡고 Fuzzing&Exploit을 진행했다.
그때 내용을 다시 상기시키면서 Exploit과정을 정리해봤다.
[Crash파일 & 정리문서]
[ ALmind Version ]
우선 1day Exploit을 진행할 제품의 버전은 1.32버전이다.
ALmind Lite버전으로 무료로 제공했을 때 사용하던 제품인 것 같다.
[ 취약점 패치 ]
1.32버전에는 Stack Overflow 취약점이 존재한다.
1.3 ~ 1.71까지 존재했던 걸 보면 꽤 오랫동안 발견하지 못했나보다.
취약점은 발생하는 이유는 아래와 같다.
'.emm'이라는 알마인드 확장자에서 파일이름의 길이값을 관리하는 헤더부분을 제대로 체크하지 않았고,
이 값을 MAX값으로 수정하면 프로그램이 파일이름을 파싱하는 과정에서 Stack Overflow가 발생하게 된다.
어디서 발생하는지 이미 나와있지만, 나는 이 취약점을 Fuzzing을 통해서 찾아보고자 한다.
Fuzzing은 Dumb방식과 Smart방식 크게 두가지로 나뉜다.
모든 User_Input에 말도 안되는 값들을 넣어서 Crash가 발생하는지 테스트하는 것이 Dumb방식이고,
무작위로 값을 넣는게 아니라 값과 Input위치를 정해서 좀더 정교하게 테스트하는 것이 Smart방식이다.
정의만 놓고보면 Smart방식을 채택하는게 무조건 좋아보이지만 그렇지도 않다.
Dumb방식은 마구잡이로 Input을 넣는 것이기 때문에 예상치 못한 곳에서 Crash가 발생할 수 있다.
하지만, Smart방식은 우리가 정해놓은 틀이 있기 때문에 Dumb에서 찾은걸 Smart로는 못 찾을 수가 있다.
따라서, 둘중에 뭐가 더 좋은지 정하기 보다는 각 상황에 맞는 방식을 채택해서 진행하면 되겠다.
[ Crash Info ]
프로그램의 오류가 발생하게 되면 그 오류에 대한 Event_code를 알아낼 수 있다.
대부분의 Fuzzer들은 Event_code가 Access Violation과 같은 Memory Corruption이라면,
해당 Crash가 발생된 위치와 레지스터 상태등의 상세내용을 파일로 기록하도록 만들어 놓는다.
위 내용을 보면 Security Risk Level이 Exploitable하다고 나와있다.
이것을 보고 취약점의 정도를 파악한다.
이 정보를 무조건적으로 신뢰해서는 안되고, 참고하는 용도로만 알고있는게 좋다.
[ Debugger Setting ]
분석하는 과정에서 디버거를 여러 번 껐다 켜야하기 때문에, 배치파일을 미리 만들어 놓는게 좋다.
이렇게 설정해 놓으면, 명령 프롬프트창에서 1.bat만 입력해서 Crash파일을 로드할 수 있다.
디버거는 Windbg를 사용했다.
[ Analysis#1 ]
'sxd ld'명령어로 모듈단위 Exception을 비활성화 하고 어떤 모듈에서 Crash가 발생했는지 확인한다.
'msxml3.dll'이 로드된 후, Crash가 발생했으니 해당 모듈로 분석범위를 좁힐 수 있다.
[ Analysis#2 ]
'sxe ld'명령어로 아까 확인한 'msxml3.dll'모듈에 Break Point를 걸어서 해당 모듈부터 분석을 시작한다.
[ BP Script ]
해당 Crash의 원인은 Access Violations이다.
이 원인이 무엇을 의미하냐면,
어떠한 메모리에 데이터를 저장하다가 잘못된 메모리를 참조하여 발생했다는 것이다.
메모리에 데이터를 저장하기 위해서는
scanf( ), read( ), fread( ), fgets( )와 같은 입력함수들을 사용해야 하는데,
이 함수들은 memcpy( )로 부터 파생된 함수들이다.
따라서, memcpy( )가 실행됐을 때의 상황만 보면 된다.
그리고 Windbg에서는 BreakPoint가 걸릴때마다 스크립트를 실행할 수 있는 편리한 기능이 있는데,
이 스크립트를 이용해 memcpy( )가 실행될 때의 설정된 인자값들을 확인해보도록 하자
[ Analysis#3 ]
쭉 실행되다가 Size값으로 3408값이 넘어간 시점에서 Crash가 발생한 것을 확인할 수 있다.
EIP도 처음 Report에 기록된 주소인 [0x616D2F73]으로 Crash가 발생한 위치를 찾아냈다.
[ Analysis#4 ]
Break Point를 Crash발생하기 직전으로 설정하고 그때의 콜스택을 확인해보도록 하자
스크립트는 'Size값이 3408이 아니면 쭉 실행하라'를 의미한다.
[ Analysis#5 ]
Call Stack을 확인해보면 ALmind+0x4359에서 fread( )를 사용할 때, Size값이 3408로 넘어가서
Crash가 발생했다는 것을 알아낼 수 있다.
그리고 노란박스에서 fread( )가 실행되는 바이너리가 HMindmapApp.dll임을 알 수 있다.
이제 IDA로 HMindmapApp.dll을 로드해서 HMindmapApp.dll+0x4359 부분을 분석해보도록 하자
[ Analysis#6 ]
Base Address에 위에서 확인한 0x4359를 더해서 해당위치를 찾아가보면
실제로 fread( )가 사용되고 있는걸 확인할 수 있다.
[ Analysis#7 ]
fread( )로 넘어오는 인자들을 확인해보면
문제가 됐던 size값은 [ESI+28]의 저장된 2Byte를 참조해서 넘어오고
데이터는 [ESI+46]부터 저장되게 된다.
모든 값이 ESI를 중심으로 넘어오고 있으니, 위로 올라가면서 값이 어디서 어떻게 넘어오는지 확인을 해야한다.
우선, RET가 위치한 Stack Frame이 어디인지 확인할 필요가 있다.
Stack Frame을 정확히 판단해야 [ESI+46]에서 RET까지의 정확한 offset을 구할 수 있다.
[ Stack Frame 확인 ]
ESI값에 최초로 주소가 저장되는건 이전함수의 'lea esi, [esp+60]' Instruction이다.
따라서, 우리가 핸들링할 RET는 이전함수의 RET라는 것을 확인했다.
이제는 RET까지의 offset을 구해야 한다.
[ RET offset 구하기#1 ]
우선 RET공간의 주소를 알아내야 한다.
함수의 복귀주소가 저장되는 공간은 함수프롤로그 직전의 ESP공간임으로 +0x33F0에 BP를 설정해서
이 때의 ESP를 확인하면 된다.
[ RET offset 구하기#2 ]
확인해보면 RET는 [0x13E4FC]에 저장되고, Input이 저장되는 주소는 [0x13E3E6]으로
[0x13E4FC] - [0x13E3E6] = 0x116(278) 인것을 알아낼 수 있다.
따라서, RET까지의 거리는 278Byte이다.
이제 필요한 정보를 모두 알아냈으니, 맞는지 확인해보도록 하자
[ Analysis Check#1 ]
우선 정상적인 파일을 하나 만들어서 Windbg에 로드시켜준다.
그리고 fread( )까지 실행시켜준 뒤, 읽어온 데이터를 생성한 파일에서 찾아주면 된다.
[ Analysis Check#2 ]
저 값이 우리가 핸들링할 수 있는 Input값이고, 저 부분이 [ESI+46]부분이니까
18을 빼준 위치가 [ESI+28]인 Size값이 된다.
2Byte를 Size값으로 사용하고 있으니, 최대값인 [FF FF]로 바꿔준 뒤,
[ESI+46] + 0x116(278) 해준 위치에 [41414141]로 값을 덮어씌어 준다.
이렇게 되면, [ESI+46]으로부터 0xFFFF(65535)만큼 읽어오게 되면서 Buffer Overflow가 발생하게 되고,
RET에 [41414141]이 덮어 씌여지게 된다.
[ Analysis Check#3 ]
Windbg에 로드해서 확인해보면, 정확히 RET가 [41414141]로 변조된 것을 확인할 수 있다.
[ 좀 더 깊이있게 ]
이번엔 공격자의 시점이 아닌 보안개발자의 시점에서 이 상황을 분석해보면서
취약점이 왜 발생했고, 어떻게 패치해야 할지 생각해보자
[ Custom 확장자 ]
알마인드에서 사용하는 .emm은 자체적으로 만든 확장자이지만
내부적으로 .ZIP파일과 동일한 구조를 가지고 있다.
파일 시그니쳐를 확인해보면 'PK..'(504B0304)인 것을 확인할 수 있다.
[ Data Template ]
헥스에디터 프로그램 중 '010Editor'라는 프로그램이 있는데,
이 프로그램에서는 ZIP Template에 맞게 데이터들을 구분해 준다.
[ 취약점 발생 원인 ]
우리가 [0xFFFF]로 조작했던 부분을 살펴보면
FileName의 길이값을 정의하는 헤더였던 것을 알 수 있다.
정리해보면, 개발자가 파일의 길이값은 제대로 들어올 것이라고 사용자를 신뢰하고 있었고,
Microsoft에서 설정할 수 있는 파일이름의 최대길이값도 260글자 내외 였으니
더더욱 신경쓰지 못한 상황이였을 것이다.
취약점패치는 파일이름의 길이값을 체크해주는 분기문을 추가해주면 충분할 것 같다.
다음 글에서는 해당 취약점을 Mona툴을 이용해서 Exploit하는 내용을 정리해보도록 하겠다 :)
'System > Windows' 카테고리의 다른 글
Universal Calc Shellcode (+ExitProcess) (0) 2020.01.26 Notepad++_v7.6.3 Command Injection (1) 2019.06.13 [Windows] ALMind 1day Exploit (1) 2019.03.08 댓글