最近看了一下TDI的网络过滤驱动,在Vista之后就不支持了,但是据说windows7还是能用,于是想试试在win10上还能不能玩。需要注意的是在win10上的TCP和UDP设备对象的驱动对象变成了tdx,于是有了下面的代码。

#include <Ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>
#include <Tdikrnl.h>
#define MEM_TAG     'YCAI'
#define malloc_np(size) ExAllocatePoolWithTag(NonPagedPool, (size), MEM_TAG)
#define free(ptr)       ExFreePool(ptr)
#define TDI_ADDRESS_MAX_LENGTH  TDI_ADDRESS_LENGTH_OSI_TSAP
#define TA_ADDRESS_MAX          (sizeof(TA_ADDRESS) - 1 + TDI_ADDRESS_MAX_LENGTH)
#define TDI_ADDRESS_INFO_MAX    (sizeof(TDI_ADDRESS_INFO) - 1 + TDI_ADDRESS_MAX_LENGTH)
/* filter result */
enum {FILTER_ALLOW = 1,FILTER_DENY,FILTER_PACKET_LOG,FILTER_PACKET_BAD,FILTER_DISCONNECT
};
typedef struct {PIO_COMPLETION_ROUTINE  old_cr;         /* old (original) completion routine */PVOID                    old_context;    /* old (original) parameter for old_cr */PIO_COMPLETION_ROUTINE new_cr;         /* new (replaced) completion routine */PVOID                    new_context;    /* new (replaced) parameter for new_cr */PFILE_OBJECT           fileobj;        /* FileObject from IO_STACK_LOCATION */PDEVICE_OBJECT           new_devobj;     /* filter device object */UCHAR                 old_control;    /* old (original) irps->Control */
} TDI_SKIP_CTX;
struct completion {PIO_COMPLETION_ROUTINE   routine;PVOID                   context;
};
typedef struct {TDI_ADDRESS_INFO    *tai;       /* address info -- result of TDI_QUERY_ADDRESS_INFO */PFILE_OBJECT      fileobj;    /* FileObject from IO_STACK_LOCATION */
} TDI_CREATE_ADDROBJ2_CTX;
struct tdi_obj {PFILE_OBJECT fileobj, associate_obj;UCHAR   local_addr[TA_ADDRESS_MAX];
};NTKERNELAPI NTSTATUS ObReferenceObjectByName(IN PUNICODE_STRING ObjectName, IN ULONG Attributes, IN PACCESS_STATE PassedAccessState OPTIONAL, IN ACCESS_MASK DesiredAccess OPTIONAL, IN POBJECT_TYPE ObjectType OPTIONAL, IN KPROCESSOR_MODE AccessMode, IN OUT PVOID ParseContext OPTIONAL, OUT  PVOID *Object);
extern PVOID *IoDriverObjectType;
PDEVICE_OBJECT g_tcpfltobj = NULL;
PDEVICE_OBJECT g_udpfltobj = NULL;
PDEVICE_OBJECT g_tcpfltobj6 = NULL;
PDEVICE_OBJECT g_udpfltobj6 = NULL;
DRIVER_OBJECT g_old_DriverObject;
PDRIVER_OBJECT new_DriverObject;
unsigned long ntohl(unsigned long netlong)
{unsigned long result = 0;((char *)&result)[0] = ((char *)&netlong)[3];((char *)&result)[1] = ((char *)&netlong)[2];((char *)&result)[2] = ((char *)&netlong)[1];((char *)&result)[3] = ((char *)&netlong)[0];return result;
}unsigned short ntohs(unsigned short netshort)
{unsigned short result = 0;((char *)&result)[0] = ((char *)&netshort)[1];((char *)&result)[1] = ((char *)&netshort)[0];return result;
}NTSTATUS get_device_object(wchar_t *name, PDEVICE_OBJECT *devobj) {UNICODE_STRING str;NTSTATUS status;PFILE_OBJECT fileobj;RtlInitUnicodeString(&str, name);status = IoGetDeviceObjectPointer(&str, FILE_ALL_ACCESS, &fileobj, devobj);if (status == STATUS_SUCCESS)ObDereferenceObject(fileobj);return status;
}NTSTATUS tdi_skip_complete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) {TDI_SKIP_CTX *ctx = (TDI_SKIP_CTX *)Context;NTSTATUS status = STATUS_SUCCESS;PIO_STACK_LOCATION irps;if (Irp->IoStatus.Status != STATUS_SUCCESS)DbgPrint("tdi_skip_complete: status 0x%x\n", Irp->IoStatus.Status);//这里是回到之前的那一层Irp->CurrentLocation--;Irp->Tail.Overlay.CurrentStackLocation--;irps = IoGetCurrentIrpStackLocation(Irp);DeviceObject = irps->DeviceObject;//保存一下当前的设备对象if (ctx->new_cr != NULL) {// restore fileobject (it's NULL)irps->FileObject = ctx->fileobj;// set new device object in irpsirps->DeviceObject = ctx->new_devobj;// call new completion status = ctx->new_cr(ctx->new_devobj, Irp, ctx->new_context);//这里是到tdi_create_addrobj_complete 去执行}elsestatus = STATUS_SUCCESS;// restore routine and context (and even control!)irps->CompletionRoutine = ctx->old_cr;irps->Context = ctx->old_context;irps->Control = ctx->old_control;// restore device objectirps->DeviceObject = DeviceObject;Irp->CurrentLocation++;Irp->Tail.Overlay.CurrentStackLocation++;if (ctx->old_cr != NULL) {if (status != STATUS_MORE_PROCESSING_REQUIRED) {BOOLEAN b_call = FALSE;if (Irp->Cancel) {// cancelif (ctx->old_control & SL_INVOKE_ON_CANCEL)b_call = TRUE;}else {if (Irp->IoStatus.Status >= STATUS_SUCCESS) {// successif (ctx->old_control & SL_INVOKE_ON_SUCCESS)b_call = TRUE;}else {// errorif (ctx->old_control & SL_INVOKE_ON_ERROR)b_call = TRUE;}}if (b_call)status = ctx->old_cr(DeviceObject, Irp, ctx->old_context);//之前老的complete如果有的话}else {irps->Control = ctx->old_control;}}free(ctx);return status;
}NTSTATUS tdi_dispatch_complete(PDEVICE_OBJECT devobj, PIRP irp, int filter, PIO_COMPLETION_ROUTINE cr, PVOID context) {PIO_STACK_LOCATION irps = IoGetCurrentIrpStackLocation(irp);NTSTATUS status;if (filter == FILTER_DENY) {if (irp->IoStatus.Status == STATUS_SUCCESS) {// change statusstatus = irp->IoStatus.Status = STATUS_ACCESS_DENIED;}else {// set IRP status unchangedstatus = irp->IoStatus.Status;}IoCompleteRequest(irp, IO_NO_INCREMENT);}else if (filter == FILTER_ALLOW) {if (cr != NULL) {// save old completion routine and contextTDI_SKIP_CTX *ctx = (TDI_SKIP_CTX *)malloc_np(sizeof(*ctx));if (ctx == NULL) {DbgPrint("tdi_send_irp_to_old_driver: malloc_np\n");status = irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;IoCompleteRequest(irp, IO_NO_INCREMENT);return status;}ctx->old_cr = irps->CompletionRoutine;ctx->old_context = irps->Context;ctx->new_cr = cr;ctx->new_context = context;ctx->fileobj = irps->FileObject;ctx->new_devobj = devobj;ctx->old_control = irps->Control;//手动模拟setcompleteirps->Context = ctx;irps->CompletionRoutine = (PIO_COMPLETION_ROUTINE)tdi_skip_complete;irps->Control = SL_INVOKE_ON_ERROR | SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_CANCEL;//IoSetCompletionRoutine(irp, tdi_skip_complete, ctx, TRUE, TRUE, TRUE);}/* call original driver */status = g_old_DriverObject.MajorFunction[irps->MajorFunction](devobj, irp);}else {  /* FILTER_UNKNOWN */status = irp->IoStatus.Status = STATUS_SUCCESS;    // ???IoCompleteRequest(irp, IO_NO_INCREMENT);}return status;
}NTSTATUS tdi_create_addrobj_complete2(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) {NTSTATUS status;TDI_CREATE_ADDROBJ2_CTX *ctx = (TDI_CREATE_ADDROBJ2_CTX *)Context;TA_ADDRESS *addr = ctx->tai->Address.Address;struct tdi_obj* obj_item = NULL;unsigned long  Ip= ntohl(((TDI_ADDRESS_IP *)(addr->Address))->in_addr);//得到地址unsigned long * IpAddress =&Ip;//打印一下ip地址和端口DbgPrint("tdi_create_addrobj_complete2: IPaddress : %d.%d.%d.%d:%u fileobj0x%llX\n",((UCHAR *)IpAddress)[3], ((UCHAR *)IpAddress)[2], ((UCHAR *)IpAddress)[1], ((UCHAR *)IpAddress)[0], ntohs(((TDI_ADDRESS_IP *)(addr->Address))->sin_port), ctx->fileobj);status = STATUS_SUCCESS;if (Irp->MdlAddress != NULL) {IoFreeMdl(Irp->MdlAddress);Irp->MdlAddress = NULL;}free(ctx->tai);free(ctx);return STATUS_SUCCESS;
}NTSTATUS tdi_generic_complete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) {if (Irp->PendingReturned) {DbgPrint("tdi_generic_complete: PENDING\n");IoMarkIrpPending(Irp);}return STATUS_SUCCESS;
}NTSTATUS tdi_create_addrobj_complete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) {NTSTATUS status = STATUS_SUCCESS;PIO_STACK_LOCATION irps = IoGetCurrentIrpStackLocation(Irp);PIRP query_irp = (PIRP)Context;PDEVICE_OBJECT devobj;TDI_CREATE_ADDROBJ2_CTX *ctx = NULL;PMDL mdl = NULL;if (Irp->IoStatus.Status != STATUS_SUCCESS) {DbgPrint("tdi_create_addrobj_complete: status 0x%x\n", Irp->IoStatus.Status);status = Irp->IoStatus.Status;goto done;}// query addrobj address:portctx = (TDI_CREATE_ADDROBJ2_CTX *)malloc_np(sizeof(TDI_CREATE_ADDROBJ2_CTX));if (ctx == NULL) {DbgPrint("tdi_create_addrobj_complete: malloc_np\n");status = STATUS_INSUFFICIENT_RESOURCES;goto done;}ctx->fileobj = irps->FileObject;ctx->tai = (TDI_ADDRESS_INFO *)malloc_np(TDI_ADDRESS_INFO_MAX);if (ctx->tai == NULL) {DbgPrint("tdi_create_addrobj_complete: malloc_np!\n");status = STATUS_INSUFFICIENT_RESOURCES;goto done;}mdl = IoAllocateMdl(ctx->tai, TDI_ADDRESS_INFO_MAX, FALSE, FALSE, NULL);if (mdl == NULL) {DbgPrint("tdi_create_addrobj_complete: IoAllocateMdl!\n");status = STATUS_INSUFFICIENT_RESOURCES;goto done;}MmBuildMdlForNonPagedPool(mdl);devobj = DeviceObject;if (devobj == NULL) {DbgPrint("tdi_create_addrobj_complete: get_original_devobj!\n");status = STATUS_INVALID_PARAMETER;goto done;}TdiBuildQueryInformation(query_irp, devobj, irps->FileObject, tdi_create_addrobj_complete2, ctx, TDI_QUERY_ADDRESS_INFO, mdl);status = IoCallDriver(devobj, query_irp);//这里需要继续往下传query_irp = NULL;mdl = NULL;ctx = NULL;if (status != STATUS_SUCCESS) {//STATUS_PENDINGDbgPrint("tdi_create_addrobj_complete: IoCallDriver: 0x%x   STATUS_PENDING==0x103\n", status);goto done;}status = STATUS_SUCCESS;done:// cleanupif (mdl != NULL)IoFreeMdl(mdl);if (ctx != NULL) {if (ctx->tai != NULL)free(ctx->tai);free(ctx);}if (query_irp != NULL)IoCompleteRequest(query_irp, IO_NO_INCREMENT);Irp->IoStatus.Status = status;return tdi_generic_complete(DeviceObject, Irp, Context);
}int tdi_create(PIRP irp, PIO_STACK_LOCATION irps, struct completion *completion) {FILE_FULL_EA_INFORMATION *ea = (FILE_FULL_EA_INFORMATION *)irp->AssociatedIrp.SystemBuffer;if (ea != NULL) {PDEVICE_OBJECT devobj;devobj = irps->DeviceObject;if (devobj == NULL) {DbgPrint("tdi_create: unknown device object 0x%llX!\n", irps->DeviceObject);return FILTER_DENY;}if (ea->EaNameLength == TDI_TRANSPORT_ADDRESS_LENGTH && memcmp(ea->EaName, TdiTransportAddress, TDI_TRANSPORT_ADDRESS_LENGTH) == 0) {//传输PIRP query_irp;//搞一个空的irpquery_irp = TdiBuildInternalDeviceControlIrp(TDI_QUERY_INFORMATION, devobj, irps->FileObject, NULL, NULL);if (query_irp == NULL) {DbgPrint("tdi_create: TdiBuildInternalDeviceControlIrp\n");return FILTER_DENY;}completion->routine = tdi_create_addrobj_complete;//这里是设置完成的回调completion->context = query_irp;}else if (ea->EaNameLength == TDI_CONNECTION_CONTEXT_LENGTH && memcmp(ea->EaName, TdiConnectionContext, TDI_CONNECTION_CONTEXT_LENGTH) == 0) {//连接CONNECTION_CONTEXT conn_ctx = *(CONNECTION_CONTEXT *)(ea->EaName + ea->EaNameLength + 1);//这个是链接上下文}}return FILTER_ALLOW;
}int tdi_connect(PIRP irp, PIO_STACK_LOCATION irps, struct completion *completion){PTDI_REQUEST_KERNEL_CONNECT param = (PTDI_REQUEST_KERNEL_CONNECT)(&irps->Parameters);TA_ADDRESS *remote_addr = ((TRANSPORT_ADDRESS *)(param->RequestConnectionInformation->RemoteAddress))->Address;unsigned long  Ip = ntohl(((TDI_ADDRESS_IP *)(remote_addr->Address))->in_addr);//得到地址unsigned long * IpAddress = &Ip;// 监控TCP外连参数 ---在这里处理DbgPrint("pid:%d tdi_connect: connobj 0x%llX, remote:%d.%d.%d.%d:%u\n",PsGetCurrentProcessId(),irps->FileObject,((UCHAR *)IpAddress)[3], ((UCHAR *)IpAddress)[2], ((UCHAR *)IpAddress)[1], ((UCHAR *)IpAddress)[0],ntohs(((TDI_ADDRESS_IP *)(remote_addr->Address))->sin_port));return FILTER_ALLOW;
}
int tdi_associate_address(PIRP irp, PIO_STACK_LOCATION irps, struct completion *completion){HANDLE addr_handle = ((TDI_REQUEST_KERNEL_ASSOCIATE *)(&irps->Parameters))->AddressHandle;PFILE_OBJECT addrobj = NULL;NTSTATUS status;int result = FILTER_DENY;status = ObReferenceObjectByHandle(addr_handle, GENERIC_READ, NULL, KernelMode, &addrobj, NULL);if (status != STATUS_SUCCESS) {DbgPrint("[tdi_fw] tdi_associate_address: ObReferenceObjectByHandle: 0x%x\n", status);}DbgPrint("tdi_associate_address: connobj = 0x%llX ---> addrobj = 0x%llX\n",irps->FileObject, addrobj);//这里是绑定本地的transport,只打印不记录result = FILTER_ALLOW;if (addrobj != NULL)ObDereferenceObject(addrobj);return result;
}NTSTATUS TdiHookDeviceDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp) {PIO_STACK_LOCATION irps;NTSTATUS status;int result;struct completion completion = { 0 };//假装检查一下if (irp == NULL)return STATUS_SUCCESS;irps = IoGetCurrentIrpStackLocation(irp);if (DeviceObject == g_tcpfltobj || DeviceObject == g_udpfltobj) {//这里过滤TCP和UDPswitch (irps->MajorFunction) {case IRP_MJ_CREATE:result = tdi_create(irp, irps, &completion);status = tdi_dispatch_complete(DeviceObject, irp, result, completion.routine, completion.context);//DbgPrint("IRP_MJ_CREATE \n");break;/*case IRP_MJ_DEVICE_CONTROL:if (KeGetCurrentIrql() == PASSIVE_LEVEL) {status = TdiMapUserRequest(DeviceObject, irp, irps);}elsestatus = STATUS_NOT_IMPLEMENTED; // set fake statusif (status != STATUS_SUCCESS) {void *buf = (irps->Parameters.DeviceIoControl.IoControlCode == IOCTL_TDI_QUERY_DIRECT_SEND_HANDLER) ?irps->Parameters.DeviceIoControl.Type3InputBuffer : NULL;//这里的buf是send的指针,目前不用// send IRP to original driverstatus = tdi_dispatch_complete(DeviceObject, irp, FILTER_ALLOW, NULL, NULL);break;}*/case IRP_MJ_INTERNAL_DEVICE_CONTROL:/* 根据irps->MinorFunction类型进行处理 */if (irps->MinorFunction == TDI_CONNECT) {tdi_connect(irp, irps, &completion);}else if (irps->MinorFunction == TDI_ASSOCIATE_ADDRESS) {tdi_associate_address(irp, irps, &completion);}status = tdi_dispatch_complete(DeviceObject, irp, FILTER_ALLOW,completion.routine, completion.context);break;default:DbgPrint("default \n");status = tdi_dispatch_complete(DeviceObject, irp, FILTER_ALLOW, NULL, NULL);return status;}}elsestatus = g_old_DriverObject.MajorFunction[irps->MajorFunction](DeviceObject, irp);//避免二次完成return status;
}void HookTcpIp() {UNICODE_STRING drv_name;NTSTATUS status;int i;//之后用来做过滤的status = get_device_object(L"\\Device\\Tcp", &g_tcpfltobj);status = get_device_object(L"\\Device\\Udp", &g_udpfltobj);//status = get_device_object(L"\\Device\\Tcp6", &g_tcpfltobj6);//status = get_device_object(L"\\Device\\Udp6", &g_udpfltobj6);//DbgPrint("Tcp  %llX  Udp  %llX  Tcp6  %llX  Udp6  %llX \n", g_tcpfltobj, g_udpfltobj, g_tcpfltobj6, g_udpfltobj6);RtlInitUnicodeString(&drv_name, L"\\Driver\\tdx");//tdx status = ObReferenceObjectByName(&drv_name, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL, &new_DriverObject);if (status != STATUS_SUCCESS) {DbgPrint("ObReferenceObjectByName failed \n");return;}ObDereferenceObject(new_DriverObject);//解引用for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {g_old_DriverObject.MajorFunction[i] = new_DriverObject->MajorFunction[i];new_DriverObject->MajorFunction[i] = TdiHookDeviceDispatch;//替换方法}
}VOID DriverUnload(PDRIVER_OBJECT DriverObject) {int i;for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)new_DriverObject->MajorFunction[i] = g_old_DriverObject.MajorFunction[i];//还原DbgPrint("See You !\n");
}NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath) {DriverObject->DriverUnload = DriverUnload;HookTcpIp();return STATUS_SUCCESS;
}

经过测试无法在打开ie的时候无法拦截到connect请求,但是如果关闭网卡之后在打开,可以看到IRP_MJ_CREATE和IRP_MJ_INTERNAL_DEVICE_CONTROL(TDI_ASSOCIATE_ADDRESS)的消息,由于技术有限也不知道是不是姿势不对还是说windows真的改了。

TDIfw在windows 10 1903的测试相关推荐

  1. win7原版iso_【JUJUMAO_MSDN系统】Windows 10 1903 64位 五版合一 原版ISO镜像

    [JUJUMAO_MSDN系统]Windows 10 1903 64位 五版合一 原版ISO镜像  MSDN系统具有更安全.更稳定.更纯净等特点,设置布局更加突出完美,采用全新技术,全自动无人值守安装 ...

  2. 微软服务器芯片和芯片组更新,Windows 10 1903 突然无法自动更新 Realtek 声卡和 Intel 芯片驱动...

    几天前(2019-8-26),同一台电脑全新安装同个U盘上的Windows 10 1903(July),所有的驱动都在Windows安装后,系统自动在线更新,系统也完全正常.而就过几天(2019-9- ...

  3. Windows 10 1903新特性概览

    Windows 10 1903镜像已经放出,不出意外的话功能和正式推送的时候应该一样.MSDN我告诉你上也已经有相关镜像了.正好我也安装尝尝鲜,顺便为大家介绍一下1903的新特性.大家如果也想尝鲜的话 ...

  4. Windows 10 1903 版本 打开 MMC无法创建管理单元

    安装 Windows 10 1903版本后,MMC控制台无法新增,自动退出. 找了很多方法,无法解决. *** 以下方法证明不行: Dism /Online /Cleanup-Image /ScanH ...

  5. 微软 Windows 10 1903 九月更新官方 ISO 镜像

    前两天微软更新了最新的Windows 10,发布了九月更新ISO镜像,镜像依然想往常那样,此次镜像分为消费者版(consumer)和企业版(business). 欢迎关注我们微信公众号,我们会定期发送 ...

  6. Windows 10 1803 升级到 Windows 10 1903

    官网下载 https://www.microsoft.com/ZH-CN/software-download/windows10 Media Creation Tool工具来创建安装U盘安装介质,然后 ...

  7. Windows 10 1903以后的版本组策略默认关闭自动登录

    1. Rundll32 netplwiz.dll UsersRunDll 2.  regedit - 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows ...

  8. Windows 10 ver.1903 升级报错问题处理

    偶然的机会, 看到网上有人推荐Windows 10(1903版) 于是便使用该版本对台式机进行了安装 大概半年前,系统更新提示有新的更新可以装, 升级到20H2 就因为点了"同意升级&quo ...

  9. nvme固态必须uefi启动吗_为什么 Windows 10 开机启动这么快

    硬件配置高,CPU,内存,固态硬盘,主板等都速度提升了,以及从磁盘分区.镜像选择.系统安装.设置优化.软件设置.垃圾清理,再到系统备份.系统安全.日常使用等都给与了优化. 除了 Windows 10 ...

最新文章

  1. layer的一种用法,自己画出弹出框样式
  2. GDCM:区分音量DiscriminateVolume的测试
  3. Acegi 安全框架
  4. 详细讲述STP过程【转自56cto.com】
  5. 038、JVM实战总结:200小时积累,6小时烹制,史上最强图,图解:大厂面试题,Young GC和Full GC分别在什么情况下会发生?
  6. 创建简单的静态库和动态库
  7. 缓存和数据库同步问题解决方案
  8. 使用Python库valuequant和每股收益历史数据计算股权价值
  9. n9009+android+4.4.2,三星N9009 (Galaxy Note 3 电信版 Android 4.4)ROOT教程,一键获取ROOT权限...
  10. NIVIDIA Xavier联网问题
  11. 设计模式 -- 组合模式(Composite)
  12. java处理器,JAVA注解处理器
  13. Linux设备驱动开发基础
  14. cmath模块——复数域数学函数模块
  15. CSR8670/8675 发射(TX SOURCE)USB发射A2DP音乐,实现MIC声音到主机
  16. 【84期分享】4款中国风PPT模板免费下载
  17. android 恢复照片误删,安卓手机照片误删怎么恢复?一般人不知道这个恢复方法...
  18. 智慧物流解决方案-最新全套文件
  19. 【关于IT专业怎么学】
  20. 世界睡眠医学杂志世界睡眠医学杂志杂志社世界睡眠医学杂志编辑部2023年第2期目录

热门文章

  1. 我们为什么选择计算机专业?为什么学习编程?
  2. 手机计算机隐藏,手机计算机自带的隐藏功能,我也是现在才知道,功能比你想得多...
  3. leetcode学习记录_贪心
  4. 玩游戏计算机频繁重启,win7系统玩lol游戏电脑总是重启如何解决
  5. Java 日期的各种操作
  6. CSDN20181211博客黑板报
  7. 探索 Android Q:位置权限
  8. 男人买鞋有点难——中国十大皮鞋
  9. 淘宝网热浪引擎平台资费规则
  10. 【工具使用】go build 命令打包成exe