Chromium Code Reviews| 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 |