64位内开发第二十三讲,分层过滤驱动-键盘过滤
64位内开发第二十三讲,分层过滤驱动-键盘过滤
来自: iBinary - 博客园 禁止爬虫.如果遇到此文章不是 出自 博客园 或者 腾讯云+社区. 请举报目标网站. 或者跳转至 本人博客园进行查看.
因为文章随时更改.可能今天只是写了一部分.或者有错误. 而明天就进行更改重发了.
但是爬虫爬取的文章还是之前错误的文章.会为读者造成文章有错误的假象.
一丶键盘过滤的两种方式
1.1 第一种方式 驱动对象方式绑定
第一种方式是通过 寻找键盘驱动对象. 然后遍历其下面的所有设备. 对于每一个设备创建一个过滤设备,并且附加上去.
此方式可以应用于多个键盘设备.
核心代码如下:
#include "CMain.h" #include <ntddkbd.h> // 声明微软未公开的ObReferenceObjectByName()函数 extern "C" NTSTATUS ObReferenceObjectByName( PUNICODE_STRING ObjectName, ULONG Attributes, PACCESS_STATE AccessState, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID ParseContest, PVOID* Object ); extern "C" POBJECT_TYPE * IoDriverObjectType; VOID FilterUnload(IN PDRIVER_OBJECT pDriverObject) { //跟以往卸载不通.过滤驱动卸载的时候 需要解除挂载.然后删除该设备对象 //循环卸载 //IoEnumerateDeviceObjectList() KdPrint(("[Filter]-->DriverUnload \r\n")); PDEVICE_OBJECT next_device = nullptr; if (pDriverObject->DeviceObject == nullptr) { KdPrint(("[Filter]--> Previous Driver Unload \r\n")); return; } next_device = pDriverObject->DeviceObject; while (next_device != nullptr) { PDEVICE_SAVE_INFOMATION device_save_info = (PDEVICE_SAVE_INFOMATION)next_device->DeviceExtension; if (device_save_info == nullptr) { IoDeleteDevice(next_device); break; } //得到记录的下一个设备. if (device_save_info->attach_to_device != nullptr) { //解除附加 IoDetachDevice(device_save_info->attach_to_device); device_save_info->attach_to_device = nullptr; } //删除设备 IoDeleteDevice(next_device); device_save_info->src_device = nullptr; next_device = next_device->NextDevice; } KdPrint(("[Filter]--> Perfect Driver Unload \r\n")); } NTSTATUS Ctrl2capPower( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PDEVICE_SAVE_INFOMATION devExt; devExt = (PDEVICE_SAVE_INFOMATION)DeviceObject->DeviceExtension; PoStartNextPowerIrp(Irp); IoSkipCurrentIrpStackLocation(Irp); return PoCallDriver(devExt->attach_to_device, Irp); } NTSTATUS Ctrl2capPnP( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PDEVICE_SAVE_INFOMATION devExt; PIO_STACK_LOCATION irpStack; NTSTATUS status = STATUS_SUCCESS; KIRQL oldIrql; KEVENT event; devExt = (PDEVICE_SAVE_INFOMATION)DeviceObject->DeviceExtension; irpStack = IoGetCurrentIrpStackLocation(Irp); switch (irpStack->MinorFunction) { case IRP_MN_REMOVE_DEVICE: IoSkipCurrentIrpStackLocation(Irp); IoCallDriver(devExt->attach_to_device, Irp); IoDetachDevice(devExt->attach_to_device); IoDeleteDevice(DeviceObject); status = STATUS_SUCCESS; break; case IRP_MN_SURPRISE_REMOVAL: // // Same as a remove device, but don't call IoDetach or IoDeleteDevice. // IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->attach_to_device, Irp); break; case IRP_MN_START_DEVICE: case IRP_MN_QUERY_REMOVE_DEVICE: case IRP_MN_QUERY_STOP_DEVICE: case IRP_MN_CANCEL_REMOVE_DEVICE: case IRP_MN_CANCEL_STOP_DEVICE: case IRP_MN_FILTER_RESOURCE_REQUIREMENTS: case IRP_MN_STOP_DEVICE: case IRP_MN_QUERY_DEVICE_RELATIONS: case IRP_MN_QUERY_INTERFACE: case IRP_MN_QUERY_CAPABILITIES: case IRP_MN_QUERY_DEVICE_TEXT: case IRP_MN_QUERY_RESOURCES: case IRP_MN_QUERY_RESOURCE_REQUIREMENTS: case IRP_MN_READ_CONFIG: case IRP_MN_WRITE_CONFIG: case IRP_MN_EJECT: case IRP_MN_SET_LOCK: case IRP_MN_QUERY_ID: case IRP_MN_QUERY_PNP_DEVICE_STATE: default: IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->attach_to_device, Irp); break; } return status; } PDEVICE_OBJECT FilterAttach(PDEVICE_OBJECT src_device, PDEVICE_OBJECT target_device) { PDEVICE_OBJECT attach_to_device = nullptr; NTSTATUS status = STATUS_UNSUCCESSFUL; status = IoAttachDeviceToDeviceStackSafe(src_device, target_device, &attach_to_device); if (NT_ERROR(status)) return nullptr; return attach_to_device; } NTSTATUS FilterComplete(IN PDEVICE_OBJECT driver, IN PIRP pIrp) { NTSTATUS ntStatus = STATUS_SUCCESS; //将自己完成IRP,改成由底层驱动负责 PDEVICE_SAVE_INFOMATION pdx = (PDEVICE_SAVE_INFOMATION)driver->DeviceExtension; //调用底层驱动 IoSkipCurrentIrpStackLocation(pIrp); ntStatus = IoCallDriver(pdx->attach_to_device, pIrp); return ntStatus; } NTSTATUS keyboard( PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context ) { KdBreakPoint(); PIO_STACK_LOCATION irp_stack = nullptr; PKEYBOARD_INPUT_DATA key_data_ptr = nullptr; ULONG number_keys = 0; irp_stack = IoGetCurrentIrpStackLocation(Irp); if (NT_SUCCESS(Irp->IoStatus.Status)) { //获取Irp中的数据. key_data_ptr = (PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer; number_keys = Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA); for (int i = 0; i < number_keys; i++) { KdPrint( ("The Code is [%x] key State = [%s] \r\n", key_data_ptr[i].MakeCode, key_data_ptr[i].Flags ? "up" : "down") ); } } //处理pending位传播 if (Irp->PendingReturned) { IoMarkIrpPending(Irp); } return Irp->IoStatus.Status; } NTSTATUS FilterRead(IN PDEVICE_OBJECT driver, IN PIRP pIrp) { NTSTATUS status = STATUS_SUCCESS; if (pIrp->CurrentLocation == 1) { status = STATUS_INVALID_DEVICE_REQUEST; pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return status; } IoCopyCurrentIrpStackLocationToNext(pIrp); //设置一个完成例程,用来进行过滤.当底层按键返回的时候则会触发完成例程. IoSetCompletionRoutine( pIrp, keyboard, driver, TRUE, TRUE, TRUE); //调用底层驱动 PDEVICE_SAVE_INFOMATION pdx = (PDEVICE_SAVE_INFOMATION)driver->DeviceExtension; return IoCallDriver(pdx->attach_to_device, pIrp); } PDRIVER_OBJECT GetKeyBoardDriverObjByDriverName(WCHAR* driver_name) { NTSTATUS status = STATUS_UNSUCCESSFUL; PDRIVER_OBJECT keyboard_driver = nullptr; UNICODE_STRING ucd_kbd_driver_name = { 0 }; status = RtlUnicodeStringInit(&ucd_kbd_driver_name, driver_name); if (NT_ERROR(status)) { return nullptr; } //通过驱动名寻找其驱动对象. 然后寻找驱动对象里面记录的设备.找到所属的设备进行绑定. status = ObReferenceObjectByName( &ucd_kbd_driver_name, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL, (PVOID*)&keyboard_driver); if (NT_ERROR(status)) { return nullptr; } else { ObDereferenceObject(keyboard_driver); return keyboard_driver; } return keyboard_driver; } PDEVICE_OBJECT CreateDevice( PDRIVER_OBJECT driver, PDEVICE_OBJECT target_device) { NTSTATUS status = STATUS_UNSUCCESSFUL; PDEVICE_OBJECT filter_device = nullptr; status = IoCreateDevice(driver, sizeof(DEVICE_SAVE_INFOMATION), NULL, target_device->DeviceType, target_device->Characteristics, FALSE, &filter_device); if (NT_ERROR(status)) { return nullptr; } return filter_device; } PDEVICE_OBJECT AttachToDevice( PDEVICE_OBJECT filter_device, PDEVICE_OBJECT target_device) { PDEVICE_OBJECT stack_low_device = nullptr; NTSTATUS status = STATUS_UNSUCCESSFUL; status = IoAttachDeviceToDeviceStackSafe( filter_device, target_device, &stack_low_device); if (NT_ERROR(status)) { return nullptr; } return stack_low_device; } void InitDeviceExtension( PDEVICE_OBJECT filter_device, PDEVICE_OBJECT target_device, PDEVICE_OBJECT stack_low_device) { PDEVICE_SAVE_INFOMATION save_info = (PDEVICE_SAVE_INFOMATION)filter_device->DeviceExtension; save_info->src_device = filter_device; save_info->target_next_device = target_device; save_info->attach_to_device = stack_low_device; } NTSTATUS InitAttach(PDRIVER_OBJECT keyboard_driver, PDRIVER_OBJECT driver) { NTSTATUS status = STATUS_UNSUCCESSFUL; PDEVICE_OBJECT target_device = nullptr; PDEVICE_OBJECT filter_device = nullptr; PDEVICE_OBJECT stack_low_device = nullptr; //循环遍历驱动对象里面的设备.并且创建相同过滤设备进行绑定. KdBreakPoint(); target_device = keyboard_driver->DeviceObject; while (target_device) { //创建过滤设备 filter_device = CreateDevice(driver, target_device); if (filter_device == nullptr) { return STATUS_UNSUCCESSFUL; } else { //进行绑定 stack_low_device = AttachToDevice(filter_device, target_device); if (stack_low_device == nullptr) { if (filter_device != nullptr) { IoDeleteDevice(filter_device); } filter_device = nullptr; return status; } //记录相应的设备. InitDeviceExtension(filter_device, target_device, stack_low_device); //初始化过滤设备的属性 filter_device->DeviceType = stack_low_device->DeviceType; filter_device->Characteristics = stack_low_device->Characteristics; filter_device->StackSize = stack_low_device->StackSize + 1; filter_device->Flags |= stack_low_device->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE); } target_device = target_device->NextDevice; } } NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING regpath) { //1.设置驱动的卸载以及派遣函数 driver->DriverUnload = FilterUnload; for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { driver->MajorFunction[i] = FilterComplete; } driver->MajorFunction[IRP_MJ_READ] = FilterRead; driver->MajorFunction[IRP_MJ_POWER] = Ctrl2capPower; driver->MajorFunction[IRP_MJ_PNP] = Ctrl2capPnP; //1.寻找目标设备的驱动对象 KdBreakPoint(); PDRIVER_OBJECT keyboard_driver = nullptr; keyboard_driver = GetKeyBoardDriverObjByDriverName(L"\\Driver\\Kbdclass"); if (keyboard_driver == nullptr) { return STATUS_UNSUCCESSFUL; } //2.进行绑定 return InitAttach(keyboard_driver, driver); }
1.2 第二种方式,直接设备类型绑定.
此方式可以使用Winobj
来查看一下你想绑定的键盘设备. 代码还是使用 上一节所用.
在Winobj如下界面则可以看到键盘驱动有多少了.
只需要微微改动即可.
#include "CMain.h" #include <ntddkbd.h> PDEVICE_OBJECT GetAttachDeviceByName( IN wchar_t* attach_device_name, OUT PFILE_OBJECT* fileobj) { NTSTATUS status = STATUS_UNSUCCESSFUL; UNICODE_STRING ucd_attach_device_name = { 0 }; PDEVICE_OBJECT target_next_device = nullptr; if (attach_device_name == nullptr) return nullptr; status = RtlUnicodeStringInit(&ucd_attach_device_name, attach_device_name); if (NT_ERROR(status)) return nullptr; status = IoGetDeviceObjectPointer(&ucd_attach_device_name, FILE_ALL_ACCESS, fileobj, &target_next_device); if (NT_ERROR(status)) { KdPrint(("[Filter]--->IoGetDeviceObjectPointer Error\r\n")); return nullptr; } return target_next_device; } VOID FilterUnload(IN PDRIVER_OBJECT pDriverObject) { //跟以往卸载不通.过滤驱动卸载的时候 需要解除挂载.然后删除该设备对象 //循环卸载 //IoEnumerateDeviceObjectList() KdPrint(("[Filter]-->DriverUnload \r\n")); PDEVICE_OBJECT next_device = nullptr; if (pDriverObject->DeviceObject == nullptr) { KdPrint(("[Filter]--> Previous Driver Unload \r\n")); return; } next_device = pDriverObject->DeviceObject; while (next_device != nullptr) { PDEVICE_SAVE_INFOMATION device_save_info = (PDEVICE_SAVE_INFOMATION)next_device->DeviceExtension; if (device_save_info == nullptr) { IoDeleteDevice(next_device); break; } //得到记录的下一个设备. if (device_save_info->attach_to_device != nullptr) { //解除附加 IoDetachDevice(device_save_info->attach_to_device); device_save_info->attach_to_device = nullptr; } if (device_save_info->target_next_fileobj != nullptr) { //解除引用 ObDereferenceObject(device_save_info->target_next_fileobj); device_save_info->target_next_fileobj = nullptr; device_save_info->target_next_device = nullptr; } //删除设备 IoDeleteDevice(next_device); device_save_info->src_device = nullptr; next_device = next_device->NextDevice; } KdPrint(("[Filter]--> Perfect Driver Unload \r\n")); } PDEVICE_OBJECT CreateFilterDevice(PDRIVER_OBJECT driver) { UNICODE_STRING ucd_filter_device_name = { 0 }; NTSTATUS status = STATUS_UNSUCCESSFUL; PDEVICE_OBJECT filter_device = nullptr; if (driver == nullptr) return nullptr; //创建设备 status = IoCreateDevice( driver, sizeof(DEVICE_SAVE_INFOMATION), nullptr, FILE_DEVICE_KEYBOARD, 0, FALSE, &filter_device); if (NT_ERROR(status)) return nullptr; //初始化驱动 filter_device->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE); filter_device->Flags &= ~DO_DEVICE_INITIALIZING; return filter_device; } PDEVICE_OBJECT FilterAttach(PDEVICE_OBJECT src_device, PDEVICE_OBJECT target_device) { PDEVICE_OBJECT attach_to_device = nullptr; NTSTATUS status = STATUS_UNSUCCESSFUL; status = IoAttachDeviceToDeviceStackSafe(src_device, target_device, &attach_to_device); if (NT_ERROR(status)) return nullptr; return attach_to_device; } NTSTATUS FilterComplete(IN PDEVICE_OBJECT driver, IN PIRP pIrp) { NTSTATUS ntStatus = STATUS_SUCCESS; //将自己完成IRP,改成由底层驱动负责 PDEVICE_SAVE_INFOMATION pdx = (PDEVICE_SAVE_INFOMATION)driver->DeviceExtension; //调用底层驱动 IoSkipCurrentIrpStackLocation(pIrp); ntStatus = IoCallDriver(pdx->attach_to_device, pIrp); return ntStatus; } NTSTATUS Ctrl2capPower( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PDEVICE_SAVE_INFOMATION devExt; devExt = (PDEVICE_SAVE_INFOMATION)DeviceObject->DeviceExtension; PoStartNextPowerIrp(Irp); IoSkipCurrentIrpStackLocation(Irp); return PoCallDriver(devExt->attach_to_device, Irp); } NTSTATUS Ctrl2capPnP( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PDEVICE_SAVE_INFOMATION devExt; PIO_STACK_LOCATION irpStack; NTSTATUS status = STATUS_SUCCESS; KIRQL oldIrql; KEVENT event; devExt = (PDEVICE_SAVE_INFOMATION)DeviceObject->DeviceExtension; irpStack = IoGetCurrentIrpStackLocation(Irp); switch (irpStack->MinorFunction) { case IRP_MN_REMOVE_DEVICE: IoSkipCurrentIrpStackLocation(Irp); IoCallDriver(devExt->attach_to_device, Irp); IoDetachDevice(devExt->attach_to_device); IoDeleteDevice(DeviceObject); status = STATUS_SUCCESS; break; case IRP_MN_SURPRISE_REMOVAL: // // Same as a remove device, but don't call IoDetach or IoDeleteDevice. // IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->attach_to_device, Irp); break; case IRP_MN_START_DEVICE: case IRP_MN_QUERY_REMOVE_DEVICE: case IRP_MN_QUERY_STOP_DEVICE: case IRP_MN_CANCEL_REMOVE_DEVICE: case IRP_MN_CANCEL_STOP_DEVICE: case IRP_MN_FILTER_RESOURCE_REQUIREMENTS: case IRP_MN_STOP_DEVICE: case IRP_MN_QUERY_DEVICE_RELATIONS: case IRP_MN_QUERY_INTERFACE: case IRP_MN_QUERY_CAPABILITIES: case IRP_MN_QUERY_DEVICE_TEXT: case IRP_MN_QUERY_RESOURCES: case IRP_MN_QUERY_RESOURCE_REQUIREMENTS: case IRP_MN_READ_CONFIG: case IRP_MN_WRITE_CONFIG: case IRP_MN_EJECT: case IRP_MN_SET_LOCK: case IRP_MN_QUERY_ID: case IRP_MN_QUERY_PNP_DEVICE_STATE: default: IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->attach_to_device, Irp); break; } return status; } NTSTATUS Ctrl2capReadComplete( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PIO_STACK_LOCATION IrpSp = nullptr; PKEYBOARD_INPUT_DATA key_data_ptr = nullptr; ULONG numKeys,i = 0; IrpSp = IoGetCurrentIrpStackLocation(Irp); if (NT_SUCCESS(Irp->IoStatus.Status)) { key_data_ptr = (PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer; numKeys = Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA); for (i = 0; i < numKeys; i++) { KdPrint( ("The Code is [%x] key State = [%s] \r\n", key_data_ptr[i].MakeCode, key_data_ptr[i].Flags ? "up" : "down") ); //替换按键 if (key_data_ptr[i].MakeCode == 0x1f) { key_data_ptr[i].MakeCode = 0x20; } } } //传播pending if (Irp->PendingReturned) { IoMarkIrpPending(Irp); } return Irp->IoStatus.Status; } NTSTATUS FilterRead(IN PDEVICE_OBJECT device, IN PIRP pIrp) { NTSTATUS ntStatus = STATUS_UNSUCCESSFUL; IoCopyCurrentIrpStackLocationToNext(pIrp); IoSetCompletionRoutine( pIrp, Ctrl2capReadComplete, device, TRUE, TRUE, TRUE); PDEVICE_SAVE_INFOMATION pdx = (PDEVICE_SAVE_INFOMATION)device->DeviceExtension; return IoCallDriver(pdx->attach_to_device,pIrp); } NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING regpath) { //1.设置驱动的卸载以及派遣函数 driver->DriverUnload = FilterUnload; for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { driver->MajorFunction[i] = FilterComplete; } driver->MajorFunction[IRP_MJ_READ] = FilterRead; driver->MajorFunction[IRP_MJ_POWER] = Ctrl2capPower; driver->MajorFunction[IRP_MJ_PNP] = Ctrl2capPnP; //2.寻找目标设备.也就是我们想要Attah的设备 PDEVICE_OBJECT target_device = nullptr; PFILE_OBJECT target_fileobj = nullptr; PDEVICE_OBJECT src_device = nullptr; PDEVICE_OBJECT attach_to_device = nullptr; KdBreakPoint(); target_device = GetAttachDeviceByName(L"\\Device\\KeyboardClass0", &target_fileobj); if (target_device == nullptr) { KdPrint(("[Filter]--> GetAttachDeviceByName Fail \r\n")); return STATUS_UNSUCCESSFUL; } KdPrint(("[Filter]--> Mount Target Device = [%p] \r\n", target_device)); //3.创建设备,以及设置设备扩展 src_device = CreateFilterDevice(driver); if (src_device == nullptr) { KdPrint(("[Filter]--> CreateFilterDevice Fail \r\n")); if (target_fileobj != nullptr) { ObReferenceObject(target_fileobj); target_fileobj = nullptr; } return STATUS_UNSUCCESSFUL; } KdPrint(("[Filter]--> Filter Device = [%p] \r\n", src_device)); //4.Attach到目标设备 attach_to_device = FilterAttach(src_device, target_device); if (attach_to_device == nullptr) { KdPrint(("[Filter]--> FilterAttach Fail \r\n")); if (target_fileobj != nullptr) { ObReferenceObject(target_fileobj); target_fileobj = nullptr; } return STATUS_UNSUCCESSFUL; } KdPrint(("[Filter]--> Attach Device = [%p] \r\n", attach_to_device)); //5.记录一下信息 PDEVICE_SAVE_INFOMATION device_save_info_ptr = (PDEVICE_SAVE_INFOMATION)src_device->DeviceExtension; device_save_info_ptr->src_device = src_device; //记录我们的设备 device_save_info_ptr->target_next_device = target_device; //记录我们要挂载的目标设备 device_save_info_ptr->target_next_fileobj = target_fileobj; //记录文件对象 device_save_info_ptr->attach_to_device = attach_to_device; //记录下一层设备 return STATUS_SUCCESS; }
1.3 效果
第一种方式 只是进行了打印输出。
第二种方式 是把s键替换成了d键 所以此时如果按下s 那么将会被替换成d
注意:
代码只是一个demo 便于说明键盘过滤是怎么一回事. 并不保证运行后不会蓝屏.因为并没有做同步等相关处理.
也可能会有其他问题.
作者:IBinary
坚持两字,简单,轻便,但是真正的执行起来确实需要很长很长时间.当你把坚持两字当做你要走的路,那么你总会成功. 想学习,有问题请加群.群号:725864912(收费)群名称: 逆向学习小分队 群里有大量学习资源. 以及定期直播答疑.有一个良好的学习氛围. 涉及到外挂反外挂病毒 司法取证加解密 驱动过保护 VT 等技术,期待你的进入。
详情请点击链接查看置顶博客 https://www.cnblogs.com/iBinary/p/7572603.html
本文来自博客园,作者:iBinary,未经允许禁止转载 转载前可联系本人.对于爬虫人员来说如果发现保留起诉权力.https://www.cnblogs.com/iBinary/p/16609865.html
欢迎大家关注我的微信公众号.不定期的更新文章.更新技术. 关注公众号后请大家养成 不白嫖的习惯.欢迎大家赞赏. 也希望在看完公众号文章之后 不忘 点击 收藏 转发 以及点击在看功能.

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2020-08-21 根据字符串生成对应Hash值
2018-08-21 学习逆向知识之用于游戏外挂的实现.第二讲,快速寻找植物大战僵尸阳光基址.以及动态基址跟静态基址的区别