/ POSTS

Vtable Overwrite

0x0b Vtable Overwirte

안녕하세요. Joel입니다.

오늘은 Vtable Overwrite에 대해 알아보도록 하겠습니다.

What is Virtual Method?

Vtable에 대한 이해를 하기 위해서는 먼저, 가상함수가 무엇인지부터 알아야 합니다.

여기서 상속, 다형성 등 모든 개념들을 다 살펴볼 수는 없습니다.

간단히 필요 부분만 살펴보고 넘어갈 텐데, 모르는 부분은 직접 찾아서 공부해보시기 바랍니다.

가상함수는 한마디로 다형성을 위해 씁니다.

예를 들어 다음과 같은 클래스가 있다고 칩시다.

#include <stdio.h>

class Unit {
public:
	void Sound()
	{
		printf("Unit!\n");
	}

};

class Marine : public Unit {
public:
	void Sound() {
		printf("You wanna piece of me, boy?\n");
	}

};

class Firebat : public Unit {
public:
	void Sound() {
		printf("Need a light?\n");
	}

};

int main() {

	Unit * unit = new Unit;
	Marine * marine = new Marine;
	Firebat * firebat = new Firebat;

	unit->Sound();

	unit = marine;
	unit->Sound();

	unit = firebat;
	unit->Sound();

}

위와 같이 코드를 짜고 실행시키면 마린과 파이어뱃은 각자의 대사를 말할 것 같지만, 다음과 같이 출력됩니다.

[그림 1-1]

예상과는 다르죠?

이번에는 다음과 같이 virtual 키워드를 사용하여 가상함수로 코드를 수정했습니다.

#include <stdio.h>

class Unit {
public:
	virtual void Sound()
	{
		printf("Unit!\n");
	}

};

class Marine : public Unit {
public:
	virtual void Sound() {
		printf("You wanna piece of me, boy?\n");
	}

};

class Firebat : public Unit {
public:
	virtual void Sound() {
		printf("Need a light?\n");
	}

};

int main() {

	Unit * unit = new Unit;
	Marine * marine = new Marine;
	Firebat * firebat = new Firebat;

	unit->Sound();

	unit = marine;
	unit->Sound();

	unit = firebat;
	unit->Sound();

}

이 코드를 실행하면 다음과 같이 됩니다.

[그림 1-2]

virtual만 붙였을 뿐인데 처음 예상했던 대로 동작이 되네요.

즉, 이 가상함수라는 것은 파생 클래스가 안전하게 재정의할 수 있는 것이라고 할 수 있습니다.

이를 우리가 동적 바인딩이라고도 하는데, 동적 바인딩은 실행할 함수의 주소가 컴파일 시에 결정되는 것이 아니라, 포인터로 호출할 때 그 주소가 결정됩니다.

좀 더 정확하게는 호출 시에 정해진다라기보다 컴파일 시에 미리 가상함수 테이블을 만들어두고 거기에 호출하고자 하는 함수를 넣어두는 형태입니다.

이 가상함수 테이블이 바로 vtable입니다.

그래서 실행이 되면 객체의 vtable을 찾아가서 호출할 함수의 번지를 찾아가는 로직이죠.

지금까지 설명한 내용을 도식화하면 다음과 같습니다.

[그림 1-3]

이번엔 아래 코드를 가지고 vtable부터 overwrite를 살펴보도록 하겠습니다.

#include <stdio.h>
#include <cstring>
#include "windows.h"
#include <iostream>

#pragma warning (disable:4996)
#define _CRT_SECURE_NO_WARNINGS
using namespace std;

class Book {
private:
	char name[100];
	int page;
public:
	Book(const char * _name, int _page) {
		strcpy(name, _name);
		page = _page;
	}
	virtual void setName(char * input) {
		char * buf = (char *)malloc(20);
		strncpy(buf, input, 19);
		printName(buf);
		getName();
	}
	virtual char * getName() {
		return name;
	}
	virtual void printName(char * buf) {
		printf("%s\n", buf);
	}

};

int main(int argc, char * argv[]) {

	static char contents[1000] = { 0, };
	static Book mybook("Windows_Hacking", 1);
	printf("object addr : 0x%08x\n", &mybook);
	printf("object vtable addr : 0x%08p\n", mybook);
	printf("buf addr : %08x\n", &contents);

	FILE * f = fopen(argv[1], "r");
	fgets(contents, 1500, f);
	mybook.setName(contents);
	return 0;
}

디버거로 vtable을 따라가 보도록 하겠습니다.

[그림 1-3]

디버거로 쭉 따라가보면 0x004047D0에 객체가 생성된 것을 알 수 있습니다.

그리고 그 객체에는 vtable의 주소가 들어있고 뒤 이어 “Windows_Hacking”이 담겨 있습니다.

해당 주소에서 빨간색으로 표시해놓은 부분이 vtable의 주소입니다.

더 따라가 봅시다.

[그림 1-5]

vtable 시작 주소로 찾아간 모습입니다.

여기에 보면 0x401040, 0x401080, 0x401090 등등 보이시죠? 얘들이 virtual method의 주소입니다.

[그림 1-6]

[eax+8]이 call되는데 오른쪽에 eax를 보면 vtable을 가리키고 있는 것을 알 수 있습니다.

vtable에서 +8 위치는 0x00401090이 되겠네요.

[그림 1-7]

위 그림을 통해 확인해보면 0x00401090은 printfName이고 화면에 buf 값이 출력되겠습니다.

[그림 1-8]

지금까지 vtable에 대해 알아봤고 공격 이야기로 넘어가볼게요.

Vtable Overwrite?

이제 대충 vtable이라는 게 무엇인지는 알겠는데 공격은 어떻게 하겠다는 걸까요?

공격 아이디어는 아까 전 위에서 살펴봤던 이 그림에 있습니다.

[그림 1-3]

객체가 생성되었을 때의 주소와 값입니다.

여기 첫 4바이트가 vtable의 주소라고 했었죠?

만약 이 주소를 Shellcode가 있는 곳으로 바꿀 수가 있다면?

네 맞습니다. 끝났어요. ㅎㅎ

다만 주의해야 할 점이 객체의 vtable 값을 수정할 때는 shellcode가 들어있는 곳의 주소가 들어가야한다는 거죠.

method가 실행되기까지의 과정을 생각해보시면 됩니다.

객체에서 vtable 주소 -> vtable에서 함수의 주소 -> 실제 함수

이런 식으로 따라가는 것이죠?

그럼 우리는 다음과 같이 구성해야겠네요.

객체에서 vtable 변조 -> 가짜 vtable (쉘 코드 주소) -> 쉘 코드

자 그럼 실제로 overwrite를 진행해보도록 하겠습니다.

먼저 얼마나 값을 넣어야 덮을 수 있을지 봐야겠네요?

[그림 1-9]

fgets로 버퍼에 값을 넣어주는 것을 따라가보면 0x004043E8에 AAAA를 넣고 있는 것을 알 수 있습니다.

[그림 1-10]

그리고 객체의 위치는 0x004047D8이네요.

그럼 거리는 4047D8 - 4043E8 = 3F0 = 1008이네요.

vtable 값도 덮어써야 하니까 총 거리는 1012!!

거리도 알아냈으니 Payload를 작성해보겠습니다.

[그림 1-12]

Payload는 이렇게 작성했습니다.

Payload 마지막 부분은 버퍼 시작 주소로 구성했고, 첫 부분은 fake function들로 구성했습니다.

그런데 Payload를 보면 fake_vfunc 사이에 AAAA가 들어 있죠?

이건 다음 코드를 보시면 이해가 가실겁니다.

[그림 1-11]

어셈블리어 코드를 보면 [eax+8] 위치의 함수를 호출하고 있죠?

여기서 eax의 값은 우리가 변조한 버퍼의 시작주소입니다.

그래서 버퍼 시작 주소에 +8 위치에 Shellcode 주소를 두었습니다.

[그림 1-13]

짠! Shell이 잘 떨어지네요.

지금까지 Vtable 개념과 공격 기법을 알아봤습니다.

다음 시간에는 UAF와 HeapSpray를 알아볼 것이고, 버전별 IE exploit을 진행해보겟습니다.