【滴水基础】4.Win32Api调用(下1)
第二十二:文件系统
#文件系统定义:
---OS用于管理磁盘上文件的方法和数据结构,即在磁盘上如何组织文件的方法(不同OS的方法不同,windows 硬盘文件格式是fat32或NTSF,而linux 需要的文件格式是ext2或ext)
---Windows的磁盘文件格式:

---现在一般都是NTFS:
---优点是:安全性和稳定性,使用中不易产生文件碎片。
---并且能对用户的操作进行记录,通过对用户权限进行严格的限制,使每个用户只能按照系统赋予的权限进行操作,充分保护了系统与数据的安全。

---其中,EFS可以对文件进行加密(我这里是灰色,要启动)

---CMD窗口,输入services.msc打开计算机服务,然后启动EFS就可以进行文件加密
---被加密的文件,只有当前用户可以使用,其它用户不能查看

---磁盘配额:计算机中指定磁盘的储存限制,就是管理员可以为用户所能使用的磁盘空间进行配额限制,每一用户只能使用最大配额范围内的磁盘空间。
---限制指定账户能够使用的磁盘空间,这样可以避免因某个用户的过度使用磁盘空间造成其他用户无法正常工作甚至影响系统运行。
----在服务器管理中此功能非常重要,但对单机用户来说意义不大。
#卷
---文件系统的最上层,如C盘、D盘、E盘(下一级为文件目录)

#卷相关API
---获取卷
---输出十六进制1c,对于的二进制:0001 1100
---从数据低位到高位,分别对应ABCDEF,那么我这里是存在C盘、D盘和E盘
---获取一个卷的盘符字符串
---相关代码
---这里有点问题,只能打印开头

---直接下断点查看szBuff的内存
---发现输出了C:\ 和D:\和E:\

---获取卷的类型
---注意:一定要加 //,我刚开始用 D:\ 就报错了
---查看3和5对应的卷的类型:可移动磁盘和网络磁盘(驱动器不能识别,返回0)

---获取卷的详细信息
---lpVolumeNameBuffer:卷标名称(相当与给当前磁盘取得别名,这里为Data)

---获取卷的指定信息
---打印的信息如下:

#目录相关API
---创建文件CreateDirectory()
---如果在创建时不指定绝对路径,就会创建在:当前进程的工作目录
---在C盘下面创建A目录,并且更名为B目录,最后删除目录
---获取当前进程的目录并且修改
---输出如下:

#文件相关的API
---获取文件的大小
---获取文件的属性
---接受文件属性的结构体
---创建文件并获取文件的
---结果如下:这里获取文件大小失败
---我按照这个方法尝试了还是不行:https://bbs.csdn.net/topics/380103266?page=2

---注意:这里的时间是FILETIME结构体
---需要使用
---这里网上找了个粒子
---效果如下(不知道正确与否):

#读取文件
---设置读取的位置
---读取文件
----创建文件写入内容

---读取
---输出如下

---写入文件
---拷贝文件
---在D盘根目录新建HuangBo.txt,输入123456789
---1.写入“中国“4Byte(默认是ASCII),但是没有使用FlushViewOfFile更新缓存,所有这里获取任然是之前的文件大小:9Byte
---2.这里WriteFile存在一个弊端,就是写在文本的首部而不是尾部,并且把原来的1234这4个字节覆盖掉了

---这里发现HuangBo.txt被删除,而且复制也成功了

#内存映射之文件共享
---A进程修改文件的值,本质是修改物理内存的值
---B进程读取文件的值,本质是读取物理内存的值

---创建文件HuangBo.txt,写入:中国China!;然后读取
---效果如下

---当然,也可以通过创建物理页、虚拟内存和文件相互关联的方式,然后直接读取程序的内存
---查找文件(遍历某个盘的重要文件)
第二十三:内存映射文件
---用ULtraEdit打开Dbgview.exe
---由于计算机是小端存储,数据低位对应内存低位,数据高位对应内存高位
---那么一个指向Dbgview.exe首地址的DWORD指针,读取的应该是 00 90 5A 4D

---读取Dbgview.exe首地址的4Byte(这里用内存读取的弊端:只能读取4Byte,不能像读取文本一样读取数组的形式)
---读取效果如下

---如果想在0000 004c的位置写入1DWORD的数据,就要在指针处

---一般修改不是实时修改,而是关闭进程后修改
---所有需要FlushViewOfFile函数更新缓存
---读取并且修改0000 004c处的值
---我这里不要刷新也可以修改

---对应B进程,也可以通过打开物理内存来读取A进程写入物理内存的值
---B进程读取物理内存(重开一个VC界面)
---2个进程同时运行,发现读取成功
---需要注意:2个进程对应的虚拟内存可能是不一样的,但是偏移是一样的

#内存映射之写拷贝
---1个进程可以看成1个exe模块和多个dll组成(系统dll+自己的dll)
---这些DLL在物理页上只有1块,进程使用dll的本质:映射物理页
---那么,当一个进程在使用kenel32.dll,另一个进程也在进行写操作怎么办?

---在kernal32.dll中的某个函数下个断点
---断点的本质:把程序的汇编改为int 3,对应的机器码:0xCC
---或者在OD的某个函数头按F2(本质是将函数的机器码第一个Byte改为:0xCC)
---那么,B进程执行kernel32.dll其中的函数时,遇到该断点也会停止(但是实际上,B进程根本不受到断点的影响)
---写拷贝FILE_MAP_COPY(MapViewOfFile:物理页和虚拟内存映射):
---系统会从系统页交换文件调拨物理存储器,大小有dwNumberOfBytesToMap指定。
---只要我们不执行读取数据之外的任何操作,系统就不会使用从页交换文件中调拨页面 。
---但是一旦有任何线程写入文件映射视图的任何地址,系统就会从已经调拨的页交换文件中选择一个页面把原始数据复制到页交换文件中的页面,然后让线程进行修改这个副本,再将此页面映射到进程地址空间中。因此任何线程都只会修改数据的副本而不会修改原始数据。
---也就是说:当一个进程执行写操作的时候,就会复制一个新的物理页

---例如:将AB进程的虚拟内存和物理页映射的权限均改为:FILE_MAP_COPY(MapViewOfFile)
---发现A进程虽然修改了,但是B进程读取的任然是原来未修改的值

#总结写拷贝:
---不影响文件的执行
---避免别的进程受到影响
第二十四:静态链接库
---软件模块化的一种思路:
#创建静态链接库

---选择预编译头文件

---在ClassViewz中new Class,发现分别生成A.cpp和A.h

---在A.h中声明函数

---在A.cpp中实现函数,并且构建Build(F7)

---在A/Debug目录下生成了A.lib

---将A.lib和A.h复制到桌面(这两个就是编译后的静态链接库程序)
---然后新建控制台项目Test.cpp,如果Test项目想使用静态链接库,有2种方法
---第一:直接将A.lib和A.h复制到Test项目的目录下

---在FileView处,插入文件到项目

---然后在Main程序里面调用静态链接库,发现构建(build)成功
---VC6里面的Compile(Ctrl+F7)和Build(F7)的区别
---“compile"是“编译”,对你的代码进行语法检查,将你的文本程序语言转化成计算机可以运行的“01010…”形式的二进制文件。compile过程生成“.obj”文件或”.o"文件,这个和编译器有关,vc++中是“.obj”文件。
---“build”是“链接”:将你在程序中调用到的类库融合到你的程序中,比如你用到了printf()函数,那么内部实现该函数的类库代码就会添加到你的程序中。build过程生成“.exe”文件。这个可以直接运行(注意:是直接放到exe内存中)
---理论上来说应该先点"complile",再点"build"。不过在vc++中直接点“build”它会自动先给你compile再build
---第二种:将静态链接库放到VC6的自带系统库里面
---将A.h放入Include目录下,将A.lib放入Lib目录下

---在工程>设置>链接>对象/模块库中添加A.lib

---然后使用包含系统头文件的方式包含<A.h>
---发现执行成功

---查看反汇编:发现难以区分自己写的和静态链接库
---可以看出和自己写的差不多,但是有时候程序会反复的调用静态链接库,那么,相当于重复的写了多分相同的代码,然后Compile到了exe中,最后造成浪费
#总结

第二十五:动态链接库
#什么是动态链接库(Dynamic Link Libary简称DLL)
---Dll是微软在OS中,实现共享函数库概念的一种方式,这些库函数的扩展名(“dll”、".ocx")
---发现1个进程里面存在一个exe和多个Dll(DLL不会使得exe模块的体积变大)
---虽然有许多进程共用DLL(如kernel32.dll),但是DLL模块在物理页上只存在1份(写拷贝权限),解决了静态链接库exe模块包含相同代码的问题。(只有写操作才会复制到物理页)

#创建动态链接库


---发现创建了一个主函数,以及一个头文件,和头文件的实现cpp

---在B.cpp发现主函数和Main()有所不同
---新建类MyDll.cpp和MyDll.h

---在MyDll.h中声明加法Plus和减法Sub

---在MyDll.cpp实现2个函数

---但是这样自己进程可以使用,别人进程不能使用
---需要按照特定的格式进行声明
#什么是调用约定
----常见的调用约定有:__cdecl(C语言默认)、__fastcall、__stdcall、__thiscall

---在MyDll.h中按照动态链接库的格式进程声明
---extern:全局函数;“C”:按照C语言的语法导出函数,_declspec(dllexport):动态链接库的导出声明
---在MyDll.cpp中注意到声明调用约定
#总结
---Dll、exe都是一个模块,而有些模块在执行时需要使用其它模块的功能
---dll的作用:为其它模块提供功能(函数)
---在PE(文件在OS中必须遵循的一种文件结构)中,dll存在导出表(说明提供了那些函数),而exe未必存在导出表
#PELoad查看导出表
---PELoad下载:https://down.52pojie.cn/Tools/PEtools/LordPE.7z
---使用PELoad打开B.dll>目录>输出表>...
---可以从导出表中发现,B.dll提供了2个函数,函数名为Plus和Sub,而且参数的大小都为8Byte,Offset为偏移

---而打开一个exe的helloword文件,发现没有导出表

---除了使用头文件进行extern "C" _declspec(dllexport)方式生成导出表
---也可以使用 .def 文件生成导出表
---将MyDll.h和MyDll.cpp重置为正常
---添加文本文件,后缀名为.def

---在B.def写下要导出的函数,以及显不显示函数名

---编译后,用LoadPE打开导出表,发现指定的函数名被隐藏,而且可以指定序号,也没有参数大小(更加方便便捷)

---VC6新建一个控制台项目,然后将之前的B.dll放入目录下

#在2_19.cpp中使用B.dll(显式链接)
---获取函数在虚拟内存的地址GetProcAddress(主要争对dll)
---具体的代码如下
---执行结果如下:
---动态链接库优点(显示链接):不把dll加入exe模块中,现用现找,非常方便,减少重复代码;缺点:加载不方便

第二十六:隐式链接
#显式链接:
---优点:灵活,什么时候想调用就调用;缺点:相对繁琐,需要获取函数地址
#隐式链接
---在上一节的B.dll中,将导出表修改如下,其它地方不变

----发现在Debug目录下生成了B.dll(显式链接只使用了B.dll)和B.lib
---静态链接库里面的lib文件包含了真正的代码,而动态链接库的lib文件不包含真正的代码,只是一些辅助性的信息

#隐式链接的使用
---在之前使用dll时我们根本没使用.lib文件,因为函数根本不在他里面。他不是静态链接库里的lib文件。
---但是这里面有着辅助信息,所以我们要使用隐式链接的话就要使用他,来让编译器找到dll。
---将B.dll和B.lib放到工程目录下

---然后在main函数里面调用动态链接库
---我们使用有着辅助信息的B.lib,所以我们要使用隐式链接的话就要使用他来让编译器找到B.dll。
---执行如下

---注意:如果是以extern "C"导出的dll和lib
---那么,在加入函数说明的时候,也有加上extern "C"
#总结

---查看隐式链接的反汇编
---发现call的是一个进程内存的值(也就是说:将Sub函数的地址,存储进了缓冲区中)
#总结
---静态库(将静态库编译到exe中,所以call的是一个程序的函数地址)
---动态库(间接调用,将dll和exe分开编译,留出一个缓冲区,真正运行的时候,才会把dll函数在进程空间的地址传给缓冲区)
---在B.dll里面(隐式链接),会生成一个导出表
---当然:B.dll也可能会存在导入表,因为dll也可能调用其它的dll

---而在隐式链接的exe中,也会存在一个导入表(一定存在),但是通常没有导出表

#隐式链接的调用流程

---1.exe启动时,将调用的函数地址的缓冲区空出
---2.OS根据B.lib的辅助信息找到B.dll,然后调用LoadLibarary()加载dll到虚拟内存
---3.OS调用GetProcAddress()获取dll中函数的虚拟地址
---4.将函数的地址,放入缓冲区,然后call dword ptr[缓冲区地址]
---因此:隐式链接和显式链接没有太大的区别,一个是自己写代码调用,一个是OS写代码自动调用
#DllMain函数
---和main()函数一样,Dll也存在主函数,但是DllMain()函数可以执行多次
---对应dll调用的原因,可能存在以下几点
---在B的DllMain()函数,重新生成B.dll
---然后使用显式链接,调用B.dll
---分别在LoadLibrary()和FreeLibrary()下断点调试,发现DllMain()函数被调用2次

---将B的DllMain()修改为
---然后main()函数创建线程
---发现线程调用也会触发DllMain()函数

第二十七:远程线程
#线程
---线程是附属在进程上的执行实体,是代码的执行流程
---代码必须要通过线程才能执行
#创建远程线程CreateRemoteThread()
---创建一个Test.exe进程,创建1个线程,调用1个打印函数
---打印了0-9

---创建一个远程线程.cpp,通过CreateRemoteThread()为Test.exe进程创建远程线程
---首先,需要通过OpenProcess获取Test.exe的进程句柄(根据进程ID获取句柄)
---在远程线程.cpp中,创建远程注入函数
---通过任务管理器查看Test.exe的进程PID:8332

---通过反汇编查看进程的线程函数地址:0x0040D460
---在主函数里面调用远程线程函数注意进程

---发现Test.exe中的线程又执行了一次

第二十八:远程线程注入
---如果想要让远程线程执行自己自定义的函数
---将预定的程序注入DLL,让注入的进程执行DLL
#什么是注入
---在第三方进程不知道/不允许的情况下,将模块/代码写入对方的进程空间,并且设法执行的技术,在安全领域,注入与反注入的对抗愈发激烈。

#远程注入流程
---由于LoadLibrary()和ThreadProc()的格式,传参和返回值都是4Byte
---而基本上exe执行都会调用kernel32.dll,所以只要是exe都会调用LoadLibrary()函数,而普通的exe,不一定会创建线程调用ThreadProc()函数
---思路:在进程A中创建线程,将线程函数指定为LoadLibrary(),通常我们把要实现的功能封装到一个dll里,然后想办法让A进程加载这个dll(前提是同一台电脑)
1.在进程A中分配空间,存储“A.dll”
2.获取LoadLibrary()的地址
3.创建远程线程,执行LoadLibrary()函数(传参)

---使用WriteProcessMemory拷贝A.dll的路径到目标进程虚拟空间,进行传参
#创建动态链接库A.dll
---MyDll.h
---MyDll.cpp
---MyDll.def
---A.cpp
---通过DtDebug获取test.exe中LoadLibrary()加载kernel32.dll时的地址
---打开test.exe > 点E > 点击kernel32.dll > 按Ctrl+N > 找到LoadLibraryA的程序地址:766c27F0
---注意:不同进程的LoadLibraryA是一样的

---将A.dll放到远程线程的工作空间里面,然后在远程线程.cpp中
---远程线程.h
---Main.cpp
---先执行Test.exe,然后再执行main.cpp
---在DtDebug中,文件>attach > Test.exe > 按E,查看进程加载的模块
---发现:A.dll已经加载到了Test.exe的内存空间中,但是没有执行dll

---利用Dll的入口函数DllMain()来执行自定义的程序
---在A.dll的A.cpp修改如下
---重新生成A.dll,注入到Test.exe中
---发现不仅加载了dll,还执行了A.dll中的恶意代码

第二十九:进程间通信
---1.不同主机:Socket;2.同一台主机:物理页

#创建一个Game.cpp,根据键盘输入执行操作
---利用DtDebug查看Game.exe三个函数的位置(结合堆栈图观察call的位置)
---也可以使用VC6的反汇编查看
---三个函数的地址:00401030(Attack)、00401080(Reset)、004010D0(Blood)
#创建一个Dll(WGDLL.cpp)
#创建一个给Game.exe加载WGDLL.dll的控制台.cpp
---StdAfx.h
---StdAfx.cpp
---控制台.cpp
---效果如下:

#总结:
---在控制台和Dll进程之间,创建一个共享内存(4KB)
---让Game.exe加载Dll,并执行DllMain()和线程里面的控制命令
---控制台每隔1S将命令写入共享内存(4Byte)
---DLL线程不间断的获取共享内存(每个循环获取到特定命令,取出后,将共享内存设置为NULL,等待下一次循环)
---控制台每隔又将命令写入共享内存(比获取的慢),DLL线程获取,直到DLL线程获取到结束释放DLL的命令。