A forum for reverse engineering, OS internals and malware analysis 

Ask your beginner questions here.
 #24181  by TurlaBoy
 Tue Oct 21, 2014 1:52 pm
Hello kernelmode,

I've been using KeAttachProcess function to attach to target process's address space, it works nice when I need to read from the process memory,

And when I try to write to ntdll.dll( cr0 WP disabled ) after overwriting, then I restore CR0 WP and use KeDetachProcess to detach from the target process.

However, I found that not only the target process's ntdll.dll was overwritten, but all running process's ntdll.dll was overwritten, it looks like to me that ntdll.dll is shared and mmaped to a single physical page and linked to proper virtual addresses, so I have a couple of questions:


1 - Whats the diference between KeAttachProcess and KeStackAttachProcess ?
2 - How this function works internally? It only changes CR3?
3 - How to be sure when I write to an address it will not be paged out to disk?
4 - In my device driver, are local variables(Stack) Paged Pools?
5 - What about global vars are they located at paged pools?
6 - There is any function at kernel that allows me to know if some memory block is Paged or not?
7 - What happens when someone writes to ntdll from usermode(using writeprocessmemory), will ntdll be mmaped somewhere else in physical memory(by ntoskrnl)?
8 - How can I write to ntdll safely from kernelmode and overwrite the dll just in the current process?
 #24182  by Vrtule
 Tue Oct 21, 2014 2:33 pm
Hello,

1) KeStackAttachProcess allows you to attach "recursively"; you do not need to detach before doing another attach, howerver, you still must perform a detach for any attach you did before. It works like a stack which is why the routine got its name.
2) AFAIK it also swaps certain APC-related values in ETHREAD structure of the current thread so the PsGetCurrentProcess routine should return the process you are already attached to (but you'd better to test this). Look to WRK sources (they leaked multiple times) to find more details.
3) MmProbeAndLockPages will work well for usermode addresses. The routine locks the pages to physical memory so they cannot be paged out. Howerver, the correpsonding virtual addresses still may be deallocated.
4) The whole driver is nonpaged unless you say otherwise (MmPageEntireDriver or some compile-time pragmas and macros). The kernel stack is allocated from nonpaged memory.
5) See the previous point.
7) Windows attempts to share mapped PE images accross multiple processes via the copy-on-write mechanism. That menas, multiple processes are reading from the same physical pages. However, when one of them writes some data to a COW-protected area, it gets its private copy of the page it changed. Other processes will not see the change.
8) your problem lies in the CR0 hack you used to make read-only memory writable. When you write to ntdll.dll image in memory, no page fault occurs (because write-protection is off), hence the operating system is not notified that you are attempting to write to a COW-protected area. This results in changing the physical pages shared by multiple processes.

To solve your problem, you can use Zw/NtProtectVirtualMemory system call to change protection on pages you wish to change. Well, these routines are probably not exported, so you can find them yourself (for example by looking at their entries in the system call table). There are possibly other solutions to the problem, however, I seem unable to get to an elegant one right now.
 #24183  by TurlaBoy
 Tue Oct 21, 2014 2:43 pm
Vrtule wrote:Hello,

1) KeStackAttachProcess allows you to attach "recursively"; you do not need to detach before doing another attach, howerver, you still must perform a detach for any attach you did before. It works like a stack which is why the routine got its name.
2) AFAIK it also swaps certain APC-related values in ETHREAD structure of the current thread so the PsGetCurrentProcess routine should return the process you are already attached to (but you'd better to test this). Look to WRK sources (they leaked multiple times) to find more details.
3) MmProbeAndLockPages will work well for usermode addresses. The routine locks the pages to physical memory so they cannot be paged out. Howerver, the correpsonding virtual addresses still may be deallocated.
4) The whole driver is nonpaged unless you say otherwise (MmPageEntireDriver or some compile-time pragmas and macros). The kernel stack is allocated from nonpaged memory.
5) See the previous point.
7) Windows attempts to share mapped PE images accross multiple processes via the copy-on-write mechanism. That menas, multiple processes are reading from the same physical pages. However, when one of them writes some data to a COW-protected area, it gets its private copy of the page it changed. Other processes will not see the change.
8) your problem lies in the CR0 hack you used to make read-only memory writable. When you write to ntdll.dll image in memory, no page fault occurs (because write-protection is off), hence the operating system is not notified that you are attempting to write to a COW-protected area. This results in changing the physical pages shared by multiple processes.

To solve your problem, you can use Zw/NtProtectVirtualMemory system call to change protection on pages you wish to change. Well, these routines are probably not exported, so you can find them yourself (for example by looking at their entries in the system call table). There are possibly other solutions to the problem, however, I seem unable to get to an elegant one right now.
Thank you so much Vrtule
 #24201  by Carlbyte
 Thu Oct 23, 2014 11:05 pm
CallZwFunction("ZwProtectVirtualMemory",&status,5,(HANDLE)-1,&Addr2,&size,PAGE_EXECUTE_READWRITE,&old);

BOOLEAN CallZwFunction(CONST CHAR *FunctionName,PVOID pCallRet,ULONG ArgNum,...)
{
char *vl;
BOOLEAN retval = FALSE;
ULONG FunctionIndex;
ULONG CallRet;
ULONG Argv[20],temp;
LONG i;
ULONG FunctionAddr;
BYTE Sign[]="\xb8\x44\x00\x00\x00";
BYTE *p;

FunctionIndex = GetDllFunctionIndex(FunctionName);
if(!FunctionIndex || FunctionIndex==-1)
goto END;

FunctionAddr = 0;
*(ULONG *)(Sign+1)=FunctionIndex;
p=(BYTE *)GetSystemFunctionAddrW(L"ZwAccessCheckAndAuditAlarm");
for(i=0;i<0x2000;i++)
{
if(memcmp(p+i,Sign,5)==0)
{
FunctionAddr=(ULONG)p+i;
break;
}
}
if(!FunctionAddr)
goto END;

va_start(vl, ArgNum);
memcpy(Argv,vl,sizeof(ULONG)*ArgNum);
va_end(vl);

for(i=ArgNum-1;i>=0;i--)
{
temp=Argv;
__asm
{
push temp;
}
}
__asm
{
mov eax,FunctionAddr;
call eax;
mov CallRet,eax;
}
*(ULONG *)pCallRet = CallRet;
retval = TRUE;

END:
return retval;
}
Last edited by Carlbyte on Thu Oct 23, 2014 11:07 pm, edited 1 time in total.
 #24202  by Carlbyte
 Thu Oct 23, 2014 11:06 pm
ULONG GetDllFunctionIndex(char* lpFunctionName)
{
HANDLE hSection, hFile, hMod;
IMAGE_DOS_HEADER* dosheader;
IMAGE_OPTIONAL_HEADER* opthdr;
IMAGE_EXPORT_DIRECTORY* pExportTable;
ULONG* arrayOfFunctionAddresses;
ULONG* arrayOfFunctionNames;
USHORT* arrayOfFunctionOrdinals;
ULONG functionOrdinal;
ULONG Base, x, functionAddress;
char* functionName;
STRING ntFunctionName, ntFunctionNameSearch;
PVOID BaseAddress = NULL;
SIZE_T size=0;
OBJECT_ATTRIBUTES oa;// = {sizeof oa, 0, pDllName, OBJ_CASE_INSENSITIVE};
IO_STATUS_BLOCK iosb;
NTSTATUS status;
ULONG uIndex=-1;
UNICODE_STRING pDllName;


RtlInitUnicodeString(&pDllName,L"\\SystemRoot\\system32\\ntdll.dll");
InitializeObjectAttributes ( &oa, &pDllName, OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE, NULL, NULL);

status=ZwOpenFile(&hFile, FILE_EXECUTE | SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if(status!=STATUS_SUCCESS)
{
//FILE_SUPERSEDED
KdPrint(("ZwOpenFile Error:0x%x,0x%x",status,iosb.Information));
return 0;
}
oa.ObjectName = 0;
status=ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &oa, 0,PAGE_EXECUTE, 0x01000000, hFile);
if(status!=STATUS_SUCCESS)
{
KdPrint(("ZwCreateSection Error"));
ZwClose(hFile);
return 0;
}
ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 1000, 0, &size, (SECTION_INHERIT)1, MEM_TOP_DOWN, PAGE_READWRITE);
ZwClose(hFile);

hMod = BaseAddress;
dosheader = (IMAGE_DOS_HEADER *)hMod;
opthdr =(IMAGE_OPTIONAL_HEADER *) ((BYTE*)hMod+dosheader->e_lfanew+24);
pExportTable =(IMAGE_EXPORT_DIRECTORY*)((BYTE*) hMod + opthdr->DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT]. VirtualAddress);
// now we can get the exported functions, but note we convert from RVA to address
arrayOfFunctionAddresses = (ULONG*)( (BYTE*)hMod + pExportTable->AddressOfFunctions);
arrayOfFunctionNames = (ULONG*)( (BYTE*)hMod + pExportTable->AddressOfNames);
arrayOfFunctionOrdinals = (USHORT*)( (BYTE*)hMod + pExportTable->AddressOfNameOrdinals);
Base = pExportTable->Base;

RtlInitString(&ntFunctionNameSearch, lpFunctionName);
for(x = 0; x < pExportTable->NumberOfFunctions; x++)
{
functionName = (char*)( (BYTE*)hMod + arrayOfFunctionNames[x]);
RtlInitString(&ntFunctionName, functionName);
functionOrdinal = arrayOfFunctionOrdinals[x] + Base - 1; // always need to add base, -1 as array counts from 0
// this is the funny bit. you would expect the function pointer to simply be arrayOfFunctionAddresses[x]...
// oh no... thats too simple. it is actually arrayOfFunctionAddresses[functionOrdinal]!!

//KdPrint(("0x%x\t0x%x \n",x,functionOrdinal));
functionAddress = (ULONG)( (BYTE*)hMod + arrayOfFunctionAddresses[functionOrdinal]);
if (RtlCompareString(&ntFunctionName, &ntFunctionNameSearch, TRUE) == 0)
{
//INT3
uIndex=*(PULONG)((PUCHAR)functionAddress+1);
break;
//return functionAddress;
}
}

ZwClose(hSection);
ZwUnmapViewOfSection(NtCurrentProcess(),BaseAddress);
return uIndex;
}

PVOID GetSystemFunctionAddrW(CONST WCHAR *FunctionName)
{
UNICODE_STRING uniFunctionName;

RtlInitUnicodeString(&uniFunctionName,FunctionName);
return MmGetSystemRoutineAddress(&uniFunctionName);
}