In a previous post, I covered a bit about how Windows Processes are initialized. But how does process creation work in Windows? Let's explore a bit further into Windows processes.
Tuesday, August 29, 2023
Thursday, August 24, 2023
Subshells in Linux (and Windows)
Or rather, subshells in Bash and Powershell. A subshell functions as a sort of isolated environment for executing commands, creating a subprocess or child process within the parent shell.
Wednesday, August 23, 2023
Portable Executable Format and Structured Exception Handling
The Portable Executable (PE) file format is the native file format for executable and binary files in the Microsoft Windows ecosystem.
Tuesday, August 22, 2023
Monday, August 21, 2023
XNU, a hybrid kernel
XNU was originally based on the Mach microkernel. But nowadays macOS blurs the lines. Though some parts of macOS follow the microkernel spirit, other parts are monolithic. It's more complex than a "pure" microkernel. Perhaps a microkernel has less abstractions. But XNU is a hybrid kernel that nonetheless still employs the priciple of least privilege well - striking a balance between the two realms.
Friday, August 18, 2023
A Parlor Trick with Primitive Roots
OK, the title is a bit of a pun -- really, the parlor trick uses elementary number theory. A primitive root modulo n however, is an integer g such that every integer relatively prime to n can be expressed as some power of g modulo n. In other words, g can generate all numbers relatively prime to n through its powers.
When dealing with modular arithmetic, cyclic groups, and primitive roots, we find patterns emerge. For example, we can see the powers of 3 are congruent to a cyclic pattern that repeats with numbers modulo 7, the powers of 3 give: 3, 2, 6, 4, 5, 1 — and then it loops back to 3.
This kind of repetition shows up even in something as simple as the last digit of powers. A neat trick is using this modular property to deduce the last digit of a large integer.
For example, consider the integer 7. As we increment the powers of 7, the last digit begins to repeat: 7, 9, 3, 1, and so on.
Those four numbers repeat over and over again. So, we say that it has a cycle length of four:
\begin{align*} 7^1 &\equiv 7 \\ 7^2 &\equiv 49 \\ 7^3 &\equiv 343 \\ 7^4 &\equiv 2401 \\ 7^5 &\equiv 16807 \\ 7^6 &\equiv 117649 \\ 7^7 &\equiv 823543 \\ 7^8 &\equiv 5764801 \\ 7^9 &\equiv 40353607 \\ \end{align*}Why do powers repeat like this? The answer is modular arithmetic, again. Similar to how primitive roots generate cycles, our last digit is a product of mod 10 -- and there are only ten possible numbers it can be, so it eventually cycles, too.
So, how can we use this knowledge to do a fun parlor trick, like guess the last digit of an extremely large integer, such as \(7^{3001}\)?
As demonstrated, the powers of 7 have a cycle length of four, so the last digit repeats every 4 numbers. It follows that we first divide our exponent, 3001, by 4.
\[ 3001 \div 4 = 750 \text{, with a remainder of } 1 \]Since we are left with a remainder, we use it, calculating \(7^{1} = 7\). Thus, we know the last digit of the number \(7^{3001}\) is 7.
But what if the division has no remainder? For example, let's consider \(7^{3000}\). Dividing 3000 by 4 gives:
\[ 3000 \div 4 = 750 \text{, with a remainder of } 0 \]Since there's no remainder, the exponent is a perfect multiple of the cycle length four, so we look at the last digit of \(7^4\), which equals 2401. The last digit of \(7^{3000}\) is 1.
Patterns like these show up so often in number theory that once you see them, you can’t unsee them.
Wednesday, August 16, 2023
Windows Process Initialization
Most code on Windows runs in user-space. This means that, when we first run a program, it needs to perform some rituals to successfully callback into the kernel.
Tuesday, August 15, 2023
Windows
From "user space and system space":
Windows gives each user-mode application a block of virtual addresses. This is known as the user space of that application. The other large block of addresses, known as system space or kernel space, cannot be directly accessed by the application.
More or less everything in the user space talks to NTDLL.DLL to make appropriate calls to hand off work to the Windows kernel, effectively context-switching. While some other software calls are diverted to libraries such as:
- MSVCRT.DLL: the standard C library
- MSVCP*.DLL: the standard C++ library
- CRTDLL.DLL.: library for multithreaded support
- System Processes
- Session manager
- LSASS
- Winlogon
- Session Manager
- Services
- Service control manager
- SvcHost.exe
- WinMgt.exe
- SpoolSv.exe
- Services exe
- Applications
- Task Manager
- Explorer
- User apps
- Subsystem DLLs
- Environment Subsystems
- Win32
- POSIX
- OS/2
- Kernel Mode
- Kernel mode drivers
- Hardware Abstraction Layer (HAL)
- System Threads
- System Service Dispatcher
- Virtual Memory
- Processes and Threads
- Security
- Security Reference Monitor
- Device & File Systems
- Device & File System cache
- Kernel Drivers
- I/O manager
- Plug and play manager
- Local procedure call
- Graphics drivers
- Hardware Abstraction Layer (HAL)
- Hardware interfaces
All code that runs in kernel mode shares a single virtual address space. Therefore, a kernel-mode driver isn't isolated from other drivers and the operating system itself. If a kernel-mode driver accidentally writes to the wrong virtual address, data that belongs to the operating system or another driver could be compromised. If a kernel-mode driver crashes, the entire operating system crashes.
Windows Architecture Overview
|
User Space:
|
Kernel Space:
|
Windows Ecosystem
OK, so of course the real question is, how do we interact with Windows ecosystem to actually do things? Like other software ecosystems, we have some set of libraries which we can use to implement functions which return values. Consider the CreateFileA API. Per Microsoft's documentation, here is the prototype for this interface:
HANDLE CreateFileA(
[in] LPCSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
A file name, access, share mode, security attributes (optional), a disposition, flags, and a template (optional). We'll also use printf and scanf to read some inputs. First we'll get a file path, and then a name for our new file. We'll concatenate the two into a full path, and call it with hFile on the CreateFileA API. And we'll define a constant to point to the content we wish to write to our text file.
We'll use FormatMessageA, as listed in Microsoft's documentation, to obtain possible error messages in case of failure. And check for errors against the WriteFile API with our if(!WriteFile statement - that is, if our write fails, let us know that it failed, close our handle, and return a fail status. Else, if our file has been created, close our handle and let us know by printing a message and the conjoined fullPath of our file, then exit cleanly with 0:
#include <stdio.h>
#include <windows.h>
int main() {
char path[MAX_PATH];
char filename[MAX_PATH];
HANDLE hFile;
DWORD bytesWritten;
// Get user input for path and filename
printf("Enter the path: ");
scanf("%s", path);
printf("Enter the filename: ");
scanf("%s", filename);
char fullPath[MAX_PATH];
snprintf(fullPath, sizeof(fullPath), "%s\\%s", path, filename);
hFile = CreateFileA(fullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
LPVOID errorMsg;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
error,
0, // Default language
(LPSTR)&errorMsg,
0,
NULL
);
printf("Failed to create the file: %s\n", (char*)errorMsg);
LocalFree(errorMsg);
return 1;
}
const char* content = "Noted";
if (!WriteFile(hFile, content, strlen(content), &bytesWritten, NULL))
{
printf("Failed to write to the file.\n");
CloseHandle(hFile);
return 1;
}
CloseHandle(hFile);
printf("File created successfully: %s\n", fullPath);
return 0;
}
Just a Prologue
After compiling, we can test and observe this to make some observations about Windows system behavior. The prologue, stack unwinding, and it's use of undocumented calls, which happen abstracted and hidden away from the user.
C:\Users\User\Downloads>.\createfile.exe Enter the path: C:\Users\User Enter the filename: ts File created successfully: C:\Users\User\text.txt
For example, when we first run our program, we immediately observe calls to NTDLL, which negotiates a thread and begins the work of running and executing our file. We can see this here:
0, ntdll.dll!RtlUserThreadStart
After hitting the first return, we can pull a stack trace to see our thread has now unwound a bit, and we've initiated contact with the kernel at KERNEL32.DLL, which is home to x64 function calls.
0, ntdll.dll!NtWaitForWorkViaWorkerFactory+0x14 1, ntdll.dll!RtlClearThreadWorkOnBehalfTicket+0x35e 2, kernel32.dll!BaseThreadInitThunk+0x1d 3, ntdll.dll!RtlUserThreadStart+0x28
During this time, we see multiple calls to LdrpInitializeProcess which initialize the structures in our process. Then we see our BaseThreadInitThunk, a similar kernel mode callback like LdrInitializeThunk, and a call to RtlNtImageHeader to get the image headers for our process.
Skipping forward a bit, later, when we enter our path and filename, those values are moved into the registers, like so. And following this, many cmp comparisons are made, checking the path to see that it is ok:
mov rbx,qword ptr ss:[rsp+70] | __pioinfo mov rsi,qword ptr ss:[rsp+78] | Users\\User\n\n
After a very long dance handling the file path, we finally see assembly calls involving our filename emerge. The filename is effectively loaded into a register like so:
push rbx | rbx:&"ts\n\nsers\\User\n\n" sub rsp,20 | mov rbx,rcx | rbx:&"ts\n\nsers\\User\n\n" lea rcx,qword ptr ds:[<_iob>] | cmp rbx,rcx | jb msvcrt.7FFF040306F5 | lea rax,qword ptr ds:[7FFF04088 | cmp rbx,rax | rbx:&"ts\n\nsers\\User\n\n" ja msvcrt.7FFF040306F5 |
Much later on when our file is created, we see that this file creation likely could have been logged by Event Tracing For Windows.
call createfile.7FF60B1C6D00 | jmp createfile.7FF60B1C860C | sub r10d,2 | mov rcx,qword ptr ds:[r13] | rcx:"ts", [r13]:"ts" lea rbx,qword ptr ds:[r13+8] | [r13+8]:EtwEventWriteTransfer+260
And after many assembly instructions later, we finally see our text get the lea, load effective address, containing our message for the text file we're writing. "Noted":
call rax | mov eax,1 | jmp createfile.7FF60B1C1743 | lea rax,qword ptr ds:[7FF60B1D1 | 00007FF60B1D104F:"Noted" mov qword ptr ss:[rbp+300],rax | mov rax,qword ptr ss:[rbp+300] |
And a syscall for NtWriteFile:
mov r10,rcx | NtWriteFile mov eax,8 | test byte ptr ds:[7FFE0308],1 | jne ntdll.7FFF055AEE55 | syscall | ret |
And lastly, our call to closeHandle:
mov rax,qword ptr ds:[<CloseHandle>] | rax:CloseHandle call rax | rax:CloseHandle
Though, much more happens - this is the gist of it.
Most of the stuff in the Microsoft API is well documented. Some of the code is even partially compatible with Unix systems. But other things in the Microsoft ecosystem however, are not officially documented. Microsoft gives us some public APIs. Some of which are just wrappers that call undocumented features under the hood. In a future post, we'll use an undocumented API to talk to the Windows kernel.
Sunday, August 13, 2023
API Endpoints
While scrolling twitter recently I saw Intigriti linked to some JavaScript bookmarklet for discovering API endpoints. When doing reconnaissance, sometimes tools like ffuf aren't fine-grained enough for enumerating API endpoints.
Friday, August 11, 2023
Interprocess Communication
In C
Let's review inter-process communication. IPC is, of course, how software sometimes passes information to other components, as well as to divy out access to restricted resources. This can be quite convoluted and complex in some cases. But here we'll review how this works in C.
Using Python To Access archive.today, July 2025
It seems like a lot of the previous software wrappers to interact with archive.today (and archive.is, archive.ph, etc) via the command-line ...
-
Latin1 was the early default character set for encoding documents delivered via HTTP for MIME types beginning with /text . Today, only ...
-
From "Overfitting and the strong version of Goodhart's law" : Increased efficiency can sometimes, counterintuitively, lead to ...
-
Playing around with writing malware proof-of-concepts, running red and blue team simulations in my computer lab against Windows Home edition...