Virtual Function and Virtual Function Call Hijacking

20171211001

Introduction

In this post, I will first give a detailed explanation on how virtual function works on Linux platform. Based on my given information, I will further explain how virtual function call can be utilised to hijack control flow.

Virtual Function

The virtual function is a key part in C++, which supports polymorphism. I will talk about the constructor function, virtual table and virtual function call respectively. The source code used in this post is given below.

// g++ test.cpp -o test

#include<iostream>
using namespace std;

class Test
{
    public :
        int count;
        virtual void Show()
        {
            cout<<"I am in Test Class"<<endl;
        }

        Test()
        {
             count = -1;
        }
};

class Test1 : public Test
{
      public:
           virtual void Show()
           {
            cout<<"I am in Test1 Class"<<endl;
           }
           virtual void T1Show()
           {
            cout<<"I am in Test1 own Class"<<endl;
           }
	  Test1() {t1 = 1;}
	  private:
		  int t1;

};

class Test2 : public Test
{
      public:
           virtual void Show()
           {
            cout<<"I am in Test2 Class"<<endl;
           }
           virtual void T2Show()
           {
            cout<<"I am in Test2 own Class"<<endl;
           }
           Test2() {t2 = 2;}
	   private:
		  int t2;

};

class Test3: public Test1, public Test2
{
	  public:
			virtual void Show()
			{
				cout<<"I am in Test Derived Class"<<endl;
			}
			virtual void T1Show()
			{
				cout<<"I am in T1 Derived Class"<<endl;
			}
			virtual void T2Show()
			{
				cout<<"I am in T2 Derived Class"<<endl; 			}                         Test3() {t3 = 3;} 	  private: 		  int t3; }; int foo(Test3 *t) { 	t->Show();
	return 0;
}

int bar(Test t)
{
	t.Show();
	return 0;
}

int main()
{
    Test *Obj;
    Test BaseObj;   // Base Class Object
    Test1 Obj1;
    Test2 Obj2;
    Test3 Obj3;
    Test3 *Obj4 = new Test3();
    int x;
    Obj = &BaseObj;
    Obj->Show();  //In this case derived class show function called.

    cin>>x;
    if(x%2==1)
    {
              Obj = &Obj1;
    }
    else if(x%2==0)
    {
         Obj = &Obj2;
    }
    Obj->Show();
    foo(Obj4);
    bar(BaseObj);
    return 0;
}

Constructor Function

The constructor function is the first step to create an object that supports virtual function. The following assembly code lists the constructor functions in the target binary. As presented in the source code, I create a diamond structure in the inheritance relation for Class test3. So let’s dive into the assembly code and see how the constructor function works.

|0x00000dca      4881ec980000.  sub rsp, 0x98
|0x00000dd1      488d45c0       lea rax, qword [rbp - local_40h]
|0x00000dd5      4889c7         mov rdi, rax
|0x00000dd8      e889010000     call fcn.00000f66
|0x00000ddd      488d45b0       lea rax, qword [rbp - local_50h]
|0x00000de1      4889c7         mov rdi, rax
|0x00000de4      e811020000     call sym.Test1::Test1
|0x00000de9      488d45a0       lea rax, qword [rbp - local_60h]
|0x00000ded      4889c7         mov rdi, rax
|0x00000df0      e8a9020000     call sym.Test2::Test2
|0x00000df5      488d8570ffff.  lea rax, qword [rbp - local_90h]
|0x00000dfc      4889c7         mov rdi, rax
|0x00000dff      e882030000     call sym.Test3::Test3
|0x00000e04      bf28000000     mov edi, 0x28               ; '('
|0x00000e09      e822feffff     call sym.operatornew
|0x00000e0e      4889c3         mov rbx, rax
|0x00000e11      4889df         mov rdi, rbx
|0x00000e14      e86d030000     call sym.Test3::Test3

Constructor Function of Base Object
As the base class in the source code, the constructor function only needs to do two jobs: (1) set the virtual table pointer of the current object (0xf6e), and (2) initialise the member variable (0xf80).

|; var int local_8h @ rbp-0x8
|; CALL XREF from 0x00000dd8 (sym.main)
|; CALL XREF from 0x0000100d (sym.Test1::Test1)
|; CALL XREF from 0x000010b1 (sym.Test2::Test2)
|0x00000f66      55             push rbp
|0x00000f67      4889e5         mov rbp, rsp
|0x00000f6a      48897df8       mov qword [rbp - local_8h], rdi
|0x00000f6e      488d15bb0d20.  lea rdx, qword 0x00201d30   ; 0x201d30
|0x00000f75      488b45f8       mov rax, qword [rbp - local_8h]
|0x00000f79      488910         mov qword [rax], rdx
|0x00000f7c      488b45f8       mov rax, qword [rbp - local_8h]
|0x00000f80      c74008ffffff.  mov dword [rax + 8], 0xffffffff ;
|0x00000f87      90             nop
|0x00000f88      5d             pop rbp
\0x00000f89      c3             ret

Constructor Function of Derived Object Test1
As a derived class of the base class, the constructor function of class Test1 clearly contains more steps than base class. In the first, it will call the constructor function of base class to initialise the its member variable (count) and put the vtable (virtual table) pointer on the top of object. Next, the vtable pointer will be overwritten with its own vtable pointer (0x101d) and its own member variable will be initialised (0x1024).

|; var int local_8h @ rbp-0x8
|; CALL XREF from 0x00000de4 (sym.main)
|; CALL XREF from 0x00001199 (sym.Test3::Test3)
|0x00000ffa      55             push rbp
|0x00000ffb      4889e5         mov rbp, rsp
|0x00000ffe      4883ec10       sub rsp, 0x10
|0x00001002      48897df8       mov qword [rbp - local_8h], rdi
|0x00001006      488b45f8       mov rax, qword [rbp - local_8h]
|0x0000100a      4889c7         mov rdi, rax
|0x0000100d      e854ffffff     call fcn.00000f66
|0x00001012      488d15f70c20.  lea rdx, qword 0x00201d10   ; 0x201d10
|0x00001019      488b45f8       mov rax, qword [rbp - local_8h]
|0x0000101d      488910         mov qword [rax], rdx
|0x00001020      488b45f8       mov rax, qword [rbp - local_8h]
|0x00001024      c7400c010000.  mov dword [rax + 0xc], 1
|0x0000102b      90             nop
|0x0000102c      c9             leave
\0x0000102d      c3             ret

The constructor function of class Test2 is almost the same. It will first call the constructor function of the base class and initialise its own member variable afterwards.

Constructor Function of Derived Object Test3
The constructor function of a diamond structured object is a little bit complicated. It will try to call the constructor function of Test1 and Test2 in the first and then overwrite the vtable pointer of the current object and initialise its own membere variable.

|; var int local_8h @ rbp-0x8
|; CALL XREF from 0x00000dff (sym.main)
|; CALL XREF from 0x00000e14 (sym.main)
|0x00001186      55             push rbp
|0x00001187      4889e5         mov rbp, rsp
|0x0000118a      4883ec10       sub rsp, 0x10
|0x0000118e      48897df8       mov qword [rbp - local_8h], rdi
|0x00001192      488b45f8       mov rax, qword [rbp - local_8h]
|0x00001196      4889c7         mov rdi, rax
|0x00001199      e85cfeffff     call sym.Test1::Test1
|0x0000119e      488b45f8       mov rax, qword [rbp - local_8h]
|0x000011a2      4883c010       add rax, 0x10
|0x000011a6      4889c7         mov rdi, rax
|0x000011a9      e8f0feffff     call sym.Test2::Test2
|0x000011ae      488d15f30a20.  lea rdx, qword 0x00201ca8   ; 0x201ca8
|0x000011b5      488b45f8       mov rax, qword [rbp - local_8h]
|0x000011b9      488910         mov qword [rax], rdx
|0x000011bc      488d150d0b20.  lea rdx, qword 0x00201cd0   ; 0x201cd0
|0x000011c3      488b45f8       mov rax, qword [rbp - local_8h]
|0x000011c7      48895010       mov qword [rax + 0x10], rdx
|0x000011cb      488b45f8       mov rax, qword [rbp - local_8h]
|0x000011cf      c74020030000.  mov dword [rax + 0x20], 3
|0x000011d6      90             nop
|0x000011d7      c9             leave
\0x000011d8      c3             ret

From the operation above, we can infer the memory layout of class Test3 as following:

    ++++++++++++++++++++++++++++
 0x8+    vft addr of test1     +
    ++++++++++++++++++++++++++++
0x10+ member variable of test1 +
    ++++++++++++++++++++++++++++
0x18+     vft addr of test2    +
    ++++++++++++++++++++++++++++
0x20+ member variable of test2 +
    ++++++++++++++++++++++++++++
0x28+     private variable     +
    ++++++++++++++++++++++++++++

Virtual Function Table

After discussing the constructor function, let’s talk about the virtual table.
Virtual Table of Class Test
In the virtual table of base class it only contains a function pointer, which points to Test::show.

[0x00000c50]> pxq 0x10 @0x201d30
0x00201d30  0x0000000000000f2e  0x0000000000000000   ................
[0x00000c50]> pd 1 @0xf2e
            ;-- Test::Show:
            0x00000f2e      55             push rbp

Virtual Table of Class Test1
The virtual table of the derived object is shown as below. The first function pointer points to the function derived from base class (Test1::Show). The second function pointer points to its own virtual function (Test1::T1Show).

[0x00000c50]> pxq 0x10 @0x201d10
0x00201d10  0x0000000000000f8a  0x0000000000000fc2   ................

[0x00000c50]> pd 1 @0xf8a
            ;-- Test1::Show:
            0x00000f8a      55             push rbp
[0x00000c50]> pd 1 @0xfc2
            ;-- Test1::T1Show:
            0x00000fc2      55             push rbp

Virtual Table of Class Test3
The virtual table of class Test3 is much more interesting. In the object of class Test3, there exists two virtual table pointers (0x201ca8 and 0x201cd0) as shown below.

[0x00000c50]> pxq 0x18 @0x201ca8
0x00201ca8  0x00000000000010d2  0x0000000000001110   ................
0x00201cb8  0x0000000000001148                       H.......
[0x00000c50]> pxq 0x10 @0x201cd0
0x00201cd0  0x0000000000001109  0x000000000000117f   ................

[0x00000c50]> pd 1 @0x10d2
            ;-- Test3::Show:
            0x000010d2      55             push rbp
[0x00000c50]> pd 1 @0x1110
            ;-- Test3::T1Show:
            0x00001110      55             push rbp
[0x00000c50]> pd 1 @0x1148
            ;-- Test3::T2Show:
            0x00001148      55             push rbp
[0x00000c50]> pd 3 @0x1109
        |   ;-- non-virtualthunktoTest3::Show():
        |   0x00001109      4883ef10       sub rdi, 0x10
        `=< 0x0000110d      ebc3           jmp sym.Test3::Show             0x0000110f      90             nop [0x00000c50]> pd 3 @0x117f
        |   ;-- non-virtualthunktoTest3::T2Show():
        |   0x0000117f      4883ef10       sub rdi, 0x10
        `=< 0x00001183      ebc3           jmp sym.Test3::T2Show
            0x00001185      90             nop

The first virtual table pointer is normal, which contains the function pointer of Test3::Show, Test3::T1Show and Test3::T2Show. The second virtual table pointer contains two function pointer that jumps to Test3:Show and Test3:T2Show. The existence of this vtable pointer is for type casting. When an object of class Test3 is cast to class Test2, i.e. plus 0x10 to the base address of the current object, the cast object can still work well with the virtual function of class Test2

Virtual Function Call

Based on the information given above, let’s now come to how a virtual function works at binary level.

/* source code level
Obj = &BaseObj;
Obj->Show();
*/
0x00000e1d      488d45c0       lea rax, qword [rbp - 0x40]
0x00000e21      488945e8       mov qword [rbp - 0x18], rax
0x00000e25      488b45e8       mov rax, qword [rbp - 0x18]
0x00000e29      488b00         mov rax, qword [rax]
0x00000e2c      488b00         mov rax, qword [rax]
0x00000e2f      488b55e8       mov rdx, qword [rbp - 0x18]
0x00000e33      4889d7         mov rdi, rdx
0x00000e36      ffd0           call rax

Therefore, the basic workflow of a virtual function call can be divided into 4 steps:
(1) Fetch vtable pointer (0xe29)
(2) Fetch function pointer (0xe2c)
(3) Set argument variable (0xe33)
(4) Call virtual function (0xe36)

Virtual Function Call Hijacking

In real exploitation, a common technique is to corrupt the vtable pointer located in an object first via some memory corruption errors (use-after-free or overflow). Then attacker needs to craft a fake virtual table in memory and then trigger the virtual function call of the victim object.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.