逆向探究c++之类
一:前言
自己总结的学习的三个过程:
1:知道怎么用
2:知道为什么这么用
3:想怎么用就怎么用
本文探究的知识比较基础,大神请飘过。
二:正文
1:起因
在工作中分析一个进程退出时的崩溃dump时,调查原因发现是子类在调用虚函数时父类已经被释放引起的崩溃,虽然已经解决了此问题,但因此起了对类的继承虚表等特性的探究的想法。
2:探究
首先构建了三个类 TestClass:public FatherTwo :public Fatherone,然后通过od和ida对类的析构和类的结构进行了一些探究。
Class :FathOne(父类)FatherTwo(父类)TestClass(子类)
#ifndef _CLASS_FATHERONE_
#define _CLASS_FATHERONE_
class FatherOne
{
public:
FatherOne();
~FatherOne();
public:
virtual void TestVirFunOne();
private:
int iOne;
};
#endif // _CLASS_FATHERONE_
#include "stdafx.h"
#include "fatherOne.h"
#include <stdio.h>
FatherOne::FatherOne():
iOne(0)
{
printf("FatherOne Init\n");
}
FatherOne::~FatherOne()
{
printf("FatherOne unInit\n");
}
//virtual function
void FatherOne::TestVirFunOne()
{
iOne = 1;
printf("Virtual Function From One\n");
}
#ifndef _CLASS_FATHERTWO_
#define _CLASS_FATHERTWO_
#include "fatherOne.h"
class FatherTwo :
public FatherOne
{
public:
FatherTwo();
~FatherTwo();
public:
virtual void TestVirFunTwo();
private:
int iTwo;
};
#endif
#include "stdafx.h"
#include "fatherTwo.h"
#include <stdio.h>
FatherTwo::FatherTwo():
iTwo(0)
{
printf("FatherTwo Init\n");
}
FatherTwo::~FatherTwo()
{
printf("FatherTwo UnInit\n");
}
// virtual function
void FatherTwo::TestVirFunTwo()
{
iTwo = 1;
printf("Virtual Function From Two\n");
}
#ifndef _ClASS_TEST_
#define _ClASS_TEST_
#include "stdafx.h"
#include "fatherTwo.h"
class TestClass :
public FatherTwo
{
public:
TestClass();
~TestClass();
public:
virtual void TestVirFun();
private:
int iTest;
};
#endif
#include "stdafx.h"
#include "test.h"
#include <stdio.h>
TestClass::TestClass():
iTest(0)
{
printf("TestClass Init\n");
}
TestClass::~TestClass()
{
printf("TestClass UnInit\n");
}
// virtual function
void TestClass::TestVirFun()
{
iTest = 1;
printf("Virtual Function From Test\n");
}
TestClass继承自FatherTwo,FatherTwo继承自FatherOne,三个类的结构类似:都包含一个虚函数和一个私有属性。
对以下代码进行调试测试:
int _tmain(int argc, _TCHAR* argv[])
{
TestClass test;
test.TestVirFun();
// stop the pro to get the result
int getChar = 0;
scanf_s("%d", &getChar);
return 0;
} 具体的od分析 (只贴出了main的反汇编代码,其他的非常简单,在这里略去。有兴趣可以自行研究)
00EB18D0 > 55 push ebp ; main函数
00EB18D1 8BEC mov ebp,esp
00EB18D3 6A FF push -0x1
00EB18D5 68 E853EB00 push TestClas.00EB53E8
00EB18DA 64:A1 00000000 mov eax,dword ptr fs:[0]
00EB18E0 50 push eax
00EB18E1 81EC F4000000 sub esp,0xF4
00EB18E7 53 push ebx
00EB18E8 56 push esi
00EB18E9 57 push edi
00EB18EA 8DBD 00FFFFFF lea edi,dword ptr ss:[ebp-0x100]
00EB18F0 B9 3D000000 mov ecx,0x3D
00EB18F5 B8 CCCCCCCC mov eax,0xCCCCCCCC
00EB18FA F3:AB rep stos dword ptr es:[edi]
00EB18FC A1 00A0EB00 mov eax,dword ptr ds:[__security_cookie]
00EB1901 33C5 xor eax,ebp
00EB1903 8945 F0 mov dword ptr ss:[ebp-0x10],eax
00EB1906 50 push eax
00EB1907 8D45 F4 lea eax,dword ptr ss:[ebp-0xC]
00EB190A 64:A3 00000000 mov dword ptr fs:[0],eax
00EB1910 8D4D DC lea ecx,dword ptr ss:[ebp-0x24] ; 首先分配空间,调用构造函数
00EB1913 E8 C3F8FFFF call TestClas.00EB11DB ; TestClas.TestClass::TestClass
00EB1918 C745 FC 00000000 mov dword ptr ss:[ebp-0x4],0x0
00EB191F 8D4D DC lea ecx,dword ptr ss:[ebp-0x24]
00EB1922 E8 E2F7FFFF call TestClas.00EB1109 ; TestClas.TestClass::TestVirFun
00EB1927 C745 D0 00000000 mov dword ptr ss:[ebp-0x30],0x0
00EB192E 8BF4 mov esi,esp
00EB1930 8D45 D0 lea eax,dword ptr ss:[ebp-0x30]
00EB1933 50 push eax
00EB1934 68 7079EB00 push TestClas.00EB7970 ; ASCII "%d"
00EB1939 FF15 1CB1EB00 call dword ptr ds:[<&MSVCR120D.scanf_s>] ; MSVCR120.scanf_s
00EB193F 83C4 08 add esp,0x8
00EB1942 3BF4 cmp esi,esp
00EB1944 E8 2EF8FFFF call TestClas.00EB1177
00EB1949 C785 04FFFFFF 00000000 mov dword ptr ss:[ebp-0xFC],0x0
00EB1953 C745 FC FFFFFFFF mov dword ptr ss:[ebp-0x4],-0x1
00EB195A 8D4D DC lea ecx,dword ptr ss:[ebp-0x24]
00EB195D E8 BBF7FFFF call TestClas.00EB111D ; TestClas.TestClass::~TestClass
00EB1962 8B85 04FFFFFF mov eax,dword ptr ss:[ebp-0xFC]
00EB1968 52 push edx
00EB1969 8BCD mov ecx,ebp
00EB196B 50 push eax
00EB196C 8D15 A419EB00 lea edx,dword ptr ds:[0xEB19A4]
00EB1972 E8 1FF7FFFF call TestClas.00EB1096
这里可以得到几个要点:
1: 构造函数的调用图为
析构函数的调用顺序相同。
2:子类中的虚函数表中虚函数的顺序为先最上层的父类依次下排,最后为子类的虚函数指针,其私有属性的排序也相同,如下图
3:每个类的虚函数表在编译之后是固定的,其存储在PE文件中的rdata段或者data段,从pe的角度可以理解为全局的,如果要进行虚函数hook可以直接从基址+偏移的思路去进行xx。
4:子类和父类的虚函数表并不是连续放在一起的,而是独立的,可能有RTTI干扰的原因(其位于虚函数表指针-4的位置)。
5:从代码中可以看出,我们并没有显示的调用析构函数,但在内存中却看到了析构的代码,这里要感谢编译器的强大功能。(其实很多语法是由编译器的,从程序的本质 (代码+数据)的角度,语法其实是编译器对于一些规则的限定而已),虽然是这样,但我们在使用类的时候,要自动脑补编译器为我们添加的代码,这样才可以更好的把握住我们写的代码。
6:当无虚函数时,类偏移0(也就是原来虚函数表地址)的值为空,所以也无法确定RTTI的地址(通过RTTI可以获取很多信息,对逆向用处比较大的是类的继承关系和类的链接符号名称),故通过RTTI进行typeid和dynamic_cast时是错误的,当然这个编译器会自动报错提醒。(编译器真的很强大)
第一次写排版不太好,如果感兴趣请看附件吧。
[课程]Linux pwn 探索篇!
上传的附件: