Inside Code Virtualizer
OreansのCode Virtualizer 1.0.1.0のデモ版をリバースエンジニアリングした結果を纏めたScherzo氏の文献を日本語に翻訳したものです。
ただし、このバージョンがリリースされたのが2006年の6月なので現行のCode VirtualizerやThemidaで利用されるものとは大きく異なっている可能性もあります。あくまでCode Virtualizerの動作原理の基礎的な部分を知るための読み物です。
# 例によってフェアユースの概念で公開していますが問題があれば削除致します
注意:本稿の内容は学術/研究目的のためにのみ使用が許されています。本稿の著者は、本稿で説明されている知識の任意の用途あるいは違法な目的での使用について一切の責任を負いません。本免責事項に同意する場合はのみ本稿を読むことが許されるものとします。
1. Introduction
1.1. About Code Virtualizer
Code Virtualizerは、開発者が彼らのセンシティブなコード領域をリバースエンジニアリングから保護するのに役立つ強力なコード難読化システムです。Code Virtualizerは最小限のシステムリソースであなたのセンシティブなコードを高いセキュリティを持たせるように設計されています。Code Virtualizerはオリジナルのコードを内部の仮想マシンによってのみ理解可能な仮想オペコードに変換します。これらの仮想オペコードと仮想マシン自体は保護されるアプリケーションごとに異なり、Code Virtualizer上では一般的な攻撃を回避することが可能となっています。Code Virtualizerは実行ファイル(。exe)、DLL、OCX、ActiveXコントロール、スクリーンサーバやデバイスドライバのような、あらゆるx32およびx64のネイティブPEファイル内のコードを保護することができます[1]。
1.2. About This Article
まず始めに、私の英語には多くの誤りが表示される可能性があることを断っておきます。本稿の目的は、Code Virtualizerがどのように動作するかを説明するものです。先月中に、私は余暇を利用してsoftworm[2]によってアンパックされたCode Virtualizer Demo 1.0.1.0を分析しました。幸運なことに私は分析を完了し、これは私がこれまでに見た中でも最高のソフトウェアであると言うことができます。「保護」の観点で最高という意味ではなくその「構成」の観点からです。これは私が分析した中でも最も満足のいくソフトウェアでした。最も重要な気付きは3つで、OllyDbg[3]によって逆アセンブルされたコードの記述と説明、それからコードの実行順序になります。私が言おうとしていることのほとんどは、Code Virtualizerの1.0.1.0バージョンにのみ適用されます。新バージョンに対するコメントについては、第5章の「Hopes for the Future and Acknowledgments」を参照して下さい。
本稿は次の3つのパートに分かれています。始めに、私は仮想マシンがどのように生成されているか、そしてOreans[4]が各仮想マシンは独自の特徴を持っている言及している理由について説明します。次に、仮想オペコードが生成される方法を説明する前に、それらがどのように実行され如何にして元のアプリケーションのコードをエミュレートするかのコンセプトを示します。最後に、Code Virtualizerの完全にアンパックされたバージョンを作成する方法について学習するためのボーナスパートを設けています。
それでは、本稿をお楽しみ下さい。そしてあなたがこれを呼んで何か学べることを願っています。
2. The Virtual Machine – Light VM
2.1. The Virtual Machine itself
私が仮想マシンのことを”Light VM”と呼称したことについて、既に気づいたのではないかと思います。実際に、私ではなくOreansの開発者はおそらくThemida Virtual Machineを参照していることでしょう。
基本的に、各仮想マシンは150のハンドラと1つのメインハンドラを持っています。ハンドラとは、仮想オペコードに対処するための関数のようなものを意味します。一般に、それらは小さく(1~6行程度のアセンブリコード)、各オペコードを理解するために非常に重要なものとなっています。
Handler_Informationと呼ばれる第1の構造体とその一例を以下に示します(図1):
- WORD id // ハンドラを示す数値
- DWORD start // Code Virtualizerファイル内のこのハンドラの開始アドレス
- DWORD end // Code Virtualizerファイル内のこのハンドラの終了アドレス
- DWORD address // 保護ファイル内のハンドラの開始アドレス
- WORD order // 保護ファイル内のハンドラの位置を示す(0EhからA4hまでの)乱数
図1: Handler_Information 構造体の例
この構造体がVMを生成する主要な要素となっています。本稿では150ハンドラ全ては表示しません。面倒ですが、Code Virtualizerをより深く理解するためには、あなたがこの構造体を1つ1つ読んで理解する必要があります。図1に示した構造体ではハンドラのidが0000h、開始アドレスが006035F0h、終了アドレスが006035F8hとなっています。参照されるハンドラ(図2)とメインハンドラ(図3)を示します。
図2: ハンドラ 0000h
図3: メインハンドラ
メインハンドラ内で11111111hというDWORD値が3回現れているのが確認できます。これらは、保護されるアプリケーションによって異なるもので、1つ目のDWORD値は保護されたファイルのメインハンドラの7行目のアドレス、2つ目のDWORD値は仮想マシンの「イメージベース」、3つ目のDWORD値はそのVM内に存在するハンドラの合計数となります。
2.2. Generating the Virtual Machine
「これらの仮想オペコードと仮想マシン自体は保護アプリケーションによってすべて異なっており、Code Virtualizerに対する一般的な攻撃を回避している」というフレーズの証拠を与えますが、これは非常に重要な機能というわけではありません。
Code Virtualizerによって行われる最初のステップはメインハンドラを記述することです。次に、その他の150のハンドラが1EhからA4hまでのHandler_Information.orderシーケンスに従って記述されます。このHandler_Information.orderはランダムに生成されるため、ハンドラの対応は全ての保護アプリケーションによって異なります(付録の資料[5]を参照)。
ここではハンドラ0000h(図2を参照)がどのように書かれるか説明します。まず同様のプロセス(説明は割愛)が全てのハンドラにおいて発生します。そして次のステップでは以下のコードで示されたことを行います:
図4:LODS命令の例
これはLODS命令のオペコードを探索するコードです。これはハンドラ0154hと0156hには適用されないことが分かります。しかしなぜこのようなチェックを行っているのでしょうか? ここでハンドラ内で探索されるLODS命令はそれぞれ1, 2, または4バイトを読み込む仮想オペコードを表しています。そして、セキュリティを高めるためにOreansの開発者はLODS命令の直後にランダムコードを挿入しようと考えたわけです。そのため、彼らは私がSpecial_handlerと呼んでいる別の構造体を使用します。それがこちらです:
- WORD Handler_Informatioon.id // Handler_Information構造体を参照
- BYTE instruction3 // 3番目の命令として書かれる命令のの種類を示す番号
- BYTE instruction2 // 2番目の命令として書かれる命令のの種類を示す番号
- BYTE instruction1 // 1番目の命令として書かれる命令のの種類を示す番号
- BYTE instruction4 // 4番目の命令として書かれる命令のの種類を示す番号
- DWORD Random1 // 命令2の一部となる乱数
- DWORD Random2 // 命令3の一部となる乱数
表1:ランダム命令として利用可能な命令表(各命令はそれぞれ対応するax,bx,al,blレジスタも利用してBYTE,WORD,またはDWORD形式のいずれかで記述される)
instruction1 | instruction2 | instruction3 | instruction1 | |
0 | sub eax,ebx | sub eax,Random1 | sub eax,Random2 | sub ebx,eax |
1 | add eax,ebx | add eax,Random1 | add eax,Random2 | add ebx,eax |
2 | xor eax,ebx | xor eax,Random1 | xor eax,Random2 | xor ebx,eax |
図5:Special_Handler構造体の例
よって、これらの操作が行われる前のハンドラ0000h(図2)は以下のようになります:
図6:4命令が追加される前のハンドラ0000h
次のステップでは、別のセキュリティ機能が付加されます。いくつかの種類の命令はOreansf1.dllモジュールによってエクスポートされたOreansf1.F4関数にマウントされます。これにより各ハンドラのコードがより高度に難読化されることを意味し、このミューテーションエンジンはVM難読化オプションに厳密に関連しています。実際に、このオプションはミューテーションされたオペコードの複雑さのみを変更します。Code Virtualizerへの一般的な攻撃においてVMの複雑さの高低に差はないため,これは本当に不思議な操作です。(より多くのコメントについては、"Hopes for the Future and Acknowledgments"の項を参照してください。)
図7:オペコードがミューテーションされたハンドラ0000h
メインハンドラへのジャンプが書かれる前に次のハンドラが呼び出されます。
次のセキュリティ機能は見ていて非常に楽しいです:150すべてのハンドラがランダムに混在しています!!! 例えば、ハンドラ0161hにハンドラ0001hとハンドラ0069hなどが続いています。最後に、難読化された完了すると、ユニークで分析が困難なコードが出来上がります。本当に!私はそうは思いませんがね:)
3. The Virtual Opcodes
3.1. Disassembling and "Assembling" again
私は説明が曖昧であることを知っています。読者の皆さんはおそらくこれらのハンドラがどのように動作するのか検討もつかないでしょうが、3.3節でそれを明らかにすることをお約束致します。次の図は、仮想化されていないマクロを示しています。仮想化されたコードは0040106Ehで始まり0040107Dhで終わります。
図8:仮想化されていないマクロ
次のPUSH 0040108DhとRET命令はプログラムが正常に実行を続けることができるようにオリジナルコードが追加されたものです。
その後、エクスポート関数Oreansf1.F1は以下に示すようにオリジナルコードを逆アセンブルします。私がそれを見た時本当に驚きました。私はCode Virtualizerが文字列を介してではなくオリジナルコードのバイト列を介してコードを生成していると期待していたからです。Code VirtualizerはDelphiの関数を使用して文字列を変換しており。これは高速な方法ではありませんが確かsに実装が容易な方法であると思います。
図9:逆アセンブルされたコード
ここでOreansX2dllR.dllによってエクスポートされたOreansX2dllR.F1関数がCode Virtualizer構文でアセンブリコードを組み立てるための主要かつ最も複雑な作業を行います。そしてOreansX2と私が呼称する最も重要な構造体を生成します。
OreansX2構造体:
- DWORD instruction // Code Virtualizer構文に続く命令の種類
- DWORD sufix // 命令のサフィックス
- DWORD data1 // 命令のデータ
- DWORD data2 // 命令のデータ
- WORD unknown // 用途不明
表2:OreansX2構造体で利用可能な命令
OreansX2.instruction | instruction |
00 | LOAD |
01 | STORE |
02 | MOVE |
03 | IFJMP |
04 | EXTRN |
05 | UNDEF |
06 | IMULC |
07 | ADC |
08 | ADD |
09 | AND |
0A | CMP |
0B | OR |
0C | SUB |
0D | TEST |
0E | XOR |
0F | MOVZX |
10 | MOVZX_W |
11 | LEA |
12 | INC |
13 | RCL |
14 | RCR |
15 | ROL |
16 | ROR |
17 | SAL |
18 | SAR |
19 | SHL |
1A | SHR |
1B | DEC |
1C | NOP |
1D | MOVSX |
1E | MOVSX_W |
1F | CLC |
20 | CLD |
21 | CLI |
22 | CMC |
23 | STC |
24 | STD |
25 | STI |
26 | HLT |
27 | BT |
28 | BTC |
29 | BTR |
2A | BTS |
2B | SBB |
2C | MUL |
2D | IMUL |
2E | DIV |
2F | IDIV |
30 | BSWAP |
31 | NEG |
32 | NOT |
33 | RET |
表3:利用可能なサフィックス
OreansX2.suffix | suffix |
00 | |
01 | ADDR |
02 | %sADDR, %d |
03 | %sADDR, %.8x%h |
04 | BYTE PTR %s[ADDR] |
05 | WORD PTR %s[ADDR] |
06 | DWORD PTR %s[ADDR] |
07 | QWORD PTR %s[ADDR] |
08 | %sBYTE PTR [%.8x%h] |
09 | %sWORD PTR [%.8x%h] |
0A | %sDWORD PTR [%.8x%h] |
0B | %sQWORD PTR [%.8x%h] |
0C | ADDR, BYTE PTR %s[%.8x%h] |
0D | ADDR, WORD PTR %s[%.8x%h] |
0E | ADDR, DWORD PTR %s[%.8x%h] |
0F | ADDR, QWORD PTR %s[%.8x%h] |
10 | %s%d |
11 | %s%.8x%h |
12 | reserved |
13 | reserved |
14 | reserved |
15 | reserved |
16 | reserved |
17 | reserved |
18 | BYTE |
19 | WORD |
1A | DWORD |
1B | QWORD |
1C | reserved |
1D | reserved |
1E | FLAGS |
1F | %s[ADDR] |
20 | %sBYTE %d |
21 | %sWORD %d |
22 | %sDWORD %d |
23 | %sQWORD %d |
表から読み取れるようにこの構文は非常に論理的です。これはXORやADDなどのよく知られた命令や、特殊な用途のためにMOVEやSTORE、LOADなどの文字通りの命令を使用します。サフィックスは単一の変数ADDRとDWORD PTR [ADDR]のようなよく知られたフォーマットを使用します。
私はこれらの命令がどのように逆アセンブルされたオリジナルコードから生成されるのか完全には理解していませんが、そのパターンを確認するためのテストを行うならばそれはさして問題ではないないと考えています。次に、それぞれのOreansX2構造体でCode Virtualizer命令の等価ブロックによって生成されるアセンブリ命令を示します(より多くの例については付録[5]を参照して下さい)。
図10:Code Virtualizer構文の例
OreansX2構造体の最初のパラメータは80000002hとなっており、この"02"は表2で確認することのできるように"MOVE"を意味しています。しかし、この"80"は相対アドレスを持つ命令であることを意味しています。つまり、アドレスF0000028hは仮想マシンのイメージベースの相対アドレスであることが分かります。
3.2. Generating and Writing the Virtual Opcodes
OreansX2構造体では、私がPre_Handlerと呼ぶ次の構造体に到達するために一連の操作が行われます。この構造体のサイズは28hバイトです。
- DWORD counter // 各Pre_Handler構造体により0Ehずつ増えるカウンタ
- DWORD real_opcode_mark // 割り当てメモリ内の元のオペコードのアドレスであり、元のオペコードを表す命令ブロックの最初のCode Virtualizer命令にのみ適用される
- DWORD unknown1 // 用途不明
- DWORD counter_0E // Pre_handler.counter + 0Eh (用途不明)
- BOOL is_special // 元のオペコードの種類がcallやjumpなどであればTRUEとなり、特殊な構造体がそれらの命令のために生成される
- BYTE instruction // OreansX2.instructionと同じ
- DWORD sux // OreansX2.suxと同じ
- DWORD data1 // OreansX2.data1と同じ
- DWORD data2 // OreansX2.data2と同じ
- WORD unknown2 // OreansX2.unknownと同じ
- 7 bytes unknown
- BOOL is_relative_address // 命令が相対アドレスを持つ場合TRUEとなる
図11:Pre_Handler構造体の例
さて、仮想オペコード生成に直接関連する主要な構造体の分析も既に済んでいます。私はこの構造体をHandlerと呼んでいます。
- WORD handler // 重要なパラメータ:呼び出すハンドラを決定するのに利用され、Handler_Information.idに相当する
- DWORD Pre_Handler_addr // 本構造体によって生成されるPre_Handler構造体に対応するメモリアドレス
- DWORD memory_opcode // 本構造体によって生成される仮想オペコードのメモリアドレス
- BYTE type_of_handler // ハンドラがLODS命令を介して仮想オペコードを読まない場合には0となり、ハンドラが1,2,4,8の仮想オペコードを読む場合にはそれぞれ1,2,4,8となる
- BYTE unknown2 // 用途不明
- DWORD data1 // Code Virtualizer命令のデータ(例えば、LOAD 18hなら18hとなる)
- DWORD data2 // 64bit Code Virtualizer命令のデータ
- DWORD le_opcode // 本構造体によって生成される仮想オペコードの保護ファイルにおけるアドレス
図12:Handler構造体の例
各Handler構造体は1,2,または4の仮想オペコードを生成することができ、これはHandler構造体のベクタが生成される方法を理解するために必要不可欠なものです。これはそれほど複雑ではありませんが、各ケースについて説明すると膨大になるため本稿では割愛していますが、詳細についてはどのように機能するのかコメントを加えた付録[6]を参照して下さい。
基本的に各Handler構造体のベクタはハンドラ015Bhで始まり、ハンドラ0161hと015Chで終わります。ハンドラ015Bhと015Chは実際には存在しません。これらは単にCode Virtualizerに対して、仮想オペコードの実行開始時と終了時に特別なコードをハンドラに挿入することを伝えるために存在しています。この特別なコードは後で少しだけ示します。
これらのハンドラとPre_Handlerの間で次のような処理がなされます:Pre_Handler.is_specialがTRUEの場合、ハンドラ0161hが対応するHandler構造体に追加されます。その後、Handler構造体の異なるシーケンスが各ケース(MOVE, LOAD, STORE, SHL, ADD,SUB, IFJMP, RET, UNDEF, またはその他のCode Virtualizer命令)ごとに生成されます。詳細については付録[6]を参照して下さい。
Handler構造体のベクタが生成される方法について理解したので、仮想オペコードがどのように構築されているか、というCode Virtualizerの華麗な部分を最後に理解することができるようになります。
まず初めにCode Virtualizerはハンドラ015Bhと015Chを検索します。仮想マシンを初期化および初期化解除する責任を持った仮想オペコードが事前に構築されます(この時点ではCode Virtualizer命令やその他の構造体が存在しないことを意味します)。例えば、保護アプリケーションがその仮想オペコードを実行する前にレジスタやフラグを保持・復元するための処理が含まれます。
そこで、Handler構造体に当てられた仮想オペコードの生成についてまず説明します。Code Virtualizerが最初に行うことは非常に驚くべきことです。乱数ジェネレーターを使用して特定のCALLの実行について決定します。このCALLは"fake"仮想オペコードを生成するものです。つまり、これらの仮想オペコードが実行されるもののプログラムには何の影響も与えません(NOPシーケンスのようなものです)。したがって、これらは実際の仮想オペコードを難読化するために利用されているものと考えられます。加えて、異なる5種類の"fake"仮想オペコードが存在しておりプログラムの分析がさらに困難となります。そして、これら"fake"仮想オペコードは仮想オペコード難読化のオプション(低、通常、高、最高)に厳密に関連しています。そのオプションに応じて、乱数によって決まる特定のCALLの再帰的実行回数が増減します。これにより、命令のエミュレーションの途中で"fake"仮想オペコードが数多く存在することになります。これらによって仮想オペコードのサイズが3倍も大きくなるのです!
"fake"仮想オペコードがなければ、同じアプリケーションをCode Virtualizerによって2回保護して仮想オペコードを比較した時、その仮想オペコードは同一になります。この差異は私がkeyと呼称するCode Virtualizer内のグローバル変数によって決まります。
さて、ここでハンドラ0010hの呼び出しを確認してみます。ハンドラ0010hを呼び出す必要がある場合、Handler_Information.orderとそのSpecial_Handler構造体(これらの構造体の詳細については2.1項と2.2項を参照)を与えていましたが、正しい仮想オペコードに到達すために表1に記載された命令(ADD, SUB, XOR)とは逆の操作が実行されていました。これらは私を少し混乱させました。それでは、これらの操作について詳しく見て行きましょう。
3.3. Completing the analysis: why does this really work?
本セクションの目的は、仮想マシンの初期化と仮想オペコードの実行についてステップ・バイ・ステップで説明することです。そのために、断片化されたハンドラとミューテーションエンジンを持たないファイル(付録[7])を使用します。
保護されたアプリケーションがマクロに到達すると、そのコードはCode Virtualizerによって作成されたセクション内のPUSH/JMPシーケンスにリダイレクトされます。
図13:PUSH/JMPシーケンスの例
プッシュされた値は最初の仮想オペコードのアドレスであり、ジャンプ先はメインハンドラとなります。
図14:仮想オペコード
図15:メインハンドラ
004072D8hで開始されるコードは常に全てのハンドラの実行前に呼び出されます。これは仮想オペコードによって死体されたハンドラを呼び出すためです。キーは最初の仮想オペコードのアドレスで初期化され、EBXレジスタに格納されます。ESIレジスタには仮想オペコードが読み込む現在のアドレス、EDIレジスタには仮想マシンのイメージベースが格納されています。スタックは値を格納するために使用され、EAXレジスタはXORやADDなどの命令に使用されます。
コードがアドレス004072D8hに達した時のレジスタは以下のようになります:
図16:メインハンドラにおけるレジスタ
今バイト値62hが読み込まれた後でキーでいくつかの操作が行われています(図15で見られるこれらのランダムな操作については2.2項で説明しました)。コードがアドレス004072E4hに達した時のレジスタの値は以下のとおりです:
図17:ハンドラ2Dhにジャンプした時のレジスタ
ご覧のように、キーが変更されESIレジスタが更新されました。今このコードjmp dword ptr ds:[edi+eax*4]がどのような操作であるかは明白です:EDIレジスタは仮想マシンのイメージベースを格納しており、EAXレジスタは仮想オペコードから取得された値にいくつかの操作を加えた値を格納しています。お気づきかもしれませんがこれはハンドラへのポインタのテーブルであり、ハンドラを呼び出す上で非常に重要な値となります:
図18:ハンドラのポインタテーブルの一部
今では、全てのハンドラが呼び出される方法が分かっており、すべての保護アプリケーションにおいて仮想オペコードが独特である理由を説明することが可能になりました。その理由はキーによるものです。キーは何度も変更されその値はアドレス依存です。仮想オペコードがキーに依存(3.2項の解説を参照)しているため、仮想マシンのサイズは一定ではなく、仮想オペコードはユニークになります。
メインハンドラにおける最初の2つの命令(PUSHADとPUSHFD)はスタックに汎用レジスタ(EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI)とフラグレジスタ(EFLAGS)の値を格納するものです。その後、事前に構築された仮想オペコードが仮想マシンの最初の38バイトでこれらのレジスタをポップしています。今、XOR ECX,ECXのような命令がアドレス00407014hで値を変更します。仮想化されたコードの実行の終了時に、アプリケーションの実行を継続させるためにレジスタの値が正しい位置に復元されます。
ここからは分析者の皆さんの時間です。私は全ての実行業についてコメントを残していません。私はあなたにCode Virtualizerについての基礎を与えました。その知識が今ではより明白となっていることを願っています。例えば、サンプルプログラム[7]を追跡して、他のハンドラが実行される方法を理解できるか試してみてください。
4. How to Make an Unpacked version of Code Virtualizer Full
unpack.cnでリリースされたCode Virtualizer Demoバージョンのクラック版がについて、これはアプリケーションごとに複数の仮想マシンを許可するのみで、同じ仮想マシンで2つ以上のマクロを保護することはできません。そのことを明白にするために私は本章を追加しました。
まず第一に知る必要があるのは、保護オプションに関する情報が格納される場所とCode Virtualizerの利用を制限するためにそれがどのように変更されるかです(はい、あなたは多分私のいうことが信じられないかもしれませんが、カスタムオプションはデモ版にも存在しており、これは開発者の大きな過ちでもあります:) )。
図20:保護オプションのデータ構造体
- 仮想オペコードの難読化:0,1,2,3はそれぞれ低,通常,高,最高に対応
- 仮想マシンの複雑さ:0,1,2,3はそれぞれ低,通常,高,最高に対応
- 仮想マシンの数:1から5までの番号が生成される仮想マシンの数を示す
- ラストセクション名:ラストセクションの名前が変更されない場合は1、される場合は0
- 再配置のストライプ化:コード再配置がストライプ化される場合は1、されない場合は0
以下でデモ版の動作を確認することができます(なお、問題解決のためにNOPパッチが加えられています):
図21:1つ目のパッチ
パッチを加えた今、仮想オペコード難読化と仮想マシンの複雑さが通常のみとなる制限が破壊されています。他にもアプリケーションごとに単一のマクロしか適用できない制限へのパッチが残っています。コードのもう1つの重要な部分は以下のとおりです:
図22:仮想オペコード生成の関数
そして、アドレス00609C82hでNOPを挿入します:
図23:2つ目のパッチ
アプリケーションに複数の保護を許容するためにJEからJMPに変更します:
図24:3つ目のパッチ
NOPのコードブロックは、MessageBox LimitationとMacro単一使用の制限を回避するためにものです:
図25:4つ目のパッチ
アプリケーションごとの仮想マシン数の制限を解除するためにJLEをJMPに変更します:
図26:5つ目のパッチ
おめでとうございます。これでCode Virtualizerのデモ版をカスタム可能となりました。
5. Hopes for the Future and Acknowledgments
5.1. Why write this article?
初めの免責事項で言ったように、本稿の主な目的は、私がCode Virtualizerを分析して得られた知識をあなたに伝えることです。それに加えて、本稿の知識が活用されたツールを作成してもらうことを意図しております。私もツールの作成に取り組み始めておりますが、私はプログラマーではないためそのようなツールを書き込むことができないのではないかと考えています。
そこで私は誰かがこのツールの書き込みに興味を持ってくれることを願っています(ぜひとも私にメールしてください)。私が今までコード化してきたソースコードを提供するなどのお手伝いはできるかと思います。ただし、これは容易な作業ではないことに注意してください。
重要なのが、本稿は非常に概略的な記事であるということです。私は多くの詳細について省略していますし、私が気づかなかったその他の技術も存在するでしょう。何か質問や本稿に誤りを見つけた場合、それから本稿を改善したい場合には私にメールしてください。
そして私の主な希望はThemida Virtual Machineについての同様の記事を参照することです。本稿を読む前であればそれほど難しすぎることはないでしょうし、ThemidaがCode VirtualizerのためにいくつかのDLLを使用していることがその主な理由です。すなわち、Oreans開発者自身が我々にCode VirtulizerがThemida Virtual Machineの少し単純なバージョンであると語っているようなものです(最初にLight VMと呼称したことを思い出してください)。
Oreansの開発者に向けた言葉: これはセンシティブなコードを保護する分野においては非常に優れたツールですが、100%安全というわけではありません(100%安全なものなど存在しない)。私はこの市場においてこれほど優れたツールは他に存在しないと思います。このソフトウェアを改善し、より良い仕事を続けてください!
5.2. The general attack approach
本項ではCode Virtualizerを対処するためのツールについての私の考えを記述します。問題は次の3つに部分に分かれています。
- 前処理
- 分析
- ”fake”仮想オペコードを探してそれらを排除する
- Code Virtualizer命令を取得する
- それらを分析して元のコードを取得する
- 後処理
- 正しいアドレスにオリジナルコードを保存する
- PEヘッダを修正する
- ファイルを保存する
最も難しいのが、仮想マシン内の各ハンドラを特定して分析することと、Code Virtualizer命令のブロックから元のコードを取得することです。
まず始めに、ミューテションエンジンを分析するためにエンジンそのもののリバースエンジニアリング(非常に難しい)を実行するか、または前述したようにミューテーションエンジンは全てのオペコードを変化させることはないため、その変化されていない命令から各ハンドラを探索することもほぼ100%可能です。
次に、Code Virtualizer命令が元の逆アセンブルコードからどのように生成されるかを分析するためにCode Virtualizerのリバースエンジニアリング(難しい)を実行するか、またはいくつかの異なる種類の命令でCode Virtualizerによる難読化を試してそのパターンを参照する方法があります。
ちなみに、ヒントとして、すべてのオリジナルの命令に常に使用されるハンドラ(STORE FLAGS)は非常認識しやすいです。これはオリジナルの命令の数を探す作業がより簡単になります。
また、作成するツールはCode Virtualizerの異なるバージョンをサポートする必要があります。バージョンの変化によってその構造が変化した場合、例えば、新しいハンドラや変更されたハンドラなどにそれぞれ対応する必要があります。
面白い例として、ADD, XOR, SHLなどのコマンドは一般的に3つのハンドラを持っています。1つはBYTE操作のため、1つはWORD操作のため、1つはDWORD操作のためのハンドラとなります。しかし、私が最初にSHL命令のための3つのハンドラを見た時はそれはとても奇妙なものでした:
しかしバージョン1.2.0.0のパッチ項目には"[!] Fixed Virtualization of "SHL reg16, imm"とあります。興味深くありませんか?
5.3. Acknowledgments
本稿を執筆するにあたり、直接および間接的にお世話になった以下の方々に感謝致します:
- Melvill, Portuogral, forgetoz, Spec0p (CRKTeam): 彼らにはリバースエンジニアリングを紹介から始まり多くの助けを頂きました。本稿は特に彼らのために作成されたものです。
- softworm: まあ…なんといったらいいいか…彼の働きなしでは本稿は存在しなかったでしょう。
- Ricardo NarvajaとCrackSLatinoS: 良いチュートリアルをありがとうございます。
- リバースエンジニアリングコミュニティ(私が活動している): CrkPortugal, ARTeam, Unpack.cn, Tuts4you, EXETOOLS
References
- [1] Code Virtualizer Help File - Code Virtualizer Help.chm
- [2] ttp://www.unpack.cn/viewthread.php?tid=5802&fpage= 1&highlight=code%2Bvirtualizer
- [3] OllyDbg v1.10 by Oleh Yuschuk
- [4] https://www.oreans.com/
- [5] ..\Annex\Example of Code Virtualizer instructions.rtf - this file is included in the file Inside Code Virtializer.rar
- [6] ..\Annex\Analysis of Code Virtualizer instructions - this folder is included in the file Inside Code Virtializer.rar
- [7] ..\Annex\handler.exe - this file is included in the file Inside Code Virtializer.rar
- [8] https://www.oreans.com/CodeVirtualizerWhatsNew.php