- 著者: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;
}