プログラム難読化プログラム及びプログラム難読化装置
【課題】恒偽(又は恒真)の条件式を追加することでループに対して複数の入り口を設ける方式よりも動的解析に対する耐性が高いプログラム難読化のための装置を提供する。
【解決手段】プログラム難読化装置は、難読化対象のプログラムからループA→B1→B2→Aを検出するループ検出部と、前記ループの前に恒偽及び恒真のいずれでもない条件式A'を追加すると共に、条件式A'の論理値が偽の場合には前記ループの先頭の条件式Aに進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組B1と等価な実行文の組B1'を実行した後前記ループ内の前記途中の実行文の次の実行文以降の組B2に進む、というフローを追加する条件分岐追加部と、を備える。
【解決手段】プログラム難読化装置は、難読化対象のプログラムからループA→B1→B2→Aを検出するループ検出部と、前記ループの前に恒偽及び恒真のいずれでもない条件式A'を追加すると共に、条件式A'の論理値が偽の場合には前記ループの先頭の条件式Aに進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組B1と等価な実行文の組B1'を実行した後前記ループ内の前記途中の実行文の次の実行文以降の組B2に進む、というフローを追加する条件分岐追加部と、を備える。
【発明の詳細な説明】
【技術分野】
【0001】
本発明は、プログラム難読化プログラム及びプログラム難読化装置に関する。
【背景技術】
【0002】
プログラムを悪意あるリバースエンジニアリングから保護するために、様々なプログラム難読化技術が提案されている。難読化とは、プログラムを複雑に変換することで、プログラムの解析コストを大きくする技術である。
【0003】
従来の難読化手法の1つに、プログラムの逆コンパイルを阻止する手法がある。この手法を以降、逆コンパイル阻止手法と呼ぶ。逆コンパイル阻止手法は、プログラムを意味的に等価であるが逆コンパイルされにくい(例えば、逆コンパイラの異常終了や、逆コンパイラの誤った出力結果を引き起こす)プログラムに変換する手法である。
【0004】
非特許文献1では、恒偽(又は恒真)の条件式を用いてループ構造の外から中へのダミーのフロー、つまり実行時には決して辿ることのないフロー、を付け加える難読化手法が言及されている。この手法により、元々制御フローグラフ上で単一の入り口しか持たなかったループ構造が、複数の入り口を持つようになる。なお、恒偽(又は恒真)の条件式とは、プログラム実行時に必ず偽(又は必ず真)と評価される条件式のことである。C言語やJava言語を始めとした一般的なプログラミング言語によるソースコードでは、複数の入り口を持つループ構造を表現できない。そのため、この手法を適用されたバイナリプログラムが入力された逆コンパイラは、入力バイナリプログラムに対応する適切な出力ソースプログラムを出力できず、異常終了したり誤った結果を出力したりするようになる。
【先行技術文献】
【非特許文献】
【0005】
【非特許文献1】C. Collberg, C. Thomborson, and D. Low, "Manufacturing Cheap, Resilient, and Stealthy Opaque Constructs,'' Proceedings of the 25th ACM Symposium on Principles of Programming Languages, pp.184--196, 1998
【発明の概要】
【発明が解決しようとする課題】
【0006】
しかしながら、プログラムに対する入力を様々に変えながらプログラムの実行状況を解析する動的解析を行えば、プログラム中に恒偽(又は恒真)の条件式があればそれを判定することができる。そして、恒偽(又は恒真)と判定された条件式を用いた条件分岐をそのプログラムから削除すれば、難読化の効果が消失してしまう。
【0007】
本発明は、恒偽(又は恒真)の条件式を追加することでループに対して複数の入り口を設ける方式よりも動的解析に対する耐性が高いプログラム難読化のためのプログラム及び装置を提供することを目的とする。
【課題を解決するための手段】
【0008】
請求項1に係る発明は、コンピュータを、難読化対象のプログラムからループを検出する検出手段、前記難読化対象のプログラムに対し、前記ループの前に恒偽及び恒真のいずれでもない条件式を追加すると共に、前記条件式の論理値が偽の場合には前記ループの先頭に進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後前記ループ内の前記途中の実行文の次の実行文に進む、というフローを追加する追加手段、として機能させるためのプログラム難読化プログラムである。
【0009】
請求項2に係る発明は、請求項1に係る発明において、前記追加手段は、前記ループ内に実行文が1つしかない場合には、当該実行文を当該実行文と等価な複数の実行文に置き換えた上で、前記条件式の論理値が真の場合には、当該複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後、前記ループ内の前記途中の実行文の次の実行文に進むというフローを前記プログラムに追加する、ことを特徴とする。
【0010】
請求項3に係る発明は、請求項1又は2に記載の発明において、前記追加手段は、前記ループを継続するか否かを判定する判定条件式が前記ループの先頭に位置する場合には、前記恒偽及び恒真のいずれでもない条件式として、前記判定条件式の論理値が偽となる場合には必ず偽となる条件式を前記ループの前に追加する、ことを特徴とする。
【0011】
請求項4に係る発明は、請求項1〜3のいずれか1項に記載の発明において、前記等価な実行文の組は、前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と異なる表現を含むことを特徴とする。
【0012】
請求項5に係る発明は、難読化対象のプログラムからループを検出する検出手段と、前記難読化対象のプログラムに対し、前記ループの前に恒偽及び恒真のいずれでもない条件式を追加すると共に、前記条件式の論理値が偽の場合には前記ループの先頭に進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後前記ループ内の前記途中の実行文の次の実行文に進む、というフローを追加する追加手段と、を備えるプログラム難読化装置である。
【発明の効果】
【0013】
請求項1又は5に係る発明によれば、恒偽(又は恒真)の条件式を追加することでループに対して複数の入り口を設ける方式よりも動的解析に対する耐性が高いプログラム難読化が実現できる。
【0014】
請求項2に係る発明によれば、ループ内の実行文が1つしかない場合でも、請求項1に係る発明の処理を実現することができる。
【0015】
請求項3に係る発明によれば、ループを継続するか否かを判定する判定条件式が前記ループの先頭に位置する場合でも、請求項1に係る発明の処理を実現することができる。
【0016】
請求項4に係る発明によれば、ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と全く同じ実行文の組を実行した後、その途中の実行文の次の実行文に進むフローを用いる場合よりも、難読性を高めることができる。
【図面の簡単な説明】
【0017】
【図1】実施形態のプログラム難読化装置の概略構成を示す図である。
【図2】whileループ構造を難読化する処理の例を示すフローチャートである。
【図3】whileループ構造を表す制御フローグラフを例示する図である。
【図4】whileループ構造中の実行文の組の分割を例示する図である。
【図5】whileループ構造の難読化結果の制御フローグラフを例示する図である。
【図6】whileループ構造を表す制御フローグラフの具体例を示す図である。
【図7】図7のwhileループ構造の難読化結果の例を示す図である。
【図8】do-whileループ構造を難読化する処理の例を示すフローチャートである。
【図9】do-whileループ構造を表す制御フローグラフを例示する図である。
【図10】do-whileループ構造中の実行文の組の分割を例示する図である。
【図11】do-whileループ構造の難読化結果の制御フローグラフを例示する図である。
【図12】do-whileループ構造を表す制御フローグラフの具体例を示す図である。
【図13】図12のdo-whileループ構造の難読化結果の例を示す図である。
【図14】do-whileループ構造を難読化する処理の別の例を示すフローチャートである。
【図15】do-whileループ構造を表す制御フローグラフを例示する図である。
【図16】do-whileループ構造中の実行文の組の分割を例示する図である。
【図17】do-whileループ構造の難読化結果の制御フローグラフを例示する図である。
【図18】do-whileループ構造を表す制御フローグラフの具体例を示す図である。
【図19】図18のdo-whileループ構造の難読化結果の例を示す図である。
【発明を実施するための形態】
【0018】
図1に、本実施形態のプログラム難読化装置10の概略構成を示す。プログラム難読化装置10は、入力された難読化対象プログラム100に対し難読化処理を施すことで、難読化されたプログラム110を生成し出力する。難読化対象プログラム100及び難読化されたプログラム110は例えばバイナリプログラムである。
【0019】
プログラム難読化装置10は、ループ検出部12、条件分岐追加部14及びルール記憶部16を備える。ループ検出部12は、難読化対象プログラム100からループ構造を検出する。条件分岐追加部14は、検出されたループ構造に対して、別の入り口を追加するための条件分岐を追加する。追加される条件分岐は、条件式や実行文などが含まれる。ルール記憶部16は、追加される条件分岐を構成する条件式や実行文を生成するためのルール情報を保持している。条件分岐追加部14は、それらルール情報を参照して、条件分岐を追加する。
【0020】
プログラム難読化装置10が実行する難読化処理の一例を図2に示す。この例は、難読化対象プログラム100中に含まれるwhileループ構造に対して難読化のための条件分岐を追加する場合の例である。
【0021】
この処理では、装置10は、まず難読化対象のバイナリプログラムPを入力として受け取る(S21)。次に、ループ検出部12は、そのバイナリプログラムPから制御フローグラフGを作成する(S22)。バイナリプログラムから制御フローグラフを生成する処理は従来から知られており、そのような従来の処理を用いればよい。次に、ループ検出部12は、制御フローグラフGから、任意にwhileループ構造を特定する(S23)。ここで、特定したwhileループ構造の入り口の条件式をA、その条件式の論理値が真の場合に実行される実行文の組をBと呼ぶ(図3参照)。なお、図3以下のフローチャート例では、条件式のブロックから下に延びる矢印は条件式の論理値が真の場合のフローであり、条件式のブロックから左に延びる矢印は条件式の論理値が偽の場合のフローを示す。
【0022】
次に条件分岐追加部14が、実行文の組Bを2つの組B1, B2に分割する(図4参照)(S24)。各組B1, B2には、それぞれ1以上の実行文が含まれるようにする。次に、条件分岐追加部14が、恒真及び恒偽のいずれでもなくかつ条件式Aの論理値が偽の時は必ず偽と評価される条件式A'と、B1と等価な文の組B1'を生成する(S25)。次に条件分岐追加部14が、制御フローグラフG に以下の手順(i), (ii)に従って文B1'と条件式A'を追加することで、制御フローグラフG'(図5参照)を作成する(S26)。すなわち、(i)制御フローグラフGに実行文の組B1'を追加し、さらに実行文の組B1'の実行後に実行文の組B2に進むという無条件分岐のフローを追加する。(ii) 制御フローグラフGに条件式A'を追加し、さらに条件式A'の論理値が真である場合は実行文の組B1'に進み、偽である場合には条件式Aに進むという条件分岐のフローを追加する。
【0023】
次に、条件分岐追加部14が、制御フローグラフG'からバイナリプログラムP'を作成し(S27)、生成したバイナリプログラムP'を出力する(S28)。
【0024】
なお、制御フローグラフG内に複数のwhileループが存在する場合には、それらwhileループごとにステップS23〜S26の処理が行われる。
【0025】
次に具体例を用いて、図2の処理手順の処理内容を更に説明する。例えば、難読対象のプログラムPに例えばwhileループ while(a>0){b=a+1;c=2*b;a=a-1;} が含まれていたとする。
【0026】
この場合、制御フローグラフG中のそのwhileループを表す部分は図6に示すようなものとなる。このwhileループには、条件式601(a>0)の論理値が真の場合に3つの実行文{b=a+1;c=2*b;a=a-1;}からなる実行文の組602に進むというフロー603と、実行文の組602の実行後に条件式601に戻るというフロー604とが含まれている。ステップS23でこのループ構造が選択され、ステップS24で実行文の組B(602)が、B1={b=a+1;}と、B2={c=2*b; a=a-1;}という2つの組に分割されたとする。実行文の組をステップS24でどのように分割するかは、あらかじめルール記憶部16にルール情報として登録しておけばよい。このようなルール情報としては、例えば、実行文の組Bの最初の1つの文をB1とし、残りの文の組をB2とする、という規則を示すものでもよい。
【0027】
次に条件分岐追加部14は、ステップS25において、条件式A'として例えば a > 10 を、追加する実行文の組B1'としてb ={ a + a*(a-1)%2 + 1; }を作成したとする(ここで、%は剰余演算子とする)。この例で作成した条件式A'は、ステップS25中に示した「恒真及び恒偽のいずれでもなくかつ条件式Aの論理値が偽の時は必ず偽と評価される」という条件を満たしている。このような条件を満たす条件式A'は、ルール記憶部16に登録されたルール情報に基づき自動生成すればよい。例えば、元の条件式Aが不等式の場合には、その不等式における小さい側の辺の値(図6の例では「0」)をより大きな値(例えば「10」)に変更するというルール、又はその逆に、大きい側の辺の値(図6の例では「a」)を、より小さい値(例えば「a−10」)に変更するというルールなどが考えられる。また、別のルールとして、条件式Aの左辺又は右辺に、演算結果が0となる項(例えば「a*(a-1)%2」)を追加するルールも考えられる。このルールは、元の条件式Aと追加する条件式A'とは等価となる。このように元の条件式Aと等価な条件式A'も「恒真及び恒偽のいずれでもなくかつ条件式Aの論理値が偽の時は必ず偽と評価される」という条件を満たす。なお、追加する条件式A'は、元の条件式Aと全く同じものであってもよいが、難読化のためには上に例示したように元の条件式と異なった表現となるものを用いる方がよい。また、この例で作成した文の組B1'についても、変数aはいかなる値でもa*(a-1)%2の値は0になるので、文の組B1と等価となっている。ある実行文に等価な条実行文を生成するルールとしては、表現は異なるが算術的に等価な式を生成するルールを用いることが考えられる。制御フローグラフGに追加する文の組B1'は、元の文の組B1と全く同じものでもよいが、難読性を高めるためには、異なった表現のものとした方がよい。
【0028】
そして、条件分岐追加部14は、図7に例示するように、図6のwhileループに対し、条件式A'と実行文の組B1'とを追加する。そして、更に、条件式A'の評価結果が偽の場合に条件式Aに進むというフロー701と、条件式A'の評価結果が真の場合には実行文の組B1'に進むというフロー702と、その実行文の組B1'の実行後に実行文の組B2(より厳密にはその組内の先頭の文「c=2*b;」)に進むというフロー703とを追加する。なお、図7のフローグラフのうち、フロー704,705及び706からなるループ構造は、図6に示したフロー603及び604からなるループ構造と等価である。
【0029】
図7に示した難読化結果の制御フローグラフG'では、フロー603及び604と等価なループ構造(704〜706)に対し、フロー701及び703という2つの入り口が存在している。また、条件式A'は恒真及び恒偽のいずれでもないので、aの値によってはフロー701及び703はどちらも辿られ得る。また、この難読化処理では、元の制御フローグラフGにあったループ構造603及び604には、新たな文は追加されない。
【0030】
図6に示した構造を持つプログラム片と、図7に示した難読化後の構造を持つプログラム片の動作は等しい。すなわち、まず図7のプログラム片の実行直前でのaの値が10以下であったとする。この時、a > 10の条件判定は偽となり、制御はフロー701を辿って条件式Aに進む。それ以降の制御は、明らかに図6のそれと同じである。一方、実行直前でのaの値が10より大きいとする。この時、a > 10の条件判定は真となり、制御はフロー702を辿って実行文の組B1'(すなわち「 b = a + a*(a-1)%2 + 1;」)に進む。その後、制御はループ中の実行文の組B2(すなわち「c=2*b; a=a-1; 」)に進み、B2の実行後に条件式Aに戻る。ここで、実行文の組B1(「b = a + 1; 」)とB1'(「 b = a + a*(a-1)%2 + 1; 」)とは等価なので、実行文の組B1を実行した後実行文の組B2を実行した場合の演算結果と、実行文の組B1'を実行した後実行文の組B2を実行した場合の演算結果とは等しくなる。そして、実行文の組B2の実行後の処理は、条件式Aに進むので、図6のループと同じになる。このように、この図2に例示した処理は、元のプログラムの動作を変更することなく、ループ構造が複数の入り口を持つように変更している。
【0031】
以上、元のプログラム中のwhileループ構造を難読化する処理の例を説明したが、次にdo-whileループ構造を難読化する処理の例を説明する。図8を参照して、この処理の手順の一例を説明する。
【0032】
この処理では、装置10は、まず難読化対象のバイナリプログラムPを入力として受け取る(S81)。次に、ループ検出部12は、そのバイナリプログラムPから制御フローグラフGを作成する(S82)。次に、ループ検出部12は、制御フローグラフGから、任意にdo-whileループ構造を特定する(S83)。ここで、特定したdo-whileループ構造中の実行文の組をB、出口の条件式をAと呼ぶ(図9参照)。次に条件分岐追加部14が、実行文の組Bを2つの組B1, B2に分割する(図10参照)(S84)。
【0033】
次に、条件分岐追加部14が、恒真及び恒偽のいずれでもない条件式Cと、B1と等価な文B1'を生成する(S85)。次に条件分岐追加部14が、制御フローグラフG に以下の手順(i), (ii)に従って文B1'と条件式Cを追加することで、制御フローグラフG'(図11参照)を作成する(S86)。すなわち、(i)制御フローグラフGに実行文の組B1'を追加し、さらに実行文の組B1'の実行後に実行文の組B2に進むという無条件分岐のフローを追加する。(ii) 制御フローグラフGに条件式Cを追加し、さらに条件式Cの論理値が真である場合は実行文B1'に進み、偽である場合には実行文の組B1に進むという条件分岐のフローを追加する。次に、条件分岐追加部14が、制御フローグラフG'からバイナリプログラムP'を作成し(S87)、生成したバイナリプログラムP'を出力する(S88)。
【0034】
なお、制御フローグラフG内に複数のdo-whileループが存在する場合には、それらdo-whileループごとにステップS83〜S86の処理が行われる。
【0035】
次に具体例を用いて、図8の処理手順の処理内容を更に説明する。例えば、難読対象のプログラムPに例えばdo-whileループ「do{b=a+1;c=2*b;a=a-1;}while(a>0)」 が含まれていたとする。この場合、制御フローグラフG中のそのdo-whileループを表す部分は図12に示すようなものとなる。以下では、このようなループが、図13に示すようなループに変換される処理の流れを説明する。
【0036】
この場合、そのdo-whileループは、3つの実行文{b=a+1;c=2*b;a=a-1;}からなる実行文の組1202を実行した後条件式1201(a>0)に進むというフロー1203と、条件式1201が真の場合に実行文の組1202に進むというフロー1204とから構成される。ステップS83でこのループ構造が選択され、ステップS84で実行文の組B(1202)が、B1={b=a+1;}と、B2={c=2*b; a=a-1;}という2つの組に分割されたとする。実行文の組をステップS84でどのように分割するかは、あらかじめルール記憶部16にルール情報として登録しておけばよい。
【0037】
次に条件分岐追加部14は、ステップS85において、条件式Cとして例えば「 b > c 」を、追加する実行文の組B1'としてb ={ a + a*(a-1)%2 + 1; }を作成したとする。この例で作成した条件式Cは「恒真及び恒偽のいずれでもない」という条件を満たしている。このような条件を満たす条件式Cは、ルール記憶部16に登録されたルール情報に基づき自動生成すればよい。例えば、難読化対象のプログラム中に含まれる2つの変数を左辺と右辺に持つ不等式を条件式Cとして生成する、というルールが考えられる。難読化対象のプログラム中にdo-whileループが複数含まれる場合、追加する条件式Cをすべてのdo-whileループで同じものとしてもよいが、難読性を高めるためには、ループごとに条件式Cとして異なる式を用いた方がよい。この例で作成した文の組B1'は、図7の例で追加したものと同じであるため、説明を省略する。
【0038】
そして、条件分岐追加部14は、図13に例示するように、図12のdo-whileループに対し、条件式Cと実行文の組B1'とを追加する。そして、更に、条件式Cの評価結果が偽の場合に条件式B1に進むというフロー1301と、条件式Cの評価結果が真の場合には実行文の組B1'に進むというフロー1302と、その実行文の組B1'の実行後に実行文の組B2(より厳密にはその組内の先頭の文「c=2*b;」)に進むというフロー1303とを追加する。なお、図13のフローグラフのうち、フロー1304,1305及び1306からなるループ構造は、図12に示したフロー1203及び1204からなるループ構造と等価である。
【0039】
図13に示した難読化結果の制御フローグラフG'では、フロー1203及び1204からなるループ構造と等価なループ構造(1304〜1306)に対し、フロー1301及び1303という2つの入り口が存在している。また、条件式Cは恒真及び恒偽のいずれでもないので、式内の変数の値によってはフロー1301及び1303はどちらも辿られ得る。また、元の制御フローグラフGにあったループ構造1203及び1204には、新たな文は追加されない。
【0040】
図12に示した構造を持つプログラム片と、図13に示した難読化後の構造を持つプログラム片の動作は等しい。すなわち、まず図13に示すプログラム片の実行直前でbはc以下であったとする。この時、制御はフロー1301を辿って実行文の組B1に進む。それ以降の制御は、明らかに図12のそれと同じである。一方、実行直前でcがb未満であったとする。この時、制御はフロー1302を辿って実行文の組B1'(すなわち「 b = a + a*(a-1)%2 + 1;」)に進む。その後、制御はループ中の実行文の組B2(すなわち「c=2*b; a=a-1; 」)に進み、B2の実行後に条件式Aに戻る。ここで、実行文の組B1とB1'とは等価なので、実行文の組B1を実行した後実行文の組B2を実行した場合の演算結果と、実行文の組B1'を実行した後実行文の組B2を実行した場合の演算結果とは等しくなる。そして、実行文の組B2の実行後の処理は、条件式Aに進むので、図12のループと同じになる。このように、この図8に例示した処理は、元のプログラムの動作を変更することなく、do-whileループ構造が複数の入り口を持つように変更している。
【0041】
次に、do-whileループ構造を図8に例示した処理よりも更に難読化する処理の例を、図14〜図19を参照して説明する。
【0042】
この処理では、装置10は、まず難読化対象のバイナリプログラムPを入力として受け取る(S141)。次に、ループ検出部12は、そのバイナリプログラムPから制御フローグラフGを作成する(S142)。次に、ループ検出部12は、制御フローグラフGから、任意にdo-whileループ構造を特定する(S143)。ここで、特定したdo-whileループ構造中の実行文の組をB、出口の条件式をAと呼び、条件式Aが偽となった場合に実行文の組Dが実行されるものとする(図15参照)。次に条件分岐追加部14が、実行文の組Bを2つの組B1, B2に分割する(図16参照)(S144)。
【0043】
次に、条件分岐追加部14が、恒真及び恒偽のいずれでもない条件式Cと、条件式Cと等価な条件式C'と、実行文の組B1と等価な文の組B1'とを生成する(S145)。また、ステップS145では、難読化対象のdo-while構造内で用いられていない変数を可逆変換する実行文M1及びM2と、それら実行文M1及びM2の逆変換を表す実行文R1及びR2を生成する。すなわち、実行文M1及びM2は、可逆的な(すなわち逆変換により元の変数と全く同じ変数に戻る)変数変換を表す実行文である。実行文M1で変換された変数は、対応する実行文R1を実行することにより元の変数と同じ値に戻る。M2とR2も同様の関係にある。
【0044】
次に条件分岐追加部14が、制御フローグラフG に以下の手順(i)〜(viii)に従って文B1'と条件式Cを追加することで、制御フローグラフG'(図17参照)を作成する(S146)。すなわち、まず(i)制御フローグラフGに実行文の組B1'を追加し、さらに実行文の組B1'の実行後に実行文の組B2に進むという無条件分岐のフローを追加する。また、(ii)実行文M1を追加し、M1の実行後に実行文の組B1'に進むという無条件分岐のフローを追加する。(iii) 実行文M2を追加し、M2の実行後に実行文の組B1に進むという無条件分岐のフローを追加する。(iv)条件式Cを追加し、更に条件式Cの論理値が真である場合は実行文M1に進み、偽である場合には実行文M2に進むという条件分岐のフローを追加する。(v)条件式Aの論理値が偽である場合に実行文の組Dに進むというフローを削除する。(vi)条件式C'を追加し、さらに条件式Aの論理値が偽である場合に条件式C'に進むというフローを追加する。(vii)実行文R1を追加し、さらに条件式C'の論理値が真である場合にR1に進むというフローと、R1の実行後に条件式の組Dに進むという無条件分岐のフローとを追加する。(viii)実行文R2を追加し、さらに条件式C'の論理値が偽である場合にR2に進むというフローと、R2の実行後に無条件でDに進むというフローを追加する。
【0045】
次に、条件分岐追加部14が、以上のようにして生成された制御フローグラフG'からバイナリプログラムP'を作成し(S147)、生成したバイナリプログラムP'を出力する(S148)。
【0046】
なお、制御フローグラフG内に複数のdo-whileループが存在する場合には、それらdo-whileループごとにステップS143〜S146の処理が行われる。
【0047】
次に具体例を用いて、図14の処理手順の処理内容を更に説明する。例えば、難読対象のプログラムPに例えばdo-whileループを含んだプログラム片「do{b=a+1;c=2*b;a=a-1;}while(a>0); d=b+c+e;」 が含まれていたとする。
【0048】
この場合、制御フローグラフG中のそのプログラム片を表す部分は図18に示すようなものとなる。以下では、このようなプログラム片が、図19に示すようなプログラム片に変換される処理の流れを説明する。
【0049】
このプログラム片の中のdo-whileループは、3つの実行文{b=a+1;c=2*b;a=a-1;}からなる実行文の組1802を実行した後条件式1801(a>0)に進むというフロー1803と、条件式1801が真の場合に実行文の組1802に進むというフロー1804とから構成される。また、このプログラム片には、条件式1802が偽の場合に実行文D(1805)に進むというフローが含まれる。ステップS143でそのループ構造が選択され、ステップS144で実行文の組B(1802)が、B1={b=a+1;}と、B2={c=2*b; a=a-1;}という2つの組に分割されたとする。実行文の組をステップS144でどのように分割するかは、あらかじめルール記憶部16にルール情報として登録しておけばよい。
【0050】
次に条件分岐追加部14は、ステップS145において、条件式Cとして例えば「 e > 10 」を、これと等価な条件式C'として「 e > 0 && e*e > 100」を、追加する実行文の組B1'として「b ={ a + a*(a-1)%2 + 1; }」を、それぞれ作成する。この例で作成した条件式Cは「恒真及び恒偽のいずれでもない」という条件を満たしている。このような条件を満たす条件式Cは、ルール記憶部16に登録されたルール情報に基づき自動生成すればよい。例えば、難読化対象のプログラム中で用いられていない変数を導入し、その変数があらかじめ定められた定数より大きいという不等式を条件式Cとして生成する、というルールが考えられる。また、条件式C'の生成ルールは、条件式Cの生成ルールと対応して定めることができる。例えば、上述のような不等式を条件式Cとする場合、その不等式に現れる変数を二乗したものがその定数の二乗より大きく、かつその変数は正であるという条件式C'を生成するというルールが考えられる。このように、ある条件式に等価な条件式を生成するルールとしては、表現は異なるが算術的に等価な式を生成するルールを用いることが考えられる。この例で作成した文の組B1'は、図7の例で追加したものと同じであるため、説明を省略する。
【0051】
また、条件分岐追加部14は、ステップS145において、ルール記憶部16を参照して、変数を可逆変更する文M1, M2としてそれぞれd=2*d + 3, d=4*d - 5を、M1, M2のそれぞれの逆変換R1, R2としてd=(d - 3) / 2, d=(d+5) / 4を作成する。ルール記憶部16には、例えば可逆的な変換x=2*x + 3とその逆変換x=(x - 3) / 2との組をルール情報として登録しておき、条件分岐追加部14は難読化対象のループ構造内に含まれない変数を求め、その変数をそのルール情報の変数x に代入することで、具体的な変換M1とR1を生成すればよい。また、ルール記憶部16には、そのような変換と逆変換の組を複数登録しておき、それら複数の組の中から、M1とM2に用いるものをそれぞれ個別に選択するようにしてもよい。また、難読化対象のプログラム中に複数のdo-whileループが存在する場合、難読性を高めるためには、それらdo-whileループごとに、使用するM1とM2の組合せを変えてもよい。図19の例では、追加する可逆変換の文で用いる変数として、do-whileループから出た直後の実行文Dで用いられている変数d(この変数dはdo-whileループ内では用いられていない)を用いたが、これに限られるわけではない。例えば、難読化対象のプログラムに含まれていない変数を導入し、その変数を可逆変換する文を生成してもよい。ただし、難読性を増すためには、do-whileループから出た直後の実行文Dに含まれる変数を用いることが考えられる。
【0052】
そして、条件分岐追加部14は、図19に例示するように、図18のdo-whileループに対し、実行文の組B1'、条件式C及びC'、変数の可逆変換の文M1, M2とその逆変換R1, R2とを追加する。そして、更に、条件式Cの評価結果が偽の場合に文M2に進むというフロー1901と、文M2の実行後に文B1に進むというフロー1902とを追加し、条件式Cの評価結果が真の場合に文M1に進むというフロー1903と、文M1の実行後に実行文の組B1'に進むというフロー1904と、その実行文の組B1'の実行後に実行文の組B2(より厳密にはその組内の先頭の文「c=2*b;」)に進むというフロー1905とを追加する。
【0053】
図19のフローグラフのうち、フロー1906,1907及び1908からなるループ構造は、図18に示したフロー1803及び1804からなるループ構造と等価である。
【0054】
また、条件分岐追加部14は、条件式Aが偽である場合に文Dに進むというフローを削除し、条件式Aが偽である場合に条件式C'に進むというフロー1909を追加する。そして、条件式C'が真である場合に文M1の逆変換R1に進むフロー1910と、R1の実行後に文Dに進むというフロー1911を追加する。また、条件式C'が偽である場合に文M2の逆変換R2に進むフロー1912と、R2の実行後に文Dに進むというフロー1913を追加する。
【0055】
以上のような処理において追加された変数変換の文M1及びM2により変換された変数が実際に使用される実行文Dが実行される直前で、対応する逆変換の文R1及びR2によりそれぞれ元の値に戻される。したがって、図19が示す制御フローの処理結果は、図18が示す制御フローの処理結果と等価となる。
【0056】
以上、本発明の実施形態を説明した。以上に説明したように、本実施形態では、一般的なマシン語が備えている条件分岐命令や無条件分岐命令、算術命令のみを用いることでプログラムを変換している。
【0057】
以上では、whileループとdo-whileループという2種類のループの難読化処理の例を説明したが、上記実施形態の手法は他の種類のループにも適用できる。例えば、ループの終了判定(すなわち条件式A)をループの処理(すなわちループ内の実行文の組B)を実行する前に行う前判定方式のループ(例えばforループ)は、上述のwhileループと同様の処理を適用すればよい。また、ループの終了判定をループの処理を実行した後に行う後判定方式のループは、上述のdo-whileループと同様の処理を適用すればよい。
【0058】
難読化対象のプログラム中に複数種類のループが含まれる場合には、個々のループごとに、当該ループの種類に応じて、前判定方式に対する難読化処理、後判定方式に対する難読化処理をそれぞれ適用すればよい。
【0059】
また、以上の例では、難読化対象のループ中に複数の実行文が含まれていた(図6、図12、図18参照)。しかし、難読化対象のループ中に実行文が1つしかない場合にも、上述の実施形態の処理は適用できる。このような場合、条件分岐追加部14は、ループ中の実行文を、その実行文と等価な処理を表す複数の実行文に変換すればよい。このような変換は、例えば、難読化対象のプログラムに影響を与えないダミーの実行文をループ内に追加することにより行えばよい。また、別の例として、ループ中の実行文Bで用いられる定数cを変数x(この変数xは難読化対象のプログラム中で用いられていないものにする)に代入する処理を表す実行文「x=c」をループ中の実行文Bの前に追加し、実行文B中の定数cを変数xに変更してもよい。
【0060】
また、上記実施形態では、難読化においてループ構造中に新たに文や条件式を追加しない。従って、難読化に伴って発生する処理オーバーヘッドが予測しやすい。例えば、図2の例の処理によって生じるオーバーヘッドは以下のように見積もることができる。
【0061】
まず、whileループの繰り返し実行回数をnとおく( n ≧ 1)。この時、難読化前のプログラム(図6)の実行コストCbeforeは以下のように算出される。
Cbefore = n( c(A) + c(B1) + c(B2) ) + c(A)
ここで、c(X)は、文Xの実行コストを表すとする。
【0062】
一方で、難読化後のプログラム(図7)の実行コストCafterは以下のように算出される。
Cafter = ( c(A') + c(B1') + c(B2) ) + (n - 1)( c(A) + c(B1) + c(B2) ) + c(A)
となる。
【0063】
従って、実行オーバーヘッドCafter - Cbeforeは、
Cafter - Cbefore = ( c(A') - c(A) ) + ( c(B1') - c(B1) )
と見積もることができる。このように、本実施形態によって生じる実行オーバーヘッドはループ構造の実行回数に関わらず一定なので、予測しやすい。
【0064】
以上に説明したプログラム難読化装置10は、一つの例では、汎用のコンピュータに上述の処理を表すプログラムを実行させることにより実現される。ここで、コンピュータは、例えば、ハードウエアとして、CPU等のマイクロプロセッサ、ランダムアクセスメモリ(RAM)およびリードオンリメモリ(ROM)等のメモリ(一次記憶)、HDD(ハードディスクドライブ)コントローラを経由して接続されたHDD、各種I/O(入出力)インタフェース等が、バスを介して接続された回路構成を有する。バスには、ローカルエリアネットワーク等のネットワークに接続するためのネットワークインタフェースが接続されていてもよい。また、そのバスに対し、例えばI/Oインタフェース経由で、CDやDVDなどの可搬型ディスク記録媒体に対する読み取り及び/又は書き込みのためのディスクドライブ、フラッシュメモリなどの各種規格の可搬型の不揮発性記録媒体に対する読み取り及び/又は書き込みのためのメモリリーダライタなどが接続されてもよい。上に例示した各機能モジュールの処理内容が記述されたプログラムがCDやDVD等の記録媒体を経由して、又はネットワーク等の通信手段経由で、ハードディスクドライブ等の固定記憶装置に保存され、コンピュータにインストールされる。インストールされたプログラムがRAMに読み出されCPU等のマイクロプロセッサにより実行されることにより、上に例示したプログラム難読化装置10の機能が実現される。
【符号の説明】
【0065】
10 プログラム難読化装置、12 ループ検出部、14 条件分岐追加部、16 ルール記憶部、100 難読化対象のプログラム、110 難読化されたプログラム。
【技術分野】
【0001】
本発明は、プログラム難読化プログラム及びプログラム難読化装置に関する。
【背景技術】
【0002】
プログラムを悪意あるリバースエンジニアリングから保護するために、様々なプログラム難読化技術が提案されている。難読化とは、プログラムを複雑に変換することで、プログラムの解析コストを大きくする技術である。
【0003】
従来の難読化手法の1つに、プログラムの逆コンパイルを阻止する手法がある。この手法を以降、逆コンパイル阻止手法と呼ぶ。逆コンパイル阻止手法は、プログラムを意味的に等価であるが逆コンパイルされにくい(例えば、逆コンパイラの異常終了や、逆コンパイラの誤った出力結果を引き起こす)プログラムに変換する手法である。
【0004】
非特許文献1では、恒偽(又は恒真)の条件式を用いてループ構造の外から中へのダミーのフロー、つまり実行時には決して辿ることのないフロー、を付け加える難読化手法が言及されている。この手法により、元々制御フローグラフ上で単一の入り口しか持たなかったループ構造が、複数の入り口を持つようになる。なお、恒偽(又は恒真)の条件式とは、プログラム実行時に必ず偽(又は必ず真)と評価される条件式のことである。C言語やJava言語を始めとした一般的なプログラミング言語によるソースコードでは、複数の入り口を持つループ構造を表現できない。そのため、この手法を適用されたバイナリプログラムが入力された逆コンパイラは、入力バイナリプログラムに対応する適切な出力ソースプログラムを出力できず、異常終了したり誤った結果を出力したりするようになる。
【先行技術文献】
【非特許文献】
【0005】
【非特許文献1】C. Collberg, C. Thomborson, and D. Low, "Manufacturing Cheap, Resilient, and Stealthy Opaque Constructs,'' Proceedings of the 25th ACM Symposium on Principles of Programming Languages, pp.184--196, 1998
【発明の概要】
【発明が解決しようとする課題】
【0006】
しかしながら、プログラムに対する入力を様々に変えながらプログラムの実行状況を解析する動的解析を行えば、プログラム中に恒偽(又は恒真)の条件式があればそれを判定することができる。そして、恒偽(又は恒真)と判定された条件式を用いた条件分岐をそのプログラムから削除すれば、難読化の効果が消失してしまう。
【0007】
本発明は、恒偽(又は恒真)の条件式を追加することでループに対して複数の入り口を設ける方式よりも動的解析に対する耐性が高いプログラム難読化のためのプログラム及び装置を提供することを目的とする。
【課題を解決するための手段】
【0008】
請求項1に係る発明は、コンピュータを、難読化対象のプログラムからループを検出する検出手段、前記難読化対象のプログラムに対し、前記ループの前に恒偽及び恒真のいずれでもない条件式を追加すると共に、前記条件式の論理値が偽の場合には前記ループの先頭に進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後前記ループ内の前記途中の実行文の次の実行文に進む、というフローを追加する追加手段、として機能させるためのプログラム難読化プログラムである。
【0009】
請求項2に係る発明は、請求項1に係る発明において、前記追加手段は、前記ループ内に実行文が1つしかない場合には、当該実行文を当該実行文と等価な複数の実行文に置き換えた上で、前記条件式の論理値が真の場合には、当該複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後、前記ループ内の前記途中の実行文の次の実行文に進むというフローを前記プログラムに追加する、ことを特徴とする。
【0010】
請求項3に係る発明は、請求項1又は2に記載の発明において、前記追加手段は、前記ループを継続するか否かを判定する判定条件式が前記ループの先頭に位置する場合には、前記恒偽及び恒真のいずれでもない条件式として、前記判定条件式の論理値が偽となる場合には必ず偽となる条件式を前記ループの前に追加する、ことを特徴とする。
【0011】
請求項4に係る発明は、請求項1〜3のいずれか1項に記載の発明において、前記等価な実行文の組は、前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と異なる表現を含むことを特徴とする。
【0012】
請求項5に係る発明は、難読化対象のプログラムからループを検出する検出手段と、前記難読化対象のプログラムに対し、前記ループの前に恒偽及び恒真のいずれでもない条件式を追加すると共に、前記条件式の論理値が偽の場合には前記ループの先頭に進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後前記ループ内の前記途中の実行文の次の実行文に進む、というフローを追加する追加手段と、を備えるプログラム難読化装置である。
【発明の効果】
【0013】
請求項1又は5に係る発明によれば、恒偽(又は恒真)の条件式を追加することでループに対して複数の入り口を設ける方式よりも動的解析に対する耐性が高いプログラム難読化が実現できる。
【0014】
請求項2に係る発明によれば、ループ内の実行文が1つしかない場合でも、請求項1に係る発明の処理を実現することができる。
【0015】
請求項3に係る発明によれば、ループを継続するか否かを判定する判定条件式が前記ループの先頭に位置する場合でも、請求項1に係る発明の処理を実現することができる。
【0016】
請求項4に係る発明によれば、ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と全く同じ実行文の組を実行した後、その途中の実行文の次の実行文に進むフローを用いる場合よりも、難読性を高めることができる。
【図面の簡単な説明】
【0017】
【図1】実施形態のプログラム難読化装置の概略構成を示す図である。
【図2】whileループ構造を難読化する処理の例を示すフローチャートである。
【図3】whileループ構造を表す制御フローグラフを例示する図である。
【図4】whileループ構造中の実行文の組の分割を例示する図である。
【図5】whileループ構造の難読化結果の制御フローグラフを例示する図である。
【図6】whileループ構造を表す制御フローグラフの具体例を示す図である。
【図7】図7のwhileループ構造の難読化結果の例を示す図である。
【図8】do-whileループ構造を難読化する処理の例を示すフローチャートである。
【図9】do-whileループ構造を表す制御フローグラフを例示する図である。
【図10】do-whileループ構造中の実行文の組の分割を例示する図である。
【図11】do-whileループ構造の難読化結果の制御フローグラフを例示する図である。
【図12】do-whileループ構造を表す制御フローグラフの具体例を示す図である。
【図13】図12のdo-whileループ構造の難読化結果の例を示す図である。
【図14】do-whileループ構造を難読化する処理の別の例を示すフローチャートである。
【図15】do-whileループ構造を表す制御フローグラフを例示する図である。
【図16】do-whileループ構造中の実行文の組の分割を例示する図である。
【図17】do-whileループ構造の難読化結果の制御フローグラフを例示する図である。
【図18】do-whileループ構造を表す制御フローグラフの具体例を示す図である。
【図19】図18のdo-whileループ構造の難読化結果の例を示す図である。
【発明を実施するための形態】
【0018】
図1に、本実施形態のプログラム難読化装置10の概略構成を示す。プログラム難読化装置10は、入力された難読化対象プログラム100に対し難読化処理を施すことで、難読化されたプログラム110を生成し出力する。難読化対象プログラム100及び難読化されたプログラム110は例えばバイナリプログラムである。
【0019】
プログラム難読化装置10は、ループ検出部12、条件分岐追加部14及びルール記憶部16を備える。ループ検出部12は、難読化対象プログラム100からループ構造を検出する。条件分岐追加部14は、検出されたループ構造に対して、別の入り口を追加するための条件分岐を追加する。追加される条件分岐は、条件式や実行文などが含まれる。ルール記憶部16は、追加される条件分岐を構成する条件式や実行文を生成するためのルール情報を保持している。条件分岐追加部14は、それらルール情報を参照して、条件分岐を追加する。
【0020】
プログラム難読化装置10が実行する難読化処理の一例を図2に示す。この例は、難読化対象プログラム100中に含まれるwhileループ構造に対して難読化のための条件分岐を追加する場合の例である。
【0021】
この処理では、装置10は、まず難読化対象のバイナリプログラムPを入力として受け取る(S21)。次に、ループ検出部12は、そのバイナリプログラムPから制御フローグラフGを作成する(S22)。バイナリプログラムから制御フローグラフを生成する処理は従来から知られており、そのような従来の処理を用いればよい。次に、ループ検出部12は、制御フローグラフGから、任意にwhileループ構造を特定する(S23)。ここで、特定したwhileループ構造の入り口の条件式をA、その条件式の論理値が真の場合に実行される実行文の組をBと呼ぶ(図3参照)。なお、図3以下のフローチャート例では、条件式のブロックから下に延びる矢印は条件式の論理値が真の場合のフローであり、条件式のブロックから左に延びる矢印は条件式の論理値が偽の場合のフローを示す。
【0022】
次に条件分岐追加部14が、実行文の組Bを2つの組B1, B2に分割する(図4参照)(S24)。各組B1, B2には、それぞれ1以上の実行文が含まれるようにする。次に、条件分岐追加部14が、恒真及び恒偽のいずれでもなくかつ条件式Aの論理値が偽の時は必ず偽と評価される条件式A'と、B1と等価な文の組B1'を生成する(S25)。次に条件分岐追加部14が、制御フローグラフG に以下の手順(i), (ii)に従って文B1'と条件式A'を追加することで、制御フローグラフG'(図5参照)を作成する(S26)。すなわち、(i)制御フローグラフGに実行文の組B1'を追加し、さらに実行文の組B1'の実行後に実行文の組B2に進むという無条件分岐のフローを追加する。(ii) 制御フローグラフGに条件式A'を追加し、さらに条件式A'の論理値が真である場合は実行文の組B1'に進み、偽である場合には条件式Aに進むという条件分岐のフローを追加する。
【0023】
次に、条件分岐追加部14が、制御フローグラフG'からバイナリプログラムP'を作成し(S27)、生成したバイナリプログラムP'を出力する(S28)。
【0024】
なお、制御フローグラフG内に複数のwhileループが存在する場合には、それらwhileループごとにステップS23〜S26の処理が行われる。
【0025】
次に具体例を用いて、図2の処理手順の処理内容を更に説明する。例えば、難読対象のプログラムPに例えばwhileループ while(a>0){b=a+1;c=2*b;a=a-1;} が含まれていたとする。
【0026】
この場合、制御フローグラフG中のそのwhileループを表す部分は図6に示すようなものとなる。このwhileループには、条件式601(a>0)の論理値が真の場合に3つの実行文{b=a+1;c=2*b;a=a-1;}からなる実行文の組602に進むというフロー603と、実行文の組602の実行後に条件式601に戻るというフロー604とが含まれている。ステップS23でこのループ構造が選択され、ステップS24で実行文の組B(602)が、B1={b=a+1;}と、B2={c=2*b; a=a-1;}という2つの組に分割されたとする。実行文の組をステップS24でどのように分割するかは、あらかじめルール記憶部16にルール情報として登録しておけばよい。このようなルール情報としては、例えば、実行文の組Bの最初の1つの文をB1とし、残りの文の組をB2とする、という規則を示すものでもよい。
【0027】
次に条件分岐追加部14は、ステップS25において、条件式A'として例えば a > 10 を、追加する実行文の組B1'としてb ={ a + a*(a-1)%2 + 1; }を作成したとする(ここで、%は剰余演算子とする)。この例で作成した条件式A'は、ステップS25中に示した「恒真及び恒偽のいずれでもなくかつ条件式Aの論理値が偽の時は必ず偽と評価される」という条件を満たしている。このような条件を満たす条件式A'は、ルール記憶部16に登録されたルール情報に基づき自動生成すればよい。例えば、元の条件式Aが不等式の場合には、その不等式における小さい側の辺の値(図6の例では「0」)をより大きな値(例えば「10」)に変更するというルール、又はその逆に、大きい側の辺の値(図6の例では「a」)を、より小さい値(例えば「a−10」)に変更するというルールなどが考えられる。また、別のルールとして、条件式Aの左辺又は右辺に、演算結果が0となる項(例えば「a*(a-1)%2」)を追加するルールも考えられる。このルールは、元の条件式Aと追加する条件式A'とは等価となる。このように元の条件式Aと等価な条件式A'も「恒真及び恒偽のいずれでもなくかつ条件式Aの論理値が偽の時は必ず偽と評価される」という条件を満たす。なお、追加する条件式A'は、元の条件式Aと全く同じものであってもよいが、難読化のためには上に例示したように元の条件式と異なった表現となるものを用いる方がよい。また、この例で作成した文の組B1'についても、変数aはいかなる値でもa*(a-1)%2の値は0になるので、文の組B1と等価となっている。ある実行文に等価な条実行文を生成するルールとしては、表現は異なるが算術的に等価な式を生成するルールを用いることが考えられる。制御フローグラフGに追加する文の組B1'は、元の文の組B1と全く同じものでもよいが、難読性を高めるためには、異なった表現のものとした方がよい。
【0028】
そして、条件分岐追加部14は、図7に例示するように、図6のwhileループに対し、条件式A'と実行文の組B1'とを追加する。そして、更に、条件式A'の評価結果が偽の場合に条件式Aに進むというフロー701と、条件式A'の評価結果が真の場合には実行文の組B1'に進むというフロー702と、その実行文の組B1'の実行後に実行文の組B2(より厳密にはその組内の先頭の文「c=2*b;」)に進むというフロー703とを追加する。なお、図7のフローグラフのうち、フロー704,705及び706からなるループ構造は、図6に示したフロー603及び604からなるループ構造と等価である。
【0029】
図7に示した難読化結果の制御フローグラフG'では、フロー603及び604と等価なループ構造(704〜706)に対し、フロー701及び703という2つの入り口が存在している。また、条件式A'は恒真及び恒偽のいずれでもないので、aの値によってはフロー701及び703はどちらも辿られ得る。また、この難読化処理では、元の制御フローグラフGにあったループ構造603及び604には、新たな文は追加されない。
【0030】
図6に示した構造を持つプログラム片と、図7に示した難読化後の構造を持つプログラム片の動作は等しい。すなわち、まず図7のプログラム片の実行直前でのaの値が10以下であったとする。この時、a > 10の条件判定は偽となり、制御はフロー701を辿って条件式Aに進む。それ以降の制御は、明らかに図6のそれと同じである。一方、実行直前でのaの値が10より大きいとする。この時、a > 10の条件判定は真となり、制御はフロー702を辿って実行文の組B1'(すなわち「 b = a + a*(a-1)%2 + 1;」)に進む。その後、制御はループ中の実行文の組B2(すなわち「c=2*b; a=a-1; 」)に進み、B2の実行後に条件式Aに戻る。ここで、実行文の組B1(「b = a + 1; 」)とB1'(「 b = a + a*(a-1)%2 + 1; 」)とは等価なので、実行文の組B1を実行した後実行文の組B2を実行した場合の演算結果と、実行文の組B1'を実行した後実行文の組B2を実行した場合の演算結果とは等しくなる。そして、実行文の組B2の実行後の処理は、条件式Aに進むので、図6のループと同じになる。このように、この図2に例示した処理は、元のプログラムの動作を変更することなく、ループ構造が複数の入り口を持つように変更している。
【0031】
以上、元のプログラム中のwhileループ構造を難読化する処理の例を説明したが、次にdo-whileループ構造を難読化する処理の例を説明する。図8を参照して、この処理の手順の一例を説明する。
【0032】
この処理では、装置10は、まず難読化対象のバイナリプログラムPを入力として受け取る(S81)。次に、ループ検出部12は、そのバイナリプログラムPから制御フローグラフGを作成する(S82)。次に、ループ検出部12は、制御フローグラフGから、任意にdo-whileループ構造を特定する(S83)。ここで、特定したdo-whileループ構造中の実行文の組をB、出口の条件式をAと呼ぶ(図9参照)。次に条件分岐追加部14が、実行文の組Bを2つの組B1, B2に分割する(図10参照)(S84)。
【0033】
次に、条件分岐追加部14が、恒真及び恒偽のいずれでもない条件式Cと、B1と等価な文B1'を生成する(S85)。次に条件分岐追加部14が、制御フローグラフG に以下の手順(i), (ii)に従って文B1'と条件式Cを追加することで、制御フローグラフG'(図11参照)を作成する(S86)。すなわち、(i)制御フローグラフGに実行文の組B1'を追加し、さらに実行文の組B1'の実行後に実行文の組B2に進むという無条件分岐のフローを追加する。(ii) 制御フローグラフGに条件式Cを追加し、さらに条件式Cの論理値が真である場合は実行文B1'に進み、偽である場合には実行文の組B1に進むという条件分岐のフローを追加する。次に、条件分岐追加部14が、制御フローグラフG'からバイナリプログラムP'を作成し(S87)、生成したバイナリプログラムP'を出力する(S88)。
【0034】
なお、制御フローグラフG内に複数のdo-whileループが存在する場合には、それらdo-whileループごとにステップS83〜S86の処理が行われる。
【0035】
次に具体例を用いて、図8の処理手順の処理内容を更に説明する。例えば、難読対象のプログラムPに例えばdo-whileループ「do{b=a+1;c=2*b;a=a-1;}while(a>0)」 が含まれていたとする。この場合、制御フローグラフG中のそのdo-whileループを表す部分は図12に示すようなものとなる。以下では、このようなループが、図13に示すようなループに変換される処理の流れを説明する。
【0036】
この場合、そのdo-whileループは、3つの実行文{b=a+1;c=2*b;a=a-1;}からなる実行文の組1202を実行した後条件式1201(a>0)に進むというフロー1203と、条件式1201が真の場合に実行文の組1202に進むというフロー1204とから構成される。ステップS83でこのループ構造が選択され、ステップS84で実行文の組B(1202)が、B1={b=a+1;}と、B2={c=2*b; a=a-1;}という2つの組に分割されたとする。実行文の組をステップS84でどのように分割するかは、あらかじめルール記憶部16にルール情報として登録しておけばよい。
【0037】
次に条件分岐追加部14は、ステップS85において、条件式Cとして例えば「 b > c 」を、追加する実行文の組B1'としてb ={ a + a*(a-1)%2 + 1; }を作成したとする。この例で作成した条件式Cは「恒真及び恒偽のいずれでもない」という条件を満たしている。このような条件を満たす条件式Cは、ルール記憶部16に登録されたルール情報に基づき自動生成すればよい。例えば、難読化対象のプログラム中に含まれる2つの変数を左辺と右辺に持つ不等式を条件式Cとして生成する、というルールが考えられる。難読化対象のプログラム中にdo-whileループが複数含まれる場合、追加する条件式Cをすべてのdo-whileループで同じものとしてもよいが、難読性を高めるためには、ループごとに条件式Cとして異なる式を用いた方がよい。この例で作成した文の組B1'は、図7の例で追加したものと同じであるため、説明を省略する。
【0038】
そして、条件分岐追加部14は、図13に例示するように、図12のdo-whileループに対し、条件式Cと実行文の組B1'とを追加する。そして、更に、条件式Cの評価結果が偽の場合に条件式B1に進むというフロー1301と、条件式Cの評価結果が真の場合には実行文の組B1'に進むというフロー1302と、その実行文の組B1'の実行後に実行文の組B2(より厳密にはその組内の先頭の文「c=2*b;」)に進むというフロー1303とを追加する。なお、図13のフローグラフのうち、フロー1304,1305及び1306からなるループ構造は、図12に示したフロー1203及び1204からなるループ構造と等価である。
【0039】
図13に示した難読化結果の制御フローグラフG'では、フロー1203及び1204からなるループ構造と等価なループ構造(1304〜1306)に対し、フロー1301及び1303という2つの入り口が存在している。また、条件式Cは恒真及び恒偽のいずれでもないので、式内の変数の値によってはフロー1301及び1303はどちらも辿られ得る。また、元の制御フローグラフGにあったループ構造1203及び1204には、新たな文は追加されない。
【0040】
図12に示した構造を持つプログラム片と、図13に示した難読化後の構造を持つプログラム片の動作は等しい。すなわち、まず図13に示すプログラム片の実行直前でbはc以下であったとする。この時、制御はフロー1301を辿って実行文の組B1に進む。それ以降の制御は、明らかに図12のそれと同じである。一方、実行直前でcがb未満であったとする。この時、制御はフロー1302を辿って実行文の組B1'(すなわち「 b = a + a*(a-1)%2 + 1;」)に進む。その後、制御はループ中の実行文の組B2(すなわち「c=2*b; a=a-1; 」)に進み、B2の実行後に条件式Aに戻る。ここで、実行文の組B1とB1'とは等価なので、実行文の組B1を実行した後実行文の組B2を実行した場合の演算結果と、実行文の組B1'を実行した後実行文の組B2を実行した場合の演算結果とは等しくなる。そして、実行文の組B2の実行後の処理は、条件式Aに進むので、図12のループと同じになる。このように、この図8に例示した処理は、元のプログラムの動作を変更することなく、do-whileループ構造が複数の入り口を持つように変更している。
【0041】
次に、do-whileループ構造を図8に例示した処理よりも更に難読化する処理の例を、図14〜図19を参照して説明する。
【0042】
この処理では、装置10は、まず難読化対象のバイナリプログラムPを入力として受け取る(S141)。次に、ループ検出部12は、そのバイナリプログラムPから制御フローグラフGを作成する(S142)。次に、ループ検出部12は、制御フローグラフGから、任意にdo-whileループ構造を特定する(S143)。ここで、特定したdo-whileループ構造中の実行文の組をB、出口の条件式をAと呼び、条件式Aが偽となった場合に実行文の組Dが実行されるものとする(図15参照)。次に条件分岐追加部14が、実行文の組Bを2つの組B1, B2に分割する(図16参照)(S144)。
【0043】
次に、条件分岐追加部14が、恒真及び恒偽のいずれでもない条件式Cと、条件式Cと等価な条件式C'と、実行文の組B1と等価な文の組B1'とを生成する(S145)。また、ステップS145では、難読化対象のdo-while構造内で用いられていない変数を可逆変換する実行文M1及びM2と、それら実行文M1及びM2の逆変換を表す実行文R1及びR2を生成する。すなわち、実行文M1及びM2は、可逆的な(すなわち逆変換により元の変数と全く同じ変数に戻る)変数変換を表す実行文である。実行文M1で変換された変数は、対応する実行文R1を実行することにより元の変数と同じ値に戻る。M2とR2も同様の関係にある。
【0044】
次に条件分岐追加部14が、制御フローグラフG に以下の手順(i)〜(viii)に従って文B1'と条件式Cを追加することで、制御フローグラフG'(図17参照)を作成する(S146)。すなわち、まず(i)制御フローグラフGに実行文の組B1'を追加し、さらに実行文の組B1'の実行後に実行文の組B2に進むという無条件分岐のフローを追加する。また、(ii)実行文M1を追加し、M1の実行後に実行文の組B1'に進むという無条件分岐のフローを追加する。(iii) 実行文M2を追加し、M2の実行後に実行文の組B1に進むという無条件分岐のフローを追加する。(iv)条件式Cを追加し、更に条件式Cの論理値が真である場合は実行文M1に進み、偽である場合には実行文M2に進むという条件分岐のフローを追加する。(v)条件式Aの論理値が偽である場合に実行文の組Dに進むというフローを削除する。(vi)条件式C'を追加し、さらに条件式Aの論理値が偽である場合に条件式C'に進むというフローを追加する。(vii)実行文R1を追加し、さらに条件式C'の論理値が真である場合にR1に進むというフローと、R1の実行後に条件式の組Dに進むという無条件分岐のフローとを追加する。(viii)実行文R2を追加し、さらに条件式C'の論理値が偽である場合にR2に進むというフローと、R2の実行後に無条件でDに進むというフローを追加する。
【0045】
次に、条件分岐追加部14が、以上のようにして生成された制御フローグラフG'からバイナリプログラムP'を作成し(S147)、生成したバイナリプログラムP'を出力する(S148)。
【0046】
なお、制御フローグラフG内に複数のdo-whileループが存在する場合には、それらdo-whileループごとにステップS143〜S146の処理が行われる。
【0047】
次に具体例を用いて、図14の処理手順の処理内容を更に説明する。例えば、難読対象のプログラムPに例えばdo-whileループを含んだプログラム片「do{b=a+1;c=2*b;a=a-1;}while(a>0); d=b+c+e;」 が含まれていたとする。
【0048】
この場合、制御フローグラフG中のそのプログラム片を表す部分は図18に示すようなものとなる。以下では、このようなプログラム片が、図19に示すようなプログラム片に変換される処理の流れを説明する。
【0049】
このプログラム片の中のdo-whileループは、3つの実行文{b=a+1;c=2*b;a=a-1;}からなる実行文の組1802を実行した後条件式1801(a>0)に進むというフロー1803と、条件式1801が真の場合に実行文の組1802に進むというフロー1804とから構成される。また、このプログラム片には、条件式1802が偽の場合に実行文D(1805)に進むというフローが含まれる。ステップS143でそのループ構造が選択され、ステップS144で実行文の組B(1802)が、B1={b=a+1;}と、B2={c=2*b; a=a-1;}という2つの組に分割されたとする。実行文の組をステップS144でどのように分割するかは、あらかじめルール記憶部16にルール情報として登録しておけばよい。
【0050】
次に条件分岐追加部14は、ステップS145において、条件式Cとして例えば「 e > 10 」を、これと等価な条件式C'として「 e > 0 && e*e > 100」を、追加する実行文の組B1'として「b ={ a + a*(a-1)%2 + 1; }」を、それぞれ作成する。この例で作成した条件式Cは「恒真及び恒偽のいずれでもない」という条件を満たしている。このような条件を満たす条件式Cは、ルール記憶部16に登録されたルール情報に基づき自動生成すればよい。例えば、難読化対象のプログラム中で用いられていない変数を導入し、その変数があらかじめ定められた定数より大きいという不等式を条件式Cとして生成する、というルールが考えられる。また、条件式C'の生成ルールは、条件式Cの生成ルールと対応して定めることができる。例えば、上述のような不等式を条件式Cとする場合、その不等式に現れる変数を二乗したものがその定数の二乗より大きく、かつその変数は正であるという条件式C'を生成するというルールが考えられる。このように、ある条件式に等価な条件式を生成するルールとしては、表現は異なるが算術的に等価な式を生成するルールを用いることが考えられる。この例で作成した文の組B1'は、図7の例で追加したものと同じであるため、説明を省略する。
【0051】
また、条件分岐追加部14は、ステップS145において、ルール記憶部16を参照して、変数を可逆変更する文M1, M2としてそれぞれd=2*d + 3, d=4*d - 5を、M1, M2のそれぞれの逆変換R1, R2としてd=(d - 3) / 2, d=(d+5) / 4を作成する。ルール記憶部16には、例えば可逆的な変換x=2*x + 3とその逆変換x=(x - 3) / 2との組をルール情報として登録しておき、条件分岐追加部14は難読化対象のループ構造内に含まれない変数を求め、その変数をそのルール情報の変数x に代入することで、具体的な変換M1とR1を生成すればよい。また、ルール記憶部16には、そのような変換と逆変換の組を複数登録しておき、それら複数の組の中から、M1とM2に用いるものをそれぞれ個別に選択するようにしてもよい。また、難読化対象のプログラム中に複数のdo-whileループが存在する場合、難読性を高めるためには、それらdo-whileループごとに、使用するM1とM2の組合せを変えてもよい。図19の例では、追加する可逆変換の文で用いる変数として、do-whileループから出た直後の実行文Dで用いられている変数d(この変数dはdo-whileループ内では用いられていない)を用いたが、これに限られるわけではない。例えば、難読化対象のプログラムに含まれていない変数を導入し、その変数を可逆変換する文を生成してもよい。ただし、難読性を増すためには、do-whileループから出た直後の実行文Dに含まれる変数を用いることが考えられる。
【0052】
そして、条件分岐追加部14は、図19に例示するように、図18のdo-whileループに対し、実行文の組B1'、条件式C及びC'、変数の可逆変換の文M1, M2とその逆変換R1, R2とを追加する。そして、更に、条件式Cの評価結果が偽の場合に文M2に進むというフロー1901と、文M2の実行後に文B1に進むというフロー1902とを追加し、条件式Cの評価結果が真の場合に文M1に進むというフロー1903と、文M1の実行後に実行文の組B1'に進むというフロー1904と、その実行文の組B1'の実行後に実行文の組B2(より厳密にはその組内の先頭の文「c=2*b;」)に進むというフロー1905とを追加する。
【0053】
図19のフローグラフのうち、フロー1906,1907及び1908からなるループ構造は、図18に示したフロー1803及び1804からなるループ構造と等価である。
【0054】
また、条件分岐追加部14は、条件式Aが偽である場合に文Dに進むというフローを削除し、条件式Aが偽である場合に条件式C'に進むというフロー1909を追加する。そして、条件式C'が真である場合に文M1の逆変換R1に進むフロー1910と、R1の実行後に文Dに進むというフロー1911を追加する。また、条件式C'が偽である場合に文M2の逆変換R2に進むフロー1912と、R2の実行後に文Dに進むというフロー1913を追加する。
【0055】
以上のような処理において追加された変数変換の文M1及びM2により変換された変数が実際に使用される実行文Dが実行される直前で、対応する逆変換の文R1及びR2によりそれぞれ元の値に戻される。したがって、図19が示す制御フローの処理結果は、図18が示す制御フローの処理結果と等価となる。
【0056】
以上、本発明の実施形態を説明した。以上に説明したように、本実施形態では、一般的なマシン語が備えている条件分岐命令や無条件分岐命令、算術命令のみを用いることでプログラムを変換している。
【0057】
以上では、whileループとdo-whileループという2種類のループの難読化処理の例を説明したが、上記実施形態の手法は他の種類のループにも適用できる。例えば、ループの終了判定(すなわち条件式A)をループの処理(すなわちループ内の実行文の組B)を実行する前に行う前判定方式のループ(例えばforループ)は、上述のwhileループと同様の処理を適用すればよい。また、ループの終了判定をループの処理を実行した後に行う後判定方式のループは、上述のdo-whileループと同様の処理を適用すればよい。
【0058】
難読化対象のプログラム中に複数種類のループが含まれる場合には、個々のループごとに、当該ループの種類に応じて、前判定方式に対する難読化処理、後判定方式に対する難読化処理をそれぞれ適用すればよい。
【0059】
また、以上の例では、難読化対象のループ中に複数の実行文が含まれていた(図6、図12、図18参照)。しかし、難読化対象のループ中に実行文が1つしかない場合にも、上述の実施形態の処理は適用できる。このような場合、条件分岐追加部14は、ループ中の実行文を、その実行文と等価な処理を表す複数の実行文に変換すればよい。このような変換は、例えば、難読化対象のプログラムに影響を与えないダミーの実行文をループ内に追加することにより行えばよい。また、別の例として、ループ中の実行文Bで用いられる定数cを変数x(この変数xは難読化対象のプログラム中で用いられていないものにする)に代入する処理を表す実行文「x=c」をループ中の実行文Bの前に追加し、実行文B中の定数cを変数xに変更してもよい。
【0060】
また、上記実施形態では、難読化においてループ構造中に新たに文や条件式を追加しない。従って、難読化に伴って発生する処理オーバーヘッドが予測しやすい。例えば、図2の例の処理によって生じるオーバーヘッドは以下のように見積もることができる。
【0061】
まず、whileループの繰り返し実行回数をnとおく( n ≧ 1)。この時、難読化前のプログラム(図6)の実行コストCbeforeは以下のように算出される。
Cbefore = n( c(A) + c(B1) + c(B2) ) + c(A)
ここで、c(X)は、文Xの実行コストを表すとする。
【0062】
一方で、難読化後のプログラム(図7)の実行コストCafterは以下のように算出される。
Cafter = ( c(A') + c(B1') + c(B2) ) + (n - 1)( c(A) + c(B1) + c(B2) ) + c(A)
となる。
【0063】
従って、実行オーバーヘッドCafter - Cbeforeは、
Cafter - Cbefore = ( c(A') - c(A) ) + ( c(B1') - c(B1) )
と見積もることができる。このように、本実施形態によって生じる実行オーバーヘッドはループ構造の実行回数に関わらず一定なので、予測しやすい。
【0064】
以上に説明したプログラム難読化装置10は、一つの例では、汎用のコンピュータに上述の処理を表すプログラムを実行させることにより実現される。ここで、コンピュータは、例えば、ハードウエアとして、CPU等のマイクロプロセッサ、ランダムアクセスメモリ(RAM)およびリードオンリメモリ(ROM)等のメモリ(一次記憶)、HDD(ハードディスクドライブ)コントローラを経由して接続されたHDD、各種I/O(入出力)インタフェース等が、バスを介して接続された回路構成を有する。バスには、ローカルエリアネットワーク等のネットワークに接続するためのネットワークインタフェースが接続されていてもよい。また、そのバスに対し、例えばI/Oインタフェース経由で、CDやDVDなどの可搬型ディスク記録媒体に対する読み取り及び/又は書き込みのためのディスクドライブ、フラッシュメモリなどの各種規格の可搬型の不揮発性記録媒体に対する読み取り及び/又は書き込みのためのメモリリーダライタなどが接続されてもよい。上に例示した各機能モジュールの処理内容が記述されたプログラムがCDやDVD等の記録媒体を経由して、又はネットワーク等の通信手段経由で、ハードディスクドライブ等の固定記憶装置に保存され、コンピュータにインストールされる。インストールされたプログラムがRAMに読み出されCPU等のマイクロプロセッサにより実行されることにより、上に例示したプログラム難読化装置10の機能が実現される。
【符号の説明】
【0065】
10 プログラム難読化装置、12 ループ検出部、14 条件分岐追加部、16 ルール記憶部、100 難読化対象のプログラム、110 難読化されたプログラム。
【特許請求の範囲】
【請求項1】
コンピュータを、
難読化対象のプログラムからループを検出する検出手段、
前記難読化対象のプログラムに対し、前記ループの前に恒偽及び恒真のいずれでもない条件式を追加すると共に、前記条件式の論理値が偽の場合には前記ループの先頭に進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後前記ループ内の前記途中の実行文の次の実行文に進む、というフローを追加する追加手段、
として機能させるためのプログラム難読化プログラム。
【請求項2】
前記追加手段は、前記ループ内に実行文が1つしかない場合には、当該実行文を当該実行文と等価な複数の実行文に置き換えた上で、前記条件式の論理値が真の場合には、当該複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後、前記ループ内の前記途中の実行文の次の実行文に進むというフローを前記プログラムに追加する、ことを特徴とする請求項1記載のプログラム難読化プログラム。
【請求項3】
前記追加手段は、前記ループを継続するか否かを判定する判定条件式が前記ループの先頭に位置する場合には、前記恒偽及び恒真のいずれでもない条件式として、前記判定条件式の論理値が偽となる場合には必ず偽となる条件式を前記ループの前に追加する、ことを特徴とする請求項1又は2に記載のプログラム難読化プログラム。
【請求項4】
前記等価な実行文の組は、前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と異なる表現を含むことを特徴とする請求項1〜3のいずれか1項に記載のプログラム難読化プログラム。
【請求項5】
難読化対象のプログラムからループを検出する検出手段と、
前記難読化対象のプログラムに対し、前記ループの前に恒偽及び恒真のいずれでもない条件式を追加すると共に、前記条件式の論理値が偽の場合には前記ループの先頭に進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後前記ループ内の前記途中の実行文の次の実行文に進む、というフローを追加する追加手段と、
を備えるプログラム難読化装置。
【請求項1】
コンピュータを、
難読化対象のプログラムからループを検出する検出手段、
前記難読化対象のプログラムに対し、前記ループの前に恒偽及び恒真のいずれでもない条件式を追加すると共に、前記条件式の論理値が偽の場合には前記ループの先頭に進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後前記ループ内の前記途中の実行文の次の実行文に進む、というフローを追加する追加手段、
として機能させるためのプログラム難読化プログラム。
【請求項2】
前記追加手段は、前記ループ内に実行文が1つしかない場合には、当該実行文を当該実行文と等価な複数の実行文に置き換えた上で、前記条件式の論理値が真の場合には、当該複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後、前記ループ内の前記途中の実行文の次の実行文に進むというフローを前記プログラムに追加する、ことを特徴とする請求項1記載のプログラム難読化プログラム。
【請求項3】
前記追加手段は、前記ループを継続するか否かを判定する判定条件式が前記ループの先頭に位置する場合には、前記恒偽及び恒真のいずれでもない条件式として、前記判定条件式の論理値が偽となる場合には必ず偽となる条件式を前記ループの前に追加する、ことを特徴とする請求項1又は2に記載のプログラム難読化プログラム。
【請求項4】
前記等価な実行文の組は、前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と異なる表現を含むことを特徴とする請求項1〜3のいずれか1項に記載のプログラム難読化プログラム。
【請求項5】
難読化対象のプログラムからループを検出する検出手段と、
前記難読化対象のプログラムに対し、前記ループの前に恒偽及び恒真のいずれでもない条件式を追加すると共に、前記条件式の論理値が偽の場合には前記ループの先頭に進み、真の場合には前記ループ内の複数の実行文のうちの最初の実行文から途中の実行文までの実行文の組と等価な実行文の組を実行した後前記ループ内の前記途中の実行文の次の実行文に進む、というフローを追加する追加手段と、
を備えるプログラム難読化装置。
【図1】
【図2】
【図3】
【図4】
【図5】
【図6】
【図7】
【図8】
【図9】
【図10】
【図11】
【図12】
【図13】
【図14】
【図15】
【図16】
【図17】
【図18】
【図19】
【図2】
【図3】
【図4】
【図5】
【図6】
【図7】
【図8】
【図9】
【図10】
【図11】
【図12】
【図13】
【図14】
【図15】
【図16】
【図17】
【図18】
【図19】
【公開番号】特開2010−191847(P2010−191847A)
【公開日】平成22年9月2日(2010.9.2)
【国際特許分類】
【出願番号】特願2009−37526(P2009−37526)
【出願日】平成21年2月20日(2009.2.20)
【公序良俗違反の表示】
(特許庁注:以下のものは登録商標)
1.JAVA
【出願人】(000005496)富士ゼロックス株式会社 (21,908)
【Fターム(参考)】
【公開日】平成22年9月2日(2010.9.2)
【国際特許分類】
【出願日】平成21年2月20日(2009.2.20)
【公序良俗違反の表示】
(特許庁注:以下のものは登録商標)
1.JAVA
【出願人】(000005496)富士ゼロックス株式会社 (21,908)
【Fターム(参考)】
[ Back to top ]