Introduction à la technologie de lutte contre les codes malveillants

  • Auteur : ZERO-A-ONE
  • Date : 2021-01-20

De la vidéo "Vulnerability Bank | Un bref exposé sur les contre-mesures contre les codes malveillants", il commence principalement par deux aspects:

  • Démontage du compteur
  • Anti-débogage

1. Contre-démontage (statique)

1.1 Concepts de base

  • Langage d'assemblage: La CPU exécute le code machine Afin de faciliter la mémoire, des mnémoniques sont utilisés pour remplacer les opcodes des instructions machine en langage d'assemblage.
  • Désassemblage: le processus de conversion du code objet en code d'assemblage
  • Technologie anti-démontage: consiste à utiliser un code ou des données spécialement structurés dans le programme pour que l'outil de démontage produise un code d'assemblage incorrect

1.2 Algorithme de démontage

1.2.1 Algorithme de désassemblage linéaire

  • L'analyse linéaire est un algorithme de désassemblage de base, qui parcourt les données binaires d'un fichier du début à la fin et les traduit en langage d'assemblage. L'algorithme est simple et le plus facile à mettre en œuvre, et il est également facile de provoquer des erreurs d'analyse
  • Utilisez la bibliothèque de désassemblage libdisasm pour réaliser rapidement le démontage
    • http://bastard.sourceforge.net/libdisasm.html

Exemple de code:

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 Frustrer l'algorithme de désassemblage linéaire

Dans le fichier exécutable, en plus du code d'instruction, il y a également les données requises pour que le programme s'exécute. Si vous insérez des octets de données inutiles dans le code, le désassembleur peut analyser l'erreur.
Insérez la description de l'image ici

Comme indiqué sur le côté gauche de la figure ci-dessus, il est facile à trouver dans ce code

call near ptr 15FF2A71h

Il y a un gros problème, car il s'agit d'une très grande adresse dans la mémoire, il est impossible de sauter à cet emplacement, on peut considérer qu'il y a une erreur de démontage. Comme indiqué sur la droite, le résultat du démontage est correct

1.2.3 Algorithme de désassemblage pour le flux de code

  • Le désassemblage orienté flux de code est un algorithme de désassemblage plus avancé. Le désassembleur ne démontera pas aveuglément tout le fichier. Il vérifiera chaque instruction et construira une liste d'adresses qui doit être démontée. Cet algorithme est largement utilisé dans les désassembleurs commerciaux, tels que IDA

1.2.4 Comparaison de deux algorithmes de désassemblage

Lorsque le désassembleur linéaire rencontre l'instruction jmp, quelle que soit la logique du code, il la désassemble selon la séquence d'octets. Comme le montre la figure, l'auteur de code malveillant peut utiliser cette fonctionnalité pour masquer certaines chaînes sensibles.
Insérez la description de l'image ici

Lorsque le désassembleur orienté flux de code rencontre l'instruction jmp, il calculera la position à laquelle sauter, traitant ainsi le code qui ne sera pas exécuté comme des données
Insérez la description de l'image ici

1.3 Algorithme anti-démontage

1.3.1 Défauts des algorithmes orientés flux de code

La principale méthode de technologie de désassemblage contre la mise en œuvre de code malveillant est l'utilisation d'un algorithme de sélection de désassembleur et d'hypothèses de failles d'algorithme:

  • La branche conditionnelle oblige le désassembleur orienté flux de code à choisir l'une des deux branches de true ou false à désassembler en premier, et la plupart des désassembleurs orientés flux de code donneront la priorité à la branche false.
  • La plupart des désassembleurs désassembleront l'octet immédiatement après l'appel d'appel, suivi de l'octet de position de l'appel d'appel

1.3.2 Instructions de saut vers la même cible

Il existe les instructions de code de démontage suivantes:

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

Les sauts jz et jnz pointent tous les deux vers la même adresse, ce qui équivaut à l'instruction jmp de saut inconditionnel, mais après avoir vu le jnz, le désassembleur donnera toujours la priorité à la décompilation de la fausse branche par défaut, malgré le fait que cette branche sera ne jamais être exécuté
Insérez la description de l'image ici

À partir de E8 est loc_4011C4, ce qui signifie que jz et jnz devraient sauter E8 et démarrer l'exécution à partir de 58. Cependant, en raison de la priorité du décompilateur pour décompiler la fausse branche, E8 est considéré à tort comme faisant partie du code d'instruction et décompilé Become une instruction d'appel, le code de démontage correct doit être:

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 Instructions de saut avec conditions fixes

Les instructions de saut sont toujours fixes, ce qui fait que les instructions de saut sautent toujours vers la vraie branche, car le désassembleur donnera la priorité à la fausse branche, mais le faux code de branche entrera en conflit avec le vrai code de branche

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

Insérez la description de l'image ici

Le code de démontage correct doit être:

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 Anti-anti-démontage

Les deux méthodes précédemment introduites sont dues au fait que l’existence d’octets non fiables fait que le désassembleur génère un code de désassemblage incorrect, convertissant ainsi les octets de données en octets d’instruction, ce qui vous permet d’effectuer manuellement des conversions de données et d’instructions. Dans IDA In, le bouton C peut convertir les données en instructions, et le bouton D peut convertir des instructions en données

La marque rouge dans IDA indique qu'il y a une erreur de démontage

Il existe un moyen d'utiliser des octets non autorisés qui ne peuvent être ignorés. Tous les octets font partie de l'instruction, et certains octets appartiennent à deux ou plusieurs instructions en même temps pendant l'exécution. À l'heure actuelle, il n'y a pas de désassembleur dans l'industrie qui peut Un seul octet est représenté comme faisant partie de deux instructions

1.3.5 Cas d'entrée de démontage invalide

Insérez la description de l'image ici

  • Dans la séquence de 4 octets, la première instruction est une instruction jmp de 2 octets et la cible de saut de l'instruction jmp est son deuxième octet, de sorte que le deuxième octet FF appartient à l'instruction jmp-1. Il appartient à inc instruction eax à nouveau
  • Si le désassembleur utilise FF dans le cadre de l'instruction jmp, il ne peut pas être affiché comme l'octet de début de l'instruction inc eax
  • Cette séquence de 4 octets est essentiellement une séquence NOP complexe, qui peut être insérée presque n'importe où dans le programme, rompant ainsi la chaîne de désassemblage. Lors de la rencontre d'une telle séquence d'octets, les 4 octets ci-dessus peuvent être convertis en données et ignorés

Équivaut à:

JMP -1
INC EAX
DEX EAX

1.3.6 Cas avancé de démontage invalide

Insérez la description de l'image ici

  • Cette séquence est équivalente à xor eax eax, mais le code de désassemblage obscurci ne reconnaîtra pas l'instruction de retour retn, ce qui empêchera la fonction de se terminer normalement, ce qui entraînera un grand nombre d'erreurs de code.
  • La solution est d'analyser soigneusement le code machine et d'analyser correctement les opérations de modification des données réelles

Équivaut à:

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

Ce code dans IDA sera désassemblé en:

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

Après une modification correcte, il devrait lire:

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. Anti-débogage (dynamique)

2.1 Technologie anti-débogage

  • Le code malveillant utilise une technologie anti-débogage pour identifier s'il est en cours de débogage. Après identification, il change généralement le chemin d'exécution ou se modifie lui-même pour planter le programme, augmentant ainsi le temps et la complexité du débogage.

2.2 Utilisation de l'API Windows

  • IsDebuggerPresent: interroge l'indicateur IsDebugger dans le bloc d'environnement de processus (PEB). Il renvoie 0 s'il n'est pas débogué et renvoie une valeur différente de zéro s'il est débogué (les détails de PEB seront présentés ultérieurement)

  • CheckRemoteDebuggerPresent: similaire à IsDebuggerPresent, la différence est qu'il peut également détecter si d'autres processus sont en cours de débogage

  • NtQueryInformationProcess: utilisé pour extraire les informations d'un processus donné, le premier paramètre est le descripteur de processus, le deuxième paramètre est le type d'information, si le deuxième paramètre est défini sur ProcessDebugPort (valeur 0x7), retourne si le processus est débogué

  • FindWindowA: détecter le nom de la fenêtre

  • OutputDebugString: affiche une chaîne dans le débogueur

    • Utilisez la fonction SetLastError pour définir un code d'erreur. Si le processus n'est pas débogué, l'appel de OutputDebugString échouera et le système réinitialisera le code d'erreur.

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

2.3 Vérification du registre

  • Voici l'emplacement couramment utilisé du débogueur dans le registre:, HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AeDebugpar défaut, il est défini sur Dr.Wastson, s'il est modifié en OllyDbg, le code malveillant peut déterminer qu'il est en cours de débogage

2.4 Bloc d'environnement de processus (PEB)

  • Windows gère la structure PEB pour chaque processus en cours d'exécution. La structure contient tous les paramètres du mode utilisateur liés au processus, y compris l'état de débogage du processus.
  • Référence spécifique: https://bbs.pediy.com/thread-52398.html

La structure PEB est la suivante:

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;

Les membres étroitement liés à la technologie anti-débogage sont les suivants:

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

Ensuite, expliquez séparément les 4 membres du PEB ci-dessus

2.4.1 Vérification de l'indicateur BeingDebugged

  • Lorsque le programme est en cours d'exécution, fs: [30h] pointe vers l'adresse de base PEB, et le programme peut vérifier l'indicateur BeginDebugger pour confirmer s'il est en cours de débogage
  • Lorsque le processus est à l'état de débogage, la valeur de BeingDebugged est définie sur 1 et lorsque le processus est en cours d'exécution dans l'état de non-débogage, sa valeur est définie sur 0. Nous pouvons donc déterminer le processus de fonctionnement de notre programme en jugeant la valeur de ce membre

Le code de test est le suivant:

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;
}

Pour le code d'assemblage:

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

ou:

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

2.4.2 Vérifier le logo ProcessHeap

  • ProcessHeap est un attribut de PEB qui n'est pas divulgué par Microsoft. Il se trouve à 0 x 18 de la structure PEB. ProcessHeap contient des ForceFlags. Cette valeur d'attribut peut déterminer si le processus en cours est débogué. Le décalage est de 0 x 10 sous XP et de 0 x 44 sous Win7.
  • ProcessHeap est un pointeur vers la structure HEAP. Les deux membres Flags et Force Flags dans la structure HEAP ont des décalages de 0xC et 0x10, respectivement. Lorsque le processus s'exécute normalement, la valeur de Heap.Flags est 0x2, HEAP.ForceFlags La valeur du membre est 0x0. Lorsque le processus est dans l'état de débogage, ces valeurs changent

Le code de test est le suivant:

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;
}

Le code d'assemblage peut également être:

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

2.4.3 Vérification de l'indicateur NtGlobalFlag

  • Étant donné que le processus de démarrage dans le débogueur est différent du processus de démarrage en mode normal, la façon dont ils créent le tas de mémoire est également différente. NTGlobalFlag est également non divulgué. Le décalage PEB est 0x68. Lorsque la valeur est 0x70, cela signifie que le Le programme est lancé à partir du débogueur.

Le code de test est le suivant:

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;
}

Le code d'assemblage est le suivant:

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

2.4.4 Vérification du drapeau Ldr

Lors du débogage d'un processus, des marques spéciales apparaîtront dans sa mémoire de tas, indiquant qu'il est en cours de débogage. Le plus frappant de ces signes est que la zone de mémoire du tas inutilisée est remplie d'OxFEEEFEEE. Nous pouvons utiliser cette fonctionnalité pour déterminer si le processus est en cours de débogage. Le membre PEB.Ldr pointe vers une structure _PEB_LDR_DATA, et cette structure est créée dans la zone de mémoire du tas, nous pouvons donc analyser cette zone pour déterminer si le processus est dans un état de débogage

Le code de détection est le suivant:

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;
}

Je suppose que tu aimes

Origine blog.csdn.net/kelxLZ/article/details/112909915
conseillé
Classement