AntiMida 1.0
This article is just a general overview about protections who play with system structures like the SDT. It has nothing to do with reversing and no debugger or disassembler was used to write it. In addition TheMida will not implement a kernel-mode part in the near future.
Click here to download the project files.
This article is just for fun, don't take it too serious. Some time ago a friend asked me if i was interested in helping him
out with the coding stuff related the analyzing of themida (the commercial successor of xprotector). I agreed. However, i had second thoughts about it. 'Cause, to be sincere, i don't like to waste my time over useless things and themida sure is one of them. For those who still don't know what i'm talking about: themida is a packer, moreover it's a packer who runs partly in kernel-mode. This means: if you try to debug themida with a debugger, it will crash your system. Very unprofessional, i know. And that's mainly the reason i look at themida as a toy for reversers. Lets get this straight, themida will never be used by major companies, nobody would protect a good software with a protector who patches the kernel of windows, patches the SDT, patches the IDT, in certain situations makes you reboot your computer etc etc? This means themida isn't widely used and there's no real interest in unpacking it other than the fun itself. I have no fun doing useless things, but since Littleluk is the reverser, i have just to code some stuff (and using old code snippets i've already coded). And this is what this article is all about, a tool i wrote. It's not a tool to unpack themida. The tool makes work some tools you already have on you computer against themida
or any other protector who uses the same tricks.
A few warnings:
1 - this article is nothing hardcore, don't let you impress by the driver coding stuff, it's ridiculous stuff for a driver writer (and i say that 'cause sometime in the reversing scene it's easy to find people with no knowledge of kernel-mode programming).
2 - the things you're going to read in this article will be obsolete in a few years (as the tricks themida uses).
3 - i never touched themida, i wrote the tool out of my experience and the results i got from Littleluk.
4 - i'm not going to explain what SDT, IDT and other internals relative concepts mean. If you ignore the meaning look on the web. I have no time to explain more than necessary, in fact this is one of the reason i'm releasing this article right now. I just have available a coule of days and then i have to go back to work (real work).
5 - i'm going to explain what i know about themida, or better what you need to know to understand the tool i wrote. I'm not reversing themida, so i don't care about a lot things.
6 - i don't know if there will be another version of the AntiMida, it depends on the information which is given to me by the reverser/s and on how much free time i'll have.
7 - it doesn't work if u're using PAE extension (i was too lazy to add some code).
8 - The way the tool acts wasn't absolutely necessary, there were other ways.
The victim which was used for the tests is nothing else than the themida itself (the demo version i mean). You can download it from the official webpage (current version is 1.0.0.5).
The AntiMida is not a planned tool, everytime there was a problem i tried to code the solution for it. At the moment AntiMida lets you:
1 - use common user-mode applications to dump themida.
2 - use tools like imprec, winhex (to see the proc memory), etc.
3 - monitor file and registry access.
But one thing at a time. The first step was to dump themida. How? First we had to know what themida does to protect itself against dumping. Actually the first idea to dump themida was to use KeAttachProcess, we dumped ntoskrnl (with wark) and saw that the keattachprocess was patched with a jmp to a themida routine. So, to use keattachprocess it was necessary to pacth the ntoskrnl first (i paste later the code). Here's the routine i wrote with KeAttachProcess:
case CODE_READ_MEM:
{
MemReadInfo mri;
ULONG_PTR ptr, addr;
BYTE *Buffer;
UINT x, y;
RtlCopyMemory(&mri, pInput, sizeof (MemReadInfo));
if (PsLookupProcessByProcessId(mri.PID, &ptr)
!= STATUS_SUCCESS)
return STATUS_INVALID_PARAMETER;
Buffer = (BYTE *) ExAllocatePool(NonPagedPool, dwOutputSize);
if (Buffer == NULL)
return STATUS_INVALID_PARAMETER;
KeAttachProcess(ptr);
if (dwOutputSize <= 0x1000)
{
x = 1;
}
else
{
x = dwOutputSize / 0x1000;
if (dwOutputSize % 0x1000 != 0)
x++;
}
for (y = 0; y < x; y++)
{
addr = y * 0x1000 + (ULONG_PTR) mri.Address;
if (MmIsAddressValid((PVOID) addr) ==
FALSE)
{
ExFreePool(Buffer);
return STATUS_INVALID_PARAMETER;
}
}
RtlCopyMemory(Buffer, mri.Address, dwOutputSize);
KeDetachProcess();
RtlCopyMemory(pOutput, Buffer, dwOutputSize);
ExFreePool(Buffer);
*pdwInfo = dwOutputSize;
break;
}
I pasted it just for information, 'cause this code isn't used anymore by the AntiMida. It was obvious that themida was hooking the SDT, so after a scanning with sdtrestore a list of hooked services was given to me:
ZwAllocateVirtualMemory 11 --[hooked by unknown at F5938BC4]--
ZwCreateThread 35 --[hooked by unknown at F5938CBE]--
ZwDebugContinue 3A --[hooked by unknown at F59391A0]--
ZwQueryVirtualMemory B2 --[hooked by unknown at F5938ACA]--
ZwReadVirtualMemory BA --[hooked by unknown at F5938014]--
ZwTerminateProcess 101 --[hooked by unknown at F59389D0]--
ZwWriteVirtualMemory 115 --[hooked by unknown at F5938000]--
Ok ZwAllocateVirtualMemory, ZwQueryVirtualMemory, ZwReadVirtualMemory, ZwWriteVirtualMemory, ZwCreateThread make sense, since we could use all these functions to dump themida. ZwDebugContinue is to avoid debugging, although themida fucks with the IDT as well (so this is way not the only trick against debugging, would be too easy, of course). ZwTerminateProcess is unimportant, i guess it's only hooked to know when a protected process is killed. Of course if you try to restore one of these services themida crashes the system.
The idea i had was to build a tool which would make other already existing tools work. So i organized an interface with a list of the running processes and the possibility of selecting one of those processes and make it AntiMida, this means immune against themida. A brief look at the interface is thousand of words worth.
BOOL InjectModule(IN ULONG_PTR ProcessID, IN TCHAR *ModuleName,
OUT ULONG_PTR *BaseAddress OPTIONAL)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
ProcessID);
if (hProcess == NULL)
return FALSE;
HANDLE hFile = CreateFile(ModuleName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
CloseHandle(hProcess);
return FALSE;
}
UINT PE_Size = GetFileSize(hFile, NULL);
BYTE *PE_Buffer = (BYTE *) VirtualAlloc(NULL, PE_Size,
MEM_COMMIT, PAGE_READWRITE);
if (PE_Buffer == NULL)
{
CloseHandle(hProcess);
CloseHandle(hFile);
return FALSE;
}
DWORD BR;
if (!ReadFile(hFile, PE_Buffer, PE_Size, &BR,
NULL))
{
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hFile);
return FALSE;
}
CloseHandle(hFile);
BYTE *PE_Image;
IMAGE_DOS_HEADER *ImgDosHdr;
IMAGE_NT_HEADERS *ImgNtHdrs;
ULONG_PTR ImgBase, Delta, Reloc_Offset;
IMAGE_BASE_RELOCATION *ImgBaseReloc;
WORD *wData;
UINT i, nItems;
ULONG_PTR Offset;
DWORD Type;
ULONG_PTR *Block, BlockOffs;
ULONG_PTR IT_Offset;
IMAGE_IMPORT_DESCRIPTOR *ImgImpDescr;
UINT x = 0, y = 0;
CHAR *DllName;
ULONG_PTR *Thunks, *FThunks;
IMAGE_IMPORT_BY_NAME *ImgImpName;
_try
{
ImgDosHdr = (IMAGE_DOS_HEADER *) PE_Buffer;
ImgNtHdrs = (IMAGE_NT_HEADERS *) (ImgDosHdr->e_lfanew +
(ULONG_PTR) PE_Buffer);
if (ImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE
||
ImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
{
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
ImgBase = (ULONG_PTR) VirtualAllocEx(hProcess,
(PVOID) 0, //ImgNtHdrs->OptionalHeader.ImageBase,
ImgNtHdrs->OptionalHeader.SizeOfImage, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
if (ImgBase == NULL)
{
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
//
// is file image base == memory image base?
// if not, relocate
//
if (ImgNtHdrs->OptionalHeader.ImageBase !=
ImgBase)
{
Delta = ImgBase - ImgNtHdrs->OptionalHeader.ImageBase;
if (!ImgNtHdrs->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress ||
!(Reloc_Offset = RvaToOffset(ImgNtHdrs,
ImgNtHdrs->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress)))
{
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
ImgBaseReloc = (IMAGE_BASE_RELOCATION *)
(Reloc_Offset + (ULONG_PTR) PE_Buffer);
do
{
if (!ImgBaseReloc->SizeOfBlock)
break;
nItems = (ImgBaseReloc->SizeOfBlock -
IMAGE_SIZEOF_BASE_RELOCATION) / sizeof
(WORD);
wData = (WORD *)(IMAGE_SIZEOF_BASE_RELOCATION +
(ULONG_PTR) ImgBaseReloc);
for (i = 0; i < nItems; i++)
{
Offset = (*wData & 0xFFF) + ImgBaseReloc->VirtualAddress;
Type = *wData >> 12;
if (Type !=
IMAGE_REL_BASED_ABSOLUTE)
{
BlockOffs = RvaToOffset(ImgNtHdrs, Offset);
if (BlockOffs == NULL)
{
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
Block = (DWORD *)(BlockOffs + (ULONG_PTR) PE_Buffer);
*Block += Delta;
}
wData++;
}
ImgBaseReloc = (PIMAGE_BASE_RELOCATION) wData;
} while (*(DWORD *) wData);
}
//
// fill the import addres table
//
if (ImgNtHdrs->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress)
{
IT_Offset = RvaToOffset(ImgNtHdrs,
ImgNtHdrs->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
ImgImpDescr = (IMAGE_IMPORT_DESCRIPTOR *) (IT_Offset +
(ULONG_PTR) PE_Buffer);
// for each descriptor
while (ImgImpDescr[x].FirstThunk != 0)
{
DllName = (CHAR *) (RvaToOffset(ImgNtHdrs,
ImgImpDescr[x].Name) + (ULONG_PTR) PE_Buffer);
Thunks = (ULONG_PTR *) (RvaToOffset(ImgNtHdrs,
ImgImpDescr[x].OriginalFirstThunk != 0 ?
ImgImpDescr[x].OriginalFirstThunk :
ImgImpDescr[x].FirstThunk) + (ULONG_PTR) PE_Buffer);
FThunks = (ULONG_PTR *) (RvaToOffset(ImgNtHdrs,
ImgImpDescr[x].FirstThunk) + (ULONG_PTR) PE_Buffer);
y = 0;
//
// every imported function of the module
//
while (Thunks[y] != 0)
{
//
// imported by ordinal?
//
if (Thunks[y] & IMAGE_ORDINAL_FLAG)
{
FThunks[y] = (ULONG_PTR) GetProcAddress(
GetModuleHandle(DllName),
(LPCSTR) (Thunks[y] - IMAGE_ORDINAL_FLAG));
y++;
continue;
}
ImgImpName = (IMAGE_IMPORT_BY_NAME *) (RvaToOffset(
ImgNtHdrs, Thunks[y]) + (ULONG_PTR) PE_Buffer);
FThunks[y] = (ULONG_PTR) GetProcAddress(GetModuleHandle(DllName),
(LPCSTR) &ImgImpName->Name);
y++;
}
x++;
}
}
}
_except (EXCEPTION_EXECUTE_HANDLER)
{
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree((LPVOID) PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
//
// create virtual image of the PE file
//
PE_Image = (BYTE *) VirtualAlloc(NULL,
ImgNtHdrs->OptionalHeader.SizeOfImage,
MEM_COMMIT, PAGE_READWRITE);
if (PE_Image == NULL)
{
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
ZeroMemory(PE_Image, ImgNtHdrs->OptionalHeader.SizeOfImage);
//
// copy headers
//
IMAGE_SECTION_HEADER *Sect;
Sect = IMAGE_FIRST_SECTION(ImgNtHdrs);
RtlCopyMemory(PE_Image, PE_Buffer, Sect[0].PointerToRawData);
//
// map sections
//
for (UINT j = 0; j < ImgNtHdrs->FileHeader.NumberOfSections;
j++)
{
BYTE *Source = (BYTE *)(Sect[j].PointerToRawData +
(ULONG_PTR) PE_Buffer);
BYTE *Dest = (BYTE *)(Sect[j].VirtualAddress +
(ULONG_PTR) PE_Image);
RtlCopyMemory(Dest, Source, Sect[j].SizeOfRawData);
}
BOOL bRet = WriteProcessMemory(hProcess, (LPVOID) ImgBase,
PE_Image, ImgNtHdrs->OptionalHeader.SizeOfImage, &BR);
if (!bRet)
VirtualFreeEx(hProcess, (LPVOID) ImgBase, 0, MEM_RELEASE);
VirtualFree(PE_Image, 0, MEM_RELEASE);
VirtualFree(PE_Buffer, 0, MEM_RELEASE);
CloseHandle(hProcess);
if (bRet == TRUE && BaseAddress != NULL)
*BaseAddress = ImgBase;
return bRet;
}
To fill the import table i used no remote getprocaddress since the dll imports just not relocated system dlls, so a locale getprocaddress is enough. Here's the hooking routine:
void CAntiMidaDlg::OnBnClickedAntimida()
{
ProcList.GetItemText(ProcList.GetNextItem(-1, LVNI_SELECTED), 1,
Buffer, sizeof (Buffer) -1);
ULONG_PTR PID = _tcstoul(Buffer, 0, 16);
wsprintf(Buffer, _T("%samdll.dll"), CurDir);
ULONG_PTR BaseAddress;
//
// inject the module
//
if (InjectModule(PID, _T("amdll.dll"), &BaseAddress))
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL)
{
MessageBox(_T("Cannot make the process AntiMida"), "AntiMida");
return;
}
//
// hook ReadProcessMemory
//
ULONG_PTR Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("kernel32.dll")),
"ReadProcessMemory");
BYTE Instr = 0xB8;
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
ULONG_PTR Rva = GetExportRva(Buffer, "_FakeReadProcessMemory@20");
ULONG_PTR HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WORD Instr2 = 0xE0FF;
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// hook VirtualQueryEx
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("kernel32.dll")),
"VirtualQueryEx");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeVirtualQueryEx@16");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// hook WriteProcessMemory
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("kernel32.dll")),
"WriteProcessMemory");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeWriteProcessMemory@20");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// hook NtAllocateVirtualMemory
// from here i start to hook the ntdll directly
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("ntdll.dll")),
"NtAllocateVirtualMemory");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeNtAllocateVirtualMemory@24");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// hook NtCreateThread
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("ntdll.dll")),
"NtCreateThread");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeNtCreateThread@32");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// hook ZwDebugContinue
//
Addr = (ULONG_PTR) GetProcAddress(
GetModuleHandle(_T("ntdll.dll")),
"ZwDebugContinue");
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr,
sizeof (BYTE), NULL);
Addr++;
Rva = GetExportRva(Buffer, "_FakeZwDebugContinue@12");
HookVA = BaseAddress + Rva;
WriteProcessMemory(hProcess, (LPVOID) Addr, &HookVA,
sizeof (ULONG_PTR), NULL);
Addr += sizeof (ULONG_PTR);
WriteProcessMemory(hProcess, (LPVOID) Addr, &Instr2,
sizeof (WORD), NULL);
//
// everything's hooked
//
CloseHandle(hProcess);
MessageBox(_T("The process is now AntiMida"), "AntiMida");
}
else
{
MessageBox(_T("Cannot make the process AntiMida"), "AntiMida");
}
}
If you're asking yourself why i hooked the first functions in kernel32 and the remaining in ntdll: there's no reason. I was trying if it was working and hooked kernel32 for no reason, but then i was too lazy to rewrite the code and hook ntdll (you see i'm really lazy). In fact the best way is too hook ntdll, not only because it's the direct way but also the shortest (you'll see). GetExportRva is just a small function i wrote to get the rva of an export:
ULONG_PTR GetExportRva(IN TCHAR *FileName, IN CHAR *FunctionName)
{
HMODULE hModule = LoadLibrary(FileName);
if (hModule == NULL)
return 0;
ULONG_PTR VA = (ULONG_PTR) GetProcAddress(hModule,
FunctionName);
FreeLibrary(hModule);
return (ULONG_PTR) (VA - (ULONG_PTR) hModule);
}
Very useful isn't it? Here's the code of the dll i inject:
#include <windows.h>
#include <tchar.h>
//
// driver stuff
//
#ifndef NTSTATUS
typedef LONG NTSTATUS;
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >=
0)
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif
BOOL OpenDevice(IN LPCTSTR DriverName, HANDLE * lphDevice);
#define FILE_DEVICE_ANTIMIDA 0x8000
#define CODE_RESTORE_INFO CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x800, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_READ_MEM CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x801, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_QUERY_MEM CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x802, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_WRITE_MEM CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x803, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_ALLOC_MEM CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x804, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_CREATE_THREAD CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x805, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_DBG_CONTINUE CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x806, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
//
// ZwReadVirtualMemory stuff
//
typedef struct
_Input_ZwReadVirtualMemory
{
HANDLE ProcessHandle;
PVOID BaseAddress;
PVOID Buffer;
ULONG BufferLength;
PULONG ReturnLength;
} Input_ZwReadVirtualMemory;
extern "C" __declspec(dllexport)
BOOL WINAPI FakeReadProcessMemory(HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer, DWORD nSize,
LPDWORD lpNumberOfBytesRead)
{
HANDLE hDevice;
if (OpenDevice(_T("antimida"), &hDevice) ==
FALSE)
return FALSE;
Input_ZwReadVirtualMemory Input;
Input.ProcessHandle = hProcess;
Input.BaseAddress = (PVOID) lpBaseAddress;
Input.Buffer = (PVOID) lpBuffer;
Input.BufferLength = (ULONG) nSize;
Input.ReturnLength = (PULONG) lpNumberOfBytesRead;
DWORD RetBytes;
if (!DeviceIoControl(hDevice, CODE_READ_MEM,
&Input, sizeof (Input_ZwReadVirtualMemory),
NULL, 0, &RetBytes, NULL))
{
CloseHandle(hDevice);
return FALSE;
}
CloseHandle(hDevice);
return TRUE;
}
//
// ZwQueryVirtualMemory stuff
//
typedef enum
_MEMORY_INFORMATION_CLASS {
MemoryBasicInformation,
MemoryWorkingSetList,
MemorySectionName,
MemoryBasicVlmInformation
} MEMORY_INFORMATION_CLASS;
typedef struct
_Input_ZwQueryVirtualMemory
{
HANDLE ProcessHandle;
PVOID BaseAddress;
MEMORY_INFORMATION_CLASS MemoryInformationClass;
ULONG MemoryInformationLength;
PVOID MemoryInformation;
PULONG ReturnLength;
} Input_ZwQueryVirtualMemory;
extern "C" __declspec(dllexport)
SIZE_T WINAPI FakeVirtualQueryEx(IN HANDLE hProcess,
IN LPCVOID lpAddress,
OUT PMEMORY_BASIC_INFORMATION lpBuffer,
IN SIZE_T dwLength)
{
Input_ZwQueryVirtualMemory Input;
ULONG ReturnLength;
Input.ProcessHandle = hProcess;
Input.BaseAddress = (PVOID) lpAddress;
Input.MemoryInformationClass = MemoryBasicInformation;
Input.MemoryInformationLength = (ULONG) dwLength;
Input.MemoryInformation = (PVOID) lpBuffer;
Input.ReturnLength = &ReturnLength;
HANDLE hDevice;
if (OpenDevice(_T("antimida"), &hDevice) ==
FALSE)
return FALSE;
DWORD RetBytes;
if (!DeviceIoControl(hDevice, CODE_QUERY_MEM,
&Input, sizeof (Input_ZwQueryVirtualMemory),
NULL, 0, &RetBytes, NULL))
{
CloseHandle(hDevice);
return 0;
}
CloseHandle(hDevice);
return (SIZE_T) ReturnLength;
}
//
// ZwReadVirtualMemory stuff
//
typedef struct
_Input_ZwWriteVirtualMemory
{
HANDLE ProcessHandle;
PVOID BaseAddress;
PVOID Buffer;
ULONG BufferLength;
PULONG ReturnLength;
} Input_ZwWriteVirtualMemory;
extern "C" __declspec(dllexport)
BOOL WINAPI FakeWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress,
LPVOID lpBuffer, DWORD nSize,
LPDWORD lpNumberOfBytesWritten)
{
HANDLE hDevice;
if (OpenDevice(_T("antimida"), &hDevice) ==
FALSE)
return FALSE;
//
// change memory protection
//
DWORD dwOldProtection;
if (!VirtualProtectEx(hProcess, lpBaseAddress,
nSize,
PAGE_EXECUTE_READWRITE, &dwOldProtection))
{
CloseHandle(hDevice);
return FALSE;
}
Input_ZwWriteVirtualMemory Input;
Input.ProcessHandle = hProcess;
Input.BaseAddress = (PVOID) lpBaseAddress;
Input.Buffer = (PVOID) lpBuffer;
Input.BufferLength = (ULONG) nSize;
Input.ReturnLength = (PULONG) lpNumberOfBytesWritten;
DWORD RetBytes;
BOOL bRet;
if (!DeviceIoControl(hDevice, CODE_WRITE_MEM,
&Input, sizeof (Input_ZwWriteVirtualMemory),
NULL, 0, &RetBytes, NULL))
{
bRet = FALSE;
}
CloseHandle(hDevice);
//
// restore memory protection
//
VirtualProtectEx(hProcess, lpBaseAddress, nSize,
dwOldProtection, &dwOldProtection);
return TRUE;
}
//
// ZwAllocateVirtualMemory stuff
//
typedef struct
_Input_ZwAllocateVirtualMemory
{
HANDLE ProcessHandle;
PVOID *BaseAddress;
ULONG_PTR ZeroBits;
PSIZE_T RegionSize;
ULONG AllocationType;
ULONG Protect;
} Input_ZwAllocateVirtualMemory;
extern "C" __declspec(dllexport)
NTSTATUS NTAPI FakeNtAllocateVirtualMemory(IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG_PTR ZeroBits,
IN OUT PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect)
{
Input_ZwAllocateVirtualMemory Input;
Input.ProcessHandle = ProcessHandle;
Input.BaseAddress = BaseAddress;
Input.ZeroBits = ZeroBits;
Input.RegionSize = RegionSize;
Input.AllocationType = AllocationType;
Input.Protect = Protect;
HANDLE hDevice;
if (OpenDevice(_T("antimida"), &hDevice) ==
FALSE)
return FALSE;
DWORD RetBytes;
if (!DeviceIoControl(hDevice, CODE_ALLOC_MEM,
&Input, sizeof (Input_ZwAllocateVirtualMemory),
NULL, 0, &RetBytes, NULL))
{
CloseHandle(hDevice);
// who cares? (invalid parameter)
return ((NTSTATUS)0xC000000DL);
}
CloseHandle(hDevice);
return STATUS_SUCCESS;
}
//
// ZwCreateThread stuff
//
typedef struct
_Input_ZwCreateThread
{
PHANDLE ThreadHandle;
ACCESS_MASK DesiredAccess;
PVOID ObjectAttributes;
HANDLE ProcessHandle;
PVOID ClientId;
PCONTEXT ThreadContext;
PVOID UserStack;
BOOLEAN CreateSuspended;
} Input_ZwCreateThread;
extern "C" __declspec(dllexport)
NTSTATUS NTAPI FakeNtCreateThread(OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
OUT PVOID ClientId,
IN PCONTEXT ThreadContext,
IN PVOID UserStack,
IN BOOLEAN CreateSuspended)
{
Input_ZwCreateThread Input;
Input.ThreadHandle = ThreadHandle;
Input.DesiredAccess = DesiredAccess;
Input.ObjectAttributes = ObjectAttributes;
Input.ProcessHandle = ProcessHandle;
Input.ClientId = ClientId;
Input.ThreadContext = ThreadContext;
Input.UserStack = UserStack;
Input.CreateSuspended = CreateSuspended;
HANDLE hDevice;
if (OpenDevice(_T("antimida"), &hDevice) ==
FALSE)
return FALSE;
DWORD RetBytes;
if (!DeviceIoControl(hDevice,
CODE_CREATE_THREAD,
&Input, sizeof (Input_ZwCreateThread),
NULL, 0, &RetBytes, NULL))
{
CloseHandle(hDevice);
// who cares? (invalid parameter)
return ((NTSTATUS)0xC000000DL);
}
CloseHandle(hDevice);
return STATUS_SUCCESS;
}
//
// ZwDebugContinue stuff
//
typedef struct
_Input_ZwDebugContinue
{
PVOID *A;
PVOID *B;
PVOID *C;
} Input_ZwDebugContinue;
extern "C" __declspec(dllexport)
NTSTATUS NTAPI FakeZwDebugContinue(PVOID *A, PVOID *B, PVOID *C)
{
Input_ZwDebugContinue Input;
Input.A = A;
Input.B = B;
Input.C = C;
HANDLE hDevice;
if (OpenDevice(_T("antimida"), &hDevice) ==
FALSE)
return FALSE;
DWORD RetBytes;
if (!DeviceIoControl(hDevice, CODE_DBG_CONTINUE,
&Input, sizeof (Input_ZwDebugContinue),
NULL, 0, &RetBytes, NULL))
{
CloseHandle(hDevice);
// who cares? (invalid parameter)
return ((NTSTATUS)0xC000000DL);
}
CloseHandle(hDevice);
return STATUS_SUCCESS;
}
BOOL OpenDevice(IN LPCTSTR DriverName, HANDLE * lphDevice)
{
TCHAR completeDeviceName[64];
HANDLE hDevice;
/*if ( (GetVersion() & 0xFF) >= 5 ) { wsprintf(completeDeviceName,
TEXT("\\\\.\\Global\\%s"), DriverName); } else {*/
wsprintf(completeDeviceName, TEXT("\\\\.\\%s"), DriverName);
//}
hDevice = CreateFile(completeDeviceName, GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == ((HANDLE)-1))
return FALSE;
if (lphDevice)
*lphDevice = hDevice;
else
CloseHandle(hDevice);
return TRUE;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD Reason, LPVOID lpReserved)
{
switch (Reason)
{
case DLL_PROCESS_ATTACH:
{
DisableThreadLibraryCalls((HMODULE) hModule);
break;
}
case DLL_PROCESS_DETACH:
{
return TRUE;
}
default:
{
return FALSE;
}
}
return TRUE;
}
I don't think there's something to explain about this code. As i was saying i perform in the driver the requested operations, but how? Just by calling the original service in ntoskrnl. The problem is that ntoskrnl doesn't export in his export table the services in the SDT, how is it then possible to get the right address? There's a little trick. ntoskrnl has a copy of the SDT in itself. So i wrote the code to get the address of the original services:
//
// thanks to 90210 for the code
// http://www.rootkit.com/newsread.php?newsid=176
// u saved some of my time
//
#include "stdafx.h"
#define RVATOVA(base,offset) ((PVOID)((DWORD)(base)+(DWORD)(offset)))
#define ibaseDD *(PDWORD)&ibase
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >=
0)
typedef struct {
WORD offset:12;
WORD type:4;
} IMAGE_FIXUP_ENTRY, *PIMAGE_FIXUP_ENTRY;
typedef LONG NTSTATUS;
NTSTATUS (WINAPI *pNtQuerySystemInformation)(
DWORD SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
typedef struct
_SYSTEM_MODULE_INFORMATION {//Information Class 11
ULONG Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
}SYSTEM_MODULE_INFORMATION,*PSYSTEM_MODULE_INFORMATION;
typedef struct {
DWORD dwNumberOfModules;
SYSTEM_MODULE_INFORMATION smi;
} MODULES, *PMODULES;
#define SystemModuleInformation 11
DWORD GetHeaders(PBYTE ibase,
PIMAGE_FILE_HEADER *pfh,
PIMAGE_OPTIONAL_HEADER *poh,
PIMAGE_SECTION_HEADER *psh)
{
PIMAGE_DOS_HEADER mzhead=(PIMAGE_DOS_HEADER)ibase;
if ((mzhead->e_magic!=IMAGE_DOS_SIGNATURE)
||
(ibaseDD[mzhead->e_lfanew]!=IMAGE_NT_SIGNATURE))
return FALSE;
*pfh=(PIMAGE_FILE_HEADER)&ibase[mzhead->e_lfanew];
if (((PIMAGE_NT_HEADERS)*pfh)->Signature!=IMAGE_NT_SIGNATURE)
return FALSE;
*pfh=(PIMAGE_FILE_HEADER)((PBYTE)*pfh+sizeof(IMAGE_NT_SIGNATURE));
*poh=(PIMAGE_OPTIONAL_HEADER)((PBYTE)*pfh+sizeof(IMAGE_FILE_HEADER));
if ((*poh)->Magic!=IMAGE_NT_OPTIONAL_HDR32_MAGIC)
return FALSE;
*psh=(PIMAGE_SECTION_HEADER)((PBYTE)*poh+sizeof(IMAGE_OPTIONAL_HEADER));
return TRUE;
}
DWORD FindKiServiceTable(HMODULE hModule,DWORD dwKSDT)
{
PIMAGE_FILE_HEADER pfh;
PIMAGE_OPTIONAL_HEADER poh;
PIMAGE_SECTION_HEADER psh;
PIMAGE_BASE_RELOCATION pbr;
PIMAGE_FIXUP_ENTRY pfe;
DWORD dwFixups=0,i,dwPointerRva,dwPointsToRva,dwKiServiceTable;
BOOL bFirstChunk;
GetHeaders((PBYTE)hModule,&pfh,&poh,&psh);
// loop thru relocs to speed up the search
if ((poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress)
&&
(!((pfh->Characteristics)&IMAGE_FILE_RELOCS_STRIPPED))) {
pbr=(PIMAGE_BASE_RELOCATION)RVATOVA(poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress,hModule);
bFirstChunk=TRUE;
// 1st IMAGE_BASE_RELOCATION.VirtualAddress of
ntoskrnl is 0
while (bFirstChunk || pbr->VirtualAddress)
{
bFirstChunk=FALSE;
pfe=(PIMAGE_FIXUP_ENTRY)((DWORD)pbr+sizeof(IMAGE_BASE_RELOCATION));
for (i=0;i<(pbr->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))>>1;i++,pfe++)
{
if (pfe->type==IMAGE_REL_BASED_HIGHLOW)
{
dwFixups++;
dwPointerRva=pbr->VirtualAddress+pfe->offset;
// DONT_RESOLVE_DLL_REFERENCES flag
means relocs aren't fixed
dwPointsToRva=*(PDWORD)((DWORD)hModule+dwPointerRva)-(DWORD)poh->ImageBase;
// does this reloc point to
KeServiceDescriptorTable.Base?
if (dwPointsToRva==dwKSDT) {
// check for mov [mem32],imm32. we
are trying to find
// "mov ds:_KeServiceDescriptorTable.Base,
offset _KiServiceTable"
// from the KiInitSystem.
if (*(PWORD)((DWORD)hModule+dwPointerRva-2)==0x05c7)
{
// should check for a reloc
presence on KiServiceTable here
// but forget it
dwKiServiceTable=*(PDWORD)((DWORD)hModule+dwPointerRva+4)-poh->ImageBase;
return dwKiServiceTable;
}
}
} else
if (pfe->type!=IMAGE_REL_BASED_ABSOLUTE)
{
// should never get here
}
}
*(PDWORD)&pbr+=pbr->SizeOfBlock;
}
}
if (!dwFixups)
{
// should never happen - nt, 2k, xp kernels have
relocation data
}
return 0;
}
BOOL SDT_GetOriginalFunctions(PRESTORE_INFO pRestoreInfo)
{
HMODULE hKernel;
DWORD dwKSDT; // rva of
KeServiceDescriptorTable
DWORD dwKiServiceTable; // rva of KiServiceTable
PMODULES pModules=(PMODULES)&pModules;
DWORD dwNeededSize,rc;
DWORD dwKernelBase,dwServices=0;
PCHAR pKernelName;
PDWORD pService;
PIMAGE_FILE_HEADER pfh;
PIMAGE_OPTIONAL_HEADER poh;
PIMAGE_SECTION_HEADER psh;
pNtQuerySystemInformation = (NTSTATUS (WINAPI *)(
DWORD, PVOID, ULONG, PULONG)) GetProcAddress(
GetModuleHandle("ntdll.dll"), "NtQuerySystemInformation");
// get system modules - ntoskrnl is always first there
rc=pNtQuerySystemInformation(SystemModuleInformation,pModules,4,&dwNeededSize);
if (rc==STATUS_INFO_LENGTH_MISMATCH) {
pModules = (PMODULES) GlobalAlloc(GPTR,dwNeededSize);
rc=pNtQuerySystemInformation(SystemModuleInformation,pModules,dwNeededSize,NULL);
} else {
strange:
return FALSE;
}
if (!NT_SUCCESS(rc)) goto
strange;
// imagebase
dwKernelBase=(DWORD)pModules->smi.Base;
// filename - it may be renamed in the boot.ini
pKernelName=pModules->smi.ModuleNameOffset+pModules->smi.ImageName;
// map ntoskrnl - hopefully it has relocs
hKernel=LoadLibraryEx(pKernelName,0,DONT_RESOLVE_DLL_REFERENCES);
if (!hKernel) {
return FALSE;
}
GlobalFree(pModules);
// our own export walker is useless here - we have
GetProcAddress :)
if (!(dwKSDT=(DWORD)GetProcAddress(hKernel,"KeServiceDescriptorTable")))
{
return FALSE;
}
// get KeServiceDescriptorTable rva
dwKSDT-=(DWORD)hKernel;
// find KiServiceTable
if (!(dwKiServiceTable=FindKiServiceTable(hKernel,dwKSDT)))
{
return FALSE;
}
// let's dump KiServiceTable contents
// MAY FAIL!!!
// should get right ServiceLimit here, but this is
trivial in the kernel mode
GetHeaders((PBYTE) hKernel,&pfh,&poh,&psh);
//
// our code
//
//
// get ZwAllocateVirtualMemory
//
DWORD nServ = GrabService("ZwAllocateVirtualMemory");
pService = (PDWORD)((nServ * sizeof (DWORD)) +
(DWORD) hKernel + dwKiServiceTable);
pRestoreInfo->ZwAllocateVirtualMemory =
(*pService-poh->ImageBase + dwKernelBase);
//
// get ZwCreateThread
//
nServ = GrabService("ZwCreateThread");
pService = (PDWORD)((nServ * sizeof (DWORD)) +
(DWORD) hKernel + dwKiServiceTable);
pRestoreInfo->ZwCreateThread =
(*pService-poh->ImageBase + dwKernelBase);
//
// get ZwDebugContinue
//
nServ = GrabService("ZwDebugContinue");
pService = (PDWORD)((nServ * sizeof (DWORD)) +
(DWORD) hKernel + dwKiServiceTable);
pRestoreInfo->ZwDebugContinue =
(*pService-poh->ImageBase + dwKernelBase);
//
// get ZwQueryVirtualMemory
//
nServ = GrabService("ZwQueryVirtualMemory");
pService = (PDWORD)((nServ * sizeof (DWORD)) +
(DWORD) hKernel + dwKiServiceTable);
pRestoreInfo->ZwQueryVirtualMemory =
(*pService-poh->ImageBase + dwKernelBase);
//
// get ZwReadVirtualMemory
//
nServ = GrabService("ZwReadVirtualMemory");
pService = (PDWORD)((nServ * sizeof (DWORD)) +
(DWORD) hKernel + dwKiServiceTable);
pRestoreInfo->ZwReadVirtualMemory =
(*pService-poh->ImageBase + dwKernelBase);
//
// get ZwTerminateProcess
//
nServ = GrabService("ZwTerminateProcess");
pService = (PDWORD)((nServ * sizeof (DWORD)) +
(DWORD) hKernel + dwKiServiceTable);
pRestoreInfo->ZwTerminateProcess =
(*pService-poh->ImageBase + dwKernelBase);
//
// get ZwWriteVirtualMemory
//
nServ = GrabService("ZwWriteVirtualMemory");
pService = (PDWORD)((nServ * sizeof (DWORD)) +
(DWORD) hKernel + dwKiServiceTable);
pRestoreInfo->ZwWriteVirtualMemory =
(*pService-poh->ImageBase + dwKernelBase);
FreeLibrary(hKernel);
}
The GrabService function is based on another little trick to get the SDT entry. In fact SDT entries change every version of windows, so the best way to get the right entry is read it from the ntdll. Look at the NtReadVirtualMemory:
.text:7C91E2BB public ZwReadVirtualMemory
.text:7C91E2BB B8 BA 00 00 00 mov eax, 0BAh ; this is the SDT entry
.text:7C91E2C0 BA 00 03 FE 7F mov edx, 7FFE0300h
.text:7C91E2C5 FF 12 call dword ptr [edx]
.text:7C91E2C7 C2 14 00 retn 14h
So i wrote this function to get the right SDT entry:
//
// thanks to gareth for the idea
// http://www.rootkit.com/newsread.php?newsid=248
//
#include "stdafx.h"
DWORD GrabService(IN CHAR *FunctionName)
{
DWORD Exp = (DWORD) GetProcAddress(
GetModuleHandle(_T("ntdll.dll")), FunctionName);
Exp++; // mov opcode
DWORD *ptr = (DWORD *) Exp;
return *ptr;
}
So i'm collecting the information i send to the driver (restore info), i also send him the original bytes of KeAttachProcess, i don't use it anymore but it could be useful to have it restored:
#include "stdafx.h"
ULONG_PTR RvaToOffset(IMAGE_NT_HEADERS *, ULONG_PTR);
BOOL GetExport(BYTE *, ULONG_PTR *, WORD *, CHAR *);
BOOL GetNtoskrnlOriginalBytes(PRESTORE_INFO pRestoreInfo)
{
TCHAR Buffer[MAX_PATH];
GetSystemDirectory(Buffer, MAX_PATH);
_tcscat(Buffer, _T("\\ntoskrnl.exe"));
HANDLE hFile = CreateFile(Buffer, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return FALSE;
DWORD FileSize = GetFileSize(hFile, NULL);
BYTE *ptrNtoskrnl = (BYTE *) VirtualAlloc(NULL, FileSize,
MEM_COMMIT, PAGE_READWRITE);
if (ptrNtoskrnl == NULL)
{
CloseHandle(hFile);
return FALSE;
}
DWORD BR;
if (!ReadFile(hFile, ptrNtoskrnl, FileSize, &BR,
NULL))
{
VirtualFree(ptrNtoskrnl, 0, MEM_RELEASE);
CloseHandle(hFile);
return FALSE;
}
CloseHandle(hFile);
IMAGE_DOS_HEADER *ImgDosHdr = (IMAGE_DOS_HEADER *) ptrNtoskrnl;
IMAGE_NT_HEADERS *ImgNtHdrs = (IMAGE_NT_HEADERS *)
&ptrNtoskrnl[ImgDosHdr->e_lfanew];
ULONG_PTR EP_Rva = 0;
if (!GetExport(ptrNtoskrnl, &EP_Rva, NULL, "KeAttachProcess"))
{
VirtualFree(ptrNtoskrnl, 0, MEM_RELEASE);
return FALSE;
}
BYTE *ptr = (BYTE *) (EP_Rva + (ULONG_PTR) ptrNtoskrnl);
memcpy(pRestoreInfo->KeAttachProcessPatch, ptr, 5);
VirtualFree(ptrNtoskrnl, 0, MEM_RELEASE);
return TRUE;
}
BOOL CollectInformation(PRESTORE_INFO pRestoreInfo)
{
if (SDT_GetOriginalFunctions(pRestoreInfo) ==
FALSE)
return FALSE;
return GetNtoskrnlOriginalBytes(pRestoreInfo);
}
The GetExport function is a function i already had, very useful, gets the information you request of an export:
BOOL GetExport(BYTE *PE, ULONG_PTR *EP, WORD *Ordinal, CHAR *FuncName)
{
IMAGE_DOS_HEADER *ET_DOS;
IMAGE_NT_HEADERS *ET_NT;
IMAGE_EXPORT_DIRECTORY *Export;
ULONG_PTR ET, *Functions;
PSTR *Names;
WORD *Ordinals;
CHAR *ApiName;
__try
{
ET_DOS = (IMAGE_DOS_HEADER *)(ULONG_PTR) PE;
ET_NT = (IMAGE_NT_HEADERS *)(ULONG_PTR) &PE[ET_DOS->e_lfanew];
ET = ET_NT->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (ET == NULL)
return FALSE;
ET = RvaToOffset(ET_NT, ET);
if (ET == 0) return
FALSE;
Export = (IMAGE_EXPORT_DIRECTORY *)(ET + (ULONG_PTR) PE);
Functions = (ULONG_PTR *)(RvaToOffset(ET_NT,
Export->AddressOfFunctions) + (ULONG_PTR) PE);
Ordinals = (WORD *)(RvaToOffset(ET_NT,
Export->AddressOfNameOrdinals) + (ULONG_PTR) PE);
Names = (PSTR *)(RvaToOffset(ET_NT,
Export->AddressOfNames) + (ULONG_PTR) PE);
if (EP != NULL && *EP != 0)
{
for (WORD x = 0; x < Export->NumberOfFunctions;
x++)
{
if (*EP == Functions[x])
{
if (Ordinal) *Ordinal = (WORD) (x +
Export->Base);
if (FuncName != NULL)
{
for (WORD i = 0; i < Export->NumberOfNames;
i++)
{
if (Ordinals[i] == x)
{
ApiName = (char *)
RvaToOffset(ET_NT, (ULONG_PTR) Names[i]);
if (ApiName != NULL)
{
ApiName = (char *)((ULONG_PTR)
ApiName +
(ULONG_PTR) PE);
strcpy(FuncName, ApiName);
break;
}
}
}
}
return TRUE;
}
}
return FALSE;
}
else
{
if (FuncName == NULL || FuncName[0] == 0)
{
if (Ordinal == NULL || *Ordinal == 0)
return FALSE;
if (*Ordinal < Export->Base ||
*Ordinal > (Export->Base + (Export->NumberOfFunctions - 1)))
return FALSE;
WORD FuncEntry = (WORD) (*Ordinal - Export->Base);
if (EP) *EP = Functions[FuncEntry];
if (FuncName != NULL)
{
for (DWORD i = 0; i < Export->NumberOfNames;
i++)
{
if (Ordinals[i] == FuncEntry)
{
ApiName = (char *) RvaToOffset(ET_NT,
(ULONG_PTR) Names[i]);
if (ApiName != NULL)
{
ApiName = (char *)((ULONG_PTR)
ApiName + (ULONG_PTR) PE);
strcpy(FuncName, ApiName);
break;
}
}
}
}
return TRUE;
}
else
{
for (DWORD x = 0; x < Export->NumberOfFunctions;
x++)
{
if (Functions[x] == 0)
continue;
for (DWORD i = 0; i < Export->NumberOfNames;
i++)
{
if (Ordinals[i] == x)
{
ApiName = (char *) RvaToOffset(ET_NT,
(ULONG_PTR) Names[i]);
if (ApiName != NULL)
{
ApiName = (char *)((ULONG_PTR)
ApiName + (ULONG_PTR) PE);
if (strcmp(ApiName,
FuncName) == 0)
{
if (Ordinal) *Ordinal =
(WORD) (x + Export->Base);
if (EP) *EP = Functions[x];
return TRUE;
}
}
}
}
}
return FALSE;
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return FALSE;
}
return FALSE;
}
After collecting the information i send it to the driver through DeviceIoControl and he receives and processes the information:
NTSTATUS ControlDispatcher(PDEVICE_CONTEXT pDeviceContext, DWORD dwCode,
BYTE *pInput, DWORD dwInputSize,
BYTE *pOutput, DWORD dwOutputSize, DWORD *pdwInfo)
{
switch (dwCode)
{
case CODE_RESTORE_INFO:
{
RtlCopyMemory(&RestoreInfo, pInput, sizeof
(RESTORE_INFO));
pZwReadVirtualMemory = (NTSTATUS (*)(HANDLE,
PVOID, PVOID, ULONG, PULONG))
RestoreInfo.ZwReadVirtualMemory;
pZwQueryVirtualMemory = (NTSTATUS (*)(HANDLE,
PVOID, MEMORY_INFORMATION_CLASS, PVOID, ULONG,
PULONG)) RestoreInfo.ZwQueryVirtualMemory;
pZwWriteVirtualMemory = (NTSTATUS (*)(HANDLE, PVOID,
PVOID, ULONG, PULONG))
RestoreInfo.ZwWriteVirtualMemory;
pZwAllocateVirtualMemory = (NTSTATUS (*)(HANDLE,
PVOID *, ULONG_PTR, PSIZE_T, ULONG, ULONG))
RestoreInfo.ZwAllocateVirtualMemory;
pZwCreateThread = (NTSTATUS (*)(PHANDLE, ACCESS_MASK,
POBJECT_ATTRIBUTES, HANDLE, PCLIENT_ID,
PCONTEXT, PUSER_STACK, BOOLEAN))
RestoreInfo.ZwCreateThread;
pZwDebugContinue = (NTSTATUS (*)(PVOID *A, PVOID *B, PVOID *C))
RestoreInfo.ZwDebugContinue;
RebuildNtoskrnl();
break;
}
RebuildNtoskrnl function patches the replaced bytes of KeAttachProcess:
//
// patch KeAttachProcess
//
VOID RebuildNtoskrnl()
{
PMDL Mdl;
DWORD CR0Backup;
DWORD *ptr;
BYTE *Patch;
ptr = (DWORD *)(2 + (DWORD) &KeAttachProcess);
ptr = (DWORD*) *ptr;
Patch = (BYTE *) *ptr;
Mdl = MmCreateMdl(0, (PVOID) Patch, 5);
if (Mdl == NULL)
return;
MmProbeAndLockPages(Mdl, 0, 0);
if (*Patch == 0xE9)
{
__asm
{
mov eax, cr0
mov CR0Backup, eax
and eax, 0xFFFEFFFF
mov cr0, eax
}
//
// patch
//
memcpy(Patch, RestoreInfo.KeAttachProcessPatch, 5);
__asm
{
mov eax, CR0Backup
mov cr0, eax
}
}
MmUnlockPages(Mdl);
IoFreeMdl(Mdl);
}
And that's all about the SDT. Now lets talk about monitoring, this means: regmon and filemon. TheMida doesn't start if one of these two is open. There are several ways to detect these to drivers, too many i'm afraid. Here are some of them:
1 - look for the process.
2 - look for the window (i found this trick in some packers as well).
3 - look up the drivers in the registry.
4 - look up the drivers in memory.
5 - look up the object table for the device.
6 - look up the SDT (only for regmon).
There are other ways, but these would be the most usual, i think. I tried to solve point 4 and 5 (cause i already had source code available to do that).
#include <wdm.h>
typedef struct
_MODULE_ENTRY
{
LIST_ENTRY le_mod;
ULONG unknown[4];
ULONG base;
ULONG driver_start;
ULONG unk1;
UNICODE_STRING driver_Path;
UNICODE_STRING driver_Name;
//...
} MODULE_ENTRY, *PMODULE_ENTRY;
//
// this structure was missing from valerino's code
//
typedef struct
_OBJECT_HEADER
{
ULONG PointerCount;
ULONG HandleCount;
PVOID Type;
UCHAR NameInfoOffset;
UCHAR HandleInfoOffset;
UCHAR QuotaInfoOffset;
UCHAR Flags;
PVOID ObjectCreateInfo;
PVOID SecurityDescriptor;
struct _QUAD Body;
}OBJECT_HEADER, * POBJECT_HEADER;
// valerino's code
#define NUMBER_HASH_BUCKETS 37
typedef struct
_OBJECT_DIRECTORY_ENTRY {
struct _OBJECT_DIRECTORY_ENTRY *ChainLink;
PVOID Object;
} OBJECT_DIRECTORY_ENTRY, *POBJECT_DIRECTORY_ENTRY;
typedef struct
_OBJECT_DIRECTORY {
struct _OBJECT_DIRECTORY_ENTRY *HashBuckets[
NUMBER_HASH_BUCKETS ];
struct _OBJECT_DIRECTORY_ENTRY **LookupBucket;
BOOLEAN LookupFound;
USHORT SymbolicLinkUsageCount;
struct _DEVICE_MAP *DeviceMap;
} OBJECT_DIRECTORY, *POBJECT_DIRECTORY;
typedef struct
_DEVICE_MAP {
ULONG ReferenceCount;
POBJECT_DIRECTORY DosDevicesDirectory;
ULONG DriveMap;
UCHAR DriveType[ 32 ];
} DEVICE_MAP, *PDEVICE_MAP;
typedef struct
_OBJECT_HEADER_NAME_INFO {
POBJECT_DIRECTORY Directory;
UNICODE_STRING Name;
ULONG Reserved;
} OBJECT_HEADER_NAME_INFO, *POBJECT_HEADER_NAME_INFO;
#define OBJECT_TO_OBJECT_HEADER( o ) \
CONTAINING_RECORD( (o), OBJECT_HEADER, Body )
#define OBJECT_HEADER_TO_NAME_INFO( oh )
((POBJECT_HEADER_NAME_INFO) \
((oh)->NameInfoOffset == 0 ? NULL : ((PCHAR)(oh) - (oh)->NameInfoOffset)))
NTSTATUS ObOpenObjectByName (IN POBJECT_ATTRIBUTES ObjectAttributes,
IN POBJECT_TYPE ObjectType OPTIONAL, IN KPROCESSOR_MODE AccessMode,
IN OUT PACCESS_STATE AccessState OPTIONAL, IN ACCESS_MASK DesiredAccess
OPTIONAL,
IN OUT PVOID ParseContext OPTIONAL, OUT PHANDLE Handle);
// end
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath);
VOID Unload(IN PDRIVER_OBJECT DriverObject);
VOID StealthInitializeLateMore(VOID);
VOID HideModule(PDRIVER_OBJECT DriverObject);
#pragma code_seg("INIT")
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
PDEVICE_OBJECT pDeviceObject = NULL;
NTSTATUS Status = IoCreateDevice(DriverObject, 0, NULL,
FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
if (NT_SUCCESS(Status))
{
DriverObject->DriverUnload = Unload;
HideModule(DriverObject);
StealthInitializeLateMore();
}
return Status;
}
#pragma code_seg()
VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
IoDeleteDevice(DriverObject->DeviceObject);
}
// hide the module from the PsLoadedModuleList
VOID HideModule(PDRIVER_OBJECT DriverObject)
{
PMODULE_ENTRY pCurrentModule = NULL;
// pointer to PsLoadedModuleList
pCurrentModule = ((PMODULE_ENTRY)(ULONG_PTR) DriverObject->DriverSection);
if (pCurrentModule == NULL)
return;
// start to scan to find our driver
while (TRUE)
{
pCurrentModule = (PMODULE_ENTRY) pCurrentModule->le_mod.Flink;
if (pCurrentModule == NULL)
return;
if (pCurrentModule->driver_Name.Length > 3 &&
pCurrentModule->driver_Name.Buffer)
{
if (wcscmp(pCurrentModule->driver_Name.Buffer,
L"hide.sys") == 0)
{
// unlink our driver
pCurrentModule->le_mod.Blink->Flink = pCurrentModule->le_mod.Flink;
pCurrentModule->le_mod.Flink->Blink = pCurrentModule->le_mod.Blink;
break;
}
}
}
}
// thanks to valerino for this code
// article on rootkit
//************************************************************************
// VOID HideFromObjectDirectory()
//
// Hide driver from object directory
//************************************************************************/
VOID StealthInitializeLateMore(VOID)
{
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING ucName;
NTSTATUS Status;
HANDLE hDirectory = NULL;
POBJECT_DIRECTORY pDirectoryObject = NULL;
KIRQL OldIrql;
POBJECT_HEADER ObjectHeader;
POBJECT_HEADER_NAME_INFO NameInfo;
POBJECT_DIRECTORY_ENTRY DirectoryEntry;
POBJECT_DIRECTORY_ENTRY DirectoryEntryNext;
POBJECT_DIRECTORY_ENTRY DirectoryEntryTop;
ULONG Bucket = 0;
UNICODE_STRING ObjectName;
BOOLEAN found = FALSE;
// open driver directory in the object directory
RtlInitUnicodeString(&ucName,L"\\Driver");
InitializeObjectAttributes(&ObjectAttributes,&ucName,OBJ_CASE_INSENSITIVE,NULL,NULL);
Status = ObOpenObjectByName(&ObjectAttributes,NULL,KernelMode,NULL,
0x80000000,NULL,&hDirectory);
if (!NT_SUCCESS (Status))
goto __exit;
// get pointer from handle
Status = ObReferenceObjectByHandle(hDirectory,FILE_ANY_ACCESS,NULL,
KernelMode,&pDirectoryObject, NULL);
if (!NT_SUCCESS (Status))
goto __exit;
// we raise the irql too to protect the list from
being accessed by kernel APC
KeRaiseIrql(APC_LEVEL,&OldIrql);
// walk the object directory
for (Bucket=0; Bucket<NUMBER_HASH_BUCKETS;
Bucket++)
{
// are we done yet ?
if (found)
break;
DirectoryEntry = pDirectoryObject->HashBuckets[Bucket];
if (!DirectoryEntry)
continue;
// check if we're at the top of a bucket
ObjectHeader = OBJECT_TO_OBJECT_HEADER( DirectoryEntry->Object );
NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
if (NameInfo != NULL)
{
ObjectName = NameInfo->Name;
// here you compare the name of the object
with the one of your driver (ex: ROOTKIT)
// this function is just my extension to
wcsstr, just forget about it .......
if (wcscmp(ObjectName.Buffer, L"hide")
== 0)
{
// get top and next pointers
DirectoryEntryTop = pDirectoryObject->HashBuckets[Bucket];
DirectoryEntryNext = DirectoryEntryTop->ChainLink;
// substitute top
pDirectoryObject->HashBuckets[Bucket] = DirectoryEntryNext;
DirectoryEntryTop = pDirectoryObject->HashBuckets[Bucket];
// walk the chain and shift back the
entries by one place
while (DirectoryEntryNext)
{
DirectoryEntryTop->ChainLink = DirectoryEntryNext->ChainLink;
DirectoryEntryTop = DirectoryEntryTop->ChainLink;
DirectoryEntryNext = DirectoryEntryNext->ChainLink;
}
if (DirectoryEntryTop)
DirectoryEntryTop->ChainLink = NULL;
found = TRUE;
// we can exit safely
break;
}
}
// if we're not at top of a bucket, check the
entry->next fields
// for every entry, we check the next
DirectoryEntryNext = DirectoryEntry->ChainLink;
while (DirectoryEntryNext)
{
ObjectHeader = OBJECT_TO_OBJECT_HEADER( DirectoryEntryNext->Object
);
NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
if (NameInfo != NULL)
{
ObjectName = NameInfo->Name;
if (wcscmp(ObjectName.Buffer, L"hide")
== 0)
{
// found our object, now we must
unlink it, this time is easy
DirectoryEntry->ChainLink = DirectoryEntryNext->ChainLink;
found = TRUE;
// exit
break;
}
}
// walk the next entry if any
if (DirectoryEntry)
{
DirectoryEntry = DirectoryEntry->ChainLink;
DirectoryEntryNext = DirectoryEntry->ChainLink;
}
else
{
DirectoryEntryNext = NULL;
}
}
}
// adjust back the irql
KeLowerIrql(OldIrql);
__exit:
// dereference and cleanup
if (pDirectoryObject)
ObDereferenceObject(pDirectoryObject);
if (hDirectory)
ZwClose (hDirectory);
return;
}
of course it's necessary to replace the hide in the code with the actual names. Anyway it seems that TheMida looks at least in the PsLoadedModuleList, but he checks other things too (i think he looks in the registry). I don't have time to try, i'll find out soon (just hiding the keys). Anyway i tried to see if he checks if the SDT is hooked (using the code of regmon to hook one entry), and apparentely he doesn't, he starts correctly. I could also implement a small monitor of registry and files in my tool (it's a possibility). Of course i wouldn't code a file system filter, just hook the SDT like regmon (it's easier). Lets see. For the moment, here's the driver code (with the SDT hook code, which could be extended to actually monitor things).
#include <ntddk.h>
#include <common.h>
WCHAR DeviceName[] = L"\\Device\\antimida";
WCHAR SymLinkName[] = L"\\DosDevices\\antimida";
UNICODE_STRING usDeviceName;
UNICODE_STRING usSymbolicLinkName;
typedef struct
_DEVICE_CONTEXT
{
PDRIVER_OBJECT pDriverObject;
PDEVICE_OBJECT pDeviceObject;
}
DEVICE_CONTEXT, *PDEVICE_CONTEXT, **PPDEVICE_CONTEXT;
PDEVICE_OBJECT g_pDeviceObject = NULL;
PDEVICE_CONTEXT g_pDeviceContext = NULL;
#define FILE_DEVICE_ANTIMIDA 0x8000
#define CODE_RESTORE_INFO CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x800, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_READ_MEM CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x801, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_QUERY_MEM CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x802, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_WRITE_MEM CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x803, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_ALLOC_MEM CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x804, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_CREATE_THREAD CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x805, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CODE_DBG_CONTINUE CTL_CODE(FILE_DEVICE_ANTIMIDA,
0x806, \
METHOD_BUFFERED, FILE_ANY_ACCESS)
NTSTATUS DriverInitialize(PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pusRegistryPath);
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pusRegistryPath);
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverInitialize)
#pragma alloc_text (INIT, DriverEntry)
#endif
VOID NTAPI KeAttachProcess(IN PEPROCESS);
//
// ZwReadVirtualMemory stuff
//
typedef struct
_Input_ZwReadVirtualMemory
{
HANDLE ProcessHandle;
PVOID BaseAddress;
PVOID Buffer;
ULONG BufferLength;
PULONG ReturnLength;
} Input_ZwReadVirtualMemory;
NTSTATUS (*pZwReadVirtualMemory)(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
OUT PVOID Buffer,
IN ULONG BufferLength,
OUT PULONG ReturnLength OPTIONAL);
KMUTEX DumpMutex;
//
// ZwQueryVirtualMemory stuff
//
typedef enum
_MEMORY_INFORMATION_CLASS {
MemoryBasicInformation,
MemoryWorkingSetList,
MemorySectionName,
MemoryBasicVlmInformation
} MEMORY_INFORMATION_CLASS;
NTSTATUS (*pZwQueryVirtualMemory)(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
OUT PVOID MemoryInformation,
IN ULONG MemoryInformationLength,
OUT PULONG ReturnLength OPTIONAL);
typedef struct
_Input_ZwQueryVirtualMemory
{
HANDLE ProcessHandle;
PVOID BaseAddress;
MEMORY_INFORMATION_CLASS MemoryInformationClass;
ULONG MemoryInformationLength;
PVOID MemoryInformation;
PULONG ReturnLength;
} Input_ZwQueryVirtualMemory;
//
// ZwWriteVirtualMemory stuff
//
NTSTATUS (*pZwWriteVirtualMemory)(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN PVOID Buffer,
IN ULONG BufferLength,
OUT PULONG ReturnLength OPTIONAL);
typedef struct
_Input_ZwWriteVirtualMemory
{
HANDLE ProcessHandle;
PVOID BaseAddress;
PVOID Buffer;
ULONG BufferLength;
PULONG ReturnLength;
} Input_ZwWriteVirtualMemory;
//
// ZwAllocateVirtualMemory stuff
//
NTSTATUS (*pZwAllocateVirtualMemory)(IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG_PTR ZeroBits,
IN OUT PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect);
typedef struct
_Input_ZwAllocateVirtualMemory
{
HANDLE ProcessHandle;
PVOID *BaseAddress;
ULONG_PTR ZeroBits;
PSIZE_T RegionSize;
ULONG AllocationType;
ULONG Protect;
} Input_ZwAllocateVirtualMemory;
//
// ZwCreateThread stuff
//
typedef struct
_USER_STACK {
PVOID FixedStackBase;
PVOID FixedStackLimit;
PVOID ExpandableStackBase;
PVOID ExpandableStackLimit;
PVOID ExpandableStackBottom;
} USER_STACK, *PUSER_STACK;
NTSTATUS (*pZwCreateThread)(OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN HANDLE ProcessHandle,
OUT PCLIENT_ID ClientId,
IN PCONTEXT ThreadContext,
IN PUSER_STACK UserStack,
IN BOOLEAN CreateSuspended);
typedef struct
_Input_ZwCreateThread
{
PHANDLE ThreadHandle;
ACCESS_MASK DesiredAccess;
POBJECT_ATTRIBUTES ObjectAttributes;
HANDLE ProcessHandle;
PCLIENT_ID ClientId;
PCONTEXT ThreadContext;
PUSER_STACK UserStack;
BOOLEAN CreateSuspended;
} Input_ZwCreateThread;
//
// ZwDebugContinue stuff
// i was too lazy to find the correct declaration
//
NTSTATUS (*pZwDebugContinue)(PVOID *A, PVOID *B, PVOID *C);
typedef struct
_Input_ZwDebugContinue
{
PVOID *A;
PVOID *B;
PVOID *C;
} Input_ZwDebugContinue;
//
// restore info struct
//
typedef struct
_RESTORE_INFO
{
DWORD ZwAllocateVirtualMemory;
DWORD ZwCreateThread;
DWORD ZwDebugContinue;
DWORD ZwQueryVirtualMemory;
DWORD ZwReadVirtualMemory;
DWORD ZwTerminateProcess;
DWORD ZwWriteVirtualMemory;
BYTE KeAttachProcessPatch[5];
} RESTORE_INFO, *PRESTORE_INFO;
RESTORE_INFO RestoreInfo;
//
// SDT hook stuff
//
extern PSERVICE_DESCRIPTOR_TABLE
KeServiceDescriptorTable;
PVOID *KeServiceTablePointers;
PMDL KeServiceTableMdl;
BOOLEAN *ServiceIsHooked;
PPVOID MapServiceTable(BOOLEAN **);
VOID UnmapServiceTable(PVOID);
#ifdef ALPHA
#define FUNCTION_PTR(_Function) (*(PULONG)
_Function) & 0x0000FFFF
#else
#define FUNCTION_PTR(_Function) *(PULONG)((PUCHAR)
_Function + 1)
#endif
#define HOOK_SYSCALL(_Function, _Hook, _Orig) \
if (!ServiceIsHooked[FUNCTION_PTR(_Function)])
{ \
_Orig = (PVOID) InterlockedExchange((PLONG) \
&KeServiceTablePointers[FUNCTION_PTR(_Function)], (LONG) _Hook ); \
ServiceIsHooked[ FUNCTION_PTR(_Function) ] = TRUE; }
#define UNHOOK_SYSCALL(_Function, _Hook, _Orig) \
if (ServiceIsHooked[FUNCTION_PTR(_Function)]
&& \
KeServiceTablePointers[FUNCTION_PTR(_Function) ] == (PVOID) _Hook ) { \
InterlockedExchange((PLONG) &KeServiceTablePointers[ \
FUNCTION_PTR(_Function)], (LONG) _Orig ); \
ServiceIsHooked[FUNCTION_PTR(_Function)] = FALSE; }
NTSTATUS (*RealZwCreateFile)(OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength
);
NTSTATUS (*RealZwOpenKey)(OUT PHANDLE, IN ACCESS_MASK, IN POBJECT_ATTRIBUTES);
NTSTATUS HookedZwCreateFile(OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength)
{
DbgPrint("%ws\n", ObjectAttributes->ObjectName->Buffer);
return RealZwCreateFile(FileHandle,
DesiredAccess, ObjectAttributes,
IoStatusBlock, AllocationSize, FileAttributes, ShareAccess,
CreateDisposition, CreateOptions, EaBuffer, EaLength);
}
NTSTATUS HookedZwOpenKey(OUT PHANDLE KeyHandle, IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes)
{
return RealZwOpenKey(KeyHandle, DesiredAccess,
ObjectAttributes);
}
PPVOID MapServiceTable(BOOLEAN **ServiceIsHooked)
{
PVOID Mem;
Mem = ExAllocatePoolWithTag(0,
KeServiceDescriptorTable->ntoskrnl.ServiceLimit,
0x206B6444);
if (Mem == NULL)
return NULL;
*ServiceIsHooked = (BOOLEAN *) Mem;
memset(Mem, 0, KeServiceDescriptorTable->ntoskrnl.
ServiceLimit);
KeServiceTableMdl = MmCreateMdl(NULL,
KeServiceDescriptorTable->ntoskrnl.ServiceTable,
(KeServiceDescriptorTable->ntoskrnl.ServiceLimit *
sizeof (POINTER)));
if (KeServiceTableMdl == NULL)
return NULL;
MmBuildMdlForNonPagedPool(KeServiceTableMdl);
return (PPVOID) MmMapLockedPages(KeServiceTableMdl,
0);
}
VOID UnmapServiceTable(PVOID KeServiceTablePointers)
{
if (KeServiceTableMdl == NULL)
return;
MmUnmapLockedPages(KeServiceTablePointers,
KeServiceTableMdl);
ExFreePool(KeServiceTableMdl);
}
//
// patch KeAttachProcess
//
VOID RebuildNtoskrnl()
{
PMDL Mdl;
DWORD CR0Backup;
DWORD *ptr;
BYTE *Patch;
ptr = (DWORD *)(2 + (DWORD) &KeAttachProcess);
ptr = (DWORD*) *ptr;
Patch = (BYTE *) *ptr;
Mdl = MmCreateMdl(0, (PVOID) Patch, 5);
if (Mdl == NULL)
return;
MmProbeAndLockPages(Mdl, 0, 0);
if (*Patch == 0xE9)
{
__asm
{
mov eax, cr0
mov CR0Backup, eax
and eax, 0xFFFEFFFF
mov cr0, eax
}
//
// patch
//
memcpy(Patch, RestoreInfo.KeAttachProcessPatch, 5);
__asm
{
mov eax, CR0Backup
mov cr0, eax
}
}
MmUnlockPages(Mdl);
IoFreeMdl(Mdl);
}
NTSTATUS ControlDispatcher(PDEVICE_CONTEXT pDeviceContext, DWORD dwCode,
BYTE *pInput, DWORD dwInputSize,
BYTE *pOutput, DWORD dwOutputSize, DWORD *pdwInfo)
{
switch (dwCode)
{
case CODE_RESTORE_INFO:
{
RtlCopyMemory(&RestoreInfo, pInput, sizeof
(RESTORE_INFO));
pZwReadVirtualMemory = (NTSTATUS (*)(HANDLE,
PVOID, PVOID, ULONG, PULONG))
RestoreInfo.ZwReadVirtualMemory;
pZwQueryVirtualMemory = (NTSTATUS (*)(HANDLE,
PVOID, MEMORY_INFORMATION_CLASS, PVOID, ULONG,
PULONG)) RestoreInfo.ZwQueryVirtualMemory;
pZwWriteVirtualMemory = (NTSTATUS (*)(HANDLE, PVOID,
PVOID, ULONG, PULONG))
RestoreInfo.ZwWriteVirtualMemory;
pZwAllocateVirtualMemory = (NTSTATUS (*)(HANDLE,
PVOID *, ULONG_PTR, PSIZE_T, ULONG, ULONG))
RestoreInfo.ZwAllocateVirtualMemory;
pZwCreateThread = (NTSTATUS (*)(PHANDLE, ACCESS_MASK,
POBJECT_ATTRIBUTES, HANDLE, PCLIENT_ID,
PCONTEXT, PUSER_STACK, BOOLEAN))
RestoreInfo.ZwCreateThread;
pZwDebugContinue = (NTSTATUS (*)(PVOID *A, PVOID *B, PVOID *C))
RestoreInfo.ZwDebugContinue;
RebuildNtoskrnl();
break;
}
case CODE_READ_MEM:
{
Input_ZwReadVirtualMemory Input;
RtlCopyMemory(&Input, pInput, sizeof (Input_ZwReadVirtualMemory));
return pZwReadVirtualMemory(Input.ProcessHandle,
Input.BaseAddress,
Input.Buffer, Input.BufferLength, Input.ReturnLength);
}
case CODE_QUERY_MEM:
{
Input_ZwQueryVirtualMemory Input;
RtlCopyMemory(&Input, pInput, sizeof (Input_ZwQueryVirtualMemory));
return pZwQueryVirtualMemory(Input.ProcessHandle,
Input.BaseAddress,
Input.MemoryInformationClass, Input.MemoryInformation,
Input.MemoryInformationLength, Input.ReturnLength);
}
case CODE_WRITE_MEM:
{
Input_ZwWriteVirtualMemory Input;
RtlCopyMemory(&Input, pInput, sizeof (Input_ZwWriteVirtualMemory));
return pZwWriteVirtualMemory(Input.ProcessHandle,
Input.BaseAddress,
Input.Buffer, Input.BufferLength, Input.ReturnLength);
}
case CODE_ALLOC_MEM:
{
Input_ZwAllocateVirtualMemory Input;
RtlCopyMemory(&Input, pInput, sizeof (Input_ZwAllocateVirtualMemory));
return pZwAllocateVirtualMemory(Input.ProcessHandle,
Input.BaseAddress,
Input.ZeroBits, Input.RegionSize, Input.AllocationType,
Input.Protect);
}
case CODE_CREATE_THREAD:
{
Input_ZwCreateThread Input;
RtlCopyMemory(&Input, pInput, sizeof (Input_ZwCreateThread));
return pZwCreateThread(Input.ThreadHandle,
Input.DesiredAccess,
Input.ObjectAttributes, Input.ProcessHandle, Input.ClientId,
Input.ThreadContext, Input.UserStack, Input.CreateSuspended);
}
case CODE_DBG_CONTINUE:
{
Input_ZwDebugContinue Input;
RtlCopyMemory(&Input, pInput, sizeof (Input_ZwDebugContinue));
return pZwDebugContinue(Input.A, Input.B,
Input.C);
}
default:
return STATUS_INVALID_PARAMETER;
}
return STATUS_SUCCESS;
}
NTSTATUS DeviceDispatcher(PDEVICE_CONTEXT pDeviceContext, PIRP pIrp)
{
PIO_STACK_LOCATION pisl;
DWORD dwInfo = 0;
NTSTATUS ns = STATUS_NOT_IMPLEMENTED;
pisl = IoGetCurrentIrpStackLocation(pIrp);
switch (pisl->MajorFunction)
{
case IRP_MJ_CREATE:
case IRP_MJ_CLEANUP:
case IRP_MJ_CLOSE:
{
ns = STATUS_SUCCESS;
break;
}
case IRP_MJ_DEVICE_CONTROL:
{
MUTEX_ACQUIRE(DumpMutex);
ns = ControlDispatcher(pDeviceContext,
pisl->Parameters.DeviceIoControl.IoControlCode,
(BYTE *) pIrp->AssociatedIrp.SystemBuffer,
pisl->Parameters.DeviceIoControl.InputBufferLength,
(BYTE *) pIrp->AssociatedIrp.SystemBuffer,
pisl->Parameters.DeviceIoControl.OutputBufferLength,
&dwInfo);
MUTEX_RELEASE(DumpMutex);
break;
}
}
pIrp->IoStatus.Status = ns;
pIrp->IoStatus.Information = dwInfo;
IoCompleteRequest (pIrp, IO_NO_INCREMENT);
return ns;
}
NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
return (pDeviceObject == g_pDeviceObject ?
DeviceDispatcher(g_pDeviceContext, pIrp)
: STATUS_INVALID_PARAMETER_1);
}
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
UNHOOK_SYSCALL(ZwCreateFile, HookedZwCreateFile, RealZwCreateFile);
UNHOOK_SYSCALL(ZwOpenKey, HookedZwOpenKey, RealZwOpenKey);
UnmapServiceTable(KeServiceTablePointers);
IoDeleteSymbolicLink(&usSymbolicLinkName);
IoDeleteDevice(pDriverObject->DeviceObject);
}
NTSTATUS DriverInitialize(PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pusRegistryPath)
{
PDEVICE_OBJECT pDeviceObject = NULL;
NTSTATUS ns = STATUS_DEVICE_CONFIGURATION_ERROR;
RtlInitUnicodeString(&usDeviceName, DeviceName);
RtlInitUnicodeString(&usSymbolicLinkName, SymLinkName);
if ((ns = IoCreateDevice(pDriverObject,
sizeof (DEVICE_CONTEXT),
&usDeviceName, FILE_DEVICE_ANTIMIDA, 0, FALSE,
&pDeviceObject)) == STATUS_SUCCESS)
{
if ((ns = IoCreateSymbolicLink(&usSymbolicLinkName,
&usDeviceName)) == STATUS_SUCCESS)
{
g_pDeviceObject = pDeviceObject;
g_pDeviceContext = pDeviceObject->DeviceExtension;
g_pDeviceContext->pDriverObject = pDriverObject;
g_pDeviceContext->pDeviceObject = pDeviceObject;
}
else
{
IoDeleteDevice(pDeviceObject);
}
}
return ns;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pusRegistryPath)
{
PDRIVER_DISPATCH *ppdd;
NTSTATUS ns = STATUS_DEVICE_CONFIGURATION_ERROR;
if ((ns = DriverInitialize(pDriverObject,
pusRegistryPath)) == STATUS_SUCCESS)
{
ppdd = pDriverObject->MajorFunction;
ppdd[IRP_MJ_CREATE ] =
ppdd[IRP_MJ_CREATE_NAMED_PIPE ] =
ppdd[IRP_MJ_CLOSE ] =
ppdd[IRP_MJ_READ ] =
ppdd[IRP_MJ_WRITE ] =
ppdd[IRP_MJ_QUERY_INFORMATION ] =
ppdd[IRP_MJ_SET_INFORMATION ] =
ppdd[IRP_MJ_QUERY_EA ] =
ppdd[IRP_MJ_SET_EA ] =
ppdd[IRP_MJ_FLUSH_BUFFERS ] =
ppdd[IRP_MJ_QUERY_VOLUME_INFORMATION] =
ppdd[IRP_MJ_SET_VOLUME_INFORMATION ] =
ppdd[IRP_MJ_DIRECTORY_CONTROL ] =
ppdd[IRP_MJ_FILE_SYSTEM_CONTROL ] =
ppdd[IRP_MJ_DEVICE_CONTROL ] =
ppdd[IRP_MJ_INTERNAL_DEVICE_CONTROL ] =
ppdd[IRP_MJ_SHUTDOWN ] =
ppdd[IRP_MJ_LOCK_CONTROL ] =
ppdd[IRP_MJ_CLEANUP ] =
ppdd[IRP_MJ_CREATE_MAILSLOT ] =
ppdd[IRP_MJ_QUERY_SECURITY ] =
ppdd[IRP_MJ_SET_SECURITY ] =
ppdd[IRP_MJ_POWER ] =
ppdd[IRP_MJ_SYSTEM_CONTROL ] =
ppdd[IRP_MJ_DEVICE_CHANGE ] =
ppdd[IRP_MJ_QUERY_QUOTA ] =
ppdd[IRP_MJ_SET_QUOTA ] =
ppdd[IRP_MJ_PNP ] = DriverDispatcher;
pDriverObject->DriverUnload = DriverUnload;
MUTEX_INIT(DumpMutex);
KeServiceTablePointers = MapServiceTable(&ServiceIsHooked);
HOOK_SYSCALL(ZwCreateFile, HookedZwCreateFile, RealZwCreateFile);
HOOK_SYSCALL(ZwOpenKey, HookedZwOpenKey, RealZwOpenKey);
}
return ns;
}
Ok, i'm tired now. There's nothing more to explain, if you have doubts, google is there for you. The continuing of the project depends on the information which is given to me. I have no intent in reversing TheMida.
Daniel Pistelli