본문 바로가기

리버싱!

가상 함수(Virtual function)와 가상 함수 테이블(vtable) 공부

오버라이딩 공부

클래스에서 선언된 함수에 대해, 해당 클래스의 자식 클래스가 정의되었을 때 자식 클래스에 서 상속받은 함수를 새롭게 정의하여 사용하는것을 오버라이딩이라 한다. 부모 클래스의 함 수에서 선언된 리턴타입과 함수 인자 구성이 똑같아야 한다.(인자갯수나 타입, 리턴타입이 다른건 오버로딩인걸로 기억)이렇게 정의된 클래스 타입으로 객체를 선언하여 사용하는 경우, 함수를 새로 정의한 자식 클래스를 타입으로 지정하여 함수를 호출했을 때는 새롭게 정의된 함수(자식)가 호출된다.

#include <iostream>

using namespace std;

class Parent {
public:
	void show() {
		cout << "Parent\n";
	}
};
class Child : public Parent {
public:
	void show() {
		cout << "CHILD\n";
	}
};
class Child2 : public Child {
public:
	void show() {
		cout<<"child 2 \n";
	}
};
int main() {
	Parent* a = new Parent;
	Child* b = new Child;
	Child2* c = new Child2;
	a->show();
	b->show();
	c->show();
}

결과:

Parent* a = new Parent;
Child* b = new Child;
Child2* c = new Child2;
a = b;
a->show();
a = c;
a->show();

Parent 포인터 변수에 에 b나 c의 객체를 넣어도 컴파일러 구간에서 바인딩을 했기때문에 Parent로 고정되어 show가 실행된다.

 

부모클래스에 상속을 받은 자식클래스가 부모클래스의 함수명과 동일한 함수를 만들어서 오버라이딩을 해주면 부모클래스 함수의 기능이 안되는 부분을 부모클래스의 함수를 숨겼다 하여 Hiding(하이딩)이라 한다.

 

가상함수

c++에서는 virtual 이라는 키워드로 함수를 선언시 가상함수를 만들어 첫번째 문제였던 정적바인딩 되어 부모에서 자식객체로 바꿔도 부모그대로 작동하는 현상을 해결할수있다. 이는 하나의 객체가 여러 형태의 자료형을 가지는 다형성을 지킬수있다.

virtual 쓰기전

virtual 쓴 후

가상함수 테이블(VTable)

컴파일시 가상함수(virtual)이 있다면 가상함수테이블(vtable)이 만들어져 바이너리의 rdata영역에 기록된다.

가상함수의 주소번지 목록을 가지고있는 포인터배열이기에 객체에서 함수를 호출할때 가상함수 테이블을 참조해서 함수를 호출하게된다.

이 가상함수 테이블은 클래스마다 생성되는데 오버라이딩된 함수는 같은 함수라도 주소가 다르고 되지않았다면 같은 주소를 가진다.

 

#include <stdio.h>
class Parent {
public:
	virtual void show1() {
		printf("this is parent1\n");
	}
	virtual void show2() {
		printf("this is parent2\n");
	}
	virtual void show3() {
		printf("this is parent3\n");
	}
};
class Child : public Parent {
public:
	virtual void show1() {
		printf("this is child1\n");
	}
	virtual void show3() {
		printf("this is child1\n");
	}
};
class Child2 : public Child {
public:
	virtual void show1() {
		printf("this is child2 \n");
	}
};
int main() {
	Parent* p = new Parent;
	Child* c = new Child;
	Child2* c2 = new Child2;
	void (Parent:: * fptr1) (void) = &Parent::show1;
	void (Parent:: * fptr2) (void) = &Parent::show2;
	void (Parent:: * fptr3) (void) = &Parent::show3;
	void (Child:: * fptrc1) (void) = &Child::show1;
	void (Child:: * fptrc2) (void) = &Child::show2;
	void (Child:: * fptrc3) (void) = &Child::show3;
	void (Child2:: * fptrcc1) (void) = &Child2::show1;
	void (Child2:: * fptrcc2) (void) = &Child2::show2;
	void (Child2:: * fptrcc3) (void) = &Child2::show3;
	printf("Parent::show1 : 0x%08lx \n", fptr1);
	printf("Parent::show2 : 0x%08lx \n", fptr2);
	printf("Parent::show3 : 0x%08lx \n", fptr3);
	printf("----------------------------------\n");
	printf("Child::show1 : 0x%08lx \n", fptrc1); //overriding
	printf("Child::show2 : 0x%08lx \n", fptrc2);
	printf("Child::show3 : 0x%08lx \n", fptrc3); //overriding
	printf("----------------------------------\n");
	printf("Child2::show1 : 0x%08lx \n", fptrcc1); //overriding
	printf("Child2::show2 : 0x%08lx \n", fptrcc2);
	printf("Child2::show3 : 0x%08lx \n", fptrcc3);
}

show2는 오버라이딩 자체가 없으므로 주소값은 동일 show3은 child에서 오버라이딩하고 child2에서 안했기때문에 주소를 공유하게 된다.

 

상속이 될때 부모클래스의 가상함수테이블도 그대로 복사되고 오버라이팅된 함수만 새로 업데이트 되는 형식이다.

만약 부모클래스에 없는 가상함수를 자식클래스가 선언할경우 자신의 가상함수 테이블에만 추가되는 형식이다.

 

 

'리버싱!' 카테고리의 다른 글

D3D Hooking 공부  (2) 2022.02.23
가상 함수 테이블(vtable) 공부 2 - 자세히 분석  (0) 2022.02.22
detours를 이용한 hooking  (0) 2022.02.16
x64 Hooking 공부(메모장을 통한)  (0) 2022.02.09
Remote Template Injection  (2) 2022.02.04