This repository contains the client side of a Windows antivirus solution built as a university project.
The architecture consists of two executables: a system-tray GUI (rbpo-app.exe) and a background Windows service (rbpo-service.exe) that communicate through local RPC (ALPC).
The service also talks to a remote Spring Boot backend (rbpo_backend) over HTTPS for authentication, license management, and signature updates.
rbpo-front ← you are here (client)
rbpo_backend ← Java / Spring Boot server
Each branch adds a new layer on top of the previous one.
| Branch | What it covers |
|---|---|
main |
Current working state (tasks 1-5 merged). |
zad-1 |
Qt6 tray app: PNG icon, system tray, context menu, QMenuBar File→Exit, single-instance via named mutex. |
zad-2 |
Windows service: launch GUI into every user session, CreateProcessAsUserW with SecurityImpersonation, RPC ncalrpc endpoint for remote stop, PowerShell install script. |
zad-3 |
Auth & licensing: JWT tokens kept only in memory, HTTPS POSTs to backend, GUI login/activation forms, background license worker polling every 5s. |
zad-4 |
Antivirus engine: file / directory / drive scanning, Aho-Corasick automaton, scheduled scans, directory monitoring via ReadDirectoryChangesW, all RPC calls guarded by LicenseGate(). |
zad-5 |
Binary storage: custom RBDB v1 format on disk, HMAC-SHA256 manifest (RBMF v1), automatic backup before updates, periodic refresh from GET /api/signatures. |
src/main.cpp— Qt6 GUI (tray icon, login form, activation screen, AV dashboard).src/service/service_main.cpp— Service entry point (ServiceMain), RPC server loop, session enumeration, GUI spawning.src/service/state.cpp— Token & license workers: login, refresh, activate, check-license loop.src/service/http_client.h/cpp— WinHTTP wrapper for HTTPS REST calls.src/service/av_engine.h/cpp— Core scanner, Aho-Corasick tree,ScanFile,ScanDirectory,ScanAllDrives.src/service/av_db_io.h/cpp— Binary serialization (avdb.bin), manifest signing / verification, backup/restore.src/rpc/rbpo_rpc.idl— MIDL interface definition (compiled intorpc_gen/).src/rbpo_rpc_constants.h— Service name (RBPOService), endpoint name (RBPOServiceEndpoint), error codes.scripts/install_service.ps1— Admin script: stop old service → delete → create → start.
Service name on the system: RBPOService.
RPC transport: ALPC (ncalrpc:), endpoint name: RBPOServiceEndpoint.
- GUI never stores passwords. It sends them once over RPC to the service.
- Service keeps
accessTokenandrefreshTokenonly in RAM (state.cpp). - On service stop everything is wiped. On next start the user must log in again.
productIdis persisted to the registry (underHKLM) so the license check worker knows which product to validate.
A background thread (LicenseWorker) wakes every 5 seconds and:
- Builds a JSON body with
{ deviceMac, productId }. - POSTs it to
/api/licenses/checkwith the currentBearertoken. - If HTTP 200 → parses the signed ticket, stores expiry date, sets
licenseHeld = true. - If HTTP 404 → no license; if HTTP 403 → blocked by admin.
- If the ticket says
blocked=trueor the expiry date has passed, the GUI automatically switches back to the activation screen.
The backend returns productId as a number (1) inside the ticket object, while some implementations send it as a string. Our ParseSignedTicket falls back to numeric extraction and defaults to "1" if the field is missing entirely.
std::map<uint64_t, std::vector<AvRecord>>
key → first 8 bytes of the signature (little-endian uint64)
value → list of records sharing that prefix
Per-record fields:
prefix— same 8-byte prefix as the map key.sigLen— total signature length in bytes.sigHash— SHA-256 of the entire signature bytes.offsetBegin— earliest allowed file offset for a match (-1= any).offsetEnd— latest allowed file offset for a match (-1= any).type—0= PE,1= Script.recordSig— SHA-256 overprefix|sigLen|sigHash|offsetBegin|offsetEnd|type(integrity check).
The std::map gives us O(log K) prefix lookup.
- Open the file, cursor = 0.
- Read 8 bytes, convert to
uint64_tkey. - Look up the key in the map. If nothing found → shift cursor by 1 byte, repeat.
- For every candidate record with that prefix (cheapest tests first):
- Does the detected file type match
type? - Does cursor lie inside
[offsetBegin, offsetEnd]? - Read the remaining
sigLen - 8bytes. - Compute SHA-256 of
prefix_bytes + extra_bytes. - Compare with
sigHash.
- Does the detected file type match
- Discard mismatches. If at least one record survives → threat detected.
During AvLoad we also build an AC automaton from the real signature bytes. ScanStream can then do a single pass over the file in O(N + M) instead of the sliding-window approach above. When the automaton reports a hit we still verify type and offset constraints.
| Rule | Assigned type |
|---|---|
Extension is .py, .ps1, .js, .vbs |
Script |
First bytes are MZ |
PE |
| Anything else | Script |
| 16-byte payload | Type | Trigger |
|---|---|---|
RBPOTESTVRS1.000 |
PE | File contains these bytes AND starts with MZ |
#RBPOTESTVRS2.00 |
Script | File contains these bytes AND has .py/.ps1 extension |
Required:
RBPO_GetAvDbInfo— returns release date + record count.RBPO_ScanFile— scan a single file path.RBPO_ScanDirectory— recursive scan.
Optional (extra credit):
RBPO_ScanAllDrives— all fixed drives.RBPO_SetScanSchedule/RBPO_ClearScanSchedule/RBPO_GetScheduleResultsRBPO_AddMonitorDirectory/RBPO_RemoveMonitorDirectory/RBPO_GetMonitorResults
All scanning RPCs first call LicenseGate(). If the license is not active the call returns RBPO_ERR_NO_LICENSE.
Four files live next to rbpo-service.exe:
| File | Purpose |
|---|---|
avdb.bin |
Active signature database |
avdb.manifest |
HMAC-SHA256 manifest protecting avdb.bin |
avdb.bin.bak / avdb.manifest.bak |
Backup created before every update |
avdb.default.bin / avdb.default.manifest |
Hard-coded fallback database (2 test signatures) |
[4] magic 'R','B','D','B'
[2] version LE uint16 = 1
[4] count LE uint32 — number of records
[2] dateLen LE uint16 — length of UTF-8 release date string
[N] date UTF-8 release date
[32] dataHash SHA-256 over the entire record section
------ record section ------
for each record:
[8] prefix LE uint64
[4] sigLen LE uint32
[1] sigHashLen 0 or 32
[N] sigHash bytes (if len > 0)
[8] offsetBegin LE int64
[8] offsetEnd LE int64
[1] type 0=PE, 1=Script
[1] hasRemainderHash
[4] sigBytesLen LE uint32
[N] sigBytes raw signature bytes
[2] nameLen LE uint16
[N] threatName UTF-8
[32] recordSig SHA-256(prefix|sigLen|sigHash|offsetBegin|offsetEnd|type)
[4] magic 'R','B','M','F'
[2] version LE uint16 = 1
[32] fileHash SHA-256(avdb.bin contents)
[32] manifestSig HMAC-SHA256(key, magic|version|fileHash)
- If
avdb.default.binis missing → generate it from the built-in test signatures. - Try to verify
avdb.manifest:- Valid → load
avdb.bin, validate each record signature. - If some records are invalid and network is up → force an update from backend.
- Invalid manifest + network + valid token → force an update.
- Valid → load
- If step 2 fails → try loading
avdb.bin.bak(backup). - If backup also fails → fall back to
avdb.default.bin.
AvDbStartUpdate(3600) launches a thread that sleeps 3600 seconds between cycles:
- Copy current
avdb.bin→avdb.bin.bakandavdb.manifest→avdb.manifest.bak. - Download fresh signatures via
GET /api/signatures. - Serialize new
avdb.bin+avdb.manifest. - If write fails → restore from
.bak.
All calls go to port 8081 (HTTP by default). Override via environment variables:
RBPO_BACKEND_HOSTRBPO_BACKEND_PORTRBPO_BACKEND_USE_TLS
| Method | Path | Body highlights |
|---|---|---|
| POST | /api/auth/login |
{ username, password } |
| POST | /api/auth/refresh |
{ refreshToken } |
| GET | /api/auth/me |
— |
| POST | /api/licenses/activate |
{ activationKey, deviceMac, deviceName } |
| POST | /api/licenses/check |
{ deviceMac, productId } |
| GET | /api/signatures |
Full signature database (for updates) |
Prerequisites: Visual Studio 2022, Qt6, CMake.
cmake -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config ReleaseOutputs:
build/Release/rbpo-app.exebuild/Release/rbpo-service.exe
Both must reside in the same directory at runtime.
sc create RBPOService binPath= "C:\path\to\rbpo-service.exe" start= demand DisplayName= "RBPO Service"
sc start RBPOServicesc stop RBPOService
sc delete RBPOServiceRun as Administrator:
powershell -ExecutionPolicy Bypass -File scripts\install_service.ps1The script stops/deletes any old service instance, re-creates it, and starts it.
See DEMO_GUIDE.md for a step-by-step walkthrough with exact Postman requests and expected GUI behaviour for each task.