ring0 恢复SSDTHook
原理:
用ZwQuerySystemInformation 功能号为11(SystemModuleInformation) 得到所有系统模块的地址 遍历搜索得到ntos模块的基地址
读Ntos模块到System进程空间中
在ntos中找到函数真正地址
将地址转换为ssdt的索引
//X64版本
#include "ResumeSSDTHook.h" #define SEC_IMAGE 0x1000000 PVOID __NtosModuleBaseAddress = NULL;
ULONG64 __NtosModuleLength = ;
PVOID __SSDTAddress = ;
ULONG32 __NtOpenProcessIndex = ; NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegisterPath)
{
PDEVICE_OBJECT DeviceObject;
NTSTATUS Status;
int i = ;
char FunctionName[] = "NtOpenProcess";
char ModuleName[] = "ntoskrnl.exe"; UNICODE_STRING DeviceName;
UNICODE_STRING LinkName; RtlInitUnicodeString(&DeviceName,DEVICE_NAME);
RtlInitUnicodeString(&LinkName,LINK_NAME); //创建设备对象; Status = IoCreateDevice(DriverObject,,
&DeviceName,FILE_DEVICE_UNKNOWN,,FALSE,&DeviceObject);
if (!NT_SUCCESS(Status))
{
return Status;
} Status = IoCreateSymbolicLink(&LinkName,&DeviceName); for (i = ; i<IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DriverObject->MajorFunction[i] = DefaultPassThrough;
} DriverObject->DriverUnload = UnloadDriver;
//得Ntos
if (GetSystemMoudleInfoBySystemModuleName(ModuleName,
&__NtosModuleBaseAddress, &__NtosModuleLength) == FALSE)
{
return Status;
} DbgPrint("Win7 Ntos模块地址:%p\r\n", __NtosModuleBaseAddress);
DbgPrint("Win7 Ntos模块大小:%x\r\n", __NtosModuleLength);
//得ssdt
if (GetSSDTAddress(&__SSDTAddress) == FALSE)
{
return Status;
}
DbgPrint("Win7 SSDT地址:%p\r\n", __SSDTAddress);
//得需要hook的函数的索引
if (GetSSDTFunctionIndexFromNtdllExportTableByFunctionName(FunctionName,
&__NtOpenProcessIndex) == FALSE)
{
return STATUS_UNSUCCESSFUL;
}
DbgPrint("Win7 __NtOpenProcessIndex地址:%d\r\n", __NtOpenProcessIndex); //
if (ResumeSSDT(__SSDTAddress, __NtosModuleBaseAddress, __NtOpenProcessIndex, ) == TRUE)
{
DbgPrint("恢复成功\r\n");
} #ifdef WIN64
DbgPrint("WIN64: ResumeSSDTHook IS RUNNING!!!");
#else
DbgPrint("WIN32: ResumeSSDTHook SIS RUNNING!!!"); #endif return STATUS_SUCCESS;
} NTSTATUS
DefaultPassThrough(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = ;
IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS;
} VOID
UnloadDriver(PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING LinkName;
PDEVICE_OBJECT NextDeviceObject = NULL;
PDEVICE_OBJECT CurrentDeviceObject = NULL;
RtlInitUnicodeString(&LinkName,LINK_NAME); IoDeleteSymbolicLink(&LinkName);
CurrentDeviceObject = DriverObject->DeviceObject;
while (CurrentDeviceObject != NULL)
{ NextDeviceObject = CurrentDeviceObject->NextDevice;
IoDeleteDevice(CurrentDeviceObject);
CurrentDeviceObject = NextDeviceObject;
} DbgPrint("ResumeSSDTHook IS STOPPED!!!");
} BOOLEAN GetSystemMoudleInfoBySystemModuleName(char* ModuleName,
PVOID* ModuleBase, ULONG64* ModuleLength)
{
int i = ;
NTSTATUS Status = STATUS_SUCCESS;
PVOID BufferData = NULL;
ULONG ReturnLength = ;
//功能号为11,先获取所需的缓冲区大小 得大小
Status = ZwQuerySystemInformation(SystemModuleInformation, NULL, , &ReturnLength); //SSDT
if (Status != STATUS_INFO_LENGTH_MISMATCH) //没有内存
{
return FALSE;
} //申请内存
BufferData = ExAllocatePool(PagedPool, ReturnLength); //PagedPool(数据段 置换到磁盘) NonPagedPool(代码段 不置换到磁盘) if (BufferData == NULL)
{
return FALSE;
} //再次调用 得数据
Status = ZwQuerySystemInformation(SystemModuleInformation, BufferData, ReturnLength, &ReturnLength);
if (!NT_SUCCESS(Status))
{
ExFreePool(BufferData);
return FALSE;
} for (i = ; i < ((PSYSTEM_MODULES_INFO)BufferData)->NumberOfModules; i++)
{
if (strstr(((PSYSTEM_MODULES_INFO)BufferData)->Modules[i].ModuleFullPathData,
ModuleName) != NULL) //Ntoskernel.exe
{
*ModuleBase = ((PSYSTEM_MODULES_INFO)BufferData)->Modules[i].ModuleBase;
*ModuleLength = ((PSYSTEM_MODULES_INFO)BufferData)->Modules[i].ModuleLength; if (BufferData != NULL)
{
ExFreePool(BufferData);
BufferData = NULL;
}
return TRUE;
}
}
if (BufferData != NULL)
{
ExFreePool(BufferData);
BufferData = NULL;
} return FALSE;
} BOOLEAN ResumeSSDT(ULONG64 SSDTAddress, ULONG64 ModuleBase, ULONG32 SSDTFunctionIndex, ULONG32 ParameterCount)
{
int i = ;
PUINT8 RVAOfSSDT = ;
WCHAR FileFullPathData[] = L"\\SystemRoot\\System32\\ntoskrnl.exe";
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NtHeader = NULL;
PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;
PVOID PresupporseImageBase = ; //优先加载地址
PVOID BaseOfSSDTInFile = ; //文件基地址
PVOID v1 = ; //函数真正地址
ULONG32 v2 = ; //SSDT[Offset]
char v3;
CHAR Bits[] = { }; PVOID BufferData = NULL;
//获得SSDT与ntos模块基地址RVA
RVAOfSSDT = ((PUINT8)(((PSERVER_SERVICE_DESCRIPTOR_TABLE)SSDTAddress)->Unknow0)) - (PUINT8)ModuleBase; //读Ntos模块到System进程空间中 (FileAlign)
if (ReadingFileInRing0Space(FileFullPathData, &BufferData) == FALSE)
{
return FALSE;
} DosHeader = (PIMAGE_DOS_HEADER)BufferData;
NtHeader = (PIMAGE_NT_HEADERS)(DosHeader->e_lfanew + (PUINT8)BufferData);
OptionalHeader = &(NtHeader->OptionalHeader);
PresupporseImageBase = OptionalHeader->ImageBase; //优先加载的地址 SectionHeader = (PIMAGE_SECTION_HEADER)((PUINT8)NtHeader + sizeof(IMAGE_NT_HEADERS)); for (i = ; i<NtHeader->FileHeader.NumberOfSections; i++)
{
if (RVAOfSSDT >= SectionHeader[i].VirtualAddress && RVAOfSSDT <
(SectionHeader[i].VirtualAddress + SectionHeader[i].SizeOfRawData))
{
//SSDT的文件偏移
BaseOfSSDTInFile = (PVOID)((PUINT8)BufferData + (RVAOfSSDT - (PUINT8)(SectionHeader[i].VirtualAddress)) + SectionHeader[i].PointerToRawData);
break;
}
} //NtOpenProcess 文件中ssdt索引对应的函数地址 - 模块加载基地址 + ntos模块的基地址
v1 = (PUINT8)(((PULONG64)BaseOfSSDTInFile)[SSDTFunctionIndex]) - (PUINT8)PresupporseImageBase + (PUINT8)ModuleBase;
//偏移
v1 = (PUINT8)v1 - (PUINT8)(((PSERVER_SERVICE_DESCRIPTOR_TABLE)SSDTAddress)->Unknow0);
v1 = (ULONG32)v1 << ; //处理参数个数
if (ParameterCount>)
{
ParameterCount = ParameterCount - ;
}
else
{
ParameterCount = ;
} //处理低四位,填写参数个数 如果一个函数的参数为5 那么dwTemp的低4位就是 0001 如果参数是6 就是0002 因为 6要减4
#define SETBIT(x,y) x|=(1<<y) //将X的第Y位置1
#define CLRBIT(x,y) x&=~(1<<y) //将X的第Y位清0
#define GETBIT(x,y) (x & (1 << y)) //取X的第Y位,返回0或非0 memcpy(&v3, &v1, );
for (i = ; i<; i++) //一个16进制 4个二进制 0000
{
Bits[i] = GETBIT(ParameterCount, i);
if (Bits[i])
{
SETBIT(v3, i);
}
else
{
CLRBIT(v3, i);
}
}
memcpy(&v1, &v3, ); //获得真实的数据了 //获得当前的数据
v2 = ((PULONG32)(((PSERVER_SERVICE_DESCRIPTOR_TABLE)SSDTAddress)->Unknow0))[SSDTFunctionIndex];
if (v1 != v2)
{
WPOFF();
((PULONG32)(((PSERVER_SERVICE_DESCRIPTOR_TABLE)SSDTAddress)->Unknow0))[SSDTFunctionIndex] = (ULONG32)v1;
WPON();
} if (BufferData != NULL)
{
ExFreePool(BufferData);
BufferData = NULL;
}
return TRUE;
} VOID WPOFF()
{
_disable();
__writecr0(__readcr0() & (~(0x10000))); }
VOID WPON()
{
__writecr0(__readcr0() ^ 0x10000);
_enable();
} BOOLEAN GetSSDTAddress(ULONG64* SSDTAddress)
{
//kd> rdmsr c0000082
PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082);
PUCHAR EndSearchAddress = StartSearchAddress + PAGE_SIZE;
PUCHAR i = NULL;
UCHAR v1 = , v2 = , v3 = ;
INT64 Offset = ; //002320c7
ULONG64 VariableAddress = ;
*SSDTAddress = NULL;
for (i = StartSearchAddress; i < EndSearchAddress; i++)
{
if (MmIsAddressValid(i) && MmIsAddressValid(i + ) && MmIsAddressValid(i + ))
{
v1 = *i;
v2 = *(i + );
v3 = *(i + );
if (v1 == 0x4c && v2 == 0x8d && v3 == 0x15)
{
memcpy(&Offset, i + , );
*SSDTAddress = Offset + (ULONG64)i + ;
break;
}
}
}
//如果是Win32 导出表 搜索 KeServiceDescriptorTable
if (*SSDTAddress == NULL)
{
return FALSE;
} return TRUE;
}
BOOLEAN GetSSDTFunctionIndexFromNtdllExportTableByFunctionName(CHAR* FunctionName, ULONG32* SSDTFunctionIndex)
{
ULONG i;
BOOLEAN IsOk = FALSE;
WCHAR FileFullPath[] = L"\\SystemRoot\\System32\\ntdll.dll"; //C:\Windows\
SIZE_T MappingViewSize = ;
PVOID MappingBaseAddress = NULL;
PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
PIMAGE_NT_HEADERS NtHeader = NULL;
UINT32* AddressOfFunctions = NULL;
UINT32* AddressOfNames = NULL;
UINT16* AddressOfNameOrdinals = NULL;
CHAR* v1 = NULL;
ULONG32 FunctionOrdinal = ;
PVOID FunctionAddress = ;
ULONG32 Offset_SSDTFunctionIndex = ;
//将Ntdll.dll映射 当前的空间中 *SSDTFunctionIndex = -;
IsOk = MappingPEFileInRing0Space(FileFullPath, &MappingBaseAddress, &MappingViewSize);
if (IsOk == FALSE)
{
return FALSE;
}
else
{
__try {
NtHeader = RtlImageNtHeader(MappingBaseAddress); //extern
if (NtHeader && NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
ImageExportDirectory = (IMAGE_EXPORT_DIRECTORY*)((UINT8*)MappingBaseAddress +
NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); AddressOfFunctions = (UINT32*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfFunctions);
AddressOfNames = (UINT32*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfNames);
AddressOfNameOrdinals = (UINT16*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfNameOrdinals);
for (i = ; i < ImageExportDirectory->NumberOfNames; i++)
{
v1 = (char*)((ULONG64)MappingBaseAddress + AddressOfNames[i]); //获得函数名称
if (_stricmp(FunctionName, v1) == )
{
FunctionOrdinal = AddressOfNameOrdinals[i];
FunctionAddress = (PVOID)((UINT8*)MappingBaseAddress + AddressOfFunctions[FunctionOrdinal]); *SSDTFunctionIndex = *(ULONG32*)((UINT8*)FunctionAddress + Offset_SSDTFunctionIndex);
break;
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
;
}
} ZwUnmapViewOfSection(NtCurrentProcess(), MappingBaseAddress); //解除映射 if (*SSDTFunctionIndex == -)
{
return FALSE;
} return TRUE;
}
BOOLEAN MappingPEFileInRing0Space(WCHAR* FileFullPath, PVOID* MappingBaseAddress, PSIZE_T MappingViewSize)
{ NTSTATUS Status;
UNICODE_STRING v1;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE FileHandle = NULL;
HANDLE SectionHandle = NULL; if (!FileFullPath &&MmIsAddressValid(FileFullPath))
{
return FALSE;
} if (!MappingBaseAddress&&MmIsAddressValid(MappingBaseAddress))
{
return FALSE;
} RtlInitUnicodeString(&v1, FileFullPath);
InitializeObjectAttributes(&ObjectAttributes,
&v1,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL
); //获得文件句柄
Status = IoCreateFile(&FileHandle,
GENERIC_READ | SYNCHRONIZE,
&ObjectAttributes, //文件绝对路径
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
,
CreateFileTypeNone,
NULL,
IO_NO_PARAMETER_CHECKING
);
if (!NT_SUCCESS(Status))
{
return FALSE;
} ObjectAttributes.ObjectName = NULL;
Status = ZwCreateSection(&SectionHandle,
SECTION_QUERY | SECTION_MAP_READ,
&ObjectAttributes,
NULL,
PAGE_WRITECOPY,
SEC_IMAGE, //内存对齐 0x1000
FileHandle
);
ZwClose(FileHandle);
if (!NT_SUCCESS(Status))
{
return FALSE;
}
Status = ZwMapViewOfSection(SectionHandle,
NtCurrentProcess(), //映射到当前进程的内存空间中
MappingBaseAddress,
,
,
,
MappingViewSize,
ViewUnmap,
,
PAGE_WRITECOPY
);
ZwClose(SectionHandle);
if (!NT_SUCCESS(Status))
{
return FALSE;
}
return TRUE;
}
BOOLEAN ReadingFileInRing0Space(WCHAR* FileFullPathData, PVOID* BufferData)
{ NTSTATUS Status;
LARGE_INTEGER ReturnLength = { , };
UNICODE_STRING v1;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE FileHandle;
IO_STATUS_BLOCK IoStatusBlock;
FILE_STANDARD_INFORMATION v2 = { };
if (FileFullPathData == NULL&&*BufferData != NULL)
{
return FALSE;
} RtlInitUnicodeString(&v1, FileFullPathData);
InitializeObjectAttributes(&ObjectAttributes, &v1, OBJ_CASE_INSENSITIVE, NULL, NULL); //获取文件句柄
Status = ZwCreateFile(&FileHandle,
SYNCHRONIZE,
&ObjectAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
);
if (!NT_SUCCESS(Status))
{
return FALSE;
} //计算文件长度 Status = ZwQueryInformationFile(FileHandle,
&IoStatusBlock,
&v2,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
if (!NT_SUCCESS(Status))
{
ZwClose(FileHandle);
return FALSE;
} //动态申请内存
*BufferData = ExAllocatePool(PagedPool, v2.EndOfFile.LowPart);
if (*BufferData == NULL)
{
ZwClose(FileHandle);
return FALSE;
} //读取文件到内存 Status = ZwReadFile(FileHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
*BufferData,
v2.EndOfFile.LowPart,
&ReturnLength,
NULL); if (!NT_SUCCESS(Status))
{
ExFreePool(*BufferData);
ZwClose(FileHandle);
return FALSE;
}
ZwClose(FileHandle); return TRUE;
}
#include <ntifs.h>
#include <ntimage.h>
#ifndef CXX_ResumeSSDTHook_H
#define CXX_ResumeSSDTHook_H #define DEVICE_NAME L"\\Device\\ResumeSSDTHookDevice"
#define LINK_NAME L"\\??\\ResumeSSDTHookLink"
#define SystemModuleInformation 0xB typedef struct SYSTEM_MODULE_INFO
{
UINT32 Unknow0[];
UINT64 ModuleBase;
UINT32 ModuleLength;
UINT32 Flags;
UINT64 Unknow1;
char ModuleFullPathData[];
} SYSTEM_MODULE_INFO, *PSYSTEM_MODULE_INFO; typedef struct _SYSTEM_MODULES_INFO
{
UINT32 NumberOfModules;
SYSTEM_MODULE_INFO Modules[];
}SYSTEM_MODULES_INFO, *PSYSTEM_MODULES_INFO; typedef struct _SERVER_SERVICE_DESCRIPTOR_TABLE_
{
PVOID Unknow0;
PVOID Unknow1;
PVOID Unknow2;
PVOID Unknow3;
}SERVER_SERVICE_DESCRIPTOR_TABLE, *PSERVER_SERVICE_DESCRIPTOR_TABLE; extern
PIMAGE_NT_HEADERS
NTAPI
RtlImageNtHeader(PVOID BaseAddress); extern
NTSTATUS
ZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength); VOID
UnloadDriver(PDRIVER_OBJECT DriverObject); BOOLEAN GetSystemMoudleInfoBySystemModuleName(char * ModuleName, PVOID * ModuleBase, ULONG64 * ModuleLength); BOOLEAN ResumeSSDT(ULONG64 SSDTAddress, ULONG64 ModuleBase, ULONG32 SSDTFunctionIndex, ULONG32 ParameterCount); VOID WPOFF(); VOID WPON(); BOOLEAN GetSSDTAddress(ULONG64 * SSDTAddress); BOOLEAN GetSSDTFunctionIndexFromNtdllExportTableByFunctionName(CHAR * FunctionName, ULONG32 * SSDTFunctionIndex); BOOLEAN MappingPEFileInRing0Space(WCHAR * FileFullPath, PVOID * MappingBaseAddress, PSIZE_T MappingViewSize); BOOLEAN ReadingFileInRing0Space(WCHAR * FileFullPathData, PVOID * BufferData); NTSTATUS
DefaultPassThrough(PDEVICE_OBJECT DeviceObject,PIRP Irp); #endif
ring0 恢复SSDTHook的更多相关文章
- ring0 关于SSDTHook使用的绕过页面写保护的原理与实现
原博:http://www.cnblogs.com/hongfei/archive/2013/06/18/3142162.html 为了安全起见,Windows XP及其以后的系统将一些重要的内存页设 ...
- ring0 SSDTHook
SSDT 的全称是 System Services Descriptor Table,系统服务描述符表.这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来. ...
- ring0 SSDTHook 实现x64/x86
#include "HookSSDT.h" #include <ntimage.h> #define SEC_IMAGE 0x001000000 ULONG32 __N ...
- SSDTHook实例--编写稳定的Hook过滤函数
解说怎样写Hook过滤函数,比方NewZwOpenProcess.打开进程. 非常多游戏保护都会对这个函数进行Hook. 因为我们没有游戏保护的代码,无法得知游戏公司是怎样编写这个过滤函数. 我看到非 ...
- ring0 与 ring3 层之间的交互
在进行Windows的ring0层开发时,必不可免的要与 ring3 层进行交互.进行数据间的相互传输.可用的方法有DeviceIoCntrol,ReadFile.我平常都是用的DeviceIoCon ...
- RING0到RING3
在前一篇文章里面,我们将了CPU保护模式中的几种特权RING0,RING1,RING2,RING3!操作系统通常运行在RING0,应用程序通常运行在RING3. CPU如何从RING0到RING3 先 ...
- 64位内核开发第十二讲,进程监视,ring3跟ring0事件同步.
一丶同步与互斥详解,以及实现一个进程监视软件. 1.用于线程同步的 KEVENT 事件很简单分别分为 事件状态. 以及事件类别. 事件状态: 有信号 Signaled 无信号 Non-signaled ...
- X86驱动:恢复SSDT内核钩子
SSDT 中文名称为系统服务描述符表,该表的作用是将Ring3应用层与Ring0内核层,两者的API函数连接起来,起到承上启下的作用,SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用 ...
- Hook集合----SSDTHook(x86 Win7)
最近在学习Ring0层Hook的一些知识点,很久就写完SSDTHook的代码了,但是一直没有整理成笔记,最近有时间也就整理整理. 介绍: SSDTHook 实质是利用Ntoskrnl.exe 中全局导 ...
随机推荐
- 牛客小白月赛5 I - 区间
看到一份不错的操作..... 链接:https://www.nowcoder.com/acm/contest/135/I 来源:牛客网 Apojacsleam喜欢数组. 他现在有一个n个元素的数组a, ...
- Linq 与 Lambda 简单使用
//Lambda表达式详解 //int //List<int> numbers = new List<int> {1,2,3,4,5,6,7,8,9 }; //var n = ...
- python查看模块版本及所在文件夹
# 以Numpy为例 第一种方法:import numpy as np np.__version__ >>> '1.12.1' np.__file__ >>> '/ ...
- filter get乱码 全站编码解决 包装模式
包装模式简介: package com.itheima.test; import java.io.BufferedReader; import java.io.IOException; import ...
- 使用jxl读取excel内容,并转换成Json,用于Datagrid
一.上传excel文件,得到InputStream,由InputStream得到Jxl中的Workbook,取出内容,存到二维数组中. 1.使用 Jquery Uploadify 插件(http:// ...
- 用 Python 构建 web 应用
用 Python 构建 web 应用 如果说仅仅要用 Python 构建 web 应用,可以将 socket 连接.HTTP 原始请求和响应格式等涉及网络基础的东西交给现成的库来实现,只需要专注于 w ...
- FZU 1922——非主流——————【技巧题】
非主流 Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Status P ...
- SpringSecurity 3.2入门(5)自定义登录页面
增加spring-security.xml文件配置如下 <!-- 配置SpringSecurity的http安全服务 --> <security:http auto-config=& ...
- HashMap和Hashtable存放null
Hashmap是可以放key为null的,Hashtable不能放key为null.hashtable放key为null会报空指针异常 1. hashmap put方法源码 public V put( ...
- Android-自定义View实现ImageView播放gif
http://blog.csdn.net/guolin_blog/article/details/11100315 总体思路是这样的 PowerImageView类继承ImageView类 给Powe ...