C++ 查询硬件与系统配置的API函数 的代码
Posted lpypg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 查询硬件与系统配置的API函数 的代码相关的知识,希望对你有一定的参考价值。
如下资料是关于C++ 查询硬件与系统配置的API函数 的内容,希望对各朋友有用途。
ActivateKeyboardLayout 激活一个新的键盘布局。键盘布局定义了按键在一种物理性键盘上的位置与含义
Beep 用于生成简单的声音
CharToOem 将一个字串从ANSI字符集转换到OEM字符集
ClipCursor 将指针限制到指定区域
ConvertDefaultLocale 将一个特殊的地方标识符转换成真实的地方ID
CreateCaret 根据指定的信息创建一个插入符(光标),并将它选定为指定窗口的默认插入符
DestroyCaret 清除(破坏)一个插入符
EnumCalendarInfo 枚举在指定“地方”环境中可用的日历信息
EnumDateFormats 列举指定的“当地”设置中可用的长、短日期格式
EnumSystemCodePages 枚举系统中已安装或支持的代码页
EnumSystemLocales 枚举系统已经安装或提供支持的“地方”设置
EnumTimeFormats 枚举一个指定的地方适用的时间格式
ExitWindowsEx 退出windows,并用特定的选项重新启动
ExpandEnvironmentStrings 扩充环境字串
FreeEnvironmentStrings 翻译指定的环境字串块
GetACP 判断目前正在生效的ANSI代码页
GetAsyncKeyState 判断函数调用时指定虚拟键的状态
GetCaretBlinkTime 判断插入符光标的闪烁频率
GetCaretPos 判断插入符的当前位置
GetClipCursor 取得一个矩形,用于描述目前为鼠标指针规定的剪切区域
GetCommandLine 获得指向当前命令行缓冲区的一个指针
GetComputerName 取得这台计算机的名称
GetCPInfo 取得与指定代码页有关的信息
GetCurrencyFormat 针对指定的“地方”设置,根据货币格式格式化一个数字
GetCursor 获取目前选择的鼠标指针的句柄
GetCursorPos 获取鼠标指针的当前位置
GetDateFormat 针对指定的“当地”格式,对一个系统日期进行格式化
GetDoubleClickTime 判断连续两次鼠标单击之间会被处理成双击事件的间隔时间
GetEnvironmentStrings 为包含了当前环境字串设置的一个内存块分配和返回一个句柄
GetEnvironmentVariable 取得一个环境变量的值
GetInputState 判断是否存在任何待决(等待处理)的鼠标或键盘事件
GetKBCodePage 由GetOEMCP取代,两者功能完全相同
GetKeyboardLayout 取得一个句柄,描述指定应用程序的键盘布局
GetKeyboardLayoutList 获得系统适用的所有键盘布局的一个列表
GetKeyboardLayoutName 取得当前活动键盘布局的名称
GetKeyboardState 取得键盘上每个虚拟键当前的状态
GetKeyboardType 了解与正在使用的键盘有关的信息
GetKeyNameText 在给出扫描码的前提下,判断键名
GetKeyState 针对已处理过的按键,在最近一次输入信息时,判断指定虚拟键的状态
GetLastError 针对之前调用的api函数,用这个函数取得扩展错误信息
GetLocaleInfo 取得与指定“地方”有关的信息
GetLocalTime 取得本地日期和时间
GetNumberFormat 针对指定的“地方”,按特定的格式格式化一个数字
GetOEMCP 判断在OEM和ANSI字符集间转换的windows代码页
GetQueueStatus 判断应用程序消息队列中待决(等待处理)的消息类型
GetSysColor 判断指定windows显示对象的颜色
GetSystemDefaultLangID 取得系统的默认语言ID
GetSystemDefaultLCID 取得当前的默认系统“地方”
GetSystemInfo 取得与底层硬件平台有关的信息
GetSystemMetrics 返回与windows环境有关的信息
GetSystemPowerStatus 获得与当前系统电源状态有关的信息
GetSystemTime 取得当前系统时间,这个时间采用的是“协同世界时间”(即UTC,也叫做GMT)格式
GetSystemTimeAdjustment 使内部系统时钟与一个外部的时钟信号源同步
GetThreadLocale 取得当前线程的地方ID
GetTickCount 用于获取自windows启动以来经历的时间长度(毫秒)
GetTimeFormat 针对当前指定的“地方”,按特定的格式格式化一个系统时间
GetTimeZoneInformation 取得与系统时区设置有关的信息
GetUserDefaultLangID 为当前用户取得默认语言ID
GetUserDefaultLCID 取得当前用户的默认“地方”设置
GetUserName 取得当前用户的名字
GetVersion 判断当前运行的Windows和DOS版本
GetVersionEx 取得与平台和操作系统有关的版本信息
HideCaret 在指定的窗口隐藏插入符(光标)
IsValidCodePage 判断一个代码页是否有效
IsValidLocale 判断地方标识符是否有效
keybd_event 这个函数模拟了键盘行动
LoadKeyboardLayout 载入一个键盘布局
MapVirtualKey 根据指定的映射类型,执行不同的扫描码和字符转换
MapVirtualKeyEx 根据指定的映射类型,执行不同的扫描码和字符转换
MessageBeep 播放一个系统声音。系统声音的分配方案是在控制面板里决定的
mouse_event 模拟一次鼠标事件
OemKeyScan 判断OEM字符集中的一个ASCII字符的扫描码和Shift键状态
OemToChar 将OEM字符集的一个字串转换到ANSI字符集
SetCaretBlinkTime 指定插入符(光标)的闪烁频率
SetCaretPos 指定插入符的位置
SetComputerName 设置新的计算机名
SetCursor 将指定的鼠标指针设为当前指针
SetCursorPos 设置指针的位置
SetDoubleClickTime 设置连续两次鼠标单击之间能使系统认为是双击事件的间隔时间
SetEnvironmentVariable 将一个环境变量设为指定的值
SetKeyboardState 设置每个虚拟键当前在键盘上的状态
SetLocaleInfo 改变用户“地方”设置信息
SetLocalTime 设置当前地方时间
SetSysColors 设置指定窗口显示对象的颜色
SetSystemCursor 改变任何一个标准系统指针
SetSystemTime 设置当前系统时间
SetSystemTimeAdjustment 定时添加一个校准值使内部系统时钟与一个外部的时钟信号源同步
SetThreadLocale 为当前线程设置地方
SetTimeZoneInformation 设置系统时区信息
ShowCaret 在指定的窗口里显示插入符(光标)
ShowCursor 控制鼠标指针的可视性
SwapMouseButton 决定是否互换鼠标左右键的功能
SystemParametersInfo 获取和设置数量众多的windows系统参数
SystemTimeToTzSpecificLocalTime 将系统时间转换成地方时间
ToAscii 根据当前的扫描码和键盘信息,将一个虚拟键转换成ASCII字符
ToUnicode 根据当前的扫描码和键盘信息,将一个虚拟键转换成Unicode字符
UnloadKeyboardLayout 卸载指定的键盘布局
VkKeyScan 针对Windows字符集中一个ASCII字符,判断虚拟键码和Shift键的状态
C++一行代码实现任意系统函数Hook!
导语 | 一句话实现系统API的Hook,参数记录以及数据过滤与修改,关注敏感数据本身而不是哪个API的哪个参数可能有敏感的需要处理的信息,写工具的时候想到上述能力可以借助模板实现,赶紧尝试了一下,也做个笔记分享供大家学习。
一、AnyCall
(一)背景
一般来说所有ApiHook库都会需要提供一个与被HookApi相似/相同的Myxxx函数以实现参数访问,这里以BlackBone的LocalHook举例,其需要的是被HookApi的引用参数形式,如下所示:
bool TestFunc1(char a, int b)
return a + b;
bool MyTestFunc1(char& a, int& b)
return a + b;
blackbone::Detour<decltype(&TestFunc1)> hook;
hook.Hook(&TestFunc1, &MyTestFunc1, blackbone::HookType::Inline);
上述使用方式需要为每个被挂钩的函数都写一个符合参数要求的Myxxx函数并将其所有的参数加上引用符号,多写些API就产生了大量重复性的代码。
(一)通用化处理逻辑的优势
既然在这里已经知道被钩挂的函数类型,那么是否可以利用C++模板为我们自动生成一个通用函数,以实现一行代码完成任意API的Hook呢?进一步来说,这样的处理方式是否可以分离API和参数的对应关系,使我们不再关注需要修改哪个API的哪个参数的内容,而是只关注什么数据是敏感数据,对所有参数只要出现敏感数据的参数就进行修改呢,下面是尝试实现上述逻辑的代码笔记。
(二)类型萃取生成函数
函数的参数类型萃取需要借助struct辅助实现,先看下如果不使用struct辅助直接定义模板函数的困难在哪,代码如下:
template<typename RET, typename... ARGS>
RET FunctionCreater(ARGS&... args)
//do something...
blackbone::Detour<decltype(&TestFunc1)> hook;
hook.Hook(&TestFunc1, &FunctionCreater<bool,char,int>, blackbone::HookType::Inline);
这里的模板参数需要用testFuncHooker<bool,char,int>形式传递,即先是返回值类型再是各个参数类型,如果需要进一步自动化处理的话则需要实现自动提取参数类型并将其逐个依次在此展开的能力,使用struct可以避免实现上述复杂的逻辑,代码如下:
template<typename RET, typename... ARGS>
struct AnyCall;
template<typename RET, typename... ARGS>
struct AnyCall<RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
//do something...
;
这里利用变参模板+类型萃取,struct先申明返回值和可变参数包类型的名称,并在特化匹配阶段将decltype(&TestFunc1) 整体拆分出其中的返回值类型和各个参数类型,再通过叠加使用宏定义即可在代码层面实现一行钩挂指定API的能力,如下:
template<typename RET, typename... ARGS>
struct AnyCall;
template<typename RET, typename... ARGS>
struct AnyCall<RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
//do something...
;
#define HookApi(FuncName) static blackbone::Detour<decltype(&FuncName)> hook##FuncName; \\
hook##FuncName.Hook(&FuncName, &AnyCall<decltype(FuncName)>::FunctionCreater, blackbone::HookType::Inline);
HookApi(ReadFile);
HookApi(WriteFile);
HookApi(CreateFile);
......
二、任意函数调用参数监控
(一)函数名称获取
Hook的一大目标就是需要辅助分析关键API调用信息,用上述AnyCall可以很好地解决参数打印需求,但首先需要解决的就是函数名获取的问题,不然日志会很难读,Anycall的模板参数中只传递了函数的类型,是感知不到函数名的,因此函数名的信息只有在宏定义的阶段才能访问到,好在从c++ 17起静态局部字符串变量可以作为模板参数传递,这使得我们可以较为轻松的把他纳入我们的宏定义中去实现,如下:
#define HookApi(FuncName) static const wchar_t name##FuncName[] = L#FuncName; \\
static blackbone::Detour<decltype(&FuncName)> hook##FuncName; \\
hook##FuncName.Hook(&FuncName, &AnyCall<name##FuncName,decltype(FuncName)>::FunctionCreater, blackbone::HookType::Inline);
HookApi(ReadFile);
//宏展开后代码如下
static const wchar_t nameReadFile[] = L"ReadFile";
static blackbone::Detour<decltype(&ReadFile)> hookReadFile;
hookReadFile.Hook(&ReadFile, &AnyCall<nameReadFile, decltype(ReadFile)>::FunctionCreater, blackbone::HookType::Inline);
(二)展开可变参数包打印
对变参模板使用递归的方式进行展开+任意日志库即可实现参数信息的打印,这里以打印到控制台为例:
template<typename RET, typename... ARGS>
struct AnyCall;
template<typename RET, typename... ARGS>
struct AnyCall<RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
std::initializer_list<int> expandLog (std::wcout << args << "|", 0)... ;
;
LogArgs使用初始化列表+逗号表达式的方式逐个展开可变参数包,并在每个参数间添加"|"符号分割,但这么写会有些问题,比如遇到为空的字符串指针会崩溃以及遇到特殊的不能被wstringstream处理的类型就会报错,前者为运行时的问题可以通过运行时判断处理,后者作为类型问题可以通过模板参数匹配解决。
(三)适配特殊参数的处理逻辑
由于需要额外的判断逻辑,因此在初始化列表内不能再使用operator<<默认处理了,需要调用自定义的包装函数,修改如下:
template<typename ArgType>
void LogArgs(std::wstringstream& logInfo, ArgType&& arg)
logInfo << typeid(ArgType).name() << "|";
template<typename RET, typename... ARGS>
struct AnyCall;
template<typename RET, typename... ARGS>
struct AnyCall<RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
std::wstringstream logInfo;
std::initializer_list<int> expandLog (LogArgs(logInfo,args),0)... ;
std::wcout << logInfo.str() << std::endl;
;
后续解决上面提到的两个问题:
首先是空字符串指针的崩溃问题:对指针类型且值为nullptr的情况特殊处理。
其次是没被wstringstream的operator<<重载的参数类型的打印问题:使用requires定义一个concept让编译器帮助判断参数是否可被打印,然后特化处理可以被打印的部分逻辑,在不能处理的类型部分将其类型名称打印出来。
template<typename T> concept CANLOG_TYPE = requires(std::wstringstream & logInfo, T x) logInfo << x; ;
template<CANLOG_TYPE ArgType>
void LogArgs(std::wstringstream& logInfo, ArgType&& arg)
if (std::is_pointer_v<std::decay_t<ArgType>> && !arg)
logInfo << "nullptr|";
return;
logInfo << arg << "|";
template<typename ArgType>
void LogArgs(std::wstringstream& logInfo, ArgType&& arg)
logInfo << typeid(ArgType).name() << "|";
三、任意函数调用参数过滤
Hook的第二大目的一般是需要对指定数据进行过滤/欺骗,数据获取可以用上述方案通用化解决,但是参数的过滤方面用AnyCall会有一些挑战,尤其是如果希望做到完全通用化的敏感数据过滤的目标的话,后面会提,先看下如何进行相关逻辑处理,类似参数日志打印的处理方式,将参数逐个展开传递给ArgHandler,在ArgHandler内即可实现基于参数类型的数据过滤策略,AnyCall实现如下:
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall;
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall<funcName, RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
std::wstringstream logInfo;
std::initializer_list<int> expandLog (LogArgs(logInfo,args),0)... ;
LOG() << funcName << L": " << logInfo.str();
std::initializer_list<int> expandScan (ArgHandler(args),0)... ;
if constexpr (!std::is_same_v<RET, void>)
return RET;
;
ArgHandle需要对不同类型的参数做处理。
(一)特殊修饰符的参数类型
首先是对具有const修饰符的参数不做处理(系统函数具有const修饰符的参数一般也不会有需要被修改的内容)
template<typename ARG>
void ArgHandler(const ARG& x)
(二)指针参数类型
然后即是对指针类型的参数的处理,结构体指针类型的参数需要解引用才能获取到其结构体的大小。
template<typename T> concept POINTER_TYPE = std::is_pointer_v<T> && !std::is_same_v<T, void*>;
template<POINTER_TYPE ARG>
void ArgHandler(ARG x)
if (!x) return;
//...
if constexpr (!std::is_pointer_v<std::remove_pointer_t<ARG>>)
ArgHandler((byte*)x, sizeof(std::remove_pointer_t<ARG>));
else
ArgHandler(*x);
这里的处理方式是将指针移除拿到其结构体的大小,拥有地址和大小后对其数据进行处理(两个参数的ArgHandler函数在此省略实现),多级指针使用递归的方式解决,此处递归过程在编译后可以全部优化掉,另外在//...处省略了对字符串指针类型的处理过程。
(三)链表形结构体的处理
上述的参数通用处理逻辑在处理非内存连续性结构体时会出现遗漏,比如链表形结构体这样内部有类似next指针变量就会导致只能扫描到头结点,这种结构体内部的特殊字段导致结构体的实际范围扩展的情况,由于很难有更通用的处理方式,只能使用特化解决,但可以使用if constexpr替代特化简化相关代码,让用不到此逻辑的函数优化掉该分支。
template<typename T> concept POINTER_TYPE = std::is_pointer_v<T> && !std::is_same_v<T, void*>;
template<POINTER_TYPE ARG>
void ArgHandler(ARG x)
if (!x) return;
if constexpr (std::is_same_v<ARG, PNodeList>)
while (x)
//dosometing
x = x->Next;
return;
if constexpr (!std::is_pointer_v<std::remove_pointer_t<ARG>>)
ArgHandler((BYTE*)x, sizeof(std::remove_pointer_t<ARG>));
else
ArgHandler(*x);
(四)最难以处理的void*类型
上述指针逻辑在概念阶段就排除掉了void*类型的指针,因为此类指针通常的使用方式是强制类型转换成其他的结构体指针类型,这里的转换逻辑API文档定义的,编译期不可能推导出来,以一个驱动通信函数DeviceIoControl为例,其实现形式如下:
BOOL DeviceIoControl(
[in] HANDLE hDevice,
[in] DWORD dwIoControlCode,
[in, optional] LPVOID lpInBuffer,
[in] DWORD nInBufferSize,
[out, optional] LPVOID lpOutBuffer,
[in] DWORD nOutBufferSize,
[out, optional] LPDWORD lpBytesReturned,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
其中的lpOutBuffer是我们关注的内容,但他是LPVOID类型,实际在使用的时候,外部会强制转换成当前需要的结构体指针进行访问,这里外部对lpOutBuffer的大小的感知是通过随lpOutBuffer一同返回的nOutBufferSize确定的。但问题就在这里,一是ArgHandler参数扫描每次只能接受一个参数,二是对于编译器来说AnyCall的内部是无法感知这里参数间人为定义的关系,所以这种问题也只能通过特化去解决,那么可以使用字符串编译期比较解决特化问题吗?
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall;
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall<funcName, RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
if constexpr(wcscmp_compiletime(funName,L"DeviceIoControl"))
//指定第4与第5号参数的关联性
//......
std::initializer_list<int> expandScan (ArgHandler(args),0)... ;
if constexpr (!std::is_same_v<RET, void>)
return RET;
;
这里即使wcscmp_compiletime函数可以实现编译期的字符串比较也不能实现编译期的结果计算,测试是这样原因,应该是编译器还是将funcName当做一个外部符号有关?这里暂不清楚原因,引入std::integer_sequence拆分字符串的话可能能解决这里的问题,当然还有次一级的解决方案,即类型比较的特化,代码如下:
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall;
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall<funcName, RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
if constexpr (std::is_same_v<RET(ARGS...), decltype(DeviceIoControl)>)
//指定第4与第5号参数的关联性
//......
std::initializer_list<int> expandScan (ArgHandler(args),0)... ;
if constexpr (!std::is_same_v<RET, void>)
return RET;
;
相对字符比较来说可能会存在不同的函数同一个类型的问题,但是同类型大概率关联性指定也是一样的,这里参数关联使用tuple去访问即可。
template<unsigned INDEX1, unsigned INDEX2, typename... ARGS>
void RelationArgHandler(ARGS&... args)
std::tuple<ARGS...> argsTuple(args...);
auto buffer = std::get<INDEX1>(argsTuple);
auto size = std::get<INDEX2>(argsTuple);
//do someting...
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall;
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall<funcName, RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
if constexpr (std::is_same_v<RET(ARGS...), decltype(DeviceIoControl)>)
//指定第4与第5号参数的关联性
RelationArgHandler<4, 5, ARGS...>(args...);
std::initializer_list<int> expandScan (ArgHandler(args),0)... ;
if constexpr (!std::is_same_v<RET, void>)
return RET;
;
四、从汇编角度看生成的一个API案例
简化后的测试代码如下:
bool TestFunc1(int a, int* b,int** c)
return a + b;
template<typename T> concept POINTER_TYPE = std::is_pointer_v<T>;
template<typename ARG>
__forceinline void ArgHandler(ARG x)
printf("%s|", typeid(ARG).name());
template<POINTER_TYPE ARG>
__forceinline void ArgHandler(ARG x)
ArgHandler(*x);
template<typename RET, typename... ARGS>
struct AnyCall;
template<typename RET, typename... ARGS>
struct AnyCall<RET(ARGS...)>
static RET FunctionCreater(ARGS&... args)
std::initializer_list<int> expandLog (std::wcout << args << "|", 0)... ;
std::initializer_list<int> expandScan (ArgHandler(args),0)... ;
if constexpr (!std::is_void_v<RET>) return RET;
;
auto func = AnyCall<decltype(TestFunc1)>::FunctionCreater;
int a = 1;
int* b = nullptr;
int** c = nullptr;
func(a, b, c);
逻辑为先打印参数值再打印参数类型,测试输出为: 1|0000000000000000|0000000000000000|int|int|int|,符合预期(记得开启优化) 在Compiler Explorer (godbolt.org)上查看对应的汇编代码,可以看到生成的逻辑很简单就是依次将参数输出以及依次将参数调用ArgHandler函数:
五、总结
一句话实现系统API的HOOK。
参数记录以及数据过滤与修改。
关注敏感数据本身而不是哪个API的哪个参数可能有敏感的需要处理的信息。
完全通用化的参数处理逻辑。
起初的3点设想能够比较好的实现,但是在第4点完全通用化的处理逻辑层面尚且存在些难题,尤其是特殊参数的处理上还是得少量的使用特化逻辑去解决。
作者简介
朱敬峰
腾讯客户端安全工程师
腾讯客户端安全工程师,毕业于南京邮电大学,目前负责游戏安全方案开发工作。
推荐阅读
温馨提示:因公众号平台更改了推送规则,公众号推送的文章文末需要点一下“赞”和“在看”,新的文章才会第一时间出现在你的订阅列表里噢~
以上是关于C++ 查询硬件与系统配置的API函数 的代码的主要内容,如果未能解决你的问题,请参考以下文章
clickhouse,硬件管理与优化(cpu,内存,网络,存储,操作系统配置),profile管理,Quotas设置,约束管理,查询权限,用户管理配置等
Linux内核与内核函数与操作系统,系统调用,这几者的联系是什么?