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)
+0x008 name : [0] "[email protected]@[email protected]@"

debugging

gotcha sdk 全盘文件名搜索开发库

想必大家都知道著名的全盘搜索工具everything,它极速的搜索速度让人眼前一亮。虽然everything提供了SDK,但是SDK是通过IPC的方式,获得everything程序里的数据。也就是说想在自己的程序中使用搜索功能那么必须带everything的主程序,这就是我开发gotcha sdk的主要原因,他能集成到程序当中,不需要依赖其他主程序,只需要你的程序是管理员权限运行,因为这样才能直接访问磁盘数据。另外网上也有一些关于everything原理和实现的代码,但是大部分都有问题,比如崩溃,死锁,内存占用过高等,并不适合直接用到产品当中。而gotcha sdk在自己开发了everything_study,并且使用了相当长的时间,解决性能,内存占用,死锁等问题的基础上提炼出来的开发库,我对其稳定性还是比较有信心的。

利用gotcha sdk,既可以开发出everything_study这样用C++写的程序,也能够开发出如gotcha sdk的sample里的gotcha,一个C#编写的全盘搜索程序,该程序也展示了gotcha sdk的用法。

gotcha sdk的用法非常简单,详细情况可以参考sample里的simple例子,该例子展示了sdk最简单的使用方式,我下一篇blog会介绍这套sdk的用法。

20151124233627

gotcha sdk 代码SVN:
http://code.taobao.org/svn/gotcha_sdk/

NTInternals

程序初始化失败DUMP分析

拿到程序初始化失败的DUMP,一般情况下我们看到的栈是这个样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

0:000> kb
ChildEBP RetAddr Args to Child
0012fc7c 7c92d9ca 7c972b53 c0000145 00000001 ntdll!KiFastSystemCallRet
0012fc80 7c972b53 c0000145 00000001 00000000 ntdll!NtRaiseHardError+0xc
0012fca4 7c960f9f c0000005 0012fd30 00370034 ntdll!LdrpInitializationFailure+0x2d
0012fd1c 7c92e457 0012fd30 7c920000 00000000 ntdll!_LdrpInitialize+0x1f9
00000000 00000000 00000000 00000000 00000000 ntdll!KiUserApcDispatcher+0x7

0:000> !error c0000145
Error code: (NTSTATUS) 0xc0000145 (3221225797) - {Application Error} The application was unable to start correctly (0x%lx). Click OK to close the application.

0:000> !error c0000005
Error code: (NTSTATUS) 0xc0000005 (3221225477) - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

可以看到最后报错是c0000145,应用程序无法运行。而引起出错的是LdrpInitializationFailure,出错原因内存访问异常。但是具体是哪出错还不无法从此刻的栈看到,我们需要进一步分析。

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

0:000> dds esp-1000 esp
...
0012f3c0 7c92e920 ntdll!_except_handler3
0012f3c4 00000001
0012f3c8 0012f470
0012f3cc 0012fd0c
0012f3d0 7c953fdc ntdll!RtlDispatchException+0xb1
0012f3d4 0012f470
0012f3d8 0012fd0c
0012f3dc 0012f48c
0012f3e0 0012f444
0012f3e4 7c92e920 ntdll!_except_handler3
0012f3e8 003d3810 someapp!PostMsg+0x27aa0
0012f3ec 0012f470
0012f3f0 b36caf32
0012f3f4 00153960
0012f3f8 7c93e584 ntdll!DbgPrint+0x1c
0012f3fc 00150178
0012f400 000000e8
0012f404 00000668
0012f408 00150000
0012f40c 0012f204
0012f410 7c940571 ntdll!RtlCreateActivationContext+0x2c
0012f414 c0000000
0012f418 00153960
0012f41c 003f0000
0012f420 00000000
0012f424 0012f444
0012f428 7c940610 ntdll!RtlCreateActivationContext+0xed
0012f42c 001539b4
0012f430 00000002
0012f434 00000008
0012f438 00000000
0012f43c 00000000
0012f440 00000000
0012f444 0012f750
0012f448 7c814880 kernel32!CreateActCtxW+0x75c
0012f44c 00130000
0012f450 0012d000
0012f454 00000000
0012f458 0012f76c
0012f45c 7c92e48a ntdll!KiUserExceptionDispatcher+0xe
0012f460 00000000
0012f464 0012f48c
0012f468 0012f470
0012f46c 0012f48c
0012f470 c0000005
0012f474 00000000
0012f478 00000000
0012f47c 7c93ccf2 ntdll!LdrpHandleOneOldFormatImportDescriptor+0x21
0012f480 00000002
0012f484 00000000
0012f488 d16cca32
0012f48c 0001003f
...

这里我们就可以看到异常发生的栈了。

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

0:000> .exr 0012f470
ExceptionAddress: 7c93ccf2 (ntdll!LdrpHandleOneOldFormatImportDescriptor+0x00000021)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000000
Parameter[1]: d16cca32
Attempt to read from address d16cca32

0:000> .cxr 0012f48c
eax=003a0000 ebx=00253010 ecx=d132ca32 edx=00033810 esi=b36caf32 edi=003d3810
eip=7c93ccf2 esp=0012f758 ebp=0012f76c iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010282
ntdll!LdrpHandleOneOldFormatImportDescriptor+0x21:
7c93ccf2 833c0800 cmp dword ptr [eax+ecx],0 ds:0023:d16cca32=????????
0:000> kb
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
0012f76c 7c93ccc4 7ffd9000 00020498 00253010 ntdll!LdrpHandleOneOldFormatImportDescriptor+0x21
0012f784 7c93bc1e 7ffd9000 00020498 00253010 ntdll!LdrpHandleOldFormatImportDescriptors+0x1f
0012f800 7c93d216 00020498 00253010 00434398 ntdll!LdrpWalkImportDescriptor+0x19e
0012fa50 7c93cd1d 00020498 004396ca 00400000 ntdll!LdrpLoadImportModule+0x1c8
0012fa80 7c93ccc4 7ffd9000 00020498 00251ec0 ntdll!LdrpHandleOneOldFormatImportDescriptor+0x5e
0012fa98 7c93bc1e 7ffd9000 00020498 00251ec0 ntdll!LdrpHandleOldFormatImportDescriptors+0x1f
0012fb14 7c9418b5 00020498 00251ec0 7ffdf000 ntdll!LdrpWalkImportDescriptor+0x19e
0012fc94 00000000 0012fca0 00000000 0012fd1c ntdll!LdrpInitializeProcess+0xe02

0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY 00253010
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x251e9c - 0x252ee0 ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x251ea4 - 0x252ee8 ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x018 DllBase : 0x003a0000 Void
+0x01c EntryPoint : 0x003c1ae4 Void
+0x020 SizeOfImage : 0x43000
+0x024 FullDllName : _UNICODE_STRING "C:\Program Files\S-dir\Some-dir\someapp.dll"
+0x02c BaseDllName : _UNICODE_STRING "someapp.dll"
+0x034 Flags : 0x200006
+0x038 LoadCount : 0
+0x03a TlsIndex : 0
+0x03c HashLinks : _LIST_ENTRY [ 0x7c99e2f0 - 0x252a5c ]
+0x03c SectionPointer : 0x7c99e2f0 Void
+0x040 CheckSum : 0x252a5c
+0x044 TimeDateStamp : 0x5618c3dc
+0x044 LoadedImports : 0x5618c3dc Void
+0x048 EntryPointActivationContext : 0x00153960 Void
+0x04c PatchInformation : (null)

可以看到正在加载someapp.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

0:000> !dh someapp -f

File Type: DLL
FILE HEADER VALUES
14C machine (i386)
6 number of sections
5618C3DC time date stamp Sat Oct 10 15:53:00 2015

0 file pointer to symbol table
0 number of symbols
E0 size of optional header
2102 characteristics
Executable
32 bit word machine
DLL

OPTIONAL HEADER VALUES
10B magic #
10.00 linker version
27A00 size of code
15C00 size of initialized data
0 size of uninitialized data
21AE4 address of entry point
1000 base of code
----- new -----
10000000 image base
1000 section alignment
200 file alignment
2 subsystem (Windows GUI)
5.01 operating system version
0.00 image version
5.01 subsystem version
43000 size of image
400 size of headers
4943E checksum
00100000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
140 DLL characteristics
Dynamic base
NX compatible
34BE0 [ 1E0] address [size] of Export Directory
33810 [ B4] address [size] of Import Directory
3A000 [ 4CC] address [size] of Resource Directory
0 [ 0] address [size] of Exception Directory
0 [ 0] address [size] of Security Directory
3B000 [ 3954] address [size] of Base Relocation Directory
29340 [ 1C] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
2DD80 [ 18] address [size] of Thread Storage Directory
2DD38 [ 40] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
29000 [ 2CC] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory

0:000> dc someapp+33810 someapp+33810+B4
003d3810 60325c32 68326432 90326c32 b332af32 2\2`2d2h2l2.2.2.
003d3820 d132ca32 df32db32 0032fc32 30332033 2.2.2.2.2.2.3 30
003d3830 40333833 4c334833 54335033 5c335833 [email protected]\
003d3840 64336033 6c336833 74337033 7c337833 3`3d3h3l3p3t3x3|
003d3850 84338033 8c338833 94339033 ce33b433 3.3.3.3.3.3.3.3.
003d3860 e933d233 f733f333 b134a633 0434e134 3.3.3.3.3.4.4.4.
003d3870 44353935 94357135 d435c935 57361a35 595D5q5.5.5.5.6W
003d3880 be366736 0c36d036 4b374037 b3377b37 [email protected]{7.
003d3890 ea37cf37 45380e37 81385e38 a0388a38 7.7.7.8E8^8.8.8.
003d38a0 fb38d338 4b392438 a4399939 0039dd39 8.8.8$9K9.9.9.9.
003d38b0 403a353a c33a863a 2a3ad33a 6b3b3c3b :5:@:.:.:.:*;<;k
003d38c0 cf3b933b 1d3bea3b ;.;.;.;.

所以这样就清楚了,someapp.dll的输入表被破坏了,导致加载他的程序无法运行起来。

debugging

Windbg插件0cchext

0cchext.dll是我一直在开发和维护的一个Windbg扩展程序。扩展程序中包含了一些或者有趣,或者实用,或者纯个人偏好的功能。这篇文章就来介绍一些主要的功能:


!a

!a - Assembles instruction mnemonics and puts the resulting
instruction codes into memory.

这个指令是写入汇编代码的扩展,虽然Windbg有自己的汇编命令a,但是这个命令无法配合脚本使用。你一旦输入命令a,Windbg就会进入汇编模式,此时你就无法让脚本继续进行了。所以我开发了!a,这个命令只会对一条命令进行汇编,并且将下一条汇编的地址存储在@#LastAsmAddr中,然后马上执行下面的命令,对脚本而已再好不过了。
例如下面这个脚本,他可以注入dll到debuggee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ad /q ${/v:alloc_addr}
ad /q ${/v:@#LastAsmAddr}
x kernel32!LoadlibraryA
.foreach /pS 5 (alloc_addr {.dvalloc 0x200}) {r $.u0 = alloc_addr}
.block {aS ${/v:@#LastAsmAddr} 0; !a $u0 pushfd};
.block {!a ${@#LastAsmAddr} pushad}
.block {!a ${@#LastAsmAddr} push 0x$u0+0x100}
.block {!a ${@#LastAsmAddr} call kernel32!LoadLibraryA}
.block {!a ${@#LastAsmAddr} popad}
.block {!a ${@#LastAsmAddr} popfd}
.block { eza 0x$u0+0x100 "${$arg1}"}
r @[email protected]
r @eip=$u0
.block {g ${@#LastAsmAddr}}
r @[email protected]$t0
.dvfree 0x$u0 0


!autocmd

!autocmd - Execute the debugger commands.(The config file is
autocmd.ini)

自动执行特定指令。有的时候我希望调试器附加到进程或者运行程序的时候能够自动运行一连串的命令,这个功能虽然可以由脚本完成,但是对我而言还是不够简洁,所以就有了这个命令。我可以在0cchext.dll的目录下,创建autocmd.ini文件,然后输入以下内容:

1
2
3
4
5
6
7
[notepad.exe]
.sympath+ c:\notepad_pdb
~*k

[calc.exe]
.sympath+ c:\calc_pdb
~*k

这样,在调试不同程序的时候输入!autocmd会执行不同的命令。


!bing & !google

!bing - Use bing to search.
!google - Use google to search.

这个命令非常简单,就是用bing和google去搜索指定的字符串。


!favcmd

!favcmd - Display the favorite debugger commands.(The config file is
favcmd.ini)

这个命令也非常简单,只需要把自己喜欢的命令一行一行的写在favcmd.ini文件里就行了,当然这个文件也需要和0cchext.dll在同一个目录。然后运行这个命令后,你所喜欢的命令就会打印到Windbg上,你可以用鼠标选择执行这些命令。

例如在文件中分别写入:

~*k
!address
!heap

20151005162754


!hwnd

!hwnd - Show window information by handle.

这个命令很简单,可以输入窗口句柄为参数,查看窗口相关信息。主要作用是在内核调试的时候,用调试器看到窗口信息会比较方便。


!url

!url - Open a URL in a default browser.

这个命令会打开一个url,实际上他就是一个ShellExecute。Windbg本来就有.shell功能了,这个似乎是多余了一点。


!init_script_env

!init_script_env - Initialize script environment.

这个命令是我给脚本准备的,他方便了脚本判断系统环境。如下图所示

20151005163744


!import_vs_bps

!import_vs_bps - Import visual studio breakpoints.

这个命令可以将VS存储在suo文件的断点导入到Windbg中。我有的时候会碰到这样的情况,VS里设置了一堆断点,但是调试环境里只有Windbg,那么我需要把这些断点转移到Windbg,有了这个命令,我只需要将VS解决方案的suo文件拷贝到调试环境中,然后运行这条命令即可。

例如

!import_vs_bps c:\proj\xxx.suo


!setvprot

!setvprot - Set the protection on a region of committed pages in the
virtual address space of the debuggee process.

这个命令能帮助我设置debuggee的内存属性,一个有趣的用法就是模仿Ollydbg的内存断点功能,比如给目标内存设置一个PAGE_GUARD属性,这样访问这部分内存的时候就会触发访问异常,调试器就能捕获到它了。

例如

!setvprot 0x410000 0x1000 0x100


!pe_export & !pe_import

!pe_export - Dump PE export functions
!pe_import - Dump PE import modules and functions

这两个命令可以分别帮助我们查看导出和导入函数,他们都支持通配符查找函数,在没有符号的情况下有时候会起到很好的作用。另外,他们配合好参数/b和.foreach命令,可以发挥出API Monitor的作用。

例如

.foreach( place { !pe_export /b kernel32 *Create* } ) { bp place “g” }


!wql

!wql - Query system information with WMI.

这也是我比较喜欢的一个功能,他可以在调试的时候通过WQL来查询系统的一些信息,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0:000> !0cchext.wql select * from win32_process where name="explorer.exe"
-------------------------------------------------------------
Caption local CIM_STRING explorer.exe
CommandLine local CIM_STRING C:\Windows\Explorer.EXE
CreationClassName local CIM_STRING Win32_Process
CreationDate local CIM_DATETIME 2015-09-17 09:41:53.959
CSCreationClassName local CIM_STRING Win32_ComputerSystem
...
...
ThreadCount local CIM_UINT32 40
UserModeTime local CIM_UINT64 605439881
VirtualSize local CIM_UINT64 435580928
WindowsVersion local CIM_STRING 6.1.7601
WorkingSetSize local CIM_UINT64 109813760
WriteOperationCount local CIM_UINT64 399
WriteTransferCount local CIM_UINT64 1545945
-------------------------------------------------------------


!logcmd

!logcmd - Log command line to log file

这个命令是一个开关,打开后,他会记录调试的命令到文件中,这样下次调试相同的程序的时候就不需要在此去输入这些命令了,只需要读取这个命令文件,就可以用鼠标点击执行命令了。

20151005170422


!dpx

!dpx - Display the contents of memory in the given range.

这个命令是集dps dpa dpu大成者。他的会对目标指针做一个简单的判断,判断是符号,字符串,还是宽字符串。这样在我们查看栈信息的时候就不会漏掉一些有用的线索了。

1
2
3
4
5
6
7
8
9
10
11
0:000> !dpx esp 100
00c3f28c 7605cb33 [S] USER32!GetMessageA+0x53 (7605cb33)
...
00c3f2b4 012b6ca9 [S] usbview!WinMain+0xe3 (012b6ca9)
...
00c3f2f4 012ce723 [S] usbview!WinMainCRTStartup+0x151 (012ce723)
00c3f2f8 01260000 [S] usbview!__guard_check_icall_fptr <PERF> (usbview+0x0)
...
00c3f320 01025618 [A] "Winsta0\Default"
00c3f324 01025640 [A] "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\usbview.exe"
00c3f328 00000000 [D] ....

!dtx

!dtx - Displays information about structures. (The config file is
struct.ini)

这个命令主要用在逆向工程的时候。因为逆向工程的时候,我们往往没有符号文件,就不可能直接知道内存数据的结构是什么样子的,我们需要自己通过代码推断出来。在IDA中,我们可以自己设置结构体帮助分析。但是在Windbg中,并没有一个功能能方便的帮助我们用这推断的结构体去显示内存。不可否认我们其实可以用其他的办法来完成这个目的,但操作很繁琐。那么这个命令就解决了这些问题。我们可以在struct.ini文件中写入我们推断的结构体,然后通过这个命令去打印内存数据。当然,这个文件也必须在0cchext.dll的同目录下。

20151005172455

到目前位置脚本解析器支持的基本类型有BYTE WORD DWORD CHAR WCHAR,支持数组和指针,支持结构体嵌套,有了这些,对于基本的逆向就能够满足需求了。


现在0cchext.dll就是这些命令了,我也会根据自己的需求继续添加命令,如果你有什么有趣或者实用的想法,可以通过邮件或者留言告诉我。

debugging