Add detailed analysis and documentation for RSNet.dll download functionality

- Introduced a comprehensive markdown document outlining the API surface of RSNet.dll related to downloading video files.
- Documented initialization, connection, record querying, and download processes.
- Provided insights into the exported functions, their parameters, and expected behaviors.
- Included practical implications and recommendations for implementing a downloader script using C# interop.
- Highlighted the necessary struct layouts and callback mechanisms for effective integration with the DLL.
This commit is contained in:
MaddoScientisto 2026-04-17 21:19:46 +02:00
commit de077ca5d5
34 changed files with 2084 additions and 152 deletions

View file

@ -1,234 +1,389 @@
# Surveillance Client DPI Analysis
# Surveillance Client Server And Recorded Video Download Analysis
Date: 2026-03-25
Date: 2026-04-17
Target binary: `Surveillance_client.exe`
Analysis method: Ghidra MCP against the already-imported and analyzed program database.
Analysis method: Ghidra MCP against the already-open executable, with non-destructive analysis only. During this pass, allowed writes were limited to function renames and comments.
## Objective
Determine how the client initializes its UI, whether it already contains any DPI-awareness logic, and what practical patch points exist for making the application usable on high-resolution displays.
Determine how the client connects to remote devices and how it searches for and downloads recorded video, with the practical goal of building a small standalone downloader that can fetch an arbitrary duration from an arbitrary camera.
## Executive Summary
- The binary is a Qt Widgets desktop application, and the available imports strongly suggest an older Qt 4-era codebase.
- No direct static use of Windows DPI-awareness APIs was found.
- No obvious embedded DPI manifest strings were found in the loaded program strings.
- The application persists window geometry and display layout state through `QSettings`, including explicit `QRect` geometry records.
- That persistence layer is likely to matter for any DPI patch, because old pixel-based geometry can become incorrect or visually tiny once DPI behavior changes.
- The EXE is mostly UI, orchestration, and request-shaping code.
- The real connection, record-query, playback-control, and download logic is implemented in `RSNET.DLL`.
- Local playback and file rendering are implemented in `RSPLAY.DLL`.
- The EXE contains a clear playback/download workflow that prepares request structures and hands them to `RSNET.DLL` exports.
- The current executable analysis is enough to map the high-level flow and the parameter shapes, but not enough to recover the on-the-wire protocol. For that, `RSNet.dll` needs to be loaded into Ghidra next.
## Confirmed Entry Flow
## Installed Module Layout
The startup chain currently identified is:
The installed client directory contains the key vendor modules:
1. `___tmainCRTStartup` at `0x00708d44`
2. `qt_startup_dispatch` at `0x00709b00`
3. `qWinMain` thunk at `0x00709c02`
4. Application main routine at `0x004c7260`
- `RSNet.dll`
- `RSPlay.dll`
- `QRSosAdapter.dll`
- `QtNetwork4.dll`
Notes:
This matches the import thunks present in the EXE.
- `qt_startup_dispatch` converts `GetCommandLineW()` output into a local 8-bit argv-style buffer and forwards execution into Qt startup.
- This is a useful early patch region if a future binary modification needs to happen before the first top-level window is created.
## Confirmed DLL Boundary
## Evidence This Is An Older Qt Widgets Application
The following named functions in the EXE are import thunks, not native implementations:
The import table and strings include multiple indicators that this is not a modern Qt high-DPI-aware build:
- `RSNetQueryParam` at `0x006e96f0`
- `RSNetQueryParamEx` at `0x006e9720`
- `RSNetAlarmSubscribe` at `0x006e99a0`
- `RSPlayGetPlayFileTimeRange` at `0x006ea760`
- `QApplication`
- `QPlastiqueStyle`
- `QProxyStyle`
- `QStyleOptionViewItemV4`
- `desktop`
- `screenGeometry`
- `availableGeometry`
- `primaryScreen`
Example:
Why that matters:
- `RSNetQueryParam` is just `JMP dword ptr [0x0076e648]`
- `QPlastiqueStyle` and `QStyleOptionViewItemV4` are strong Qt 4-era signals.
- A Qt 4-era app is much less likely to support automatic per-monitor DPI scaling cleanly.
- If DPI awareness is turned on without additional UI scaling work, the usual failure mode is a crisp but very small interface.
External symbol resolution in the loaded EXE confirms these libraries and exports:
## What Was Not Found
### `RSNET.DLL`
The following searches did not produce evidence of a built-in DPI implementation:
Relevant exports confirmed in the import table:
- `SetProcessDPIAware`
- `SetProcessDpiAwareness`
- `SetThreadDpiAwarenessContext`
- `WM_DPICHANGED`
- `devicePixelRatio`
- `HighDpi`
- `QT_AUTO_SCREEN_SCALE_FACTOR`
- `AA_EnableHighDpiScaling`
- `RSNetConnectionStart`
- `RSNetConnectionStartEx`
- `RSNetQueryRecordEx`
- `RSNetStartRecordPlayEx`
- `RSNetReqRecordPlayCtrol`
- `RSNetStartDownloadRecord`
- `RSNetStartDownloadRecordEx`
- `RSNetStopDownloadRecord`
- `RSNetReqRecordData`
- `RSNetStopRecordPlay`
- `RSNetReplayRecordData`
- `RSNetReposRecordData`
- `RSNetGetDevInfo`
- `RSNetSearchDev`
I also searched program strings for common manifest content such as:
### `RSPLAY.DLL`
- `dpiAware`
- `asmv3`
- `requestedExecutionLevel`
- `trustInfo`
Relevant playback exports confirmed in the import table:
None of those were found in the loaded strings.
## Resource Layout
The PE section layout includes a resource section:
- `.rsrc: 0x00dd7000 - 0x00df05ff`
Even with a resource section present, no obvious manifest or DPI-related XML strings surfaced through the Ghidra string search during this pass.
## Settings And Geometry Findings
Several settings-related functions were isolated and renamed in the Ghidra database for future work.
### `init_app_settings_defaults` at `0x0059ca30`
This function initializes many application settings keys and default values.
Relevant confirmed keys:
- `mainwindow/fullscreen`
- `mainwindow/size`
- `window_status`
- `gen/liveviewstatus`
- `gen/autolive`
- `screenAutoSwitch/interval`
- `screenAutoSwitch/forceSync`
- `screenAutoSwitch/isOn`
Relevant confirmed defaults:
- `mainwindow/size = 0x400 x 0x300` which is `1024 x 768`
- `mainwindow/fullscreen = false`
- `RSPlayCreatePlayInstance`
- `RSPlaySetPlayWnd`
- `RSPlayStartPlay`
- `RSPlaySetCallbackMessageEx`
- `RSPlayImageCallback`
- `RSPlayOpenPlayFile`
- `RSPlayOpenPlayFileListEx`
- `RSPlayGetPlayFileTimeRange`
- `RSPlaySetCurPlayedTimeEx`
- `RSPlayInputNetFrame`
- `RSPlayStartlocalrecord`
- `RSPlayStoplocalrecord`
Interpretation:
- The main window has a fixed persisted default size in pixels.
- That is a strong warning sign for DPI patching, because a legacy saved size can remain visually small after changing DPI behavior.
- `RSNET.DLL` is the module that matters for device login, remote record queries, remote playback, and remote download.
- `RSPLAY.DLL` is mostly decode/render/local playback support, not the primary network protocol implementation.
### `load_window_status_from_settings` at `0x005a72b0`
## Confirmed EXE-Side Flow
This function reads a `QSettings` array named `window_status` and loads nested UI state.
### UI widgets involved
Confirmed per-window fields:
The strings and handlers in the EXE show a playback and file-download workflow:
- `windowGeometry`
- `isCurWindow`
- `isFullScreen`
- `isMainWindow`
- `maxTabIndex`
- `Widget_Playback`
- `Widget_Playback_FileDownload`
- `Download By Date`
- `Download By Files`
- `DownLoad Start Time`
- `MainStream`
- `SubStream`
- `playback-download`
- `QRSAdapterReqRecordPlayCtrol`
Confirmed nested view/screen fields:
Relevant strings also show signals/slots for:
- `view_status`
- `mainview_status`
- `screenIndex`
- `screenBeginIndex`
- `isScreenEnlarge`
- `autoSwitchInterval`
- `autoSwitchIndependent`
- `autoSwitchIsOn`
- `stream_status`
- `groupChannelID`
- `videoIndex`
- `isCurSelected`
- `isSoundOn`
- `Emap_status`
- `emapIDIndex`
- `searchRequest(QString)`
- `playbackResponse()`
- `fileDownload()`
- `download()`
- `stopdownload()`
### Functions renamed during analysis
The following EXE functions were renamed in the Ghidra database:
- `0x004a9690` -> `PlaybackFileDownloadWidget_connectSignals`
- `0x0049f570` -> `RecordPlaybackControl_toggleMode`
- `0x004afb30` -> `PlaybackFileDownloadWidget_download`
- `0x004b1be0` -> `PlaybackFileDownloadWidget_downloadByTime`
These names reflect verified behavior from decompilation.
## What The Download UI Does
### `PlaybackFileDownloadWidget_connectSignals`
This function wires the file-download UI:
- download button -> `download()`
- stop button -> `stopdownload()`
- cancel button -> `cancel()`
- date/time selector changes -> `GetFileNumAndSize(QDateTime)`
That establishes the UI entrypoint cleanly.
### `PlaybackFileDownloadWidget_download`
This is the main dispatcher for the download dialog.
It has two modes:
1. File-based download mode
2. Time-range download mode
In file-based mode it:
- iterates rows in the record table
- checks selected rows
- tracks row state in a per-row metadata structure
- starts a timer to process queued download items
In time-range mode it:
- reads the selected start and end `QDateTime` values from the UI
- iterates available remote record segments
- compares the requested interval against each segment
- computes overlap windows
- schedules one or more download requests accordingly
The function logs several debug markers:
- `DownloadByTime 111111111111111111`
- `DownloadByTime 222222222222222222`
- `DownloadByTime 33333333333333333333`
- `DownloadByTime 444444444444444444444`
Those correspond to different interval-overlap cases between the selected range and a returned recording segment.
### `PlaybackFileDownloadWidget_downloadByTime`
This function handles the actual by-time iteration across remote record segments.
It reconstructs begin/end timestamps from record metadata, converts them to `QDateTime`, compares them against the user-selected range, then dispatches a lower-level download request for the relevant slice.
The logic strongly suggests the client does not ask the device for an arbitrary time range in one step unless the selected window aligns with a single record segment. Instead, it walks matching segments and issues one or more segment-bounded download requests.
Implication for a standalone tool:
- arbitrary time-range download likely requires:
- querying the device for record segments first
- intersecting those with the requested interval
- issuing one or more download requests
- stitching or sequencing the resulting data if needed
## EXE-Side RSNET Wrapper Layer
The EXE contains thin wrappers around the `RSNET.DLL` APIs. These wrappers are useful because they reveal request structure shapes and callback wiring even though the real protocol lives in the DLL.
### Connection wrapper
`FUN_006eaf70`
Behavior:
- copies fields from a caller-supplied struct into a local 0x38-byte request block
- stores two callback/context values into the owning object
- calls `RSNetConnectionStartEx(&local_38)`
Interpretation:
- The application stores a substantial amount of layout state in settings.
- `windowGeometry` is especially important because it appears to be persisted as a `QRect`.
- If the process DPI mode changes, previously stored geometry may need migration, scaling, deletion, or selective reset.
- connection/login is performed through `RSNetConnectionStartEx`
- the caller passes a structured login/connect request, not loose scalar parameters
- the request likely includes address or P2P information, credentials, and callback context
### `save_window_status_to_settings` at `0x005a6170`
### Remote record query wrapper
This function writes back the same structures into `QSettings`.
`FUN_006eb6f0`
Behavior:
- allocates a small 8-byte helper object from fields `param_2[0x12]` and `param_2[0x11]`
- copies roughly 0x60 bytes of request data into a local buffer
- assigns completion callbacks
- calls `RSNetQueryRecordEx(handle, &local_64)`
Interpretation:
- Even if a DPI hack works visually at first launch, the application can overwrite the new layout with old pixel-based assumptions on exit.
- Any durable solution should consider both the load and save side of geometry persistence.
- remote record search is performed with `RSNetQueryRecordEx`
- the query request is non-trivial and includes multiple fields beyond just channel and time
- two tail fields are treated specially and likely represent callback target/context or an auxiliary selection object
### `apply_mainwindow_fullscreen_setting` at `0x0047c1f0`
### Remote playback wrapper
This function compares a key against `mainwindow/fullscreen` and activates the matching `QAction` when the value is true.
`FUN_006e9e00`
Behavior:
- creates an `RSPlay` instance via `RSPlayCreatePlayInstance(1)`
- optionally binds the playback window handle via `RSPlaySetPlayWnd`
- starts local rendering via `RSPlayStartPlay`
- registers callback handlers with `RSPlaySetCallbackMessageEx` and `RSPlayImageCallback`
- fills a request block from the provided playback parameters
- calls `RSNetStartRecordPlayEx(connectionHandle, &local_40)`
Interpretation:
- The UI state is driven through Qt actions, not direct Win32 window management.
- That reinforces the view that DPI fixes will likely need to be applied at Qt startup or Qt widget/layout level rather than through a narrow Win32 patch alone.
- remote recorded playback is a network operation through `RSNET.DLL`
- incoming data is then fed into an `RSPLAY.DLL` decode/render pipeline
- the standalone downloader probably does not need the `RSPLAY` path unless raw playback streaming is required instead of file download
## Implications For A DPI Fix
### Download wrappers
### Most likely current problem model
`FUN_006e9810`
Based on the evidence, the binary looks like a legacy Qt Widgets application with no obvious modern DPI-awareness implementation. That creates two realistic scenarios:
Behavior:
1. The process is effectively DPI-unaware and Windows bitmap scaling is not being applied in the way the user wants.
2. The process is being made DPI-aware somewhere outside the obvious static import path, causing the UI to render at legacy pixel sizes and appear tiny.
- allocates a 12-byte state object
- copies a compact request from the caller
- sets a callback function `FUN_006e8690`
- calls `RSNetStartDownloadRecord(handle, &local_14)`
This analysis did not prove which of those two runtime cases is active on the target system. It did establish that the executable itself does not appear to contain a straightforward built-in high-DPI implementation.
`FUN_006e98b0`
### Why a simple `SetProcessDPIAware` patch is risky
Behavior:
For an older Qt Widgets app:
- allocates a 12-byte state object
- copies a slightly larger request including an extra byte field
- sets callback `FUN_006e8730`
- calls `RSNetStartDownloadRecordEx(handle, &local_20)`
- enabling DPI awareness alone often produces a sharp but tiny UI
- stored `QRect` geometry may restore undersized windows
- icon sizes, style metrics, and custom-painted controls may remain pixel-based
- there is no evidence yet of `WM_DPICHANGED` handling for per-monitor updates
`FUN_006e9980`
### What a workable solution will probably require
Behavior:
At minimum, a successful patch likely needs one of these paths:
- calls `RSNetStopDownloadRecord(downloadHandle)`
- frees the associated state object
1. A compatibility-style solution that lets Windows scale the app acceptably.
2. An early startup patch plus application-wide Qt scaling adjustments before top-level widgets are shown.
3. A deeper patch that also normalizes saved geometry and possibly icon/font/style metrics.
Interpretation:
## Ghidra Database Changes Made During Analysis
- there are at least two remote download entrypoints: base and extended
- the extended form includes one extra byte field that may be stream type, record type, or a mode selector
- both return or populate a download handle that is later passed to `RSNetStopDownloadRecord`
The following functions were renamed in the active program database:
### Record data callback path
- `0x00709b00` -> `qt_startup_dispatch`
- `0x0059ca30` -> `init_app_settings_defaults`
- `0x005a72b0` -> `load_window_status_from_settings`
- `0x005a6170` -> `save_window_status_to_settings`
- `0x0047c1f0` -> `apply_mainwindow_fullscreen_setting`
`FUN_006e85b0`
Decompiler comments were also added at those entrypoints summarizing their purpose.
Behavior:
## Current Confidence Level
- if callback state is valid and `param_1 == 1`, it calls `RSNetReqRecordData(*param_2)`
- otherwise it posts a Qt event with type `0x3ed` to a target object
High confidence:
Interpretation:
- Qt-based GUI application
- older Qt lineage is likely
- no obvious static DPI-awareness implementation exists
- window geometry persistence is real and important
- remote playback/download is at least partly callback-driven
- one callback state value appears to be a target `QObject`
- `RSNetReqRecordData` is likely a pull/continue/read-next mechanism for record data transfer
Medium confidence:
### Record playback control
- the best durable fix will require more than a manifest-only change
- geometry migration/reset will likely be necessary for a clean result
`RecordPlaybackControl_toggleMode`
Low confidence without runtime testing:
Behavior:
- whether the best final mode is DPI-unaware with OS scaling, system-DPI-aware, or a custom Qt-side scaling patch
- toggles internal playback state based on UI mode values `0x10` through `0x13`
- logs `QRSAdapterReqRecordPlayCtrol`
- calls a helper that eventually dispatches `RSNetReqRecordPlayCtrol`
## Recommended Direction
Interpretation:
Treat this as a legacy-Qt DPI retrofit problem, not just a missing API call problem.
- pause/resume/seek/speed or similar playback controls go through `RSNetReqRecordPlayCtrol`
- this is playback control, not initial search or initial download setup
The next pass should focus on:
## Current Reconstructed Download Pipeline
- locating the `QApplication` construction site inside `0x004c7260`
- locating first top-level window creation/show
- tracing where `windowGeometry` is applied to the main window
- deciding whether to pursue an OS-scaling strategy or a real application-side scaling patch
From the EXE alone, the likely pipeline is:
1. Establish device/session connection through `RSNetConnectionStartEx`
2. Query available recordings with `RSNetQueryRecordEx`
3. In the UI, present file/segment rows to the user
4. For time-based download, intersect the requested interval against returned record segments
5. For each needed segment or sub-range, call either `RSNetStartDownloadRecord` or `RSNetStartDownloadRecordEx`
6. Receive progress/data/completion via callback functions and Qt events
7. Stop transfer with `RSNetStopDownloadRecord` when complete or canceled
## What This Means For A Standalone Downloader
At a high level, the minimum downloader architecture is likely:
1. Login/connect request builder
2. Record query request builder
3. Segment intersection logic for arbitrary user time ranges
4. Download request builder
5. Callback/event loop or polling layer for transfer progress and completion
The most important unresolved details are all inside `RSNet.dll`:
- exact login request structure
- whether connection uses direct IP, media port, P2P ID, or multiple modes under one API
- exact query-record structure fields
- exact download request structure fields
- callback message formats and result codes
- file format or chunk framing of downloaded data
- authentication handshake and any encryption or session-key behavior
## What Is Known Versus Unknown
### Known from EXE analysis
- The EXE does not appear to implement the wire protocol itself.
- `RSNET.DLL` is the primary networking and remote-record module.
- `RSPLAY.DLL` handles playback/render support.
- The UI supports both file-based and time-based record download.
- Arbitrary time-range download is implemented as interval intersection over returned recording segments.
- The download path uses `RSNetStartDownloadRecord` or `RSNetStartDownloadRecordEx`.
- Record search uses `RSNetQueryRecordEx`.
- Record playback uses `RSNetStartRecordPlayEx` and `RSNetReqRecordPlayCtrol`.
### Unknown until `RSNet.dll` is analyzed
- packet layout and transport protocol
- exact authentication mechanism
- exact request structure fields
- whether the client speaks directly to the device, uses P2P relay logic, or switches transport modes internally
- how downloaded bytes are framed and persisted to disk
## Recommended Next Step
Load `RSNet.dll` into Ghidra next.
That is the correct next target if the goal is to build an independent downloader rather than automate the existing EXE.
After `RSNet.dll` is loaded, the next analysis pass should focus on:
- `RSNetConnectionStartEx`
- `RSNetQueryRecordEx`
- `RSNetStartDownloadRecord`
- `RSNetStartDownloadRecordEx`
- `RSNetReqRecordData`
- `RSNetStopDownloadRecord`
- any credential, P2P, relay, encryption, or session-management helpers they call
## Credentials And Live Validation
Credentials are not required yet for the current static pass.
They will become useful later for one of these reasons:
- validating a reconstructed downloader against a real target
- comparing dynamic behavior with captured traffic
- understanding whether connection mode changes based on device type, P2P ID, or network reachability
If live testing becomes necessary, credentials should be stored in an untracked local file such as a new `.gitignore`d notes/config file inside the repository or workspace.
## Analysis State Notes
- Earlier in the session, a few function definitions were accidentally deleted by using the wrong MCP endpoint on the writable program. Those functions were restored immediately.
- A repository instruction file now explicitly forbids destructive Ghidra actions during analysis unless the user explicitly requests them.
- Current repository instruction file: `.github/copilot-instructions.md`