Vlad Ioan Topan

My playground

Archive for May 2009

Listing the modules (DLLs) of the current process without API functions

with one comment

There is a simple way to walk the list of loaded modules (DLLs) of the current Windows process without calling any API functions. It also works with other processes, but it obviously involves reading the respective process’ memory space, which in turn involves using the aptly-named ReadProcessMemory function, defeating the whole purpose of not using APIs.

The PEB and the TIB

The PEB (Process Enviroment Block), briefly documented here (the page which passes as “documentation” on MSDN just… isn’t) is a structure (stored for each process in it’s own memory space) which holds various process parameters used by the OS such as the PID, a flag set if the process is being debugged, some localization information etc. The PEB is pointed to by the TIB (Thread Information Block), which is always located at FS:[0] (if that sounds like black magic, either pick up a book on assembly language and/or x86 processor architecture, or ignore that piece of information and use the code below to get to it).

One of the PEB entries is a list of, well, the loaded modules of the process. Actually, a PEB member points to a structure called LoaderData (it’s type being PEB_LDR_DATA), again “almost documented” by MS, which points to the list we’re interested in, looking something like this:

typedef struct LDR_DATA_ENTRY {
  LIST_ENTRY              InMemoryOrderModuleList;
  PVOID                   BaseAddress;
  PVOID                   EntryPoint;
  ULONG                   SizeOfImage;
  UNICODE_STRING          FullDllName;
  UNICODE_STRING          BaseDllName;
  ULONG                   Flags;
  SHORT                   LoadCount;
  SHORT                   TlsIndex;
  LIST_ENTRY              HashTableEntry;
  ULONG                   TimeDateStamp;
  } LDR_DATA_ENTRY, *PLDR_DATA_ENTRY;

It’s a linked list (the LIST_ENTRY structure contains the forward pointer called Flink) which contains serveral useful pieces of information about each module, among which it’s image base (BaseAddress), virtual address of the entrypoint (EntryPoint) (NOT the RVA you find in the PE header, but the actual computed VA) and DLL name with (FullDllName) and without (BaseDllName) the path, as UNICODE_STRINGs. The list is circularly linked and has a sentinel element with the BaseAddress member set to 0.

The list entries should be of the LDR_DATA_TABLE_ENTRY data type described by MS here, but the actual structure found in-memory doesn’t have the first member (BYTE Reserved1[2]). An alternative (and more complete) definition of the structure can be found here, but it has three LIST_ENTRY members at the beginning instead of just one.

Getting a pointer to the list

We retrieve the pointer to the list from the PEB using inline assembly from the C source code:

__declspec(naked)
PLDR_DATA_ENTRY firstLdrDataEntry() {
   __asm {
      mov eax, fs:[0x30]  // PEB
      mov eax, [eax+0x0C] // PEB_LDR_DATA
      mov eax, [eax+0x1C] // InInitializationOrderModuleList
      retn
      }
   }

There are actually three lists of modules, sorted by three different criterias; the one we’re using is sorted by the order in which the modules were loaded.

Example

Using the structure defined above and the firstLdrDataEntry function, it becomes trivial to walk the list of loaded modules:

void main() {
   PLDR_DATA_ENTRY cursor;
   cursor = firstLdrDataEntry();
   while (cursor->BaseAddress) {
      printf( "Module [%S] loaded at [%p] with entrypoint at [%p]\n",
              cursor->BaseDllName.Buffer, cursor->BaseAddress,
              cursor->EntryPoint);
      cursor = (PLDR_DATA_ENTRY)cursor->InMemoryOrderModuleList.Flink;
      }
   }

Written by vtopan

May 27, 2009 at 11:22 PM

Using NtDeleteFile from Delphi

with 2 comments

The NtDeleteFile API

The native API NtDeleteFile performs the same task as the user-mode API DeleteFile, but interrestingly enough, the user-mode API does not call the native API to perform it’s task. As explained here, normally files are deleted through calls to NtSetInformationFile. The main difference in behavior comes from the fact that NtDeleteFile does not wait for handles on the file to close before deleting it (note that if the file is “open for normal I/O or as a memory-mapped file”, it still can’t be deleted, so only read-only handles will be ignored).

Data structures

The required structures (not defined in Delphi) are UNICODE_STRING and OBJECT_ATTRIBUTES.

Steps

The steps to remove the file are:

  • convert the “DOS” path name to an “NT” path name (basically prepend “\??\” to whatever the plain path is) using RtlDosPathNameToNtPathName_U;
  • fill in the OBJECT_ATTRIBUTES structure;
  • call NtDeleteFile.

Importing the APIs

Using the afore-linked-to type definitions, we still need to import the native APIs:

function NtDeleteFile(ObjectAttributes:POBJECT_ATTRIBUTES):DWORD; stdcall; external 'ntdll.dll';
function RtlDosPathNameToNtPathName_U(DosName:PWChar; var NtName:UNICODE_STRING; DosFilePath:PPChar; NtFilePath:PUNICODE_STRING):BOOL; stdcall; external 'ntdll.dll';

Do note that this statically links the imported functions, making the whole application unable to load if the undocumented APIs are not present on the system (at least for Windows 2000 and XP they should be).

Converting DOS paths to native paths using RtlDosPathNameToNtPathName_U

Converting the DOS path name to a native path is done by the RtlDosPathNameToNtPathName_U API, which takes in a PWChar argument containing the DOS path and a pre-allocated UNICODE_STRING structure of MAX_PATH WideChars and returns True if it has successfully converted the path.

Source code

The code looks something like this:

function _DeleteFile(fileName:string):DWORD;
var
   oa:OBJECT_ATTRIBUTES;
   ws1, ws2:WideString;
   us:UNICODE_STRING;
begin
   Result := $C0000001; // STATUS_UNSUCCESSFUL, "generic" error 
   ws1 := fileName; // automatic String -> WideString conversion
   SetLength(ws2, MAX_PATH);
   us.Length := MAX_PATH;
   us.MaximumLength := MAX_PATH;
   us.Buffer := @ws2[1];
   if not RtlDosPathNameToNtPathName_U(@ws1[1], us, nil, nil)
      then Exit;
   oa.Length := SizeOf(OBJECT_ATTRIBUTES);
   oa.RootDirectory := 0;
   oa.ObjectName := @us;
   oa.Attributes := $40; // case insensitive
   oa.SecurityDescriptor := nil;
   oa.SecurityQualityOfService := nil;
   Result := NtDeleteFile(@oa); // pass on the NTSTATUS
end;

Written by vtopan

May 26, 2009 at 3:43 PM

Posted in Delphi, Snippets

Tagged with , ,