Introduction to malicious code countermeasures technology

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

From the video "Vulnerability Bank | A Brief Talk on Malicious Code Countermeasures", it mainly starts from two aspects:

  • Counter disassembly
  • Anti-debug

1. Counter disassembly (static)

1.1 Basic concepts

  • Assembly language: The CPU executes machine code. In order to facilitate memory, mnemonics are used to replace the opcodes of machine instructions in assembly language.
  • Disassembly: the process of converting object code into assembly code
  • Anti-disassembly technology: is to use some specially structured code or data in the program to make the disassembly tool produce incorrect assembly code

1.2 Disassembly algorithm

1.2.1 Linear disassembly algorithm

  • Linear scan is a basic disassembly algorithm that traverses the binary data of a file from the beginning to the end and translates it into assembly language. The algorithm is simple and easiest to implement, and it is also easy to cause analysis errors
  • Use libdisasm disassembly library to quickly realize disassembly
    • http://bastard.sourceforge.net/libdisasm.html

Sample 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 Frustrating the linear disassembly algorithm

​ In the executable file, in addition to the instruction code, there is also the data required for the program to run. If you insert useless data bytes in the code, it may cause the disassembler to parse the error
Insert picture description here

​ As shown on the left side of the figure above, it is easy to find in this code

call near ptr 15FF2A71h

​ There is a big problem, because this is a very large address in the memory, it is impossible to jump to this location, it can be considered that there is a disassembly error. As shown on the right is the correct disassembly result

1.2.3 Disassembly algorithm for code flow

  • Code flow-oriented disassembly is a more advanced disassembly algorithm. The disassembler will not blindly disassemble the entire file. It will check every instruction and build an address list that needs to be disassembled. This algorithm is widely used in commercial disassemblers, such as IDA

1.2.4 Comparison of two disassembly algorithms

​ When the linear disassembler encounters the jmp instruction, regardless of the code logic, it will disassemble it according to the byte sequence. As shown in the figure, the malicious code author can use this feature to hide some sensitive strings
Insert picture description here

​ When the code flow-oriented disassembler encounters the jmp instruction, it will calculate the position to jump to, thereby treating the code that will not be executed as data
Insert picture description here

1.3 Anti-disassembly algorithm

1.3.1 Defects of code flow-oriented algorithms

The main method of disassembly technology against malicious code implementation is the use of a disassembler selection algorithm and algorithm loopholes assumptions:

  • Conditional branch makes the code flow-oriented disassembler choose one of the two branches from true or false to disassemble first, and most code flow-oriented disassemblers will give priority to the false branch.
  • Most disassemblers will disassemble the byte immediately following the call call first, followed by the position byte of the call call

1.3.2 Jump instructions to the same target

​ There are the following disassembly code instructions:

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

​ Both the jz and jnz jumps point to the same address, which is equivalent to the unconditional jump jmp instruction, but after seeing the jnz, the disassembler will still give priority to decompiling the false branch by default, despite the fact that this branch will never be executed
Insert picture description here

​ Starting from E8 is loc_4011C4, which means that jz and jnz should skip E8 and start execution from 58. However, because of the priority of the decompiler to decompile the false branch, E8 is mistakenly regarded as part of the instruction code and decompiled Become a call instruction, the correct disassembly code should be:

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 Jump instructions with fixed conditions

​ Jump instructions are always fixed, resulting in jump instructions always jump to the true branch, because the disassembler will prioritize the false branch, but the false branch code will conflict with the true branch code

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

Insert picture description here

​ The correct disassembly code should be:

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-disassembly

​ The two methods introduced before are because the existence of rogue bytes causes the disassembler to generate incorrect disassembly code, thereby translating data bytes into instruction bytes, so you can manually perform data and instruction conversions. In IDA In, button C can convert data into instructions, and button D can convert instructions into data

The red mark in IDA indicates that there is a disassembly error

​ There is a way to make use of rogue bytes that cannot be ignored. All bytes are part of the instruction, and some bytes belong to two or more instructions at the same time during execution. At present, there is no disassembler in the industry that can A single byte is represented as part of two instructions

1.3.5 Invalid disassembly-entry case

Insert picture description here

  • In the 4-byte sequence, the first instruction is a 2-byte jmp instruction, and the jump target of the jmp instruction is his second byte, so that the second byte FF belongs to the jmp-1 instruction. It belongs to the inc eax instruction again
  • If the disassembler uses FF as part of the jmp instruction, it cannot be displayed as the beginning byte of the inc eax instruction
  • This 4-byte sequence is essentially a complex NOP sequence, which can be inserted almost anywhere in the program, thereby breaking the disassembly chain. When encountering such a byte sequence, the above 4 bytes can be converted into data and ignored

​ is equivalent to:

JMP -1
INC EAX
DEX EAX

1.3.6 Invalid disassembly-advanced case

Insert picture description here

  • This sequence is equivalent to xor eax eax, but the obfuscated disassembly code will not recognize the retn return instruction, which will cause the function to fail to end normally, resulting in a large number of code errors.
  • The solution is to carefully analyze the machine code and correctly analyze the actual data modification operations

​ is equivalent to:

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

​ This code in IDA will be disassembled into:

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

​ After correct modification, it should read:

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-debugging (dynamic)

2.1 Anti-debugging technology

  • Malicious code uses anti-debugging technology to identify whether it is currently being debugged. After identification, it usually changes the execution path or modifies itself to crash the program, thereby increasing the debugging time and complexity

2.2 Using Windows API

  • IsDebuggerPresent: Query the IsDebugger flag in the process environment block (PEB). It returns 0 if it is not debugged, and returns non-zero if it is debugged (the details of PEB will be introduced later)

  • CheckRemoteDebuggerPresent: Similar to IsDebuggerPresent, the difference is that it can also detect whether other processes are being debugged

  • NtQueryInformationProcess: used to extract the information of a given process, the first parameter is the process handle, the second parameter is the information type, if the second parameter is set to ProcessDebugPort (value 0x7), return whether the process is debugged

  • FindWindowA: detect window name

  • OutputDebugString: Display a string in the debugger

    • Use the SetLastError function to set any error code. If the process is not debugged, calling OutputDebugString will fail, and the system will reset the error code

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

2.3 Registry check

  • The following is the commonly used location of the debugger in the registry:, HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AeDebugby default, it is set to Dr.Wastson, if it is modified to OllyDbg, the malicious code may determine that it is being debugged

2.4 Process Environment Block (PEB)

  • Windows maintains the PEB structure for each running process. The structure contains all user mode parameters related to the process, including the debugging status of the process.
  • Specific reference: https://bbs.pediy.com/thread-52398.html

The PEB structure is as follows:

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;

The members closely related to anti-debugging technology are as follows:

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

Next, explain the above 4 PEB members separately

2.4.1 Checking the BeingDebugged flag

  • When the program is running, fs:[30h] points to the PEB base address, and the program can check the BeginDebugger flag to confirm whether it is being debugged
  • When the process is in the debugging state, the value of BeingDebugged is set to 1, and when the process is running in the non-debugging state, its value is set to 0. So we can determine the running process of our program by judging the value of this member

The test code is as follows:

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

For assembly code:

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

or:

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

2.4.2 Check ProcessHeap logo

  • ProcessHeap is an attribute of PEB that is not disclosed by Microsoft. It is located at 0x18 of PEB structure. ProcessHeap contains ForceFlags. This attribute value can determine whether the current process is debugged. The offset is 0x10 in XP and 0x44 in Win7.
  • ProcessHeap is a pointer to the HEAP structure. The two members Flags and Force Flags in the HEAP structure have offsets of 0xC and 0x10, respectively. When the process is running normally, the value of Heap.Flags is 0x2, HEAP.ForceFlags The value of the member is 0x0. When the process is in the debug state, these values ​​will change

The test code is as follows:

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

The assembly code can also be:

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

2.4.3 Checking the NtGlobalFlag flag

  • Since the startup process in the debugger is different from the startup process in the normal mode, the way they create the memory heap is also different. NTGlobalFlag is also undisclosed, and the PEB offset is 0x68. When the value is 0x70, it means that the program is started from the debugger.

The test code is as follows:

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

The assembly code is as follows:

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

2.4.4 Checking the Ldr flag

​ When debugging a process, some special marks will appear in its heap memory, indicating that it is being debugged. The most eye-catching of these signs is that the unused heap memory area is filled with OxFEEEFEEE. We can use this feature to determine whether the process is being debugged. The PEB.Ldr member points to a _PEB_LDR_DATA structure, and this structure is created in the heap memory area, so we can scan this area to determine whether the process is in a debugging state

The detection code is as follows:

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

Guess you like

Origin blog.csdn.net/kelxLZ/article/details/112909915