vtable

vtable

2025년 10월 10일

ISO 표준 #

Itanium ABI #

MSVC ABI #

테스트 환경

ubuntu 22.04 WSL

 1class Base {
 2public:
 3  Base(){}
 4  ~Base(){}
 5  int b0 = 10;
 6  int b1 = 20;
 7  int* b2 = &b0;
 8  int* b3 = &b1;
 9
10  void print() { std::cout << "test" << std::endl; }
11
12};
13
14class Derive : public Base {
15public:
16  int d0 = 11;
17  int d1 = 21;
18  int* d2 = &d0;
19  int* d3 = &d1;
20
21  void print2() { std::cout << "test" << std::endl; }
22};
23
24class Base2 {
25public:
26  Base2(){}
27  ~Base2(){}
28  int b0 = 30;
29  int b1 = 40;
30  int* b2 = &b0;
31  int* b3 = &b1;
32
33  virtual void print() { std::cout << "test" << std::endl; }
34
35};
36
37class Derive2 : public Base2 {
38public:
39  int d0 = 31;
40  int d1 = 41;
41  int* d2 = &d0;
42  int* d3 = &d1;
43
44  void print() override { std::cout << "test" << std::endl; }
45};
 1pwndbg> x/8gx *(void**)($rbp-0x20)
 20x55555556b300: 0x0000555555557d38      0x000000280000001e
 30x55555556b310: 0x000055555556b308      0x000055555556b30c
 40x55555556b320: 0x000000290000001f      0x000055555556b320
 50x55555556b330: 0x000055555556b324      0x000000000000ecd1
 6pwndbg> x/8gx *(void**)($rbp-0x28)
 70x55555556aeb0: 0x000000140000000a      0x000055555556aeb0
 80x55555556aec0: 0x000055555556aeb4      0x000000150000000b
 90x55555556aed0: 0x000055555556aec8      0x000055555556aecc
100x55555556aee0: 0x0000000000000000      0x0000000000000411

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

 1+0x00: int   b0
 2+0x04: int   b1
 3+0x08: int*  b2 (&b0)
 4+0x10: int*  b3 (&b1)
 5+0x18: int   d0
 6+0x1C: int   d1
 7+0x20: int*  d2 (&d0)
 8+0x28: int*  d3 (&d1)
 9
10
11+0x00: void* vptr   (vtable 엔트리들을 가리킴)
12+0x08: int   b0
13+0x0C: int   b1
14+0x10: int*  b2 (&b0)
15+0x18: int*  b3 (&b1)
16+0x20: int   d0
17+0x24: int   d1
18+0x28: int*  d2 (&d0)
19+0x30: int*  d3 (&d1)

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

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

 1pwndbg> x/4gx (*((void**)*(void**)($rbp-0x20)) - 0x10)
 20x555555557d28 <_ZTV7Derive2>:  0x0000000000000000      0x0000555555557d58
 30x555555557d38 <_ZTV7Derive2+16>:       0x00005555555553f8      0x0000000000000000
 4
 5pwndbg> x/10i **(void***)*(void**)($rbp-0x20)
 6   0x5555555553f8 <_ZN7Derive25printEv>:        endbr64
 7   0x5555555553fc <_ZN7Derive25printEv+4>:      push   rbp
 8   0x5555555553fd <_ZN7Derive25printEv+5>:      mov    rbp,rsp
 9   0x555555555400 <_ZN7Derive25printEv+8>:      sub    rsp,0x10
10
11pwndbg> x/6gx *(void**)(*((void**)*(void**)($rbp-0x20)) - 0x8)
120x555555557d58 <_ZTI7Derive2>:  0x00007ffff7fa3c30      0x0000555555556010
130x555555557d68 <_ZTI7Derive2+16>:       0x0000555555557d70      0x00007ffff7fa2fa0
140x555555557d78 <_ZTI5Base2+8>:  0x0000555555556019      0x0000000000000001

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

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

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

1pwndbg> x/1s 0x555555556010
20x555555556010 <_ZTS7Derive2>:  "7Derive2"
3
4pwndbg> x/2gx 0x0000555555557d70
50x555555557d70 <_ZTI5Base2>:    0x00007ffff7fa2fa0      0x0000555555556019
6
7pwndbg> x/1s 0x0000555555556019
80x555555556019 <_ZTS5Base2>:    "5Base2"
comments powered by Disqus