windows apiset 研究

前言

众所周知,微软兼容了历史包袱,成功的实现了xp时代(更早的不是我的时代)编译的大部分程序在现在的win10最新版中都能运行,这在macos、linux等系统上是不能实现的。

但也是为了这种兼容,Microsoft C++ 从 VC6更新到vc2022,也一直在文件结构上做了几次尝试。也是令咱们广大程序员头疼,但之前的历史就不细说了,这里只研究下从vc2015开始的apiset模式。

Visual Studio 2015 开始分拆运行时库

从vc7开始微软的vc运行时库的名称就是 msvcr[N].dllmsvcp[N].dll。但从vc2015开始,没有msvcr[N].dll了,而是改成了vcruntime140.dll,并且编译的程序向后兼容新的vc版本。所以所有版本的vc库的名称都统一了,都是vcruntime140.dll、msvcp140.dll(后续版本又更新了msvcp140_1.dll、
msvcp140_2.dll)。它们也都有着调试版本(dll名称后面加d,如vcruntime140.dll)。

在vc2015中将crt拆分成了两个部分:VCRuntime和UniversalCRT。

VCRuntime

vcruntime 库包含特定于 Visual C++ CRT 实现的代码:异常处理和调试支持、运行时检查和类型信息、实现详细信息以及某些扩展库函数。 vcruntime 库版本需要与所使用的编译器版本匹配。

这部分是跟随Microsoft Visual Studio版本一起更新。可以在 项目属性中选择平台工具集来切换版本,不同版本区别有:对c++标准的支持程度不同、语法细节不同。选择使用哪个工具集编译的,最好就得使用比此版本新的vc运行时库。

image.png

UniversalCRT

(UCRT) 包含通过标准 C99 CRT 库导出的函数和全局函数。 UCRT 现为 Windows 组件,作为 Windows 10 及更高版本的一部分提供。 在windows 10版本升级时会同时升级这些dll,做为系统补丁一起升级。在win7到win8之间的版本,也有对应的更新包(Windows Update MSU packages)来安装UCRT。

在xp中没有系统更新来安装UCRT,所以VCRedist在安装时会安装。

在开发环境中,静态库、DLL 导入库和 UCRT 的头文件现在 Windows SDK 中提供。安装Windows SDK后在C:\Program Files (x86)\Windows Kits\10\Redist中可以找到运行时文件,如果安装了多版本也会在目录中按版本号新增子目录。

vc2015之后的无论是哪个版本,都可以使用任意版本的Windows SDK。在Microsoft Visual Studio的项目属性中可以切换。

image.png

文件关系对照表

文件 功能 所属板块
concrt140.dll 并发运行时函数 concurrency:: 等 UCRT
ucrtbase.dll UCRT
api-ms-xxx.dll UCRT
msvcp140.dll、msvcp140_1.dll、msvcp140_2.dll VCRuntime
msvcp140_atomic_wait.dll、msvcp140_codecvt_ids.dll VCRuntime
vcruntime140.dll VCRuntime
vccorlib140.dll VCRuntime

软件分发及dll分布

软件在开发环境编译链接后(动态链接vcruntime),可以使用两种方式发布:

  • 只发布自己开发的程序:在用户电脑上还需要安装VCRedist,VCRedist同时包含UCRT和VCRuntime,VCRedist会将dll文件安装到系统目录中。
  • App-local deployment:将所有的ucrt dll 和 vcruntime 打包一起发布。一开始微软并不提供这种方式,在2015年9月11号才开始提供。

在win10中所有的dll都是安装到C:\Windows\WinSxS\中,并且在C:\Windows\System32\中添加硬链接。在系统升级时会更换硬链接到新版本中。

比如在我的当前机器上,下面两个文件是同一个:

  • C:\Windows\WinSxS\amd64_microsoft-windows-ucrt_31bf3856ad364e35_10.0.22000.1_none_0204d2ed1e48fcf8\ucrtbase.dll
  • C:\Windows\System32\ucrtbase.dll

而在win7中,安装vcredist后会直接将所有文件释放到C:\windows\system32中,但是仅释放了vcruntime系列软件,原来在系统中已经有了22个apiset文件,见下图:


image.png

这部分就对应上面提到的win7中使用系统补丁来安装ucrt,这些文件在。

ApiSet

在上面ucrt中包含很多个api-ms-win-xxx之类的dll,这些dll的原理是什么呢,查阅资料后得出以下知识点。

所有版本(不同设备、不同架构)的windows 10共享一个基础组件叫Core OS(有时叫OneCore),微软把原来的Win32函数分成了功能组,这些功能组叫ApiSets。区分Api set主要是为了分离调用者和实际提供api的dll之间的关系。

  • 在有些设备上仅提供部分Win32 api,比如XBOX。这时通过Api set的名称(例:if (!IsApiSetImplemented("ext-ms-win-session-wtsapi32-l1-1-0")))就能查询是否支持某些api。
  • 在不同的设备中有时提供win32 api的dll名称不同,在检测 API 可用性时使用 API 集名称而不是 DLL 名称,然后延迟加载 API 时再提供正确的实现途径。

在所有设备中都可用的Api放在了OneCore.lib中。

API set有固定的命名规则:

  • 名称必须以字符串 api 或 ext-开头。
    • 以 api 开头的名称表示保证存在于所有Windows版本上的 API。
    • 以 ext- 表示 API 开头的名称,这些 API 可能不存在于所有Windows版本。
  • 名称必须以序列 ln-n-n<<><>> 结尾,其中 n 由十进制数字组成。
  • 名称正文可以是字母数字字符,也可以是 (-) 短划线。
  • 此名称不区分大小写。

api set使用与链接

传统动态加载dll并获取函数地址时使用LoadLibrary or GetProcAddress。但由于win10中有反向转发,所以这些不能用于测试api set是否存在,即使系统中不提供该api也有可能返回一个有效的函数指针,但这时指针指向的Stub函数被调用时仅返回错误。这种情况下需要使用 IsApiSetImplemented 函数来查询给定 API 是否实现。

在windows 10 SDK中部分lib文件中的dll名称api set的dll名称。比如:dumpbin /HEADERS "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x86\pathcch.lib"的输出结果如下:

  Version      : 0
  Machine      : 14C (x86)
  TimeDateStamp: FFFFFFFF
  SizeOfData   : 00000034
  DLL name     : api-ms-win-core-path-l1-1-0.dll
  Symbol name  : _PathCchAppendEx@16
  Type         : code
  Name type    : undecorate
  Hint         : 6
  Name         : PathCchAppendEx

再比如ucrt.lib里就包含了以下dll名称:

dumpbin /HEADERS "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\ucrt\x86\ucrt.lib"  | grep dll | uniq
  DLL name     : api-ms-win-crt-conio-l1-1-0.dll
  DLL name     : api-ms-win-crt-convert-l1-1-0.dll
  DLL name     : api-ms-win-crt-environment-l1-1-0.dll
  DLL name     : api-ms-win-crt-filesystem-l1-1-0.dll
  DLL name     : api-ms-win-crt-heap-l1-1-0.dll
  DLL name     : api-ms-win-crt-locale-l1-1-0.dll
  DLL name     : api-ms-win-crt-math-l1-1-0.dll
  DLL name     : api-ms-win-crt-multibyte-l1-1-0.dll
  DLL name     : api-ms-win-crt-process-l1-1-0.dll
  DLL name     : api-ms-win-crt-runtime-l1-1-0.dll
  DLL name     : api-ms-win-crt-stdio-l1-1-0.dll
  DLL name     : api-ms-win-crt-string-l1-1-0.dll
  DLL name     : api-ms-win-crt-time-l1-1-0.dll
  DLL name     : api-ms-win-crt-utility-l1-1-0.dll

所以生成的exe中就显示静态链接了这些dll(部分)。


image.png

但实测下来windows UWP 的app中的dll使用了更多的仅win10才有的api set,比如dbghelp.dll,win10商店里的dll导入如下:


image.png

而vs自带的如下:


image.png

Api set运行时加载

以上面程序为例,程序导入表中有一个api-ms-win-crt-runtime-l1-1-0.dll,但是通过ProcMon监控,在程序运行时并不会真的去加载该dll,而是直接加载了ucrtbase.dll。并且如果程序目录同时有VCRuntime和UCRT,会加载目录下的VCRuntime,但是UCRT还是加载系统目录下的。

在官方文档API 集操作加载程序中提到,Api set依赖于系统的Library Loader使用api set名称进行运行时重定向到目标主机的二进制文件(Api实际实现的dll)。当加载器遇到api set时,使用API set schema来识别重定向目标dll,在不同设备这个映射关系是不一样的。

windows 10支持两种使用Api set的技术,直接转发和反向转发。

直接转发即使用api set的名称直接映射到一个dll上,根据本地配置(API set schema)将其转换为另外一个dll(不同设备可能不同名称),api set虽然以.dll结尾,但可以没有物理dll文件。如果用户自己新建一个api set名称的dll,比如api-ms-win-crt-runtime-l1-1-0.dll,结果就是在win10系统中exe不能正常运行,因为在导入时系统已经强制切换了名称,并不会加载真实的dll。而在win7系统中会加载这个dll,但由于同时这dll提供了crt的一些函数,所以也会报错(win7中不加载此dll的原因是它Api set schema上只有35个映射,全是api-ms-win-core|Security|Service,不包括所有的api-ms-win-crt系列dll,这也就是上文中提到的win7系统目录下已经有的22个dll中包含crt系列dll但少了很多api-ms-win-core系列dll的原因)。

win10
win7中

反向转发即在非windows电脑的设备上,当加载到一个windows pc中的dll名称,但该dll不存在于此设备中,加载器会查看配置中是否有一对应的反向转发器,反向转发器映射到一个对应功能的api set中,再由api set定位到该设备上实现该功能的dll上。

API set schema

上文提到的api set映射关系在系统的apisetschema.dll文件中,在开源项目Dependencies中有代码对其进行加载,使用Runtime DLL name resolution: ApiSetSchema - Part I (quarkslab.com)中提到的方法。

ApiSetSchema机制在系统启动的早期就激活了,winload.exe在 在系统启动时调用winload!OslpLoadAllModules and winload!OsLoadImage时就加载了 ApiSetSchema.dll,这时再加载windows kernel和HAL等重要模块。
总结一下我们目前所看到的:
Windows 使用一种机制将虚拟 DLL 重定向到逻辑(实现)DLL。
1.在启动时,Winload.exe加载“apisetschema.dll”文件。
2.在内核初始化阶段 1 期间,DLL 中的“.apiset”文件部分将映射到系统内核内存:将创建内核对象部分和此部分的视图。
3.启动流程时,将在流程地址中创建该部分的视图,并可通过_PEB访问。ApisetMap 字段:因此,“apisetschema.dll”文件中的“.apiset”文件部分的内容可用于进程用户空间。

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容