悪意のあるコード対策技術の紹介

  • 著者:ZERO-A-ONE
  • 日付:2021-01-20

ビデオ「VulnerabilityBank |悪意のあるコードの対策に関する簡単な説明」から、主に2つの側面から始まります。

  • カウンター分解
  • アンチデバッグ

1.カウンターの分解(静的)

1.1基本概念

  • アセンブリ言語:CPUはマシンコードを実行します。メモリを容易にするために、ニーモニックを使用して、アセンブリ言語のマシン命令のオペコードを置き換えます。
  • 逆アセンブリ:オブジェクトコードをアセンブリコードに変換するプロセス
  • 逆アセンブル防止テクノロジ:プログラムで特別に構造化されたコードまたはデータを使用して、逆アセンブルツールが誤ったアセンブリコードを生成するようにします。

1.2分解アルゴリズム

1.2.1線形分解アルゴリズム

  • 線形スキャンは基本的な逆アセンブルアルゴリズムであり、ファイルのバイナリデータを最初から最後までトラバースし、アセンブリ言語に変換します。アルゴリズムはシンプルで実装が簡単で、分析エラーも発生しやすいです。
  • libdisasm逆アセンブリライブラリを使用して、逆アセンブリをすばやく実現します
    • http://bastard.sourceforge.net/libdisasm.html

サンプルコード:

char buffer[BUF_SIZE];
int position = 0;
while(position < BUF_SIZE){
    
    
    x86_insn_t insn;    //初始化一个结构体
    int size=x86_sisasm(buffer,BUF_SIZE,0,position,&insn);  //填充insn结构体
    if(size != 0){
    
    
        char disassembly_line[1024];
        x86_format_insn(&insn,disassembly_line,1024,intel_syntax);  //接收反汇编的结果
        printf("%s\n",disassembly_line);
        position += size;
    }else{
    
    
        position++;
    }
}
x86_cleanup();

1.2.2線形分解アルゴリズムのフラストレーション

実行可能ファイルには、命令コードに加えて、プログラムの実行に必要なデータも含まれています。コードに不要なデータバイトを挿入すると、逆アセンブラがエラーを解析する可能性があります。
ここに画像の説明を挿入

上の図の左側に示されているように、このコードで簡単に見つけることができます

call near ptr 15FF2A71h

これはメモリ内の非常に大きなアドレスであるため、この場所にジャンプすることはできず、逆アセンブルエラーがあると考えられるという大きな問題があります。右に示すように、正しい分解結果です

1.2.3コードフローの逆アセンブルアルゴリズム

  • コードフロー指向の逆アセンブルは、より高度な逆アセンブルアルゴリズムです。逆アセンブラは、ファイル全体を盲目的に逆アセンブルしません。すべての命令をチェックし、逆アセンブルが必要なアドレスリストを作成します。このアルゴリズムは、IDAなどの商用逆アセンブラで広く使用されています

1.2.42つの分解アルゴリズムの比較

線形逆アセンブラがjmp命令を検出すると、コードロジックに関係なく、バイトシーケンスに従って逆アセンブルします。図に示すように、悪意のあるコード作成者はこの機能を使用して、機密性の高い文字列を非表示にすることができます。
ここに画像の説明を挿入

コードフロー指向の逆アセンブラがjmp命令に遭遇すると、ジャンプ先の位置を計算するため、実行されないコードはデータとして扱われます。
ここに画像の説明を挿入

1.3逆分解防止アルゴリズム

1.3.1コードフロー指向のアルゴリズムの欠陥

悪意のあるコードの実装に対する逆アセンブルテクノロジの主な方法は、逆アセンブラ選択アルゴリズムとアルゴリズムの抜け穴の仮定を使用することです。

  • 条件分岐により、コードフロー指向の逆アセンブラはtrueまたはfalseの2つの分岐のいずれかを選択して最初に逆アセンブルし、ほとんどのコードフロー指向の逆アセンブラはfalse分岐を優先します。
  • ほとんどの逆アセンブラは、最初に呼び出し呼び出しの直後にバイトを逆アセンブルし、次に呼び出し呼び出しの位置バイトを逆アセンブルします

1.3.2同じターゲットへのジャンプ命令

次の逆アセンブルコードの説明があります:

74 03				jz	short near ptr loc_4011C4+1
75 01				jnz	short near ptr loc_4011C4+1
					loc_4011C4:			;CODE XREF:sub_4011C0
										;  sub_4011C0+2j
E8 58 C3 90 90		call near ptr 90D0D521h

jzとjnzの両方のジャンプは同じアドレスを指します。これは無条件のジャンプjmp命令と同等ですが、jnzを確認した後、逆アセンブラはデフォルトでfalseブランチの逆コンパイルを優先します。決して実行されない
ここに画像の説明を挿入

E8から始まるのはloc_4011C4です。これは、jzとjnzがE8をスキップして、58から実行を開始する必要があることを意味します。ただし、逆コンパイラーがfalseブランチを逆コンパイルする優先度があるため、E8は誤って命令コードの一部と見なされ、逆コンパイルされます。呼び出し命令の場合、正しい逆アセンブリコードは次のようになります。

74 03				jz	short near ptr loc_4011C5
75 01				jnz	short near ptr loc_4011C5
	;------------------------------------------------
E8					db 0E8h
	;------------------------------------------------
					loc_4011C5:			;CODE XREF:sub_4011C0
										;  sub_4011C0+2j
58					pop eax
C3					retn

1.3.3固定条件のジャンプ命令

逆アセンブラはfalseブランチを優先するため、ジャンプ命令は常に修正され、ジャンプ命令は常にtrueブランチにジャンプしますが、falseブランチコードはtrueブランチコードと競合します

33 C0				xor eax,eax
74 01				jz	short near ptr loc_4011C4+1
					loc_4011C4:			;CODE XREF: 004011C2j
										;DATA XREF: .rdata:004020AC0
E9 58 C3 68 94		jmp near ptr 94A8D521h

ここに画像の説明を挿入

正しい逆アセンブリコードは次のとおりです。

33 C0				xor eax,eax
74 01				jz	short near ptr loc_4011C4+1
	;------------------------------------------------
E9					db 0E9h
	;------------------------------------------------
					loc_4011C5:			;CODE XREF: 004011C2j
										;DATA XREF: .rdata:004020AC0
58 					pop eax
C3					retn

1.3.4反分解防止

前に紹介した2つの方法は、不正なバイトが存在すると逆アセンブラが誤った逆アセンブリコードを生成し、データバイトを命令バイトに変換するため、データと命令の変換を手動で実行できるためです。IDAInでは、ボタンCでデータを次のように変換できます。命令、およびボタンDは命令をデータに変換できます

IDAの赤いマークは、分解エラーがあることを示します

無視できない不正なバイトを利用する方法があります。すべてのバイトは命令の一部であり、一部のバイトは実行中に同時に2つ以上の命令に属します。現在、業界には逆アセンブラはありません。 1バイトは2つの命令の一部として表されます

1.3.5無効な分解-エントリケース

ここに画像の説明を挿入

  • 4バイトシーケンスでは、最初の命令は2バイトのjmp命令であり、jmp命令のジャンプターゲットは2番目のバイトであるため、2番目のバイトFFはjmp-1命令に属します。これはincに属します。再びeax命令
  • 逆アセンブラがjmp命令の一部としてFFを使用する場合、inceax命令の開始バイトとして表示することはできません。
  • この4バイトのシーケンスは本質的に複雑なNOPシーケンスであり、プログラムのほぼどこにでも挿入できるため、逆アセンブルチェーンが切断されます。このようなバイトシーケンスが発生した場合、上記の4バイトをデータに変換して無視することができます

と同等です:

JMP -1
INC EAX
DEX EAX

1.3.6無効な分解-高度なケース

ここに画像の説明を挿入

  • このシーケンスはxoreax eaxと同等ですが、難読化された逆アセンブリコードはretn return命令を認識しないため、関数が正常に終了せず、多数のコードエラーが発生します。
  • 解決策は、マシンコードを注意深く分析し、実際のデータ変更操作を正しく分析することです。

と同等です:

MOV ax,05EBh
XOR eax,eax
JZ -6
JMP 5
Real Code

IDAのこのコードは次のように分解されます:

66 B8 EB 05		mov ax,5EBh
31 C0			xor eax,eax
74 FA			jz short near ptr sub_4011C0+2
			loc_4011C8:
E8 58 C3 90 90	call near ptr 98A80525h

正しく変更すると、次のようになります。

66  byte_4011C0	db 66h
88				db 0B8h
EB				db 0E8h
05				db    5
	;------------------------------------------------
31 C0			xor eax,eax
	;------------------------------------------------
74				db 74h
FA				db 0FAh
E8				db 0E8h
	;------------------------------------------------
58				pop eax
C3				retn

2.アンチデバッグ(動的)

2.1アンチデバッグテクノロジー

  • 悪意のあるコードは、デバッグ防止テクノロジを使用して、現在デバッグされているかどうかを識別します。識別後、通常、実行パスを変更するか、プログラムをクラッシュさせるように自身を変更するため、デバッグ時間と複雑さが増します。

2.2 WindowsAPIの使用

  • IsDebuggerPresent:プロセス環境ブロック(PEB)のIsDebuggerフラグを照会します。デバッグされていない場合は0を返し、デバッグされている場合はゼロ以外を返します(PEBの詳細は後で説明します)。

  • CheckRemoteDebuggerPresent:IsDebuggerPresentと同様に、他のプロセスがデバッグされているかどうかも検出できる点が異なります。

  • NtQueryInformationProcess:特定のプロセスの情報を抽出するために使用されます。最初のパラメーターはプロセスハンドル、2番目のパラメーターは情報タイプです。2番目のパラメーターがProcessDebugPort(値0x7)に設定されている場合、プロセスがデバッグされているかどうかを返します。

  • FindWindowA:ウィンドウ名を検出します

  • OutputDebugString:デバッガーに文字列を表示します

    • SetLastError関数を使用して、エラーコードを設定します。プロセスがデバッグされていない場合、OutputDebugStringの呼び出しは失敗し、システムはエラーコードをリセットします。

    • DWORD	errorValue = 12345;
      SetLastError(errorValue);
      
      OutputDebugString("Test for Debugger");
      if(GetLastError() == errorValue){
              
              
          ExitProcess();
      }
      else{
              
              
          ...
      }
      

2.3レジストリチェック

  • 以下は、レジストリ内で一般的に使用されるデバッガの場所HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AeDebugです。デフォルトでは、に設定されていますDr.Wastson。OllyDbgに変更すると、悪意のあるコードがデバッグ中であると判断する可能性があります。

2.4プロセス環境ブロック(PEB)

  • Windowsは、実行中の各プロセスのPEB構造を維持します。この構造には、プロセスのデバッグステータスを含む、プロセスに関連するすべてのユーザーモードパラメーターが含まれます。
  • 具体的なリファレンス:https://bbs.pediy.com/thread-52398.html

PEBの構造は次のとおりです。

typedef struct _PEB {
    
                   // Size: 0x1D8
    000h    UCHAR           InheritedAddressSpace;
    001h    UCHAR           ReadImageFileExecOptions;
    002h    UCHAR           BeingDebugged;              //Debug运行标志
    003h    UCHAR           SpareBool;
    004h    HANDLE          Mutant;
    008h    HINSTANCE       ImageBaseAddress;           //程序加载的基地址
    00Ch    struct _PEB_LDR_DATA    *Ldr                //Ptr32 _PEB_LDR_DATA
    010h    struct _RTL_USER_PROCESS_PARAMETERS  *ProcessParameters;
    014h    ULONG           SubSystemData;
    018h    HANDLE         ProcessHeap;
    01Ch    KSPIN_LOCK      FastPebLock;
    020h    ULONG           FastPebLockRoutine;
    024h    ULONG           FastPebUnlockRoutine;
    028h    ULONG           EnvironmentUpdateCount;
    02Ch    ULONG           KernelCallbackTable;
    030h    LARGE_INTEGER   SystemReserved;
    038h    struct _PEB_FREE_BLOCK  *FreeList
    03Ch    ULONG           TlsExpansionCounter;
    040h    ULONG           TlsBitmap;
    044h    LARGE_INTEGER   TlsBitmapBits;
    04Ch    ULONG           ReadOnlySharedMemoryBase;
    050h    ULONG           ReadOnlySharedMemoryHeap;
    054h    ULONG           ReadOnlyStaticServerData;
    058h    ULONG           AnsiCodePageData;
    05Ch    ULONG           OemCodePageData;
    060h    ULONG           UnicodeCaseTableData;
    064h    ULONG           NumberOfProcessors;
    068h    LARGE_INTEGER   NtGlobalFlag;               // Address of a local copy
    070h    LARGE_INTEGER   CriticalSectionTimeout;
    078h    ULONG           HeapSegmentReserve;
    07Ch    ULONG           HeapSegmentCommit;
    080h    ULONG           HeapDeCommitTotalFreeThreshold;
    084h    ULONG           HeapDeCommitFreeBlockThreshold;
    088h    ULONG           NumberOfHeaps;
    08Ch    ULONG           MaximumNumberOfHeaps;
    090h    ULONG           ProcessHeaps;
    094h    ULONG           GdiSharedHandleTable;
    098h    ULONG           ProcessStarterHelper;
    09Ch    ULONG           GdiDCAttributeList;
    0A0h    KSPIN_LOCK      LoaderLock;
    0A4h    ULONG           OSMajorVersion;
    0A8h    ULONG           OSMinorVersion;
    0ACh    USHORT          OSBuildNumber;
    0AEh    USHORT          OSCSDVersion;
    0B0h    ULONG           OSPlatformId;
    0B4h    ULONG           ImageSubsystem;
    0B8h    ULONG           ImageSubsystemMajorVersion;
    0BCh    ULONG           ImageSubsystemMinorVersion;
    0C0h    ULONG           ImageProcessAffinityMask;
    0C4h    ULONG           GdiHandleBuffer[0x22];
    14Ch    ULONG           PostProcessInitRoutine;
    150h    ULONG           TlsExpansionBitmap;
    154h    UCHAR           TlsExpansionBitmapBits[0x80];
    1D4h    ULONG           SessionId;
} PEB, *PPEB;

アンチデバッグテクノロジに密接に関連するメンバーは次のとおりです。

002h  UCHAR       BeingDebugged; 
00Ch  struct _PEB_LDR_DATA  *Ldr;
018h  HANDLE      ProcessHeap;
068h  LARGE_INTEGER  NtGlobalFlag; 

次に、上記の4つのPEBメンバーを個別に説明します

2.4.1BeingDebuggedフラグの確認

  • プログラムの実行中、fs:[30h]はPEBベースアドレスを指し、プログラムはBeginDebuggerフラグをチェックして、デバッグされているかどうかを確認できます。
  • プロセスがデバッグ状態の場合、BeingDebuggedの値は1に設定され、プロセスが非デバッグ状態で実行されている場合、その値は0に設定されます。したがって、このメンバーの価値を判断することで、プログラムの実行プロセスを判断できます。

テストコードは次のとおりです。

int main()
{
    
    
	charresult=0;
	__asm
	{
    
    
		moveax,fs:[0x30];//获取PEB的地址。
		moval,BYTE PTR [eax+2];
		movresult,al;//得到BeingDebugged成员的值。
	}
    if(result==1)
		printf("isdebugging\n");
	else
        printf("notdebugging\n");
	system("pause");//为了观察方便,添加的。
	return 0;
}

アセンブリコードの場合:

mov 	eax,dword ptr fs:[30h]
mov 	ebx,byte ptr [eax+2]
test 	ebx,ebx
jz		NoDebuggerDetected

または:

push	dword ptr fs:[30h]
pop		edx
cmp		byte ptr [edx+2],1
je		DebuggerDetected

2.4.2ProcessHeapロゴを確認する

  • ProcessHeapは、Microsoftによって開示されていないPEBの属性です。PEB構造の0x18にあります。ProcessHeapにはForceFlagsが含まれています。この属性値は、現在のプロセスがデバッグされているかどうかを判別できます。オフセットは、XPでは0x10、Win7では0x44です。
  • ProcessHeapは、HEAP構造体へのポインターです。HEAP構造体の2つのメンバーFlagsとForce Flagsのオフセットは、それぞれ0xCと0x10です。プロセスが正常に実行されている場合、Heap.Flagsの値は0x2、HEAP.ForceFlagsです。メンバーのは0x0です。プロセスがデバッグ状態にある場合、これらの値は変更されます

テストコードは次のとおりです。

int main()
{
    
    
	intresult=0;
	__asm
	{
    
    
		moveax,fs:[0x30]; //PEB地址
		moveax,[eax+0x18];//ProcessHeap成员
		moveax,[eax+0x10];//ForceFlags成员
		movresult,eax;
	}
	if(result!=0)
		printf("isdebugging\n");
	else
		printf("notdebugging\n");
	system("pause");
	return 0;
}

アセンブリコードは次のようにもなります。

mov	eax,large fs:30h
mov eax,dword ptr [eax+18h]
cmp dword ptr ds:[eax+10h],0
jne DebuggerDetected

2.4.3NtGlobalFlagフラグの確認

  • デバッガーの起動プロセスは通常モードの起動プロセスとは異なるため、メモリヒープの作成方法も異なります。NTGlobalFlagも非公開です。PEBオフセットは0x68です。値が0x70の場合、プログラムはデバッガから起動します。

テストコードは次のとおりです。

int main()
{
    
    
	intresult=0;
	__asm
	{
    
    
		moveax,fs:[0x30]; //PEB地址
		moveax,[eax+0x68];//NtGlobalFlag成员
		movresult,eax;
	}
	if(result==0x70)
		printf("isdebugging\n");
	else
		printf("notdebugging\n");
	system("pause");
	return 0;
}

アセンブリコードは次のとおりです。

mov eax,large fs:30h
cmp dword ptr ds:[eax+68h],70h
jz DebuggerDetected

2.4.4Ldrフラグの確認

プロセスをデバッグすると、ヒープメモリにいくつかの特別なマークが表示され、デバッグ中であることを示します。これらの兆候の中で最も目を引くのは、未使用のヒープメモリ領域がOxFEEEFEEEで埋められていることです。この機能を使用して、プロセスがデバッグされているかどうかを判断できます。PEB.Ldrメンバーは_PEB_LDR_DATA構造体を指し、この構造体はヒープメモリ領域に作成されるため、この領域をスキャンして、プロセスがデバッグ状態にあるかどうかを判断できます。

検出コードは次のとおりです。

int main()
{
    
    
	LPBYTEpLdr;       
	DWORDpLdrSig[4]={
    
    0xEEFEEEFE,0xEEFEEEFE,0xEEFEEEFE,0xEEFEEEFE};
	__asm
	{
    
    
		moveax,fs:[0x30]; //PEB地址
		moveax,[eax+0xC];//Ldr
		movpLdr,eax;
	}
	__try
	{
    
    
		while(1){
    
    
              if(!memcmp(pLdr,pLdrSig,sizeof(pLdrSig)){
    
    
					printf("is debuggig\n");
                    break;}
			  else{
    
    
					pLdr++;}
        }
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
    
    
		printf("notdebugging\n");
	}      
	system("pause");
	return 0;
}

おすすめ

転載: blog.csdn.net/kelxLZ/article/details/112909915