Wednesday, April 08, 2009

Extract UIKit artwork

While writing an iPhone application (more about it on this blog soon), I wanted to tint my UIAlertView with the same color I tinted my other controls. Maybe this is not a good idea, but I wanted to try anyway. Unfortunately, only UISearchBar, UISegmentedControl, UINavigationBar and UIToolbar have the tintColor property. So I started digging into UIKit. The first place to look was obviously the -(void)[UIAlertView(Private) drawRect:] method. To draw its background, a UIAlertView calls _popupAlertBackground which in turn call _UIImageWithName(@"UIPopupAlertSheetBackground.png"). This file probably lies inside the UIKit.framework would think a Mac developer. In fact it is not there. Only two pngs are there: DefaultWallpaper-iPhone.png and DefaultWallpaper-iPod.png. So, where is it?

It is in fact somehow embedded in the Other.artwork file which contains UIKit artwork. This file is memory mapped at application startup, probably for performance reasons and the content is easily accessed with the private _UIImageWithName() function. _UIImageWithName looks up in a dictionary that associates a file name to some memory location that contains the actual image data.

Now, it would be cool to extract all this content to see what artwork UIKit contains. It turns out to be quite easy. The only trick is to find a reference to the dictionary containing all the file names.

If you want to experiment yourself, just create a new View-based Application in Xcode and paste this code in your view controller.



Then connect a button to the extractArtwork: action and that's it, you're done. This will obviously only work in the Simulator, but for having a look at the images, it should be enough.

In an attempt to encourage comments, I'm offering a beer at NSConference to the first person who will clearly explain how to find the magic address of the images dictionary (except for Nicolas who knows the answer already) and I'm offering two beers to the one who provides a cleaner way to obtain a reference to the dictionary that is not UIKit version specific ;-)

Edit: I have updated the code so that it is not UIKit version specific, so I won the beers myself that I'm going to drink right away.

Monday, March 23, 2009

chmod, ACL and symbolic links

Let's say you want to set an ACL on a symbolic link. So you try the -h option of chmod that is documented to change the mode of the link itself rather than the file that the link points to.

  $ ln -s aFileThatDoesNotExist myLink
$ /bin/chmod -h +a "everyone allow delete" myLink
chmod: Failed to set ACL on file 'myLink': No such file or directory

So, it seems myLink was followed even though we passed the -h option. Let's try with a link that points to an existing file:

  $ touch aFileThatExist
$ ln -fs aFileThatExist myLink
$ /bin/chmod -h +a "everyone allow delete" myLink
$ ls -le myLink aFileThatExist
-rw-r--r--+ 1 cluthi staff 0 5 mar 18:11 aFileThatExist
0: group:everyone allow delete
lrwxr-xr-x 1 cluthi staff 14 5 mar 18:12 myLink -> aFileThatExist

Our hypothesis is confirmed, the link was followed. Let's investigate and have a look at chmod source code (file_cmds-188 on darwinsource).

chmod.c:125 variable hflag is set to true if the -h argument is passed
chmod.c:388 function modify_file_acl() is called, the hflag is not passed to this function

Doh, lazy boys! Let's patch it and add a follow argument to the modify_file_acl() function:
int modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow);
and call it with modify_file_acl(..., !hflag)

From acl_set(3) man: The acl_set_link_np() function acts on a symlink rather than its target, if the target of the path is a symlink. Perfect, that's what we need: we must call acl_set_link_np instead of acl_set_file in order not to follow the link.

chmod_acl.c:807 acl_set_file is called: (0 != acl_set_file(path, ACL_TYPE_EXTENDED, oacl))
Replace with (0 != (follow ? acl_set_file(path, ACL_TYPE_EXTENDED, oacl) : acl_set_link_np(path, ACL_TYPE_EXTENDED, oacl)))

With this small modifications, the -h option should be respected. Rebuild chmod and try again:

  $ ./chmod -h +a "everyone allow delete" myLink
chmod: Failed to set ACL on file 'myLink': Operation not supported
$ ln -fs aFileThatDoesNotExist myLink
$ ./chmod -h +a "everyone allow delete" myLink
chmod: Failed to set ACL on file 'myLink': Operation not supported

Now, whether the link points to an existing file or not, we get the Operation not supported error. That's better diagnostic, but not exactly what we expected :-( So, why is it not supported? Let's dig a bit more. acl_set_link_np implementation is found at Libc-498.1.5/posix1e/acl_file.c:175 and is:

  return(acl_set_file1(path, acl_type, acl, 0));

The last argument (follow) passed to acl_set_file1 is 0 and the first lines of acl_set_file1 implementation reads:

  if (follow == 0) {
/* XXX this requires some thought - can links have ACLs? */
errno = ENOTSUP;
return(-1);
}

We have the explanation of the Operation not supported error we got earlier. Note that the comment is not mine, it is actually in the libc source code!

Does it mean it is impossible to set an ACL on a symlink? Does it mean our only option is to file a bug asking some Apple engineer to think harder if links can have ACLs? Fortunately no. There is a third function in the acl_set(3) API: acl_set_fd, which acts on a file descriptor. Hopefully, getting the file descriptor of a symlink is as simple as open(path, O_SYMLINK).

That's it. With this patch, you'll be able to run ./chmod -h +a "everyone allow delete" myLink and have the ACL to be set on the symlink!

If you need to set ACLs on symlinks on a daily basis, I suggest you do not overwrite /bin/chmod but install the patched version of chmod in /usr/local/bin. Well, if you read that post till there, you probably know that already.

This bug has been reported to Apple and is known as radar #6264303 which is a duplicate of radar #5684438.

Sunday, January 04, 2009

Using your own address book in the iPhone Simulator

Requires a jailbroken iPhone with openssh:

scp mobile@iPhone:~/Library/AddressBook/* ~/Library/Application\ Support/iPhone\ Simulator/User/Library/AddressBook
Say bye bye to John Appleseed & Co.

Wednesday, October 15, 2008

Kagi Registration Module (lack of) security

I'm in the process of choosing an eCommerce partner for selling my future shareware. I have narrowed down to eSellerate and Kagi as they are widely adopted by Mac shareware developers. After reading their respecting obfuscated pricing policies, I decided to have a look at what they offer for integrating the purchasing process into the application.

Kagi offers the Kagi Registration Module (KRM) which is basically a library that provides an in-application one click purchase experience. Sounds pretty good. I start reading the KRM developer documentation and stumble on the Security section:

The ZonicKRM submits orders through an SSL connection for security, however pricing information is currently passed from the application to the ZonicKRM as an XML string.

If this data is not checksummed, or otherwise protected, a malicious user may be able to edit the XML string within an application's executable and submit an order with an invalid price.

In the long term, this attack will be denied by moving the responsibility for pricing information from the KRM library to the KRM server. When this process is complete, vendors will be able to override the pricing information in shipping copies of an application using their Kagi database entry.


WHAT THE FUCK ? The user is able to choose the price he wants to pay ? Can't be true, this part of the documentation must be outdated. Guess what... it's not, long term is long term!

Note that checksumming or protecting is pure bullshit as long as the price comes from the application and not Kagi's server.

I searched for the first shareware using KRM I found, opened it with an hex editor, did search and replace of the string 30.00 to 01.00 and I indeed successfully ordered the shareware for $1.

This is totally irresponsible from Kagi. I don't know how this registration scheme could have been designed this way in the first place. No sensible person can design an ordering system where the price is set by the client.

Please don't flame me, I'm a good guy. I contacted the $30 shareware author and offered to pay the remaining $29 I owe him. I should have searched a bit longer for a cheaper shareware. $1 + $29 is a bit expensive to demonstrate that KRM sucks ;-)

The Bottom Line
Don't use KRM as long as you can't set the price of your shareware on Kagi's server.

Wednesday, September 03, 2008

QuietXcode

Have you ever noticed the Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x319a380, has non-zero refcount = 1 messages in the console ? Chris Espinosa replied about these warnings to Matt Neuburg:


The second one is an error deep in some system framework when running under Garbage Collection that we have not been able to track down yet. It simply means that somebody has neglected to do a final release on a memory block that nobody has kept a pointer to (making the final release technically impossible). The Garbage Collector knows the block is inaccessible and is freeing it, but is warning us that somebody forgot to formally release it before the pointer to it went out of scope. Bad form, but no actual harm.

I think it actually harms. These messages are filling the console so much it becomes unusable. I'm a big fan of GeekTool and I always have the tail of the console on the desktop. Now, it looks always the same and interesting messages from various applications are lost in the mass of free_garbage messages:

Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x319a380, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x31bf6e0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3263da0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3274eb0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x319a380, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x31bf6e0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3263da0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3274eb0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x201c8b0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3256110, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x201c8b0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3256110, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x2045540, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x208ca10, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x2045540, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x208ca10, has non-zero refcount = 1

So I decided to tackle the problem and here is my Solution: QuietXcode 1.1.4 (5 KB). This is an Xcode plugin that patches the culpable call to malloc_printf("free_garbage: garbage ptr = %p, has non-zero refcount = %d", ...).

You can build the plugin either by typing xcodebuild in a terminal or by building it (⌘ + B) in Xcode. Building the plugin will also automatically install it into your ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins folder.

Once it's installed, you have to relaunch Xcode. You should see the message <QuietXcode> loaded successfully in the console and no more free_garbage messages.

The plugin performs a safe patch, that is, if it does not find the expected call to malloc_printf and the expected Xcode version (greater than or equal to 3.1/1099), it logs a more or less comprehensive error to the console. Have fun browsing the source code, it demonstrates how to use the dyld and mach-o apis to locate non exported symbols and the mach api to dynamically patch code.

Note that the plugin is for i386 only, porting it to ppc and/or 64 bits is left as an exercise to the reader.

Friday, August 22, 2008

Exploring iPhone OS 2 files

It turns out to be pretty simple:


  1. Download iPhone OS 2.0.2 (5C1)
    curl -O http://appldnld.apple.com.edgesuite.net/content.info.apple.com/iPhone/061-5241.20080818.t5Fv3/iPhone1,2_2.0.2_5C1_Restore.ipsw

  2. Unzip the ipsw which is actually a zip file
    unzip iPhone1,2_2.0.2_5C1_Restore.ipsw

  3. Download vfdecrypt
    svn co http://iphone-elite.googlecode.com/svn/trunk iphone-elite

  4. Compile vfdecrypt
    make -C iphone-elite/vfdecrypt_win32

  5. Decrypt the dmg (key from The iPhone Wiki)
    ./iphone-elite/vfdecrypt_win32/vfdecrypt -i 018-3978-1.dmg -k 31e3ff09ff046d5237187346ee893015354d2135e3f0f39480be63dd2a18444961c2da5d -o iPhoneOS-2.0.2.dmg

  6. Mount iPhone OS dmg and start exploring
    open iPhoneOS-2.0.2.dmg

Do not buy iQuarantine X

From iQuarantine X website:


  • iQuarantine X is not a background script or a script that gets attached to files or folders.
  • iQuarantine X is the first application to make the LEOPARD FILE QUARANTINE ALERTS go away.
  • iQuarantine X is the easiest way to rid LEOPARD of all FILE QUARANTINE ALERTS.

So, if it's not a script, what is it (beside a scam) ?
It's a hack that binary patches a system framework (/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore) by short-circuiting the private function _FSAllocateQuarantineData.

So we have four good reasons not to buy it:
  1. It has an unacceptable upgrade policy.
  2. It binary patches a system framework.
  3. Developer does not reply to e-mails.
  4. You can disable the Leopard quarantine for free with an official technique.

Thursday, July 10, 2008

The missing NSLocale documentation

+ (NSArray *)preferredLanguages
Return Value
The user's language preference order as an array of NSString objects, each of which is a canonicalized IETF BCP 47 language identifier. This is defined by the user in System Preferences → International → Language.

For your information: this is stored in the gloal domain in the AppleLanguages key.

+ (id)currentLocale
Return Value
The logical locale for the current user. The locale is formed from the settings for the current user’s chosen system locale overlaid with any custom settings the user has specified in System Preferences → International → Formats.
This method may return a retained cached object.


+ (id)autoupdatingCurrentLocale
This one I have not understood how it is supposed to be used. If someone knows, please let us know in the comments.

Now, if what you want is the current language, you should not use either of these methods. Instead you should use something like NSLocalizedString(@"ISOLanguageCode", @"iso language code"). Then you have to define "ISOLanguageCode" = "en"; etc. in all your Localizable.strings files.