OLD | NEW |
---|---|
(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 // Tests don't run in a wow64 environment, so fine to compile based on ifdef. | |
Will Harris
2017/02/06 22:33:36
I'm not sure I understand this comment. We have 32
penny
2017/02/07 23:12:56
Done.
| |
20 #if defined(_WIN64) | |
21 | |
22 const USHORT kCmpRsi = 0x3B48; | |
23 const BYTE kRax = 0xF0; | |
24 const USHORT kJzLoc = 0x0074; | |
25 const ULONG kMovRcxRsiCall = 0xFFCE8B48; | |
26 const BYTE kPadding = 0x00; | |
27 const ULONG kMovEcxEbxCallRsi = 0xD6FFCB8B; | |
28 | |
29 const ULONG kMovRcxRsiAdd = 0x48CE8B48; | |
30 const ULONG kRcx10hNop = 0x9010C183; | |
31 | |
32 // Sequence of bytes in GetSystemMetrics. | |
33 struct Signature { | |
34 // This struct contains roughly the following code: | |
35 // cmp rsi, rax | |
36 // jz jmp_addr | |
37 // mov rcx, rsi | |
38 // call ULONG_addr <-- CFG check function | |
39 // mov ecx, ebx | |
40 // call rsi <-- Actual call to GetSystemMetrics | |
41 | |
42 // Patch will start here and be 8 bytes. | |
43 USHORT cmp_rsi; // = 48 3B | |
44 BYTE rax; // = F0 | |
45 USHORT jz_loc; // = 74 XX | |
46 ULONG mov_rcx_rsi_call; // = 48 8B CE FF | |
47 ULONG guard_check_icall_fptr; // = XX XX XX XX | |
48 BYTE padding; // = 00 | |
49 ULONG mov_ecx_ebx_call_rsi; // = 8B CB FF D6 | |
50 }; | |
51 | |
52 struct Patch { | |
53 // Just add 16 to the existing function address. | |
54 // This ensures a 16-byte aligned address that is | |
55 // definitely not a valid function address. | |
56 // mov rcx, rsi = 48 4B CE | |
57 // add rcx, 10h = 48 83 C1 10 | |
58 // nop = 90 | |
59 ULONG first_four_bytes; // = 48 4B CE 48 | |
60 ULONG second_four_bytes; // = 83 C1 10 90 | |
61 }; | |
62 | |
63 #else // x86 | |
64 | |
65 const USHORT kCmpEbx = 0x0081; | |
66 const USHORT kJzLoc = 0x0074; | |
67 const ULONG kMovEcxEbxCall = 0x15FFCB8B; | |
68 const USHORT kCallEbx = 0xD3FF; | |
69 | |
70 const ULONG kMovEcxEbxAddEcx = 0xC183CB8B; | |
71 const ULONG k16Nops = 0x90909010; | |
72 const USHORT kTwoNops = 0x9090; | |
73 | |
74 // Sequence of bytes in GetSystemMetrics, x86. | |
75 struct Signature { | |
76 // This struct contains roughly the following code: | |
77 // cmp ebx, offset | |
78 // jz jmp_addr | |
79 // mov ecx, ebx | |
80 // call ULONG_addr <-- CFG check function | |
81 // call ebx <-- Actual call to GetSystemMetrics | |
82 | |
83 // Patch will start here and be 10 bytes. | |
84 USHORT cmp_ebx; // = 81 XX | |
85 ULONG addr; // = XX XX XX XX | |
86 USHORT jz_loc; // = 74 XX | |
87 ULONG mov_ecx_ebx_call; // = 8B CB FF 15 | |
88 ULONG guard_check_icall_fptr; // = XX XX XX XX | |
89 USHORT call_ebx; // = FF D3 | |
90 }; | |
91 | |
92 struct Patch { | |
93 // Just add 16 to the existing function address. | |
94 // This ensures a 16-byte aligned address that is | |
95 // definitely not a valid function address. | |
96 // mov ecx, ebx = 8B CB | |
97 // add ecx, 10h = 83 C1 10 90 | |
98 // nop = 90 90 90 90 | |
99 ULONG first_four_bytes; // = 8B CB 83 C1 | |
100 ULONG second_four_bytes; // = 10 90 90 90 | |
101 USHORT last_two_bytes; // = 90 90 | |
102 }; | |
103 | |
104 #endif // _WIN64 | |
105 | |
106 #pragma pack(pop) | |
107 //------------------------------------------------------------------------------ | |
108 | |
109 // - Search binary starting at |address_start| for a matching chunk of | |
110 // |binary_to_find|, of size |size_to_match|. | |
111 // - A byte of value 0 in |binary_to_find|, is a wildcard for anything. | |
112 // - Give a |max_distance| to find the chunk within, before failing. | |
113 // - If return value is true, |out_offset| will hold the offset from | |
114 // |address_start| that the match starts. | |
115 bool FindBinary(BYTE* binary_to_find, | |
116 DWORD size_to_match, | |
117 BYTE* address_start, | |
118 DWORD max_distance, | |
119 DWORD* out_offset) { | |
120 assert(size_to_match <= max_distance); | |
121 assert(size_to_match > 0); | |
122 | |
123 BYTE* max_byte = address_start + max_distance - size_to_match; | |
124 BYTE* temp_ptr = address_start; | |
125 // Yes, it's a double while loop. | |
126 while (temp_ptr <= max_byte) { | |
127 size_t i = 0; | |
128 // 0 is a wildcard match. | |
129 while (binary_to_find[i] == 0 || temp_ptr[i] == binary_to_find[i]) { | |
130 // Check if this is the last byte. | |
131 if (i == size_to_match - 1) { | |
132 *out_offset = temp_ptr - address_start; | |
133 return true; | |
134 } | |
135 ++i; | |
136 } | |
137 ++temp_ptr; | |
138 } | |
139 | |
140 return false; | |
141 } | |
142 | |
143 // - Will write patch starting at |start_addr| with the patch for x86 | |
144 // or x64. | |
145 // - This function is only for writes within this process. | |
146 bool DoPatch(BYTE* start_addr) { | |
147 Patch patch = {}; | |
148 #if defined(_WIN64) | |
149 patch.first_four_bytes = kMovRcxRsiAdd; | |
150 patch.second_four_bytes = kRcx10hNop; | |
151 #else // x86 | |
152 patch.first_four_bytes = kMovEcxEbxAddEcx; | |
153 patch.second_four_bytes = k16Nops; | |
154 patch.last_two_bytes = kTwoNops; | |
155 #endif // _WIN64 | |
156 | |
157 DWORD old_protection; | |
158 // PAGE_WRITECOPY explicitly allows "copy-on-write" behaviour for | |
159 // system DLL patches. | |
160 if (!::VirtualProtect(start_addr, sizeof(patch), PAGE_WRITECOPY, | |
161 &old_protection)) | |
162 return false; | |
163 | |
164 ::memcpy(start_addr, &patch, sizeof(patch)); | |
165 ::VirtualProtect(start_addr, sizeof(patch), old_protection, &old_protection); | |
166 | |
167 return true; | |
168 } | |
169 | |
170 // - Find the offset from |start| that the x86 or x64 signature starts. | |
171 bool FindSignature(BYTE* start, DWORD* offset_found) { | |
172 Signature signature = {}; | |
173 #if defined(_WIN64) | |
174 signature.cmp_rsi = kCmpRsi; | |
175 signature.rax = kRax; | |
176 signature.jz_loc = kJzLoc; | |
177 signature.mov_rcx_rsi_call = kMovRcxRsiCall; | |
178 signature.padding = kPadding; | |
179 signature.mov_ecx_ebx_call_rsi = kMovEcxEbxCallRsi; | |
180 | |
181 // This is far enough into GetSystemMetrics that the signature should | |
182 // have been found. See disassembly. | |
183 DWORD max_area = 0xB0; | |
184 #else // x86 | |
185 signature.cmp_ebx = kCmpEbx; | |
186 signature.jz_loc = kJzLoc; | |
187 signature.mov_ecx_ebx_call = kMovEcxEbxCall; | |
188 signature.call_ebx = kCallEbx; | |
189 | |
190 // This is far enough into GetSystemMetrics x86 that the signature should | |
191 // have been found. See disassembly. | |
192 DWORD max_area = 0xD9; | |
193 #endif // _WIN64 | |
194 if (!FindBinary(reinterpret_cast<BYTE*>(&signature), sizeof(signature), start, | |
195 max_area, offset_found)) | |
196 return false; | |
197 | |
198 return true; | |
199 } | |
200 | |
201 // - This function tests for CFG in MS system binaries, in process. | |
202 void TestMsIndirect() { | |
203 base::ScopedNativeLibrary user32(base::FilePath(L"user32.dll")); | |
204 if (!user32.is_valid()) | |
205 _exit(1); | |
206 | |
207 using GetSystemMetricsFunction = decltype(&::GetSystemMetrics); | |
208 GetSystemMetricsFunction get_system_metrics = | |
209 reinterpret_cast<GetSystemMetricsFunction>( | |
210 user32.GetFunctionPointer("GetSystemMetrics")); | |
211 if (!get_system_metrics) | |
212 _exit(2); | |
213 | |
214 // Sanity check the function works fine pre-patch. Tests should only be | |
215 // running from normal boot (0). | |
216 if (0 != get_system_metrics(SM_CLEANBOOT)) | |
217 _exit(3); | |
218 | |
219 BYTE* target = reinterpret_cast<BYTE*>(get_system_metrics); | |
220 DWORD offset = 0; | |
221 if (!FindSignature(target, &offset)) | |
222 _exit(4); | |
223 | |
224 // Now patch the function. Don't bother saving original code, | |
225 // as this process will end very soon. | |
226 if (!DoPatch(target + offset)) | |
227 _exit(5); | |
228 | |
229 // Call the patched function! | |
230 get_system_metrics(SM_CLEANBOOT); | |
231 } | |
232 | |
233 } // namespace | |
234 | |
235 //------------------------------------------------------------------------------ | |
236 // PUBLIC | |
237 //------------------------------------------------------------------------------ | |
238 | |
239 // Good ol' main. | |
240 // - Exe exits with non-zero return codes for unexpected errors. | |
241 // - Return code of zero indicates no issues at all. | |
242 // - Else, a CFG exception will result in the process being destroyed. | |
243 int main(int argc, char** argv) { | |
244 if (argc != 2) | |
245 _exit(-1); | |
246 | |
247 const char* arg = argv[1]; | |
248 | |
249 int iarg = ::atoi(arg); | |
250 if (!iarg) | |
251 _exit(-1); | |
252 | |
253 switch (iarg) { | |
254 // kSysDllTest | |
255 case 1: | |
256 TestMsIndirect(); | |
257 break; | |
258 // Unsupported argument. | |
259 default: | |
260 _exit(-1); | |
261 } | |
262 | |
263 return 0; | |
264 } | |
OLD | NEW |