- 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.
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.
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
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é
À 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
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
- 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
- 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\AeDebug
par défaut, il est défini surDr.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;
}