3

How does this app "System Console" (made by a company named Electric Labs Ltd) access the device logs on iOS 8?

I'm trying to do the same thing but all I get is my own app's log messages. I'm using the api for the Apple System Logging facility following the example from this article "Accessing the iOS System Log".

I'm aware that due the sandbox subsystem in iOS 7+, my app can only see its own log messages. But I'm wondering how does the "System Console" app avoid this limitation?

Mohamed AMAZIRH
  • 1,159
  • 7
  • 26

3 Answers3

6

It seems you can read the "syslog.sock". I found a project on Github which works under iOS8: https://github.com/eswick/ondeviceconsole

I created the following test code (empty project) and ran it to see current logging info in the console. It recognized starting the Mail app, Notes app, etc. The info needs parsing, but that's a different issue. (Warning: disgusting code below!) Use for good, not evil ;)

//
//  ViewController.m
//  LogTest
//
//  Created by Freek Sanders on 26-02-15.

#import "ViewController.h"
#import <asl.h>

#import <sys/socket.h>
#import <sys/un.h>

#import <unistd.h>
#import <fcntl.h>
#import <poll.h>



#define SOCKET_PATH "/var/run/lockdown/syslog.sock"

#define COLOR_RESET         "\e[m"
#define COLOR_NORMAL        "\e[0m"
#define COLOR_DARK          "\e[2m"
#define COLOR_RED           "\e[0;31m"
#define COLOR_DARK_RED      "\e[2;31m"
#define COLOR_GREEN         "\e[0;32m"
#define COLOR_DARK_GREEN    "\e[2;32m"
#define COLOR_YELLOW        "\e[0;33m"
#define COLOR_DARK_YELLOW   "\e[2;33m"
#define COLOR_BLUE          "\e[0;34m"
#define COLOR_DARK_BLUE     "\e[2;34m"
#define COLOR_MAGENTA       "\e[0;35m"
#define COLOR_DARK_MAGENTA  "\e[2;35m"
#define COLOR_CYAN          "\e[0;36m"
#define COLOR_DARK_CYAN     "\e[2;36m"
#define COLOR_WHITE         "\e[0;37m"
#define COLOR_DARK_WHITE    "\e[0;37m"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    int nfd = unix_connect(SOCKET_PATH);

    // write "watch" command to socket to begin receiving messages
    write(nfd, "watch\n", 6);

    struct pollfd pfd[2];
    unsigned char buf[16384];
    int n = fileno(stdin);
    int lfd = fileno(stdout);
    int plen = 16384;

    pfd[0].fd = nfd;
    pfd[0].events = POLLIN;

    while (pfd[0].fd != -1) {

        if ((n = poll(pfd, 1, -1)) < 0) {
            close(nfd);
            perror("polling error");
            exit(1);
        }

        if (pfd[0].revents & POLLIN) {
            if ((n = read(nfd, buf, plen)) < 0)
                perror("read error"), exit(1); /* possibly not an error, just disconnection */
            else if (n == 0) {
                shutdown(nfd, SHUT_RD);
                pfd[0].fd = -1;
                pfd[0].events = 0;
            } else {
                if (atomicio(write_colored, lfd, buf, n) != n)
                    perror("atomicio failure"), exit(1);
            }
        }
    }
}

size_t atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n)
{
    char *s = _s;
    size_t pos = 0;
    ssize_t res;
    struct pollfd pfd;

    pfd.fd = fd;
    pfd.events = f == read ? POLLIN : POLLOUT;
    while (n > pos) {
        res = (f) (fd, s + pos, n - pos);
        switch (res) {
            case -1:
                if (errno == EINTR)
                    continue;
                if ((errno == EAGAIN) || (errno == ENOBUFS)) {
                    (void)poll(&pfd, 1, -1);
                    continue;
                }
                return 0;
            case 0:
                errno = EPIPE;
                return pos;
            default:
                pos += (size_t)res;
        }
    }
    return (pos);
}

int unix_connect(char* path) {
    struct sockaddr_un sun;
    int s;

    if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return (-1);
    (void)fcntl(s, F_SETFD, 1);

    memset(&sun, 0, sizeof(struct sockaddr_un));
    sun.sun_family = AF_UNIX;

    if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) {
        close(s);
        errno = ENAMETOOLONG;
        return (-1);
    }
    if (connect(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) < 0) {
        close(s);
        return (-1);
    }

    return (s);
}

#define LINE_REGEX "(\\w+\\s+\\d+\\s+\\d+:\\d+:\\d+)\\s+(\\S+|)\\s+(\\w+)\\[(\\d+)\\]\\s+\\<(\\w+)\\>:\\s(.*)"

ssize_t write_colored(int fd, void* buffer, size_t len) {

    char *escapedBuffer = malloc(len + 1);
    memcpy(escapedBuffer, buffer, len);
    escapedBuffer[len] = '\0';

    NSString *str = [NSString stringWithUTF8String:escapedBuffer];
    free(escapedBuffer);

    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression
                                  regularExpressionWithPattern:@LINE_REGEX
                                  options:NSRegularExpressionCaseInsensitive
                                  error:&error];

    NSArray *matches = [regex matchesInString:str
                                      options:0
                                        range:NSMakeRange(0, [str length])];

    if ([matches count] == 0)
        return write(fd, buffer, len);

    for (NSTextCheckingResult *match in matches) {

        if ([match numberOfRanges] < 6) {
            write(fd, buffer, len); // if entry doesn't match regex, print uncolored
            continue;
        }

        NSRange dateRange    =  [match rangeAtIndex:1];
        NSRange deviceRange  =  [match rangeAtIndex:2];
        NSRange processRange =  [match rangeAtIndex:3];
        NSRange pidRange     =  [match rangeAtIndex:4];
        NSRange typeRange    =  [match rangeAtIndex:5];
        NSRange logRange     =  [match rangeAtIndex:6];

        NSString *date       =  [str substringWithRange:dateRange];
        NSString *device     =  [str substringWithRange:deviceRange];
        NSString *process    =  [str substringWithRange:processRange];
        NSString *pid        =  [str substringWithRange:pidRange];
        NSString *type       =  [str substringWithRange:typeRange];
        NSString *log        =  [str substringWithRange:
                                 NSMakeRange(logRange.location,
                                             [str length] - logRange.location)];

        log = [log stringByTrimmingCharactersInSet:
               [NSCharacterSet newlineCharacterSet]];

        NSMutableString *build = [NSMutableString new];

        [build appendString:@COLOR_DARK_WHITE];
        [build appendString:date];
        [build appendString:@" "];
        [build appendString:device];
        [build appendString:@" "];

        [build appendString:@COLOR_CYAN];
        [build appendString:process];
        [build appendString:@"["];
        [build appendString:pid];
        [build appendString:@"]"];

        char *typeColor = COLOR_DARK_WHITE;
        char *darkTypeColor = COLOR_DARK_WHITE;

        if ([type isEqualToString:@"Notice"]) {
            typeColor = COLOR_GREEN;
            darkTypeColor = COLOR_DARK_GREEN;
        } else if ([type isEqualToString:@"Warning"]) {
            typeColor = COLOR_YELLOW;
            darkTypeColor = COLOR_DARK_YELLOW;
        } else if ([type isEqualToString:@"Error"]) {
            typeColor = COLOR_RED;
            darkTypeColor = COLOR_DARK_RED;
        } else if ([type isEqualToString:@"Debug"]) {
            typeColor = COLOR_MAGENTA;
            darkTypeColor = COLOR_DARK_MAGENTA;
        }

        [build appendString:@(darkTypeColor)];
        [build appendString:@" <"];
        [build appendString:@(typeColor)];
        [build appendString:type];
        [build appendString:@(darkTypeColor)];
        [build appendString:@">"];
        [build appendString:@COLOR_RESET];
        [build appendString:@": "];
        [build appendString:log];

        printf("%s\n", [build UTF8String]);
    }

    return len;
}

@end
Freek Sanders
  • 1,187
  • 10
  • 26
  • How much can you see from other apps? – ahwulf Feb 26 '15 at 14:46
  • Not sure. Try it out ;) – Freek Sanders Feb 26 '15 at 20:36
  • Sorry for the late response. Didn't find time to test your example. Are you sure this will work on a device that is not jailbroken? – Mohamed AMAZIRH Mar 02 '15 at 11:17
  • Yes, my device is not jailbroken. – Freek Sanders Mar 02 '15 at 11:57
  • I tried your code on the simulator (XCode 6.1, iOS 8.1) and it did not work, the code can't find the path to the socket **"/var/run/lockdown/syslog.sock"** (maybe the location of the socket on the simulator is different). I'll try tomorrow on a real device. Here is the test code I used on the simulator : **https://gist.github.com/m-amazirh/1b64d6e5107a74f8ce72** In my case when testing on the simulator, the execution stops at line [31](https://gist.github.com/m-amazirh/1b64d6e5107a74f8ce72#file-lorgretriever-L31). And the error is **"No such file or directory"**. – Mohamed AMAZIRH Mar 02 '15 at 20:44
  • @Freek Sanders : Still can't test on an iPhone, but you said that it works on your device, So I awarded you the bounty before it expires. I'll update the question with more informations after I try the code on a real device. – Mohamed AMAZIRH Mar 05 '15 at 10:04
  • @MohamedA. you should only award if it resolves your problem. Also not sure this would be allowed in the app store so it doesn't resolve the problem really. There is a difference between working on non-jailbroken devices and being allowed in the app store and working on non-jailbroken device an **NOT** being allowed in the app store. And I don't believe this is allowed in the App Store. – Popeye Mar 05 '15 at 11:40
  • @Popeye I tested the code today on a real device (not a jailbroken one) and it works !! . I'm not putting the app on the AppStore so it's not a problem. The company I work at, distribute their applications outside of the AppStore using the **IOS Developer Enterprise Program**, and we have a lot of users with iPhones having problems installing our apps after the iOS 8.0 and 8.1.3 update. So this solution is good enough for me because now I have an app that can send back the device logs to my server (without the user having to install additional software on his PC (most of them have Windows)). – Mohamed AMAZIRH Mar 11 '15 at 11:13
  • @MohamedA. Good to hear it's working. If the problem you encounter is during installation, it might be practical to add logging to the app. You could use https://github.com/CocoaLumberjack/CocoaLumberjack if you can get users to send you logs, or something like Flurry if you can't. – Freek Sanders Mar 16 '15 at 10:53
  • Haven't checked the iOS9 beta yet (you're a quick guy ;)) – Freek Sanders Jun 11 '15 at 09:14
  • 1
    I couldn't make this work on iOS 9.2.1. I'm not sure how this should go on but i'm receiving a "nfd = -1", doesn't smell right. Any news on this iOS version? – Felipe Mar 09 '16 at 23:13
0

See below code:

 aslmsg q, m;
 int i;
 const char *key, *val;
 q = asl_new(ASL_TYPE_QUERY);
 aslresponse r = asl_search(NULL, q);
 while (NULL != (m = aslresponse_next(r)))
{
    NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
    for (i = 0; (NULL != (key = asl_key(m, i))); i++)
    {
        NSString *keyString = [NSString stringWithUTF8String:(char *)key];
        val = asl_get(m, key);
        NSString *string = val?[NSString stringWithUTF8String:val]:@"";
        [tmpDict setObject:string forKey:keyString];
    }
    NSLog(@"%@", tmpDict);
}
aslresponse_free(r);
annu
  • 137
  • 4
  • 12
  • This method doesnt work anymore since iOS 7. You can only read your own log entries. – Helge Becker Mar 11 '15 at 13:06
  • This is the method I tried first (I mentioned this in my question). I was looking for an alternative because ASL on iOS7+ gives access only to message logs created by your own application (you can't see message logs created by other apps). This is because of the sandbox system introduced in iOS7. The example given by @Freek Sanders works on iOS7+. – Mohamed AMAZIRH Mar 11 '15 at 15:19
0

Fails to connect to socket, at this line. Is there an alternative available?

if (connect(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) < 0)

Jenis
  • 80
  • 1
  • 7