【逆向】从逆向角度看C++

从逆向角度看C++

1.2.1

  • 虚函数地址表(虚表)
    1. 定义:当类中定义有虚函数时,编译器会把该类中所有虚函数的首地址保存在一张地址表中,即虚函数地址表
    2. 虚表信息在编译后被链接到执行文件中,因此所获得的虚表地址是一个固定的地址。
    3. 虚表中虚函数的地址排列顺序依据虚函数在类中的声明顺序而定。

虚表指针

  • 同时编译器还会在类的每个对象添加一个隐藏数据成员,称为虚表指针,保存着虚表的首地址,用于记录和查找虚函数。
  • 虚表指针的初始化是通过编译器在构造函数中插入代码实现的。由于必须初始化虚表指针,编译器会提供默认的构造函数。

虚函数调用过程

  • 虚表间接寻址访问:
    使用对象的指针或引用调用虚函数。根据对象的首地址,取出相应的虚表指针,在虚表查找对应的虚函数的首地址,并调用执行。
  • 直接调用访问:
    使用对象调用虚函数,和调用普通成员函数一样。
  • 虚函数的识别:
  • 类中隐式定义一个数据成员
  • 数据成员在首地址处,占4字节
  • 构造函数初始化该数据成员为某个数组的首地址
  • 地址属于数据区,相对固定的地址
  • 数组的成员是函数指针
  • 函数被调用方式是thiscall
  • 构造函数与析构函数都会将虚表指针设置为当前对象所属类中的虚表地址。
  • 构造函数中是完成虚表指针的初始化,此时虚表指针并没有指向虚表函数。
  • 执行析构函数时,其对象的虚表指针已经指向某个虚表首地址。虚函数是在还原虚表指针,让其指向自身的虚表首地址,防止在析构函数中调用虚函数时取到非自身虚表

img

内存中虚表结构图

img

在 VC2008 中编译如下代码并调试

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include <iostream>
using namespace std;class base_class {
private:int m_base;
public:virtual void v_func1() {cout << "This is base_class's v_func1()" << endl;//printf("This is base_class's v_func1()");}virtual void v_func2() {cout << "This is base_class's v_func2()" << endl;//printf("This is base_class's v_func2()");}virtual void v_func3() {cout << "This is base_class's v_func3()" << endl;//printf("This is base_class's v_func3()");}
};int main(int argc, char* argv[]) {base_class MyClass;base_class v_func1();//MyClass v_func1();base_class v_func2();//MyClass v_func2();base_class v_func3();//MyClass v_func3();return 0;
}

找到程序入口和main函数位置,进入main函数,进入call语句

image-20230120000438624

image-20230120000503500

可以看到虚表指针是通过offset来定义的,相当于是一个全局的地址,由编译器固定

image-20230120000620618

如果继续向下执行,可以推测这句语句执行的效果是将0x00416804这个位置的内容(也就是虚表指针),放到ds:[0012FF70].

image-20230120001210176

在数据窗口中跟随eax,这是之行前后改地址处发生的变化

image-20230120001536584

image-20230120001630905

我们知道,虚表指针指向虚表首地址,在虚表中存放着地址信息(各虚函数的首地址)

在数据区选中指针,右键-在数据窗口中跟随DWORD,找到了我们刚刚定义的几个函数

image-20230120002101569

image-20230120002336259

在反汇编窗口跟随前三个字段表示的地址,

image-20230120003552150

image-20230120003625656

image-20230120003824895

进入v_func1进行查看,可以看到就是实现一个打印的功能

image-20230120004122561

1.2.2

从反汇编侧方位看C++虚函数表

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <iostream>using namespace std;class employee {
public:employee() { printf("employee()!\n");}~employee() { printf("~employee()!\n");}
};class manager : public employee
{
public:manager() { printf("manager()!\n");}~manager() {  printf("~maneger()!\n");}
};int main(int argc, char* argv[]) {manager My;getchar();return 0;
}

在VS2008编译出现如下错误,尝试将运行时库调成“多线程调试(/MTd)”:

error LNK2019: 无法解析的外部符号 \_\_malloc_dbg,该符号在函数 "void * \_\_cdecl operator new(unsigned int,struct std::_DebugHeapTag_t const &,char *,int)"

在OllyICE的mian函数中第一个call指令会获取类的首指针的地址,也就是我们定义的manager类

image-20230122235217518

image-20230122235247085

可以发现指针所指位置的代码对应的就是初始化构造函数的内容。在解析完类之后、程序退出后,会用指针的方式调用析构函数把现在类初始化、堆栈、结构、指针的一些临时信息清空然后释放,让系统去回收这一部分内存。

image-20230122235314178

跟进到该构造函数中,发现ECX指向地址被填充为CC,这是因为它其中没有其他一些成员的函数。

image-20230123002336612

下面第一个call会获取指向其父类也就是employee类的指针,它也是一个构造函数,会根据可访问的内容(public)进行初始化。

从反汇编角度看this指针

再来看第二段代码

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>struct MyStruct {int x ;int y ;
};//函数在结构体外部
void Max(MyStruct* str) {if (str->x > str->y)printf("%d",str->x);elseprintf("%d",str->y);
}int main(int argc, TCHAR* argv[]) {MyStruct haha ;haha.x = 1 ;haha.y = 2 ;Max(&haha);printf("%d\n",sizeof(haha));return 0;
}

可以找到给结构体的两个变量赋值的语句,执行后在内存中跟随到1和2已经被写入。

image-20230123004505391

或者直接在Command:dd eax+4

image-20230123004431762

接下来程序将1和2作为参数压入,并在下面第一个call指令调用Max函数进行比较。

1.2.3

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>class MyTest {
public:MyTest();~MyTest();void SetTest(DWORD dwTest);DWORD GetTest();
public:DWORD m_dwTest;
};MyTest::MyTest() {printf("1111\r\n");}MyTest::~MyTest() {printf("2222\r\n");}
void MyTest::SetTest(DWORD dwTest) {this->m_dwTest = dwTest;   
}
DWORD MyTest::GetTest() {return this->m_dwTest;
}int main(int argc, char* argv[]) {MyTest Test;Test.SetTest(1);int Number = Test.GetTest();      //添加了Set,Get方法,并调用getchar();return 0;
}

这里简单说一下OD断点运作的原理:通过触发软件断点使用CC填充了这一段,使得本来可以正常执行的程序生成一条系统的SEH异常链,调试器通过捕捉改异常,中断主线程,使新线程执行到断点位置

image-20230124000527973

MyTest Test;

首先运行到MyTest Test,它会调用printf打印1111。

Test.SetTest(1);

接着是,Test.SetTest(1),这里采用硬编码的方法(直接push 1)传参,接着调用GetTest

image-20230124001639797

不难看出,C++的一行指令在汇编中需要四行来完成,从逆向类的角度可以这么看

this->m_dwTest;
##################################
mov     dword ptr ss:[ebp-0x8],ecx     # i=this
mov     eax,dword ptr ss:[ebp-0x8]     
mov     ecx,dword ptr ss:[ebp+0x8]     
mov     dword ptr ds:[eax],ecx     

执行这四句之前,0x0012FE68处内容为初始化的CC

image-20230124003627318

执行第一句,把this指针的首地址压栈

image-20230124003803841

第二句把该地址赋给eax

第三句,把第一个参数(在ebp+8处)给到ecx

第四句,把该参数写到指针所指向的数据区中去(也就是写到类对象的那个局部变量m_dwTest里去)

int Number = Test.GetTest();

再接着是int Number = Test.GetTest(),作用是取出类对象中刚刚被赋值的局部变量

查看汇编,一共是三句

int Number = Test.GetTest();
##################################
mov     dword ptr ss:[ebp-0x8],ecx
mov     eax,dword ptr ss:[ebp-0x8]
mov     eax,dword ptr ds:[eax]

执行第一句,将ecx的值作为一个地址指针压栈

image-20230124011249959

执行第二句,将该指针赋给eax

执行第三句,将数据区中eax指向的内容保存到eax,作为函数结果进行返回

跳出GetTest函数可以看到,程序紧接着使用该返回值进行了写回(选中右键-数据窗口跟随-内存地址)

image-20230124011624847
image-20230124011742314

1.2.4 弹出Radmin的对话框

安装Radmin:Radmin是一款远控软件,区别于木马的是,它作用与局域网,也就是说它只能连接静态ip,不具备反弹连接(上线之后主动请求客户端)的功能。

image-20230126154735444

双击受控机口会弹出一个对话框,目的是安全性配置,否则任何人都可以连接我们的被控端,从而对非授限的主机进行操作。

在这一部分我们的目标是:使用远程线程的代码来代替用户的手动输入。

打开x32_dbg,点击文件-附加-双击打开,程序暂停在主线程的代码部分;或者文件-打开程序,F9运行,即可找到程序的入口。

image-20230126160552163

在这里我们选择附加

image-20230126160935189

该弹窗是用mfc写的,我们可以使用一些工具抓到它的句柄。从Spy4Win可以看出,这里使用的是mfc的win32界面,上面有很多控件,绑定着一个标识ID。

image-20230126161508564

现在的窗口设计是独占式线程,也就是说不进行输入主线程会一直暂停直到子线程结束。我们如果要对此进行修改,意味着而我们必须生成一个新线程,于是想到CreateThread。

使用快捷键Ctrl+G找到构造新线程的部分

image-20230126162333620

注意看,在0x7C8106F6处有一个retn 18,这不是返回值,而是Windows下API调用的一个约定,目的是由内部函数来平衡堆栈。

在IDAx86中打开kernel32.dll,在导出表中查找函数CreateThread并跳转,可见内容与x32_dbg中一模一样

image-20230126164049670

对该部分内容F5反编译为伪代码

HANDLE __stdcall CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId)
{return CreateRemoteThread((HANDLE)0xFFFFFFFF,lpThreadAttributes,dwStackSize,lpStartAddress,lpParameter,dwCreationFlags,lpThreadId);
}

第一个参数lpThreadAttributes是线程的安全属性,用于避免回调函数崩溃;
第二个参数dwStackSize表示堆栈大小,指的不是CreateThread函数的堆栈大小,而是回调函数里局部堆栈的大小;
第三个参数lpStartAddress,设置回调函数的指针;
第四个参数lpParameter,用于传参(强转成指针,什么都可以传);
第五个参数dwCreationFlags相当于一个flag标志(CreateThread调用CreateRemoteThreadEx_0再调用NTCreateThread,flag保证一致性);
第六个参数lpThreadId是分配的线程ID。

这就解释了为什么最后要加一个retn 18,因为一共6个参数,每个参数占用4个字节(在内存中以4B对齐),十进制的24就是是十六进制的18,从而做到现场还原。

现在我们在CreateThread的第一句下一个断点,并再次双击受控机图标,发现程序确实被我们断下来了,这说明我们确实找对了位置。

我们现在通过单步+栈回溯的方法找到CreateThread的调用者(或者Alt+F9)

image-20230127225056306

设置断点让程序运行至此

image-20230127225200094

再往外层单步走三层,程序运行到一个较关键位置

image-20230127230617211

通过jnz和jmp可以初步判断这里是一个分支语句

mov esi,dword ptr ss:[esp+83F4]
push eax
push edi
add ebp,3458
push ebp
push esi
call radmin.143F8E0 # 这里调用的CreateThread
add esp,10
test eax,eax

也就是说上面这一部分就是负责弹窗的汇编代码。

那么接下来我们在这段代码的开头设一个断点,重新运行程序,使之中断在该位置。

image-20230128000743533

在内存窗口中跟随地址[esp+83F4],下图错误,应是ESI的值(指针)000B0BF2(指向的内存地址)

image-20230128001035111

image-20230128005640094

经历一系列压栈后,EAX值为00009C49(功能号),EDI值为008C5A74,EBP值为00128B94,ESI值为000B0BF2

image-20230128002624756

跟随EDI指向内存内容如下,这里其实是我们需要连接的被控端的结构体,用于存放配置信息。可以看到存在User等字样,再往下拉可以看到有我们的要连接的IP信息127.0.0.1

image-20230128003239954

image-20230128003510190

知道了调用前的状态,接下来我们就可以伪造寄存器了。这里我们使用Code Injector远程注入汇编代码

pushad
mov esi,0
mov eax,0x9C49
mov edi,0x008C5A74
push eax
push edi
mov ebp, 0x00128B94
push ebp
push esi
mov  eax,0x143F8E0
call eax
add esp, 10
popad

image-20230128005916267

选择要注入的目标程序

image-20230128010243127

F9运行,成功调用弹窗

image-20230128013645747

image-20230128013754358

image-20230128110952592

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/591980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《3D数学基础-图形和游戏开发》阅读笔记 | 3D数学基础 (学习中)

文章目录 3D数学基础矢量/向量概述 - 什么是向量单位矢量&#xff1a;只关注方向不关注大小 数学运算矢量的加法与减法减法的几何意义计算一个点到另一个点的位移矢量的点积与叉积 矩阵矩阵的几何意义 3D数学基础 矢量/向量 在笔记中 变量使用小写字母表示&#xff0c;a由于…

Linux实用指令篇

目录结构 Linux文件系统结构是从Unix文件结构演进过来的。在Linux文件系统中&#xff0c;通用的目录名用于表示一些常见的功能。 Linux 的文件系统是采用层级式的树状目录结构&#xff0c;在此结构中的最上层是 根目录 “/”&#xff0c;然后在此目录下再创建其他的目录。在L…

安装 Node.js、npm

安装 nodejs 安装Node.js的最简单的方法是通过软件包管理器。 Node.js官网&#xff1a;https://nodejs.org/en/download/ cd /usr/local/src/wget -c https://nodejs.org/dist/v18.16.0/node-v18.16.0-linux-x64.tar.xz xz -d node-v18.16.0-linux-x64.tar.xz tar -xf node…

轻松搞定软件开发:找对软件开发公司的流程与注意事项!

随着数字化时代的来临&#xff0c;软件开发在企业和个人生活中扮演着越来越重要的角色&#xff0c;然而&#xff0c;如何找到一家合适的软件开发公司却成为了一个令人头疼的问题。 本文将为你详细解读找软件开发公司的流程&#xff0c;以及在选择过程中需要注意的事项&#xf…

C#编程-编写和执行C#程序

编写和执行C#程序 可以使用Windows记事本应用程序来编写C#程序。在记事本应用程序中创建C#程序后,您需要编译并执行该程序以获得所需的输出。编译器将程序的源代码转换为机器代码,这样计算机就能理解程序中的指令了。 注释 除了记事本,您还可以使用任何其他文本编辑器来编写…

UnityRenderStreaming使用记录(一)

UnityRenderStreaming 地址https://github.com/Unity-Technologies/UnityRenderStreaming 一、客户端相关 1、unity工程添加Package 2、WebRTC选Version 3.0.0-pre.6&#xff0c;升级会报错 导入Samples 3、打开Broadcast场景 二、服务器相关 这里使用github上的源码&…

kubeadm创建k8s集群

kubeadm来快速的搭建一个k8s集群&#xff1a; 二进制搭建适合大集群&#xff0c;50台以上。 kubeadm更适合中下企业的业务集群。 部署框架 master192.168.10.10dockerkubelet kubeadm kubectl flannelnode1192.168.10.20dockerkubelet kubeadm kubectl flannelnode2192.168.1…

MySQL数据库高级SQL语句及存储过程

目录 一、高级SQL语句 &#xff08;一&#xff09;case语句 1.语法定义 2.示例 &#xff08;二&#xff09;空值(NULL) 和 无值( ) 1.区别 2.示例 &#xff08;1&#xff09;字符长度 &#xff08;2&#xff09;判断方法 ① 空值(NULL) ② 无值( ) &#xff08;3…

centos 7.9 升级系统默认的python2.7到python 2.7.18

centos 7.9 升级系统默认的python2.7到python 2.7.18 备份旧版本 mv /usr/bin/python /usr/bin/python_2.7.5 下载新版本 Download Python | Python.org Python Release Python 2.7.18 | Python.org wget https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz cd /…

哨兵1号回波数据(L0级)FDBAQ压缩算法详解

本专栏目录: 全球SAR卫星大盘点与回波数据处理专栏目录-CSDN博客 1. 全球SAR卫星回波数据压缩算法统计 各国的SAR卫星的压缩算法按照时间轴排列如下: 可以看出传统的分块BAQ压缩算法(上图粉色)仍然是主流,哨兵1号其实也有传统的BAQ压缩模式。 本文介绍哨兵1号用的FDBAQ算…

【数据分析】指数移动平均线的直观解释

slavahead 一、介绍 在时间序列分析中&#xff0c;通常需要通过考虑先前的值来了解序列的趋势方向。序列中下一个值的近似可以通过多种方式执行&#xff0c;包括使用简单基线或构建高级机器学习模型。 指数&#xff08;加权&#xff09;移动平均线是这两种方法之间的稳健权衡。…

nginx源码分析-4

这一章内容讲述nginx的模块化。 ngx_module_t&#xff1a;一个结构体&#xff0c;用于描述nginx中的各个模块&#xff0c;其中包括核心模块、HTTP模块、事件模块等。这个结构体包含了一些模块的关键信息和回调函数&#xff0c;以便nginx在运行时能够正确地加载和管理这些模块。…

UE5.1_Gameplay Debugger启用

UE5.1_Gameplay Debugger启用 重点问题&#xff1a; Gamplay Debugger启用不知道&#xff1f; Apostrophe、Tilde键不知道是哪个&#xff1f; Gameplay调试程序 | 虚幻引擎文档 (unrealengine.com) Gameplay Debugger

day02 有序数组的平方 长度最小子数组 螺旋矩阵

题目1&#xff1a;977 有序数组的平方 题目链接&#xff1a;977 有序数组的平方 题意 返回非递减整数数组的每个数字的平方和 也按照递减排序 双指针★ 代码 class Solution { public:vector<int> sortedSquares(vector<int>& nums) {vector<int>…

VMware 虚拟机 ubuntu 20.04 硬盘扩容方法

前言 最近由于需要编译 【RK3568】的 Linux SDK&#xff0c;发现 虚拟机默认的 200G 空间不足了&#xff0c;因此想增加这个 200G 空间的限制&#xff0c;通过网络上查找了一些方法&#xff0c;加上自己亲自验证&#xff0c;确认 硬盘扩容 正常&#xff0c;方法也比较的容易&a…

Vue:Vue与VueComponent的关系图

1.一个重要的内置关系&#xff1a;VueComponent.prototype.proto Vue.prototype 2.为什么要有这个关系&#xff1a;让组件实例对象&#xff08;vc&#xff09;可以访问到 Vue原型上的属性、方法。 案例证明&#xff1a; <!DOCTYPE html> <html lang"en"&…

Java日期和时间(二)

新增的日期和时间 为什么要学习新增的日期和时间 1、代替Calendar LocalDate&#xff1a;年、月、日 LocalTime&#xff1a;时、分、秒 LocalDateTime&#xff1a;年、月、日、时、分、秒 ZoneId&#xff1a;时区 ZoneldDatetime&#xff1a;带时区的时间 2、代替Date Instan…

解决npm,pnpm,yarn等安装electron超时等问题

我在安装electron的时候&#xff0c;出现了超时等等各种问题&#xff1a; &#xff08;RequestError: connect ETIMEDOUT 20.205.243.166:443&#xff09; npm yarn&#xff1a;Request Error: connect ETIMEDOUT 20.205.243.166:443 RequestError: socket hang up npm ER…

【排序】堆排序(C语言实现)

文章目录 前言1. 堆排序1.1 堆排序的思想1.2 堆排序的实现 2. 为什么向下调整而不是向上调整 前言 本章主要会讲堆排序的实现过程以及向上调整和向下调整的时间复杂度&#xff0c;在学习本章前&#xff0c;需要对堆、以及向上调整和向下调整有一个了解&#xff0c;如果不了解的…

vite+Vue3学习笔记(3)——界面设计

1 Element-plus 这是一个基于Vue3的组件库&#xff0c;能够快速构建界面样式。 官网链接&#xff1a; https://element-plus.gitee.io/zh-CN/guide/design.html 1.1 基础组件 1.1.1 安装 项目中的终端输入&#xff1a; npm install --save element-plus 1.1.2 引用 1.1.2.1…