22.4 使用远程线程来注入DLL

22.4.1 概述

(1)远程线程注入是指一个进程在另一个进程中创建线程,然后载入我们编写的DLL,并执行该DLL代码的技术。其基本思路是通过CreateRemoteThread创建一个远程线程,并将LoadLibrary函数作为该线程函数来启动线程,同时将Dll文件名作为线程函数的参数传入。大致执过程如下:CreateRemoteThread()→LoadLibrary()→DllMain()。

(2)核心函数:CreateRemoteThread

参数

说明

HANDLE hProcess

要创建远程线程的进程句柄。除了这参数外,CreateRemoteThread与CreateThread函数参数含义完全相同!

PSECURITY_ATTRIBUTES psa

用于定义新线程的安全属性,这里设为NULL采用默认值即可

DWORD dwStackSize

初始化线程堆栈大小,NULL为默认大小

PTHREAD_START_ROUTINE pfnStartAddr

线程函数的地址,这里传入LoadLibrary函数的地址,但它须在远程进程的地址空间中,因为它是让远程线程调用的。如何获得远程进程中这函数地址,可参考后面内容。

PVOID pvParam

线程函数参数,这里一般是DLL文件名,但这个名称须保存在远程进程的地址空间中,这也是个比较棘手的问题。

DWORD fdwCreate

函数表示创建线程后线程的运行状态

PDWORD pdwThreadId

返回线程ID,不关心可以设为NULL不返回

备注:使用这个函数关键要解决三个参数问题:①获得远程线程的进程句柄,而且要确保相应权限(如Debug权限);②获取远程进程中线程函数的开始地址,而非本地地址;③向远程线程成功传入DLL路径字符串。

(3)其他函数

  ①在远程进程中分配/释放内存:VirtualAllocEx/VirtualFreeEx

  ②对远程进程地址空间进行读写:ReadProcessMemory/WriteProcessMemory

22.4.2 获取LoadLibrary函数的远程地址

(1)LoadLibrary要作为远程线程函数来使用,必须满足两个条件:

  ①该函数符合线程函数的原型。(查看MSDN,他们有相同的调用约定,都有一个参数和一个返回值。至于类型不同,可以通过强转类型得到,所以该条件满足)

  ②该函数存在于远程线程地址空间内。这一点也可以保证的,因为LoadLibrary函数位于Kernel32.dll中,对于Windows系统而言,本地进程和远程进程中的Kernel32.dll被映射到地址空间的同一内存地址,因而只要通过GetProcAddress获取本地进程中LoadLibrary的地址,在远程进程中也同样是这个地址,可以直接传给CreateRemoteThread。

(2)LoadLibrary是被定义为一个宏,而不是函数。有两个版本LoadLibraryA和LoadLibraryW。

(3)为什么CreateRemoteThread的pfnStartAddr参数不能直接写成LoadLibrary W(或A),而要使用GetProcAddress获得的LoadLibary函数的地址

  ①LoadLibrary W(或A)是Kernel32.dll中的一个导出函数,但我们的Dll中直接引用该函数时,会在Dll的导入表记录下来。我们都知道导入函数的真实地址是在DLL加载的时候才能确定的,加载程序会从导入表中取得导入函数名,在被加载到进程地址空间后,会计算出该函数地真实地址,然后填入导入表(IAT)相应的位置。这种函数在编译期无法知道切确的地址,所以被编译成CALL DWORD PTR[XXXXXXXX]之类的代码,中括号中的数值虽然是一个确定的数值,但并不是导入函数的真实地址(形如CALL XXXXXXXX),而是一个子程序的地址,该程序被称为转换函数(Thunk)。【顺便说一下,这也是为什么在声明一个导入函数时要加上__declspec(dllimport)前缀的原因,因为编译器无法区分应用程序是对一般函数调用还是对导入函数调用。当加上这个前缀时,编译器会认为此函数来自导入函数,就会产生CALL DWORD PTR[XXXXXXXX]的指令,而不是CALL XXXXXXXX。】

  ②当程序调用导入函数时,编译器会处理成先调用转换函数,然后转换函数从IAT表中获得导入函数的真实地址,再调用相应的地址。所以如果将CreateRemoteThread的pfnStartAddr参数写成LoadLibraryW(或A),这里地址将被编译成转换函数的地址,而不是LoadLibrary的真实地址。

22.4.3 将DLL的路径字符串存放到远程的地址空间中

(1)如果直接向CreateRemoteThread()传入DLL路径,如”C:\\MyDLL.dll”那么实际向远程线程传递的是一个本地的指针值,这个值在远程进程的地址空间中是没有意义的

(2)可以使用VirtualAllocEx()函数在远程进程中先分配一段空间,然后再使用WriteProcessMemory将DLL路径字符串复制到远程进程的地址空间中去,最后将该远程内存的指针传给CreateRemoteThead相应的参数。

24.4.4 总结使用远程线程注入DLL的步骤

  ①用VirtualAllocEx函数在远程进程的地址空间中分配一块内存。

  ②用WriteProcessMemory函数反映Dll的路径名复制到第1步分配的内存中

  ③用GetProcAddress函数来得到LoadLibrary W(或A)函数在Kernel32.dll的真实地址。

  ④用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并在参数中传入第1步分配的内存地址。这时,DLL己经被注入到远程进程的地址空间中,DLL的DllMain函数会收到DLL_PROCESS_ATTACH通知并县城可以执行我们想要执行的代码。当DllMain返回时,远程线程会从线程函数(LoadLibraryW/A)调用返回到线程启动函数RtlUserThreadStart(该函数的实现可参考第6章),最后调用ExitThread使远程线程终止。

  ⑤此时远程进程中那块在第1步分配的内存还在,DLL也还在远程进程的地址空间中。这里只需调用VirtualFreeEx就可以释放远程进程的内存。

  ⑥但DLL的释放,要先通过GetProcAddress获得FreeLibrary的地址,然后再通过CreateRemoteThread在远程进程中创建一个线程,让该线程调用FreeLibrary,pvParam参数传入远程DLL中句柄。

【InjectLibrary示例程序】

        

注入进程本身                                                                                    注入“记事本”程序

//ImgWalk动态库,这个DLL用来检测被注入的进程中当前载入的各个模块名称

/************************************************************************
Module: ImgWalk.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/ #include "../../CommonFiles/CmnHdr.h"
#include <tchar.h> //////////////////////////////////////////////////////////////////////////
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID fImpload){
if (fdwReason == DLL_PROCESS_ATTACH){
char szBuf[MAX_PATH * ] = { }; PBYTE pb = NULL;
MEMORY_BASIC_INFORMATION mbi;
while (VirtualQuery(pb, &mbi, sizeof(mbi)) == sizeof(mbi)){
int nLen;
char szModName[MAX_PATH]; if (mbi.State == MEM_FREE)
mbi.AllocationBase = mbi.BaseAddress; if ((mbi.AllocationBase == hInstDll) || //该区域包含该DLL,则隐藏掉该DLL,不显示在后面对话框中
(mbi.AllocationBase !=mbi.BaseAddress || //块不是区域的开始地址
(mbi.AllocationBase == NULL))){ //区域地址为NULL
nLen = ;
} else{
nLen = GetModuleFileNameA((HINSTANCE)mbi.AllocationBase,
szModName, _countof(szModName));
} if (nLen > ){
wsprintfA(strchr(szBuf, ),//找到第一个0
"\n%p-%s",
mbi.AllocationBase, szModName);
} pb += mbi.RegionSize;
} //注意:正常情况下,不应该在DllMain里显示一个对话框,因为
//装载器被锁。但为了程序简单化,这里违返了这个规则
chMB(&szBuf[]); //从1开始,因为 "\n%p-%s",表示szBuf[0]为换行符
}
return (TRUE);
}

//InjLib,创建远程线程用于将上述的DLL注入到指定的进程中去

 /************************************************************************
Module: InjLib.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/ #include "../../CommonFiles/CmnHdr.h" #include <tchar.h>
#include <malloc.h> // For alloca
#include <strsafe.h>
#include <TlHelp32.h>
#include "resource.h" //////////////////////////////////////////////////////////////////////////
#ifdef UNICODE
#define InjectLib InjectLibW
#define EjectLib EjectLibW
#else
#define InjectLib InjectLibA
#define EjectLib EjectLibA
#endif //////////////////////////////////////////////////////////////////////////
//创建远程线程并注入DLL
//参数:dwProcessID——进程ID
// pszLibFile ——要注入的DLL路径(含名称)
BOOL WINAPI InjectLibW(DWORD dwProcessId, PCWSTR pszLibFile){
BOOL bOk = FALSE; //假设注入失败
HANDLE hProcess = NULL, hThread = NULL;
PWSTR pszLibFileRemote = NULL; __try{
//获取目标进程句柄
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD | //For CreateRemoteThread
PROCESS_VM_OPERATION | //For VirtualAllocEx/VirtualFreeEx
PROCESS_VM_WRITE, //For WriteProcessMemory
FALSE,dwProcessId); if (hProcess == NULL) __leave; //计算存储Dll路径名所需的字节数
int cch = + lstrlen(pszLibFile);//字符个数,因strlen不含\0,所以加1为\0预留
int cb = cch*sizeof(wchar_t); //为远程进程分配一个内存以存储DLL路径名称
pszLibFileRemote = (PWSTR)
VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
if (pszLibFileRemote == NULL) __leave; //复制DLL路径名称到远程进程的内存中
if (!WriteProcessMemory(hProcess, pszLibFileRemote,
(PVOID)pszLibFile, cb, NULL)) __leave; //获取LoadLibraryW在Kernel32.dll中的地址
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); if (pfnThreadRtn == NULL) __leave; //创建远程线程调用LoadLibraryW(DllPathName);
hThread = CreateRemoteThread(hProcess,
NULL, ,
pfnThreadRtn, //LoadLibraryW(远程进程地址空间中)
pszLibFileRemote, //Dll路径名(远程进程地址空间中)
, NULL);
if (hThread == NULL) __leave; //等待远程线程结束
WaitForSingleObject(hThread, INFINITE); bOk = TRUE; //注入成功
}
__finally{
//释放用于保存Dll路径名称的内存
if (pszLibFileRemote != NULL)
VirtualFreeEx(hProcess, pszLibFileRemote, , MEM_RELEASE); if (hThread != NULL)
CloseHandle(hThread); if (hProcess != NULL)
CloseHandle(hProcess);
}
return (bOk);
} //////////////////////////////////////////////////////////////////////////
BOOL WINAPI InjectLibA(DWORD dwProcessId, PCSTR pszLibFile){
//分配一个栈内存(无需手用释放),用于存储Unicode版本的路径名
SIZE_T cchSize = lstrlenA(pszLibFile) + ;//字符个数,含\0
PWSTR pszLibFileW = (PWSTR)
_alloca(cchSize*sizeof(wchar_t)); //将ANSI路径名转化为等价的Unicode版本
StringCchPrintfW(pszLibFileW, cchSize, L"%S", pszLibFile); return (InjectLibW(dwProcessId, pszLibFileW));
} //////////////////////////////////////////////////////////////////////////
//将DLL从进程地址空间中撤销
//先根据DLL文件名,在进程加载的模块中查找是否该DLL己被加载
//如果被加载,记下这个DLL的句柄。然后创建远程线程去调用FreeLibrary卸载
BOOL WINAPI EjectLibW(DWORD dwProcessId, PCWSTR pszLibFile){
BOOL bOk = FALSE; //假定撤销失败
HANDLE hthSnapshot = NULL;
HANDLE hProcess = NULL, hThread = NULL;
__try{
//抓取进程快照
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,//指定进程中加载的所有模块
dwProcessId); //获得Unicode版本的目标DLL库句柄
MODULEENTRY32W me = { sizeof(me) };
BOOL bFound = FALSE;
BOOL bMoreMods = Module32FirstW(hthSnapshot, &me); //Unicode版本
for (; bMoreMods;bMoreMods = Module32NextW(hthSnapshot,&me)){
bFound = (_wcsicmp(me.szModule, pszLibFile) == ) ||
(_wcsicmp(me.szExePath, pszLibFile) == );
if (bFound)
break;
}
if (!bFound) __leave; //获得目标进程的句柄
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD | //For CreateRemoteThread
PROCESS_VM_OPERATION, //For VirtualAllocEx/VirtualFreeEx
FALSE, dwProcessId);
if (hProcess == NULL) __leave; //获取FreeLibrary在Kernel32.dll中的地址
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if (pfnThreadRtn == NULL) __leave; //创建远程线程
hThread = CreateRemoteThread(hProcess,
NULL, ,
pfnThreadRtn, //FreeLibrary(远程进程地址空间中)
me.modBaseAddr,
, NULL);
if (hThread == NULL) __leave; //等待远程线程结束
WaitForSingleObject(hThread, INFINITE); bOk = TRUE; //撤销成功 }
__finally{ if (hthSnapshot != NULL)
CloseHandle(hthSnapshot); if (hThread != NULL)
CloseHandle(hThread); if (hProcess != NULL)
CloseHandle(hProcess);
} return (bOk);
} //////////////////////////////////////////////////////////////////////////
BOOL WINAPI EjectLibA(DWORD dwProcessId, PCSTR pszLibFile){
//分配一个栈内存(无需手用释放),用于存储Unicode版本的路径名
SIZE_T cchSize = lstrlenA(pszLibFile) + ;//字符个数,含\0
PWSTR pszLibFileW = (PWSTR)
_alloca(cchSize*sizeof(wchar_t)); //将ANSI路径名转化为等价的Unicode版本
StringCchPrintfW(pszLibFileW, cchSize, L"%S", pszLibFile); return (EjectLibW(dwProcessId, pszLibFileW));
} //////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam){
chSETDLGICONS(hwnd, IDI_INJLIB);
return (TRUE);
}
//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity){
switch (id)
{
case IDCANCEL:
EndDialog(hwnd, id);
break; case IDC_INJECT:
DWORD dwProcessId = GetDlgItemInt(hwnd, IDC_PROCESSID, NULL, FALSE);
if (dwProcessId == ){
//如果dwProcessId为0,表示注入到本地进程
dwProcessId = GetCurrentProcessId();
} TCHAR szLibFile[MAX_PATH];
GetModuleFileName(NULL, szLibFile, _countof(szLibFile)); //获得当前进程的完整路径
PTSTR pFilename = _tcsrchr(szLibFile, TEXT('\\')) + ;
_tcscpy_s(pFilename, _countof(szLibFile) - (pFilename - szLibFile),
TEXT("22_ImgWalk.DLL")); if (InjectLib(dwProcessId,szLibFile)){
chVERIFY(EjectLib(dwProcessId, szLibFile));
chMB("DLL注入/撤消成功!");
} else{
chMB("DLL注入/撤消失败!");
} break;
}
} //////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
switch (uMsg)
{
chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
}
return (FALSE);
} //////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int){
DialogBox(hInstExe, MAKEINTRESOURCE(IDD_INJLIB), NULL, Dlg_Proc);
return ();
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 22_InjLib.rc 使用
//
#define IDD_INJLIB 1
#define IDC_INJECT 100
#define IDC_PROCESSID 101
#define IDI_INJLIB 101 // Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

//.rc文件

// Microsoft Visual C++ generated resource script.
//
#include "resource.h" #define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h" /////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS /////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED /////////////////////////////////////////////////////////////////////////////
//
// Icon
// // Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_INJLIB ICON "InjLib.ico" #ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
// TEXTINCLUDE
BEGIN
"resource.h\0"
END TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END #endif // APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////////
//
// Dialog
// IDD_INJLIB DIALOGEX , , ,
STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "远程线程注入DLL"
FONT , "MS Shell Dlg", , , 0x0
BEGIN
LTEXT "进程ID(10进制):",-,,,,
EDITTEXT IDC_PROCESSID,,,,,ES_AUTOHSCROLL
DEFPUSHBUTTON "注入",IDC_INJECT,,,,,WS_GROUP
END /////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
// #ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_INJLIB, DIALOG
BEGIN
END
END
#endif // APSTUDIO_INVOKED #endif // 中文(简体,中国) resources
///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
// /////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

22.5 使用木马DLL来注入DLL

(1)如果某个进程必须载入一个DLL(如xyz.dll)。则可以创建自己的DLL并给他起同样的名称,然后进行替换。并将旧的xyz.dll改为别的名称(如abc.dll)。

(2)在我们的xyz.dll的内部,导出原来xyz.dll导出的所有符号。(用函数转发器导为abc.dll相应的符号)。

(3)在我们的xyz.dll内部,可以增加一些我们自己的代码,以便以我们操作被注入的进程。

第22章 DLL注入和API拦截(2)的更多相关文章

  1. 第22章 DLL注入和API拦截(3)

    22.6 API拦截的一个例子 22.6.1 通过覆盖代码来拦截API (1)实现过程 ①在内存中对要拦截的函数(假设是Kernel32.dll中的ExitProcess)进行定位,从而得到它的内存地 ...

  2. 第22章 DLL注入和API拦截(1)

    22.1 注入的一个例子(跨进程子类化窗口) ①子类化窗口可以改变窗口的行为,让发往该窗口的消息重新发到我们指定的过程来处理.但这种行为只能在本进程中(如A),对于从一个进程(如B)去子类化另一个进程 ...

  3. DLL注入_拦截技术之Hook方式

    后卫大师教你进程注入 首先提一下,由于文章完全是我手写,所以打不了太多,请包含,由于我已经提供了源代码,所以我在这里详细讲一下理论,至于想看代码的下载代码就可以了.代码中关于注入的部分做了详细的注释. ...

  4. Win32环境下代码注入与API钩子的实现(转)

    本文详细的介绍了在Visual Studio(以下简称VS)下实现API钩子的编程方法,阅读本文需要基础:有操作系统的基本知识(进程管理,内存管理),会在VS下编写和调试Win32应用程序和动态链接库 ...

  5. Win32环境下代码注入与API钩子的实现

    本文详细的介绍了在Visual Studio(以下简称VS)下实现API钩子的编程方法,阅读本文需要基础:有操作系统的基本知识(进程管理,内存管理),会在VS下编写和调试Win32应用程序和动态链接库 ...

  6. 20145319 《网络渗透》免考—API拦截技术

    20145319 <网络渗透>免考-API拦截技术 概述 本次实验在window环境下进行,主要通过编写hook代码和注入程序,将我们的hook代码通过dll文件的形式注入到目标中,拦截其 ...

  7. <ReversingEngineering>关于windows32位系统下的dll注入技术经验汇

    上个学期把自己闷在图书馆一直在看关于逆向工程技术方面的书,从入门到初级,现在也敢说自己一条腿已经迈进了这片知识的大门里,因为该博客刚开通先将一些经验记录下来,也是留给自己一方面做个参照. <逆向 ...

  8. DLL注入新姿势:反射式DLL注入研究

    在分析koadic渗透利器时,发现它有一个注入模块,其DLL注入实现方式和一般的注入方式不一样.搜索了一下发现是由HarmanySecurity的Stephen Fewer提出的ReflectiveD ...

  9. Dll注入技术之消息钩子

    转自:黑客反病毒 DLL注入技术之消息钩子注入 消息钩子注入原理是利用Windows 系统中SetWindowsHookEx()这个API,他可以拦截目标进程的消息到指定的DLL中导出的函数,利用这个 ...

随机推荐

  1. 【GOF23设计模式】外观模式

    来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_外观模式.公司注册流程.迪米特法则 package com.test.facade; public interface 工 ...

  2. jQuery淡入淡出效果轮播图

    用JavaScript做了平滑切换的焦点轮播图之后,用jQuery写了个简单的淡入淡出的轮播图,代码没有做优化,html结构稍微有一些调整,图片部分用ul替换了之前用的div. html结构如下: & ...

  3. 上载EXCEL到SAP系统的方法之一

    TEXT_CONVERT_XLS_TO_SAP实例 使用:gui_upload去上传excel数据,每次都出现乱码,不管中文英文都乱码. 至今不知道gui_upload是否支持excel文件上传,. ...

  4. Office版本差别引发的语法问题

    由于没有源代码,今天反编译了一个基于.NET的dll类库,再次遇到office版本差异问题,所以把它记录下来. 在反编译时,需要Aspose.Cells 5.3.1(Aspose是一套.NET类库,其 ...

  5. android 事件

    package com.example.yanlei.my2; import android.app.Activity; import android.content.Context; import ...

  6. Disconnected: No supported authentication methods available (server sent: publickey)

    安装Git客户端后,进行PULL时报如下错误 disconnected no supported authentication methods available(server sent: publi ...

  7. Hibernate学习0.Hibernate入门

    Hibernate是什么 面向java环境的对象/关系数据库映射工具. 1.开源的持久层框架. 2.ORM(Object/Relational Mapping)映射工具,建立面向对象的域模型和关系数据 ...

  8. ios 计算缓存大小

    - (void)getSize2 { // 图片缓存 NSUInteger size = [SDImageCache sharedImageCache].getSize; //  NSLog(@&qu ...

  9. iOS网络-03-NSURLSession与NSURLSessionTask

    简介 NSURLSession也能完成网络请求 NSURLConnection在iOS9中不推荐使用,NSURLSession是iOS9中推荐使用的网络请求方式 NSURLSession需要与NSUR ...

  10. iOS之UI--UITabBarController

    前言:跟UINavigationController类似,UITabBarController也可以轻松地管理多个控制器,轻松完成控制器之间的切换,典型例子就是QQ.微信等应用. UITabBarCo ...