私たちのiOSアプリケーションでは、改ざん防止チェックを試みています。適用したいのは、テーパー防止技術で使用される一般的な手順です。 Mach-Oファイルの__text
セクションを取得してチェックサムを取得しようとしています。このチェックサムは別のファイルで難読化され、実行中のアプリによって実行時に取得されるチェックサムと照合されます。特に、私たちが適用している手順はそのコードに基づいています(クレジット 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
は、作成されたばかりのMach-Oファイルに対してビルドフェーズが起動された後、コマンドラインツールによって取得されます。
ランタイムとファイルから__text
セクションを取得できましたが、問題はチェックサムが互いに異なることです。この問題を調査すると、ランタイムとMach-Oファイルの読み取りから取得されたメモリマップは、いくつかの16進数値(小さなプログラムでは約6)を除いて、ほとんど同じであることがわかりました。逆アセンブラを使用すると、これらの異なる値はprintf
またはsprintf
実装から取得されているようです。これらの関数を使用して署名を処理します。
このような動作はどのようにして可能ですか?ソースを変更せずにMach-Oファイルから元の署名を取得するために使用できる他の方法はありますか?
元の署名の値をハードコードすることは決してできないことに注意してください。その変更は以前の署名を無効にするためです。
これは、動的リンカーdyld
が原因です。
__TEXT
などの__stubs
セグメントの特定のセクションは、リンクされたシンボルを呼び出すことができるように、dyld
によってメモリ内で変更されます。基本的には、printf
やsprintf
などの外部リンク関数の正しいメモリアドレスを動的に挿入します。
ちなみに、コード署名はすでにこれ以上のことを行っているため、このセットアップ全体はかなりばかげています。彼らがコードを変更している限り、追加の整合性チェックも変更する可能性があります。中途半端なきちんとしたハッカーがそれを倒すのに費やすよりも多くの時間をこれに費やしてしまうとは信じ難い。