逆向基础特征分析

  • 时间:
  • 浏览:
  • 来源:互联网

逆向基础特征分析

  • 栈帧
    通常开始需要开辟栈帧,初始化安全cookie, (j_CheckDebuggerJustMyCode)
    通常一个函数的结束需要对eax即对返回值赋值,恢复寄存器环境,检测安全cookie是否被改变,平衡堆栈(esp+xx),检查(CheckEsp),关闭栈帧
    开栈操作 通常初始化安全cookie(ebp-4),保存非易失性寄存器(如ebp,esi,edi),初始化栈帧
    开辟栈帧的作用是把ebp作为基址调用参数和变量
  • 参数
    通常大于8字节的参数,先开辟栈帧在赋值
    小于四字节的参数会被隐式转换为4个字节,但地址还是保持在开头位置(扩展填充)
  • 变量
    变量之间通常用0xCC隔开,连续的相同类型变量可以猜测为数组,不同类型为结构体
    变量之间的间隔通常为两个0xCCCCCCCC
    在编译通常会自动对齐 align 0x10
  • 常量
    大多数常量保存在opcode中,浮点数常量保存在常量区,以全局变量的方式保存,使用浮点数寄存器如xmm0寄存器操作,先赋值到寄存器中才赋值给变量
    常量字符串保存在常量去,使用首地址
    bool的本质就是0,1
  • 跳转指令
    E8 call offset
    E9 jmp offset
    ff15 call 全局地址
    ff25 borland (ff15)call特征
  • 字符串操作
    较小数据每次使用mov DWORD ptr 0xxxxxxxx为单位初始化,不足使用WORD,BYTE,剩下未赋值的使用memset设置为0
    较大数据使用串操作指令优化,不足的使用movsw/b,剩下的使用memset设置为0,rep movsd
    未指定大小的,没有多余的空间使用memset赋值
  • 堆空间
    未初始化0xcdcdcdcd 结尾0xfdfdfdfd
  • 对象操作
    _thiscall:使用ecx传递this指针
    构造函数:首先将ecx保存的this指针赋值到变量(第一个变量ebp-8),返回值为eax,初始化后的对象,通常调用完设置返回码mov exit_code,0
    构造函数使用 ecx 传递对象的地址,在其中进行初始化
    初始化当前对象的虚函数表指针
    会在执行具体的逻辑代码前,调用父类的构造函数
    父类构造>对象成员构造>子类构造
    会将返回值设置为当前的 this 指针
    析构函数:首先将ecx保存的this指针赋值到变量,没有返回值,调用完析构设置返回值,mov eax,exit_code
    在执行逻辑代码后,函数退出前调用父类的析构
    子类析构>对象成员析构>父类析构
    调用成员函数第一个参数都需要将this指针赋值给ecx,在复制给eax使用eax作为基址
    当一个类对象什么都不实现会被优化,没有继承,没有虚函数
    ; 取出一个对象的地址进行初始化,大概率是指针或引用
  • 存在虚函数的情况
    在链接的时候初始化虚函数表,复制父类的虚函数表,相同的重写不同的添加
    只有同时存在虚函数表和指针或引用的情况下才属于动态联编
    调用按照虚函数表首地址+偏移,和变量的使用方式类似
    构造函数
    mov eax, [ebp+this]mov dword ptr [eax], offset ??_7CObj@@6B@ ;; 如果存在虚函数,那么会在构造函数中初始化虚表指针指向虚函数表; 虚函数表的初始化由链接器完成,父类构造函数会出初始化自己的虚; 表指针,表内只保存了自己的虚函数,子类初始化的指针指向的表中; 除了父类继承的以及重写的虚函数外还会添加自己的虚函数,注意, ; 子类的虚函数和父类的虚函数位置是相同

析构函数
没有返回值,会再次复制虚函数表,局部对象在函数返回前析构

在执行析构函数逻辑代码前会再次将虚函数表地址赋值给对象

  • 变量成员及成员函数的初始化
    任何一个动态联编调用虚函数以及访问数据的地方都会使用 this
    使用 [ecx(基址) + (对象内偏移)] 的方式操作数据成员

mov eax, [ebp+this]mov ecx, [ebp+arg_0]mov [eax], ecxmov eax, [ebp+this]mov ecx, [ebp+arg_4]mov [eax+4], ecxmov eax, [ebp+this]

初始化列表赋值,再构造函数内赋值,如果构造函数并未对变量赋值则使用变量的初始化

  • 全局对象
    全局对象的构造函数调用
    全局对象的构造函数:CObjA::CObjA() 行 8 C++

调用全局对象的构造函数:dynamic initializer for ‘g_object1’’() 行 24 C++

所有的全局的构造函数:_initterm(void()() * first, void()() * last) 行 22 C++

  • 函数
    // 全局函数 -> 友元函数 -> 成员函数 -> 静态函数 -> 内联函数
    // 全局函数: push + call
    // 友元函数: 实现上和全局函数一致,语法上可以访问私有
    // 成员函数: 实现上就是全局函数限定参数一为this
    // 虚函数: 十分的复杂,自己体会
    // 静态函数: 实现上和全局函数一致,语法上作用域在类内
    // 内联函数: 通常是经过优化的函数,没有函数调用语句(call)
    // 逆向的时候可能猜不出这是一个内联函数(strlen\strcpy)
  • 指针和引用
    引用本质上就是带语法限制的指针
    const在后面就是指向不可变,在前面就是指向的元素不可变
  • mov与lea
    当是一个变量使用lea计算地址(在编译时未确定的地址使用lea,不能使用offset)
    全局地址,(指针)使用mov
  • 特征运行时库函数
    CheakEsp:检测堆栈是否平衡,检查esp的值,不平衡程序直接崩溃
    cmp ebp, espcall j_CheckEsp

通常涉及数组操作,检测数组是否越界访问,在数组结尾添加0xCCCCCCCC,判断是否被修改
防止缓冲区溢出攻击

push edxmov ecx, ebp ; ; 有效 1push eaxlea edx, dword_0Xxxxxxx ; ; 有效 2call j_CheckStackValue ; ; 有效 3

pop eax

pop edx

获取一个全局变量放到ecx中调用函数,vs2019运行时函数特征,JMC选项中关闭,仅调试我的代码
mov ecx, offset 0xxxxxxxcall j_CheckForDebuggerJustMyCode ;(特征call GetCurrentThreadId)

获取初始化安全cookie与ebx异或保存到ebp+4(第一个变量中)
mov eax, ___security_cookiexor eax, ebpmov [ebp+xor_security_cookie], eax

作用:检测栈

mov ecx, [ebp+xor_security_cookie]xor ecx, ebpcall j_@__security_check_cookie@4 ; ; 判断当前安全 cookie 是否被修改,如果被修改就表示栈的内 ; 容被修改了,可能被利用了缓冲区溢出技术进行攻击

  • 常见函数
    printf(format_s,arg1,arg2)
    scanf_s(format_s,&buf,size)
    memset(&addr,0,size)
    strlen(s)
    strcmp(s1,s2)
    VirtualProtect(&addr,size,newProt,&OldProt)

本文链接http://www.dzjqx.cn/news/show-617485.html