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();
}
위와 같이 코드를 짜고 실행시키면 마린과 파이어뱃은 각자의 대사를 말할 것 같지만, 다음과 같이 출력됩니다.
예상과는 다르죠?
이번에는 다음과 같이 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();
}
이 코드를 실행하면 다음과 같이 됩니다.
virtual만 붙였을 뿐인데 처음 예상했던 대로 동작이 되네요.
즉, 이 가상함수라는 것은 파생 클래스가 안전하게 재정의할 수 있는 것이라고 할 수 있습니다.
이를 우리가 동적 바인딩이라고도 하는데, 동적 바인딩은 실행할 함수의 주소가 컴파일 시에 결정되는 것이 아니라, 포인터로 호출할 때 그 주소가 결정됩니다.
좀 더 정확하게는 호출 시에 정해진다라기보다 컴파일 시에 미리 가상함수 테이블을 만들어두고 거기에 호출하고자 하는 함수를 넣어두는 형태입니다.
이 가상함수 테이블이 바로 vtable입니다.
그래서 실행이 되면 객체의 vtable을 찾아가서 호출할 함수의 번지를 찾아가는 로직이죠.
지금까지 설명한 내용을 도식화하면 다음과 같습니다.
이번엔 아래 코드를 가지고 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을 따라가 보도록 하겠습니다.
디버거로 쭉 따라가보면 0x004047D0에 객체가 생성된 것을 알 수 있습니다.
그리고 그 객체에는 vtable의 주소가 들어있고 뒤 이어 “Windows_Hacking”이 담겨 있습니다.
해당 주소에서 빨간색으로 표시해놓은 부분이 vtable의 주소입니다.
더 따라가 봅시다.
vtable 시작 주소로 찾아간 모습입니다.
여기에 보면 0x401040, 0x401080, 0x401090 등등 보이시죠? 얘들이 virtual method의 주소입니다.
[eax+8]이 call되는데 오른쪽에 eax를 보면 vtable을 가리키고 있는 것을 알 수 있습니다.
vtable에서 +8 위치는 0x00401090이 되겠네요.
위 그림을 통해 확인해보면 0x00401090은 printfName이고 화면에 buf 값이 출력되겠습니다.
지금까지 vtable에 대해 알아봤고 공격 이야기로 넘어가볼게요.
Vtable Overwrite?
이제 대충 vtable이라는 게 무엇인지는 알겠는데 공격은 어떻게 하겠다는 걸까요?
공격 아이디어는 아까 전 위에서 살펴봤던 이 그림에 있습니다.
객체가 생성되었을 때의 주소와 값입니다.
여기 첫 4바이트가 vtable의 주소라고 했었죠?
만약 이 주소를 Shellcode가 있는 곳으로 바꿀 수가 있다면?
네 맞습니다. 끝났어요. ㅎㅎ
다만 주의해야 할 점이 객체의 vtable 값을 수정할 때는 shellcode가 들어있는 곳의 주소가 들어가야한다는 거죠.
method가 실행되기까지의 과정을 생각해보시면 됩니다.
객체에서 vtable 주소 -> vtable에서 함수의 주소 -> 실제 함수
이런 식으로 따라가는 것이죠?
그럼 우리는 다음과 같이 구성해야겠네요.
객체에서 vtable 변조 -> 가짜 vtable (쉘 코드 주소) -> 쉘 코드
자 그럼 실제로 overwrite를 진행해보도록 하겠습니다.
먼저 얼마나 값을 넣어야 덮을 수 있을지 봐야겠네요?
fgets로 버퍼에 값을 넣어주는 것을 따라가보면 0x004043E8에 AAAA를 넣고 있는 것을 알 수 있습니다.
그리고 객체의 위치는 0x004047D8이네요.
그럼 거리는 4047D8 - 4043E8 = 3F0 = 1008이네요.
vtable 값도 덮어써야 하니까 총 거리는 1012!!
거리도 알아냈으니 Payload를 작성해보겠습니다.
Payload는 이렇게 작성했습니다.
Payload 마지막 부분은 버퍼 시작 주소로 구성했고, 첫 부분은 fake function들로 구성했습니다.
그런데 Payload를 보면 fake_vfunc 사이에 AAAA가 들어 있죠?
이건 다음 코드를 보시면 이해가 가실겁니다.
어셈블리어 코드를 보면 [eax+8] 위치의 함수를 호출하고 있죠?
여기서 eax의 값은 우리가 변조한 버퍼의 시작주소입니다.
그래서 버퍼 시작 주소에 +8 위치에 Shellcode 주소를 두었습니다.
짠! Shell이 잘 떨어지네요.
지금까지 Vtable 개념과 공격 기법을 알아봤습니다.
다음 시간에는 UAF와 HeapSpray를 알아볼 것이고, 버전별 IE exploit을 진행해보겟습니다.