source: trunk/Cocoa/F-Script Anywhere/Source/SCPatch/SCPatchController/SCPatchController.cpp@ 153

Last change on this file since 153 was 153, checked in by Nicholas Riley, 18 years ago

Integrates SCPatch and mach_inject; unfinished, buggy.

File size: 16.5 KB
Line 
1#include <mach-o/dyld.h>
2#include <syslog.h>
3#include <pthread.h>
4#include <CoreFoundation/CoreFoundation.h>
5#include "SCPatchPrivate.h"
6#include "SCPatchCommon.h"
7#include "SCPatchController.h"
8#include "mach_inject.h"
9
10typedef list<SCPatchRecord>::iterator SCPatchRecordIterator;
11
12//-------------------------------------------------------------------------------------------------------------
13mach_error_t SCmac_err_FromOSErr(OSErr err) {
14 return err ? (err_mac|err) : err_none;
15}
16
17//-------------------------------------------------------------------------------------------------------------
18OSErr SCOSErrFrom_mac_err(mach_error_t error) {
19 return (error & err_mac) ? (err & ^err_mac) : noErr;
20}
21
22//-------------------------------------------------------------------------------------------------------------
23#pragma mark -
24//-------------------------------------------------------------------------------------------------------------
25SCPatchController::SCPatchController()
26{
27 mBundle = CFBundleGetMainBundle();
28 CFRetain(mBundle);
29
30 mApplicationBundleIdentifier = CFBundleGetIdentifier(mBundle);
31 CFRetain(mApplicationBundleIdentifier);
32}
33
34//-------------------------------------------------------------------------------------------------------------
35SCPatchController::SCPatchController(CFStringRef bundleIdentifier)
36{
37 mBundle = CFBundleGetBundleWithIdentifier(bundleIdentifier);
38 CFRetain(mBundle);
39
40 mApplicationBundleIdentifier = bundleIdentifier;
41 CFRetain(mApplicationBundleIdentifier);
42}
43
44//-------------------------------------------------------------------------------------------------------------
45SCPatchController::~SCPatchController(void)
46{
47 if(mApplicationBundleIdentifier)
48 CFRelease(mApplicationBundleIdentifier);
49
50 if(mBundle)
51 CFRelease(mBundle);
52}
53
54//-------------------------------------------------------------------------------------------------------------
55#pragma mark -
56//-------------------------------------------------------------------------------------------------------------
57void SCPatchController::AddPatch(CFStringRef bundleIdentifier,
58 CFStringRef subPath,
59 CFStringRef name)
60{
61 SCPatchRecord patch(mBundle, bundleIdentifier, subPath, name);
62
63 mPatchList.push_back(patch);
64}
65
66//-------------------------------------------------------------------------------------------------------------
67// Inject the patches into running applications and start watching app launches
68OSErr SCPatchController::InstallPatches(void)
69{
70 OSErr procErr = noErr, err = noErr;
71 EventTypeSpec appSpec[] = { { kEventClassApplication, kEventAppLaunched },
72 { kEventClassApplication, kEventAppTerminated } };
73
74 // Start listening for messages from patches
75 SetMessageRetryInterval(0.1); // Try resending messages to patches every 1/10 second.
76 SetMessageRetryLimit(600); // Give up after 60 seconds.
77
78 if((err = StartListening(mApplicationBundleIdentifier, false, true)) != noErr)
79 {
80 printf("SCPC: controller could not open port for listening\n");
81 }
82
83 if(err == noErr)
84 {
85 InstallApplicationEventHandler(NewEventHandlerUPP(ApplicationEventHandler), 2, appSpec, this, NULL);
86
87 ProcessSerialNumber psn = { 0, kNoProcess };
88
89 while(!procErr)
90 {
91 if((procErr = GetNextProcess(&psn)) == noErr)
92 {
93 if(!IsProcessPatched(&psn))
94 {
95 InstallPatchesInProcess(&psn);
96 }
97 else
98 {
99 ProcessInfoRec info;
100 CFStringRef str;
101 Str255 pStr;
102
103 info.processInfoLength = sizeof(ProcessInfoRec);
104 info.processName = pStr;
105 info.processAppSpec = nil;
106
107 if((err = GetProcessInformation(&psn, &info)) == noErr &&
108 (str = CFStringCreateWithPascalString(NULL, pStr, kCFStringEncodingMacRoman)) != NULL)
109 {
110 PatchNotification(&psn, info.processSignature, info.processType, str, info.processMode);
111 CFRelease(str);
112 }
113
114 }
115 }
116 }
117 }
118 return err;
119}
120
121// Patch a single process (ignoring return value of ShouldPatchProcess)
122mach_error_t SCPatchController::PatchProcess(ProcessSerialNumber *psn)
123{
124 return InstallPatchesInProcess(psn, true);
125}
126
127//-------------------------------------------------------------------------------------------------------------
128// Get info and keep tabs on patched processes
129Boolean SCPatchController::IsProcessPatched(ProcessSerialNumber *inPSN)
130{
131 SCPatchRecordIterator iter;
132
133 if(mPatchContextList.GetContext(inPSN))
134 {
135 // fprintf(stderr, "Process patched (found context)\n");
136 return true;
137 }
138
139 for(iter = mPatchList.begin(); iter != mPatchList.end(); iter++)
140 {
141 OSErr err;
142
143 err = SendPing(iter->GetIdentifier(), inPSN);
144
145 // If we get a destPortErr, no one's listening on the desired port.
146 // If we get noErr or noResponseErr, there's someone listening.
147 if(err == noErr || err == noResponseErr)
148 {
149 pid_t pid;
150 ProcessInfoRec info;
151 Str255 pStr;
152
153 info.processInfoLength = sizeof(ProcessInfoRec);
154 info.processName = pStr;
155 info.processAppSpec = nil;
156
157 if((err = GetProcessInformation(inPSN, &info)) == noErr &&
158 (err = GetProcessPID(inPSN, &pid)) == noErr)
159 {
160 mPatchContextList.NewContext(inPSN, pid, info.processSignature);
161 }
162 // fprintf(stderr, "Process patched (ping succeeded)\n");
163 return true;
164 }
165 else
166 {
167 // fprintf(stderr, "Process not patched (ping failed)\n");
168 }
169 }
170
171 return false;
172}
173
174//-------------------------------------------------------------------------------------------------------------
175UInt32 SCPatchController::GetPatchFlags(ProcessSerialNumber *inPSN)
176{
177 SCPatchContext *context = mPatchContextList.GetContext(inPSN);
178
179 if(context)
180 return context->flags;
181 else
182 return 0;
183}
184
185//-------------------------------------------------------------------------------------------------------------
186void SCPatchController::SetPatchFlags(ProcessSerialNumber *inPSN, UInt32 flags, UInt32 whichFlags)
187{
188 SCPatchContext *context = mPatchContextList.GetContext(inPSN);
189
190 if(context)
191 context->flags = (context->flags & ~whichFlags) | flags;
192}
193
194//-------------------------------------------------------------------------------------------------------------
195OSErr SCPatchController::ForEachPatchedProcess(SCPatchIterationProc proc, void *data)
196{
197 SCPatchContextIterator iter;
198 OSErr err = noErr;
199
200 for(iter = mPatchContextList.begin(); iter != mPatchContextList.end(); iter++)
201 if((err = proc(&iter->second.psn, iter->second.creator, iter->second.flags, data)) != noErr)
202 break;
203 return err;
204}
205
206//-------------------------------------------------------------------------------------------------------------
207#pragma mark -
208//-------------------------------------------------------------------------------------------------------------
209OSErr SCPatchController::HandleMessage(const AppleEvent *theAE)
210{
211 OSErr err = eventNotHandledErr;
212 OSType eventClass, eventID;
213 Size actualSize;
214 DescType actualType;
215 ProcessSerialNumber psn;
216
217 if((err = AEGetAttributePtr(theAE, keyEventIDAttr, typeType, &actualType, &eventID, sizeof(OSType), &actualSize)) != noErr ||
218 (err = AEGetAttributePtr(theAE, keyEventClassAttr, typeType, &actualType, &eventClass, sizeof(OSType), &actualSize)) != noErr)
219 {
220 return err;
221 }
222
223 if(eventClass == kSCMessageClass && eventID == kSCPatchSuccess &&
224 (err = AEGetParamPtr(theAE, keyPSN, typeProcessSerialNumber, &actualType, &psn, sizeof(ProcessSerialNumber), &actualSize)) == noErr)
225 {
226 RecordPatchAndNotify(&psn, NULL);
227 }
228
229 // Return eventNotHandledErr even though we've done our
230 // thing so that the subclass gets a chance at it too.
231 return eventNotHandledErr;
232}
233
234//-------------------------------------------------------------------------------------------------------------
235pascal OSStatus SCPatchController::ApplicationEventHandler(EventHandlerCallRef handlerRef,
236 EventRef event, void *userData)
237{
238 #pragma unused (handlerRef)
239
240 SCPatchController *self = (SCPatchController *)userData;
241 UInt32 kind = GetEventKind(event);
242 OSStatus err;
243 ProcessSerialNumber psn;
244
245 err = GetEventParameter(event, kEventParamProcessID, typeProcessSerialNumber,
246 NULL, sizeof(ProcessSerialNumber), NULL, &psn);
247
248 fprintf(stderr, "ApplicationEventHandler called\n");
249 if(err == noErr)
250 {
251 if(kind == kEventAppLaunched)
252 {
253 // fprintf(stderr, "SCPC: App launched\n");
254 if(!self->IsProcessPatched(&psn))
255 self->InstallPatchesInProcess(&psn);
256 }
257 else if(kind == kEventAppTerminated)
258 {
259 pid_t pid;
260 ProcessInfoRec info;
261 CFStringRef str;
262 Str255 pStr;
263
264 info.processInfoLength = sizeof(ProcessInfoRec);
265 info.processName = pStr;
266 info.processAppSpec = nil;
267
268 if((err = GetProcessInformation(&psn, &info)) == noErr &&
269 (err = GetProcessPID(&psn, &pid)) == noErr &&
270 (str = CFStringCreateWithPascalString(NULL, pStr, kCFStringEncodingMacRoman)) != NULL)
271 {
272 self->UnpatchNotification(&psn, info.processSignature, info.processType, str, info.processMode);
273 CFRelease(str);
274 }
275 // fprintf(stderr, "SCPC: App died\n");
276 self->mPatchContextList.DeleteContext(&psn);
277 }
278 }
279
280 // Always pass the event down
281 return eventNotHandledErr;
282}
283
284//-------------------------------------------------------------------------------------------------------------
285mach_error_t SCPatchController::InstallPatchesInProcess(ProcessSerialNumber *psn, bool onDemand)
286{
287 SCPatchRecordIterator iter;
288 pid_t pid;
289 ProcessInfoRec info;
290 CFStringRef str;
291 Str255 pStr;
292 OSErr err = noErr;
293 mach_error_t error = err_none;
294 SCPatchLoaderParams *params = NULL;
295
296 info.processInfoLength = sizeof(ProcessInfoRec);
297 info.processName = pStr;
298 info.processAppSpec = nil;
299
300 if((err = GetProcessInformation(psn, &info)) == noErr &&
301 (err = GetProcessPID(psn, &pid)) == noErr &&
302 (str = CFStringCreateWithPascalString(NULL, pStr, kCFStringEncodingMacRoman)) != NULL)
303 {
304 // fprintf(stderr, "SCPC: examining application (sig = %.4s, type = %.4s, flags = 0x%x, name = %s)\n",
305 // &info.processSignature, &info.processType, info.processMode,
306 // CFStringGetCStringPtr(str, kCFStringEncodingMacRoman));
307
308 if((onDemand ||
309 ShouldPatchProcess(psn, info.processSignature, info.processType, str, info.processMode)) &&
310 (params = (SCPatchLoaderParams *)malloc(sizeof(SCPatchLoaderParams))) != NULL)
311 {
312 // fprintf(stderr, "SCPC: patching application (sig = %.4s, type = %.4s, flags = 0x%x, name = %s)\n",
313 // &info.processSignature, &info.processType, info.processMode,
314 // CFStringGetCStringPtr(str, kCFStringEncodingMacRoman));
315
316 params->version = 1;
317 params->size = sizeof(SCPatchLoaderParams);
318 params->patchCount = 0;
319
320 for(iter = mPatchList.begin(); iter != mPatchList.end() && err == noErr; iter++)
321 err = AddPatchToParams(iter->GetIdentifier(), iter->GetURL(), &params);
322
323 if(err == noErr)
324 error = InjectPatches(psn, params);
325
326 free(params);
327 }
328 CFRelease(str);
329 }
330 if (error != err_none) return error;
331 else return mac_err(err);
332}
333
334//-------------------------------------------------------------------------------------------------------------
335OSErr SCPatchController::AddPatchToParams(CFStringRef bundleIdentifier, CFURLRef url, SCPatchLoaderParams **params)
336{
337 OSErr err = err_couldnt_find_patch_bundle;
338 CFStringRef patchPath = NULL;
339 size_t newSize;
340
341 if(bundleIdentifier == NULL || url == NULL || params == NULL)
342 return paramErr;
343
344#if 0
345 if((patchPath = CFURLGetString(url)) != NULL)
346 err = noErr;
347#else
348 CFBundleRef patchBundle;
349 CFURLRef patchURL;
350
351 // Find out where the patch lives
352 if((patchBundle = CFBundleCreate(kCFAllocatorDefault, url)) != NULL &&
353 (patchURL = CFBundleCopyExecutableURL(patchBundle)) != NULL)
354 {
355 CFURLRef absoluteURL;
356
357 if((absoluteURL = CFURLCopyAbsoluteURL(patchURL)) != NULL)
358 {
359 CFRelease(patchURL);
360 patchURL = absoluteURL;
361 }
362
363 if((patchPath = CFURLCopyFileSystemPath(patchURL, kCFURLPOSIXPathStyle)) != NULL)
364 err = noErr;
365
366 CFRelease(patchURL);
367 CFRelease(patchBundle);
368 }
369#endif
370
371 // Increase the size of params. This allocation may have waste - CFStringGetLength * 2 is the max _possible_ size.
372 if(!err)
373 {
374 newSize = (*params)->size + sizeof(SCPatchLoaderData) + CFStringGetLength(patchPath) * 2;
375
376 if((*params = (SCPatchLoaderParams *)realloc(*params, newSize)) != NULL)
377 (*params)->size = newSize;
378 else
379 err = ENOMEM;
380 }
381
382 // Fill in all the params
383 if(!err)
384 {
385 HFSUniStr255 *bundleID = SCPatchGetHFSUniStrPointer(*params, (*params)->patchCount);
386 char *urlData =SCPatchGetStringPointer(*params, (*params)->patchCount);
387
388 if((bundleID->length = CFStringGetLength(bundleIdentifier)) > 256)
389 {
390 fprintf(stderr, "SCPC: patch bundleIdentifier is too long. It must be 256 chars or less.\n");
391 err = err_couldnt_load_injection_bundle;
392 }
393 else
394 {
395 CFStringGetCharacters(bundleIdentifier, CFRangeMake(0, bundleID->length), bundleID->unicode);
396 if(CFStringGetCString(patchPath, urlData, CFStringGetLength(patchPath) * 2, kCFStringEncodingUTF8))
397 (*params)->patchCount++;
398 else
399 err = err_couldnt_load_injection_bundle;
400 }
401 }
402#if 1
403 if(patchPath)
404 CFRelease(patchPath);
405#endif
406
407 return err;
408}
409
410//-------------------------------------------------------------------------------------------------------------
411mach_error_t SCPatchController::InjectPatches(ProcessSerialNumber *psn, SCPatchLoaderParams *params)
412{
413 mach_error_t err = err_none;
414
415 // Convert PSN to PID.
416 pid_t pid;
417 if(!err)
418 err = mac_err(GetProcessPID(psn, &pid));
419
420 // Fill in all the params
421 if(!err)
422 {
423 // Make copies of the bundle identifiers and set up other params
424 if((params->parentBundleID.length = CFStringGetLength(mApplicationBundleIdentifier)) > 256)
425 {
426 fprintf(stderr, "SCPC: app bundleIdentifier is too long. It must be 256 chars or less.\n");
427 err = err_couldnt_load_injection_bundle;
428 }
429 else
430 {
431 params->parent = pid;
432 CFStringGetCharacters(mApplicationBundleIdentifier, CFRangeMake(0, params->parentBundleID.length), params->parentBundleID.unicode);
433 }
434 }
435
436 // Find the loader
437 CFURLRef loaderURL = NULL;
438 if(!err)
439 if((loaderURL = CFBundleCopyResourceURL(mBundle, CFSTR("SCPatchLoader"), CFSTR("bundle"), NULL)) == NULL)
440 err = err_couldnt_find_injection_bundle;
441
442#if 0
443 // If we get "file not found", the user must have moved our application bundle. Try finding it
444 // with Launch Services and going on that way.
445 if(err == err_couldnt_load_injection_bundle) // REVISIT
446 {
447 CFURLRef appURL, patchURL;
448 CFStringRef patchPath;
449
450 if(LSFindApplicationForInfo(NULL, sApplicationBundleIdentifier, NULL, NULL, &appURL) == noErr)
451 {
452 if((patchURL = CFURLCreateCopyAppendingPathComponent(NULL, appURL, subPath, false)) != NULL &&
453 (patchPath = CFURLCopyFileSystemPath(patchURL, kCFURLPOSIXPathStyle)) != NULL)
454 {
455 err = PatchControllerLoadPatchForProcess(pid, bundleID, patchPath, true);
456 CFRelease(patchPath);
457 CFRelease(patchURL);
458 }
459 CFRelease(appURL);
460 }
461 }
462#endif
463
464 // Create injection bundle instance.
465 CFBundleRef injectionBundle = NULL;
466 if(!err)
467 if((injectionBundle = CFBundleCreate(kCFAllocatorDefault, loaderURL)) == NULL)
468 err = err_couldnt_load_injection_bundle;
469
470 // Load the thread code injection.
471 void *injectionCode = NULL;
472 if(!err)
473 if((injectionCode = CFBundleGetFunctionPointerForName(injectionBundle, CFSTR(INJECT_ENTRY_SYMBOL))) == NULL)
474 err = err_couldnt_find_injectedThread_symbol;
475
476 // Inject the code.
477 if(!err)
478 {
479 err = ::mach_inject((mach_inject_entry)injectionCode, params, params->size, pid, 0);
480 }
481 else
482 {
483 fprintf(stderr, "Patcher got error %d\n", err);
484 }
485
486 // Clean up.
487 if(loaderURL)
488 CFRelease(loaderURL);
489 if(injectionBundle)
490 CFRelease(injectionBundle);
491
492 return err;
493}
494
495//-------------------------------------------------------------------------------------------------------------
496OSErr SCPatchController::RecordPatchAndNotify(ProcessSerialNumber *psn, ProcessInfoRec *info)
497{
498 OSErr err = noErr;
499 ProcessInfoRec localInfo;
500 Str255 pStr;
501 CFStringRef str;
502 pid_t pid;
503
504 // Make sure we haven't already added this process
505 if(mPatchContextList.GetContext(psn))
506 return noErr;
507
508 // If no info was given, get it ourselves
509 if(!info)
510 {
511 info = &localInfo;
512 info->processInfoLength = sizeof(ProcessInfoRec);
513 info->processName = pStr;
514 info->processAppSpec = nil;
515 err = GetProcessInformation(psn, info);
516 }
517
518 // Now add it to our context list and let the subclass know
519 if(err == noErr && (err = GetProcessPID(psn, &pid)) == noErr &&
520 (str = CFStringCreateWithPascalString(NULL, info->processName, kCFStringEncodingMacRoman)) != NULL)
521 {
522 mPatchContextList.NewContext(psn, pid, info->processSignature);
523 PatchNotification(psn, info->processSignature, info->processType, str, info->processMode);
524 CFRelease(str);
525 }
526 return err;
527}
Note: See TracBrowser for help on using the repository browser.