Felgo¶
Felgo is a cross-platform SDK built on top of Qt, using QML (Qt Modeling Language) as its primary scripting layer and C++ for performance-critical native code. Originally launched as "V-Play" for game development, it rebranded to Felgo and expanded into general app development. Felgo apps ship Qt's core native libraries alongside Felgo-specific libraries and bundle QML scripts inside Qt Resource Container (.rcc) files. The reverse engineering approach splits between extracting and reading QML scripts from resource files and analyzing compiled C++/Qt native libraries with disassemblers.
Architecture¶
Component Stack¶
| Layer | Component |
|---|---|
| QML Scripts | Application logic, UI definitions, game mechanics written in QML/JavaScript |
| Felgo SDK | Game engine components (scenes, entities, physics), app components (navigation, theming) |
| Qt Framework | Core platform abstraction -- networking, file I/O, rendering, event loop |
| Native C++ | Custom C++ modules, Qt plugin implementations, performance-critical code |
| Android Shell | Thin Java wrapper using org.qtproject.qt5.android.QtActivity or Felgo's custom activity |
QML Engine¶
QML is a declarative language with inline JavaScript support. Felgo apps define their UI and logic in .qml files that are interpreted by Qt's QML engine at runtime. The QML engine (libQt5Qml*.so) parses and executes these scripts, resolving property bindings and signal-slot connections dynamically.
QML files can contain embedded JavaScript blocks for imperative logic:
import Felgo 4.0
import QtQuick 2.0
App {
onInitTheme: {
var endpoint = "https://api.example.com/v1/auth"
HttpRequest.get(endpoint).then(function(res) {
storage.setValue("token", res.body.token)
})
}
}
This means API endpoints, authentication logic, and business rules are often visible in extracted QML files.
Rendering Pipeline¶
Felgo uses Qt's scene graph for rendering, which runs on a dedicated render thread using OpenGL ES or Vulkan. Game apps use Felgo's entity-component system built on top of the scene graph. This architecture means UI and game logic live in QML, while the rendering pipeline is handled entirely in native code.
Identification¶
| Indicator | Location |
|---|---|
libQt5Core*.so |
Qt core library in lib/ |
libQt5Qml*.so |
QML engine library |
libQt5Quick*.so |
Qt Quick rendering library |
libFelgo*.so |
Felgo SDK library |
assets/*.rcc |
Qt Resource Container files holding QML scripts and assets |
net.vplay.* |
Legacy V-Play package prefix in DEX/manifest |
com.felgo.* |
Current Felgo package prefix |
org.qtproject.qt5.* |
Qt Android integration classes |
Quick check:
Distinguishing Felgo from Plain Qt¶
Both Felgo and plain Qt apps contain libQt5Core*.so and .rcc files. The presence of libFelgo*.so or Felgo-specific package names (com.felgo.*, net.vplay.*) distinguishes Felgo apps from generic Qt applications.
If no Felgo-specific libraries are found but Qt libraries are present, the app is a plain Qt application (covered in the Qt for Android page).
Code Location & Extraction¶
Qt Resource Container (.rcc) Files¶
QML scripts, JavaScript files, images, and other assets are bundled into .rcc files -- Qt's binary resource archive format. These files are the primary reverse engineering target.
Extract .rcc files from the APK, then decompile them with Qt's rcc tool:
unzip target.apk "assets/*.rcc" -d extracted/
rcc --reverse --output extracted/rcc_out/ extracted/assets/qml_resources.rcc
The .rcc format starts with a qres magic header (bytes 71 72 65 73), followed by a tree of resource entries. If rcc is unavailable, the Python package qtrcc provides equivalent extraction.
Extracted QML Analysis¶
After extraction, the QML files are plaintext and fully readable:
Search for high-value targets in extracted QML/JS:
Compiled QML Cache¶
Some Qt apps pre-compile QML to bytecode (.qmlc / .jsc files) for faster startup. These are cached compilations of QML files and contain Qt's internal bytecode format.
The bytecode is version-specific to the Qt release. Tools for decompiling .qmlc are limited, but the string table within the bytecode still contains readable identifiers and literals:
Native Libraries¶
C++ logic compiles into shared libraries. The primary targets:
| Library | Contents |
|---|---|
libFelgo*.so |
Felgo SDK -- game engine, app components, networking |
libQt5Core*.so |
Qt core runtime |
libQt5Network*.so |
Qt networking stack (HTTP, SSL, sockets) |
App-specific .so |
Custom C++ modules registered as QML types |
Analyze with Ghidra or IDA. Qt's signal-slot mechanism uses string-based method lookups via qt_metacall, which preserves method name strings in the binary.
Analysis Workflow¶
- Unzip APK and confirm Felgo indicators (
libFelgo*.so,.rccfiles) - Extract
.rccfiles usingrcc --reverseor Python tooling - Read QML files -- these contain UI definitions, business logic, API calls
- Search QML/JS for endpoints, credentials, encryption keys, storage operations
- Map Felgo/Qt API usage (
HttpRequest,Storage,WebSocket,FileUtils) - Analyze native
.sofiles in Ghidra for C++ logic not exposed in QML - Hook at runtime with Frida targeting Qt and Felgo native functions
Key Felgo APIs to Search For¶
| API | Purpose |
|---|---|
HttpRequest |
HTTP client -- reveals endpoints |
WebSocket |
Persistent connections -- potential C2 channel |
Storage |
Key-value persistence -- stored credentials |
FileUtils |
File system operations -- data exfiltration paths |
NativeUtils |
Platform-specific native calls |
GameNetwork |
Felgo multiplayer backend communication |
Firebase |
Push notifications, analytics |
Hooking Strategy¶
Native Library Hooks¶
Frida hooks on Qt and Felgo native libraries:
var qtNetwork = Process.findModuleByName("libQt5Network.so");
if (qtNetwork) {
qtNetwork.enumerateExports().forEach(function(exp) {
if (exp.name.indexOf("QNetworkAccessManager") !== -1 && exp.name.indexOf("createRequest") !== -1) {
Interceptor.attach(exp.address, {
onEnter: function(args) {
console.log("[Qt Network] createRequest called");
},
onLeave: function(retval) {}
});
}
});
}
QML Engine Interception¶
Hook the QML engine to intercept script evaluation:
var qtQml = Process.findModuleByName("libQt5Qml.so");
if (qtQml) {
qtQml.enumerateExports().forEach(function(exp) {
if (exp.name.indexOf("evaluate") !== -1 && exp.type === "function") {
console.log("[QML] " + exp.name + " @ " + exp.address);
}
});
}
SSL Pinning Bypass¶
Felgo/Qt apps use QSslSocket for TLS, which links against OpenSSL or BoringSSL compiled into libQt5Network*.so. Standard Java-layer bypasses do not work because Qt manages its own TLS stack.
Hook the native SSL verification:
var sslLib = Process.findModuleByName("libssl.so");
if (sslLib) {
var SSL_CTX_set_verify = sslLib.findExportByName("SSL_CTX_set_verify");
if (SSL_CTX_set_verify) {
Interceptor.attach(SSL_CTX_set_verify, {
onEnter: function(args) {
args[1] = ptr(0);
args[2] = ptr(0);
}
});
}
}
Obfuscation¶
Pre-compiled QML (.qmlc) converts readable QML into Qt bytecode, providing a baseline level of obfuscation -- though string literals remain visible. C++ modules in .so files are stripped of debug symbols in release builds but retain signal/slot method names via Qt's qt_metacall mechanism. Felgo apps in the wild rarely apply additional obfuscation beyond release-mode compilation.
RE Difficulty Assessment¶
| Aspect | QML Layer | C++ Native Layer |
|---|---|---|
| Code format | Plaintext QML/JavaScript | Compiled ARM native |
| Readability | High after .rcc extraction | Low -- standard native RE |
| String extraction | Trivial | Moderate (Qt preserves some metadata) |
| Control flow recovery | Full | Partial (Ghidra) |
| Patching | Edit QML, rebuild .rcc, repackage | Binary patching in Ghidra |
| Overall difficulty | Easy (QML scripts) | Hard (native C++) |
| Combined ranking | Moderate-Hard (rank 25/28) |
The split between readable QML scripts and compiled C++ native code creates a two-tier analysis challenge. If the app's logic is primarily in QML (common for Felgo apps), reverse engineering is straightforward. If significant logic is pushed into C++ modules, difficulty increases substantially. The limited tooling ecosystem for Qt/QML mobile reverse engineering compared to other frameworks adds friction.