3.23~3.25

switch 结构,内存类函数,标准库函数和底层函数的区别

样本和代码以及所需要预习的已经上传

#include
#include
#include


int main()
{
	int a = 0;
	printf("select:");
	scanf_s("%d", &a);
	switch (a) {
	case 1:
		{
			//从堆中申请内存,需要释放
			PCHAR tmp = (PCHAR)malloc(100);
			free(tmp);
		}
		break;
	case 2:
		{
			//从堆中申请内存,需要释放
			PCHAR tmp = (PCHAR)malloc(50);
			PCHAR pnew = (PCHAR)realloc(tmp, 100);
			free(pnew);
		}
		break;
	case 3:
		{	//从堆中申请内存,已经初始化,需要释放
			PCHAR tmp = (PCHAR)calloc(8, 4);
			free(tmp);
		}
		break;
	case 4:
		{
			//从栈中申请内存,
			PCHAR tmp = (PCHAR)_alloca(20);
			_freea(tmp);
		}
		break;
	default:
		break;
	}
	return 0;
}

跟以往一样还是用IDA,OD工具
特别注意:
switch 汇编形式,每一个C函数F7,进去看看,底层实现是什么API,还有三组API,注意观察,C函数和API函数的参数个数和每个函数参数含义

先找变量,改名
3.23~3.25_第1张图片
之前不怎么会找API函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
有具体名字的才算API函数

如何找主函数,od工具的使用在之前的博客中已经讲到
这里我们直接跟进

在这里插入图片描述
按F7进入

在这里插入图片描述得到malloc ---- ntdll.RtlAllocateHeap
malloc 是高层函数, 实际上内部调用了ntdll.RtlAllocateHeap 底层函数
而在源码中一共有四个内存申请函数
底层调用的是什么API函数找出来
malloce 和free这里我们跳过
找个有区别的
realloc— ntdll.RtReALLcateHeap
仔细发现,从1~3case代码都有申请的API函数,而4呢?
没有

原因:
4 是堆栈申请,所以是可以通过汇编代码实现的,并没有底层函数
栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收

这里看到知识链接:https://blog.csdn.net/study_live/article/details/5304354

汇编层面,switch 是怎么构造的?(静态不明白,就动态跳,注意寄存器的操作)

在这里插入图片描述首先将把得到的数放入edx中sub 1减去1
因为据算计都是从0开始的
将这个数放入case的栈中
用cmp跟3进行判断
补充:ja 是判断当前的值,如果不是下列case 中的任何一个,则进入default分支

ja指令后将case的值转入EAX,跳转数据段

在这里插入图片描述在这里插入图片描述

根据信息
是去执行匹配对应的case段的命令
也就是下面的指令

3.23~3.25_第2张图片

但准确来说是计算所要跳转地址的偏移

最后switch 编译器会生成一个跳转地址表,然后根据值,算出来之后,eax 就是索引,取里面的偏移,到对应的分支继续执行

内存分配函数的接口,每个函数的参数个数是多少,每个参数的含义是什么?

各个函数:
malloc
realloc
calloc
_alloca
VirtualAlloc
VirtualAllocEx
HeapAlloc
HeapFree
GlobalAlloc
GlobalFree
RtReALLcateHeap

malloc

需要一个参数字节的整数型

calloc

需要两个参数

提问:calloc 能不能用malloc 来替代?

不能
malloc不能初始化所分配的内存空间,而calloc可以
calloc会将分配的内存空间中的每一位初始化为零
但这只是一方面;

单单从分配内存大小角度来看

那分配一块0x40大小的内存,两个函数的参数分别怎么填写?

/分配一块0x40大小的内存,两个函数的参数分别怎么填写?
int *p = (int*)malloc(16*sizeof(int))
int *p = (int*)calloc(16,sizeof(int))

realloc

重新分配内存并返回void类型, 如果没有足够的内存扩展内存块,则原来的指向的内存指针无变化, 并返回NULL;
如果重新分配大小设为0, 而释放原来的内存块, 并返回NULL
原文链接:https://blog.csdn.net/a063225/article/details/103156819

realloc 有两种可能,一种是扩大范围,另一种是缩小范围

面对缩小范围的情况,大概率是不需要重新调用内存分配机制算法

去重新寻找一个内存块的,只需要在原来基础上砍字节而已

扩大范围且尾部空间不足时realloc会重新分配内存,此时指针会改变

而且,当内存指针发生变化之后,之前内存区域的内容,会被拷贝到新的内存区域

这个地方就涉及到系统内存管理的机制问题了,在调试任务中,这种点往往是解决问题的关键

_alloca

参数个数一个
作用是分配一个连续栈内存

在汇编层面,具体实现算法是怎样的?(_alloca)

最开始弄错了
在这里插入图片描述
这函数,是对堆栈的一个上限进行检查

下面这个才是

3.23~3.25_第3张图片
如果,当前申请的内存空间小于堆栈的总大小
那么直接返回当前堆栈地址,作为内存指针
关键还是那个堆栈顶的校验
堆栈是容易溢出的

VirtualAlloc

在这里插入图片描述

一共四个参数
1要分配的区域的起始地址
2区域的大小(以字节为单位)
3内存分配的类型
4要分配的页面区域的内存保护

参数1是否必要存在?
可以是0
此时虚拟内存的地址由系统分配

存在和不存在,区别在哪里?(该函数所分配的内存区域是在堆区,栈区,或者其他什么区域?)

该函数分配的内存空间是调用进程的虚拟空间

堆,和栈 包括进程虚拟空间,这些都是系统进程管理里面的概念

堆区和栈区也是在进程虚拟空间内

简单可以理解为堆区和栈区只是之前规划使用的住宅用地

而进程虚拟空间,是该进程所拥有的所有土地资源

也就是说这些都在虚拟内存空间

进程加载之后,所有的东西

都会在总的土地资源上进行规划使用

分配的类型分别有哪些,内存保护属性有什么作用?

分配类型有提交物理内存和不提交物理内存

内存保护属性可以设置读、写和执行的权限

第三个参数一共有几个可选项?

MEM_COMMIT
0x00001000
MEM_RESERVE
0x00002000
MEM_RESET
0x00080000
MEM_RESET_UNDO
0x1000000
MEM_LARGE_PAGES
0x20000000
MEM_PHYSICAL
0x00400000
MEM_TOP_DOWN
0x00100000
MEM_WRITE_WATCH
0x00200000

提交物理内存,和不提交物理内存,如果不提交物理内存,分配机制上和物理内存有什么差异?

这个就是系统内存管理机制中的缓存机制

有时候,物理内存紧张的时候,可以先给一个虚拟空间

也就是有一张借条

等其他的物理内存页

释放后,内存管理子系统

再给你兑现成真实物理内存

当系统内存紧张时

内存管理子系统会把溢出的内存写到文件存储磁盘上

这个在windows 上有设置,叫虚拟内存大小

其实,就是通过占用文件存储空间,充当内存,而不是内存条

第四个,参数这些属性,有什么作用,限制不同内存区域的操作,在实际应用中有什么作用?

比如,你在程序中,存放用户密码的内存,是不是不想让其他程序访问

这个时候,申请什么类型的内存块呢?

或者,不希望被其他程序修改的数据时,你由申请什么属性的内存块呢?
PAGE_READONLY

我想申请一块内存,把代码拷贝上去,再运行,这个时候申请什么属性的内存块?
PAGE_EXCUTE_WRITECOPY

这个标志就是这么用的

具体的其他标志可以上官网搜索,建议后面再做细致了解

3.23~3.25_第4张图片

延申

目前流行的网络攻击工具,CobaltStrike 生成的网络攻击载荷
就是利用可执行内存页,去加载解密后的shellcode 然后运行的
正常使用和攻击使用
仅仅是看,你的目的是什么
接触底层接口,更多可以了解到系统内部的运作机制

那些C语言函数参数个数 和这种底层接口参数个数,哪个更加复杂?

铭记这句话:搞安全,不懂底层机制,那是不合格的

显然底层更难

所以说,引出另一个知识点,上层接口,更多的是方便,代码编写,把很多参数封装了

目前得知道:
底层内存申请接口,可以控制内存块的使用属性,和控制内存块申请的顺序

VirtualAllocEx

还是先看看参数
3.23~3.25_第5张图片
和之前没有EX的进行对比后发现

Ex 兼容了没有Ex的所有参数

看到这个参数,我们就知道,其实系统分配内存的最小单元,是进程

从系统接口中,就能读到系统的机制

这个就是学习底层接口的意义

所以这个Ex 能作为 没有ex的内部实现

其实,在系统API中,有很多对,又Ex和没有Ex的同名函数

而往往,没有Ex 是作为一个外层封装接口,具体内部,就是这些Ex函数。

HeapAlloc

在这里插入图片描述

**补充:**HANDLE 也叫句柄

是windows 内部管理系统的一个身份标志

刚才的VirtualAllocEx 第一个参数,也是句柄

能表示,当前这个函数操作,是在那个区域内去完成的

进程句柄,表示进程ID,堆句柄,表示哪个堆的ID

通俗点,就是门牌号

简单的,你再单元楼一,申请了一块内存

然后你释放的时候,没告诉函数,这块内存再单元楼一

那这个函数怎么释放呢?

或者给你个,错误的门牌号

快递都有地址

理解成快递的地址,都可以

从数据管理的角度来说,这个就是一个索引

找东西的依据

HeapFree 包括这个对应释放函数也是一样的

如果,你给的堆门牌号不对,函数肯定会返回失败

因为它找不对,对应的地址

通过,句柄去管理不同的内存区域,这个是windows系统内部一个重要的机制

GlobalAlloc

UINT uFlags
SIZE_T dwBytes

GlobalAlloc是从什么区域分配的内存?

是在全局的、公用的远堆上分配

uFlags 有没有标记,可以完成内存块的全0的初始化

GHND
GMEM_FIXED
GMEM_MOVEABLE
GMEM_ZEROINIT
GPTR

GMEM_ZEROINIT 将所申请内存初始化为0

和 calloc 等效

GlobalFree

[in] _ Frees_ptr_opt HGLOBAL hMem

全局内存对象的句柄

这个句柄,是不是之前 GlobalAlloc 的返回值呢?


所以,这对函数的关联对象,就是这HGLOBAL

往往系统内部很多函数,都是需要一个句柄参数

去做关联的

根据某个句柄去做操作

逆向的时候

当你已知这个地址是句柄之后

可以设置相应的访问断点

然后F9 就可以快速的找到

其他的操作这个句柄的其他代码位置

关联地址,的用法之一,要记得

定位完成后,汇编代码,一个个看
怎么滴都能看得懂
往往费时间的地方就是定位

一定要学会,通过系统函数接口的学习,理解系统内部的机制

操作系统内部,会划分无数的逻辑内存块单元,不编号,怎么管理?

操作系统子系统,都是管理自己这些一亩三分地
所以这就是为什么最初要认识和了解各大子系统的功能

题外话

搞安全,一定得有个大致的子系统概念,安全场景,都是根据各个子系统展开的

补充:虚拟内存和物理内存怎么理解?

物理内存,就是实际占用内内存条上存储颗粒的内存块

虚拟内存,是系统内部的逻辑内存块,不一定会被实际分配到内存条上的存储颗粒

当内存条资源紧张时,虚拟内存中的东西会被装入其他缓存区域,比如文件磁盘

但是,从程序的角度看,都是内存,且都能访问到里面的数据,只是读写速度不同

你可能感兴趣的:(上课内容,学习)