Skip to content

leolovenet/logan-dump

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

logandump

A command-line tool for decrypting Logan mobile log files.

Logan is Meituan's open-source mobile logging framework, used by Android and iOS apps for compact encrypted log collection. logandump reads a Logan binary log file, decrypts each record with AES (CBC or CTR), strips the PKCS7 padding, decompresses the gzip payload, and writes the resulting JSON log lines to stdout or to a file.

Features

  • CBC and CTR modes — supports both the vanilla Meituan format and the OPPO CTR-mode variant observed in the wild
  • Key and IV as file — read secrets from files so they don't leak via ps (--key-file, --iv-file)
  • Lenient with statistics — bad records are skipped with a per- record stderr warning and a final summary, so a partially-corrupted file still yields whatever records survive
  • Pipeable output — raw JSON stream to stdout, ready for jq or grep
  • Safe defaults — write-binary output (no accidental appending), SIGPIPE restored (no traceback when piped to head)

Prerequisites

Required

  • Python 3.10+ (uses built-in generic syntax list[str], str | None)
  • pycryptodome — the AES primitive. Install with python3 -m pip install pycryptodome.

pycryptodome IS published to PyPI (unlike some other crypto libraries of a similar name, and unlike Meituan's MMKV which is source-build only). A single pip install should suffice.

Installation

Step 1 — Install pycryptodome

# For your default python3
python3 -m pip install pycryptodome

# ...or for a specific interpreter if you have multiple
python3.11 -m pip install pycryptodome

Verify under the interpreter you plan to use:

python3.11 -c "from Crypto.Cipher import AES; print(AES)"

Step 2 — Install the script

Pick the pattern that matches your Python setup.

Pattern A — Simple symlink

If python3 -c "from Crypto.Cipher import AES" works, the script's shebang (#!/usr/bin/env python3) will find the right interpreter. Just symlink the script onto your $PATH:

ln -s "$(pwd)/logandump.py" /usr/local/bin/logandump

Pattern B — Wrapper script

If your default python3 doesn't have pycryptodome, create a small bash wrapper that pins the correct interpreter. Save the following as ~/bin/logandump (or anywhere on your $PATH):

#!/bin/bash
exec python3.11 /path/to/logan-dump/logandump.py "$@"

Then make it executable:

chmod +x ~/bin/logandump

Tune to taste:

  • Replace python3.11 with the interpreter where pycryptodome actually lives (~/.venvs/logan/bin/python, /opt/homebrew/bin/python3.11, …)
  • Replace /path/to/logan-dump/logandump.py with the absolute path to the script in this repository

Two details in the wrapper matter:

  • exec replaces the bash process with Python, avoiding a dangling shell and making signal handling (Ctrl-C, SIGPIPE) behave correctly
  • "$@"quote it! Without the quotes, arguments containing spaces get re-split by the shell, so a path like -f "~/Desktop/some dir/logan.log" silently breaks

Step 3 — Verify

logandump --version
logandump --help

Usage

logandump -f <logan-file> (--key KEY | --key-file PATH) (--iv IV | --iv-file PATH) [options]

Options

Flag Description
-f <path> Required. Path to the Logan binary log file
-o <path> Write decoded JSON to a file (default: stdout)
-m {CBC,CTR} AES mode (default: CBC; use CTR for OPPO-produced files)
-k, --key <str> AES key as a literal string — visible via ps
--key-file <path> Read the AES key from a file (preferred)
-i, --iv <str> AES IV as a literal string — visible via ps
--iv-file <path> Read the AES IV from a file (preferred)
--version Print version and exit

Exactly one of --key/--key-file and one of --iv/--iv-file must be supplied.

Both key and IV must encode to exactly 16 bytes of UTF-8.

Examples

In the snippets below, KEY16 / IV16 stand for your own 16-byte AES key and IV — logandump does not ship with any credentials.

# Decrypt an OPPO Logan file (CTR mode) to stdout
logandump -m CTR --key KEY16 --iv IV16 -f test.log

# Pipe through jq to pretty-print each record
logandump -m CTR --key KEY16 --iv IV16 -f test.log | jq -c '.'

# Write to a file instead of stdout
logandump -m CTR --key KEY16 --iv IV16 -f test.log -o out.json

# Read key and IV from files (avoids leaking via `ps`)
printf '%s' 'YOUR_16_BYTE_KEY' > ~/.logan.key
printf '%s' 'YOUR_16_BYTE_IV'  > ~/.logan.iv
chmod 600 ~/.logan.key ~/.logan.iv   # don't leave secrets world-readable
logandump -m CTR --key-file ~/.logan.key --iv-file ~/.logan.iv -f test.log

# Decrypt a vanilla Meituan file (CBC mode is the default)
logandump --key KEY16 --iv IV16 -f logan.log

# Filter for a specific thread
logandump -m CTR --key KEY16 --iv IV16 -f test.log | jq -c 'select(.n == "JsThread")'

Exit codes

Code Meaning
0 All records decrypted successfully, OR some failed but some succeeded
1 Fatal error: file not found, wrong key length, or ZERO records decrypted (usually wrong key/IV/mode)
2 argparse rejected the command line (missing required option, etc.)

How it works

Logan's binary format is not documented in the upstream README or wiki. The format understanding for this tool was derived from the C reference implementation in Meituan's repository at Logan/Clogan/ — specifically the restore_last_position_clogan function in clogan_core.c and clogan_zlib_end_compress in zlib_util.c.

The per-record layout on disk is:

[1 byte ] 0x01                      LOGAN_WRITE_PROTOCOL_HEADER
[4 bytes] content_len (big-endian u32)
[N bytes] encrypted content         AES(gzip(JSON lines) + PKCS7)
[1 byte ] 0x00                      LOGAN_WRITE_PROTOCOL_TAIL

The decryption pipeline per record:

  1. Read marker byte and verify it is 0x01.
  2. Read the 4-byte big-endian length.
  3. Read length bytes of encrypted content.
  4. Read and discard the trailing 0x00 tail byte (it is always present and is NOT counted in length).
  5. AES-decrypt the encrypted bytes using a fresh cipher (Meituan's writer resets the IV to the master value at each record start).
  6. Strip PKCS7 padding using the last byte as the length (always 1-16 by the writer's invariant, never 0).
  7. gzip.decompress() the remaining bytes to get the raw JSON log stream, each record terminated by \n.
  8. Write the decompressed bytes to the output stream as-is.

Each gzip chunk decodes to one or more newline-terminated JSON objects with the Logan field schema: c (content), f (flag), l (local time in ms epoch), n (thread name), i (thread id), and optionally m (is-main-thread).

See CLAUDE.md in this repository for the full design-decision rationale, including why PKCS7 length is guaranteed to be in [1, 16], why CTR mode shares CBC's framing in the OPPO variant, and how this tool was verified against a real OPPO test file.

License

MIT — see LICENSE.

About

CLI tool for decrypting Meituan Logan mobile log files (AES-CBC/CTR)

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages