Nella nostra applicazione iOS stiamo cercando di effettuare un controllo anti-manomissione.
Quello che vorremmo applicare è una procedura comune utilizzata nelle tecniche anti-tapering. Stiamo cercando di ottenere la sezione __text
di un file Mach-O e ottenere un checksum da esso, questo checksum sarebbe offuscato in un file separato e abbinato a un checksum ottenuto in fase di esecuzione dall'app in esecuzione.
In particolare la procedura che stiamo applicando si basa su quel codice (credito a 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
è ottenuto da uno strumento a linea di comando dopo la fase di build avviata contro il file Mach-O appena prodotto.
Siamo riusciti a ottenere la sezione __text
dal runtime e dal file, il problema è che i checksum sono diversi l'uno dall'altro.
Ispezionando il problema abbiamo scoperto che la mappa della memoria ottenuta dal runtime e dalla lettura del file Mach-O è quasi identica, tranne che da alcuni valori esadecimali (in un piccolo programma circa 6).
Usando un disassemblatore sembra che quei diversi valori provengano da printf
o sprintf
di implementazione.
Usiamo queste funzioni per elaborare la firma.
Come è possibile un simile comportamento? C'è qualche altra metodologia che può essere utilizzata per recuperare la firma originale dal file Mach-O senza modificare la fonte?
È importante notare che non possiamo in alcun modo codificare il valore della firma originale poiché tale modifica annullerebbe la precedente.