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.