Hi guys. I've been trying almost the entire day to get this to work.
So, I want to decrypt the local Chrome cookies, using the CryptUnprotectData function with winim. The function itself basically takes two real arguments - one DATA_BLOB pointer for the input and one DATA_BLOB pointer for the output. The DATA_BLOBs themselves are apparently made up of two parts - a DWORD (specifying the length of the blob) and a BYTE pointer, containing the actual data.
However, I can't figure out how to pass a BYTE pointer to the function. Nothing seems to work. I've tried putting the data in a seq[BYTE], passing addr var[0] as the BYTE pointer. I've tried putting the data in a BSTR, and I've tried casting pretty much everything into BYTE pointers. Still nothing. Here is my latest attempt, just so you can see what the code looks like (it's obviously wrong, but at this point I'm just throwing stuff at the wall to see if anything sticks):
import winim
import db_sqlite
let db = open("C:\\Users\\Daniel\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\cookies", "", "", "")
let result = db.getAllRows(sql"SELECT encrypted_value FROM cookies WHERE host_key LIKE '%hostname.com%' AND name LIKE '%COOKIE_NAME%'")[0][0]
var decryptedCookie : DATA_BLOB
var encryptedCookie : DATA_BLOB
var byteData : BSTR
byteData = result
encryptedCookie.cbData = int32(len(result))
encryptedCookie.pbData = cast[ptr BYTE](addr byteData[0])
let decryptResult = CryptUnprotectData(addr encryptedCookie, nil, nil, nil, nil, 0, addr decryptedCookie)
echo decryptedCookie
db.close()
Please guys. I need your help. What am I supposed to do here?
Interesting topic.
What you do about CryptUnprotectData is CORRECT. The problems are:
I wrote a example using tiny_sqlite and nimcrypto to list all the chrome cookies.
import base64, json, tiny_sqlite
import nimcrypto/[rijndael, bcmode]
import winim/lean
proc cryptUnprotectData(data: openarray[byte|char]): string =
var
input = DATA_BLOB(cbData: cint data.len, pbData: cast[ptr BYTE](unsafeaddr data[0]))
output: DATA_BLOB
if CryptUnprotectData(addr input, nil, nil, nil, nil, 0, addr output) != 0:
result.setLen(output.cbData)
if output.cbData != 0:
copyMem(addr result[0], output.pbData, output.cbData)
LocalFree(cast[HLOCAL](output.pbData))
proc cryptUnprotectData(data: string): string {.inline.} =
result = cryptUnprotectData(data.toOpenArray(0, data.len - 1))
proc expandvars(path: string): string =
var buffer = T(MAX_PATH)
ExpandEnvironmentStrings(path, &buffer, MAX_PATH)
result = $buffer
proc cookieDecrypt(data: openarray[byte]): string =
# Reference: https://stackoverflow.com/questions/60416350/chrome-80-how-to-decode-cookies
var key {.global.}: string
if data[0 ..< 3] == [byte 118, 49, 48]: # start with "v10"
if key.len == 0:
let json = parseFile(expandvars(r"%LocalAppData%\Google\Chrome\User Data\Local State"))
key = json["os_crypt"]["encrypted_key"].getStr().decode().substr(5).cryptUnprotectData()
var
ctx: GCM[aes256]
aad: seq[byte]
iv = data[3 ..< 3 + 12]
encrypted = data[3 + 12 ..< data.len - 16]
tag = data[data.len - 16 ..< data.len]
dtag: array[aes256.sizeBlock, byte]
if encrypted.len > 0:
result.setLen(encrypted.len)
ctx.init(key.toOpenArrayByte(0, key.len - 1), iv, aad)
ctx.decrypt(encrypted, result.toOpenArrayByte(0, result.len - 1))
ctx.getTag(dtag)
assert(dtag == tag)
else:
result = cryptUnprotectData(data)
proc main() =
let db = openDatabase(expandvars(r"%LocalAppData%\Google\Chrome\User Data\Default\Cookies"))
defer: db.close()
for row in db.rows("SELECT host_key, name, encrypted_value FROM cookies"):
echo "Host Name: ", row[0].fromDbValue(string)
echo "Name: ", row[1].fromDbValue(string)
echo "Value: ", cookieDecrypt(row[2].fromDbValue(seq[byte]))
echo ""
main()
Thank you so much for the answer!
I think I did manage to convert the db output to correct byte data (at least in a few of my attempts). After looking at your example and performing some additional tests, I believe that one of the the main issues I've been having is that CryptUnprotectData can't handle the encrypted cookie data and just has been returning 0 without giving me any information about whether it is my implementation or the submitted data that is wrong.
Sidenote: From what I can read, CryptUnprotectData is supposed to throw an ERROR_INVALID_DATA exception if the submitted data is incorrect, but I haven't been able to reproduce that behavior. I tried to put it in a try/except block, but that didn't do much.
However, I did manage to get some of my original saved code to work when I submit the key from Local State instead of the cookie data from the db - which is nice!
Anyway, your code was really helpful for me to understand what is going on. Also, since I'm relatively new to Nim, it's valuable to see how things are supposed to be done, rather than me just fumbling blindly towards something that the compiler accepts ;)
I think I did manage to convert the db output to correct byte data (at least in a few of my attempts).
Nim's string can store binary data, but std/db_sqlite cannot handle binary data well. Precisely speaking, if the BLOB data contain '0', the returned value will be truncated.
From what I can read, CryptUnprotectData is supposed to throw an ERROR_INVALID_DATA exception if the submitted data is incorrect.
Windows API don't throw any nim's exception. Here is the document about CryptUnprotectData . It just returns FALSE if the function fails, then you can use GetLastError() to get the error code.