C++逆向

                                               C++逆向总结

一、call前的this指针参数传递

Windows和Linux下的大部分编译器将this指针存放到ecx寄存器中传递,就是在call某个对象成员函数时。但是,也有部分编译器使用rdi寄存器传递的。

 

 如上图所示,第一个参数是用ecx寄存器传递的,后面的两个参数都是栈传递。我们可以通过观察发现,这里ida识别出现错误,没有第四个参数,a1就是this指针,里面的this其实是第二个运算参数。

 二、C++函数名编译后的编码

编译器会对C++编写的源程序中的函数名进行编码,又称为C++ Name Mangling。

那么我们还原函数名的方法有:

  1. 由编译器提供的函数进行解码
    #include <cxxabi.h>
    abi::__cxa_demangle(...);
  2. 使用NDK工具链中的C++ filt命令
    cxxfilt 函数名

     

三、虚函数表

如果类对象中存在虚函数,则类对象在内存中的空间增加4个字节,用于存放虚函数表指针,且虚函数表指针默认为第一个成员变量。

虚函数表指针指向虚函数表,也可以理解为一个函数地址数组。

测试代码如下:

#include <iostream>

using namespace std;
class BaseObject{
public:
    virtual int sub() = 0;
};

class MyObject:public BaseObject {
public:
    int x;
    int y;
public:
    MyObject(int a,int b) {
        x = a;
        y = b;
    }

    ~MyObject() {
        x = 0;
        y = 0;
    }

    int add() {
        return x + y;
    }

    int sub(){
        if(x>y)
            return x-y;
        else
            return y-x;
    }
};

int main() {
    BaseObject* obj = new MyObject(1,2);
    cout << "Hello!" << endl;
    cout << obj->sub() << endl;
    return 0;
}

 

 我们可以发现new的内存空间多了4个字节(2 * 4 + 4 = 0xC),不然应该申请8个字节的内存。

 

 我们可以发现在MyObject类的构造函数中首先调用了父类BaseObject的构造函数。

 

 在BaseObject的构造函数中,就做了一件事,将off_4C1470赋值给this[0],off_4C1470是BaseObject的虚函数表地址。

而MyObject类的构造函数在完成父类的构造函数后,将自己的虚函数表地址off_4C147C地址赋值给this[0],也就是覆盖了父类的虚函数表。

 四、虚函数的调用

虚函数的调用是通过虚函数表指针来调用的。

测试代码如下:

class BaseObject{
public:
    virtual int add() = 0;
    virtual int sub() = 0;
};

class MyObject:public BaseObject {
public:
    int x;
    int y;
public:
    MyObject(int a,int b) {
        x = a;
        y = b;
    }

    ~MyObject() {
        x = 0;
        y = 0;
    }

    int add() {
        return x + y;
    }

    int sub(){
        if(x>y)
            return x-y;
        else
            return y-x;
    }
};

int main() {
    BaseObject* obj = new MyObject(1,2);
    cout << "Hello!" << endl;
    cout << obj->add() << endl;
    cout << obj->sub() << endl;
    return 0;
}

 

 我们可以看到其通过虚函数表以及虚函数在表中的偏移来调用add函数与sub函数。

五、构造函数

C++类的构造函数在编译为汇编后和普通函数一样,只是多了个this指针作为参数。对象作为局部变量则在栈中提前留出空间。

 

 如果是使用new函数创建的类对象,则先调用new函数完成内存申请创建对象,再将this指针传递给构造函数初始化。

 

六、线程同步问题

 Windows 平台上, Interlock系列函数、atomic模板类、CriticalSection、Event、Mutex、Semphore、atomic等API 函数以及两个重要的函数 WaitForSingleObject、WaitForMultipleObjects,常被用于多线程同步。

Interlock系列函数:进行原子操作,通过volatile修饰符实现的,直接操作内存,不作缓存。

引:C/C++作为系统级语言,它们与硬件的联系是很紧密的。volatile的意思是“易变的”,这个关键字最早就是为了针对那些“异常”的内存操作而准备的。它的效果是让编译器不要对这个变量的读写操作做任何优化,每次读的时候都直接去该变量的内存地址中去读,每次写的时候都直接写到该变量的内存地址中去,即不做任何缓存优化。

atomic模板类参考资料: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter7-Atomic/7.3%20Atomic-tutorial.md

CriticalSection对象的使用:https://learn.microsoft.com/en-us/windows/win32/sync/using-critical-section-objects

 

 

 

 

 

 

 

 

在linux平台上,使用 mutex、semphore、condition_variable、read-write-lock 等操作系统API。

 

posted @ 2022-07-14 18:01  An2i  阅读(478)  评论(0)    收藏  举报