FinFisherを怜出する方法。 ESETガむド

深刻な分析察策のおかげで、FinFisherスパむりェアはあたり理解されおいたせんでした。 これはよく知られた远跡ツヌルですが、以前のサンプルでは郚分的な分析のみが公開されおいたした。



ESETによるFinFisherサむバヌスパむ掻動の分析埌、2017幎倏に状況は倉わり始めたした。 この調査の過皋で、 むンタヌネットサヌビスプロバむダヌの被害者を䟵害する攻撃を特定したした。







Malvariの分析を開始したずき、WindowsバヌゞョンでのFinFisherの分析に察抗するための察策を克服するこずに䞻な努力が費やされたした。 高床な難読化ず独自の仮想化の組み合わせにより、FinFisherマスキングプロセスは非垞に困難になりたす。



このガむドでは、FinFisher分析プロセスで孊んだこずを共有したす。 FinFisher仮想マシンの分析に関するヒントに加えお、このガむドは、仮想マシン党䜓、぀たりバむナリコヌドで怜出され、゜フトりェアの保護に䜿甚される独自の仮想マシンを䜿甚した保護を理解するのに圹立ちたす。



たた、FinFisher for Androidのバヌゞョンも分析したした。その保護メカニズムは、オヌプンアクセスLLVM難読化ツヌルに基づいおいたす。 Windowsのバヌゞョンメカニズムほど耇雑でおもしろくないので、このガむドでは説明したせん。



このガむドが、情報セキュリティの研究者やりむルス分析者がFinFisherのツヌルず戊術を理解し、この脅嚁から顧客を保護するのに圹立぀こずを願っおいたす。



分解察策



IDA ProでFinFisherサンプルを開くず、メむン機胜に、分解ですが、最初の防埡策に察抗するためのシンプルだが効果的な方法がありたす。



FinFisherは、逆アセンブリに察しお䞀般的な手法を䜿甚したす。1぀の無条件ゞャンプ呜什を2぀の補完的な条件付き遷移に眮き換えるこずにより、実行の進行を隠したす。 これらは同じ遷移ポむントを指しおいるため、実行される遷移に関係なく、コヌドの実行順序は倉わりたせん。 条件付きゞャンプの埌は、意味のないコヌドバむトになりたす。 通垞の条件䞋では、非䜜業領域を認識できず、このゎミの分解䜜業を継続するため、逆アセンブラヌを混乱させるように蚭蚈されおいたす。



特に、このマルりェアはこの手法を䜿甚したす。 私たちが調査したほずんどのマルりェアは、この方法を特定の回数䜿甚しおいたす。 ただし、FinFisherは各コマンドの埌にこのトリックを適甚したす。



この保護は逆アセンブラに察しお非垞に効果的であり、倚くのコヌドが適切にプロセスを通過しないように混乱させたす。 そしお、もちろん、IDA Proのグラフィックモヌドは䜿甚できなくなりたす。 最初のタスクは、この保護を取り陀くこずです。

コヌドは明らかに手動では難読化されおいたせんが、自動ツヌルの助けを借りお、移行コマンドのすべおのペアで特定のパタヌンを芳察しおいたす。



ゞャンプペアには2぀の異なるタむプがありたす。むンデントが32ビットの内郚ゞャンプずむンデントが8ビットの短い遷移です。



䞡方の条件付き内郚遷移DWORDは遷移むンデントのオペレヌションコヌドは、バむト0x0Fで始たり、2番目のバむトは0x8ですか 䞡方の遷移呜什で、それらは1ビットだけ異なりたす。 これは、盞補的な遷移のx86 OSオペレヌションコヌドが数倀的に連続しおいるずいう事実によるものです。 たずえば、この難読化スキヌムは、垞にJEずJNEオペコヌド0x0F 0x84および0x0F 0x85、JPずJNPオペコヌド0x0F 0x8Aおよび0x0F 0x8Bなどをペアにしたす。



これらのオペコヌドの埌に​​、遷移が行われるむンデントを定矩する32ビット匕数がありたす。 䞡方の呜什のサむズは6バむトであるため、2぀の連続した遷移のむンデントは正確に6異なりたす図1を参照。





図1.毎回2぀の条件付き内郚遷移が続くコマンドのスクリヌンショット



たずえば、次のコヌドを䜿甚しお、これら2぀の連続した遷移を怜出できたす。



def is_jump_near_pair(addr): jcc1 = Byte(addr+1) jcc2 = Byte(addr+7) #      ? if Byte(addr) != 0x0F || Byte(addr+6) != 0x0F: return False #   2    ? if (jcc1 & 0xF0 != 0x80) || (jcc2 & 0xF0 != 0x80): return False #     ? if abs(jcc1-jcc2) != 1: return False #        ? dst1 = Dword(addr+2) dst2 = Dword(addr+8) if dst1-dst2 != 6 return False return True
      
      





短い遷移の難読化解陀は同様の考え方に基づいおおり、定数のみが異なりたす。



短い条件分岐のオペコヌドは0x7で、その埌に1バむト遷移のむンデントが続きたす。 したがっお、再び2぀の連続した条件付き内郚遷移を探しおおり、オペレヌションコヌド0x7;が必芁です。 むンデント; 0x7 ±1; むンデント-2。 最初のオペコヌドの埌、1぀のバむトがあり、2぀の連続した遷移で2だけ異なりたすこれも䞡方の呜什のサむズです図2。





図2.毎回2぀の短い条件付き遷移が続くコマンドの䟋



たずえば、このコヌドを䜿甚しお、2぀の条件付きショヌトゞャンプを怜出できたす。



 def is_jcc8(b): return b&0xF0 == 0x70 def is_jump_short_pair(addr): jcc1 = Byte(addr) jcc2 = Byte(addr+2) if not is_jcc8(jcc1) || not is_jcc8(jcc2): return False if abs(jcc2–jcc1) != 1: return False dst1 = Byte(addr+1) dst2 = Byte(addr+3) if dst1 – dst2 != 2: return False return True
      
      





これらの条件分岐のペアの1぀を芋぀けた埌、パッチを䜿甚しおコヌドの難読化を解陀し、最初の条件分岐を無条件にし内郚遷移ペアに0xE9オペコヌドを䜿甚し、ショヌトゞャンプペアに0xEBを䜿甚、残りのバむトを空の呜什0x90で埋めたす



 def patch_jcc32(addr): PatchByte(addr, 0x90) PatchByte(addr+1, 0xE9) PatchWord(addr+6, 0x9090) PatchDword(addr+8, 0x90909090) def patch_jcc8(addr): PatchByte(addr, 0xEB) PatchWord(addr+2, 0x9090)
      
      





これら2぀の状況に加えお、遷移のペアが短いタむプず内郚タむプ、぀たり異なるタむプで構成される堎合がありたす。 しかし、これは少数の堎所でしか芋぀からないため、FinFisherサンプルではこれを手動で修正できたす。



これらの挿入物を䜿甚しお、IDA Proは新しいコヌドを「理解」し始め、チャヌトを䜜成する準備ができおいたすたあ、たたはほが準備ができおいたす。 終了点を远加する、぀たりノヌドに遷移䜍眮を割り圓お、チャヌト䞊で遷移コマンドず䞀臎するように、さらに改善する必芁がある堎合がありたす。 このために、Python IDA関数append_func_tail



䜿甚できたす。



逆アセンブラの通垞の動䜜を劚げるトリックを回避する最埌の手順は、関数定矩を修正するこずです。 移行埌のコマンドがpush ebp



である堎合がありたす。この堎合、IDA Proは誀っおこれを関数の開始ず芋なし、それに応じお新しい定矩を開始したす。 この堎合、関数定矩を削陀し、正しい゚ントリを䜜成し、さらに末尟を远加する必芁がありたす。

このようにしお、分解に察する最初のFinFisher保護を削陀したす。



FinFisher仮想マシン



第1レベルの保護の難読化解陀に成功するず、メむン機胜が開きたす。その䞻な目的は、特別に䜜成された仮想マシンを起動し、さらにペむロヌドを䜿甚しおバむトコヌドを解釈するこずです。



通垞の実行可胜ファむルずは異なり、内郚に仮想マシンを持぀実行可胜ファむルは、プロセッサ呜什を盎接実行する代わりに、仮想化された呜什セットを䜿甚したす。 仮想化されたコマンドは、独自の構造を持ち、バむトコヌドをアンマネヌゞマシンコヌドに倉換しない仮想プロセッサによっお実行されたす。 この仮想プロセッサは、バむトコヌドおよび仮想呜什ず同様に、仮想マシンをプログラムする人によっお決定されたす図3。



はじめに、仮想マシンのよく知られた䟋の1぀がJava VMであるず述べたした。 ただし、この堎合、仮想マシンはバむナリコヌド内にあるため、ここではリバヌス゚ンゞニアリングから保護するための仮想マシンに盎面しおいたす。 VMProtectやCode Virtualizerなどの有名な商甚仮想マシン保護がありたす。



FinFisherスパむりェアは゜ヌスからコンパむルされ、生成されたバむナリはアセンブリレベルの仮想マシンによっお保護されたす。 保護プロセスでは、゜ヌスバむナリファむルの呜什を仮想呜什に倉換し、バむトコヌドず仮想プロセッサを含む新しいバむナリファむルを䜜成したす。 ゜ヌスバむナリからのネむティブ呜什は倱われたす。 保護された仮想化されたサンプルは、保護されおいないサンプルず同じ動䜜をする必芁がありたす。



仮想マシンで保護されたバむナリファむルを分析するには、次のものが必芁です。



  1. 仮想CPUの分析
  2. この非暙準の仮想CPU甚に独自の逆アセンブラヌを䜜成し、バむトコヌドを解析したす
  3. オプション逆アセンブルされたコヌドをバむナリファむルにコンパむルし、仮想マシンを取り陀きたす。


最初の2぀のタスクには倚くの時間がかかり、最初のタスクは非垞に耇雑になる可胜性がありたす。 各vm_handlerハンドラヌの分析ず、レゞスタヌ、メモリヌアクセス、呌び出しなどの転送方法の理解が含たれたす。





図3.仮想CPUによっお解釈されるバむトコヌド



甚語ず定矩



仮想マシンの個々の郚分を定矩するための暙準はありたせん。 したがっお、このホワむトペヌパヌで参照するいく぀かの甚語を定矩したす。





バむトコヌドを解釈するプロセスで、仮想マシンは仮想スタックず単䞀の仮想レゞスタを䜿甚したす。





以䞋のセクションでは、仮想マシンの芁玠を技術的な詳现で説明し、それらを分析する方法を説明したす。



Malvariのメむン関数の難読化されたグラフィカル衚珟は、初期化ずvm_startおよびむンタヌプリタヌ vm_dispatcher + vm_handlers ず呌ばれる他の2぀の3぀の郚分で構成されおいたす。



初期化コンポヌネントは、バむトコヌドの゚ントリポむントずしお解釈できるものに䞀意の識別子を蚭定し、スタックにプッシュしたす。 次に、 vm_start郚分ぞの移行、぀たり仮想マシン自䜓の初期化プロセスが行われたす。 バむトコヌドが埩号化され、制埡がvm_dispatcherに枡されたす。vm_dispatcherは、バむトコヌドの仮想呜什サむクルを開始し、 vm_handlersを䜿甚しおそれらを解釈したす。



Vm_dispatcherはpushaコマンドで始たり、 jmp dword ptr [eax+ecx*4]



コマンドたたは同様、぀たり察応するvm_handlerぞの移行で終わりたす。



Vm_start



第1レベルの難読化解陀埌に䜜成されたグラフィックモデルを図4に瀺したす。vm_startに関連する郚分は、むンタヌプリタヌの分析にずっおそれほど重芁ではありたせん。 ただし、仮想マシン党䜓の実装、仮想フラグ、仮想スタックなどの䜿甚方法ず管理方法を理解するず圹立ちたす。 2番目の郚分であるvm_disperscherずvm_handlersは、基盀です。





図4. vm_startおよびvm_dispatcherのグラフィカル衚珟



vm_startの呌び出しは 、メむンを含むほがすべおの関数から行われたす。



呌び出し関数は垞に仮想識別子をプッシュしおから、 vm_startに移行したす 。 各仮想チヌムには独自の仮想識別子がありたす。 この䟋では、メむン関数からの実行が開始される仮想゚ントリポむントの識別子は0x21CD0554です図5。





図5. vm_startは、119個の仮想化された関数のそれぞれから呌び出されたす。



察応する関数の最初の仮想コマンドの識別子が匕数ずしお䞎えられたす。



この郚分では、ほずんどのコヌドはvm_dispatcherの準備に専念しおいたす。䞻にバむトコヌドずむンタヌプリタヌ党䜓のメモリの割り圓おです。 最も重芁なコヌドは次のこずを行いたす。



  1. バむトコヌドずいく぀かの倉数の割り圓お。読み取り、曞き蟌み、実行の蚱可を持぀1 MBのメモリ。
  2. 珟圚のタスクチェヌンの仮想マシンのロヌカル倉数に察しお同じ解像床で0x10000バむトが割り圓おられおいるのはvm_stackです。
  3. XOR埩号化排他的OR。 埩号化されたコヌドはaPLibずしお解凍されたす。 サンプルの埩号化プロセスでは、XOR dwordキヌのわずかに倉曎されたバヌゞョンを䜿甚しおいたす。 最初の6぀のDWORDをスキップし、キヌを䜿甚しお残りの5぀のDWORDにXORを適甚したす。 以䞋はプロセスアルゎリズムです以降、XOR埩号化コヌドず呌びたす



      int array[6]; int key; for (i = 1; i < 6; i++) { array[i] ^= key; }
          
          



  4. バむトコヌドを解凍するaPLibプロセスを呌び出したす。 その埌、仮想オペコヌドは暗号化されたたたになりたす図6。


仮想オペコヌドの準備ステップ1、3、4 は最初に䞀床だけ実行され、以降のvm_startの実行ではスキップされたすが、フラグずレゞスタを䜿甚するコマンドのみが実行されたす。





図6. vm_startからvm_dispatcherたでのすべおのコヌドはグルヌプ化され、宛先に応じお名前が付けられたす。



FinFisher通蚳



この郚分には、すべおのvm_handlers FinFisherサンプルでは34を含むvm_dispatcherが含たれ、仮想マシンを分析および/たたは仮想化するための重芁な芁玠です。 むンタヌプリタヌはバむトコヌドを実行したす。



jmp dword ptr [eax+ecx*4]



コマンドは、34 個のvm_handlersのいずれかにゞャンプしたす。 各vm_handlerは、1぀の仮想マシンコマンドを実装したす。 vm_handlerのそれぞれの機胜を理解するには 、 vm_contextずvm_dispatcherを凊理する必芁がありたす。



1. IDAでグラフィカル構造を䜜成する



適切に構造化されたチャヌトを䜜成するず、むンタヌプリタヌをよりよく理解できたす。 vm_startずvm_dispatcherの 2぀の郚分に分けるこずをお勧めしたす。 ぀たり 、最初のvm_dispatcherコマンドで関数の開始を決定する必芁がありたす。 この堎合、 vm_dispatcherによっお参照されるvm_handlers自䜓はただありたせん。 これらのハンドラヌをvm_dispatcher図に接続するには、次の関数を䜿甚できたす。



 AddCodeXref(addr_of_jmp_instr, vm_handler,XREF_USER|fl_JN)
      
      





最埌のvm_dispatcherコマンドの vm_handlersリンクの先頭に远加



 AppendFchunk
      
      





最埌に远加を生成したす



各vm_handlerハンドラヌをディスパッチャヌ関数に远加するず、図は次の図7に瀺すようになりたす。



2. Vm_dispatcher



この郚分は、バむトコヌドの凊理ずデコヌドを担圓したす。 圌女は次の手順を実行したす。





コマンドのvm_handlerが実行された埌、最初のvm_dispatcherコマンドから開始しお、同じシヌケンスが埌続のシヌケンスに察しお繰り返されたす。 vm_callハンドラヌの堎合、制埡はvm_startに枡されたす 非仮想化関数が埌に続く堎合を陀く 。





図7. 34個すべおのvm_handlersを含むvm_dispatcherダむアグラム。



3. Vm_context



このパヌトでは、 vm_context - vm_dispatcherおよび各vm_handlerの実行に必芁なすべおの情報を含む仮想マシンが䜿甚する構造に぀いお説明したす。



vm_dispatcherおよびvm_handlersコヌドの詳现な調査では、 ebx+offset



を参照するデヌタ凊理コマンドが含たれおいるこずがわかりたす。ここで、offsetは0x00〜0x50の数字です。 図8では、FinFisherサンプルの1぀でvm_handler 0x05の䞻芁郚分がどのように芋えるかを確認できたす。





図8. vm_handlersの1぀のスクリヌンショット



ebxレゞスタは、 vm_contextず呌ばれる構造䜓を指したす。 この構造がどのように䜿甚されるのか、そのメンバヌが䜕であるのか、それらが䜕を意味するのか、どのように適甚されるのかを理解する必芁がありたす。 この問題を初めお解決するには、 vm_contextずその郚分の䜿甚方法を掚枬する必芁がありたす。



䟋ずしお、 vm_dispatcherの最埌にある䞀連のコマンドを芋おみたしょう。



 movzx ecx, byte ptr [ebx+0x3C] //   vm_handler jmp dword ptr [eax+ecx*4] //     34 vm_handlers
      
      





最埌のコマンドはvm_handlerぞの移行であるこずがわかっおいるため、ecxには仮想オペコヌドが含たれおおり、したがっお0x3C vm_struct芁玠は仮想オペコヌドを参照しおいるず結論付けるこずができたす。



もう1぀の根拠のない仮定をしおみたしょう。 ほずんどすべおのvm_handlerの最埌に、次のコマンドがありたす。



 add dword ptr [ebx], 0x18.
      
      





同じvm_context芁玠がvm_dispatcherコヌドで以前に䜿甚されおいたした-vm_handlerに移行する盎前 。 vm_dispatcherは、構造芁玠から別の堎所 [ebx+38h]



に24バむトをコピヌし、珟圚のバむトコヌドの䞀郚を取埗するためにXORを䜿甚しおそれを解読したす。



぀たり、 vm_context  [ebx+0h]



芁玠をvm_instruction_pointerポむンタヌずしお、埩号化された䜍眮 [ebx+38h]



から[ebx+50h]



を仮想コマンドのID、その仮想オペコヌド、および匕数ず芋なし始めるこずができたす。 この構造党䜓にvi_paramsずいう名前を付けたした。



䞊蚘の手順を完了し、デバッグプログラムを䜿甚するず、構造の察応する芁玠に含たれる倀がわかりたす。 ぀たり 、すべおのvm_context芁玠を定矩できたす。



分析埌、FinFisherの vm_contextずvi_paramsの䞡方の構造を埩元できたす。



 struct vm_context { DWORD vm_instruct_ptr; //     DWORD vm_stack; //  vm_stack DWORD tmp_REG; //   “”    DWORD vm_dispatcher_loop; //  vm_dispatcher DWORD cleanAndVMDispatchFn; //  ,       vm_dispatcher,     DWORD cleanUpDynamicCodeFn; // ,  vm_instr_ptr   cleanAndVMDispatchFn DWORD jmpLoc1; //     DWORD jmpLoc2; //   vm_opcode –    vm_instruction DWORD Bytecode_start; //       DWORD DispatchEBP; DWORD ImageBase; //     DWORDESP0_flags;//  (  vm_code) DWORDESP1_flags;//  DWORD LoadVOpcodesSectionFn; vi_params bytecode; //     vm_handler,  DWORD limitForTopOfStack; //   }; struct vi_params { DWORD Virtual_instr_id; DWORD OpCode; // values 0 – 33 -> ,    DWORD Arg0; // 4  dword  vm_handler DWORD Arg4; //    DWORD Arg8; //    DWORD ArgC; //    };
      
      





4.仮想コマンドの䜿甚-vm_handlers



vm_handlerのそれぞれは、1぀の仮想オペコヌド、぀たり34のvm_handlersハンドラヌごずに最倧34の仮想オペコヌドで動䜜したす。 1぀のvm_handlerの実行は1぀のvm_instructionの実行を意味するため、 vm_instructionが䜕を行うかを刀断するには、察応するvm_handlerを分析する必芁がありたす。



vm_contextを再構築し、ebxのすべおのむンデントに名前を付けるず、前述のvm_handlerが読みやすくなりたす図9を参照。

この関数の最埌に、 vm_instruction_pointerで始たるコマンドのシヌケンスがありたす。これは24ず぀増加したす。 ぀たり 、各vm_instructionのvi_params構造䜓のサむズによっお増加したす。 このシヌケンスはほがすべおのvm_handlerの最埌で繰り返されるため、これは暙準の終了関数コヌドであり、 vm_handler自䜓の本䜓は次のように簡単に蚘述できるず結論付けおいたす。



 mov [tmp_REG], Arg0
      
      





したがっお、この仮想マシンの最初のコマンドを分析したした:-)





図9. vm_context構造に挿入した埌の以前のvm_handler



分析されたコマンドの操䜜を説明するために、vi_params構造䜓が次のように蚭定されおいるず仮定したす。



 struct vi_params { DWORD ID_of_virt_instr =  ,   ; DWORD OpCode = 0x0C; DWORD Arg0 = 0x42; DWORD Arg4 = 0; DWORD Arg8 = 0; DWORD ArgC = 0; };
      
      





䞊蚘から、次のコマンドが実行されおいるこずがわかりたす。



 mov [tmp_REG], 0x42
      
      





この時点で、 vm_instructionsの 1぀が䜕をするかをすでに理解しおいるはずです。 完璧なステップは、通蚳者党䜓の仕事のデモンストレヌションずしお圹立぀はずです。



ただし、 解析がより困難なvm_handlersがいく぀かありたす。 この仮想マシンの条件付き遷移は、フラグの倉換がどのように発生するかを理解するのがより困難です。



前述のように、 vm_dispatcherは、ネむティブEFLAGS vm_codeからを独自のスタックの先頭に眮くこずから始めたす。 したがっお、察応する遷移のハンドラヌは、それを行うかどうかを決定するずきに、独自のスタック内のEFLAGSをチェックし、独自の遷移メ゜ッドを適甚したす。 図10は、パリティチェックを介しお仮想JNPパリティがない堎合はゞャンプハンドラがどのように適甚されるかを瀺しおいたす。





図10. JNP_handlerのスクリヌンショット



他の仮想条件付き遷移の堎合、いく぀かの兆候を確認する必芁がある堎合がありたす-たずえば、仮想化されたJBE以䞋の堎合はゞャンプの遷移の結果は、キャリヌフラグずれロフラグの䞡方の倀に䟝存したすが、原理は同じです。



FinFisher仮想マシンの34 個のvm_handlersをすべお分析した埌、その仮想コマンドを次のように蚘述できたす。





図11. 34個すべおのvm_handlersを持぀vm_table



キヌワヌド「 tmp_REG 」は、仮想マシンが䜿甚する仮想レゞスタ、 vm_contex t構造内の䞀時レゞスタを指し、「 reg 」はプロセッサ自身のレゞスタ、぀たり eax。



分析された仮想マシンのコマンドを芋おみたしょう。 たずえば、 case_3_vm_jccは、条件付きたたは無条件の任意のプロセッサゞャンプ呜什を実行できる汎甚ゞャンプ呜什ハンドラです。



明らかに、この仮想マシンはすべおのハヌドりェアコマンドを仮想化するわけではありたせん。ここで、䞊蚘のリストケヌス4およびケヌス6のコマンドが圹立ちたす。



これら2぀のvm_handlerは、コヌドを盎接実行するために䜿甚されたす。 それらが行うこずは、匕数ずしおプロセッサコマンドオペコヌドを読み取り、コマンドを実行するこずです。



たた、 vm_registersは垞に自身のスタックの最䞊䜍にあり、実行可胜レゞスタの識別子は仮想コマンドのarg0の最埌のバむトに栌玍されるこずに泚意しおください 。 察応する仮想レゞスタにアクセスするには、次のコヌドを䜿甚できたす。



 def resolve_reg(reg_pos): stack_regs = ['eax', 'ecx', 'edx', 'ebx', 'esp', 'ebp', 'esi', 'edi'] stack_regs.reverse() return stack_regs[reg_pos] reg_pos = 7 – (state[arg0] & 0x000000FF) reg = resolve_reg(reg_pos)
      
      





5.独自の逆アセンブラヌの䜜成



すべおのvm_instructionsを正しく分析した埌、サンプル分析を開始する前に実行する必芁があるもう1぀のステップが残っおいたす-バむトコヌド甚に独自の逆アセンブラヌを䜜成する必芁がありたす手動で解析するずサむズが問題になりたす。



努力し、より信頌性の高い逆アセンブラヌを䜜成するこずで、埌でFinFisher仮想マシンが倉曎および曎新されたずきに自分自身を救いたす。次のコマンドを実行するvm_handler 0x0Cから

始めたしょう。



 mov [tmp_REG], reg
      
      





このコマンドは匕数を1぀だけ取りたす-regずしお䜿甚される独自のレゞスタの識別子。この識別子はresolve_reg



、たずえば䞊の䟋のコマンドを䜿甚しお、独自のレゞスタの名前で衚瀺する必芁がありたす。



このvm_handlerを逆アセンブルするには、次のコヌドを䜿甚できたす。



 def vm_0C(state, vi_params): global instr reg_pos = 7 – (vi_arams[arg0] & 0x000000FF) tmpinstr = “mov [tmp_REG], %s” % resolve_reg(reg_pos) instr.append(tmpinstr) return
      
      





繰り返したすが、移行甚のvm_handlersは理解するのが困難です。遷移の堎合、vm_context.vi_params.Arg0およびvm_contextコンポヌネント。vi_params.Arg1は、遷移が発生するむンデントを栌玍したす。この「ゞャンプむンデント」は、実際にはバむトコヌドでむンデントされおいたす。遷移ハンドラを解析するには、遷移領域のマヌカヌを再配眮する必芁がありたす。次のコヌドが適しおいたす。



 def computeLoc1(pos, vi_params): global instr jmp_offset = (vi_params[arg0] & 0x00FFFFFF) + (vi_params[arg1] & 0xFF000000) if jmp_offset < 0x7FFFFFFF: jmp_offset /= 0x18 # their increment by 0x18 is my increment by 1 else: jmp_offset = int((- 0x100000000 + jmp_offset) / 0x18) return pos+jmp_offset
      
      





最埌に、特別な凊理が必芁な匕数を䜿甚しおネむティブコマンドを実行するvm_handlerがありたす。これを行うには、たずえばオヌプンアクセスのDistormツヌルなど、独自のx86コマンド甚の逆アセンブラヌが必芁です。



コマンドの長さはvm_context.vi_params.OpCode0x0000FF00に保存されたす。実行甚のネむティブコマンドのオペコヌドは、匕数に栌玍されたす。ネむティブコヌドを実行するvm_handlerを解析するには、次のコヌドを䜿甚できたす。



 def vm_04(vi_params, pos): global instr nBytes = vi_params[opCode] & 0x0000FF00 dyn_instr = pack(“<LLLL”, vi_params[arg0], vi_params[arg4], vi_params[arg8], vi_params[argC])[0:nBytes] dec_instr = distorm3.Decode(0x0, dyn_instr, distorm3.Decode32Bits) tmpinstr = “%s” % (dec_instr[0][2]) instr.append(tmpinstr) return
      
      





この時点で、各vm_handlersを解析するためのすべおの関数をPythonで䜜成したした。トランゞション甚の領域をマヌクするコヌド、呌び出し埌の仮想コマンドのIDを決定するコヌドなど、それらすべおは、独自の逆アセンブラを䜜成するために必芁です。



このすべおの埌、バむトコヌドによっお駆動できたす。





図12.解凍および埩号化されたFinFisherバむトコヌドの䞀郚



たずえば、図12に瀺すバむトコヌドから、次の出力を取埗できたす。



 mov tmp_REG, 0 add tmp_REG, EBP add tmp_REG, 0x10 mov tmp_REG, [tmp_REG] push tmp_REG mov tmp_REG, EAX push tmp_REG
      
      





6.この仮想マシンの䜿甚を理解する



すべおの仮想ハンドラヌを分析し、独自のカスタム逆アセンブラヌを構築した埌、仮想コマンドを再床芋お、䜜成の基本的な考え方を理解できたす。



最初に、仮想化保護がアセンブラレベルで適甚されおいるこずを理解する必芁がありたす。䜜成者は、独自のチヌムを独自のやや耇雑なチヌムに倉換し、特別な仮想CPUで実行したす。このために、䞀時的な「レゞスタ」tmp_REGが䜿甚されたす。



いく぀かの䟋を芋お、この倉換がどのように機胜するかを理解できたす。前の䟋から仮想コマンドを取りたす-



 mov tmp_REG, EAX push tmp_REG
      
      





-圌女は自分のチヌムから倉身したしたpush eax



。仮想化を䜿甚する堎合、タむムステップの䞀時レゞスタを䜿甚しお、コマンドをより耇雑なものに倉曎したす。



別の䟋を挙げたす。



 mov tmp_REG, 0 add tmp_REG, EBP add tmp_REG, 0x10 mov tmp_REG, [tmp_REG] push tmp_REG
      
      





これらの仮想化コマンドに倉換されたネむティブコマンドを以䞋に瀺したすregは独自のレゞスタの1぀です。



 mov reg, [ebp+0x10] push reg
      
      





ただし、䞀連のコマンドを仮想化する方法はこれだけではありたせん。他のアプロヌチでは、保護のための仮想マシンの他の甚途がありたす。たずえば、1぀ではなく耇数の䞀時レゞスタを䜿甚しお、NOR数孊ロゞック䞡方の入力が負の堎合を䜿甚する仮想マシンによる保護の商甚実装がありたす。



順番に、FinFisherはここたで行かず、すべおの自瀟チヌムを転換したせんでした。以䞋のような数孊的なコマンド、 -それらの倚くは、仮想化されおいたすが、いく぀かはそうではないかもしれないadd



、imul



ずdiv



。これらのコマンドが元のバむナリである堎合、vm_handler、保護されたファむルで凊理するために、独自のコマンドの実行を担圓したす。発生する唯䞀の倉曎は、EFLAGSずその独自のレゞスタが独自のコマンドの実行盎前に取埗され、実行の完了埌に削陀されるこずです。これにより、各チヌムの仮想化を回避できたす。



バむナリを仮想マシンで保護するこずの重倧な欠点は、パフォヌマンスぞの悪圱響です。 FinFisher仮想マシンの堎合、各vm_instructionvm_dispatcher + vm_handlerを凊理するために実行されるコマンドの数をカりントするこずに基づいお、内郚コヌドの堎合よりも速床が玄100倍遅いず抂算したす



したがっお、バむナリの遞択された郚分のみを保護するこずは理にかなっおいたす。これは、分析したFinFisherサンプルでそれらが行うこずずたったく同じです。



さらに、既に述べたように、䞀郚の仮想マシンハンドラヌは独自の関数を盎接呌び出すこずができたす。その結果、仮想マシン保護のナヌザヌ぀たり、FinFisherの䜜成者は、アセンブラヌ段階で、その助けを借りお保護する機胜を決定できたす。マヌクされた機胜に぀いおは、チヌムは仮想化されたす。残りに぀いおは、元の関数は察応する仮想ハンドラヌによっお呌び出されたす。したがっお、バむナリファむルの最も興味深い郚分は保護されたたたですが、コヌドの実行にかかる時間は短くなりたす図13。





13. , FinFisher, ,



7. FinFisher



パヌサヌが凊理する必芁のあるバむトコヌド長に加えお、FinFisherのサンプルには䜕らかのミキシングがあるこずを芚えおおく必芁がありたす。保護には同じ仮想マシンが䜿甚されたすが、仮想オペコヌドずvm_handlers間の察応の定矩は垞に䞀臎するずは限りたせん。それらはランダムにペアにできたすそしお、これが起こりたす。これらのペアは、分析するFinFisherサンプルごずに異なりたす。぀たり、このサンプルの0x5仮想オペコヌドのvm_handlerはコマンドを凊理し、mov [tmp_REG], arg0



別の保護されたサンプルでは別の仮想オペコヌドに割り圓おるこずができたす。



この問題を解決するために、分析されたvm_handlersのそれぞれに眲名を䜿甚できたす。付録AのPython IDAのスクリプトは、図7からダむアグラムを取埗した埌に適甚できたすこのマニュアルの最初のセクションで説明したように、jz / jnz遷移の難読化を削陀するこずが特に重芁です。マむナヌな改善により、FinFisherの曎新バヌゞョンでvm_handlersが倉曎された堎合、このスクリプトを䜿甚しお眲名を埩元するこずもできたす。



前述のように、FinFisherサンプルの最初のvm_handlerは、分析䞭に遭遇するJLずは異なる堎合がありたすサンプルの結果になりたしたが、スクリプトはすべおのvm_handlersを正しい方法で決定したす。



8.仮想マシンを䜿甚しない逆アセンブルコヌドのコンパむル



分解しおいく぀かの倉曎を加えた埌、コヌドをコンパむルできたす。私たちは、仮想チヌムを独自のものずしお䜿甚したす。その結果、保護されおいない玔粋なバむナリコヌドが取埗されたす。vm_instructions



コマンドのほずんどは、単玔なコピヌでコンパむルできたす。これは、逆アセンブラの出力では、基本的にコマンドがネむティブコマンドのように芋えるためです。ただし、䞀郚のセクションは特別な方法で倉曎する必芁がありたす。•tmp_REG-tmp_REGをグロヌバル倉数ずしお定矩したため、栌玍されおいるアドレスが逆参照されおいる堎合はコヌドを倉曎する必芁がありたす。x86コマンドセットでは、グロヌバル倉数のアドレスを逆参照するこずはできたせん。たずえば、仮想マシンには仮想コマンドが含たれたす



mov tmp_REG, [tmp_REG]



次のように曞き換える必芁がありたす。



 push eax mov eax, tmp_REG mov eax, [eax] mov tmp_REG, eax pop eax
      
      





•フラグ-仮想チヌムはフラグを倉曎したせんが、独自の数孊チヌムが倉曎したす。したがっお、仮想化されたバむナリの仮想数孊呜什でもこれを行わないこずが重芁です。぀たり、呜什を実行する前にフラグを保存し、実行の完了埌にフラグを埩元する必芁がありたす。



•遷移ず呌び出し-マヌカヌを仮想コマンド遷移たたは関数呌び出しの領域に移動する必芁がありたす。



•API関数呌び出し-ほずんどの堎合、動的にロヌドされたすが、それ以倖の堎合は、バむナリファむルのIATアドレスむンポヌトテヌブルからアクセスされるため、それに応じお凊理する必芁がありたす。



•グロヌバル倉数、ネむティブコヌド-䞀郚のグロヌバル倉数は、仮想化されたバむナリに保存する必芁がありたす。たた、FinFisherドロッパヌには、x64ずx86を切り替える機胜があり、これはプロセッサヌモヌドで実行されたす実際、これはコマンドでのみ行われretf



たす。これらはすべお、コンパむル䞭に保持する必芁がありたす。



結果によっおは、逆アセンブラの出力で、コンパむル可胜な玔粋に独自のコマンドを取埗するために、さらにいく぀かの倉曎を行う必芁がある堎合がありたす。次に、奜みのコンパむラを䜿甚しお、仮想マシンなしでバむナリにコヌドをコンパむルする必芁がありたす。



おわりに



このガむドでは、FinFisherが2぀の方法を䜿甚しお䞻芁な成果物を保護する方法に぀いお説明したした。保護の目的は、りむルス察策の怜出に察抗するこずではなく、リバヌス゚ンゞニアリングの問題を䜜成するこずにより、スパむりェアで䜿甚される構成ファむルず新しい手法を隠すこずです。難読化されたスパむりェアFinFisherの他の詳现な分析はこれたで公開されおいないため、これたでこの保護メカニズムの開発者のタスクは正垞に完了したず芋なすこずができたした。



分解に察する保護レベルが自動化された方法によっおどのように克服できるか、仮想マシンを効果的に分析する方法を瀺したした。



このガむドが、FinFisherで保護された仮想マシンのサンプルを分析するリバヌス゚ンゞニアリングの専門家を助け、仮想マシン党䜓を䜿甚した保護の詳现をよりよく理解するこずを願っおいたす。



付録A





FinFisherでvm_handlersハンドラヌに名前を付けるためのPython IDAスクリプト



このスクリプトは、GitHubのESETリポゞトリでも利甚できたす。



  import sys SIGS={'8d4b408b432c8b0a90800f95c2a980000f95c03ac275ff631c':'case_0_JL _loc1','8d4b408b432c8b0a9400074ff631c':'case_1_JNP_loc1','8d4b408b432c 8b0a94000075a90800f95c2a980000f95c03ac275ff631c':'case_2_JLE_loc1','8d4 b408b7b508b432c83e02f8dbc38311812b5c787cfe7ed4ae92f8b066c787d3e7e 4af9b8e80000588d80' : 'case_3_vm_jcc', '8b7b508b432c83e02f3f85766c77ac6668 137316783c728d7340fb64b3df3a4c67e98037818b43c89471c64756c80775af83318588b6 32c' : 'case_4_exec_native_code', '8d4b408b98b438898833188b43c8b632c' : 'c ase_5_mov_tmp_REGref_arg0', '8b7b508b432c83e02f3f85766c77ac6668137316783c7 28d7340fb64b3df3a4c67e98037818b43c89471c64756c80775af83318588b632c' : 'cas e_6_exec_native_code','8d4b408b432c8b0a94000075ff631c':'case_7_JZ_loc1' , '8d4b408b432c8b0a94000075a90800f95c2a980000f95c03ac275ff6318' : 'case_8_ JG_loc1','8d43408b089438833188b43c8b632c':'case_9_mov_tmp_REG_arg0','3 3c9894b8833188b632c8b43c' : 'case_A_zero_tmp_REG', '8d4b408b432c8b0a980000 75ff631c':'case_B_JS_loc1','8d4b40fb69b870002bc18b4b2c8b548148b4b889118 33188b43c8b632c' : 'case_C_mov_tmp_REGDeref_tmp_REG', '8d4b40fb69b870002bc 18b4b2c8b4481489438833188b43c8b632c' : 'case_D_mov_tmp_REG_tmp_REG', '8d4b 408b432c8b0a9100075ff631c':'case_E_JB_loc1','8d4b408b432c8b0a9100075a94 000075ff631c':'case_F_JBE_loc1','8d4b408b432c8b0a94000074ff631c':'cas e_10_JNZ_loc1','8d4b408b432c8b0a9080074ff631c':'case_11_JNO_loc1','8b7 b50834350308d4b408b414343285766c773f50668137a231c6472c280772aa8d57d83c7389 1783ef3c7477a300080777cb83c7889783ef8c647cf28077c3183c7dc67688b383c0188947 183c7566c7777fe668137176283c72c672d803745895f183c75c67848037df478b4314c674 08037288947183c75c67928037515f8b632c' : 'case_12_vm_call', '8d4b40b870002b 18b532c8b4482489438833188b43c8b632c' : 'case_13_mov_tmp_REG_tmp_REG_notRly ','8d4b408b432c8b0a9400075ff631c':'case_14_JP_loc1','8d4b40fb69b870002 bc18b4b2c8b5388954814833188b43c8b632c' : 'case_15_mov_tmp_REG_tmp_REG', '8 d4b408b432c8b0a9080075ff631c':'case_16_JO_loc1','8d4b408b432c8b0a90800f 95c2a980000f95c03ac274ff631c':'case_17_JGE_loc1','8b4388b089438833188b4 3c8b632c' : 'case_18_deref_tmp_REG', '8d4b408b4388b9d3e089438833188b43c8b6 32c' : 'case_19_shl_tmp_REG_arg0l', '8d4b408b432c8b0a98000074ff631c' : 'ca se_1A_JNS_loc1','8d4b408b432c8b0a9100074ff631c':'case_1B_JNB_loc1','8b 7b2c8b732c83ef4b924000fcf3a4836b2c48b4b2c8b438894124833188b43c8b632c' : 'c ase_1C_push_tmp_REG', '8d4b408b432c8b0a94000075a9100075ff6318' : 'case_1D_ JA_loc1','8d4b40b870002b18b532c8b448241438833188b43c8b632c':'case_1E_ad d_stack_val_to_tmp_REG', '8b7b508343503066c77ac3766813731565783c728d4b40c6 72e803746fb6433d3c783c058947183c758d714fb64b3df3a45ac671280377a8b383c01889 47183c7566c777f306681371fac83c72c671f803777895f183c75c677080372b47c6798037 618b4b14894f183c75c67778037b48b632c8d12' : 'case_1F_vm_jmp', '8d4b408b914b 8833188b43c8b632c' : 'case_20_add_arg0_to_tmp_REG', '8d4b408b98b4388918331 88b632c8b43c' : 'case_21_mov_tmp_REG_to_arg0Dereferenced' } SWITCH = 0 # addr of jmp dword ptr [eax+ecx*4] (jump to vm_handlers) SWITCH_SIZE=34 sig = [] def append_bytes(instr, addr): for j in range(instr.size): sig.append(Byte(addr)) addr += 1 return addr defmakeSigName(sig_name,vm_handler): print “naming %x as %s” % (vm_handler, sig_name) MakeName(vm_handler,sig_name) return if SWITCH == 0: print “First specify address of switch jump - jump to vm_handlers!” sys.exit(1) foriinrange(SWITCH_SIZE): addr = Dword(SWITCH+i*4) faddr = addr sig = [] while 1: instr = DecodeInstruction(addr) if instr.get_canon_mnem() == “jmp” and (Byte(addr) == 0xeb or Byte (addr) == 0xe9): addr = instr.Op1.addr continue if instr.get_canon_mnem() == “jmp” and Byte(addr) == 0xff and Byte (addr+1) == 0x63 and (Byte(addr+2) == 0x18 or Byte(addr+2) == 0x1C): addr = append_bytes(instr, addr) break if instr.get_canon_mnem() == “jmp” and Byte(addr) == 0xff: break if instr.get_canon_mnem() == “jz”: sig.append(Byte(addr)) addr += instr.size continue if instr.get_canon_mnem() == “jnz”: sig.append(Byte(addr)) addr += instr.size continue if instr.get_canon_mnem() == “nop”: addr += 1 continue addr = append_bytes(instr, addr) sig_str = “”.join([hex(l)[2:] for l in sig]) hsig = ''.join(map(chr, sig)).encode(“hex”) for key, value in SIGS.iteritems(): if len(key) > len(sig_str): ifkey.find(sig_str)>=0: makeSigName(value,faddr) else: ifsig_str.find(key)>=0: makeSigName(value,faddr)
      
      






All Articles