Back

虚函数以及虚表在IDA中的体现

Reverse中虚函数/虚表浅析

this指针

在C++的类中,需要研究一个非常重要并且特殊的指针就是this指针:

一个简单的demo来测试一下this指针:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main() {
	struct Test{
		int a;
		int b;
		int c;
	};
	Test* test = new Test;
	test->a = 100;
	printf("%d", *test);
}

可以看到,直接取test的值就是a的值,这里的test就是this指针本身,而this指针的特性就是指向类的首地址,在对类进行内存分配时,this指针会默认指向该类的首地址:

image-20211231145134480

关于类和结构体:其底层实现是一样的,所以demo中用struct和class的结果相同

image-20211231145838157

demo2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;


struct Test {
	int a;
	int b;
	int c;
	virtual void func1();
	virtual void func2();
	virtual void func3();

};

void Test::func1() {
	printf("func1");
}

void Test::func2() {
	printf("func2");
}

void Test::func3() {
	printf("func3");
}

int main() {

	Test* test = new Test;
	test->a = 100;
	test->func1();
	printf("%d", *test);
}

这里我定义了一个结构体,带着三个虚函数,虚函数需要定义,通过结构体名::方法名的形式进行定义,重点在实例化对象调用函数的位置,调试来到反汇编窗口,红框中标注的为实际调用函数的位置,这里我们在声明结构体时从上到下的声明为func1func2func3,我们注意到这里call的函数内容来自[edx][edx+4][edx+8]

image-20211231151743342

VS的监视器中已经帮我们标注出来了,存在一个__vfptr,这里指针就是指向虚表的指针,对于一个类or结构体中,如果其虚函数的数量大于1,那么编译器在初始化时会自动创建一个虚表,用来存储函数的地址,通过代码的编写顺序进行函数索引的映射,虚表中存放的就是函数定义的地址:

image-20211231152320103

但是在VS中默认开启了增量链接,所以此时虚表中指向的还是一个跳转地址,跳转地址之后才是函数真正的位置:

image-20211231152713891

image-20211231152733713

IDA加载该程序

直接来到main函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  _DWORD *v4; // [esp+Ch] [ebp-DCh]
  void *v5; // [esp+14h] [ebp-D4h]
  _DWORD *v6; // [esp+E0h] [ebp-8h]

  __CheckForDebuggerJustMyCode(&unk_41C066);
  v5 = operator new(0x10u);
  if ( v5 )
    v4 = (_DWORD *)sub_411460(v5);
  else
    v4 = 0;
  v6 = v4;
  v4[1] = 100;
  (*(void (__thiscall **)(_DWORD *))*v6)(v6);
  (*(void (__thiscall **)(_DWORD *))(*v6 + 4))(v6);
  (*(void (__thiscall **)(_DWORD *))(*v6 + 8))(v6);
  sub_411442("%d", *v4);
  return 0;
}

根据源码,这一部分就是虚函数调用在IDA中的反编译结果:

1
2
3
(*(void (__thiscall **)(_DWORD *))*v6)(v6);
(*(void (__thiscall **)(_DWORD *))(*v6 + 4))(v6);
(*(void (__thiscall **)(_DWORD *))(*v6 + 8))(v6);

这里v6是指向v4,来到v4的初始化,可以看到存在一个virtual function table也就是虚函数表:

1
2
3
4
5
_DWORD *__thiscall sub_411880(_DWORD *this)
{
  *this = &Test::`vftable';
  return this;
}

根据前面VS中的调试可以知道这个表中存放的是增量链接:

image-20211231160032664

基本上关于虚函数在IDA中的体现就介绍完了

一道逆向题

SWPUCTF2019 easyRE,同样来到main函数,本文重点在虚函数的创建以及体现,其他部分不做分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int __cdecl main(int argc, const char **argv, const char **envp)
{
  _DWORD v4[28]; // [esp-6Ch] [ebp-F8h] BYREF
  _DWORD *v5; // [esp+4h] [ebp-88h]
  _DWORD *v6; // [esp+8h] [ebp-84h]
  int v7; // [esp+Ch] [ebp-80h]
  char v8[108]; // [esp+10h] [ebp-7Ch] BYREF
  int v9; // [esp+88h] [ebp-4h]

  if ( sub_40EF90() )
    return 1;
  sub_4026C0(0x6Cu);
  sub_401FE0(v4[27], v5);
  v9 = 0;
  v6 = v4;
  sub_40F360(v8);
  sub_40F080(v4[0], v4[1]);
  v5 = v4;
  sub_40F360(v8);
  sub_40F150(argc, (int)argv);
  v7 = 0;
  v9 = -1;
  sub_4021C0(v8);
  return v7;
}

进入sub_401FE0:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
_DWORD *__thiscall sub_401FE0(_DWORD *this)
{
  int i; // [esp+4h] [ebp-14h]

  *this = &EASYRE::`vftable';
  this[1] = 0;
  *((_BYTE *)this + 52) = 8;
  *((_BYTE *)this + 53) = -22;
  *((_BYTE *)this + 54) = 88;
  *((_BYTE *)this + 55) = -34;
  *((_BYTE *)this + 56) = -108;
  *((_BYTE *)this + 57) = -48;
  *((_BYTE *)this + 58) = 59;
  *((_BYTE *)this + 59) = -66;
  *((_BYTE *)this + 60) = -120;
  *((_BYTE *)this + 61) = -44;
  *((_BYTE *)this + 62) = 50;
  *((_BYTE *)this + 63) = -74;
  *((_BYTE *)this + 64) = 20;
  *((_BYTE *)this + 65) = -126;
  *((_BYTE *)this + 66) = -73;
  *((_BYTE *)this + 67) = -81;
  *((_BYTE *)this + 68) = 20;
  *((_BYTE *)this + 69) = 84;
  *((_BYTE *)this + 70) = 127;
  *((_BYTE *)this + 71) = -49;
  qmemcpy(this + 18, "  03\"3   0 203\"   $ ", 20);
  sub_4030A0(this + 23);
  sub_402DE0(this + 26);
  for ( i = 0; i < 40; ++i )
    *((_BYTE *)this + i + 12) = 0;
  return this;
}

该题目中不存在增量链接,所以这里的虚函数表中指向的就是函数真正的地址:

image-20211231160559928

来到校验flag的函数,可以发现存在虚函数的特征:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
BOOL __thiscall sub_4024B0(_DWORD *this, int flag)
{
  BOOL result; // eax

  this[2] = flag;
  result = 0;
  if ( (*(int (__thiscall **)(_DWORD *))(*this + 0xC))(this) )
  {
    (*(void (__thiscall **)(_DWORD *))(*this + 0x18))(this);
    if ( (*(int (__thiscall **)(_DWORD *))(*this + 0x28))(this) )
      result = 1;
  }
  return result;
}

这里this指针指向的首地址是0x4124E4,根据偏移可以映射:

1
2
3
(*(int (__thiscall **)(_DWORD *))(*this + 0xC))(this) -> 0x4124F0 -> sub_402500
(*(void (__thiscall **)(_DWORD *))(*this + 0x18))(this) -> 0x4124FC -> sub_4026E0
(*(int (__thiscall **)(_DWORD *))(*this + 0x28))(this) -> 0x41250C -> sub_402A00

映射:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
BOOL __thiscall sub_4024B0(_DWORD *this, int flag)
{
  BOOL result; // eax

  this[2] = flag;
  result = 0;
  if ( sub_402500() )
  {
    sub_4026E0();
    if ( sub_402A00() )
      result = 1;
  }
  return result;
}

End

Licensed under CC BY-NC-SA 4.0
YuSec Github Blog
Built with Hugo
Theme Stack designed by Jimmy