web-dev-qa-db-ja.com

Mach-Oの実行時チェックサムが実行可能チェックサムと異なる

私たちの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ファイルから元の署名を取得するために使用できる他の方法はありますか?

元の署名の値をハードコードすることは決してできないことに注意してください。その変更は以前の署名を無効にするためです。

1
Andrea

これは、動的リンカーdyldが原因です。

__TEXTなどの__stubsセグメントの特定のセクションは、リンクされたシンボルを呼び出すことができるように、dyldによってメモリ内で変更されます。基本的には、printfsprintfなどの外部リンク関数の正しいメモリアドレスを動的に挿入します。

ちなみに、コード署名はすでにこれ以上のことを行っているため、このセットアップ全体はかなりばかげています。彼らがコードを変更している限り、追加の整合性チェックも変更する可能性があります。中途半端なきちんとしたハッカーがそれを倒すのに費やすよりも多くの時間をこれに費やしてしまうとは信じ難い。

3