给程序内部菜单增加指定的explorer菜单

为了将explorer的右键菜单项的某个菜单增加到我们程序内部的菜单,我们需要做以下几件事情:

  1. 获得指定文件的IShellFolder
  2. 获得指定文件的IContextMenu
  3. 创建菜单A,并且把IContextMenu的内容填充到菜单A
  4. 查询菜单A,找到我们想要的菜单项
  5. 取出我们想要的菜单项的内容,填充到我们想要真正弹出的菜单B
  6. 弹出菜单B
  7. 用IContextMenu响应用户对菜单的选择

代码如下:

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
CComPtr<IShellFolder> GetParentFolder(LPCWSTR szFolder)
{
CComPtr<IShellFolder> pDesktop;
SHGetDesktopFolder(&pDesktop);
if (NULL == pDesktop)
{
return NULL;
}

ULONG pchEaten = 0;
LPITEMIDLIST pidl = NULL;
DWORD dwAttributes = 0;
HRESULT hr = pDesktop->ParseDisplayName(NULL, NULL, (LPTSTR)szFolder, NULL, &pidl, NULL);
if (S_OK != hr)
{
return NULL;
}

CComPtr<IShellFolder> pParentFolder = NULL;
hr = pDesktop->BindToObject(pidl, NULL, IID_IShellFolder, (void**)&pParentFolder);
if (S_OK != hr)
{
CoTaskMemFree(pidl);
return NULL;
}

CoTaskMemFree(pidl);

return pParentFolder;
}

std::wstring GetDirectory(LPCWSTR szFile)
{
WCHAR szDrive[_MAX_DRIVE];
WCHAR szDir[_MAX_DIR];
WCHAR szFName[_MAX_FNAME];
WCHAR szExt[_MAX_EXT];
_wsplitpath_s(szFile, szDrive, szDir, szFName, szExt);

std::wstring strResult = szDrive;
strResult += szDir;
return strResult;
}

std::wstring GetFileNameWithExt(LPCWSTR szFile)
{
WCHAR szDrive[_MAX_DRIVE];
WCHAR szDir[_MAX_DIR];
WCHAR szFName[_MAX_FNAME];
WCHAR szExt[_MAX_EXT];
_wsplitpath_s(szFile, szDrive, szDir, szFName, szExt);

std::wstring strResult = szFName;
strResult += szExt;
return strResult;
}
void qtmenutest::ShowContextMenu(const QPoint &pos)
{
m_pContextMenu.Release();
QMenu contextMenu(tr("Context menu"), this);

std::wstring strFilePath = L"d:\\1.jpg";
CComPtr<IShellFolder> pParentFolder = GetParentFolder(GetDirectory(strFilePath.c_str()).c_str());
if (NULL == pParentFolder)
{
return;
}

std::wstring strFile = GetFileNameWithExt(strFilePath.c_str());
ULONG pchEaten = 0;
LPITEMIDLIST pidl = NULL;
DWORD dwAttributes = 0;
HRESULT hr = pParentFolder->ParseDisplayName(WId(), NULL, (LPWSTR)strFile.c_str(), &pchEaten, &pidl, &dwAttributes);
if (S_OK != hr)
{
return;
}


UINT refReversed = 0;
hr = pParentFolder->GetUIObjectOf(WId(), 1, (LPCITEMIDLIST *)&pidl, IID_IContextMenu, &refReversed, (void **)&m_pContextMenu);
if (S_OK != hr)
{
CoTaskMemFree(pidl);
return;
}

HMENU hMenu = CreatePopupMenu();
if (hMenu == NULL) {
CoTaskMemFree(pidl);
return;
}

m_pContextMenu->QueryContextMenu(hMenu, 0, 100, 200, CMF_EXPLORE | CMF_NORMAL);
int nMenuCount = GetMenuItemCount(hMenu);
HMENU hSubMenu = NULL;
WCHAR szMenuText[MAX_PATH];
for (int i = 0; i < nMenuCount; i++) {
GetMenuStringW(hMenu, i, szMenuText, MAX_PATH, MF_BYPOSITION);
if (wcsstr(szMenuText, L"some_ui_text")) {
hSubMenu = GetSubMenu(hMenu, i);
break;
}
}

QMenu *subMenu = contextMenu.addMenu(QString::fromWCharArray(szMenuText));
int nSubMenuCount = GetMenuItemCount(hSubMenu);
for (int i = 0; i < nSubMenuCount; i++) {
GetMenuStringW(hSubMenu, i, szMenuText, MAX_PATH, MF_BYPOSITION);
int nCmdId = GetMenuItemID(hSubMenu, i);
QAction *curAct = subMenu->addAction(QString::fromWCharArray(szMenuText));
if (curAct) {
curAct->setData(nCmdId);
}
}

QAction *selAct = contextMenu.exec(mapToGlobal(pos));
if (selAct) {
int nSelId = selAct->data().toInt();
if (nSelId) {
CMINVOKECOMMANDINFO info = {0};
info.cbSize = sizeof(CMINVOKECOMMANDINFOEX);
info.lpVerb = MAKEINTRESOURCEA(nSelId - 100);
m_pContextMenu->InvokeCommand(&info);
}
}

DestroyMenu(hMenu);
CoTaskMemFree(pidl);
return;
}

Tips

0cchext插件更新 1.0.19.1

最近把windbg插件0cchext升级到1.0.19.1,完善了autocmd命令,并且增加了accessmask,oledata和cppexcr命令。

autocmd更新:

现在支持全局自动运行命令,区分应用层调试和内核调试,并且区分了普通调试和DUMP分析,配置文件依然是插件同目录下的autocmd.ini。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[all]
? 88 * 66

[kernel]
!process 0 0 explorer.exe

[kernel dump]
!analyze -v

[notepad.exe]
.sympath+ c:\notepad_pdb
~*k

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

[calc.exe dump]
.excr

在[all]区间的命令,会在所有情况下执行;[kernel]区间的命令会在内核调试的情况下执行;[kernel dump]区间的命令会在内核调试dump的情况下执行;[app.exe]区间是在调试某exe的时候执行;最后[app.exe dump]命令会在调试指定exe的dump的时候执行。

accessmask命令:

这个命令很简单,就是查询权限标志的,例如

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
0:000> !accessmask process 0x1fffff
Access mask: 0x1fffff

Generic rights:
STANDARD_RIGHTS_READ (0x20000)
STANDARD_RIGHTS_WRITE (0x20000)
STANDARD_RIGHTS_EXECUTE (0x20000)
STANDARD_RIGHTS_REQUIRED (0xf0000)
STANDARD_RIGHTS_ALL (0x1f0000)
READ_CONTROL (0x20000)
DELETE (0x10000)
SYNCHRONIZE (0x100000)
WRITE_DAC (0x40000)
WRITE_OWNER (0x80000)

Specific rights:
PROCESS_QUERY_LIMITED_INFORMATION (0x1000)
PROCESS_SUSPEND_RESUME (0x800)
PROCESS_QUERY_INFORMATION (0x400)
PROCESS_SET_INFORMATION (0x200)
PROCESS_SET_QUOTA (0x100)
PROCESS_CREATE_PROCESS (0x80)
PROCESS_DUP_HANDLE (0x40)
PROCESS_VM_WRITE (0x20)
PROCESS_VM_READ (0x10)
PROCESS_VM_OPERATION (0x8)
PROCESS_CREATE_THREAD (0x2)
PROCESS_TERMINATE (0x1)
PROCESS_ALL_ACCESS (0x1fffff)

其中第一个参数是对象类型,比如process,thread,file;第二个参数则是具体要查询的值。

oledata命令:

这个命令是方便我们当前线程查询com和ole相关结构的命令,不需要参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0:000> !oledata
dt combase!tagSOleTlsData 0x0000019370ad0360
dx (combase!tagSOleTlsData *)0x0000019370ad0360
0:000> dt combase!tagSOleTlsData 0x0000019370ad0360
+0x000 pvThreadBase : (null)
+0x008 pSmAllocator : (null)
+0x010 dwApartmentID : 0x1e3d4
+0x014 dwFlags : 0x81
+0x018 TlsMapIndex : 0n0
+0x020 ppTlsSlot : 0x00000018`66fc9758 -> 0x00000193`70ad0360 Void
+0x028 cComInits : 3
+0x02c cOleInits : 0
+0x030 cCalls : 0
...

另外可以看调试COM的一个tip来简单了解以下这个命令的用途

cppexcrname命令:

这个命令用于查询C++ 异常名

1
2
3
4
5
6
7
8
9
10
0:000> .exr -1
ExceptionAddress: 74e61812 (KERNELBASE!RaiseException+0x00000062)
ExceptionCode: e06d7363 (C++ EH exception)
ExceptionFlags: 00000001
NumberParameters: 3
Parameter[0]: 19930520
Parameter[1]: 006ff46c
Parameter[2]: 00372294
0:000> !cppexcrname
Exception name: [email protected]@@

这个的详情可以参考C++异常的参数分析(0xE06D7363)

Tips

不要使用GetFullPathName获得相对路径的全路径

上周末在网上闲逛,看到一篇介绍Path*相关API的文章,发现文章中推荐了GetFullPathName这个API,因为他能方便的获得相对路径对于当前工作目录的全路径。然而,我对于这个推荐API是坚决反对的。如果所有工程代码都是一个人开发,这种情况下调用这个API,我认为尚可理解,但是如果对于多人维护的大型工程,就不要使用它了。原因很简单,这个API是根据当前工作目录去确定绝对路径的,然而你无法确定当前工作目录是否是你认为的工作目录。即使在调用GetFullPathName之前,检查了这个路径,我认为也是不可信的。因为在检查过后,也有可能在其他线程中改变当前工作目录,要知道这个变量是全局唯一的。

所以,以下代码是不可取的:

1
2
3
4
5
WCHAR lpBuffer[MAX_PATH];
LPWSTR lpFname = NULL;

SetCurrentDirectoryW(L"C:\\some\\thing\\dir");
GetFullPathNameW(L"..\\other\\dir", MAX_PATH, lpBuffer, &lpFname);

其实微软为我们提供挺好的API来代替他,比如PathCanonicalize以及PathCombine(实际上有更安全的API,比如 PathCchCanonicalize和PathCchCombine,只不过需要高版本的系统)。

所以,以下代码是正确的:

1
2
3
4
WCHAR lpBuffer[MAX_PATH];
WCHAR lpSrc[MAX_PATH] = L"C:\\some\\thing\\dir";
wcscat_s(lpSrc, L"..\\other\\dir");
PathCanonicalizeW(lpBuffer, lpSrc);
1
2
WCHAR lpBuffer[MAX_PATH];
PathCombineW(lpBuffer, L"C:\\some\\thing\\dir", L"..\\other\\dir");

最后在介绍一个实用的函数:PathCompactPath,这个函数把路径缩写为指定的像素长度:

1
2
3
4
WCHAR buffer[MAX_PATH] = L"C:\\some\\thing\\very\\long\\long\\long\\long\\long\\path";
PathCompactPathW(NULL, buffer, 200);

// buffer="C:\some\thing\very...\path"

Tips

关于TokenLinkedToken的一点记录2

内核原理

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
0: kd> !process 0 1 explorer.exe
PROCESS ffffc306d0c34580
SessionId: 11 Cid: 44e0 Peb: 006fc000 ParentCid: 5758
DirBase: 2b1d00002 ObjectTable: ffff8b8e568c7040 HandleCount: 29648.
Image: explorer.exe
VadRoot ffffc306ddf5fca0 Vads 1625 Clone 0 Private 403154. Modified 771103. Locked 50.
DeviceMap ffff8b8e3147ed30
Token ffff8b8e4937f940
ElapsedTime 06:29:46.426
UserTime 00:00:48.921
KernelTime 00:00:53.250
QuotaPoolUsage[PagedPool] 2123416
QuotaPoolUsage[NonPagedPool] 225280
Working Set Sizes (now,min,max) (56547, 50, 345) (226188KB, 200KB, 1380KB)
PeakWorkingSetSize 462611
VirtualSize 2103798 Mb
PeakVirtualSize 2104556 Mb
PageFaultCount 2358568
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 426963

0: kd> !token ffff8b8e4937f940
_TOKEN 0xffff8b8e4937f940
TS Session ID: 0xb
User: S-1-5-21-3854333306-943506906-3328512208-1001
User Groups:
00 S-1-5-21-3854333306-943506906-3328512208-513
Attributes - Mandatory Default Enabled
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-114
Attributes - DenyOnly
03 S-1-5-21-3854333306-943506906-3328512208-1002
Attributes - Mandatory Default Enabled
04 S-1-5-32-544
Attributes - DenyOnly
05 S-1-5-32-559
Attributes - Mandatory Default Enabled
06 S-1-5-32-545
Attributes - Mandatory Default Enabled
07 S-1-5-4
Attributes - Mandatory Default Enabled
08 S-1-2-1
Attributes - Mandatory Default Enabled
09 S-1-5-11
Attributes - Mandatory Default Enabled
10 S-1-5-15
Attributes - Mandatory Default Enabled
11 S-1-5-113
Attributes - Mandatory Default Enabled
12 S-1-5-5-0-4234506195
Attributes - Mandatory Default Enabled LogonId
13 S-1-2-0
Attributes - Mandatory Default Enabled
14 S-1-5-64-10
Attributes - Mandatory Default Enabled
15 S-1-16-8192
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-3854333306-943506906-3328512208-513
Privs:
19 0x000000013 SeShutdownPrivilege Attributes - Enabled
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default
25 0x000000019 SeUndockPrivilege Attributes -
33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes -
34 0x000000022 SeTimeZonePrivilege Attributes -
Authentication ID: (0,fc65706c)
Impersonation Level: Anonymous
TokenType: Primary
Source: User32 TokenFlags: 0x2a00 ( Token in use )
Token ID: fc664685 ParentToken ID: fc65706f
Modified ID: (0, fe4096af)
RestrictedSidCount: 0 RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 3e7
PackageSid: (null)
CapabilityCount: 0 Capabilities: 0x0000000000000000
LowboxNumberEntry: 0x0000000000000000
Security Attributes:


0: kd> dt _TOKEN 0xffff8b8e4937f940
nt!_TOKEN
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x7fffffff`ffffffff
+0x030 TokenLock : 0xffffc306`c7b5dd40 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
+0x058 AuditPolicy : _SEP_AUDIT_POLICY
+0x078 SessionId : 0xb
+0x07c UserAndGroupCount : 0x11
+0x080 RestrictedSidCount : 0
+0x084 VariableLength : 0x228
+0x088 DynamicCharged : 0x1000
+0x08c DynamicAvailable : 0
+0x090 DefaultOwnerIndex : 0
+0x098 UserAndGroups : 0xffff8b8e`4937fdd0 _SID_AND_ATTRIBUTES
+0x0a0 RestrictedSids : (null)
+0x0a8 PrimaryGroup : 0xffff8b8e`2b2a3b10 Void
+0x0b0 DynamicPart : 0xffff8b8e`2b2a3b10 -> 0x501
+0x0b8 DefaultDacl : 0xffff8b8e`2b2a3b2c _ACL
+0x0c0 TokenType : 1 ( TokenPrimary )
+0x0c4 ImpersonationLevel : 0 ( SecurityAnonymous )
+0x0c8 TokenFlags : 0x2a00
+0x0cc TokenInUse : 0x1 ''
+0x0d0 IntegrityLevelIndex : 0x10
+0x0d4 MandatoryPolicy : 3
+0x0d8 LogonSession : 0xffff8b8e`143fc870 _SEP_LOGON_SESSION_REFERENCES
+0x0e0 OriginatingLogonSession : _LUID
+0x0e8 SidHash : _SID_AND_ATTRIBUTES_HASH
+0x1f8 RestrictedSidHash : _SID_AND_ATTRIBUTES_HASH
+0x308 pSecurityAttributes : 0xffff8b8e`0c3b7f30 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
+0x310 Package : (null)
+0x318 Capabilities : (null)
+0x320 CapabilityCount : 0
+0x328 CapabilitiesHash : _SID_AND_ATTRIBUTES_HASH
+0x438 LowboxNumberEntry : (null)
+0x440 LowboxHandlesEntry : (null)
+0x448 pClaimAttributes : (null)
+0x450 TrustLevelSid : (null)
+0x458 TrustLinkedToken : (null)
+0x460 IntegrityLevelSidValue : (null)
+0x468 TokenSidValues : (null)
+0x470 IndexEntry : 0xffff8b8e`349cd270 _SEP_LUID_TO_INDEX_MAP_ENTRY
+0x478 DiagnosticInfo : (null)
+0x480 BnoIsolationHandlesEntry : (null)
+0x488 SessionObject : 0xffffc306`cd464140 Void
+0x490 VariablePart : 0xffff8b8e`4937fee0

0: kd> dt 0xffff8b8e`143fc870 _SEP_LOGON_SESSION_REFERENCES
nt!_SEP_LOGON_SESSION_REFERENCES
+0x000 Next : (null)
+0x008 LogonId : _LUID
+0x010 BuddyLogonId : _LUID
+0x018 ReferenceCount : 0n1799
+0x020 Flags : 0xd
+0x028 pDeviceMap : 0xffff8b8e`3147ed30 _DEVICE_MAP
+0x030 Token : 0xffff8b8e`20c65060 Void
+0x038 AccountName : _UNICODE_STRING "win"
+0x048 AuthorityName : _UNICODE_STRING "DESKTOP-GJGV2E2"
+0x058 CachedHandlesTable : _SEP_CACHED_HANDLES_TABLE
+0x068 SharedDataLock : _EX_PUSH_LOCK
+0x070 SharedClaimAttributes : (null)
+0x078 SharedSidValues : (null)
+0x080 RevocationBlock : _OB_HANDLE_REVOCATION_BLOCK
+0x0a0 ServerSilo : (null)
+0x0a8 SiblingAuthId : _LUID
+0x0b0 TokenList : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]


0: kd> dt 0xffff8b8e`143fc870 _SEP_LOGON_SESSION_REFERENCES BuddyLogonId.
nt!_SEP_LOGON_SESSION_REFERENCES
+0x010 BuddyLogonId :
+0x000 LowPart : 0xfc657047
+0x004 HighPart : 0n0

0: kd> ? 0xfc657047&0xf
Evaluate expression: 7 = 00000000`00000007

0: kd> dq nt!SepLogonSessions L1
fffff802`45a744a0 ffff8b8e`0b0020d0
0: kd> dq ffff8b8e`0b0020d0+8*7 L1
ffff8b8e`0b002108 ffff8b8e`367ef010

0: kd> dt ffff8b8e`367ef010 _SEP_LOGON_SESSION_REFERENCES
nt!_SEP_LOGON_SESSION_REFERENCES
+0x000 Next : 0xffff8b8e`593ba230 _SEP_LOGON_SESSION_REFERENCES
+0x008 LogonId : _LUID
+0x010 BuddyLogonId : _LUID
+0x018 ReferenceCount : 0n56
+0x020 Flags : 0xa
+0x028 pDeviceMap : 0xffff8b8e`0e5e0890 _DEVICE_MAP
+0x030 Token : 0xffff8b8e`15b56940 Void
+0x038 AccountName : _UNICODE_STRING "win"
+0x048 AuthorityName : _UNICODE_STRING "DESKTOP-GJGV2E2"
+0x058 CachedHandlesTable : _SEP_CACHED_HANDLES_TABLE
+0x068 SharedDataLock : _EX_PUSH_LOCK
+0x070 SharedClaimAttributes : (null)
+0x078 SharedSidValues : (null)
+0x080 RevocationBlock : _OB_HANDLE_REVOCATION_BLOCK
+0x0a0 ServerSilo : (null)
+0x0a8 SiblingAuthId : _LUID
+0x0b0 TokenList : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]

0: kd> !token 0xffff8b8e`15b56940
_TOKEN 0xffff8b8e15b56940
TS Session ID: 0xb
User: S-1-5-21-3854333306-943506906-3328512208-1001
User Groups:
00 S-1-5-21-3854333306-943506906-3328512208-513
Attributes - Mandatory Default Enabled
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-114
Attributes - Mandatory Default Enabled
03 S-1-5-21-3854333306-943506906-3328512208-1002
Attributes - Mandatory Default Enabled
04 S-1-5-32-544
Attributes - Mandatory Default Enabled Owner
05 S-1-5-32-559
Attributes - Mandatory Default Enabled
06 S-1-5-32-545
Attributes - Mandatory Default Enabled
07 S-1-5-4
Attributes - Mandatory Default Enabled
08 S-1-2-1
Attributes - Mandatory Default Enabled
09 S-1-5-11
Attributes - Mandatory Default Enabled
10 S-1-5-15
Attributes - Mandatory Default Enabled
11 S-1-5-113
Attributes - Mandatory Default Enabled
12 S-1-5-5-0-4234506195
Attributes - Mandatory Default Enabled LogonId
13 S-1-2-0
Attributes - Mandatory Default Enabled
14 S-1-5-64-10
Attributes - Mandatory Default Enabled
15 S-1-16-12288
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-3854333306-943506906-3328512208-513
Privs:
05 0x000000005 SeIncreaseQuotaPrivilege Attributes -
08 0x000000008 SeSecurityPrivilege Attributes -
09 0x000000009 SeTakeOwnershipPrivilege Attributes -
10 0x00000000a SeLoadDriverPrivilege Attributes -
11 0x00000000b SeSystemProfilePrivilege Attributes -
12 0x00000000c SeSystemtimePrivilege Attributes -
13 0x00000000d SeProfileSingleProcessPrivilege Attributes -
14 0x00000000e SeIncreaseBasePriorityPrivilege Attributes -
15 0x00000000f SeCreatePagefilePrivilege Attributes -
17 0x000000011 SeBackupPrivilege Attributes -
18 0x000000012 SeRestorePrivilege Attributes -
19 0x000000013 SeShutdownPrivilege Attributes -
20 0x000000014 SeDebugPrivilege Attributes -
22 0x000000016 SeSystemEnvironmentPrivilege Attributes -
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default
24 0x000000018 SeRemoteShutdownPrivilege Attributes -
25 0x000000019 SeUndockPrivilege Attributes -
28 0x00000001c SeManageVolumePrivilege Attributes -
29 0x00000001d SeImpersonatePrivilege Attributes - Enabled Default
30 0x00000001e SeCreateGlobalPrivilege Attributes - Enabled Default
33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes -
34 0x000000022 SeTimeZonePrivilege Attributes -
35 0x000000023 SeCreateSymbolicLinkPrivilege Attributes -
36 0x000000024 SeDelegateSessionUserImpersonatePrivilege Attributes -
Authentication ID: (0,fc657047)
Impersonation Level: Anonymous
TokenType: Primary
Source: User32 TokenFlags: 0x2020 ( Token NOT in use )
Token ID: fc657079 ParentToken ID: 0
Modified ID: (0, fc65706b)
RestrictedSidCount: 0 RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 3e7
PackageSid: (null)
CapabilityCount: 0 Capabilities: 0x0000000000000000
LowboxNumberEntry: 0x0000000000000000
Security Attributes:

Tips

关于TokenLinkedToken的一点记录

我们用GetTokenInformation可以获得一个TokenLinkedToken,简单的说就是要获得与我们进程token关联的token。

接下来就有趣了,如果当你的进程是一个提权的管理员权限的进程,那么你获得的token会是一个标准用户进程的token,也就是一个提权之前进程。那么这有什么用呢?比如我们的子程序需要运行其他开发者开发的插件,而我们不想给予他们过高的权限,那么这个就有用了。当然,如果你更谨慎一些,你希望给予他更低的权限,那就得实用CreateRestrictedToken来创建一个新的token了。

聪明的程序员看到这里肯定就会想,既然管理员权限下的进程获得的TokenLinkedToken是一个标准用户权限的token,那么标准用户权限环境下的进程能不能获得一个管理员权限的TokenLinkedToken呢?没错,答案是可以。更聪明的程序员肯定会惊讶,那这个不是安全漏洞么?答案是并不是,因为虽然可以获得一个管理员权限的token,但是这个token只是一个IDENTIFY level token,这是一个token的_SECURITY_IMPERSONATION_LEVEL,不同的模仿等级,对应于不同的功能。比如SecurityIdentification,这个等级就只能用来查询token的信息。比方说有外部一个进程访问我们的进程,我们可以让他提供token验证其身份。但是外部进程为了防止我们用他的token干坏事,所以只给我们一个IDENTIFY level token,这样一来,我们就只能验证身份而无法做其他事情了。

我们真的没办法通过TokenLinkedToken获得可以使用的管理员身份的token了么?也不是,我们确实有办法获得能够使用的管理员身份的token。但是有个前提,我们的进程必须有SeTcbPrivilege权限。那这不也是个安全漏洞么?不是,因为SeTcbPrivilege是SYSTEM用户的权限,简单的说,这个用户的权限比管理员还要高。那这玩意不是也没什么用么?也有用,当你想在系统服务中启动一个管理员身份的进程的时候,可以先获得标准用户权限的token,然后获得其TokenLinkedToken,最后CreateProcessAsUser来创建进程。

TokenLinkedToken

Tips

tensorflow入门 —— mnist

自从有tensorflow这样的平台工具横空出世,机器学习的代码编写逐渐变的平民化了。我们不需要太多的数学理论知识就能够完成一些机器学习的项目。比如在使用tensorflow的时候,我们只需要定义好损失函数,tensorflow会自动的帮我们完成反向传播改善参数。我们所需要做的就是合理利用tensorflow创建模型。

当然,我并不是在鼓动初学者跳过数学原理部分,而是认为,如果没有数学基础,入门就死磕原理容易产生挫败感导致放弃。这样就不如先接触简单的项目,在有了一定的体会后,再回头去看看数学原理的东西,这样会更容易接受。比如,当能用tensorflow完成对mnist的训练之后,再去理解反向传播这四个公式,应该会更有感觉。

$$\begin{align} \delta^L &= \nabla_a C \odot \sigma'(z^L) \tag{1} \\ \delta^l &= ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^l) \tag{2}\\ \frac{\partial C}{\partial b^l_j} &= \delta^l_j \tag{3}\\ \frac{\partial C}{\partial w^l_{jk}} &= a^{l-1}_k \delta^l_j \tag{4} \end{align}$$

接下来相信看看这份代码

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
# -*- coding: utf-8 -*-
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data

# step1
mnist = input_data.read_data_sets(".", one_hot=True)

# step2
INPUT_PARAMETERS = 784
L1_PARAMETERS = 300
W1 = tf.Variable(tf.truncated_normal([INPUT_PARAMETERS, L1_PARAMETERS], stddev=0.1))
b1 = tf.Variable(tf.truncated_normal([L1_PARAMETERS], stddev=0.1))
W2 = tf.Variable(tf.truncated_normal([L1_PARAMETERS, 10], stddev=0.1))
b2 = tf.Variable(tf.truncated_normal([10], stddev=0.1))

# step3
x = tf.placeholder(tf.float32, [None, INPUT_PARAMETERS])
y_ = tf.placeholder(tf.float32, [None, 10])

# step4
hidden1 = tf.nn.sigmoid(tf.matmul(x, W1) + b1)
y = tf.matmul(hidden1, W2) + b2

# step5
loss = (tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
logits=y, labels=y_)))
train_step = tf.train.GradientDescentOptimizer(0.3).minimize(loss)

# step6
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# step7
loss_array = []
accuracy_array = []
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(10000):
batch_xs, batch_ys = mnist.train.next_batch(100)
_, step_loss = sess.run([train_step, loss], feed_dict={x: batch_xs, y_: batch_ys})
if i % 500 == 0:
step_accuracy = accuracy.eval({x: mnist.test.images, y_: mnist.test.labels})
loss_array.append(step_loss)
accuracy_array.append(step_accuracy)
print(step_loss, step_accuracy)

loss_array.append(step_loss)
accuracy_array.append(step_accuracy)
print(step_loss, step_accuracy)

plt.plot([i*500 for i in range(len(loss_array))], loss_array, 'b-',
[i*500 for i in range(len(accuracy_array))], accuracy_array, 'r-')

step1:read_data_sets是tf提供读取mnist数据的函数,运行时会有函数过时的警告提示,但是不用管它,因为用起来完全没有问题。one_hot参数在这里很重要,他将实际的标签值转化为一个向量,例如,将标签值3,转化成表示0-9是否置位的向量:\( \left(\begin{array}{cccccccccc}0;0;0;1;0;0;0;0;0;0;\end{array}\right)^T \)

step2:创建权重和偏置,创建方法有很多,我用的是期望为0,标准差为0.1的随机分布。

step3:创建输入层和标签的placeholder,大概的意思是占住空间,以方便后续数据喂给模型。

step4:实际上是向前传播 \( z = w^{T}x\,+\,b\),\( a = sigmoid(z)\),其中sigmoid是\( \frac{1}{1+\exp(-z)}\)。这里可能有一个疑问,第二个隐藏层到输出层,没有使用sigmoid函数。原因继续往下看就知道了。

step5:计算损失函数,调用了tf的sigmoid_cross_entropy_with_logits,这个函数把交叉熵和sigmoid的计算放在了一起,所以上面不需要去计算sigmoid了。这样做的好处很明显,就是方便我们修改从最后一个隐藏层到输出层的激活函数,比如将sigmoid_cross_entropy_with_logits替换为softmax_cross_entropy_with_logits,那么我们最后一个激活函数就变成了softmax。最后设置学习率并且把我们的损失函数传给梯度下降类的最小化函数中,tf就会自动的帮我们优化参数,从而最小化损失值了。

step6:使用测试数据去计算模型的识别准确率

step7:最后一步,我们将数据分为小份,随着迭代,逐步喂给模型。然后记录损失和准确率的变化,并做图。

这里我们为了简单,没有使用softmax,dropout和正则化等优化方法,所以识别率只达到了90%,不过作为一个入门来说已经够了。

2018-07-07-tensorflow-mnist

machinelearning

机器学习练习题1

掐指一算,已经有三个月没有更新blog了。因为最近一直在学习机器学习的内容,所以没空也没有新鲜的技术值得写下来分享。还好经过一段时间的积累(学习线性代数和概率论),机器学习这块内容也算是入门了。这是机器学习的第一个习题,线性回归。用最直白的话来说,就是用函数去拟合数据分布,从而达到预测新数据的效果。需要的知识是矩阵的计算,最小二乘法以及求偏微分。

关键的公式只有两个:
$$\begin{align} Cost(\theta) = \frac{1}{2m}\,\sum^{m}_{i=1}\,(h_\theta(x^{i})-y^{i})^2 \tag{1}\\ \theta_{j} = \theta_{j}-\alpha\frac{1}{m}\,\sum^{m}_{i=1}(h_\theta(x^{i})-y^{i})\frac{\partial{h_\theta(x^{i})}}{\partial{\theta_{j}}}\tag{2} \end{align}$$

那么最后需要确定的只剩下一个,我们希望用什么样的曲线去拟合样本,这个就需要经验和尝试了。这里的练习只需要用直线来拟合就足够了: \( h_\theta(x) = \theta^{T}x = \theta_{0}+\theta_{1}x_{1} \) .

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
# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt

data = np.loadtxt('ex1data1.txt', delimiter=',');

# 分离样本数据输入X和输出Y
X = np.concatenate((np.ones((len(data),1)), data[:,0].reshape((len(data),1))), axis=1)
theta = np.random.randn(2,1)
Y = data[:,1].reshape((len(data),1))

# 为了加速计算,把除法优化位乘法
alpha = 0.01
div_m = 1 / len(data);

for loop_count in range(1000):
Y1 = X.dot(theta)
# 根据公式计算损失
cost = ((Y-Y1)**2).sum() * 0.5 * div_m;
print(cost)
# 更新参数
for i in range(2):
theta[i, 0] = theta[i, 0] - alpha * div_m * (np.diagflat(X[:, i]) * (Y1 - Y)).sum()

# 最后把拟合直线画出来
Xl = np.linspace(0, 30, 100)
Yl = Xl * theta[1, 0] + theta[0, 0]

plt.plot(data[:,0],data[:,1],'x', Xl, Yl, 'r')

2018-06-23-machine-learning-ex1

machinelearning

广度遍历删除文件

最近遇到一个要删除文件夹的问题,文件夹内有大量文件和子文件夹,而且结构非常复杂,删除特别慢。于是思考了一下如何能加速文件删除的问题。我看到大部分的实现方法都是深度遍历,即遇到新的文件夹就进入文件夹遍历文件,直到结束后返回上一层继续遍历。实际上这种方法存在一个问题,在我们的硬盘上,文件夹和文件一般是B-Tree分布的,所以同一层文件夹的文件的数据都比较相近,而不同的层的文件夹的文件可能差距比较远,于是深度遍历让硬盘磁头从一个文件夹跨越到另外一个文件夹,磁头花的时间更长了,更何况子文件夹遍历结束,还得从子文件夹在移动回父文件夹。而是用广度遍历就不同,他把当前目录遍历完成后才去遍历另外一个,这样磁头移动的总距离肯定更少,遍历速度当然更快。以下代码我的一个实现:

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

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <vector>
#include <stack>
#include <string>

class AutoFindHandle {
public:
AutoFindHandle(HANDLE find_handle) : find_handle_(find_handle) {}
~AutoFindHandle() {
FindClose(find_handle_);
}

private:
HANDLE find_handle_;
};

BOOL CollectFileAndDir(const std::wstring &dir, std::vector<std::wstring> &file_path, std::vector<std::wstring> &dir_path)
{
HANDLE find_handle;
WIN32_FIND_DATAW data;
std::wstring temp_path;
std::wstring current_dir = dir;
std::stack<std::wstring> temp_dir;

if (current_dir[current_dir.length() - 1] != L'\\') {
current_dir += L"\\";
}

std::wstring path = current_dir + L"*";

find_handle = FindFirstFileW(path.c_str(), &data);
if (find_handle == INVALID_HANDLE_VALUE) {
return TRUE;
}

AutoFindHandle auto_find_handle(find_handle);

if (wcscmp(data.cFileName, L"..") != 0 &&
wcscmp(data.cFileName, L".") != 0) {

temp_path = current_dir;
temp_path += data.cFileName;

if ((data.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) != 0) {
SetFileAttributesW(temp_path.c_str(), FILE_ATTRIBUTE_NORMAL);
}

if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {

temp_dir.push(temp_path);

}
else {

file_path.push_back(temp_path);

}
}

while (FindNextFileW(find_handle, &data)) {

if (wcscmp(data.cFileName, L"..") != 0 &&
wcscmp(data.cFileName, L".") != 0) {

temp_path = current_dir;
temp_path += data.cFileName;

if ((data.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) != 0) {
SetFileAttributesW(temp_path.c_str(), FILE_ATTRIBUTE_NORMAL);
}

if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {

temp_dir.push(temp_path);

}
else {

file_path.push_back(temp_path);

}
}

}

while (!temp_dir.empty()) {

CollectFileAndDir(temp_dir.top(), file_path, dir_path);

temp_dir.pop();
}


dir_path.push_back(dir);
return TRUE;
}


BOOL DeleteFolder(LPCWSTR path)
{
std::vector<std::wstring> file_path;
std::vector<std::wstring> dir_path;
if (!CollectFileAndDir(path, file_path, dir_path)) {
return FALSE;
}


for (std::vector<std::wstring>::iterator it = file_path.begin(); it != file_path.end(); ++it) {
DeleteFileW(it->c_str());
}

for (std::vector<std::wstring>::iterator it = dir_path.begin(); it != dir_path.end(); ++it) {
RemoveDirectoryW(it->c_str());
}

return TRUE;
}

Tips

鸡年总结,狗年计划

虽然没到十五,但是这个年基本上也是过完了。是收心总结鸡年,展望狗年的时候了。

生活方面,鸡年过的不算顺心,尤其是上半年,麻烦事情接二连三,具体也事情也就不想多写了,不愉快的事情就应该被忘记。工作上,虽然没有明显的进步,但也算是得到了不错的回报,内心有愧的拿了一个优秀员工。重点来了,要说说技术方面的事情。去年下半年快到年底的时候,我做了一个决定,学习深度学习相关知识,作为今后转型的砝码。这里不得不说,做这个决定其实已经很晚了,因为已经有很多人早就踏入了这个领域。我总是想,如果能早两年开窍就好了,但是凡是哪有如果呢。更何况就在去年初我还在跟以前同事争论,坚定的认为个人PC行业还是非常宽阔的,但实际上那个想法也只是说自己的一厢情愿而已。下半年某企业找到我说想让我去他们的个人PC业务部门,给的等级还不错,薪资水平也很快给我翻倍了,但是我拒绝了,那个时候我就知道自己内心里已经不看好个人PC,尤其是Windows上面的发展了。于是乎,打算学习更接近未来趋势的一些东西。

当今最火的两个领域,区块链和深度学习,我很快的就选择学习深度学习的相关知识了。原因很简单,深度学习现在已经在很大程度上推进了社会发展。而区块链技术,除了在各种加密币方面非常火之外,貌似还没看到什么用途能促进社会进步,至少没有深度学习那么明显吧。下半年的两三个月里开始慢慢接触了深度学习,当然也包括一些其他的机器学习领域的知识。于是深深的感慨,数学在这些领域的重要性,当然又一次后悔没有学好数学,也不得不吐槽我们的高等数学教育是多么的死板无趣。

今年计划,生活上的事情其实没有什么计划可言,就希望过的更加顺心吧。工作方面,希望能主导一些真正有意义的项目,希望能推进公司往深度学习方面来发展。另外技术方面专注于数学和深度学习。具体的计划就不列了,因为貌似每次列出来也不一定能全部完成 =v=

Tips

ssh的反向连接

家里有个树莓派,基本上用来当了一个微型服务器,24小时跑着。主要用途是在我不在家的时候控制家里网络,充分利用带宽。一直以来,我都是用树莓派把IP更新到DDNS(家里是外网IP),并且在路由器上做端口映射。这样就能在其他地方访问树莓派了。但是不知为何,这段时间无法从外网ping通家里(后来发现是光猫问题,换了个猫就好了),从外围直接联通家里的树莓派这条路被封了,所以就折腾了SSH的反向连接功能。用这个方法也可以克服家里是内网IP的情况,当然前提是我们在外网有一台服务器或者vps。原理上和其他的反向连接是一样的,这里就不讨论原理了,直接介绍方法。

1
# ssh -fCNR remote_port:localhost:local_port [email protected]_addr
1
2
3
4
-f 后台执行ssh指令
-C 允许压缩数据
-N 不执行远程指令
-R 将远程主机(服务器)的某个端口转发到本地端指定机器的指定端口

这条命令的意思是:请将remote_addr机器里对remote_port的连接转发到本地机器的本地端口。举个例子,假设我有一台内网机器A,其ssh端口为6622,另外有一台VPS,地址是VPS_ADDR.com,SSH端口是8822。这个时候我想通过VPS的9922端口去访问树莓派的SSH。那么我需要用命令:

1
# ssh -fCNR 9922:localhost:6622 [email protected]_ADDR.com

当完成了这个命令后,我们就可以在VPS上登陆树莓派了:

1
# ssh -p 9922 [email protected]

解决了这个问题后,我们需要解决另外一个问题。我们每次输入反向连接的命令的时候总需要输入VPS密码,这个非常不利于我们把命令设置为开机启动。为了解决这个问题,首先想到的是sshpass。这个工具可以帮助我们自动输入密码,命令如下:

1
# sshpass -p your_VPS_password ssh -fCNR 9922:localhost:6622 [email protected]_ADDR.com

把命令放到树莓派的开机启动命令后,重启树莓派,成功从VPS连入了树莓派。不过第二天,我又发现了另外一个问题,SSH的反向连接在无人访问的时候会超时,超时后会断开链接。这样明显不符合我想连就连的需求。于是就得上autossh这个工具了,这个工具会守护ssh,让ssh的反向连接保持联通。但是autossh不支持自动输入密码,网上很多解决办法是用证书的方式省去了输入密码的过程,不过我这里提供另外一个方法,使用expect工具。

1
2
3
4
5
6
# expect -c "
spawn autossh -M 11122 -CNR 9922:localhost:6622 [email protected]_ADDR.com -o StrictHostKeyChecking=no
expect \"Password:\"
send \"your_password\r\"
expect eof
"

这个工具可以自动和ssh进行交互,当VPS提示Password:的时候,我们send密码过去就行了,注意使用StrictHostKeyChecking=no来避免Host key的验证提示。把这个命令放在开机启动的时候执行,完美解决了外网访问家里树莓派的问题。

Tips