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

Last change on this file since 219 was 219, checked in by rchin, 18 years ago

Fixed commit of nib file.

Added support for automatic injection into apps.

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_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 free(params);
326 }
327 CFRelease(str);
328 }
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.