Cleaning Up a WordPress Hack With an iPad

June 2nd, 2012

I received an email today from someone saying "hey, check your website, it looks like you've got a virus".

I'm not sure how this happens. I use strong passwords, change them regularly, and try to keep my WordPress installation up to date, and yet every now and then, my site gets hacked.

I was at home watching the kids, doing some reading on the iPad when I got the email, and I decided to investigate. Using the tool I had in my hand. The iPad.

To see what was going on I loaded up the site in Safari and went to view the page source. The damage done to the site, at least to my site, in the past, has been in the form of spammy links that aren't visible to the end user. I guess they're leveraging the PageRank of my site to try to raise the rank of certain other sites.

Anyway, I needed to view the page source. Whoops, can't do that in Safari. But I found a fairly simple workaround, that involves installing a View Source bookmarklet that redirects through an external server that just sends you back the source for the page you're viewing. Instructions on setting that up are here, and this is worth doing now before you need it.

Looking at the page source, I could see there was a large block of links, inside a comment block, near the bottom of every page. This usually indicates that the WordPress [cci]footer.php[/cci] file has been hacked to include some text that if fetches on page load.

I can check on this using the WordPress dashboard, so I loaded that up. Because I use strong passwords and change them regularly, I don't know what my password is for my blog, but 1Password does. I recently installed the 1Password Pro app on my iPad, and set up 1Password sync through DropBox, so I was able to get the password there. (It's a bit awkward getting the password through the 1Password app onto the clipboard, but at least it's possible).

So now I'm in the site, and I see I was wrong. There's no evidence of a hack in the [cci]footer.php[/cci] file.

Next step is to ssh into the server and start looking around. I'd just installed another app, the new Diet Coda, and it's got an SSH shell built in. I had to switch back to 1Password again to copy the shell password for my site (it's different from my blog password) to paste into ssh, but once that's done. I have a shell session.

My strategy for finding the problem is to locate calls to the JavaScript eval function, which is used by most of these WordPress viruses. So in the ssh shell, [cci]grep -r eval *[/cci] finds a number of files I need to check.

To actually examine the files I used Diet Coda again. It's SFTP support lets me browse the server file system, and directly view and edit files on disk.

This story has a happy ending. I managed to determine that the offending file was [cci]themes/steam/style.php[/cci] which had a large chunk of complex JavaScript in it. This malicious code was inserting the bogus links. I edited it to remove that code, saved the change, and verified that those links on my blog are gone.

I'm a bit annoyed that I need to do this at all, but it was impressive that I was able to tackle the whole problem from start to finish with the tools I already had on my iPad.

App Review Times

May 29th, 2012

App store review times, since as far back as I've been working on iOS apps, have been about a week. I'm starting to think this is intentional.

Traditionally, software developers (at least, ones who sell software through some mechanism that allows downloads of the final product) would release new versions of their apps on their own schedule. This meant that if there was a bug fix that needed to be pushed out, that could be done at any time.

With my own iOS apps (primarily MealPlan and Resume Designer) , I've run into the situation where I released a version that had an issue that I wanted to fix right away. Fixing and testing the problem took just a few hours. Uploading the new version to iTunes took just a few minutes. But then the update was in the review queue with everyone else, waiting a week for Apple to bless it before I could release it. Meanwhile, I'm receiving emails from users who are running into the problem and all I can say is "I'm sorry, I'm waiting for Apple".

Well, there is one other option. You can request an expedited review from the App Review Board. I did this once, and I was approved (and my app was updated within just a few hours), but the email I received made it clear that I shouldn't try to do this again any time soon. So this isn't an option for a minor bug fix - save it for when you really need it.

Anyway, back to my point: If Apple wanted app review times to be shorter, then they would make it so. The review process scales out. Adding more reviewers would shorten review times.

"But app quality would suffer!". I don't believe the reviewers are there to check the quality of your apps. Apps (and I'm not just talking about mine here) get through all the time with major bugs, features that don't work, crashes, and other problems you'd expect the reviews to catch. No, I think the reviewers are there to check for things like whether the URL associated with your app working? Is the icon or description offensive? Is it in the right category? In other words, things that protect the integrity of the store, not necessarily the users.

These are easy things to check for. It's almost a Mechanical Turk job, except for the secrecy involved.

So why doesn't Apple do this? My theory is that it's because the current process is working for them.

If there were no built-in waiting period in the review process, then I could post an app, have it reviewed, and have the update in the store in hours (or even minutes - typical "In Review" times are about a half hour).

If this were the case, then I could release a new version every few hours, if I wanted to. And some developers would do just that. Add a minor feature, release. Fix a bug, release. I'm pretty sure Apple doesn't want that.

For one, Apple pays for the downloads. If your app is 200mb, then that's 200mb times the number of customers you have worth of bandwidth every time you release an update. The review time is a gating factor that prevents you from releasing more than about 50 updates a year.

And frequent updates would also frustrate users. When you publish an update, thousands or millions of phones get a little badge on their App Store icon. Lots of users like to keep all their apps updated. I know I'm a bit compulsive about hitting the Update All button whenever the badge says there's an update, but lately it seems like I'm doing that every day.

Imagine how much worse it could be.

So that's why I think the review process will stay exactly as it is. At least it's predictable.

Editable UITextView with Links

May 4th, 2012

A number of users requested that they be able to add notes to a meal in MealPlan, and users have also requested being able to add links. I wanted to add a single field for notes, which could include one or more links. The note field would need to be editable, but the links also need to be clickable.

UITextView supports both these things, but not at the same time.

I wanted this when viewing:

201205041639-1
But when you tap to edit, the field becomes editable.

201205041639
Turns out this is very easy to set up.

In a nutshell, you install a tap gesture recogniser on the view that, when a users taps on it, makes it editable and assigns keyboard focus to it. The tap gesture recogniser is triggered when the user taps anywhere on the field, but if the user taps and holds on the link, they are given the opportunity to open it.

201205041641

Setting this up requires a gesture recogniser on the UITextView that makes the field editable and transfers focus to it when the user taps on it, and requires that your UITextViewDelegate set the field back to non-editable when the user is done editing. Here's code that accomplishes all these things.

[cc lang="objc"]
// Add the tap gesture recogniser to the view.
- (void)configureTextView {
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textViewTapped:)];
textView.editable = NO;
textView.dataDetectorTypes = UIDataDetectorTypeLink;
[textView addGestureRecognizer:recognizer];
}

// Notification from the recogniser that the UITextView was tapped
- (void)textViewTapped:(UIGestureRecognizer *)recognizer {
UITextView *textView = (UITextView *)recognizer.view;
textView.editable = YES;
[textView becomeFirstResponder];
}

// UITextViewDelegate method
- (void)textViewDidEndEditing:(UITextView *)textView {
textView.editable = NO;
}
[/cc]

Static Libraries in Xcode

April 27th, 2012

I wanted to do something that I think is straightforward, basic IDE functionality. Have a project that references a static library, use the static library from a client application (including referencing headers and linking with it), and have the library automatically rebuild when changes are made.

With most IDEs this is functionality that you get without even having to think too hard about it. You create a library, you say "this application depends on this library", and *poof* it just works. Xcode makes you jump through a few hoops.

Here are the steps involved in making this work, starting from scratch.

1. Create a new workspace. We'll start from scratch so we're both on the same page. BTW, I'm using Xcode 4.3.2.

2. Create the application project. I'm creating an empty iOS project called TestApp. Build and run, make sure it works.

3. Create the library project. I created a Cocoa Touch Static Library called TestLib. One gotcha here is that the default location for the library project will be inside the TestApp project. That's not what I wanted; I wanted the library and the project that uses it as peers in the Project Navigator (so that the library can be shared with other projects). To make this happen, make sure you select the workspace as the group for the new project when the file dialog appears to ask you where to save it:

201204270643-1
The first thing I want to accomplish is getting access to the library's public headers from the app. Xcode will copy the library's public headers as a build step, but this needs a little configuration. Here are the steps for that:

4. Mark the library project as Shared. Open the Manage Schemes… panel and click the Shared checkbox for the library project.
201204270648

201204270647-1

5. Add the headers we want to share to the static library's Copy Headers build step. Click on TestLib in the project navigator, click on the target, and drag TestLib.h into the Public section of the Copy Headers item.

201204270650

Now if you build the test library project, you should see this header being copied in the build output.

201204270707

That crazy target path means the headers are going into Xcode's derived data folder, in the folder it creates for products of our build. If you wanted your headers in a subdirectory so that you could include them as <TestLib/MyHeader.h> then you can edit the "Public Headers Folder Path" in Build Settings for the library project.

6. Set the Skip Install setting in the static library to YES. Otherwise, you can run into trouble archiving your project.

So the library is ready. Now we need to make TestApp depend on the library. There are a couple of steps to setting this up.

7. Edit the TestApp scheme, and in the Build section, add TestLib as a target to build.

201204270655

8. Add the TestLib library to TestApp. Click on the TestApp project, and in the TestApp target, on the Summary tab, in the Linked Frameworks and Libraries section, hit the + button and add the TestLib library.

201204270727

9. Add the common location for the public header files to the header search path.

201204270703

Now to test things out. Let's add some code to the object whose header file we made public. I'll add a simple method, "testString", that returns a string.

In TestLib.h:

@interface TestLib : NSObject
+ (
NSString *)testString;
@end

and

In TestLib.m:

+ (NSString *)testString {
return @"Hello";
}

And in the test application, where Xcode created a default AppDelegate object, simply add an NSLog that uses this test method to see the string printed.

In MYAppDelegate.m:

#import "TestLib.h"
// and later
NSLog(
@"%@", [TestLib testString]);

Build and run the application. With any luck, it will print "Hello" to the console.

Now, back in the static library, change "Hello" to "Goodbye", and run again. This prints "Hello", indicating that Xcode didn't recompile the static library. You want the static library to rebuild when you change files in it and run the test app, and there seems to be a bug in Xcode 4.3.2 that causes this to not work (so this fix may not apply if you're running a newer version). There's a workaround for this here but in a nutshell…

10. Select the static library in the Navigator pane.

201204270737-1

11. In the File Inspector on the right, change the Location to Relative to Build Products.

201204270739

12. Select TestApp and select Show in Finder. This should open a Finder window containing the Xcode project.

13. Select the Xcode project and right-click or Control-click to bring up the context menu, and select Show Package Contents.

14. Select the "project.pbxproj" file and Open it with TextEdit.

15. Look for the reference to libTestLib.a and change it so that instead of being a relative path, it's just the filename. For example, if it's "../Debug/libTestLib.a" change it to just "libTestLib.a".

16. Run TestApp again. This time, and from now on, the static library should rebuild whenever you change something in it.

Most of this writeup is basic "how to use a static library in Xcode", but steps 12 through 15 are a workaround for what seems to be a bug in Xcode.

If you still have trouble with this, check out this page, which has some great information on troubleshooting static libraries. This page is a modified version of the method he suggests, where I'm copying the public headers during the build process (which seems to be how Xcode is designed to work) and he's adding them as Source Trees. Switching between these two methods isn't a big deal and doesn't affect the overall process much, so you can pick one you like.

Avoid Premature Localization

April 21st, 2012

Localization is one of the signs of a mature software product. I did a bit of research into whether localizing an app translates into increased sales, and in my non-scientific review of blog posts and forum posts from developers discussing the issue, the conclusion I came to is that localization doesn't make that much sense for games, but does increase sales of non-game apps by about 25%.

25% isn't incredible but it's nothing to sneeze at either. It's not a lot of work to localize an iOS app. Xcode and Cocoa makes it easy to do from the development side, and there are translation houses that will provide localization services on the order of $0.05 per word. The apps I've been working on seem to average about 2000 words, so that's $100 to translate, per language. French, German and Italian currently provide the best localized app sales (reference), so that's $300 to translate.

There's another cost to localization that's important to take into account, and that's the incremental cost of developing your application.

Localization means having multiple copies of any resource that includes text. This includes your nib files, storyboard files, localized string files, graphics, and any other assets (sample documents, for example).

Once you've localized, any change to your application will need to be translated. If you need to fix a bug, and the bug fix requires a new error message, then that means a trip through the localization service for that new error message. This increases your turnaround time for fixing that bug.

Same with any new features. If you add new user interface to your main storyboard, you'll need to send it for translation, and then when it comes back, you'll need to go into the UI for each language and make sure it looks good, that the strings fit, and so on.

And you'll need to test your app in every language, on every device. I expect many developers take shortcuts here and just check the interface in Interface Builder, but I have been going to the trouble to reboot the device in every language I support and run the app to make sure things work.

The message I'm trying to convey here is, localization is worthwhile, but put it off until your app has reached a point where you're not actively developing it. Take a look at your product's roadmap (or make one, if you haven't considered it) and look for a spot where it makes sense to localize.

In my case, with MealPlan, I have a roadmap that includes adding iPhone support and adding iCloud support. Once these two major features are done, I will localize it. Otherwise I know I'd be making multiple localization passes, and those features would take longer to develop if the app were already localized.

(Note that I'm talking about localization here - localization is really part two of making an app world-ready. Part one is globalization, which means making sure that your app is referencing strings in a localizable way, not hard-coding things like date formats and number formats and so on. Globalization should be done from day one).

One last comment on localization. Support. If you ship your app with a German translation and a German user sends you a question in German, what are you going to do? I don't have an answer for that one yet.

Fall Day Software

April 19th, 2012

It's been 5 months since my employment with Adobe ended. I've been doing some interesting contracting work since then, but also putting some effort into my own independent iPhone and iPad apps.

That's been a lot of fun. I started a company called Fall Day Software (http://www.falldaysoftware.com) and built two apps I'm really proud of, as well as a couple of smaller kid-focused apps that I did for our own kids.

MealPlan was my first app, and it's one I created for myself. I still use it, every week, to plan out our meals, and my own usage drive the features I add to the app. I'm working on an update now that will add automatic grocery list generation, which I think will be a real time saver. Once you fill out the week's meal plan (with the help of the "magic wand" that makes suggestions and fills things in for you), the app will know what groceries you need, and you can toggle "need it" or "have it" for each item.

Eventually I want to have an iPhone version so that with iCloud syncing, you could bring up the meal plan on your phone, but for now the workflow will likely be that you an print it or email a copy to yourself to refer to in the store.

The other app I built, which is now available for both the iPhone and the iPad, is Resume Designer. It was originally called Resume Maker but that name seems to be already in use, so I changed it. I think Resume Designer is turning out quite nicely.

The different usage patterns of users picking up MealPlan and Resume Designer makes for a bit of a quandary. MealPlan users will use the app regularly, and appreciate feature updates. Resume Designer users use the app once, or just a few times, and then forget the app even exists.

I'd like to think that Fall Day Software is building personal productivity tools and will someday build a following of people who appreciate the kind of apps I'm building. Maybe a community of users won't spring up around Resume Designer, but that is a goal I have for my other apps.

That’s one way to find viruses

April 6th, 2012

I just received an obviously-spam email, that was sent to an email address I don't use along with a bunch of other people. The URL looked interesting because it used words like wiki and Railroad. I used to open these in a browser just to see what's what, but since Mac attacks have been stepping up lately, I took a look in curl.

The URL in the email looked like a hacked wiki, which pointed to a throwaway domain, which pointed to what looks like a generated addresss on a site who's name makes it sound like it's a Microsoft Security site (but obviously isn't, as it's in the .info TLD).

Anyway, here's the core of the page's virus scanning engine:

[cc]
per = Math.round((i*100)/503);
[/cc]

And it would use this value to pick which trojan it was going to tell you it "found" on your computer.

No Recourse

March 28th, 2012

This is a frustrating situation for me. My iPad app, Resume Maker, has received a bad review.

I know that bad reviews are just part of doing business - some users will give a one-star review because your app doesn't have a feature that they thought it might have, I guess as an encouragement for you to add the feature. But this particular bad review was troubling. Here it is:

This app started off great... Spend hours typing up my resume on an ipad...click the email function as per picture...app immediately closes. So if you want an resume only available on your ipad, this app is for you! Everybody else who needs to email a resume they spent hours on only to find out that you can't stay away!

This was a one-star review by a user named Neurochippy in Australia.

Here's why this review is bothering me: There is absolutely nothing I can do to help this user.

Rather than contacting me, they posted their problem as a review. There's a Report a Problem link on the same page where you'd post a review, but instead of asking for help, this user took to the soapbox to warn other users not to use the app.

I can understand this. It's frustrating to lose a lot of time to a crashy app. But I don't believe Resume Maker is a crashy app.

For one thing, I've sold quite a few copies. If the app really did crash for users when they used its most important function, I'd have heard about it by now. So there's something going on for Neurochippy that's not happening to other people, and I'd love to know what.

iTunes Connect, the site that app developers use to track their apps once they're in the store, has a section for downloading crash reports. But in my experience, it takes a lot of crashes before the crash report shows up in iTunes Connect, so for smaller apps, or apps that don't crash often, this isn't as useful as it sounds. There are no crash reports for Resume Maker.

So what are my options?

It seems to me I only have two. I can sit back and wait for iTunes Connect to gather enough crash reports that it will tell me what's going on, or I can sit back and wait for a user to email me for support, at which point I can ask them to send me a crash log.

Neither of these options is very good.

In the meantime, there's my app, in the store, with one review, and that one review is warning people not to download the app. It's a serious deterrent to future customers, and there's nothing I can do about it.

Visioneer DocAir App and iCloud Sync

March 26th, 2012

One of the projects I've been working on lately is iOS software to accompany Visioneer's cordless mobile scanner. The app is in the App Store now, here, but you'll need one of these to use it.

One of the things that makes this app special is that it uses iCloud to sync your scanned documents between devices. This makes for a slick workflow when you're on the road. You can scan a document using the scanner in a hotel room or at a customer's site, and the document will automatically sync, through iCloud, to any other iOS device on the same iCloud account.

In developing this, I came across an incredibly useful feature of iCloud, where Apple will actually host images for you and provide links to the document in iCloud that you can include in email messages.

It's easy, using a high resolution scanner that can output multi-page PDF files, to make very large documents. So you've scanned an 80mb PDF and now you want to send it to someone. What do you do?

The magic here is NSFileManager's URLForPublishingUbiquitousItemAtURL:expirationDate:error:, which lets you query iCloud for an URL that you can use to refer to the item in iCloud.

This function gives you a URL like this one:

https://www.icloud.com/documents/dl/?p=03&t=BAJ_zAdNJv869B6wNp4BKYhjFjWsGPzJS2kB

There's no credentials required to access the data at the URL, but the URL itself is not guessable and the request is https, so this is as secure as your email. The link will expire at some point (you can request a particular expiry time, but I believe the default is about a week) so this isn't permanent hosting, but it's a good way to send a scanned image to someone.

You could do this with DropBox or other services, or by uploading the file to an FTP site, but the nice thing about doing this with iCloud is there's zero setup. Assuming the device is already using iCloud (and most iOS devices are), this just works.

Worst Case Scenario

March 19th, 2012

Well, that was no fun. Yesterday morning, I ran my iPad app, MealPlan, and it crashed. It wouldn't launch - it just exited. I wasn't at home, where I could debug it, and I wanted to show the app to someone, and I figured the problem was something to do with the fact that I had two copies of the app installed (one by Xcode and one by the App Store) so I just deleted and reinstalled it.

Later yesterday, I got an email from a user, telling me that the app was crashing on launch.

Uh oh.

I apologized to the user, said that they were "the first report person to report this" (when I hear that from a company I always assume they're just saying that to make me feel better, but in this case it really was true), and asked if they wouldn't mind sending me a crash log.

I had checked iTunes Connect, and there were no crash reports. Even now, it still says "Too few reports have been submitted for a report to be shown". But without a crash log, I had no idea what was going on. The instructions I sent to the user were:

One thing that would help would be if you could send me a crash log. It's a bit of work to go and get it, but it would help give me some idea what's going wrong in the application. To get a crash log, sync the iPad with your computer, and then look in these directories on your computer:

Mac: /Library/Logs/CrashReporter/MobileDevice/<iPad name>
Windows: Application Data/Apple Computer/Logs/CrashReporter/MobileDevice/<iPad name>

Fortunately the user was willing to go to some trouble to get the app working again, so they sent me a crash log. The crash log showed MealPlan on the call stack:

Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x3691e32c 0x3690d000 + 70444
1 libsystem_c.dylib 0x36b77208 0x36b2a000 + 315912
2 libsystem_c.dylib 0x36b70298 0x36b2a000 + 287384
3 libsystem_c.dylib 0x36b90cba 0x36b2a000 + 421050
4 MealPlan 0x00043f84 0x3a000 + 40836
5 MealPlan 0x000401b2 0x3a000 + 25010
6 MealPlan 0x0003cf6c 0x3a000 + 12140
7 UIKit 0x30f88a7e 0x30f5c000 + 182910
8 UIKit 0x310868fc 0x30f5c000 + 1222908

So this was a real clue. The crash log from the device doesn't show symbols, and to know what's going on I needed to turn the locations in the crash log into symbols that I could look up in my source. Xcode makes this easy: Drop the .crash file onto the Device Logs section of the Organizer window.

201203190650-1

If everything is working, the bottom of the Organizer window will briefly say that it's Symbolicating your crash log:

201203190652

And then it will show you the crash log with the locations in the crash log converted into symbols that point right to where the problem is happening in your source.

Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x3691e32c __pthread_kill + 8
1 libsystem_c.dylib 0x36b77208 pthread_kill + 48
2 libsystem_c.dylib 0x36b70298 abort + 88
3 libsystem_c.dylib 0x36b90cba __assert_rtn + 174
4 MealPlan 0x00043f84 -[MPDocument day:] (MPDocument.m:76)
5 MealPlan 0x000401b2 -[MPViewController populate] (MPViewController.m:197)
6 MealPlan 0x0003cf6c -[MPMainView scrollViewDidScroll:] (MPMainView.m:78)
7 UIKit 0x30f88a7e -[UIScrollView setContentOffset:] + 682
8 UIKit 0x310868fc -[UIScrollView(Static) _smoothScroll:] + 3604

Note that for this process to work, Xcode must have access to the symbols for the binary you submitted to the App Store. When you choose Archive for your project, Xcode builds an archive file and adds it to the organizer. You must keep this archive file for any binary you submit to the App Store, so that you can symbolicate your crash logs.

Anyway, once symbolicated, the problem became a bit more clear. I had an assertion at line 76 of MPDocument.m that would fire if there were duplicate entries in my Core Data database. Basically if the app went to get a record for a particular day, and found more than one, it would assert. I leave these asserts on in the release version so that if something does go wrong, even at runtime, the app will crash, rather than continuing on with bad data. The assert pointed right to the problem, where if I didn't have the assert, the app may have continued but would have been updating the wrong record - so sometimes the data from one of the duplicate rows would show up, and sometimes the other would. No good.

After a bit of sleuthing, I managed to figure out what was going on. MealPlan uses UIManagedDocument, and opening a UIManagedDocument is asynchronous. MealPlan would ask the document to open, and then when the document opened, would populate the main view.

Problem is, the main view was active during the brief window while this was happening, and if the user tapped on a row during this interval, the app would create a record in the database before the document was completely open. This led to the inconsistency. The solution for me was to set userInteractionEnabled to NO on my main view while the document was opening, and then set it back to YES when the UIManagedDocument's open completion handler was called. And to clean up the mess that the previous version had made.

The update to 1.0.1 is waiting in Apple's submission queue, and due to the nature of the problem, I've submitted a request for an expedited review (there's a form for that here). So hopefully the fix will go live today.

It's really disappointing when users run into a problem like this "in the wild". For some reason, a lot of people ran into this problem yesterday - I had five reports by the end of the day - even though there's nothing special about yesterday that should have triggered this. But in every case, users weren't angry or looking for a refund, they just wanted to get back to using the app. That makes me happy. :)