mirror of
https://github.com/NomanNasirMinhas/Ringer
synced 2026-08-19 05:01:13 +00:00
193 lines
7.6 KiB
Markdown
193 lines
7.6 KiB
Markdown
# Ringer
|
||
|
||
Static and live analysis of Windows kernel drivers (`.sys`). Ringer extracts
|
||
**IOCTL control codes** and **shared ring-buffer strings** from a driver image,
|
||
then — by default — loads the driver as a kernel service and verifies each
|
||
finding against the *running* system.
|
||
|
||
It is a generic tool: it does not know about any specific driver's protocol. It
|
||
finds the IOCTLs a driver dispatches, the device names and symbolic links it
|
||
registers, and the named shared-memory sections it creates, then reads back
|
||
whatever the live driver returns.
|
||
|
||
## Features
|
||
|
||
- **IOCTL extraction** from the PE image:
|
||
- x86/x64 disassembly of `.text` — `CMP`/`MOV` immediates and MSVC
|
||
`switch`-jump-table dispatch (high confidence).
|
||
- Data scan of `.rdata`/`.data`/`.pdata` for 32-bit values that decode to a
|
||
plausible IOCTL and are referenced by code (medium confidence).
|
||
- Plain IOCTL-array detection for vendor device types `0x8000`–`0xBFFF`
|
||
(low confidence).
|
||
- Each code is decoded into `DeviceType`, `Function`, `Method`, and `Access`
|
||
via the `CTL_CODE` layout, deduplicated, and sorted.
|
||
- **String extraction** — ASCII and UTF-16LE, classified into device names,
|
||
symbolic links, section names, and ring-buffer/shared-memory keywords.
|
||
Format-string templates (e.g. `\??\%ls`) are filtered out.
|
||
- **Live verification**:
|
||
- `DeviceIoControl` against each discovered device, with a per-IOCTL timeout
|
||
and automatic retry on `ERROR_INSUFFICIENT_BUFFER`.
|
||
- `OpenFileMapping` + `MapViewOfFile` against each shared section, dumping the
|
||
mapped region (raw hex — no protocol decoding).
|
||
- **Runtime discovery** — enumerates the live object manager (`\Device`,
|
||
`\GLOBAL??`, `\BaseNamedObjects`) and the loaded-module list, so device names
|
||
and shared sections are taken from the running system rather than trusted from
|
||
static strings alone.
|
||
- **Auto-load** — installs and starts the driver as a demand-start kernel
|
||
service (no-op if it is already running).
|
||
- **System check** — audit every loaded kernel driver at once, extracting
|
||
IOCTLs, ring-buffer strings, and device names from each and saving a JSON
|
||
report.
|
||
|
||
## How it works
|
||
|
||
### IOCTL extraction
|
||
|
||
Three passes over the image, merged by confidence:
|
||
|
||
1. **Disassembly** — a linear sweep of `.text` with `x86asm.Decode` (64-bit for
|
||
AMD64, 32-bit for I386). 32-bit immediates in `CMP`/`MOV` are IOCTL
|
||
candidates. Compiler-generated `switch` dispatch (`sub`/`add` + range check +
|
||
jump table) is recognized and every case value is recovered, including
|
||
sparse switches via their byte-index table.
|
||
2. **Data scan** — 32-bit values in `.rdata`/`.data`/`.pdata` that decode to a
|
||
plausible IOCTL *and* are referenced by code (the `cmp IoControlCode,
|
||
[table+idx*4]` pattern).
|
||
3. **Table scan** — runs of consecutive DWORDs sharing a custom vendor device
|
||
type (`0x8000`–`0xBFFF`), which catches plain IOCTL arrays iterated in a loop
|
||
and therefore missed by the xref-gated data scan.
|
||
|
||
Candidates are filtered against known NTSTATUS values, degenerate codes, and
|
||
values whose device type falls in the NTSTATUS error range, then deduplicated
|
||
keeping the strongest confidence.
|
||
|
||
### Live verification
|
||
|
||
Device names and section names are derived from the extracted strings, then
|
||
cross-referenced against the live object manager. For each device, every IOCTL
|
||
is sent with a zeroed input buffer and the output is read back. For each
|
||
section, the mapping is opened and its first bytes are dumped.
|
||
|
||
## Building
|
||
|
||
Requires Go 1.26+ and a Windows build target (the tool uses
|
||
`golang.org/x/sys/windows`).
|
||
|
||
```sh
|
||
go build -o ringer.exe .
|
||
```
|
||
|
||
To cross-compile from another OS:
|
||
|
||
```sh
|
||
GOOS=windows GOARCH=amd64 go build -o ringer.exe .
|
||
```
|
||
|
||
## Usage
|
||
|
||
```sh
|
||
ringer.exe [flags] <driver.sys>
|
||
```
|
||
|
||
| Flag | Default | Description |
|
||
| --- | --- | --- |
|
||
| `--file` | | Path to the driver `.sys` file (also accepted as a positional argument) |
|
||
| `--device` | | Device name override (e.g. `\\.\MyDevice`) |
|
||
| `--section` | | Section name override (e.g. `Global\MySection`) |
|
||
| `--static` | `false` | Skip live verification (static extraction only) |
|
||
| `--json` | `false` | Emit JSON instead of text |
|
||
| `--min-string` | `4` | Minimum string length to extract |
|
||
| `--buffer-size` | `4096` | IOCTL input/output buffer size in bytes |
|
||
| `--timeout` | `2s` | Per-IOCTL timeout (e.g. `2s`, `500ms`) |
|
||
| `--method` | `all` | Filter IOCTLs: `buffered`, `in_direct`, `out_direct`, `neither`, `all` |
|
||
| `--cleanup` | `false` | Stop and delete the driver service after verification (only if this run created it) |
|
||
| `--system-check` | `false` | Audit every loaded kernel driver and save a JSON report (see below) |
|
||
| `--output` | `system-check.json` | Output path for the `--system-check` JSON report |
|
||
|
||
### Examples
|
||
|
||
Static extraction only (safe — no driver is loaded or contacted):
|
||
|
||
```sh
|
||
ringer.exe --static C:\path\to\driver.sys
|
||
```
|
||
|
||
Full analysis — load the driver as a service, discover its live device names and
|
||
sections, and verify every IOCTL and section:
|
||
|
||
```sh
|
||
ringer.exe C:\path\to\driver.sys
|
||
```
|
||
|
||
Full analysis, then stop and delete the service it created:
|
||
|
||
```sh
|
||
ringer.exe --cleanup C:\path\to\driver.sys
|
||
```
|
||
|
||
Machine-readable output:
|
||
|
||
```sh
|
||
ringer.exe --json --static driver.sys > report.json
|
||
```
|
||
|
||
### System check
|
||
|
||
Audit every loaded kernel driver on the machine, extract IOCTLs, ring-buffer
|
||
strings, and device names from each, and save a JSON report:
|
||
|
||
```sh
|
||
ringer.exe --system-check --output system-check.json
|
||
```
|
||
|
||
This prints a summary to stdout (total drivers, IOCTLs, ring buffers, device
|
||
names, and a per-driver table sorted by IOCTL count) and writes the full
|
||
per-driver findings to the JSON file. It does not load or contact any driver —
|
||
it only reads the loaded-module list and the on-disk images.
|
||
|
||
## Output
|
||
|
||
Text output is organized into sections:
|
||
|
||
- **IOCTL Codes** — code, device type, function, method, access, confidence,
|
||
and source (`disasm`, `data`, `table`, `switch`).
|
||
- **Shared Ring Buffer Strings** — section names and ring-buffer keywords.
|
||
- **Device Names / Symbolic Links** — names extracted from the image.
|
||
- **Runtime Discovery** — the device names and shared sections found in the
|
||
live object manager.
|
||
- **Live Verification: IOCTL** — per-device `DeviceIoControl` results with
|
||
hex-dumped output.
|
||
- **Live Verification: Shared Sections** — mapped size and hex-dumped contents.
|
||
|
||
`--json` emits the same data as structured JSON (`ioctls`,
|
||
`shared_ring_buffer_strings`, `device_strings`, `runtime_devices`,
|
||
`runtime_sections`, `ioctl_verification`, `section_verification`).
|
||
|
||
## Safety
|
||
|
||
> **Live verification sends real IOCTLs to a kernel driver and can bugcheck the
|
||
> machine — especially `METHOD_NEITHER`, where the kernel dereferences user
|
||
> pointers directly. Run it in a VM.**
|
||
|
||
- Loading a driver as a service requires **administrator privileges** and, for
|
||
unsigned drivers, **test signing mode** (`bcdedit /set testsigning on`).
|
||
- By default Ringer leaves the service it created installed and running. Pass
|
||
`--cleanup` to stop and delete it after verification. A service that already
|
||
existed before the run is never touched.
|
||
- Use `--static` for analysis that never touches the live system.
|
||
|
||
## Limitations
|
||
|
||
- **IOCTL codes cannot be read from kernel memory from user mode.** They are
|
||
extracted from the loaded image (the `.sys` file the kernel maps) and
|
||
confirmed by live `DeviceIoControl`. Device names and shared sections *are*
|
||
read from the live object manager.
|
||
- Split-immediate IOCTLs (`mov hi16; or lo16`) are not reconstructed.
|
||
- Packed or encrypted drivers are flagged by high entropy; their static results
|
||
are unreliable.
|
||
- ARM64 images are data-scanned only (no x86 disassembly).
|
||
|
||
## License
|
||
|
||
MIT
|