Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Access and decrypt Chrome cookies on Windows

by Discipulus (Canon)
on Nov 30, 2022 at 08:45 UTC ( [id://11148454]=perlquestion: print w/replies, xml ) Need Help??

Discipulus has asked for the wisdom of the Perl Monks concerning the following question:

Hello folks,

Christmas approaches and raises my will to open cookies jars.. :)

If I just modify the path and the table name in this program to access Chrome 107 cookies ('C:/Users/'.$ENV{UserName}.'/AppData/Local/Google/Chrome/User Data/Default/Network/Cookies'; the path and cookies the table) I get a nice screenful of... encrypted cookies.

To offer encrypted cookies is against the Halloween trick or treat policy: is both trick and treat in the same time!

I read here:

> Since Chrome version 80 and higher, cookies are encrypted using AES-256 in GCM mode. The applied key is encrypted using DPAPI. (...) The encrypted key starts with the ASCII encoding of DPAPI (i.e. 0x4450415049) and is Base64 encoded, i.e. the key must first be Base64 decoded and the first 5 bytes must be removed. Afterwards a decryption with win32crypt.CryptUnprotectData is possible. The decryption returns a tuple whose second value contains the decrypted key:

From what I understand there is key and this key is used to enrypt the encrypted_value inside the cookies DB. Too much for me..

So again on metacpan for HTTP::Cookies::Chrome that installs fine under windows. The load_cookies.t sounds promising but fails, because guess_password method is unimplemented for Windows. Meh!

This perl gist (probably by PerlRob)

#!/usr/bin/perl -w use File::Copy qw(copy); use DBI; use Win32::API; use strict; use warnings; print ("Decrypting cookies...\n") && &fix_cookies && print ("Cookies d +ecrypted!\n"); sub fix_cookies { #Chrome has been encrypting cookie values since Chrome..33? #We need to decrypt the value before we can use it. my $chrome_cookie_file = 'C:/Users/'.$ENV{"USERNAME"}.'/AppData/Lo +cal/Google/Chrome/User Data/Default/Cookies'; copy($chrome_cookie_file, 'Cookies') || die "Failed to move files: + $!";; my $dbc = DBI->connect("dbi:SQLite:dbname=Cookies", '', '', { Rais +eError => 1, AutoCommit => 0}); my @rows = @{$dbc->selectall_arrayref("SELECT host_key, name, valu +e, encrypted_value FROM cookies")}; foreach my $row (@rows){ my ($host_key, $name, $value, $encrypted_value) = @{$row}; my $new_value = decryptData($encrypted_value) || $value || '0' +; #This is optional, but it allows us to use any session cookies + that may exist at the time of running this. #This is assuming that you will be generating a new decrypted +session file whenever you run your script. my $sth = $dbc->prepare(qq{ UPDATE cookies SET value = ?, has_expires = 1, expires_utc + = 99999999999999999, is_persistent = 1 WHERE host_key = ? AND name = ? }); $sth->execute($new_value, $host_key, $name); } $dbc->commit(); #SQLite is slow at excuting one row at a time. SEE +: http://stackoverflow.com/a/8882184 $dbc->disconnect(); } sub decryptData { #Cleaned up version of http://www.perlmonks.org/?node_id=776481 my $encryptedData = shift; if($encryptedData eq ''){ return undef; } #avoid errors... my $pDataIn = pack('LL', length($encryptedData)+1, unpack('L!', pa +ck('P', $encryptedData))); my $DataOut; my $pDataOut = pack('LL', 0, 0); my $CryptUnprotectData = Win32::API->new('Crypt32', 'CryptUnprotec +tData', ['P', 'P', 'P', 'P', 'P', 'N', 'P'], 'N'); if($CryptUnprotectData->Call($pDataIn, pack('L', 0), 0, pack('L', +0), pack('L4', 16, 0, 0, unpack('L!', pack('P', 0))), 0, $pDataOut)){ my($len, $ptr) = unpack('LL', $pDataOut); $DataOut = unpack('P'.$len, pack('L!', $ptr)); return $DataOut; }else{ return undef; } }

seems to be the solution, calling the native Windows CryptUnprotectData API but fails with Win32::API::Call: parameter 7 had a buffer overflow

Porting to Perl the code presented here or here and then patching the HTTP::Cookies::Chrome module?

Why this? I'm not so gluttonous, but I suppose that accessing a real cookie is key point in web automation: if I'm able to present the right session cookie my program is me.

Thanks for reading!

L*

There are no rules, there are no thumbs..
Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

Replies are listed 'Best First'.
Re: Access and decrypt Chrome cookies on Windows
by bliako (Monsignor) on Nov 30, 2022 at 12:08 UTC
Re: Access and decrypt Chrome cookies on Windows
by bliako (Monsignor) on Nov 30, 2022 at 11:35 UTC
    my $DataOut; my $pDataOut = pack('LL', 0, 0);

    In my limited understanding of pack(), $pDataOut simulates a memory pointer pointing to address 0. LL 2 longs: 0 and 0. Now I don't know what the size of a memory pointer in windows 32/64bit is and how does that relate to the size of a long according to Perl's pack().

    I think the problem is that you need to allocate memory for receiving the decrypted data in, via the API call's last parameter (#7). Thankfully I run a totally windows-free environment, so I can't try it, but perhaps replacing the above with:

    my $pDataOut = " "x1000; # just saying, this will allocate at least 1000 bytes I guess

    Once you jump over this hurdle, let's see how to read that memory you get back.

    FYI (from https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata#syntax):

    Edit: fixed below as it was referring to CryptProtectData

    DPAPI_IMP BOOL CryptUnprotectData( [in] DATA_BLOB *pDataIn, [out, optional] LPWSTR *ppszDataDescr, [in, optional] DATA_BLOB *pOptionalEntropy, PVOID pvReserved, [in, optional] CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct, [in] DWORD dwFlags, [out] DATA_BLOB *pDataOut ); typedef struct _CRYPTOAPI_BLOB { DWORD cbData; BYTE *pbData; } ...

    Edit: Oh I get it perhaps $pDataOut just creates "enough" space to receive a memory pointer to the decrypted data, so it does not matter where it points to (re: 0LL). So, perhaps the size of a memory pointer in your system is not equal to the size of 2 Perl/pack longs. Try bigger!

    Edit2: Yep, this can be possible as in the doc it does not say that you need to allocate it, only to free it, so the API allocates it for you, so just increase the size of pDataOut?:

    [out] pDataOut A pointer to a DATA_BLOB structure that receives the encrypted data. W +hen you have finished using the DATA_BLOB structure, free its pbData +member by calling the LocalFree function.

    p.s. Cookie, cookie, give me a cookie hehehe I was seriously told off at my alma mater for recreating this, now that its memory has faded I would be expelled.

    bw, bliako

      Thanks bliako,

      your guess is not so bad :) infact with the line my $pDataOut = " "x1000; the program runs without any issue but if I print $new_value it is always 0 and so if I inspect the copied DB using DBeaver I find value column with all 0 and the expiration set to 99999999999999999

      So the decryption didnt happened.

      L*

      There are no rules, there are no thumbs..
      Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

        Win32::API shows how to work with structures and perhaps this way one can eliminate some of those packs. So this is another angle (thankfully untested):

        Edit: I have just edited this 3 mins after, because I was using the CryptProtectData

        Win32::API::Struct->typedef( DATA_BLOB => qw{ DWORD cbData; BYTE *pbData; }) or die; Win32::API->Import('user32', <<EOP) or die; BOOL CryptUnprotectData( DATA_BLOB *pDataIn, LPWSTR *ppszDataDescr, DATA_BLOB *pOptionalEntropy, PVOID pvReserved, CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct, DWORD dwFlags, DATA_BLOB *pDataOut ) EOP # if all went well you have imported CryptUnprotectData() my $datain = Win32::API::Struct->new('DATA_BLOB'); $datain->{'cbData'} = pack('LL', length($encryptedData)+1; $datain->{'pbData'} = unpack('L!', pack('P', $encryptedData)); my $dataout = Win32::API::Struct->new('DATA_BLOB'); # call the imported func my $result = CryptUnprotectData($datain, pack('L', 0), 0, pack('L', 0) +, pack('L4', 16, 0, 0, unpack('L!', pack('P', 0))), 0, $dataout); my $len = $dataout->{'cbData'}; my $dat = unpack('P'.$len, pack('L!', $dataout->{'pbData'})); print "result ($len bytes) : '$dat'\n";

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11148454]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (9)
As of 2024-04-19 16:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found