/// <summary> /// Convert the ACL to a byte array /// </summary> /// <returns>The ACL as a byte array</returns> public byte[] ToByteArray() { AclRevision revision; byte[] aces; using (var ace_stm = new MemoryStream()) { using (var writer = new BinaryWriter(ace_stm)) { revision = Revision; if (revision != AclRevision.Revision || revision != AclRevision.RevisionDS) { revision = AclRevision.Revision; } foreach (Ace ace in this) { ace.Serialize(writer); if (ace.IsObjectAce) { revision = AclRevision.RevisionDS; } } } aces = ace_stm.ToArray(); } using (var buffer = new SafeHGlobalBuffer(Marshal.SizeOf(typeof(AclStructure)) + aces.Length)) { NtRtl.RtlCreateAcl(buffer, buffer.Length, revision).ToNtException(); NtRtl.RtlAddAce(buffer, revision, uint.MaxValue, aces, aces.Length).ToNtException(); return(buffer.ToArray()); } }
/// <summary> /// Constructor /// </summary> /// <param name="acl">Buffer containing an ACL in memory</param> /// <param name="defaulted">True if the ACL was defaulted</param> public Acl(byte[] acl, bool defaulted) { using (var buffer = new SafeHGlobalBuffer(acl)) { InitializeFromPointer(buffer.DangerousGetHandle(), defaulted); } }
public static ProcessAttribute ImageName(string image_name) { SafeHGlobalBuffer name = new SafeHGlobalBuffer(Marshal.StringToHGlobalUni(image_name), image_name.Length * 2, true); return(new ProcessAttribute(ProcessAttributeNum.ImageName, false, true, false, name)); }
/// <summary> /// Constructor /// </summary> /// <param name="security_descriptor">Binary form of security descriptor</param> public SecurityDescriptor(byte[] security_descriptor) { using (SafeHGlobalBuffer buffer = new SafeHGlobalBuffer(security_descriptor)) { ParseSecurityDescriptor(buffer); } }
/// <summary> /// Constructor from an manged buffer. /// </summary> /// <param name="sid">A buffer containing a valid SID.</param> /// <exception cref="NtException">Thrown if the buffer is not valid.</exception> public Sid(byte[] sid) { using (SafeHGlobalBuffer buffer = new SafeHGlobalBuffer(sid)) { InitializeFromPointer(buffer.DangerousGetHandle()).ToNtException(); } }
/// <summary> /// Lookup a SID from a username. /// </summary> /// <param name="username">The username, can be in the form domain\account.</param> /// <returns>The Security Identifier.</returns> /// <exception cref="NtException">Thrown if account cannot be found.</exception> public static Sid LookupAccountName(string username) { int sid_length = 0; int domain_length = 0; SidNameUse name; if (!LookupAccountName(null, username, SafeHGlobalBuffer.Null, ref sid_length, SafeHGlobalBuffer.Null, ref domain_length, out name)) { if (sid_length <= 0) { throw new NtException(NtStatus.STATUS_INVALID_USER_PRINCIPAL_NAME); } } using (SafeHGlobalBuffer buffer = new SafeHGlobalBuffer(sid_length), domain = new SafeHGlobalBuffer(domain_length * 2)) { if (!LookupAccountName(null, username, buffer, ref sid_length, domain, ref domain_length, out name)) { throw new NtException(NtStatus.STATUS_INVALID_USER_PRINCIPAL_NAME); } return(new Sid(buffer)); } }
private static IEnumerable <string> EnumNameList(SafeKernelObjectHandle handle) { int size = 522; for (int i = 0; i < 10; ++i) { using (var buffer = new SafeHGlobalBuffer(size)) { NtStatus status = NtSystemCalls.NtUserBuildNameList(handle, buffer.Length, buffer, out size); if (!status.IsSuccess()) { if (status == NtStatus.STATUS_BUFFER_TOO_SMALL) { continue; } status.ToNtException(); } int total_count = buffer.Read <int>(4); int offset = 8; while (total_count > 0) { string name = buffer.ReadNulTerminatedUnicodeString((ulong)offset); yield return(name); offset += (name.Length + 1) * 2; total_count--; } yield break; } } throw new NtException(NtStatus.STATUS_NO_MEMORY); }
/// <summary> /// Gets the SID for a service name. /// </summary> /// <param name="service_name">The service name.</param> /// <returns>The service SID.</returns> /// <exception cref="NtException">Thrown on error.</exception> public static Sid GetServiceSid(string service_name) { using (SafeHGlobalBuffer buffer = new SafeHGlobalBuffer(1024)) { int sid_length = buffer.Length; NtRtl.RtlCreateServiceSid(new UnicodeString(service_name), buffer, ref sid_length).ToNtException(); return(new Sid(buffer)); } }
/// <summary> /// Query a variable buffer from the object. /// </summary> /// <param name="info_class">The information class to query.</param> /// <param name="init_buffer">A buffer to initialize the initial query. Can be null.</param> /// <param name="throw_on_error">True to throw on error.</param> /// <returns>The result of the query.</returns> /// <exception cref="NtException">Thrown on error.</exception> public virtual NtResult <SafeHGlobalBuffer> QueryRawBuffer(Q info_class, byte[] init_buffer, bool throw_on_error) { NtStatus status; int return_length; // First try base size before trying to reallocate. using (var buffer = init_buffer.ToBuffer()) { status = QueryInformation(info_class, buffer, out return_length); if (status.IsSuccess()) { return(status.CreateResult(false, () => buffer.Detach(return_length))); } } if (!IsInvalidBufferStatus(status)) { return(status.CreateResultFromError <SafeHGlobalBuffer>(throw_on_error)); } // If the function returned a length then trust it. if (return_length > 0 && GetTrustReturnLength(info_class)) { using (var buffer = new SafeHGlobalBuffer(return_length)) { return(QueryInformation(info_class, buffer, out return_length).CreateResult(throw_on_error, () => buffer.Detach(return_length))); } } // Function length can't be trusted, we'll need to brute force it. return_length = 256; int max_length = GetMaximumBruteForceLength(info_class); while (return_length <= max_length) { using (var buffer = new SafeHGlobalBuffer(return_length)) { status = QueryInformation(info_class, buffer, out int dummy_length); if (status.IsSuccess()) { if (dummy_length > 0 && dummy_length < return_length) { return_length = dummy_length; } return(status.CreateResult(throw_on_error, () => buffer.Detach(return_length))); } else if (!IsInvalidBufferStatus(status)) { return(status.CreateResultFromError <SafeHGlobalBuffer>(throw_on_error)); } return_length *= 2; } } return(NtStatus.STATUS_BUFFER_TOO_SMALL.CreateResultFromError <SafeHGlobalBuffer>(throw_on_error)); }
internal static Ace Parse(IntPtr ace_ptr) { AceHeader header = (AceHeader)Marshal.PtrToStructure(ace_ptr, typeof(AceHeader)); using (var buffer = new SafeHGlobalBuffer(ace_ptr, header.AceSize, false)) { using (var reader = new BinaryReader(new UnmanagedMemoryStream(buffer, 0, header.AceSize))) { return(CreateAceFromReader(reader)); } } }
/// <summary> /// Get a capability group sid by name. /// </summary> /// <param name="capability_name">The name of the capability.</param> /// <returns>The capability SID.</returns> public static Sid GetCapabilityGroupSid(string capability_name) { using (SafeHGlobalBuffer cap_sid = new SafeHGlobalBuffer(Sid.MaximumSidSize), cap_group_sid = new SafeHGlobalBuffer(Sid.MaximumSidSize)) { NtRtl.RtlDeriveCapabilitySidsFromName( new UnicodeString(capability_name), cap_group_sid, cap_sid).ToNtException(); return(new Sid(cap_group_sid)); } }
private static void GetCapabilitySids(string capability_name, out Sid capability_sid, out Sid capability_group_sid) { using (SafeHGlobalBuffer cap_sid = new SafeHGlobalBuffer(Sid.MaximumSidSize), cap_group_sid = new SafeHGlobalBuffer(Sid.MaximumSidSize)) { NtRtl.RtlDeriveCapabilitySidsFromName( new UnicodeString(capability_name), cap_group_sid, cap_sid).ToNtException(); capability_sid = new Sid(cap_sid); capability_group_sid = new Sid(cap_group_sid); } }
/// <summary> /// Write memory to a process. /// </summary> /// <param name="process">The process to write to.</param> /// <param name="base_address">The base address in the process.</param> /// <param name="data">The data to write.</param> /// <returns>The number of bytes written to the location</returns> /// <exception cref="NtException">Thrown on error.</exception> public static int WriteMemory(SafeKernelObjectHandle process, long base_address, byte[] data) { using (SafeHGlobalBuffer buffer = new SafeHGlobalBuffer(data)) { NtStatus status = NtSystemCalls.NtWriteVirtualMemory(process, new IntPtr(base_address), buffer, buffer.Length, out int return_length); if (status != NtStatus.STATUS_PARTIAL_COPY) { status.ToNtException(); } return(return_length); } }
/// <summary> /// Get a list of handles /// </summary> /// <param name="pid">A process ID to filter on. If -1 will get all handles</param> /// <param name="allow_query">True to allow the handles returned to query for certain properties</param> /// <returns>The list of handles</returns> public static IEnumerable <NtHandle> GetHandles(int pid, bool allow_query) { using (SafeHGlobalBuffer handle_info = new SafeHGlobalBuffer(0x10000)) { AllocateSafeBuffer(handle_info, SystemInformationClass.SystemHandleInformation); int handle_count = handle_info.Read <Int32>(0); SystemHandleTableInfoEntry[] handles = new SystemHandleTableInfoEntry[handle_count]; handle_info.ReadArray((ulong)IntPtr.Size, handles, 0, handle_count); return(handles.Where(h => pid == -1 || h.UniqueProcessId == pid).Select(h => new NtHandle(h, allow_query))); } }
/// <summary> /// Read memory from a process. /// </summary> /// <param name="process">The process to read from.</param> /// <param name="base_address">The base address in the process.</param> /// <param name="length">The length to read.</param> /// <returns>The array of bytes read from the location. /// If a read is short then returns fewer bytes than requested.</returns> /// <exception cref="NtException">Thrown on error.</exception> public static byte[] ReadMemory(SafeKernelObjectHandle process, long base_address, int length) { using (SafeHGlobalBuffer buffer = new SafeHGlobalBuffer(length)) { int return_length; NtStatus status = NtSystemCalls.NtReadVirtualMemory(process, new IntPtr(base_address), buffer, buffer.Length, out return_length); if (status != NtStatus.STATUS_PARTIAL_COPY) { status.ToNtException(); } return(buffer.ReadBytes(return_length)); } }
private static void AllocateSafeBuffer(SafeHGlobalBuffer buffer, SystemInformationClass info_class) { NtStatus status = 0; int return_length = 0; while ((status = NtSystemCalls.NtQuerySystemInformation(info_class, buffer, buffer.Length, out return_length)) == NtStatus.STATUS_INFO_LENGTH_MISMATCH) { int length = buffer.Length * 2; buffer.Resize(length); } status.ToNtException(); }
private void ParseAcl(IntPtr acl) { var size_info = GetAclInformation <AclSizeInformation>(acl, AclInformationClass.AclSizeInformation); using (var buffer = new SafeHGlobalBuffer(acl, size_info.AclBytesInUse, false)) { using (var reader = new BinaryReader(new UnmanagedMemoryStream(buffer, 0, size_info.AclBytesInUse))) { for (int i = 0; i < size_info.AceCount; ++i) { NtRtl.RtlGetAce(acl, i, out IntPtr ace).ToNtException(); reader.BaseStream.Position = ace.ToInt64() - acl.ToInt64(); Add(Ace.CreateAceFromReader(reader)); } } } Revision = GetAclInformation <AclRevisionInformation>(acl, AclInformationClass.AclRevisionInformation).AclRevision; }
/// <summary> /// Convert the ACL to a byte array /// </summary> /// <returns>The ACL as a byte array</returns> public byte[] ToByteArray() { AclRevision revision; byte[] aces; using (var ace_stm = new MemoryStream()) { using (var writer = new BinaryWriter(ace_stm)) { revision = Revision; switch (revision) { case AclRevision.Revision: case AclRevision.RevisionCompound: case AclRevision.RevisionDS: break; default: revision = AclRevision.Revision; break; } foreach (Ace ace in this) { ace.Serialize(writer); if (ace.IsObjectAce) { revision = AclRevision.RevisionDS; } else if (ace.Type == AceType.AllowedCompound && revision < AclRevision.RevisionCompound) { revision = AclRevision.RevisionCompound; } } } aces = ace_stm.ToArray(); } using (var buffer = new SafeHGlobalBuffer(Marshal.SizeOf(typeof(AclStructure)) + aces.Length)) { NtRtl.RtlCreateAcl(buffer, buffer.Length, revision).ToNtException(); NtRtl.RtlAddAce(buffer, revision, uint.MaxValue, aces, aces.Length).ToNtException(); return(buffer.ToArray()); } }
/// <summary> /// Query a license value. While technically not directly a registry key /// it has many of the same properties such as using the same registry /// value types. /// </summary> /// <param name="name">The name of the license value.</param> /// <param name="throw_on_error">True to throw an exception on error</param> /// <returns>The license value key</returns> public static NtResult <NtKeyValue> QueryLicenseValue(string name, bool throw_on_error) { RegistryValueType type; int ret_length; UnicodeString name_string = new UnicodeString(name); NtStatus status = NtSystemCalls.NtQueryLicenseValue(name_string, out type, SafeHGlobalBuffer.Null, 0, out ret_length); if (status != NtStatus.STATUS_BUFFER_TOO_SMALL) { return(status.CreateResultFromError <NtKeyValue>(throw_on_error)); } using (var buffer = new SafeHGlobalBuffer(ret_length)) { return(NtSystemCalls.NtQueryLicenseValue(name_string, out type, buffer, buffer.Length, out ret_length) .CreateResult(throw_on_error, () => new NtKeyValue(name, type, buffer.ToArray(), 0))); } }
/// <summary> /// Get list of page filenames. /// </summary> /// <returns>The list of page file names.</returns> public static IEnumerable <string> GetPageFileNames() { using (SafeHGlobalBuffer buffer = new SafeHGlobalBuffer(0x10000)) { AllocateSafeBuffer(buffer, SystemInformationClass.SystemPageFileInformation); int offset = 0; while (true) { var pagefile_info = buffer.GetStructAtOffset <SystemPageFileInformation>(offset).Result; yield return(pagefile_info.PageFileName.ToString()); if (pagefile_info.NextEntryOffset == 0) { break; } offset += pagefile_info.NextEntryOffset; } } }
/// <summary> /// Read structured memory array from a process. /// </summary> /// <param name="process">The process to read from.</param> /// <param name="base_address">The base address in the process.</param> /// <param name="count">The number of elements in the array to read.</param> /// <returns>The read structure.</returns> /// <exception cref="NtException">Thrown on error.</exception> /// <typeparam name="T">Type of structure to read.</typeparam> public static T[] ReadMemoryArray <T>(SafeKernelObjectHandle process, long base_address, int count) where T : new() { int element_size = Marshal.SizeOf(typeof(T)); using (var buffer = new SafeHGlobalBuffer(element_size * count)) { NtSystemCalls.NtReadVirtualMemory(process, new IntPtr(base_address), buffer, buffer.Length, out int return_length).ToNtException(); if (return_length != buffer.Length) { throw new NtException(NtStatus.STATUS_PARTIAL_COPY); } T[] result = new T[count]; for (int i = 0; i < count; ++i) { int offset = i * element_size; result[i] = (T)Marshal.PtrToStructure(buffer.DangerousGetHandle() + offset, typeof(T)); } return(result); } }
/// <summary> /// Query state data for the WNF object. /// </summary> /// <param name="type_id">Optional Type ID.</param> /// <param name="explicit_scope">Optional explicit scope.</param> /// <param name="throw_on_error">True to throw on error.</param> /// <returns>The state data.</returns> public NtResult <WnfStateData> QueryStateData(WnfTypeId type_id, IntPtr explicit_scope, bool throw_on_error) { int tries = 10; int size = 4096; while (tries-- > 0) { using (var buffer = new SafeHGlobalBuffer(size)) { NtStatus status = NtSystemCalls.NtQueryWnfStateData(StateName, type_id, explicit_scope, out int changestamp, buffer, ref size); if (status == NtStatus.STATUS_BUFFER_TOO_SMALL) { continue; } return(status.CreateResult(throw_on_error, () => new WnfStateData(buffer.ReadBytes(size), changestamp))); } } return(NtStatus.STATUS_BUFFER_TOO_SMALL.CreateResultFromError <WnfStateData>(throw_on_error)); }
/// <summary> /// Get a list of handles /// </summary> /// <param name="pid">A process ID to filter on. If -1 will get all handles</param> /// <param name="allow_query">True to allow the handles returned to query for certain properties</param> /// <returns>The list of handles</returns> public static IEnumerable <NtHandle> GetHandles(int pid, bool allow_query) { SafeHGlobalBuffer handleInfo = new SafeHGlobalBuffer(0x10000); try { NtStatus status = 0; int return_length = 0; while ((status = NtSystemCalls.NtQuerySystemInformation(SystemInformationClass.SystemHandleInformation, handleInfo.DangerousGetHandle(), handleInfo.Length, out return_length)) == NtStatus.STATUS_INFO_LENGTH_MISMATCH) { int length = handleInfo.Length * 2; handleInfo.Close(); handleInfo = new SafeHGlobalBuffer(length); } status.ToNtException(); IntPtr handleInfoBuf = handleInfo.DangerousGetHandle(); int handle_count = Marshal.ReadInt32(handleInfoBuf); List <NtHandle> ret = new List <NtHandle>(); handleInfoBuf += IntPtr.Size; for (int i = 0; i < handle_count; ++i) { SystemHandleTableInfoEntry entry = (SystemHandleTableInfoEntry)Marshal.PtrToStructure(handleInfoBuf, typeof(SystemHandleTableInfoEntry)); if (pid == -1 || entry.UniqueProcessId == pid) { ret.Add(new NtHandle(entry, allow_query)); } handleInfoBuf += Marshal.SizeOf(typeof(SystemHandleTableInfoEntry)); } return(ret); } finally { handleInfo.Close(); } }
/// <summary> /// Create a Window Station by name. /// </summary> /// <param name="object_attributes">Object attributes for the Window Station.</param> /// <param name="desired_access">Desired access for the Window Station.</param> /// <param name="kbd_dll_path">Path to Keyboard DLL e.g. kbusa.dll.</param> /// <param name="keyboard_locale">Locale ID, e.g. 0x4090409.</param> /// <param name="language_id">Language ID e.g. 0x409.</param> /// <param name="throw_on_error">True to throw on error.</param> /// <returns>The Window Station.</returns> public static NtResult <NtWindowStation> Create(ObjectAttributes object_attributes, WindowStationAccessRights desired_access, string kbd_dll_path, int language_id, int keyboard_locale, bool throw_on_error) { string dll_path; IntPtr layout_offset; IntPtr nls_offset; using (var kbd_dll = SafeLoadLibraryHandle.LoadLibrary(kbd_dll_path, LoadLibraryFlags.None, throw_on_error)) { if (!kbd_dll.IsSuccess) { return(kbd_dll.Cast <NtWindowStation>()); } dll_path = kbd_dll.Result.FullPath; layout_offset = GetKdbLayoutOffset(kbd_dll.Result, 1); nls_offset = GetKdbLayoutOffset(kbd_dll.Result, 2); } using (var buffer = new SafeHGlobalBuffer(0x318)) { BufferUtils.FillBuffer(buffer, 0); using (var file = NtFile.Open(NtFileUtils.DosFileNameToNt(dll_path), null, FileAccessRights.GenericRead | FileAccessRights.Synchronize, FileShareMode.Read | FileShareMode.Delete, FileOpenOptions.NonDirectoryFile | FileOpenOptions.SynchronousIoNonAlert, throw_on_error)) { if (!file.IsSuccess) { return(file.Cast <NtWindowStation>()); } var handle = NtSystemCalls.NtUserCreateWindowStation(object_attributes, desired_access, file.Result.Handle, layout_offset, nls_offset, buffer, new UnicodeString($"{language_id:X08}"), keyboard_locale); if (handle.IsInvalid) { return(NtObjectUtils.CreateResultFromDosError <NtWindowStation>(throw_on_error)); } return(new NtWindowStation(handle).CreateResult()); } } }
private SafeHGlobalBuffer CreateRelativeSecurityDescriptor() { using (var sd_buffer = CreateAbsoluteSecurityDescriptor()) { int total_length = 0; NtStatus status = NtRtl.RtlAbsoluteToSelfRelativeSD(sd_buffer, new SafeHGlobalBuffer(IntPtr.Zero, 0, false), ref total_length); if (status != NtStatus.STATUS_BUFFER_TOO_SMALL) { status.ToNtException(); } var relative_sd = new SafeHGlobalBuffer(total_length); try { NtRtl.RtlAbsoluteToSelfRelativeSD(sd_buffer, relative_sd, ref total_length).ToNtException(); return(Interlocked.Exchange(ref relative_sd, null)); } finally { relative_sd?.Close(); } } }
private NtResult <SafeHGlobalBuffer> CreateRelativeSecurityDescriptor(bool throw_on_error) { using (var sd_buffer = CreateAbsoluteSecurityDescriptor(throw_on_error)) { if (!sd_buffer.IsSuccess) { return(sd_buffer); } int total_length = 0; NtStatus status = NtRtl.RtlAbsoluteToSelfRelativeSD(sd_buffer.Result, SafeHGlobalBuffer.Null, ref total_length); if (status != NtStatus.STATUS_BUFFER_TOO_SMALL) { return(status.CreateResultFromError <SafeHGlobalBuffer>(throw_on_error)); } using (var relative_sd = new SafeHGlobalBuffer(total_length)) { return(NtRtl.RtlAbsoluteToSelfRelativeSD(sd_buffer.Result, relative_sd, ref total_length) .CreateResult(throw_on_error, () => relative_sd.Detach())); } } }
private NtResult <IContext> GetAmd64Context(ContextFlags flags, bool throw_on_error) { var context = new ContextAmd64 { ContextFlags = flags }; // Buffer needs to be 16 bytes aligned, so allocate some extract space in case. using (var buffer = new SafeHGlobalBuffer(Marshal.SizeOf(context) + 16)) { int write_ofs = 0; long ptr = buffer.DangerousGetHandle().ToInt64(); // Almost certainly 16 byte aligned, but just in case. if ((ptr & 0xF) != 0) { write_ofs = (int)(0x10 - (ptr & 0xF)); } Marshal.StructureToPtr(context, buffer.DangerousGetHandle() + write_ofs, false); var sbuffer = buffer.GetStructAtOffset <ContextAmd64>(write_ofs); return(NtSystemCalls.NtGetContextThread(Handle, sbuffer).CreateResult(throw_on_error, () => sbuffer.Result).Cast <IContext>()); } }
private IContext GetAmd64Context(ContextFlags flags) { var context = new ContextAmd64(); context.ContextFlags = flags; // Buffer needs to be 16 bytes aligned, so allocate some extract space in case. using (var buffer = new SafeHGlobalBuffer(Marshal.SizeOf(context) + 16)) { int write_ofs = 0; long ptr = buffer.DangerousGetHandle().ToInt64(); // Almost certainly 8 byte aligned, but just in case. if ((ptr & 0xF) != 0) { write_ofs = (int)(0x10 - (ptr & 0xF)); } Marshal.StructureToPtr(context, buffer.DangerousGetHandle() + write_ofs, false); var sbuffer = buffer.GetStructAtOffset <ContextAmd64>(write_ofs); NtSystemCalls.NtGetContextThread(Handle, sbuffer).ToNtException(); return(sbuffer.Result); } }
private static SafeHGlobalBuffer EnumEnvironmentValues(SystemEnvironmentValueInformationClass info_class) { int ret_length = 0; NtStatus status = NtSystemCalls.NtEnumerateSystemEnvironmentValuesEx(info_class, SafeHGlobalBuffer.Null, ref ret_length); if (status != NtStatus.STATUS_BUFFER_TOO_SMALL) { throw new NtException(status); } var buffer = new SafeHGlobalBuffer(ret_length); try { ret_length = buffer.Length; NtSystemCalls.NtEnumerateSystemEnvironmentValuesEx(info_class, buffer, ref ret_length).ToNtException(); return(buffer); } catch { buffer.Dispose(); throw; } }
/// <summary> /// Get all process information for the system. /// </summary> /// <returns>The list of process information.</returns> public static IEnumerable <NtProcessInformation> GetProcessInformation() { using (SafeHGlobalBuffer process_info = new SafeHGlobalBuffer(0x10000)) { AllocateSafeBuffer(process_info, SystemInformationClass.SystemProcessInformation); int offset = 0; while (true) { var process_buffer = process_info.GetStructAtOffset <SystemProcessInformation>(offset); var process_entry = process_buffer.Result; SystemThreadInformation[] thread_info = new SystemThreadInformation[process_entry.NumberOfThreads]; process_buffer.Data.ReadArray(0, thread_info, 0, thread_info.Length); yield return(new NtProcessInformation(process_entry, thread_info.Select(t => new NtThreadInformation(process_entry.ImageName.ToString(), t)))); if (process_entry.NextEntryOffset == 0) { break; } offset += process_entry.NextEntryOffset; } } }