A reverse-engineered decryptor for OPPO / OnePlus ColorOS DOG3 log files.
DOG3 files are encrypted on-disk log archives produced by the clogan /
libopluslog.so telemetry stack shipped with ColorOS. They persist app-level
diagnostic logs (e.g. HLog_File_*.txt, comheytapspeechassist_*.txt) in a
binary, AES-encrypted container that is undocumented and unsupported by any
official tool. This project provides a working Python decryptor for those
files.
- Background
- How It Works
- Repository Structure
- Requirements
- Installation
- Usage
- Output Format
- Limitations
- Reverse-Engineering Notes
- Disclaimer
- License
ColorOS (the Android variant shipped on OPPO, OnePlus and Realme devices for
the Chinese market) writes application logs to the user-accessible storage as
*.txt files with names such as:
HLog_File_metis_main_2026_08_13_10.txt
HLog_File_main_process_2026_08_13_10.txt
comheytapspeechassist_2026_08_09_06.txt
ipspace_main_process_2026_08_13_10.txt
Despite the .txt extension, these files are not text: they are binary
containers in the Logan format (originally open-sourced by Meituan-Dianping as
Logan) with OPPO-specific
modifications. Each record is AES-encrypted and gzip-compressed, making the
logs unreadable without the matching key material.
Because there is no documented way to read these files back, this project was created to fill that gap for users who legitimately want to inspect the diagnostic data their own device has been writing.
The decryptor performs three stages:
-
Container parsing — the DOG3 file is split into individual records. Each record follows the layout:
+--------+-------------------+-----------------------------+--------+ | type | content_len (BE) | encrypted content | tail | | 1 byte | 4 bytes | content_len bytes | 0x00 | +--------+-------------------+-----------------------------+--------+typeis0x01(or occasionally0x02),content_lenis a big-endian uint32, and the record ends with a single0x00byte. -
AES decryption — each record is decrypted independently using AES-128-CTR with an empty nonce and the IV used as the initial 128-bit counter. The key and IV are hard-coded constants extracted from
libopluslog.soinside the device's OTA firmware (see Reverse-Engineering Notes):Parameter Value Source offset in libopluslog.so Key w5rr6tiSDL6Usl8Q0x02E0E40BIV nNEFaeOInosl3hwF0x02E0E239The cipher is re-initialised for every record (each record is encrypted with the same key+IV — they are not chained).
-
gzip decompression — the decrypted bytes form a valid gzip stream (
1f 8b 08 ...). Inflating it yields UTF-8 JSON: one log event per record.
Dog3Decryption/
├── decrypt_dog3.py # Main decryptor (CLI + library)
├── requirements.txt # Python dependencies
├── LICENSE # Apache License 2.0
└── README.md # This file
The examples/ directory (sample DOG3 files from a real device) and the
output/ directory (decryption results) are intentionally not tracked.
See .gitignore.
- Python 3.8+
- pycryptodome — AES primitives
- Python standard library:
struct,zlib,os,sys
Tested on CPython 3.11 / 3.12 on Windows and Linux. No native compilation required.
git clone https://github.com/ChidcGithub/Dog3Decryption.git
cd Dog3Decryption
pip install -r requirements.txtpython decrypt_dog3.pypython decrypt_dog3.py /path/to/HLog_File_metis_main_2026_08_13_10.txt
python decrypt_dog3.py file1.txt file2.txt file3.txtDecrypted output is written to ./output/<filename>.dec as UTF-8 text.
from decrypt_dog3 import parse_dog3, decrypt_record
with open("HLog_File_metis_main_2026_08_13_10.txt", "rb") as f:
records = parse_dog3(f.read())
for rec_type, length, ciphertext, tail in records:
plaintext = decrypt_record(ciphertext) # bytes, UTF-8 JSON
print(plaintext.decode("utf-8", errors="replace"))Each decrypted record is one JSON object representing a single log event:
{
"c": "{\"m\":\"onCellInfoChanged: cell size\u003d1\",\"t\":\"metis_v2_MetroAwarenessCollector\",\"l\":3,\"p\":\"\",\"pid\":4631}",
"f": 101,
"l": 1786586405155,
"n": "pool-8-thread-329",
"i": 26642
}| Field | Meaning |
|---|---|
c |
Log content (often a nested JSON string) |
f |
File / module id |
l |
Event timestamp (Unix milliseconds) |
n |
Thread name |
i |
Sequence / thread id |
A typical run produces ~120 decrypted records and several megabytes of plain JSON per DOG3 file.
- Static keys. The AES key and IV are baked into
libopluslog.soand are shared across all ColorOS devices running the same firmware branch. They are not device-specific on the tested firmware (ColorOS 16.1.0, Android 16, OnePlus RMX5200), but this is not guaranteed for future releases. If a future build introduces a device-specific KDF (e.g. seeding the key from the TEE, IMEI or serial number), this decryptor will stop working without additional reverse-engineering. - No partial-record recovery. The final record in each file is frequently a truncated gzip tail written at session shutdown. It cannot be recovered and is reported as a failed record in the script output. All prior records decode cleanly.
- Format only. This tool decrypts the container; it does not interpret the
inner JSON schemas emitted by individual OPPO subsystems (
metis_v2_*,ContextAware_*, etc.).
The key material was located by statically analysing libopluslog.so,
extracted from system_ext.img inside a stock OTA payload.bin (EROFS
image, payload_dumper-go).
Relevant strings found in the binary include:
AES-CFB128-%3d (%s):at0x02E0E005— a misleading mode hint; the actual cipher used on-disk is AES-CTR, not CFB128. Counter mode was identified empirically: CTR is the only standard AES mode whose first keystream block equalsAES-ECB(K, IV)and whose per-block feedback is independent of the previous block — which is exactly the behaviour exhibited by the file: each record starts with a valid gzip header (1f 8b 08 …) underAES_CTR(K, nonce=b"", initial_value=IV), and the full stream decompresses correctly.w5rr6tiSDL6Usl8Qat0x02E0E40B— the 16-byte AES key.nNEFaeOInosl3hwF{at0x02E0E239— 17 bytes on disk; the first 16 form the IV, the trailing{is an adjacent unrelated constant.just_byteord,init_key_iv,set_key_iv,aes_init,.dog3,logancrypt— runtime symbol fragments confirming Logan lineage.- Verification:
AES-ECB(key, IV)produces a keystream block whose XOR with the first 16 ciphertext bytes recovers the canonical gzip header1f 8b 08 00 00 00 00 00 02 03 …, confirming the key/IV pair.
This project is provided for legitimate diagnostic and interoperability purposes only — for example, inspecting telemetry that your own device has written about your own usage. The author is not affiliated with OPPO, OnePlus, Realme or Meituan. Do not use this tool to access logs that you do not have permission to read. You are solely responsible for complying with all applicable laws and the terms of service of your device.
Released under the Apache License, Version 2.0. This license explicitly disclaims any grant of trade-secret, trademark or patent rights; only copyright is conveyed, and only for uses that independently comply with applicable law (see the Disclaimer above).