1 : // Copyright 2015 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/agent/asan/iat_patcher.h"
16 :
17 : #include <vector>
18 :
19 : #include "base/logging.h"
20 : #include "base/win/iat_patch_function.h"
21 : #include "base/win/pe_image.h"
22 : #include "syzygy/agent/asan/scoped_page_protections.h"
23 : #include "syzygy/common/align.h"
24 : #include "syzygy/common/com_utils.h"
25 :
26 : namespace agent {
27 : namespace asan {
28 :
29 : namespace {
30 :
31 : using OnUnprotectCallback = ScopedPageProtections::OnUnprotectCallback;
32 :
33 : PatchResult UpdateImportThunk(volatile PIMAGE_THUNK_DATA iat,
34 i : FunctionPointer function) {
35 : // Writing to an IAT is inherently racy, as there may be other parties also
36 : // writing the same page at the same time. This gets ugly where multiple
37 : // parties mess with page protections, as VirtualProtect causes surprising
38 : // serialization. We therefore proceed with an abundance of caution, by
39 : // running inside an exception handler and using a compare-and-swap to
40 : // detect races on VM operations as well as on assignment.
41 :
42 i : __try {
43 i : DWORD old_fn = iat->u1.Function;
44 i : DWORD new_fn = reinterpret_cast<DWORD>(function);
45 : DWORD prev_fn =
46 i : ::InterlockedCompareExchange(&iat->u1.Function, new_fn, old_fn);
47 : // Check whether we collided on the assignment.
48 i : if (prev_fn != old_fn)
49 i : return PATCH_FAILED_RACY_WRITE;
50 i : } __except(EXCEPTION_EXECUTE_HANDLER) {
51 : // We took an exception, that goes down as failure. This can occur if we
52 : // are racing with another thread in our process to patch the IAT entries
53 : // in the same physical page.
54 i : return PATCH_FAILED_ACCESS_VIOLATION;
55 : }
56 :
57 : // All shiny!
58 i : return PATCH_SUCCEEDED;
59 i : }
60 :
61 : class IATPatchWorker {
62 : public:
63 : explicit IATPatchWorker(const IATPatchMap& patch);
64 :
65 : PatchResult PatchImage(base::win::PEImage* image);
66 :
67 E : void set_on_unprotect(OnUnprotectCallback on_unprotect) {
68 E : scoped_page_protections_.set_on_unprotect(on_unprotect);
69 E : }
70 :
71 : private:
72 : static bool VisitImport(const base::win::PEImage &image, LPCSTR module,
73 : DWORD ordinal, LPCSTR name, DWORD hint,
74 : PIMAGE_THUNK_DATA iat, PVOID cookie);
75 : PatchResult OnImport(const char* name, PIMAGE_THUNK_DATA iat);
76 :
77 : ScopedPageProtections scoped_page_protections_;
78 : const IATPatchMap& patch_;
79 : PatchResult result_;
80 :
81 : DISALLOW_COPY_AND_ASSIGN(IATPatchWorker);
82 : };
83 :
84 : IATPatchWorker::IATPatchWorker(const IATPatchMap& patch)
85 E : : patch_(patch), result_(PATCH_SUCCEEDED) {
86 E : }
87 :
88 E : PatchResult IATPatchWorker::PatchImage(base::win::PEImage* image) {
89 E : DCHECK_NE(static_cast<base::win::PEImage*>(nullptr), image);
90 :
91 : // This is actually '0', so ORing error conditions to it is just fine.
92 E : result_ = PATCH_SUCCEEDED;
93 :
94 : // The IAT patching takes place during enumeration.
95 E : image->EnumAllImports(&VisitImport, this);
96 :
97 : // Clean up whatever we soiled, success or failure be damned.
98 E : if (!scoped_page_protections_.RestorePageProtections())
99 i : result_ |= PATCH_FAILED_REPROTECT_FAILED;
100 :
101 E : return result_;
102 E : }
103 :
104 : bool IATPatchWorker::VisitImport(
105 : const base::win::PEImage &image, LPCSTR module, DWORD ordinal,
106 E : LPCSTR name, DWORD hint, PIMAGE_THUNK_DATA iat, PVOID cookie) {
107 E : if (!name)
108 E : return true;
109 :
110 E : IATPatchWorker* worker = reinterpret_cast<IATPatchWorker*>(cookie);
111 E : PatchResult result = worker->OnImport(name, iat);
112 E : if (result == PATCH_SUCCEEDED)
113 E : return true;
114 :
115 : // Remember the reason for failure.
116 E : worker->result_ |= result;
117 E : return false;
118 E : }
119 :
120 E : PatchResult IATPatchWorker::OnImport(const char* name, PIMAGE_THUNK_DATA iat) {
121 E : auto it = patch_.find(name);
122 : // See whether this is a function we care about.
123 E : if (it == patch_.end())
124 E : return PATCH_SUCCEEDED;
125 :
126 : // Make the containing page writable.
127 E : if (!scoped_page_protections_.EnsureContainingPagesWritable(
128 : iat, sizeof(IMAGE_THUNK_DATA))) {
129 i : return PATCH_FAILED_UNPROTECT_FAILED;
130 : }
131 :
132 E : return UpdateImportThunk(iat, it->second);
133 E : }
134 :
135 : } // namespace
136 :
137 E : PatchResult PatchIATForModule(HMODULE module, const IATPatchMap& patch_map) {
138 E : OnUnprotectCallback dummy_on_unprotect;
139 E : return PatchIATForModule(module, patch_map, dummy_on_unprotect);
140 E : }
141 :
142 : PatchResult PatchIATForModule(HMODULE module, const IATPatchMap& patch_map,
143 E : OnUnprotectCallback on_unprotect) {
144 E : base::win::PEImage image(module);
145 E : if (!image.VerifyMagic())
146 i : return PATCH_FAILED_INVALID_IMAGE;
147 :
148 E : IATPatchWorker worker(patch_map);
149 E : worker.set_on_unprotect(on_unprotect);
150 E : return worker.PatchImage(&image);
151 E : }
152 :
153 : } // namespace asan
154 : } // namespace agent
|