본문 바로가기
Reversing/Reversing

IDA로 C++ 클래스의 가상함수를 볼 때

by bbolmin 2013. 12. 15.



간단하게 아래와 같이 c++을 작성하고 분석해보았다.


#include "iostream"
 
using namespace std;
 
class A{
public:
	int myval;

	void set(int val) {	myval = val; }
	virtual void print() { cout << "[A] myval : " << myval << endl; }

};

class B : public A
{
public:
	void print() { cout << "[B] myval : " << myval << endl; }
};

int main()
{
	//A *ptr1 = new A();
	//A *ptr1 = new B();
	//B *ptr1 = new B();

	ptr1->set(123);
	ptr1->print();


    return 0;
}



C++에서 가상함수는 다형성을 위해서 존재한다. 아래와 같이 A클래스의 포인터에 A객체가 들어 갈수도 있고 A객체를 상속하는 B객체가 들어갈 수 도 있다. 일반적으로 A객체 포인터가 print메소드를 호출한다면 A의 print메소드가 호출된다. 

이 때 오버라이딩된 B객체에 대한 메소드가 호출될 수 있도록 A의 print 메소드에 virtual 선언을 해주면 된다.



A *ptr1 = new A();         --->         A::print()

A *ptr1 = new B();         --->         B::print()        //virtual이 선언이 없다면 A::print() 었을것이다.

B *ptr1 = new B();         --->         B::print()





소스에서 주석처리된 A *ptr1 = new A(), A *ptr1 = new B(), B *ptr1 = new B() 의 3가지 경우에 대해 IDA로 볼 때 어떻게 다른지 살펴보자.




1. 먼저 A *ptr1 = new A();인 경우를 IDA로 열어 보면 Main부분은 아래와 같다.




int __cdecl sub_411500() { int v1; // [sp+Ch] [bp-DCh]@1 void *v2; // [sp+14h] [bp-D4h]@1 int v3; // [sp+E0h] [bp-8h]@4 memset(&v1, -858993460, 0xDCu); v2 = operator new(8u); if ( v2 ) v1 = sub_41113B(v2); else v1 = 0; v3 = v1; sub_41100F(123); (**(void (__thiscall ***)(_DWORD))v3)(v3); sub_4111B8(); return sub_4111B8(); }



[*] operator new에서 8byte만큼 할당하는데 myval의 4byte와 virtual선언된 함수가 있기 때문에 만들어진 vtable 4byte가 할당 된 것이다.



ex) vtable이 생성된다면 하상 아래와 같이 this포인터의 첫 4byte에 생성된다.


-----------------

|     int myval |

----------------- <-- this


-----------------

|     int myval |

-----------------

|      &vtable |

----------------- <-- this



sub_41113B                                                              <-- A클래스의 생성자

sub_41100F                                                              <-- A클래스의 set함수

(**(void (__thiscall ***)(_DWORD))v3)(v3);                 <-- A클래스가 vtable을 참조하여 호출하는 print() 함수


- 객체의 data는 고유하지만 멤버 함수는 별도로 위치하여 객체들간에 서로 공유한다.

- virtual선언이 아닌 set함수는 바로 호출되고, virtual로 선언된 printf 메소드는 vtable 참조로 호출됨.




이제 생성자 부분을 보자


int __thiscall sub_411610(void *this) { void *v2; // [sp+D0h] [bp-8h]@1 v2 = this; *(_DWORD *)this = &off_417834; return (int)v2; }



this포인터의 첫 DWORD에 vtable의 주소 &off_417834를 넣어서 구성하는 것을 확인할 수 있다.



[ 여기서 &부분을 잠시 설명하면]





해당 클래스에 대한 구조체는 아래와 같이 만들어주면 분석할 준비가 완성된다.



00000000 A               struc ; (sizeof=0x8)

00000000 vtable          dd ?                    ; offset     // 'Y' 커맨드로 vtable*형으로 만들어줌

00000004 myval           dd ?

00000008 A               ends

00000008

00000000 ; ---------------------------------------------------------------------------

00000000

00000000 vtable          struc ; (sizeof=0x4)

00000000 print           dd ?

00000004 vtable          ends










2. 그럼 이제 나머지 두 경우에 대해서 알아보자.


- A *ptr1 = new B();

- B *ptr1 = new B();


위의 경우에는 A클래스 객체가 아니라 A클래스를 상속하는 B클래스 객체를 할당한 상황이다. 이 때는 B클래스의 생성자에서A클래스의 생성자도 호출한다는 것만 달라진다.




IDA로 생성자 부분만 살펴 보겠다.



[ B 생성자 ]


int __thiscall sub_411620(void *this) { char v2; // [sp+Ch] [bp-CCh]@1 void *v3; // [sp+D0h] [bp-8h]@1 memset(&v2, -858993460, 0xCCu); v3 = this; sub_411145(this);                                //A의 생성자를 호출함 *(_DWORD *)v3 = &off_417834; return sub_4111C2(); }



[ A 생성자 ]

int __thiscall sub_411720(void *this) { void *v2; // [sp+D0h] [bp-8h]@1 v2 = this; *(_DWORD *)this = &off_417850; return (int)v2; }



A 생성자에서 먼저 vtable에 off_417850을 넣어준다음 다시 B생성자에서 off_417834를 넣어서 덮어버린다. 따라서 여기서는 B의 vtable이 들어가며 A의 vtable은 무시해도 된다.




A의 vtable과 B의 vtable을 보면 아래와 같다.

-----------------

| A::print() |

----------------- <--- A의 vtable - off_417850 (new A();한 경우 사용됨)


-----------------

| B::print() |

----------------- <--- B의 vtable - off_417834 (new B();한 경우 사용됨)


[*] vtable에는 해당 객체의 가장 마지막에 오버라이딩된 가상 함수들이 들어있다.






위와 같이 C++ 가상함수가 있을 때 vtable이 구성되는 것을 확인하였다. 구성 방식이 위와 같으므로 IDA로 볼 때 struct로 잘 naming하여 좀 더 편하게 분석할 수 있도록 해야겠다.







'Reversing > Reversing' 카테고리의 다른 글

code injection에서 shellcode to exe  (0) 2014.01.15
64bit환경의 인자값 전달 방식  (0) 2013.12.15
IDA - 변수에 따른 데이터 영역 정리  (0) 2013.12.14
무료 .NET Decompiler  (1) 2013.11.04
ollydbg로 dll파일 디버깅  (0) 2013.02.28