⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 29a-7.024

📁 从29A上收集的病毒源码
💻 024
📖 第 1 页 / 共 4 页
字号:
	We will set ProcessInfromationClass to ProcessBasicInformation. Then 
the PROCESS_BASIC_INFORMATION structure would be returned to ProcessInformation 
buffer which size is given by ProcessInformationLength.

	#define ProcessBasicInformation 0

	typedef struct _PROCESS_BASIC_INFORMATION {
		NTSTATUS ExitStatus;
		PPEB PebBaseAddress;
		KAFFINITY AffinityMask;
		KPRIORITY BasePriority;
		ULONG UniqueProcessId;
		ULONG InheritedFromUniqueProcessId;
	} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

	PebBaseAddress is what we were looking for. On PebBaseAddress+0x0C is 
address PPEB_LDR_DATA. This would be get calling NtReadVirtualMemory.

	NTSTATUS NtReadVirtualMemory(
		IN HANDLE ProcessHandle,
		IN PVOID BaseAddress,
		OUT PVOID Buffer,
		IN ULONG BufferLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	Parameters are similar like in NtWriteVirtualMemory. 
	On PPEB_LDR_DATA+0x1C is address InInitializationOrderModuleList. It is 
the list of libraries loaded to the process. We are interested only in a part 
of this structure.

	typedef struct _IN_INITIALIZATION_ORDER_MODULE_LIST {
		PVOID Next,
		PVOID Prev,
		DWORD ImageBase,
		DWORD ImageEntry,
		DWORD ImageSize,
		...
	);

	Next is a pointer on next record, Prev on previous, last record points 
on first. ImageBase is an address of module in the memory, ImageEntry is 
the EntryPoint of the module, ImageSize is its size.
	
	For all libraries in which we want to hook we will get their ImageBase 
(e.g. using GetModuleHandle or LoadLibrary). This ImageBase we will compare 
with ImageBase of each entry in InInitializationOrderModuleList.
	Now we are ready for hooking. Because we are hooking running processes 
there is a possibility that the code we would be executed in the moment we will 
be rewriting it. This can cause error, so at first we will stop all threads 
in target process. The list of its threads can get via NtQuerySystemInformation 
with SystemProcessesAndThreadsInformation class. Result of this function is 
described in chapter 4. But we have to add the description of SYSTEM_THREADS 
structure where the information about thread is.

	typedef struct _SYSTEM_THREADS {
		LARGE_INTEGER KernelTime;
		LARGE_INTEGER UserTime;
		LARGE_INTEGER CreateTime;
		ULONG WaitTime;
		PVOID StartAddress;
		CLIENT_ID ClientId;
		KPRIORITY Priority;
		KPRIORITY BasePriority;
		ULONG ContextSwitchCount;
		THREAD_STATE State;
		KWAIT_REASON WaitReason;
	} SYSTEM_THREADS, *PSYSTEM_THREADS; 

	For each thread we have to get its handle using NtOpenThread. We will 
use ClientId for it.

	NTSTATUS NtOpenThread(
		OUT PHANDLE ThreadHandle,
		IN ACCESS_MASK DesiredAccess,
		IN POBJECT_ATTRIBUTES ObjectAttributes,
		IN PCLIENT_ID ClientId
	);

	The handle we want will be stored to ThreadHandle. We will set 
DesiredAccess to THREAD_SUSPEND_RESUME.

	#define THREAD_SUSPEND_RESUME 2

	ThreadHandle will be used for calling NtSuspendThread.

	NTSTATUS NtSuspendThread(
		IN HANDLE ThreadHandle,
		OUT PULONG PreviousSuspendCount OPTIONAL
	);


	Suspended process is ready for rewriting. We will proceed as it is 
described in chapter 3.2.2 in "Hooking Windows API". Only difference will be 
in using functions for other processes.

	After a hook we will revive all process threads calling NtResumeThread.

	NTSTATUS NtResumeThread(
		IN HANDLE ThreadHandle,
		OUT PULONG PreviousSuspendCount OPTIONAL
	);


=====[ 7.3 New processes ]======================================================

	Infection of all running processes does not affect processes which 
would be run later. We could get the process list and after a while get a new 
one and compare them and then infect those processes which are in second list 
but not in first. But this method is very unreliable.
	Much better is to hook function which is always called when new process 
starts. Because of hooking all running processes on the system we can't miss 
any new with this method. We can hook NtCreateThread but it is not the easiest 
way. We will hook NtResumeThread which is also called everytime after the new 
process is created. It is called after NtCreateThread.
	The only problem with NtResumeThread is that it is called not only when 
new process starts. But we can easily get over this. NtQueryInformationThread 
will give us an information about which process owns the specific thread. 
The last thing we have to do is to check whether this process is already hooked 
or not. This can be done by reading first byte of any function we are hooking.

	NTSTATUS NtQueryInformationThread(
		IN HANDLE ThreadHandle,
		IN THREADINFOCLASS ThreadInformationClass,
		OUT PVOID ThreadInformation,
		IN ULONG ThreadInformationLength, 
		OUT PULONG ReturnLength OPTIONAL
	);

	ThreadInformationClass is information class and it should be set in our 
case to ThreadBasicInformation. ThreadInformation is the buffer for result 
which size is ThreadInformationLength bytes.

	#define ThreadBasicInformation 0

	For class ThreadBasicInformation is this structure returned: 

	typedef struct _THREAD_BASIC_INFORMATION {
		NTSTATUS ExitStatus;
		PNT_TIB TebBaseAddress;
		CLIENT_ID ClientId;
		KAFFINITY AffinityMask;
		KPRIORITY Priority;
		KPRIORITY BasePriority;
	} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

	In ClientId is the PID of which owns the thread.

	Now we have to infect the new process. The problem is that the new 
process has only ntdll.dll in its memory. All others modules are loaded 
immediately after calling NtResumeThread. There are several ways how to handle 
this problem. E.g. we can hook API called LdrInitializeThunk which is called 
during process init.

	NTSTATUS LdrInitializeThunk(
		DWORD Unknown1,
		DWORD Unknown2,
		DWORD Unknown3
	);

	At first we will run original code and then we will hook all functions 
we want in this new process. But it will be better to unhook LdrInitializeThunk 
because it is called many times later and we don't want to rehook all functions 
again. Everything here is done before execution of the first instruction of 
hooked application. That's why there is no chance it would call any of hooked 
functions before we hook it.
	The hooking in itself is the same as when hooking running process 
but here we don't care about running threads.


=====[ 7.4 DLL ]================================================================

	In each process in the system is the copy of ntdll.dll. That mean we 
can hook any function from this module in the process init. But how about 
functions from other modules like kernel32.dll or advapi32.dll? And there are 
also several processes which has only ntdll.dll. All other modules can be 
loaded dynamically in the middle of the code after the process hook. That's why 
we have to hook LdrLoadDll which loades new modules.
	
	NTSTATUS LdrLoadDll( 
		PWSTR szcwPath,
		PDWORD pdwLdrErr,      
		PUNICODE_STRING pUniModuleName,
		PHINSTANCE pResultInstance
	);

	The most important for us here is pUniModuleName which is the name of 
the module. pResultInstance will be filled with its address if the call is 
successful.
	We will call original LdrLoadDll and then hook all functions in loaded 
module.



=====[ 8. Memory ]==============================================================

	When we are hooking a function we modify its first bytes. Via calling 
NtReadVirtualMemory anyone can detect that a function is hooked. So we have to 
hook NtReadVirtualMemory to prevent detecting.

	NTSTATUS NtReadVirtualMemory(
		IN HANDLE ProcessHandle,
		IN PVOID BaseAddress,
		OUT PVOID Buffer,
		IN ULONG BufferLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	We have changed bytes on the begining of all functions we hooked and 
we have also allocated memory for our new code. We should check whether caller 
reads some of these bytes. If we have our bytes in the range from BaseAddress 
to BaseAddress + BufferLength we have to change some bytes in Buffer.
	If one ask for bytes from our allocated memory we should return empty 
Buffer and an error STATUS_PARTIAL_COPY. This value says not all requested 
bytes were copied to the Buffer. It is also used when asking for unallocated 
memory. ReturnLength should be set to 0 in this case.

	#define STATUS_PARTIAL_COPY 0x8000000D

	If one ask for first bytes of hooked function we have to call original 
code and than we should copy original bytes (we have saved them for original 
calls) to Buffer. 
	Now the process is not able to detect he is hooked via reading its 
memory. Also if you debug hooked process debugger will have a problem. It will 
show original bytes but it will execute our code.

	To make hiding perfect we can also hook NtQueryVirtualMemory. This 
function is used to get information about virtual memory. We can hook it to 
prevent detecting our allocated memory.

	NTSTATUS NtQueryVirtualMemory(
		IN HANDLE ProcessHandle,
		IN PVOID BaseAddress,
		IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
		OUT PVOID MemoryInformation,
		IN ULONG MemoryInformationLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	MemoryInformationClass specifies the class of data which are returned. 
First two types are interesting for us.

	#define MemoryBasicInformation 0
	#define MemoryWorkingSetList 1

	For class MemoryBasicInformation is returned this structure:

	typedef struct _MEMORY_BASIC_INFORMATION {
		PVOID BaseAddress;
		PVOID AllocationBase;
		ULONG AllocationProtect;
		ULONG RegionSize;
		ULONG State;
		ULONG Protect;
		ULONG Type;
	} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

	Each memory section has its size RegionSize and its type Type. Free 
memory has type MEM_FREE.

	#define MEM_FREE 0x10000

	If a section before ours has type MEM_FREE we should add the size of 
ours section to its RegionSize. If the following section is also MEM_FREE we 
should add following section size again that RegionSize.
	If a section before ours has another type we will return MEM_FREE 
for our section. Its size is counted again according to following section.

	For class MemoryWorkingSetList is returned structure:

	typedef struct _MEMORY_WORKING_SET_LIST { 
		ULONG NumberOfPages;
		ULONG WorkingSetList[1];
	} MEMORY_WORKING_SET_LIST, *PMEMORY_WORKING_SET_LIST;

	NumberOfPages is the number of items in WorkingSetList. This number 
should be decreased. We should find ours section in WorkingSetList and move 
following records over ours. WorkingSetList is an array of DWORDs where higher 
20 bits specifies higher 20 bits of section address and lower 12 bits specifies 
flags.



=====[ 9. Handle ]==============================================================

	Calling NtQuerySystemInformation with SystemHandleInformation class
gives us array of all open handles in _SYSTEM_HANDLE_INFORMATION_EX strucure.

	#define SystemHandleInformation 0x10

	typedef struct _SYSTEM_HANDLE_INFORMATION {
		ULONG ProcessId;
		UCHAR ObjectTypeNumber;
		UCHAR Flags;
		USHORT Handle;
		PVOID Object;
		ACCESS_MASK GrantedAccess;
	} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

	typedef struct _SYSTEM_HANDLE_INFORMATION_EX {
		ULONG NumberOfHandles;
		SYSTEM_HANDLE_INFORMATION Information[1];
	} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;

	ProcessId specifies the process which owns the handle. ObjectTypeNumber 
is handle type. NumberOfHandles is number of records in Information array. 
Hiding one item is trivial. We have to remove all following records by one 
and decrease NumberOfHandles. Removing all following is needed because handles 
in array are grouped by ProcessId. That mean all handles from one single 
process are together. And for one process the number Handle is growing.
	Now remember structure _SYSTEM_PROCESSES which is returned by this 
function with SystemProcessesAndThreadsInformation class. Here we can see that 
each process has an information about its number of handles in HandleCount. 
If we want to be perfect we should modify HandleCount owing to how many handles 
we hide when calling this function with SystemProcessesAndThreadsInformation
class. But this correction would be very time-consuming. There are many handles 
opening and closing in very short time during normal system running. So it can 
easily happend that number of handles is changed in between two calls of this 
function and we don't need to change HandleCount.


=====[ 9.1 Naming handle and getting type ]=====================================

	Handle hiding is trivial but find out which handle to hide is little 
bit harder. If we have e.g. hidden process we should hide all its handles and 
all handles which are connected with it. Hiding handles of this process is 
again trivial. We are only comparing ProcessId of handle and PID of our process 
and when they equals we hide it. But handles of other processes have to be 
named before we can compare something. The number of handles in the system is 
usually very big, so the best we can do is to compare handle type first before 
trying to name it. Naming types will save a lot of time for handles we are not 
interested in. 
	Naming handle and handle type can be done via calling NtQueryObject.

	NTSTATUS ZwQueryObject(
		IN HANDLE ObjectHandle,
		IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
		OUT PVOID ObjectInformation,
		IN ULONG ObjectInformationLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	ObjectHandle is a handle we want to get info about, 
ObjectInformationClass is the type of information which will be stored into 
ObjectInformation buffer which is ObjectInformationLength bytes long.

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -