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))
@Ownster
Copy link
Copy Markdown

Ownster commented Oct 7, 2018

Thank you very much

@topin89
Copy link
Copy Markdown
Author

topin89 commented Oct 16, 2018

You're welcome

@cdez-cactus-os
Copy link
Copy Markdown

How long does this take? I have been sitting here for a while, and it seems to just keep going and going...

@topin89
Copy link
Copy Markdown
Author

topin89 commented Dec 4, 2018

It should not have be this long. Strange. If you have more RAM than size of a backup, try first revision. If not, write me directly to topin89@mail.ru, I will try to help.

@topin89
Copy link
Copy Markdown
Author

topin89 commented Jan 9, 2019

New script, zip_anonymizer.py
If lbf_extractor.py is not working, you can run backup through zip_anonymizer.py and send me result. It basically replaces everything except ZIP header with '0' (not '\0').
Then you can zip this new file and send it to me so I can actually improve my code.

@topin89
Copy link
Copy Markdown
Author

topin89 commented Jan 9, 2019

New script, lbf_extractor_unpacker.py
It should unpack all files by itself, but it requires "7z.exe" to be in the same folder with it. It should be in 7-Zip folder in Program Files. You can also add 7z.exe to your PATH env variable. On Debian or Ubuntu, you can just install p7zip-full. On anything else, I sincerely don't know.

@MrSALSA
Copy link
Copy Markdown

MrSALSA commented Mar 4, 2019

Great work topin89!

I was looking for a way to get the databases of LG Health app. Now I got it thanks to you. ;-)

I edited it using "DB Browser for SQLite" and tried to get it back on my LG G6 using LG UP. But LG UP throw the error that the backup file is defective. I guess it's about the missing "broken zip" files, which I can not recreate from my extracted and modified files.

So question is: How do I get the database file(s) back to the smartphone without LG UP complaining about "defctive backup files"?
Do you got any idea or even a script already for processing the vice versa way to your scripts?

Cheers

@topin89
Copy link
Copy Markdown
Author

topin89 commented Mar 7, 2019

Thanks, @MrSALSA

Sorry, but I don't know how to recreate backup files. I made this for a friend with new LG that was not compatible with old backups for some reason (or we didn't get how to use it), so there was no reason to examine how it works in full, just how to extract.

If you need to just edit db of an app, best thing I can suggest is edit via something like this (root required)
https://play.google.com/store/apps/details?id=com.ksk.sqliteeditor

or via ADB shell from Android SKD, as suggested here:
https://android.stackexchange.com/questions/41353/android-manually-alter-the-contents-of-an-apps-database

@MrSALSA
Copy link
Copy Markdown

MrSALSA commented Mar 11, 2019

Thanks, for reply topin!
I'll write you a messege to explain my problem furtherly. I guess it's to boring to other users... ;-)
Cheers

@Mysak0CZ
Copy link
Copy Markdown

Hello, @topin89
Recently I've done a bit of digging into lbf files and here are my findings (including extract tool), if you are interested.
https://forum.xda-developers.com/android/general/tool-lg-restore-com-lge-bnr-lbf-file-t4053579

@topin89
Copy link
Copy Markdown
Author

topin89 commented Feb 26, 2020

Hello, @Mysak0CZ

Holy cow!
Well, I don't really interested in development, but I'm sure as hell I'll link give a link in all files in this gist! I also recommend to make a gist for your script. At least three persons got me directly from here, not from the XDA thread. Repo would be even better.

Edit: "HP" Replaced with "LG". I guess I'm actually tired now.
My script, I did it to restore notes of my friend from some LG notepad app. I didn't even have an LG phone.

How do you get that header is "AES/ECB/PKCS5Padding + SHA256"? What tool do you use? That's what I'm really interested in. To be clear, I have zero crypto-related reverse engineering experience.

Also, link to technical part goes to https://www.amazon.com/dp/1012097587 (Technical Reports, Part 4 by Miami Conservancy District ) and most likely wrong.
Edit: so, random "funny link", XDA? Hilarious.

@tazik561
Copy link
Copy Markdown

tazik561 commented Nov 2, 2020

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
Copy link
Copy Markdown
Author

topin89 commented Nov 2, 2020

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