Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: sandbox/win/tests/integration_tests/cfi_unittest_exe.cc

Issue 2679793002: [Windows CFG Test] Added unittest for CFG enabling on process. (Closed)
Patch Set: Code review fixes, part 2. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sandbox/win/tests/integration_tests/cfi_unittest.cc ('k') | sandbox/win/tests/readme.txt » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 #include <windows.h>
5
6 #include "base/files/file_path.h"
7 #include "base/scoped_native_library.h"
8
9 namespace {
10
11 //------------------------------------------------------------------------------
12 // PRIVATE: Control Flow Guard test things
13 // - MS binary indirect call tests.
14 //------------------------------------------------------------------------------
15
16 // These structures hold chunks of binary. No padding wanted.
17 #pragma pack(push, 1)
18
19 #if defined(_WIN64)
20
21 const USHORT kCmpRsi = 0x3B48;
22 const BYTE kRax = 0xF0;
23 const USHORT kJzLoc = 0x0074;
24 const ULONG kMovRcxRsiCall = 0xFFCE8B48;
25 const BYTE kPadding = 0x00;
26 const ULONG kMovEcxEbxCallRsi = 0xD6FFCB8B;
27
28 const ULONG kMovRcxRsiAdd = 0x48CE8B48;
29 const ULONG kRcx10hNop = 0x9010C183;
30
31 // Sequence of bytes in GetSystemMetrics.
32 struct Signature {
33 // This struct contains roughly the following code:
34 // cmp rsi, rax
35 // jz jmp_addr
36 // mov rcx, rsi
37 // call ULONG_addr <-- CFG check function
38 // mov ecx, ebx
39 // call rsi <-- Actual call to GetSystemMetrics
40
41 // Patch will start here and be 8 bytes.
42 USHORT cmp_rsi; // = 48 3B
43 BYTE rax; // = F0
44 USHORT jz_loc; // = 74 XX
45 ULONG mov_rcx_rsi_call; // = 48 8B CE FF
46 ULONG guard_check_icall_fptr; // = XX XX XX XX
47 BYTE padding; // = 00
48 ULONG mov_ecx_ebx_call_rsi; // = 8B CB FF D6
49 };
50
51 struct Patch {
52 // Just add 16 to the existing function address.
53 // This ensures a 16-byte aligned address that is
54 // definitely not a valid function address.
55 // mov rcx, rsi = 48 4B CE
56 // add rcx, 10h = 48 83 C1 10
57 // nop = 90
58 ULONG first_four_bytes; // = 48 4B CE 48
59 ULONG second_four_bytes; // = 83 C1 10 90
60 };
61
62 #else // x86
63
64 const USHORT kCmpEbx = 0x0081;
65 const USHORT kJzLoc = 0x0074;
66 const ULONG kMovEcxEbxCall = 0x15FFCB8B;
67 const USHORT kCallEbx = 0xD3FF;
68
69 const ULONG kMovEcxEbxAddEcx = 0xC183CB8B;
70 const ULONG k16Nops = 0x90909010;
71 const USHORT kTwoNops = 0x9090;
72
73 // Sequence of bytes in GetSystemMetrics, x86.
74 struct Signature {
75 // This struct contains roughly the following code:
76 // cmp ebx, offset
77 // jz jmp_addr
78 // mov ecx, ebx
79 // call ULONG_addr <-- CFG check function
80 // call ebx <-- Actual call to GetSystemMetrics
81
82 // Patch will start here and be 10 bytes.
83 USHORT cmp_ebx; // = 81 XX
84 ULONG addr; // = XX XX XX XX
85 USHORT jz_loc; // = 74 XX
86 ULONG mov_ecx_ebx_call; // = 8B CB FF 15
87 ULONG guard_check_icall_fptr; // = XX XX XX XX
88 USHORT call_ebx; // = FF D3
89 };
90
91 struct Patch {
92 // Just add 16 to the existing function address.
93 // This ensures a 16-byte aligned address that is
94 // definitely not a valid function address.
95 // mov ecx, ebx = 8B CB
96 // add ecx, 10h = 83 C1 10 90
97 // nop = 90 90 90 90
98 ULONG first_four_bytes; // = 8B CB 83 C1
99 ULONG second_four_bytes; // = 10 90 90 90
100 USHORT last_two_bytes; // = 90 90
101 };
102
103 #endif // _WIN64
104
105 #pragma pack(pop)
106 //------------------------------------------------------------------------------
107
108 // - Search binary starting at |address_start| for a matching chunk of
109 // |binary_to_find|, of size |size_to_match|.
110 // - A byte of value 0 in |binary_to_find|, is a wildcard for anything.
111 // - Give a |max_distance| to find the chunk within, before failing.
112 // - If return value is true, |out_offset| will hold the offset from
113 // |address_start| that the match starts.
114 bool FindBinary(BYTE* binary_to_find,
115 DWORD size_to_match,
116 BYTE* address_start,
117 DWORD max_distance,
118 DWORD* out_offset) {
119 assert(size_to_match <= max_distance);
120 assert(size_to_match > 0);
121
122 BYTE* max_byte = address_start + max_distance - size_to_match;
123 BYTE* temp_ptr = address_start;
124 // Yes, it's a double while loop.
125 while (temp_ptr <= max_byte) {
126 size_t i = 0;
127 // 0 is a wildcard match.
128 while (binary_to_find[i] == 0 || temp_ptr[i] == binary_to_find[i]) {
129 // Check if this is the last byte.
130 if (i == size_to_match - 1) {
131 *out_offset = temp_ptr - address_start;
132 return true;
133 }
134 ++i;
135 }
136 ++temp_ptr;
137 }
138
139 return false;
140 }
141
142 // - Will write patch starting at |start_addr| with the patch for x86
143 // or x64.
144 // - This function is only for writes within this process.
145 bool DoPatch(BYTE* start_addr) {
146 Patch patch = {};
147 #if defined(_WIN64)
148 patch.first_four_bytes = kMovRcxRsiAdd;
149 patch.second_four_bytes = kRcx10hNop;
150 #else // x86
151 patch.first_four_bytes = kMovEcxEbxAddEcx;
152 patch.second_four_bytes = k16Nops;
153 patch.last_two_bytes = kTwoNops;
154 #endif // _WIN64
155
156 DWORD old_protection;
157 // PAGE_WRITECOPY explicitly allows "copy-on-write" behaviour for
158 // system DLL patches.
159 if (!::VirtualProtect(start_addr, sizeof(patch), PAGE_WRITECOPY,
160 &old_protection))
161 return false;
162
163 ::memcpy(start_addr, &patch, sizeof(patch));
164 ::VirtualProtect(start_addr, sizeof(patch), old_protection, &old_protection);
165
166 return true;
167 }
168
169 // - Find the offset from |start| that the x86 or x64 signature starts.
170 bool FindSignature(BYTE* start, DWORD* offset_found) {
171 Signature signature = {};
172 #if defined(_WIN64)
173 signature.cmp_rsi = kCmpRsi;
174 signature.rax = kRax;
175 signature.jz_loc = kJzLoc;
176 signature.mov_rcx_rsi_call = kMovRcxRsiCall;
177 signature.padding = kPadding;
178 signature.mov_ecx_ebx_call_rsi = kMovEcxEbxCallRsi;
179
180 // This is far enough into GetSystemMetrics that the signature should
181 // have been found. See disassembly.
182 DWORD max_area = 0xB0;
183 #else // x86
184 signature.cmp_ebx = kCmpEbx;
185 signature.jz_loc = kJzLoc;
186 signature.mov_ecx_ebx_call = kMovEcxEbxCall;
187 signature.call_ebx = kCallEbx;
188
189 // This is far enough into GetSystemMetrics x86 that the signature should
190 // have been found. See disassembly.
191 DWORD max_area = 0xD9;
192 #endif // _WIN64
193 if (!FindBinary(reinterpret_cast<BYTE*>(&signature), sizeof(signature), start,
194 max_area, offset_found))
195 return false;
196
197 return true;
198 }
199
200 // - This function tests for CFG in MS system binaries, in process.
201 //
202 // A few words about the patching for this test:
203 //
204 // - In both x86 and x64, the function FindSignature() will scan
205 // GetSystemMetrics in user32.dll, to find the ideal place chosen
206 // for this test. It's a spot where a CFG check was compiled into
207 // a Microsoft system DLL. For more visualization, open user32.dll
208 // in IDA to follow along (especially if planning to change this test).
209 //
210 // - The CFG security check basically calls __guard_check_icall_fptr, with
211 // the function address about to be called as the argument in EAX/RAX reg.
212 // If the address is not in the process' "valid indirect call bitmap", a
213 // CFG exception will be thrown.
214 //
215 // - The DoPatch() function then overwrites with a small, custom change. This
216 // change will simply add 16 (0x10) to the real function address in EAX/RAX
217 // about to be checked. This will maintain the 16-byte alignment required in
218 // a target address by CFG, but also ensure that it fails the check.
219 //
220 // The whole purpose of this unittest is to ensure that a failed CFG check in
221 // a Microsoft binary results in an exception. If CFG is not properly
222 // enabled for a process, no exception will be thrown.
223 // This test EXE is built with
224 // configs += [ "//build/config/win:win_msvc_cfg" ]
225 // which should result in CFG enabled on the process.
226 //
227 // - The patches (x86 or x64) were carefully constructed to be valid and not
228 // mess up the executing instructions. Need to ensure that the CFG check
229 // fully happens, and that nothing else goes wrong before OR AFTER that
230 // point. The only exception expected is a very intentional one.
231 // **The patches also allow the call to GetSystemMetrics to SUCCEED if CFG is
232 // NOT enabled for the process! This makes for very clear behaviour.
233 void TestMsIndirect() {
234 base::ScopedNativeLibrary user32(base::FilePath(L"user32.dll"));
235 if (!user32.is_valid())
236 _exit(1);
237
238 using GetSystemMetricsFunction = decltype(&::GetSystemMetrics);
239 GetSystemMetricsFunction get_system_metrics =
240 reinterpret_cast<GetSystemMetricsFunction>(
241 user32.GetFunctionPointer("GetSystemMetrics"));
242 if (!get_system_metrics)
243 _exit(2);
244
245 // Sanity check the function works fine pre-patch. Tests should only be
246 // running from normal boot (0).
247 if (0 != get_system_metrics(SM_CLEANBOOT))
248 _exit(3);
249
250 BYTE* target = reinterpret_cast<BYTE*>(get_system_metrics);
251 DWORD offset = 0;
252 if (!FindSignature(target, &offset))
253 _exit(4);
254
255 // Now patch the function. Don't bother saving original code,
256 // as this process will end very soon.
257 if (!DoPatch(target + offset))
258 _exit(5);
259
260 // Call the patched function!
261 get_system_metrics(SM_CLEANBOOT);
262 }
263
264 } // namespace
265
266 //------------------------------------------------------------------------------
267 // PUBLIC
268 //------------------------------------------------------------------------------
269
270 // Good ol' main.
271 // - Exe exits with non-zero return codes for unexpected errors.
272 // - Return code of zero indicates no issues at all.
273 // - Else, a CFG exception will result in the process being destroyed.
274 int main(int argc, char** argv) {
275 if (argc != 2)
276 _exit(-1);
277
278 const char* arg = argv[1];
279
280 int iarg = ::atoi(arg);
281 if (!iarg)
282 _exit(-1);
283
284 switch (iarg) {
285 // kSysDllTest
286 case 1:
287 TestMsIndirect();
288 break;
289 // Unsupported argument.
290 default:
291 _exit(-1);
292 }
293
294 return 0;
295 }
OLDNEW
« no previous file with comments | « sandbox/win/tests/integration_tests/cfi_unittest.cc ('k') | sandbox/win/tests/readme.txt » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698