これは不気味―iPhoneには過去の位置情報が逐一記録されていることが判明 | TechCrunch Japanã¨ããè¨äºã話é¡ã«ã
iPhoneã§åå¾ããä½ç½®æ
å ±ãè¨é²ããã¦ãããã¨ãããã®ããã®ãã¼ã¿ãæãåºãã¦å¯è¦åãããã¼ã«ãå
¬éããã¦ããã
petewarden/iPhoneTracker @ GitHub
ã½ã¼ã¹ãå
¬éããã¦ããã®ã§è¦ãã¦ã¿ãã¨ãããã©ããã"$HOME/Library/Application Support/MobileSync/Backup"以ä¸ã®ãã¡ã¤ã«ã«ãããã®æ
å ±ãæ ¼ç´ãã¦ããsqliteã®ãã¡ã¤ã«ããããããããããããã¹ã¦æãåºãã¦ãããããããã Backupãã£ã¬ã¯ããªä»¥ä¸ã«ã¯ç¡æ°ã®ãã¡ã¤ã«ããããã©ããã©ããåãããªãããããå¤å¥ããããã«"Manifest.mbdb", "Manifest.mbdx"ã¨ãããã¡ã¤ã«ã解æãã¦ããããã ã解ææ¹æ³èªä½ã¯iphone - How to parse the Manifest.mbdb file in an iOS 4.0 iTunes Backup - Stack Overflowã«ããpythonã®ã³ã¼ããåèã«ãã¦ããããããããããsqliteã®ãã¡ã¤ã«ãå²ãåºããã°ãæ¯è¼çç°¡åã«ãã¼ã¿ãæãåºãã¦èªç±ã«æ±ããã¨ãã§ãããã
ã¨ãããã¨ã§ä½ã£ã¦ã¿ããPerlã§ã
#!/usr/bin/env perl use strict; use warnings; use File::HomeDir; use Path::Class 'dir'; my $home = File::HomeDir->my_home; my @dir = sort { $b->stat->mtime <=> $a->stat->mtime } dir("$home/Library/Application Support/MobileSync/Backup")->children; my $mbdb = process_mbdb_file($dir[0]->file('Manifest.mbdb')); my $mbdx = process_mbdx_file($dir[0]->file('Manifest.mbdx')); my $dbfile; for my $key (keys %{ $mbdb }) { $dbfile = $dir[0]->file($mbdx->{$mbdb->{$key}{start_offset}})->stringify; } die unless $dbfile; print "dbfile: $dbfile\n"; sub process_mbdb_file { my ($mbdb) = @_; my $fh = $mbdb->openr; $fh->binmode; my $buffer; $fh->read($buffer, 4); die if $buffer ne 'mbdb'; $fh->read($buffer, 2); my $offset = 6; my $data = +{}; while ($offset < $mbdb->stat->size) { my $fileinfo = +{}; $fileinfo->{start_offset} = $offset; $fileinfo->{domain} = getstring($fh, \$offset); $fileinfo->{filename} = getstring($fh, \$offset); $fileinfo->{linktarget} = getstring($fh, \$offset); $fileinfo->{datahash} = getstring($fh, \$offset); $fileinfo->{unknown1} = getstring($fh, \$offset); $fileinfo->{mode} = getint($fh, 2, \$offset); $fileinfo->{unknown2} = getint($fh, 4, \$offset); $fileinfo->{unknown3} = getint($fh, 4, \$offset); $fileinfo->{userid} = getint($fh, 4, \$offset); $fileinfo->{groupid} = getint($fh, 4, \$offset); $fileinfo->{mtime} = getint($fh, 4, \$offset); $fileinfo->{atime} = getint($fh, 4, \$offset); $fileinfo->{ctime} = getint($fh, 4, \$offset); $fileinfo->{filelen} = getint($fh, 8, \$offset); $fileinfo->{flag} = getint($fh, 1, \$offset); $fileinfo->{numprops} = getint($fh, 1, \$offset); $fileinfo->{properties} = +{}; for (1 .. $fileinfo->{numprops}) { my $key = getstring($fh, \$offset); my $value = getstring($fh, \$offset); $fileinfo->{properties}{$key} = $value; } # å¿ è¦ãªã®ã¯ãããå«ã¾ãã¦ãããã®ã ã if ($fileinfo->{filename} eq 'Library/Caches/locationd/consolidated.db') { $data->{$fileinfo->{start_offset}} = $fileinfo; } }; return $data; } sub process_mbdx_file { my ($mbdx) = @_; my $fh = $mbdx->openr; $fh->binmode; my $buffer; $fh->read($buffer, 4); die if $buffer ne 'mbdx'; $fh->read($buffer, 2); my $offset = 6; my $filecount = getint($fh, 4, \$offset); my $data = +{}; while ($offset < $mbdx->stat->size) { $fh->read($buffer, 20); $offset += 20; my $file_id = unpack("H*", $buffer); my $mbdb_offset = getint($fh, 4, \$offset); my $mode = getint($fh, 2, \$offset); $data->{$mbdb_offset + 6} = $file_id; } return $data; } sub getint { my ($fh, $size, $offset) = @_; $fh->read(my $buffer, $size); $$offset += $size; return oct('0x' . unpack("H*", $buffer)); } sub getstring { my ($fh, $offset) = @_; my $buffer; $fh->read($buffer, 2); $$offset += 2; my $unpacked = unpack('H*', $buffer); return '' if $unpacked eq 'ffff'; my $length = oct("0x${unpacked}"); $fh->read($buffer, $length); $$offset += $length; return $buffer; }
å ã®ãæ®éã«ç¿»è¨³ããã ããããã§å¯¾è±¡ã®sqliteãã¡ã¤ã«ãçªãæ¢ãããã¨ãã§ããã
$ perl dbfile.pl dbfile: /Users/sugyan/Library/Application Support/MobileSync/Backup/****************************************/****************************************
ããããWifiLocation, CellLocationãªã©ã®ãã¼ãã«ã®æ
å ±ãèªã¿åããã¨ã§ä½ç½®æ
å ±ã®è¨é²ã辿ããããã ã
ããããã°Google Fusion Tablesã£ã¦ããã®ããã£ã¦GoogleMapsã®Layerã¨ãã¦ä½¿ãããã ã£ããã¨ããã®ãæãåºããã®ã§ãåå¾ãããã¼ã¿ãããã«çªã£è¾¼ãã¹ã¯ãªãããæ¸ãã¦ã¿ãã
#!/usr/bin/env perl use strict; use warnings; use Config::Pit; use Data::Section::Simple; use Furl; use List::Util 'shuffle'; use Text::Xslate; my $dbfile = shift or die; my @data = (); for my $table (qw/WifiLocation CellLocation/) { my $result = qx{ sqlite3 '$dbfile' 'SELECT Timestamp, Latitude, Longitude FROM $table;' }; for my $row (split /\n/, $result) { my @col = split /\|/, $row; $col[0] += 31 * 365.25 * 24 * 60 * 60; push @data, \@col; } } @data = shuffle(@data); my $token = get_token(); my $tableid = create_table($token); for (1 .. 20) { warn $_; my @queries = (); for (1 .. 500) { my $record = shift @data; push @queries, qq{INSERT INTO $tableid (timestamp, location) VALUES ($record->[0], '$record->[1],$record->[2]')}; } my $query = join(';', @queries); insert_rows($token, $tableid, $query); warn 'insert ok'; sleep 1; } my $tx = Text::Xslate->new( path => [ Data::Section::Simple->new()->get_data_section(), ], ); print $tx->render('tracker.tx', { tableid => $tableid }); sub get_token { my $conf = pit_get('google.com', require => { username => 'google user name', password => 'password', }); my $furl = Furl->new; my $url = 'https://www.google.com/accounts/ClientLogin'; my $res = $furl->post($url, [], [ Email => $conf->{username}, Passwd => $conf->{password}, accountType => 'GOOGLE', service => 'fusiontables', ]); die unless $res->is_success; my ($token) = (split /\n/, $res->content)[2] =~ /^Auth=(.*)$/; return $token; } sub create_table { my ($token) = @_; my $furl = Furl->new; my $url = 'https://www.google.com/fusiontables/api/query'; my $res = $furl->post($url, [ Authorization => "GoogleLogin auth=$token", 'Content-Type' => 'application/x-www-form-urlencoded', ], [ sql => q{CREATE TABLE tracker (location: Location, timestamp: DATETIME)}, ]); die unless $res->is_success; my $tableid = (split /\n/, $res->content)[1]; return $tableid; } sub insert_rows { my ($token, $tableid, $query) = @_; my $furl = Furl->new; my $url = 'https://www.google.com/fusiontables/api/query'; my $res = $furl->post($url, [ Authorization => "GoogleLogin auth=$token", 'Content-Type' => 'application/x-www-form-urlencoded', ], [ sql => $query, ]); die unless $res->is_success; } __DATA__ @@ tracker.tx <html> <head> <title>iPhone Tracker</title> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript">google.load("jquery", "1.5.2");</script> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> <script type="text/javascript"> function initialize() { var latlng = new google.maps.LatLng(36, 140); var options = { zoom: 6, center: latlng, mapTypeId: google.maps.MapTypeId.ROADMAP }; var map = new google.maps.Map( document.getElementById("map_canvas"), options ); var layer = new google.maps.FusionTablesLayer(<: $tableid :>, { map: map }); google.maps.event.addListener(layer, 'click', function(e) { var date = new Date(e.row.timestamp.value * 1000); var div = $(e.infoWindowHtml); div.append('<br>').append(date.toLocaleString()); e.infoWindowHtml = div.html(); }); } </script> </head> <body onload="initialize();"> <div id="map_canvas" style="width:100%; height:100%"></div> </body> </html>
æ®å¿µãªãã¨ã«Perlã§FusionTablesAPIãæ±ãã©ã¤ãã©ãªã¯ç¡ãããã ã£ããã©ãINSERTããã ãã ãæ®éã«ãªã¯ã¨ã¹ããçæãã¦éãããã¼ã¿ãµã¤ãºã®å¶éãªã©ããããããªã®ã§ã·ã£ããã«ãã¦10000件ç¨åº¦ã ãINSERTããããã«ããã
å®äºå¾ãçæãããã¼ãã«idãFusionTablesLayerã¨ãã¦æ±ãmapsã®HTMLãåºåããã
$ perl fusion.pl '/Users/sugyan/Library/Application Support/MobileSync/Backup/****************************************/****************************************' > result.html $ open result.html
ããããVisibilityãPrivateã ã¨ãã¼ã¿ãåç
§åºæ¥ãªãã®ã§ã http://www.google.com/fusiontables/Home ããä¸è¨ã§çæãããã¼ãã«ãé¸æãã"Unlisted"ã«è¨å®ããï¼ä»äººããæ®éã«ã¢ã¯ã»ã¹ã§ããããã«ãªã£ã¦ãã¾ãã®ã§æ±ãã«ã¯æ³¨æï¼ã
æ¨å¹´å¤ã«ä¹å·ã«è¡ã£ãã®ã¨ããç§ã«é森ã«è¡ã£ãã®ã¨ããå
æä»å°ã«å¸°ã£ãã®ã¨ãããã¹ã¦è¨é²ããã¦ããã®ã確èªåºæ¥ãã
https://gist.github.com/934815
追è¨
sqlite3ã®ãã¼ã¿ãèªããªãDBIã¢ã¸ã¥ã¼ã«ã使ãã¾ãããï¼ï¼
å¤ãã®æ¹ãããææããã¾ããããããã¨ããããã¾ããã