En nuestra aplicación para iOS, estamos tratando de hacer una verificación contra la manipulación indebida.
Lo que nos gustaría aplicar es un procedimiento común que se utiliza en las técnicas de reducción. Estamos tratando de obtener la sección __text
de un archivo de Mach-O y obtener una suma de control de la misma, esta suma de comprobación se confunde en un archivo separado y se compara con una suma de comprobación obtenida en tiempo de ejecución por la aplicación en ejecución.
En particular, el procedimiento que estamos aplicando se basa en ese código (crédito para 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
se obtiene mediante una herramienta de línea de comandos después de la fase de compilación iniciada en el archivo Mach-O que se acaba de producir.
Hemos logrado obtener la sección __text
del tiempo de ejecución y del archivo, el problema es que las sumas de comprobación son diferentes entre sí.
Al inspeccionar el problema, hemos descubierto que el mapa de memoria obtenido en el tiempo de ejecución y al leer el archivo de Mach-O es casi idéntico, excepto por unos pocos valores hexadecimales (en un programa pequeño de aproximadamente 6).
Al usar un desensamblador, parece que esos valores diferentes provienen de la implementación printf
o sprintf
.
Usamos esas funciones para procesar la firma.
¿Cómo es posible tal comportamiento? ¿Existe alguna otra metodología que pueda usarse para recuperar la firma original del archivo Mach-O sin modificar la fuente?
Es importante tener en cuenta que no podemos codificar de ninguna manera el valor de la firma original, ya que ese cambio invalidaría la anterior.