在NTFS元文件目录里创建文件

说到Rootkit就不能提到他的文件隐藏,Rootkit隐藏文件的方式千奇百怪,这里说其中一个通过NTFS元文件目录无法被普通程序显示的特性隐藏文件的方法。

我们都知道NTFS是有元文件的,比如$MFT(NTFS主文件表),这种文件是我们看不到的,但是系统能访问。同样还有一种元文件目录,这个目录也是看不到的,无论你是否打开了显示系统文件,隐藏文件的选项。那么如果我们把要隐藏的文件放在这种目录下,那么就达到了隐藏的效果。

举个例子 $Extend\$RmMetadata 这个目录。我们可以通过Winhex解析NTFS来读取这个目录的情况,而普通程序不行。这里我们通过这样的代码来创建文件。

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
#define GPA(x) *(FARPROC *)&My##x = GetProcAddress(GetModuleHandle(L"ntdll.dll"), #x)
GPA(NtCreateFile);
GPA(RtlInitUnicodeString);
IO_STATUS_BLOCK iob;
HANDLE h;
UNICODE_STRING uni_str;
MyRtlInitUnicodeString(&uni_str, L"\\??\\Global\\D:\\$Extend\\$RmMetadata\\$0cch");
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &uni_str, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL)
LONG l = MyNtCreateFile(&h,
FILE_APPEND_DATA | SYNCHRONIZE,
&oa,
&iob,
0,
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
0,
FILE_SUPERSEDE,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL,
0);
char buffer[] = "0123456789";
WriteFile(h, buffer, strlen(buffer), (ULONG *)&l, NULL);
CloseHandle(h);

值得注意的是我们必须用System用户权限去运行这个程序,才能创建文件到元文件目录,这里要用到psexec:

psexec -s C:\0cch\Test.exe

然后我们看看效果

20160824115523

Tips

关于Windows Timer精确度

Windows Timer相比大家都用过,WM_TIMER, WM_SYSTIMER, Waitable Timer, Multimedia Timer, Timer Queue Timer,这么多种Timer,给我们变成提供了很大的方便,有窗口无窗口都能自如选择。所以尽量也不要自己再造轮子,用什么Sleep来写Timer。这种“自定义”的Timer肯定是没有由系统内核DPC触发的Timer效率高的。

OK,回到正题,关于Timer的精确度。首先看看SysInternal工具集的clockres的显示:

20160725102628

从图中可以看出,我这个系统的最大精确度15.6毫秒,最小是0.5毫秒,当前是15.6毫秒。默认情况下,Windows会用最大精确度,因为这样可以减少CPU的消耗,而且高精度的定时器,绝大多数程序都不会用到。基于15.6毫秒这个精度,那么我们设置Timer间隔为15.6毫秒以下都是没有意义的,这里再提一下,Sleep函数在内核也是用的定时器,也就是说这个精确度下,Sleep(10)也是没有意义的,间隔会达到15-16毫秒。

当然,我们有的时候也是需要高精度的定时器的,这个时候我们需要设置时间精度。timeBeginPeriod这个函数就可以完成这个任务,这个函数调用了ntdll的NtSetTimerResolution函数,我们也可以直接调用这个ntdll函数,只不过我们需要动态获得这个函数的地址罢了。值得注意的是,并不是你想设置什么精确度都可以,Windows内部实际上维护了一份可以设置的精度列表,他会选择一个和你设置相近的的精度设置上去,这个列表保存在Hal里面。

好了,再说下Windows时钟,Windows时钟更新时间总是用的最大精度,在我个系统上也就是每次更新时间都是间隔15.6毫秒。也就是说如果用GetTickCount来统计性能问题,最大精度也就是15-16毫秒。举个例子,一段代码运行时间不足15.6毫秒,要么统计结果是0,要么是15-16毫秒,时间精度不会影响Windows时钟更新。

最后说下Windows高精度时钟查询的实现,在2000和XP时代,系统用TSC来演算时间,但是那个时候,多核并不支持TSC同步,这回带来一些问题。Vista系统采用了High Precision Event Timer (HPET)或者ACPI Power Management Timer (PM timer),但是这种Timer的延时比较高,当然,这个延时是百纳秒级别的,可以说基本上不会对普通程序有什么影响。之后的系统就使用了固定频率的TSC,这样在多核状态下也能保证同步,而且延时很低。更详细的资料可以参考:https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx

Tips

Windows 10 任务管理器结束任务流程

从Win8开始,任务管理已经悄然发生变化了,这篇文章要说的就是结束任务这一个功能。以Win10的任务管理器为主来说明,没有了从窗口关闭进程的标签。取而代之的是一个区分前台和后台程序的进程树。通过这个界面结束进程也不再像以前一样调用User32的EndTask(https://msdn.microsoft.com/en-us/library/windows/desktop/ms633492(v=vs.85).aspx),而是重新规划了一套逻辑。

具体逻辑如下:

1.区分程序类型

2.如果是窗口程序,则给窗口发送WM_SYSCOMMAND+SC_CLOSE结束窗口来结束进程

3.如果是服务程序,则调用ControlService+SERVICE_CONTROL_STOP结束服务来结束进程

4.如果既没有窗口也不是服务的程序,或者说在第2,3步没有结束成功的进程,会调用TerminateProcess来强行结束进程。

5.第五步是和之前结束任务最大的一个区别,以前的任务管理器,如果没能结束进程,例如一些僵尸进程,他就不会做其他动作了,而新的任务管理器为了释放这种进程所占用的内核资源,他还会做另外一些事情,那就是关闭目标进程的所有句柄。使用的方式就是DuplicateHandle+DUPLICATE_CLOSE_SOURCE。这样做的另外一个好处就是,如果顽固进程还在运行,句柄关闭会造成其崩溃而结束。

Tips

Windows 8 Shell API对于长路径文件名的支持

在Windows 8之前,Shell API对于长路径的文件名的支持并不理想。比如PathAppend这个函数,函数规定pszPath,也就是第一个参数,它的buffer大小必须要能够容纳MAX_PATH个字符。第二个参数pszMore也不能超过MAX_PATH的长度。这样的API不仅不能满足我们对长文件路径需求,同时也可能让我们的软件由于字符串检查不严格出现严重BUG和漏洞。

还好,这个问题在Windows 8以及以后的系统上得到了解决。还是以路径拼接为例。微软向我们介绍了PathCchAppend和PathCchAppendEx函数。其中PathCchAppend函数,增加了cchPath参数,用来指定输出buffer的大小。用这样的方式来加强参数的检查,增加了函数的安全性。而PathCchAppendEx这个函数在PathCchAppend基础上,又加入了dwFlags,现在这个标志只有PATHCCH_ALLOW_LONG_PATHS,意思就是让我们的路径名超过MAX_PATH。

不知道微软设计PathCchAppend和PathCchAppendEx这两个API的时候是怎么样的一个想法,我觉得完全没必要设计成两个函数,一个PathCchAppendEx就足够了。大家是不是也有这个疑问呢?

最后,由于Windows 7现在的使用量还是非常大的,我们也不能因为要使用这些新的API而放弃兼容老版本的Windows。比较合适的做法还是动态导入这些函数,如果成功了就可以使用新的函数,失败就用老的函数。另外值得注意的是,PathCchAppend这类新的函数并不是放在shlwapi.dll里面,而是在kernelbase.dll,动态获取函数的时候需要注意这一点。

Tips

获取桌面图标位置

用来干什么就不用说了,反正不是什么好事情 =v=

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
typedef struct _DESKTOP_ICON_INFO {
LVITEMW item;
WCHAR item_text[MAX_PATH];
RECT rc;
} DESKTOP_ICON_INFO, *PDESKTOP_ICON_INFO;
BOOL GetDesktopIconInfo(LPCWSTR pattern, RECT &rc, HWND &desktop)
{
HWND progman = FindWindow(TEXT("Progman"), TEXT("Program Manager"));
if (progman == NULL) {
return FALSE;
}
HWND def_view = FindWindowEx(progman, NULL, TEXT("SHELLDLL_DefView"), NULL);
if (def_view == NULL) {
return FALSE;
}
HWND list_view = FindWindowEx(def_view, NULL, TEXT("SysListView32"), TEXT("FolderView"));
if (list_view == NULL) {
return FALSE;
}
desktop = list_view;
ULONG process_id = 0;
GetWindowThreadProcessId(progman, &process_id);
if (process_id == 0) {
return FALSE;
}
int count = (int)::SendMessage(list_view, LVM_GETITEMCOUNT, 0, 0);
HANDLE process_handle = OpenProcess(
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, FALSE, process_id);
if (process_handle == NULL) {
return FALSE;
}
PUCHAR remote_addr = (PUCHAR)VirtualAllocEx(process_handle, NULL,
sizeof(DESKTOP_ICON_INFO), MEM_COMMIT, PAGE_READWRITE);
DESKTOP_ICON_INFO icon_info;
icon_info.item.iItem = 0;
icon_info.item.iSubItem = 0;
icon_info.item.mask = LVIF_TEXT;
icon_info.item.pszText = (WCHAR *)(remote_addr + offsetof(DESKTOP_ICON_INFO, item_text));
icon_info.item.cchTextMax = MAX_PATH;
for (int i = 0; i < count; i++) {
icon_info.rc.left = LVIR_BOUNDS;
ZeroMemory(icon_info.item_text, sizeof(icon_info.item_text));
if (WriteProcessMemory(process_handle, remote_addr, &icon_info, sizeof(icon_info), NULL)) {
::SendMessage(list_view, LVM_GETITEMTEXT, (WPARAM)i, (LPARAM)(remote_addr + offsetof(DESKTOP_ICON_INFO, item)));
::SendMessage(list_view, LVM_GETITEMRECT, (WPARAM)i, (LPARAM)(remote_addr + offsetof(DESKTOP_ICON_INFO, rc)));
ReadProcessMemory(process_handle, remote_addr, &icon_info, sizeof(icon_info), NULL);
if (_wcsicmp(icon_info.item_text, pattern) == 0) {
rc = icon_info.rc;
break;
}
}
}
VirtualFreeEx(process_handle, remote_addr, 0, MEM_RELEASE);
CloseHandle(process_handle);
return TRUE;
}

Tips

查看消息窗口工具

我们都知道用Spy++去查看窗口句柄的相关信息,但是这款工具无法找到消息窗口(Message-Only Windows)。所以写了个查看消息窗口的工具,帮我排查一些这方面的问题。

20160317120246

下载:MsgOnlyWnd

Tips

c06d007f异常的解决方法

c06d007f这个异常通常是在PE的延迟加载dll的时候发生的,加载器找不到对应的dll就会抛出这个异常。如果我们对这个异常不熟悉,按照常规方式去找上下文,那么结果肯定会让你失望。例如3.2526.1373.0版本的libcef在XP上运行的情况。

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
0:000> kb
# ChildEBP RetAddr Args to Child
00 0012f218 7c92d9ac 7c86449d d0000144 00000004 ntdll!KiFastSystemCallRet
01 0012f21c 7c86449d d0000144 00000004 00000000 ntdll!ZwRaiseHardError+0xc
02 0012f4a0 7c843892 0012f4c8 7c839b21 0012f4d0 kernel32!UnhandledExceptionFilter+0x628
03 0012f4a8 7c839b21 0012f4d0 00000000 0012f4d0 kernel32!BaseProcessStart+0x39
04 0012f4d0 7c9232a8 0012f5bc 0012ffe0 0012f5d4 kernel32!_except_handler3+0x61
05 0012f4f4 7c92327a 0012f5bc 0012ffe0 0012f5d4 ntdll!ExecuteHandler2+0x26
06 0012f5a4 7c92e46a 00000000 0012f5d4 0012f5bc ntdll!ExecuteHandler+0x24
07 0012f5a4 00000000 00000000 0012f5d4 0012f5bc ntdll!KiUserExceptionDispatcher+0xe
WARNING: Frame IP not in any known module. Following frames may be wrong.
08 0012fff4 004a991e 00000000 78746341 00000020 0x0
09 0012fff8 00000000 78746341 00000020 00000001 cefclient!pre_c_init+0xb9 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 261]
0:000> .cxr 0012f5d4;k
eax=0012f8a4 ebx=1314a58c ecx=00000000 edx=00000001 esi=0012f954 edi=68d60000
eip=00000000 esp=0012fff8 ebp=00000000 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
00000000 ?? ???
*** Stack trace for last set context - .thread/.cxr resets it
# ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0012fff4 004a991e 0x0
01 0012fff8 00000000 cefclient!pre_c_init+0xb9 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 261]

直接看栈回溯或者通过设置cxr看栈回溯,并没有帮助我们找到什么有用的信息。

这里要使用的方法是,利用异常的参数来找到具体延迟加载谁的时候发生了异常。

1
2
3
4
5
6
7
8
0:000> .exr 0012f5bc
ExceptionAddress: 7c812aeb (kernel32!RaiseException+0x00000053)
ExceptionCode: c06d007f
ExceptionFlags: 00000000
NumberParameters: 1
Parameter[0]: 0012f918

这里的参数0,就是我们要找的目标,记录了出错时候ebp-0x30的数据,也就是含有关键信息的地方。让我们仔细看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
0:000> dds 0012f918
0012f918 00000024
0012f91c 1314a58c libcef!_DELAY_IMPORT_DESCRIPTOR_dbghelp_dll
0012f920 13181dbc libcef!_imp__SymGetSearchPathW
0012f924 12ebdd20 libcef!_sz_dbghelp_dll
0012f928 00000001
0012f92c 1314ac8e libcef!dxva2_NULL_THUNK_DATA_DLN+0x7e
0012f930 68d60000 dbghelp!_imp__CryptAcquireContextA <PERF> (dbghelp+0x0)
0012f934 00000000
0012f938 0000007f
0012f93c 1314c138 libcef!dxva2_NULL_THUNK_DATA_DLN+0x1528
0012f940 00000003
0012f944 00000000
0012f948 0012f9f8
0012f94c 11d17587 libcef!_tailMerge_dbghelp_dll+0xd
0012f950 0012f918
0012f954 13181dbc libcef!_imp__SymGetSearchPathW
0012f958 00000008
0012f95c 7c9301bb ntdll!RtlAllocateHeap+0xeac
0012f960 1019014e libcef!base::debug::`anonymous namespace'::InitializeSymbols+0x9e [f:\stnts\browser\cef\ws\src\chromium\src\base\debug\stack_trace_win.cc @ 79]
0012f964 ffffffff
0012f968 00170880

我们可以清楚的看到加载器延迟加载SymGetSearchPathW的时候发生了问题。让我们进一步用depends工具验证一下

20160223003624

如上图所示,XP自带的dbghelp里没有SymGetSearchPathW这个导出函数。要解决这个异常,实际上就需要在运行目录里添加一个稍微新一点的dbghelp文件,我这里替换的是6.2.9200.16384的dbghelp,替换过后问题已经不再出现了。

20160223003711

debugging

调试器最早的中断应用程序的方法

这篇Blog分享一个Windbg的小技巧,就是让被调试程序更早的中断到调试器。熟悉Windbg的朋友都知道,用调试器运行程序,默认情况下都会中断到ntdll!LdrpDoDebuggerBreak。但是有时候我们会想去调试程序加载的过程,这个时候就需要我们更早的中断下来。那么这里就用利用到调试器最早接受到的调试事件了。CREATE_PROCESS_DEBUG_EVENT,这个调试事件是创建进程的时候进程发给调试器的,在这个时候,你甚至连ntdll都没有完成加载,这也导致ntdll的符号无法加载,很多有用的功能用不上。但幸运的是,虽然ntdll没有完成加载,但是已经加载到了内存,另外我们可以用手动加载符号的方法,把符号文件加载到ntdll的内存上去。

演示如下:

windbg.EXE -xe cpr -xe ld notepad.exe

这里设置中断系统事件cpr,也就是CREATE_PROCESS_DEBUG_EVENT

1
2
3
4
5
6
7
8
0:000> lm
start end module name
00007ff7`3f6e0000 00007ff7`3f721000 notepad (deferred)
0:000> !teb
TEB at 000000d995d21000
error InitTypeRead( TEB )...

中断下来后我们可以看到,!teb是没法用的

1
2
3
4
5
6
7
8
0:000> .imgscan
MZ at 00007ff7`3f6e0000, prot 00000002, type 01000000 - size 41000
Name: notepad.exe
MZ at 00007ffb`7c7b0000, prot 00000002, type 01000000 - size 1c1000
Name: ntdll.dll
0:000> .reload /f ntdll.dll=00007ffb`7c7b0000

我们需要找到ntdll的模块,然后手动加载符号,然后就可以使用和ntdll有关系的命令了。

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
0:000> lm
start end module name
00007ff7`3f6e0000 00007ff7`3f721000 notepad (deferred)
00007ffb`7c7b0000 00007ffb`7c971000 ntdll (pdb symbols) e:\workspace\mysymbols\ntdll.pdb\F296699DB5314A06935E88564D8CD2731\ntdll.pdb
0:000> !teb
TEB at 000000d995d21000
ExceptionList: 0000000000000000
StackBase: 000000d995af0000
StackLimit: 000000d995adf000
SubSystemTib: 0000000000000000
FiberData: 0000000000001e00
ArbitraryUserPointer: 0000000000000000
Self: 000000d995d21000
EnvironmentPointer: 0000000000000000
ClientId: 0000000000001c8c . 00000000000017c4
RpcHandle: 0000000000000000
Tls Storage: 0000000000000000
PEB Address: 000000d995d20000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0

debugging

总结和展望:转折的一年

过去的2015年应该是我工作和生活中的一个大转折。

这一年里,我选择从北京回到了武汉。这件事情上还是需要一些魄力的,最主要的就是收入拦腰截断,剩下原来的二分之一。其次发展空间上也不能和帝都相提并论。就拿接到猎头电话这件事情上说,回来大半年里,猎头电话一个接一个,但是绝大部分都是北京打来的。不过,在家乡有父母,有亲戚,有未婚妻,这种“交换”也是值得的,毕竟我不能期待什么都能得到。回来后,健身依然在坚持,只不过没有跑步机,还是觉得缺少了很多东西。自己的兴趣方面,MiniKernel,编译器和虚拟机依旧没什么进展,有一种写不动了的感觉,有进展的依旧是小工具合集,有些工具增加了一些新的功能,比如everything_study就优化了算法,现在查找速度已经和everthing看不出区别了。今年最美好的事情就是求婚,最悲催的事情就是学车。求婚对每个人来说想必都是最美好的事情,这个自然不必多说。至于学车,也是找了个不靠谱的驾校,被坑的不轻。幸运的是自己对车接受的比较快,没被教练坑的太惨,科目一到科目三都是满分通过,现在就剩下科目四了,春节前就把驾照给拿了。另外IXWebhosting这个主机我也不准备用了,换成GitHub Page来当blog,过段时间把0CCh.net这个域名也转移的godaddy算了。

新的2016将会是一个真正新的开始!我将在这一年组建自己的小家,要买车,要装修房子。工作上希望武汉的互联网大环境会更好,希望我的劳动能给公司带来更高的价值。健身方面,我打算在新家里买上一个跑步机,过时如同北京时那样的健康生活。另外,练字也应该继续。兴趣方面,小工具集可以继续壮大,MiniKernel,编译器和虚拟机中,我更倾向多花时间写写编译器。

另外,好友初步完成了自己的梦想,去美国工作了。很羡慕,祝福他能扎根那边,别回来吸雾霾了=v=。

最后,还是祝愿家人,朋友,在新的2016健健康康,平平安安,开开心心,财源广进!

C++异常的参数分析(0xE06D7363)

Visual C++ 的编译器用0xE06D7363表示C++异常。 0xE06D7363表示的意思就是.msc。

1
2
3
4
5
6
7
8
9
10
11
12
0:025> .formats 0xE06D7363
Evaluate expression:
Hex: e06d7363
Decimal: -529697949
Octal: 34033271543
Binary: 11100000 01101101 01110011 01100011
Chars: .msc
Time: ***** Invalid
Float: low -6.84405e+019 high 0
Double: 1.86029e-314

抛出异常代码的同时,还会带有三个到四个参数:
参数0是一个magic code,一般为0x19930520,我们不用管他
参数1是时异常抛出的对象指针
参数2是抛出异常的基本信息
参数3是抛出异常的模块基址(只有64位的程序才会有这个参数),该基址加上异常信息的偏移才能获得信息的真正内存地址。

1
2
3
4
5
6
7
8
9
10
0:025> .exr -1
ExceptionAddress: 75c8c41f (KERNELBASE!RaiseException+0x00000058)
ExceptionCode: e06d7363 (C++ EH exception)
ExceptionFlags: 00000001
NumberParameters: 3
Parameter[0]: 19930520
Parameter[1]: 09c9f324
Parameter[2]: 6b5d0298

6b5d0298就是我们想要取得的信息,信息存储的格式为_s__ThrowInfo。

1
2
3
4
5
6
7
0:025> dt 6b5d0298 ole32!_s__ThrowInfo
+0x000 attributes : 0
+0x004 pmfnUnwind : 0x6b523b50 void +0
+0x008 pForwardCompat : (null)
+0x00c pCatchableTypeArray : 0x6b5d028c _s__CatchableTypeArray

然后可以取得pCatchableTypeArray,我们可以从中获取抛出异常的类型信息。

1
2
3
4
5
6
7
8
9
10
0:025> dt 0x6b5d028c ole32!_s__CatchableTypeArray -r1
+0x000 nCatchableTypes : 0n2
+0x004 arrayOfCatchableTypes : [0] 0x6b5d0270 _s__CatchableType
+0x000 properties : 0
+0x004 pType : 0x6b5e58f0 _TypeDescriptor
+0x008 thisDisplacement : _PMD
+0x014 sizeOrOffset : 0n48
+0x018 copyFunction : 0x6b523cc0 void +0

到这里我们就取得了类型的描述结构体了,最后就能从中获取抛出的异常类型

1
2
3
4
5
6
0:025> dt 0x6b5e58f0 ole32!_TypeDescriptor
+0x000 pVFTable : 0x6b5c36e8 Void
+0x004 spare : (null)

debugging