为了探究虚表的今生前世,先来一段测试代码

虚函数类:

 class    CTest
{
public:
int m_nData; virtual void PrintData()
{
printf("Data = 0x%x\n", m_nData);
}
}; class CBase1
{
public:
int m_nData; virtual void PrintData1() = ;
}; class CBase2
{
public:
int m_nData; virtual void PrintData2() = ;
}; class CBaseTest : public CBase1, public CBase2
{
public:
void PrintData1()
{
printf("Data = 0x%x\n", CBase1::m_nData);
} void PrintData2()
{
printf("Data = 0x%x\n", CBase2::m_nData);
}
};

测试代码:

 void    Test()
{
CTest oCTest;
CTest* pCTest = new CTest(); pCTest->m_nData = 0x8888;
pCTest->PrintData(); oCTest.m_nData = ;
oCTest.PrintData(); delete pCTest;
} void BaseTest()
{
CBaseTest oCBaseTest; oCBaseTest.PrintData1();
}

1、虚表位于何处?

WinDbg显示虚表的地址的属性:Usage    RegionUsageImage(代表此地址区域被映射到二进制文件的镜像),为只读属性。

2、同一个类对象的虚表位置相同吗?

同一个类对象的虚表位置相同。

加载模块的内存位置:0x00380000

虚表的VA = 0x003FDF2C - 0x00380000 = 0x0007DF2C

3、虚表需要在加载后进行初始化吗?

否,虚表的位置在PE文件的 .rdata 节中,.rdata 是存放程序常量的地方,属性为只读

从2的截图中可以看出,虚表第一个函数(CTest::PrintData)的地址 = 0x003A9EFB
其VA =  0x003A9EFB- 0x00380000 = 0x00029EFB
由于该PE文件的基址 = 0x00040000(默认情况下)
故其未重定位前的虚拟地址 = 0x00040000 + 0x00029EFB = 0x00429EFB
由于x86平台文件都是小尾储存,倒过来写就是 FB 9E 42 00,即如图左上红圈所示
说明PE文件在编译好后已经将虚表和虚表中的虚函数地址填写完毕并写入.rdata区

4、多父类继承的虚表如何存放?

多重继承的子类对象实际上是将每个父类的完整数据按顺序依次排布,所以拥有每个父类的虚表,父类每个虚表的位置同样在每个父类的起始位置。

oCBaseTest对象内存分布

5、何为虚表Hook?
通过以上对虚表的来龙去脉的分析,有IAT Hook基础的同学可以很容易的想到如何进行虚表Hook了。
5.1 改PE文件
    直接改掉PE文件虚表位置的函数指针,指向自己伪造的虚表地址(当然必须保证自己的虚表函数指针有效,且函数调用形式和参数个数一致)
5.2 内存中Hook
    当PE文件加载后,直接在内存中修改虚表函数指针(注意要去写保护,当然调试工具是没问题的)。

5.3 Hook测试(内存Hook的方式)

测试代码:

 class    CVFHook
{
public:
typedef void (CVFHook::* MemberFunctionPtr)(); public:
static BOOL Hook(void* pVirtrulFunctionVA); private:
void PrintData();
};
BOOL CVFHook::Hook(void* pVirtrulFunctionVA)
{
HMODULE hHookedModule = ::GetModuleHandle(NULL); if (NULL == hHookedModule)
{
_tprintf(_T("Get module handle fail\n"));
return FALSE;
} //取重定位后的虚表地址
MemberFunctionPtr* pMemberFunctionPtr = (MemberFunctionPtr*)((INT64)pVirtrulFunctionVA + (INT64)hHookedModule); //去除读写保护
DWORD dwOldProtect = ;
::VirtualProtect(pMemberFunctionPtr, sizeof(pMemberFunctionPtr), PAGE_READWRITE, &dwOldProtect);
*pMemberFunctionPtr = &CVFHook::PrintData; return TRUE;
} void CVFHook::PrintData()
{
printf("PrintData is facked!!!\n");
}

然后再dll的加载位置传入虚表的VA参数调用即可:

 BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
CVFHook::Hook((void*)0x0007DF2C);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

注意:这里我们伪造的虚函数 CVFHook::PrintData() 我直接用的是与要 Hook 的虚函数相同的成员函数形式,由于Release版的代码可能会被编译器优化掉函数调用形式,如果如此,需要我们手动编写汇编形式的虚函数代码。

启动测试程序 Console.exe ,然后用 APIMonitor 工具将 VirtrualFunctionHook.dll 注入,然后按任意键观察结果如下:

我们发现堆对象被成功Hook了,但局部对象没有被Hook掉,这是为什么呢?难道上面的分析错了?

我们来对前面的测试代码进行反汇编后观察:

发现对象调用PrintData时并没有通过虚表调用,而是直接使用普通成员函数的调用方式,以节约开销,只有使用对象指针或引用来调用虚函数时才会使用虚表调用的方式,故虚表Hook的方式失效.

C++浅析——虚表和虚表Hook的更多相关文章

  1. 逆向实用干货分享,Hook技术第二讲,之虚表HOOK

    逆向实用干货分享,Hook技术第二讲,之虚表HOOK 正好昨天讲到认识C++中虚表指针,以及虚表位置在反汇编中的表达方式,这里就说一下我们的新技术,虚表HOOK 昨天的博客链接: http://www ...

  2. C++学习 - 虚表,虚函数,虚函数表指针学习笔记

    http://blog.csdn.net/alps1992/article/details/45052403 虚函数 虚函数就是用virtual来修饰的函数.虚函数是实现C++多态的基础. 虚表 每个 ...

  3. C++ 继承与接口 知识点 小结(一)

    [摘要] 要求理解覆盖.重载.隐藏的概念与相互之间的差别.熟记类继承中对象.函数的訪问控制:掌握虚函数.虚函数表.虚函数指针的联系:理解区分虚函数和虚继承在虚方法.虚指针在空间分配上的重点与难点:熟练 ...

  4. c++ 虚函数和纯虚函数

    在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的.从设计的角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现.通过这样的方法,就可以将对象 ...

  5. Oracle数据库基础知识2

    字符操作相关_1 1.CONCAT关键字作用:连接字符串语法:CONCAT(字串1, 字串2)例如: CONCAT('hello','world') FROM DUAL; 注意:Oracle的CONC ...

  6. C++学习笔记 封装 继承 多态 重写 重载 重定义

    C++ 三大特性 封装,继承,多态 封装 定义:封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员,目的在于将对 ...

  7. C++类所占内存大小计算

    C++类所占内存大小计算 说明:笔者的操作系统是32位的. class A {}; sizeof( A ) = ? sizeof( A ) = 1明明是空类,为什么编译器说它是1呢? 空类同样可以实例 ...

  8. OD: Windows Security Techniques & GS Bypassing via C++ Virtual Function

    Windows 安全机制 漏洞的万源之本在于冯诺依曼设计的计算机模型没有将代码和数据进行区分——病毒.加壳脱壳.shellcode.跨站脚本攻击.SQL注入等都是因为计算机把数据和代码混淆这一天然缺陷 ...

  9. [置顶] 【C/C++学习】之十三、虚函数剖析

    所谓虚函数,虚就虚在“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的.由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被称为“ ...

随机推荐

  1. get()和post()方法编码的区别

    在做ssh搭建的框架中,在后台条件查询的时候,组合查询传到后台的值一直是乱码,其中在form表单中必须要加上method,这一点是肯定的,但是加上了提交的方式之后,会出现如下问题: 如果使用get方法 ...

  2. HBase集群安装过程中的问题集锦

    1.HRegionServer启动不正常 在namenode上执行jps,则可看到hbase启动是否正常,进程如下: [root@master bin]# jps26341 HMaster26642 ...

  3. jQuery jquery.windy 快速浏览内容

    在线实例 效果一 效果二 效果三 使用方法 <div class="container">     <section class="main" ...

  4. Skippr – 轻量、快速的 jQuery 幻灯片插件

    Skippr 是一个超级简单的 jQuery 幻灯片插件.只是包括你的网页中引入 jquery.skippr.css 和 jquery.skippr.js 文件就能使用了.Skippr 能够自适应窗口 ...

  5. 24个很赞的 Node.js 免费教程和在线指南

    JavaScript 最初是用来创建动态网站效果的的前端语言.而如今,这门脚本语言也可以用作后端开发,用于搭建 Web 服务器,开发接口,甚至创建博客.在下面这个列表中包括24个 Node.js 教程 ...

  6. (转)JavaScript二:JavaScript语言的基本语法要求

    摘自:http://blog.csdn.net/erlian1992 要学习好JavaScript,首先我们要懂JavaScript语言的一些基本语法要求: 一,区分大小写 JavaScript语言区 ...

  7. 【web前端面试题整理01】各位加班累了吧,来做点前端面试题吧

    前言 最近小叶子有点疲惫,主要是在外地工作生活上不太适应,吃一样的东西,我居然会拉肚子,而且是一个星期一个星期的.... 脸上长了一个豆豆一个星期还没消,我那个去啊. 昨天上午上班后,本来想继续研究j ...

  8. mysql支持IOS的Emoji表情

    原因: UTF-8编码有可能是两个.三个.四个字节.Emoji表情是4个字节,而Mysql的utf8编码最多3个字节,所以数据插不进去. 解决办法: 将Mysql的编码从utf8转换成utf8mb4 ...

  9. MJRefresh自定义刷新动画

    [一]常见用法 最原始的用法,耦合度低,但是不能统一管理.我们需要在每一个控制器都写以下代码,很繁琐,以后项目修改起来更繁琐,得一个控制器一个控制器的去定位.修改. 1.1 使用默认刷新(耦合度底,但 ...

  10. iOS 获取User-Agent

    第一种方法 UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];    NSString *userAgent = [w ...