使用WMI监控进程创建和结束

Windows Management Instrumentation (WMI) 是微软实现的一套可以通过网页管理计算机的系统,我们可以通过WMI查询计算机的方方面面。从Vista开始,这个机制增加了Instance Event的提醒机制,这个机制可以帮助我们监控各种Instance的创建、删除和修改。所以,我们可以想到的是进程也是在WMI里的Win32_Process有Instance的记录,这样我们就可以跟踪到进程的创建和结束了。当然,我们还可能监控到文件等等WMI里的各种Instance。下面是一个监控进程的例子:

20150712232158

下载:MonitorProcessWithWMI.zip

Tips

用Windbg script将内存中的PE文件dump出来

最近看到有些恶意程序,从网络上下载PE文件后,直接放在内存里重定位和初始化,为了能将其dump出来,所以写了这个Windbg脚本。

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
.foreach( place { !address /f:VAR,MEM_PRIVATE,MEM_COMMIT /c:"s -[1]a %1 %2 \"MZ\"" } )
{
ad *
.catch {
r @$t2 = place;
r @$t0 = place;
r @$t1 = @@C++(((ntdll!_IMAGE_DOS_HEADER *)@$t0)->e_lfanew);
r @$t0 = @$t0 + @$t1;
r @$t1 = $vvalid(@$t0, 4);
.if (@@C++(@$t1 && @@C++(((ntdll!_IMAGE_NT_HEADERS *)@$t0)->Signature) == 0x00004550))
{
r @$t1 = @@C++(((ntdll!_IMAGE_NT_HEADERS *)@$t0)->OptionalHeader.SizeOfImage);
.printf "%08x %08x\n", @$t2, @$t1;
aS /x start_addr @$t2
aS /x dump_size @$t1
.block {
aS target_file e:\\${start_addr}.dll
}
.block {
.printf "${target_file}"
.writemem "${target_file}" ${start_addr} L?${dump_size}
}
}
}
}

Tips

将blog迁移到了jekyll

上周终于下定决心把blog从wordpress转到jekyll,不是因为wordpress臃肿,也不是因为jekyll的更加Geek,纯粹是因为穷。我一直都觉得wordpress是一个非常伟大的blog程序,虽然臃肿了点,但确实功能强大操作简单,对于我这种懒人和对前端代码完全不熟的程序员来说,wordpress确实是一个非常好的选择。但是问题就出在了webhost上,我使用的webhost刚刚买的时候是50多刀一年,之后每年涨价,今年续费看了下需要100刀左右,这个确实让我心中无数的羊驼奔腾了起来。于是就决定把blog搬离这个地方。

刚开始我只是想找便宜的地方转移wordpress的blog。网上也有很多这类的webhost,第一年进去都很便宜,甚至有1刀一个月的。但是一朝被蛇咬啊,为了防止以后又被迫搬家,于是打消了这个念头。想要便宜和稳定的blog空间,看来是只有伟大的Github。而Github只支持静态程序,那么我也只能放弃wordpress的方便,自己折腾点静态博客程序了。摆在眼前的选择其实很多最基础jekyll,加强版的octopress以及hexo。第一个程序的优点就是简单基础,缺点就是太基础了,而octopress在jekyll的基础之上加上了一些插件,让blog默认的功能变得丰富起来。之后hexo,也是一个自带很多基础功能的程序而且还带了很多非常漂亮的主题,主题控的bloger不妨选择这个,我就特别喜欢他其中的一个默认主题,但是折腾样式的时候jekyll的基本结构都搭建好了,所以就没有更换hexo程序,于是极度痛苦的折腾了一周的css和ruby插件才把现在的blog折腾的和之前的差不多。

简单说下用jekyll在Github上搭建blog的步骤,其实网上很多很多教程,这里记录下就是防止自己忘了把。

  1. 首先在http://rubyinstaller.org/downloads/下载ruby和DevKit,安装分别安装他们,然后运行Devkit,分别执行:
    1) dk.rb init
    2) dk.rb review
    3) dk.rb install
  2. 接下来就是安装jekyll了,安装之前推荐更换Gem的源到https://ruby.taobao.org/ 这样下载程序比较快。具体方式是:
    1) gem source -r (url)
    2) gem source -a (new url)
    3) gem source -u
  3. 然后就可以开始下载jekyll和他的代码高亮程序rouge了,gem install (app name)
  4. 最后记得要设置_config.yml文件,尤其是高亮highlighter: rouge

这样,最基础功能的blog就搭建好了,接下来就是把blog从wordpress转移到jekyll了。方法是使用exitwp这个python脚本。

  1. 先导出wordpress的数据到一个xml里,这个功能wordpress是自带的。
  2. 然后同个这个脚本把数据转换成markdown文件,放在jekyll生产的_post里面。并且把里面的图片和下载的url替换了。
  3. 最后把wordpress的upload目录下载下来,放到jekyll里面即可。

这样我们看到的就是一个最简单的jekyll的blog,要想改变主题,自己去折腾吧。我能做的就是推荐两个jekyll的插件,分别是按日期和分类生成归档网页的,可以在我的Github上看到。

最后要说的是rouge语法高亮有个bug,在使用显示行号linenos参数的时候会出现嵌套错误的问题,解决方法倒是有,不过有了行号之后高亮的显示极其丑陋,所以还是我还是没用这个参数。如果有需求可以使用代码rouge_linenos_patch.rb覆盖”\lib\ruby\gems\2.2.0\gems\jekyll-2.5.3\lib\jekyll\tags\highlight.rb”里对应的函数即可。

Tips

关于Zone.Identifier的一点记录

自从Windows XP SP2开始,微软对文件加入了Zone.Identifier的数据流,所以这个也不算什么新东西了,最近偶然有机会研究了下所以就记录了下来。
说起Zone.Identifier,我们最常见的应用就是在我们从Internet上下载了可执行文件后,运行的时候会弹出如下图的警告窗口:

20150615150628

弹出这个窗口就是因为Explorer在运行这个文件的时候先检查了Zone.Identifier的数据,发现了如下文本
[ZoneTransfer]
ZoneId=3

这个ZoneId=3,就是指明这个文件是由Internet上下载的。根据MSDN,这个id有以下几种:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef enum tagURLZONE {
URLZONE_INVALID = -1,
URLZONE_PREDEFINED_MIN = 0,
URLZONE_LOCAL_MACHINE = 0,
URLZONE_INTRANET,
URLZONE_TRUSTED,
URLZONE_INTERNET,
URLZONE_UNTRUSTED,
URLZONE_PREDEFINED_MAX = 999,
URLZONE_USER_MIN = 1000,
URLZONE_USER_MAX = 10000
} URLZONE;

查看这个数据流的方法也很简单,用notepad就行了。

20150615153805

另外如果想给添加或者去除这个数据流,我们这里有两种方法:
1.直接读写数据流,其实这个跟普通文件读写没什么两样。
2.调用微软提供的com接口,这个比较是规范的。

对于第一种方法,没什么可说的,无非就是文件操作的那些API。第二种方法我们需要用到以下两个接口:
IPersistFile
IZoneIdentifier

我们先创建IZoneIdentifier接口,然后query出IPersistFile打开文件,最后读取或者写入文件。
代码详见:http://blogs.msdn.com/b/oldnewthing/archive/2013/11/04/10463035.aspx

最后说一下,之所以能有Zone.Identifier这种功能,完全依赖于NTFS文件系统,它允许多个数据流的存在,对它而言,每个数据流无非就是一个属性而已,只不过Zone.Identifier是一个名字为Zone.Identifier的数据流,而文件本身的数据是一个没有命名的数据流而已。用ntfs_study查看,如下图,第一个Data数据没有名字是文件本身的数据,第二个就是Zone.Identifier的数据了。
20150615160110

NTInternalsTips

Windows 8 SpellChecking API

在Windows 8下,多了一套很有趣的API,SpellChecking,这套API的作用也是一目了然,是做拼写检查的。这么有趣的一套API怎么能不写个程序玩玩呢,于是我写了个小程序,看了看对英文拼写检查的效果,如图。
20150518202121
拼写检查会给出三个结果,分别是删除,替换和建议,根据不同的结果我们可以调用不同的接口来获得最佳的体验。代码如下:

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
// SpellCheck.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <atlbase.h>
#include <atlstr.h>
#include <Spellcheck.h>
class CCoInitialize {
public:
CCoInitialize() {
CoInitializeEx(NULL, COINIT_MULTITHREADED);
}
~CCoInitialize() { CoUninitialize(); }
};
LPCWSTR kActionStrings[] = {
L"CORRECTIVE_ACTION_NONE",
L"CORRECTIVE_ACTION_GET_SUGGESTIONS",
L"CORRECTIVE_ACTION_REPLACE",
L"CORRECTIVE_ACTION_DELETE"
};
int _tmain(int argc, _TCHAR* argv[])
{
CCoInitialize com_init;
CComPtr spell_checker_factory;
HRESULT hr = CoCreateInstance(__uuidof(SpellCheckerFactory), NULL, CLSCTX_INPROC_SERVER, __uuidof(spell_checker_factory),
reinterpret_cast(&spell;_checker_factory));
if (FAILED(hr)) {
return 1;
}
LPCWSTR lang_tag = L"en-US";
BOOL suppored = FALSE;
spell_checker_factory->IsSupported(lang_tag, &suppored;);
if (!suppored) {
return 1;
}
CComPtr spell_checker;
hr = spell_checker_factory->CreateSpellChecker(lang_tag, &spell;_checker);
if (FAILED(hr)) {
return 1;
}
WCHAR my_text[] = L"Helloo world, I am am new heere, hvae fun";
wprintf(L"%s\n\n", my_text);
CComPtr spell_errors;
hr = spell_checker->Check(my_text, &spell;_errors);
if (FAILED(hr)) {
return 1;
}
CComPtr spell_error;
while (spell_errors->Next(&spell;_error) == S_OK) {
ULONG index, length;
if (SUCCEEDED(spell_error->get_StartIndex(&index;)) && SUCCEEDED(spell_error->get_Length(&length;))) {
CStringW tmp_str(my_text + index, length);
wprintf(L"%-10s ", tmp_str.GetString());
CORRECTIVE_ACTION action;
if (SUCCEEDED(spell_error->get_CorrectiveAction(&action;))) {
wprintf(L"%-40s ", kActionStrings[action]);
}
if (action == CORRECTIVE_ACTION_DELETE) {
wprintf(L"delete %s\n", tmp_str.GetString());
}
else if (action == CORRECTIVE_ACTION_GET_SUGGESTIONS) {
CComPtr spell_suggestions;
hr = spell_checker->Suggest(tmp_str.GetString(), &spell;_suggestions);
if (FAILED(hr)) {
break;;
}
WCHAR *suggestion_str;
while (spell_suggestions->Next(1, &suggestion;_str, NULL) == S_OK) {
wprintf(L"%s ", suggestion_str);
CoTaskMemFree(suggestion_str);
}
wprintf(L"\n");
}
else if (action == CORRECTIVE_ACTION_REPLACE) {
WCHAR *replace_str;
hr = spell_error->get_Replacement(&replace;_str);
wprintf(L"%s\n", replace_str);
CoTaskMemFree(replace_str);
}
}
spell_error.Release();
}
return 0;
}

Tips

Windows 8.1 GenericMapping对应的ACCESS_MASK

我们在创建或者打开对象的时候需要指定ACCESS_MASK,有的时候为了方便,我们会在ACCESS_MASK的参数中填GenericRead,GenericWrite这样的值,那么对于这些对象来说,这些GenericXXX究竟是什么样的ACCESS_MASK都是保存在对象的GenericMapping中,以下就是Windows 8.1中所有对象的GenericMapping了。
20150502010045

将ACCESS_MASK数字转换成我们看得懂的宏,可以使用我写的一个小网页:
http://0cch.com/accessmask.html

Tips

防止Global Windows Hooks注入的一个方法

我们都知道SetWindowsHookEx可以设置全局钩子,让自己的dll注入到有窗口的进程中去。注入原理就不再赘述了,网上资料很多,简单看一下调用堆栈方便我们说明怎么去防注入。
kernel32!LoadLibraryExW
USER32!__ClientLoadLibrary
ntdll!KiUserCallbackDispatcher
nt!KiCallUserMode
nt!KeUserModeCallback
win32k!ClientLoadLibrary
win32k!xxxLoadHmodIndex
win32k!xxxCallHook2
win32k!xxxCallHook
win32k!xxxCreateWindowEx
win32k!NtUserCreateWindowEx
nt!KiFastCallEntry
ntdll!KiFastSystemCallRet
ntdll!KiUserCallbackDispatcher
USER32!NtUserCreateWindowEx
USER32!_CreateWindowEx

看着个堆栈,防注入的方法这里就可以大概说出三种:

  1. 被创建窗口程序了。
  2. Hook LoadLibraryExW,判断是否是自己的模块。
  3. Hook __ClientLoadLibrary,替换为空函数。

第一个方法其实也谈不上方法,也就是说控制台程序就不用担心这些了。第二个方法需要是否是判断自己的模块,这个方法也挺麻烦的,因为你得放过一些不是自己的模块,比如微软的模块。所以这里重点说第三个方法,我们去Hook ClientLoadLibrary,这样我们就只是避免了全局钩子的注入了。这里我们不用去Inline Hook该函数,Inline Hook比较麻烦。我们的做法是修改user32!apfnDispatch这个数组,直接替换对应于ClientLoadLibrary所在位置的值。这样摆在我们面前的稍微麻烦一点的事情有两个,一个是确定数组开始的地址,第二就是确定__ClientLoadLibrary在数组中的index。
那么分别来解决这两个问题:

  1. 组数的位置
    其实就是PEB的KernelCallbackTable,虽然PEB没有文档化,但是也没见过他变过什么。所以我们可以写死KernelCallbackTable的偏移。说稍微有点麻烦就是指的,这个偏移在32bit和64bit的系统上是不同的而已,32位系统的偏移是0x2c,64位系统是0x58。另外一个就是获得PEB的方法了,32位程序你既可以写点汇编从fs中获取,也能调用readfsdword获得,64位程序会麻烦点,你需要先获得TEB,然后从TEB里得到PEB,至于获得TEB的方法,你可以直接调用readgsqword获得,也可以调用ntdll的NtCurrentTeb获得。

  2. ClientLoadLibrary在数组中的index
    这个就稍微繁琐点,我们需要把我们关心的系统用windbg带上符号都看一眼才能知道是多少了。
    我这里提供几个常用系统中
    ClientLoadLibrary在数组中的index:
    XP=0x42,Win7=0x41,Win8.1=0x47

好了,知道了这些,后面的就不用说太详细了,无非就是这三步:

  1. 写个空的__ClientLoadLibrary函数MyClientLoadLibrary。
  2. VirtualProtect设置KernelCallbackTable + index * sizeof(PVOID)地址内存保护属性为PAGE_READWRITE。
  3. 替换__ClientLoadLibrary为MyClientLoadLibrary,再把内存属性换回原来的。

OK,大功告成了。

NTInternalsTips

发现File System Minifilter的一处问题

这两天有个朋友一直问我用用户普通权限连接minifilter server port的问题。给他解答的同时,也发现了这个方面的一个问题。首先说用普通用户权限连接port的方法,其实就是设置FltCreateCommunicationPort参数里ObjectAttributes的SecurityDescriptor,加入everyone的ACE就行了。那么加入everyone的ace你就要指定一个ACCESS_MASK,在MSDN里,介绍了两个可以使用的MASK

20150326155009

其中FLT_PORT_CONNECT=1,FLT_PORT_ALL_ACCESS=1F0001。看到这里,多数人都可能会认为如果只想让everyone连接上去,不给他所用权限,那么在这个ACE里加入FLT_PORT_CONNECT就可以了。然后就掉到微软的坑里了,和我那个朋友一样:)。

20150326154028

实际上指定FLT_PORT_CONNECT会让R3的程序无法连接驱动的port,原因就是FilterConnectCommunicationPort函数没有让你指定你需求的ACCESS,而是在底层打开port的时候直接请求FLT_PORT_ALL_ACCESS。这个时候如果你的ACE里面是FLT_PORT_CONNECT,那当然无法连接上去了。所以这里把ACE里的ACCESS_MASK设置为FLT_PORT_ALL_ACCESS就行了。

NTInternalsTips

Windbg查看Object Hook的脚本

学好Windbg,基本上可以代替很多工具,这次分享一个查看Object Hook的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
r @$t0 = 2;
r? @$t1 = ((nt!_OBJECT_TYPE**)@@(nt!ObTypeIndexTable))[@$t0];
.while ((@$t1 & 0xffffffff) != 0) {
.printf "Type Name:%-20msu\t", @@C++(&@$t1->Name);
.printf /D "detail\n", @$t1;
.printf "DumpProcedure : %y\n", @@C++(@$t1->TypeInfo.DumpProcedure);
.printf "OpenProcedure : %y\n", @@C++(@$t1->TypeInfo.OpenProcedure);
.printf "CloseProcedure : %y\n", @@C++(@$t1->TypeInfo.CloseProcedure);
.printf "DeleteProcedure : %y\n", @@C++(@$t1->TypeInfo.DeleteProcedure);
.printf "ParseProcedure : %y\n", @@C++(@$t1->TypeInfo.ParseProcedure);
.printf "SecurityProcedure : %y\n", @@C++(@$t1->TypeInfo.SecurityProcedure);
.printf "QueryNameProcedure : %y\n", @@C++(@$t1->TypeInfo.QueryNameProcedure);
.printf "OkayToCloseProcedure : %y\n\n", @@C++(@$t1->TypeInfo.OkayToCloseProcedure);
r @$t0 = @$t0 + 1;
r? @$t1 = ((nt!_OBJECT_TYPE**)@@(nt!ObTypeIndexTable))[@$t0];
};

20150324095806

Tips