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, 14 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.