调试COM的一个tip

最近遇到朋友的一个程序崩溃,原因是接口没有释放的时候调用了CoUninitialize,接着才释放接口。这个应该是个很明显的问题,但是朋友告诉我以前代码就是这个样子的,没有崩溃过,最近修改了部分代码但并不是这一块的。为了看看究竟什么回事,我把没有崩溃的程序抓了dump,看了COM的初始化引用计数:

1
2
3
4
5
6
7
8
9
10
11
0:000> dt _teb @$teb ReservedForOle
ntdll!_TEB
+0x1758 ReservedForOle : 0x00000000`00271b00 Void

0:000> dt ole32!SOleTlsData 0x00000000`00271b00 cComInits pNativeApt
+0x028 cComInits : 5
+0x080 pNativeApt : 0x00000000`00272680 CComApartment

0:000> dt 0x00000000`00272680 CComApartment _AptKind
ole32!CComApartment
+0x010 _AptKind : 4 ( APTKIND_APARTMENTTHREADED )

没有崩溃的时候,引用计数确实不为0,也能看出是个STA。后来朋友发现,之所以之前没有崩溃,是因为之前线程加载的某个dll中,有初始化COM的调用,所以引用计数不为0。后来移开了这个dll,问题就出现了。

Tips

gotcha sdk 文件监控功能更新

在15年的一篇blog中,我介绍了gotcha sdk。gotcha sdk 全盘文件名搜索开发库

当时gotcha sdk没有提供文件监控功能,也就是说当搜索文件发生变化的时候,这个变化不会体现到搜索结果列表中。其实这个功能一直在todo list中,只不过忙的时候没时间写这部分代码,闲的时候又忘了。前几天终于有时间把这部分代码补上,升级了sdk。

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

NTInternals

切换到session 0

这是一个小技巧,可以帮助我们从session 1切换到session 0,并且获得system权限。有了system权限,可以做一些admin做不了的事情,具体哪些事情大伙可以自己挖掘。

1
2
切换到session 0:    rundll32 winsta.dll WinStationSwitchToServicesSession
切换会原session: rundll32 winsta.dll WinStationRevertFromServicesSession

但是如果直接切换到session 0,会发现一个问题,我们没有桌面程序,所以什么事情也做不了。解决方法也很简单,创建一个explorer就可以了。但是普通方法创建explorer,怎么会不能创建到session 0,于是这里可想而知,我们需要一个服务来创建explorer。专门写一个服务程序未免太麻烦,这里可以使用cmd来快速创建explorer。

1
2
sc create desktop0 binpath= "cmd /c start explorer.exe" type= own type= interact
net start desktop0

虽然cmd不是服务,但是也会被运行起来,只不过不能与服务管理器交互,所以在超时的时候会被结束。不过那个时候已经没关系了,因为explorer已经创建起来了。接下来就可以切换了session 0,用system权限管理电脑了。

Tips

0cchext插件实用命令dttoc

最近给0cchext添加了一个实用的逆向命令,dttoc,这个命令可以把dt命令输出的结构体转化为C的结构,方便我们做逆向还原工作。

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
0:000> !0cchext.dttoc nt!_peb
struct _PEB {
BYTE InheritedAddressSpace;
BYTE ReadImageFileExecOptions;
BYTE BeingDebugged;
union {
BYTE BitField;
struct {
BYTE ImageUsesLargePages:1;
BYTE IsProtectedProcess:1;
BYTE IsImageDynamicallyRelocated:1;
BYTE SkipPatchingUser32Forwarders:1;
BYTE IsPackagedProcess:1;
BYTE IsAppContainer:1;
BYTE IsProtectedProcessLight:1;
BYTE IsLongPathAwareProcess:1;
};
};
VOID* Mutant;
VOID* ImageBaseAddress;
_PEB_LDR_DATA* Ldr;
_RTL_USER_PROCESS_PARAMETERS* ProcessParameters;
VOID* SubSystemData;
VOID* ProcessHeap;
_RTL_CRITICAL_SECTION* FastPebLock;
_SLIST_HEADER* AtlThunkSListPtr;
VOID* IFEOKey;
union {
DWORD CrossProcessFlags;
struct {
DWORD ProcessInJob:1;
DWORD ProcessInitializing:1;
DWORD ProcessUsingVEH:1;
DWORD ProcessUsingVCH:1;
DWORD ProcessUsingFTH:1;
DWORD ReservedBits0:27;
};
};
union {
VOID* KernelCallbackTable;
VOID* UserSharedInfoPtr;
};
DWORD SystemReserved[1];
_SLIST_HEADER* AtlThunkSListPtr32;
VOID* ApiSetMap;
DWORD TlsExpansionCounter;
VOID* TlsBitmap;
DWORD TlsBitmapBits[2];
VOID* ReadOnlySharedMemoryBase;
VOID* SparePvoid0;
VOID** ReadOnlyStaticServerData;
VOID* AnsiCodePageData;
VOID* OemCodePageData;
VOID* UnicodeCaseTableData;
DWORD NumberOfProcessors;
DWORD NtGlobalFlag;
_LARGE_INTEGER CriticalSectionTimeout;
DWORD HeapSegmentReserve;
DWORD HeapSegmentCommit;
DWORD HeapDeCommitTotalFreeThreshold;
DWORD HeapDeCommitFreeBlockThreshold;
DWORD NumberOfHeaps;
DWORD MaximumNumberOfHeaps;
VOID** ProcessHeaps;
VOID* GdiSharedHandleTable;
VOID* ProcessStarterHelper;
DWORD GdiDCAttributeList;
_RTL_CRITICAL_SECTION* LoaderLock;
DWORD OSMajorVersion;
DWORD OSMinorVersion;
WORD OSBuildNumber;
WORD OSCSDVersion;
DWORD OSPlatformId;
DWORD ImageSubsystem;
DWORD ImageSubsystemMajorVersion;
DWORD ImageSubsystemMinorVersion;
DWORD ActiveProcessAffinityMask;
DWORD GdiHandleBuffer[34];
void* PostProcessInitRoutine;
VOID* TlsExpansionBitmap;
DWORD TlsExpansionBitmapBits[32];
DWORD SessionId;
_ULARGE_INTEGER AppCompatFlags;
_ULARGE_INTEGER AppCompatFlagsUser;
VOID* pShimData;
VOID* AppCompatInfo;
_UNICODE_STRING CSDVersion;
_ACTIVATION_CONTEXT_DATA* ActivationContextData;
_ASSEMBLY_STORAGE_MAP* ProcessAssemblyStorageMap;
_ACTIVATION_CONTEXT_DATA* SystemDefaultActivationContextData;
_ASSEMBLY_STORAGE_MAP* SystemAssemblyStorageMap;
DWORD MinimumStackCommit;
_FLS_CALLBACK_INFO* FlsCallback;
_LIST_ENTRY FlsListHead;
VOID* FlsBitmap;
DWORD FlsBitmapBits[4];
DWORD FlsHighIndex;
VOID* WerRegistrationData;
VOID* WerShipAssertPtr;
VOID* pUnused;
VOID* pImageHeaderHash;
union {
DWORD TracingFlags;
struct {
QWORD HeapTracingEnabled:1;
QWORD CritSecTracingEnabled:1;
QWORD LibLoaderTracingEnabled:1;
QWORD SpareTracingBits:29;
};
};
QWORD CsrServerReadOnlySharedMemoryBase;
DWORD TppWorkerpListLock;
_LIST_ENTRY TppWorkerpList;
VOID* WaitOnAddressHashTable[128];
};

Debugging

Delphi异常0EEDFADE

0EEDFADE是Delphi内部异常代码,该异常通常有7个参数,我们用的上的是第二个参数,这个参数指向的是Exception的对象,通过这个对象,我们就可以查出异常的一些信息。

以Delphi XE2为例,Class name的偏移为(不同的版本偏移有所不同):

1
2
x86_vmtClassName = -56(0x38);
x64_vmtClassName = -112(0x70);

我们可以用如下命令获取相关信息:

1
2
x86: da poi(poi(exception_object)-38)+1;du /c 100 poi(exception_object+4)  
x64: da poi(poi(exception_object)-70)+1;du /c 100 poi(exception_object+8)

以上命令就能获取异常的类名,而exception_object+sizeof(pointer)则是Exception Message的所在偏移,这是一个unicode string。实际效果如下:

1
2
3
0:002> da poi(poi(003a2800)-38)+1;du /c 100 poi(003a2800 +4)
00b9ec47 "TTransportExceptionUnknown"
00375b8c "ServerTransport.Accept() may not return NULL"

当然,我们也可以设置event filter去截获异常:

1
2
x86: sxe -c "da poi(poi(poi(@ebp+1c))-38)+1;du /c 100 poi(poi(@ebp+1c)+4)" 0EEDFADE
x64: sxe -c "da poi(poi(poi(@rbp+48))-70)+1;du /c 100 poi(poi(@rbp+48)+8)" 0EEDFADE

Debugging

Windows 10设置系统DPI

现在的显示器分辨率越来越高2K,4K甚至5K,而很多程序并不支持这一的高分辨率,所以这些程序在桌面上会显示的很小,好在Windows 8以后的系统中,我们可以设置DPI来放大程序的窗口,如下图所示:

20170309145111

但是,微软并没有把设置DPI的接口文档化。所以我把这个功能逆了一下,还原的代码如下:

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

/*
BOOL ApplyDpiSetting(int val);
val 为DPI要设置的数字,例如0是100%,1是125%,以此类推,注意250%以后是300%。
另外上面的对应关系只是通常情况下的,还有可能有其他对应关系,例如0是300%,-1是250%等等。
具体怎么对应可以通过GetDpiForMonitor函数来获取
*/

typedef struct _SET_DPI {
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
ULONG val;
} SET_DPI;

BOOL ApplyDpiSetting(ULONG val)
{
UINT32 num_of_paths = 0;
UINT32 num_of_modes = 0;
DISPLAYCONFIG_PATH_INFO* display_paths = NULL;
DISPLAYCONFIG_MODE_INFO* display_modes = NULL;
BOOL retval = FALSE;

do
{
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS,
&num_of_paths,
&num_of_modes) != ERROR_SUCCESS) {
break;
}


display_paths = (DISPLAYCONFIG_PATH_INFO*)calloc((int)num_of_paths, sizeof(DISPLAYCONFIG_PATH_INFO));
display_modes = (DISPLAYCONFIG_MODE_INFO*)calloc((int)num_of_modes, sizeof(DISPLAYCONFIG_MODE_INFO));

if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS,
&num_of_paths,
display_paths,
&num_of_modes,
display_modes,
NULL) != ERROR_SUCCESS) {
break;
}

SET_DPI dpi;
dpi.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)0xFFFFFFFC;
dpi.header.size = sizeof(dpi);
dpi.header.adapterId = display_paths[0].sourceInfo.adapterId;
dpi.header.id = display_paths[0].sourceInfo.id;
dpi.val = val;

if (DisplayConfigSetDeviceInfo((DISPLAYCONFIG_DEVICE_INFO_HEADER*)&dpi) == ERROR_SUCCESS) {
retval = TRUE;
}

} while (0);

if (display_paths) {
free(display_paths);
}

if (display_modes) {
free(display_modes);
}

return retval;
}



Tips

让编译器不推荐(deprecate)使用一个函数

在开发一些公共库函数的时候,我们常常会对函数进行改写,这个时候我们会希望使用者用新的函数。为了提醒使用者,我们可以通过将函数声明为deprecated,这样编译器在编译的时候会抛出一个C4995或者C4996的警告。这个警告我们应该也经常看到过,比如使用strcpy,编译器会提示我们使用strcpy_s。

使用这个编译器特性有两种方法:

  1. __declspec(deprecated)
  2. #pragma deprecated

当然我们还可以给警告自定义消息信息

1
__declspec(deprecated("** this is a deprecated function **")) void func2(int) {}

Tips

PDB 下载工具

前段时间微软的符号服务器特别不稳定,Windbg下载符号文件老是失败,所以就专门写了个pdb的下载工具(pdbdownloader)放到Github上。

P.S. 用WPF写界面确实是很有趣

20170103120319

Tips

快速抛出失败的方法 INT 29H

从Windows8开始,Windows设计了一个新的中断,INT 29H,用来快速的抛出失败。在sdk中,他被声明为 __fastfail:

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


#define FAST_FAIL_LEGACY_GS_VIOLATION 0
#define FAST_FAIL_VTGUARD_CHECK_FAILURE 1
#define FAST_FAIL_STACK_COOKIE_CHECK_FAILURE 2
#define FAST_FAIL_CORRUPT_LIST_ENTRY 3
#define FAST_FAIL_INCORRECT_STACK 4
#define FAST_FAIL_INVALID_ARG 5
#define FAST_FAIL_GS_COOKIE_INIT 6
#define FAST_FAIL_FATAL_APP_EXIT 7
#define FAST_FAIL_RANGE_CHECK_FAILURE 8
#define FAST_FAIL_UNSAFE_REGISTRY_ACCESS 9
#define FAST_FAIL_GUARD_ICALL_CHECK_FAILURE 10
#define FAST_FAIL_GUARD_WRITE_CHECK_FAILURE 11
#define FAST_FAIL_INVALID_FIBER_SWITCH 12
#define FAST_FAIL_INVALID_SET_OF_CONTEXT 13
#define FAST_FAIL_INVALID_REFERENCE_COUNT 14
#define FAST_FAIL_INVALID_JUMP_BUFFER 18
#define FAST_FAIL_MRDATA_MODIFIED 19
#define FAST_FAIL_CERTIFICATION_FAILURE 20
#define FAST_FAIL_INVALID_EXCEPTION_CHAIN 21
#define FAST_FAIL_CRYPTO_LIBRARY 22
#define FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT 23
#define FAST_FAIL_INVALID_IMAGE_BASE 24
#define FAST_FAIL_DLOAD_PROTECTION_FAILURE 25
#define FAST_FAIL_UNSAFE_EXTENSION_CALL 26
#define FAST_FAIL_DEPRECATED_SERVICE_INVOKED 27
#define FAST_FAIL_INVALID_BUFFER_ACCESS 28
#define FAST_FAIL_INVALID_BALANCED_TREE 29
#define FAST_FAIL_INVALID_NEXT_THREAD 30
#define FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED 31 // Telemetry, nonfatal
#define FAST_FAIL_APCS_DISABLED 32
#define FAST_FAIL_INVALID_IDLE_STATE 33
#define FAST_FAIL_MRDATA_PROTECTION_FAILURE 34
#define FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION 35
#define FAST_FAIL_INVALID_FAST_FAIL_CODE 0xFFFFFFFF

#if _MSC_VER >= 1610

DECLSPEC_NORETURN
VOID
__fastfail(
_In_ unsigned int Code
);

#pragma intrinsic(__fastfail)

#endif

/*
// 汇编代码为
mov ecx, code
int 29h
*/

在中断代码执行后,操作系统会根据执行代码的环境来做出不同的处理。
如果fastfail发生在Ring0中,操作系统会抛出一个KERNEL_SECURITY_CHECK_FAILURE (0x139)的蓝屏。如果fastfail发生在Ring3,系统会抛出一个第二次机会的不可继续执行的异常,异常代码为0xC0000409,然后走进我们熟悉的Windows Error Reporting(WER)流程。另外,无论__fastfail发生在R0或者R3,如果有调试器正在调试系统或进程,都将得到一次中断到调试器的机会,这让我们能够看清楚具体发生了什么事情。但是正如我上面所说,这个是一个不可继续执行的异常,所以我们不能在调试器里处理了异常后让程序继续向前跑,当然也不能用try和except去捕获异常。

我觉得__fastfail是个非常不错的设计,它让程序可以快速的进入内核异常处理流程,不需要执行额外的用户层的代码,也不需要额外的内存空间,提高了不可恢复的异常处理的性能,更重要的是,简单快速不依赖内存的执行方式也保证了系统的安全。所以在系统的安全检查失败处理中,大量使用了这个方式,减少被攻击的可能性。

最后,如果INT 29H发生在Windows8以下的系统上,内核里会抛出一个常规的UNEXPECTED_KERNEL_MODE_TRAP的蓝屏,而用户层程序会抛出一个ACCESS VIOLATION的异常。

Debugging

windbg的lua脚本扩展luadbg

2012年的时候,我在blog上写到过开发了一个windbg的lua扩展dbglua,当时觉得windbg的原生脚本语法太奇怪了,而且太不容易使用。现在来看,依旧如此,只不过我已经很熟悉这个原生脚本了。而这个lua扩展反倒是没什么用,因为用起来也不太方便,比如访问结构体。

最近无意之中看了一眼pykd,他用重载.操作符的方式访问符号和结构体深深的吸引了我,感觉非常有趣。而python本身依赖比较多,这也促使我拿起之前的代码看了看,并且决定在github上重新建立这个项目叫做luadbg,这次我决定长期维护这个项目,想到新的功能就往里面写,就像我一直维护的0cchext一样。luadbg除了兼容了老dbglua的函数以外,还添加了几个我觉得很方便的类,主要是用重载.操作符的方式来访问模块和结构体的数据,效果如下图所示:

20161116113129

当然,也可以用!luacmd命令进入input模式,从而一条一条的输入语句来测试正确性。

Debugging