CSAW 2018 WhyOS Writeup
This is a writeup of the WhyOS challenge at CSAW 2018.
This is a forensics challenge worth 300 points.
We are given an iPhone application and a debug log with 185k lines.
My team originally solved this challenge by running the application on a jailbroken iPhone and observing the debug log from that session, but the prompt said that no device was needed, so I set out to find an alternative but reasonable solution method based on information I already had.
I spent a lot of time searching for certain strings, but I instead could have searched simply for an uncommon logging format. There was an hint released later that I didn't see but definitely would have helped. However, this method works without that hint.
I first wanted to take a look inside the application, so I extracted it and looked at the extracted files.
dpkg-deb -x com.yourcompany.whyos_4.2.0-28debug_iphoneos-arm.deb app
root@kali:~/csaw/app# ls */*/*
Library/MobileSubstrate/DynamicLibraries:
whyOS.dylib whyOS.plist
Library/PreferenceBundles/whyOSsettings.bundle:
Info.plist Root.plist whyOSsettings
Library/PreferenceLoader/Preferences:
whyOSsettings.plist
There appeared to be two binary files and four property list files. Two strings from those files that appeared interesting were com.yourcompany.whyossettings and CSAWRootListController along with those in Root.plist more generally.
However, when I searched for whyOS in the debug log, I was very surprised to only find two lines that contained that term, considering how important and broad it seemed.
I searched for CSAWRootListController along with other interesting terms in Root.plist, but I had no results. There were too many instances of flag for me to browse.
I looked for several hundred lines above and below the whyOS matches because I thought that's where the activity happened, but I did not find anything useful.
I ran strings on the binaries, but didn't find anything that would lead to the flag, so I decided to take a look closer look in IDA, even though this wasn't a reversing challenge, which made me unsure if I was on the right track.
I looked at whyOS.dylib, but it basically didn't contain anything, so I moved on to the other file.
There are two functions, specifiers and setflag. I decompiled them with IDA to get a better look at what they do.
id __cdecl -[CSAWRootListController specifiers](struct CSAWRootListController *self, SEL a2)
{
void *v2; // r0@2
struct CSAWRootListController *v4; // [sp+10h] [bp-Ch]@1
v4 = self;
if ( !*(_DWORD *)&self->PSListController_opaque[OBJC_IVAR___PSListController__specifiers] )
{
v2 = objc_msgSend(self, "loadSpecifiersFromPlistName:target:", CFSTR("Root"), self);
*(_DWORD *)&v4->PSListController_opaque[OBJC_IVAR___PSListController__specifiers] = objc_msgSend(v2, "retain");
}
return *(id *)&v4->PSListController_opaque[OBJC_IVAR___PSListController__specifiers];
}
This method just looks like it loads specifiers from Root.plist, which may be useful for the program but isn't that interesting for us since that's all it does.
The setflag method obviously sounds more interesting for us.
void __cdecl -[CSAWRootListController setflag](struct CSAWRootListController *self, SEL a2)
{
void *v2; // r0@1
__CFString *v3; // [sp+4h] [bp-34h]@2
void *v4; // [sp+20h] [bp-18h]@1
v2 = objc_msgSend(&OBJC_CLASS___NSMutableDictionary, "alloc");
v4 = objc_msgSend(v2, "initWithContentsOfFile:", CFSTR("/var/mobile/Library/Preferences/com.yourcompany.whyos.plist"));
if ( objc_msgSend(v4, "objectForKey:", CFSTR("flag")) )
v3 = (__CFString *)objc_msgSend(v4, "objectForKey:", CFSTR("flag"));
else
v3 = &stru_8044;
NSLog(CFSTR("%@"), v3);
}
It looks like the function creates a dictionary from the whyos.plist file and looks for the value for the key flag. If it exists, it sets a string variable to that value. Otherwise, that string is set to what is actually an empty string.
Finally, and most importantly, it logs the string to the console.
I looked online for the default NSLog output format and discovered the format.
Date Time OurApp[<processid>] output
Since the NSLog call is just printing out the string, which is the flag, with no extra parameters, we can expect our output to be in a known format.
X Y Z flag
Looking through the debug log, the lines are incredibly verbose, so having just one word might stand out. Additionally, we know the flag is not in the standard flag{} format from the description.
I would say flags are generally at least 10 characters long, and since there are no curly brackets, may just contain alphanumeric characters, although there could still be underscores.
We can take a look at some example lines from console log.
default 18:59:15.308337 -0400 nsurlsessiond [5757 <private> stream, bundle id: mobileassetd, pid: 386, url...
default 18:59:15.308698 -0400 nsurlsessiond 0.000s [5757 E6368DF7-4A60-45CF-A579-4C1AF76AE40E <private> ...
error 18:59:15.308772 -0400 nsurlsessiond Task <A4B92146-9429-451D-B0FC-88C778AE5177>.<29> finish...
default 18:59:15.308823 -0400 nsurlsessiond 0.000s [5757 E6368DF7-4A60-45CF-A579-4C1AF76AE40E <private> p...
default 18:59:15.309335 -0400 nsurlsessiond 0.000s [5757 E6368DF7-4A60-45CF-A579-4C1AF76AE40E <private> p...
default 18:59:15.309412 -0400 nsurlsessiond 0.000s [5757 E6368DF7-4A60-45CF-A579-4C1AF76AE40E <private> ...
default 18:59:15.309464 -0400 nsurlsessiond 10.801s [5757] path:cancel
We want to get the part that the application actually logs, not the metadata at the start. We can use cut to get that part, but first we need to remove the extra spaces using tr before the application logging so we can easily make a single space as a delimiter for cut.
tr -s '[:space:]' < console.log | cut -d" " -f5-
Now that we have the application part, we are only interested in what might be our flag. Based on our previous criteria, we search for 10 or more alphanumeric characters at the start of a line followed by the end of a line.
tr -s '[:space:]' < console.log | cut -d" " -f5- | grep -E '^[[:alnum:]]{10,}$'
We see that there are only 296 lines of one word as output, which we can easily look at quickly for anything interesting.
root@kali:~/csaw# tr -s '[:space:]' < console.log | cut -d" " -f5- | grep -E '^[[:alnum:]]{10,}$' | wc -l
296
There are a lot of technical and repeated terms, but in the noise we see a clear outlier.
postPearlInterlockFollowUpItem
DeviceDiscoveryActivate
DeviceDiscoveryActivate
DeviceDiscoveryUpdate
postPearlInterlockFollowUpItem
postPearlInterlockFollowUpItem
keybagDidUnlock
ca3412b55940568c5b10a616fa7b855e
postPearlInterlockFollowUpItem
Invalidating
Invalidated
There appears to be a hash!
If we search for this in the debug log, we find the line.
root@kali:~/csaw# grep -n 'ca3412b55940568c5b10a616fa7b855e' console.log
97190:default 19:12:18.884704 -0400 Preferences ca3412b55940568c5b10a616fa7b855e
The application is Preferences, which makes sense since this appears to be a settings or preferences related application.
The flag is ca3412b55940568c5b10a616fa7b855e.
Overall, I did not really enjoy this challenge. The moderator of the challenge on chat noted that the application was "littered with hints" about where to look, but I disagree.
The hint that it was a hex string made it more reasonable, but that hint came late and the challenge still remained a "grepping" challenge. We had to use a jailbroken iPhone before the hint to find the flag, which should not have felt like it was important when the challenge explicitly said a device was not needed.
Anyway, I'm glad I came up with an alternative way, but I think I was too focused looking for a keyword or something else in the debug log that would hint at the location to think of a method of filtering by format.