Tuesday, August 22, 2023

Processes and Call Stacks

In Windows, our process information looks something like this.

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.

\begin{array}{rcrcrcrcrcr}3^{1}&=&3^{0}\times 3&\equiv &1\times 3&=&3&\equiv &3{\pmod {7}}\\3^{2}&=&3^{1}\times 3&\equiv &3\times 3&=&9&\equiv &2{\pmod {7}}\\3^{3}&=&3^{2}\times 3&\equiv &2\times 3&=&6&\equiv &6{\pmod {7}}\\3^{4}&=&3^{3}\times 3&\equiv &6\times 3&=&18&\equiv &4{\pmod {7}}\\3^{5}&=&3^{4}\times 3&\equiv &4\times 3&=&12&\equiv &5{\pmod {7}}\\3^{6}&=&3^{5}\times 3&\equiv &5\times 3&=&15&\equiv &1{\pmod {7}}\\3^{7}&=&3^{6}\times 3&\equiv &1\times 3&=&3&\equiv &3{\pmod {7}}\\\end{array}

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
  • 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:
    • 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 Space:
    • 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

    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 ...