Jailbreak and Root Detection

When I'm testing mobile applications, as part of static analysis, I always check to see if the application may give me a hard time running on rooted or jailbroken devices. This normally includes searching for common detection strings like "Cydia" within the compiled application, for example;

strings ~/Downloads/UnCrackable_Level_2/Payload/UnCrackable\ Level\ 2.app/UnCrackable\ Level\ 2 | grep Cydia
/Applications/Cydia.app
/Applications/Cydia.app

Over time this of common strings has got quite long, and I end up making a new list for each project.

Another way I like to look for these common detection strings is by searching with radare2, for example;

r2 -c izzq~+Cydia ~/Downloads/UnCrackable_Level_2/Payload/UnCrackable\ Level\ 2.app/UnCrackable\ Level\ 2
0xeea1 24 23 /Applications/Cydia.app
0xef20 36 35 cydia://package/com.example.package

Automate the boring stuff

So I wanted to have a standard script that I could run that would do this for me, both with radare2 and with strings. I also wanted this to work for Android applications. So after little thought, I decided to use python and specifically the r2pipe library to interact with radare2 and then using the sh library fall back to strings.

I ended up with this function

def string_search(app, strings):
    for string in strings:
        try:
            r_pipe = r2pipe.open(app)
            r_strings = r_pipe.cmd('izzq~+{}'.format(string))
            if r_strings:
                yield r_strings
        except Exception:
            pass

    # /bin/strings if radare2 fails
    sh_strings = sh.strings(app)
    for line in sh_strings:
        for string in strings:
            if string in line:
                yield line

Nice, now that the searching function is complete, I added this function to handle user arguments.

def get_args():
    parser = argparse.ArgumentParser(description='[+] root & jailbreak detection')
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('--dex', help='path to android dex file')
    group.add_argument('--ios', help='path to extracted payload binary')
    return parser.parse_args()

And then depending option selected by the user, would result in which set of stings get imported at run-time.

if args.dex:
    app = args.dex
    from jailrootdetector.detections import root_strings
    strings = root_strings
elif args.ios:
    app = args.ios
    from jailrootdetector.detections import jailbreak_strings
    strings = jailbreak_strings
else:
    sys.exit()

print('\n[+] searching\n')
search_results = set(string_search(app, strings))
if search_results:
    print('[+] detection strings found:')
    for string in search_results:
        print('{}'.format(string.strip()))
else:
    print('[+] no detection strings found in: {}'.format(app))

This works well, but it did not quite hit the mark, I wanted this to be callable ate the users shell prompt, and not have to cd into this project directory to run one small script.

Python Poetry

I was using poetry to manage the virtualenv and any other dependencies of this script, so I initially looked at the documentation to see how I could have this installed to the users $PATH, and discovered the tool.poetry.scripts in the pyproject.toml file. This was exactly what I was looking for, so I added in the following;

[tool.poetry.scripts]
jrd = "jailrootdetector.main:main"

This now means users can install the script and then call it with jrd --help, amazing!!

PyPi

All that was left was to upload this to PyPi so people could install it with pip or pipx. poetry also helped here too with the poetry publish --build command.

Now that the script had been uploaded to PyPi, its as simple as pip(3|x) install jailrootdetector, like so;

pipx install jailrootdetector

And then call jrd.

jrd --help
usage: jrd [-h] (--dex DEX | --ios IOS)

[+] root & jailbreak detection

optional arguments:
  -h, --help  show this help message and exit
  --dex DEX   path to android dex file
  --ios IOS   path to extracted payload binary