Showing posts with label ios. Show all posts
Showing posts with label ios. Show all posts

Monday, April 10, 2017

Changing the line spacing only for the last line in a label with attributed text

Lately I've been working on a code-only UICollectionView, and I've been experimenting a little bit with the self-sizing UICollectionViewCells. Needless to say, nightmares followed. Having been involved in Android development previously, it was never clear to me why UILabels behave the way they do – the default behavior being truncating – and not automatically create new lines without what's called Auto Layout.

I wanted to create a collection view, which would list the notifications in an app. I was after something that looked like below:


The notification text could be arbitrarily long, with the time indicator being a little bit lower at the bottom. Now, every single developer that has a little bit of experience with auto layout knows that working with multiple labels is a one-way ticket to programmer's hell. So using a separate label for the time was a no-no. Thankfully, there's also NSAttributedString. With this type of object, it is possible to create a string which is composed of multiple substrings, each with their own custom styling. So I could create the text above using something like below:

notificationAttrText = NSMutableAttributedString(string: notificationText, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 14)])

timeAttrText = NSMutableAttributedString(string: "\n\(timeText)", attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 11), NSForegroundColorAttributeName: UIColor.init(colorLiteralRed: 158/255, green: 158/255, blue: 158/255, alpha: 1)])

let combined = NSMutableAttributedString()
        
combined.append(_nameAttrText)
combined.append(_verbAttrText)
notificationLabel.attributedText = combined

This was enough to create my label to look the way I wanted it, but... The time text was pretty close to the notification text.
At first I thought this is a pretty easy one. There is a way to add paragraph styling to an NSMutableAttributedString, by specifying the NSMutableParagraphStyle which you create, and also the range of the text you want to apply this attribute on. The drawback of this method is that you need to know the exact length of the line above. This is because you need two lines to define the spacing between them , so the range must be from the beginning of the line above, to the ending of the line below. In my case I needed to know the range for this piece of text "spacing only for the last line in a NSAttributedText\n2 hours ago". Obviously, it's not an easy thing to do. There's no easy way to get a specific line in a label. So I was doomed...

Chapter II - Redemption

Except I wasn't. NSMutableParagraphStyle to the rescue, everyone. But how, I hear you asking. Well, the answer lies on another property of this type of object. Not lineSpacing, but lineHeightMultiple. By using this property, I only needed to know the range for the time text, which I could very easily determine.

let paragraph = NSMutableParagraphStyle()
paragraph.lineHeightMultiple = 1.2
            
let length = _timeAttrText.length
let start = combined.length - length
            
combined.addAttribute(NSParagraphStyleAttributeName, value: paragraph, range: NSMakeRange(start, length))

The actual line height is multiplied by the value you assign to the lineHeightMultiple attribute, so a value of 1.2 would actually raise the height of the line by 20%, giving me the result I wanted.

Tuesday, February 7, 2017

Use constants for your dynamic UITableView sections

I started this blog as a way to post random thoughts on reverse-engineering and related stuff. However, I also do iOS development (Android development too, for that matter). Hence, this...short blog post.

Let's suppose we are writing a fictitious app about displaying pictures of dogs and cats, separated in two sections: one for the dogs, and the other one for the cats. A common pattern seen in code written in tutorials all over the web — and books...sigh — when creating dynamic table views, is having code similar to that shown below:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 0 {
        return dogs.count
    } else if section == 1 {
        return cats.count
    } else {
        return 0
    }
}


This is only one method, which we use to determine how many rows each section will have. Now, we've decided that it would be cool if we displayed pictures of dogs in a custom UITableViewCell, and pictures of cats in another custom UITableViewCell:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.section == 0 {
        let cell = tableView.dequeueReusableCell(withIdentifier: "doggoCell", for: indexPath) as! DogCell
        cell.dogImage = dogs[indexPath.row].image
        return cell
    } else { // indexPath.section == 1
        let cell = tableView.dequeueReusableCell(withIdentifier: "catCell", for: indexPath) as! CatCell
        cell.catImage = cats[indexPath.row].image
        return cell
    }
}

Alright, looking good. Now we decide that it's time to provide a custom title for each section:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == 0 {
        return "No bad boys allowed"
    } else if section == 1 {
        return "We can haz cheezburgerz"
    }
    
    return nil
}

We deserve a pat on the back for the hard work, except that we need to do, ahem...one last thing. Somehow we decide that it would be a great idea to include a special section for owls. Don't get me wrong though, not just any type of owl. Superb owls only. As if that made any difference. But I have even better news: market research has shown that our app would have much greater success if we placed cats first, followed by owls, and lastly dogs (sorry dogs, nothing personal).

I hope that by now you understand that we've crafted our failure with our own hands. Hardcoding sections (and rows for that matter) in the various table view delegate and data source methods is woefully wrong. The solution to this problem is simple: declare constants for each section (or for rows, similarly). Doing this would make it easier for us in the future should the need arise to add some kind of other animal in our app:

let sectionDogs = 0
let sectionCats = 1

In such a case, our original titleForHeaderInSection method would look like this:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == sectionDogs {
        return "No bad boys allowed"
    } else if section == sectionCats {
        return "We can haz cheezburgerz"
    }
    
    return nil
}

Not only would it be easier to the eye to understand which section you are setting up, but also the late minute changes described above (adding the owls) would be a breeze. Let me illustrate this only for the titleForHeaderInSection method:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == sectionDogs {
        return "No bad boys allowed"
    } else if section == sectionCats {
        return "We can haz cheezburgerz"
    } else if section == sectionOwls {
        return "Don't call me, owl call you!"
    }
    
    return nil
}

And we would only need to add the sectionOwls constant, and also adjust the sectionDogs and sectionCats constants:

let sectionDogs = 2
let sectionCats = 0
let sectionOwls = 1

So by merely changing the values of each of the constants, we could easily reorder sections, without touching the data source and delegate methods.

I hope this was an interesting read, and I really hope that this will prevent any (further?) headache to future developers who are just starting out.

Sunday, September 4, 2016

A First Look at Tweak Development: Enabling File URL Support for Safari

I've always been interested in getting into tweak development, but I've been busy doing other things, and I've also not had a jailbroken iPhone (sigh). A few days ago, I got my golden chance. It was a request on the r/jailbreak subreddit, asking for a tweak to enable browsing the filesystem using the Safari browser through the file:// protocol/scheme. I imagined it was a simple tweak to develop, which would be suitable for a first time tweak.

 The Problem

Normally, the Safari app on iOS, doesn't allow viewing files in the filesystem. If you would attempt to browse a local file, using an URL like file:///path/to/my/file, you would be greeted with a message:

Safari cannot open the page because it is a local file.
The error message displayed in iOS 9.3.3

Our mission is to grab the MobileSafari binary from our iDevice, and then find out where in the app this alert is triggered.

 Prerequisites

We really need to talk about the prerequisites to following this tutorial. First of all, it is a no brainer that you need to know Objective-C. It follows that you also need to know a little bit of C. I'm not saying you should be able to write a compiler in C — you only need to understand the basics, and preferably be able to write a simple class in Objective-C.

Now, for this tutorial, you won't need to know ARM assembly — I know I don't (albeit I do have experience with x86 assembly). ARM assembly is a must if you wish to become a good mobile reverser, however. Also, you will need a disassembler. I'll be using a trial version of Hopper (https://hopperapp.com), but feel free to use what you have. Hex Rays (https://hex-rays.com) also offers IDA as a demo version which is known to work with 32 bit binaries (it doesn't work with 64 bit binaries).

Next, you need to have Theos set up in your development environment. I won't go into the details of setting up Theos, but I might do so in another post.

Lastly, you need to have a jailbroken iOS device, which I suppose you already do.


 Examining the Binary

Now that we are ready, let's first retrieve the binary for Safari. In order to do that, connect to your iOS device with your favorite SCP client, and retrieve the following file:

/Applications/MobileSafari.app/MobileSafari

Now let's load the binary in our disassembler. The first thing to do after loading the binary, is to search for the error string that Safari shows when we try to browse the filesystem. Hopper has a very nice user interface — it allows us to quickly search for a string within the binary, on its left pane.

Searching for the string
Search results

We search for the string "local file", and the first result in the list is actually the string we are looking for. So we go ahead and double click it — this takes us to the location of the string in the disassembly.

Now that we found the string, we need to find the part of the code which accesses the string. Hopper is once again very useful: we click on the address (0x1001698ac), and then press the 'x' key. This shows us the list of the parts of the code which have references to the string we found.

References to the string we found
List of references to our string

As we can see, there is only one reference to the string we found. We select the only result, and then click on the Go button. This will take us, in the disassembly, to the part of code which accesses our string.

Code that references our string
Code that references our string

I know you might be puzzled about what this is. First of all, notice that there is a bunch of similar things, prefixed by the dq pseudo-instruction, which stands for declare-quad (word). We can see that between each line there is a difference of 0x10, or 16 bytes. So each line, or entry, is made of 16 bytes, or 4 words. Each word represents a thing. We are dealing with some sort of internal representation of strings. Each entry is made of the type, which for the string is ___CFConstantStringClassReference, a word which should be a bunch of flags for internal purposes which seems to be the same: 0x7c8 (I'm not sure about it, nor do we care), another word which is the actual address of the string literal (does 0x1001698ac ring a bell?), and finally another word which seems to be the length of our string. Indeed, 0x37 is 55 in decimal, which just happens to be the length of the error string.

Let's click on the address once again (0x1001911b0, it's highlighted in the screenshot above), and press 'x' once again.

Code that uses our string finally shows up

We get this nice popup once again, and this time we found our target. We can see that it is a method in the TabDocument class which starts with _decidePolicyForAction. Let's click on the Go button, and find out its full name.

This time we will land on real code in the disassembly. What we need to do is scroll up until we see that "Beginning of Procedure" thing that Hopper nicely shows for us.

The method we should place our hook on

There it is, in all its glory, the method which shows us that nasty popup when we try to browse our filesystem from Safari. This means we should hook onto this method, in order to defeat Safari and make it obey to us. So our target method is _decidePolicyForAction:request:inMainFrame:forNewWindow:currentURLIsFileURL:decisionHandler: of the TabDocument class.

But what should we do, specifically?

 A Little Bit of Theo(s)ry

Before we can start working on anything, we should create the project for our tweak. Open up your terminal, and type $THEOS/bin/nic.pl. Theos will prompt you for the basic configuration needed:

Eltons-iMac:arm Elton$ $THEOS/bin/nic.pl
NIC 2.0 - New Instance Creator
------------------------------
  [1.] iphone/activator_event
  [2.] iphone/application_modern
  [3.] iphone/cydget
  [4.] iphone/flipswitch_switch
  [5.] iphone/framework
  [6.] iphone/ios7_notification_center_widget
  [7.] iphone/library
  [8.] iphone/notification_center_widget
  [9.] iphone/preference_bundle_modern
  [10.] iphone/tool
  [11.] iphone/tweak
  [12.] iphone/xpc_service
Choose a Template (required): 11

So initially Theos asks you for the template. Here we enter 11, because we want to write a tweak.

Project Name (required): fileProto

It also asks you for the project name, which you can feel free to change :)

Package Name [com.yourcompany.fileProto]: com.youcanchangeit.fileproto

Next is the package name, which you can choose to change (recommended).

Author/Maintainer Name [Default Name]: Your Name

Next it asks you for the author's name.

[iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.apple.mobilesafari

This is the bundle filter for the process we are going to hook onto. We enter com.apple.mobilesafari, which is the bundle filter for the MobileSafari process.

[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: MobileSafari

Here is the list of applications which should be terminated upon our tweak's installation, so that the tweak is loaded. We enter MobileSafari, because we want Safari to restart so that our tweak get's loaded.

Instantiating iphone/tweak in fileProto/...
Done.

And, this is it. The tweak project is set up in the fileProto directory (or whatever the project name you chose was).

The project directory structure is rather simple: there is control, which is just a text file containing meta-data about our tweak, Makefile, which is used by Theos in the build process, projectName.plist (in my case fileProto.plist), and also the Tweak.xm file. We will only play with the Tweak.xm file, where the source code of our tweak will reside. This file also contains some comments which help you when you're writing your first tweak.

I know you can't wait to get your hands dirty, so let's learn just enough Logos for our purposes. Logos is the set of directives which we use in our tweak code to enable them to hook on methods we choose, and it looks pretty nice, too.

The directives start with a % symbol, followed by the directive name. We are going to use only four directives: %hook, %orig and %log...%end. I placed %end intentionally where it belongs, in the end. Let's now see how these directives are used.

The %hook directive is used to hook onto a specific class. So we create what is called a hook block, and we place all the methods we want to hook onto, inside this block.

%hook ClassName
    -(void) methodName {
        // yay! we hooked on a method
    }
%end

The hook block is closed with the %end directive. Inside the block we place the method we are going to replace (the method must exist, obviously). There are ways to add new methods, but this is out of the scope of this post. The %hook directive will automatically include the ClassName.h header. So where do we get this header? Well, we could use a tool named class-dump, or search the web if some nice guy has already uploaded the headers somewhere (There's also class-dump-z, but I used class-dump as I'm on a Hackintosh). Using class-dump is really easy, all you have to do is type the command:

Eltons-iMac:arm Elton$ ./class-dump MobileSafari > MobileSafari.h

This will dump the headers of our MobileSafari binary into MobileSafari.h. Now we have some housecleaning to do. Open MobileSafari.h, and find the string @interface TabDocument. Now delete everything above this string. Now find the string @end. Delete everything below this string. Now we are left with the header of the TabDocument class only. But, we can take it further.

Replace the long string in the beginning @interface TabDocument : NSObject <AppBannerMetaTagContentObserver... (until the first curly bracket {), with simply @interface TabDocument. Now delete everything between the curly braces. Next, delete everything between @interface TabDocument and @end, except for the definition of our method, which should look like this:

- (void)_decidePolicyForAction:(id)arg1 request:(id)arg2 inMainFrame:(_Bool)arg3 forNewWindow:(_Bool)arg4 currentURLIsFileURL:(_Bool)arg5 decisionHandler:(CDUnknownBlockType)arg6;

Lastly, replace CDUnknownBlockType with id. Now save the header. The header file should now look like this:

@interface TabDocument

- (void)_decidePolicyForAction:(id)arg1 request:(id)arg2 inMainFrame:(_Bool)arg3 forNewWindow:(_Bool)arg4 currentURLIsFileURL:(_Bool)arg5 decisionHandler:(id)arg6;

@end


Now feel free to rename MobileSafari.h into TabDocument.h, this way the header can be included automatically.

Now we are going to use %log and %orig. %log is used to log all the parameters, whereas %orig is used to call the original version of the function we are hooked onto. Right now we are going to hook onto the method we found earlier, log all the arguments and then call the original function.

So let's load our Tweak.xm file and write this code:


%hook TabDocument
    - (void)_decidePolicyForAction:(id)arg1 request:(id)arg2 inMainFrame:(_Bool)arg3 forNewWindow:(_Bool)arg4 currentURLIsFileURL:(_Bool)arg5 decisionHandler:(id)arg6 {
        %log;  // this is enough to log all our arguments :)
        %orig; // and this is all that's needed to call the original function with the original arguments
    }
%end

We can see how easy Logos makes it to log the arguments and call the original function with the supplied arguments. Now let's build our tweak and install it, by opening the terminal, changing the working directory to the directory of our project, and then running:

make package install

This will compile our tweak, build the deb package inside the packages folder, and then install it into our device. Next, we should open up Safari, browse to a URL like file:///test and then inspect syslog for the logged arguments. Here is a good article which explains how to read syslog on your iDevice.

You should be able to find something like the following in your syslog:

Sep 4 23:09:20 Eltons-iPhone MobileSafari[6377] <Notice>: [fileProto] Tweak.xm:4 DEBUG: ...

Something of interest in the log message is the request argument: there we can see that this is actually of type NSMutableURLRequest. Also, it is important to notice that currentURLIsFileURL has a value of 0, so it probably doesn't do what it looks like it does. My first thought was actually to set this argument to NO, but it was NO already, i.e. 0.


 Writing the Tweak


We concluded the previous section with a brief note about two seemingly important arguments, where one argument was actually useless.

Now, we could attempt to rewrite the whole method we hooked onto, but that would be too difficult: I promised we won't need ARM disassembling skills. Well, there is an easier way.

By using our critical thinking skills, we can conclude that, since currentURLIsFileURL is useless, the only argument that has information about the URL being visited is the request argument. So, if we mess with that argument, and temporarily set the URL to a fake URL, perhaps we could get away with it and have a working tweak. Sounds like a plan.

%hook TabDocument
    - (void)_decidePolicyForAction:(id)arg1 request:(id)arg2 inMainFrame:(_Bool)arg3 forNewWindow:(_Bool)arg4 currentURLIsFileURL:(_Bool)arg5 decisionHandler:(id)arg6 {
        NSURL *originalUrl = [arg2 URL];
        
        BOOL urlStartsWithFile = [[originalUrl absoluteString] hasPrefix:@"file://"];
        
        // only make the change if the URL starts with file://
        if (urlStartsWithFile) {
            // set a fake URL
            [arg2 setURL:[NSURL URLWithString:@"http://www.google.com"]];
        }
        
        %orig; // let the original function think we're visiting google :D
 
        if (urlStartsWithFile) {
             [arg2 setURL:originalUrl]; // restore the original URL if it was changed
        }
    }
%end


So we first save the original URL, and then we check if it starts with the file:// prefix. If so, we change the URL temporarily to google.com,  so that the original function doesn't know we're actually trying to view a local file. Next we call the original function, and finally we restore the original URL if it was changed.

Let's build the tweak and install it with:

make package install

Now, let's create a file in our device, by ssh-ing into our device and typing:

echo "hi" > /var/tmp/pwn.txt

The final test is approaching. Open up Safari, and browse to the following URL:

file:///var/tmp/pwn.txt

You should be greeted with something like below:

Congratulations!

This means our small tweak has done its job. You deserve to pat yourself on the back.


 Final Words

We took a first look at tweak development with Theos. We saw how we can take a binary from our device and reverse it to find out more. We generated the headers, took a quick look at Logos, gathered information about the arguments of our target method, devised a simple strategy, and finally we developed our tweak.

You can find the source code of the fileProto project in github. Note: the source code may be a little bit different from what we've presented here, but the idea remains the same.