Skip to content

Commit ef8ef7a

Browse files
author
ThreePlanetsSoftware
committed
Updated Android backup export to not attempt to only work in memory as large files crashed out
1 parent 7ede10a commit ef8ef7a

File tree

2 files changed

+80
-65
lines changed

2 files changed

+80
-65
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
By: Jon Baumann, [Ciofeca Forensics](https://www.ciofecaforensics.com)
33

44
## About
5-
This script mines SQLite databases for hidden gems that might be overlooked. It identifies for the forensic examiner which databases, tables, and columns had which potential types of files within them.
5+
This script mines SQLite databases for hidden gems that might be overlooked. It identifies for the forensic examiner which databases, tables, and columns had which potential types of files within them. For an explanation of why I wrote this script, please see [this blog entry](https://www.ciofecaforensics.com/2017/10/23/mining-hidden-gems-with-sqlite-miner/).
66

77
## How It Works
88
This script searches identified SQLite databases to find files that are hidden in blob objects within the database. The `fun_stuff.pl` file controls the regular expressions that will be matched to assert that a given blob is a given file type. Currently it only supports file types whose magic number starts at offset 0.
@@ -13,13 +13,13 @@ This script is run by perl on a command line. The easiest usage is to look at on
1313

1414
For a larger outlook, the script can be run to recursively look at an entire directory with `perl sqlite_miner.pl --dir=<path to directory>`. That will cause the script to recursively walk through every file under that directory, check the file's header to see if it is SQLite format 3, and run each identified SQLite file as if it had been done using the `--file=` option above. The only difference is all of the results from that entire folder will be stored under an output directory named `YYYY_MM_DD_<folder_name>`. For example, running this on /home/test/backup_unpacked today will generate `2017_10_21_backup_unpacked/`. The `results.csv` will contain all results from the entire directory, but each specific database will have its own output folder within the overall directory.
1515

16-
The script can also be used to open an Adnroid backup without unpacking it first with `perl sqlite_miner.pl --android-backup=<path to backup>`. That will cause the script to open the Android backup, decompress the TAR portion to a temporary folder, then recurseivly walk through the TAR file, exporting any files that have a SQLite format 3 header. Then the script will behave the same as using the `--dir=` option described above. When done, the script will remove the temporary folder that the TAR file was saved in, but please be aware that for large, full, backups, this may result in an additional 1GB+ of space (the same amount of sapce as if you decompressed the file in order to run the `--dir=` option against an actual folder.
16+
The script can also be used to open an Adnroid backup without unpacking it first with `perl sqlite_miner.pl --android-backup=<path to backup>`. That will cause the script to open the Android backup, decompress the TAR portion to a temporary folder, then iteratively walk through the TAR file, exporting any files that have a SQLite format 3 header. At that point the script will behave the same as using the `--dir=` option described above. When done, the script will remove the temporary folder that the TAR file was saved in, but please be aware that for large, full, backups, this may result in an additional 1GB+ of space (the same amount of space as if you decompressed the file in order to run the `--dir=` option against an actual folder). In addition, while this script attempts to cut down on memory usage, for large files tar may run out of memory and crash. If that occurs, decompress the file separately, then run this script with the `--dir` option on the exported data. Finally, decompressing the Android backup and working through the TAR will add time on to the script's processing. For example, a 1GB backup.ab file took roughly an additional 45 seconds to run against the Android backup, as compared to the exported files on a not-very-powerful computer. That said, decompressing the files takes roughly as long.
1717

1818
### Options
1919
The required options that are currently supported are (one of):
2020
1. `--file=`: This option tells the script where to find the SQLite you want to mine.
21-
2. `--dir=`: This option tells the script where to find a directory to recursively search for SQLite format 3 database files and to parse each of them as if the --fiile option was called on them above.
22-
3. `--android-backup=`: This option tells the script where to find ain Android backup file to recursively search for SQLite format 3 database files and to parse each of them as if the --fiile option was called on them above.
21+
2. `--dir=`: This option tells the script where to find a directory to recursively search for SQLite format 3 database files and to parse each of them as if the `--file` option was called on them above.
22+
3. `--android-backup=`: This option tells the script where to find an Android backup file to recursively search for SQLite format 3 database files and to parse each of them as if the `--file` option was called on them above.
2323

2424
The optional arguments are:
2525
1. `--decompress`: This option tells the script to decompress any compressed data it knows it can unpack and replace the original data with the decompressed data to provide the examiner with a plaintext view. Note, this option drastically increases the run time as now the script is reading in the comrpessed object, decompressing it, and writing it back into the database.

sqlite_miner.pl

Lines changed: 76 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ sub create_run_output_directory {
629629

630630
# Check to make sure the overall output folder exists
631631
if(! -e $output_directory) {
632-
print "Output folder: ".File::Spec->abs2rel($output_directory)."\n" if $verbose;
632+
print "\nOutput folder: ".File::Spec->abs2rel($output_directory)."\n" if $verbose;
633633
mkdir $output_directory;
634634
}
635635

@@ -712,82 +712,96 @@ sub extract_sqlite_from_android_backup {
712712
my $output_directory = @_[2];
713713
my $results_file = @_[3];
714714

715+
print_log_line($log_file_handle, "Searching Android backup file: ".File::Spec->abs2rel($android_backup)."\n");
716+
715717
# Create a temporary space to hold our work
716718
my $tmp_export_dir = File::Spec->catdir(File::Spec->curdir(), ".decompressed_tmp");
719+
720+
# Clean up from any previous runs that error'd out
721+
if(-e $tmp_export_dir) {
722+
print_log_line_if($log_file_handle, "Deleting existing export directory left over from previous run\n", $very_verbose;
723+
File::Path->remove_tree($tmp_export_dir);
724+
}
725+
726+
# Create export directory
717727
mkdir $tmp_export_dir;
718-
print_log_line($log_file_handle, "Creating temporary directory for decompressed files: ".File::Spec->abs2rel($tmp_export_dir)."\n");
728+
print_log_line_if($log_file_handle, "Creating temporary directory for decompressed files: ".File::Spec->abs2rel($tmp_export_dir)."\n", $verbose);
719729

720730
# Identify where we'll put the TAR portion of the backup
721731
my $tmp_android_tar_file = File::Spec->catfile($tmp_export_dir, "backup.tar");
722732

723-
my $tar_handler = new Archive::Tar;
724-
725733
# Open the full Android backup and remove the header
726734
print_log_line_if($log_file_handle, "Opening Android backup and saving a copy, this may take a few seconds\n", $verbose);
727735
open(ANDROIDBACKUP, "<$android_backup");
728-
undef $/;
729-
seek(ANDROIDBACKUP, 24, 0);
730-
my $entire_tar = <ANDROIDBACKUP>; # Get everything that's after the header
731-
close(ANDROIDBACKUP);
732-
733-
# Decrompress the android backup's ZLIB and save it to disk (Archive::Tar won't work from memory)
734-
anyuncompress(\$entire_tar, $tmp_android_tar_file);
735-
print_log_line_if($log_file_handle, "Saving the TAR portion of the backup to: ".File::Spec->abs2rel($tmp_android_tar_file)."\n", $verbose);
736-
737-
# Open the TAR archive
738-
$tar_handler->read($tmp_android_tar_file);
739-
print_log_line($log_file_handle, "Opening TAR portion of archive\n");
740-
741-
# Loop over all the files contained therein
742-
my $tmp_tar_files_extracted = 0;
743-
my @tmp_tar_files = $tar_handler->list_files();
744-
foreach $tmp_tar_file (@tmp_tar_files) {
745-
my $tmp_tar_content = $tar_handler->get_content($tmp_tar_file);
746-
747-
# Check for SQLite files and save them to the export directory
748-
if($tmp_tar_content =~ /^SQLite format 3/) {
749-
my $tmp_sqlite_file = File::Spec->catfile($tmp_export_dir, $tmp_tar_file);
750-
$tar_handler->extract_file($tmp_tar_file, $tmp_sqlite_file);
751-
print_log_line_if($log_file_handle, "Found embedded SQLite file in TAR: $tmp_tar_file\n", $very_verbose);
752-
$tmp_tar_files_extracted += 1;
753-
}
754-
}
755736

756-
# Walk through exported files and mine them all
757-
my @files_to_mine;
758-
find(
759-
sub {
760-
if(! -d $_ && file_is_sqlite($_)) {
761-
my $tmp_filepath = $File::Find::name;
762-
print_log_line_if($log_file_handle, "Found SQLite: $tmp_filepath\n", $verbose);
763-
push(@files_to_mine, $tmp_filepath);
737+
# Check to make sure this is an Android backup
738+
my $tmp_file_header;
739+
read ANDROIDBACKUP, $tmp_file_header, 14;
740+
if($tmp_file_header =~ /^ANDROID BACKUP/) {
741+
undef $/;
742+
seek(ANDROIDBACKUP, 24, 0);
743+
my $entire_tar = <ANDROIDBACKUP>; # Get everything that's after the header
744+
close(ANDROIDBACKUP);
745+
746+
# Decrompress the android backup's ZLIB and save it to disk (Archive::Tar won't work from memory)
747+
anyuncompress(\$entire_tar, $tmp_android_tar_file);
748+
print_log_line_if($log_file_handle, "Saving the TAR portion of the backup to: ".File::Spec->abs2rel($tmp_android_tar_file)."\n", $verbose);
749+
undef $entire_tar;
750+
751+
# Open the TAR archive
752+
print_log_line($log_file_handle, "Opening TAR portion of archive\n");
753+
my $tmp_tar_files_extracted = 0;
754+
my $tar_iterator = Archive::Tar->iter($tmp_android_tar_file);
755+
while(my $tmp_tar_file = $tar_iterator->()) {
756+
my $tmp_tar_content = $tmp_tar_file->get_content();
757+
if($tmp_tar_content =~ /^SQLite format 3/) {
758+
my $tmp_sqlite_file = File::Spec->catfile($tmp_export_dir, $tmp_tar_file->name());
759+
$tmp_tar_file->extract($tmp_sqlite_file);
760+
print_log_line_if($log_file_handle, "Found embedded SQLite file in TAR: ".$tmp_tar_file->name()."\n", $very_verbose);
761+
$tmp_tar_files_extracted += 1;
764762
}
765-
},
766-
$tmp_export_dir
767-
);
768-
print_log_line_if($log_file_handle, "\n", $verbose);
769-
foreach $tmp_file (sort(@files_to_mine)){
770-
771-
# Remember how many blobs we're currently at
772-
my $current_blob_count = $total_identified_blobs;
773-
774-
# Run the parsing and store the export folder
775-
my $tmp_run_folder = mine_file($output_directory, $tmp_file, $results_file, 1, $log_file_handle);
763+
}
776764

777-
# Remove the copied files if we didn't actually do any work with them
778-
if($total_identified_blobs == $current_blob_count) {
779-
File::Path->remove_tree(File::Spec->abs2rel($tmp_run_folder));
765+
# Walk through exported files and mine them all
766+
my @files_to_mine;
767+
find(
768+
sub {
769+
if(! -d $_ && file_is_sqlite($_)) {
770+
my $tmp_filepath = $File::Find::name;
771+
print_log_line_if($log_file_handle, "Found SQLite: $tmp_filepath\n", $verbose);
772+
push(@files_to_mine, $tmp_filepath);
773+
}
774+
},
775+
$tmp_export_dir
776+
);
777+
print_log_line_if($log_file_handle, "\n", $verbose);
778+
foreach $tmp_file (sort(@files_to_mine)){
779+
780+
# Remember how many blobs we're currently at
781+
my $current_blob_count = $total_identified_blobs;
782+
783+
# Run the parsing and store the export folder
784+
my $tmp_run_folder = mine_file($output_directory, $tmp_file, $results_file, 1, $log_file_handle);
785+
786+
# Remove the copied files if we didn't actually do any work with them
787+
if($total_identified_blobs == $current_blob_count) {
788+
File::Path->remove_tree(File::Spec->abs2rel($tmp_run_folder));
789+
}
780790
}
781-
}
782791

783-
# Tell the user what we've done here
784-
my $tmp_tar_status_line = "Found $tmp_tar_files_extracted SQLite database";
785-
if($tmp_tar_files_extracted > 1) {
786-
$tmp_tar_status_line .= "s";
792+
# Tell the user what we've done here
793+
my $tmp_tar_status_line = "Found $tmp_tar_files_extracted SQLite database";
794+
if($tmp_tar_files_extracted > 1) {
795+
$tmp_tar_status_line .= "s";
796+
}
797+
$tmp_tar_status_line .= " in the Android backup, mining them all.\n";
798+
print_log_line_if($log_file_handle, $tmp_tar_status_line, $tmp_tar_files_extracted);
799+
print_log_line_if($log_file_handle, "Found no SQLite databases in Android backup.\n", !$tmp_tar_files_extracted);
800+
801+
} else {
802+
close(ANDROIDBACKUP);
803+
print_log_line($log_file_handle, "$android_backup does not appear to be an Android backup, quitting\n");
787804
}
788-
$tmp_tar_status_line .= " in the Android backup, mining them all.\n";
789-
print_log_line_if($log_file_handle, $tmp_tar_status_line, $tmp_tar_files_extracted);
790-
print_log_line_if($log_file_handle, "Found no SQLite databases in Android backup.\n", !$tmp_tar_files_extracted);
791805

792806
# Clean up our temporary directory
793807
File::Path->remove_tree($tmp_export_dir);
@@ -812,6 +826,7 @@ sub print_usage {
812826
print "\t--dir=<path>: Identifies a directory to recursively search to find SQLite files to work on.\n";
813827
print "\t--android-backup=<path>: Identifies an Android backup to open and search for SQLite files to work on.\n";
814828
print "\t\tNote: This will unpack the Android backup on disk for TAR, so please ensure you have available space.\n";
829+
print "\t\tNote: This also can be memory intensive while decompressing. If it errors out due to low memory, extract the backup yourself and use the --dir= option.\n";
815830
print "\nOptional Options:\n";
816831
print "\t--decompress: If set, will decompress recognized and supported compressed blobs, replacing the original blob contents on the working copy\n";
817832
print "\t\tNote: Decompress gets very slow in a database with large compressed objects. Expect this to take a few seconds to run.\n";

0 commit comments

Comments
 (0)