vtable

ISO 표준

Itanium ABI

MSVC ABI

테스트 환경

ubuntu 22.04 WSL

class Base {
public:
  Base(){}
  ~Base(){}
  int b0 = 10;
  int b1 = 20;
  int* b2 = &b0;
  int* b3 = &b1;

  void print() { std::cout << "test" << std::endl; }

};

class Derive : public Base {
public:
  int d0 = 11;
  int d1 = 21;
  int* d2 = &d0;
  int* d3 = &d1;

  void print2() { std::cout << "test" << std::endl; }
};

class Base2 {
public:
  Base2(){}
  ~Base2(){}
  int b0 = 30;
  int b1 = 40;
  int* b2 = &b0;
  int* b3 = &b1;

  virtual void print() { std::cout << "test" << std::endl; }

};

class Derive2 : public Base2 {
public:
  int d0 = 31;
  int d1 = 41;
  int* d2 = &d0;
  int* d3 = &d1;

  void print() override { std::cout << "test" << std::endl; }
};
pwndbg> x/8gx *(void**)($rbp-0x20)
0x55555556b300: 0x0000555555557d38      0x000000280000001e
0x55555556b310: 0x000055555556b308      0x000055555556b30c
0x55555556b320: 0x000000290000001f      0x000055555556b320
0x55555556b330: 0x000055555556b324      0x000000000000ecd1
pwndbg> x/8gx *(void**)($rbp-0x28)
0x55555556aeb0: 0x000000140000000a      0x000055555556aeb0
0x55555556aec0: 0x000055555556aeb4      0x000000150000000b
0x55555556aed0: 0x000055555556aec8      0x000055555556aecc
0x55555556aee0: 0x0000000000000000      0x0000000000000411

virtual 함수가 있어야만 vtable 포인터가 생성된다.

+0x00: int   b0
+0x04: int   b1
+0x08: int*  b2 (&b0)
+0x10: int*  b3 (&b1)
+0x18: int   d0
+0x1C: int   d1
+0x20: int*  d2 (&d0)
+0x28: int*  d3 (&d1)


+0x00: void* vptr   (vtable 엔트리들을 가리킴)
+0x08: int   b0
+0x0C: int   b1
+0x10: int*  b2 (&b0)
+0x18: int*  b3 (&b1)
+0x20: int   d0
+0x24: int   d1
+0x28: int*  d2 (&d0)
+0x30: int*  d3 (&d1)

3b408ca0-cecf-493a-90a4-ba2312ea5a08
3b408ca0-cecf-493a-90a4-ba2312ea5a08

Derive2 클래스 vtable의 offset-to-top 부터 읽어보면, 이런 형식으로 되어있다.
vtable[0]은 override된 print함수이다.

pwndbg> x/4gx (*((void**)*(void**)($rbp-0x20)) - 0x10)
0x555555557d28 <_ZTV7Derive2>:  0x0000000000000000      0x0000555555557d58
0x555555557d38 <_ZTV7Derive2+16>:       0x00005555555553f8      0x0000000000000000

pwndbg> x/10i **(void***)*(void**)($rbp-0x20)
   0x5555555553f8 <_ZN7Derive25printEv>:        endbr64
   0x5555555553fc <_ZN7Derive25printEv+4>:      push   rbp
   0x5555555553fd <_ZN7Derive25printEv+5>:      mov    rbp,rsp
   0x555555555400 <_ZN7Derive25printEv+8>:      sub    rsp,0x10

pwndbg> x/6gx *(void**)(*((void**)*(void**)($rbp-0x20)) - 0x8)
0x555555557d58 <_ZTI7Derive2>:  0x00007ffff7fa3c30      0x0000555555556010
0x555555557d68 <_ZTI7Derive2+16>:       0x0000555555557d70      0x00007ffff7fa2fa0
0x555555557d78 <_ZTI5Base2+8>:  0x0000555555556019      0x0000000000000001

RTTI 객체는 7d58이고, 구조는 아래와 같다.

[0] vptr to vtable of __si_class_type_info
[1] const char* name
[2] const __class_type_info* base_type

그래서 실제로 읽어보면 [1]6010은 Derive 문자열이고, [2]7d70은 Base 클래스의 RTTI라서 [2][1]은 Base 문자열인 것을 확인할 수 있다.

pwndbg> x/1s 0x555555556010
0x555555556010 <_ZTS7Derive2>:  "7Derive2"

pwndbg> x/2gx 0x0000555555557d70
0x555555557d70 <_ZTI5Base2>:    0x00007ffff7fa2fa0      0x0000555555556019

pwndbg> x/1s 0x0000555555556019
0x555555556019 <_ZTS5Base2>:    "5Base2"

Comments

ESC
Type to search...