Monday, December 30, 2013

Android NinePatch Image Pain

Since 2010, Android has provided 9-patch image support. It enables app developers to specify bitmap areas that can be stretched in order to allow text to fit inside graphics. This format allows custom graphics to be used as buttons with fairly quick ease.  Even iOS appears to have borrowed this concept and third-party libraries are available to leverage this format.

There are a multitude of sites explaining how to use the tool to create your own 9 patch images.  The documentation on Android's site provides a walkthrough about how to use the draw9patch tool to create new ones.  The idea of 9-patch is to define the areas that can be stretched both horizontally and vertically by drawing a line along the outside border.  If you also want to specify where the text should fix, you also need to define the region by creating lines along the right and bottom lines.


The draw9patch tool provides the ability to show how these areas are defined as you change the borders, but it often doesn't show you what the gotchas are.  I encountered some of these hiccups when trying to create custom speech bubbles with the Android Map Utils library.  Here was my first attempt to define the stretchable region:


Here's the result:


The arrow gets stretched too, so we actually want to define two stretchable regions:


The actual result?

Android's documentation says that if the bottom and right lines are not included, it uses the left and top lines.   It doesn't say what happens what happens if two stretchable regions are defined.   To avoid this confusion, we add a black line to the border below:


Now we get:


Also, if you're trying to understand how NinePatch internals works, you actually have to dig into NinePatchImpl.cpp.  The NinePatch implementation is actually a JNI interface to C++ code. The header file ResourceTypes.h is actually the best documented source of how the stretchable regions are defined internally.    The implementation code handles the magic of stretching the regions and proportionately scaling them.  In the example above,  only 1 pixels are defined on both left and right so they will be stretched uniformly.

Underlying all these internals is that Android relies on the Skia 2D library, so many of the esoteric units that start with Sk has to deal with this fact that much of the library deals in SkScalar metrics.   The magic happens with the calculateStretch() function, which figures out how many pixels to scale based on the total boundary size and the remaining number of stretchable regions.  It just turns out that much of Android's graphics library is built with Skia, so trying to understand how any of it works leads you down the path of reviewing the C++ code

Finally, keep in mind that the only way the draw9patch/Android knows that your images are 9-patch drawable is by the file extension.  If you save your custom button with rounded corners with the 9.png file extension, it's likely you'll find your corners cut off since it assumes that the outer 1px borders have been created to define the stretchable and content regions.   There's no magic number type, much less any type of header prefix, so be careful about naming of files.

Furthermore, there's also a source and compiled version of 9.png files.   If you try to retrieve the 9.png file from the .apk file, chances are it will not look like what's in your source code.

Sunday, December 22, 2013

RSA encryption and IntelliJ's 128-bit RSA keys

With the recent press about the NSA's attempts to introduce backdoors into the RSA algorithm and a research paper about how GnuPG encryption keys could be derived from acoustic analysis, I decided to refresh my understanding of how the encryption algorithm works. One of the best intros is located here, and while it doesn't cover the advanced topics of Fermat's Little Theorem, Extended Euclidean Algorithm, or the Chinese Remainder Theorem, it does show it works in basic mathematical terms.

I also noticed that the license keys for IntelliJ's JetBrains products are only using 128-bit RSA encryption keys (updated previously from 256-bits to reflect the right size). The reverse engineering work to figure out how they are generated shows how 128-bit keys can be easily factored and the private key derived from the public key and modulus. Since many of IntelliJ's products are built in Java/Swing, the developers must have known that the bytecode could easily be decompiled. Why would they introduce such weak encryption?   Given that RSA algorithms are license-free, the most plausible explanation to me seems export-related. If they were to use stronger encryption keys, they would be subject to export reviews.

Tuesday, December 10, 2013

DD-WRT, Afraid.org, and truncating base64 hashes

I've noticed that since 2010 that DD-WRT has had problems with correctly implementing Dynamic DNS authentication with Afraid.org.   You enter your username and password, but the authentication fails.   To get around this issue, you need to follow this workaround: http://www.dd-wrt.com/phpBB2/viewtopic.php?p=781615

I decided to poke into the DD-WRT source code to understand why this nuisance continues to persist to this day.  What did I find?  Inside router/httpd/validate.c, DD-WRT makes a call to http://freedns.afraid.org/api/?action=getdyndns&sha=[SHA] where [SHA] represents the SHA1 function of "username|password" (as an example of how this authentication process works, see https://github.com/atdt/afraid/blob/master/afraid/__init__.py#L94)).  The response, assuming you aren't using the Curl command-line, comes in this form:

DYNDNS_HOST|IP_ADDRESS|http://freedns.afraid.org/dynamic/update.php?HASH_VALUE

Afraid.org needs this hash value when DD-WRT reports its DNS location. The problem is that DD-WRT only assumes there are only 36 characters in the hash. The offending section is here: https://github.com/mirror/dd-wrt/blob/master/src/router/httpd/validate/webs.c#L3438

This pull request should fix this issue:

https://github.com/mirror/dd-wrt/pull/5/files

How did I verify?  Well, I saved the response from making an authenticated call to Afraid.org:

>>> open("/tmp/hash", "w").write(urllib.urlopen("http://freedns.afraid.org/api/?action=getdyndns&sha=xxxx").read())

Then I created and compiled this source code to verify:
#include <stdio.h>
#include <malloc.h> char *main() { int i; FILE *in = fopen("hash", "rb"); while (getc(in) != '?' && feof(in) == 0) ; i = 0; char *hash = malloc(64); if (feof(in)) { free(hash); return NULL; } for (i = 0; i < 63 && feof(in) == 0; i++) { hash[i] = getc(in); if (hash[i] == EOF) break; } fclose(in); hash[i] = 0; printf("%s", hash); return hash; }
Note in the bolded text that we have to break out of the loop if the character is EOF, since feof() will only exit after one extra loop.  We could also set the string terminator to be i-1 too (see Stack Overflow article about the challenges feof() presents)

Buffalo WZR-600DHP Firmware Fun

I got this Buffalo WZR-600DHP router thinking that it was a worthy successor to the WRT64GL router, which radio stopped working on me over the weekend. The Buffalo router also had DD-WRT installed, so I thought it meant one less step to install. Turns out to have been a 2-hour pain to configure.

1. The default username/password didn't even work, trying every single combination of admin/password, root/password, buffalo/dd-wrt123, etc. that was possible. I had to hold down the Reset button for 30 seconds to reset everything back to a default state.  (If you hold down shorter than this period, I often had issues resetting the firmware).

2. Soon after tweaking the IP address to something other than the default IP address of 192.168.3.1 and rebooting, the Buffalo router started to create 302 redirects on its web interface to 192.168.11.1. Sure you could use the router so long as you never needed to touch the admin interface. Was there something in the firmware that has some hard-coded setting? Or is it running a DD-WRT version that makes assumptions about its IP address?

 3. The solution for both items was simply to replace Buffalo's stock DD-WRT firmware with the latest one available. Make sure to "reset to default settings" when uploading the new firmware to clear out the old stuff setup by Buffalo.  Buffalo's stock firmware also prevents you from enabling SSH into the router, which makes even harder given problems with #2 to troubleshoot.

4. Don't close any windows until the firmware is flashed.

5. Connect to the router at 192.168.1.1. If you're using DynDNS and setting up with afraid.org, keep in mind that the hostname specified needs to be hostname,hash value (i.e. xxxx.mooo.com,abcde). There's still a bug in DD-WRT with the hash being truncated (http://www.dd-wrt.com/phpBB2/viewtopic.php?p=781615). You can go to http://freedns.afraid.org/dynamic/ to copy/paste the right hash value from the direct URL (i.e. http://freedns.afraid.org/dynamic/update.php?HASHVALUEHERE)

Thursday, December 5, 2013

Running Django's unit tests

1. Create a settngs.py file (or copy from django/conf/project_template/project_name/settings.py).

2. Make sure the databases are setup:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',
        'USER': 'test',
        'PASSWORD': 'test'
    }
}

3. DJANGO_SETTINGS_MODULE=settings PYTHONPATH=. tests/runtests.py

If you get:
from django.utils._os import upath
ImportError: cannot import name upath

Chances are your DJANGO_SETTINGS_MODULE and PYTHONPATH are set.

Sunday, December 1, 2013

Samsung DVR firmware

It turns out that the Samsung DVR firmware is basically a gzip'ed package of an embedded lighttpd Linux kernel built for the ARM processor.   If you're curious about the code that it runs, you can mount the image and inspect.  There's no source code included, but Samsung should probably open source it given there are so many inherent securrity vulnerabilities.

1. Download the firmware http://www.samsungsv.com/Support/DVRFirmwareUpdate

2. Unzip the zip file.

3. Gzip decompress the .img file.

4. If you have Linux, you can mount the jffs2 file.  You'll need to install the packages to mount JFFS2 images.

sudo apt-get install mtd-tools
sudo modprobe -v mtd
sudo modprobe -v jffs2
sudo modprobe -v mtdram total_size=256000 erase_size=256
sudo modprobe -v mtdchar
sudo modprobe -v mtdblock

5. Create a mount directory for the image (i.e. mkdir /mt/tst)

6. Create the JFFS2 image by writing to the /dev/mtdblock0 device:

sudo dd if=hi3520_data.jffs2 of=/dev/mtdblock0

7. Mount the device:
mount -t jffs2 /dev/mtdblock0  /mnt/tst


8. Much of the web server code resides in the /root/www directory.