为了简便,就没有创建.h和.cpp文件,直接在main函数中写的,结果在运行时就出现了 undefined reference to `vtable for * * * '这种错误。 看到这里,你也就知道了由于上面三个虚函数没有被实现,所以会有undefined reference to `vtable for * * * '这种错误。 4. 小结 认真生活, 努力感悟!
接下来主要描述如何通过劫持vtable去实现控制函数执行流以及通过FSOP来进行利用。 虚函数表劫持 本文是基于libc 2.23及之前的libc上可实的,libc2.24之后加入了vtable check机制,无法再构造vtable。 ,*fake_vtable; fp=fopen("123.txt","rw"); fake_vtable=malloc(0x40); vtable_addr=(long long *)((long long)fp+0xd8); //vtable offset vtable_addr[0]=(long long)fake_vtable; memcpy( 小结 vtable劫持和FSOP还是比较好理解的,下一篇将介绍vtable check机制和它的绕过方法。
多态的底层实现逻辑 编译器找对应虚函数的步骤: this指针->vptr指针->vtable表(虚函数表)->对应的虚函数(通过指针偏移,vptr就是虚函数表的起始指针,函数指针在虚函数表占的位置也是固定的 调用的过程也是上面这样:his指针->vptr指针->vtable表(虚函数表)->对应的虚函数 为什么只有非静态成员函数才可以是虚拟的?
GCC编译遇到如下的错误,可能是因为在编译时没有指定-fPIC,记住:-fPIC即是编译参数,也是链接参数: relocation R_x86_64_32S against `vtable for CMyClass
深入浅出C++虚函数的vptr与vtable 1.基础理论 为了实现虚函数,C ++使用一种称为虚拟表的特殊形式的后期绑定。该虚拟表是用于解决在动态/后期绑定方式的函数调用函数的查找表。 虚拟表有时会使用其他名称,例如“vtable”,“虚函数表”,“虚方法表”或“调度表”。 虚拟表实际上非常简单,虽然用文字描述有点复杂。 /** * @file vptr1.cpp * @brief C++虚函数vptr和vtable * 编译:g++ -g -o vptr vptr1.cpp -std=c++11 * @author 基类指针指向派生类实例并调用虚函数"<<endl; pt->fun1(); cout<<"基类引用指向基类实例并调用虚函数"<<endl; p.fun1(); // 手动查找vptr 和 vtable 除此之外,上述代码大家会看到,也包含了手动获取vptr地址,并调用vtable中的函数,那么我们一起来验证一下上述的地址与真正在自动调用vtable中的虚函数,比如上述pt->fun1()的时候,是否一致
VTable 的出现,就是为了解决这些痛点:渲染快、交互流畅、表达丰富! 和 vue-vtable 封装,更易集成现代前端框架编辑扩展支持内联编辑、drop handle 批量填充,结合 @visactor/vtable-editors 可扩展输入组件技术架构解析技术优势: 界面效果使用示例安装与快速使用npm install @visactor/vtable# 或yarn add @visactor/vtable
<script src="https://unpkg.com/@visactor/<em>vtable</em>/dist/<em>vtable</em>.min.js 项目地址https://github.com/VisActor/<em>VTable</em>vtable 的大小,并将当前类的 vtable 的大小设置为父类 vtable 的大小。 = NULL || vtable_length == Universe::base_vtable_size(), "bad vtable size for class Object") Universe::base_vtable_size(), "vtable too small"); *vtable_length_ret = vtable_length;//返回虚方法个数 } 上面这段代码计算 vtable 个数的思路主要分为两步 : a:获取父类 vtable 的个数,并将当前类的 vtable 的个数设置为父类 vtable 的个数。 Animal 的 vtable,因此类 Dog 便也有一个 vtable ,并且 vtable 里有一个指针指向类 Animal 的 say方法的内存地址 。
是cq的核心内容 const cq_vtable* vtable = &g_cq_vtable[completion_type]; const cq_poller_vtable* poller_vtable *>( gpr_zalloc(sizeof(grpc_completion_queue) + vtable->data_size + poller_vtable ->size())); //赋值给了cq里的变量 cq->vtable = vtable; cq->poller_vtable = poller_vtable; /* One for 和g_poller_vtable_by_poller_type都定义在completion_queue.cc文件中,很容易定位到 这里需要解释的是cq的初始化和两个vtable的init调用(poller_vtable ->init和vtable->init)
); grpc_set_pollset_vtable(&grpc_posix_pollset_vtable); grpc_set_pollset_set_vtable(&grpc_posix_pollset_set_vtable ); grpc_set_resolver_impl(&grpc_posix_resolver_vtable); grpc_set_iomgr_platform_vtable(&vtable); ->init(); } 关注点:iomgr_platform_vtable,而这个iomgr_platform_vtable就是由2.1.2节中的grpc_set_iomgr_platform_vtable iomgr_platform_vtable定义在: static grpc_iomgr_platform_vtable vtable = { iomgr_platform_init, iomgr_platform_flush ) { ……………… return &vtable; } 一起看下这个vtable的定义 //ev_epollex_linux.cc static const grpc_event_engine_vtable
->data_size + poller_vtable->size())); vtable->data_size为 sizeof(cq_next_data) poller_vtable grpc_pollset_impl->pollset_size()调用的是下面代码中的pollset_size //ev_posix.cc grpc_pollset_vtable grpc_posix_pollset_vtable vtable = { sizeof(grpc_pollset),//第一个 ……………… 3.2.2 vtable的初始化 先来看第一句: poller_vtable->init(POLLSET_FROM_CQ ,poller_vtable->init其实是在初始化cq中的grpc_pollset 箭头3中又遇到了3.2.1节中老朋友grpc_pollset_impl,所以调用关系参看上图就可以了 poller_vtable ); grpc-vtable的初始化.jpg 没啥好说的,代码很清晰
二者分工明确:vtable存储虚函数地址,是类的“方法目录”;vptr则是对象指向其类vtable的“导航指针”。 vtable大小及内容,运行时只读; 继承关联性:派生类vtable与基类vtable存在结构性关联,是实现多态的基础。 派生类vtable并非独立创建,而是以基类vtable为基础扩展: Base vtable结构Base类的vtable包含RTTI信息和其声明的虚函数: Base vtable:[0] type_info 的地址); 索引vtable:根据func1在vtable中的固定索引(示例中为索引1),获取函数指针; 调用函数:通过函数指针执行Derived::func1。 单继承虚函数机制的特点单一vptr:整个继承链中仅需一个vptr,所有虚函数通过该指针访问; vtable扩展式增长:派生类vtable是基类vtable的“超集”,结构清晰; 高效调用:虚函数调用仅需一次
由于 vtable 可以存储在任何位置,所以它的 offset 应该是从存储 object 开始,减去 vtable 开始,即计算 object 和 vtable 之间的 offset。 if cap(b.vtable) < numfields || b.vtable == nil { b.vtable = make([]UOffsetT, numfields) } else { b.vtable = b.vtable[:numfields] for i := 0; i < len(b.vtable); i++ { b.vtable[i] = 0 } } vtable 相同的会共享同一份 vtable。 第二步就是添加每个字段。 去掉末尾 0 i := len(b.vtable) - 1 for ; i >= 0 && b.vtable[i] == 0; i-- { } b.vtable = b.vtable[:i+1]
(一)获取 VTable 使用 NPM 包 首先,你需要在项目根目录下使用以下命令安装 VTable: # 使用 npm 安装 npm install @visactor/vtable # 使用 yarn 安装 yarn add @visactor/vtable 使用 CDN 你还可以通过 CDN 获取构建好的 VTable 文件。 = new VTable.ListTable(option);</script> (二)引入VTable 通过 NPM 包引入 在 JavaScript 文件顶部使用 import 引入 VTable : import * as VTable from '@visactor/vtable'; import { ListTable, PivotTable, TYPES, themes } from ' 在绘图前我们需要为 VTable 准备一个具备高宽的 DOM 容器。
VTABLE_TYPE = generator_vtable_type::kDefault> class generator_future; template <class TPROMISE, bool RETURN_VOID, generator_vtable_type VTABLE_TYPE> class generator_awaitable; template <class TVALUE, = generator_vtable<context_type, generator_vtable_type::kLightWeight>; using context_pointer_type ), nullptr)) {} LIBCOPP_UTIL_FORCEINLINE generator_vtable_delegate() noexcept : vtable_(nullptr) { & context) { if (vtable_ && vtable_->get_await_suspend_callback()) { vtable_->get_await_suspend_callback
("addr1: 0x{:x}, vtable1: 0x{:x}", addr1, vtable1); // trait object(s / Debug) 的 ptr 地址和 vtable 地址 ("addr2: 0x{:x}, vtable2: 0x{:x}", addr2, vtable2); // String 类型拥有相同的 vtable? assert_eq! (vtable1, vtable2); } 如果你在 rust playground 里运行,会得到下面的结果: addr1: 0x7ffd1c524910, vtable1: 0x556591eae4c8 ("addr1: 0x{:x}, vtable1: 0x{:x}", addr1, vtable1); // trait object(s / Debug) 的 ptr 地址和 vtable 地址 ("addr2: 0x{:x}, vtable2: 0x{:x}", addr2, vtable2); // trait object(s1 / Display) 的 ptr 地址和 vtable
, int max_entries = 10) { cout << "Virtual Table Address: " << vtable << endl; if (vtable == nullptr) { cout << "Invalid vtable pointer!" PrintVFTable_Safe(vtable_b); cout << "=== Derive Virtual Table ===" << endl; PrintVFTable_Safe(<em>vtable</em>_d , int max_entries = 10) { cout << "Virtual Table Address: " << vtable << endl; if (vtable == nullptr ) { cout << "Invalid vtable pointer!"
3.2 C语言实现 通过函数指针和虚函数表(vtable)实现多态。每个对象维护一个指向函数指针数组的指针,该数组存储其所有方法的地址。 \n"); } // 默认虚函数表 staticconst ShapeVTable shape_vtable = { .print = shape_print_default }; Shape shape_print(const Shape* shape) { shape->vtable->print(shape); } Rectangle.h #ifndef RECTANGLE_H = &rectangle_vtable; rect->width = width; rect->height = height; } return rect 四、总结 4.1 C语言实现OOP的核心技术 特性 实现方式 封装 struct + 函数指针 + 静态函数 继承 struct 嵌套 多态 虚函数表(vtable)+ 函数指针 4.2 优点 灵活性高
很多时候,我们可能需要使用变量表中的列,例如: VAR vTable = FILTER( 'Order' , [Discount] <> 0 ) 这里定义了一个 vTable 表示订单中没有折扣的那些订单 进一步地,我们想对这个表求和,可能会这样写: VAR vResult = SUM( vTable[LineSellout] ) 这里是希望表达计算销售额,但会遭遇一个语法错误,这里不能使用 vTable 聚合运算 如果希望直接进行聚合运算,则: VAR vResult = SUMX( vTable , [LineSellout] ) 这里的 vTable 作为表使用,而 [LineSellout] 作为其中的列被引用到 取出某列 如果想直接取出某列,也必须注意使用的方式,例如,错误的方式如下: VAR vList = VALUES( vTable[LineSellout] ) 这就是一个错误的语法,因为 vTable[ 正确的做法如下: VAR vList = SELECTCOLUMNS( vTable , "LineSellout" , [LineSellout] ) 这样就可以返回其中某个列作为的表。
劫持控制流 vtable 劫持分为两种,一种是直接改写 vtable 中的函数指针,通过任意地址写就可以实现。 = 0xf02a4 onegadget_offset = 0x45216 fake_vtable_offset = 3953800 fake_vtable_setbuf_offset = fake_vtable_offset = libc_base + vtable_point_offset print "vtable_point = " + hex(vtable_point) onegadget = libc_base print "fake_vtable = " + hex(fake_vtable) fake_vtable_setbuf = libc_base + fake_vtable_setbuf_offset print "fake_vtable_setbuf = " + hex(fake_vtable_setbuf) for x in xrange(0,2): p.send(p64(vtable_point
虚函数表布局: Vtable for 'Base' (5 entries). 0 | offset_to_top (0) 1 | Base RTTI -- (Base, 0) vtable 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 | 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 | 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 | 0) vtable address -- -- (Derive, 0) vtable address -- 2 | Derive::~Derive() [complete] 3 |