In our iOS application we are trying to make an anti-tamper check. What we would like to apply is a common procedure used in anti-tapering techniques. We are trying to get the __text section of a Mach-O file and obtain a checksum from it, this checksum would be obfuscated in a separate file and matched with a checksum obtained at runtime by the running app. In particular the procedure we are applying is based on that code (credit to applidium):

#include <CommonCrypto/CommonCrypto.h>
#include <dlfcn.h>
#include <mach-o/dyld.h>

int correctCheckSumForTextSection(const char * originalSignature) {
  const struct mach_header * header;
  Dl_info dlinfo;
  if (dladdr(main, &dlinfo) == 0 || dlinfo.dli_fbase == NULL)
    return 0; // Can't find symbol for main
  header = dlinfo.dli_fbase;  // Pointer on the Mach-O header
  struct load_command * cmd = (struct load_command *)(header + 1); // First load command
  // Now iterate through load command
  //to find __text section of __TEXT segment
  for (uint32_t i = 0; cmd != NULL && i < header->ncmds; i++) {
    if (cmd->cmd == LC_SEGMENT) {
      // __TEXT load command is a LC_SEGMENT load command
      struct segment_command * segment = (struct segment_command *)cmd;
      if (!strcmp(segment->segname, "__TEXT")) {
        // Stop on __TEXT segment load command and go through sections
        // to find __text section
        struct section * section = (struct section *)(segment + 1);
        for (uint32_t j = 0; section != NULL && j < segment->nsects; j++) {
          if (!strcmp(section->sectname, "__text"))
            break; //Stop on __text section load command
          section = (struct section *)(section + 1);
        // Get here the __text section address, the __text section size
        // and the virtual memory address so we can calculate
        // a pointer on the __text section
        uint32_t * textSectionAddr = (uint32_t *)section->addr;
        uint32_t textSectionSize = section->size;
        uint32_t * vmaddr = segment->vmaddr;
        char * textSectionPtr = (char *)((int)header + (int)textSectionAddr - (int)vmaddr);
        // Calculate the signature of the data,
        // store the result in a string
        // and compare to the original one
        unsigned char digest[CC_MD5_DIGEST_LENGTH];
        char signature[2 * CC_MD5_DIGEST_LENGTH];            // will hold the signature
        CC_MD5(textSectionPtr, textSectionSize, digest);     // calculate the signature
        for (int i = 0; i < sizeof(digest); i++)             // fill signature
          sprintf(signature + (2 * i), "%02x", digest[i]);
        return strcmp(originalSignature, signature) == 0;    // verify signatures match
    cmd = (struct load_command *)((uint8_t *)cmd + cmd->cmdsize);
  return 0;

originalSignature is obtained by a command line tool after the build phase launched against the just produced Mach-O file.

We successfully managed to obtain the __text section from runtime and from the file, the problem is that the checksums are different each other. Inspecting the issue we've found out that the memory map obtained from the runtime and from reading the Mach-O file is almost identical, except from a few hex values (in a small program about 6). Using a disassembler it seems that those different values are coming from printf or sprintf implementation. We use those functions to process the signature.

How is such behavior possible? Is there any other methodology that can be used in order to retrieve the original signature from the Mach-O file without modifying the source?

It is important to note that we cannot in any way hardcode the value of the original signature since that change would invalidate the previous one.

  • 2
    What are you hoping to get from that check that isn't covered by iOS application signing in the first place? This is security by obscurity at best. Any algorithm in your iOS app (which the checksum must be) can be run against any change and your file replaced. The advantage of iOS app signing is that it is cryptographic - the app MUST be signed with YOUR private key, which an attacker does not have. And who it is signed by is verifiable by end users. – crovers Oct 5 '16 at 13:51
  • I don't get it if I understood your comment, but this code is implemented to avoid attack on jailbroken devices, where an app can be resigned and/or modified. This control is used to verify the code integrity. A hacker can modify the code using a disassembler, re-sign the app and do whatever he/she wants. If the checksum differs the app will be blocked at startup. – Andrea Oct 5 '16 at 13:58
  • 1
    But if they modify the code using a disassembler, they can jmp over your checksum check too.... – crovers Oct 5 '16 at 14:00
  • not if that particular code and check is obfuscated, but I agree that is not a full solution against an experienced hacker. – Andrea Oct 5 '16 at 14:02
  • 1
    Are you really gaining significantly by investing this effort? – crovers Oct 5 '16 at 14:19

This is because of the dynamic linker, dyld.

Certain sections of the __TEXT segment, such as __stubs get modified in memory by dyld so as to be able to call out to the linked symbols. Basically it is dynamically inserting the correct memory address for your externally linked functions like printf and sprintf.

Incidentally, this whole setup sounds rather silly, as code-signing already does this and more. So long as they are modifying the code, they will might as well modify your additional integrity checks too. It's hard to believe you won't end up spending more of your time on this than a half-way decent hacker will spend defeating it.

  • Thank you, that makes sense. I understand your concerns, but if you take a look at commercial products, security conferences, this kind of setup is always shown as one to be applied. For instance speakerdeck.com/mbazaliy/securing-ios-applications – Andrea Oct 5 '16 at 16:08
  • 1
    @Andrea I have no doubt there are people who spend vast resources doing this. I do doubt that is cost effective though, or even remotely effective at converting pirating users into paying customers. – Alexander O'Mara Oct 5 '16 at 16:21
  • totally agree with yyou – Andrea Oct 5 '16 at 16:33
  • It can be cost efficient if combined with other efforts. It's usually pretty cheap to implement, but it can cause significant hurdles to a hacker if implemented correctly, that's why this setup is recommended at conferences etc. Of course its efficiency relies on the nature of your product and potential rewards hackers can get by removing your protection. – vladich Jun 4 '18 at 21:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.