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 #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 } |
OLD | NEW |