難読化手法(Software Obfuscation)

2007/10/11

ソフトウェアの難読化に興味を持ったので、どういう手法があるのか調べてみました。 結構面白かったです。 この分野は勉強していくと非常に面白そうですね。 (メチャメチャ奥が深そうですね)

以下に、簡単な手法をいくつかピックアップしてみました。 様々な手法を組み合わせて、ぐちゃぐちゃの読みにくい出力を生成できるそうです。 また、自動的に出力をぐちゃぐちゃにするツールも色々あるようです。

名前マングリング

何をするかバレバレの名前をバイナリ中に残してしまうと、すぐに解析されてしまいます。 それを防ぐために関数名などをマングリングしてしまいます。

例えば、以下のようなコードがあったとします。 secret_key_funcという秘密鍵を扱う関数があるとします。 例えば、これをdefineでhogera()に変えてしまうと、何をしたい関数なのかが多少わかりにくくなります。


#include <stdio.h>

#define secret_key_func() hogera()

void
secret_key_func()
{
  printf("function to handle secret\n");
}

int
main()
{
    secret_key_func();
}

上記C言語のコードを gcc -S にかけると以下のようになります。 defineが無い場合、call _hogera は call secret_key_func になります。


        .section .rdata,"dr"
LC0:
        .ascii "function to handle secret\12\0"
        .text
.globl _hogera
        .def    _hogera;   .scl 2; .type   32; .endef
_hogera:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    $LC0, (%esp)
        call    _printf
        leave
        ret
        .def    ___main;   .scl 2; .type   32; .endef
.globl _main
        .def    _main;  .scl 2; .type   32; .endef
_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        andl    $-16, %esp
        movl    $0, %eax
        addl    $15, %eax
        addl    $15, %eax
        shrl    $4, %eax
        sall    $4, %eax
        movl    %eax, -4(%ebp)
        movl    -4(%ebp), %eax
        call    __alloca
        call    ___main
        call    _hogera
        leave
        ret
        .def    _printf;   .scl  3; .type 32; .endef

無意味なコードの挿入

コード中に必ず真になったり、必ず偽になるような条件文を入れることによってコードを読みにくくする手法も一般的だそうです。 以下のサンプルでは、aという変数に5を入れて、それを二乗したものの平方根と5を比べています。 5の二乗の平方根は必ず5なので、「aaa」という文字列が必ず表示されます。


#include <stdio.h>
#include <math.h>

int
main()
{
  int a = 5;

  if (sqrt(a * a) == 5) {
    printf("aaa\n");
  } else {
    printf("hage\n");
  }
}

無意味な計算式の挿入

全体の動作には影響を与えないような、無意味な計算式や処理を混ぜることによって出力バイナリを読みにくくできます。 無意味なものを挿入する系の難読化をライブラリなどに対して行う事によって、finger printを改変してしまい、どのソフトウェア(ライブラリ)を使っているかを解りにくくするという利用法もあるそうです。


#include <stdio.h>

int
main()
{
  int i = 0;

  int a, b, c, d;

  a = 7;
  b = 0xffff;
  c = 0x12345678;
  d = 0xdeadbeef;

  a = a + c;
  d >>= 2;
  b = a * 2;
  c = b ^ a;
  a++;

  printf("%d\n", i);

  return 0;
}

文字列の変換

stringsなどで簡単に文字列を解析されないように、変数を内部でいじるという手法も良く使われるそうです。 以下の例では、非常に単純にXORしているだけですが、もっと複雑な変換などをすれば多少解析がしにくくなります。


#include <stdio.h>

int
main()
{
  int i;
  unsigned char buf[] =
    {0xd9, 0xcf, 0xc9, 0xd8,
     0xcf, 0xde, 0x8a, 0xdd,
     0xc5, 0xd8, 0xce, 0xaa,
     0x00};

  for (i = 0; i < strlen(buf); i++) {
    buf[i] ^= 0xaa;
  }

  printf("%s\n", buf);
}

無駄に例外を投げる

無駄に例外を投げたうえで、来るはずが無い例外でcatchするコードを追加する事で読みにくくできるそうです。


#include <iostream>

int
main()
{
 try {
   throw 1;
 } catch (char ch) {
     printf("hoge\n");
 } catch (double d) {
     printf("hogera\n");
 } catch (int err) {
   if (err == 1) {
     printf("aaa\n");
   } else {
     printf("bbb\n");
   }
 }

 return 0;
}

NullPointerExceptionをわざと発生させる

Javaにも色々と難読化手法があるようです。 無駄に例外を発生させる手法と同じですが、わざとNullPointerException(いわゆる「ぬるぽ」)を発生させることで読みにくくできるそうです。


class a {
  public static void main(String argv[])
  {
    String hage = null;

    try {
      hage.substring(0,1);
    } catch (NullPointerException e) {
      System.out.println("aaa");
    }

    return;
  }

}

最近のエントリ

過去記事

過去記事一覧

IPv6基礎検定

YouTubeチャンネルやってます!