Skip to content

Instantly share code, notes, and snippets.

@topin89
Last active May 15, 2026 06:43
Show Gist options
  • Select an option

  • Save topin89/b109342b135649bf8c2e405a6ae79a39 to your computer and use it in GitHub Desktop.

Select an option

Save topin89/b109342b135649bf8c2e405a6ae79a39 to your computer and use it in GitHub Desktop.
Extract files from LG Backup .lbf file
# For modern LG backup files use a script from here: https://github.com/Mysak0CZ/LBFtool
# with details from there https://forum.xda-developers.com/android/general/tool-lg-restore-com-lge-bnr-lbf-file-t4053579
# Fair warning, this script was designed for really old version of LGBackup and won't work on with new devices
# So, if you don't get what you wanted, try to use every data carving tools you can get
#
# Also, good person with nickname SEVENTY ONE recommends ExtractJPEG( https://www.gunamoi.com.au/soft/extractjpeg/download.html )
# To, well, extract JPG Images from a backup
# 0. You will need 7-zip context menu "Extract here".
# 1. Edit filename to your backup file
# 2. Run this script from same folder
# 3. Select all *.brokenscript and choose 7-zip > Extract here
# 4. On overwrite request, choose either "Yes to all" or "No to all", id doesn't matter
# 5. Ta-da! You just got your files.
# 6. Most .db files are in fact SQLite DBs, you can open 'em with SQLiteStudio
# 7. Sms/sms.vmsg you can read here: http://sms-vmsg.org/sms-reader-app/free-app/
# 8. Guess you can import SMS and MMS to your phone with this app (I don't know, It was friend's backup) https://play.google.com/store/apps/details?id=com.aoe.vmgconverter&hl=ru
# 9. All else, I don't know
# 10. May google be with you
import mmap
index=0
filename=f"LGBackup_xxxxxx.lbf"
def read_in_chunks(file_object, chunk_size=1024):
"""Lazy function (generator) to read a file piece by piece.
Default chunk size: 1k."""
while True:
data = file_object.read(chunk_size)
if not data:
break
yield data
def find_in_file(f,signature, startpos):
prev=b''
chunk_size=2**20
for i,cur in enumerate(read_in_chunks(f,chunk_size)):
searchee=prev+cur
pos=searchee.find(signature)
if pos>=0:
return i*chunk_size+pos-len(prev)+startpos
prev=cur
else:
return -1
with open(filename,"rb") as backup:
starpos=0
while True:
pos=find_in_file(backup, b'PK\x03\x04',starpos)
if pos<0:
break
print(backup.tell())
backup.seek(pos)
print(backup.tell())
index+=1
filename=f"{index}.brokenzip"
with open(filename,"wb") as nextfile:
for chunk in read_in_chunks(backup,2**20):
nextfile.write(chunk)
starpos=pos+1
backup.seek(starpos)
print(backup.tell())
print()
# For modern LG backup files use a script from here: https://github.com/Mysak0CZ/LBFtool
# with details from there https://forum.xda-developers.com/android/general/tool-lg-restore-com-lge-bnr-lbf-file-t4053579
# Fair warning, this script was designed for really old version of LGBackup and won't work on with new devices
# So, if you don't get what you wanted, try to use every data carving tools you can get
#
# Also, good person with nickname SEVENTY ONE recommends ExtractJPEG( https://www.gunamoi.com.au/soft/extractjpeg/download.html )
# To, well, extract JPG Images from a backup
# 0. You will need 7-zip.
# 1. Find 7z.exe in a 7-zip folder in Program Files (or whenever you install it) and copy it to a folder with the script
# 2. Edit filename to your backup file
# 3. Run this script from same folder
# 4. Wait a bit (maybe a lot). New subfolder "restored" should appear.
# 5. Ta-da! You just got your files.
# 6. Most .db files are in fact SQLite DBs, you can open 'em with SQLiteStudio
# 7. Sms/sms.vmsg you can read here: http://sms-vmsg.org/sms-reader-app/free-app/
# 8. Guess you can import SMS and MMS to your phone with this app (I don't know, It was friend's backup) https://play.google.com/store/apps/details?id=com.aoe.vmgconverter&hl=ru
# 9. All else, I don't know
# 10. May google be with you
import mmap
from os import system, remove
filename=f"LGBackup_xxxxxx.lbf"
filename_output=f"one_and_only.brokenzip"
def read_in_chunks(file_object, chunk_size=1024):
"""Lazy function (generator) to read a file piece by piece.
Default chunk size: 1k."""
while True:
data = file_object.read(chunk_size)
if not data:
break
yield data
def find_in_file(f,signature, startpos):
prev=b''
chunk_size=2**20
for i,cur in enumerate(read_in_chunks(f,chunk_size)):
searchee=prev+cur
pos=searchee.find(signature)
if pos>=0:
return i*chunk_size+pos-len(prev)+startpos
prev=cur
else:
return -1
def unpack_brokenzip(filename_output):
system(f"7z x -aou -orestored {filename_output}")
with open(filename,"rb") as backup:
starpos=0
while True:
pos=find_in_file(backup, b'PK\x03\x04',starpos)
if pos<0:
break
backup.seek(pos)
print(backup.tell())
with open(filename_output,"wb") as nextfile:
for chunk in read_in_chunks(backup,2**20):
nextfile.write(chunk)
unpack_brokenzip(filename_output)
starpos=pos+1
backup.seek(starpos)
remove(filename_output)
# For modern LG backup files use a script from here: https://github.com/Mysak0CZ/LBFtool
# with details from there https://forum.xda-developers.com/android/general/tool-lg-restore-com-lge-bnr-lbf-file-t4053579
filename="LGBackup_xxxxxx.lbf"
filename_out="LGBackup_xxxxxx_headers_only.lbf"
CHUNK_LENGTH=4096
HEADER=b'PK\x03\x04'
with open(filename,"rb") as backup, open(filename_out,"wb") as newfile:
window=bytearray(backup.read(CHUNK_LENGTH*2))
while window:
header_pos=window.find(HEADER)
if header_pos > -1:
newfile.write(b'0'*header_pos+HEADER)
del window[:header_pos+4]
elif len(window)>CHUNK_LENGTH:
newfile.write(b'0'*CHUNK_LENGTH)
del window[:CHUNK_LENGTH]
else:
newfile.write(b'0'*len(window))
del window[:]
window+=bytearray(backup.read(CHUNK_LENGTH))
@tazik561

tazik561 commented Nov 2, 2020

Copy link
Copy Markdown

I download zip file and extract theme. After extracting i copied my backup into same folder :

tazik@mx-linux:~/recoverlg
$ ls
lbf_extractor.py  lbf_extractor_unpacker.py  LGBackup_201102.lbf  zip_anonymizer.py
tazik@mx-linux:~/recoverlg

I changed filename=f"LGBackup_201102.lbf" in lbf_extractor.py and run it but i got

tazik@mx-linux:~/recoverlg
$ python lbf_extractor.py 
  File "lbf_extractor.py", line 26
    filename=f"LGBackup_201102.lbf"
                                  ^
SyntaxError: invalid syntax
tazik@mx-linux:~/recoverlg

How can i run ?

@topin89

topin89 commented Nov 2, 2020

Copy link
Copy Markdown
Author

Try python3 lbf_extractor.py. Default python is still python2 on most Linux distros.
Also, unless your phone is really old, I suggest https://github.com/Mysak0CZ/LBFtool
The author actually used debugger to get lbf structure and it should extract all files, not just zip archives.

@woodywood25

Copy link
Copy Markdown

Hi,
I have a backup file of my phone in LBF format. Could someone help me write my photos?
I can pay.

I do not speak English. I use a translator.

Thank you

@AlexDev404

Copy link
Copy Markdown

Here's an updated script that doesn't require you to use 7Zip:

import struct
import zlib
import os
import sys

# ── Config ─────────────────────────────────────────────────────────────────────
FILENAME   = "LGBackup_FILENAME.lbf"
OUTPUT_DIR = "extracted"
# ───────────────────────────────────────────────────────────────────────────────

LOCAL_SIG       = b'PK\x03\x04'
DATA_DESC_SIG   = b'PK\x07\x08'

# Local file header layout (after the 4-byte signature):
#   version_needed  2B  (H)
#   flags           2B  (H)
#   compression     2B  (H)
#   mod_time        2B  (H)
#   mod_date        2B  (H)
#   crc32           4B  (I)
#   compressed_sz   4B  (I)
#   uncompressed_sz 4B  (I)
#   fname_len       2B  (H)
#   extra_len       2B  (H)
HEADER_FMT  = '<HHHHHIIIHH'
HEADER_SIZE = struct.calcsize(HEADER_FMT)   # 26 bytes

COMPRESSION_STORED  = 0
COMPRESSION_DEFLATE = 8


def scan_to_signature(f, sig):
    """Advance f until `sig` is found; return file offset of first byte of sig,
    or -1 if EOF is reached first."""
    buf = b''
    n   = len(sig)
    while True:
        byte = f.read(1)
        if not byte:
            return -1
        buf = (buf + byte)[-n:]
        if buf == sig:
            return f.tell() - n


def read_local_header(f):
    """Parse a local file header that starts at the current position (after sig)."""
    raw = f.read(HEADER_SIZE)
    if len(raw) < HEADER_SIZE:
        return None
    fields = struct.unpack(HEADER_FMT, raw)
    (version_needed, flags, compression, mod_time, mod_date,
     crc32, compressed_sz, uncompressed_sz, fname_len, extra_len) = fields

    fname = f.read(fname_len).decode('utf-8', errors='replace')
    f.read(extra_len)   # skip extra field

    return {
        'flags':           flags,
        'compression':     compression,
        'crc32':           crc32,
        'compressed_sz':   compressed_sz,
        'uncompressed_sz': uncompressed_sz,
        'fname':           fname,
        'has_data_desc':   bool(flags & 0x0008),
    }


def read_data_with_descriptor(f):
    """When sizes are 0 (data descriptor flag), slurp bytes until we find
    PK\\x07\\x08 or the next PK\\x03\\x04, then back up past the descriptor."""
    buf = bytearray()
    while True:
        byte = f.read(1)
        if not byte:
            # Reached EOF — return whatever we have
            return bytes(buf)
        buf += byte
        tail = bytes(buf[-4:])

        if tail == DATA_DESC_SIG:
            # Consume the 12-byte descriptor body (crc32 + comp_sz + uncomp_sz)
            f.read(12)
            return bytes(buf[:-4])

        if tail == LOCAL_SIG:
            # Stumbled into the next entry — rewind
            f.seek(-4, 1)
            return bytes(buf[:-4])


def safe_output_path(base_dir, fname):
    """Resolve an Android absolute path safely under base_dir."""
    # Strip leading slashes so os.path.join doesn't discard base_dir
    fname = fname.lstrip('/')
    # Replace internal@/external@ storage prefixes with readable folder names
    fname = fname.replace('internal@/', 'internal/').replace('external@/', 'external/')
    target = os.path.realpath(os.path.join(base_dir, fname))
    if not target.startswith(os.path.realpath(base_dir)):
        raise ValueError(f"Path traversal attempt blocked: {fname!r}")
    return target


def extract(lbf_path, out_dir):
    os.makedirs(out_dir, exist_ok=True)
    extracted = skipped = 0

    with open(lbf_path, 'rb') as f:
        while True:
            pos = scan_to_signature(f, LOCAL_SIG)
            if pos < 0:
                break   # No more entries

            f.seek(pos + 4)   # skip past the 4-byte signature we just found
            header = read_local_header(f)
            if header is None:
                continue

            fname       = header['fname']
            compression = header['compression']

            # ── Directory entry ───────────────────────────────────────────────
            if fname.endswith('/'):
                os.makedirs(safe_output_path(out_dir, fname), exist_ok=True)
                continue

            # ── Read compressed payload ───────────────────────────────────────
            if header['has_data_desc'] and header['compressed_sz'] == 0:
                compressed_data = read_data_with_descriptor(f)
            else:
                compressed_data = f.read(header['compressed_sz'])

            # ── Decompress ────────────────────────────────────────────────────
            try:
                if compression == COMPRESSION_STORED:
                    raw = compressed_data
                elif compression == COMPRESSION_DEFLATE:
                    raw = zlib.decompress(compressed_data, -15)   # raw deflate
                else:
                    print(f"  [SKIP] {fname}  (unsupported compression={compression})")
                    skipped += 1
                    continue
            except zlib.error as e:
                print(f"  [ERR]  {fname}  ({e})")
                skipped += 1
                continue

            # ── Write ─────────────────────────────────────────────────────────
            try:
                out_path = safe_output_path(out_dir, fname)
                os.makedirs(os.path.dirname(out_path), exist_ok=True)
                with open(out_path, 'wb') as out:
                    out.write(raw)
                extracted += 1
                print(f"  [OK]   {fname}  ({len(raw):,} bytes)")
            except (OSError, ValueError) as e:
                print(f"  [ERR]  {fname}  ({e})")
                skipped += 1

    print(f"\n{'─'*50}")
    print(f"Extracted : {extracted}")
    print(f"Skipped   : {skipped}")
    print(f"Output    : {os.path.abspath(out_dir)}/")


if __name__ == '__main__':
    lbf = sys.argv[1] if len(sys.argv) > 1 else FILENAME
    if not os.path.exists(lbf):
        sys.exit(f"File not found: {lbf!r}")
    extract(lbf, OUTPUT_DIR)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment