样本和代码以及所需要预习的已经上传
#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函数的参数个数和每个函数参数含义
有具体名字的才算API函数
如何找主函数,od工具的使用在之前的博客中已经讲到
这里我们直接跟进
得到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
首先将把得到的数放入edx中sub 1减去1
因为据算计都是从0开始的
将这个数放入case的栈中
用cmp跟3进行判断
补充:ja 是判断当前的值,如果不是下列case 中的任何一个,则进入default分支
ja指令后将case的值转入EAX,跳转数据段
根据信息
是去执行匹配对应的case段的命令
也就是下面的指令
但准确来说是计算所要跳转地址的偏移
最后switch 编译器会生成一个跳转地址表,然后根据值,算出来之后,eax 就是索引,取里面的偏移,到对应的分支继续执行
各个函数:
malloc
realloc
calloc
_alloca
VirtualAlloc
VirtualAllocEx
HeapAlloc
HeapFree
GlobalAlloc
GlobalFree
RtReALLcateHeap
需要一个参数字节的整数型
需要两个参数
不能
malloc不能初始化所分配的内存空间,而calloc可以
calloc会将分配的内存空间中的每一位初始化为零
但这只是一方面;
单单从分配内存大小角度来看
能
那分配一块0x40大小的内存,两个函数的参数分别怎么填写?
/分配一块0x40大小的内存,两个函数的参数分别怎么填写?
int *p = (int*)malloc(16*sizeof(int))
int *p = (int*)calloc(16,sizeof(int))
重新分配内存并返回void类型, 如果没有足够的内存扩展内存块,则原来的指向的内存指针无变化, 并返回NULL;
如果重新分配大小设为0, 而释放原来的内存块, 并返回NULL
原文链接:https://blog.csdn.net/a063225/article/details/103156819
realloc 有两种可能,一种是扩大范围,另一种是缩小范围
面对缩小范围的情况,大概率是不需要重新调用内存分配机制算法
去重新寻找一个内存块的,只需要在原来基础上砍字节而已
扩大范围且尾部空间不足时realloc会重新分配内存,此时指针会改变
而且,当内存指针发生变化之后,之前内存区域的内容,会被拷贝到新的内存区域
这个地方就涉及到系统内存管理的机制问题了,在调试任务中,这种点往往是解决问题的关键
参数个数一个
作用是分配一个连续栈内存
下面这个才是
如果,当前申请的内存空间小于堆栈的总大小
那么直接返回当前堆栈地址,作为内存指针
关键还是那个堆栈顶的校验
堆栈是容易溢出的
一共四个参数
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
这个标志就是这么用的
具体的其他标志可以上官网搜索,建议后面再做细致了解
目前流行的网络攻击工具,CobaltStrike 生成的网络攻击载荷
就是利用可执行内存页,去加载解密后的shellcode 然后运行的
正常使用和攻击使用
仅仅是看,你的目的是什么
接触底层接口,更多可以了解到系统内部的运作机制
铭记这句话:搞安全,不懂底层机制,那是不合格的
显然底层更难
所以说,引出另一个知识点,上层接口,更多的是方便,代码编写,把很多参数封装了
目前得知道:
底层内存申请接口,可以控制内存块的使用属性,和控制内存块申请的顺序
Ex 兼容了没有Ex的所有参数
看到这个参数,我们就知道,其实系统分配内存的最小单元,是进程
从系统接口中,就能读到系统的机制
这个就是学习底层接口的意义
所以这个Ex 能作为 没有ex的内部实现
其实,在系统API中,有很多对,又Ex和没有Ex的同名函数
而往往,没有Ex 是作为一个外层封装接口,具体内部,就是这些Ex函数。
**补充:**HANDLE 也叫句柄
是windows 内部管理系统的一个身份标志
刚才的VirtualAllocEx 第一个参数,也是句柄
能表示,当前这个函数操作,是在那个区域内去完成的
进程句柄,表示进程ID,堆句柄,表示哪个堆的ID
通俗点,就是门牌号
简单的,你再单元楼一,申请了一块内存
然后你释放的时候,没告诉函数,这块内存再单元楼一
那这个函数怎么释放呢?
或者给你个,错误的门牌号
快递都有地址
理解成快递的地址,都可以
从数据管理的角度来说,这个就是一个索引
找东西的依据
HeapFree 包括这个对应释放函数也是一样的
如果,你给的堆门牌号不对,函数肯定会返回失败
因为它找不对,对应的地址
通过,句柄去管理不同的内存区域,这个是windows系统内部一个重要的机制
UINT uFlags
SIZE_T dwBytes
是在全局的、公用的远堆上分配
GHND
GMEM_FIXED
GMEM_MOVEABLE
GMEM_ZEROINIT
GPTR
GMEM_ZEROINIT 将所申请内存初始化为0
和 calloc 等效
[in] _ Frees_ptr_opt HGLOBAL hMem
全局内存对象的句柄
是
所以,这对函数的关联对象,就是这HGLOBAL
往往系统内部很多函数,都是需要一个句柄参数
去做关联的
根据某个句柄去做操作
逆向的时候
当你已知这个地址是句柄之后
可以设置相应的访问断点
然后F9 就可以快速的找到
其他的操作这个句柄的其他代码位置
关联地址,的用法之一,要记得
定位完成后,汇编代码,一个个看
怎么滴都能看得懂
往往费时间的地方就是定位
操作系统子系统,都是管理自己这些一亩三分地
所以这就是为什么最初要认识和了解各大子系统的功能
搞安全,一定得有个大致的子系统概念,安全场景,都是根据各个子系统展开的
物理内存,就是实际占用内内存条上存储颗粒的内存块
虚拟内存,是系统内部的逻辑内存块,不一定会被实际分配到内存条上的存储颗粒
当内存条资源紧张时,虚拟内存中的东西会被装入其他缓存区域,比如文件磁盘
但是,从程序的角度看,都是内存,且都能访问到里面的数据,只是读写速度不同