윈도우 서버 2003 R2버전에서 테스트 했다. 비주얼 스튜디오 2008으로 GS설정을 일단 끄고 컴파일 해보았다. 코드는 다음과 같다.
// vulnerable server.cpp : Defines the entry point for the console application.//
#include "stdafx.h"
#include "winsock.h"
#include "windows.h"
//load windows socket
#pragma comment(lib, "wsock32.lib")
//Define Return Messages
#define SS_ERROR 1
#define SS_OK 0
void pr( char *str)
{
char buf[500]="
";
strcpy(buf,str);
}
void sError(char *str)
{
printf("Error %s",str);
WSACleanup();
}
int _tmain(int argc, _TCHAR* argv[])
{
WORD sockVersion;
WSADATA wsaData;
int rVal;
char Message[5000]="
";
char buf[2000]="
";
u_short LocalPort;
LocalPort = 200;
//wsock32 initialized for usage
sockVersion = MAKEWORD(1,1);
WSAStartup(sockVersion, &wsaData);
//create server socket
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if(serverSocket == INVALID_SOCKET)
{
sError("Failed socket()");
return SS_ERROR;
}
SOCKADDR_IN sin;
sin.sin_family = PF_INET;
sin.sin_port = htons(LocalPort);
sin.sin_addr.s_addr = INADDR_ANY;
//bind the socket
rVal = bind(serverSocket, (LPSOCKADDR)&sin, sizeof(sin));
if(rVal == SOCKET_ERROR)
{
sError("Failed bind()");
WSACleanup();
return SS_ERROR;
}
//get socket to listen
rVal = listen(serverSocket, 10);
if(rVal == SOCKET_ERROR)
{
sError("Failed listen()");
WSACleanup();
return SS_ERROR;
}
//wait for a client to connect
SOCKET clientSocket;
clientSocket = accept(serverSocket, NULL, NULL);
if(clientSocket == INVALID_SOCKET)
{
sError("Failed accept()");
WSACleanup();
return SS_ERROR;
}
int bytesRecv = SOCKET_ERROR;
while( bytesRecv == SOCKET_ERROR )
{
//receive the data that is being sent by the client max limit to 5000 bytes.
bytesRecv = recv( clientSocket, Message, 5000, 0 )
if ( bytesRecv == 0 || bytesRecv == WSAECONNRESET )
{
printf( "\nConnection Closed.\n");
break;
}
}
//Pass the data received to the function pr
pr(Message);
//close client socket
closesocket(clientSocket);
//close server socket
closesocket(serverSocket);
WSACleanup();
return SS_OK;
}
Project->property에서 창을 띄우고 다음과 같이 설정한다.
코드를 보면 알겠지만, pr함수가 취약해서 오버플로가 일어난다. Buf를 500개 이상 주면 공격 가능하다.
다음과 같이 파이썬 코드를 짜서 서버를 터뜨려 보자 J ^^
|
GS를 꺼두었기 때문에 스택쿠키가 없어 다음과 같이 eip가 변경된다.
그럼, GS를 키고 컴파일 해보면 어떻게 될까?
필자의 경우 그냥 꺼진다. 스택쿠키를 확인하는 절차가 있어서 그렇다.
그렇다면 스택쿠키를 우회하는 방법은 무엇일까? 앞선 글에서도 설명 했는데, 예외처리 코드를 이용한다. 즉, SEH overwrite 한다. 다음과 같은 코드를 예제로 삼겠다.
|
간단하다, GetInput함수에서 buffer에 strcpy하는 부분이 있는데 여기서 오버플로우가 터진다.
일단 GS와 RTC없이 컴파일해 windbg로 600개의 인자를 주고 실행 해 보자.
Eip의 위치까지 가기 전에 예외가 발생하게 되고, 핸들러 부분이 A의 값으로 바뀌었다. 즉, SEH 오버라이트 공격이 가능하다.
이번엔 GS를 키고 컴파일 한 후 A를 600개로 인자로 주고 실행해 보자.
마찬가지로 SEH가 바뀌었다.
여기서 SafeSEH옵션을 주고 컴파일 해서 SafeSEH와 스택쿠키를 우회하는 법을 알아 보자. 근데, windows server 2003 sp2 버전부터 SEH를 보호하는 기법이 업데이트 되었다. 로드된 모듈과 SafeSEH로 컴파일된 모듈, 또 스택을 가리키는 포인터는 SEH핸들러로 올 수 없다는 것이다. 따라서 핸들러로 pop pop ret의 명령어 주소를 가진 모듈을 찾기가 힘들기 때문에, 해법으로 CALL [ebp+30h], jmp [ebp+30h], call [esp+8h], jmp [esp+8h]등이 온다. 근데 아직까지 이해하지 못한 점은 Esp+8h는 원래 예외핸들러의 인자 값으로 next seh를 담은 구조체가 넘어가기 때문에 그렇다고 쳐도, [ebp+30h]는 어째서 가능한지 모르겠다. 물론 Olly로 확인했을 때, 정확하게 다음 next_seh를 가리키지만.. 암튼, 우린 call [ebp+30h]하는 부분을 찾아 셸코드를 작성한다.
0x280b0b에 관련 명령어가 있다. 이걸 이용해 쉘코드를 짜는데, 쉘은 seh가 등록된 스택보다 낮은 주소값에 넣는다. 왜냐하면 높은 주소값에 넣으면, 오버플로우로 인해 쉘이 망가질 위험이 있어서 이다. 자 다음과 같이 코드를 작성한다. 일단 nseh오프셋 값을 찾는 작업을 밟는 것이다.
|
실행 시키면, 다음과 같은 결과가 나온다.
(정확히 입력됬다.)
자 그럼 다음과 같이 익스플로잇을 짜고 실행시킨다.
|
그럼 다음과 같이 떠야되는데… 안된다..
몇 시간을 삽질 했지만.. 도저히 이유를 모르겠다. 아마 windows server 2003 한글버전으로 테스트 해서 그런 것 같긴 하다. Windbg로 확인해 본 결과 0x00280b0b로 eip가 바뀌질 않는다. 하.. 이유를 모르겠다. 누가 설명 좀 해주세요 ㅠㅠ
'Exploit' 카테고리의 다른 글
RTL을 이용한 쉘코드 작성 (3) | 2014.08.02 |
---|---|
유니코드 익스플로잇 (0) | 2014.07.21 |
C++에서 VTable (0) | 2014.07.12 |
여러 보호기법 (0) | 2014.07.11 |
SEH overwriting (0) | 2014.07.10 |