( GS + SafeSEH 우회 익스플로잇 : http://5kyc1ad.tistory.com/312 )

SEH Overwrite에 대한 기초적인 내용은 http://5kyc1ad.tistory.com/308 여기와 위 링크를 참고하시기 바랍니다.

 

SEHOP : Linked-List 형태인 SEH 체인을 참조하여 마지막 SEHSEHRecord->pNextSEHRecord에는 0xFFFFFFFF, SEHRecord->pExceptionHandler에는 Ntdll!FinalExceptionHandler 함수 주소가 지정되어 있는지 확인하는 보호 기법.

 

 * 이하에서 익스플로잇에 사용한 프로그램에 스택 기반 BOF SEH Overwrite 취약점이 존재하지만 NULL을 입력할 수 없는 문제가 있어 실습을 위해 메모리에 직접 수정을 가해서 입력되었다는 가정 하에 익스플로잇 실습을 진행했습니다. 해당 프로그램은 위 ‘GS + SafeSEH 우회 익스플로잇에서 사용한 것과 동일합니다.


 

 

[정상적인 SEH 체인]

 

[비정상적인 SEH 체인]

 기존에 SafeSEH를 피해 SEH Overwrite를 했던 기법을 그대로 사용하면 pNextSEHRecordRelative Short JMPOPCode로 변조되어버리기 때문에 SEH 체인이 끊기고 SEHOP에 탐지되어 정상적으로 Exploit이 실행되지 않습니다.

 

GS + DEP + SafeSEH + SEHOP가 적용되어 있고 ASLR이 있다고 가정할 경우 공격 방법은 다음과 같습니다.

 

[덮어쓸 SEHRecord 주소와 값 확인]

 스택에서 덮어쓸 SEHRecord 주소와 pNextSEHRecord 값을 확인합니다. pNextSEHRecord 0x18FA90이며 바로 밑에 다음 SEHRecord가 있는 것을 볼 수 있습니다. SEH 체인을 끝까지 다 따라가면서 덮어씌워주기는 힘드므로 저 두 번째 체인을 0xFFFFFFFF Ntdll!FinalExceptionHandler 함수 주소로 변조하여 정상적으로 보이는 SEH 체인을 만들어야 합니다

 

[마지막 SEHRecord ]

 마지막 SEHRecord까지 계속 참조하여 Ntdll!FinalExceptinHandler 주소값이 0x772E7428인 것을 확인할 수 있습니다.

 여기까지를 페이로드로 작성하면 다음과 같습니다.

Dummy(0x54) + 0x18FA90 + ???? + Dummy(4) + 0xFFFFFFFF + 0x772E7428

 

[익셉션 발생 직후 Handler 함수]

 익셉션 발생으로 SEHRecord->pExceptionHandler가 호출된 직후의 ESP의 위치를 확인하기 위해 Handler 함수에 바로 BP를 걸고 강제로 익셉션을 발생시켜 ESP0x18F448에 위치하게 됨을 확인했습니다. 위에서 인위적으로 만든 종료 체인 이후 스택 주소가 0x18FA98 이므로 0x18FA98 - 0x18F448 = 0x650 이상 ESPPOP 또는 ADD ESP로 옮긴 후 ret을 해야 원하는 대로 ROP 체인 구성이 가능합니다.

 

[Ollydbg Search for All Sequences]

 해당하는 가젯을 찾기 위해 Ollydbg CPU 창에서 우클릭 -> Search for -> All Sequences 창을 열고 위와 같이 와일드카드 문자 CONSTR32를 이용하여 가젯을 검색했습니다. (pop 없이 해봤더니 적당한 가젯이 나오지 않았습니다)

 

[발견한 가젯]

 바이너리 내에서 해당 가젯을 찾아내었습니다. ADD ESP, 0x800 POP 1번 후 ret을 하므로 총 0x804만큼 ESP가 움직이게 됩니다. 0x650 - 0x804 = -0x1b4 이므로 총 0x1b4개만큼 Dummy를 추가로 넣은 후 적당한 함수 주소를 넣어 주면 ROP가 가능할 것으로 보입니다.

 

여기까지 페이로드는 다음과 같습니다.

Dummy(0x54) + 0x18FA90 + 0x452336 + Dummy(4) + 0xFFFFFFFF + 0x772E7428 + Dummy(0x1b4) + ROP Chain

 

[익스플로잇 스택 구조도]

 실제 스택 주소 기반으로 페이로드가 들어가 익스플로잇된 스택 구조를 그려봤습니다. 익셉션이 터지는 순간 익셉션 처리 과정에서 스택이 추가로 늘어나고 0x18F448ESP가 위치하게 됩니다. 이후 &GadgetpExceptionHandler 위치이므로 이 가젯을 실행하게 되는데 여기에는 ADD ESP, 0x800POP, ret이 있으므로 ROP Chain이 존재하는 0x18FC4C에서 ret을 하게 되고, ROP Chain이 실행되어 익스플로잇에 성공하게 될 것입니다. ROP Chain 위의 Dummy 부분은 어차피 말 그대로 Dummy이므로 그곳에 문자열을 넣고 간단히 MessageBox를 호출하도록 짜서 돌려봤습니다.

 

[Exploit 1]

 익셉션 발생하기 직전의 샘플, EAX0x90909090이 들어 있는데 포인터 참조하여 익셉션이 발생하려고 하고 있고 pExceptionHandler는 이미 0x452336으로 변조된 상황입니다.

 

[Exploit 2]

익셉션이 발생하고 0x452336BP에서 멈춘 상태. ESP0x18F448까지 내려갔고, ADD ESP, 800Pop이후 ret이 실행되므로 0x18FC4C의 값이 ret 될 것으로 예상 가능합니다.

 

[Exploit 3]

정확히 0x18FC4C의 값을 ret하며, 그 밑에 두번째 인자로 0x1B4 크기의 Dummy에 넣은 문자열 “Exploited!!”가 들어가는 것을 볼 수 있습니다.

 

[Exploit 4]

해당 함수는 MessageBoxA 함수였으며, 넣은 값 그대로 출력 후 종료됩니다. POP~RET 가젯도 찾아두었으니 여기에 라이브러리 함수를 연결해서 돌리기만 하면 ROP 체인을 계속 연결해 나갈 수 있겠습니다.

 ASLR이 걸려있지 않았기 때문에 주소값 넣는 데에 지장이 없었지만 ASLR까지 걸려있다면 주소를 Leak할 수 있는 취약점이 없는 이상 익스플로잇은 어려워 보입니다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요


[GS (Stack Canary/Cookie) 해제법]


 프로젝트 속성 -> C/C++ -> 코드 생성 -> 보안 검사 -> 보안 검사 사용 안 함(/GS-) 으로 변경.

함수 프롤로그 이후 스택에 버퍼 할당 전에 랜덤한 값을 Push해 두고, 함수 에필로그 직전에 검사하여 변조되었는지 체크하는 보호 기법. 보통 윈도우에서는 GS 또는 Stack Cookie라고 불리고 리눅스에서는 Stack Canary라고 불리지만 그거나 그거나이므로 넘어가자.



[DEP(NX) 해제법]


 프로젝트 속성 -> 링커 -> 고급 -> DEP(데이터 실행 방지) -> 아니요(/NXCOMPAT:NO) 로 변경.

각 페이지마다 Read/Write/Execute 권한 플래그를 설정하여 Write/Execute 권한을 동시에 주지 않는 보호 기법. 간단하면서도 강력한 기법.


[ASLR 해제법]


 프로젝트 속성 -> 링커 -> 고급 -> 임의 기준 주소 -> 아니요(/DYNAMICBASE:NO) 로 변경.

매핑된 바이너리 및 DLL 주소, 버전에 따라서는 힙, 스택, PEB, TEB까지 프로그램 실행 시마다 랜덤하게 변환해버리는 보호 기법. 높은 버전에서는 메모리 릭을 통해서 주소를 찾거나 고정된 DLL 주소를 찾지 않으면 주소값을 사용할 수 없기 때문에 골치아픈 보호 기법이다.



[SafeSEH 해제법]


 프로젝트 속성 -> 링커 -> 명령줄 -> 추가 옵션(D) -> /SAFESEH:NO 추가 작성.

SEH가 가리키는 주소가 스택이거나, MS Library이지만 핸들러로 등록된 주소가 아닐 경우, 혹은 SafeSEH가 적용된 모듈의 주소일 경우 탐지하여 실행되지 않게 하는 보호 기법. 이 보호 기법을 우회하고 SEH Overwrite를 하기 위해서는 SafeSEH가 적용되지 않은 고정된 바이너리의 주소를 찾아서 점프해야 한다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요

[스택 오버플로우를 발생시키는 memcpy 루틴]

 실제 SEH Overwrite 취약점이 존재하는 상용 프로그램을 분석했습니다. 프로그램 종류나 제품명은 공개하지 않겠습니다. 정확히는 SafeSEH밖에 걸려있지 않지만 여기서의 방법을 이용하면 설령 Stack Canary가 있더라도 우회가 가능하기 때문에 제목에는 두 미티게이션 모두 적어두었습니다.

 문자열을 입력받는 루틴에서 길이 체크를 하지 않아 스택 기반 오버플로우가 발생합니다. 현재 명령을 보면 rep movsd 으로 ESI의 값을 EDImemcpy하게 되는데, 이들은 각각 덤프 1 탭과 스택에 나타나 있습니다. ESI에는 오버플로우용 NOP Sled와 쉘코드, SEH Overwrite 코드가 Input으로 들어와 있고 EDI는 부모 함수의 스택 프레임 내부 주소값입니다.

 

[오버플로우 전]


[오버플로우 후]

 rep movsd 명령이 수행되고 위에서 설명한대로 SEH가 변조되었음을 볼 수 있습니다.

 

[익셉션 발생]

 부모 함수의 스택 프레임을 수정하여 오버플로우를 일으켰기 때문에 원래 있던 함수에서는 정상적으로 리턴되고, 직후 EBP-10의 값을 EAX에 넣고 EAX에 포인터 참조 연산을 수행합니다. 여기서 EBP – 10은 아까 rep movsd 명령에 의해 NOP으로 채워졌기 때문에 잘못된 포인터(0x90909090) 참조로 함수 에필로그 이전에 익셉션이 발생합니다.

 

[SEH Overwrite 실행 과정]

 이 글에서 설명할 때 사용했던 그림으로 살펴보면 pExceptionHandlerppr 가젯의 주소가 들어가 있고, pExceptionHandlerCall한 상태이므로 ESP는 스택 위쪽 어딘가에 있는 ret을 가리키고 있을 것입니다. ppr 가젯이 실행되면서 retExceptionRecordpop되고, EstablisherFrameret되는데 해당 주소는 pNextSEHRecord이고 여기에는 미리 넣어둔 Short JMPEB AC 가 있어 현재 주소에서 -82만큼 점프하게 됩니다. 이는 NOP Sled + Shellcode의 주소이고, 따라서 쉘코드가 정상적으로 실행됩니다.

 

[쉘코드 실행 성공]

 NOP Sled를 타고 내려와 계산기를 실행시키는 간단한 쉘코드가 실행된 모습입니다.


블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요

SEH Overwrite

Analysis/Technique 2018. 5. 29. 17:15

SEH : Structured Exception Handling의 약자로, Windows를 위한 네이티브 예외 처리 메커니즘이다. 각 스레드마다 독립적으로 설치되고 처리된다.

 __try, __except, __finally 구문을 사용하여 SEH를 설치하고 해제할 수 있다. __try{} 선언은 컴파일러에서 정의된 EH_prolog 함수를 호출하는데, 이 함수는 스택에 _EXCEPTION_REGISTRATION_RECORD를 할당하고 SEH 링크드 리스트의 헤드에 레코드를 추가한다. (출처)

[SEH 설치 디스어셈블 모습]

 어셈블리 코드를 확인해 보면 익셉션 핸들러 함수 주소를 먼저 push하고, FS:[0x00]에 접근하여 그 값을 Push하는 것을 볼 수 있다. _EXCEPTION_REGISTRATION_RECORD는 다음과 같은 구조를 갖고 있다.

typedef struct _EXCEPTION_REGISTRATION_RECORD

{

     PEXCEPTION_REGISTRATION_RECORD Next;

     PEXCEPTION_DISPOSITION Handler;

} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;


 Stack은 높은 주소에서 낮은 주소로 할당되므로 Handler 함수를 먼저 push하였고, 두번째로 push한 값은 Next 값이 되는데 이는 이 레코드에서 처리하지 못했을 경우 처리될 다음 _EXCEPTION_REGISTRATION_RECORD의 주소이다. 위에서 언급한 FS:[0x00]에는 TIB(Thread Information Block) 또는 TEB(Thread Environment Block)이라고 불리는 구조체가 존재한다.

 

[TIB 내용. 출처]

 그림과 같이 FS[0x00]에 할당된 TIB는 첫번째 인자로 현재 SEH 프레임의 주소, 즉 링크드 리스트의 헤더 주소를 갖고 있다.

 

[SEH Chain]

 _EXCEPTION_REGISTRATION_RECORD__try 문의 중첩 정도에 따라 그림과 같은 링크드 리스트 구조를 갖고 스택에 할당된다. 먼저 pExceptionHandler 함수가 호출되고, 해당 핸들러에서 익셉션을 처리하지 못할 경우 pNextSEHRecord를 참조하여 다음 핸들러를 호출한다. 이를 계속 반복하다가 pNextSEHRecord0xFFFFFFFF가 할당되어 있는 디폴트 핸들러인 UnhandledExceptionHandler까지 도달할 경우 해당 익셉션을 커널로 넘겨 프로세스를 재개하거나 종료시킨다. 

 

 

[SEH in Stack]

 함수가 Call 될 때 자동으로 스택에 저장되는 ret과 함수 프롤로그에서 자동으로 생성되는 SFP 이후에 함수 내부에서 __try 구문을 사용했다는 가정 하에 스택 프레임에는 위와 같은 구조로 _EXCEPTION_REGISTRATION_RECORD가 쌓일 것이다. Buffer에 원하는 만큼 데이터를 집어넣어서 BOF를 일으킬 수 있다면 가장 쉬운 공격 방법은 역시 ret을 덮어씌워서 공격하는 것이지만, 이는 보통 Stack cookie 또는 Stack canary라고 불리는 보호 기법에 의해 공격이 여의치 않다.

 

[Stack Canary]

 Stack Canary는 고전적인 BOF 방지 기법 중 하나로, retSFP 뒤에 랜덤한 값을 생성하여 설정한 후 함수가 끝날 때 검사하여 값이 바뀌었을 경우 탐지하고 강제로 종료시켜버리는 보호 기법이다. 이런 상황에서는 Canary를 덮어쓰고도 공격이 가능하게 하기 위해 SEH Overwrite를 이용할 수 있다.

 

[Basic SEH Overwrite]

 가장 간단하게 SEH Overwrite를 사용하는 방법은 위와 같다. pExceptionHandler를 덮어쓰기 위해 Canary 값을 변경했으므로 함수 에필로그 직전에 Canary를 검사하는 루틴을 지나가는 순간 프로그램이 종료되어 버린다. 따라서 SEH Overwrite가 제대로 기능하도록 하기 위해서 Canary 검사가 진행되기 전에 다른 Exception이 발생하도록 만들어야 한다. 이 방법은 여러 가지가 있지만 대표적으로는 위 그림처럼 파라미터로 넘어온 포인터까지 Dummy 값으로 덮어씌우고 이를 사용했을 때 Exception이 발생하도록 할 수 있겠다.

 위처럼 단순하게 스택에서 _EXCEPTION_REGISTRATION_RECORDpExceptionHandlerShellcode의 주소로 덮어쓰는 방법을 쓸 수 있으면 좋겠지만, 이 방법은 Microsoft에서 SafeSEH라는 보호 기법을 도입하면서 불가능하게 되었다. 이 보호 기법은 pExceptionHandler 내부에 스택 주소가 들어가거나, MS에서 핸들러로 등록한 주소가 아닌데 kernel32.dll MS에서 지정한 모듈 주소가 들어갈 경우 실행되지 않도록 한다. 따라서 이를 우회하기 위해서는 다른 방식을 사용해야 한다.


typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) ( 

    IN PEXCEPTION_RECORD ExceptionRecord, 

    IN ULONG EstablisherFrame

    IN OUT PCONTEXT ContextRecord, 

    IN OUT PDISPATCHER_CONTEXT DispatcherContext 

);


 위의 함수 정의부는 pExceptionHandler 함수의 프로토타입이다. SEH 처리가 시작되면 pExceptionHandler 주소를 위의 인자들과 함께 Call하게 되는 것인데, 여기서 중요한 부분은 두번째 인자인 EstablisherFrame이다. EstablisherFrame은 이 pExceptionHandler 함수를 호출한 _EXCEPTION_REGISTRATION_RECORD 구조체의 주소를 갖게 된다. , 익셉션 발생 후 SEH 처리가 정상적으로 실행될 경우 다음과 같은 스택 상황이 만들어진다.


[정상적으로 실행된 SEH]

 여기서 주의 깊게 봐야 할 부분은 바로 ESP+8에 위치한 EstablisherFrame 값이 우리가 직접 변조 가능한 pNextSEHRecord를 가리키고 있다는 점이다. 만약 pExceptionHandler를 변조하여 Pop-Pop-Ret 가젯의 주소로 바꿔놓는다면 위 스택에서 ret이 먼저 Pop되고, ExceptionRecordPop되고, EstablisherFrameret되면서 자연스럽게 EIP가 스택의 pNextSEHRecord를 가리킬 것이다.

 

[변조된 SEH]

 위에서 적은 대로 pExceptionHandler &ppr로 덮어씌우고 추가적인 작업을 더 해서 Exploit을 완성한 모습이다. ppr 가젯에 의해 EIP&pNextSEHRecord를 가리키게 되면서 최대 4바이트의 임의의 OPCode를 실행시킬 수 있다. 여기서 사용할 수 있는 가장 간단하면서 강력한 OPCodeshort relative-jmp, EB XX2바이트짜리 점프이다

[Negative Short JMP. 출처]

 이 명령은 [현재 주소] + [Second Byte Value(XX)] + 2 주소로 점프하게 된다여기서 +2가 붙은 이유는 이 명령 자체의 크기가 2이기 때문이다. 예를 들어 0x00120100이라는 스택 주소에 EB 9A 90 90 이라는 OPCode가 들어갈 경우 0x9Asigned int8 형에서 -102와 같으므로 -102 + 2 하여 현재 EIP에서 -100의 주소로 점프한다. 이를 잘 이용하면 위의 버퍼에 미리 입력해 둔 NOP Sled + Shellcode로 점프하는 것이 가능하다.


블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요