函数指针和成员函数指针有什么不同,反汇编带看清成员函数指针的本尊(gcc@x64平台)

http://www.cnblogs.com/bbqzsl/p/5087912.html

函数指针是什么,可能会答指向函数的指针。

成员函数指针是什么,答指向成员函数的指针。

成员函数指针和函数指针有什么不同?

虚函数指针和非虚成员函数指针有什么不同?

你真正了解成员函数指针了吗?

本篇带你看一看反汇编中,成员函数指针的实体,以及运作机理,与函数指针到底有什么不同。

函数指针是函数执行功能的第一条机器指令的地址,这样描述也不让人满意,至少比指向函数的指针具体一些。也就是call指令的地址操作数。那么成员函数指针也应该指向一条执行指令的地址。但是其实成员函数指针它是一个trunk。
下面我们通过两个类来进行分析,父类obj以及子类objobj。我们定义成员函数指针和通过成员函数指针来调用成员函数观察真相。

复制代码
struct point {float x,y;};
struct obj
{
  virtual ~obj {}
  void foo(int) {}
  void foo(point) {}
  virtual void vfoo() {}
};
struct objobj : public obj
{
  virtual ~objobj {}
  virtual void vfoo() {}
};

void main()
{
    obj o;
        objobj oo;
        void* pofp = (void*) (void(obj::*)(point))&obj::foo;
        void(obj::*pi)(int) = &obj::foo;
        void(obj::*pp)(point) = &obj::foo;
        void(objobj::*vp)() = &objobj::vfoo;
        NOOP
        ((&oo)->*vp)();
        NOOP
        ((&oo)->*pi)(1);
        NOOP
        ((&o)->*pp)(pt);
}
复制代码

相信大家很轻松就知道这里有4个指针变量 pofp, pi, pp, vp, 分别指向obj::foo或objobj::vfoo函数的执行地址。通过‘*’号引用地址就可以调用函数的代码。其中有一个不是函数指针类型,但只要进行类型转换就可以调用到函数的代码了。

 

究竟事实是这样吗
gcc对本例的void* pofp = (void*) (void(obj::*)(point))&obj::foo;只作出警告并且可以编译,然而在vc中是错误不能通过编译的。因为将成员函数指针转换成其它类型的指针是被禁止的。
为什么vc要禁止这种转换呢,原因是成员函数指针不是一个单纯指向函数执行代码的地址的指针。先来看上面成员函数指针定义所对应的反汇编代码:

复制代码
  0x0000000000400975 <+417>:    movq   $0x400c60,-0x18(%rbp)    # void* pofp = (void*) (void(obj::*)(point))&obj::foo;
   0x000000000040097d <+425>:    movq   $0x400bde,-0x80(%rbp)    
   0x0000000000400985 <+433>:    movq   $0x0,-0x78(%rbp)        # void(obj::*pi)(int) = &obj::foo;
   0x000000000040098d <+441>:    movq   $0x400c60,-0x90(%rbp)
   0x0000000000400998 <+452>:    movq   $0x0,-0x88(%rbp)        # void(obj::*pp)(point) = &obj::foo;
   0x00000000004009a3 <+463>:    movq   $0x11,-0xa0(%rbp)
   0x00000000004009ae <+474>:    movq   $0x0,-0x98(%rbp)        # void(objobj::*vp)() = &objobj::vfoo;
复制代码

可以看到除了void* pofp是一个8字节长的指针外, pi, pp, vp都是一个有两个8字节长成员变量的结构体。而且vp中并没有存放代码地址。这是怎么一回事呢?


这个成员函数指针到底是怎么样运作的,请看下面对通过成员函数指针调用函数的代码的反汇编:

复制代码
((&oo)->*vp)();
   0x00000000004009ba <main+486>:    mov    -0xa0(%rbp),%rax
   0x00000000004009c1 <main+493>:    and    $0x1,%eax
   0x00000000004009c4 <main+496>:    test   %al,%al
   0x00000000004009c6 <main+498>:    je     0x4009f6 <main+546>
   0x00000000004009c8 <main+500>:    mov    -0x98(%rbp),%rax
   0x00000000004009cf <main+507>:    mov    %rax,%rdx
   0x00000000004009d2 <main+510>:    lea    -0x160(%rbp),%rax
   0x00000000004009d9 <main+517>:    add    %rdx,%rax
   0x00000000004009dc <main+520>:    mov    (%rax),%rdx
   0x00000000004009df <main+523>:    mov    -0xa0(%rbp),%rax
   0x00000000004009e6 <main+530>:    sub    $0x1,%rax
   0x00000000004009ea <main+534>:    lea    (%rdx,%rax,1),%rax
=> 0x00000000004009ee <main+538>:    mov    (%rax),%rax
   0x00000000004009f1 <main+541>:    mov    %rax,%rdx
   0x00000000004009f4 <main+544>:    jmp    0x4009fd <main+553>
   0x00000000004009f6 <main+546>:    mov    -0xa0(%rbp),%rdx
   0x00000000004009fd <main+553>:    mov    -0x98(%rbp),%rax
   0x0000000000400a04 <main+560>:    mov    %rax,%rcx
   0x0000000000400a07 <main+563>:    lea    -0x160(%rbp),%rax
   0x0000000000400a0e <main+570>:    add    %rcx,%rax
   0x0000000000400a11 <main+573>:    mov    %rax,%rdi
   0x0000000000400a14 <main+576>:    callq  *%rdx
复制代码
复制代码
((&oo)->*pi)(1);
   0x0000000000400a17 <main+579>:    mov    -0x80(%rbp),%rax
   0x0000000000400a1b <main+583>:    and    $0x1,%eax
   0x0000000000400a1e <main+586>:    test   %al,%al
   0x0000000000400a20 <main+588>:    je     0x400a4a <main+630>
   0x0000000000400a22 <main+590>:    mov    -0x78(%rbp),%rax
   0x0000000000400a26 <main+594>:    mov    %rax,%rdx
   0x0000000000400a29 <main+597>:    lea    -0x160(%rbp),%rax
   0x0000000000400a30 <main+604>:    add    %rdx,%rax
   0x0000000000400a33 <main+607>:    mov    (%rax),%rdx
   0x0000000000400a36 <main+610>:    mov    -0x80(%rbp),%rax
   0x0000000000400a3a <main+614>:    sub    $0x1,%rax
   0x0000000000400a3e <main+618>:    lea    (%rdx,%rax,1),%rax
   0x0000000000400a42 <main+622>:    mov    (%rax),%rax
   0x0000000000400a45 <main+625>:    mov    %rax,%rdx
   0x0000000000400a48 <main+628>:    jmp    0x400a4e <main+634>
   0x0000000000400a4a <main+630>:    mov    -0x80(%rbp),%rdx
   0x0000000000400a4e <main+634>:    mov    -0x78(%rbp),%rax
   0x0000000000400a52 <main+638>:    mov    %rax,%rcx
   0x0000000000400a55 <main+641>:    lea    -0x160(%rbp),%rax
   0x0000000000400a5c <main+648>:    add    %rcx,%rax
   0x0000000000400a5f <main+651>:    mov    $0x1,%esi
   0x0000000000400a64 <main+656>:    mov    %rax,%rdi
   0x0000000000400a67 <main+659>:    callq  *%rdx
复制代码

可以看到在call真正的执行地址之前都有一段相同的处理,这段相同的代码就是成员函数指针在幕后做的处理。
大至逻辑为
1.区分是不是虚函数;
2.找出正确的this位置,初始化this参数;
3.虚函数的话,找出正确的this位置,取出正确的虚函数表。
4.最后才能正确地调用正确的成员函数。

我逆向这段处理的伪代码如下:

复制代码
struct trunk{
  int64 off_poly;
  int64 off_func;
};

obj obj;
trunk trunk;
void* f;
if(trunk.off_func & 1)
{
  // a virtual function trunk;
  void** poly = (char*)&obj + trunk.off_poly;
  void** vtable = (void**)*poly;
  vtable = (char*)vtable + (trunk.off_func - 1);
  f = *vtable;
}
else
{
  // a non-virt function trunk;
  f = (void*)trunk.off_func;
}

poly* poly = (char*)&obj + trunk.off_poly;
poly->f();
复制代码

对于多重继承必须要找出正确的this位置。所以成员函数指针并不能像函数指针那样只是一个指向函数的指针,而需要一个trunk。


下面是objobj和obj的虚函数表的分布:

复制代码
vtable of objobj
0x400f30 <_ZTV6objobj+16>:    0x0000000000400cce    0x0000000000400d08
0x400f40 <_ZTV6objobj+32>:    0x0000000000400d2e

(gdb) i sy 0x0000000000400cce
objobj::~objobj() in section .text of a.out
(gdb) i sy 0x0000000000400d08
objobj::~objobj() in section .text of a.out
(gdb) i sy 0x0000000000400d2e
objobj::vfoo() in section .text of a.out

vtable of obj
0x400f70 <_ZTV3obj+16>:    0x0000000000400b8a    0x0000000000400bb8    # obj::~obj(), obj::~obj()
0x400f80 <_ZTV3obj+32>:    0x0000000000400ca6                # obj::vfoo()
复制代码

如果你还没有离开,并且足够细心的话,你就会发现obj和objobj只有两个虚函数dtor和vfoo,怎么会虚函数表中会有三个函数?
请留意下一篇,在析构函数中调用虚函数会发生什么?

逆向深入objc,c++ windows下仿objc动画层UI引擎
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 首先,需要明确一下,对于一个函数返回值的修改,一般并不直接使用指针hook的方式。指针hook的主要作用是修改函数指针,使其指向我们自己编写的函数,从而达到替换原函数的目的。而对于函数返回值的修改,更常见的做法是修改函数内部的实现逻辑,或者使用一些特殊的技术手段来篡改函数返回值。下面我将介绍两种可能的方案: 方案一:修改函数内部逻辑 可以尝试修改 initofy_init() 函数的实现逻辑,使其在返回前将 fd 值设置为假值。具体实现方法可能因代码细节而异,但一般可以通过以下方式进行: ```c int initofy_init() { // 假设原函数实现中使用了一个名为 fd 的 int 变量 int* fd_ptr = &fd; // 修改 fd 的值为假值 *fd_ptr = -1; // 返回原函数的返回值 return original_initofy_init(); } ``` 需要注意的是,这种方法的实现依赖于原函数的实现细节,如果原函数的实现变化了,可能需要相应地修改这个 hook 函数。 方案二:使用 LD_PRELOAD 技术 另一种可能的方案是使用 LD_PRELOAD 技术来篡改 initofy_init() 函数的返回值。LD_PRELOAD 是一种 Linux 下的动态链接技术,可以通过预先加载一个共享库,来替换掉程序中的某些函数。具体实现方法如下: 1. 编写一个共享库,其中包含一个和原函数同名的函数 initofy_init(),并在这个函数中返回假值。 ```c int initofy_init() { return -1; // 返回假值 } ``` 2. 使用 gcc 编译这个共享库,并使用 LD_PRELOAD 技术将其加载到程序中,替换掉原函数。 ```bash gcc -shared -fPIC -o libfake.so fake.c LD_PRELOAD=./libfake.so ./your_program ``` 需要注意的是,这种方法只适用于 Linux 系统下的动态链接库,且需要保证原函数和 hook 函数的签名一致。 ### 回答2: 使用指针hook对initofy_init()函数返回假的fd需要进行以下几个步骤: 1. 首先,我们需要找到initofy_init()函数的地址。可以通过在程序启动时打印函数地址来获取,或者可以使用动态调试工具来定位函数地址。 2. 在找到并记录initofy_init()函数的地址后,我们可以使用指针hook来修改函数返回的fd。指针hook可以通过修改函数实现的地址,将其指向我们所期望返回的fd地址。 3. 我们需要事先准备好一个存储我们期望返回的fd的内存块。可以通过malloc()函数来动态分配一块内存作为fd的存储区域,并将其初始化为我们需要的fd值。 4. 将initofy_init()函数的地址与我们准备的内存块地址进行关联,并将initofy_init()函数的地址指向我们的内存块地址。 5. 当程序调用initofy_init()函数时,由于其地址已经被修改为我们的内存块地址,函数将会返回我们准备的fd值,即假的fd。 需要注意的是,修改函数指针可能需要对程序进行逆向工程和动态调试,并且可能需要特定的权限和专业知识。此外,对程序中的函数进行hook需要谨慎操作,以确保不会破坏程序的正常运行。 ### 回答3: 指针hook是一种针对函数的劫持技术,可以修改函数的行为。在这个问题中,我们需要对initofy_init()函数返回假的fd。 首先,我们需要定义一个指针,并将其指向initofy_init()函数。然后,我们通过指针来修改函数的返回值。 具体步骤如下: 1. 声明一个与initofy_init()函数相对应的函数指针。例如,可以使用以下代码创建一个函数指针: int (* old_initofy_init)(); 2. 使用以下代码将指针指向initofy_init()函数: old_initofy_init = initofy_init; 3. 修改initofy_init()函数的返回值。可以通过以下代码将其修改为返回假的fd: int fake_fd = 0; // 假的fd值 int modified_initofy_init() { return fake_fd; } 4. 将函数指针指向修改后的函数: initofy_init = modified_initofy_init; 现在,当调用initofy_init()函数时,它将返回假的fd值。 需要注意的是,这种修改函数行为的方法需要在程序执行initofy_init()函数之前完成。另外,修改函数行为可能会对程序的正常运行产生影响,因此在使用指针hook时应特别谨慎,并仔细考虑可能产生的副作用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值