Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取

2023-10-27

为什么要写这篇文章

1.      因为最近在学习《软件调试》这本书,看到书中的某个调试历程中讲了Windows的系统调用的实现机制,其中讲到了从Ring3跳转到Ring0之后直接进入了KiFastCallEntry这个函数。

2.      碰巧前天又在网上看到了一篇老文章介绍xxx安全卫士对Windows系统调用的Hook,主要就是Hook到这个函数

3.      刚刚做完毕业设计,对使用中断来实现系统调用的方式记忆犹新。

 

以上原因导致我最近眼前总是出现系统调用这个词,脑海中总是出现系统调用的实现方式,所以决定写篇文章清理一下思维。本文所有举例如无明确指明,均是在Windows XP SP3系统中获得。

 

本文的目的是探索一下Windows目前的系统调用的实现架构,介绍一种中规中矩的获取KiFastCallEntry函数地址的方法,但是在介绍之前还是要把该解释的解释清除。

 

Windows API和系统调用的关系

肯定会有人说Windows API就是系统调用系统调用就是Windwos API,是这样的么?负责任的说:不是这样的!

先从广义上来说,Windows API是对于整个Windows操作系统自身的程序代码之外的应用程序来说的,而系统调用Windows内核对于非内核程序代码之外的Windows系统程序代码来说的。也就是说系统调用要比Windows API低一个层次。一个Windows API是一个函数,这个函数可能会使用系统调用,请注意是可能,因为并不是所有的API都需要进入内核去完成这个API的功能。

 

Windows内核实现文件和应用层文件

在一个安装完成的Windows操作系统中可见并有效的内核实现文件是

C:\Windows\System32\ntoskrnl.exe

C:\Windows\System32\ntkrnlpa.exe

请注意有两个内核文件,其中第二个比第一个的名字少了os多了个pa,省去的os没有任何意义,但是多出来的pa所代表的意思是PAE(物理地址扩展),这是X86CPU的一个硬件特性,Windows启动之后根据当前系统设置是否开启了PAE特性会自动选择把其中一个作为内核加载到内存中。

为什么加了这么多限定词,因为ntoskrnl.exe这个文件名并不一定是这个文件的真实名称,可以从文件属性中看到

ntoskrnl.exe原始文件名为可能为ntoskrnl.exe或者ntkrnlmp.exe

ntkrnlpa.exe原始文件名为可能为ntkrnlpa.exe或者ntkrpamp.exe

可以发现其中的不同之处就是mpmp就是Multi-processor(多处理器,也可以理解为多核,因为IA-32架构对多核处理器的编程和多处理器的编程是相似的机制)。为什么会出现这中情况呢?因为这完全是由计算机硬件的不同配置导致的。当安装Windows操作系统的时候,Windows安装程序会自动检测机器的CPU特性,根据CPU的核心数来确定使用哪一套内核。如果是单核心就只复制ntkrnlpa.exentoskrnl.exe到系统目录下,如果是多核心就复制ntkrnlpamp.exentoskrnlmp.exe到系统目录下,所以如果你有一台单核心CPU的机器,有一天你换了双核的CPU却没有重新安装操作系统,那么你就不会在看到熟悉的Windows启动画面了。类似这两个文件的还有一个文件C:\Windows\System32\hal.dll,这是Windows的硬件抽象层程序文件,这个就不做具体介绍了。额外补充一个不同的硬件配置所需要的文件列表:

 

Standard PC

hal.dll
ntkrnlpa.exe
ntoskrnl.exe

Advanced Configuration and Power Interface (ACPI) PC

halacpi.dll---->hal.dll
ntkrnlpa.exe
ntoskrnl.exe

ACPI Uniprocessor PC

halaacpi.dll--->hal.dll
ntkrnlpa.exe
ntoskrnl.exe

MPS Uniprocessor PC

halapic.dll---->hal.dll
ntkrnlpa.exe
ntoskrnl.exe

ACPI Multiprocessor PC

halmacpi.dll--->hal.dll
ntkrpamp.exe--->ntkrnlpa.exe
ntkrnlmp.exe---->ntoskrnl.exe

Compag SystemPro Multiprocessor or 100% Compatible

halsp.dll---->hal.dll
ntkrpamp.exe--->ntkrnlpa.exe
ntkrnlmp.exe--->ntoskrnl.exe

MPS Multiprocessor PC

halmps.dll----->hal.dll
ntkrpamp.exe----->ntkrnlpa.exe
ntkrnlmp.exe--->ntoskrnl.exe

Silicon Graphics Visual Workstation

halsp.dll---->hal.dll
ntkrpamp.exe----->ntkrnlpa.exe
ntkrnlmp.exe---->ntoskrnl.exe

 

不论什么配置,一旦系统安装完成后,对我们来说可见的内核文件就只有两个ntoskrnl.exentkrnlpa.exe。这两个文件中的代码就是运行于RING0下的内核代码,他们里面所包含了真正的系统调用的代码。我们用Dependency Walker看一下:

(P1)

 

可以看到很多函数,他们之中有些是系统调用,会和RING3的程序代码有联系,有些仅仅是内核中的函数,只跟内核中其他的代码有联系。有一点需要说,在用Windbg调试内核的时候,无论系统使用的是哪个内核文件,Windbg都会把这个内核文件的符号(模块名)识别为nt。所以如果我们要查看内核中的NtOpenFile函数的反汇编,只需要输入一下命令

u nt!NtNtOpenFile

如下图:

(P2)

下面再说包含Windows API的文件,这个就非常多了,最基础的User32.dllNtdll.dllKernel.dll这三个文件包含了Windows系统相关的绝大多数API,当然还有其他函数簇比如包含socket函数簇的ws2_32.dll等等,这些dll中都导出了大量的API函数以及结构。这些函数都是运行在RING3层的代码,有些会跟内核层中的代码发生联系,有写也不会,就是上面提到的Windows API不一定会使用系统调用。比如我们举个简单的例子,拿CharNextA这个Windows API来说,这个API是在User32.dll中实现的。我们可以反汇编一下看看这个API的具体实现。

(P3)

可以看到这个函数在ret之前只有一个跳转je并且目标代码仍然在本函数内,而且没有任何中断或者快速系统调用,所以这个函数并没有离开RING3层,也就是说当你编写一个程序调用了这个API之后,这个API并没有把你的程序流程带入RING0的内核层代码。

 

Windows API使用系统调用的方法

通过中断实现系统调用

Windows API如果设涉及到系统调用就要由RING3进入RING0,这就牵扯到了X86保护模式下有特权级变化的控制转移。在早期的CPU(Pentium II之前),没有快速系统调用这个机制,所以能用来进行特权级变化的控制转移的机制只有通过中断实现,保护模式下的中断的实现方式是通过IDT表来实现,IDT表中存放的是一种特殊的X86段描述符——门描述符,门描述符的格式如下

(P4)

可以看到其中有一个Selector字段和一个Offset字段,并且是不连续的,这里只介绍这两个字段的含义,其他字段的含义这里不再赘述,有兴趣的话可以自己去看下保护模式相关资料。说到底这个门描述符的作用就是描述一个程序段,对我们来说重要的就是SelectorOffset字段了,因为Selector可以帮我们找到它所描述的程序的【段】,Offset就是程序在【段】内的【偏移】,有了【段】和【偏移】就可以确定程序的线性地址。那么我们来试着找一找Windows通过中断来实现系统调用时候的流程!不会用Windbg的程序员不是好的狙击手。

首先用Windbg打开一个Calc.exe或者其他的普通应用程序,然后键入命令:

u ntdll!KiIntSystemCall

(P5)

如果有疑问为什么直接来到这个函数,现在先不忙解释,后面再说。可以看到这个函数中

int 2Eh

这一句汇编代码,我们知道了系统调用了2E号中断,从而进入了系统内核,知道了中断号下面我们要做的就是找到这个中断的服务程序,也就是RING3进入到RING0之后的第一条指令在哪里。下面就进入内核调试模式。通过

!pcr

查找IDT的线性地址

(P6)

找到了IDT的线性地址:0x8003f400,前面说过IDT中存放的是门描述符,每一个门描述符占用8个字节,所以我们要找的第2e个门描述符的地址应该为0x8003f400+2e*8,然后我们通过如下命令:

dq /c 1 8003f400+2e*8

查看内存内容:

(P7)

看到第一行的八个字节就是我们要找的2e号中断所对应的门描述符。根据门描述符的格式计算,得到这个门描述符中包含的

Offset【偏移】 = 0x8053e4a1

Selector = 0x0008

【偏移】已经找到了,下面就剩【段】了,【段】可以通过Selector【选择子】找到,这里需要说下【选择子】,选择子占用2个字节,格式如下

(P8)

选择子是真正可以存放在保护模式下的段寄存器中的结构,既然它被放在段寄存器中,那么通过它必然能够找到段的信息,保护模式下的段是通过段描述符来描述的,描述符的具体分类有三种:存储段描述符,系统段描述符,门描述符,这里只介绍存储段描述符,其他的请自行查阅保护模式相关资料。存储段描述符格式如下:

(P9)

可以看到存储段描述里包含了基址,界限和属性。对我们来说只要找到基址就行了。存储段描述符是保存在GDT中的,而选择子则包含了【描述符索引】即这个选择子所指向的描述符在GDT中的索引号,比如我们刚才计算得到的选择子Selector = 0x0008,其中索引号为1,就是说我们要找的段的段描述符在GDT中的第1项。由此可知,我们要找到【段】还需要找到段描述符,这个算法跟从IDT中找门描述符是一样的,先找到GDT的地址,从刚才的!pcr命令执行结果看到GDT = 0x8003f000,一个描述符占用8个字节,我们要找第一个描述符,计算得到描述符的地址为:0x8003f000+8*1,执行命令

dq /c 1 8003f000+1*8

得到结果:

(P10)

根据上述存储段描述符的格式计算得到该段的基地址为0x000000所以费劲千辛万苦找到了【段】=0x00000,现在【段】和【偏移】都找到了,那么我们要找的一个线性地址就找到了:

【段】:【偏移】 = 0x8053e4a1

反汇编看看这个地址的代码!

u 8053e4a1

得到结果:

(P11)

是这个函数nt!KiSystemService。为了验证我们没有计算错误,可以用Windbg直接显示2E号中断所对应的中断服务程序,执行命令:

!idt 2e

(P12)

可以看到通过脑力算出的结果与Windbg dump出来的结果是一样的,证明了我们的算法是没有问题的。

这样我们先总结一下使用中断实现系统调用时候RING3RING3的函数接续:

NtDll!KiIntSystemCall > Nt!KiSystemService

使用快速系统调用机制

Pentium II系列开始的CPU引入了快速系统调用这一特性,增加了两条指令SYSENTERSYSEXIT(AMD CPU中的指令为SYSCALLSYSRET)。这一机制的实现就是专门用于解决操作系统的系统调用的性能问题的,这种机制实现的控制转移比中断系统要快很多,因为转移的目标地址是存放在MSR寄存器内,而中断实现的系统调用目标地址存放在内存中的IDT中,所以能提高执行速度。

下面看一下在应用层是在哪里调用了这条指令的:

(P13)

可以看到在ntdll!KiFastSystemCall中有这条指令。

SYSENTER指令的工作机制是在调用SYSENTER指令前,软件必须通过下面的MSR寄存器,指定0层的代码段和代码指针,0层的堆栈段和堆栈指针:

1. IA32_SYSENTER_CS:一个32位值。低16位是0层的代码段的选择子。该值同时用来计算0层的堆栈的选择子。

2. IA32_SYSENTER_EIP:包含一个32位的0层的代码指针,指向第一条指令。

3. IA32_SYSENTER_ESP:包含一个32位的0层的堆栈指针。

MSR寄存器可以通过指令RDMSR/WRMSR来进行读写。寄存器地址如下表。这些地址值在以后的intel 64IA32处理器中是固定不变的。

MSR

地址

IA32_SYSENTER_CS

174H

IA32_SYSENTER_ESP

175H

IA32_SYSENTER_EIP

176H

当执行SYSENTER,处理器会做下面的动作:

1.IA32_SYSENTER_CS从取出段选择子加载到CS中。

2.IA32_SYSENTER_EIP取出指令指针放到EIP

3.IA32_SYSENTER_CS的值加上8,将其结果加载到SS中。

4.IA32_SYSENTER_ESP取出堆栈指针放到ESP寄存器中

5.切换到0层。

6.EFLAGSVM标志已被置,则清除VM标志。

7.开始执行选择的系统过程。

 

     又看到了【选择子】,了解了执行流程,下面还可以手工一步步计算出SYSENTER指令行执行CPU取出的第一条内核指令到底在哪里。有了上面的计算过程,这次计算就不需要写的很详细了,目标还是要找到【段】和【偏移】,很明显【偏移】放在MSR176号地址中,Windbg用如下指令读取MSR

      rdmsr 176

(P14)

【偏移】 = 0x8053e60

下面找【段】,依然是通过选择子来计算,选择子在MSR174号地址中存放

     rdmsr 174

(P15)

【选择子】 = 0x0008,跟之前的选择子一样,这里就不用再去计算了,从刚才的结果中知道这个选择子指向的描述符所描述的内存基址是线性地址0x0000

所以我们要找的目标线性地址为【段】:【偏移】 = 0x8053e60

下面反汇编一下这个地址:

(P16)

看到是函数Nt!KiFastCallEntry

总结一下使用快速系统调用机制的时候RING3RING3的函数接续:

NtDll!KiFastSystemCall > Nt!KiFastCallEntry

完整的Windows API使用系统调用的过程

XP系统之前,Windows只实现了一种系统调用的方式,那就是通过INT 2E号中断来实现的,所以从RING3RING0之后的第一个函数一定是nt!KiSystemService,这个函数就是核心的系统调用分派函数,但是XP开始,Windows系统开始使用快速系统调用这一机制了,但是并没有失去对中断方式的支持,所以XP之后的系统都是两种实现方式共存的。具体做法我们可以用Windbg来继续探索,下面的工作就要在RING3来做了,因为我们要分析一个完整的Windows API使用系统调用的过程。在这之前需要先说一下有关Nt*Zw*函数的问题。在应用层的Ntdll.dll中有大量的Nt开头的函数和Zw开头的函数,并且都是配对出现,他们所指向的地址都是相同的,所以他们的实现是完全相同的,只不过是别名问题。我们可以用Depdency Walker查看他们的入口地址发现都是相同的,也可以用Windbg反汇编看,关于这一点没什么好说的了,网上很多资料都有说。那我们就可以随便选择一个Nt开头的函数来进行分析,或者Zw开头的都是一样的。内核中也存在这样的函数,但是Nt开头的函数是真正的函数实现,Zw开头的函数是通过nt!KiSystemService函数最终调用的Nt开头的函数。我们选择NtOpenFile函数,需要说明的是这个函数是未导出的,我们在编程的时候使用的是kernel32.dll中导出的OpenFile或者OpenFile,最终是要进入NtOpenFile,从KERNEL32.dllNtdll.dll的过程省略。先反汇编NtOpenFile看看:

(P17)

看到在NtOpenFile函数中红色标注的代码,把一个内存中的一个dwrod取到了edx中,这个内存的符号为SharedUserData!SystemCallStub,然后跳转到edx中的值,所以edx中应该是一个函数的地址,我们看一下这个地址是什么

(P18)

看到这个地址中存放的地址是0x7c92e510,反汇编这个地址

(P19)

看到SharedUserData!SystemCallStub保存的是快速系统调用的入口函数的地址,其中通过sysenter进入内核,进入内核之后的过程上面已经介绍过了。

现在就可以总结一下一个Windows API如果使用了系统调用之后的流程了:

无论在何处直接调用了RING3API并且需要使用系统调用,最终都会通过Ntdll.dll这个模块来进入内核(此说法是错误的,因为Win32子系统就是个例外,Win32子系统也可以进入内核与Win32k.sys交互),例如一个函数OpenXXX,无论其在哪个DLL实现,最终都要进入Ntdll.dll这个函数中的NtOpenXXX,在该函数中的内容就是读取一个用户共享数据区中的一个变量SytemCallStub,这个变量的值就是包含实现特权级变化的控制转移代码的函数入口,其实就是ntdll!KiFastSystemCall的地址,在这个函数中使用了SYSENTER指令,可以直接进入内核的nt!KiFastCallEntry,这个函数会调用nt!KiSystemService这个函数,这个函数的任务是查找SSDT中对应系统调用号的系统调用的实现地址nt!NtOpenXXX

 

本来我是想做一个推测:在XP系统中通过中断方式实现系统调用的代码仍然存在,那么我们能不能根据需要选择我们希望使用的系统调用的实现方式呢?产生这个想法的原因是Windows并没有直接把实现特权级变化的控制转移代码的函数入口硬编码到每一个API中,而是使用了一个变量—SharedUserData!SytemCallStub中,既然这样实现肯定是为了可以方便切换这个函数的地址,在现在的XP以及更新的操作系统中,这个变量中存放的是ntdll!KiFastSystemCall,就是通过快速系统调用的方式来实现系统调用,那么如果我们修改这个变量的值,使其等于ntdll!KiIntSystemCall的话,是不是就可以改变整个系统的系统调用方式为中断方式呢?关于这个猜想还我还没有做下验证,打算过几天再验证。

 

nt!KiFastCallEntry函数地址的获取

下面说一下nt!KiFastCallEntry这个函数,无论通过何种系统调用的实现方式,都会调用这个函数来进行系统调用,所以这个函数也就成了一是十分重要的领地,就相当于兵家必争之地,无论是安全软件还是恶意软件。但是这个这个函数地址的获取还是有一点技巧的。

在这里先说一下XXX安全卫士的获取nt!KiFastCallEntry这个函数地址的方式,因为网上有人分析过它的实现方式,并且被大量转载,所以这里只是简单的提一下。我们已知的信息是,Windows的凡是涉及系统调用的API在内核中的调用顺序是:

快速系统调用:nt!KiFastCallEntry->Nt*

中断方式系统调用:nt!KiSystemService-> nt!KiFastCallEntry->Nt*

所以为了过滤系统调用,只要Hooknt!KiFastCallEntry这个函数就一定能拦截所有的系统调用。XXX安全卫士的做法是先Hook住了一个nt!NtSetEvent函数,当进入这个函数的时候利用栈回溯,找到当前函数的返回地址,这个地址一定是在上层函数内的,得到这个地址就以后,就可以根据特征码搜索需要Inline Hook的目标地址了。借助nt!ZwCreateFile函数来动态跟踪分析,如下图:

(P20)

很多人说这种方法巧妙,通过栈回溯找到目标函数地址域内的一地址,然后特征码匹配,为何不直接找到函数入口然后直接特征码匹配呢?栈回溯的方法固然看起来很巧妙,可是如果内核中对nt!KiFastCallEntry函数启用了FPO(帧指针优化),那这种方法就完全被挫败了,所以这种方法并不能称为完美。

 

网上也有很多其他方法来获取nt!KiFastCallEntry函数入口地址,有暴力搜索的,也有通过MSR寄存器的。通过MSR寄存器读取的确是一种中规中矩的方法,但是看过不少代码,都是建立在一个假设上:nt!KiFastCallEntry的段基址一定是线性地址0x00开始。这些代码都直接忽略了对MSR 174号地址内的段选择子的处理,完整的做法应该利用本文中之前的方法计算段和偏移求得nt!KiFastCallEntry的入口地址,由于汇编语言处理这些计算相对方便,所以本人用汇编实现了一个得到nt!KiFastCallEntry函数地址的函数。代码如下:

 

#define IA32_SYSENTER_CS 174H
#define IA32_SYSENTER_ESP 175H
#define IA32_SYSENTER_EIP 176H

ULONG GetAddressOfKiFastCallEntry()
{
	ULONG dwAddress = 0;
	__asm
	{
		jmp func_main
vgdtr:
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00
		_emit 0x00

func_main:
		push eax
		push ebx
		push ecx
		push edx
		mov ecx, 0x174
		rdmsr
		mov ebx, eax		//Selector offset
		
		sgdt vgdtr
		mov edx, vgdtr
		add edx, 0x02
		mov eax, [edx]		//GDT base
		add ebx, eax		//Selector base

		mov edx, ebx
		add edx, 0x07
		mov eax, [edx]
		shl eax, 24;
		mov edx, ebx
		add edx, 0x02
		mov ecx, [edx]
		and ecx, 0x00FFFFFF
		add eax, ecx		//Address CodeSegment
		mov ebx, eax

		mov ecx, 0x176
		rdmsr
		add eax, ebx

		mov dwAddress, eax

		pop edx
		pop ecx
		pop ebx
		pop eax
	}

	return dwAddress;
} 

 

测试用驱动代码及结果如下图:

(P21)

(P22)

这个结果和我们用Windbg查看得到的是一样的。

 

这篇文章写的有点长,而且知识有点砸碎希望各位看过之后能理解。?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取 的相关文章

  • Sage One API - unsupported_grant_type

    我正在尝试通过以下方式获取 Sage One API 的访问令牌docs https developers sageone com docs en v1 authentication request access token using G
  • R 脚本自动化时的不同结果

    以下命令对 pdf 文件执行 Ghostscript 这pdf file变量包含该 pdf 的路径 bbox lt system paste C gs gs8 64 bin gswin32c exe sDEVICE bbox dNOPAUS
  • 如何让脚本执行结束后自动删除?

    是否可以制作一个Python脚本 在Windows中执行结束时删除 py文件 自删除 这种方式使您的程序不依赖于操作系统 from os import remove from sys import argv remove argv 0 奖励
  • 如何从任何进程关闭 Windows 上的套接字(ipv4 和 ipv6)连接?

    如何在 Windows 上关闭 tcp v4 和 tcp v6 连接 我不想终止具有开放连接的整个进程 因为这显然会将其他人踢出该进程 我需要从一个单独的进程执行此操作 因此无法访问套接字句柄等 我正在使用 Windows API 来获取
  • 在OpenCV中将YUV转换为BGR或RGB

    我有一个电视采集卡 其输入内容为 YUV 格式 我在这里看到了与此问题类似的其他帖子 并尝试尝试所述的所有可能的方法 但它们都没有提供清晰的图像 目前最好的结果是 OpenCVcvCvtColor scr dst CV YUV2BGR 函数
  • 更改desktop.ini不会在Windows中自动更新文件夹图标

    我使用此批处理脚本将所有文件夹和子文件夹的图标更改为位于文件夹中的 ico 文件 但是 资源管理器中的文件夹图标不会改变除非我手动重命名desktop ini将资源管理器中的文件更改为其他内容 然后返回desktop ini或者例如将字母更
  • 由于图形处理单元配置,不支持 Windows Phone 模拟器(Mac 上的 Windows 7)

    启动 Windows Phone 模拟器时出现错误 不支持 Windows Phone 模拟器 因为您的计算机没有所需的图形处理单元配置 如果没有图形处理单元 XNA 框架页面将无法运行 您想继续启动模拟器吗 当我尝试访问网页 任何网页 时
  • Canvas.drawVertices(...) 不绘制任何内容

    下一类是红色三角形的视图 public class FreeStyleViewII extends View private final Paint paint new Paint private final int colors new
  • Qt(在 Windows 上)将权限级别设置为“requireAdministrator”

    我正在使用 Qt Creator 并努力制作 exe文件默认以管理员身份运行 在线阅读所有解决方案我试图将这一行放入我的 pro file QMAKE LFLAGS MANIFESTUAC level requireAdministrato
  • Git 扩展 - 无法在 Windows 上推送到网络驱动器中的 git bare 存储库

    我正在 Windows 上学习 git 我已经安装了 Git 扩展 版本 2 47 3 并使用了它 我在我的 C 单元中创建了一个裸存储库 作为中央存储库 并在硬盘中的其他任何位置创建了个人存储库 我对硬盘中的这两个存储库进行提交 推送和拉
  • 访问图像的 Windows“标签”元数据字段

    我正在尝试进行一些图像处理 所以现在我正在尝试读取图像 exif 数据 有 2 个内置函数可用于读取图像的 exif 数据 问题是我想读取图像标签 exifread and imfinfo这两个函数都不显示图像标签 Is there any
  • 在库的公共接口中使用 boost::shared_ptr

    我们有一个 C 库 提供给多个不同的客户 最近 我们从在公共接口中使用原始指针改为使用 boost sharedptr 正如您可能猜到的那样 这提供了巨大的好处 因为现在客户不再需要担心谁需要删除什么以及何时删除 当我们进行切换时 我相信这
  • Qt 支持 Windows 蓝牙 API 吗?

    谁能告诉我 Qt 是否支持 Windows 蓝牙 API 如果是这样 您能否分享一些有关如何使用它的信息 自上次答复以来 这个问题的答案发生了一些变化 Qt 5 2 版为 Linux BlueZ 和 BlackBerry 设备实现了蓝牙 A
  • 在 Windows 上不使用 OpenSSL 从 pfx 文件或证书存储中提取私钥

    正如标题所示 我想在不使用 OpenSSL 或任何其他第三方工具的情况下导出我的私钥 如果我需要一个 cer文件或 pfx我可以通过 MMC 或 PowerShell 轻松导出这些文件pkiclient但我找不到获取私钥的方法 https
  • 相当于Linux中的导入库

    在 Windows C 中 当您想要链接 DLL 时 您必须提供导入库 但是在 GNU 构建系统中 当您想要链接 so 文件 相当于 dll 时 您就不需要链接 为什么是这样 是否有等效的 Windows 导入库 注意 我不会谈论在 Win
  • Windows 窗口对接

    我想知道如何在 Windows 中将窗口停靠 捕捉到屏幕的一侧 最好使用直接的 Win32 API 我正在寻找的效果就像任务栏 一个在屏幕上有保留空间的窗口 因此最大化另一个窗口会使该窗口占据屏幕的其余部分 但使我的窗口保持在适当的位置并可
  • R 的 ggplot2 有 Python API 吗? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我的问题就像标题一样简单 我想使用R s ggplot2但我所有的数据处理都是在Python 有没有Py
  • 在哪里可以获得 PHP 5.3+ 的 runkit DLL 扩展?

    这是一个简单的问题 我在哪里可以获得 PHP 5 3 版本的 runkit 扩展 它的手册 http php net manual en book runkit php http php net manual en book runkit
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • neo4j - python 驱动程序,服务不可用

    我对 neo4j 非常陌生 我正在尝试建立从 python3 6 到 neo4j 的连接 我已经安装了驱动程序 并且刚刚开始执行第一步 导入请求 导入操作系统 导入时间 导入urllib 从 neo4j v1 导入 GraphDatabas

随机推荐