11

Using the method described in this question, I can get a list of apps running on an iOS device. I know PIDs and have access to their kinfo_proc structures. How can I determine which are foreground processes and which are background (assuming my app is background)?

I tried to find this out base on information in kinfo_proc (see 1st link), via kp_proc.p_priority, but it looks like it is not possible to infer background/foreground state from priority.

I don't really care if this works correctly for AppStore Review but I would prefer a method that will work without a jailbreak(i.e. Private APIs are ok but which ones?). I want this to work at least on iOS 5

I considered writing a simple MobileSubstrate extension, injecting it into all apps and just hook everyone's applicationDidBecomeActive, but this requires a jailbreak and is too invasive.

Community
  • 1
  • 1
Tauri
  • 1,291
  • 1
  • 14
  • 28

3 Answers3

10

Well, looks like some usage of nm and IDA on SpringBoardServices binary from simulator helped me on this. Following code works on iOS 5.0.1 running on iPod Touch 4, iPhone 4 and iPad1 WiFi(all non-JB) Of course you should never try to submit that to AppStore

- (NSArray*) getActiveApps
{
mach_port_t *p;
void *uikit = dlopen(UIKITPATH, RTLD_LAZY);
int (*SBSSpringBoardServerPort)() = 
dlsym(uikit, "SBSSpringBoardServerPort");
p = (mach_port_t *)SBSSpringBoardServerPort(); 
dlclose(uikit);

void *sbserv = dlopen(SBSERVPATH, RTLD_LAZY);
NSArray* (*SBSCopyApplicationDisplayIdentifiers)(mach_port_t* port, BOOL runningApps,BOOL debuggable) = 
dlsym(sbserv, "SBSCopyApplicationDisplayIdentifiers");
//SBDisplayIdentifierForPID - protype assumed,verification of params done
void* (*SBDisplayIdentifierForPID)(mach_port_t* port, int pid,char * result) = 
dlsym(sbserv, "SBDisplayIdentifierForPID");
//SBFrontmostApplicationDisplayIdentifier - prototype assumed,verification of params done,don't call this TOO often(every second on iPod touch 4G is 'too often,every 5 seconds is not)
void* (*SBFrontmostApplicationDisplayIdentifier)(mach_port_t* port,char * result) = 
dlsym(sbserv, "SBFrontmostApplicationDisplayIdentifier");



//Get frontmost application
char frontmostAppS[256];
memset(frontmostAppS,sizeof(frontmostAppS),0);
SBFrontmostApplicationDisplayIdentifier(p,frontmostAppS);
NSString * frontmostApp=[NSString stringWithFormat:@"%s",frontmostAppS];
//NSLog(@"Frontmost app is %@",frontmostApp);
//get list of running apps from SpringBoard
NSArray *allApplications = SBSCopyApplicationDisplayIdentifiers(p,NO, NO);
//Really returns ACTIVE applications(from multitasking bar)
/*   NSLog(@"Active applications:");
 for(NSString *identifier in allApplications) {
 // NSString * locName=SBSCopyLocalizedApplicationNameForDisplayIdentifier(p,identifier);
 NSLog(@"Active Application:%@",identifier);
 }
 */ 

//get list of all apps from kernel
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
size_t miblen = 4;

size_t size;
int st = sysctl(mib, miblen, NULL, &size, NULL, 0);

struct kinfo_proc * process = NULL;
struct kinfo_proc * newprocess = NULL;

do {

    size += size / 10;
    newprocess = realloc(process, size);

    if (!newprocess){

        if (process){
            free(process);
        }

        return nil;
    }

    process = newprocess;
    st = sysctl(mib, miblen, process, &size, NULL, 0);

} while (st == -1 && errno == ENOMEM);

if (st == 0){

    if (size % sizeof(struct kinfo_proc) == 0){
        int nprocess = size / sizeof(struct kinfo_proc);

        if (nprocess){

            NSMutableArray * array = [[NSMutableArray alloc] init];

            for (int i = nprocess - 1; i >= 0; i--){

                int ruid=process[i].kp_eproc.e_pcred.p_ruid;
                int uid=process[i].kp_eproc.e_ucred.cr_uid;
                //short int nice=process[i].kp_proc.p_nice;
                //short int u_prio=process[i].kp_proc.p_usrpri;
                short int prio=process[i].kp_proc.p_priority;
                NSString * processID = [[NSString alloc] initWithFormat:@"%d", process[i].kp_proc.p_pid];
                NSString * processName = [[NSString alloc] initWithFormat:@"%s", process[i].kp_proc.p_comm];


                BOOL systemProcess=YES;
                if (ruid==501)
                    systemProcess=NO;



                char * appid[256];
                memset(appid,sizeof(appid),0);
                int intID,intID2;
                intID=process[i].kp_proc.p_pid,appid;
                SBDisplayIdentifierForPID(p,intID,appid);/

                NSString * appId=[NSString stringWithFormat:@"%s",appid];

                if (systemProcess==NO)
                {
                    if ([appId isEqualToString:@""])
                    {
                        //final check.if no appid this is not springboard app
                        NSLog(@"(potentially system)Found process with PID:%@ name %@,isSystem:%d,Priority:%d",processID,processName,systemProcess,prio);
                    }
                    else
                    {

                        BOOL isFrontmost=NO;
                        if ([frontmostApp isEqualToString:appId])
                        {
                            isFrontmost=YES;
                        }
                        NSNumber *isFrontmostN=[NSNumber numberWithBool:isFrontmost];
                        NSDictionary * dict = [[NSDictionary alloc] initWithObjects:[NSArray arrayWithObjects:processID, processName,appId,isFrontmostN, nil] 
                                                                            forKeys:[NSArray arrayWithObjects:@"ProcessID", @"ProcessName",@"AppID",@"isFrontmost", nil]];

                        NSLog(@"PID:%@, name: %@, AppID:%@,isFrontmost:%d",processID,processName,appId,isFrontmost);
                        [array addObject:dict];
                    }
                }
            }

            free(process);
            return array;
        }
    }
  }

    dlclose(sbserv);
}

Of course 2nd loop is not strictly necessary but I needed non-localized names & PIDs too.

Tauri
  • 1,291
  • 1
  • 14
  • 28
  • 1
    I believe there is one problem with this solution. At least, I tried it on my iPad 1 (with iOS 5.1). SBFrontmostApplicationDisplayIdentifier returns last foremost non Springboard application. It means that if you entered Mail and later existed to Springboard, it will still report that Mail is foremost application. I am not sure whether it's per design for this API or specific problem with iPad 1 or isOS 5.1 – Victor Ronin Jul 17 '12 at 00:24
  • well, for me it works. iOS 5.1 on iPhone4,iPad1,iPad3 and 6.0beta2(not yet checked on beta3) on iPhone4 – Tauri Jul 17 '12 at 04:21
  • 1
    Way too much work here. Simply pass YES to SBSCopyApplicationDisplayIdentifiers for the second param and you don't need any of the other code since that param means get active apps. – malhal Dec 11 '13 at 04:17
  • 2
    @VikartiAnatra Used the above code in iOS 6 and iOS 7 it is working fine. However it is not working in iOS 8. Found out that apple has improved the security in iOS8 to prevent this. Is there any alternative approach to bypass this. Link: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-4361 – Avinash Sep 24 '14 at 06:55
  • 1
    @Avinash did you found any bypass solution or altarnative for ios 8.0 ? – Bittu Sep 30 '14 at 05:54
  • @Bittu Nope not yet. Will let u know if i find any – Avinash Oct 02 '14 at 04:26
  • @Avinash You may found a solution yet?! – davidOhara Feb 18 '15 at 09:29
  • @davidOhara Not yet. It seems not possible. I tired with almost all the private api's available did not get any solution. No luck yet – Avinash Feb 21 '15 at 06:14
  • @Avinash Maybe it is possible over the priority of running processes!? – davidOhara Feb 24 '15 at 14:32
  • @davidOhara It does not, I checked it. If you have found out please share the logic. – Avinash Mar 02 '15 at 08:24
  • Sadly not working for iOS9...@dkzm,could you please help me what went wrong in iOS9? – tounaobun Aug 26 '15 at 10:59
  • @BensonTommy, see Avinash's comment. Apple thinks it's security issue as of iOS8 – Tauri Aug 27 '15 at 03:07
  • @Avinash Have u found any solution yet?I have tried FBProcessManager,sysctl,VMUProcList,but all these private APIs doesn't work at call. – tounaobun Sep 01 '15 at 13:18
5

Great answer! But there is a small typo in your code, it should be:

First make sure that SBSERVPATH is defined and the correct header files are included:

#import <sys/sysctl.h>
#import <dlfcn.h>

#define SBSERVPATH "/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices"

Then first find the correct SB port:

mach_port_t *port;
void *lib = dlopen(SBSERVPATH, RTLD_LAZY);
int (*SBSSpringBoardServerPort)() = 
dlsym(lib, "SBSSpringBoardServerPort");
port = (mach_port_t *)SBSSpringBoardServerPort(); 
dlclose(lib);

And then find the active app:

mach_port_t * port = [self getSpringBoardPort];
// open springboard lib
void *lib = dlopen(SBSERVPATH, RTLD_LAZY);

// retrieve function SBFrontmostApplicationDisplayIdentifier
void *(*SBFrontmostApplicationDisplayIdentifier)(mach_port_t *port, char *result) =
dlsym(lib, "SBFrontmostApplicationDisplayIdentifier");

// reserve memory for name
char appId[256];
memset(appId, 0, sizeof(appId));

// retrieve front app name
SBFrontmostApplicationDisplayIdentifier(port, appId);

// close dynlib
dlclose(lib);
hackerdiehack
  • 372
  • 3
  • 11
  • 2
    There is a much easier way: extern NSString * SBSCopyFrontmostApplicationDisplayIdentifier(); – malhal Dec 11 '13 at 04:15
  • Used the above code in iOS 6 and iOS 7 it is working fine. However it is not working in iOS 8. Found out that apple has improved the security in iOS8 to prevent this. Is there any alternative approach to bypass this. Link: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-4361 – Avinash Sep 24 '14 at 06:56
  • As far as I know there is absolutely no way to do this anymore :) – hackerdiehack Jan 27 '15 at 08:59
-1

This is what works for me on all IOS devices:

#define UIKITPATH "/System/Library/Framework/UIKit.framework/UIKit"
#define SBSERVPATH "/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices"

 - (NSArray*) getActiveApps
 {
mach_port_t *p;
void *uikit = dlopen(UIKITPATH, RTLD_LAZY);
int (*SBSSpringBoardServerPort)() =
dlsym(uikit, "SBSSpringBoardServerPort");
p = (mach_port_t *)SBSSpringBoardServerPort();
dlclose(uikit);

if(self.frameWorkPath == nil || self.frameWorkPath.length == 0)
{
    self.frameWorkPath = @SBSERVPATH;
    self.frameWorkPath = [self.frameWorkPath stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
}

const char *cString = [self.frameWorkPath cStringUsingEncoding:NSUTF8StringEncoding];
//const char *bar = [self.frameWorkPath UTF8String];
void *sbserv = dlopen(cString, RTLD_LAZY);
NSArray* (*SBSCopyApplicationDisplayIdentifiers)(mach_port_t* port, BOOL runningApps,BOOL debuggable) =
dlsym(sbserv, "SBSCopyApplicationDisplayIdentifiers");
//SBDisplayIdentifierForPID - protype assumed,verification of params done
void* (*SBDisplayIdentifierForPID)(mach_port_t* port, int pid,char * result) =
dlsym(sbserv, "SBDisplayIdentifierForPID");
//SBFrontmostApplicationDisplayIdentifier - prototype assumed,verification of params done,don't call this TOO often(every second on iPod touch 4G is 'too often,every 5 seconds is not)
void* (*SBFrontmostApplicationDisplayIdentifier)(mach_port_t* port,char * result) =
dlsym(sbserv, "SBFrontmostApplicationDisplayIdentifier");



//Get frontmost application
char frontmostAppS[512];
memset(frontmostAppS,sizeof(frontmostAppS),0);
SBFrontmostApplicationDisplayIdentifier(p,frontmostAppS);
NSString * frontmostApp=[NSString stringWithFormat:@"%s",frontmostAppS];

if([self iOsMajorVersion] >= 7){
    NSNumber *topmost = [NSNumber numberWithBool:YES];
    NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
    NSMutableArray  * splitted = [frontmostApp componentsSeparatedByString:@"."];
    if(frontmostApp.length > 0 && splitted != nil && splitted.count > 1 && topmost.boolValue == YES){
        NSString *appname = [splitted lastObject];
        [dict setObject:[appname capitalizedString] forKey:@"ProcessName"];
        [dict setObject:frontmostApp forKey:@"ProcessID"];
        [dict setObject:frontmostApp forKey:@"AppID"];
        [dict setObject:topmost forKey:@"isFrontmost"];
        NSLog(@"Running TOPMOST App %@",dict);
        return @[dict];
    }
    else{
        return nil;
    }
}
//NSLog(@"Frontmost app is %@",frontmostApp);
//get list of running apps from SpringBoard
NSArray *allApplications = SBSCopyApplicationDisplayIdentifiers(p,NO, NO);
//Really returns ACTIVE applications(from multitasking bar)
   NSLog(@"Active applications:");
 for(NSString *identifier in allApplications) {
     // NSString * locName=SBSCopyLocalizedApplicationNameForDisplayIdentifier(p,identifier);
     NSLog(@"Active Application:%@",identifier);
 }


//get list of all apps from kernel
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
size_t miblen = 4;

size_t size;
int st = sysctl(mib, miblen, NULL, &size, NULL, 0);

struct kinfo_proc * process = NULL;
struct kinfo_proc * newprocess = NULL;

do {

    size += size / 10;
    newprocess = realloc(process, size);

    if (!newprocess){

        if (process){
            free(process);
        }

        return nil;
    }

    process = newprocess;
    st = sysctl(mib, miblen, process, &size, NULL, 0);

} while (st == -1 && errno == ENOMEM);

if (st == 0){

    if (size % sizeof(struct kinfo_proc) == 0){
        int nprocess = size / sizeof(struct kinfo_proc);

        if (nprocess){

            NSMutableArray * array = [[NSMutableArray alloc] init];

            for (int i = nprocess - 1; i >= 0; i--){

                int ruid=process[i].kp_eproc.e_pcred.p_ruid;
                int uid=process[i].kp_eproc.e_ucred.cr_uid;
                //short int nice=process[i].kp_proc.p_nice;
                //short int u_prio=process[i].kp_proc.p_usrpri;
                short int prio=process[i].kp_proc.p_priority;
                NSString * processID = [[NSString alloc] initWithFormat:@"%d", process[i].kp_proc.p_pid];
                NSString * processName = [[NSString alloc] initWithFormat:@"%s", process[i].kp_proc.p_comm];


                BOOL systemProcess=YES;
                if (ruid==501){
                    systemProcess=NO;
                }


                char * appid[256];
                memset(appid,sizeof(appid),0);
                int intID,intID2;
                intID=process[i].kp_proc.p_pid,appid;
                SBDisplayIdentifierForPID(p,intID,appid);

                NSString * appId=[NSString stringWithFormat:@"%s",appid];

                if (systemProcess==NO)
                {
                    if ([appId isEqualToString:@""])
                    {
                        //final check.if no appid this is not springboard app
                        //NSLog(@"(potentially system)Found process with PID:%@ name %@,isSystem:%d,Priority:%d",processID,processName,systemProcess,prio);
                    }
                    else
                    {

                        BOOL isFrontmost=NO;
                        if ([frontmostApp isEqualToString:appId])
                        {
                            isFrontmost=YES;
                        }
                        NSNumber *isFrontmostN=[NSNumber numberWithBool:isFrontmost];
                        NSDictionary * dict = [[NSDictionary alloc] initWithObjects:[NSArray arrayWithObjects:processID, processName,appId,isFrontmostN, nil] 
                                                                            forKeys:[NSArray arrayWithObjects:@"ProcessID", @"ProcessName",@"AppID",@"isFrontmost", nil]];
                        NSLog(@"PID:%@, name: %@, AppID:%@,isFrontmost:%d",processID,processName,appId,isFrontmost);
                        [array addObject:dict];
                    }
                }
            }

            free(process);
            return array;
        }
    }
}

dlclose(sbserv);
 }
Gal Blank
  • 2,070
  • 2
  • 18
  • 18