【黑客免杀攻防】读书笔记14 - 面向对象逆向-虚函数、MFC逆向
虚函数存在是为了克服类型域解决方案的缺陷,以使程序员可以在基类里声明一些能够在各个派生类里重新定义的函数。
1 识别简单的虚函数
代码示例:
#include "stdafx.h"
#include <Windows.h>
class CObj
{
public:
CObj():m_Obj_1(0xAAAAAAAA),m_Obj_2(0xBBBB)
{
printf("CObj() Constructor...\r\n");
}
~CObj()
{
printf("CObj() Destructor...\r\n");
}
virtual void Show(int nID) // 注意这里
{
m_Obj_1 = 1;
printf("ID:%d Who is your God? I am!\r\n",nID);
}
private:
int m_Obj_1;
WORD m_Obj_2;
};
class CPeople : public CObj
{
public:
CPeople():m_People_1(0xCCCCCCCC),m_People_2(0xDDDD)
{
printf("CPeople() Constructor...\r\n");
}
~CPeople()
{
printf("CPeople() Destructor...\r\n");
}
void Show(int nID)
{
printf("ID:%d People!\r\n",nID);
}
private:
int m_People_1;
WORD m_People_2;
};
int _tmain(int argc, _TCHAR* argv[])
{
CObj obj;
CPeople people;
CObj *pobj;
pobj = &obj;
pobj->Show(0);
pobj = &people;
pobj->Show(1);
return 0;
}
// ---------- 输出结果 ----------
// CObj() Constructor...
// CObj() Constructor...
// CPeople() Constructor...
// ID:0 Who is your God? I am!
// ID:1 People!
// CPeople() Destructor...
// CObj() Destructor...
// CObj() Destructor...
// ----------------------------
反汇编代码:
int _tmain(int argc, _TCHAR* argv[])
{
001273B0 push ebp
001273B1 mov ebp,esp
001273B3 push 0FFFFFFFFh
001273B5 push 1B3730h
001273BA mov eax,dword ptr fs:[00000000h]
001273C0 push eax
001273C1 sub esp,108h
001273C7 push ebx
001273C8 push esi
001273C9 push edi
001273CA lea edi,[ebp+FFFFFEECh]
001273D0 mov ecx,42h
001273D5 mov eax,0CCCCCCCCh
001273DA rep stos dword ptr es:[edi]
001273DC mov eax,dword ptr ds:[001D9004h]
001273E1 xor eax,ebp
001273E3 push eax
001273E4 lea eax,[ebp-0Ch]
001273E7 mov dword ptr fs:[00000000h],eax ; 栈保护基址相关代码
CObj obj;
001273ED lea ecx,[ebp-1Ch] ; this 指针
001273F0 call 00123D87 ; CObj::CObj (0123D87h)
001273F5 mov dword ptr [ebp-4],0 ; 异常处理的辅助标志,以-1为结尾
CPeople people;
001273FC lea ecx,[ebp-38h] ; this指针
001273FF call 001211DB ; CPeople::CPeople (01211DBh)
00127404 mov byte ptr [ebp-4],1
CObj *pobj;
pobj = &obj;
00127408 lea eax,[ebp-1Ch] ; 将obj的this指针给eax
0012740B mov dword ptr [ebp-44h],eax ; 将this指针给pobj的指针
pobj->Show(0);
0012740E mov esi,esp
00127410 push 0 ; 参数压栈
00127412 mov eax,dword ptr [ebp-44h]
00127415 mov edx,dword ptr [eax]
00127417 mov ecx,dword ptr [ebp-44h] ; 将Obj的指针(指向的是 Obj的this指针)给ecx
0012741A mov eax,dword ptr [edx] ; 将Obj的this指针所指向的第一项的内容(即Vtbl的第一个元素)给eax
0012741C call eax
0012741C ; 在调用完CPeople的构造后,程序采用如下步骤实现
0012741C ; pobj = &obj;
0012741C ; pobj ->Show(0);
0012741C ;
0012741C ; 1、将创建完的Obj对象的this指针传递给pobj
0012741C ; 2、将指this指针给eax
0012741C ; 3、将this指针第一项(即虚函数表指针)传递给edx
0012741C ; 4、将pobj的值传递给ecx(注意此步)
0012741C ; 5、将既虚函数表数组的地址传递给eax
0012741C ; 6、调用eax
0012741E cmp esi,esp
00127420 call 00122329
pobj = &people;
00127425 lea eax,[ebp-38h] ; 将People的this指针传给eax
00127428 mov dword ptr [ebp-44h],eax ; 将People的this指针给Obj
pobj->Show(1);
0012742B mov esi,esp
0012742D push 1 ; 参数压栈
0012742F mov eax,dword ptr [ebp-44h] ; 将People的this指针给eax
00127432 mov edx,dword ptr [eax] ; 将this指针中的第一项,即Vptr给edx
00127434 mov ecx,dword ptr [ebp-44h] ; 将People的this指针给ecx
00127437 mov eax,dword ptr [edx] ; 将Vptr指向的Vtbl给eax
00127439 call eax ; 调用eax
0012743B cmp esi,esp
0012743D call 00122329 ; __RTC_CheckEsp
return 0;
00127442 mov dword ptr [ebp+FFFFFEF0h],0
0012744C mov byte ptr [ebp-4],0
00127450 lea ecx,[ebp-38h]
00127453 call 00121E10
00127458 mov dword ptr [ebp-4],0FFFFFFFFh
0012745F lea ecx,[ebp-1Ch]
00127462 call 00123BC0
00127467 mov eax,dword ptr [ebp+FFFFFEF0h]
}
如果没有Debug的符号文件,或者逆向过程中代码不是我们自己写的,那就要先判断它是否是一个类的应用。
跟进函数内部情况:
class CObj
{
00126FD0 push ebp
00126FD1 mov ebp,esp
00126FD3 sub esp,0CCh
00126FD9 push ebx
00126FDA push esi
00126FDB push edi
00126FDC push ecx
00126FDD lea edi,[ebp-0CCh]
00126FE3 mov ecx,33h
00126FE8 mov eax,0CCCCCCCCh
00126FED rep stos dword ptr es:[edi]
00126FEF pop ecx
00126FF0 mov dword ptr [this],ecx ; 取this指针 this == [ebp-8]
00126FF3 mov eax,dword ptr [this] ; 取this指针
00126FF6 mov dword ptr [eax],offset CObj::`vftable' (01B5E54h)
public:
CObj():m_Obj_1(0xAAAAAAAA),m_Obj_2(0xBBBB)
00126FFC mov eax,dword ptr [this]
00126FFF mov dword ptr [eax+4],0AAAAAAAAh ; 初始化m_Obj_1为0xAAAAAAAA
00127006 mov eax,0BBBBh ; 初始化m_Obj_2为0xBBBB
0012700B mov ecx,dword ptr [this] ; this指针 this == ecx-8
0012700E mov word ptr [ecx+8],ax
printf("CObj() Constructor...\r\n");
00127012 push offset string "CObj() Constructor...\r\n" (01B5E5Ch)
printf("CObj() Constructor...\r\n");
00127017 call _printf (0123D00h)
0012701C add esp,4
}
0012701F mov eax,dword ptr [this] ; 将this指针作为返回值 this == ebp-8
00127022 pop edi
00127023 pop esi
00127024 pop ebx
00127025 add esp,0CCh
0012702B cmp ebp,esp
0012702D call __RTC_CheckEsp (0122329h)
00127032 mov esp,ebp
00127034 pop ebp
00127035 ret
通过阅读以上代码可以得出以下过程:
1)找出虚表位置,以及操作的流程
- 代码里的例子操作了虚表 00126FF6 mov dword ptr [eax],offset CObj::`vftable' (01B5E54h)
这是一个保存函数地址的指针,再通过汇编上下文的猜测,则可大致确定这就是一个虚表,且将值传到了寄存器参数ecx记录地址的第一项。
以寄存器参数ecx为首地址,分别给其4偏移与8偏移处赋值
寄存器参数ecx又作为返回值传了回去。
通过调用函数的分析,ecx里保存的是this指针,并且根据类的内存结构可知,this里的第一项是Vptr。
2)识别构造函数
- 由于此成员函数是第一个被调用的,通过代码看出汇编函数中的第二件事是初始化数据成员。最后一件事是将this指针当做返回值返回,所以推测该函数为构造函数。
3)逐步分析函数
- 构造函数与析构函数会对Vptr操作。
- 在VS默认设置下,构造与析构前都会有相应的异常处理标记置位操作。
- 虚函数的调用一般采用eax。
2 识别较复杂的虚函数
经验小结:
- new出来的对象会以其在堆中申请空间的指针作为this指针传入参与构造。
- new出来的对象其虚函数调用的寻址方式与普通构造出来的不同。
- delete对象时会先析构自己,再析构父类,最后再执行delete。
- new出来的对象如果其成员函数派生于纯虚函数,在delete时只调用父类的析构。
- 如果此类为抽象类(包含纯虚函数),那么其虚表的对应项会填充指向库函数__purecall的函数指针。
虚函数调用的固定模式,紧盯对各个虚表的操作。从而根据上下文即可大致确定虚函数的调用与类的析构与构造。
3 识别类的继承关系
- 根据构造函数内的构造顺序分辨此函数所属类的继承情况
- 总结并记录分析结果
- VS的release版中存在同时使用ecx、esi寄存器传递this指针的情况。
4 逆向MFC程序
MFC程序关键特征点
| 版本 | 对应动态库 | 静态库中使用MFC时的特征 | 动态库中使用MFC的特征 |
|---|---|---|---|
| 4.0 | mfc40.dll | call [ebp+0x14] | call [ebp+0x14] |
| 6.0 | mfc42.dll | call [ebp+0x14] | call [ebp+0x14] |
| 7.1 | mfc71.dll | call [ebp+0x14] | call [ebp+0x14] |
| 10.0 | mfc100.dll | call [ebp+0x14] | mov edx,[ebp+0x14] |
分析核心重点
1)判断目标程序是不是MFC程序,如果是,判断其MFC版本
OD快捷键:Ctrl+E 打开模块窗口,并在模块窗口寻找类似于mfc*.dll这样的模块。
如果找到了就可以根据DLL的名称判定程序所用的MFC版本,如果找不到则证明这是一个在静态库中使用MFC的程序。
2)根据目标程序调用MFC方式的不同而采取不同的方式搜索特征
OD快捷键:Ctrl+F 搜索特征 call [ebp+0x14]
由于搜索的特征位于消息分发函数里,因此特征指令所在的位置应该是一个非常大的switch-case。
3)在合适的地方下断点,并跟进到相应消息的函数中。
设置按钮点击事件下断点,即可跟进到达相应消息的函数中。
这里可以参考:
看雪《MFC程序逆向》
https://bbs.pediy.com/thread-54150.htm
【黑客免杀攻防】读书笔记14 - 面向对象逆向-虚函数、MFC逆向的更多相关文章
- 【黑客免杀攻防】读书笔记5 - PE格式讲解
0x01 MS-DOS头 MS-DOS头部的字段重点关注e_magic与最后一个e_lfanew是需要关注的. 第一个e_magic字段的值为4D5A,作用是可以作为判断这个文件是否是PE文件. 最后 ...
- 【黑客免杀攻防】读书笔记8 - 软件逆向工程基础2(if-else,三目运算符)
0x1 if-else分支 if-else分支4种状态 1.1 以常量为判断条件的简单if-else分支 C源代码: 单层if-else判断,常量为判断条件 int _tmain(int argc, ...
- 【黑客免杀攻防】读书笔记7 - 软件逆向工程基础1(函数调用约定、Main函数查找)
0x1 准备工作 1.1.准备工具 IDA:交互式反汇编工具 OllyDbg:用户层调试工具 Visual Studio:微软开发工具 1.2.基础知识 C++开发 汇编语言 0x2 查找真正的mai ...
- 【黑客免杀攻防】读书笔记6 - PE文件知识在免杀中的应用
0x1 PE文件与免杀思路 基于PE文件结构知识的免杀技术主要用于对抗启发式扫描. 通过修改PE文件中的一些关键点来达到欺骗反病毒软件的目的. 修改区段名 1.1 移动PE文件头位置免杀 工具:PeC ...
- 【黑客免杀攻防】读书笔记18-最终章Anti Rootkit
1.免杀技巧的遏制 1.1.PE文件 入口点不在第一个区段或在最后一个区段 入口点处代码附近只有一小段代码 入口点在正常范围之外 入口点为一个无效的值,实际入口点为TLS的入口点 区段名重复或者不属于 ...
- 【黑客免杀攻防】读书笔记2 - 免杀与特征码、其他免杀技术、PE进阶介绍
第3章 免杀与特征码 这一章主要讲了一些操作过程.介绍了MyCCL脚本木马免杀的操作,对于定位特征码在FreeBuf也曾发表过类似工具. VirTest5.0特征码定位器 http://www.fre ...
- 【黑客免杀攻防】读书笔记15 - 源码免杀、C++壳的编写
1.源码免杀 1.1 定位产生特征的源码 定位文件特征 1.根据MyCCL的特征码定位工具,定位出有特征的地址 2.根据VS的反汇编窗口,输入有特征的地址得到特征地址与源码的关系 3.插入Messag ...
- 【黑客免杀攻防】读书笔记10 - switch-case分支
0x1 switch-case分支 switch-case其实就是if-else语句的另一种体现形式.但大于3之后的switchc-case.编译器会对代码进行优化. 1.1 简单switch-cas ...
- 【黑客免杀攻防】读书笔记17 - Rootkit基础
1.构建Rootkit基础环境 1.1.构建开发环境 VS2012+WDK8 1.2.构建基于VS2012的调试环境 将目标机.调试机配置在同一个工作组内 sVS2012配置->DRIVER-& ...
随机推荐
- 链表java实现
链表是用指针将多个节点联系在一起,通过头节点和尾节点还有节点数量,可以对链表进行一系列的操作.是线性表的链式存储实现. 1.链表是多个不连续的地址组成在一起根据指针链接在一起的,由多个节点组成,每个节 ...
- SQLServer 重建索引前后对比 (转)
https://www.cnblogs.com/mingl12/p/5730178.html
- MT【147】又见最大最小
(2018浙江省赛12题)设$a\in R$,且对任意的实数$b$均有$\max\limits_{x\in[0,1]}|x^2+ax+b|\ge1$求$a$的范围_____解答:由题意$\min\li ...
- 【BZOJ4027】兔子与樱花(贪心)
[BZOJ4027]兔子与樱花(贪心) 题面 BZOJ 洛谷 题解 很直观的一个感受就是对于每个节点, 考虑它的所有儿子,如果能删就删. 那么我们把所有儿子按照给删去后给父亲\(c[i]\)的贡献从小 ...
- redis协议
Redis的通讯协议可以说大集汇了……消息头标识,消息行还有就行里可能还有个数据块大小描述.首先Redis是以行来划分,每行以\r\n行结束.每一行都有一个消息头,消息头共分为5种分别如下: (+) ...
- View的setLayerType() , setDrawingCacheEnabled() 方法用法
一.Android开发:用getDrawingCache方法获取ImageView中的图像需要注意的问题http://www.linuxidc.com/Linux/2011-09/43131.htm ...
- vue入门教程
vue视频教程(对vue有个概览,要掌握vue-cli的用法,对vue-router,vuex有基本的概念) https://www.imooc.com/learn/1091 1. vue-cli v ...
- 解题: SDOI 2011 染色
题面 强行把序列问题通过树剖套在树上...算了算是回顾了一下树剖的思想=.= 每次树上跳的时候注意跳的同时维护当前拼出来的左右两条链的靠上的端点,然后拼起来的时候讨论一下拼接点,最后一下左右两边的端点 ...
- 关于表单中Readonly和Disabled
Readonly和Disabled是用在表单中的两个属性,它们都能够做到使用户不能够更改表单域中的内容.但是它们之间有着微小的差别,总结如下: Readonly只针对input(text / pass ...
- pg删除账号,权限的回收问题
在pg中删除账号时,一般不能直接删除账号,要先将该账号上所有的对应权限收回,但往往这一步是比较繁琐的,可能当时赋权的对象类型很多,对象也比较多,虽然可以通过sql按照类型来收回针对整个schema的所 ...