서문
이번 포스팅은 위 글의 원문을 따라하면서 공부해보는 포스팅입니다.
Part1에서 바로 Part3로 넘어갔는데, Part2는 기존 bof에서 shellcode로 점프하는 다양한 방법들을 소개를 합니다.
물론 자만하면 안되지만, 약간 어느정도 아는 내용이기도 하고 해서 잘 모르는 SEH관련 내용인 part3로 바로 넘어왔습니다.
저번 포스팅에서 Windows XP및 어느정도 개발 환경을 구축을 한 상태이므로, 거기서 추가적으로 환경 구축을 해야 하는 것들만 설명하면서 넘어가도록 하겠습니다.
이번 포스팅에서는 SEH Based Exploit이라는 새로운 개념을 이용한 공격 코드를 작성하는 것을 다루는데, SEH는 Windows에서만 있습니다. 아마 리눅스에도 비슷한 역할을 하는 녀석이 있겠지만 SEH라는 이름과 매커니즘은 윈도우즈 계열 OS에서만 쓰이는 녀석이라고 할 수 있겠지요.
따라서 저처럼 리눅스 Exploit을 공부하다가 윈도우로 넘어오신 분들은 생소하실 것이라 생각합니다.
환경설정
Windows XP에서 동작하는 C 컴파일러 설치
중간에 예제코드를 빌드한 뒤 확인해보는 과정이 있어서 C 컴파일러를 하나 구해야 한다.
구글링을 해보니, MSVC++ 2008 Express가 Windows XP에서 동작한다고 한다.
맨 위의 링크를 누르니, 바로 exe파일이 다운로드 된다. 마소에서 공식적으로는 지원하지 않는데 링크는 남아있어서 받을 수 잇는 모양이다.
Windows XP 가상머신에서 실행해서 설치를 해보자.
다행히 되는 것 같은 느낌이다.
조금 오래 걸리는 것 같긴한데, 그래도 되는게 어딘가 하는 마음으로 기다리고 있다.
좀 기다려 보니 설치가 잘 되었다.
ollydbg (올리디버거) 설치
구글링하면 올리디버거 공식홈페이지에서 다운로드 받을 수 있다.
최신버전인 1.10 버전을 받아야지 플러그인을 사용할 수 있어서, 최신버전을 받았다.
XP에서 압축을 풀고 실행해보았다.
Ollygraph
그리고 코어랜 블로그에서 추천하는 올리디버거 플러그인인 ollygraph를 설치해보자.
근데 wingraph32.exe라는 IDA에 있는 분석 툴이 있어야 한다.
후 이젠 구버전 IDA까지 구해야 한다니 이건 좀 에반거같긴하다.
못구했기 때문에 일단 안쓰는것도 괜찮을듯 ㅠ
그래도 일단 플러그인 설치를 하자면 아래와 같이 진행하면된다.
http://www.openrce.org/downloads/details/173/OllyGraph
OpenRCE라는 곳에서 다운받을 수 있다.
압축을 푼 다음 위에 있는 ollygraph 안에 있는 모든 파일들을 Ollydbg가 있는 폴더에 다 같이 넣어주면 플러그인이 설치된다고 한다.
올리디버거를 재시작 하면 Plugins에 Ollygraph가 보이게 된다.
SEH(Structured Exception Handling)이란?
구글에 SEH를 치면 꽤 많은 자료가 나오긴 합니다. 위키백과에도 한글로 된 문서가 있네요. Microsoft에서 예외 처리를 하는 루틴이라고 볼 수 있습니다. 그리고 이 SEH라는 예외 처리 매커니즘은 라이선스에 특허권이 있어서 오픈소스 운영체제들은 이 SEH 기반 매커니즘을 사용하지 않는다고 합니다. 따라서 리눅스에서 하는 방식과 다를 수 밖에 없군요.
SEH에 대한 자세한 설명을 한 글들은 다른데도 많을 것으로 생각됩니다. 저는 상단에 표기된 코어랜 튜터리얼을 따라하는 것에 포커스를 맞추어 보도록 하겠습니다.
예외 처리(Exception handling)이란?
프로그램이 실행중에는 여러 예외가 나타날 수 있지요. 0으로 나눈다던지, 스택오버플로가 나서 segmentation fault가 난다던지 등등이 일어날 수 있습니다.
어플리케이션단에선 try ~ catch 구문으로 어느정도 예외를 처리할 수 있는데, 이 앱단에서 예외를 다 처리하지 못하면 OS단으로 넘어가게 됩니다.
이 OS단에서 예외를 처리할 때 윈도우는 SEH라는 것에 기반해서 처리를 하게 됩니다.
근데 이 예외 처리하는 루틴들의 주소가 스택에 저장이 되게 됩니다.
코어렌 블로그에서 퍼온 이미지입니다.
Local vars에서 buffer overflow가 나게 되면, 지역변수 밑에 있는 saved ebp, saved eip(ret addr) 및 함수 인자들과 이런 것들을 다 덮어쓰게 되는데, 맨 마지막에 address of exception handler가 보이지요?
요녀석 까지 원하는 다른 값으로 덮어쓰게 되면, 나중에 Exception이 발생했을때 내가 원하는 루틴으로 점프를 뛰게 만들 수 있다는 것이 핵심입니다.
SEH로 넘어가게 되면, 무슨무슨 앱이 크래시 나서 꺼졌습니다~ 라는 OS단의 에러메시지가 나게 되는데, 따라서 훌륭한 개발자라면 SEH로 넘어가기 전에 개발 언어 자체에 있는 예외 처리 루틴에서 최대한 처리하도록 해주는게 좋습니다.
기본적으로는 이러한 개발자가 만든 예외 핸들러가 먼저 돌고, 이 녀석들이 다 예외를 제대로 처리하지 못하면 최후의 보루로 Windows OS에서 제공하는 SEH가 쓰이게 되는 것이지요.
SEH 채인 구성요소의 구조
SEH는 스택 프레임의 구성요소입니다. 따라서 스택 프레임에 저장되지요.
대충 구조를 보자면, SEH 구조체는 8바이트 크기이고 4바이트 요소 2개로 구성됩니다.
하나는 다음 SEH 구조체의 주소이고, 하나는 예외 핸들러의 주소입니다. 그래서 만약 지금 핸들러로 예외를 처리하지 못하면 다음 핸들러로 넘어가는 식이고, 따라서 Single Linked list형태가 됩니다.
그리고 이 SEH 체인의 첫번째 주소를 가리키는 포인터는, 앱의 main 함수가 있는 곳의 데이터 블럭, 혹은 TEB(Thread Environment Block, 쓰레드 정보를 가지고 있는 OS 자료구조, TIB; Thread information block라고도 불림)에 저장됩니다.
그리고 요녀석(TEB)은 FS라는 세그먼트 레지스터가 가리키고 있나봅니다. 그래서 SEH 체인은 FS:[0] 체인이라고 불리기도 한답니다.
위키에 안그래도 잘 설명이 되어 있네요. FS:[0]에는 SEH 프레임의 주소가 4바이트로 저장되어있다고 합니다.
FS, GS는 x86 아키텍쳐에서는 그냥 여분의 세그먼트 레지스터인데, Windows 운영체제에서는 쓰레드 관련된 정보들을 가리키는데 사용하는 것 같습니다.
그래서 인텔 CPU머신에서는 SEH 코드를 디스어샘블 해보면, move DWORD ptr from FS:[0]와 같은 명령어를 볼 수 있습니다. 요 명령어로 예외 핸들러가 쓰레드에 설정되고 에러가 났을때 처리할 수 있게 되는 것이지요. 이 명령어의 기계어 opcode는 64A64A100000000 인데요, 만약 이 코드를 못봣다면, 해당 앱이나 쓰레드는 예외 핸들러가 없는 것입니다.
코어랜에서 제공한 예제코드를 한번 빌드를 해보겠습니다.
MSVC++ 2008 Express를 켜서, Win32 Console App 프로젝트를 생성을 합니다.
뭔가 옛날 생각이 새록새록 나네요. 프로젝트 명은 sehtest로 했습니다.
곧이어서 Empty Project에 체크를 하고 Finish를 한 뒤 Hello world를 찍어보겠습니다.
Xp에서 코딩하는 맛이란 참 특이하네요. 그러면 corelan 예제코드를 한번 빌드해보도록 하겠습니다.
빌드를 합니다.
바로 실행을 하니 Exception이 뜹니다. 아마 argv[1]이 존재하지 않아서 그런 것 같네요. Exception handler가 호출이 된 모습도 보입니다.
argv[1]를 넣어서 실행하니 App launched만 뜹니다. 예외가 발생안하고 그런 모습이지요.
일단 그러면 코어랜 블로그에서는 저 바이너리를 디버거 붙여서 보라고 했으니 한번 그렇게 해보도록 하겠습니다.
Windbg를 킨 뒤, File - Open Executable로 sehtest.exe를 열어보았습니다. 실행을 해버리면 크래시가 나니 이 상태만 보라고 하네요.
sehtest.exe는 00400000와 0041b000 범위의 주소에 할당되어 메모리에 올라갔네요. 여기서 64 A1을 한번 찾아보겠습니다.
잘 있네요. Exception Handler가 있다는 걸 알 수 있습니다. TEB를 한번 까봅시다. FS 레지스터가 가리키는 메모리를 보면 됩니다.
0c fd 12 00가 첫번째 SEH 구조체(Record)임을 알 수 있습니다. 엔디안을 고려해서 0x12fd0c가 주소가 되겠지요.
첫번째 SEH 레코드 입니다. 첫 4바이트가 0xffffffff로 SEH의 마지막 레코드이라는 것을 나타내는데, 이게 프로그램이 실행이 되지 않아서 이런거라고 합니다. F5를 누르거나 명령어 g를 쳐서 프로그램을 실행시킨 뒤 다시 확인해볼 수 있습니다.
실행을 하고 SEH 첫번째 레코드와 두번째 레코드의 모습입니다.
마지막 레코드는 0xffffffff로 끝나는 걸 알 수 있지요.
올리디버거로 보면 이게 더 쉽게 보이게 되어있다고 합니다.
View - SEH Chain을 누르면 다음과 같게 쉽게 볼 수 있습니다.
첫 SEH 레코드가 0x12ff58에 있다는걸 알려줍니다.
여기서 두번째 SEH Record의 SE handler가 0x411078 ExceptionHandler()라고 합니다. 한번 찾아가봅시다.
또 jmp를 하는군요. 블로그랑은 좀 다르게 되어있긴 합니다.
대충 이런식이라 하네요. 그래서 Exception이 발생하면 맨 위에 SEH Record부터 착착 하고 넘어가고, 거기서 처리를 못하면 다음 녀석으로 가고 이런식으로 반복이 된다고 합니다.
그리고 마지막 SEH Record는 윈도우에서 제공하는 Exception handler라고 합니다.
최종보스인 운영체제에서 처리를 하는 경우가 되는 거죠.
익스플로잇 작성
익스플로잇 전략
이번에는 소리통이라고 하는 고전 프로그램의 취약점을 익스플로잇 해볼건데, 이때 SEH를 이용해서 공격한다고 합니다.
공격 전략은 다음과 같습니다.
선행조건들입니다.
1. Exception을 발생시킵니다. 그래야지 SEH를 이용해먹을 수 있거든요.
2. 다음 SEH Record의 포인터를 jmp 코드로 덮어써놓습니다.
3. SE Handler의 포인터를 2에서 덮은 점프코드를 실행하도록 다른걸로 덮어놓습니다.
4. 쉘코드는 덮어쓰여진 SE Handler 바로 뒤에 있어야 합니다.
익스플로잇이 발동되는 시퀀스를 그린 그림입니다. (1)에서 Exception발생이 되서 SEH로 넘어오게 됩니다.
그리고 SE Handler로 넘어가는데, 요녀석은 pop pop ret 가젯으로 덮어쓰여져 있습니다.
Exception Handler에서 다음 SEH Record의 주소는 esp+8에 저장된다고 합니다. 그래서 pop pop ret을 하면 다음 SEH Record의 주소로 뛰게 되는데, 이 주소 역시 아까 쉘코드로 jmp뛰는 코드로 덮어놓았지요.
그렇게 연계 공격이 되는 것입니다.
esp+8에 다음 SEH Record 주소가 저장되는 거는, Exception handler 작성하는 Convention인 것 같습니다. 소스코드 레벨이든 어샘블리 레벨이든간에 말이죠.
크래시 재현
이번에 쓸 취약 프로그램은 소리통이라는 프로그램입니다.
http://pds.dnavi.info/windows95/121987
위 링크에서 다운받을 수 있습니다. 링크를 얻는데에는 블랙펄의 콘치님이 작성한 블로그 글이 도움이 되었습니다.
여기서 취약점이 터지는 부분은 프로그램의 UI가 저장되는 txt파일에서 bof가 터진다고 합니다.
설치 경로/Skin/Default/UI.txt가 있습니다.
저녀석을 덮어써보도록 하지요.
A를 5천개 넣어서 UI.txt를 만든 뒤 실행을 해보도록 하겠습니다.
그냥 켜졌다가 조용히 꺼지게 되네요.
코어랜 블로그 말로는 Exception이 나서 Exception Handler로 넘어갔는데, 적절한 SEH를 못찾아서 그런거라고 합니다. 왜냐면 SEH Record도 우리가 덮어써 버렸기 때문이지요.
이제 그러면 디버거를 붙여서 어떻게 된 건지 자세히 알아봐야겠지요?
디버거로 분석 - 올리디버거
올리디버거나 이뮤니티 디버거를 이용해서 소리통을 실행시킨 뒤, play 버튼을 눌러서 앱을 실행시켜 봅시다.
Loading Skin에서 앱이 멈춰있습니다.
디버거도 Paused라고 뜨네요.
SEH Chain을 확인해보겠습니다.
정말로 41414141로 덮어져 있군요.
블로그에서 보면, Current Stack Frame을 일단 살펴보네요.
0xFFFFFFFF가 보이는 걸로 봐서 SEH 체인의 끝이라고 보고 있습니다. 바로 위에서 본 SEH Chain 주소랑은 다른데,
아마 다른 스택 프레임이라서 그런 것 같네요. 창으로 별도로 뜬 SEH Chain 주소는 주소값이 더 큽니다. 따라서 스택은 아래로 자라므로 Caller function의, 즉 더 부모 함수의 스택 프레임인 것 같습니다.
그리고 FFFFFFFF가 가리키는 0x7e41882a가 기본 Exception handler의 주소인걸로 보이네요.
그리고 해당 주소는 USER32.DLL에 있는 주소입니다. View - Executable Modules를 누르면 나오는 창입니다.
그리고 크래시가 난 때에 스택프레임의 더 높은 주소들을 뒤져봐도 별다른 예외 핸들러가 보이지 않는 것으로 보아서, 적어도 크래시를 낸 함수는 예외 핸들러 루틴이 없다고 가정을 하네요.(블로그에서요)
이미 덮어쓰여져서 꼭 그런지는 모르겠는데 말입니다.
디버거로 분석 - Windbg 윈디버거
아까 올리디버거로 크래시난 상황을 SEH 을 분석하는 것을 윈디버거로 똑같이 해보겠습니다.
윈디버거로 소리통을 킨 뒤 명령어 g (go)를 친 뒤 엔터로 실행을 시켜보겠습니다.
Exception이 일어나기 전에 windbg가 알아서 멈춰버리는군요.
스택을 좀 구경해보면, 0xffffffff로 SEH Chain의 마지막 부분이 보입니다.
!analyze -v 명령어를 한번 사용해보도록 하지요.
이 외에도 엄청 긴 값이 나옵니다. 어떤 Exception 상황인지 등등이 나오네요.
!exchain 이라는 명령어를 쳐봅시다.
Exception handler를 덮어썻다는 걸 알 수 있네요.
여기서 g 명령어를 치거나, F5를 눌러서 진행을 해보면 EIP가 41414141로 변경되는 것을 알 수 있습니다.
레지스터값에 쉘코드 주소를 바로 박는다면?
레지스터에 쉘코드 주소를 넣고 바로 점프를 뛴다고 생각해봅시다. 근데 이 방법은 Windows XP SP1 이전에는 가능했지만, SP1 이후부터는 보호기법이 생겨서, exception handling 하기 전에 모든 레지스터의 값들이 XOR되어서 0으로 셋팅됩니다. 그래서 SEH로 넘어가게 되면 레지스터들은 쓸모가 없어지지요.
스택 BOF에서 ret 주소 덮어쓰기 보다 SEH 기반 익스플로잇의 장점은?
기존 stack base bof에서 ret addr를 덮어쓰면 간단한데 SEH 기반 익스플로잇을 하는 이유가 무엇일까요?
안정성에서 차이가 납니다. 만약 dll에서 jmp 명령어를 찾지 못한다면 주소를 하드코딩해서 박아서 넘어가야하는데, 이런경우 익스플로잇이 언제는 동작하고 언제는 동작하지 않겠지요.
그리고 버퍼사이즈가 적어서 쉘코드를 박기 힘든 경우에도 고생하게 됩니다.
그래서 스택 bof로 ret 주소를 덮어쓸 수 있다면 더 덮어서, SEH 체인까지 닿을 수 있도록 하는게 좋습니다. 그리고 스택에 있는 ret 주소를 쓰래기 값으로 덮었으면 exception은 어차피 일어나게 되고 SEH exploit으로 변경이 되는 것이지요.
SEH 기반 익스플로잇은 어떻게 이루어지죠?
간단합니다. SEH 포인터 주소와 SE 핸들러 주소를 덮어쓰고, 쉘코드를 넣으면 됩니다. 그리고 Exception Handler 내부에서 쉘코드로 뛰도록 하면 됩니다.
대충 위와 같은 그림인데, Exception이 실행이 되면 예외 핸들러 주소가 가리키는 데로 EIP가 이동합니다. 그리고 pop pop ret을 하게 되는데, 예외 핸들러에서는 esp+8에 다음 SEH 주소가 써잇는 스택 메모리의 주소가 써있습니다.
따라서 pop pop ret을 하면 EIP는 다음 SEH 주소가 써잇는 스택 영역으로 가게 되죠.
다음 SEH 주소에 있는 instruction을 실행하게 되는데 여기에 relative jmp instruction opcode가 있으면 요녀석을 실행해서 지금 주소의 4바이트 정도를 앞으로 뛰어넘게 됩니다. 이 jmp instruction opcode가 4바이트 이하의 크기이기 때문에 가능한 것이겠지요. 그러면 예외 핸들러 주소 다음에 있는 쉘코드로 이동하게 되고 쉘코드가 실행되지요.
물론 쉘코드는 예외 핸들러 이후에 몇바이트 더 뒤에 두고, jmp opcode를 더 많이 뛰게 해도 됩니다.
그럼 익스를 해봅시다
일단 Next SEH Pointer와 SE Handle Pointer를 덮어쓰는 양을 알아내야겠죠? UI.txt에 패턴을 줘서 알아내봅시다.
메타스플로잇에 rulez라는 녀석을 이용해서 만든것 같군요.
엔디안 고려하면 패턴은 At6A가 됩니다.
이를 바탕으로 기본 Exploit 코드를 좀 짜보면
위와 같이 대충 됩니다. !exchain이라고 쳣을때 제대로 나오는 것 같네요. g를 한번 더 쳐서 eip가 어떤놈을 가리키는지도 봤는데 정상적으로 ehandler 부분을 가리킵니다.
이제 pop pop ret 가젯과 jmp 4 opcode만 알아내서 채우면 익스플로잇이 완성될 것 같습니다.
코어랜 블로그에선 findjmp.exe란 녀석을 썻는데, 찾아보니, 깃헙에 있습니다.
https://github.com/nickvido/littleoldearthquake/blob/master/corelan/findjmp/findjmp/bin/findjmp.exe
다운받아서 한번 써보도록 하지요.
Player.dll에서 pop pop ret 가젯 중 주소값에 Null byte가 없는 녀석을 사용해봅시다.
촤라락 하고 나온다.
이제 그러면 full exploit을 작성하고 실행해보자.
후 성공했다.
eb 06 90 90이게 jmp relative 6byte라는 녀석이라는데, 지금으로부터 6바이트 앞으로 뛴다고 한다.
이녀석은 내가 skip햇던 part2에 설명이 있는데 short jump(jmp)의 opcode가 0xeb라고 한다. 그리고 그 뒤에 나오는 수만큼 지금 eip에 상대 주소만큼 점프뛴다고 한다.
크흐 드디어 끝!. 이제 잘 수 있다.
---------------------------------------------------------------------------------------
https://c9x.me/x86/html/file_module_x86_id_147.html
http://pds.dnavi.info/windows95/121987
https://bpsecblog.wordpress.com/2016/12/15/seh_02/
https://go.microsoft.com/?linkid=7729279
https://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture#Segment_Registers
https://stackoverflow.com/questions/10810203/what-is-the-fs-gs-register-intended-for
'해킹 & 보안 > 시스템 해킹' 카테고리의 다른 글
Virtual box에 포너블 환경 구축(Ubuntu 18.04+pwntools + gef) (0) | 2020.08.22 |
---|---|
[코어렌 튜토리얼] Windows Exploit 작성 튜토리얼 part6 : 보호기법 우회 (0) | 2020.02.28 |
[코어렌 튜토리얼] Windows Exploit 작성 튜토리얼 part1 : 스택 bof (0) | 2020.02.26 |
유니코드 쉘코드 / 베니스 쉘코드 (0) | 2020.02.25 |