5

I am building an iOS app that transmits sensitive data to my server, and I'm signing my API requests as an additional measure. I want to make reverse engineering as hard as possible, and having used Cycript to find signing keys of some real-world apps, I know it's not hard to find these keys by attaching to a process. I am absolutely aware that if someone is really skilled and tries hard enough, they eventually will exploit, but I'm trying to make it as hard as possible, while still being convenient for myself and users.

I can check for jailbroken status and take additional measures, or I can do SSL pinning, but both are still easy to bypass by attaching to the process and modifying the memory.

Is there any way to detect if something (whether it be Cycript, gdb, or any similar tool that can be used for cracking the process) is attached to the process, while not being rejected from App Store?

EDIT: This is not a duplicate of Detecting if iOS app is run in debugger. That question is more related to outputting and it checks an output stream to identify if there's an output stream attached to a logger, while my question is not related to that (and that check doesn't cover my condition).

Community
  • 1
  • 1
Can Poyrazoğlu
  • 33,241
  • 48
  • 191
  • 389
  • Possible duplicate of [Detecting if iOS app is run in debugger](http://stackoverflow.com/questions/4744826/detecting-if-ios-app-is-run-in-debugger) – Anya Shenanigans Dec 07 '15 at 11:50
  • 1
    @Petesh nope, see my edit. – Can Poyrazoğlu Dec 07 '15 at 11:58
  • It seems to be *exactly* what you're asking - it uses the sysctl to check if the process is being traced, which is what happens when you run a process under a debugger or have one attached while you're running. – Anya Shenanigans Dec 07 '15 at 12:01
  • @Petesh no. it is one way and *may* work under certain circumstances, but it may not cover all the cases. – Can Poyrazoğlu Dec 07 '15 at 12:44
  • 1
    The linked item will detect an attached debugger. Cycript injects a `.dylib` into the process - I think it's called `cynject` - you can use the `_dyld_image_count()` and `_dyld_get_image_name()` calls to get the names and find a match. I'm pretty sure neither route would get you rejected from the app store. I don't have a jailbroken iOS device to detect these on. There's also a socket `/System/Caches/.s.cy.(pid)` that's used to talk from Cycript to the program (I think this may get you rejected). – Anya Shenanigans Dec 07 '15 at 14:39
  • @Petesh okay, I'll try to test with it, could you post it as an answer so I can accept it when I try, or if it doesn't work, tell you why it doesn't after I try. – Can Poyrazoğlu Dec 07 '15 at 14:52

3 Answers3

6

gdb detection is doable via the linked stackoverflow question - it uses the kstat to determine if the process is being debugged. This will detect if a debugger is currently attached to the process.

There is also a piece of code - Using the Macro SEC_IS_BEING_DEBUGGED_RETURN_NIL in iOS app - which allows you to throw in a macro that performs the debugger attached check in a variety of locations in your code (it's C/Objective-C).

As for detecting Cycript, when it is run against a process, it injects a dylib into the process to deal with communications between the cycript command line and the process - the library has part of the name looking like cynject. That name doesn't look similar to any libraries that are present on a typical iOS app. This should be detectable with a little loop like (C):

BOOL hasCynject() {
    int max = _dyld_image_count();
    for (int i = 0; i < max; i++) {
        const char *name = _dyld_get_image_name(i);
        if (name != NULL) {
            if (strstr(name, "cynject") == 0) return YES;
        }
    }
}

Again, giving it a better name than this would be advisable, as well as obfuscating the string that you're testing.

These are only approaches that can be taken - unfortunately these would only protect you in some ways at run-time, if someone chooses to point IDA or some other disassembler at it then you would not be protected.

The reason that the check for debugger is implemented as a macro is that you would be placing the code in a variety of places in the code, and as a result someone trying to fix it would have to patch the app in a variety of places.

Community
  • 1
  • 1
Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122
  • how can i implement in in a swift project? – Konstantinos Natsios Jan 22 '18 at 09:32
  • How can you you implement what exactly? There are essentially 3 answers in here - two links to other answers and this code, which can be translated to swift with a bit of effort (you'll need a bridging header for the `_dyld_image_count` and `_dyld_get_image_name` functions) – Anya Shenanigans Jan 22 '18 at 13:06
4

Based on @petesh's answer, I found the below code achieved what I wanted on a jailbroken phone with Cycript. The existence of printf strings is gold to a reverse engineer, so this code is only suitable for demo / crack-me apps.

#include <stdio.h>
#include <string.h>
#include <mach-o/dyld.h>

int main ()
{
        int max = _dyld_image_count();
        for (int i = 0; i < max; i++) {
            const char *name = _dyld_get_image_name(i);
            const char needle[11] = "libcycript";
            char *ret;

            if ((ret = strstr(name, needle)) != NULL){
                printf("%s\nThe substring is: %s\n", name, ret);
            }
        }

    return 0;
}
rustyMagnet
  • 3,479
  • 1
  • 31
  • 41
  • really clever! did you actually try this? and do you think that presence of `libcycript` lead to a rejected app? – Can Poyrazoğlu Jul 11 '17 at 08:29
  • Hi @CanPoyrazoğlu I set my demo app to crash when it detects this library. Cycript is injected into the app's process on the jailbroken iOS device. The next step is to make sure the crash actually scrambles what data is in the memory heap, pre-exit. – rustyMagnet Jul 11 '17 at 09:44
  • any way to implement it in swift? – Konstantinos Natsios Jan 22 '18 at 09:24
  • yes @KwnstantinosNatsios. My front end app was written in Swift and I was using a bridging header file to access C functions like the above. But you could also re-write in Swift. The Bundle API can tell you all dynamically loaded frameworks. You search through that to see whether Cycript, Frida or Frida-Gadget was present. – rustyMagnet Jan 23 '18 at 08:13
2

As far as I know, Cycript process injection is made possible by debug symbols. So, if you strip out debug symbols for the App Store release (the default build setting for the Release configuration), that would help.

Another action you could take, which would have no impact on the usability of the App, would be to use an obfuscator. However, this would render any crash reports useless, since you wouldn't be able to make sense of the symbols, even if the crash report was symbolicated.

Sheamus
  • 6,506
  • 3
  • 35
  • 61
  • Stripping a binary - i.e. a non-debug version - slows down a reverse engineer who is using tools like Hooper, ida pro, radare2 etc. In my opinion, it does not help a great deal against Cycript or Frida. Once you can inject a Cycript or Frida library, you are performing run-time modifications and can dump the class or functions with minimal effort. – rustyMagnet Jul 10 '17 at 09:56